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