From shipwright-changelog
Parses Conventional Commits from git history, generates Keep-a-Changelog entries, creates version tags, and opens PRs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/shipwright-changelog:changelogThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generates changelogs from Conventional Commits and manages release workflow.
Generates changelogs from Conventional Commits and manages release workflow.
Governing rules: Read and follow shared/constitution.md (ALWAYS / ASK FIRST / NEVER boundaries).
================================================================================
SHIPWRIGHT-CHANGELOG: Release Management
================================================================================
Analyzes git history, generates changelog, creates PR.
Usage: /shipwright-changelog
or: /shipwright-changelog --from v0.1.0
or: Invoked by /shipwright-run (orchestrator)
Steps:
1. Analyze commits since last tag
2. Categorize by Conventional Commits type
3. Generate changelog entry
4. Preview and confirm with user
5. Commit changelog + create tag
6. Create PR (if feature branches exist)
================================================================================
The SessionStart hook injects SHIPWRIGHT_PLUGIN_ROOT=<path>. Use it directly.
The phaseTaskId the orchestrator hands you at dispatch is the authority — NOT any
state field inside shipwright_run_config.json. The pipeline's v1 state fields are no
longer advanced, so keying on them made every driven phase past the first misclassify
itself as standalone; the rationale is in shared/scripts/lib/phase_invocation_mode.py.
Never re-derive the mode yourself. Ask the resolver:
uv run "{shared_root}/scripts/tools/get_phase_context.py" \
--phase-task-id "{phaseTaskId}" --phase changelog --project-root "{project_root}"
Omit --phase-task-id if you were not handed one. Set invocation_mode from the returned
mode, which is exactly one of:
pipeline — you were dispatched. Enforce gates, and do the phase's real work.
Do NOT call orchestrator.py update-step (nor any other run-state write): in a
driven run single-session-apply owns phase completion — it records your status when
it applies your result. See plugins/shipwright-run/skills/run/SKILL.md. (update-step
is inert in a driven run anyway, but do not rely on that.)standalone — no token, so this is a hand-invoked run (the normal case for a
release: /shipwright-changelog is usually invoked by hand):
orchestrator.py update-step calls)CHANGELOG.md, version tags, PRs)"Running in standalone mode — pipeline state will not be updated."requires_out_of_sequence_warning is true, a driven run is LIVE at
active_phases. Warn that cutting a release out-of-band may collide with it, and
ask the user before continuing. (The changelog phase has no cataloged gate id yet
— it is a tracked pending_phases follow-up in shared/config/gate_catalog.json — so
ask interactively rather than resolving a gate policy.)error (exit code 2) — you were dispatched but the token does not resolve (stale,
terminal, wrong phase, or an unreadable config). STOP. Do NOT continue as
standalone: that is precisely what stamps a driven run's artifacts "mode": "standalone"
and deadlocks the pipeline. Surface it to the orchestrator as an ok: false result.uv run "{plugin_root}/scripts/checks/setup-changelog.py" \
--plugin-root "{plugin_root}"
Parse JSON output for git state, last tag, and unreleased commits.
If the orchestrator handed you a phaseTaskId — i.e. /shipwright-run dispatched
you as a phase-runner subagent — you are part of an active pipeline. Run this as your
very first action:
uv run "${SHIPWRIGHT_PLUGIN_ROOT}/../../shared/scripts/tools/get_phase_context.py" \
--phase-task-id <phaseTaskId-from-context>
The tool prints structured JSON with runId, phase, splitId, prerequisites,
runConditions, and a skill_artifacts_to_read list. Read those artifacts
before proceeding so this phase session has full context for what came before.
If NO phaseTaskId was handed to you, this is a standalone invocation —
continue with Step 1 below as normal.
One resolver, one verdict. This is the same tool your "Detect Invocation Mode" step
already ran, so reuse that payload rather than re-deriving anything: its mode IS your
invocation_mode. Pass --phase <your phase> so a token belonging to another phase is
rejected, and if mode is "error" (exit 2) STOP — a dispatched phase must never
fall back to standalone.
Goal: Collect all commits since the last version tag.
The setup script returns:
last_tag — most recent semver tag (or null if none)commits_since_tag — list of commit messages + hashesbranch — current branch nameIf no commits since last tag: print "No unreleased changes" and stop.
See conventional-commits.md for parsing rules.
Goal: Parse each commit message into type, scope, and description.
uv run "{plugin_root}/scripts/lib/git_utils.py" parse-commits \
--since "{last_tag}" \
--format json
Categories:
| Type | Changelog Section |
|---|---|
feat | Added |
fix | Fixed |
refactor | Changed |
docs | Documentation |
test | Testing |
chore | Maintenance |
BREAKING CHANGE | Breaking Changes |
Goal: Suggest next version based on commit types.
Rules:
BREAKING CHANGE in any commit → major bumpfeat → minor bumpfix, refactor, docs, etc. → patch bumpIf no previous tag exists: suggest v0.1.0.
Autonomous mode (check autonomy in shipwright_run_config.json):
Accept the suggested version automatically. No prompt.
Guided mode (default): Present suggestion to user:
Suggested version: v{X.Y.Z} (based on: {reason})
Accept or enter custom version:
Since the file-per-iterate refactor, iterate F4 writes one Markdown file
per bullet under CHANGELOG-unreleased.d/<category>/. Release time
reads those drop files, renders a versioned Keep-a-Changelog section,
inserts it at the structural point in CHANGELOG.md (above the first
existing ## [version] heading, NOT blindly at the top — that would
corrupt the # Changelog title), and deletes only the drop files that
were actually aggregated.
uv run "{shared_root}/scripts/tools/aggregate_changelog.py" \
--project-root "{project_root}" \
--version "{version}" \
[--release-date "{YYYY-MM-DD}"] \
[--dry-run]
Use --dry-run first to preview the rendered section without modifying
disk. When the aggregator encounters legacy bullets under
## [Unreleased] (e.g. from pre-refactor iterates that wrote directly
to CHANGELOG.md), it prints a loud stderr WARNING with the count.
Those bullets are NOT migrated automatically — the operator chooses
whether to fold them into the new version manually or accept the
split-brain.
Iterate F3 no longer appends ADRs directly to decision_log.md. Since
the unconditional-worktree refactor it writes one JSON drop per ADR
under .shipwright/agent_docs/decision-drops/, keyed by run_id. Release
time is the ONE serialized point that assigns the sequential ADR-NNN,
so two parallel iterates can never claim the same number:
uv run "{shared_root}/scripts/tools/aggregate_decisions.py" \
--project-root "{project_root}" \
[--dry-run]
It renders each drop into decision_log.md (continuing the ADR
numbering), embeds a Run-ID: line for run-id ↔ ADR traceability, and
deletes only the drops it aggregated. Run with --dry-run first to
preview the numbers that will be assigned. Drops written after the
snapshot survive into the next release.
Fallback for non-iterate commits: if this release includes bullets that
weren't produced through iterate F4 (rare — e.g. a cherry-pick from an
unrelated branch), write them with append_changelog_entry.py BEFORE
running the aggregator; they land in the legacy [Unreleased] block
and surface as a warning at aggregation time.
See changelog-format.md for output format details.
Goal: Show the generated changelog entry to the user.
Autonomous mode (check autonomy in shipwright_run_config.json):
Skip preview confirmation. Proceed directly to Step 6.
Guided mode (default): Present the full entry and ask:
AskUserQuestion:
question: "Review the changelog entry. Proceed?"
options:
- Accept
- Edit (describe changes)
- Cancel
If edit: apply changes and re-preview.
git add CHANGELOG.md
git commit -m "chore(release): v{version}"
git tag -a v{version} -m "Release v{version}"
Only if on a feature branch (not main/develop).
gh pr create \
--title "Release v{version}" \
--body "## Changelog\n\n{entry}" \
--base main
Parallel Iterate Handling
- Multiple open PRs against the same default branch: rebase per PR is expected — no skill-logic change required.
gh pr merge --mergevs--squash: the default stays--merge;--squashis optional for parallel-iterate PRs when linear history matters.- Tag creation is single-writer (only the release-iterate tags a version) — no concurrency change needed.
- Conventional-Commit sort is deterministic: merge order does not affect changelog ordering.
CHANGELOG.md [Unreleased]is a merge hotspot. Every iterate F4 appends to[Unreleased]. Two parallel iterates conflict on merge — the second PR rebases and resolves the bullet merge manually. Structural fix tracked as aCHANGELOG-unreleased.d/drop pattern bundled with the iterate_history file-per-iterate refactor.- Full parallel-iterate conventions live in
/shipwright-iterateB1a.
Autonomous mode: After creating the PR, merge it immediately:
gh pr merge --merge --delete-branch
Guided mode: PR stays open for manual review and merge.
If already on main: skip PR, just push tag.
Push tags and updated main to remote:
git push --tags origin main
Record changelog event (captures version and PR URL for downstream consumers):
uv run "{shared_root}/scripts/tools/record_event.py" \
--project-root "$(pwd)" \
--type phase_completed \
--phase changelog \
--detail "v{version} — {PR_URL}"
Where {shared_root} = {plugin_root}/../../shared.
If no PR was created (on main), use --detail "v{version} — tagged on main".
Phase complete — update pipeline state:
Iterate 12.4 wires the changelog plugin into the Minimum Phase
Completion Canon at C1/C2/C3 only. C4 is skipped by policy —
release tagging is process management, not an architectural decision.
C5 is not applicable — this plugin IS the one that writes
[Unreleased] prepends; appending to [Unreleased] after a release
would pollute the next version.
: "${SHIPWRIGHT_RUN_ID:=changelog-v{version}-$(date +%Y%m%d-%H%M%S)}"
export SHIPWRIGHT_RUN_ID
# C1 — already emitted as the phase_completed event above.
# C2 — delivery dashboard
uv run "{shared_root}/scripts/tools/update_build_dashboard.py" \
--project-root "$(pwd)" --phase changelog --session-id "{SHIPWRIGHT_SESSION_ID}"
# C3 (NEW 12.4) — canon-marker handoff
uv run "{shared_root}/scripts/tools/generate_session_handoff.py" \
--project-root "$(pwd)" --canon-marker --phase changelog \
--reason "release v{version}"
# C4 — SKIPPED by policy.
# C5 — n/a (this plugin prepends the released version block; adding a
# new [Unreleased] bullet would collide with the next release).
# phase_history (NEW 12.4)
uv run "{shared_root}/scripts/tools/append_phase_history.py" \
--project-root "$(pwd)" --phase changelog --run-id "$SHIPWRIGHT_RUN_ID" \
--entry-json '{"version":"v{version}","outcome":"tagged"}'
# Mark changelog phase complete (triggers compliance update automatically).
# _validate_changelog() now runs test_checks + the new check_git_tag_exists
# and check_changelog_version_matches_tag Sonder-Checks, so a broken tag
# push or a CHANGELOG drift blocks this call.
uv run "{plugin_root}/../../plugins/shipwright-run/scripts/lib/orchestrator.py" \
update-step --project-root "$(pwd)" --step changelog --status complete
Print Summary:
================================================================================
SHIPWRIGHT-CHANGELOG COMPLETE
================================================================================
Version: v{version}
Commits: {N} categorized
Changelog: CHANGELOG.md updated
Tag: v{version} created
PR: {PR_URL | "skipped (on main)"}
Tags + main pushed to origin
================================================================================
npx claudepluginhub svenroth-ai/shipwright --plugin shipwright-changelogGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Resolves in-progress git merge or rebase conflicts by analyzing history, understanding intent, and preserving both changes where possible. Runs automated checks after resolution.