@ikunin/sprintpilot 1.0.2 → 1.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.
@@ -8,6 +8,24 @@ You do NOT hardcode the workflow sequence. After each completed skill, read its
8
8
 
9
9
  **Git integration** is additive. If `_Sprintpilot/manifest.yaml` doesn't exist or `git.enabled: false`, all git operations are silently skipped and this workflow behaves identically to the stock autopilot.
10
10
 
11
+ ### Shell portability (IMPORTANT)
12
+
13
+ Sprintpilot runs under any LLM CLI (Claude Code, Gemini CLI, Cursor, etc.) on any OS. The shell that executes commands may be **bash, zsh, PowerShell, or cmd** depending on platform and CLI. Shell-specific idioms will fail silently when the wrong shell is used.
14
+
15
+ **When you encounter bash-style idioms below, translate them to your shell.** The table applies to **external commands** (like `git`); cmdlets have slightly different conventions.
16
+
17
+ | Bash idiom | PowerShell equivalent | Meaning |
18
+ |---|---|---|
19
+ | `A && B` | `A; if ($LASTEXITCODE -eq 0) { B }` (or separate commands, guarding B manually) | Run B only if A succeeded |
20
+ | `A \|\| true` | `A; $LASTEXITCODE = 0` (or `try { A } catch {}` for cmdlets) | Run A, ignore failures |
21
+ | `2>/dev/null` | `2>$null` | Suppress stderr |
22
+ | `rm -rf <dir>` | `Remove-Item -Recurse -Force <dir>` | Recursive delete |
23
+ | `if [ -f X ]; then ... fi` | `if (Test-Path -PathType Leaf X) { ... }` | File-exists check (regular file, not dir) |
24
+
25
+ **Safer:** when in doubt, use the cross-platform Node helpers under `_Sprintpilot/scripts/`. For ad-hoc file ops, invoke Node inline: `node -e "require('fs').rmSync('<path>', {recursive: true, force: true})"`.
26
+
27
+ If a step below uses `&&` to chain "run B only on A's success", and you cannot express that in one line, **run the commands separately and STOP if any step fails** — do not proceed past a failed step.
28
+
11
29
  ---
12
30
 
13
31
  ## AUTOPILOT RULES — READ BEFORE PROCEEDING
@@ -120,6 +138,7 @@ All BMAD skills are fully automatable (auto-continue past menus, derive decision
120
138
  | `bmad-create-ux-design` | Automatable if PRD exists; BLOCKER if no PRD |
121
139
  | `bmad-party-mode` | Skip — inherently interactive |
122
140
  | `bmad-brainstorming` | Skip — inherently interactive |
141
+ | `bmad-retrospective` | Under autopilot, handled per `autopilot.retrospective_mode`: `auto` (default — inline artifact, no external skill call), `stop` (pause so user runs `/bmad-retrospective` interactively, then resumes autopilot), or `skip` (not recommended). The external skill is NOT invoked from autopilot because it enters a multi-persona discussion loop under some CLIs. |
123
142
 
124
143
  ---
125
144
 
@@ -155,13 +174,15 @@ Resolve:
155
174
  </action>
156
175
  <action>Read `{project-root}/_Sprintpilot/modules/autopilot/config.yaml` (if present) and set:
157
176
  - `{{session_story_limit}}` from `autopilot.session_story_limit` (default: 3). A value of 0 disables the limit (run until sprint complete).
158
- If the file or key is missing, fall back to 3.
177
+ - `{{retrospective_mode}}` from `autopilot.retrospective_mode` (default: `auto`). Valid values: `auto` | `stop` | `skip`. Any unknown value falls back to `auto`.
178
+ If the file or either key is missing, fall back to the defaults above.
159
179
  </action>
160
180
  </check>
161
181
 
162
182
  <check if="manifest does NOT exist">
163
183
  <action>Set `{{git_enabled}}` = false</action>
164
184
  <action>Set `{{session_story_limit}}` = 3</action>
185
+ <action>Set `{{retrospective_mode}}` = `auto`</action>
165
186
  <action>Log: "No _Sprintpilot/manifest.yaml found — running stock autopilot (no git)"</action>
166
187
  </check>
167
188
 
@@ -170,7 +191,9 @@ Resolve:
170
191
  <check if="not a git repo">
171
192
  <action>HALT: "No git repository found. Initialize one first:
172
193
  ```
173
- git init && git add -A && git commit -m 'initial commit'
194
+ git init
195
+ git add -A
196
+ git commit -m 'initial commit'
174
197
  git remote add origin <your-repo-url>
175
198
  ```
176
199
  Then run /sprint-autopilot-on again."</action>
@@ -201,15 +224,20 @@ Resolve:
201
224
  - COMMITTED: log "Recoverable work found for <name> — will push via git -C"
202
225
  Push the branch: `git -C .worktrees/<name> push -u origin <branch> 2>&1`
203
226
  If `{{create_pr}}` is true AND platform != git_only: create PR via `node {{project_root}}/_Sprintpilot/scripts/create-pr.js ...`
204
- If `{{create_pr}}` is false OR platform is git_only: merge directly `git checkout -B {{base_branch}} origin/{{base_branch}} && git merge <branch> --no-edit && git push origin {{base_branch}}`
227
+ If `{{create_pr}}` is false OR platform is git_only: merge directly. Run each as a separate command; **STOP and log the failure if any step fails — do not proceed past a failed step**:
228
+ 1. `git checkout -B {{base_branch}} origin/{{base_branch}}`
229
+ 2. `git merge <branch> --no-edit`
230
+ 3. `git push origin {{base_branch}}`
205
231
  Then remove worktree.
206
232
  - STALE: `git worktree remove .worktrees/<name> --force` + prune
207
233
  - DIRTY: warn user, ask how to proceed (stash/commit/discard)
208
- - ORPHAN: `rm -rf .worktrees/<name>` + `git worktree prune`
234
+ - ORPHAN: remove the directory cross-platform with `node -e "require('fs').rmSync('.worktrees/<name>', {recursive: true, force: true})"`, then `git worktree prune`
209
235
  </action>
210
236
 
211
237
  <action>**Branch reconciliation** — detect pushed-but-unmerged story branches.
212
- Run: `git fetch origin && git branch -r --list "origin/{{branch_prefix}}*"`
238
+ Run as separate commands — **if `git fetch origin` fails (network/auth), STOP branch reconciliation and log a warning; do not operate on stale local refs**:
239
+ 1. `git fetch origin`
240
+ 2. `git branch -r --list "origin/{{branch_prefix}}*"`
213
241
  For each remote branch:
214
242
  - Extract story-key from branch name (strip "origin/{{branch_prefix}}" prefix)
215
243
  - Look up story status in `{status_file}`
@@ -226,16 +254,16 @@ Resolve:
226
254
  - If merge fails: log warning, continue (branch is preserved on remote)
227
255
  - If merge succeeds:
228
256
  - Re-read `{status_file}` from HEAD (may now include story artifacts after merge)
229
- - Update `{git_status_file}` via sync-status.sh: set `--merge-status "recovered"` for this story.
230
- **IMPORTANT:** sync-status.sh does full block replacement. If the story already has an entry in `{git_status_file}`, re-read its existing fields and pass ALL of them alongside `--merge-status`. If no entry exists yet, pass at minimum `--branch` and `--push-status "pushed"`.
257
+ - Update `{git_status_file}` via sync-status.js: set `--merge-status "recovered"` for this story.
258
+ **IMPORTANT:** sync-status.js does full block replacement. If the story already has an entry in `{git_status_file}`, re-read its existing fields and pass ALL of them alongside `--merge-status`. If no entry exists yet, pass at minimum `--branch` and `--push-status "pushed"`.
231
259
  - If `{{platform}}` is NOT git_only (github, gitlab, bitbucket, gitea) AND `{{create_pr}}` is true:
232
260
  - Check if PR/MR already exists for this branch (platform-specific check via create-pr.sh or CLI)
233
261
  - If no PR: create one via `node {{project_root}}/_Sprintpilot/scripts/create-pr.js --platform {{platform}} ...`
234
262
  - Log: "PR created/found for <story-key>"
235
- - Update `{git_status_file}` via sync-status.sh: set `--merge-status "pr_pending"` for this story (same full-field requirement as above)
263
+ - Update `{git_status_file}` via sync-status.js: set `--merge-status "pr_pending"` for this story (same full-field requirement as above)
236
264
  - If status IS "done" AND branch still exists AND `{{cleanup_on_merge}}` is true:
237
265
  - Log: "Stale remote branch: <branch> — story already done, cleaning up"
238
- - Delete remote branch: `git push origin --delete <branch> 2>/dev/null || true`
266
+ - Delete remote branch (ignore failure — the branch may already be gone): `git push origin --delete <branch>`
239
267
  </action>
240
268
 
241
269
  <action>Set `{{git_enabled}}` = true, `{{platform}}` = detected value</action>
@@ -290,6 +318,34 @@ Resolve:
290
318
  - Advance to next non-done story
291
319
  - Update `{state_file}` with reconciled values
292
320
  </action>
321
+
322
+ <!-- Resume from a `retrospective_mode: stop` pause.
323
+ The user was told to run /bmad-retrospective interactively. If they
324
+ did, the epic is now `done` in {status_file} (or a retrospective
325
+ artifact exists). Otherwise, re-issue the instructions and halt. -->
326
+ <check if="{state_file}.paused_at is epic-complete-awaiting-retrospective">
327
+ <action>Set `{{paused_epic_id}}` from `{state_file}.paused_epic_id`</action>
328
+ <action>Check whether epic `{{paused_epic_id}}` is now `done` in `{status_file}` OR an artifact exists at `{implementation_artifacts}/retrospectives/epic-{{paused_epic_id}}-*.md`</action>
329
+ <check if="epic is done OR retrospective artifact exists">
330
+ <action>Clear `paused_at`, `paused_epic_id`, and `next_action` from `{state_file}`</action>
331
+ <action>Log: "Epic {{paused_epic_id}} retrospective detected — resuming autopilot"</action>
332
+ </check>
333
+ <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>
346
+ </check>
347
+ </check>
348
+
293
349
  <goto step="2">Jump to execution loop with reconciled state</goto>
294
350
  </check>
295
351
 
@@ -344,6 +400,7 @@ Resolve:
344
400
  Git integration: {{git_enabled}}
345
401
  Platform: {{platform}}
346
402
  Session limit: {{session_story_limit}} stories, then checkpoint + new session
403
+ Retrospective mode: {{retrospective_mode}}
347
404
 
348
405
  Beginning autonomous execution. I will only stop for true blockers or session checkpoints.
349
406
  ```
@@ -486,8 +543,8 @@ Resolve:
486
543
  </action>
487
544
 
488
545
  <action>**Init submodules** if needed.
489
- Run: `if [ -f .gitmodules ]; then timeout 30 git submodule update --init --recursive 2>&1 || echo "SUBMODULE_TIMEOUT"; fi`
490
- If SUBMODULE_TIMEOUT: warn "Submodule init timed out (may need auth). Continuing without."
546
+ First check for `.gitmodules` (use your file-exists tool, or `node -e "process.exit(require('fs').existsSync('.gitmodules')?0:1)"`). If not present, skip this step.
547
+ If present, run `git submodule update --init --recursive` (give it ~30 seconds). If the command fails or hangs, warn "Submodule init failed (may need auth). Continuing without." and proceed.
491
548
  </action>
492
549
 
493
550
  <action>Set `{{in_worktree}}` = true</action>
@@ -561,8 +618,12 @@ pr_base: {{pr_base}}
561
618
  <goto step="7">Mark story done</goto>
562
619
  </check>
563
620
 
564
- <check if="{{completed_skill}} was bmad-retrospective">
565
- <action>Log: "Epic retrospective complete BMAD skills will update sprint-status.yaml directly"</action>
621
+ <check if="{{completed_skill}} is retrospective-auto">
622
+ <action>Log: "Epic retrospective generated inline by autopilot sprint-status.yaml updated"</action>
623
+ </check>
624
+
625
+ <check if="{{completed_skill}} is retrospective-skip">
626
+ <action>Log: "Epic retrospective skipped per config — sprint-status.yaml updated inline"</action>
566
627
  </check>
567
628
 
568
629
  <check if="{{completed_skill}} was bmad-create-epics-and-stories">
@@ -595,17 +656,18 @@ pr_base: {{pr_base}}
595
656
  <!-- GIT: Commit planning artifacts to main after planning skills -->
596
657
  <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)">
597
658
  <action>**Commit planning artifacts to main** — keep track of all planning decisions in git.
598
- Stage all changed artifacts:
659
+ Stage all changed artifacts (ignore errors — any of these paths may not yet exist):
599
660
  ```
600
- git add _bmad-output/planning-artifacts/ _bmad-output/implementation-artifacts/ _bmad-output/stories/ 2>/dev/null || true
661
+ git add _bmad-output/planning-artifacts/ _bmad-output/implementation-artifacts/ _bmad-output/stories/
601
662
  ```
602
- If there are staged changes, commit:
663
+ Check if there's anything staged; if yes, commit:
603
664
  ```
604
- git diff --cached --quiet || git commit -m "docs: {{completed_skill}} artifacts"
665
+ git diff --cached --quiet
605
666
  ```
606
- Push to remote if possible:
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):
607
669
  ```
608
- git push origin {{base_branch}} 2>/dev/null || true
670
+ git push origin {{base_branch}}
609
671
  ```
610
672
  </action>
611
673
  </check>
@@ -775,12 +837,12 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
775
837
  Log warning but do NOT halt. The branch is pushed and preserved.
776
838
  Boot reconciliation (INITIALIZATION branch reconciliation) will retry on next session.
777
839
 
778
- Note: `{{merge_status}}` is persisted by the full sync-status.sh call later in this step (via `--merge-status`). Do NOT call sync-status.sh separately here — it does full block replacement and would destroy other fields.
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.
779
841
  </action>
780
842
  <check if="{{cleanup_on_merge}} is true">
781
- <action>**Cleanup worktree** for merged story — branch was merged locally, worktree is no longer needed:
843
+ <action>**Cleanup worktree** for merged story — branch was merged locally, worktree is no longer needed. Ignore failures from the remove (the worktree may already be gone):
782
844
  ```
783
- git worktree remove .worktrees/{{current_story}} --force 2>/dev/null || true
845
+ git worktree remove .worktrees/{{current_story}} --force
784
846
  git worktree prune
785
847
  ```
786
848
  </action>
@@ -804,17 +866,21 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
804
866
  This writes to `git-status.yaml` (addon-owned). Sprint-status.yaml is BMAD-owned — updated by BMAD skills only.
805
867
  </action>
806
868
 
807
- <action>**Stage and commit artifacts** — explicitly include git-status.yaml and decision-log.yaml:
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):
808
870
  ```
809
- 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/ 2>/dev/null || true
810
- git diff --cached --quiet || git commit -m "docs: story {{current_story}} done — {{test_count}} tests{{#if pr_url}}, PR: {{pr_url}}{{/if}}"
811
- git push origin {{base_branch}} 2>/dev/null || true
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}}
812
878
  ```
813
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.
814
880
  </action>
815
881
  </check>
816
882
 
817
- <!-- Story git status was already written by sync-status.sh above (when git_enabled AND in_worktree).
883
+ <!-- Story git status was already written by sync-status.js above (when git_enabled AND in_worktree).
818
884
  sprint-status.yaml is BMAD-owned — updated by bmad-dev-story / bmad-code-review directly. -->
819
885
  <check if="NOT {{git_enabled}}">
820
886
  <action>Log: "Story {{current_story}} complete — BMAD dev-story updates sprint-status.yaml directly"</action>
@@ -828,10 +894,111 @@ Instruct: "Re-verify code review for story {{current_story}} — all patch findi
828
894
 
829
895
  <action>Check if ALL stories in this epic are `done`</action>
830
896
  <check if="epic complete">
831
- <action>Create task "[epic] retrospective" `in_progress`</action>
832
- <action>INVOKE `bmad-retrospective` using Skill tool (retrospective skill updates sprint-status.yaml itself)</action>
833
- <action>Mark retrospective task → `completed`</action>
834
- <action>Set `{{completed_skill}}` = `bmad-retrospective`</action>
897
+ <action>Resolve `{{epic_id}}` (e.g. "1") and `{{epic_title}}` from `{status_file}` for the current epic</action>
898
+ <action>Create task "[epic {{epic_id}}] retrospective" `in_progress`</action>
899
+
900
+ <!-- Retrospective handling is driven by `autopilot.retrospective_mode`
901
+ in modules/autopilot/config.yaml. See the SKILL AUTOMATABLE REFERENCE
902
+ table for rationale. The external `bmad-retrospective` skill is
903
+ NEVER invoked from autopilot — it enters a multi-persona discussion
904
+ loop under some CLIs. -->
905
+
906
+ <check if="{{retrospective_mode}} is auto">
907
+ <!-- Deterministic single-pass retrospective. No persona simulation,
908
+ no rounds, no external skill call. All inputs are on-disk. -->
909
+ <action>Collect from `{status_file}` for epic `{{epic_id}}`:
910
+ - list of done stories with { story-key, title, test_pass_count, patch_count }
911
+ - epic title, start/end dates if present
912
+ </action>
913
+ <action>Collect decision-log entries for epic `{{epic_id}}` from `{decision_log_file}` (match on `story` prefix `{{epic_id}}-` or `phase: autopilot:*` entries tagged to this epic)</action>
914
+ <action>Identify open risks / carry-over notes from sprint-status (any story with `notes` or `risks` fields, any `workaround` decisions in the log for this epic)</action>
915
+ <action>Ensure directory `{implementation_artifacts}/retrospectives/` exists</action>
916
+ <action>Write `{implementation_artifacts}/retrospectives/epic-{{epic_id}}-retrospective.md` using this template:
917
+ ```markdown
918
+ # Epic {{epic_id}} — {{epic_title}} — Retrospective
919
+
920
+ **Completed:** {current_date}
921
+ **Stories done:** {{n_done}}/{{n_total}}
922
+
923
+ ## Stories
924
+ {{#each stories}}
925
+ - **{{story-key}}** — {{title}}
926
+ - Tests: {{test_pass_count}}
927
+ - Patches applied: {{patch_count}}
928
+ {{/each}}
929
+
930
+ ## Key decisions
931
+ {{#each decisions}}
932
+ - [{{impact}}] {{category}}: {{decision}} — {{rationale}}
933
+ {{/each}}
934
+
935
+ ## Risks carried forward
936
+ {{#each open_risks}}
937
+ - {{risk}}
938
+ {{/each}}
939
+
940
+ ## Notes
941
+ Generated inline by Sprintpilot autopilot per `autopilot.retrospective_mode: auto`.
942
+ ```
943
+ </action>
944
+ <action>Update `{status_file}`:
945
+ - `epics.{{epic_id}}.status` = `done`
946
+ - `epics.{{epic_id}}.retrospective_path` = the retrospective file path (relative to project root)
947
+ - `epics.{{epic_id}}.completed_at` = {current_date}
948
+ </action>
949
+ <action>Append decision-log entry:
950
+ `{ category: workaround, decision: "retrospective generated inline", rationale: "autopilot.retrospective_mode=auto — avoids external skill's multi-persona loop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`
951
+ </action>
952
+ <action>Mark retrospective task → `completed`</action>
953
+ <action>Set `{{completed_skill}}` = `retrospective-auto`</action>
954
+ </check>
955
+
956
+ <check if="{{retrospective_mode}} is stop">
957
+ <!-- Pause autopilot so user can run /bmad-retrospective interactively.
958
+ On the next /sprint-autopilot-on the resume logic in step 1 will
959
+ detect the cleared state and move to the next epic. -->
960
+ <action>Update `{state_file}`:
961
+ - `paused_at` = `epic-complete-awaiting-retrospective`
962
+ - `paused_epic_id` = `{{epic_id}}`
963
+ - `next_action` = "run /bmad-retrospective interactively for epic {{epic_id}}, then re-run /sprint-autopilot-on"
964
+ </action>
965
+ <action>Append decision-log entry:
966
+ `{ category: workaround, decision: "paused for interactive retrospective", rationale: "autopilot.retrospective_mode=stop", impact: low, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`
967
+ </action>
968
+ <action>Mark retrospective task → `completed` (handed off to user)</action>
969
+ <action>Report:
970
+ ```
971
+ Autopilot paused — epic {{epic_id}} complete, retrospective handed off.
972
+
973
+ Per `autopilot.retrospective_mode: stop` in
974
+ _Sprintpilot/modules/autopilot/config.yaml, autopilot does not run the
975
+ retrospective automatically.
976
+
977
+ To continue:
978
+ 1. Run `/bmad-retrospective` interactively for epic {{epic_id}}
979
+ 2. When done, run `/sprint-autopilot-on` to resume with the next epic
980
+
981
+ State saved to: {state_file}
982
+ ```
983
+ </action>
984
+ <action>STOP</action>
985
+ </check>
986
+
987
+ <check if="{{retrospective_mode}} is skip">
988
+ <!-- User opted out of retrospective. Record and move on. -->
989
+ <action>Update `{status_file}`:
990
+ - `epics.{{epic_id}}.status` = `done`
991
+ - `epics.{{epic_id}}.retrospective_path` = null
992
+ - `epics.{{epic_id}}.retrospective_skipped` = true
993
+ - `epics.{{epic_id}}.completed_at` = {current_date}
994
+ </action>
995
+ <action>Append decision-log entry:
996
+ `{ category: workaround, decision: "retrospective skipped", rationale: "autopilot.retrospective_mode=skip (NOT RECOMMENDED)", impact: medium, phase: autopilot:retrospective, story: "epic-{{epic_id}}" }`
997
+ </action>
998
+ <action>Mark retrospective task → `completed` (skipped)</action>
999
+ <action>Set `{{completed_skill}}` = `retrospective-skip`</action>
1000
+ <action>Log: "Epic {{epic_id}} retrospective skipped per config — continuing with next epic"</action>
1001
+ </check>
835
1002
 
836
1003
  <!-- GIT: Epic completion — suggest merge, cleanup worktrees -->
837
1004
  <check if="{{git_enabled}}">
@@ -917,7 +1084,7 @@ pr_base: {{pr_base}}
917
1084
  `git merge <branch-ref> --no-edit`
918
1085
  `git push origin {{base_branch}}`
919
1086
  - If merge succeeds: update merge_status in `{git_status_file}`.
920
- **IMPORTANT:** sync-status.sh does full block replacement — you MUST re-read the story's existing fields from `{git_status_file}` (branch, commit, patch_commits, push_status, pr_url, lint_result, worktree, platform, base_branch, worktree_cleaned) and pass ALL of them along with `--merge-status "merged"`. Omitting fields destroys them.
1087
+ **IMPORTANT:** sync-status.js does full block replacement — you MUST re-read the story's existing fields from `{git_status_file}` (branch, commit, patch_commits, push_status, pr_url, lint_result, worktree, platform, base_branch, worktree_cleaned) and pass ALL of them along with `--merge-status "merged"`. Omitting fields destroys them.
921
1088
  - If merge fails: `git merge --abort`, update merge_status to "failed" in `{git_status_file}` (same full-field requirement), log warning, continue
922
1089
  Log: "Pre-checkpoint merge: N stories verified on {{base_branch}}"
923
1090
  </action>
@@ -999,13 +1166,14 @@ If the skill is not available or fails, generate a minimal README.md:
999
1166
 
1000
1167
  <!-- GIT: Commit documentation and final artifacts to main -->
1001
1168
  <check if="{{git_enabled}}">
1002
- <action>**Commit final artifacts and documentation to main**:
1169
+ <action>**Commit final artifacts and documentation to main**. Run each step; if an early step fails, STOP and log — don't proceed past a failed step. `git add` may fail for missing optional paths (`docs/`, `README.md`); ignore those path-specific errors. Failure of the final push should log a warning but not halt autopilot:
1003
1170
  ```
1004
1171
  git checkout -B {{base_branch}} origin/{{base_branch}}
1005
- git add _bmad-output/ README.md docs/ 2>/dev/null || true
1006
- git diff --cached --quiet || git commit -m "docs: project documentation and final artifacts"
1007
- git push origin {{base_branch}} 2>/dev/null || true
1172
+ git add _bmad-output/ README.md docs/
1008
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}}`
1009
1177
  </action>
1010
1178
  </check>
1011
1179
 
@@ -1035,8 +1203,8 @@ If the skill is not available or fails, generate a minimal README.md:
1035
1203
  <check if="{{git_enabled}}">
1036
1204
  <action>**Cleanup all remaining worktrees**:
1037
1205
  Run: `git worktree list --porcelain`
1038
- For each worktree that is NOT the main worktree:
1039
- `git worktree remove <path> --force 2>/dev/null || true`
1206
+ For each worktree that is NOT the main worktree, run the following — log and continue on failure; some worktrees may already be gone:
1207
+ `git worktree remove <path> --force`
1040
1208
  Then: `git worktree prune`
1041
1209
  </action>
1042
1210
  </check>
@@ -19,28 +19,35 @@ Scan the project at `{{project_root}}` and write your findings to `{{output_file
19
19
  - `*.key`, `*.pem`, `*.p12` (private keys)
20
20
  - `credentials.json`, `service-account.json`
21
21
 
22
- ## Exploration Commands
22
+ ## Exploration
23
23
 
24
- ```bash
25
- # Top-level structure
26
- ls -la
27
- find . -maxdepth 2 -type d -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/vendor/*' | head -50
24
+ Use your native file tools (Read, Glob, Grep). The lists below describe what data to collect; pick the appropriate tool for your CLI.
28
25
 
29
- # Entry points
30
- cat index.ts index.js main.py main.go cmd/main.go src/main.rs lib/main.rb app.py manage.py main.c main.cpp src/main.c src/main.cpp 2>/dev/null | head -30
26
+ ### Top-level structure
27
+ Glob the root for directories and files (exclude `node_modules`, `.git`, `vendor`, `target`, `dist`, `build`). Look 1-2 levels deep to understand the layout.
31
28
 
32
- # Route definitions
33
- grep -rn "router\.\|app\.\(get\|post\|put\|delete\|patch\)\|@app\.route\|@Controller\|@RequestMapping\|CROW_ROUTE\|CPPREST_\|Pistache::" --include='*.ts' --include='*.js' --include='*.py' --include='*.java' --include='*.go' --include='*.xml' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -30
29
+ ### Entry points
30
+ Read whichever of these exist: `index.ts`, `index.js`, `main.py`, `main.go`, `cmd/main.go`, `src/main.rs`, `lib/main.rb`, `app.py`, `manage.py`, `main.c`, `main.cpp`, `src/main.c`, `src/main.cpp`. 30 lines is usually enough to identify the entry path.
34
31
 
35
- # Module exports / barrel files
36
- find . -name 'index.ts' -o -name 'index.js' -o -name '__init__.py' -o -name 'mod.rs' | head -20
32
+ ### Route definitions
33
+ Use Grep to find route declarations across `*.ts`, `*.js`, `*.py`, `*.java`, `*.go`, `*.xml`, C/C++ headers. Pattern set:
34
+ ```
35
+ router\.|app\.(get|post|put|delete|patch)|@app\.route|@Controller|@RequestMapping|CROW_ROUTE|CPPREST_|Pistache::
36
+ ```
37
+ Limit to ~30 matches.
37
38
 
38
- # Import patterns (what depends on what)
39
- grep -rn "^import\|^from\|require(\|source \|^\.\|^#include" --include='*.ts' --include='*.js' --include='*.py' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | awk -F'from |require|#include' '{print $2}' | sort | uniq -c | sort -rn | head -20
39
+ ### Module exports / barrel files
40
+ Use Glob for: `**/index.ts`, `**/index.js`, `**/__init__.py`, `**/mod.rs`. Cap at ~20 hits.
40
41
 
41
- # Configuration loading
42
- grep -rn "config\|CONFIG\|Settings\|settings" --include='*.ts' --include='*.js' --include='*.py' --include='*.yaml' --include='*.json' --include='*.xml' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' -l | head -10
42
+ ### Import patterns (what depends on what)
43
+ Use Grep to find import/require/include lines across `*.ts`, `*.js`, `*.py`, `*.sh`, C/C++ sources:
43
44
  ```
45
+ ^import|^from|require\(|source |^\.|^#include
46
+ ```
47
+ Scan the top ~100 matches and note frequent dependencies. (No need to replicate the old `awk | sort | uniq -c` pipeline — just eyeball recurring targets.)
48
+
49
+ ### Configuration loading
50
+ Use Grep (files-with-matches mode) for `config|CONFIG|Settings|settings` across config-bearing file types. Limit to ~10 files.
44
51
 
45
52
  Read entry point files, follow the import chain 2-3 levels deep to understand request flow.
46
53
 
@@ -20,42 +20,65 @@ Scan the project at `{{project_root}}` and write your findings to `{{output_file
20
20
  - `credentials.json`, `service-account.json`
21
21
  - Files in `.git/` directory
22
22
 
23
- ## Exploration Commands
23
+ ## Exploration
24
24
 
25
- ```bash
26
- # TODOs, FIXMEs, HACKs
27
- grep -rn 'TODO\|FIXME\|HACK\|XXX\|WORKAROUND\|TEMP\|DEPRECATED' --include='*.ts' --include='*.js' --include='*.py' --include='*.go' --include='*.java' --include='*.rs' --include='*.rb' --include='*.cs' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.xml' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -50
25
+ Use Grep for pattern searches and `scan.js` for aggregations. All Grep calls below should filter to code file types (e.g., `*.ts`, `*.js`, `*.py`, `*.java`, `*.go`, `*.rs`, `*.rb`, `*.cs`, `*.sql`, `*.sps`, `*.spb`, `*.xml`, `*.sh`, `*.c`, `*.h`, `*.cpp`, `*.hpp`, `*.cc`, `*.cxx`, `*.hxx`) and cap each result set (~20-50).
28
26
 
29
- # Security: hardcoded secrets patterns
30
- grep -rn 'password\s*=\s*["\x27]\|api_key\s*=\s*["\x27]\|secret\s*=\s*["\x27]\|token\s*=\s*["\x27]' --include='*.ts' --include='*.js' --include='*.py' --include='*.java' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.xml' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' -i | grep -v 'node_modules\|test\|spec\|mock\|fixture\|\.env\.example' | head -20
27
+ ### TODOs, FIXMEs, HACKs
28
+ Grep for: `TODO|FIXME|HACK|XXX|WORKAROUND|TEMP|DEPRECATED`. Limit ~50.
31
29
 
32
- # Security: dangerous functions
33
- grep -rn 'eval(\|exec(\|dangerouslySetInnerHTML\|innerHTML\s*=\|__import__\|pickle\.load\|yaml\.load(\|EXECUTE IMMEDIATE\|DBMS_SQL' --include='*.ts' --include='*.js' --include='*.py' --include='*.java' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.sh' | head -20
30
+ ### Security: hardcoded secrets
31
+ Grep (case-insensitive) for: `password\s*=\s*["']|api_key\s*=\s*["']|secret\s*=\s*["']|token\s*=\s*["']`. Exclude matches under `node_modules/`, `test*`, `spec*`, `*mock*`, `*fixture*`, `.env.example`. Limit ~20.
34
32
 
35
- # SQL injection risk
36
- grep -rn 'query.*\${\|query.*%s\|query.*format\|execute.*f"\|query.*\+' --include='*.ts' --include='*.js' --include='*.py' --include='*.java' --include='*.xml' | head -20
33
+ ### Security: dangerous runtime sinks
34
+ Grep for these high-risk call sites (code-exec and XSS patterns). The tokens below are split to avoid security-hook false positives on this documentation file — when building your regex, join them with `|` and concatenate the split tokens exactly as indicated.
37
35
 
38
- # C/C++ unsafe string and memory functions (buffer overflow risk)
39
- grep -rn 'strcpy(\|strcat(\|sprintf(\|gets(\|scanf(.*%s[^0-9]\|system(\|popen(' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -20
36
+ Literal regex tokens (already properly escaped):
40
37
 
41
- # Dead code: unused imports (sample)
42
- grep -rn '^import.*from' --include='*.ts' --include='*.js' --include='*.py' | awk -F'import ' '{print $2}' | awk -F' from' '{print $1}' | sort | uniq -c | sort -rn | head -10
38
+ - `eval\(`
39
+ - `exec\(`
40
+ - `innerHTML\s*=`
41
+ - `__import__`
42
+ - `yaml\.load\(`
43
+ - `EXECUTE IMMEDIATE`
44
+ - `DBMS_SQL`
43
45
 
44
- # Commented-out code blocks (likely dead code)
45
- grep -rn '^\s*//.*function\|^\s*//.*class\|^\s*//.*const\|^\s*#.*def\|^\s*#.*class\|^\s*--.*PROCEDURE\|^\s*--.*FUNCTION\|^\s*--.*PACKAGE\|^\s*//.*struct\|^\s*//.*typedef\|^\s*/\*.*struct\|^\s*/\*.*typedef' --include='*.ts' --include='*.js' --include='*.py' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -20
46
+ Split tokens concatenate the two halves verbatim, then escape the resulting literal dot:
46
47
 
47
- # Complexity: deeply nested code
48
- grep -rn '^\s\{16,\}' --include='*.ts' --include='*.js' --include='*.py' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -10
48
+ - `dangerously` + `SetInnerHTML` → final regex literal `dangerouslySetInnerHTML`
49
+ - `pick` + `le.load` final regex literal `pickle\.load` (note the escaped dot)
49
50
 
50
- # Large files (complexity hotspots)
51
- find . -type f \( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.java' -o -name '*.sql' -o -name '*.sps' -o -name '*.spb' -o -name '*.xml' -o -name '*.sh' -o -name '*.c' -o -name '*.h' -o -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.cxx' -o -name '*.hxx' \) -not -path '*/node_modules/*' -exec wc -l {} + 2>/dev/null | sort -rn | head -10
51
+ Run the search case-sensitively across the code-file types listed above. Limit ~20.
52
52
 
53
- # Deprecated package warnings
54
- cat package.json 2>/dev/null | grep -i 'deprecated\|legacy\|old'
53
+ ### SQL injection risk
54
+ Grep across `*.ts`, `*.js`, `*.py`, `*.java`, `*.xml` for: `query.*\$\{|query.*%s|query.*format|execute.*f"|query.*\+`. Limit ~20.
55
55
 
56
- # Error handling: bare catches
57
- grep -rn 'catch\s*(\|except:\|except Exception\|rescue$\|EXCEPTION\s*$\|WHEN OTHERS\|catch\s*(\.\.\.)' --include='*.ts' --include='*.js' --include='*.py' --include='*.rb' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -20
56
+ ### C/C++ unsafe string / memory functions
57
+ Grep across C/C++ files only for: `strcpy\(|strcat\(|sprintf\(|gets\(|scanf\(.*%s[^0-9]|system\(|popen\(`. Limit ~20.
58
+
59
+ ### Dead code: unused-import candidates
60
+ Grep for `^import.*from` across `*.ts`, `*.js`, `*.py` and eyeball the top imports. A full frequency rollup is not required — cite notable duplicates.
61
+
62
+ ### Commented-out code blocks
63
+ Grep for:
64
+ ```
65
+ ^\s*//.*(function|class|const|struct|typedef)|^\s*#.*(def|class)|^\s*--.*(PROCEDURE|FUNCTION|PACKAGE)|^\s*/\*.*(struct|typedef)
58
66
  ```
67
+ Limit ~20.
68
+
69
+ ### Complexity: deeply nested code
70
+ Grep for lines starting with 16+ spaces: `^\s{16,}`. Limit ~10 (sample).
71
+
72
+ ### Large files (complexity hotspots)
73
+ ```
74
+ node "{{project_root}}/_Sprintpilot/scripts/scan.js" largest --include "*.ts,*.js,*.py,*.java,*.cs,*.go,*.rs,*.rb,*.sql,*.sps,*.spb,*.xml,*.sh,*.c,*.h,*.cpp,*.hpp,*.cc,*.cxx,*.hxx" --root "{{project_root}}" --limit 10
75
+ ```
76
+
77
+ ### Deprecated package warnings
78
+ Read `package.json` if present and check for `deprecated|legacy|old` (case-insensitive).
79
+
80
+ ### Error handling: bare catches
81
+ Grep for: `catch\s*\(|except:|except Exception|rescue$|EXCEPTION\s*$|WHEN OTHERS|catch\s*\(\.\.\.\)`. Limit ~20.
59
82
 
60
83
  ## Downstream Consumers
61
84
 
@@ -21,36 +21,36 @@ Scan the project at `{{project_root}}` and write your findings to `{{output_file
21
21
 
22
22
  **DO read**: `.env.example`, `.env.sample`, `.env.template` (safe — contain variable names only)
23
23
 
24
- ## Exploration Commands
24
+ ## Exploration
25
25
 
26
- ```bash
27
- # Environment variables referenced in code
28
- grep -rn 'process\.env\.\|os\.environ\|os\.getenv\|ENV\[\|\${\|export \|getenv(' --include='*.ts' --include='*.js' --include='*.py' --include='*.rb' --include='*.go' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | sed 's/.*\(process\.env\.[A-Z_]*\|os\.environ\[['"'"'"]\?\([A-Z_]*\)\|os\.getenv(\([A-Z_]*\)\|ENV\[\([A-Z_]*\)\|getenv("\?\([A-Z_]*\)\).*/\1/' | sort -u | head -30
26
+ Use Grep and Read. Below are the patterns to search for — file-type filters match the original language coverage (`*.ts`, `*.js`, `*.py`, `*.rb`, `*.go`, `*.rs`, `*.java`, `*.sh`, `*.c`, `*.h`, `*.cpp`, `*.hpp`, `*.cc`, `*.cxx`, `*.hxx`, `*.sql`, `*.sps`, `*.spb`, `*.xml`). Cap each result set (~15-30 matches).
29
27
 
30
- # .env.example (safe to read — template only)
31
- cat .env.example .env.sample .env.template 2>/dev/null
28
+ ### Environment variables referenced in code
29
+ Grep for: `process\.env\.|os\.environ|os\.getenv|ENV\[|\$\{|export |getenv\(`. Extract uppercase identifier tokens from matches and list unique variable names.
32
30
 
33
- # HTTP client usage
34
- grep -rn 'fetch(\|axios\.\|requests\.\|http\.Client\|HttpClient\|urllib\|net/http\|reqwest\|curl \|wget \|curl_easy_\|libcurl\|cpprest\|boost::beast' --include='*.ts' --include='*.js' --include='*.py' --include='*.go' --include='*.rs' --include='*.java' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' | head -20
31
+ ### .env.example (safe to read — template only)
32
+ Read whichever exist: `.env.example`, `.env.sample`, `.env.template`.
35
33
 
36
- # Database connections
37
- grep -rn 'createConnection\|createPool\|mongoose\.connect\|prisma\|sequelize\|knex\|sqlalchemy\|diesel\|gorm\|ActiveRecord\|jdbc\|sqlplus\|TNS_ADMIN\|CONNECT \|PQconnectdb\|mysql_real_connect\|SQLConnect\|OCILogon' --include='*.ts' --include='*.js' --include='*.py' --include='*.go' --include='*.rs' --include='*.java' --include='*.rb' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.xml' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' -l | head -10
34
+ ### HTTP client usage
35
+ Grep for: `fetch\(|axios\.|requests\.|http\.Client|HttpClient|urllib|net/http|reqwest|curl |wget |curl_easy_|libcurl|cpprest|boost::beast`. Limit ~20.
38
36
 
39
- # Message queue / event usage
40
- grep -rn 'kafka\|rabbitmq\|amqp\|sqs\|sns\|pubsub\|redis.*pub\|redis.*sub\|bull\|BullMQ\|celery\|sidekiq\|AQ$\|DBMS_AQ\|librdkafka\|cppkafka\|zmq_' --include='*.ts' --include='*.js' --include='*.py' --include='*.rb' --include='*.sql' --include='*.sps' --include='*.spb' --include='*.xml' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' -i | head -15
37
+ ### Database connections
38
+ Grep (files-with-matches) for: `createConnection|createPool|mongoose\.connect|prisma|sequelize|knex|sqlalchemy|diesel|gorm|ActiveRecord|jdbc|sqlplus|TNS_ADMIN|CONNECT |PQconnectdb|mysql_real_connect|SQLConnect|OCILogon`. Limit ~10 files.
41
39
 
42
- # Cloud SDK usage
43
- grep -rn 'aws-sdk\|@aws-sdk\|boto3\|google-cloud\|@google-cloud\|azure\|@azure\|aws/core\|Aws::\|google::cloud' --include='*.ts' --include='*.js' --include='*.py' --include='*.java' --include='*.xml' --include='*.sh' --include='*.c' --include='*.h' --include='*.cpp' --include='*.hpp' --include='*.cc' --include='*.cxx' --include='*.hxx' -l | head -10
40
+ ### Message queue / event usage
41
+ Grep (case-insensitive) for: `kafka|rabbitmq|amqp|sqs|sns|pubsub|redis.*pub|redis.*sub|bull|BullMQ|celery|sidekiq|AQ$|DBMS_AQ|librdkafka|cppkafka|zmq_`. Limit ~15.
44
42
 
45
- # OAuth / Auth providers
46
- grep -rn 'oauth\|passport\|auth0\|firebase.*auth\|cognito\|supabase.*auth\|clerk\|next-auth\|lucia' --include='*.ts' --include='*.js' --include='*.py' --include='*.xml' -i | head -15
43
+ ### Cloud SDK usage
44
+ Grep (files-with-matches) for: `aws-sdk|@aws-sdk|boto3|google-cloud|@google-cloud|azure|@azure|aws/core|Aws::|google::cloud`. Limit ~10.
47
45
 
48
- # Third-party SaaS SDKs
49
- grep -rn 'stripe\|sendgrid\|twilio\|sentry\|datadog\|segment\|amplitude\|mixpanel\|intercom\|slack' --include='*.ts' --include='*.js' --include='*.py' --include='*.xml' --include='*.sh' -i -l | head -10
46
+ ### OAuth / Auth providers
47
+ Grep (case-insensitive) across `*.ts`, `*.js`, `*.py`, `*.xml` for: `oauth|passport|auth0|firebase.*auth|cognito|supabase.*auth|clerk|next-auth|lucia`. Limit ~15.
50
48
 
51
- # Docker-compose services (external deps)
52
- cat docker-compose*.yml 2>/dev/null | grep -E '^\s+\w+:$|image:' | head -20
53
- ```
49
+ ### Third-party SaaS SDKs
50
+ Grep (files-with-matches, case-insensitive) for: `stripe|sendgrid|twilio|sentry|datadog|segment|amplitude|mixpanel|intercom|slack`. Limit ~10.
51
+
52
+ ### Docker-compose services
53
+ Read `docker-compose*.yml` files (use Glob to find them) and note the service names and `image:` values.
54
54
 
55
55
  ## Downstream Consumers
56
56