@ikunin/sprintpilot 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -84,6 +84,15 @@ Many BMAD skills present interactive menus or ask for confirmation. In autopilot
84
84
 
85
85
  For everything else: decide, document briefly, continue.
86
86
 
87
+ ### Conventions referenced throughout this file (hoisted to avoid per-site repetition)
88
+
89
+ - **`log-timing.js` is fire-and-forget**: every `node …/log-timing.js …` invocation must never halt the autopilot on failure (treat exit codes as advisory).
90
+ - **`resolve-profile.js get` falls back to documented default**: every `resolve-profile.js get <key>` call falls back to the documented default on non-zero exit; never halt.
91
+ - **SYNC_STATUS_RULE**: `sync-status.js` does full block replacement. When updating an existing story entry in `{git_status_file}`, re-read its existing fields and pass ALL of them alongside the new values (branch, commit, patch_commits, push_status, pr_url, lint_result, worktree, platform, base_branch, worktree_cleaned). For a brand-new entry, pass at minimum `--branch` and the targeted status field.
92
+ - **`{{has_origin}}` is false**: when this flag is false, skip every `git fetch origin` and `git push origin` call below; substitute `origin/{{base_branch}}` → `{{base_branch}}` for read operations. Do NOT repeat this qualifier per site.
93
+ - **FINALIZE_HANDOFF macro** (sprint-finalize-pending checkpoint): write `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []` to `{state_file}`; release the autopilot lock idempotently (`lock.js release`); report sprint-complete-with-handoff to user; HALT this session. The next `/sprint-autopilot-on` invocation enters via step 1, sees the pending state, and runs step 10 with a clean window.
94
+ - **FLUSH_SHARDS macro** (when `{{coalesce_state_writes}}` is true): run `state-shard.js flush --story sprint --project-root "{{project_root}}"`, then `merge-shards.js --project-root "{{project_root}}"`. Both ignore failures.
95
+
87
96
  ---
88
97
 
89
98
  ## DECISION LOGGING
@@ -136,16 +145,15 @@ Resolve:
136
145
 
137
146
  **`{state_file}` schema** (referenced as `STATE_FIELDS` below): `last_updated`, `current_story`, `current_bmad_step`, `completed_skill`, `next_skill`, `session_stories_done`, `stories_remaining`, `git_enabled`, `platform`, `in_worktree`, `pr_base`. Always update `last_updated` on every write.
138
147
 
139
- **PR 6 state-write policy (`autopilot.coalesce_state_writes`):**
148
+ **State-write policy (`autopilot.coalesce_state_writes`):**
140
149
 
141
150
  When the resolved profile sets `autopilot.coalesce_state_writes: true` (nano/small/medium/large by default; `legacy` false), state writes route through `state-shard.js` using a `sprint`-keyed shard as the authoritative state for sprint-level fields, and per-story shards for story-scoped fields. Policy:
142
151
 
143
152
  - **Critical keys** (`current_story`, `current_bmad_step`, `in_worktree`, `patch_commits`) always go to shard via `state-shard.js batch`, which auto-flushes and writes straight through because the script recognizes them as crash-recovery keys.
144
153
  - **Non-critical fields** (test counts, file lists, next_skill, session_stories_done, stories_remaining, etc.) go to `state-shard.js batch`, accumulating in the pending buffer. Flushed at each story boundary (step 7) and session checkpoint (step 9).
145
154
  - **Merged authoritative state** (`autopilot-state.yaml`) is rebuilt via `merge-shards.js` at story boundary + session checkpoint + sprint complete.
146
- - **Rollback** (`coalesce_state_writes: false`): every `Update {state_file}` action writes directly to `autopilot-state.yaml` via the existing STATE_FIELDS shape — no shard indirection. This is the v1.0.5 path byte-for-byte.
147
155
 
148
- When the flag is `false`, the direct-write instructions below are authoritative. When `true`, substitute each `Update {state_file} with STATE_FIELDS: <changes>` with a `state-shard.js batch --story sprint --json <changes>` call, followed by a `merge-shards.js --project-root "{{project_root}}"` at the story boundary / checkpoint. The merged `autopilot-state.yaml` remains the single source of truth for resume-after-crash.
156
+ When `coalesce_state_writes: false`, all state writes go directly to `autopilot-state.yaml` (legacy v1.0.5 path). When `true`, substitute each `Update {state_file} with STATE_FIELDS: <changes>` with a `state-shard.js batch --story sprint --json <changes>` call, followed by a `merge-shards.js --project-root "{{project_root}}"` at the story boundary / checkpoint.
149
157
 
150
158
  ### Git integration bootstrap
151
159
 
@@ -178,13 +186,13 @@ When the flag is `false`, the direct-write instructions below are authoritative.
178
186
  </action>
179
187
  <action>**Resolve profile-driven flow** — run:
180
188
  `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.implementation_flow`
181
- Output: `full` or `quick`. Set `{{implementation_flow}}` = output. Default to `full` if the call fails.
189
+ Output: `full` or `quick`. Set `{{implementation_flow}}` = output (default `full`).
182
190
  Run: `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.session_story_limit` → override `{{session_story_limit}}` if the resolver produces a different value than config.yaml (profile overrides config silence). Same pattern for `autopilot.retrospective_mode`.
183
191
  </action>
184
192
  <action>**Resolve coalesce flag** — run:
185
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.coalesce_state_writes` → `{{coalesce_state_writes}}`. Default `false` on failure.
193
+ `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.coalesce_state_writes` → `{{coalesce_state_writes}}` (default `false`).
186
194
  </action>
187
- <!-- PR 11: detect the running host and resolve parallel-dispatch config. -->
195
+ <!-- Detect the running host and resolve parallel-dispatch config. -->
188
196
  <action>**Detect host agent** — run:
189
197
  `node {{project_root}}/_Sprintpilot/scripts/agent-adapter.js detect --project-root "{{project_root}}"`
190
198
  Parse the JSON output: set `{{host_agent}}` = host, `{{host_supports_parallel}}` = supports_parallel, `{{host_confidence}}` = confidence.
@@ -222,7 +230,7 @@ When the flag is `false`, the direct-write instructions below are authoritative.
222
230
  <action>HALT: "No git repository found. Initialize one first:
223
231
  ```
224
232
  git init
225
- git add -A
233
+ git add README.md .gitignore
226
234
  git commit -m 'initial commit'
227
235
  git remote add origin <your-repo-url>
228
236
  ```
@@ -241,9 +249,9 @@ When the flag is `false`, the direct-write instructions below are authoritative.
241
249
  <action>Set `{{platform}}` = "git_only"</action>
242
250
  </check>
243
251
 
244
- <!-- PR 10: disable gc.auto on the main repo so git's auto-GC doesn't
245
- race with concurrent worktree operations during the sprint. Save
246
- the prior value so we can restore it at sprint complete (step 10). -->
252
+ <!-- Disable gc.auto on the main repo so git's auto-GC doesn't race with
253
+ concurrent worktree operations during the sprint. Save the prior
254
+ value so we can restore it at sprint complete (step 10). -->
247
255
  <action>**Save + disable main-repo gc.auto**: set `{{original_gc_auto_main}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}"`, then `git -C {{project_root}} config --local gc.auto 0`.</action>
248
256
 
249
257
  <action>**Lock file** — run: `node {{project_root}}/_Sprintpilot/scripts/lock.js acquire`
@@ -263,12 +271,12 @@ When the flag is `false`, the direct-write instructions below are authoritative.
263
271
  Log: "Platform detected: {{platform}}"
264
272
  </action>
265
273
 
266
- <!-- PR 7 CONDITIONAL BOOT WORK: a clean repo main worktree only, zero
267
- in-progress stories can skip the slow health-check + branch
274
+ <!-- Conditional boot work: a clean repo (main worktree only, zero
275
+ in-progress stories) can skip the slow health-check + branch
268
276
  reconciliation below. Gate honored by non-legacy, non-large profiles
269
277
  (large keeps full reconciliation for compliance/uptime reasons). -->
270
278
  <action>Read `autopilot.conditional_boot_work` from the resolver:
271
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.conditional_boot_work` → `{{conditional_boot_work}}`. Default to `false` on failure.
279
+ `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.conditional_boot_work` → `{{conditional_boot_work}}` (default `false`).
272
280
  </action>
273
281
  <action>Count worktrees (cross-platform; replaces a `... | grep -c` pipe that needed POSIX shell):
274
282
  `node {{project_root}}/_Sprintpilot/scripts/git-portable.js count-worktrees --project-root "{{project_root}}"` → `{{worktree_count}}`. The script fails open to 2 internally if git itself errors, matching the previous "fail-open to force full path" semantic.
@@ -276,8 +284,8 @@ When the flag is `false`, the direct-write instructions below are authoritative.
276
284
  <action>Count in-progress stories: read `{status_file}` and count stories whose status is NOT in {`done`, `backlog`}. Set `{{in_progress_count}}`. Fail-open to 1 (force full path) if the file is unreadable.</action>
277
285
 
278
286
  <check if="{{conditional_boot_work}} is true AND {{worktree_count}} is 1 AND {{in_progress_count}} is 0">
279
- <action>Log: "Boot fast-path (PR 7): clean repo — skipping health-check + branch reconciliation"</action>
280
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js once --story "sprint" --phase "boot.fast-path" --meta "{\"reason\":\"clean-repo\"}" --project-root "{{project_root}}"` — ignore failures.</action>
287
+ <action>Log: "Boot fast-path: clean repo — skipping health-check + branch reconciliation"</action>
288
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js once --story "sprint" --phase "boot.fast-path" --meta "{\"reason\":\"clean-repo\"}" --project-root "{{project_root}}"`.</action>
281
289
  <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
282
290
  </check>
283
291
 
@@ -322,12 +330,12 @@ When the flag is `false`, the direct-write instructions below are authoritative.
322
330
  - If merge succeeds:
323
331
  - Re-read `{status_file}` from HEAD (may now include story artifacts after merge)
324
332
  - Update `{git_status_file}` via sync-status.js: set `--merge-status "recovered"` for this story.
325
- **IMPORTANT:** sync-status.js does full block replacement. If the story already has an entry in `{git_status_file}`, re-read its existing fields and pass ALL of them alongside `--merge-status`. If no entry exists yet, pass at minimum `--branch` and `--push-status "pushed"`.
333
+ See SYNC_STATUS_RULE at top.
326
334
  - If `{{platform}}` is NOT git_only (github, gitlab, bitbucket, gitea) AND `{{create_pr}}` is true:
327
335
  - Check if PR/MR already exists for this branch (platform-specific check via create-pr.sh or CLI)
328
336
  - If no PR: create one via `node {{project_root}}/_Sprintpilot/scripts/create-pr.js --platform {{platform}} ...`
329
337
  - Log: "PR created/found for <story-key>"
330
- - Update `{git_status_file}` via sync-status.js: set `--merge-status "pr_pending"` for this story (same full-field requirement as above)
338
+ - Update `{git_status_file}` via sync-status.js: set `--merge-status "pr_pending"` (see SYNC_STATUS_RULE).
331
339
  - If status IS "done" AND branch still exists AND `{{cleanup_on_merge}}` is true:
332
340
  - Log: "Stale remote branch: <branch> — story already done, cleaning up"
333
341
  - Delete remote branch (ignore failure — the branch may already be gone): `git push origin --delete <branch>`
@@ -335,7 +343,7 @@ When the flag is `false`, the direct-write instructions below are authoritative.
335
343
 
336
344
  <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
337
345
 
338
- </check><!-- end PR 7 conditional boot work (non-fast-path branch) -->
346
+ </check><!-- end conditional-boot-work non-fast-path branch -->
339
347
  </check>
340
348
 
341
349
  ---
@@ -350,7 +358,7 @@ When the flag is `false`, the direct-write instructions below are authoritative.
350
358
  <action>Read `{state_file}` fully</action>
351
359
  <action>Extract saved state:
352
360
  - `{{current_story}}` — story in progress when last session ended
353
- - `{{current_bmad_step}}` — BMAD step that was active (2–7), or the reserved value `sprint-finalize-pending` meaning "the prior session detected sprint-complete and checkpointed; this session's only job is step 10"
361
+ - `{{current_bmad_step}}` — BMAD step that was active (2–7), or one of the reserved values: `executing` (a skill is mid-run), `sprint-finalize-pending` (prior session checkpointed; this session's only job is step 10), `sprint-complete` (terminal marker written by step-10 CRITICAL 5/7).
354
362
  - `{{completed_skill}}` — last skill that ran
355
363
  - `{{next_skill}}` — next skill recommended at save time
356
364
  - `{{session_stories_done}}` = 0 (reset counter for new session)
@@ -388,20 +396,16 @@ When the flag is `false`, the direct-write instructions below are authoritative.
388
396
  - Update `{state_file}` with reconciled values
389
397
  </action>
390
398
 
391
- <!-- Already-completed short-circuit. If the prior session wrote
392
- sprint-complete (CRITICAL 5/7) but crashed before the state file
393
- delete (CRITICAL 7/7), this invocation would otherwise loop into
394
- the finalize handoff forever. Detect it and exit cleanly. -->
399
+ <!-- Already-completed short-circuit: prior session wrote sprint-complete
400
+ (CRITICAL 5/7) but crashed before delete (CRITICAL 7/7). Exit cleanly. -->
395
401
  <check if="{{current_bmad_step}} is sprint-complete">
396
402
  <action>Delete `{state_file}` — clean up the stale terminal marker.</action>
397
403
  <action>Report: "Sprint already complete. Nothing to do — run `bmad-sprint-planning` to plan the next sprint, then re-run `/sprint-autopilot-on`." Then STOP.</action>
398
404
  </check>
399
405
 
400
- <!-- Fresh-context finalize path. If the prior session exited with
401
- current_bmad_step = "sprint-finalize-pending", this invocation's
402
- ONLY job is to run step 10 with a clean window. Verify the
403
- sprint is still genuinely complete before jumping; if a user
404
- added new work in between, fall through to the normal loop. -->
406
+ <!-- Fresh-context finalize path. See AUTOPILOT RULES (context-rot mitigation).
407
+ Verify sprint is still complete before jumping; if new work appeared,
408
+ fall through to the normal loop. -->
405
409
  <check if="{{current_bmad_step}} is sprint-finalize-pending">
406
410
  <action>Run: `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope` — parse stdout JSON.</action>
407
411
  <check if="parsed .state is sprint-complete">
@@ -473,11 +477,11 @@ When the flag is `false`, the direct-write instructions below are authoritative.
473
477
 
474
478
  <step n="2" goal="Main execution loop — route to correct handler">
475
479
 
476
- <!-- PR 12 CROSS-EPIC PARALLELISM (experimental, off by default on every
477
- profile including `large`). All safety rails must pass:
480
+ <!-- CROSS-EPIC PARALLELISM (experimental, off by default on every profile
481
+ including `large`). All safety rails must pass:
478
482
  1. ma.parallel_epics is true.
479
483
  2. Host confidence is HIGH AND supports_parallel is true (same as
480
- intra-epic gate in PR 11).
484
+ the intra-epic gate).
481
485
  3. Two or more epics in dependencies.yaml declare `independent: true`.
482
486
  4. preflight-merge.js reports NO conflicts between all pairs.
483
487
  5. Session-scoped disable flag {{cross_epic_disabled_this_session}}
@@ -511,15 +515,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
511
515
  - `.state == "pre-planning"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = false, `{{sprint_is_complete}}` = false.
512
516
  - `.state == "parse-error"` → log warning; treat as pre-planning. NEVER declare sprint-complete on a parse failure.
513
517
  </action>
514
- <!-- Sprint-complete handoff. Never run step 10 in the same session that
515
- first observed the sprint-complete condition — that session has
516
- spent a long time in context and its tail is the context-rot zone.
517
- Instead, mark the state as sprint-finalize-pending and stop; the
518
- next /sprint-autopilot-on boot will route straight to step 10 (via
519
- the step 1 gate) with a clean window. If we ALREADY are the
520
- finalize session (current_bmad_step was sprint-finalize-pending on
521
- entry), step 1 has already jumped to step 10 and we never reach
522
- this check — so this gate is unambiguous. -->
518
+ <!-- Sprint-complete handoff: see FINALIZE_HANDOFF macro at top + AUTOPILOT RULES (context-rot mitigation). -->
523
519
  <check if="{{sprint_is_complete}} is true">
524
520
  <action>Update `{state_file}` with STATE_FIELDS: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`, `session_stories_done = {{session_stories_done}}`.</action>
525
521
  <action>Release autopilot lock (idempotent): `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
@@ -528,9 +524,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
528
524
  Autopilot sprint-complete checkpoint
529
525
 
530
526
  All stories are done. Pausing before finalization so step 10 runs
531
- with a fresh context this prevents late-session instruction decay
532
- from skipping CRITICAL cleanup actions (commit artifacts, generate
533
- docs, clean up worktrees, emit final report).
527
+ with a fresh context (see AUTOPILOT RULES — context-rot mitigation).
534
528
 
535
529
  Completed {{session_stories_done}} stories this session.
536
530
  State saved to: {state_file}
@@ -538,7 +532,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
538
532
  Run `/sprint-autopilot-on` once more to finalize.
539
533
  ```
540
534
  </action>
541
- <action>HALT — do not continue the execution loop. The next invocation enters via step 1, sees `sprint-finalize-pending`, and jumps to step 10.</action>
535
+ <action>HALT — do not continue the execution loop.</action>
542
536
  </check>
543
537
  <check if="{{sprint_has_stories}} is false">
544
538
  <action>Log: "Sprint pre-planning: no stories in status file yet. Routing through bmad-help to the next planning skill (do NOT go to step 10)."</action>
@@ -547,9 +541,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
547
541
  <check if="{{next_skill}} is empty">
548
542
  <action>**Recover next_skill** — re-read `{status_file}`, find first story with status != "done"</action>
549
543
  <check if="no undone stories found AND {{sprint_has_stories}} is true">
550
- <!-- Fallback sprint-complete detection. Route through the same
551
- finalize-pending handoff as the primary gate above — never
552
- jump into step 10 from this long-running session. -->
544
+ <!-- FINALIZE_HANDOFF (see top of file). -->
553
545
  <action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
554
546
  <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
555
547
  <action>Report: "Sprint complete detected via fallback path. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
@@ -597,12 +589,11 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
597
589
 
598
590
  <step n="3" goal="Prepare and execute the recommended skill">
599
591
 
600
- <!-- ──────────────────────────────────────────────────────────────────
601
- PR 11 PARALLEL DISPATCH (gates):
592
+ <!-- PARALLEL DISPATCH (gates):
602
593
  - autopilot.parallel_stories: true (resolved at boot into
603
594
  {{parallel_stories}})
604
- - host_supports_parallel: true with high confidence (PR 11
605
- agent-adapter.js — only Claude Code today)
595
+ - host_supports_parallel: true with high confidence (only Claude
596
+ Code today, via agent-adapter.js)
606
597
  - implementation_flow != quick (nano runs sequentially per epic)
607
598
  - {{next_skill}} starts a fresh per-story flow (bmad-create-story
608
599
  OR bmad-dev-story OR bmad-quick-dev) — never mid-story
@@ -611,11 +602,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
611
602
 
612
603
  When all gates pass, the autopilot dispatches every story in the
613
604
  current layer concurrently via the host's Agent tool — instead of
614
- picking one story and running the full per-story flow sequentially.
615
- This is the integration point that finally exercises the dispatcher
616
- infrastructure built in PR 9 (resolve-dag) + PR 11 (dispatch-layer).
617
- Without this block, parallel_stories=true had no behavioral effect.
618
- ────────────────────────────────────────────────────────────────── -->
605
+ picking one story and running the full per-story flow sequentially. -->
619
606
  <check if="{{parallel_stories}} is true AND {{host_supports_parallel}} is true AND {{implementation_flow}} is NOT quick AND ({{next_skill}} is bmad-create-story OR {{next_skill}} is bmad-dev-story OR {{next_skill}} is bmad-quick-dev)">
620
607
  <action>**Compute current DAG layer.** Set `{{epic_id}}` = leading numeric segment of the FIRST undone story in `{status_file}`. Run:
621
608
  `node {{project_root}}/_Sprintpilot/scripts/resolve-dag.js layers --epic "{{epic_id}}" --project-root "{{project_root}}"`
@@ -626,11 +613,14 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
626
613
  <check if="{{active_layer}} length is 2 or more">
627
614
  <action>**Parallel dispatch:** run:
628
615
  `node {{project_root}}/_Sprintpilot/scripts/dispatch-layer.js --layer "{{active_layer | join(',')}}" --max-parallel "{{max_parallel_stories}}" --project-root "{{project_root}}" --branch-prefix "{{branch_prefix}}" --base-branch "{{base_branch}}"`
629
- Parse stdout — captures the per-story worktree paths (`stories[*].worktree`) and the `effective_parallel` count.</action>
630
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "dispatch.layer-{{epic_id}}" --project-root "{{project_root}}"` — ignore failures.</action>
631
- <action>**Spawn N concurrent sub-agents IMPORTANT: send a SINGLE message containing N parallel Agent tool calls (one per story).** Do NOT serialize. Each sub-agent runs the FULL per-story flow for its assigned story (bmad-create-story bmad-check-implementation-readiness bmad-dev-story bmad-code-review → patches → mark done) inside its own worktree. The Agent tool calls run concurrently and the message reply contains all sub-agent results.
616
+ Parse stdout — set `{{dispatched_stories}}` = the list of stories from `stories[*]` where `created === true` (typically the first `effective_parallel` entries; partial-failure runs may have fewer). Set `{{deferred_stories}}` = the response's `deferred` field (keys not dispatched in this batch — picked up by the next loop iteration).</action>
617
+ <check if="{{dispatched_stories}} length is 0">
618
+ <action>**Dispatch failed entirely**`dispatch-layer.js` produced no successful worktrees. Report the per-story stderr from the response and HALT (this is a TRUE BLOCKER per AUTOPILOT RULES). Worktrees that succeeded mid-batch were rolled back by the script.</action>
619
+ </check>
620
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "dispatch.layer-{{epic_id}}" --project-root "{{project_root}}"`.</action>
621
+ <action>**Spawn N concurrent sub-agents — IMPORTANT: send a SINGLE message containing N parallel Agent tool calls (one per story in `{{dispatched_stories}}`).** Do NOT serialize. The number of sub-agents equals `{{dispatched_stories}}.length` (capped by `--max-parallel` via `dispatch-layer.js`); deferred stories are picked up automatically when the loop re-enters step 2 after this batch. Each sub-agent runs the FULL per-story flow for its assigned story (bmad-create-story → bmad-check-implementation-readiness → bmad-dev-story → bmad-code-review → patches → mark done) inside its own worktree. The Agent tool calls run concurrently and the message reply contains all sub-agent results.
632
622
 
633
- For each story key `K` in `{{active_layer}}`, build one Agent call with `subagent_type=general-purpose` and a self-contained prompt of the form:
623
+ For each story key `K` in `{{dispatched_stories}}`, build one Agent call with `subagent_type=general-purpose` and a self-contained prompt of the form:
634
624
  ```
635
625
  You are running a single story for the Sprintpilot autopilot.
636
626
  - Project root: {{project_root}}
@@ -647,16 +637,15 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
647
637
  <action>**Merge per-story shards** after the layer completes:
648
638
  `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — collapses the per-story state shards into the authoritative project YAMLs and archives the layer's shards under `.archive/layer-<timestamp>/` so the next layer starts clean.</action>
649
639
  <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "_end" --project-root "{{project_root}}"` — closes the open `dispatch.layer-{{epic_id}}` mark.</action>
650
- <action>Update `{state_file}` with the new STATE_FIELDS (each completed story's status is reflected via the merge above; advance `session_stories_done` by `{{active_layer}}.length`). Then `goto step=2` to re-evaluate the loop.</action>
640
+ <action>Update `{state_file}` with the new STATE_FIELDS (each completed story's status is reflected via the merge above; advance `session_stories_done` by `{{dispatched_stories}}.length`). Then `goto step=2` to re-evaluate the loop — any `{{deferred_stories}}` (keys not in this batch because `--max-parallel` capped it) become the next iteration's active_layer.</action>
651
641
  <goto step="2">Re-evaluate after parallel layer dispatch</goto>
652
642
  </check>
653
643
  </check>
654
644
 
655
- <!-- PR 4 NANO ROUTING: when the active profile's implementation_flow is
656
- 'quick', route bmad-dev-story through bmad-quick-dev instead. Quick-dev
657
- runs Implement Review Classify → Commit internally (BMad
658
- step-oneshot.md), so bmad-create-story / bmad-check-readiness /
659
- bmad-code-review are not invoked in this flow. -->
645
+ <!-- Nano routing: when implementation_flow is 'quick', route bmad-dev-story
646
+ through bmad-quick-dev. Quick-dev runs Implement → Review → Classify →
647
+ Commit internally (BMad step-oneshot.md), so bmad-create-story /
648
+ bmad-check-readiness / bmad-code-review are not invoked in this flow. -->
660
649
  <check if="{{implementation_flow}} is quick AND {{next_skill}} is bmad-dev-story">
661
650
  <action>Override `{{next_skill}}` = `bmad-quick-dev`</action>
662
651
  <action>Log: "Routing {{current_story}} through bmad-quick-dev per nano profile (implementation_flow=quick)"</action>
@@ -687,11 +676,11 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
687
676
  <action>Create per-story step tasks if not already created</action>
688
677
  </check>
689
678
 
690
- <!-- PR 5: determine per-story epic key + title so the workflow can branch
679
+ <!-- Determine per-story epic key + title so the workflow can branch
691
680
  per-epic under granularity=epic and decide "is this the last story
692
681
  of the epic". Epic ID is the leading numeric segment of the story
693
- key (e.g. '1-2-foo' → '1'); slug is the epic's title from sprint-
694
- status.yaml (or the epic header in the epics file). Skip if empty. -->
682
+ key (e.g. '1-2-foo' → '1'); slug is the epic's title from
683
+ sprint-status.yaml (or the epic header in the epics file). -->
695
684
  <action>Set `{{epic_id}}` = leading numeric segment of `{{current_story}}` (e.g. `1-2-foo` → `1`). If the key doesn't match `^\d+-`, leave `{{epic_id}}` = "".</action>
696
685
  <action>Set `{{epic_branch_name}}` = `epic-{{epic_id}}` (only used when `{{granularity}} = epic`).</action>
697
686
  <action>**Detect first vs last story of epic** — read `{status_file}` (BMAD-owned; do not modify). Find all stories with the same `{{epic_id}}`:
@@ -717,7 +706,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
717
706
  Detached HEAD is fine — `git worktree add` below creates a new branch from HEAD.
718
707
  </action>
719
708
 
720
- <!-- PR 5: epic granularity — share one branch per epic instead of one per story.
709
+ <!-- Epic granularity: share one branch per epic instead of one per story.
721
710
  Under worktree.enabled=false + granularity=epic (nano default),
722
711
  subsequent stories of the same epic check out the epic branch in
723
712
  place and commit there; no worktree is created. -->
@@ -736,19 +725,19 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
736
725
  </check>
737
726
 
738
727
  <check if="{{worktree_enabled}} is true">
739
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"` — ignore failures.</action>
728
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"`.</action>
740
729
  <action>**Create worktree.** Try: `git worktree add "{{project_root}}/.worktrees/{{current_story}}" -b "{{branch_prefix}}{{branch_name}}" 2>&1`. If it fails because the branch already exists, retry without `-b`: `git worktree add "{{project_root}}/.worktrees/{{current_story}}" "{{branch_prefix}}{{branch_name}}" 2>&1`.
741
730
 
742
731
  If both fail (disk/permissions): log "WARN: worktree add failed — continuing without isolation", set `{{in_worktree}}` = false, and fall back to branch-only mode: `git checkout -b {{branch_prefix}}{{branch_name}}` (retry without `-b` if branch exists). HALT only if the checkout also fails. Git push/PR still work on the branch.
743
732
  </action>
744
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"` — ignore failures.</action>
733
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"`.</action>
745
734
  </check>
746
735
 
747
736
  <check if="{{worktree_enabled}} is true AND worktree add succeeded">
748
737
  <action>`cd {{project_root}}/.worktrees/{{current_story}}`. All subsequent commands run from here. Set `{{worktree_path}}` = this path.</action>
749
- <action>**Disable gc.auto on this worktree** (PR 10): save original value `{{original_gc_auto_worktree}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}/.worktrees/{{current_story}}"`, then `git -C {{project_root}}/.worktrees/{{current_story}} config --local gc.auto 0`.</action>
750
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"` — ignore failures.</action>
751
- <action>**Init submodules** if `.gitmodules` exists (file-exists check via `node -e "process.exit(require('fs').existsSync('.gitmodules')?0:1)"`). PR 10 fast-path:
738
+ <action>**Disable gc.auto on this worktree**: save original value `{{original_gc_auto_worktree}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}/.worktrees/{{current_story}}"`, then `git -C {{project_root}}/.worktrees/{{current_story}} config --local gc.auto 0`.</action>
739
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"`.</action>
740
+ <action>**Init submodules** if `.gitmodules` exists (file-exists check via `node -e "process.exit(require('fs').existsSync('.gitmodules')?0:1)"`). Fast-path:
752
741
  Resolve the main repo's common git dir into a captured variable `{{git_common}}` (cross-platform; replaces a `$(...)` shell substitution):
753
742
  `node {{project_root}}/_Sprintpilot/scripts/git-portable.js common-dir --project-root "{{project_root}}"`. Trim trailing whitespace; the script exits 1 if git can't resolve the dir — log a warning and skip the submodule fast-path, falling through to the degraded-mode branch below.
754
743
  For each submodule path in `.gitmodules`:
@@ -759,7 +748,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
759
748
  3. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js release --submodule "<path>" --project-root "{{project_root}}"` (best-effort, ignore failures).
760
749
  If the loop fails or hangs: warn "Submodule init failed (may need auth). Continuing." and proceed.
761
750
  </action>
762
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"` — ignore failures.</action>
751
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"`.</action>
763
752
  <action>Set `{{in_worktree}}` = true</action>
764
753
  </check>
765
754
  <action>Update `{state_file}` (write to the worktree copy since cwd is now the worktree)</action>
@@ -784,7 +773,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
784
773
  ~50% of the time), so most skills had no duration recorded. `mark`
785
774
  auto-computes the duration of the PREVIOUS phase from a small marker
786
775
  file — one call per transition, no open-phase failure mode. -->
787
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{timing_story}}" --phase "skill.{{next_skill}}" --project-root "{{project_root}}"` — ignore failures.</action>
776
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{timing_story}}" --phase "skill.{{next_skill}}" --project-root "{{project_root}}"`.</action>
788
777
  <action>INVOKE `{{next_skill}}` skill using the Skill tool</action>
789
778
  <action>Mark task "{{next_skill}}" as `completed`</action>
790
779
 
@@ -795,16 +784,16 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
795
784
 
796
785
  <step n="4" goal="Handle skill completion and route to next action">
797
786
 
798
- <!-- PR 4 NANO ROUTING: quick-dev completion handler.
787
+ <!-- Quick-dev completion handler (nano routing).
799
788
  Quick-dev's one-shot (step-oneshot.md:44) already ran Implement →
800
- Review → Classify → Commit internally. Autopilot skips the external
801
- bmad-code-review step and jumps straight to step 7 (mark story
802
- done). Escalation safety net: if tests fail or classify severity is
803
- high, flip implementation_flow to full for the rest of the session. -->
789
+ Review → Classify → Commit internally, so autopilot skips the
790
+ external bmad-code-review step and jumps straight to step 7.
791
+ Escalation safety net: if tests fail or classify severity is high,
792
+ flip implementation_flow to full for the rest of the session. -->
804
793
  <check if="{{completed_skill}} was bmad-quick-dev">
805
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
794
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
806
795
  <action>Verify tests ran — if not, run them now: report `N/N passed`. Record pass/fail into `{{tests_passed}}`.</action>
807
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
796
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
808
797
  <action>Read quick-dev's Classify severity from its stdout/output. If its output mentions `severity: high` or a failing classify, set `{{quickdev_severity_high}}` = true.</action>
809
798
  <check if="{{tests_passed}} is false OR {{quickdev_severity_high}} is true">
810
799
  <action>**Escalation** — flip the session-scoped flow to `full`: set `{{implementation_flow}}` = `full`. Do NOT write this back to config.yaml; it is session-only. Log decision: `category=scope, phase=autopilot:escalation, impact=medium, "nano story {{current_story}} triggered fallback (tests_passed={{tests_passed}}, severity_high={{quickdev_severity_high}}) — subsequent stories use full cycle"` to `{decision_log_file}`.</action>
@@ -814,9 +803,9 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
814
803
  </check>
815
804
 
816
805
  <check if="{{completed_skill}} was bmad-dev-story">
817
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
806
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
818
807
  <action>Verify tests ran — if not, run them now: report `N/N passed`</action>
819
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
808
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
820
809
  <action>**Log decisions** — review implementation choices made during dev-story and append entries to `{decision_log_file}` for any architecture, test-strategy, dependency, scope, or workaround decisions (see DECISION LOGGING section)</action>
821
810
 
822
811
  <!-- GIT: Lint, stage, and commit after dev-story -->
@@ -907,8 +896,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
907
896
  </check>
908
897
 
909
898
  <check if="{{completed_skill}} was bmad-sprint-planning AND {{git_enabled}}">
910
- <action>If `{{has_origin}}` is true, run `git fetch origin` (log a warning on failure and continue do not abort).
911
- If `{{has_origin}}` is false, skip the fetch.</action>
899
+ <action>Run `git fetch origin` (warn + continue on failure; obeys hoisted `{{has_origin}}` rule).</action>
912
900
  <action>Initialize `{git_status_file}` if it doesn't exist (with git_integration block)</action>
913
901
  </check>
914
902
 
@@ -962,8 +950,7 @@ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
962
950
  Log: "next_skill was empty but undone stories remain — resolved to {{next_skill}} for {{current_story}}".
963
951
  </action>
964
952
  <check if="all stories in status_file are done">
965
- <!-- Same finalize-pending handoff as step 2 — never run step 10 in
966
- the session that first noticed sprint-complete. -->
953
+ <!-- FINALIZE_HANDOFF (see top of file). -->
967
954
  <action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
968
955
  <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
969
956
  <action>Report: "Sprint complete detected during skill-recovery. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
@@ -1013,7 +1000,7 @@ For any finding that is DISMISSED (contradicts AC or is a false positive):
1013
1000
 
1014
1001
  <!-- Re-run code review to sync sprint-status.yaml — patches resolved all findings, so code-review will now set story to done -->
1015
1002
  <!-- Single-call timing (mark) — see step-3 comment for rationale. -->
1016
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{current_story}}" --phase "skill.bmad-code-review.rereview" --project-root "{{project_root}}"` — ignore failures.</action>
1003
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{current_story}}" --phase "skill.bmad-code-review.rereview" --project-root "{{project_root}}"`.</action>
1017
1004
  <action>Re-invoke `bmad-code-review` using the Skill tool.
1018
1005
  The review layers already ran — this pass will see zero unresolved findings and set the story status to `done` in sprint-status.yaml (code-review owns that transition per step-04-present.md:92).
1019
1006
  Instruct: "Re-verify code review for story {{current_story}} — all patch findings have been applied. Update story status accordingly."
@@ -1038,11 +1025,11 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1038
1025
  - Final test count: `N/N passed`
1039
1026
  </action>
1040
1027
 
1041
- <!-- PR 5: epic granularity — defer push/PR until the LAST story of the epic.
1042
- For intermediate stories (granularity=epic AND not is_last_story_of_epic):
1043
- the work is already committed to the epic branch locally via stage-and-
1044
- commit.js; no push or PR is attempted. The epic's last story pushes
1045
- the accumulated commits and opens one PR for the whole epic. -->
1028
+ <!-- Epic granularity: defer push/PR until the LAST story of the epic.
1029
+ For intermediate stories (granularity=epic AND not is_last_story_of_epic),
1030
+ the work is already committed to the epic branch locally via
1031
+ stage-and-commit.js; no push or PR is attempted. The epic's last
1032
+ story pushes the accumulated commits and opens one PR for the epic. -->
1046
1033
  <check if="{{granularity}} is epic AND {{is_last_story_of_epic}} is false">
1047
1034
  <action>Log: "Epic granularity: skipping push/PR for {{current_story}} — will push at end of epic {{epic_id}}"</action>
1048
1035
  <action>Set `{{push_status}}` = "deferred", `{{pr_url}}` = "DEFERRED", `{{merge_status}}` = "deferred"</action>
@@ -1089,7 +1076,7 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1089
1076
 
1090
1077
  <check if="{{create_pr}} is false OR {{platform}} is git_only OR {{pr_url}} is null or SKIPPED">
1091
1078
  <action>**Merge story/epic branch to main.** If `{{has_origin}}` is false (local-only), substitute `origin/{{base_branch}}` → `{{base_branch}}` and skip all `git push origin` / `git fetch origin` calls below.
1092
- Choose merge strategy by `{{squash_on_merge}}` (PR 5): if true, use `git merge --squash` + single commit; otherwise standard merge commit.
1079
+ Choose merge strategy by `{{squash_on_merge}}`: if true, use `git merge --squash` + single commit; otherwise standard merge commit.
1093
1080
  1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
1094
1081
  2. If `{{squash_on_merge}}` is true:
1095
1082
  `git merge --squash {{branch_prefix}}{{branch_name}}` then
@@ -1097,9 +1084,9 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1097
1084
  Otherwise: `git merge {{branch_prefix}}{{branch_name}} --no-edit`.
1098
1085
  3. On success: `git push origin {{base_branch}}`, set `{{merge_status}}` = "merged".
1099
1086
  4. On conflict: `git merge --abort`, `git fetch origin`, re-checkout base, retry merge once. On retry success: push + merged. On retry failure: `{{merge_status}}` = "failed", log warning, continue — the branch is preserved and boot reconciliation retries next session.
1100
- 5. **PR 12 cross-epic conflict interlock**: if this merge conflict involved two independent epics AND `{{parallel_epics}}` is true, set `{{cross_epic_disabled_this_session}}` = true and log "EXPERIMENTAL: cross-epic merge conflict detected; disabling parallel_epics for the remainder of this session." The flag resets on next session start.
1087
+ 5. **Cross-epic conflict interlock**: if this merge conflict involved two independent epics AND `{{parallel_epics}}` is true, set `{{cross_epic_disabled_this_session}}` = true and log "EXPERIMENTAL: cross-epic merge conflict detected; disabling parallel_epics for the remainder of this session." The flag resets on next session start.
1101
1088
 
1102
- `{{merge_status}}` is persisted by the sync-status.js call later in this step (via `--merge-status`). Do NOT call sync-status.js here it does full block replacement and would destroy other fields.
1089
+ `{{merge_status}}` is persisted by the sync-status.js call later in this step. Do NOT call sync-status.js here (see SYNC_STATUS_RULE).
1103
1090
  </action>
1104
1091
  <check if="{{cleanup_on_merge}} is true AND {{in_worktree}} is true">
1105
1092
  <action>**Cleanup worktree** (ignore failures — may already be gone): `git worktree remove .worktrees/{{current_story}} --force` then `git worktree prune`</action>
@@ -1129,13 +1116,9 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1129
1116
  <action>**Increment `{{session_stories_done}}` by 1** — this is the ONLY place the counter ticks up. It runs only after the story's full implementation cycle (dev-story GREEN + code-review + patches + artifacts committed + optional push/PR). Creating a story file in step 3 never increments this counter.</action>
1130
1117
  <action>Remove `{{current_story}}` from `{{stories_remaining}}` list</action>
1131
1118
 
1132
- <!-- PR 6 STORY-BOUNDARY FLUSH: if coalescing is on, flush the sprint
1133
- shard's pending buffer now (writes accumulated non-critical fields
1134
- to the shard) and merge all shards into the authoritative
1135
- autopilot-state.yaml / decision-log.yaml. Fast on a single-story
1136
- sprint; amortized when multiple stories complete per session. -->
1119
+ <!-- Story-boundary FLUSH_SHARDS (see top of file). -->
1137
1120
  <check if="{{coalesce_state_writes}} is true">
1138
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"` — ignore failures.</action>
1121
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"`.</action>
1139
1122
  <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story "{{current_story}}" --project-root "{{project_root}}"` — ignore failures (no-op if no per-story shard was ever batched).</action>
1140
1123
  <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"` — ignore failures. Produces merged autopilot-state.yaml + decision-log.yaml.</action>
1141
1124
  </check>
@@ -1148,29 +1131,31 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1148
1131
  <action>Create task "[epic {{epic_id}}] retrospective" → `in_progress`</action>
1149
1132
 
1150
1133
  <!-- Retrospective: driven by `autopilot.retrospective_mode`. The external
1151
- `bmad-retrospective` skill is NEVER invoked from autopilot (multi-persona
1152
- discussion loop under some CLIs). -->
1134
+ `bmad-retrospective` skill is NEVER invoked from autopilot.
1135
+ All three modes append a `autopilot:retrospective` decision-log entry
1136
+ and mark the retrospective task complete. -->
1153
1137
 
1154
1138
  <check if="{{retrospective_mode}} is auto">
1155
1139
  <action>Collect from `{status_file}` for epic `{{epic_id}}`: done stories `{ story-key, title, test_pass_count, patch_count }`, epic title, dates if present.</action>
1156
1140
  <action>Collect decision-log entries for epic `{{epic_id}}` (match `story` prefix `{{epic_id}}-` or `phase: autopilot:*` tagged to this epic). Identify open risks / carry-over notes from any story `notes`/`risks` fields or `workaround` decisions for this epic.</action>
1157
1141
  <action>Ensure `{implementation_artifacts}/retrospectives/` exists. Read template `{{project_root}}/_Sprintpilot/templates/epic-retrospective.md`, fill mustache placeholders, write to `{implementation_artifacts}/retrospectives/epic-{{epic_id}}-retrospective.md`.</action>
1158
1142
  <action>Update `{status_file}`: `epics.{{epic_id}}.status = done`, `.retrospective_path = <file>`, `.completed_at = {current_date}`.</action>
1159
- <action>Append decision-log entry: `{ category: workaround, decision: "retrospective generated inline", rationale: "retrospective_mode=auto", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`.</action>
1160
- <action>Mark retrospective task → `completed`. Set `{{completed_skill}}` = `retrospective-auto`.</action>
1143
+ <action>Append decision-log entry: `{ category: workaround, decision: "retrospective generated inline", rationale: "retrospective_mode=auto", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`. Set `{{completed_skill}}` = `retrospective-auto`.</action>
1161
1144
  </check>
1162
1145
 
1163
1146
  <check if="{{retrospective_mode}} is stop">
1164
1147
  <action>Update `{state_file}`: `paused_at = epic-complete-awaiting-retrospective`, `paused_epic_id = {{epic_id}}`, `next_action = "run /bmad-retrospective interactively for epic {{epic_id}}, then re-run /sprint-autopilot-on"`.</action>
1165
- <action>Append decision-log entry: `{ category: workaround, decision: "paused for interactive retrospective", rationale: "retrospective_mode=stop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`. Mark retrospective task → `completed`.</action>
1148
+ <action>Append decision-log entry: `{ category: workaround, decision: "paused for interactive retrospective", rationale: "retrospective_mode=stop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`.</action>
1166
1149
  <action>Report: "Autopilot paused — epic {{epic_id}} complete, retrospective handed off. Run `/bmad-retrospective` interactively for epic {{epic_id}}, then re-run `/sprint-autopilot-on`. State saved to: {state_file}." Then STOP.</action>
1167
1150
  </check>
1168
1151
 
1169
1152
  <check if="{{retrospective_mode}} is skip">
1170
1153
  <action>Update `{status_file}`: `epics.{{epic_id}}.status = done`, `.retrospective_path = null`, `.retrospective_skipped = true`, `.completed_at = {current_date}`.</action>
1171
- <action>Append decision-log entry: `{ category: workaround, decision: "retrospective skipped", rationale: "retrospective_mode=skip (NOT RECOMMENDED)", impact: medium, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`. Mark retrospective task → `completed`. Set `{{completed_skill}}` = `retrospective-skip`.</action>
1154
+ <action>Append decision-log entry: `{ category: workaround, decision: "retrospective skipped", rationale: "retrospective_mode=skip (NOT RECOMMENDED)", impact: medium, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`. Set `{{completed_skill}}` = `retrospective-skip`.</action>
1172
1155
  </check>
1173
1156
 
1157
+ <action>Mark retrospective task → `completed`.</action>
1158
+
1174
1159
  <check if="{{git_enabled}}">
1175
1160
  <action>**Epic PR summary** — list all epic PR/MR URLs from `{status_file}` and report as "Epic complete — PR/MR summary: [list]. Ready to merge — review PRs and confirm when ready."</action>
1176
1161
  <check if="{{cleanup_on_merge}} is true">
@@ -1220,16 +1205,16 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1220
1205
  `git merge <branch-ref> --no-edit`
1221
1206
  `git push origin {{base_branch}}`
1222
1207
  - If merge succeeds: update merge_status in `{git_status_file}`.
1223
- **IMPORTANT:** sync-status.js does full block replacement — you MUST re-read the story's existing fields from `{git_status_file}` (branch, commit, patch_commits, push_status, pr_url, lint_result, worktree, platform, base_branch, worktree_cleaned) and pass ALL of them along with `--merge-status "merged"`. Omitting fields destroys them.
1224
- - If merge fails: `git merge --abort`, update merge_status to "failed" in `{git_status_file}` (same full-field requirement), log warning, continue
1208
+ See SYNC_STATUS_RULE at top.
1209
+ - If merge fails: `git merge --abort`, update merge_status to "failed" in `{git_status_file}` (see SYNC_STATUS_RULE), log warning, continue
1225
1210
  Log: "Pre-checkpoint merge: N stories verified on {{base_branch}}"
1226
1211
  </action>
1227
1212
  </check>
1228
1213
 
1229
1214
  <action>Update `{state_file}` with STATE_FIELDS.</action>
1230
1215
  <check if="{{coalesce_state_writes}} is true">
1231
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"` — ignore failures.</action>
1232
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"` — ignore failures.</action>
1216
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"`.</action>
1217
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"`.</action>
1233
1218
  </check>
1234
1219
 
1235
1220
  <!-- Phase-timing session snapshot (no-op if autopilot.phase_timings is false). -->
@@ -1273,15 +1258,7 @@ No work will be repeated.
1273
1258
 
1274
1259
  <step n="10" goal="Sprint complete — emit summary and next steps">
1275
1260
 
1276
- <!-- CRITICAL-PATH-FIRST: these 6 actions MUST run before anything else in
1277
- step 10. They put the repo into a correct terminal state — orphan
1278
- worktrees removed, lock released, artifacts committed to main,
1279
- sprint-complete state marked, task checkboxes marked, final verification.
1280
- Even if the rest of step 10 is interrupted (SIGTERM, LLM improvises an
1281
- early summary, context exhaustion), the harness invariants hold. DO NOT
1282
- reorder or skip these — the order matters: worktrees-before-lock so a
1283
- concurrent autopilot session doesn't see orphans, lock-before-commit
1284
- so commits run with the repo already unlocked, etc. -->
1261
+ <!-- CRITICAL 1-7: must run in order before any other step-10 action; do not reorder or skip. -->
1285
1262
 
1286
1263
  <!-- GIT: Exit worktree if still in one (prerequisite for the rest) -->
1287
1264
  <check if="{{in_worktree}}">
@@ -1303,7 +1280,7 @@ No work will be repeated.
1303
1280
  Run: `git worktree list --porcelain` to enumerate. For each worktree whose path starts with `{{project_root}}/.worktrees/`:
1304
1281
  - `git worktree remove <path> --force 2>&1` (ignore failures — best-effort).
1305
1282
  Then once: `git worktree prune 2>&1` (ignore failures).
1306
- This is the SINGLE authoritative cleanup site for autopilot-owned worktrees do not wait for a later "safety net" cleanup that might not run.
1283
+ Final-pass cleanup: idempotent re-run that catches anything missed by per-story/epic-complete cleanup above.
1307
1284
  </check>
1308
1285
  </action>
1309
1286
 
@@ -1329,27 +1306,22 @@ Run: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore fai
1329
1306
  Parse stdout as JSON. If `.state` is `sprint-complete` AND `.remaining` is empty, the sprint is verified complete. If `.state` is anything else (e.g. `sprint-in-progress` with leftover stories), log a warning — but do NOT revert the above actions. The worktrees are gone; the lock is released; the artifacts are committed; the state is marked complete; task checkboxes are marked. Manual recovery only.
1330
1307
  </action>
1331
1308
 
1332
- <!-- Final terminal-state marker. Kept inside the CRITICAL block because
1333
- the previous "Delete state_file" action at the tail of step 10 was
1334
- being skipped by context-rot in long sessions (observed in e2e runs:
1335
- "autopilot-state.yaml still exists" warning), leaving a stale state
1336
- file that confused the next /sprint-autopilot-on boot. CRITICAL 5/7
1337
- already wrote sprint-complete as a belt-and-suspenders in case this
1338
- delete crashes mid-run; step 1 also short-circuits on a leftover
1339
- sprint-complete state so accidental re-invocation doesn't loop. -->
1309
+ <!-- See AUTOPILOT RULES (context-rot mitigation). CRITICAL 5/7 wrote
1310
+ sprint-complete as a crash-safe marker; step 1 short-circuits on a
1311
+ leftover sprint-complete state so accidental re-invocation doesn't loop. -->
1340
1312
  <action>**[CRITICAL 7/7] Delete state_file** — `node -e "require('node:fs').rmSync('{state_file}', { force: true })"` (idempotent; never halt on failure). Removes `autopilot-state.yaml` so the next invocation starts cleanly. CRITICAL 5/7 already wrote `current_bmad_step = sprint-complete` to the same file as a crash-safe marker, so if this delete fails mid-run the next session still sees the terminal state.</action>
1341
1313
 
1342
1314
  <!-- Close the last open `mark` phase so its duration gets recorded.
1343
1315
  Without this, the very last skill's duration would be lost (no
1344
1316
  subsequent mark fires after sprint-complete). -->
1345
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase _end --project-root "{{project_root}}"` — ignore failures.</action>
1317
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase _end --project-root "{{project_root}}"`.</action>
1346
1318
 
1347
1319
  <action>Run full test suite — report `N/N passed`</action>
1348
1320
 
1349
- <!-- PR 6 SPRINT-COMPLETE FLUSH + ARCHIVE (no-op if coalescing is off). -->
1321
+ <!-- Sprint-complete FLUSH_SHARDS + archive (no-op if coalescing is off). -->
1350
1322
  <check if="{{coalesce_state_writes}} is true">
1351
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"` — ignore failures.</action>
1352
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — ignore failures. --archive moves merged shards to .archive/layer-&lt;timestamp&gt;/ (the script generates the ISO timestamp itself; no shell `$(...)` substitution needed) so the next sprint starts clean.</action>
1323
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"`.</action>
1324
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — ignore failures. `--archive` moves merged shards to `.archive/layer-<timestamp>/` so the next sprint starts clean.</action>
1353
1325
  </check>
1354
1326
 
1355
1327
  <!-- Final phase-timing hotspot report (no-op if autopilot.phase_timings is false). -->
@@ -1398,11 +1370,7 @@ Parse stdout as JSON. If `.state` is `sprint-complete` AND `.remaining` is empty
1398
1370
  <action>**Collect report data** from `{status_file}` (stories grouped by epic with titles, totals, final test count; PR/MR URLs, patch/dismissed counts per story if git_enabled) and `{decision_log_file}` (medium/high-impact decisions; counts of `review-accept`, `review-triage`, code-review rounds; per-story patches-applied / findings-dismissed).</action>
1399
1371
 
1400
1372
  <check if="{{git_enabled}}">
1401
- <!-- Worktree cleanup already ran as CRITICAL 2/7 above. Intentionally
1402
- no duplicate here — relying on early cleanup to avoid orphans
1403
- when the LLM short-circuits late actions. -->
1404
- <action>(No-op: worktree cleanup already executed in CRITICAL 2/7.)</action>
1405
- <!-- PR 10: restore main-repo gc.auto to its prior value. -->
1373
+ <!-- Worktree cleanup ran as CRITICAL 2/7. Restore main-repo gc.auto. -->
1406
1374
  <action>**Restore main-repo gc.auto**:
1407
1375
  if `{{original_gc_auto_main}}` is "unset": `git config --local --unset gc.auto` (ignore failure — may already be unset).
1408
1376
  else: `git config --local gc.auto {{original_gc_auto_main}}`.