Skill v1.0.1
currentAutomated scan100/100+2 new
version: "1.0.1" name: spectra-archive description: "Archive a completed change" effort: low license: MIT compatibility: Requires spectra CLI. metadata: author: spectra version: "1.0" generatedBy: "Spectra"
<!-- 🔒 LOCKED — managed by clade Source: plugins/hub-core/skills/spectra-archive/ Edit at: $CLADE_HOME Local edits will be reverted by the next sync. -->
Archive a completed change.
Ownership(clade fork;cross-phase matrix inrules/core/spectra-workflow.md):archive gate-check 負責 archive 前再跑 Layer C data-sanity +archive-gate.shCheck 1–7(journey / schema-types / exhaustiveness / manual-review kind / screenshot quality / stale verified-ui / pre-handoff-verdict)。不負責上游 phase 已 own 的 checkpoint(propose Layer A、apply Step 6c/Layer B、pre-handoff Step 8a.6/Layer E 各在自己 phase 抓);Check 7 只是機械驗證 Step 8a.6 的 E.1 verdict record 已存在(缺則擋 archive),不重跑 self-analysis 本身。
Input: Optionally specify a change name after /spectra-archive (e.g., /spectra-archive add-auth). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
Prerequisites: This skill requires the spectra CLI. If any spectra command fails with "command not found" or similar, report the error and STOP.
Change Disposition Decision (clade fork addition)
Before running archive, classify the change into one of four dispositions. Pick the wrong path and you either (a) burn user time on a review that doesn't apply, (b) write spec for a feature that won't ship, or (c) abandon work that's actually still pending.
| Disposition | Trigger | spec sync | manual review | CLI | |
|---|---|---|---|---|---|
| Park | Change is postponed but still planned. Premise still valid. | — | — | spectra park <name> (NOT this skill) | |
| Standard archive | Implementation [x] complete + ## 人工檢查 [x] complete | applied | already done | spectra archive <name> (default flow) | |
| Skip-archive (A) | Won't be implemented. Premise dissolved (decision changed / concept removed / change became redundant). | skipped | skipped | spectra archive <name> --mark-tasks-complete --skip-specs --no-validate -y | |
| In-main-done archive (B) | Implementation [x] complete (built directly on main, no session worktree). Only ## 人工檢查 items remain unchecked. | applied | skipped | spectra archive <name> --mark-tasks-complete --no-validate -y | |
| Apply-required (block) | Non-## 人工檢查 items still [ ] + change is still wanted. | — | — | STOP — run /spectra-apply <name> first, then re-classify |
Hard rules
- NEVER use Park for "won't implement" — Park's contract is "future work pending"; abandoned changes pollute the parked queue and look like a backlog. Use Skip-archive (A) instead.
- NEVER use Skip-archive (A) when implementation work is actually done — that drops the spec delta and leaves the codebase with shipped behavior that has no spec record. Use In-main-done archive (B) instead.
- NEVER run In-main-done archive (B) without first verifying that non-
## 人工檢查task sections are fully[x]. Use the inspection check below. - NEVER auto-batch multiple changes into Skip-archive (A) without per-change user confirmation — abandoning work is destructive (spec delta thrown away, future readers lose the why).
- MUST record the disposition reason (1 line) in the archive commit message when using Skip-archive (A) or In-main-done archive (B), so future readers can tell why standard flow was bypassed:
- A:
archive: <name>; skip — <reason premise dissolved> - B:
archive: <name>; in-main-done — <reason no worktree, e.g. built directly on main>
Inspection check (before B vs Apply-required)
To distinguish In-main-done archive (B) from Apply-required, parse openspec/changes/<name>/tasks.md:
awk '/^## 人工檢查/{mr=1; next} /^## /{mr=0} !mr && /^- \[ \]/{print NR": "$0}' \openspec/changes/<name>/tasks.md
- Empty output → all non-manual-review items are
[x]→ eligible for In-main-done archive (B) - Non-empty output → there are unchecked implementation tasks → MUST be either:
- Apply-required: run
/spectra-apply <name>to finish them, then re-classify - Skip-archive (A): explicit user confirmation that the unchecked work won't be done (premise dissolved)
- NEVER silently auto-
--mark-tasks-completenon-manual-review tasks without classifying — that hides incomplete implementation behind a green archive.
Worktree handling per disposition
- Skip-archive (A): change has no implementation to land. If a session worktree exists (typically with only ingest / sync commits, no real implementation), run
node scripts/wt-helper.mjs cleanup <name> --force --force-discard-unlandto drop branch + worktree; do NOT runmerge-back(nothing meaningful to merge). - In-main-done archive (B): change was built on main, no worktree to absorb. Step 0
wt-helper merge-back ... --noop-if-missingbecomes silent no-op — proceed. - Standard archive: Step 0 absorbs the matching worktree as documented below.
When user asks for "skip 人工檢查 and archive"
"Skip 人工檢查 + archive" is ambiguous between A and B. MUST disambiguate with the user before running:
- "這條的功能會做嗎?" → No → A
- "已經在 main 做完了,只是沒走 worktree?" → Yes → B (after passing the Inspection check)
- Both ambiguous → walk through Inspection check output with the user, then pick.
Worktree exemption (clade fork addition): This skill is exempt from the [[worktree-default]] §1 worktree requirement. Archive is main-bound — every output (delta sync into openspec/specs/<capability>/spec.md, move into openspec/changes/archive/, screenshot sweep) targets main, so running inside a worktree adds a mandatory merge-back with no isolation benefit. The skill SHALL proceed regardless of whether cwd is on the main worktree or inside a session worktree; the orchestrator (e.g., /handoff Mode B Step 2B.5) SHALL dispatch this skill directly without routing through /wt. Do NOT add prose instructing the user to "open a worktree first" — that contradicts the §1 exemption.
Atomic merge-back contract (clade fork addition): Per [[worktree-default]] §5.5, worktree branches do NOT squash back to main at /wt return time — they wait until archive. Step 0 below absorbs any slug-matching session worktree into main BEFORE the archive gates run, so gates inspect the post-squash state and so main never carries half-done work between sessions.
Steps
- Atomic merge-back from active worktree (clade fork addition)
Per [[worktree-default]] §5.5, any session worktree whose slug matches this change-name MUST be absorbed into main before archive gates run. If gates run on un-absorbed main, they would see a false-clean diff (worktree changes never landed) and produce a misleading archive.
Sidecar init (TD-155) — before running merge-back, create the in-flight sidecar so that subsequent steps record progress and an interrupted run leaves a detectable orphan:
``bash node scripts/spectra-archive-sidecar.mjs init <change-name> ``
- Sidecar is always written to main worktree
.spectra/in-flight-archive/<change-name>.json(helper resolves viagit rev-parse --git-common-dir), so a linked worktree's archive is visible from main on the next session start (cross-session detection perplugins/hub-core/hooks/session-start-spectra-resume-check.sh). - If
scripts/spectra-archive-sidecar.mjsdoes not exist (consumer pre-propagation), skip silently with a one-line note:Sidecar: skipped — helper not available (consumer pre-propagate). - Skip in resume mode: when Step 0.5 has dispatched into mid-flight resume (sidecar already exists with
phase=merge-back), do NOT re-init — proceed directly to the merge-back command below.
``bash node scripts/wt-helper.mjs merge-back <change-name> --auto-stash --noop-if-missing ``
--noop-if-missingmakes this a silent no-op when no matching worktree exists (solo archive path — change implemented directly on main).--auto-stashstashes any main-worktree blockers aswt-merge-block/<change-name>/<ISO>for later reconciliation vianode scripts/stash-reconcile.mjs.- On conflict, the squash aborts, the worktree + branch are preserved, and the stash is popped back. Surface the error and STOP the archive — the change cannot be archived until the conflict is resolved.
Skip condition: if scripts/wt-helper.mjs does not exist (consumer hasn't propagated the merge-back subcommand yet), skip this step silently with a one-line note: Step 0: skipped — wt-helper merge-back not available (consumer pre-propagate).
After merge-back, sweep sibling worktrees that carry fork-time stale copies of the change being archived (prevents spectra CLI cross-worktree existence check from refusing):
``bash node scripts/wt-helper.mjs sweep-siblings <change-name> ``
Sidecar advance (TD-155) — after merge-back returns (whether absorbed, no-op, or skipped), advance phase:
``bash node scripts/spectra-archive-sidecar.mjs update <change-name> --phase gate-check --last-completed merge-back ``
(silent fail-safe: if sidecar helper or sidecar file is missing, ignore — sidecar lifecycle is best-effort visibility, not a hard dependency of archive correctness.)
Output (when worktree absorbed):
merge-back: <change-name> absorbed into main— proceed to Step 1merge-back: <change-name> absorbed into main (blockers stashed as wt-merge-block/<name>/<ISO>) + worktree cleaned— proceed; remind user in Step 8 summary that stash entry needs reconciliation
0.5. Resume / mid-flight detection (clade fork addition — fires only when explicit change-name given)
Two independent resume paths share this step:
- Mid-flight resume (TD-155) — fires when an in-flight sidecar exists from an interrupted prior run
- Discuss-deferred resume (legacy) — fires when archive was completed but discuss items were deferred to HANDOFF
### Mid-flight resume (TD-155)
Before anything else, check for an in-flight sidecar:
``bash node scripts/spectra-archive-sidecar.mjs read <change-name> 2>/dev/null ``
Branch:
- Sidecar exists + user invoked `/spectra-archive <X> --resume` → enter Mid-flight resume mode. Parse sidecar's
phasefield and jump per Resume Dispatch Table below. Do NOT re-init the sidecar; do NOT re-run completed phases. - Sidecar exists + user did NOT pass `--resume` → STOP. Prompt the user:
> Previous /spectra-archive for <X> interrupted at phase <phase> (sidecar started <ISO>). Options: > a) Resume — re-run as /spectra-archive <X> --resume > b) Discard previous run and start fresh — node scripts/spectra-archive-sidecar.mjs delete <X> then re-invoke without --resume > > Choose a / b. Standard archive cannot proceed while a sidecar exists.
- No sidecar → fall through to discuss-deferred resume detection below.
#### Resume Dispatch Table (mid-flight)
Read sidecar via node scripts/spectra-archive-sidecar.mjs read <change-name> (parse JSON .phase):
phase value | Action on --resume | |
|---|---|---|
merge-back | re-run Step 0 from the top. wt-helper merge-back --noop-if-missing is idempotent — if the worktree was already absorbed in the prior run, it silently no-ops. | |
gate-check | jump to Step 2 and re-run gates (2 / 3 / 3.3 / 3.5 / 5.5). All gates are idempotent: status / task / pattern checks are read-only; the [discuss] walkthrough in Step 3.5 only re-prompts items still unchecked. | |
spec-sync | jump to Step 4 and re-run delta spec assessment. Comparison is idempotent. | |
folder-mv | STOP — manual fixup required. Reason: Step 6 invokes spectra archive CLI which is a black box from clade's POV; mid-flight interrupt may leave openspec/changes/<X>/ partially renamed and openspec/specs/<cap>/spec.md deltas partially applied. Show the user: <br/> "phase=folder-mv means `spectra archive` CLI was mid-flight when interrupted. Cannot safely retry — reality is unknown. Manual fixup: (a) inspect `openspec/changes/<X>/` and `openspec/changes/archive/YYYY-MM-DD-<X>/` directory states; (b) inspect `git status` for partial spec delta writes; (c) reconcile by hand (either complete the move or roll back), then `node scripts/spectra-archive-sidecar.mjs delete <X>` and re-invoke from a clean state." | |
screenshot-sweep | jump to Step 7 and re-run screenshot sweep. screenshots-archive Mode B is idempotent on re-copy (existing destination files are silently kept). | |
cleanup | jump to Step 7.5 and re-run stash reconcile + Step 8 summary. Both are near no-ops on re-run. |
### Discuss-deferred resume (legacy)
When no sidecar exists, also check the legacy discuss-deferred path:
``bash if [ ! -d "openspec/changes/<change-name>" ] && [ -d "openspec/changes/archive/<change-name>" ]; then if grep -q '(deferred-to-handoff:' "openspec/changes/archive/<change-name>/tasks.md" 2>/dev/null; then # Discuss-deferred resume candidate ... fi fi ``
- No active dir + no archived dir + no sidecar → STOP with error: "change
<name>not found in active or archived" - No active dir + archived dir but no `(deferred-to-handoff:` annotation → STOP with note: "change
<name>already fully archived; nothing to resume" - No active dir + archived dir with `(deferred-to-handoff:` annotations → enter Discuss-deferred resume mode: skip Step 0 (merge-back), skip Step 1 (selection prompt), skip Steps 2 / 3 / 3.3 / 3.5 / 4 / 5 / 6 / 7 / 8 entirely. Jump to Step 3.5b — Resume walkthrough below; that step is the only work performed in this resume path.
Standard archive runs (active change directory exists + no sidecar) MUST NOT trigger discuss-deferred resume even if a homonymous archived change has deferred items.
Skip Step 0.5 entirely when no change-name was provided (Step 1 still handles selection from active changes).
- If no change name provided, prompt for selection
Run spectra list --json to get available changes. Use the AskUserQuestion tool to let the user select.
Show only active changes (not already archived). Include the schema used for each change if available.
IMPORTANT: Do NOT guess or auto-select a change. Always let the user choose.
- Check artifact completion status
Run spectra status --change "<name>" --json to check artifact completion.
Parse the JSON to understand:
schemaName: The workflow being usedartifacts: List of artifacts with their status (doneor other)
If any artifacts are not `done`:
- Display warning listing incomplete artifacts
- Prompt user for confirmation to continue
- Proceed if user confirms
- Check task completion status
Read the tasks file (typically tasks.md) to check for incomplete tasks.
Count leaf tasks marked with - [ ] (incomplete) vs - [x] (complete). For ## 人工檢查 items, parent #N lines that own scoped #N.M children have state derived from children (see rules/core/manual-review.md「Parent State Derivation」) — never count those parent lines directly.
If incomplete tasks found:
- Display warning showing count of incomplete tasks
- Prompt user for confirmation to continue
- Proceed if user confirms
If no tasks file exists: Proceed without task-related warning.
3.3. Manual-Review Pattern Re-check (clade fork addition — archive gate for ## 人工檢查 hygiene)
## 人工檢查 items can drift between propose / ingest and archive (apply phase edits, screenshot annotations, fix-up rewrites). Re-run the same enforcement hook that /spectra-propose uses, so jargon leakage / abstract reference / missing URL etc. doesn't slip into archive:
``bash bash scripts/spectra-advanced/post-propose-manual-review-check.sh <change-name> ``
Exit 2 = pattern findings (any of ABSTRACT_REFERENCE / CARD_WITHOUT_UID / UI_ITEM_NO_URL / MULTI_STEP_NOT_SCOPED / REVIEW_UI_BACKEND_ROUNDTRIP / INTERNAL_JARGON_LEAKAGE).
If findings exist:
- Display warning showing pattern hit summary (parsed from hook stderr)
- Prompt user: "Archive 前 manual-review 還有 N 個 pattern 命中(見上)— 修完再 archive vs 帶 followup 跳過?"
- Fix now → main thread Edit
tasks.mdper hook remediation guidance; re-run hook; loop until clean - Bypass with @followup → user adds
@followup[TD-NNN]to each hit item + opens consumer-side TD; main thread re-runs hook to confirm bypass markers recognized; proceed - Bypass with @no-manual-review-check → only if hit is legitimate false positive (e.g., 真機掃 SMS 無 dev replay endpoint); main thread adds marker + re-runs hook; proceed
- Proceed only after hook exits 0 or user explicitly confirms bypass strategy
If hook exits 0: Silent skip — proceed to Step 3.5.
Reference: vendor/snippets/manual-review-enforcement/patterns.json + rules/core/manual-review.data-readiness.md.
3.5. Discuss Items Walkthrough (Step 2.5 in the spec — runs after artifact + task completion checks, before delta sync preview)
Spec authority: openspec/specs/manual-review-item-kind/spec.md "Spectra-Archive Discuss Walkthrough"
Read openspec/changes/<change-name>/tasks.md ## 人工檢查 section. Identify every unchecked item where kind = "discuss" (either explicit [discuss] marker, OR derived via Default Kind Derivation Rule when proposal.md contains **No user-facing journey (backend-only)**).
For each unchecked `[discuss]` item, the main thread Claude SHALL (proactively, without prompting):
- Read the item description and surrounding context (proposal.md User Journeys, related task results, recent diff).
- Classify the item's trigger condition (key for next step):
- Internal evidence available now — code / schema / migration state / cron config / dev DB query result. Claude can collect evidence immediately.
- External signal already occurred — staging / production already deployed, soak window already elapsed, business decision already made. Claude can query the post-signal state (prod URL
<title>, prod evlog row, prod migration\doutput) and collect evidence. - External signal pending — required deploy / soak / business authorization has not yet occurred. Claude CANNOT synthesize evidence by analysis alone; any "based on code, this should work" reasoning is speculation, not walkthrough evidence.
- Prepare evidence relevant to the item — pick whichever combination is most informative:
grep/rgresults showing the relevant code paths or migrations touched- Recent
git diffexcerpts (focused on the area the item references) - Command output (if the item asks about deploy / migration / cron / data state, run the relevant query and paste the output)
- Data summary (e.g., row counts, distribution stats, drift counts)
- Cross-consumer / cross-environment check results (e.g., per-consumer migration apply status)
For "External signal pending" items: skip evidence collection (there's nothing to collect yet). Move directly to step 4 with the trigger condition stated explicitly.
- Present to the user, in this format:
``` ### Discuss item #<id> [discuss] <description>
Trigger condition: <internal evidence | external signal already occurred | external signal pending — describe>
Evidence: (omit this section if trigger is "external signal pending") <grep / diff / command output / summary>
My read: <one or two sentences explaining what the evidence implies, OR "waiting on <signal>; no evidence available yet — recommend Defer to HANDOFF so archive can proceed">
請確認:OK / Issue / Skip[ / Defer] ```
The Defer option is shown only when trigger is "external signal pending". For the other two trigger classes, only OK / Issue / Skip are valid — Claude has evidence available and there is nothing legitimate to wait on.
- Wait for the user's response. Branch on the answer:
- OK: Edit
tasks.mdfor this line: - Set checkbox to
[x] - Insert
(claude-discussed: <ISO-8601-timestamp>)annotation between description and any trailing markers (@followup[TD-NNN]/@no-screenshot), preserving canonical ordering. Use the current ISO-8601 UTC timestamp (new Date().toISOString()). - Example before:
- [ ] #2 [discuss] Confirm rollout @no-screenshot - Example after:
- [x] #2 [discuss] Confirm rollout (claude-discussed: 2026-05-10T14:23:00Z) @no-screenshot - Issue: Edit
tasks.md: - Keep checkbox as
[ ] - Append
(issue: <user note>)annotation between description and trailing markers - Note in summary: this item is intentionally left unchecked; archive does NOT block on it (user retains control)
- Skip: Edit
tasks.md: - Set checkbox to
[x] - Append
(skip)annotation (or(skip: <reason>)if the user gave a reason) - Defer (only valid when trigger is "external signal pending"): Edit
tasks.md: - Set checkbox to
[x] - Insert
(deferred-to-handoff: <ISO-8601-timestamp>) (awaiting-signal: <signal-desc>)between description and trailing markers (canonical ordering: kind marker → annotations →@followup/@no-screenshot) - Example after:
- [x] #2 [discuss] Confirm rollout (deferred-to-handoff: 2026-05-22T03:14:00Z) (awaiting-signal: prod deploy) @no-screenshot - AND write a HANDOFF entry (see "HANDOFF write" subsection below). Archive flow continues — do NOT stop.
- Move to the next unchecked
[discuss]item until all are processed.
HANDOFF write (only fires when at least one item took the Defer path in this archive run):
- Resolve target path:
$MAIN_WT_PATH/HANDOFF.md(usegit rev-parse --path-format=absolute --git-common-dirto find the main worktree even from a linked worktree; same idiom ashandoffskill Step 1.5). - Locate
## Deferred discuss itemsheading. If missing, append the heading + an empty body at the end of HANDOFF.md. - For each deferred item, append an entry block (preserving any pre-existing entries in deferred-at ascending order):
```md <!-- deferred-begin:<change-name>:<item-id> -->
- <change-name> #<item-id> — <one-line description copied from tasks.md, stripped of kind marker and annotations>
- Awaiting signal: <signal-desc same as awaiting-signal annotation>
- Resume:
/spectra-archive <change-name> - Deferred at: <ISO-8601-timestamp same as deferred-to-handoff annotation>
<!-- deferred-end:<change-name>:<item-id> --> ```
- The HTML markers are load-bearing — Resume mode (Step 1 path resolution) uses them for
sed-based entry removal. Do NOT drop or rename them.
Skip-condition: if ## 人工檢查 has no unchecked [discuss] items, skip this step silently.
Out of scope here: unchecked [review:ui] items are NOT processed in this step — they are routed to /review-screenshot by the orchestrator's Archive Flow (spectra/SKILL.md Step 1). If [review:ui] items remain unchecked at this point, the orchestrator should already have prompted the user; if they reach Step 6 unchecked, archive-gate.sh Check 4 will block.
Restrictions (hard rules from manual-review.md):
- NEVER mark a
[discuss]item[x]without showing the user evidence first AND receiving an explicit OK / Skip / Defer - NEVER write
(claude-discussed: <ISO>)annotation without an actual discussion taking place - NEVER offer the Defer option when trigger is "internal evidence available" or "external signal already occurred" — Defer is reserved for "external signal pending"
- NEVER write
(deferred-to-handoff: <ISO>)annotation without also writing the matching HANDOFF entry in the same archive run - NEVER batch-process multiple
[discuss]items in one user prompt — present them one at a time so the user can give a focused answer per item - NEVER touch
[review:ui]items during this step
3.5b. Resume walkthrough (clade fork addition — only runs when Step 0.5 detected Resume mode; skip otherwise)
For each line in openspec/changes/archive/<change-name>/tasks.md containing (deferred-to-handoff::
- Read item description + extract the
awaiting-signal:annotation text. - Re-classify trigger condition. The originally-pending signal typically has occurred by now; collect post-signal evidence (prod URL
<title>, prod evlog row, prod migration\doutput, etc.). If the signal still has not occurred, that's a legitimate "still pending" outcome. - Present to user identical to Step 3.5 walkthrough format, but with header:
``` ### Resume discuss item #<id> [discuss] <description>
Originally deferred at: <ISO from deferred-to-handoff annotation> Awaiting signal: <signal-desc from awaiting-signal annotation> Trigger condition now: <internal evidence | external signal already occurred | external signal still pending>
Evidence: (omit if signal still pending) <grep / diff / command output / summary>
My read: <one or two sentences>
請確認:OK / Issue / Skip / Still pending ```
"Defer" is NOT offered in Resume mode — that would re-defer the same item indefinitely. The user picks a terminal outcome (OK / Issue / Skip) or signals the item still needs more time (Still pending).
- Branch on user response, editing
openspec/changes/archive/<change-name>/tasks.md:
- OK: Remove
(deferred-to-handoff: ...)and(awaiting-signal: ...)annotations from the line. Insert(claude-discussed: <new-ISO>)between description and trailing markers. Keep checkbox[x]. - Issue: Remove
(deferred-to-handoff: ...)and(awaiting-signal: ...). Change checkbox[x]→[ ]. Append(issue: <user note>)between description and trailing markers. - Skip: Remove
(deferred-to-handoff: ...)and(awaiting-signal: ...). Append(skip[: reason]). Keep checkbox[x]. - Still pending: Leave line completely unchanged (annotations + checkbox both stay). HANDOFF entry also stays.
- For each item resolved (OK / Issue / Skip) — i.e. NOT "Still pending" — remove the corresponding HANDOFF entry. Resolve
$MAIN_WT_PATHfirst (same idiom ashandoffskill Step 1.5):
```bash GIT_COMMON_DIR="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" MAIN_WT_PATH="$(dirname "$GIT_COMMON_DIR")" HANDOFF_FILE="$MAIN_WT_PATH/HANDOFF.md"
# Delete the block between deferred-begin / deferred-end markers for this change/item. # Use sed; if markers not found (user manually edited), leave HANDOFF alone and report. if grep -q "<!-- deferred-begin:<change-name>:<item-id> -->" "$HANDOFF_FILE"; then sed -i.bak "/<!-- deferred-begin:<change-name>:<item-id> -->/,/<!-- deferred-end:<change-name>:<item-id> -->/d" "$HANDOFF_FILE" rm "$HANDOFF_FILE.bak" else echo "warn: HANDOFF entry for <change-name>:<item-id> not found (markers missing); user should clean manually" fi ```
- After all deferred items processed:
- If
## Deferred discuss itemssection body is now empty (no<!-- deferred-begin:markers remain anywhere under that heading), best-effort remove the heading too. If removal would risk corrupting surrounding markdown (heading is wedged between other sections), leave heading + empty body and tell user to clean manually. - Print one-line summary:
Resume walkthrough complete: X resolved (Y OK / Z Issue / W Skip) / V still pending
- Resume mode does NOT run any spectra CLI command. The archived change directory stays in place; only
tasks.md(andHANDOFF.mdentries) get edited. User stages + commits the resulting diff manually with a message likearchive: <change-name>; resume — deferred items: X resolved, Y still pending.
Restrictions (Resume mode):
- NEVER run
spectra archiveCLI in Resume mode (change is already archived — archive flow is a no-op) - NEVER delete or move the archived change directory
- NEVER re-run gates (archive-gate / manual-review pattern check) / delta sync / screenshot sweep in Resume mode — Step 0.5 explicitly skips those
- NEVER add new
(deferred-to-handoff: ...)annotations in Resume mode — Defer is forbidden here (would re-defer indefinitely) - NEVER touch items that lack
(deferred-to-handoff:)annotation, even in the same## 人工檢查section — Resume mode is scoped to deferred items only
- Assess delta spec sync state
Check for delta specs at openspec/changes/<name>/specs/. If none exist, proceed without sync prompt.
If delta specs exist:
- Compare each delta spec with its corresponding main spec at
openspec/specs/<capability>/spec.md - Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
Prompt options:
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke spectra-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
- Clean up tracking file
Delete .spectra/touched/<change-name>.json if it exists. This file contains implementation tracking data that is not needed after archiving.
``bash rm -f .spectra/touched/<change-name>.json ``
If the file does not exist, silently continue.
5.5. Post-walkthrough gate re-check (clade fork addition — paired with --pre-skill PreToolUse hook)
Journey URL Touch (Check 1) bypass marker — if Check 1 reports Journey URL Touch 未通過 for a URL whose .vue was committed in main before this worktree was forked (typical: earlier propose iteration impl'd the UI, current worktree only carries verify evidence / tasks.md annotations / screenshot sweep), this is the expected atomic-landing edge case. Drop a bypass marker into the relevant tasks.md block:
``markdown <!-- journey-touch: intentional, reason: UI already committed in <commit-sha> (earlier propose iteration); this worktree only carries verify evidence --> ``
The gate honours the marker per-URL. Use only when commit history actually contains the touched UI (verify via git log -- <ui-path>); never use to silence a missing implementation.
The PreToolUse hook pre-archive-ux-gate.sh runs archive-gate.sh --pre-skill <name> before this skill starts, which skips Check 4 (Manual Review Kind Validation) because annotations are populated by Step 3.5 above. Now that Step 3.5 has run, validate the post-walkthrough state by re-running the gate without --pre-skill:
``bash bash scripts/spectra-advanced/archive-gate.sh "<change-name>" ``
Branch on exit code:
- Exit 0 → All checks pass. Proceed to Step 6.
- Exit 2 → A check failed. Most common cause: Step 3.5 walkthrough was interrupted / skipped / produced incomplete annotations. Other checks (1/2/3/5/6) already passed pre-skill, so a failure here is almost certainly Check 4 — or Check 7(缺 Step 8a.6 的 E.1 verdict record;補跑 `pre-handoff-ledger.mjs record --layer E.1 ...` 後重試,或對非適用 change 加 `<!-- pre-handoff-verdict: intentional, reason: ... -->` 到 tasks.md)。
- Display the gate stderr to the user.
- MUST prompt via
AskUserQuestion: - Fix now — go back to Step 3.5 and finish walkthrough for the items the gate flagged
- Abort archive — stop the skill, leave change in-flight; user investigates manually
- NEVER silently bypass exit 2 — Check 4 is the only post-walkthrough validation that the pre-skill path defers.
Skip condition: if the gate script does not exist (consumer pre-propagation state), warn and proceed (fail-open, matches existing Check 5 fail-open behavior).
Layer C — data-sanity audit(clade fork addition; pre-handoff quality gates; not in upstream spectra):archive-gate 過後、spec-sync 前,對本 change 跑 static data-shape audit,擋住「client query param literal 違反 server zod bound → silent 4xx → lookup map empty → admin list column 整列 fallback」這類 typecheck/lint/視覺都抓不到的資料形狀問題(<consumer-a> app-status-badge-extraction root cause):
``bash node <clade-vendor>/scripts/audit-data-sanity.mjs --consumer-path . --json ``
- exit 0 `status: "pass"` → 通過(advisory lookupRisks 印 stderr 供參考,不 block);進 sidecar advance。
- exit 1 `status: "fail"`(PARAM_BOUNDARY,Critical)→ MUST block archive:顯示 violations 給 user,root-cause 修 client literal 到 bound 內(典型
perPage: 200→100)或調 server schema 後 re-run。NEVER silently bypass、NEVER 標 archive done 留給 user manual review 抓。 - Skip condition:
audit-data-sanity.mjs不存在(consumer pre-propagation)→ warn + 跳過(fail-open,與 Check 5 一致)。 - 完整偵測 / 限制(heuristic param-name 跨檔對應)見
/data-sanityskill。
Sidecar advance (TD-155) — once gates pass (exit 0 or user explicitly bypassed), advance phase before entering spec-sync / archive CLI:
``bash node scripts/spectra-archive-sidecar.mjs update <change-name> --phase spec-sync --last-completed gate-check ``
(silent fail-safe if helper / sidecar missing.)
- Perform the archive
Use the spectra archive CLI command which handles the full archive workflow (spec snapshot, delta application, @trace injection, identity recording, vector indexing):
``bash spectra archive <name> ``
Optional flags:
--skip-specs— skip delta spec application (for tooling/doc-only changes)--mark-tasks-complete— mark all incomplete tasks as complete before archiving--no-validate— skip delta spec validation
If archive fails with "already exists" error, suggest renaming existing archive.
Sidecar advance (TD-155) — only after `spectra archive` exits 0, advance phase:
``bash node scripts/spectra-archive-sidecar.mjs update <change-name> --phase folder-mv --last-completed spec-sync ``
NEVER advance the sidecar to folder-mv if the CLI failed or was interrupted — leaving phase=spec-sync is the trigger that lets Step 0.5 detect the dangerous mid-CLI state and force manual fixup on next --resume. (silent fail-safe if helper / sidecar missing.)
- Sweep screenshots (auto)
After successful archive, automatically invoke the screenshots-archive skill (via Skill tool) with change <change-name> to sweep the corresponding screenshot folders into screenshots/<env>/_archive/YYYY-MM/.
- Caller-trusted: spectra-archive completing = the change is closed = its screenshots belong in
_archive/(no extra confirmation here;screenshots-archiveMode B handles topic-name mismatch internally). - Skip condition: if user explicitly passed
--no-sweep(or said "不要 sweep 截圖") when invoking spectra-archive, skip this step and note in Step 8 summary:Screenshots: sweep skipped (user --no-sweep). - Failure handling: if
screenshots-archiveerrors (e.g., disk write failure), do NOT fail the overall archive — log the error and note in Step 8 summary:Screenshots: sweep failed — see error above. The change is already archived; sweep is best-effort cleanup.
Note (TD-160 preserve): when Step 0 ran wt-helper merge-back, gitignored worktree screenshots at screenshots/<env>/<topic>/ are automatically copied to main before cleanup destroys the worktree (see preserveWorktreeScreenshots in vendor/scripts/wt-helper.mjs). Step 7 sweep therefore finds the files in main as expected. Without this preserve, cleanup would delete them and sweep would be a no-op.
Sidecar advance (TD-155) — after sweep completes (success, skipped, or failed — all three count as "Step 7 phase reached"):
``bash node scripts/spectra-archive-sidecar.mjs update <change-name> --phase screenshot-sweep --last-completed folder-mv ``
(silent fail-safe if helper / sidecar missing.)
7.5. Auto-reconcile merge-back stash (clade fork; auto-善後 — no tail)
merge-back (--auto-stash, Step 0) sets aside main's pre-merge dirty as wt-merge-block/<change>/... so the squash can land cleanly. This is main's OWN work, NOT stale change residue — typically HANDOFF entries from parallel sessions, ROADMAP/spec regen, telemetry appends. It MUST be restored to main's working tree (so it lands with the user's eventual /commit); the archive auto-善後 it here and NEVER leaves a stash tail for the user (the only exception is a genuine per-file divergence, which is surfaced, not buried).
> NEVER blanket-drop a `wt-merge-block` stash. Proven incident (2026-06-11): a wt-merge-block stash held a parallel session's receiving-material-map HANDOFF entry (blocked-status handoff for another in-flight change); the old "drop archived-slug" guidance would have destroyed it. wt-merge-block = main's blockers, not change residue. (stash-reconcile.mjs itself already recommends apply for clean merge-block entries — the old AskUserQuestion+keep default was the bug, not the script.)
``bash node scripts/stash-reconcile.mjs --slug "<change-name>" --json ``
Parse JSON entries. If empty → Reconcile: 0 entries, proceed. For each entry, branch on namespace.kind (main thread runs the git ops directly — no AskUserQuestion in the common path):
- `wt-merge-block` → auto-restore per-file:
``bash REF="<entry.ref e.g. stash@{0}>" # archive already produced the authoritative version of these → keep current, do NOT restore the stale stash copy REGEN_RE='^(openspec/ROADMAP\.md|openspec/specs/)' surfaced=() for f in $(git stash show --include-untracked --name-only "$REF"); do echo "$f" | grep -qE "$REGEN_RE" && continue # keep current post-archive version # restore only when main has NOT independently changed f since the stash # (current working file === stash base → no divergence → safe to restore stash content) if diff -q <(git show "$REF^1:$f" 2>/dev/null) "$f" >/dev/null 2>&1; then git checkout "$REF" -- "$f" # restore main's set-aside work else surfaced+=("$f") # diverged since stash — do NOT auto-overwrite fi done if [ ${#surfaced[@]} -eq 0 ]; then git stash drop "$REF" # fully reconciled — no tail fi # else: keep stash, report surfaced[] for manual handling (rare) ``
Report Reconcile: restored N file(s) from merge-block, dropped stash (or, if surfaced) Reconcile: M file(s) diverged — stash kept, see <list>.
- `wt-baseline` / `wt-final-baseline` → pre-fork pin, stale post-archive (content pinned as
refs/wt-baseline/...):git stash drop <entry.ref>. ReportReconcile: dropped baseline pin.
- other / unknown kind → do NOT auto-touch; print
ref+ file list, noteReconcile: 1 unknown-shape entry kept (manual).
Restrictions:
- NEVER drop a
wt-merge-blockstash without first restoring its non-regenerated files — that loses main's real work (e.g. parallel-session HANDOFF entries). - NEVER auto-overwrite a file that diverged from the stash base (
surfaced[]) — surface it instead; this is the only path that leaves a tail, and it is warranted (a genuine conflict needs human eyes). - NEVER restore archive-regenerated paths (
openspec/ROADMAP.md,openspec/specs/**) from the stash — the archive's freshly-applied version is authoritative; the stash holds the pre-archive snapshot, which would revert the archive. - NEVER fall back to AskUserQuestion + "keep all" for a clean
wt-merge-blockentry — that is the old tail-leaving behavior this step replaces. - Skip condition:
--no-reconcile→ noteReconcile: skipped (user --no-reconcile). - Failure handling: any git op error → do NOT fail the archive (already done); note
Reconcile: failed — see error above+ leave the stash intact for the user. - Note in Step 8 summary:
Reconcile: restored N / dropped/Reconcile: M diverged — stash kept/Reconcile: dropped baseline pin/Reconcile: 0 entries/Reconcile: skipped/Reconcile: failed.
Sidecar advance (TD-155) — after stash reconcile completes (any outcome), advance to final phase:
``bash node scripts/spectra-archive-sidecar.mjs update <change-name> --phase cleanup --last-completed screenshot-sweep ``
(silent fail-safe if helper / sidecar missing.)
7.7. Notion ticket status — ensure 進行中 + surface pending 驗收中(clade fork addition — per [[spectra-notion-coupling]])
Skip-condition(任一成立即 silent skip):consumer-meta notion.ticketWorkflow !== true;或本 change 的 proposal.md(已搬到 openspec/changes/archive/<date>-<name>/proposal.md)頂部無 > **Notion ticket**: 連結。不適用時不要硬湊 ticket。
兩條件都成立時,MUST:
- 抓 ticket
page_id(proposal.md 頂部)+ consumer-metanotion.dataSourceId。 notion-fetch collection://<dataSourceId>重撈 schema 校對 property key(中文 + 全形空格 +>=,憑記憶必錯)。- Ensure 進行中:ticket 若還停在
未開始/需確認→ 補轉→ 進行中(per~/.claude/skills/_notion-tdms-board/REFERENCE.md §3)。 - Pending 驗收中:archive 是 bookkeeping,此刻尚無本次發版 git tag(標準順序「先 archive 再
/commit」,tag 在/commitStep 5 才生)→ NEVER 在此推 `驗收中`;改在 Step 8 summary 明文列 pending 動作(見下),由緊接的/commit發版後同一主線完成進行中 → 驗收中+ 填修復版本 >= <tag>。
NEVER 在 archive 當下標 驗收中(無 tag)或填 修復版本 >=。NEVER 碰客戶側轉移(驗收中→完成 等)或 發布日期 / 驗收日期 / 名稱 / 驗收完成 欄位。
- Display summary
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Spec sync status (synced / sync skipped / no delta specs)
- Note about any warnings (incomplete artifacts/tasks)
- Notion ticket pending(only when Step 7.7 applied — consumer-meta
notion.ticketWorkflow+ change 有連結):一行明文📌 Notion ticket <page_id>:已 ensure 進行中;待 /commit 發版產 git tag 後 → 轉 驗收中 + 填 修復版本 >= <tag>(同一主線跑完 /commit 立即執行,NEVER 留到下次想起來)(per [[spectra-notion-coupling]])
Sidecar cleanup (TD-155) — after the summary is displayed (archive considered complete from the user's perspective), delete the sidecar:
``bash node scripts/spectra-archive-sidecar.mjs delete <change-name> ``
(silent fail-safe: if sidecar is already missing — e.g., consumer pre-propagate — the helper is a no-op.)
Output On Success
## Archive Complete**Change:** <change-name>**Schema:** <schema-name>**Worktree:** ✓ Absorbed into main (or: no worktree — solo path)**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/**Specs:** ✓ Synced to main specs**Screenshots:** ✓ Swept to _archive/YYYY-MM/ (or: no screenshots / skipped (user --no-sweep) / sweep failed)All artifacts complete. All tasks complete.
If Step 0 stashed blockers, append a line under Worktree:
**Stash to reconcile:** wt-merge-block/<change-name>/<ISO> (run `node scripts/stash-reconcile.mjs` to plan)
Output On Success (No Delta Specs)
## Archive Complete**Change:** <change-name>**Schema:** <schema-name>**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/**Specs:** No delta specs**Screenshots:** ✓ Swept to _archive/YYYY-MM/ (or: no screenshots / skipped / failed)All artifacts complete. All tasks complete.
Output On Success With Warnings
## Archive Complete (with warnings)**Change:** <change-name>**Schema:** <schema-name>**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/**Specs:** Sync skipped (user chose to skip)**Screenshots:** Sweep failed — see error above**Warnings:**- Archived with 2 incomplete artifacts- Archived with 3 incomplete tasks- Delta spec sync was skipped (user chose to skip)- Screenshot sweep failed (archive itself succeeded)Review the archive if this was not intentional.
Output On Error (Archive Exists)
## Archive Failed**Change:** <change-name>**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/Target archive directory already exists.**Options:**1. Rename the existing archive2. Delete the existing archive if it's a duplicate3. Wait until a different date to archive
Guardrails
- Always prompt for change selection if not provided
- Use artifact graph (spectra status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use the Skill tool to invoke
spectra-sync-specs(agent-driven) - If delta specs exist, always run the sync assessment and show the combined summary before prompting
- If AskUserQuestion tool is not available, ask the same questions as plain text and wait for the user's response
- NEVER skip Step 7 screenshot sweep silently — always run it (unless
--no-sweep); sweep failure must surface in summary, but NEVER roll back the successful archive on sweep failure - ALWAYS call
screenshots-archivevia Skill tool with explicitchange <change-name>argument so Mode B logic (caller-trusted, internal topic-mismatch prompt) kicks in