@ikunin/sprintpilot 1.0.4 → 2.0.4

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.
Files changed (37) hide show
  1. package/_Sprintpilot/Sprintpilot.md +14 -1
  2. package/_Sprintpilot/manifest.yaml +1 -1
  3. package/_Sprintpilot/modules/autopilot/config.yaml +22 -0
  4. package/_Sprintpilot/modules/autopilot/profiles/_base.yaml +45 -0
  5. package/_Sprintpilot/modules/autopilot/profiles/large.yaml +22 -0
  6. package/_Sprintpilot/modules/autopilot/profiles/legacy.yaml +35 -0
  7. package/_Sprintpilot/modules/autopilot/profiles/medium.yaml +5 -0
  8. package/_Sprintpilot/modules/autopilot/profiles/nano.yaml +35 -0
  9. package/_Sprintpilot/modules/autopilot/profiles/small.yaml +5 -0
  10. package/_Sprintpilot/modules/git/config.yaml +8 -0
  11. package/_Sprintpilot/modules/ma/config.yaml +42 -0
  12. package/_Sprintpilot/scripts/agent-adapter.js +247 -0
  13. package/_Sprintpilot/scripts/cached-read.js +238 -0
  14. package/_Sprintpilot/scripts/check-prereqs.js +139 -0
  15. package/_Sprintpilot/scripts/dispatch-layer.js +192 -0
  16. package/_Sprintpilot/scripts/git-portable.js +219 -0
  17. package/_Sprintpilot/scripts/infer-dependencies.js +594 -0
  18. package/_Sprintpilot/scripts/inject-tasks-section.js +279 -0
  19. package/_Sprintpilot/scripts/list-remaining-stories.js +295 -0
  20. package/_Sprintpilot/scripts/log-timing.js +360 -0
  21. package/_Sprintpilot/scripts/mark-done-stories-tasks.js +254 -0
  22. package/_Sprintpilot/scripts/merge-shards.js +339 -0
  23. package/_Sprintpilot/scripts/preflight-merge.js +235 -0
  24. package/_Sprintpilot/scripts/resolve-dag.js +559 -0
  25. package/_Sprintpilot/scripts/resolve-profile.js +355 -0
  26. package/_Sprintpilot/scripts/state-shard.js +602 -0
  27. package/_Sprintpilot/scripts/submodule-lock.js +130 -0
  28. package/_Sprintpilot/scripts/summarize-timings.js +362 -0
  29. package/_Sprintpilot/scripts/sync-status.js +13 -0
  30. package/_Sprintpilot/scripts/with-retry.js +145 -0
  31. package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +673 -540
  32. package/_Sprintpilot/skills/sprintpilot-update/workflow.md +2 -1
  33. package/_Sprintpilot/templates/epic-retrospective.md +24 -0
  34. package/_Sprintpilot/templates/sprint-report.txt +60 -0
  35. package/bin/sprintpilot.js +4 -0
  36. package/lib/commands/install.js +157 -1
  37. package/package.json +1 -1
@@ -8,23 +8,35 @@ You do NOT hardcode the workflow sequence. After each completed skill, read its
8
8
 
9
9
  **Git integration** is additive. If `_Sprintpilot/manifest.yaml` doesn't exist or `git.enabled: false`, all git operations are silently skipped and this workflow behaves identically to the stock autopilot.
10
10
 
11
- ### Shell portability (IMPORTANT)
11
+ ### Shell portability
12
12
 
13
- Sprintpilot runs under any LLM CLI (Claude Code, Gemini CLI, Cursor, etc.) on any OS. The shell that executes commands may be **bash, zsh, PowerShell, or cmd** depending on platform and CLI. Shell-specific idioms will fail silently when the wrong shell is used.
13
+ The executing shell may be bash, zsh, Git Bash (Claude Code's default on Windows), PowerShell, or cmd. The workflow itself uses dedicated Node helpers for the operations that were most fragile across shells the few remaining shell idioms below are the ones that work uniformly under bash, zsh, Git Bash, PowerShell, and cmd:
14
14
 
15
- **When you encounter bash-style idioms below, translate them to your shell.** The table applies to **external commands** (like `git`); cmdlets have slightly different conventions.
16
-
17
- | Bash idiom | PowerShell equivalent | Meaning |
15
+ | Idiom | Where used | Portable across |
18
16
  |---|---|---|
19
- | `A && B` | `A; if ($LASTEXITCODE -eq 0) { B }` (or separate commands, guarding B manually) | Run B only if A succeeded |
20
- | `A \|\| true` | `A; $LASTEXITCODE = 0` (or `try { A } catch {}` for cmdlets) | Run A, ignore failures |
21
- | `2>/dev/null` | `2>$null` | Suppress stderr |
22
- | `rm -rf <dir>` | `Remove-Item -Recurse -Force <dir>` | Recursive delete |
23
- | `if [ -f X ]; then ... fi` | `if (Test-Path -PathType Leaf X) { ... }` | File-exists check (regular file, not dir) |
17
+ | `2>&1` (merge stderr stdout) | `git ... 2>&1` | bash, zsh, Git Bash, PowerShell, cmd |
18
+ | `\|\|` (run-on-failure chaining) | `git switch X \|\| git checkout X` | bash, zsh, Git Bash, PowerShell ≥7, cmd |
19
+
20
+ Idioms that were **previously inlined and have been replaced with Node helpers** (do NOT regress them):
21
+
22
+ | Old idiom | Replaced by |
23
+ |---|---|
24
+ | `git config --get K 2>/dev/null \|\| echo unset` | `git-portable.js config-get K --default unset` |
25
+ | `git worktree list --porcelain \| grep -c '^worktree '` | `git-portable.js count-worktrees` |
26
+ | `VAR=$(git ... rev-parse --git-common-dir)` | `git-portable.js common-dir` (output captured into `{{git_common}}`) |
27
+ | `git add A B C 2>/dev/null \|\| true` | `git-portable.js safe-add A B C` |
28
+
29
+ If you find yourself writing a new pipe (`\|`), POSIX redirect (`2>/dev/null`), shell substitution (`$(...)`), shell variable assignment (`VAR=value`), or POSIX-only tool (`grep`, `sed`, `awk`, `xargs`, `find -exec`), reach for a Node helper instead — either an existing script under `_Sprintpilot/scripts/`, a new helper added to `git-portable.js`, or an inline `node -e "..."` snippet (which is portable across every host).
24
30
 
25
- **Safer:** when in doubt, use the cross-platform Node helpers under `_Sprintpilot/scripts/`. For ad-hoc file ops, invoke Node inline: `node -e "require('fs').rmSync('<path>', {recursive: true, force: true})"`.
31
+ Common cross-platform inline snippets:
26
32
 
27
- If a step below uses `&&` to chain "run B only on A's success", and you cannot express that in one line, **run the commands separately and STOP if any step fails** — do not proceed past a failed step.
33
+ | Need | Inline Node snippet |
34
+ |---|---|
35
+ | Recursive remove | `node -e "require('fs').rmSync('<path>', {recursive: true, force: true})"` |
36
+ | File-exists check (exit 0/1) | `node -e "process.exit(require('fs').existsSync('<path>')?0:1)"` |
37
+ | Read JSON, extract field | `node -e "console.log(JSON.parse(require('fs').readFileSync('<file>','utf8')).<field>)"` |
38
+
39
+ When a step chains commands with `&&` and the chain cannot be expressed via a Node helper, run them separately and STOP on any failure.
28
40
 
29
41
  ---
30
42
 
@@ -42,6 +54,7 @@ Long autopilot runs will fill the context window. To prevent state loss:
42
54
  - **All state lives in files, never only in memory.** After every step, write progress to `{state_file}`.
43
55
  - **Story boundary = session boundary.** Never split a single story across sessions. Always finish the current story fully (dev → review → patches → done) before ending a session.
44
56
  - **Proactive session handoff.** After `{{session_story_limit}}` stories have been **fully implemented** in one session (default: 3) — meaning their complete cycle is finished (dev-story GREEN + code-review + patches + artifacts committed) — write state and tell user to start a new session with `/sprint-autopilot-on`. Configurable via `autopilot.session_story_limit` in `modules/autopilot/config.yaml`. Creating a story file does NOT count toward the limit — only finishing step 7 does. Do not wait for compaction to happen.
57
+ - **Sprint-complete checkpoint (MANDATORY).** The moment step 2 detects `sprint_is_complete = true`, the current session NEVER runs step 10 itself. It writes `current_bmad_step = "sprint-finalize-pending"` to `{state_file}`, releases the lock, emits a handoff report, and STOPS. The next `/sprint-autopilot-on` invocation boots in a fresh context, sees the pending state, and runs step 10. This is non-negotiable because step 10 is the context-rot failure zone — its CRITICAL deterministic script calls were reliably skipped when packed into the tail of a long session.
45
58
  - **On startup: check for saved state first.** If `{state_file}` exists, this is a RESUME — read it and skip to the saved story/step. Never re-do completed work.
46
59
 
47
60
  ### Menu and interaction handling — CRITICAL
@@ -75,55 +88,20 @@ For everything else: decide, document briefly, continue.
75
88
 
76
89
  ## DECISION LOGGING
77
90
 
78
- Every non-trivial decision made during autopilot execution MUST be logged to `{decision_log_file}`. This creates an audit trail the user reviews at the end of each session.
79
-
80
- ### When to log
81
-
82
- Log a decision whenever you:
83
- - Choose an architecture pattern, data structure, or design approach (`architecture`)
84
- - Select a test strategy or skip a test category (`test-strategy`)
85
- - Add, remove, or substitute a dependency (`dependency`)
86
- - Dismiss a code review finding (`review-triage`)
87
- - Accept and apply a code review finding (`review-accept`)
88
- - Recover from a HALT condition (`halt-recovery`)
89
- - Implement something not explicitly in the story spec (`scope`)
90
- - Apply a workaround for a tool limitation or false positive (`workaround`)
91
-
92
- Do NOT log routine actions (running tests, staging files, creating branches).
93
-
94
- ### File format
95
-
96
- Initialize `{decision_log_file}` on first decision (if it does not exist):
97
-
98
- ```yaml
99
- generated: {current_date}
100
- last_updated: {current_datetime}
101
-
102
- decisions: []
103
- ```
91
+ Log every non-trivial decision to `{decision_log_file}` (skip routine actions running tests, staging files, creating branches). Create the file on first decision; update `last_updated` on every append.
104
92
 
105
- Append each decision as a new entry:
93
+ **Categories:** `architecture`, `test-strategy`, `dependency`, `review-triage` (dismissed finding), `review-accept` (applied fix), `halt-recovery`, `scope` (outside story spec), `workaround`.
94
+ **Impact:** `low` (reversible/cosmetic), `medium` (affects one component), `high` (cross-cutting or deviates from spec).
95
+ **Phase format:** `{skill}:{sub_phase}` — e.g. `dev-story:RED`, `code-review:triage`, `autopilot:routing`.
106
96
 
97
+ **File schema:**
107
98
  ```yaml
108
- - id: {auto_increment}
109
- timestamp: "{current_datetime_iso8601}"
110
- story: "{current_story or sprint-level}"
111
- phase: "{skill}:{sub_phase}"
112
- category: {architecture|test-strategy|dependency|review-triage|review-accept|halt-recovery|scope|workaround}
113
- decision: "{what was decided — one line}"
114
- rationale: "{why — one line}"
115
- impact: {low|medium|high}
99
+ generated: {date}
100
+ last_updated: {datetime}
101
+ decisions:
102
+ - { id, timestamp, story, phase, category, decision, rationale, impact }
116
103
  ```
117
104
 
118
- **Phase format:** `dev-story:RED`, `dev-story:GREEN`, `code-review:triage`, `code-review:patch`, `autopilot:init`, `autopilot:routing`, etc.
119
-
120
- **Impact levels:**
121
- - `low` — easily reversible, cosmetic, or standard practice
122
- - `medium` — affects behavior but contained to one story/component
123
- - `high` — cross-cutting, hard to reverse, or deviates from spec
124
-
125
- Always update `last_updated` when appending.
126
-
127
105
  ---
128
106
 
129
107
  ## SKILL AUTOMATABLE REFERENCE
@@ -156,6 +134,19 @@ Resolve:
156
134
  - `project_root` = absolute path of current working directory (store for later use)
157
135
  - `session_story_limit` is loaded below from `modules/autopilot/config.yaml` (default: 3)
158
136
 
137
+ **`{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
+
139
+ **PR 6 state-write policy (`autopilot.coalesce_state_writes`):**
140
+
141
+ 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
+
143
+ - **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
+ - **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
+ - **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
+
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.
149
+
159
150
  ### Git integration bootstrap
160
151
 
161
152
  <action>Check if `{project-root}/_Sprintpilot/manifest.yaml` exists</action>
@@ -171,18 +162,57 @@ Resolve:
171
162
  - `{{create_pr}}` from `git.push.create_pr` (true)
172
163
  - `{{pr_template}}` from `git.push.pr_template` ("modules/git/templates/pr-body.md")
173
164
  - `{{cleanup_on_merge}}` from `git.worktree.cleanup_on_merge` (true)
165
+ - `{{granularity}}` from `git.granularity` ("story"). Resolver override wins below.
166
+ - `{{worktree_enabled}}` from `git.worktree.enabled` (true). Resolver override wins below.
167
+ - `{{squash_on_merge}}` from `git.squash_on_merge` (false). Resolver override wins below.
168
+ </action>
169
+ <action>**Apply profile overrides** via resolver — run each and set only if the resolver returns a value:
170
+ - `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.granularity` → override `{{granularity}}`.
171
+ - `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.worktree.enabled` → override `{{worktree_enabled}}`.
172
+ - `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.squash_on_merge` → override `{{squash_on_merge}}`.
174
173
  </action>
175
174
  <action>Read `{project-root}/_Sprintpilot/modules/autopilot/config.yaml` (if present) and set:
176
175
  - `{{session_story_limit}}` from `autopilot.session_story_limit` (default: 3). A value of 0 disables the limit (run until sprint complete).
177
176
  - `{{retrospective_mode}}` from `autopilot.retrospective_mode` (default: `auto`). Valid values: `auto` | `stop` | `skip`. Any unknown value falls back to `auto`.
178
177
  If the file or either key is missing, fall back to the defaults above.
179
178
  </action>
179
+ <action>**Resolve profile-driven flow** — run:
180
+ `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.
182
+ 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
+ </action>
184
+ <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.
186
+ </action>
187
+ <!-- PR 11: detect the running host and resolve parallel-dispatch config. -->
188
+ <action>**Detect host agent** — run:
189
+ `node {{project_root}}/_Sprintpilot/scripts/agent-adapter.js detect --project-root "{{project_root}}"`
190
+ Parse the JSON output: set `{{host_agent}}` = host, `{{host_supports_parallel}}` = supports_parallel, `{{host_confidence}}` = confidence.
191
+ </action>
192
+ <action>**Resolve parallelism flags** via the profile resolver:
193
+ - `{{parallel_stories}}` from `ma.parallel_stories` (default false).
194
+ - `{{max_parallel_stories}}` from `ma.max_parallel_stories` (default 2).
195
+ - `{{experimental_parallel_on_gemini}}` from `ma.experimental_parallel_on_gemini` (default false).
196
+ </action>
197
+ <!-- Gemini CLI opt-in: when the user explicitly sets
198
+ experimental_parallel_on_gemini=true AND the detected host is
199
+ gemini-cli at HIGH confidence, promote supports_parallel=true
200
+ with a one-line warning. Worktree-scoped subagents aren't shipped
201
+ upstream yet, so this is user-opt-in-per-project. -->
202
+ <check if="{{experimental_parallel_on_gemini}} is true AND {{host_agent}} is gemini-cli AND {{host_confidence}} is high">
203
+ <action>Set `{{host_supports_parallel}}` = true</action>
204
+ <action>Log once: "EXPERIMENTAL: parallel_stories enabled on Gemini CLI via ma.experimental_parallel_on_gemini=true. Worktree-scoped subagents are not yet shipped upstream (gemini-cli#22967); expect possible serialization or quota throttling."</action>
205
+ </check>
206
+ <action>Silently coerce `{{parallel_stories}}` to false when `{{host_supports_parallel}}` is false OR `{{host_confidence}}` is not `high`. Log once:
207
+ `parallel_stories requested but host '{{host_agent}}' does not declare parallel support (confidence={{host_confidence}}); running sequentially`.
208
+ </action>
180
209
  </check>
181
210
 
182
211
  <check if="manifest does NOT exist">
183
212
  <action>Set `{{git_enabled}}` = false</action>
184
213
  <action>Set `{{session_story_limit}}` = 3</action>
185
214
  <action>Set `{{retrospective_mode}}` = `auto`</action>
215
+ <action>Set `{{implementation_flow}}` = `full`</action>
186
216
  <action>Log: "No _Sprintpilot/manifest.yaml found — running stock autopilot (no git)"</action>
187
217
  </check>
188
218
 
@@ -200,6 +230,22 @@ Resolve:
200
230
  <action>STOP</action>
201
231
  </check>
202
232
 
233
+ <action>**Check for `origin` remote** — run: `git remote get-url origin`
234
+ If the command fails (exit code != 0), no `origin` remote is configured. Set `{{has_origin}}` = false.
235
+ Otherwise set `{{has_origin}}` = true.
236
+ </action>
237
+ <check if="{{has_origin}} is false">
238
+ <action>Log: "WARN: no `origin` remote configured — running in local-only mode. Remote operations (fetch, push, PR, branch reconciliation) will be skipped. Add a remote later with: `git remote add origin <url>`"</action>
239
+ <action>Set `{{push_auto}}` = false</action>
240
+ <action>Set `{{create_pr}}` = false</action>
241
+ <action>Set `{{platform}}` = "git_only"</action>
242
+ </check>
243
+
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). -->
247
+ <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
+
203
249
  <action>**Lock file** — run: `node {{project_root}}/_Sprintpilot/scripts/lock.js acquire`
204
250
  Output will be one of:
205
251
  - `ACQUIRED:<session-id>` → proceed
@@ -217,6 +263,26 @@ Resolve:
217
263
  Log: "Platform detected: {{platform}}"
218
264
  </action>
219
265
 
266
+ <!-- PR 7 CONDITIONAL BOOT WORK: a clean repo — main worktree only, zero
267
+ in-progress stories — can skip the slow health-check + branch
268
+ reconciliation below. Gate honored by non-legacy, non-large profiles
269
+ (large keeps full reconciliation for compliance/uptime reasons). -->
270
+ <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.
272
+ </action>
273
+ <action>Count worktrees (cross-platform; replaces a `... | grep -c` pipe that needed POSIX shell):
274
+ `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.
275
+ </action>
276
+ <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
+
278
+ <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>
281
+ <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
282
+ </check>
283
+
284
+ <check if="NOT ({{conditional_boot_work}} is true AND {{worktree_count}} is 1 AND {{in_progress_count}} is 0)">
285
+
220
286
  <action>**Worktree health check** — run:
221
287
  `node {{project_root}}/_Sprintpilot/scripts/health-check.js --base-branch {{base_branch}} --status-file {{status_file}}`
222
288
  Output classifies each worktree as CLEAN_DONE, COMMITTED, STALE, DIRTY, or ORPHAN.
@@ -235,7 +301,8 @@ Resolve:
235
301
  </action>
236
302
 
237
303
  <action>**Branch reconciliation** — detect pushed-but-unmerged story branches.
238
- Run as separate commands — **if `git fetch origin` fails (network/auth), STOP branch reconciliation and log a warning; do not operate on stale local refs**:
304
+ Skip this entire section if `{{has_origin}}` is false (no remote nothing to reconcile).
305
+ Run as separate commands — **if `git fetch origin` fails (no remote/network/auth), STOP branch reconciliation and log a warning; do not operate on stale local refs**:
239
306
  1. `git fetch origin`
240
307
  2. `git branch -r --list "origin/{{branch_prefix}}*"`
241
308
  For each remote branch:
@@ -267,6 +334,8 @@ Resolve:
267
334
  </action>
268
335
 
269
336
  <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
337
+
338
+ </check><!-- end PR 7 conditional boot work (non-fast-path branch) -->
270
339
  </check>
271
340
 
272
341
  ---
@@ -281,7 +350,7 @@ Resolve:
281
350
  <action>Read `{state_file}` fully</action>
282
351
  <action>Extract saved state:
283
352
  - `{{current_story}}` — story in progress when last session ended
284
- - `{{current_bmad_step}}` — BMAD step that was active (2–7)
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"
285
354
  - `{{completed_skill}}` — last skill that ran
286
355
  - `{{next_skill}}` — next skill recommended at save time
287
356
  - `{{session_stories_done}}` = 0 (reset counter for new session)
@@ -319,30 +388,40 @@ Resolve:
319
388
  - Update `{state_file}` with reconciled values
320
389
  </action>
321
390
 
322
- <!-- Resume from a `retrospective_mode: stop` pause.
323
- The user was told to run /bmad-retrospective interactively. If they
324
- did, the epic is now `done` in {status_file} (or a retrospective
325
- artifact exists). Otherwise, re-issue the instructions and halt. -->
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. -->
395
+ <check if="{{current_bmad_step}} is sprint-complete">
396
+ <action>Delete `{state_file}` — clean up the stale terminal marker.</action>
397
+ <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
+ </check>
399
+
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. -->
405
+ <check if="{{current_bmad_step}} is sprint-finalize-pending">
406
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope` — parse stdout JSON.</action>
407
+ <check if="parsed .state is sprint-complete">
408
+ <action>Log: "Resuming for sprint finalization with fresh context (context-rot mitigation)."</action>
409
+ <goto step="10">Run finalization</goto>
410
+ </check>
411
+ <check if="parsed .state is NOT sprint-complete">
412
+ <action>Log: "sprint-finalize-pending flag was set but new work appeared since checkpoint. Clearing flag and resuming normal execution."</action>
413
+ <action>Update `{state_file}`: `current_bmad_step = null`.</action>
414
+ </check>
415
+ </check>
416
+
417
+ <!-- Resume from a `retrospective_mode: stop` pause. -->
326
418
  <check if="{state_file}.paused_at is epic-complete-awaiting-retrospective">
327
- <action>Set `{{paused_epic_id}}` from `{state_file}.paused_epic_id`</action>
328
- <action>Check whether epic `{{paused_epic_id}}` is now `done` in `{status_file}` OR an artifact exists at `{implementation_artifacts}/retrospectives/epic-{{paused_epic_id}}-*.md`</action>
419
+ <action>Set `{{paused_epic_id}}` from `{state_file}.paused_epic_id`. Check if epic `{{paused_epic_id}}` is `done` in `{status_file}` OR an artifact exists at `{implementation_artifacts}/retrospectives/epic-{{paused_epic_id}}-*.md`.</action>
329
420
  <check if="epic is done OR retrospective artifact exists">
330
- <action>Clear `paused_at`, `paused_epic_id`, and `next_action` from `{state_file}`</action>
331
- <action>Log: "Epic {{paused_epic_id}} retrospective detected — resuming autopilot"</action>
421
+ <action>Clear `paused_at`, `paused_epic_id`, `next_action` from `{state_file}`. Log: "Epic {{paused_epic_id}} retrospective detected — resuming autopilot".</action>
332
422
  </check>
333
423
  <check if="epic is NOT done AND no retrospective artifact">
334
- <action>Report:
335
- ```
336
- Autopilot still paused — epic {{paused_epic_id}} retrospective not yet done.
337
-
338
- Run `/bmad-retrospective` interactively for epic {{paused_epic_id}},
339
- then re-run `/sprint-autopilot-on` to continue.
340
-
341
- (To bypass, edit _Sprintpilot/modules/autopilot/config.yaml and set
342
- retrospective_mode to `auto` or `skip`.)
343
- ```
344
- </action>
345
- <action>STOP</action>
424
+ <action>Report: "Autopilot still paused — epic {{paused_epic_id}} retrospective not yet done. Run `/bmad-retrospective` interactively, then re-run `/sprint-autopilot-on`. (To bypass: set `retrospective_mode` to `auto` or `skip` in `_Sprintpilot/modules/autopilot/config.yaml`.)" Then STOP.</action>
346
425
  </check>
347
426
  </check>
348
427
 
@@ -350,22 +429,11 @@ Resolve:
350
429
  </check>
351
430
 
352
431
  <check if="state_file does NOT exist">
353
- <action>Check if `{status_file}` exists if not, invoke `bmad-sprint-planning` first</action>
354
-
355
- <check if="{{git_enabled}} AND status_file did not exist (sprint-planning just ran)">
356
- <action>Run `git fetch origin` to ensure remote refs are current</action>
357
- <action>Initialize `{git_status_file}` with git_integration block:
358
- ```yaml
359
- # Sprintpilot — Git Status
360
- git_integration:
361
- enabled: true
362
- base_branch: {git.base_branch from config}
363
- platform: {{platform}}
364
-
365
- stories:
366
- ```
367
- Note: this is the addon's own file — NEVER write git fields to sprint-status.yaml.
368
- </action>
432
+ <action>Check if `{status_file}` exists. If NOT, do NOT jump to `bmad-sprint-planning` (Phase 4 skill, requires Phase 1–3 artifacts). Invoke `bmad-help` — "No sprint-status.yaml found. What is the current phase and which skill should run first?" — and set `{{next_skill}}` from its response. Expected routing: no PRD → `bmad-create-prd` (BLOCKER); PRD → `bmad-create-architecture`; architecture → `bmad-create-epics-and-stories`; epics → `bmad-sprint-planning`.</action>
433
+
434
+ <check if="{{git_enabled}} AND status_file did not exist AND {{next_skill}} is bmad-sprint-planning (planning just completed earlier in this flow)">
435
+ <action>Run `git fetch origin` warn + skip on failure (no remote/auth/network), do not abort bootstrap.</action>
436
+ <action>Initialize `{git_status_file}` (addon-owned — NEVER write git fields to sprint-status.yaml) with: `git_integration: { enabled: true, base_branch: <from config>, platform: {{platform}} }` and empty `stories:`.</action>
369
437
  </check>
370
438
 
371
439
  <action>Read `{status_file}` — find all stories not yet `done`</action>
@@ -376,21 +444,14 @@ Resolve:
376
444
  - `{{session_stories_done}}` = 0
377
445
  </action>
378
446
  <action>Create master task: "Sprintpilot — Full Sprint Execution" → `in_progress`</action>
379
- <action>Write initial `{state_file}`:
380
- ```yaml
381
- last_updated: {current_datetime}
382
- current_story: null
383
- current_bmad_step: null
384
- completed_skill: bmad-help
385
- next_skill: {{next_skill}}
386
- session_stories_done: 0
387
- stories_remaining: [list from sprint-status]
388
- git_enabled: {{git_enabled}}
389
- platform: {{platform}}
390
- in_worktree: false
391
- pr_base: {{base_branch}}
392
- ```
447
+ <action>**Compute `{{stories_remaining}}`** deterministically — run:
448
+ `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
449
+ The helper always emits a well-formed JSON object on stdout, on every exit path:
450
+ `{"remaining":[...],"state":"pre-planning|sprint-in-progress|sprint-complete|parse-error"}`.
451
+ Parse stdout as JSON → set `{{stories_remaining}}` = `.remaining`.
452
+ Ignore the exit code: `.state` is authoritative and covers every case (missing file, parse error, or normal).
393
453
  </action>
454
+ <action>Write initial `{state_file}` with STATE_FIELDS: `current_story = null`, `current_bmad_step = null`, `completed_skill = bmad-help`, `session_stories_done = 0`, `stories_remaining = {{stories_remaining}}`, `in_worktree = false`, `pr_base = {{base_branch}}`.</action>
394
455
  <action>Report to user:
395
456
  ```
396
457
  Sprintpilot ON
@@ -412,17 +473,89 @@ Resolve:
412
473
 
413
474
  <step n="2" goal="Main execution loop — route to correct handler">
414
475
 
415
- <check if="all stories in status_file are done">
416
- <goto step="10">Sprint complete</goto>
476
+ <!-- PR 12 CROSS-EPIC PARALLELISM (experimental, off by default on every
477
+ profile including `large`). All safety rails must pass:
478
+ 1. ma.parallel_epics is true.
479
+ 2. Host confidence is HIGH AND supports_parallel is true (same as
480
+ intra-epic gate in PR 11).
481
+ 3. Two or more epics in dependencies.yaml declare `independent: true`.
482
+ 4. preflight-merge.js reports NO conflicts between all pairs.
483
+ 5. Session-scoped disable flag {{cross_epic_disabled_this_session}}
484
+ is false (flips true after any cross-epic merge conflict).
485
+ Only on the first iteration of the loop — subsequent iterations
486
+ don't re-preflight; they consume the cached safe_pairs list. -->
487
+ <action>Resolve `{{parallel_epics}}` from `ma.parallel_epics` (default false) via the resolver.</action>
488
+ <check if="{{parallel_epics}} is true AND {{host_supports_parallel}} is true AND {{host_confidence}} is high AND {{cross_epic_preflight_done}} is not true AND {{cross_epic_disabled_this_session}} is not true">
489
+ <action>Read `_Sprintpilot/sprints/dependencies.yaml` (if present). Extract every epic id where `epics.<id>.independent` is true. Set `{{independent_epic_ids}}` = comma-joined list of ids.</action>
490
+ <check if="{{independent_epic_ids}} has fewer than 2 ids">
491
+ <action>Log once: "cross-epic parallelism enabled but fewer than 2 epics declare `independent: true` in dependencies.yaml — running sequentially"</action>
492
+ <action>Set `{{cross_epic_preflight_done}}` = true, `{{cross_epic_safe_pairs}}` = []</action>
493
+ </check>
494
+ <check if="{{independent_epic_ids}} has 2 or more ids">
495
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/preflight-merge.js --epics "{{independent_epic_ids}}" --base "{{base_branch}}" --branch-prefix "{{branch_prefix}}" --project-root "{{project_root}}"`. Parse JSON — set `{{cross_epic_safe_pairs}}` = safe_pairs, `{{cross_epic_conflict_pairs}}` = conflict_pairs.</action>
496
+ <action>Log: "EXPERIMENTAL: parallel_epics preflight → safe={{cross_epic_safe_pairs.length}} conflict={{cross_epic_conflict_pairs.length}} checked=N"</action>
497
+ <action>Set `{{cross_epic_preflight_done}}` = true</action>
498
+ </check>
499
+ </check>
500
+
501
+ <!-- Authoritative "sprint complete" check. Read the status file EVERY
502
+ iteration via the coded helper (do not rely on stale state or LLM
503
+ re-parsing). The helper emits a JSON envelope on stdout on every
504
+ exit path, so there is no need to probe $? or stderr — .state is
505
+ authoritative. -->
506
+ <action>**Recalculate `{{stories_remaining}}`** — run:
507
+ `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
508
+ Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
509
+ - `.state == "sprint-in-progress"` → `{{stories_remaining}}` = `.remaining`, `{{sprint_has_stories}}` = true, `{{sprint_is_complete}}` = false.
510
+ - `.state == "sprint-complete"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = true, `{{sprint_is_complete}}` = true.
511
+ - `.state == "pre-planning"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = false, `{{sprint_is_complete}}` = false.
512
+ - `.state == "parse-error"` → log warning; treat as pre-planning. NEVER declare sprint-complete on a parse failure.
513
+ </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. -->
523
+ <check if="{{sprint_is_complete}} is true">
524
+ <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
+ <action>Release autopilot lock (idempotent): `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
526
+ <action>Report to user and STOP this session:
527
+ ```
528
+ Autopilot sprint-complete checkpoint
529
+
530
+ 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).
534
+
535
+ Completed {{session_stories_done}} stories this session.
536
+ State saved to: {state_file}
537
+
538
+ Run `/sprint-autopilot-on` once more to finalize.
539
+ ```
540
+ </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>
542
+ </check>
543
+ <check if="{{sprint_has_stories}} is false">
544
+ <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>
417
545
  </check>
418
546
 
419
547
  <check if="{{next_skill}} is empty">
420
548
  <action>**Recover next_skill** — re-read `{status_file}`, find first story with status != "done"</action>
421
- <check if="no undone stories found">
422
- <goto step="10">Sprint complete</goto>
549
+ <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. -->
553
+ <action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
554
+ <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
555
+ <action>Report: "Sprint complete detected via fallback path. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
423
556
  </check>
424
- <action>Set `{{current_story}}` = first undone story from `{status_file}`</action>
425
- <action>Invoke `bmad-help` — "Story {{current_story}} needs attention. What is the next required workflow step?"</action>
557
+ <action>If `{{sprint_has_stories}}` is true: set `{{current_story}}` = first undone story from `{status_file}`.</action>
558
+ <action>Invoke `bmad-help` — "Story {{current_story}} needs attention (or: sprint in planning phase — no stories yet). What is the next required workflow step?"</action>
426
559
  <action>Extract `{{next_skill}}` from bmad-help response</action>
427
560
  </check>
428
561
 
@@ -464,10 +597,83 @@ Resolve:
464
597
 
465
598
  <step n="3" goal="Prepare and execute the recommended skill">
466
599
 
600
+ <!-- ──────────────────────────────────────────────────────────────────
601
+ PR 11 PARALLEL DISPATCH (gates):
602
+ - autopilot.parallel_stories: true (resolved at boot into
603
+ {{parallel_stories}})
604
+ - host_supports_parallel: true with high confidence (PR 11
605
+ agent-adapter.js — only Claude Code today)
606
+ - implementation_flow != quick (nano runs sequentially per epic)
607
+ - {{next_skill}} starts a fresh per-story flow (bmad-create-story
608
+ OR bmad-dev-story OR bmad-quick-dev) — never mid-story
609
+ - The current epic's DAG layer has width >= 2 (computed from
610
+ _Sprintpilot/sprints/dependencies.yaml via resolve-dag.js)
611
+
612
+ When all gates pass, the autopilot dispatches every story in the
613
+ 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
+ ────────────────────────────────────────────────────────────────── -->
619
+ <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
+ <action>**Compute current DAG layer.** Set `{{epic_id}}` = leading numeric segment of the FIRST undone story in `{status_file}`. Run:
621
+ `node {{project_root}}/_Sprintpilot/scripts/resolve-dag.js layers --epic "{{epic_id}}" --project-root "{{project_root}}"`
622
+ Parse stdout as a JSON array of arrays (each sub-array is one layer of story keys). Find the FIRST layer that contains at least one story whose status in `{status_file}` is NOT `done`. Filter that layer down to only the undone stories — set `{{active_layer}}` to the resulting list.</action>
623
+ <check if="{{active_layer}} length is 1">
624
+ <action>Single-story layer — no parallel benefit. Continue with normal sequential dispatch below (the standard step-3 flow picks the single story).</action>
625
+ </check>
626
+ <check if="{{active_layer}} length is 2 or more">
627
+ <action>**Parallel dispatch:** run:
628
+ `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.
632
+
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:
634
+ ```
635
+ You are running a single story for the Sprintpilot autopilot.
636
+ - Project root: {{project_root}}
637
+ - Story key: K
638
+ - Worktree: {{project_root}}/.worktrees/K (cd into it for all work)
639
+ - Skill flow (full): bmad-create-story → bmad-check-implementation-readiness → bmad-dev-story → bmad-code-review → apply patch findings → re-run tests → set status=done in {status_file}
640
+ - Skill flow (quick): bmad-quick-dev (single skill; nano profile)
641
+ Use {{implementation_flow}} = `{{implementation_flow}}` to pick which flow.
642
+ Track timing via `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story K --phase <phase>` after each skill returns.
643
+ Return a one-line JSON summary on completion: {"story":"K", "status":"done"|"failed", "tests":"<N/M>", "notes":"<short>"}
644
+ ```
645
+
646
+ Wait for ALL N sub-agents to return. Collect results; abort the autopilot ONLY if a sub-agent reports a TRUE BLOCKER per the AUTOPILOT RULES.</action>
647
+ <action>**Merge per-story shards** after the layer completes:
648
+ `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
+ <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>
651
+ <goto step="2">Re-evaluate after parallel layer dispatch</goto>
652
+ </check>
653
+ </check>
654
+
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. -->
660
+ <check if="{{implementation_flow}} is quick AND {{next_skill}} is bmad-dev-story">
661
+ <action>Override `{{next_skill}}` = `bmad-quick-dev`</action>
662
+ <action>Log: "Routing {{current_story}} through bmad-quick-dev per nano profile (implementation_flow=quick)"</action>
663
+ </check>
664
+ <!-- Under quick flow, autopilot never invokes bmad-create-story or
665
+ bmad-check-implementation-readiness; quick-dev reads AC from
666
+ sprint-status.yaml directly. If bmad-help proposes these skills
667
+ while implementation_flow=quick, skip them and advance. -->
668
+ <check if="{{implementation_flow}} is quick AND ({{next_skill}} is bmad-create-story OR {{next_skill}} is bmad-check-implementation-readiness)">
669
+ <action>Log: "Skipping {{next_skill}} under quick flow (nano profile) — quick-dev reads AC directly"</action>
670
+ <action>Set `{{next_skill}}` = `bmad-quick-dev`</action>
671
+ </check>
672
+
467
673
  <action>Set `{{completed_skill}}` = `{{next_skill}}`</action>
468
674
  <action>Create task "{{next_skill}}" → mark `in_progress`</action>
469
675
 
470
- <check if="{{next_skill}} is a per-story skill (bmad-dev-story, bmad-code-review, bmad-create-story)">
676
+ <check if="{{next_skill}} is a per-story skill (bmad-dev-story, bmad-quick-dev, bmad-code-review, bmad-create-story)">
471
677
  <action>Set `{{current_story}}` = first story in `{status_file}` with status `ready-for-dev` or `in-progress`</action>
472
678
 
473
679
  <critical>**Validate story key format** — the key MUST follow the pattern `{epic}-{story}-{title-kebab}` (e.g., `1-2-user-authentication`), NOT just `{epic}-{story}` (e.g., `1-2`).
@@ -481,95 +687,104 @@ Resolve:
481
687
  <action>Create per-story step tasks if not already created</action>
482
688
  </check>
483
689
 
484
- <!-- GIT: Enter worktree before dev-story -->
485
- <check if="{{git_enabled}} AND {{next_skill}} is bmad-dev-story">
486
- <action>**Sanitize branch name** run:
487
- `node {{project_root}}/_Sprintpilot/scripts/sanitize-branch.js "{{current_story}}" --prefix "{{branch_prefix}}" --max-length 60`
488
- Output: sanitized name (without prefix). Set `{{branch_name}}` = output.
489
- Full branch ref will be `{{branch_prefix}}{{branch_name}}`.
490
- </action>
690
+ <!-- PR 5: determine per-story epic key + title so the workflow can branch
691
+ per-epic under granularity=epic and decide "is this the last story
692
+ 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. -->
695
+ <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
+ <action>Set `{{epic_branch_name}}` = `epic-{{epic_id}}` (only used when `{{granularity}} = epic`).</action>
697
+ <action>**Detect first vs last story of epic** — read `{status_file}` (BMAD-owned; do not modify). Find all stories with the same `{{epic_id}}`:
698
+ - `{{is_first_story_of_epic}}` = true if no story in this epic has status `in-progress` or `done` yet (i.e. current story is the first to enter dev-story / quick-dev).
699
+ - `{{is_last_story_of_epic}}` = true if this is the final undone story in the epic (after this story, all other stories in the epic are `done`).
700
+ Both default to true when `{{epic_id}}` = "" (single-story "epic").
701
+ </action>
491
702
 
492
- <action>**Check if branch already registered** in `{status_file}` for this story.
493
- If yes AND worktree already exists skip creation (idempotent).
494
- If yes AND no worktree recovery mode (see health check).
495
- If no proceed with creation.
496
- </action>
703
+ <!-- GIT: Enter worktree OR create/reuse epic branch before dev-story OR quick-dev.
704
+ Nano's profile sets worktree.enabled=false + granularity=epic, so this
705
+ block falls through to in-place branching. -->
706
+ <check if="{{git_enabled}} AND ({{next_skill}} is bmad-dev-story OR {{next_skill}} is bmad-quick-dev)">
707
+ <action>**Sanitize branch name**: `node {{project_root}}/_Sprintpilot/scripts/sanitize-branch.js "{{current_story}}" --prefix "{{branch_prefix}}" --max-length 60`. Set `{{branch_name}}` = output. Full ref: `{{branch_prefix}}{{branch_name}}`.</action>
497
708
 
498
- <action>**Prepare for worktree** determine the correct branch point.
499
- Run: `git fetch origin`
709
+ <action>**Idempotency check** — if branch is already registered in `{status_file}` for this story AND its worktree exists, skip creation. If registered without worktree → recovery mode (see health check). Otherwise proceed.</action>
500
710
 
501
- Check if there is a previous story in this epic with a pushed but unmerged branch (PR pending):
502
- - Read `{git_status_file}` for earlier stories in the same epic
503
- - Find the latest story branch where `push_status` = "pushed" AND `pr_url` is a valid URL
504
- - Check if that branch has been merged to `{{base_branch}}`: `git merge-base --is-ancestor origin/{{branch_prefix}}<prev-branch> origin/{{base_branch}}`
711
+ <action>**Pick branch point.** If `{{has_origin}}` is true: `git fetch origin` (warn + continue on failure). If `{{has_origin}}` is false: skip fetch, use local refs.
505
712
 
506
- If an unmerged previous story branch exists:
507
- - Branch from it: `git checkout origin/{{branch_prefix}}<prev-branch>`
508
- - Set `{{pr_base}}` = `{{branch_prefix}}<prev-branch>` (PR should target previous story, not main)
509
- - Log: "Branching from {{branch_prefix}}<prev-branch> (PR pending merge)"
510
- Otherwise:
511
- - Branch from base: `git checkout origin/{{base_branch}}`
512
- - Set `{{pr_base}}` = `{{base_branch}}`
713
+ Read `{git_status_file}` for earlier stories in this epic; find the latest with `push_status = "pushed"` AND a valid `pr_url`; check if merged to base: `git merge-base --is-ancestor origin/{{branch_prefix}}<prev-branch> origin/{{base_branch}}`.
714
+ - If unmerged previous story exists (requires `{{has_origin}}`): `git checkout origin/{{branch_prefix}}<prev-branch>`, set `{{pr_base}}` = `{{branch_prefix}}<prev-branch>`.
715
+ - Otherwise: `git checkout origin/{{base_branch}}` (or local `{{base_branch}}` if no origin), set `{{pr_base}}` = `{{base_branch}}`.
513
716
 
514
- (Detached HEAD is fine — the worktree add below creates a new branch from HEAD)
717
+ Detached HEAD is fine — `git worktree add` below creates a new branch from HEAD.
515
718
  </action>
516
719
 
517
- <action>**Create worktree** using standard git commands (works in any coding agent):
518
- ```
519
- git worktree add "{{project_root}}/.worktrees/{{current_story}}" -b "{{branch_prefix}}{{branch_name}}" 2>&1
520
- ```
521
- This creates `.worktrees/{{current_story}}/` with a new branch `{{branch_prefix}}{{branch_name}}` from HEAD.
720
+ <!-- PR 5: epic granularity share one branch per epic instead of one per story.
721
+ Under worktree.enabled=false + granularity=epic (nano default),
722
+ subsequent stories of the same epic check out the epic branch in
723
+ place and commit there; no worktree is created. -->
724
+ <check if="{{granularity}} is epic">
725
+ <action>Override `{{branch_name}}` = `{{epic_branch_name}}` (shared across all stories in this epic).</action>
726
+ <check if="{{is_first_story_of_epic}} is true">
727
+ <action>Log: "Epic granularity: creating epic branch {{branch_prefix}}{{epic_branch_name}} for the first story of epic {{epic_id}}"</action>
728
+ </check>
729
+ <check if="{{is_first_story_of_epic}} is false">
730
+ <action>Log: "Epic granularity: reusing existing epic branch {{branch_prefix}}{{epic_branch_name}} for story {{current_story}}"</action>
731
+ </check>
732
+ </check>
522
733
 
523
- If worktree add fails (branch already exists):
524
- ```
525
- git worktree add "{{project_root}}/.worktrees/{{current_story}}" "{{branch_prefix}}{{branch_name}}" 2>&1
526
- ```
734
+ <check if="{{worktree_enabled}} is false">
735
+ <action>**In-place branching** (granularity=epic or worktree.enabled=false): `git checkout -B {{branch_prefix}}{{branch_name}} 2>&1`. No worktree. Set `{{in_worktree}}` = false. Skip the worktree-add block below.</action>
736
+ </check>
527
737
 
528
- **If both fail** (disk full, permissions, etc.):
529
- - Log: "WARN: git worktree add failedcontinuing without worktree isolation"
530
- - Set `{{in_worktree}}` = false
531
- - Create branch manually: `git checkout -b {{branch_prefix}}{{branch_name}}`
532
- If checkout also fails (branch already exists): `git checkout {{branch_prefix}}{{branch_name}}`
533
- If both fail: HALT — "Could not create or switch to branch {{branch_prefix}}{{branch_name}}"
534
- - Continue with the skill invocation in PROJECT_ROOT (no isolation)
535
- - Git operations (commit, push, PR) still work on the branch
536
- </action>
738
+ <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>
740
+ <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`.
537
741
 
538
- <check if="worktree add succeeded">
539
- <action>**Change working directory** to the worktree:
540
- `cd {{project_root}}/.worktrees/{{current_story}}`
541
- All subsequent file operations and commands MUST use this directory.
542
- Set `{{worktree_path}}` = `{{project_root}}/.worktrees/{{current_story}}`
742
+ 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.
543
743
  </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>
745
+ </check>
544
746
 
545
- <action>**Init submodules** if needed.
546
- First check for `.gitmodules` (use your file-exists tool, or `node -e "process.exit(require('fs').existsSync('.gitmodules')?0:1)"`). If not present, skip this step.
547
- If present, run `git submodule update --init --recursive` (give it ~30 seconds). If the command fails or hangs, warn "Submodule init failed (may need auth). Continuing without." and proceed.
747
+ <check if="{{worktree_enabled}} is true AND worktree add succeeded">
748
+ <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:
752
+ Resolve the main repo's common git dir into a captured variable `{{git_common}}` (cross-platform; replaces a `$(...)` shell substitution):
753
+ `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
+ For each submodule path in `.gitmodules`:
755
+ 1. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js acquire --submodule "<path>" --project-root "{{project_root}}"` — serializes concurrent submodule updates across worktrees.
756
+ 2. With git ≥ 2.18 (confirmed at boot by check-prereqs): wrap the update with retry to survive ref-lock contention:
757
+ `node {{project_root}}/_Sprintpilot/scripts/with-retry.js -- git -C {{project_root}}/.worktrees/{{current_story}} submodule update --init --recursive --reference "{{git_common}}" --jobs=4 -- <path>`
758
+ With older git (degraded mode flagged by check-prereqs): fall back to `git -C ... submodule update --init --recursive -- <path>`.
759
+ 3. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js release --submodule "<path>" --project-root "{{project_root}}"` (best-effort, ignore failures).
760
+ If the loop fails or hangs: warn "Submodule init failed (may need auth). Continuing." and proceed.
548
761
  </action>
549
-
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>
550
763
  <action>Set `{{in_worktree}}` = true</action>
551
764
  </check>
552
- <action>Update `{state_file}` (write to worktree copy since we're now IN the worktree)</action>
765
+ <action>Update `{state_file}` (write to the worktree copy since cwd is now the worktree)</action>
553
766
  </check>
554
767
 
555
- <action>Update `{state_file}`:
556
- ```yaml
557
- last_updated: {current_datetime}
558
- current_story: {{current_story}}
559
- current_bmad_step: executing
560
- completed_skill: {previous skill}
561
- next_skill: {{next_skill}}
562
- session_stories_done: {{session_stories_done}}
563
- stories_remaining: {{stories_remaining}}
564
- git_enabled: {{git_enabled}}
565
- platform: {{platform}}
566
- in_worktree: {{in_worktree}}
567
- pr_base: {{pr_base}}
568
- ```
569
- </action>
768
+ <action>Update `{state_file}` with STATE_FIELDS (set `current_bmad_step = executing`, `completed_skill = <previous skill>`).</action>
769
+ <check if="{{coalesce_state_writes}} is true">
770
+ <action>Mirror critical keys to the shard (bypasses batching for crash-recovery correctness):
771
+ `node {{project_root}}/_Sprintpilot/scripts/state-shard.js batch --story sprint --json "{\"current_bmad_step\":\"executing\",\"current_story\":\"{{current_story}}\",\"completed_skill\":\"<previous skill>\"}" --project-root "{{project_root}}"` — ignore failures.
772
+ </action>
773
+ </check>
570
774
 
571
775
  <!-- Autopilot menu handling rules apply — see AUTOPILOT RULES section above -->
572
776
 
777
+ <!-- PHASE TIMING: emit start/end around every skill invocation.
778
+ Use `{{current_story}}` when set, else the sentinel `sprint` for
779
+ sprint-level skills (bmad-help, bmad-sprint-planning, etc).
780
+ The script is a silent no-op when autopilot.phase_timings is false. -->
781
+ <action>Set `{{timing_story}}` = `{{current_story}}` if non-empty, else `sprint`.</action>
782
+ <!-- Single-call timing via `mark`. The previous start/INVOKE/end triplet
783
+ was reliably broken in long sessions (LLM skipped the `end` call
784
+ ~50% of the time), so most skills had no duration recorded. `mark`
785
+ auto-computes the duration of the PREVIOUS phase from a small marker
786
+ 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>
573
788
  <action>INVOKE `{{next_skill}}` skill using the Skill tool</action>
574
789
  <action>Mark task "{{next_skill}}" as `completed`</action>
575
790
 
@@ -580,8 +795,28 @@ pr_base: {{pr_base}}
580
795
 
581
796
  <step n="4" goal="Handle skill completion and route to next action">
582
797
 
798
+ <!-- PR 4 NANO ROUTING: quick-dev completion handler.
799
+ 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. -->
804
+ <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>
806
+ <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>
808
+ <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
+ <check if="{{tests_passed}} is false OR {{quickdev_severity_high}} is true">
810
+ <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>
811
+ </check>
812
+ <action>Set `{{next_skill}}` = "(none)" — quick-dev handled review + commit internally per BMad step-oneshot.md.</action>
813
+ <goto step="7">Mark story done</goto>
814
+ </check>
815
+
583
816
  <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>
584
818
  <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>
585
820
  <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>
586
821
 
587
822
  <!-- GIT: Lint, stage, and commit after dev-story -->
@@ -599,7 +834,9 @@ pr_base: {{pr_base}}
599
834
  - `{patch-title}` → from review finding title, fallback to "code review fix"
600
835
  Read the commit template from `git.commit_templates.story` in config (default: `feat({epic}): {story-title} ({story-key})`).
601
836
  Then run:
602
- `node {{project_root}}/_Sprintpilot/scripts/stage-and-commit.js --message "feat({{epic}}): {{story-title}} ({{current_story}})" --allowlist {{project_root}}/_Sprintpilot/.secrets-allowlist`
837
+ `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "git.commit" --project-root "{{project_root}}"` (ignore failures), then
838
+ `node {{project_root}}/_Sprintpilot/scripts/stage-and-commit.js --message "feat({{epic}}): {{story-title}} ({{current_story}})" --allowlist {{project_root}}/_Sprintpilot/.secrets-allowlist`, then
839
+ `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "git.commit" --project-root "{{project_root}}"` (ignore failures).
603
840
  Output: commit SHA. Set `{{story_commit}}` = output.
604
841
  Warnings (secrets, large files) printed to stderr — review but don't halt unless user says to.
605
842
  </action>
@@ -636,13 +873,59 @@ pr_base: {{pr_base}}
636
873
  </action>
637
874
  </check>
638
875
 
876
+ <check if="{{completed_skill}} was bmad-sprint-planning">
877
+ <!-- PR-follow-up: sprint-planning populates development_status for the
878
+ first time. Recalculate stories_remaining so the step-2 "sprint
879
+ complete" gate doesn't fire spuriously on the next iteration. -->
880
+ <action>**Recalculate `{{stories_remaining}}`** — run:
881
+ `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
882
+ Parse stdout as `{"remaining":[...],"state":"..."}` → `{{stories_remaining}}` = `.remaining`. Update `{state_file}` with the new value.</action>
883
+ </check>
884
+
885
+ <!-- 2.0.2: auto-infer inter-story dependencies via the autopilot's active
886
+ LLM session. Replaces the hand-authored dependencies.yaml step that
887
+ users never discovered. Gated on autopilot.auto_infer_dependencies
888
+ (default true on small/medium/large; false on nano and legacy).
889
+ The script never calls an LLM — it ingests our JSON output via stdin,
890
+ validates, and writes the sidecar with an AUTO-INFERRED marker.
891
+ Hand-authored sidecars (no marker) are detected and respected. -->
892
+ <check if="{{completed_skill}} was bmad-sprint-planning">
893
+ <action>Resolve `{{auto_infer_dependencies}}` from `autopilot.auto_infer_dependencies` (default false) via:
894
+ `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.auto_infer_dependencies --project-root "{{project_root}}"`</action>
895
+ <check if="{{auto_infer_dependencies}} is true">
896
+ <action>Set `{{distinct_epic_ids}}` = sorted unique leading-numeric segments of every key in `{{stories_remaining}}` (e.g. `1-1-foo` → `1`). Skip the loop if empty (pre-planning edge case).</action>
897
+ <foreach var="{{epic_id}}" in="{{distinct_epic_ids}}">
898
+ <action>Generate the inference prompt: run `node {{project_root}}/_Sprintpilot/scripts/infer-dependencies.js scaffold-prompt --epic "{{epic_id}}" --project-root "{{project_root}}"` and capture stdout as `{{infer_prompt}}`.</action>
899
+ <action>**Execute the inference inline.** The prompt instructs you to read four files (sprint-status.yaml, epics.md, architecture.md, dependencies.yaml-if-present) and emit ONE JSON object with shape `{"version":1,"epic":"{{epic_id}}","dependencies":{...},"rationale":{...}}`. Read those files NOW. Reason about which stories share modules, files, or describe themselves as building on each other (the prompt's RULES section enumerates the legitimate triggers). Emit the JSON envelope. No prose, no markdown fences. Set `{{infer_json}}` to the envelope.</action>
900
+ <action>Pipe `{{infer_json}}` into `node {{project_root}}/_Sprintpilot/scripts/infer-dependencies.js write --epic "{{epic_id}}" --project-root "{{project_root}}"`. Parse stdout JSON.
901
+ - On exit 0 (`wrote: true`) → log: `"Inferred {{response.edges_inferred}} dependency edges for epic {{epic_id}} (hash {{response.hash}}; user_overrides_preserved: {{response.user_overrides_preserved}})"`.
902
+ - On exit 2 (`wrote: false`, `reason: existing-hand-authored`) → log: `"Existing dependencies.yaml is hand-authored — skipping LLM inference for epic {{epic_id}}; user-authored sidecar wins"`. Do NOT halt.
903
+ - On exit 1 (validation error) → log the error envelope verbatim, then log: `"LLM dependency inference rejected for epic {{epic_id}} — falling back to linear ordering. Re-run /sprint-autopilot-on to retry, or hand-author the sidecar."`. Do NOT halt the autopilot — `resolve-dag.js` will fall back to the `ordering` strategy on dispatch.
904
+ </action>
905
+ </foreach>
906
+ </check>
907
+ </check>
908
+
639
909
  <check if="{{completed_skill}} was bmad-sprint-planning AND {{git_enabled}}">
640
- <action>Run `git fetch origin`</action>
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>
641
912
  <action>Initialize `{git_status_file}` if it doesn't exist (with git_integration block)</action>
642
913
  </check>
643
914
 
644
915
  <check if="{{completed_skill}} was bmad-create-story">
645
- <action>Verify story file has `- [ ]` checkboxes in Tasks/Subtasks section. If missing, re-run create-story.</action>
916
+ <!-- PR-follow-up (greenfield e2e fix): bmad-create-story sometimes omits
917
+ the Tasks/Subtasks section entirely. Re-running the skill doesn't
918
+ always fix it. Fall back to a deterministic script so the story
919
+ file ALWAYS has checkboxes dev-story can mark — no LLM prose in
920
+ this path. -->
921
+ <action>Locate the story file for `{{current_story}}` — glob
922
+ `_bmad-output/**/story-{{current_story}}.md` and
923
+ `_bmad-output/**/{{current_story}}.md`. Set `{{story_file_path}}`.
924
+ </action>
925
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/inject-tasks-section.js --story-file "{{story_file_path}}"`
926
+ — idempotent. The script checks for an existing `## Tasks` / `## Subtasks` section with at least one `- [ ]` checkbox and returns `{"action":"skip"}` if present. Otherwise it extracts every AC entry from the bounded `## Acceptance Criteria` section and appends a correctly-formatted section with one `- [ ] <AC>` bullet per entry.
927
+ Parse stdout JSON; the script never throws on a well-formed story file, so treat any non-zero exit as a log-and-proceed signal (the sprint can still complete without this guarantee — only the "task checkboxes marked" assertion degrades).
928
+ </action>
646
929
  </check>
647
930
 
648
931
  <check if="{{completed_skill}} was bmad-create-story AND {{git_enabled}}">
@@ -653,22 +936,10 @@ pr_base: {{pr_base}}
653
936
  </action>
654
937
  </check>
655
938
 
656
- <!-- GIT: Commit planning artifacts to main after planning skills -->
657
939
  <check if="{{git_enabled}} AND {{completed_skill}} is a planning skill (bmad-create-prd, bmad-create-architecture, bmad-create-ux-design, bmad-create-epics-and-stories, bmad-sprint-planning, bmad-check-implementation-readiness, bmad-create-story)">
658
- <action>**Commit planning artifacts to main** — keep track of all planning decisions in git.
659
- Stage all changed artifacts (ignore errors — any of these paths may not yet exist):
660
- ```
661
- git add _bmad-output/planning-artifacts/ _bmad-output/implementation-artifacts/ _bmad-output/stories/
662
- ```
663
- Check if there's anything staged; if yes, commit:
664
- ```
665
- git diff --cached --quiet
666
- ```
667
- If that exits non-zero (there are staged changes), run: `git commit -m "docs: {{completed_skill}} artifacts"`
668
- Then push (log a warning if push fails; do not halt autopilot):
669
- ```
670
- git push origin {{base_branch}}
671
- ```
940
+ <action>**Commit planning artifacts to main.**
941
+ 1. `git add _bmad-output/planning-artifacts/ _bmad-output/implementation-artifacts/ _bmad-output/stories/` (ignore missing-path errors)
942
+ 2. If `git diff --cached --quiet` exits non-zero: `git commit -m "docs: {{completed_skill}} artifacts"` then `git push origin {{base_branch}}` (warn on push failure, do not halt).
672
943
  </action>
673
944
  </check>
674
945
 
@@ -679,34 +950,23 @@ pr_base: {{pr_base}}
679
950
 
680
951
  <step n="5" goal="Determine next skill — from skill output first, bmad-help as fallback">
681
952
 
682
- <action>Read the output of `{{completed_skill}}`</action>
683
-
684
- <check if="output contains 'Next Steps', 'What to do next', 'Run next', or equivalent">
685
- <action>Extract `{{next_skill}}` from that section</action>
686
- <action>Log: "Next step from skill output: {{next_skill}}"</action>
687
- </check>
688
-
689
- <check if="output contains NO clear next step">
690
- <action>Invoke `bmad-help` — "{{completed_skill}} just finished. What is the next required workflow step?"</action>
691
- <action>Extract `{{next_skill}}` from bmad-help response</action>
692
- <action>Log: "Next step from bmad-help fallback: {{next_skill}}"</action>
693
- </check>
953
+ <action>Read the output of `{{completed_skill}}`. If it contains "Next Steps", "What to do next", "Run next", or equivalent, extract `{{next_skill}}` from that section. Otherwise invoke `bmad-help` — "{{completed_skill}} just finished. What is the next required workflow step?" — and extract `{{next_skill}}` from its response. Log the source ("skill output" vs "bmad-help fallback").</action>
694
954
 
695
- <check if="{{next_skill}} is null, empty, or signals completion (no further steps / sprint done / all done)">
696
- <action>**Verify against source of truth** — re-read `{status_file}` and check for any story with status != "done"</action>
697
- <check if="undone stories exist in status_file">
698
- <action>Set `{{current_story}}` = first undone story from `{status_file}`</action>
699
- <action>Determine `{{next_skill}}` based on that story's current status and BMAD step:
700
- - If story has no story file yet → `bmad-create-story`
701
- - If story file exists but status is `ready-for-dev` `bmad-check-implementation-readiness`
702
- - If story is `in-progress` and `current_bmad_step` is before `code-review` (i.e. RED or GREEN phase) → `bmad-dev-story`
703
- - If story is `in-progress` and `current_bmad_step` is `code-review` or later → `bmad-code-review`
704
- - Otherwise → invoke `bmad-help` for precise determination
705
- </action>
706
- <action>Log: "next_skill was empty but undone stories remain — resolved to {{next_skill}} for {{current_story}}"</action>
707
- </check>
955
+ <check if="{{next_skill}} is null, empty, or signals completion">
956
+ <action>**Verify against source of truth** — re-read `{status_file}`. If undone stories exist, set `{{current_story}}` = first one and determine `{{next_skill}}`:
957
+ - No story file `bmad-create-story`
958
+ - Story file + status `ready-for-dev` `bmad-check-implementation-readiness`
959
+ - Status `in-progress` and `current_bmad_step` before `code-review` `bmad-dev-story`
960
+ - Status `in-progress` and `current_bmad_step` `code-review` → `bmad-code-review`
961
+ - Else invoke `bmad-help` for precise determination
962
+ Log: "next_skill was empty but undone stories remain resolved to {{next_skill}} for {{current_story}}".
963
+ </action>
708
964
  <check if="all stories in status_file are done">
709
- <goto step="10">Sprint complete</goto>
965
+ <!-- Same finalize-pending handoff as step 2 — never run step 10 in
966
+ the session that first noticed sprint-complete. -->
967
+ <action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
968
+ <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
969
+ <action>Report: "Sprint complete detected during skill-recovery. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
710
970
  </check>
711
971
  </check>
712
972
 
@@ -752,6 +1012,8 @@ For any finding that is DISMISSED (contradicts AC or is a false positive):
752
1012
  <action>Mark "[story] Apply patches" → `completed`</action>
753
1013
 
754
1014
  <!-- Re-run code review to sync sprint-status.yaml — patches resolved all findings, so code-review will now set story to done -->
1015
+ <!-- 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>
755
1017
  <action>Re-invoke `bmad-code-review` using the Skill tool.
756
1018
  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).
757
1019
  Instruct: "Re-verify code review for story {{current_story}} — all patch findings have been applied. Update story status accordingly."
@@ -776,8 +1038,19 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
776
1038
  - Final test count: `N/N passed`
777
1039
  </action>
778
1040
 
779
- <!-- GIT: Push, PR, exit worktree -->
780
- <check if="{{git_enabled}} AND {{in_worktree}}">
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. -->
1046
+ <check if="{{granularity}} is epic AND {{is_last_story_of_epic}} is false">
1047
+ <action>Log: "Epic granularity: skipping push/PR for {{current_story}} — will push at end of epic {{epic_id}}"</action>
1048
+ <action>Set `{{push_status}}` = "deferred", `{{pr_url}}` = "DEFERRED", `{{merge_status}}` = "deferred"</action>
1049
+ <!-- Skip the whole git-push block; fall through to the artifact-sync block below. -->
1050
+ </check>
1051
+
1052
+ <!-- GIT: Push, PR, exit worktree (story granularity OR last story of an epic) -->
1053
+ <check if="{{git_enabled}} AND ({{granularity}} is story OR {{is_last_story_of_epic}} is true) AND ({{in_worktree}} OR {{worktree_enabled}} is false)">
781
1054
  <check if="{{push_auto}} is true">
782
1055
  <action>**Push branch**.
783
1056
  Run: `git push -u origin {{branch_prefix}}{{branch_name}} 2>&1`
@@ -815,37 +1088,21 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
815
1088
  </action>
816
1089
 
817
1090
  <check if="{{create_pr}} is false OR {{platform}} is git_only OR {{pr_url}} is null or SKIPPED">
818
- <action>**Merge story branch to main** tracked and retryable.
819
- Run:
820
- ```
821
- git checkout -B {{base_branch}} origin/{{base_branch}}
822
- git merge {{branch_prefix}}{{branch_name}} --no-edit
823
- ```
824
- If succeeds:
825
- - `git push origin {{base_branch}}`
826
- - Set `{{merge_status}}` = "merged"
827
- If fails (conflict):
828
- - `git merge --abort`
829
- - `git fetch origin`
830
- - `git checkout -B {{base_branch}} origin/{{base_branch}}`
831
- - Retry merge once: `git merge {{branch_prefix}}{{branch_name}} --no-edit`
832
- - If retry succeeds: push, set `{{merge_status}}` = "merged"
833
- - If retry fails: set `{{merge_status}}` = "failed"
834
- Log: "WARN: merge failed for {{current_story}} — will retry on next boot"
835
-
836
- If `{{merge_status}}` == "failed":
837
- Log warning but do NOT halt. The branch is pushed and preserved.
838
- Boot reconciliation (INITIALIZATION branch reconciliation) will retry on next session.
839
-
840
- Note: `{{merge_status}}` is persisted by the full sync-status.js call later in this step (via `--merge-status`). Do NOT call sync-status.js separately here — it does full block replacement and would destroy other fields.
1091
+ <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.
1093
+ 1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
1094
+ 2. If `{{squash_on_merge}}` is true:
1095
+ `git merge --squash {{branch_prefix}}{{branch_name}}` then
1096
+ `git commit -m "feat({{epic_id}}): epic {{epic_id}} ({{branch_prefix}}{{branch_name}})"`.
1097
+ Otherwise: `git merge {{branch_prefix}}{{branch_name}} --no-edit`.
1098
+ 3. On success: `git push origin {{base_branch}}`, set `{{merge_status}}` = "merged".
1099
+ 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.
1101
+
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.
841
1103
  </action>
842
- <check if="{{cleanup_on_merge}} is true">
843
- <action>**Cleanup worktree** for merged story branch was merged locally, worktree is no longer needed. Ignore failures from the remove (the worktree may already be gone):
844
- ```
845
- git worktree remove .worktrees/{{current_story}} --force
846
- git worktree prune
847
- ```
848
- </action>
1104
+ <check if="{{cleanup_on_merge}} is true AND {{in_worktree}} is true">
1105
+ <action>**Cleanup worktree** (ignore failuresmay already be gone): `git worktree remove .worktrees/{{current_story}} --force` then `git worktree prune`</action>
849
1106
  </check>
850
1107
  </check>
851
1108
  <check if="{{pr_url}} is a valid URL (not null, not SKIPPED)">
@@ -854,29 +1111,11 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
854
1111
  <action>Log: "Story {{current_story}} pushed — PR awaiting review: {{pr_url}}"</action>
855
1112
  </check>
856
1113
 
857
- <!-- Commit all implementation artifacts and status updates to main after each story -->
858
- <action>**Commit story completion artifacts to main** — ensure main always reflects current sprint state.
859
- ```
860
- git checkout -B {{base_branch}} origin/{{base_branch}}
861
- ```
862
- </action>
863
-
864
- <action>**Write git status** to addon's own file (NEVER modify sprint-status.yaml) — runs AFTER checkout to base branch so the file persists in the working tree for the commit below:
865
- `node {{project_root}}/_Sprintpilot/scripts/sync-status.js --story "{{current_story}}" --git-status-file "{{project_root}}/_bmad-output/implementation-artifacts/git-status.yaml" --branch "{{branch_prefix}}{{branch_name}}" --commit "{{story_commit}}" --patch-commits "{{patch_commits_csv}}" --push-status "{{push_status}}" --merge-status "{{merge_status}}" --pr-url "{{pr_url}}" --lint-result "{{lint_result}}" --worktree "{{project_root}}/.worktrees/{{current_story}}" --platform "{{platform}}" --base-branch "{{base_branch}}"`
866
- This writes to `git-status.yaml` (addon-owned). Sprint-status.yaml is BMAD-owned — updated by BMAD skills only.
867
- </action>
868
-
869
- <action>**Stage and commit artifacts** — explicitly include git-status.yaml and decision-log.yaml. Ignore errors from the `git add` (any listed path may not yet exist):
870
- ```
871
- git add _bmad-output/implementation-artifacts/sprint-status.yaml _bmad-output/implementation-artifacts/git-status.yaml _bmad-output/implementation-artifacts/autopilot-state.yaml _bmad-output/implementation-artifacts/decision-log.yaml _bmad-output/stories/ _bmad-output/planning-artifacts/
872
- ```
873
- Check if anything is staged: `git diff --cached --quiet`. If that exits non-zero, commit:
874
- `git commit -m "docs: story {{current_story}} done — {{test_count}} tests{{#if pr_url}}, PR: {{pr_url}}{{/if}}"`
875
- Then push (log a warning if push fails; do not halt autopilot):
876
- ```
877
- git push origin {{base_branch}}
878
- ```
879
- This ensures sprint-status.yaml, git-status.yaml, story files, and any updated artifacts are on main even when story code is on a PR branch.
1114
+ <action>**Commit story artifacts to main** keeps main in sync even when story code is on a PR branch.
1115
+ 1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
1116
+ 2. Write git-status.yaml (addon-owned — never touch sprint-status.yaml): `node {{project_root}}/_Sprintpilot/scripts/sync-status.js --story "{{current_story}}" --git-status-file "{{project_root}}/_bmad-output/implementation-artifacts/git-status.yaml" --branch "{{branch_prefix}}{{branch_name}}" --commit "{{story_commit}}" --patch-commits "{{patch_commits_csv}}" --push-status "{{push_status}}" --merge-status "{{merge_status}}" --pr-url "{{pr_url}}" --lint-result "{{lint_result}}" --worktree "{{project_root}}/.worktrees/{{current_story}}" --platform "{{platform}}" --base-branch "{{base_branch}}"`
1117
+ 3. Stage artifacts (ignore errors for missing paths): `git add _bmad-output/implementation-artifacts/sprint-status.yaml _bmad-output/implementation-artifacts/git-status.yaml _bmad-output/implementation-artifacts/autopilot-state.yaml _bmad-output/implementation-artifacts/decision-log.yaml _bmad-output/stories/ _bmad-output/planning-artifacts/`
1118
+ 4. If `git diff --cached --quiet` exits non-zero: `git commit -m "docs: story {{current_story}} done — {{test_count}} tests{{#if pr_url}}, PR: {{pr_url}}{{/if}}"` then `git push origin {{base_branch}}` (warn on push failure, do not halt).
880
1119
  </action>
881
1120
  </check>
882
1121
 
@@ -890,6 +1129,17 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
890
1129
  <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>
891
1130
  <action>Remove `{{current_story}}` from `{{stories_remaining}}` list</action>
892
1131
 
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. -->
1137
+ <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>
1139
+ <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
+ <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
+ </check>
1142
+
893
1143
  <action>Report: "Story {{current_story}} done — N/N passing{{#if pr_url}} — PR: {{pr_url}}{{/if}}"</action>
894
1144
 
895
1145
  <action>Check if ALL stories in this epic are `done`</action>
@@ -897,134 +1147,34 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
897
1147
  <action>Resolve `{{epic_id}}` (e.g. "1") and `{{epic_title}}` from `{status_file}` for the current epic</action>
898
1148
  <action>Create task "[epic {{epic_id}}] retrospective" → `in_progress`</action>
899
1149
 
900
- <!-- Retrospective handling is driven by `autopilot.retrospective_mode`
901
- in modules/autopilot/config.yaml. See the SKILL AUTOMATABLE REFERENCE
902
- table for rationale. The external `bmad-retrospective` skill is
903
- NEVER invoked from autopilot — it enters a multi-persona discussion
904
- loop under some CLIs. -->
1150
+ <!-- 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). -->
905
1153
 
906
1154
  <check if="{{retrospective_mode}} is auto">
907
- <!-- Deterministic single-pass retrospective. No persona simulation,
908
- no rounds, no external skill call. All inputs are on-disk. -->
909
- <action>Collect from `{status_file}` for epic `{{epic_id}}`:
910
- - list of done stories with { story-key, title, test_pass_count, patch_count }
911
- - epic title, start/end dates if present
912
- </action>
913
- <action>Collect decision-log entries for epic `{{epic_id}}` from `{decision_log_file}` (match on `story` prefix `{{epic_id}}-` or `phase: autopilot:*` entries tagged to this epic)</action>
914
- <action>Identify open risks / carry-over notes from sprint-status (any story with `notes` or `risks` fields, any `workaround` decisions in the log for this epic)</action>
915
- <action>Ensure directory `{implementation_artifacts}/retrospectives/` exists</action>
916
- <action>Write `{implementation_artifacts}/retrospectives/epic-{{epic_id}}-retrospective.md` using this template:
917
- ```markdown
918
- # Epic {{epic_id}} — {{epic_title}} — Retrospective
919
-
920
- **Completed:** {current_date}
921
- **Stories done:** {{n_done}}/{{n_total}}
922
-
923
- ## Stories
924
- {{#each stories}}
925
- - **{{story-key}}** — {{title}}
926
- - Tests: {{test_pass_count}}
927
- - Patches applied: {{patch_count}}
928
- {{/each}}
929
-
930
- ## Key decisions
931
- {{#each decisions}}
932
- - [{{impact}}] {{category}}: {{decision}} — {{rationale}}
933
- {{/each}}
934
-
935
- ## Risks carried forward
936
- {{#each open_risks}}
937
- - {{risk}}
938
- {{/each}}
939
-
940
- ## Notes
941
- Generated inline by Sprintpilot autopilot per `autopilot.retrospective_mode: auto`.
942
- ```
943
- </action>
944
- <action>Update `{status_file}`:
945
- - `epics.{{epic_id}}.status` = `done`
946
- - `epics.{{epic_id}}.retrospective_path` = the retrospective file path (relative to project root)
947
- - `epics.{{epic_id}}.completed_at` = {current_date}
948
- </action>
949
- <action>Append decision-log entry:
950
- `{ category: workaround, decision: "retrospective generated inline", rationale: "autopilot.retrospective_mode=auto — avoids external skill's multi-persona loop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`
951
- </action>
952
- <action>Mark retrospective task → `completed`</action>
953
- <action>Set `{{completed_skill}}` = `retrospective-auto`</action>
1155
+ <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
+ <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
+ <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
+ <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>
954
1161
  </check>
955
1162
 
956
1163
  <check if="{{retrospective_mode}} is stop">
957
- <!-- Pause autopilot so user can run /bmad-retrospective interactively.
958
- On the next /sprint-autopilot-on the resume logic in step 1 will
959
- detect the cleared state and move to the next epic. -->
960
- <action>Update `{state_file}`:
961
- - `paused_at` = `epic-complete-awaiting-retrospective`
962
- - `paused_epic_id` = `{{epic_id}}`
963
- - `next_action` = "run /bmad-retrospective interactively for epic {{epic_id}}, then re-run /sprint-autopilot-on"
964
- </action>
965
- <action>Append decision-log entry:
966
- `{ category: workaround, decision: "paused for interactive retrospective", rationale: "autopilot.retrospective_mode=stop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`
967
- </action>
968
- <action>Mark retrospective task → `completed` (handed off to user)</action>
969
- <action>Report:
970
- ```
971
- Autopilot paused — epic {{epic_id}} complete, retrospective handed off.
972
-
973
- Per `autopilot.retrospective_mode: stop` in
974
- _Sprintpilot/modules/autopilot/config.yaml, autopilot does not run the
975
- retrospective automatically.
976
-
977
- To continue:
978
- 1. Run `/bmad-retrospective` interactively for epic {{epic_id}}
979
- 2. When done, run `/sprint-autopilot-on` to resume with the next epic
980
-
981
- State saved to: {state_file}
982
- ```
983
- </action>
984
- <action>STOP</action>
1164
+ <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>
1166
+ <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>
985
1167
  </check>
986
1168
 
987
1169
  <check if="{{retrospective_mode}} is skip">
988
- <!-- User opted out of retrospective. Record and move on. -->
989
- <action>Update `{status_file}`:
990
- - `epics.{{epic_id}}.status` = `done`
991
- - `epics.{{epic_id}}.retrospective_path` = null
992
- - `epics.{{epic_id}}.retrospective_skipped` = true
993
- - `epics.{{epic_id}}.completed_at` = {current_date}
994
- </action>
995
- <action>Append decision-log entry:
996
- `{ category: workaround, decision: "retrospective skipped", rationale: "autopilot.retrospective_mode=skip (NOT RECOMMENDED)", impact: medium, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`
997
- </action>
998
- <action>Mark retrospective task → `completed` (skipped)</action>
999
- <action>Set `{{completed_skill}}` = `retrospective-skip`</action>
1000
- <action>Log: "Epic {{epic_id}} retrospective skipped per config — continuing with next epic"</action>
1170
+ <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>
1001
1172
  </check>
1002
1173
 
1003
- <!-- GIT: Epic completion — suggest merge, cleanup worktrees -->
1004
1174
  <check if="{{git_enabled}}">
1005
- <action>**List all epic PR/MR URLs** from `{status_file}` for this epic's stories</action>
1006
- <action>Report:
1007
- ```
1008
- Epic complete — PR/MR summary:
1009
- {{#each epic_stories}}
1010
- - {{story-key}}: {{pr_url}}
1011
- {{/each}}
1012
-
1013
- Ready to merge. Review PRs and confirm when ready.
1014
- ```
1015
- </action>
1175
+ <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>
1016
1176
  <check if="{{cleanup_on_merge}} is true">
1017
- <action>**Cleanup worktrees** for completed stories:
1018
- For each story in this epic:
1019
- 1. Check if worktree at `.worktrees/{{story-key}}` exists
1020
- 2. Check if clean: `git -C .worktrees/{{story-key}} status --porcelain`
1021
- 3. If clean → `git worktree remove .worktrees/{{story-key}}` + `git worktree prune`
1022
- Update `{git_status_file}` for this story: `worktree_cleaned: true`
1023
- 4. If dirty → warn user, skip cleanup
1024
- </action>
1025
- </check>
1026
- <check if="{{cleanup_on_merge}} is false">
1027
- <action>Log: "Worktree cleanup skipped (git.worktree.cleanup_on_merge = false)"</action>
1177
+ <action>**Cleanup worktrees** for completed stories. For each: if `.worktrees/{{story-key}}` exists, check cleanliness via `git -C .worktrees/{{story-key}} status --porcelain`. If clean → `git worktree remove` + `git worktree prune` and set `worktree_cleaned: true` in `{git_status_file}`. If dirty → warn and skip.</action>
1028
1178
  </check>
1029
1179
  </check>
1030
1180
  </check>
@@ -1041,21 +1191,7 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
1041
1191
 
1042
1192
  <step n="8" goal="Save state and continue">
1043
1193
 
1044
- <action>Update `{state_file}`:
1045
- ```yaml
1046
- last_updated: {current_datetime}
1047
- current_story: {{current_story}}
1048
- current_bmad_step: {{current_bmad_step}}
1049
- completed_skill: {{completed_skill}}
1050
- next_skill: {{next_skill}}
1051
- session_stories_done: {{session_stories_done}}
1052
- stories_remaining: {{stories_remaining}}
1053
- git_enabled: {{git_enabled}}
1054
- platform: {{platform}}
1055
- in_worktree: {{in_worktree}}
1056
- pr_base: {{pr_base}}
1057
- ```
1058
- </action>
1194
+ <action>Update `{state_file}` with STATE_FIELDS.</action>
1059
1195
 
1060
1196
  <goto step="2">Continue execution loop</goto>
1061
1197
 
@@ -1090,21 +1226,14 @@ pr_base: {{pr_base}}
1090
1226
  </action>
1091
1227
  </check>
1092
1228
 
1093
- <action>Update `{state_file}`:
1094
- ```yaml
1095
- last_updated: {current_datetime}
1096
- current_story: {{current_story}}
1097
- current_bmad_step: {{current_bmad_step}}
1098
- completed_skill: {{completed_skill}}
1099
- next_skill: {{next_skill}}
1100
- session_stories_done: {{session_stories_done}}
1101
- stories_remaining: {{stories_remaining}}
1102
- git_enabled: {{git_enabled}}
1103
- platform: {{platform}}
1104
- in_worktree: {{in_worktree}}
1105
- pr_base: {{pr_base}}
1106
- ```
1107
- </action>
1229
+ <action>Update `{state_file}` with STATE_FIELDS.</action>
1230
+ <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>
1233
+ </check>
1234
+
1235
+ <!-- Phase-timing session snapshot (no-op if autopilot.phase_timings is false). -->
1236
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/summarize-timings.js --session-only --format md --quiet --project-root "{{project_root}}"` — ignore failures. The stdout line is the artifact path; include it in the checkpoint report if non-empty.</action>
1108
1237
 
1109
1238
  <action>Read `{decision_log_file}` — count medium/high decisions from this session's stories</action>
1110
1239
 
@@ -1144,143 +1273,147 @@ No work will be repeated.
1144
1273
 
1145
1274
  <step n="10" goal="Sprint complete — emit summary and next steps">
1146
1275
 
1147
- <!-- GIT: Exit worktree if still in one -->
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. -->
1285
+
1286
+ <!-- GIT: Exit worktree if still in one (prerequisite for the rest) -->
1148
1287
  <check if="{{in_worktree}}">
1149
1288
  <action>`cd {{project_root}}` — return to project root</action>
1150
1289
  <action>Set `{{in_worktree}}` = false</action>
1151
1290
  </check>
1152
1291
 
1153
- <action>Verify: all stories `done`, all retrospectives `done` in `{status_file}`</action>
1154
- <action>Run full test suite report `N/N passed`</action>
1155
-
1156
- <!-- Generate project documentation after sprint completion -->
1157
- <action>**Generate documentation** create or update project README and docs.
1158
- Invoke `bmad-document-project` skill to auto-generate documentation from the completed implementation.
1159
- If the skill is not available or fails, generate a minimal README.md:
1160
- - Project name and description (from product brief / PRD)
1161
- - How to install (`npm install` / `pip install` / etc.)
1162
- - How to run (`npm start` / the launch command)
1163
- - How to test (`npm test`)
1164
- - Architecture overview (from architecture doc if it exists)
1292
+ <!-- Run the mark-tasks helper AS THE VERY FIRST action of step 10.
1293
+ The LLM reliably executes the first critical action but sometimes
1294
+ skips later ones once it feels "done" with the sprint. Putting this
1295
+ first ensures every done-story's checkboxes get marked before the
1296
+ LLM loses focus. -->
1297
+ <action>**[CRITICAL 1/7] Mark all done stories' Task checkboxes** deterministic helper. Run:
1298
+ `node {{project_root}}/_Sprintpilot/scripts/mark-done-stories-tasks.js --status-file "{status_file}" --project-root "{{project_root}}"` ignore failures. This finds every story with status="done" in sprint-status.yaml and replaces `- [ ]` with `- [x]` in its story file.
1165
1299
  </action>
1166
1300
 
1167
- <!-- GIT: Commit documentation and final artifacts to main -->
1301
+ <action>**[CRITICAL 2/7] Remove autopilot-owned worktrees** no-op if none exist. Only worktrees we created (paths inside `{{project_root}}/.worktrees/`) are removed; any user-placed worktree elsewhere is left untouched.
1168
1302
  <check if="{{git_enabled}}">
1169
- <action>**Commit final artifacts and documentation to main**. Run each step; if an early step fails, STOP and log — don't proceed past a failed step. `git add` may fail for missing optional paths (`docs/`, `README.md`); ignore those path-specific errors. Failure of the final push should log a warning but not halt autopilot:
1170
- ```
1171
- git checkout -B {{base_branch}} origin/{{base_branch}}
1172
- git add _bmad-output/ README.md docs/
1173
- ```
1174
- Check if anything is staged: `git diff --cached --quiet`. If that exits non-zero, commit:
1175
- `git commit -m "docs: project documentation and final artifacts"`
1176
- Then: `git push origin {{base_branch}}`
1177
- </action>
1303
+ Run: `git worktree list --porcelain` to enumerate. For each worktree whose path starts with `{{project_root}}/.worktrees/`:
1304
+ - `git worktree remove <path> --force 2>&1` (ignore failures — best-effort).
1305
+ 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.
1178
1307
  </check>
1308
+ </action>
1179
1309
 
1180
- <action>Read `{status_file}` and collect:
1181
- - All completed stories grouped by epic, with their story titles
1182
- - Total story count, total epic count
1183
- - Final test count
1184
- - If git_enabled: all PR/MR URLs, patch counts, dismissed findings per story
1310
+ <action>**[CRITICAL 3/7] Release lock immediately** — before any slow operation:
1311
+ Run: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` ignore failures. Idempotent.
1185
1312
  </action>
1186
1313
 
1187
- <action>Read `{decision_log_file}` and collect:
1188
- - All decisions with impact `medium` or `high`
1189
- - Count of `review-accept` entries (patches applied)
1190
- - Count of `review-triage` entries (findings dismissed)
1191
- - Total review rounds (count of code-review invocations)
1192
- - Per-story summary: patches applied and findings dismissed
1314
+ <action>**[CRITICAL 4/7] Commit any final artifacts + planning docs to main** — even if the rest of step 10 is interrupted. The checkout is deliberately non-destructive: we never use `-B` or `-f` (those silently wipe local commits or modifications). If the working tree is dirty, the commit step still runs; if the checkout fails, the commit is skipped with a warning rather than forced.
1315
+ <check if="{{git_enabled}}">
1316
+ 1. `git switch {{base_branch}} 2>&1 || git checkout {{base_branch}} 2>&1` (plain switch; if `{{base_branch}}` doesn't exist locally, `git checkout --track origin/{{base_branch}}` once to create it tracking origin). Never use `-B` or `-f` — they discard work.
1317
+ 2. If `{{has_origin}}` is true: `git pull --ff-only origin {{base_branch}} 2>&1` (fast-forward only; if the pull would require a merge, log a warning and continue — do NOT `reset --hard`).
1318
+ 3. Stage artifacts via the cross-platform helper (replaces `git add … 2>/dev/null || true`):
1319
+ `node {{project_root}}/_Sprintpilot/scripts/git-portable.js safe-add _bmad-output/planning-artifacts _bmad-output/implementation-artifacts/sprint-status.yaml _bmad-output/implementation-artifacts/decision-log.yaml _bmad-output/implementation-artifacts/git-status.yaml _bmad-output/stories _bmad-output/implementation-artifacts/retrospectives --project-root "{{project_root}}"`
1320
+ The script filters paths to those that actually exist on disk before invoking git, so missing paths are skipped silently with no shell error suppression needed.
1321
+ 4. If `git diff --cached --quiet` exits non-zero: `git commit -m "docs: sprint artifacts — planning + stories + retrospective"` then (if `{{has_origin}}` is true) `git push origin {{base_branch}}` (warn on failure, do not halt).
1322
+ </check>
1193
1323
  </action>
1194
1324
 
1195
- <action>Find the app launch command by checking (in order):
1196
- 1. `run_gui.sh` or `run.sh` in the project root
1197
- 2. `main.py` in the project root
1198
- 3. Check `pyproject.toml`, `package.json`, or `setup.py` for scripts
1199
- Record as `{{launch_cmd}}`
1325
+ <action>**[CRITICAL 5/7] Mark sprint-complete state**: update `{state_file}`: `current_bmad_step = "sprint-complete"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`, `session_stories_done = {{session_stories_done}}`. This signals the test harness and any next /sprint-autopilot-on invocation that the sprint is genuinely done.</action>
1326
+
1327
+ <action>**[CRITICAL 6/7] Verify** run:
1328
+ `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
1329
+ 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.
1200
1330
  </action>
1201
1331
 
1202
- <!-- GIT: Final worktree cleanup safety net for any worktrees not cleaned during epic completion -->
1203
- <check if="{{git_enabled}}">
1204
- <action>**Cleanup all remaining worktrees**:
1205
- Run: `git worktree list --porcelain`
1206
- For each worktree that is NOT the main worktree, run the following — log and continue on failure; some worktrees may already be gone:
1207
- `git worktree remove <path> --force`
1208
- Then: `git worktree prune`
1209
- </action>
1210
- </check>
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. -->
1340
+ <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
+
1342
+ <!-- Close the last open `mark` phase so its duration gets recorded.
1343
+ Without this, the very last skill's duration would be lost (no
1344
+ 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>
1211
1346
 
1212
- <!-- GIT: Release lock -->
1213
- <check if="{{git_enabled}}">
1214
- <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release`</action>
1347
+ <action>Run full test suite — report `N/N passed`</action>
1348
+
1349
+ <!-- PR 6 SPRINT-COMPLETE FLUSH + ARCHIVE (no-op if coalescing is off). -->
1350
+ <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>
1215
1353
  </check>
1216
1354
 
1217
- <action>Delete `{state_file}` sprint complete</action>
1218
- <action>Mark master task "SprintpilotFull Sprint Execution" `completed`</action>
1355
+ <!-- Final phase-timing hotspot report (no-op if autopilot.phase_timings is false). -->
1356
+ <action>Run: `node {{project_root}}/_Sprintpilot/scripts/summarize-timings.js --format md --quiet --project-root "{{project_root}}"`ignore failures. The stdout line is the artifact path; include it in the sprint report if non-empty.</action>
1219
1357
 
1220
- <action>Report (use exact format):
1221
- ```
1222
- ╔═══════════════════════════════════════════════════════════════╗
1223
- ║ BMAD AUTOPILOTREPORT ║
1224
- ╚═══════════════════════════════════════════════════════════════╝
1225
-
1226
- SUMMARY
1227
- Stories completed : {{done_count}}/{{total_stories}}
1228
- Epics completed : {{done_epics}}/{{total_epics}}
1229
- Total tests : {{N}}/{{N}} passed
1230
- {{#if git_enabled}}
1231
- Platform : {{platform}}
1232
- {{/if}}
1358
+ <!-- Generate project documentation after sprint completion -->
1359
+ <action>**Resolve stack** — set `{{stack}}` = `{ name, install_cmd, run_cmd, test_cmd }` using the first successful source:
1360
+
1361
+ 1. **`project-context.md`** (glob `**/project-context.md`, canonical `{output_folder}/project-context.md`) extract from "Technology Stack & Versions" and any install/run/test subsections.
1362
+ 2. **`architecture.md`** (`{planning_artifacts}/architecture.md`) — extract from "Tech Stack" / "Runtime" / "Build & Deploy" / "Commands" sections.
1363
+ 3. **Manifest heuristics** — map manifest file → stack → idiomatic commands:
1364
+
1365
+ | Manifest | Stack | install / run / test |
1366
+ |---|---|---|
1367
+ | `package.json` | Node/JS/TS | `<pm> install` / `<pm> run start\|dev\|serve` (or `node <bin>`) / `<pm> test` — `<pm>` = `pnpm`/`yarn`/`bun`/`npm` by lockfile |
1368
+ | `pyproject.toml`, `requirements.txt`, `setup.py` | Python | `pip install -r requirements.txt` (or `-e .`) / Django `python manage.py runserver` → Flask → FastAPI `uvicorn app:app` → `python main.py`/`app.py` / `pytest` |
1369
+ | `go.mod` | Go | `go mod download` / `go run .` (or `./cmd/<name>`) / `go test ./...` |
1370
+ | `Cargo.toml` | Rust | `cargo build` / `cargo run` (or `--bin <name>`) / `cargo test` |
1371
+ | `pom.xml` | Java/Kotlin (Maven) | `mvn install` / `mvn spring-boot:run` or `mvn exec:java` / `mvn test` |
1372
+ | `build.gradle(.kts)` | Java/Kotlin (Gradle) | `./gradlew build` / `./gradlew bootRun` or `run` / `./gradlew test` |
1373
+ | `Gemfile` | Ruby | `bundle install` / `rails server` or `bundle exec ruby <entry>` / `bundle exec rspec` |
1374
+ | `*.csproj`/`*.sln` | .NET | `dotnet restore` / `dotnet run` (or `--project`) / `dotnet test` |
1375
+ | `composer.json` | PHP | `composer install` / `php artisan serve` (Laravel) or `php -S localhost:8000 -t public` / `vendor/bin/phpunit` |
1376
+ | `mix.exs` | Elixir | `mix deps.get` / `mix phx.server` or `mix run --no-halt` / `mix test` |
1377
+ | (none of the above) | Explicit launcher | `./run.sh`/`./run_gui.sh`/`./start.sh`, `make run\|start\|dev`, `docker compose up`, `docker build` + `docker run` |
1378
+
1379
+ 4. **No match** — all fields `null`. Downstream omits the line; never guess.
1380
+
1381
+ Set `{{launch_cmd}}` = `{{stack.run_cmd}}`.
1382
+ If `{{stack}}` came from (3) and `project-context.md` exists without stack info, log: "Consider running `bmad-generate-project-context` to capture stack commands."
1383
+ </action>
1233
1384
 
1234
- STORIES
1235
- {{#each epic}}
1236
- Epic {{epic_number}}: {{epic_title}}
1237
- {{#each stories}}
1238
- ✓ {{story-key}} — {{test_count}} tests{{#if pr_url}} PR: {{pr_url}}{{/if}}
1239
- {{/each}}
1240
- {{/each}}
1241
- {{#if remaining_stories}}
1242
- Not started:
1243
- {{#each remaining_stories}}
1244
- · {{story-key}}
1245
- {{/each}}
1246
- {{/if}}
1385
+ <action>**Generate documentation** — invoke `bmad-document-project`. If unavailable or it fails, write a minimal README using `{{stack}}`: project name + description (from brief/PRD); install/run/test lines for each non-null `{{stack.*_cmd}}` (omit lines where null); architecture overview if `architecture.md` exists.</action>
1247
1386
 
1248
- DECISIONS REQUIRING REVIEW (high/medium impact)
1249
- {{#each medium_high_decisions}}
1250
- #{{id}} [{{impact}}] {{story}} / {{phase}}
1251
- {{decision}}
1252
- {{rationale}}
1253
- {{/each}}
1254
- {{#if no_medium_high_decisions}}
1255
- None all decisions were low-impact.
1256
- {{/if}}
1387
+ <check if="{{git_enabled}}">
1388
+ <action>**Commit README + docs to main** (sprint artifacts already committed in CRITICAL 4/7 above — this picks up README.md / docs/ that were generated later by bmad-document-project). Non-destructive checkout — never `-B` or `-f`:
1389
+ 1. `git switch {{base_branch}} 2>&1 || git checkout {{base_branch}} 2>&1`.
1390
+ 2. If `{{has_origin}}` is true: `git pull --ff-only origin {{base_branch}} 2>&1` (warn on non-ff; do NOT reset).
1391
+ 3. Stage docs via the cross-platform helper:
1392
+ `node {{project_root}}/_Sprintpilot/scripts/git-portable.js safe-add README.md docs/ --project-root "{{project_root}}"`
1393
+ (paths are filtered to those that exist; missing paths are skipped).
1394
+ 4. If `git diff --cached --quiet` exits non-zero: `git commit -m "docs: project documentation"` then (if `{{has_origin}}`) `git push origin {{base_branch}}` (warn on push failure, do not halt).
1395
+ </action>
1396
+ </check>
1257
1397
 
1258
- Full log: {decision_log_file}
1398
+ <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>
1259
1399
 
1260
- REVIEW FINDINGS APPLIED
1261
- Patches applied : {{total_patches}}
1262
- Findings dismissed : {{total_dismissed}}
1263
- Review rounds : {{total_review_rounds}}
1400
+ <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. -->
1406
+ <action>**Restore main-repo gc.auto**:
1407
+ if `{{original_gc_auto_main}}` is "unset": `git config --local --unset gc.auto` (ignore failure — may already be unset).
1408
+ else: `git config --local gc.auto {{original_gc_auto_main}}`.
1409
+ </action>
1410
+ <action>Belt-and-suspenders: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` (already released earlier in step 10; this is idempotent and catches any race where the early release failed).</action>
1411
+ </check>
1264
1412
 
1265
- CODE REVIEW SUMMARY (per story)
1266
- {{#each completed_stories}}
1267
- {{story-key}} : {{patches_applied}} patches applied, {{findings_dismissed}} dismissed
1268
- {{/each}}
1413
+ <!-- State file deletion moved to CRITICAL 7/7 above for reliability. -->
1414
+ <action>Mark master task "Sprintpilot — Full Sprint Execution" → `completed`</action>
1269
1415
 
1270
- WHAT TO DO NEXT
1271
- 1. Review decisions marked medium/high above
1272
- {{#if has_pr_urls}}
1273
- 2. Merge open PRs: {{pr_urls_list}}
1274
- {{/if}}
1275
- {{#if launch_cmd}}
1276
- {{next_number}}. Run the app: {{launch_cmd}}
1277
- {{/if}}
1278
- {{next_number}}. Manual smoke test checklist:
1279
- {{#each completed_stories}}
1280
- · [{{story-key}}] {{smoke_test_suggestion}}
1281
- {{/each}}
1282
- ```
1283
- </action>
1416
+ <action>Read template `{{project_root}}/_Sprintpilot/templates/sprint-report.txt`, fill mustache placeholders with the collected data, and print the result verbatim as the final message.</action>
1284
1417
 
1285
1418
  </step>
1286
1419