@ikunin/sprintpilot 2.0.10 → 2.1.0

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 (42) hide show
  1. package/README.md +245 -10
  2. package/_Sprintpilot/Sprintpilot.md +1 -1
  3. package/_Sprintpilot/bin/autopilot.js +581 -0
  4. package/_Sprintpilot/lib/orchestrator/action-ledger.js +148 -0
  5. package/_Sprintpilot/lib/orchestrator/adapt.js +502 -0
  6. package/_Sprintpilot/lib/orchestrator/decision-log.js +224 -0
  7. package/_Sprintpilot/lib/orchestrator/divergence.js +201 -0
  8. package/_Sprintpilot/lib/orchestrator/git-plan.js +259 -0
  9. package/_Sprintpilot/lib/orchestrator/impact-classifier.js +108 -0
  10. package/_Sprintpilot/lib/orchestrator/land.js +155 -0
  11. package/_Sprintpilot/lib/orchestrator/parallel-batch.js +99 -0
  12. package/_Sprintpilot/lib/orchestrator/profile-rules.js +167 -0
  13. package/_Sprintpilot/lib/orchestrator/report.js +95 -0
  14. package/_Sprintpilot/lib/orchestrator/state-machine.js +402 -0
  15. package/_Sprintpilot/lib/orchestrator/state-store.js +260 -0
  16. package/_Sprintpilot/lib/orchestrator/user-command-applier.js +157 -0
  17. package/_Sprintpilot/lib/orchestrator/user-commands.js +115 -0
  18. package/_Sprintpilot/lib/orchestrator/verify.js +397 -0
  19. package/_Sprintpilot/manifest.yaml +1 -1
  20. package/_Sprintpilot/modules/git/config.yaml +26 -0
  21. package/_Sprintpilot/scripts/agent-adapter.js +4 -5
  22. package/_Sprintpilot/scripts/auto-merge-bmad-docs.js +112 -0
  23. package/_Sprintpilot/scripts/dispatch-layer.js +12 -8
  24. package/_Sprintpilot/scripts/infer-dependencies.js +78 -21
  25. package/_Sprintpilot/scripts/inject-tasks-section.js +4 -3
  26. package/_Sprintpilot/scripts/land-this-pr.js +110 -0
  27. package/_Sprintpilot/scripts/lint-test-pitfalls.js +133 -0
  28. package/_Sprintpilot/scripts/list-remaining-stories.js +1 -1
  29. package/_Sprintpilot/scripts/log-timing.js +12 -3
  30. package/_Sprintpilot/scripts/merge-shards.js +32 -12
  31. package/_Sprintpilot/scripts/post-green-gates.js +187 -0
  32. package/_Sprintpilot/scripts/preflight-merge.js +2 -1
  33. package/_Sprintpilot/scripts/resolve-dag.js +3 -1
  34. package/_Sprintpilot/scripts/stack-snapshot.js +128 -0
  35. package/_Sprintpilot/scripts/state-shard.js +8 -1
  36. package/_Sprintpilot/scripts/summarize-timings.js +30 -12
  37. package/_Sprintpilot/scripts/with-retry.js +17 -5
  38. package/_Sprintpilot/skills/sprint-autopilot-on/SKILL.md +23 -1
  39. package/_Sprintpilot/skills/sprint-autopilot-on/workflow.orchestrator.md +148 -0
  40. package/lib/core/update-check.js +11 -1
  41. package/package.json +1 -1
  42. package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +0 -1388
@@ -1,1388 +0,0 @@
1
- # Sprintpilot — ON (Enhanced with Git Workflow)
2
-
3
- ## Purpose
4
-
5
- You are now in **Sprintpilot Mode** with **git workflow integration**. Drive the project from its current state all the way to a working, tested, reviewed application — following BMAD's own workflow guidance at every step, with automatic git branching, commits, and PR creation.
6
-
7
- You do NOT hardcode the workflow sequence. After each completed skill, read its output for a "next steps" recommendation and follow that. Only when no clear next step is in the output do you consult `bmad-help`. BMAD's own output is the primary oracle; `bmad-help` is the fallback.
8
-
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
-
11
- ### Shell portability
12
-
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
-
15
- | Idiom | Where used | Portable across |
16
- |---|---|---|
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).
30
-
31
- Common cross-platform inline snippets:
32
-
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.
40
-
41
- ---
42
-
43
- ## AUTOPILOT RULES — READ BEFORE PROCEEDING
44
-
45
- ### Autonomous execution
46
- - After each skill completes, **read its output for a "Next Steps" or "What to do next" section** — that is the primary source for `{{next_skill}}`
47
- - Only invoke `bmad-help` when the completed skill's output contains no clear next step
48
- - Execute each recommended skill immediately — do not wait for user confirmation
49
- - Keep the Claude task list fully in sync with progress at all times
50
-
51
- ### Session management and compaction prevention — CRITICAL
52
- Long autopilot runs will fill the context window. To prevent state loss:
53
-
54
- - **All state lives in files, never only in memory.** After every step, write progress to `{state_file}`.
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.
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.
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.
59
-
60
- ### Menu and interaction handling — CRITICAL
61
- Many BMAD skills present interactive menus or ask for confirmation. In autopilot mode:
62
- - **"Continue" option (C)** → automatically select C, do not halt
63
- - **Numbered choices** → select the best option for the project context; document choice in one sentence
64
- - **"Create Mode"** → always choose it
65
- - **Yes/no confirmation** → always yes
66
- - **Open-ended creative question** → answer from PRD/architecture/existing docs; if truly unanswerable → TRUE BLOCKER
67
- - **NEVER halt at a menu** unless it is a TRUE BLOCKER
68
-
69
- ### Auto-accept all BMAD suggestions
70
- - Automatically apply ALL `patch`, `bugfix`, and code review findings
71
- - Never ask user for permission on any BMAD-suggested fix
72
- - Only skip a finding if it directly contradicts a story Acceptance Criterion (document why)
73
-
74
- ### Task list discipline
75
- - Before starting a story, create a task for **each** BMAD step. Track each skill as a task (`in_progress` → `completed`). Never batch updates.
76
- - Always state test results as `N/N passed` or `N passed, M failed` — never say "tests pass" without the count.
77
-
78
- ### True blockers — the ONLY reasons to pause and ask user
79
- 1. Required skill needs **original creative user input** not derivable from any existing artifact
80
- 2. A **new external dependency** is needed that is not in the project
81
- 3. **3 consecutive test failures** with no forward progress
82
- 4. A **security vulnerability** requiring architectural decision beyond the story scope
83
- 5. **Conflicting acceptance criteria** that cannot be resolved by reading project documents
84
-
85
- For everything else: decide, document briefly, continue.
86
-
87
- ### Conventions referenced throughout this file (hoisted to avoid per-site repetition)
88
-
89
- - **`log-timing.js` is fire-and-forget**: every `node …/log-timing.js …` invocation must never halt the autopilot on failure (treat exit codes as advisory).
90
- - **`resolve-profile.js get` falls back to documented default**: every `resolve-profile.js get <key>` call falls back to the documented default on non-zero exit; never halt.
91
- - **SYNC_STATUS_RULE**: `sync-status.js` does full block replacement. When updating an existing story entry in `{git_status_file}`, re-read its existing fields and pass ALL of them alongside the new values (branch, commit, patch_commits, push_status, pr_url, lint_result, worktree, platform, base_branch, worktree_cleaned). For a brand-new entry, pass at minimum `--branch` and the targeted status field.
92
- - **`{{has_origin}}` is false**: when this flag is false, skip every `git fetch origin` and `git push origin` call below; substitute `origin/{{base_branch}}` → `{{base_branch}}` for read operations. Do NOT repeat this qualifier per site.
93
- - **FINALIZE_HANDOFF macro** (sprint-finalize-pending checkpoint): write `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []` to `{state_file}`; release the autopilot lock idempotently (`lock.js release`); report sprint-complete-with-handoff to user; HALT this session. The next `/sprint-autopilot-on` invocation enters via step 1, sees the pending state, and runs step 10 with a clean window.
94
- - **FLUSH_SHARDS macro** (when `{{coalesce_state_writes}}` is true): run `state-shard.js flush --story sprint --project-root "{{project_root}}"`, then `merge-shards.js --project-root "{{project_root}}"`. Both ignore failures.
95
-
96
- ---
97
-
98
- ## DECISION LOGGING
99
-
100
- 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.
101
-
102
- **Categories:** `architecture`, `test-strategy`, `dependency`, `review-triage` (dismissed finding), `review-accept` (applied fix), `halt-recovery`, `scope` (outside story spec), `workaround`.
103
- **Impact:** `low` (reversible/cosmetic), `medium` (affects one component), `high` (cross-cutting or deviates from spec).
104
- **Phase format:** `{skill}:{sub_phase}` — e.g. `dev-story:RED`, `code-review:triage`, `autopilot:routing`.
105
-
106
- **File schema:**
107
- ```yaml
108
- generated: {date}
109
- last_updated: {datetime}
110
- decisions:
111
- - { id, timestamp, story, phase, category, decision, rationale, impact }
112
- ```
113
-
114
- ---
115
-
116
- ## SKILL AUTOMATABLE REFERENCE
117
-
118
- All BMAD skills are fully automatable (auto-continue past menus, derive decisions from existing artifacts) except:
119
-
120
- | Skill | Notes |
121
- |-------|-------|
122
- | `bmad-create-prd` | BLOCKER if no PRD — product vision must come from user |
123
- | `bmad-product-brief` | BLOCKER if no brief — requires user input |
124
- | `bmad-create-architecture` | Automatable if PRD exists; BLOCKER if no PRD |
125
- | `bmad-create-ux-design` | Automatable if PRD exists; BLOCKER if no PRD |
126
- | `bmad-party-mode` | Skip — inherently interactive |
127
- | `bmad-brainstorming` | Skip — inherently interactive |
128
- | `bmad-retrospective` | Under autopilot, handled per `autopilot.retrospective_mode`: `auto` (default — inline artifact, no external skill call), `stop` (pause so user runs `/bmad-retrospective` interactively, then resumes autopilot), or `skip` (not recommended). The external skill is NOT invoked from autopilot because it enters a multi-persona discussion loop under some CLIs. |
129
-
130
- ---
131
-
132
- ## INITIALIZATION
133
-
134
- Load config: `{project-root}/_bmad/bmm/config.yaml`
135
-
136
- Resolve:
137
- - `project_name`, `user_name` (user)
138
- - `planning_artifacts`, `implementation_artifacts`
139
- - `status_file` = `{implementation_artifacts}/sprint-status.yaml` (BMAD-owned — written by BMAD skills, not by autopilot)
140
- - `git_status_file` = `{implementation_artifacts}/git-status.yaml` (addon-owned, write git fields here)
141
- - `state_file` = `{implementation_artifacts}/autopilot-state.yaml`
142
- - `decision_log_file` = `{implementation_artifacts}/decision-log.yaml`
143
- - `project_root` = absolute path of current working directory (store for later use)
144
- - `session_story_limit` is loaded below from `modules/autopilot/config.yaml` (default: 3)
145
-
146
- **`{state_file}` schema** (referenced as `STATE_FIELDS` below): `last_updated`, `current_story`, `current_bmad_step`, `completed_skill`, `next_skill`, `session_stories_done`, `stories_remaining`, `git_enabled`, `platform`, `in_worktree`, `pr_base`. Always update `last_updated` on every write.
147
-
148
- **State-write policy (`autopilot.coalesce_state_writes`):**
149
-
150
- When the resolved profile sets `autopilot.coalesce_state_writes: true` (nano/small/medium/large by default; `legacy` false), state writes route through `state-shard.js` using a `sprint`-keyed shard as the authoritative state for sprint-level fields, and per-story shards for story-scoped fields. Policy:
151
-
152
- - **Critical keys** (`current_story`, `current_bmad_step`, `in_worktree`, `patch_commits`) always go to shard via `state-shard.js batch`, which auto-flushes and writes straight through because the script recognizes them as crash-recovery keys.
153
- - **Non-critical fields** (test counts, file lists, next_skill, session_stories_done, stories_remaining, etc.) go to `state-shard.js batch`, accumulating in the pending buffer. Flushed at each story boundary (step 7) and session checkpoint (step 9).
154
- - **Merged authoritative state** (`autopilot-state.yaml`) is rebuilt via `merge-shards.js` at story boundary + session checkpoint + sprint complete.
155
-
156
- When `coalesce_state_writes: false`, all state writes go directly to `autopilot-state.yaml` (legacy v1.0.5 path). When `true`, substitute each `Update {state_file} with STATE_FIELDS: <changes>` with a `state-shard.js batch --story sprint --json <changes>` call, followed by a `merge-shards.js --project-root "{{project_root}}"` at the story boundary / checkpoint.
157
-
158
- ### Git integration bootstrap
159
-
160
- <action>Check if `{project-root}/_Sprintpilot/manifest.yaml` exists</action>
161
-
162
- <check if="manifest exists">
163
- <action>Read `{project-root}/_Sprintpilot/manifest.yaml`</action>
164
- <action>Read `{project-root}/_Sprintpilot/modules/git/config.yaml`</action>
165
- <action>Set config variables from `git.*` fields (defaults in parentheses):
166
- - `{{git_enabled}}` from `git.enabled` (true)
167
- - `{{base_branch}}` from `git.base_branch` (main)
168
- - `{{branch_prefix}}` from `git.branch_prefix` ("story/")
169
- - `{{push_auto}}` from `git.push.auto` (true)
170
- - `{{create_pr}}` from `git.push.create_pr` (true)
171
- - `{{pr_template}}` from `git.push.pr_template` ("modules/git/templates/pr-body.md")
172
- - `{{cleanup_on_merge}}` from `git.worktree.cleanup_on_merge` (true)
173
- - `{{granularity}}` from `git.granularity` ("story"). Resolver override wins below.
174
- - `{{worktree_enabled}}` from `git.worktree.enabled` (true). Resolver override wins below.
175
- - `{{squash_on_merge}}` from `git.squash_on_merge` (false). Resolver override wins below.
176
- </action>
177
- <action>**Apply profile overrides** via resolver — run each and set only if the resolver returns a value:
178
- - `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.granularity` → override `{{granularity}}`.
179
- - `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.worktree.enabled` → override `{{worktree_enabled}}`.
180
- - `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.squash_on_merge` → override `{{squash_on_merge}}`.
181
- </action>
182
- <action>Read `{project-root}/_Sprintpilot/modules/autopilot/config.yaml` (if present) and set:
183
- - `{{session_story_limit}}` from `autopilot.session_story_limit` (default: 3). A value of 0 disables the limit (run until sprint complete).
184
- - `{{retrospective_mode}}` from `autopilot.retrospective_mode` (default: `auto`). Valid values: `auto` | `stop` | `skip`. Any unknown value falls back to `auto`.
185
- If the file or either key is missing, fall back to the defaults above.
186
- </action>
187
- <action>**Resolve profile-driven flow** — run:
188
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.implementation_flow`
189
- Output: `full` or `quick`. Set `{{implementation_flow}}` = output (default `full`).
190
- Run: `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.session_story_limit` → override `{{session_story_limit}}` if the resolver produces a different value than config.yaml (profile overrides config silence). Same pattern for `autopilot.retrospective_mode`.
191
- </action>
192
- <action>**Resolve coalesce flag** — run:
193
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.coalesce_state_writes` → `{{coalesce_state_writes}}` (default `false`).
194
- </action>
195
- <!-- Detect the running host and resolve parallel-dispatch config. -->
196
- <action>**Detect host agent** — run:
197
- `node {{project_root}}/_Sprintpilot/scripts/agent-adapter.js detect --project-root "{{project_root}}"`
198
- Parse the JSON output: set `{{host_agent}}` = host, `{{host_supports_parallel}}` = supports_parallel, `{{host_confidence}}` = confidence.
199
- </action>
200
- <action>**Resolve parallelism flags** via the profile resolver:
201
- - `{{parallel_stories}}` from `ma.parallel_stories` (default false).
202
- - `{{max_parallel_stories}}` from `ma.max_parallel_stories` (default 2).
203
- - `{{experimental_parallel_on_gemini}}` from `ma.experimental_parallel_on_gemini` (default false).
204
- </action>
205
- <!-- Gemini CLI opt-in: when the user explicitly sets
206
- experimental_parallel_on_gemini=true AND the detected host is
207
- gemini-cli at HIGH confidence, promote supports_parallel=true
208
- with a one-line warning. Worktree-scoped subagents aren't shipped
209
- upstream yet, so this is user-opt-in-per-project. -->
210
- <check if="{{experimental_parallel_on_gemini}} is true AND {{host_agent}} is gemini-cli AND {{host_confidence}} is high">
211
- <action>Set `{{host_supports_parallel}}` = true</action>
212
- <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>
213
- </check>
214
- <action>Silently coerce `{{parallel_stories}}` to false when `{{host_supports_parallel}}` is false OR `{{host_confidence}}` is not `high`. Log once:
215
- `parallel_stories requested but host '{{host_agent}}' does not declare parallel support (confidence={{host_confidence}}); running sequentially`.
216
- </action>
217
- </check>
218
-
219
- <check if="manifest does NOT exist">
220
- <action>Set `{{git_enabled}}` = false</action>
221
- <action>Set `{{session_story_limit}}` = 3</action>
222
- <action>Set `{{retrospective_mode}}` = `auto`</action>
223
- <action>Set `{{implementation_flow}}` = `full`</action>
224
- <action>Log: "No _Sprintpilot/manifest.yaml found — running stock autopilot (no git)"</action>
225
- </check>
226
-
227
- <check if="{{git_enabled}} is true">
228
- <action>Verify git repo: run `git rev-parse --git-dir`</action>
229
- <check if="not a git repo">
230
- <action>HALT: "No git repository found. Initialize one first:
231
- ```
232
- git init
233
- git add README.md .gitignore
234
- git commit -m 'initial commit'
235
- git remote add origin <your-repo-url>
236
- ```
237
- Then run /sprint-autopilot-on again."</action>
238
- <action>STOP</action>
239
- </check>
240
-
241
- <action>**Check for `origin` remote** — run: `git remote get-url origin`
242
- If the command fails (exit code != 0), no `origin` remote is configured. Set `{{has_origin}}` = false.
243
- Otherwise set `{{has_origin}}` = true.
244
- </action>
245
- <check if="{{has_origin}} is false">
246
- <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>
247
- <action>Set `{{push_auto}}` = false</action>
248
- <action>Set `{{create_pr}}` = false</action>
249
- <action>Set `{{platform}}` = "git_only"</action>
250
- </check>
251
-
252
- <!-- Disable gc.auto on the main repo so git's auto-GC doesn't race with
253
- concurrent worktree operations during the sprint. Save the prior
254
- value so we can restore it at sprint complete (step 10). -->
255
- <action>**Save + disable main-repo gc.auto**: set `{{original_gc_auto_main}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}"`, then `git -C {{project_root}} config --local gc.auto 0`.</action>
256
-
257
- <action>**Lock file** — run: `node {{project_root}}/_Sprintpilot/scripts/lock.js acquire`
258
- Output will be one of:
259
- - `ACQUIRED:<session-id>` → proceed
260
- - `ACQUIRED_STALE:<session-id>` → stale lock removed, proceed
261
- - `LOCKED:<session-id>:<age>` → another session active
262
- </action>
263
- <check if="LOCKED">
264
- <action>HALT: "Another autopilot session is active. Close it first or delete .autopilot.lock"</action>
265
- <action>STOP</action>
266
- </check>
267
-
268
- <action>**Detect platform** — run:
269
- `node {{project_root}}/_Sprintpilot/scripts/detect-platform.js --provider {{git.platform.provider}}`
270
- Output: `github`, `gitlab`, or `git_only`. Set `{{platform}}` to the output.
271
- Log: "Platform detected: {{platform}}"
272
- </action>
273
-
274
- <!-- Conditional boot work: a clean repo (main worktree only, zero
275
- in-progress stories) can skip the slow health-check + branch
276
- reconciliation below. Gate honored by non-legacy, non-large profiles
277
- (large keeps full reconciliation for compliance/uptime reasons). -->
278
- <action>Read `autopilot.conditional_boot_work` from the resolver:
279
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.conditional_boot_work` → `{{conditional_boot_work}}` (default `false`).
280
- </action>
281
- <action>Count worktrees (cross-platform; replaces a `... | grep -c` pipe that needed POSIX shell):
282
- `node {{project_root}}/_Sprintpilot/scripts/git-portable.js count-worktrees --project-root "{{project_root}}"` → `{{worktree_count}}`. The script fails open to 2 internally if git itself errors, matching the previous "fail-open to force full path" semantic.
283
- </action>
284
- <action>Count in-progress stories: read `{status_file}` and count stories whose status is NOT in {`done`, `backlog`}. Set `{{in_progress_count}}`. Fail-open to 1 (force full path) if the file is unreadable.</action>
285
-
286
- <check if="{{conditional_boot_work}} is true AND {{worktree_count}} is 1 AND {{in_progress_count}} is 0">
287
- <action>Log: "Boot fast-path: clean repo — skipping health-check + branch reconciliation"</action>
288
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js once --story "sprint" --phase "boot.fast-path" --meta "{\"reason\":\"clean-repo\"}" --project-root "{{project_root}}"`.</action>
289
- <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
290
- </check>
291
-
292
- <check if="NOT ({{conditional_boot_work}} is true AND {{worktree_count}} is 1 AND {{in_progress_count}} is 0)">
293
-
294
- <action>**Worktree health check** — run:
295
- `node {{project_root}}/_Sprintpilot/scripts/health-check.js --base-branch {{base_branch}} --status-file {{status_file}}`
296
- Output classifies each worktree as CLEAN_DONE, COMMITTED, STALE, DIRTY, or ORPHAN.
297
- - CLEAN_DONE: `git worktree remove .worktrees/<name>` + `git worktree prune`
298
- - COMMITTED: log "Recoverable work found for <name> — will push via git -C"
299
- Push the branch: `git -C .worktrees/<name> push -u origin <branch> 2>&1`
300
- If `{{create_pr}}` is true AND platform != git_only: create PR via `node {{project_root}}/_Sprintpilot/scripts/create-pr.js ...`
301
- If `{{create_pr}}` is false OR platform is git_only: merge directly. Run each as a separate command; **STOP and log the failure if any step fails — do not proceed past a failed step**:
302
- 1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
303
- 2. `git merge <branch> --no-edit`
304
- 3. `git push origin {{base_branch}}`
305
- Then remove worktree.
306
- - STALE: `git worktree remove .worktrees/<name> --force` + prune
307
- - DIRTY: warn user, ask how to proceed (stash/commit/discard)
308
- - ORPHAN: remove the directory cross-platform with `node -e "require('fs').rmSync('.worktrees/<name>', {recursive: true, force: true})"`, then `git worktree prune`
309
- </action>
310
-
311
- <action>**Branch reconciliation** — detect pushed-but-unmerged story branches.
312
- Skip this entire section if `{{has_origin}}` is false (no remote → nothing to reconcile).
313
- 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**:
314
- 1. `git fetch origin`
315
- 2. `git branch -r --list "origin/{{branch_prefix}}*"`
316
- For each remote branch:
317
- - Extract story-key from branch name (strip "origin/{{branch_prefix}}" prefix)
318
- - Look up story status in `{status_file}`
319
- - If status is NOT "done":
320
- - Check if branch has implementation commits beyond base:
321
- `git log --oneline origin/{{base_branch}}..origin/<branch> | head -5`
322
- - If commits exist:
323
- - Log: "RECOVERY: Found unmerged work for <story-key> on <branch>"
324
- - If `{{platform}}` is git_only OR `{{create_pr}}` is false:
325
- - Merge to base:
326
- `git checkout -B {{base_branch}} origin/{{base_branch}}`
327
- `git merge origin/<branch> --no-edit`
328
- `git push origin {{base_branch}}`
329
- - If merge fails: log warning, continue (branch is preserved on remote)
330
- - If merge succeeds:
331
- - Re-read `{status_file}` from HEAD (may now include story artifacts after merge)
332
- - Update `{git_status_file}` via sync-status.js: set `--merge-status "recovered"` for this story.
333
- See SYNC_STATUS_RULE at top.
334
- - If `{{platform}}` is NOT git_only (github, gitlab, bitbucket, gitea) AND `{{create_pr}}` is true:
335
- - Check if PR/MR already exists for this branch (platform-specific check via create-pr.sh or CLI)
336
- - If no PR: create one via `node {{project_root}}/_Sprintpilot/scripts/create-pr.js --platform {{platform}} ...`
337
- - Log: "PR created/found for <story-key>"
338
- - Update `{git_status_file}` via sync-status.js: set `--merge-status "pr_pending"` (see SYNC_STATUS_RULE).
339
- - If status IS "done" AND branch still exists AND `{{cleanup_on_merge}}` is true:
340
- - Log: "Stale remote branch: <branch> — story already done, cleaning up"
341
- - Delete remote branch (ignore failure — the branch may already be gone): `git push origin --delete <branch>`
342
- </action>
343
-
344
- <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
345
-
346
- </check><!-- end conditional-boot-work non-fast-path branch -->
347
- </check>
348
-
349
- ---
350
-
351
- <workflow>
352
-
353
- <step n="1" goal="Bootstrap: check for saved state or assess project from scratch">
354
-
355
- <action>Check if `{state_file}` exists</action>
356
-
357
- <check if="state_file EXISTS">
358
- <action>Read `{state_file}` fully</action>
359
- <action>Extract saved state:
360
- - `{{current_story}}` — story in progress when last session ended
361
- - `{{current_bmad_step}}` — BMAD step that was active (2–7), or one of the reserved values: `executing` (a skill is mid-run), `sprint-finalize-pending` (prior session checkpointed; this session's only job is step 10), `sprint-complete` (terminal marker written by step-10 CRITICAL 5/7).
362
- - `{{completed_skill}}` — last skill that ran
363
- - `{{next_skill}}` — next skill recommended at save time
364
- - `{{session_stories_done}}` = 0 (reset counter for new session)
365
- - `{{in_worktree}}` — whether we were in a worktree when session ended
366
- - `{{pr_base}}` — PR target branch (previous story branch or base_branch)
367
- </action>
368
- <action>Read `{status_file}` — note all stories already `done`</action>
369
- <action>Scan TaskList for orphaned in_progress tasks from the previous session.
370
- For each task with status `in_progress`:
371
- - If it is a retrospective task and the retro file already exists → mark `completed`
372
- - If it is a per-story step task and that story is `done` in `{status_file}` → mark `completed`
373
- - If it is the master "Sprintpilot" task → leave as `in_progress`
374
- - Otherwise → leave as `in_progress` and treat as resumption point
375
- </action>
376
- <action>Report to user:
377
- ```
378
- Sprintpilot ON — Resuming
379
-
380
- Restored from: {state_file}
381
- Resuming story: {{current_story}}
382
- Resuming at step: {{current_bmad_step}}
383
- Next skill: {{next_skill}}
384
- Git integration: {{git_enabled}}
385
- ```
386
- </action>
387
- <action>**Post-resume reconciliation** — sync state with git reality.
388
- - Re-read `{status_file}` (may have been updated by boot branch reconciliation)
389
- - Recalculate `{{stories_remaining}}` by scanning all story keys where status != "done"
390
- - If `{{current_story}}` is now "done" in `{status_file}` (merged during reconciliation):
391
- - Log: "Story {{current_story}} was recovered from remote — skipping to next"
392
- - Set `{{current_story}}` = null
393
- - Set `{{next_skill}}` = next appropriate skill for first non-done story
394
- - If `{{next_skill}}` targets a story that is now "done":
395
- - Advance to next non-done story
396
- - Update `{state_file}` with reconciled values
397
- </action>
398
-
399
- <!-- Already-completed short-circuit: prior session wrote sprint-complete
400
- (CRITICAL 5/7) but crashed before delete (CRITICAL 7/7). Exit cleanly. -->
401
- <check if="{{current_bmad_step}} is sprint-complete">
402
- <action>Delete `{state_file}` — clean up the stale terminal marker.</action>
403
- <action>Report: "Sprint already complete. Nothing to do — run `bmad-sprint-planning` to plan the next sprint, then re-run `/sprint-autopilot-on`." Then STOP.</action>
404
- </check>
405
-
406
- <!-- Fresh-context finalize path. See AUTOPILOT RULES (context-rot mitigation).
407
- Verify sprint is still complete before jumping; if new work appeared,
408
- fall through to the normal loop. -->
409
- <check if="{{current_bmad_step}} is sprint-finalize-pending">
410
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope` — parse stdout JSON.</action>
411
- <check if="parsed .state is sprint-complete">
412
- <action>Log: "Resuming for sprint finalization with fresh context (context-rot mitigation)."</action>
413
- <goto step="10">Run finalization</goto>
414
- </check>
415
- <check if="parsed .state is NOT sprint-complete">
416
- <action>Log: "sprint-finalize-pending flag was set but new work appeared since checkpoint. Clearing flag and resuming normal execution."</action>
417
- <action>Update `{state_file}`: `current_bmad_step = null`.</action>
418
- </check>
419
- </check>
420
-
421
- <!-- Resume from a `retrospective_mode: stop` pause. -->
422
- <check if="{state_file}.paused_at is epic-complete-awaiting-retrospective">
423
- <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>
424
- <check if="epic is done OR retrospective artifact exists">
425
- <action>Clear `paused_at`, `paused_epic_id`, `next_action` from `{state_file}`. Log: "Epic {{paused_epic_id}} retrospective detected — resuming autopilot".</action>
426
- </check>
427
- <check if="epic is NOT done AND no retrospective artifact">
428
- <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>
429
- </check>
430
- </check>
431
-
432
- <goto step="2">Jump to execution loop with reconciled state</goto>
433
- </check>
434
-
435
- <check if="state_file does NOT exist">
436
- <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>
437
-
438
- <check if="{{git_enabled}} AND status_file did not exist AND {{next_skill}} is bmad-sprint-planning (planning just completed earlier in this flow)">
439
- <action>Run `git fetch origin` — warn + skip on failure (no remote/auth/network), do not abort bootstrap.</action>
440
- <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>
441
- </check>
442
-
443
- <action>Read `{status_file}` — find all stories not yet `done`</action>
444
- <action>Invoke `bmad-help` — "What is the current project state and next required workflow step?"</action>
445
- <action>Extract:
446
- - `{{current_phase}}` — lifecycle phase
447
- - `{{next_skill}}` — next required skill
448
- - `{{session_stories_done}}` = 0
449
- </action>
450
- <action>Create master task: "Sprintpilot — Full Sprint Execution" → `in_progress`</action>
451
- <action>**Compute `{{stories_remaining}}`** deterministically — run:
452
- `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
453
- The helper always emits a well-formed JSON object on stdout, on every exit path:
454
- `{"remaining":[...],"state":"pre-planning|sprint-in-progress|sprint-complete|parse-error"}`.
455
- Parse stdout as JSON → set `{{stories_remaining}}` = `.remaining`.
456
- Ignore the exit code: `.state` is authoritative and covers every case (missing file, parse error, or normal).
457
- </action>
458
- <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>
459
- <action>Report to user:
460
- ```
461
- Sprintpilot ON
462
-
463
- Phase: {{current_phase}}
464
- First step: {{next_skill}}
465
- Git integration: {{git_enabled}}
466
- Platform: {{platform}}
467
- Session limit: {{session_story_limit}} stories, then checkpoint + new session
468
- Retrospective mode: {{retrospective_mode}}
469
-
470
- Beginning autonomous execution. I will only stop for true blockers or session checkpoints.
471
- ```
472
- </action>
473
- </check>
474
-
475
- </step>
476
-
477
-
478
- <step n="2" goal="Main execution loop — route to correct handler">
479
-
480
- <!-- CROSS-EPIC PARALLELISM (experimental, off by default on every profile
481
- including `large`). All safety rails must pass:
482
- 1. ma.parallel_epics is true.
483
- 2. Host confidence is HIGH AND supports_parallel is true (same as
484
- the intra-epic gate).
485
- 3. Two or more epics in dependencies.yaml declare `independent: true`.
486
- 4. preflight-merge.js reports NO conflicts between all pairs.
487
- 5. Session-scoped disable flag {{cross_epic_disabled_this_session}}
488
- is false (flips true after any cross-epic merge conflict).
489
- Only on the first iteration of the loop — subsequent iterations
490
- don't re-preflight; they consume the cached safe_pairs list. -->
491
- <action>Resolve `{{parallel_epics}}` from `ma.parallel_epics` (default false) via the resolver.</action>
492
- <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">
493
- <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>
494
- <check if="{{independent_epic_ids}} has fewer than 2 ids">
495
- <action>Log once: "cross-epic parallelism enabled but fewer than 2 epics declare `independent: true` in dependencies.yaml — running sequentially"</action>
496
- <action>Set `{{cross_epic_preflight_done}}` = true, `{{cross_epic_safe_pairs}}` = []</action>
497
- </check>
498
- <check if="{{independent_epic_ids}} has 2 or more ids">
499
- <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>
500
- <action>Log: "EXPERIMENTAL: parallel_epics preflight → safe={{cross_epic_safe_pairs.length}} conflict={{cross_epic_conflict_pairs.length}} checked=N"</action>
501
- <action>Set `{{cross_epic_preflight_done}}` = true</action>
502
- </check>
503
- </check>
504
-
505
- <!-- Authoritative "sprint complete" check. Read the status file EVERY
506
- iteration via the coded helper (do not rely on stale state or LLM
507
- re-parsing). The helper emits a JSON envelope on stdout on every
508
- exit path, so there is no need to probe $? or stderr — .state is
509
- authoritative. -->
510
- <action>**Recalculate `{{stories_remaining}}`** — run:
511
- `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
512
- Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
513
- - `.state == "sprint-in-progress"` → `{{stories_remaining}}` = `.remaining`, `{{sprint_has_stories}}` = true, `{{sprint_is_complete}}` = false.
514
- - `.state == "sprint-complete"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = true, `{{sprint_is_complete}}` = true.
515
- - `.state == "pre-planning"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = false, `{{sprint_is_complete}}` = false.
516
- - `.state == "parse-error"` → log warning; treat as pre-planning. NEVER declare sprint-complete on a parse failure.
517
- </action>
518
- <!-- Sprint-complete handoff: see FINALIZE_HANDOFF macro at top + AUTOPILOT RULES (context-rot mitigation). -->
519
- <check if="{{sprint_is_complete}} is true">
520
- <action>Update `{state_file}` with STATE_FIELDS: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`, `session_stories_done = {{session_stories_done}}`.</action>
521
- <action>Release autopilot lock (idempotent): `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
522
- <action>Report to user and STOP this session:
523
- ```
524
- Autopilot sprint-complete checkpoint
525
-
526
- All stories are done. Pausing before finalization so step 10 runs
527
- with a fresh context (see AUTOPILOT RULES — context-rot mitigation).
528
-
529
- Completed {{session_stories_done}} stories this session.
530
- State saved to: {state_file}
531
-
532
- Run `/sprint-autopilot-on` once more to finalize.
533
- ```
534
- </action>
535
- <action>HALT — do not continue the execution loop.</action>
536
- </check>
537
- <check if="{{sprint_has_stories}} is false">
538
- <action>Log: "Sprint pre-planning: no stories in status file yet. Routing through bmad-help to the next planning skill (do NOT go to step 10)."</action>
539
- </check>
540
-
541
- <check if="{{next_skill}} is empty">
542
- <action>**Recover next_skill** — re-read `{status_file}`, find first story with status != "done"</action>
543
- <check if="no undone stories found AND {{sprint_has_stories}} is true">
544
- <!-- FINALIZE_HANDOFF (see top of file). -->
545
- <action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
546
- <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
547
- <action>Report: "Sprint complete detected via fallback path. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
548
- </check>
549
- <action>If `{{sprint_has_stories}}` is true: set `{{current_story}}` = first undone story from `{status_file}`.</action>
550
- <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>
551
- <action>Extract `{{next_skill}}` from bmad-help response</action>
552
- </check>
553
-
554
- <check if="{{next_skill}} is in SKIP list (party-mode, brainstorming)">
555
- <action>Log: "Skipping {{next_skill}} — interactive-only, not autopilot-compatible"</action>
556
- <action>Re-read last skill output or invoke `bmad-help` for the next skill after this one</action>
557
- <action>Update `{{next_skill}}` → loop</action>
558
- <goto step="2">Re-evaluate</goto>
559
- </check>
560
-
561
- <check if="{{next_skill}} is a BLOCKER skill AND its artifact does NOT exist">
562
- <action>Update `{state_file}`: set `next_skill = {{next_skill}}`</action>
563
- <action>Report:
564
- ```
565
- Autopilot paused — human input required
566
-
567
- Next required step: {{next_skill}}
568
- This requires your creative input (product vision / business goals)
569
- that cannot be derived from existing project documents.
570
-
571
- Please complete this step manually, then run /sprint-autopilot-on to resume.
572
- State saved to: {state_file}
573
- ```
574
- </action>
575
- <action>STOP</action>
576
- </check>
577
-
578
- <check if="{{next_skill}} is a BLOCKER skill AND its artifact already exists">
579
- <action>Log: "Artifact for {{next_skill}} already exists — treating as complete, moving on"</action>
580
- <action>Re-read last skill output or invoke `bmad-help` for next skill</action>
581
- <action>Update `{{next_skill}}` → loop</action>
582
- <goto step="2">Re-evaluate</goto>
583
- </check>
584
-
585
- <goto step="3">Execute the skill</goto>
586
-
587
- </step>
588
-
589
-
590
- <step n="3" goal="Prepare and execute the recommended skill">
591
-
592
- <!-- PARALLEL DISPATCH (gates):
593
- - autopilot.parallel_stories: true (resolved at boot into
594
- {{parallel_stories}})
595
- - host_supports_parallel: true with high confidence (only Claude
596
- Code today, via agent-adapter.js)
597
- - implementation_flow != quick (nano runs sequentially per epic)
598
- - {{next_skill}} starts a fresh per-story flow (bmad-create-story
599
- OR bmad-dev-story OR bmad-quick-dev) — never mid-story
600
- - The current epic's DAG layer has width >= 2 (computed from
601
- _Sprintpilot/sprints/dependencies.yaml via resolve-dag.js)
602
-
603
- When all gates pass, the autopilot dispatches every story in the
604
- current layer concurrently via the host's Agent tool — instead of
605
- picking one story and running the full per-story flow sequentially. -->
606
- <check if="{{parallel_stories}} is true AND {{host_supports_parallel}} is true AND {{implementation_flow}} is NOT quick AND ({{next_skill}} is bmad-create-story OR {{next_skill}} is bmad-dev-story OR {{next_skill}} is bmad-quick-dev)">
607
- <action>**Compute current DAG layer.** Set `{{epic_id}}` = leading numeric segment of the FIRST undone story in `{status_file}`. Run:
608
- `node {{project_root}}/_Sprintpilot/scripts/resolve-dag.js layers --epic "{{epic_id}}" --project-root "{{project_root}}"`
609
- 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>
610
- <check if="{{active_layer}} length is 1">
611
- <action>Single-story layer — no parallel benefit. Continue with normal sequential dispatch below (the standard step-3 flow picks the single story).</action>
612
- </check>
613
- <check if="{{active_layer}} length is 2 or more">
614
- <action>**Parallel dispatch:** run:
615
- `node {{project_root}}/_Sprintpilot/scripts/dispatch-layer.js --layer "{{active_layer | join(',')}}" --max-parallel "{{max_parallel_stories}}" --project-root "{{project_root}}" --branch-prefix "{{branch_prefix}}" --base-branch "{{base_branch}}"`
616
- Parse stdout — set `{{dispatched_stories}}` = the list of stories from `stories[*]` where `created === true` (typically the first `effective_parallel` entries; partial-failure runs may have fewer). Set `{{deferred_stories}}` = the response's `deferred` field (keys not dispatched in this batch — picked up by the next loop iteration).</action>
617
- <check if="{{dispatched_stories}} length is 0">
618
- <action>**Dispatch failed entirely** — `dispatch-layer.js` produced no successful worktrees. Report the per-story stderr from the response and HALT (this is a TRUE BLOCKER per AUTOPILOT RULES). Worktrees that succeeded mid-batch were rolled back by the script.</action>
619
- </check>
620
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "dispatch.layer-{{epic_id}}" --project-root "{{project_root}}"`.</action>
621
- <action>**Spawn N concurrent sub-agents — IMPORTANT: send a SINGLE message containing N parallel Agent tool calls (one per story in `{{dispatched_stories}}`).** Do NOT serialize. The number of sub-agents equals `{{dispatched_stories}}.length` (capped by `--max-parallel` via `dispatch-layer.js`); deferred stories are picked up automatically when the loop re-enters step 2 after this batch. Each sub-agent runs the FULL per-story flow for its assigned story (bmad-create-story → bmad-check-implementation-readiness → bmad-dev-story → bmad-code-review → patches → mark done) inside its own worktree. The Agent tool calls run concurrently and the message reply contains all sub-agent results.
622
-
623
- For each story key `K` in `{{dispatched_stories}}`, build one Agent call with `subagent_type=general-purpose` and a self-contained prompt of the form:
624
- ```
625
- You are running a single story for the Sprintpilot autopilot.
626
- - Project root: {{project_root}}
627
- - Story key: K
628
- - Worktree: {{project_root}}/.worktrees/K (cd into it for all work)
629
- - 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}
630
- - Skill flow (quick): bmad-quick-dev (single skill; nano profile)
631
- Use {{implementation_flow}} = `{{implementation_flow}}` to pick which flow.
632
- Track timing via `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story K --phase <phase> --project-root {{project_root}}` after each skill returns. The explicit `--project-root` is REQUIRED — without it the script falls back to cwd (the worktree), which orphans timing data. With per-story markers (2.0.5+) concurrent sub-agents writing to the same project root no longer race.
633
- Return a one-line JSON summary on completion: {"story":"K", "status":"done"|"failed", "tests":"<N/M>", "notes":"<short>"}
634
- ```
635
-
636
- 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>
637
- <action>**Merge per-story shards** after the layer completes:
638
- `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — collapses the per-story state shards into the authoritative project YAMLs and archives the layer's shards under `.archive/layer-<timestamp>/` so the next layer starts clean.</action>
639
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "_end" --project-root "{{project_root}}"` — closes the open `dispatch.layer-{{epic_id}}` mark.</action>
640
- <action>Update `{state_file}` with the new STATE_FIELDS (each completed story's status is reflected via the merge above; advance `session_stories_done` by `{{dispatched_stories}}.length`). Then `goto step=2` to re-evaluate the loop — any `{{deferred_stories}}` (keys not in this batch because `--max-parallel` capped it) become the next iteration's active_layer.</action>
641
- <goto step="2">Re-evaluate after parallel layer dispatch</goto>
642
- </check>
643
- </check>
644
-
645
- <!-- Nano routing: when implementation_flow is 'quick', route bmad-dev-story
646
- through bmad-quick-dev. Quick-dev runs Implement → Review → Classify →
647
- Commit internally (BMad step-oneshot.md), so bmad-create-story /
648
- bmad-check-readiness / bmad-code-review are not invoked in this flow. -->
649
- <check if="{{implementation_flow}} is quick AND {{next_skill}} is bmad-dev-story">
650
- <action>Override `{{next_skill}}` = `bmad-quick-dev`</action>
651
- <action>Log: "Routing {{current_story}} through bmad-quick-dev per nano profile (implementation_flow=quick)"</action>
652
- </check>
653
- <!-- Under quick flow, autopilot never invokes bmad-create-story or
654
- bmad-check-implementation-readiness; quick-dev reads AC from
655
- sprint-status.yaml directly. If bmad-help proposes these skills
656
- while implementation_flow=quick, skip them and advance. -->
657
- <check if="{{implementation_flow}} is quick AND ({{next_skill}} is bmad-create-story OR {{next_skill}} is bmad-check-implementation-readiness)">
658
- <action>Log: "Skipping {{next_skill}} under quick flow (nano profile) — quick-dev reads AC directly"</action>
659
- <action>Set `{{next_skill}}` = `bmad-quick-dev`</action>
660
- </check>
661
-
662
- <action>Set `{{completed_skill}}` = `{{next_skill}}`</action>
663
- <action>Create task "{{next_skill}}" → mark `in_progress`</action>
664
-
665
- <check if="{{next_skill}} is a per-story skill (bmad-dev-story, bmad-quick-dev, bmad-code-review, bmad-create-story)">
666
- <action>Set `{{current_story}}` = first story in `{status_file}` with status `ready-for-dev` or `in-progress`</action>
667
-
668
- <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`).
669
- If `{{current_story}}` matches only `^\d+-\d+$` (numeric only, no title):
670
- - Find the story file in `{implementation_artifacts}` matching `{{current_story}}-*.md` or `story-{{current_story}}*.md`
671
- - If found: extract the full kebab-case name from the filename and update `{{current_story}}`
672
- - If not found: read the epics file, find the matching story title, convert to kebab-case, and update `{{current_story}}`
673
- - Update `{{current_story}}` variable to use the full name (do NOT modify sprint-status.yaml — it is BMAD-owned)
674
- A short key like `1-1` produces branches named `story/1-1` and PRs with no description — the title is essential for human-readable git history.</critical>
675
-
676
- <action>Create per-story step tasks if not already created</action>
677
- </check>
678
-
679
- <!-- Determine per-story epic key + title so the workflow can branch
680
- per-epic under granularity=epic and decide "is this the last story
681
- of the epic". Epic ID is the leading numeric segment of the story
682
- key (e.g. '1-2-foo' → '1'); slug is the epic's title from
683
- sprint-status.yaml (or the epic header in the epics file). -->
684
- <action>Set `{{epic_id}}` = leading numeric segment of `{{current_story}}` (e.g. `1-2-foo` → `1`). If the key doesn't match `^\d+-`, leave `{{epic_id}}` = "".</action>
685
- <action>Set `{{epic_branch_name}}` = `epic-{{epic_id}}` (only used when `{{granularity}} = epic`).</action>
686
- <action>**Detect first vs last story of epic** — read `{status_file}` (BMAD-owned; do not modify). Find all stories with the same `{{epic_id}}`:
687
- - `{{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).
688
- - `{{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`).
689
- Both default to true when `{{epic_id}}` = "" (single-story "epic").
690
- </action>
691
-
692
- <!-- GIT: Enter worktree OR create/reuse epic branch before dev-story OR quick-dev.
693
- Nano's profile sets worktree.enabled=false + granularity=epic, so this
694
- block falls through to in-place branching. -->
695
- <check if="{{git_enabled}} AND ({{next_skill}} is bmad-dev-story OR {{next_skill}} is bmad-quick-dev)">
696
- <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>
697
-
698
- <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>
699
-
700
- <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.
701
-
702
- 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}}`.
703
- - If unmerged previous story exists (requires `{{has_origin}}`): `git checkout origin/{{branch_prefix}}<prev-branch>`, set `{{pr_base}}` = `{{branch_prefix}}<prev-branch>`.
704
- - Otherwise: `git checkout origin/{{base_branch}}` (or local `{{base_branch}}` if no origin), set `{{pr_base}}` = `{{base_branch}}`.
705
-
706
- Detached HEAD is fine — `git worktree add` below creates a new branch from HEAD.
707
- </action>
708
-
709
- <!-- Epic granularity: share one branch per epic instead of one per story.
710
- Under worktree.enabled=false + granularity=epic (nano default),
711
- subsequent stories of the same epic check out the epic branch in
712
- place and commit there; no worktree is created. -->
713
- <check if="{{granularity}} is epic">
714
- <action>Override `{{branch_name}}` = `{{epic_branch_name}}` (shared across all stories in this epic).</action>
715
- <check if="{{is_first_story_of_epic}} is true">
716
- <action>Log: "Epic granularity: creating epic branch {{branch_prefix}}{{epic_branch_name}} for the first story of epic {{epic_id}}"</action>
717
- </check>
718
- <check if="{{is_first_story_of_epic}} is false">
719
- <action>Log: "Epic granularity: reusing existing epic branch {{branch_prefix}}{{epic_branch_name}} for story {{current_story}}"</action>
720
- </check>
721
- </check>
722
-
723
- <check if="{{worktree_enabled}} is false">
724
- <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>
725
- </check>
726
-
727
- <check if="{{worktree_enabled}} is true">
728
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"`.</action>
729
- <action>**Create worktree.** Try: `git worktree add "{{project_root}}/.worktrees/{{current_story}}" -b "{{branch_prefix}}{{branch_name}}" 2>&1`. If it fails because the branch already exists, retry without `-b`: `git worktree add "{{project_root}}/.worktrees/{{current_story}}" "{{branch_prefix}}{{branch_name}}" 2>&1`.
730
-
731
- If both fail (disk/permissions): log "WARN: worktree add failed — continuing without isolation", set `{{in_worktree}}` = false, and fall back to branch-only mode: `git checkout -b {{branch_prefix}}{{branch_name}}` (retry without `-b` if branch exists). HALT only if the checkout also fails. Git push/PR still work on the branch.
732
- </action>
733
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"`.</action>
734
- </check>
735
-
736
- <check if="{{worktree_enabled}} is true AND worktree add succeeded">
737
- <action>`cd {{project_root}}/.worktrees/{{current_story}}`. All subsequent commands run from here. Set `{{worktree_path}}` = this path.</action>
738
- <action>**Disable gc.auto on this worktree**: save original value `{{original_gc_auto_worktree}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}/.worktrees/{{current_story}}"`, then `git -C {{project_root}}/.worktrees/{{current_story}} config --local gc.auto 0`.</action>
739
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"`.</action>
740
- <action>**Init submodules** if `.gitmodules` exists (file-exists check via `node -e "process.exit(require('fs').existsSync('.gitmodules')?0:1)"`). Fast-path:
741
- Resolve the main repo's common git dir into a captured variable `{{git_common}}` (cross-platform; replaces a `$(...)` shell substitution):
742
- `node {{project_root}}/_Sprintpilot/scripts/git-portable.js common-dir --project-root "{{project_root}}"`. Trim trailing whitespace; the script exits 1 if git can't resolve the dir — log a warning and skip the submodule fast-path, falling through to the degraded-mode branch below.
743
- For each submodule path in `.gitmodules`:
744
- 1. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js acquire --submodule "<path>" --project-root "{{project_root}}"` — serializes concurrent submodule updates across worktrees.
745
- 2. With git ≥ 2.18 (confirmed at boot by check-prereqs): wrap the update with retry to survive ref-lock contention:
746
- `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>`
747
- With older git (degraded mode flagged by check-prereqs): fall back to `git -C ... submodule update --init --recursive -- <path>`.
748
- 3. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js release --submodule "<path>" --project-root "{{project_root}}"` (best-effort, ignore failures).
749
- If the loop fails or hangs: warn "Submodule init failed (may need auth). Continuing." and proceed.
750
- </action>
751
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"`.</action>
752
- <action>Set `{{in_worktree}}` = true</action>
753
- </check>
754
- <action>Update `{state_file}` (write to the worktree copy since cwd is now the worktree)</action>
755
- </check>
756
-
757
- <action>Update `{state_file}` with STATE_FIELDS (set `current_bmad_step = executing`, `completed_skill = <previous skill>`).</action>
758
- <check if="{{coalesce_state_writes}} is true">
759
- <action>Mirror critical keys to the shard (bypasses batching for crash-recovery correctness):
760
- `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.
761
- </action>
762
- </check>
763
-
764
- <!-- Autopilot menu handling rules apply — see AUTOPILOT RULES section above -->
765
-
766
- <!-- PHASE TIMING: emit start/end around every skill invocation.
767
- Use `{{current_story}}` when set, else the sentinel `sprint` for
768
- sprint-level skills (bmad-help, bmad-sprint-planning, etc).
769
- The script is a silent no-op when autopilot.phase_timings is false. -->
770
- <action>Set `{{timing_story}}` = `{{current_story}}` if non-empty, else `sprint`.</action>
771
- <!-- Single-call timing via `mark`. The previous start/INVOKE/end triplet
772
- was reliably broken in long sessions (LLM skipped the `end` call
773
- ~50% of the time), so most skills had no duration recorded. `mark`
774
- auto-computes the duration of the PREVIOUS phase from a small marker
775
- file — one call per transition, no open-phase failure mode. -->
776
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{timing_story}}" --phase "skill.{{next_skill}}" --project-root "{{project_root}}"`.</action>
777
- <action>INVOKE `{{next_skill}}` skill using the Skill tool</action>
778
- <action>Mark task "{{next_skill}}" as `completed`</action>
779
-
780
- <goto step="4">Handle completion</goto>
781
-
782
- </step>
783
-
784
-
785
- <step n="4" goal="Handle skill completion and route to next action">
786
-
787
- <!-- Quick-dev completion handler (nano routing).
788
- Quick-dev's one-shot (step-oneshot.md:44) already ran Implement →
789
- Review → Classify → Commit internally, so autopilot skips the
790
- external bmad-code-review step and jumps straight to step 7.
791
- Escalation safety net: if tests fail or classify severity is high,
792
- flip implementation_flow to full for the rest of the session. -->
793
- <check if="{{completed_skill}} was bmad-quick-dev">
794
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
795
- <action>Verify tests ran — if not, run them now: report `N/N passed`. Record pass/fail into `{{tests_passed}}`.</action>
796
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
797
- <action>Read quick-dev's Classify severity from its stdout/output. If its output mentions `severity: high` or a failing classify, set `{{quickdev_severity_high}}` = true.</action>
798
- <check if="{{tests_passed}} is false OR {{quickdev_severity_high}} is true">
799
- <action>**Escalation** — flip the session-scoped flow to `full`: set `{{implementation_flow}}` = `full`. Do NOT write this back to config.yaml; it is session-only. Log decision: `category=scope, phase=autopilot:escalation, impact=medium, "nano story {{current_story}} triggered fallback (tests_passed={{tests_passed}}, severity_high={{quickdev_severity_high}}) — subsequent stories use full cycle"` to `{decision_log_file}`.</action>
800
- </check>
801
- <action>Set `{{next_skill}}` = "(none)" — quick-dev handled review + commit internally per BMad step-oneshot.md.</action>
802
- <goto step="7">Mark story done</goto>
803
- </check>
804
-
805
- <check if="{{completed_skill}} was bmad-dev-story">
806
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
807
- <action>Verify tests ran — if not, run them now: report `N/N passed`</action>
808
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"`.</action>
809
- <action>**Log decisions** — review implementation choices made during dev-story and append entries to `{decision_log_file}` for any architecture, test-strategy, dependency, scope, or workaround decisions (see DECISION LOGGING section)</action>
810
-
811
- <!-- GIT: Lint, stage, and commit after dev-story -->
812
- <check if="{{git_enabled}} AND {{in_worktree}}">
813
- <action>**Lint changed files** — run:
814
- `node {{project_root}}/_Sprintpilot/scripts/lint-changed.js --limit 100 --output-file lint-output.txt`
815
- Log the output summary (non-blocking — lint never halts the autopilot).
816
- Set `{{lint_result}}` from the summary line.
817
- </action>
818
-
819
- <action>**Stage and commit** — resolve commit message placeholders using `commit_placeholder_resolution` chain from config:
820
- - `{story-key}` → from sprint-status.yaml development_status key (= `{{current_story}}`)
821
- - `{epic}` → from story file epic header, fallback to story-key prefix (e.g., "1" from "1-3")
822
- - `{story-title}` → from story file title, fallback to story-key
823
- - `{patch-title}` → from review finding title, fallback to "code review fix"
824
- Read the commit template from `git.commit_templates.story` in config (default: `feat({epic}): {story-title} ({story-key})`).
825
- Then run:
826
- `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "git.commit" --project-root "{{project_root}}"` (ignore failures), then
827
- `node {{project_root}}/_Sprintpilot/scripts/stage-and-commit.js --message "feat({{epic}}): {{story-title}} ({{current_story}})" --allowlist {{project_root}}/_Sprintpilot/.secrets-allowlist`, then
828
- `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "git.commit" --project-root "{{project_root}}"` (ignore failures).
829
- Output: commit SHA. Set `{{story_commit}}` = output.
830
- Warnings (secrets, large files) printed to stderr — review but don't halt unless user says to.
831
- </action>
832
-
833
- </check>
834
-
835
- <action>Set `{{next_skill}}` = `bmad-code-review` (mandatory after dev-story)</action>
836
- <goto step="8">Save state and continue</goto>
837
- </check>
838
-
839
- <check if="{{completed_skill}} was bmad-code-review">
840
- <check if="patch findings exist">
841
- <action>Set `{{next_skill}}` = `apply-patches` (internal step 6)</action>
842
- <goto step="6">Apply patches</goto>
843
- </check>
844
- <goto step="7">Mark story done</goto>
845
- </check>
846
-
847
- <check if="{{completed_skill}} is retrospective-auto">
848
- <action>Log: "Epic retrospective generated inline by autopilot — sprint-status.yaml updated"</action>
849
- </check>
850
-
851
- <check if="{{completed_skill}} is retrospective-skip">
852
- <action>Log: "Epic retrospective skipped per config — sprint-status.yaml updated inline"</action>
853
- </check>
854
-
855
- <check if="{{completed_skill}} was bmad-create-epics-and-stories">
856
- <action>**Validate BDD acceptance criteria** — find the epics file in `{planning_artifacts}` (glob for `*epic*.md`).
857
- For each story section, check that acceptance criteria contain **Given**, **When**, and **Then** keywords.
858
- If any story lacks BDD format:
859
- - Rewrite only the acceptance criteria lines in Given/When/Then format, preserving all other content
860
- - After rewriting, re-read the file and verify: heading structure intact, all stories still present, Given/When/Then present in every story
861
- - Log: "Fixed N stories with non-BDD acceptance criteria"
862
- </action>
863
- </check>
864
-
865
- <check if="{{completed_skill}} was bmad-sprint-planning">
866
- <!-- PR-follow-up: sprint-planning populates development_status for the
867
- first time. Recalculate stories_remaining so the step-2 "sprint
868
- complete" gate doesn't fire spuriously on the next iteration. -->
869
- <action>**Recalculate `{{stories_remaining}}`** — run:
870
- `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
871
- Parse stdout as `{"remaining":[...],"state":"..."}` → `{{stories_remaining}}` = `.remaining`. Update `{state_file}` with the new value.</action>
872
- </check>
873
-
874
- <!-- 2.0.2: auto-infer inter-story dependencies via the autopilot's active
875
- LLM session. Replaces the hand-authored dependencies.yaml step that
876
- users never discovered. Gated on autopilot.auto_infer_dependencies
877
- (default true on small/medium/large; false on nano and legacy).
878
- The script never calls an LLM — it ingests our JSON output via stdin,
879
- validates, and writes the sidecar with an AUTO-INFERRED marker.
880
- Hand-authored sidecars (no marker) are detected and respected. -->
881
- <check if="{{completed_skill}} was bmad-sprint-planning">
882
- <action>Resolve `{{auto_infer_dependencies}}` from `autopilot.auto_infer_dependencies` (default false) via:
883
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.auto_infer_dependencies --project-root "{{project_root}}"`</action>
884
- <check if="{{auto_infer_dependencies}} is true">
885
- <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>
886
- <foreach var="{{epic_id}}" in="{{distinct_epic_ids}}">
887
- <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>
888
- <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>
889
- <action>Pipe `{{infer_json}}` into `node {{project_root}}/_Sprintpilot/scripts/infer-dependencies.js write --epic "{{epic_id}}" --project-root "{{project_root}}"`. Parse stdout JSON.
890
- - 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}})"`.
891
- - 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.
892
- - 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.
893
- </action>
894
- </foreach>
895
- </check>
896
- </check>
897
-
898
- <check if="{{completed_skill}} was bmad-sprint-planning AND {{git_enabled}}">
899
- <action>Run `git fetch origin` (warn + continue on failure; obeys hoisted `{{has_origin}}` rule).</action>
900
- <action>Initialize `{git_status_file}` if it doesn't exist (with git_integration block)</action>
901
- </check>
902
-
903
- <check if="{{completed_skill}} was bmad-create-story">
904
- <!-- PR-follow-up (greenfield e2e fix): bmad-create-story sometimes omits
905
- the Tasks/Subtasks section entirely. Re-running the skill doesn't
906
- always fix it. Fall back to a deterministic script so the story
907
- file ALWAYS has checkboxes dev-story can mark — no LLM prose in
908
- this path. -->
909
- <action>Locate the story file for `{{current_story}}` — glob
910
- `_bmad-output/**/story-{{current_story}}.md` and
911
- `_bmad-output/**/{{current_story}}.md`. Set `{{story_file_path}}`.
912
- </action>
913
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/inject-tasks-section.js --story-file "{{story_file_path}}"`
914
- — 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.
915
- 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).
916
- </action>
917
- </check>
918
-
919
- <check if="{{completed_skill}} was bmad-create-story AND {{git_enabled}}">
920
- <action>Sanitize branch name for `{{current_story}}` (same logic as step 3)</action>
921
- <action>Check if branch already registered in `{git_status_file}` for this story → skip if so</action>
922
- <action>Register branch in `{git_status_file}`:
923
- `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}}" --platform "{{platform}}" --base-branch "{{base_branch}}"`
924
- </action>
925
- </check>
926
-
927
- <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)">
928
- <action>**Commit planning artifacts to main.**
929
- 1. `git add _bmad-output/planning-artifacts/ _bmad-output/implementation-artifacts/ _bmad-output/stories/` (ignore missing-path errors)
930
- 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).
931
- </action>
932
- </check>
933
-
934
- <goto step="5">Read skill output for next step</goto>
935
-
936
- </step>
937
-
938
-
939
- <step n="5" goal="Determine next skill — from skill output first, bmad-help as fallback">
940
-
941
- <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>
942
-
943
- <check if="{{next_skill}} is null, empty, or signals completion">
944
- <action>**Verify against source of truth** — re-read `{status_file}`. If undone stories exist, set `{{current_story}}` = first one and determine `{{next_skill}}`:
945
- - No story file → `bmad-create-story`
946
- - Story file + status `ready-for-dev` → `bmad-check-implementation-readiness`
947
- - Status `in-progress` and `current_bmad_step` before `code-review` → `bmad-dev-story`
948
- - Status `in-progress` and `current_bmad_step` ≥ `code-review` → `bmad-code-review`
949
- - Else → invoke `bmad-help` for precise determination
950
- Log: "next_skill was empty but undone stories remain — resolved to {{next_skill}} for {{current_story}}".
951
- </action>
952
- <check if="all stories in status_file are done">
953
- <!-- FINALIZE_HANDOFF (see top of file). -->
954
- <action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
955
- <action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
956
- <action>Report: "Sprint complete detected during skill-recovery. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
957
- </check>
958
- </check>
959
-
960
- <goto step="8">Save state and continue</goto>
961
-
962
- </step>
963
-
964
-
965
- <step n="6" goal="Apply all patch findings automatically">
966
-
967
- <action>Mark task "[story] Apply patches" → `in_progress`</action>
968
-
969
- <critical>
970
- Apply ALL patch and bugfix findings automatically. For each:
971
- 1. Create sub-task "Patch: [title]" → `in_progress`
972
- 2. Apply fix
973
- 3. Run affected tests — report `N/N passed`
974
- 4. **Log decision** — append `review-accept` entry to `{decision_log_file}` with finding title and rationale
975
- 5. If {{git_enabled}} AND {{in_worktree}}:
976
- - Stage changed files explicitly: `git add -- "file1" "file2"`
977
- - Commit: `git commit -m "fix({{current_story}}): {{patch_title}}"`
978
- - Record commit SHA in `{{patch_commits}}` list
979
- 6. Mark sub-task `completed`
980
-
981
- For any finding that is DISMISSED (contradicts AC or is a false positive):
982
- - **Log decision** — append `review-triage` entry to `{decision_log_file}` with finding title, why it was dismissed, and impact level
983
- </critical>
984
-
985
- <action>Run full test suite after all patches — report `N/N passed`</action>
986
-
987
- <check if="any test fails after patching">
988
- <action>Increment failure counter</action>
989
- <check if="failure_counter >= 3 with no progress">
990
- <action>Update `{state_file}`</action>
991
- <action>PAUSE: "3 consecutive patch failures on {{current_story}}. Need guidance."</action>
992
- <action>STOP</action>
993
- </check>
994
- <action>Diagnose and fix — re-run — loop until green</action>
995
- <action>**Log decision** — append `halt-recovery` entry to `{decision_log_file}` with root cause and resolution</action>
996
- </check>
997
-
998
- <action>Log: "All patches applied — {{N}}/{{N}} passing"</action>
999
- <action>Mark "[story] Apply patches" → `completed`</action>
1000
-
1001
- <!-- Re-run code review to sync sprint-status.yaml — patches resolved all findings, so code-review will now set story to done -->
1002
- <!-- Single-call timing (mark) — see step-3 comment for rationale. -->
1003
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{current_story}}" --phase "skill.bmad-code-review.rereview" --project-root "{{project_root}}"`.</action>
1004
- <action>Re-invoke `bmad-code-review` using the Skill tool.
1005
- The review layers already ran — this pass will see zero unresolved findings and set the story status to `done` in sprint-status.yaml (code-review owns that transition per step-04-present.md:92).
1006
- Instruct: "Re-verify code review for story {{current_story}} — all patch findings have been applied. Update story status accordingly."
1007
- </action>
1008
- <action>Mark task "code-review-verify" → `completed`</action>
1009
-
1010
- <goto step="7">Mark story done</goto>
1011
-
1012
- </step>
1013
-
1014
-
1015
- <step n="7" goal="Mark story done, git push/PR, update records, check epic completion">
1016
-
1017
- <action>**Mark all task checkboxes complete** in the story file:
1018
- - Find every `- [ ]` in the Tasks / Subtasks section and replace with `- [x]`
1019
- - Verify zero `- [ ]` remain in the story file
1020
- </action>
1021
-
1022
- <action>Fill story file Dev Agent Record:
1023
- - Files changed
1024
- - Autonomous decisions made
1025
- - Final test count: `N/N passed`
1026
- </action>
1027
-
1028
- <!-- Epic granularity: defer push/PR until the LAST story of the epic.
1029
- For intermediate stories (granularity=epic AND not is_last_story_of_epic),
1030
- the work is already committed to the epic branch locally via
1031
- stage-and-commit.js; no push or PR is attempted. The epic's last
1032
- story pushes the accumulated commits and opens one PR for the epic. -->
1033
- <check if="{{granularity}} is epic AND {{is_last_story_of_epic}} is false">
1034
- <action>Log: "Epic granularity: skipping push/PR for {{current_story}} — will push at end of epic {{epic_id}}"</action>
1035
- <action>Set `{{push_status}}` = "deferred", `{{pr_url}}` = "DEFERRED", `{{merge_status}}` = "deferred"</action>
1036
- <!-- Skip the whole git-push block; fall through to the artifact-sync block below. -->
1037
- </check>
1038
-
1039
- <!-- GIT: Push, PR, exit worktree (story granularity OR last story of an epic) -->
1040
- <check if="{{git_enabled}} AND ({{granularity}} is story OR {{is_last_story_of_epic}} is true) AND ({{in_worktree}} OR {{worktree_enabled}} is false)">
1041
- <check if="{{push_auto}} is true">
1042
- <action>**Push branch**.
1043
- Run: `git push -u origin {{branch_prefix}}{{branch_name}} 2>&1`
1044
- If push fails → set `{{push_status}}` = "failed", log warning, continue.
1045
- If push succeeds → set `{{push_status}}` = "pushed".
1046
- </action>
1047
- </check>
1048
- <check if="{{push_auto}} is false">
1049
- <action>Set `{{push_status}}` = "local", `{{pr_url}}` = "SKIPPED"</action>
1050
- <action>Log: "Push skipped (git.push.auto = false). Branch {{branch_prefix}}{{branch_name}} is local only."</action>
1051
- </check>
1052
-
1053
- <action>**Create PR/MR** (if push succeeded AND `{{create_pr}}` is true AND platform != git_only):
1054
- If `{{create_pr}}` is false OR `{{push_status}}` is not "pushed" → set `{{pr_url}}` = "SKIPPED", skip PR creation.
1055
- 1. Read PR body template: `{{project_root}}/_Sprintpilot/{{pr_template}}`
1056
- If template file doesn't exist at that path, use a simple default: "## Story: {{current_story}}\n\n{{story-title}}"
1057
- 2. Fill template placeholders using the `commit_placeholder_resolution` chain from config:
1058
- - `{story-key}` → `{{current_story}}` (from sprint-status)
1059
- - `{story-title}` → from story file title, fallback to story-key
1060
- - `{epic}` → from story file epic header, fallback to story-key prefix
1061
- - `{change-summary}` → list of changed files from `git diff --stat`
1062
- - `{acceptance-criteria}` → from story file AC section
1063
- - `{lint-result}` → `{{lint_result}}`
1064
- - `{test-result}` → from last test run output
1065
- - `{patch-count}` → number of patch commits
1066
- 3. Run: `node {{project_root}}/_Sprintpilot/scripts/create-pr.js --platform {{platform}} --branch {{branch_prefix}}{{branch_name}} --base {{pr_base}} --title "{{story-title}} ({{current_story}})" --body "<filled template>"`
1067
- 4. Output: PR URL or "SKIPPED". Set `{{pr_url}}` = output.
1068
- If creation fails → log warning, set `{{pr_url}}` = null, continue.
1069
- </action>
1070
-
1071
- <action>**Exit worktree** — change working directory back to project root:
1072
- `cd {{project_root}}`
1073
- All subsequent commands now run from the project root.
1074
- Set `{{in_worktree}}` = false.
1075
- </action>
1076
-
1077
- <check if="{{create_pr}} is false OR {{platform}} is git_only OR {{pr_url}} is null or SKIPPED">
1078
- <action>**Merge story/epic branch to main.** If `{{has_origin}}` is false (local-only), substitute `origin/{{base_branch}}` → `{{base_branch}}` and skip all `git push origin` / `git fetch origin` calls below.
1079
- Choose merge strategy by `{{squash_on_merge}}`: if true, use `git merge --squash` + single commit; otherwise standard merge commit.
1080
- 1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
1081
- 2. If `{{squash_on_merge}}` is true:
1082
- `git merge --squash {{branch_prefix}}{{branch_name}}` then
1083
- `git commit -m "feat({{epic_id}}): epic {{epic_id}} ({{branch_prefix}}{{branch_name}})"`.
1084
- Otherwise: `git merge {{branch_prefix}}{{branch_name}} --no-edit`.
1085
- 3. On success: `git push origin {{base_branch}}`, set `{{merge_status}}` = "merged".
1086
- 4. On conflict: `git merge --abort`, `git fetch origin`, re-checkout base, retry merge once. On retry success: push + merged. On retry failure: `{{merge_status}}` = "failed", log warning, continue — the branch is preserved and boot reconciliation retries next session.
1087
- 5. **Cross-epic conflict interlock**: if this merge conflict involved two independent epics AND `{{parallel_epics}}` is true, set `{{cross_epic_disabled_this_session}}` = true and log "EXPERIMENTAL: cross-epic merge conflict detected; disabling parallel_epics for the remainder of this session." The flag resets on next session start.
1088
-
1089
- `{{merge_status}}` is persisted by the sync-status.js call later in this step. Do NOT call sync-status.js here (see SYNC_STATUS_RULE).
1090
- </action>
1091
- <check if="{{cleanup_on_merge}} is true AND {{in_worktree}} is true">
1092
- <action>**Cleanup worktree** (ignore failures — may already be gone): `git worktree remove .worktrees/{{current_story}} --force` then `git worktree prune`</action>
1093
- </check>
1094
- </check>
1095
- <check if="{{pr_url}} is a valid URL (not null, not SKIPPED)">
1096
- <critical>**DO NOT merge** — a PR was created at {{pr_url}}. Merging requires PR approval. The branch will be merged through the PR workflow on the platform.</critical>
1097
- <action>Set `{{merge_status}}` = "pr_pending"</action>
1098
- <action>Log: "Story {{current_story}} pushed — PR awaiting review: {{pr_url}}"</action>
1099
- </check>
1100
-
1101
- <action>**Commit story artifacts to main** — keeps main in sync even when story code is on a PR branch.
1102
- 1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
1103
- 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}}"`
1104
- 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/`
1105
- 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).
1106
- </action>
1107
- </check>
1108
-
1109
- <!-- Story git status was already written by sync-status.js above (when git_enabled AND in_worktree).
1110
- sprint-status.yaml is BMAD-owned — updated by bmad-dev-story / bmad-code-review directly. -->
1111
- <check if="NOT {{git_enabled}}">
1112
- <action>Log: "Story {{current_story}} complete — BMAD dev-story updates sprint-status.yaml directly"</action>
1113
- </check>
1114
-
1115
- <action>Mark all remaining tasks for this story → `completed`</action>
1116
- <action>**Increment `{{session_stories_done}}` by 1** — this is the ONLY place the counter ticks up. It runs only after the story's full implementation cycle (dev-story GREEN + code-review + patches + artifacts committed + optional push/PR). Creating a story file in step 3 never increments this counter.</action>
1117
- <action>Remove `{{current_story}}` from `{{stories_remaining}}` list</action>
1118
-
1119
- <!-- Story-boundary FLUSH_SHARDS (see top of file). -->
1120
- <check if="{{coalesce_state_writes}} is true">
1121
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"`.</action>
1122
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story "{{current_story}}" --project-root "{{project_root}}"` — ignore failures (no-op if no per-story shard was ever batched).</action>
1123
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"` — ignore failures. Produces merged autopilot-state.yaml + decision-log.yaml.</action>
1124
- </check>
1125
-
1126
- <action>Report: "Story {{current_story}} done — N/N passing{{#if pr_url}} — PR: {{pr_url}}{{/if}}"</action>
1127
-
1128
- <action>Check if ALL stories in this epic are `done`</action>
1129
- <check if="epic complete">
1130
- <action>Resolve `{{epic_id}}` (e.g. "1") and `{{epic_title}}` from `{status_file}` for the current epic</action>
1131
- <action>Create task "[epic {{epic_id}}] retrospective" → `in_progress`</action>
1132
-
1133
- <!-- Retrospective: driven by `autopilot.retrospective_mode`. The external
1134
- `bmad-retrospective` skill is NEVER invoked from autopilot.
1135
- All three modes append a `autopilot:retrospective` decision-log entry
1136
- and mark the retrospective task complete. -->
1137
-
1138
- <check if="{{retrospective_mode}} is auto">
1139
- <action>Collect from `{status_file}` for epic `{{epic_id}}`: done stories `{ story-key, title, test_pass_count, patch_count }`, epic title, dates if present.</action>
1140
- <action>Collect decision-log entries for epic `{{epic_id}}` (match `story` prefix `{{epic_id}}-` or `phase: autopilot:*` tagged to this epic). Identify open risks / carry-over notes from any story `notes`/`risks` fields or `workaround` decisions for this epic.</action>
1141
- <action>Ensure `{implementation_artifacts}/retrospectives/` exists. Read template `{{project_root}}/_Sprintpilot/templates/epic-retrospective.md`, fill mustache placeholders, write to `{implementation_artifacts}/retrospectives/epic-{{epic_id}}-retrospective.md`.</action>
1142
- <action>Update `{status_file}`: `epics.{{epic_id}}.status = done`, `.retrospective_path = <file>`, `.completed_at = {current_date}`.</action>
1143
- <action>Append decision-log entry: `{ category: workaround, decision: "retrospective generated inline", rationale: "retrospective_mode=auto", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`. Set `{{completed_skill}}` = `retrospective-auto`.</action>
1144
- </check>
1145
-
1146
- <check if="{{retrospective_mode}} is stop">
1147
- <action>Update `{state_file}`: `paused_at = epic-complete-awaiting-retrospective`, `paused_epic_id = {{epic_id}}`, `next_action = "run /bmad-retrospective interactively for epic {{epic_id}}, then re-run /sprint-autopilot-on"`.</action>
1148
- <action>Append decision-log entry: `{ category: workaround, decision: "paused for interactive retrospective", rationale: "retrospective_mode=stop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`.</action>
1149
- <action>Report: "Autopilot paused — epic {{epic_id}} complete, retrospective handed off. Run `/bmad-retrospective` interactively for epic {{epic_id}}, then re-run `/sprint-autopilot-on`. State saved to: {state_file}." Then STOP.</action>
1150
- </check>
1151
-
1152
- <check if="{{retrospective_mode}} is skip">
1153
- <action>Update `{status_file}`: `epics.{{epic_id}}.status = done`, `.retrospective_path = null`, `.retrospective_skipped = true`, `.completed_at = {current_date}`.</action>
1154
- <action>Append decision-log entry: `{ category: workaround, decision: "retrospective skipped", rationale: "retrospective_mode=skip (NOT RECOMMENDED)", impact: medium, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`. Set `{{completed_skill}}` = `retrospective-skip`.</action>
1155
- </check>
1156
-
1157
- <action>Mark retrospective task → `completed`.</action>
1158
-
1159
- <check if="{{git_enabled}}">
1160
- <action>**Epic PR summary** — list all epic PR/MR URLs from `{status_file}` and report as "Epic complete — PR/MR summary: [list]. Ready to merge — review PRs and confirm when ready."</action>
1161
- <check if="{{cleanup_on_merge}} is true">
1162
- <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>
1163
- </check>
1164
- </check>
1165
- </check>
1166
-
1167
- <!-- Session limit check — 0 means disabled (run until sprint complete) -->
1168
- <check if="{{session_story_limit}} > 0 AND {{session_stories_done}} >= {{session_story_limit}}">
1169
- <goto step="9">Session checkpoint</goto>
1170
- </check>
1171
-
1172
- <goto step="5">Determine next skill</goto>
1173
-
1174
- </step>
1175
-
1176
-
1177
- <step n="8" goal="Save state and continue">
1178
-
1179
- <action>Update `{state_file}` with STATE_FIELDS.</action>
1180
-
1181
- <goto step="2">Continue execution loop</goto>
1182
-
1183
- </step>
1184
-
1185
-
1186
- <step n="9" goal="Session checkpoint — proactive handoff before compaction">
1187
-
1188
- <!-- GIT: Exit worktree if we're in one before checkpointing -->
1189
- <check if="{{in_worktree}}">
1190
- <action>Commit any uncommitted work in the worktree first</action>
1191
- <action>`cd {{project_root}}` — return to project root, preserve worktree for next session</action>
1192
- <action>Write git status to git-status.yaml (same sync as step 7)</action>
1193
- <action>Set `{{in_worktree}}` = false</action>
1194
- </check>
1195
-
1196
- <check if="{{git_enabled}}">
1197
- <action>**Pre-checkpoint merge sweep** — ensure all completed stories are on base branch.
1198
- Read `{git_status_file}`. For each story completed this session:
1199
- - If merge_status is "merged" or "pr_pending": skip
1200
- - If merge_status is "pending", empty, or "failed":
1201
- - Determine branch ref: if push_status is "pushed", use `origin/{{branch_prefix}}<branch>`;
1202
- if push_status is "local", use local ref `{{branch_prefix}}<branch>`
1203
- - Attempt merge:
1204
- `git checkout -B {{base_branch}} origin/{{base_branch}}`
1205
- `git merge <branch-ref> --no-edit`
1206
- `git push origin {{base_branch}}`
1207
- - If merge succeeds: update merge_status in `{git_status_file}`.
1208
- See SYNC_STATUS_RULE at top.
1209
- - If merge fails: `git merge --abort`, update merge_status to "failed" in `{git_status_file}` (see SYNC_STATUS_RULE), log warning, continue
1210
- Log: "Pre-checkpoint merge: N stories verified on {{base_branch}}"
1211
- </action>
1212
- </check>
1213
-
1214
- <action>Update `{state_file}` with STATE_FIELDS.</action>
1215
- <check if="{{coalesce_state_writes}} is true">
1216
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"`.</action>
1217
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"`.</action>
1218
- </check>
1219
-
1220
- <!-- Phase-timing session snapshot (no-op if autopilot.phase_timings is false). -->
1221
- <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>
1222
-
1223
- <action>Read `{decision_log_file}` — count medium/high decisions from this session's stories</action>
1224
-
1225
- <action>Report to user:
1226
- ```
1227
- Autopilot session checkpoint
1228
-
1229
- Completed {{session_stories_done}} stories this session.
1230
- State fully saved to: {state_file}
1231
- {{#if git_enabled}}
1232
- Git status:
1233
- {{#each completed_stories_this_session}}
1234
- - {{story-key}}: {{push_status}} {{pr_url}}
1235
- {{/each}}
1236
- {{/if}}
1237
- {{#if medium_high_decisions_count > 0}}
1238
-
1239
- Decisions requiring review: {{medium_high_decisions_count}} (medium/high impact)
1240
- {{#each medium_high_decisions}}
1241
- #{{id}} [{{impact}}] {{story}} — {{decision}}
1242
- {{/each}}
1243
- Full log: {decision_log_file}
1244
- {{/if}}
1245
-
1246
- To continue without losing any context, please start a new session and run:
1247
- /sprint-autopilot-on
1248
-
1249
- Autopilot will resume exactly from: {{next_skill}} on {{current_story}}
1250
- No work will be repeated.
1251
- ```
1252
- </action>
1253
-
1254
- <action>STOP — wait for user to start a new session</action>
1255
-
1256
- </step>
1257
-
1258
-
1259
- <step n="10" goal="Sprint complete — emit summary and next steps">
1260
-
1261
- <!-- CRITICAL 1-7: must run in order before any other step-10 action; do not reorder or skip. -->
1262
-
1263
- <!-- GIT: Exit worktree if still in one (prerequisite for the rest) -->
1264
- <check if="{{in_worktree}}">
1265
- <action>`cd {{project_root}}` — return to project root</action>
1266
- <action>Set `{{in_worktree}}` = false</action>
1267
- </check>
1268
-
1269
- <!-- Run the mark-tasks helper AS THE VERY FIRST action of step 10.
1270
- The LLM reliably executes the first critical action but sometimes
1271
- skips later ones once it feels "done" with the sprint. Putting this
1272
- first ensures every done-story's checkboxes get marked before the
1273
- LLM loses focus. -->
1274
- <action>**[CRITICAL 1/7] Mark all done stories' Task checkboxes** — deterministic helper. Run:
1275
- `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.
1276
- </action>
1277
-
1278
- <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.
1279
- <check if="{{git_enabled}}">
1280
- Run: `git worktree list --porcelain` to enumerate. For each worktree whose path starts with `{{project_root}}/.worktrees/`:
1281
- - `git worktree remove <path> --force 2>&1` (ignore failures — best-effort).
1282
- Then once: `git worktree prune 2>&1` (ignore failures).
1283
- Final-pass cleanup: idempotent re-run that catches anything missed by per-story/epic-complete cleanup above.
1284
- </check>
1285
- </action>
1286
-
1287
- <action>**[CRITICAL 3/7] Release lock immediately** — before any slow operation:
1288
- Run: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures. Idempotent.
1289
- </action>
1290
-
1291
- <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.
1292
- <check if="{{git_enabled}}">
1293
- 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.
1294
- 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`).
1295
- 3. Stage artifacts via the cross-platform helper (replaces `git add … 2>/dev/null || true`):
1296
- `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}}"`
1297
- 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.
1298
- 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).
1299
- </check>
1300
- </action>
1301
-
1302
- <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>
1303
-
1304
- <action>**[CRITICAL 6/7] Verify** — run:
1305
- `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
1306
- Parse stdout as JSON. If `.state` is `sprint-complete` AND `.remaining` is empty, the sprint is verified complete. If `.state` is anything else (e.g. `sprint-in-progress` with leftover stories), log a warning — but do NOT revert the above actions. The worktrees are gone; the lock is released; the artifacts are committed; the state is marked complete; task checkboxes are marked. Manual recovery only.
1307
- </action>
1308
-
1309
- <!-- See AUTOPILOT RULES (context-rot mitigation). CRITICAL 5/7 wrote
1310
- sprint-complete as a crash-safe marker; step 1 short-circuits on a
1311
- leftover sprint-complete state so accidental re-invocation doesn't loop. -->
1312
- <action>**[CRITICAL 7/7] Delete state_file** — `node -e "require('node:fs').rmSync('{state_file}', { force: true })"` (idempotent; never halt on failure). Removes `autopilot-state.yaml` so the next invocation starts cleanly. CRITICAL 5/7 already wrote `current_bmad_step = sprint-complete` to the same file as a crash-safe marker, so if this delete fails mid-run the next session still sees the terminal state.</action>
1313
-
1314
- <!-- Close the last open `mark` phase so its duration gets recorded.
1315
- Without this, the very last skill's duration would be lost (no
1316
- subsequent mark fires after sprint-complete). -->
1317
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase _end --project-root "{{project_root}}"`.</action>
1318
-
1319
- <action>Run full test suite — report `N/N passed`</action>
1320
-
1321
- <!-- Sprint-complete FLUSH_SHARDS + archive (no-op if coalescing is off). -->
1322
- <check if="{{coalesce_state_writes}} is true">
1323
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"`.</action>
1324
- <action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — ignore failures. `--archive` moves merged shards to `.archive/layer-<timestamp>/` so the next sprint starts clean.</action>
1325
- </check>
1326
-
1327
- <!-- Final phase-timing hotspot report (no-op if autopilot.phase_timings is false). -->
1328
- <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>
1329
-
1330
- <!-- Generate project documentation after sprint completion -->
1331
- <action>**Resolve stack** — set `{{stack}}` = `{ name, install_cmd, run_cmd, test_cmd }` using the first successful source:
1332
-
1333
- 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.
1334
- 2. **`architecture.md`** (`{planning_artifacts}/architecture.md`) — extract from "Tech Stack" / "Runtime" / "Build & Deploy" / "Commands" sections.
1335
- 3. **Manifest heuristics** — map manifest file → stack → idiomatic commands:
1336
-
1337
- | Manifest | Stack | install / run / test |
1338
- |---|---|---|
1339
- | `package.json` | Node/JS/TS | `<pm> install` / `<pm> run start\|dev\|serve` (or `node <bin>`) / `<pm> test` — `<pm>` = `pnpm`/`yarn`/`bun`/`npm` by lockfile |
1340
- | `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` |
1341
- | `go.mod` | Go | `go mod download` / `go run .` (or `./cmd/<name>`) / `go test ./...` |
1342
- | `Cargo.toml` | Rust | `cargo build` / `cargo run` (or `--bin <name>`) / `cargo test` |
1343
- | `pom.xml` | Java/Kotlin (Maven) | `mvn install` / `mvn spring-boot:run` or `mvn exec:java` / `mvn test` |
1344
- | `build.gradle(.kts)` | Java/Kotlin (Gradle) | `./gradlew build` / `./gradlew bootRun` or `run` / `./gradlew test` |
1345
- | `Gemfile` | Ruby | `bundle install` / `rails server` or `bundle exec ruby <entry>` / `bundle exec rspec` |
1346
- | `*.csproj`/`*.sln` | .NET | `dotnet restore` / `dotnet run` (or `--project`) / `dotnet test` |
1347
- | `composer.json` | PHP | `composer install` / `php artisan serve` (Laravel) or `php -S localhost:8000 -t public` / `vendor/bin/phpunit` |
1348
- | `mix.exs` | Elixir | `mix deps.get` / `mix phx.server` or `mix run --no-halt` / `mix test` |
1349
- | (none of the above) | Explicit launcher | `./run.sh`/`./run_gui.sh`/`./start.sh`, `make run\|start\|dev`, `docker compose up`, `docker build` + `docker run` |
1350
-
1351
- 4. **No match** — all fields `null`. Downstream omits the line; never guess.
1352
-
1353
- Set `{{launch_cmd}}` = `{{stack.run_cmd}}`.
1354
- If `{{stack}}` came from (3) and `project-context.md` exists without stack info, log: "Consider running `bmad-generate-project-context` to capture stack commands."
1355
- </action>
1356
-
1357
- <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>
1358
-
1359
- <check if="{{git_enabled}}">
1360
- <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`:
1361
- 1. `git switch {{base_branch}} 2>&1 || git checkout {{base_branch}} 2>&1`.
1362
- 2. If `{{has_origin}}` is true: `git pull --ff-only origin {{base_branch}} 2>&1` (warn on non-ff; do NOT reset).
1363
- 3. Stage docs via the cross-platform helper:
1364
- `node {{project_root}}/_Sprintpilot/scripts/git-portable.js safe-add README.md docs/ --project-root "{{project_root}}"`
1365
- (paths are filtered to those that exist; missing paths are skipped).
1366
- 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).
1367
- </action>
1368
- </check>
1369
-
1370
- <action>**Collect report data** from `{status_file}` (stories grouped by epic with titles, totals, final test count; PR/MR URLs, patch/dismissed counts per story if git_enabled) and `{decision_log_file}` (medium/high-impact decisions; counts of `review-accept`, `review-triage`, code-review rounds; per-story patches-applied / findings-dismissed).</action>
1371
-
1372
- <check if="{{git_enabled}}">
1373
- <!-- Worktree cleanup ran as CRITICAL 2/7. Restore main-repo gc.auto. -->
1374
- <action>**Restore main-repo gc.auto**:
1375
- if `{{original_gc_auto_main}}` is "unset": `git config --local --unset gc.auto` (ignore failure — may already be unset).
1376
- else: `git config --local gc.auto {{original_gc_auto_main}}`.
1377
- </action>
1378
- <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>
1379
- </check>
1380
-
1381
- <!-- State file deletion moved to CRITICAL 7/7 above for reliability. -->
1382
- <action>Mark master task "Sprintpilot — Full Sprint Execution" → `completed`</action>
1383
-
1384
- <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>
1385
-
1386
- </step>
1387
-
1388
- </workflow>