@ikunin/sprintpilot 1.0.5 → 2.0.5
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.
- package/README.md +48 -1
- package/_Sprintpilot/Sprintpilot.md +14 -1
- package/_Sprintpilot/manifest.yaml +1 -1
- package/_Sprintpilot/modules/autopilot/config.yaml +22 -0
- package/_Sprintpilot/modules/autopilot/profiles/_base.yaml +45 -0
- package/_Sprintpilot/modules/autopilot/profiles/large.yaml +22 -0
- package/_Sprintpilot/modules/autopilot/profiles/legacy.yaml +35 -0
- package/_Sprintpilot/modules/autopilot/profiles/medium.yaml +5 -0
- package/_Sprintpilot/modules/autopilot/profiles/nano.yaml +35 -0
- package/_Sprintpilot/modules/autopilot/profiles/small.yaml +5 -0
- package/_Sprintpilot/modules/git/config.yaml +8 -0
- package/_Sprintpilot/modules/ma/config.yaml +42 -0
- package/_Sprintpilot/scripts/agent-adapter.js +247 -0
- package/_Sprintpilot/scripts/cached-read.js +238 -0
- package/_Sprintpilot/scripts/check-prereqs.js +139 -0
- package/_Sprintpilot/scripts/dispatch-layer.js +192 -0
- package/_Sprintpilot/scripts/git-portable.js +219 -0
- package/_Sprintpilot/scripts/infer-dependencies.js +594 -0
- package/_Sprintpilot/scripts/inject-tasks-section.js +279 -0
- package/_Sprintpilot/scripts/list-remaining-stories.js +295 -0
- package/_Sprintpilot/scripts/log-timing.js +425 -0
- package/_Sprintpilot/scripts/mark-done-stories-tasks.js +254 -0
- package/_Sprintpilot/scripts/merge-shards.js +339 -0
- package/_Sprintpilot/scripts/preflight-merge.js +235 -0
- package/_Sprintpilot/scripts/resolve-dag.js +559 -0
- package/_Sprintpilot/scripts/resolve-profile.js +355 -0
- package/_Sprintpilot/scripts/state-shard.js +602 -0
- package/_Sprintpilot/scripts/submodule-lock.js +130 -0
- package/_Sprintpilot/scripts/summarize-timings.js +362 -0
- package/_Sprintpilot/scripts/sync-status.js +13 -0
- package/_Sprintpilot/scripts/with-retry.js +145 -0
- package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +572 -42
- package/bin/sprintpilot.js +4 -0
- package/lib/commands/install.js +157 -1
- package/package.json +1 -1
|
@@ -10,17 +10,33 @@ You do NOT hardcode the workflow sequence. After each completed skill, read its
|
|
|
10
10
|
|
|
11
11
|
### Shell portability
|
|
12
12
|
|
|
13
|
-
The executing shell may be bash, zsh, PowerShell, or cmd —
|
|
13
|
+
The executing shell may be bash, zsh, Git Bash (Claude Code's default on Windows), PowerShell, or cmd. The workflow itself uses dedicated Node helpers for the operations that were most fragile across shells — the few remaining shell idioms below are the ones that work uniformly under bash, zsh, Git Bash, PowerShell, and cmd:
|
|
14
14
|
|
|
15
|
-
|
|
|
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 |
|
|
16
23
|
|---|---|
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `if [ -f X ]; then ... fi` | `if (Test-Path -PathType Leaf X) { ... }` |
|
|
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` |
|
|
22
28
|
|
|
23
|
-
|
|
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.
|
|
24
40
|
|
|
25
41
|
---
|
|
26
42
|
|
|
@@ -38,6 +54,7 @@ Long autopilot runs will fill the context window. To prevent state loss:
|
|
|
38
54
|
- **All state lives in files, never only in memory.** After every step, write progress to `{state_file}`.
|
|
39
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.
|
|
40
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.
|
|
41
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.
|
|
42
59
|
|
|
43
60
|
### Menu and interaction handling — CRITICAL
|
|
@@ -119,6 +136,17 @@ Resolve:
|
|
|
119
136
|
|
|
120
137
|
**`{state_file}` schema** (referenced as `STATE_FIELDS` below): `last_updated`, `current_story`, `current_bmad_step`, `completed_skill`, `next_skill`, `session_stories_done`, `stories_remaining`, `git_enabled`, `platform`, `in_worktree`, `pr_base`. Always update `last_updated` on every write.
|
|
121
138
|
|
|
139
|
+
**PR 6 state-write policy (`autopilot.coalesce_state_writes`):**
|
|
140
|
+
|
|
141
|
+
When the resolved profile sets `autopilot.coalesce_state_writes: true` (nano/small/medium/large by default; `legacy` false), state writes route through `state-shard.js` using a `sprint`-keyed shard as the authoritative state for sprint-level fields, and per-story shards for story-scoped fields. Policy:
|
|
142
|
+
|
|
143
|
+
- **Critical keys** (`current_story`, `current_bmad_step`, `in_worktree`, `patch_commits`) always go to shard via `state-shard.js batch`, which auto-flushes and writes straight through because the script recognizes them as crash-recovery keys.
|
|
144
|
+
- **Non-critical fields** (test counts, file lists, next_skill, session_stories_done, stories_remaining, etc.) go to `state-shard.js batch`, accumulating in the pending buffer. Flushed at each story boundary (step 7) and session checkpoint (step 9).
|
|
145
|
+
- **Merged authoritative state** (`autopilot-state.yaml`) is rebuilt via `merge-shards.js` at story boundary + session checkpoint + sprint complete.
|
|
146
|
+
- **Rollback** (`coalesce_state_writes: false`): every `Update {state_file}` action writes directly to `autopilot-state.yaml` via the existing STATE_FIELDS shape — no shard indirection. This is the v1.0.5 path byte-for-byte.
|
|
147
|
+
|
|
148
|
+
When the flag is `false`, the direct-write instructions below are authoritative. When `true`, substitute each `Update {state_file} with STATE_FIELDS: <changes>` with a `state-shard.js batch --story sprint --json <changes>` call, followed by a `merge-shards.js --project-root "{{project_root}}"` at the story boundary / checkpoint. The merged `autopilot-state.yaml` remains the single source of truth for resume-after-crash.
|
|
149
|
+
|
|
122
150
|
### Git integration bootstrap
|
|
123
151
|
|
|
124
152
|
<action>Check if `{project-root}/_Sprintpilot/manifest.yaml` exists</action>
|
|
@@ -134,18 +162,57 @@ Resolve:
|
|
|
134
162
|
- `{{create_pr}}` from `git.push.create_pr` (true)
|
|
135
163
|
- `{{pr_template}}` from `git.push.pr_template` ("modules/git/templates/pr-body.md")
|
|
136
164
|
- `{{cleanup_on_merge}}` from `git.worktree.cleanup_on_merge` (true)
|
|
165
|
+
- `{{granularity}}` from `git.granularity` ("story"). Resolver override wins below.
|
|
166
|
+
- `{{worktree_enabled}}` from `git.worktree.enabled` (true). Resolver override wins below.
|
|
167
|
+
- `{{squash_on_merge}}` from `git.squash_on_merge` (false). Resolver override wins below.
|
|
168
|
+
</action>
|
|
169
|
+
<action>**Apply profile overrides** via resolver — run each and set only if the resolver returns a value:
|
|
170
|
+
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.granularity` → override `{{granularity}}`.
|
|
171
|
+
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.worktree.enabled` → override `{{worktree_enabled}}`.
|
|
172
|
+
- `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get git.squash_on_merge` → override `{{squash_on_merge}}`.
|
|
137
173
|
</action>
|
|
138
174
|
<action>Read `{project-root}/_Sprintpilot/modules/autopilot/config.yaml` (if present) and set:
|
|
139
175
|
- `{{session_story_limit}}` from `autopilot.session_story_limit` (default: 3). A value of 0 disables the limit (run until sprint complete).
|
|
140
176
|
- `{{retrospective_mode}}` from `autopilot.retrospective_mode` (default: `auto`). Valid values: `auto` | `stop` | `skip`. Any unknown value falls back to `auto`.
|
|
141
177
|
If the file or either key is missing, fall back to the defaults above.
|
|
142
178
|
</action>
|
|
179
|
+
<action>**Resolve profile-driven flow** — run:
|
|
180
|
+
`node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.implementation_flow`
|
|
181
|
+
Output: `full` or `quick`. Set `{{implementation_flow}}` = output. Default to `full` if the call fails.
|
|
182
|
+
Run: `node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.session_story_limit` → override `{{session_story_limit}}` if the resolver produces a different value than config.yaml (profile overrides config silence). Same pattern for `autopilot.retrospective_mode`.
|
|
183
|
+
</action>
|
|
184
|
+
<action>**Resolve coalesce flag** — run:
|
|
185
|
+
`node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.coalesce_state_writes` → `{{coalesce_state_writes}}`. Default `false` on failure.
|
|
186
|
+
</action>
|
|
187
|
+
<!-- PR 11: detect the running host and resolve parallel-dispatch config. -->
|
|
188
|
+
<action>**Detect host agent** — run:
|
|
189
|
+
`node {{project_root}}/_Sprintpilot/scripts/agent-adapter.js detect --project-root "{{project_root}}"`
|
|
190
|
+
Parse the JSON output: set `{{host_agent}}` = host, `{{host_supports_parallel}}` = supports_parallel, `{{host_confidence}}` = confidence.
|
|
191
|
+
</action>
|
|
192
|
+
<action>**Resolve parallelism flags** via the profile resolver:
|
|
193
|
+
- `{{parallel_stories}}` from `ma.parallel_stories` (default false).
|
|
194
|
+
- `{{max_parallel_stories}}` from `ma.max_parallel_stories` (default 2).
|
|
195
|
+
- `{{experimental_parallel_on_gemini}}` from `ma.experimental_parallel_on_gemini` (default false).
|
|
196
|
+
</action>
|
|
197
|
+
<!-- Gemini CLI opt-in: when the user explicitly sets
|
|
198
|
+
experimental_parallel_on_gemini=true AND the detected host is
|
|
199
|
+
gemini-cli at HIGH confidence, promote supports_parallel=true
|
|
200
|
+
with a one-line warning. Worktree-scoped subagents aren't shipped
|
|
201
|
+
upstream yet, so this is user-opt-in-per-project. -->
|
|
202
|
+
<check if="{{experimental_parallel_on_gemini}} is true AND {{host_agent}} is gemini-cli AND {{host_confidence}} is high">
|
|
203
|
+
<action>Set `{{host_supports_parallel}}` = true</action>
|
|
204
|
+
<action>Log once: "EXPERIMENTAL: parallel_stories enabled on Gemini CLI via ma.experimental_parallel_on_gemini=true. Worktree-scoped subagents are not yet shipped upstream (gemini-cli#22967); expect possible serialization or quota throttling."</action>
|
|
205
|
+
</check>
|
|
206
|
+
<action>Silently coerce `{{parallel_stories}}` to false when `{{host_supports_parallel}}` is false OR `{{host_confidence}}` is not `high`. Log once:
|
|
207
|
+
`parallel_stories requested but host '{{host_agent}}' does not declare parallel support (confidence={{host_confidence}}); running sequentially`.
|
|
208
|
+
</action>
|
|
143
209
|
</check>
|
|
144
210
|
|
|
145
211
|
<check if="manifest does NOT exist">
|
|
146
212
|
<action>Set `{{git_enabled}}` = false</action>
|
|
147
213
|
<action>Set `{{session_story_limit}}` = 3</action>
|
|
148
214
|
<action>Set `{{retrospective_mode}}` = `auto`</action>
|
|
215
|
+
<action>Set `{{implementation_flow}}` = `full`</action>
|
|
149
216
|
<action>Log: "No _Sprintpilot/manifest.yaml found — running stock autopilot (no git)"</action>
|
|
150
217
|
</check>
|
|
151
218
|
|
|
@@ -174,6 +241,11 @@ Resolve:
|
|
|
174
241
|
<action>Set `{{platform}}` = "git_only"</action>
|
|
175
242
|
</check>
|
|
176
243
|
|
|
244
|
+
<!-- PR 10: disable gc.auto on the main repo so git's auto-GC doesn't
|
|
245
|
+
race with concurrent worktree operations during the sprint. Save
|
|
246
|
+
the prior value so we can restore it at sprint complete (step 10). -->
|
|
247
|
+
<action>**Save + disable main-repo gc.auto**: set `{{original_gc_auto_main}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}"`, then `git -C {{project_root}} config --local gc.auto 0`.</action>
|
|
248
|
+
|
|
177
249
|
<action>**Lock file** — run: `node {{project_root}}/_Sprintpilot/scripts/lock.js acquire`
|
|
178
250
|
Output will be one of:
|
|
179
251
|
- `ACQUIRED:<session-id>` → proceed
|
|
@@ -191,6 +263,26 @@ Resolve:
|
|
|
191
263
|
Log: "Platform detected: {{platform}}"
|
|
192
264
|
</action>
|
|
193
265
|
|
|
266
|
+
<!-- PR 7 CONDITIONAL BOOT WORK: a clean repo — main worktree only, zero
|
|
267
|
+
in-progress stories — can skip the slow health-check + branch
|
|
268
|
+
reconciliation below. Gate honored by non-legacy, non-large profiles
|
|
269
|
+
(large keeps full reconciliation for compliance/uptime reasons). -->
|
|
270
|
+
<action>Read `autopilot.conditional_boot_work` from the resolver:
|
|
271
|
+
`node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.conditional_boot_work` → `{{conditional_boot_work}}`. Default to `false` on failure.
|
|
272
|
+
</action>
|
|
273
|
+
<action>Count worktrees (cross-platform; replaces a `... | grep -c` pipe that needed POSIX shell):
|
|
274
|
+
`node {{project_root}}/_Sprintpilot/scripts/git-portable.js count-worktrees --project-root "{{project_root}}"` → `{{worktree_count}}`. The script fails open to 2 internally if git itself errors, matching the previous "fail-open to force full path" semantic.
|
|
275
|
+
</action>
|
|
276
|
+
<action>Count in-progress stories: read `{status_file}` and count stories whose status is NOT in {`done`, `backlog`}. Set `{{in_progress_count}}`. Fail-open to 1 (force full path) if the file is unreadable.</action>
|
|
277
|
+
|
|
278
|
+
<check if="{{conditional_boot_work}} is true AND {{worktree_count}} is 1 AND {{in_progress_count}} is 0">
|
|
279
|
+
<action>Log: "Boot fast-path (PR 7): clean repo — skipping health-check + branch reconciliation"</action>
|
|
280
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js once --story "sprint" --phase "boot.fast-path" --meta "{\"reason\":\"clean-repo\"}" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
281
|
+
<action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
|
|
282
|
+
</check>
|
|
283
|
+
|
|
284
|
+
<check if="NOT ({{conditional_boot_work}} is true AND {{worktree_count}} is 1 AND {{in_progress_count}} is 0)">
|
|
285
|
+
|
|
194
286
|
<action>**Worktree health check** — run:
|
|
195
287
|
`node {{project_root}}/_Sprintpilot/scripts/health-check.js --base-branch {{base_branch}} --status-file {{status_file}}`
|
|
196
288
|
Output classifies each worktree as CLEAN_DONE, COMMITTED, STALE, DIRTY, or ORPHAN.
|
|
@@ -242,6 +334,8 @@ Resolve:
|
|
|
242
334
|
</action>
|
|
243
335
|
|
|
244
336
|
<action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
|
|
337
|
+
|
|
338
|
+
</check><!-- end PR 7 conditional boot work (non-fast-path branch) -->
|
|
245
339
|
</check>
|
|
246
340
|
|
|
247
341
|
---
|
|
@@ -256,7 +350,7 @@ Resolve:
|
|
|
256
350
|
<action>Read `{state_file}` fully</action>
|
|
257
351
|
<action>Extract saved state:
|
|
258
352
|
- `{{current_story}}` — story in progress when last session ended
|
|
259
|
-
- `{{current_bmad_step}}` — BMAD step that was active (2–7)
|
|
353
|
+
- `{{current_bmad_step}}` — BMAD step that was active (2–7), or the reserved value `sprint-finalize-pending` meaning "the prior session detected sprint-complete and checkpointed; this session's only job is step 10"
|
|
260
354
|
- `{{completed_skill}}` — last skill that ran
|
|
261
355
|
- `{{next_skill}}` — next skill recommended at save time
|
|
262
356
|
- `{{session_stories_done}}` = 0 (reset counter for new session)
|
|
@@ -294,6 +388,32 @@ Resolve:
|
|
|
294
388
|
- Update `{state_file}` with reconciled values
|
|
295
389
|
</action>
|
|
296
390
|
|
|
391
|
+
<!-- Already-completed short-circuit. If the prior session wrote
|
|
392
|
+
sprint-complete (CRITICAL 5/7) but crashed before the state file
|
|
393
|
+
delete (CRITICAL 7/7), this invocation would otherwise loop into
|
|
394
|
+
the finalize handoff forever. Detect it and exit cleanly. -->
|
|
395
|
+
<check if="{{current_bmad_step}} is sprint-complete">
|
|
396
|
+
<action>Delete `{state_file}` — clean up the stale terminal marker.</action>
|
|
397
|
+
<action>Report: "Sprint already complete. Nothing to do — run `bmad-sprint-planning` to plan the next sprint, then re-run `/sprint-autopilot-on`." Then STOP.</action>
|
|
398
|
+
</check>
|
|
399
|
+
|
|
400
|
+
<!-- Fresh-context finalize path. If the prior session exited with
|
|
401
|
+
current_bmad_step = "sprint-finalize-pending", this invocation's
|
|
402
|
+
ONLY job is to run step 10 with a clean window. Verify the
|
|
403
|
+
sprint is still genuinely complete before jumping; if a user
|
|
404
|
+
added new work in between, fall through to the normal loop. -->
|
|
405
|
+
<check if="{{current_bmad_step}} is sprint-finalize-pending">
|
|
406
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope` — parse stdout JSON.</action>
|
|
407
|
+
<check if="parsed .state is sprint-complete">
|
|
408
|
+
<action>Log: "Resuming for sprint finalization with fresh context (context-rot mitigation)."</action>
|
|
409
|
+
<goto step="10">Run finalization</goto>
|
|
410
|
+
</check>
|
|
411
|
+
<check if="parsed .state is NOT sprint-complete">
|
|
412
|
+
<action>Log: "sprint-finalize-pending flag was set but new work appeared since checkpoint. Clearing flag and resuming normal execution."</action>
|
|
413
|
+
<action>Update `{state_file}`: `current_bmad_step = null`.</action>
|
|
414
|
+
</check>
|
|
415
|
+
</check>
|
|
416
|
+
|
|
297
417
|
<!-- Resume from a `retrospective_mode: stop` pause. -->
|
|
298
418
|
<check if="{state_file}.paused_at is epic-complete-awaiting-retrospective">
|
|
299
419
|
<action>Set `{{paused_epic_id}}` from `{state_file}.paused_epic_id`. Check if epic `{{paused_epic_id}}` is `done` in `{status_file}` OR an artifact exists at `{implementation_artifacts}/retrospectives/epic-{{paused_epic_id}}-*.md`.</action>
|
|
@@ -324,7 +444,14 @@ Resolve:
|
|
|
324
444
|
- `{{session_stories_done}}` = 0
|
|
325
445
|
</action>
|
|
326
446
|
<action>Create master task: "Sprintpilot — Full Sprint Execution" → `in_progress`</action>
|
|
327
|
-
<action
|
|
447
|
+
<action>**Compute `{{stories_remaining}}`** deterministically — run:
|
|
448
|
+
`node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
|
|
449
|
+
The helper always emits a well-formed JSON object on stdout, on every exit path:
|
|
450
|
+
`{"remaining":[...],"state":"pre-planning|sprint-in-progress|sprint-complete|parse-error"}`.
|
|
451
|
+
Parse stdout as JSON → set `{{stories_remaining}}` = `.remaining`.
|
|
452
|
+
Ignore the exit code: `.state` is authoritative and covers every case (missing file, parse error, or normal).
|
|
453
|
+
</action>
|
|
454
|
+
<action>Write initial `{state_file}` with STATE_FIELDS: `current_story = null`, `current_bmad_step = null`, `completed_skill = bmad-help`, `session_stories_done = 0`, `stories_remaining = {{stories_remaining}}`, `in_worktree = false`, `pr_base = {{base_branch}}`.</action>
|
|
328
455
|
<action>Report to user:
|
|
329
456
|
```
|
|
330
457
|
Sprintpilot ON
|
|
@@ -346,17 +473,89 @@ Resolve:
|
|
|
346
473
|
|
|
347
474
|
<step n="2" goal="Main execution loop — route to correct handler">
|
|
348
475
|
|
|
349
|
-
|
|
350
|
-
|
|
476
|
+
<!-- PR 12 CROSS-EPIC PARALLELISM (experimental, off by default on every
|
|
477
|
+
profile including `large`). All safety rails must pass:
|
|
478
|
+
1. ma.parallel_epics is true.
|
|
479
|
+
2. Host confidence is HIGH AND supports_parallel is true (same as
|
|
480
|
+
intra-epic gate in PR 11).
|
|
481
|
+
3. Two or more epics in dependencies.yaml declare `independent: true`.
|
|
482
|
+
4. preflight-merge.js reports NO conflicts between all pairs.
|
|
483
|
+
5. Session-scoped disable flag {{cross_epic_disabled_this_session}}
|
|
484
|
+
is false (flips true after any cross-epic merge conflict).
|
|
485
|
+
Only on the first iteration of the loop — subsequent iterations
|
|
486
|
+
don't re-preflight; they consume the cached safe_pairs list. -->
|
|
487
|
+
<action>Resolve `{{parallel_epics}}` from `ma.parallel_epics` (default false) via the resolver.</action>
|
|
488
|
+
<check if="{{parallel_epics}} is true AND {{host_supports_parallel}} is true AND {{host_confidence}} is high AND {{cross_epic_preflight_done}} is not true AND {{cross_epic_disabled_this_session}} is not true">
|
|
489
|
+
<action>Read `_Sprintpilot/sprints/dependencies.yaml` (if present). Extract every epic id where `epics.<id>.independent` is true. Set `{{independent_epic_ids}}` = comma-joined list of ids.</action>
|
|
490
|
+
<check if="{{independent_epic_ids}} has fewer than 2 ids">
|
|
491
|
+
<action>Log once: "cross-epic parallelism enabled but fewer than 2 epics declare `independent: true` in dependencies.yaml — running sequentially"</action>
|
|
492
|
+
<action>Set `{{cross_epic_preflight_done}}` = true, `{{cross_epic_safe_pairs}}` = []</action>
|
|
493
|
+
</check>
|
|
494
|
+
<check if="{{independent_epic_ids}} has 2 or more ids">
|
|
495
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/preflight-merge.js --epics "{{independent_epic_ids}}" --base "{{base_branch}}" --branch-prefix "{{branch_prefix}}" --project-root "{{project_root}}"`. Parse JSON — set `{{cross_epic_safe_pairs}}` = safe_pairs, `{{cross_epic_conflict_pairs}}` = conflict_pairs.</action>
|
|
496
|
+
<action>Log: "EXPERIMENTAL: parallel_epics preflight → safe={{cross_epic_safe_pairs.length}} conflict={{cross_epic_conflict_pairs.length}} checked=N"</action>
|
|
497
|
+
<action>Set `{{cross_epic_preflight_done}}` = true</action>
|
|
498
|
+
</check>
|
|
499
|
+
</check>
|
|
500
|
+
|
|
501
|
+
<!-- Authoritative "sprint complete" check. Read the status file EVERY
|
|
502
|
+
iteration via the coded helper (do not rely on stale state or LLM
|
|
503
|
+
re-parsing). The helper emits a JSON envelope on stdout on every
|
|
504
|
+
exit path, so there is no need to probe $? or stderr — .state is
|
|
505
|
+
authoritative. -->
|
|
506
|
+
<action>**Recalculate `{{stories_remaining}}`** — run:
|
|
507
|
+
`node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
|
|
508
|
+
Parse stdout as a single JSON object: `{"remaining":[...],"state":"..."}`.
|
|
509
|
+
- `.state == "sprint-in-progress"` → `{{stories_remaining}}` = `.remaining`, `{{sprint_has_stories}}` = true, `{{sprint_is_complete}}` = false.
|
|
510
|
+
- `.state == "sprint-complete"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = true, `{{sprint_is_complete}}` = true.
|
|
511
|
+
- `.state == "pre-planning"` → `{{stories_remaining}}` = [], `{{sprint_has_stories}}` = false, `{{sprint_is_complete}}` = false.
|
|
512
|
+
- `.state == "parse-error"` → log warning; treat as pre-planning. NEVER declare sprint-complete on a parse failure.
|
|
513
|
+
</action>
|
|
514
|
+
<!-- Sprint-complete handoff. Never run step 10 in the same session that
|
|
515
|
+
first observed the sprint-complete condition — that session has
|
|
516
|
+
spent a long time in context and its tail is the context-rot zone.
|
|
517
|
+
Instead, mark the state as sprint-finalize-pending and stop; the
|
|
518
|
+
next /sprint-autopilot-on boot will route straight to step 10 (via
|
|
519
|
+
the step 1 gate) with a clean window. If we ALREADY are the
|
|
520
|
+
finalize session (current_bmad_step was sprint-finalize-pending on
|
|
521
|
+
entry), step 1 has already jumped to step 10 and we never reach
|
|
522
|
+
this check — so this gate is unambiguous. -->
|
|
523
|
+
<check if="{{sprint_is_complete}} is true">
|
|
524
|
+
<action>Update `{state_file}` with STATE_FIELDS: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`, `session_stories_done = {{session_stories_done}}`.</action>
|
|
525
|
+
<action>Release autopilot lock (idempotent): `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
|
|
526
|
+
<action>Report to user and STOP this session:
|
|
527
|
+
```
|
|
528
|
+
Autopilot sprint-complete checkpoint
|
|
529
|
+
|
|
530
|
+
All stories are done. Pausing before finalization so step 10 runs
|
|
531
|
+
with a fresh context — this prevents late-session instruction decay
|
|
532
|
+
from skipping CRITICAL cleanup actions (commit artifacts, generate
|
|
533
|
+
docs, clean up worktrees, emit final report).
|
|
534
|
+
|
|
535
|
+
Completed {{session_stories_done}} stories this session.
|
|
536
|
+
State saved to: {state_file}
|
|
537
|
+
|
|
538
|
+
Run `/sprint-autopilot-on` once more to finalize.
|
|
539
|
+
```
|
|
540
|
+
</action>
|
|
541
|
+
<action>HALT — do not continue the execution loop. The next invocation enters via step 1, sees `sprint-finalize-pending`, and jumps to step 10.</action>
|
|
542
|
+
</check>
|
|
543
|
+
<check if="{{sprint_has_stories}} is false">
|
|
544
|
+
<action>Log: "Sprint pre-planning: no stories in status file yet. Routing through bmad-help to the next planning skill (do NOT go to step 10)."</action>
|
|
351
545
|
</check>
|
|
352
546
|
|
|
353
547
|
<check if="{{next_skill}} is empty">
|
|
354
548
|
<action>**Recover next_skill** — re-read `{status_file}`, find first story with status != "done"</action>
|
|
355
|
-
<check if="no undone stories found">
|
|
356
|
-
|
|
549
|
+
<check if="no undone stories found AND {{sprint_has_stories}} is true">
|
|
550
|
+
<!-- Fallback sprint-complete detection. Route through the same
|
|
551
|
+
finalize-pending handoff as the primary gate above — never
|
|
552
|
+
jump into step 10 from this long-running session. -->
|
|
553
|
+
<action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
|
|
554
|
+
<action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
|
|
555
|
+
<action>Report: "Sprint complete detected via fallback path. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
|
|
357
556
|
</check>
|
|
358
|
-
<action>
|
|
359
|
-
<action>Invoke `bmad-help` — "Story {{current_story}} needs attention. What is the next required workflow step?"</action>
|
|
557
|
+
<action>If `{{sprint_has_stories}}` is true: set `{{current_story}}` = first undone story from `{status_file}`.</action>
|
|
558
|
+
<action>Invoke `bmad-help` — "Story {{current_story}} needs attention (or: sprint in planning phase — no stories yet). What is the next required workflow step?"</action>
|
|
360
559
|
<action>Extract `{{next_skill}}` from bmad-help response</action>
|
|
361
560
|
</check>
|
|
362
561
|
|
|
@@ -398,10 +597,83 @@ Resolve:
|
|
|
398
597
|
|
|
399
598
|
<step n="3" goal="Prepare and execute the recommended skill">
|
|
400
599
|
|
|
600
|
+
<!-- ──────────────────────────────────────────────────────────────────
|
|
601
|
+
PR 11 PARALLEL DISPATCH (gates):
|
|
602
|
+
- autopilot.parallel_stories: true (resolved at boot into
|
|
603
|
+
{{parallel_stories}})
|
|
604
|
+
- host_supports_parallel: true with high confidence (PR 11
|
|
605
|
+
agent-adapter.js — only Claude Code today)
|
|
606
|
+
- implementation_flow != quick (nano runs sequentially per epic)
|
|
607
|
+
- {{next_skill}} starts a fresh per-story flow (bmad-create-story
|
|
608
|
+
OR bmad-dev-story OR bmad-quick-dev) — never mid-story
|
|
609
|
+
- The current epic's DAG layer has width >= 2 (computed from
|
|
610
|
+
_Sprintpilot/sprints/dependencies.yaml via resolve-dag.js)
|
|
611
|
+
|
|
612
|
+
When all gates pass, the autopilot dispatches every story in the
|
|
613
|
+
current layer concurrently via the host's Agent tool — instead of
|
|
614
|
+
picking one story and running the full per-story flow sequentially.
|
|
615
|
+
This is the integration point that finally exercises the dispatcher
|
|
616
|
+
infrastructure built in PR 9 (resolve-dag) + PR 11 (dispatch-layer).
|
|
617
|
+
Without this block, parallel_stories=true had no behavioral effect.
|
|
618
|
+
────────────────────────────────────────────────────────────────── -->
|
|
619
|
+
<check if="{{parallel_stories}} is true AND {{host_supports_parallel}} is true AND {{implementation_flow}} is NOT quick AND ({{next_skill}} is bmad-create-story OR {{next_skill}} is bmad-dev-story OR {{next_skill}} is bmad-quick-dev)">
|
|
620
|
+
<action>**Compute current DAG layer.** Set `{{epic_id}}` = leading numeric segment of the FIRST undone story in `{status_file}`. Run:
|
|
621
|
+
`node {{project_root}}/_Sprintpilot/scripts/resolve-dag.js layers --epic "{{epic_id}}" --project-root "{{project_root}}"`
|
|
622
|
+
Parse stdout as a JSON array of arrays (each sub-array is one layer of story keys). Find the FIRST layer that contains at least one story whose status in `{status_file}` is NOT `done`. Filter that layer down to only the undone stories — set `{{active_layer}}` to the resulting list.</action>
|
|
623
|
+
<check if="{{active_layer}} length is 1">
|
|
624
|
+
<action>Single-story layer — no parallel benefit. Continue with normal sequential dispatch below (the standard step-3 flow picks the single story).</action>
|
|
625
|
+
</check>
|
|
626
|
+
<check if="{{active_layer}} length is 2 or more">
|
|
627
|
+
<action>**Parallel dispatch:** run:
|
|
628
|
+
`node {{project_root}}/_Sprintpilot/scripts/dispatch-layer.js --layer "{{active_layer | join(',')}}" --max-parallel "{{max_parallel_stories}}" --project-root "{{project_root}}" --branch-prefix "{{branch_prefix}}" --base-branch "{{base_branch}}"`
|
|
629
|
+
Parse stdout — captures the per-story worktree paths (`stories[*].worktree`) and the `effective_parallel` count.</action>
|
|
630
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "dispatch.layer-{{epic_id}}" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
631
|
+
<action>**Spawn N concurrent sub-agents — IMPORTANT: send a SINGLE message containing N parallel Agent tool calls (one per story).** Do NOT serialize. Each sub-agent runs the FULL per-story flow for its assigned story (bmad-create-story → bmad-check-implementation-readiness → bmad-dev-story → bmad-code-review → patches → mark done) inside its own worktree. The Agent tool calls run concurrently and the message reply contains all sub-agent results.
|
|
632
|
+
|
|
633
|
+
For each story key `K` in `{{active_layer}}`, build one Agent call with `subagent_type=general-purpose` and a self-contained prompt of the form:
|
|
634
|
+
```
|
|
635
|
+
You are running a single story for the Sprintpilot autopilot.
|
|
636
|
+
- Project root: {{project_root}}
|
|
637
|
+
- Story key: K
|
|
638
|
+
- Worktree: {{project_root}}/.worktrees/K (cd into it for all work)
|
|
639
|
+
- Skill flow (full): bmad-create-story → bmad-check-implementation-readiness → bmad-dev-story → bmad-code-review → apply patch findings → re-run tests → set status=done in {status_file}
|
|
640
|
+
- Skill flow (quick): bmad-quick-dev (single skill; nano profile)
|
|
641
|
+
Use {{implementation_flow}} = `{{implementation_flow}}` to pick which flow.
|
|
642
|
+
Track timing via `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story K --phase <phase> --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.
|
|
643
|
+
Return a one-line JSON summary on completion: {"story":"K", "status":"done"|"failed", "tests":"<N/M>", "notes":"<short>"}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Wait for ALL N sub-agents to return. Collect results; abort the autopilot ONLY if a sub-agent reports a TRUE BLOCKER per the AUTOPILOT RULES.</action>
|
|
647
|
+
<action>**Merge per-story shards** after the layer completes:
|
|
648
|
+
`node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — collapses the per-story state shards into the authoritative project YAMLs and archives the layer's shards under `.archive/layer-<timestamp>/` so the next layer starts clean.</action>
|
|
649
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase "_end" --project-root "{{project_root}}"` — closes the open `dispatch.layer-{{epic_id}}` mark.</action>
|
|
650
|
+
<action>Update `{state_file}` with the new STATE_FIELDS (each completed story's status is reflected via the merge above; advance `session_stories_done` by `{{active_layer}}.length`). Then `goto step=2` to re-evaluate the loop.</action>
|
|
651
|
+
<goto step="2">Re-evaluate after parallel layer dispatch</goto>
|
|
652
|
+
</check>
|
|
653
|
+
</check>
|
|
654
|
+
|
|
655
|
+
<!-- PR 4 NANO ROUTING: when the active profile's implementation_flow is
|
|
656
|
+
'quick', route bmad-dev-story through bmad-quick-dev instead. Quick-dev
|
|
657
|
+
runs Implement → Review → Classify → Commit internally (BMad
|
|
658
|
+
step-oneshot.md), so bmad-create-story / bmad-check-readiness /
|
|
659
|
+
bmad-code-review are not invoked in this flow. -->
|
|
660
|
+
<check if="{{implementation_flow}} is quick AND {{next_skill}} is bmad-dev-story">
|
|
661
|
+
<action>Override `{{next_skill}}` = `bmad-quick-dev`</action>
|
|
662
|
+
<action>Log: "Routing {{current_story}} through bmad-quick-dev per nano profile (implementation_flow=quick)"</action>
|
|
663
|
+
</check>
|
|
664
|
+
<!-- Under quick flow, autopilot never invokes bmad-create-story or
|
|
665
|
+
bmad-check-implementation-readiness; quick-dev reads AC from
|
|
666
|
+
sprint-status.yaml directly. If bmad-help proposes these skills
|
|
667
|
+
while implementation_flow=quick, skip them and advance. -->
|
|
668
|
+
<check if="{{implementation_flow}} is quick AND ({{next_skill}} is bmad-create-story OR {{next_skill}} is bmad-check-implementation-readiness)">
|
|
669
|
+
<action>Log: "Skipping {{next_skill}} under quick flow (nano profile) — quick-dev reads AC directly"</action>
|
|
670
|
+
<action>Set `{{next_skill}}` = `bmad-quick-dev`</action>
|
|
671
|
+
</check>
|
|
672
|
+
|
|
401
673
|
<action>Set `{{completed_skill}}` = `{{next_skill}}`</action>
|
|
402
674
|
<action>Create task "{{next_skill}}" → mark `in_progress`</action>
|
|
403
675
|
|
|
404
|
-
<check if="{{next_skill}} is a per-story skill (bmad-dev-story, bmad-code-review, bmad-create-story)">
|
|
676
|
+
<check if="{{next_skill}} is a per-story skill (bmad-dev-story, bmad-quick-dev, bmad-code-review, bmad-create-story)">
|
|
405
677
|
<action>Set `{{current_story}}` = first story in `{status_file}` with status `ready-for-dev` or `in-progress`</action>
|
|
406
678
|
|
|
407
679
|
<critical>**Validate story key format** — the key MUST follow the pattern `{epic}-{story}-{title-kebab}` (e.g., `1-2-user-authentication`), NOT just `{epic}-{story}` (e.g., `1-2`).
|
|
@@ -415,8 +687,23 @@ Resolve:
|
|
|
415
687
|
<action>Create per-story step tasks if not already created</action>
|
|
416
688
|
</check>
|
|
417
689
|
|
|
418
|
-
<!--
|
|
419
|
-
|
|
690
|
+
<!-- PR 5: determine per-story epic key + title so the workflow can branch
|
|
691
|
+
per-epic under granularity=epic and decide "is this the last story
|
|
692
|
+
of the epic". Epic ID is the leading numeric segment of the story
|
|
693
|
+
key (e.g. '1-2-foo' → '1'); slug is the epic's title from sprint-
|
|
694
|
+
status.yaml (or the epic header in the epics file). Skip if empty. -->
|
|
695
|
+
<action>Set `{{epic_id}}` = leading numeric segment of `{{current_story}}` (e.g. `1-2-foo` → `1`). If the key doesn't match `^\d+-`, leave `{{epic_id}}` = "".</action>
|
|
696
|
+
<action>Set `{{epic_branch_name}}` = `epic-{{epic_id}}` (only used when `{{granularity}} = epic`).</action>
|
|
697
|
+
<action>**Detect first vs last story of epic** — read `{status_file}` (BMAD-owned; do not modify). Find all stories with the same `{{epic_id}}`:
|
|
698
|
+
- `{{is_first_story_of_epic}}` = true if no story in this epic has status `in-progress` or `done` yet (i.e. current story is the first to enter dev-story / quick-dev).
|
|
699
|
+
- `{{is_last_story_of_epic}}` = true if this is the final undone story in the epic (after this story, all other stories in the epic are `done`).
|
|
700
|
+
Both default to true when `{{epic_id}}` = "" (single-story "epic").
|
|
701
|
+
</action>
|
|
702
|
+
|
|
703
|
+
<!-- GIT: Enter worktree OR create/reuse epic branch before dev-story OR quick-dev.
|
|
704
|
+
Nano's profile sets worktree.enabled=false + granularity=epic, so this
|
|
705
|
+
block falls through to in-place branching. -->
|
|
706
|
+
<check if="{{git_enabled}} AND ({{next_skill}} is bmad-dev-story OR {{next_skill}} is bmad-quick-dev)">
|
|
420
707
|
<action>**Sanitize branch name**: `node {{project_root}}/_Sprintpilot/scripts/sanitize-branch.js "{{current_story}}" --prefix "{{branch_prefix}}" --max-length 60`. Set `{{branch_name}}` = output. Full ref: `{{branch_prefix}}{{branch_name}}`.</action>
|
|
421
708
|
|
|
422
709
|
<action>**Idempotency check** — if branch is already registered in `{status_file}` for this story AND its worktree exists, skip creation. If registered without worktree → recovery mode (see health check). Otherwise proceed.</action>
|
|
@@ -430,23 +717,74 @@ Resolve:
|
|
|
430
717
|
Detached HEAD is fine — `git worktree add` below creates a new branch from HEAD.
|
|
431
718
|
</action>
|
|
432
719
|
|
|
433
|
-
|
|
720
|
+
<!-- PR 5: epic granularity — share one branch per epic instead of one per story.
|
|
721
|
+
Under worktree.enabled=false + granularity=epic (nano default),
|
|
722
|
+
subsequent stories of the same epic check out the epic branch in
|
|
723
|
+
place and commit there; no worktree is created. -->
|
|
724
|
+
<check if="{{granularity}} is epic">
|
|
725
|
+
<action>Override `{{branch_name}}` = `{{epic_branch_name}}` (shared across all stories in this epic).</action>
|
|
726
|
+
<check if="{{is_first_story_of_epic}} is true">
|
|
727
|
+
<action>Log: "Epic granularity: creating epic branch {{branch_prefix}}{{epic_branch_name}} for the first story of epic {{epic_id}}"</action>
|
|
728
|
+
</check>
|
|
729
|
+
<check if="{{is_first_story_of_epic}} is false">
|
|
730
|
+
<action>Log: "Epic granularity: reusing existing epic branch {{branch_prefix}}{{epic_branch_name}} for story {{current_story}}"</action>
|
|
731
|
+
</check>
|
|
732
|
+
</check>
|
|
733
|
+
|
|
734
|
+
<check if="{{worktree_enabled}} is false">
|
|
735
|
+
<action>**In-place branching** (granularity=epic or worktree.enabled=false): `git checkout -B {{branch_prefix}}{{branch_name}} 2>&1`. No worktree. Set `{{in_worktree}}` = false. Skip the worktree-add block below.</action>
|
|
736
|
+
</check>
|
|
737
|
+
|
|
738
|
+
<check if="{{worktree_enabled}} is true">
|
|
739
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
740
|
+
<action>**Create worktree.** Try: `git worktree add "{{project_root}}/.worktrees/{{current_story}}" -b "{{branch_prefix}}{{branch_name}}" 2>&1`. If it fails because the branch already exists, retry without `-b`: `git worktree add "{{project_root}}/.worktrees/{{current_story}}" "{{branch_prefix}}{{branch_name}}" 2>&1`.
|
|
434
741
|
|
|
435
742
|
If both fail (disk/permissions): log "WARN: worktree add failed — continuing without isolation", set `{{in_worktree}}` = false, and fall back to branch-only mode: `git checkout -b {{branch_prefix}}{{branch_name}}` (retry without `-b` if branch exists). HALT only if the checkout also fails. Git push/PR still work on the branch.
|
|
436
|
-
|
|
743
|
+
</action>
|
|
744
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.add" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
745
|
+
</check>
|
|
437
746
|
|
|
438
|
-
<check if="worktree add succeeded">
|
|
747
|
+
<check if="{{worktree_enabled}} is true AND worktree add succeeded">
|
|
439
748
|
<action>`cd {{project_root}}/.worktrees/{{current_story}}`. All subsequent commands run from here. Set `{{worktree_path}}` = this path.</action>
|
|
440
|
-
<action>**
|
|
749
|
+
<action>**Disable gc.auto on this worktree** (PR 10): save original value `{{original_gc_auto_worktree}}` = output of `node {{project_root}}/_Sprintpilot/scripts/git-portable.js config-get gc.auto --default unset --project-root "{{project_root}}/.worktrees/{{current_story}}"`, then `git -C {{project_root}}/.worktrees/{{current_story}} config --local gc.auto 0`.</action>
|
|
750
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
751
|
+
<action>**Init submodules** if `.gitmodules` exists (file-exists check via `node -e "process.exit(require('fs').existsSync('.gitmodules')?0:1)"`). PR 10 fast-path:
|
|
752
|
+
Resolve the main repo's common git dir into a captured variable `{{git_common}}` (cross-platform; replaces a `$(...)` shell substitution):
|
|
753
|
+
`node {{project_root}}/_Sprintpilot/scripts/git-portable.js common-dir --project-root "{{project_root}}"`. Trim trailing whitespace; the script exits 1 if git can't resolve the dir — log a warning and skip the submodule fast-path, falling through to the degraded-mode branch below.
|
|
754
|
+
For each submodule path in `.gitmodules`:
|
|
755
|
+
1. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js acquire --submodule "<path>" --project-root "{{project_root}}"` — serializes concurrent submodule updates across worktrees.
|
|
756
|
+
2. With git ≥ 2.18 (confirmed at boot by check-prereqs): wrap the update with retry to survive ref-lock contention:
|
|
757
|
+
`node {{project_root}}/_Sprintpilot/scripts/with-retry.js -- git -C {{project_root}}/.worktrees/{{current_story}} submodule update --init --recursive --reference "{{git_common}}" --jobs=4 -- <path>`
|
|
758
|
+
With older git (degraded mode flagged by check-prereqs): fall back to `git -C ... submodule update --init --recursive -- <path>`.
|
|
759
|
+
3. `node {{project_root}}/_Sprintpilot/scripts/submodule-lock.js release --submodule "<path>" --project-root "{{project_root}}"` (best-effort, ignore failures).
|
|
760
|
+
If the loop fails or hangs: warn "Submodule init failed (may need auth). Continuing." and proceed.
|
|
761
|
+
</action>
|
|
762
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "worktree.submodule-init" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
441
763
|
<action>Set `{{in_worktree}}` = true</action>
|
|
442
764
|
</check>
|
|
443
765
|
<action>Update `{state_file}` (write to the worktree copy since cwd is now the worktree)</action>
|
|
444
766
|
</check>
|
|
445
767
|
|
|
446
768
|
<action>Update `{state_file}` with STATE_FIELDS (set `current_bmad_step = executing`, `completed_skill = <previous skill>`).</action>
|
|
769
|
+
<check if="{{coalesce_state_writes}} is true">
|
|
770
|
+
<action>Mirror critical keys to the shard (bypasses batching for crash-recovery correctness):
|
|
771
|
+
`node {{project_root}}/_Sprintpilot/scripts/state-shard.js batch --story sprint --json "{\"current_bmad_step\":\"executing\",\"current_story\":\"{{current_story}}\",\"completed_skill\":\"<previous skill>\"}" --project-root "{{project_root}}"` — ignore failures.
|
|
772
|
+
</action>
|
|
773
|
+
</check>
|
|
447
774
|
|
|
448
775
|
<!-- Autopilot menu handling rules apply — see AUTOPILOT RULES section above -->
|
|
449
776
|
|
|
777
|
+
<!-- PHASE TIMING: emit start/end around every skill invocation.
|
|
778
|
+
Use `{{current_story}}` when set, else the sentinel `sprint` for
|
|
779
|
+
sprint-level skills (bmad-help, bmad-sprint-planning, etc).
|
|
780
|
+
The script is a silent no-op when autopilot.phase_timings is false. -->
|
|
781
|
+
<action>Set `{{timing_story}}` = `{{current_story}}` if non-empty, else `sprint`.</action>
|
|
782
|
+
<!-- Single-call timing via `mark`. The previous start/INVOKE/end triplet
|
|
783
|
+
was reliably broken in long sessions (LLM skipped the `end` call
|
|
784
|
+
~50% of the time), so most skills had no duration recorded. `mark`
|
|
785
|
+
auto-computes the duration of the PREVIOUS phase from a small marker
|
|
786
|
+
file — one call per transition, no open-phase failure mode. -->
|
|
787
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{timing_story}}" --phase "skill.{{next_skill}}" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
450
788
|
<action>INVOKE `{{next_skill}}` skill using the Skill tool</action>
|
|
451
789
|
<action>Mark task "{{next_skill}}" as `completed`</action>
|
|
452
790
|
|
|
@@ -457,8 +795,28 @@ Resolve:
|
|
|
457
795
|
|
|
458
796
|
<step n="4" goal="Handle skill completion and route to next action">
|
|
459
797
|
|
|
798
|
+
<!-- PR 4 NANO ROUTING: quick-dev completion handler.
|
|
799
|
+
Quick-dev's one-shot (step-oneshot.md:44) already ran Implement →
|
|
800
|
+
Review → Classify → Commit internally. Autopilot skips the external
|
|
801
|
+
bmad-code-review step and jumps straight to step 7 (mark story
|
|
802
|
+
done). Escalation safety net: if tests fail or classify severity is
|
|
803
|
+
high, flip implementation_flow to full for the rest of the session. -->
|
|
804
|
+
<check if="{{completed_skill}} was bmad-quick-dev">
|
|
805
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
806
|
+
<action>Verify tests ran — if not, run them now: report `N/N passed`. Record pass/fail into `{{tests_passed}}`.</action>
|
|
807
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
808
|
+
<action>Read quick-dev's Classify severity from its stdout/output. If its output mentions `severity: high` or a failing classify, set `{{quickdev_severity_high}}` = true.</action>
|
|
809
|
+
<check if="{{tests_passed}} is false OR {{quickdev_severity_high}} is true">
|
|
810
|
+
<action>**Escalation** — flip the session-scoped flow to `full`: set `{{implementation_flow}}` = `full`. Do NOT write this back to config.yaml; it is session-only. Log decision: `category=scope, phase=autopilot:escalation, impact=medium, "nano story {{current_story}} triggered fallback (tests_passed={{tests_passed}}, severity_high={{quickdev_severity_high}}) — subsequent stories use full cycle"` to `{decision_log_file}`.</action>
|
|
811
|
+
</check>
|
|
812
|
+
<action>Set `{{next_skill}}` = "(none)" — quick-dev handled review + commit internally per BMad step-oneshot.md.</action>
|
|
813
|
+
<goto step="7">Mark story done</goto>
|
|
814
|
+
</check>
|
|
815
|
+
|
|
460
816
|
<check if="{{completed_skill}} was bmad-dev-story">
|
|
817
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
461
818
|
<action>Verify tests ran — if not, run them now: report `N/N passed`</action>
|
|
819
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "tests.run" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
462
820
|
<action>**Log decisions** — review implementation choices made during dev-story and append entries to `{decision_log_file}` for any architecture, test-strategy, dependency, scope, or workaround decisions (see DECISION LOGGING section)</action>
|
|
463
821
|
|
|
464
822
|
<!-- GIT: Lint, stage, and commit after dev-story -->
|
|
@@ -476,7 +834,9 @@ Resolve:
|
|
|
476
834
|
- `{patch-title}` → from review finding title, fallback to "code review fix"
|
|
477
835
|
Read the commit template from `git.commit_templates.story` in config (default: `feat({epic}): {story-title} ({story-key})`).
|
|
478
836
|
Then run:
|
|
479
|
-
`node {{project_root}}/_Sprintpilot/scripts/
|
|
837
|
+
`node {{project_root}}/_Sprintpilot/scripts/log-timing.js start --story "{{current_story}}" --phase "git.commit" --project-root "{{project_root}}"` (ignore failures), then
|
|
838
|
+
`node {{project_root}}/_Sprintpilot/scripts/stage-and-commit.js --message "feat({{epic}}): {{story-title}} ({{current_story}})" --allowlist {{project_root}}/_Sprintpilot/.secrets-allowlist`, then
|
|
839
|
+
`node {{project_root}}/_Sprintpilot/scripts/log-timing.js end --story "{{current_story}}" --phase "git.commit" --project-root "{{project_root}}"` (ignore failures).
|
|
480
840
|
Output: commit SHA. Set `{{story_commit}}` = output.
|
|
481
841
|
Warnings (secrets, large files) printed to stderr — review but don't halt unless user says to.
|
|
482
842
|
</action>
|
|
@@ -513,6 +873,39 @@ Resolve:
|
|
|
513
873
|
</action>
|
|
514
874
|
</check>
|
|
515
875
|
|
|
876
|
+
<check if="{{completed_skill}} was bmad-sprint-planning">
|
|
877
|
+
<!-- PR-follow-up: sprint-planning populates development_status for the
|
|
878
|
+
first time. Recalculate stories_remaining so the step-2 "sprint
|
|
879
|
+
complete" gate doesn't fire spuriously on the next iteration. -->
|
|
880
|
+
<action>**Recalculate `{{stories_remaining}}`** — run:
|
|
881
|
+
`node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
|
|
882
|
+
Parse stdout as `{"remaining":[...],"state":"..."}` → `{{stories_remaining}}` = `.remaining`. Update `{state_file}` with the new value.</action>
|
|
883
|
+
</check>
|
|
884
|
+
|
|
885
|
+
<!-- 2.0.2: auto-infer inter-story dependencies via the autopilot's active
|
|
886
|
+
LLM session. Replaces the hand-authored dependencies.yaml step that
|
|
887
|
+
users never discovered. Gated on autopilot.auto_infer_dependencies
|
|
888
|
+
(default true on small/medium/large; false on nano and legacy).
|
|
889
|
+
The script never calls an LLM — it ingests our JSON output via stdin,
|
|
890
|
+
validates, and writes the sidecar with an AUTO-INFERRED marker.
|
|
891
|
+
Hand-authored sidecars (no marker) are detected and respected. -->
|
|
892
|
+
<check if="{{completed_skill}} was bmad-sprint-planning">
|
|
893
|
+
<action>Resolve `{{auto_infer_dependencies}}` from `autopilot.auto_infer_dependencies` (default false) via:
|
|
894
|
+
`node {{project_root}}/_Sprintpilot/scripts/resolve-profile.js get autopilot.auto_infer_dependencies --project-root "{{project_root}}"`</action>
|
|
895
|
+
<check if="{{auto_infer_dependencies}} is true">
|
|
896
|
+
<action>Set `{{distinct_epic_ids}}` = sorted unique leading-numeric segments of every key in `{{stories_remaining}}` (e.g. `1-1-foo` → `1`). Skip the loop if empty (pre-planning edge case).</action>
|
|
897
|
+
<foreach var="{{epic_id}}" in="{{distinct_epic_ids}}">
|
|
898
|
+
<action>Generate the inference prompt: run `node {{project_root}}/_Sprintpilot/scripts/infer-dependencies.js scaffold-prompt --epic "{{epic_id}}" --project-root "{{project_root}}"` and capture stdout as `{{infer_prompt}}`.</action>
|
|
899
|
+
<action>**Execute the inference inline.** The prompt instructs you to read four files (sprint-status.yaml, epics.md, architecture.md, dependencies.yaml-if-present) and emit ONE JSON object with shape `{"version":1,"epic":"{{epic_id}}","dependencies":{...},"rationale":{...}}`. Read those files NOW. Reason about which stories share modules, files, or describe themselves as building on each other (the prompt's RULES section enumerates the legitimate triggers). Emit the JSON envelope. No prose, no markdown fences. Set `{{infer_json}}` to the envelope.</action>
|
|
900
|
+
<action>Pipe `{{infer_json}}` into `node {{project_root}}/_Sprintpilot/scripts/infer-dependencies.js write --epic "{{epic_id}}" --project-root "{{project_root}}"`. Parse stdout JSON.
|
|
901
|
+
- On exit 0 (`wrote: true`) → log: `"Inferred {{response.edges_inferred}} dependency edges for epic {{epic_id}} (hash {{response.hash}}; user_overrides_preserved: {{response.user_overrides_preserved}})"`.
|
|
902
|
+
- On exit 2 (`wrote: false`, `reason: existing-hand-authored`) → log: `"Existing dependencies.yaml is hand-authored — skipping LLM inference for epic {{epic_id}}; user-authored sidecar wins"`. Do NOT halt.
|
|
903
|
+
- On exit 1 (validation error) → log the error envelope verbatim, then log: `"LLM dependency inference rejected for epic {{epic_id}} — falling back to linear ordering. Re-run /sprint-autopilot-on to retry, or hand-author the sidecar."`. Do NOT halt the autopilot — `resolve-dag.js` will fall back to the `ordering` strategy on dispatch.
|
|
904
|
+
</action>
|
|
905
|
+
</foreach>
|
|
906
|
+
</check>
|
|
907
|
+
</check>
|
|
908
|
+
|
|
516
909
|
<check if="{{completed_skill}} was bmad-sprint-planning AND {{git_enabled}}">
|
|
517
910
|
<action>If `{{has_origin}}` is true, run `git fetch origin` (log a warning on failure and continue — do not abort).
|
|
518
911
|
If `{{has_origin}}` is false, skip the fetch.</action>
|
|
@@ -520,7 +913,19 @@ Resolve:
|
|
|
520
913
|
</check>
|
|
521
914
|
|
|
522
915
|
<check if="{{completed_skill}} was bmad-create-story">
|
|
523
|
-
|
|
916
|
+
<!-- PR-follow-up (greenfield e2e fix): bmad-create-story sometimes omits
|
|
917
|
+
the Tasks/Subtasks section entirely. Re-running the skill doesn't
|
|
918
|
+
always fix it. Fall back to a deterministic script so the story
|
|
919
|
+
file ALWAYS has checkboxes dev-story can mark — no LLM prose in
|
|
920
|
+
this path. -->
|
|
921
|
+
<action>Locate the story file for `{{current_story}}` — glob
|
|
922
|
+
`_bmad-output/**/story-{{current_story}}.md` and
|
|
923
|
+
`_bmad-output/**/{{current_story}}.md`. Set `{{story_file_path}}`.
|
|
924
|
+
</action>
|
|
925
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/inject-tasks-section.js --story-file "{{story_file_path}}"`
|
|
926
|
+
— idempotent. The script checks for an existing `## Tasks` / `## Subtasks` section with at least one `- [ ]` checkbox and returns `{"action":"skip"}` if present. Otherwise it extracts every AC entry from the bounded `## Acceptance Criteria` section and appends a correctly-formatted section with one `- [ ] <AC>` bullet per entry.
|
|
927
|
+
Parse stdout JSON; the script never throws on a well-formed story file, so treat any non-zero exit as a log-and-proceed signal (the sprint can still complete without this guarantee — only the "task checkboxes marked" assertion degrades).
|
|
928
|
+
</action>
|
|
524
929
|
</check>
|
|
525
930
|
|
|
526
931
|
<check if="{{completed_skill}} was bmad-create-story AND {{git_enabled}}">
|
|
@@ -557,7 +962,11 @@ Resolve:
|
|
|
557
962
|
Log: "next_skill was empty but undone stories remain — resolved to {{next_skill}} for {{current_story}}".
|
|
558
963
|
</action>
|
|
559
964
|
<check if="all stories in status_file are done">
|
|
560
|
-
|
|
965
|
+
<!-- Same finalize-pending handoff as step 2 — never run step 10 in
|
|
966
|
+
the session that first noticed sprint-complete. -->
|
|
967
|
+
<action>Update `{state_file}`: `current_bmad_step = "sprint-finalize-pending"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`.</action>
|
|
968
|
+
<action>Release lock: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures.</action>
|
|
969
|
+
<action>Report: "Sprint complete detected during skill-recovery. Pausing for fresh-context finalization — run /sprint-autopilot-on once more." Then HALT.</action>
|
|
561
970
|
</check>
|
|
562
971
|
</check>
|
|
563
972
|
|
|
@@ -603,6 +1012,8 @@ For any finding that is DISMISSED (contradicts AC or is a false positive):
|
|
|
603
1012
|
<action>Mark "[story] Apply patches" → `completed`</action>
|
|
604
1013
|
|
|
605
1014
|
<!-- Re-run code review to sync sprint-status.yaml — patches resolved all findings, so code-review will now set story to done -->
|
|
1015
|
+
<!-- Single-call timing (mark) — see step-3 comment for rationale. -->
|
|
1016
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story "{{current_story}}" --phase "skill.bmad-code-review.rereview" --project-root "{{project_root}}"` — ignore failures.</action>
|
|
606
1017
|
<action>Re-invoke `bmad-code-review` using the Skill tool.
|
|
607
1018
|
The review layers already ran — this pass will see zero unresolved findings and set the story status to `done` in sprint-status.yaml (code-review owns that transition per step-04-present.md:92).
|
|
608
1019
|
Instruct: "Re-verify code review for story {{current_story}} — all patch findings have been applied. Update story status accordingly."
|
|
@@ -627,8 +1038,19 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
|
|
|
627
1038
|
- Final test count: `N/N passed`
|
|
628
1039
|
</action>
|
|
629
1040
|
|
|
630
|
-
<!--
|
|
631
|
-
|
|
1041
|
+
<!-- PR 5: epic granularity — defer push/PR until the LAST story of the epic.
|
|
1042
|
+
For intermediate stories (granularity=epic AND not is_last_story_of_epic):
|
|
1043
|
+
the work is already committed to the epic branch locally via stage-and-
|
|
1044
|
+
commit.js; no push or PR is attempted. The epic's last story pushes
|
|
1045
|
+
the accumulated commits and opens one PR for the whole epic. -->
|
|
1046
|
+
<check if="{{granularity}} is epic AND {{is_last_story_of_epic}} is false">
|
|
1047
|
+
<action>Log: "Epic granularity: skipping push/PR for {{current_story}} — will push at end of epic {{epic_id}}"</action>
|
|
1048
|
+
<action>Set `{{push_status}}` = "deferred", `{{pr_url}}` = "DEFERRED", `{{merge_status}}` = "deferred"</action>
|
|
1049
|
+
<!-- Skip the whole git-push block; fall through to the artifact-sync block below. -->
|
|
1050
|
+
</check>
|
|
1051
|
+
|
|
1052
|
+
<!-- GIT: Push, PR, exit worktree (story granularity OR last story of an epic) -->
|
|
1053
|
+
<check if="{{git_enabled}} AND ({{granularity}} is story OR {{is_last_story_of_epic}} is true) AND ({{in_worktree}} OR {{worktree_enabled}} is false)">
|
|
632
1054
|
<check if="{{push_auto}} is true">
|
|
633
1055
|
<action>**Push branch**.
|
|
634
1056
|
Run: `git push -u origin {{branch_prefix}}{{branch_name}} 2>&1`
|
|
@@ -666,14 +1088,20 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
|
|
|
666
1088
|
</action>
|
|
667
1089
|
|
|
668
1090
|
<check if="{{create_pr}} is false OR {{platform}} is git_only OR {{pr_url}} is null or SKIPPED">
|
|
669
|
-
<action>**Merge story 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.
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1091
|
+
<action>**Merge story/epic branch to main.** If `{{has_origin}}` is false (local-only), substitute `origin/{{base_branch}}` → `{{base_branch}}` and skip all `git push origin` / `git fetch origin` calls below.
|
|
1092
|
+
Choose merge strategy by `{{squash_on_merge}}` (PR 5): if true, use `git merge --squash` + single commit; otherwise standard merge commit.
|
|
1093
|
+
1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
|
|
1094
|
+
2. If `{{squash_on_merge}}` is true:
|
|
1095
|
+
`git merge --squash {{branch_prefix}}{{branch_name}}` then
|
|
1096
|
+
`git commit -m "feat({{epic_id}}): epic {{epic_id}} ({{branch_prefix}}{{branch_name}})"`.
|
|
1097
|
+
Otherwise: `git merge {{branch_prefix}}{{branch_name}} --no-edit`.
|
|
1098
|
+
3. On success: `git push origin {{base_branch}}`, set `{{merge_status}}` = "merged".
|
|
1099
|
+
4. On conflict: `git merge --abort`, `git fetch origin`, re-checkout base, retry merge once. On retry success: push + merged. On retry failure: `{{merge_status}}` = "failed", log warning, continue — the branch is preserved and boot reconciliation retries next session.
|
|
1100
|
+
5. **PR 12 cross-epic conflict interlock**: if this merge conflict involved two independent epics AND `{{parallel_epics}}` is true, set `{{cross_epic_disabled_this_session}}` = true and log "EXPERIMENTAL: cross-epic merge conflict detected; disabling parallel_epics for the remainder of this session." The flag resets on next session start.
|
|
673
1101
|
|
|
674
1102
|
`{{merge_status}}` is persisted by the sync-status.js call later in this step (via `--merge-status`). Do NOT call sync-status.js here — it does full block replacement and would destroy other fields.
|
|
675
1103
|
</action>
|
|
676
|
-
<check if="{{cleanup_on_merge}} is true">
|
|
1104
|
+
<check if="{{cleanup_on_merge}} is true AND {{in_worktree}} is true">
|
|
677
1105
|
<action>**Cleanup worktree** (ignore failures — may already be gone): `git worktree remove .worktrees/{{current_story}} --force` then `git worktree prune`</action>
|
|
678
1106
|
</check>
|
|
679
1107
|
</check>
|
|
@@ -701,6 +1129,17 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
|
|
|
701
1129
|
<action>**Increment `{{session_stories_done}}` by 1** — this is the ONLY place the counter ticks up. It runs only after the story's full implementation cycle (dev-story GREEN + code-review + patches + artifacts committed + optional push/PR). Creating a story file in step 3 never increments this counter.</action>
|
|
702
1130
|
<action>Remove `{{current_story}}` from `{{stories_remaining}}` list</action>
|
|
703
1131
|
|
|
1132
|
+
<!-- PR 6 STORY-BOUNDARY FLUSH: if coalescing is on, flush the sprint
|
|
1133
|
+
shard's pending buffer now (writes accumulated non-critical fields
|
|
1134
|
+
to the shard) and merge all shards into the authoritative
|
|
1135
|
+
autopilot-state.yaml / decision-log.yaml. Fast on a single-story
|
|
1136
|
+
sprint; amortized when multiple stories complete per session. -->
|
|
1137
|
+
<check if="{{coalesce_state_writes}} is true">
|
|
1138
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"` — ignore failures.</action>
|
|
1139
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story "{{current_story}}" --project-root "{{project_root}}"` — ignore failures (no-op if no per-story shard was ever batched).</action>
|
|
1140
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"` — ignore failures. Produces merged autopilot-state.yaml + decision-log.yaml.</action>
|
|
1141
|
+
</check>
|
|
1142
|
+
|
|
704
1143
|
<action>Report: "Story {{current_story}} done — N/N passing{{#if pr_url}} — PR: {{pr_url}}{{/if}}"</action>
|
|
705
1144
|
|
|
706
1145
|
<action>Check if ALL stories in this epic are `done`</action>
|
|
@@ -788,6 +1227,13 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
|
|
|
788
1227
|
</check>
|
|
789
1228
|
|
|
790
1229
|
<action>Update `{state_file}` with STATE_FIELDS.</action>
|
|
1230
|
+
<check if="{{coalesce_state_writes}} is true">
|
|
1231
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"` — ignore failures.</action>
|
|
1232
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --project-root "{{project_root}}"` — ignore failures.</action>
|
|
1233
|
+
</check>
|
|
1234
|
+
|
|
1235
|
+
<!-- Phase-timing session snapshot (no-op if autopilot.phase_timings is false). -->
|
|
1236
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/summarize-timings.js --session-only --format md --quiet --project-root "{{project_root}}"` — ignore failures. The stdout line is the artifact path; include it in the checkpoint report if non-empty.</action>
|
|
791
1237
|
|
|
792
1238
|
<action>Read `{decision_log_file}` — count medium/high decisions from this session's stories</action>
|
|
793
1239
|
|
|
@@ -827,15 +1273,88 @@ No work will be repeated.
|
|
|
827
1273
|
|
|
828
1274
|
<step n="10" goal="Sprint complete — emit summary and next steps">
|
|
829
1275
|
|
|
830
|
-
<!--
|
|
1276
|
+
<!-- CRITICAL-PATH-FIRST: these 6 actions MUST run before anything else in
|
|
1277
|
+
step 10. They put the repo into a correct terminal state — orphan
|
|
1278
|
+
worktrees removed, lock released, artifacts committed to main,
|
|
1279
|
+
sprint-complete state marked, task checkboxes marked, final verification.
|
|
1280
|
+
Even if the rest of step 10 is interrupted (SIGTERM, LLM improvises an
|
|
1281
|
+
early summary, context exhaustion), the harness invariants hold. DO NOT
|
|
1282
|
+
reorder or skip these — the order matters: worktrees-before-lock so a
|
|
1283
|
+
concurrent autopilot session doesn't see orphans, lock-before-commit
|
|
1284
|
+
so commits run with the repo already unlocked, etc. -->
|
|
1285
|
+
|
|
1286
|
+
<!-- GIT: Exit worktree if still in one (prerequisite for the rest) -->
|
|
831
1287
|
<check if="{{in_worktree}}">
|
|
832
1288
|
<action>`cd {{project_root}}` — return to project root</action>
|
|
833
1289
|
<action>Set `{{in_worktree}}` = false</action>
|
|
834
1290
|
</check>
|
|
835
1291
|
|
|
836
|
-
|
|
1292
|
+
<!-- Run the mark-tasks helper AS THE VERY FIRST action of step 10.
|
|
1293
|
+
The LLM reliably executes the first critical action but sometimes
|
|
1294
|
+
skips later ones once it feels "done" with the sprint. Putting this
|
|
1295
|
+
first ensures every done-story's checkboxes get marked before the
|
|
1296
|
+
LLM loses focus. -->
|
|
1297
|
+
<action>**[CRITICAL 1/7] Mark all done stories' Task checkboxes** — deterministic helper. Run:
|
|
1298
|
+
`node {{project_root}}/_Sprintpilot/scripts/mark-done-stories-tasks.js --status-file "{status_file}" --project-root "{{project_root}}"` — ignore failures. This finds every story with status="done" in sprint-status.yaml and replaces `- [ ]` with `- [x]` in its story file.
|
|
1299
|
+
</action>
|
|
1300
|
+
|
|
1301
|
+
<action>**[CRITICAL 2/7] Remove autopilot-owned worktrees** — no-op if none exist. Only worktrees we created (paths inside `{{project_root}}/.worktrees/`) are removed; any user-placed worktree elsewhere is left untouched.
|
|
1302
|
+
<check if="{{git_enabled}}">
|
|
1303
|
+
Run: `git worktree list --porcelain` to enumerate. For each worktree whose path starts with `{{project_root}}/.worktrees/`:
|
|
1304
|
+
- `git worktree remove <path> --force 2>&1` (ignore failures — best-effort).
|
|
1305
|
+
Then once: `git worktree prune 2>&1` (ignore failures).
|
|
1306
|
+
This is the SINGLE authoritative cleanup site for autopilot-owned worktrees — do not wait for a later "safety net" cleanup that might not run.
|
|
1307
|
+
</check>
|
|
1308
|
+
</action>
|
|
1309
|
+
|
|
1310
|
+
<action>**[CRITICAL 3/7] Release lock immediately** — before any slow operation:
|
|
1311
|
+
Run: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` — ignore failures. Idempotent.
|
|
1312
|
+
</action>
|
|
1313
|
+
|
|
1314
|
+
<action>**[CRITICAL 4/7] Commit any final artifacts + planning docs to main** — even if the rest of step 10 is interrupted. The checkout is deliberately non-destructive: we never use `-B` or `-f` (those silently wipe local commits or modifications). If the working tree is dirty, the commit step still runs; if the checkout fails, the commit is skipped with a warning rather than forced.
|
|
1315
|
+
<check if="{{git_enabled}}">
|
|
1316
|
+
1. `git switch {{base_branch}} 2>&1 || git checkout {{base_branch}} 2>&1` (plain switch; if `{{base_branch}}` doesn't exist locally, `git checkout --track origin/{{base_branch}}` once to create it tracking origin). Never use `-B` or `-f` — they discard work.
|
|
1317
|
+
2. If `{{has_origin}}` is true: `git pull --ff-only origin {{base_branch}} 2>&1` (fast-forward only; if the pull would require a merge, log a warning and continue — do NOT `reset --hard`).
|
|
1318
|
+
3. Stage artifacts via the cross-platform helper (replaces `git add … 2>/dev/null || true`):
|
|
1319
|
+
`node {{project_root}}/_Sprintpilot/scripts/git-portable.js safe-add _bmad-output/planning-artifacts _bmad-output/implementation-artifacts/sprint-status.yaml _bmad-output/implementation-artifacts/decision-log.yaml _bmad-output/implementation-artifacts/git-status.yaml _bmad-output/stories _bmad-output/implementation-artifacts/retrospectives --project-root "{{project_root}}"`
|
|
1320
|
+
The script filters paths to those that actually exist on disk before invoking git, so missing paths are skipped silently with no shell error suppression needed.
|
|
1321
|
+
4. If `git diff --cached --quiet` exits non-zero: `git commit -m "docs: sprint artifacts — planning + stories + retrospective"` then (if `{{has_origin}}` is true) `git push origin {{base_branch}}` (warn on failure, do not halt).
|
|
1322
|
+
</check>
|
|
1323
|
+
</action>
|
|
1324
|
+
|
|
1325
|
+
<action>**[CRITICAL 5/7] Mark sprint-complete state**: update `{state_file}`: `current_bmad_step = "sprint-complete"`, `current_story = null`, `next_skill = null`, `stories_remaining = []`, `session_stories_done = {{session_stories_done}}`. This signals the test harness and any next /sprint-autopilot-on invocation that the sprint is genuinely done.</action>
|
|
1326
|
+
|
|
1327
|
+
<action>**[CRITICAL 6/7] Verify** — run:
|
|
1328
|
+
`node {{project_root}}/_Sprintpilot/scripts/list-remaining-stories.js --status-file "{status_file}" --format envelope`
|
|
1329
|
+
Parse stdout as JSON. If `.state` is `sprint-complete` AND `.remaining` is empty, the sprint is verified complete. If `.state` is anything else (e.g. `sprint-in-progress` with leftover stories), log a warning — but do NOT revert the above actions. The worktrees are gone; the lock is released; the artifacts are committed; the state is marked complete; task checkboxes are marked. Manual recovery only.
|
|
1330
|
+
</action>
|
|
1331
|
+
|
|
1332
|
+
<!-- Final terminal-state marker. Kept inside the CRITICAL block because
|
|
1333
|
+
the previous "Delete state_file" action at the tail of step 10 was
|
|
1334
|
+
being skipped by context-rot in long sessions (observed in e2e runs:
|
|
1335
|
+
"autopilot-state.yaml still exists" warning), leaving a stale state
|
|
1336
|
+
file that confused the next /sprint-autopilot-on boot. CRITICAL 5/7
|
|
1337
|
+
already wrote sprint-complete as a belt-and-suspenders in case this
|
|
1338
|
+
delete crashes mid-run; step 1 also short-circuits on a leftover
|
|
1339
|
+
sprint-complete state so accidental re-invocation doesn't loop. -->
|
|
1340
|
+
<action>**[CRITICAL 7/7] Delete state_file** — `node -e "require('node:fs').rmSync('{state_file}', { force: true })"` (idempotent; never halt on failure). Removes `autopilot-state.yaml` so the next invocation starts cleanly. CRITICAL 5/7 already wrote `current_bmad_step = sprint-complete` to the same file as a crash-safe marker, so if this delete fails mid-run the next session still sees the terminal state.</action>
|
|
1341
|
+
|
|
1342
|
+
<!-- Close the last open `mark` phase so its duration gets recorded.
|
|
1343
|
+
Without this, the very last skill's duration would be lost (no
|
|
1344
|
+
subsequent mark fires after sprint-complete). -->
|
|
1345
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/log-timing.js mark --story sprint --phase _end --project-root "{{project_root}}"` — ignore failures.</action>
|
|
1346
|
+
|
|
837
1347
|
<action>Run full test suite — report `N/N passed`</action>
|
|
838
1348
|
|
|
1349
|
+
<!-- PR 6 SPRINT-COMPLETE FLUSH + ARCHIVE (no-op if coalescing is off). -->
|
|
1350
|
+
<check if="{{coalesce_state_writes}} is true">
|
|
1351
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/state-shard.js flush --story sprint --project-root "{{project_root}}"` — ignore failures.</action>
|
|
1352
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/merge-shards.js --archive --project-root "{{project_root}}"` — ignore failures. --archive moves merged shards to .archive/layer-<timestamp>/ (the script generates the ISO timestamp itself; no shell `$(...)` substitution needed) so the next sprint starts clean.</action>
|
|
1353
|
+
</check>
|
|
1354
|
+
|
|
1355
|
+
<!-- Final phase-timing hotspot report (no-op if autopilot.phase_timings is false). -->
|
|
1356
|
+
<action>Run: `node {{project_root}}/_Sprintpilot/scripts/summarize-timings.js --format md --quiet --project-root "{{project_root}}"` — ignore failures. The stdout line is the artifact path; include it in the sprint report if non-empty.</action>
|
|
1357
|
+
|
|
839
1358
|
<!-- Generate project documentation after sprint completion -->
|
|
840
1359
|
<action>**Resolve stack** — set `{{stack}}` = `{ name, install_cmd, run_cmd, test_cmd }` using the first successful source:
|
|
841
1360
|
|
|
@@ -866,21 +1385,32 @@ No work will be repeated.
|
|
|
866
1385
|
<action>**Generate documentation** — invoke `bmad-document-project`. If unavailable or it fails, write a minimal README using `{{stack}}`: project name + description (from brief/PRD); install/run/test lines for each non-null `{{stack.*_cmd}}` (omit lines where null); architecture overview if `architecture.md` exists.</action>
|
|
867
1386
|
|
|
868
1387
|
<check if="{{git_enabled}}">
|
|
869
|
-
<action>**Commit
|
|
870
|
-
1. `git
|
|
871
|
-
2. `git
|
|
872
|
-
3.
|
|
1388
|
+
<action>**Commit README + docs to main** (sprint artifacts already committed in CRITICAL 4/7 above — this picks up README.md / docs/ that were generated later by bmad-document-project). Non-destructive checkout — never `-B` or `-f`:
|
|
1389
|
+
1. `git switch {{base_branch}} 2>&1 || git checkout {{base_branch}} 2>&1`.
|
|
1390
|
+
2. If `{{has_origin}}` is true: `git pull --ff-only origin {{base_branch}} 2>&1` (warn on non-ff; do NOT reset).
|
|
1391
|
+
3. Stage docs via the cross-platform helper:
|
|
1392
|
+
`node {{project_root}}/_Sprintpilot/scripts/git-portable.js safe-add README.md docs/ --project-root "{{project_root}}"`
|
|
1393
|
+
(paths are filtered to those that exist; missing paths are skipped).
|
|
1394
|
+
4. If `git diff --cached --quiet` exits non-zero: `git commit -m "docs: project documentation"` then (if `{{has_origin}}`) `git push origin {{base_branch}}` (warn on push failure, do not halt).
|
|
873
1395
|
</action>
|
|
874
1396
|
</check>
|
|
875
1397
|
|
|
876
1398
|
<action>**Collect report data** from `{status_file}` (stories grouped by epic with titles, totals, final test count; PR/MR URLs, patch/dismissed counts per story if git_enabled) and `{decision_log_file}` (medium/high-impact decisions; counts of `review-accept`, `review-triage`, code-review rounds; per-story patches-applied / findings-dismissed).</action>
|
|
877
1399
|
|
|
878
1400
|
<check if="{{git_enabled}}">
|
|
879
|
-
|
|
880
|
-
|
|
1401
|
+
<!-- Worktree cleanup already ran as CRITICAL 2/7 above. Intentionally
|
|
1402
|
+
no duplicate here — relying on early cleanup to avoid orphans
|
|
1403
|
+
when the LLM short-circuits late actions. -->
|
|
1404
|
+
<action>(No-op: worktree cleanup already executed in CRITICAL 2/7.)</action>
|
|
1405
|
+
<!-- PR 10: restore main-repo gc.auto to its prior value. -->
|
|
1406
|
+
<action>**Restore main-repo gc.auto**:
|
|
1407
|
+
if `{{original_gc_auto_main}}` is "unset": `git config --local --unset gc.auto` (ignore failure — may already be unset).
|
|
1408
|
+
else: `git config --local gc.auto {{original_gc_auto_main}}`.
|
|
1409
|
+
</action>
|
|
1410
|
+
<action>Belt-and-suspenders: `node {{project_root}}/_Sprintpilot/scripts/lock.js release` (already released earlier in step 10; this is idempotent and catches any race where the early release failed).</action>
|
|
881
1411
|
</check>
|
|
882
1412
|
|
|
883
|
-
|
|
1413
|
+
<!-- State file deletion moved to CRITICAL 7/7 above for reliability. -->
|
|
884
1414
|
<action>Mark master task "Sprintpilot — Full Sprint Execution" → `completed`</action>
|
|
885
1415
|
|
|
886
1416
|
<action>Read template `{{project_root}}/_Sprintpilot/templates/sprint-report.txt`, fill mustache placeholders with the collected data, and print the result verbatim as the final message.</action>
|