OpenSpec Ship — Retrospective Change Documentation
Generate permanent "as-built" OpenSpec documentation from completed code, then archive it alongside Superpowers execution artifacts.
When to Use
Invoke this skill during the SHIP phase after verification-before-completion passes and before finishing-a-development-branch. It runs automatically as part of the SHIP composition chain.
When to Skip
Skip this skill ONLY when ALL of these are true:
- No Superpowers plan was executed in this session (no
docs/plans/ordocs/superpowers/plans/artifact) - The session was debugging, reviewing, or non-feature work
Scope and size are NOT skip criteria. If a Superpowers plan was executed — regardless of how small the change — this skill MUST run. A 3-file config change that went through brainstorming → writing-plans → execution still needs as-built documentation.
If you are uncertain whether to skip, run it. The cost of an unnecessary openspec-ship is minutes; the cost of missing documentation is permanent knowledge loss.
Hard Precondition
Before proceeding, verify that verification-before-completion has already run in this conversation. Look for fresh verification evidence appropriate to the project (e.g., test runner output, lint results, build confirmation — not all projects have all three). If no fresh verification evidence exists, STOP and inform the user:
"openspec-ship requires passing verification. Invoking verification-before-completion first."
Then invoke Skill(superpowers:verification-before-completion) before continuing. Defer to verification-before-completion for the exact command set appropriate to the project.
Session State
Resolving the session token
Every read and write of ~/.claude/.skill-openspec-state-<token> in this skill uses the token resolved by these two lines. Shell state does not persist between Bash tool calls — re-run them in each call that needs $TOKEN, never rely on a $TOKEN set by an earlier call (an empty token makes every openspec_state_* helper return silently, writing nothing).
# Resolve OWN-SESSION-FIRST (issue #157). ~/.claude/.skill-session-token is a
# shared last-writer-wins singleton: under concurrent sessions it names a
# DIFFERENT conversation, so state stored under it scatters into a file the
# PLAN design guard (hooks/skill-activation-hook.sh, payload-first per #51)
# never opens — the DESIGN->PLAN completeness hint then silently never fires.
# resolve_own_session_token derives THIS conversation's token from
# CLAUDE_CODE_SESSION_ID, which IS the transcript basename readers derive their
# token from, and trusts it only when that transcript exists on disk (stale,
# foreign, injected and path-unsafe ids fall through). Do NOT re-derive
# `session-<id>` by hand — hooks/lib/session-token.sh owns that format. The
# `|| cat` tail is the last resort: a missing lib degrades to the pre-#157
# behaviour instead of failing.
PR="${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null)}"
TOKEN="$(. "$PR/hooks/lib/session-token.sh" 2>/dev/null && resolve_own_session_token || cat ~/.claude/.skill-session-token 2>/dev/null)"
echo "session token: ${TOKEN:-<unresolved>}" # a scatter is then visible in-session
Unlike project-verification, there is deliberately no SKILL_SESSION_TOKEN override here: nothing sets it for this family and no openspec-state reader honors it (there is no cross-token bridge as there is for the verdict), so wiring it in would only add a way to write state nobody reads.
Populating state
When this skill starts, populate the session state file with linkage information — as ONE Bash call:
- Resolve the session token with the two lines above.
- Source
hooks/lib/openspec-state.shfrom the auto-claude-skills plugin root. - Call
openspec_state_upsert_change "$TOKEN" "<change_slug>" "<plan_path>" "<spec_path>" "<capability_slug>". - If the state file doesn't exist yet (verification-before-completion hasn't run), the helper creates it with
verification_seen: false.
Input
The user should provide:
- Required for SP artifact archival:
plan_path— the path to the implementation plan (e.g.,docs/plans/2026-04-15-feature-plan.md); also checks legacydocs/superpowers/plans/. If not provided, skip SP artifact archival with warning. - Optional:
feature-name— kebab-case change name. If not provided, derive fromplan_pathby stripping the date prefix (e.g.,2026-03-14-feature.md→feature). If neither is available, ask the user.
Steps
Step 1: Detect Environment
Primary: Read the session's OpenSpec: capability line from the session-start output (already in conversation context). Parse surface= to determine the OPSX surface level.
Fallback (if capability line not found): Read ~/.claude/.skill-registry-cache.json and extract openspec_capabilities.surface.
Last resort (backwards compatibility): Run command -v openspec to check for CLI availability.
Based on the detected surface:
opsx-coreoropsx-expanded: Use OPSX commands in later steps.openspec-core: Use CLI commands directly (no OPSX slash commands available).none: Use Claude-native fallback templates.
Step 2: Derive Slugs
Session state (primary): Resolve the session token with the block in Resolving the session token — reading the singleton directly finds a foreign session's state, or none. Then read ~/.claude/.skill-openspec-state-<token> for pre-populated linkage:
changes.<slug>.design_path— path to the design artifact (e.g.,docs/plans/2026-04-15-feature-design.md)changes.<slug>.plan_path— path to the implementation plan (e.g.,docs/plans/2026-04-15-feature-plan.md); legacy alias:sp_plan_pathchanges.<slug>.spec_path— path to the acceptance spec (e.g.,docs/plans/2026-04-15-feature-spec.md); legacy alias:sp_spec_pathchanges.<slug>.capability_slug— use as capability
If the state file exists and has the relevant change entry, use those values. Skip user prompts for fields that are already populated.
Fallback (no state file): Use the existing user-input flow (unchanged from current behavior).
Change slug (<feature-name>):
- If
plan_pathis provided: strip the leading date prefix from the plan filename stem. E.g.,docs/plans/2026-04-15-openspec-ship-plan.md→openspec-ship. - If
plan_pathis not provided and no explicit feature name given: ask the user for a kebab-case change name. Do not guess.
Capability slug (<capability>):
- If the linked SP spec references a specific capability: use that name in kebab-case.
- If not identifiable: ask the user. Do not guess.
Step 3: Create OR Sync Change Folder
Pre-flight check: Does openspec/changes/<feature-name>/ already exist at the start of this SHIP phase?
If YES (spec-driven mode path): The change folder already exists because it was created upfront during DESIGN phase in a
spec-drivenpreset session. Do NOT overwriteproposal.mdordesign.md— those are the committed historical decision record. Instead:- Validate the existing change folder structure with
openspec validate <feature-name>(if CLI available). - Compare the existing
specs/<capability>/spec.mdagainst as-built code. If implementation diverged from the upfront spec, updatespecs/<capability>/spec.mdto reflect what was actually built. Append a brief note at the bottom ofdesign.md:## Implementation Notes (synced at ship time) - [describe any deviations from the upfront design] - Skip to Step 4 (Validate) and continue to Step 5 (Changelog) and Step 6 (Archive).
- Validate the existing change folder structure with
If NO (retrospective mode path): No upfront change exists. Proceed to create retrospectively — scaffold the change folder and populate it from as-built code and the execution plan.
Capability taxonomy inference (run BEFORE deciding on <capability-slug>):
Before choosing any capability slug, enumerate existing ones and try to reuse:
ls openspec/specs/ 2>/dev/null
Apply this 3-step heuristic to the enumerated list:
- Noun-family match — if the feature's core domain noun (e.g., "authentication", "billing", "routing") appears verbatim or as a close synonym in an existing capability folder, extend that capability. Score: HIGH confidence.
- Subsystem overlap — if the feature touches code paths already covered by an existing capability (check
openspec/specs/<cap>/spec.mdrequirements for subsystem references), extend that capability. Score: MEDIUM confidence. - Genuinely new surface — if neither matches and the feature is a distinct subsystem, create a new capability. Score: LOW confidence — pause and double-check.
Decision gate based on confidence:
- HIGH or MEDIUM confidence (match to existing): auto-extend, no prompt. Proceed.
- LOW confidence (no clear match): auto-create IS allowed to preserve plug-and-play, but emit the warning below and prefer asking the user first if the session is interactive.
- AMBIGUOUS (two existing capabilities look equally applicable): ask the user explicitly — do NOT guess.
New-capability safeguard: If creating openspec/specs/<new-capability>/ for the first time (no existing folder), emit a visible warning in your response:
⚠️ NEW CAPABILITY: This change introduces capability
<new-capability>. Confirm the taxonomy is correct before archive. Prefer extending an existing capability where possible — checkopenspec/specs/for close matches first. Existing capabilities considered and rejected:<list the enumerated capabilities and the reason each was rejected>.
The user can then course-correct (rename, merge, or approve) before openspec-ship proceeds to archive.
Bias: prefer extending an existing capability over creating a new one; err toward fewer, coarser capabilities. Micro-capabilities ("auth-token-rotation", "auth-password-reset", "auth-mfa") fragment review routing and CODEOWNERS enforcement; a single "auth" capability with multiple requirements scales better. Split a capability only when it has clearly separable concerns that belong to different teams.
Retrospective content (when no upfront change exists):
OPSX path (CLI available):
- Run
openspec new change <feature-name>to scaffold the change folder. - Populate each artifact with retrospective content from the shipped codebase and SP artifacts, not forward-looking proposals. The code is already written — describe what was actually built.
Important — verb-first CLI commands: Always use the top-level verb form. The openspec change ... subcommands are deprecated. Use:
openspec new change <name>(notopenspec change new)openspec validate <name>(notopenspec change validate)openspec list(notopenspec change list)openspec show <name>(notopenspec change show)openspec archive <name>(notopenspec change archive)
Fallback path (no CLI):
Create openspec/changes/<feature-name>/ with:
proposal.md (must match openspec's expected headers exactly):
## Why
Why we built this. (Synthesized from SP brainstorming spec if available.)
## What Changes
High-level summary of what was actually built.
## Capabilities
### New Capabilities
- `<capability-name>`: Brief description of what this capability covers
### Modified Capabilities
- `<existing-name>`: What requirement changed
## Impact
Affected code, APIs, dependencies, systems.
design.md:
# Design: <Feature Name>
## Architecture
Data flow, component breakdown, system diagrams reflecting the as-built state.
## Dependencies
New packages, APIs, or database changes introduced.
## Decisions & Trade-offs
Why Path A over Path B. Rejected alternatives and rationale.
(Synthesized from Superpowers brainstorming spec if available.)
specs//spec.md (delta spec, one per capability):
Use RFC 2119 keywords in UPPERCASE: MUST, MUST NOT, SHALL, SHALL NOT, SHOULD, SHOULD NOT, MAY. Never write these as lowercase when they express requirements.
## ADDED Requirements
### Requirement: <Name>
<Description using RFC 2119 keywords in UPPERCASE, e.g. "The system MUST ...">
#### Scenario: <Name>
- **WHEN** <condition>
- **THEN** <expected outcome>
tasks.md (when plan_path is provided):
# Tasks: <Feature Name>
> Checkpoints reference branch commits. After squash-merge they are typically
> recoverable only via the feature's GitHub PR (`gh pr view <N> --json commits`)
> — plain clones and forks do not fetch PR refs.
## Completed
- [x] 1.1 <task description> (from SP execution plan) [checkpoint: <sha7>]
- [x] 1.2 <task description>
Checkpoint stamping (issue #129): while writing the completed-task lines, attribute commits from git log --oneline --abbrev=7 <merge-base>..HEAD. Append [checkpoint: <sha7>] ONLY when exactly one in-range commit matches the task by task number or strong keyword — ambiguous or unattributable tasks stay bare (a missing stamp is honest; a guessed one is not). Then run the deterministic integrity floor:
bash "${CLAUDE_PLUGIN_ROOT:-.}/scripts/checkpoint-validate.sh" openspec/changes/<feature-name>/tasks.md
Do not stamp the no-plan placeholder variant of tasks.md — it has no per-task structure to attribute.
- Exit 1 (malformed stamp, or SHA outside
merge-base..HEAD): repair or remove the offending stamps and re-run until clean. Do not proceed to Step 4 with a failing validation. - Exit 2 (unrunnable): note it in the ship report and continue — never blocking.
- Always copy the
checkpoints: N stamped / M completed taskssummary line into the ship report (this line is the kill-criterion evidence: two consecutive ships whose checkpoints nobody reads → remove this stamping step).
The validator is scoped to pre-merge feature-branch use only — never run it against archived tasks.md files (after squash-merge the branch SHAs are gone from main's history and every stamp would spuriously fail).
tasks.md (when plan_path is NOT provided):
# Tasks: <Feature Name>
## Completed
- [x] 1.1 Retrospective tasks unavailable — no Superpowers execution plan was provided. See git log for implementation history.
Step 4: Validate (CLI only)
If OpenSpec CLI was detected in Step 1:
- Run
openspec validate <feature-name>. - On validation failure: STOP. Report the issues for the user to resolve. Do not proceed.
- On validation success: proceed to Step 5.
If CLI is not available: skip this step.
Note: The CLI command is openspec validate <change>, not openspec verify. The /opsx:verify slash command exists as an expanded workflow profile but is not the base CLI command.
Step 5: Update Changelog
- Check for
CHANGELOG.mdin the project root. - If it exists with a recognizable format, append notes matching that format.
- If it exists with Keep a Changelog format, append under
## [Unreleased]. - If none exists, create a new
CHANGELOG.mdusing Keep a Changelog format:# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - Categorize entries strictly into
### Added,### Changed,### Fixed, or### Removed.
Step 6: Archive & Cleanup
Archive the change folder:
OPSX path (CLI available):
Run openspec archive <feature-name>. This:
- Checks artifact completion.
- If delta specs exist, prompts for sync to canonical specs.
- Moves the change folder to
openspec/changes/archive/YYYY-MM-DD-<feature-name>.
Fallback path (no CLI):
- Create
openspec/changes/archive/if it doesn't exist. - Move
openspec/changes/<feature-name>/toopenspec/changes/archive/YYYY-MM-DD-<feature-name>/. - If no canonical spec exists at
openspec/specs/<capability>/spec.md, create it from the change's delta spec. - If a canonical spec already exists, do NOT mutate it. Log:
"Canonical spec exists at openspec/specs/<capability>/spec.md. Skipping canonical update — use OpenSpec CLI for safe merging."
Enrich archive with Superpowers artifacts (when plan_path is known):
- Parse the
**Spec:**line from the plan to find the linked SP spec file. - Create
openspec/changes/archive/YYYY-MM-DD-<feature-name>/superpowers/. - Move the SP plan file and SP spec file into that
superpowers/subdirectory. - Remove only the specific moved files from
docs/superpowers/. Do not delete directories or unrelated files.
If plan_path is not provided: skip SP artifact archival. Log: "No Superpowers plan path provided. Skipping SP artifact archival."
Write provenance metadata:
After the archive path exists and SP artifacts (if any) have been moved:
- Resolve the session token with the block in Resolving the session token.
- Run the
openspec_write_provenancehelper (sourcehooks/lib/openspec-state.shfrom the auto-claude-skills plugin root, then callopenspec_write_provenance "<archive_path>" "<token>" "<change_slug>"). - This creates
<archive_path>/superpowers/source.jsonwith schema_version, paths, branch, commit, surface, and timestamp. - If the write fails, log a warning but do not fail the archive.
Step 7a-bis: Extract Hypotheses into Session State
If discovery_path exists in session state AND the file at that path is readable:
- Read the
## Hypothesessection from the discovery artifact - Parse each
### H<N>:entry, extracting:id— the hypothesis ID (e.g., "H1")description— the prose hypothesis line ("We believe ...")metric— from the Metric: fieldbaseline— from the Baseline: fieldtarget— from the Target: fieldwindow— from the Window: field
- Persist hypotheses and mark the change archived. Source the state helpers from the auto-claude-skills plugin, then call them:
# Self-contained: re-resolve the token here (see "Resolving the session token")
# — a $TOKEN set in an earlier Bash call is NOT in scope, and an empty token
# makes both helpers below return silently, losing hypotheses and archived_at.
PR="${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null)}"
TOKEN="$(. "$PR/hooks/lib/session-token.sh" 2>/dev/null && resolve_own_session_token || cat ~/.claude/.skill-session-token 2>/dev/null)"
. "$PR/hooks/lib/openspec-state.sh"
HYPS='[{"id":"H1","description":"...","metric":"...","baseline":"...","target":"...","window":"..."}]'
openspec_state_set_hypotheses "$TOKEN" "<slug>" "$HYPS"
# Record the ship timestamp so write_learn_baseline captures the real ship time,
# not the stop-hook wall-clock fallback. Call after archival in Step 7b completes,
# or here if archival is already complete.
openspec_state_mark_archived "$TOKEN" "<slug>"
If the discovery description contains single-quotes (e.g., "it's faster"), construct the JSON via jq -n instead of shell-quoted literals to avoid quoting blow-ups.
If discovery_path is absent in session state or the file is unreadable: Skip silently. hypotheses stays null in state. This covers sessions that entered at DEBUG or skipped discovery.
Step 7b: Archive Intent Artifacts
If discovery_path, design_path, plan_path, or spec_path exist in session state:
- Create
docs/plans/archive/if it doesn't exist - Move the discovery, design, plan, and spec files to
docs/plans/archive/:mkdir -p docs/plans/archive for f in "$discovery_path" "$design_path" "$plan_path" "$spec_path"; do [ -f "$f" ] && mv "$f" docs/plans/archive/ done
Note: docs/plans/archive/ is the human-readable intent history. openspec/changes/archive/ is the OpenSpec change archive. They serve different purposes and coexist.
Step 7c: Generate Divergences Report
If design_path exists in session state and the design file is readable:
- Read the design artifact's Acceptance Scenarios section
- Read the design artifact's Capabilities Affected and Out-of-Scope sections
- Compare against what was actually built (from the OpenSpec change's
proposal.mdandspecs/) - Append a
## Divergencessection to the archived design doc:
## Divergences (auto-generated at ship time)
**Acceptance Scenarios:**
- [x] GIVEN ... WHEN ... THEN ... — implemented as designed
- [~] GIVEN ... WHEN ... THEN ... — implemented with modification: [describe]
- [ ] GIVEN ... WHEN ... THEN ... — not implemented: [reason]
**Scope changes:**
- Added: [capability not in original out-of-scope or capabilities list]
- Removed: [capability in original list but not implemented]
- Modified: [capability implemented differently than designed]
**Design decision changes:**
- [any trade-offs or approach changes made during implementation]
Important: Write divergences to the archived copy in docs/plans/archive/, not the live file. The archive is the historical record; the live file may still be in use if the feature spans multiple sessions.
Graceful Degradation Summary
| OpenSpec CLI | Behavior |
|---|---|
| Available | openspec new change → openspec validate → openspec archive |
| Not available | Claude-native templates → skip validation → manual archive. Same artifact contract (paths, filenames, section headings). |
Verification
Before declaring the ship documented, confirm:
- The change folder exists on disk with proposal / design / specs (or the Claude-native fallback produced the same artifact contract) -- verified by listing it, not assumed.
openspec validateran and passed when the CLI is available; when absent, the skip is stated explicitly.- The changelog entry was written and the archive step completed (or its absence is reported).
- Hypotheses and the ship timestamp were recorded to session state where applicable.