@ikunin/sprintpilot 2.2.10 → 2.2.12

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.
@@ -39,6 +39,7 @@ const userCommands = require('../lib/orchestrator/user-commands');
39
39
  const divergence = require('../lib/orchestrator/divergence');
40
40
  const reportRenderer = require('../lib/orchestrator/report');
41
41
  const gitPlan = require('../lib/orchestrator/git-plan');
42
+ const land = require('../lib/orchestrator/land');
42
43
  const {
43
44
  parseStatuses: parseSprintStatuses,
44
45
  remainingFrom: remainingStoriesFrom,
@@ -844,6 +845,42 @@ function decorateGitOp(action, state, profile, projectRoot) {
844
845
  }
845
846
  }
846
847
 
848
+ // run_script actions for op=land_story carry only metadata (helper,
849
+ // land_when, squash_on_merge, ...) — the state machine's comment
850
+ // promises "The CLI edge composes the actual argv via land.js#planLand"
851
+ // but that wiring was missing pre-v2.2.12. Without it, LLMs driving
852
+ // land_as_you_go got a metadata-only action and had to improvise.
853
+ // Symmetric to decorateGitOp: call land.planLand(state, profile,
854
+ // options), inline the resulting `steps[]` onto the action.
855
+ function decorateRunScript(action, state, profile, projectRoot) {
856
+ if (!action || action.type !== 'run_script') return action;
857
+ if (action.op !== 'land_story') return action;
858
+ try {
859
+ const root = projectRoot || process.cwd();
860
+ const scriptsDir = path.join(root, '_Sprintpilot', 'scripts');
861
+ const snapshotPath = path.join(
862
+ root,
863
+ '_bmad-output',
864
+ 'implementation-artifacts',
865
+ '.land-snapshots',
866
+ `${state.story_key || 'sprint'}.json`,
867
+ );
868
+ const branch = gitPlan.branchName(profile, state.story_key, state.current_epic, state);
869
+ const platform = profile.platform_provider || 'auto';
870
+ const planned = land.planLand(state, profile, {
871
+ scriptsDir,
872
+ snapshotPath,
873
+ branch,
874
+ platform,
875
+ projectRoot: root,
876
+ });
877
+ return { ...action, branch: planned.branch, steps: planned.steps };
878
+ } catch (e) {
879
+ log.warn(`land-plan failed for op=${action.op}: ${e.message}`);
880
+ return action;
881
+ }
882
+ }
883
+
847
884
  // Detect whether a branch exists, locally OR on origin. Used by
848
885
  // decorateGitOp so the create_branch plan can degrade to a plain switch
849
886
  // when the branch is already known. Checking remote refs avoids the
@@ -1108,7 +1145,12 @@ function cmdStart(opts) {
1108
1145
  return 0;
1109
1146
  }
1110
1147
 
1111
- const action = decorateGitOp(stateMachine.nextAction(runtime, profile), runtime, profile, projectRoot);
1148
+ const action = decorateRunScript(
1149
+ decorateGitOp(stateMachine.nextAction(runtime, profile), runtime, profile, projectRoot),
1150
+ runtime,
1151
+ profile,
1152
+ projectRoot,
1153
+ );
1112
1154
  ledger.append({ kind: 'action_emitted', phase: runtime.phase, action }, { projectRoot });
1113
1155
  persistRuntimeState(runtime, profile, projectRoot);
1114
1156
  if (profile.coalesce_state_writes) stateStore.flush(profile, { projectRoot, story: runtime.story_key });
@@ -1137,7 +1179,12 @@ function cmdNext(opts) {
1137
1179
  return 0;
1138
1180
  }
1139
1181
 
1140
- const action = decorateGitOp(stateMachine.nextAction(runtime, profile), runtime, profile, projectRoot);
1182
+ const action = decorateRunScript(
1183
+ decorateGitOp(stateMachine.nextAction(runtime, profile), runtime, profile, projectRoot),
1184
+ runtime,
1185
+ profile,
1186
+ projectRoot,
1187
+ );
1141
1188
  ledger.append({ kind: 'action_emitted', phase: runtime.phase, action }, { projectRoot });
1142
1189
  // Persist any mutations done by lockUserBranchIfNeeded — without this
1143
1190
  // every cmdNext under reuse_user_branch=true re-detects the branch and
@@ -1264,7 +1311,12 @@ function cmdRecord(opts) {
1264
1311
  }
1265
1312
 
1266
1313
  const payload = {
1267
- action: decorateGitOp(result.nextAction, result.newState, result.newProfile, projectRoot),
1314
+ action: decorateRunScript(
1315
+ decorateGitOp(result.nextAction, result.newState, result.newProfile, projectRoot),
1316
+ result.newState,
1317
+ result.newProfile,
1318
+ projectRoot,
1319
+ ),
1268
1320
  verdict: result.verdict,
1269
1321
  phase: result.newState.phase,
1270
1322
  profile: result.newProfile.name,
@@ -1353,4 +1405,4 @@ if (require.main === module) {
1353
1405
  process.exit(main(process.argv.slice(2)));
1354
1406
  }
1355
1407
 
1356
- module.exports = { main, SUBCOMMANDS, decorateGitOp, composeRuntimeState };
1408
+ module.exports = { main, SUBCOMMANDS, decorateGitOp, decorateRunScript, composeRuntimeState };
@@ -1,6 +1,6 @@
1
1
  addon:
2
2
  name: sprintpilot
3
- version: 2.2.10
3
+ version: 2.2.12
4
4
  description: Sprintpilot — autopilot and multi-agent addon for BMad Method (git workflow, parallel agents, autonomous story execution)
5
5
  bmad_compatibility: ">=6.2.0"
6
6
  modules:
@@ -23,6 +23,13 @@ Follow **`./workflow.orchestrator.md`** verbatim. Flow control lives in
23
23
  - Do not search for `workflow.md` or reconstruct it from memory; do not
24
24
  read cached BMad legacy patterns and apply them ahead of the
25
25
  orchestrator's state machine.
26
+ - **Never pause on your own initiative.** `user_input { kind: 'pause' }`
27
+ is a HUMAN command. The autopilot's purpose is to drive without
28
+ stopping until `session_story_limit`, a TRUE BLOCKER, retry-budget
29
+ exhaustion, or `sprint_is_complete`. Heuristics like "PR opened,
30
+ time for review" / "natural breakpoint" / "let CI catch up" are NOT
31
+ valid reasons to pause — see `workflow.orchestrator.md` § "Pause is
32
+ human-only."
26
33
 
27
34
  `workflow.orchestrator.md` is the **sole authority** for the rest of the
28
35
  session.
@@ -83,9 +83,33 @@ Wrap everything in `{ "status": "...", ... }` and pass to
83
83
  | `failure` | `reason`, `diagnosis` (first-class — fed back into next retry), `recoverable: boolean` |
84
84
  | `blocked` | `blocker_kind` (one of the 5 TRUE BLOCKERS or recoverable kinds), `details`, `user_input_needed`, `consecutive_count?` |
85
85
  | `propose_alternative` | `reason`, `alternative` (full Action object), `urgency_hint?` (raises impact only). Low impact → auto-accepted; medium / high → orchestrator stores the alternative in `state.pending_alternative` and emits `user_prompt`. The user accepts via `user_input` `{ kind: 'accept_alternative' }` or rejects via `force_continue` (both clear `pending_alternative`). |
86
- | `user_input` | `commands: UserCommand[]` (validated server-side; see user-commands.js). Kinds: `skip_story`, `abort_sprint`, `force_continue`, `override_decision`, `change_profile`, `pause` (cleanly halts THIS session; next `/sprint-autopilot-on` resumes), `accept_alternative` (dispatches the stored `pending_alternative`). |
86
+ | `user_input` | `commands: UserCommand[]` (validated server-side; see user-commands.js). Kinds: `skip_story`, `abort_sprint`, `force_continue`, `override_decision`, `change_profile`, `pause` (cleanly halts THIS session; next `/sprint-autopilot-on` resumes), `accept_alternative` (dispatches the stored `pending_alternative`). **NEVER send `pause` on your own initiative** — see "Pause is human-only" below. |
87
87
  | `verify_override` | `evidence: { decision_log_ref?, explanation, expected_paths? }` — used when verify.js is wrong |
88
88
 
89
+ ## Pause is human-only
90
+
91
+ The autopilot's purpose is to **drive without stopping** until one of:
92
+ - `session_story_limit` is reached (profile-defined; default 3, nano 5)
93
+ - A TRUE BLOCKER (5 kinds, listed below) fires
94
+ - The retry budget exhausts on a single phase
95
+ - `sprint_is_complete` (last story done → halt with `handoff: sprint_finalize_pending`)
96
+ - The **human user** explicitly asks to pause (e.g. types `/pause`, `pause autopilot`, or a similar direct instruction in chat)
97
+
98
+ **DO NOT issue `user_input` `{ kind: 'pause' }` on your own.** Specifically, the following are NOT valid reasons to pause:
99
+
100
+ - "Natural pause point" / "PR opened, time for human review"
101
+ - "CI is still running for the previous story"
102
+ - "Diff is large, let's get a checkpoint"
103
+ - "Merge cadence" / "want to wait for review before next story"
104
+ - Any heuristic about session length, story count, or work-in-progress
105
+
106
+ Pause is a HUMAN command. If you think the user might want to pause, **don't pause — finish the story and proceed to the next per the queue / `resolveNextStoryKey`**. The user can pause at any time by typing the command themselves; the next `/sprint-autopilot-on` cleanly resumes. Your judgment about "natural breakpoints" defeats the autopilot's purpose.
107
+
108
+ The only cases where you signal a halt yourself are:
109
+ - `blocked` with one of the 5 TRUE BLOCKERS (creative-input-required, new external dep, etc.)
110
+ - `failure` with `recoverable: false` (catastrophic, can't proceed even with retries)
111
+ - `propose_alternative` at medium/high impact (the orchestrator escalates to a `user_prompt` itself)
112
+
89
113
  ## TRUE BLOCKER kinds (per AGENTS.md)
90
114
 
91
115
  `creative_user_input_required`, `new_external_dependency`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.2.10",
3
+ "version": "2.2.12",
4
4
  "description": "Sprintpilot — autopilot and multi-agent addon for BMad Method v6: git workflow, parallel agents, autonomous story execution",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {