@ikunin/sprintpilot 2.2.11 → 2.2.13
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,
|
|
@@ -694,6 +695,13 @@ function composeRuntimeState(persisted, profile, projectRoot) {
|
|
|
694
695
|
// across halts so the next session re-emits the prompt rather than
|
|
695
696
|
// silently dropping the LLM's proposal.
|
|
696
697
|
pending_alternative: persisted.pending_alternative || null,
|
|
698
|
+
// session_story_limit counter: per-session count of stories completed.
|
|
699
|
+
// adapt.advanceState increments on STORY_DONE → EPIC_BOUNDARY_CHECK;
|
|
700
|
+
// state-machine.nextAction emits a halt when this hits profile.session_story_limit.
|
|
701
|
+
// cmdStart resets to 0 on each new session boot (the limit is per-session,
|
|
702
|
+
// not lifetime). Persisted across in-session resumes so a `pause` mid-flow
|
|
703
|
+
// doesn't reset progress against the limit.
|
|
704
|
+
session_stories_completed: persisted.session_stories_completed || 0,
|
|
697
705
|
// halt_requested is intentionally NOT carried forward here: cmdStart
|
|
698
706
|
// clears it on each new session (a `pause` cleanly halts THIS session
|
|
699
707
|
// and the next /sprint-autopilot-on resumes normally).
|
|
@@ -722,6 +730,7 @@ function persistRuntimeState(runtime, profile, projectRoot) {
|
|
|
722
730
|
story_queue: Array.isArray(runtime.story_queue) ? runtime.story_queue : [],
|
|
723
731
|
land_pending: runtime.land_pending,
|
|
724
732
|
pending_alternative: runtime.pending_alternative || null,
|
|
733
|
+
session_stories_completed: runtime.session_stories_completed || 0,
|
|
725
734
|
};
|
|
726
735
|
return persistState(updates, profile, projectRoot, runtime.story_key || 'sprint');
|
|
727
736
|
}
|
|
@@ -844,6 +853,42 @@ function decorateGitOp(action, state, profile, projectRoot) {
|
|
|
844
853
|
}
|
|
845
854
|
}
|
|
846
855
|
|
|
856
|
+
// run_script actions for op=land_story carry only metadata (helper,
|
|
857
|
+
// land_when, squash_on_merge, ...) — the state machine's comment
|
|
858
|
+
// promises "The CLI edge composes the actual argv via land.js#planLand"
|
|
859
|
+
// but that wiring was missing pre-v2.2.12. Without it, LLMs driving
|
|
860
|
+
// land_as_you_go got a metadata-only action and had to improvise.
|
|
861
|
+
// Symmetric to decorateGitOp: call land.planLand(state, profile,
|
|
862
|
+
// options), inline the resulting `steps[]` onto the action.
|
|
863
|
+
function decorateRunScript(action, state, profile, projectRoot) {
|
|
864
|
+
if (!action || action.type !== 'run_script') return action;
|
|
865
|
+
if (action.op !== 'land_story') return action;
|
|
866
|
+
try {
|
|
867
|
+
const root = projectRoot || process.cwd();
|
|
868
|
+
const scriptsDir = path.join(root, '_Sprintpilot', 'scripts');
|
|
869
|
+
const snapshotPath = path.join(
|
|
870
|
+
root,
|
|
871
|
+
'_bmad-output',
|
|
872
|
+
'implementation-artifacts',
|
|
873
|
+
'.land-snapshots',
|
|
874
|
+
`${state.story_key || 'sprint'}.json`,
|
|
875
|
+
);
|
|
876
|
+
const branch = gitPlan.branchName(profile, state.story_key, state.current_epic, state);
|
|
877
|
+
const platform = profile.platform_provider || 'auto';
|
|
878
|
+
const planned = land.planLand(state, profile, {
|
|
879
|
+
scriptsDir,
|
|
880
|
+
snapshotPath,
|
|
881
|
+
branch,
|
|
882
|
+
platform,
|
|
883
|
+
projectRoot: root,
|
|
884
|
+
});
|
|
885
|
+
return { ...action, branch: planned.branch, steps: planned.steps };
|
|
886
|
+
} catch (e) {
|
|
887
|
+
log.warn(`land-plan failed for op=${action.op}: ${e.message}`);
|
|
888
|
+
return action;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
847
892
|
// Detect whether a branch exists, locally OR on origin. Used by
|
|
848
893
|
// decorateGitOp so the create_branch plan can degrade to a plain switch
|
|
849
894
|
// when the branch is already known. Checking remote refs avoids the
|
|
@@ -1096,6 +1141,12 @@ function cmdStart(opts) {
|
|
|
1096
1141
|
// profile-aware initial phase when persisted state is empty.
|
|
1097
1142
|
const runtime = composeRuntimeState(persisted, profile, projectRoot);
|
|
1098
1143
|
|
|
1144
|
+
// session_story_limit is per-session — a fresh `autopilot start`
|
|
1145
|
+
// resets the counter so the next batch of N stories can run before
|
|
1146
|
+
// the next halt. (state-machine.nextAction enforces the cap; adapt.js
|
|
1147
|
+
// increments on STORY_DONE → EPIC_BOUNDARY_CHECK.)
|
|
1148
|
+
runtime.session_stories_completed = 0;
|
|
1149
|
+
|
|
1099
1150
|
const lockResult = lockUserBranchIfNeeded(runtime, profile, projectRoot);
|
|
1100
1151
|
if (lockResult && lockResult.halt) {
|
|
1101
1152
|
ledger.append(
|
|
@@ -1108,7 +1159,12 @@ function cmdStart(opts) {
|
|
|
1108
1159
|
return 0;
|
|
1109
1160
|
}
|
|
1110
1161
|
|
|
1111
|
-
const action =
|
|
1162
|
+
const action = decorateRunScript(
|
|
1163
|
+
decorateGitOp(stateMachine.nextAction(runtime, profile), runtime, profile, projectRoot),
|
|
1164
|
+
runtime,
|
|
1165
|
+
profile,
|
|
1166
|
+
projectRoot,
|
|
1167
|
+
);
|
|
1112
1168
|
ledger.append({ kind: 'action_emitted', phase: runtime.phase, action }, { projectRoot });
|
|
1113
1169
|
persistRuntimeState(runtime, profile, projectRoot);
|
|
1114
1170
|
if (profile.coalesce_state_writes) stateStore.flush(profile, { projectRoot, story: runtime.story_key });
|
|
@@ -1137,7 +1193,12 @@ function cmdNext(opts) {
|
|
|
1137
1193
|
return 0;
|
|
1138
1194
|
}
|
|
1139
1195
|
|
|
1140
|
-
const action =
|
|
1196
|
+
const action = decorateRunScript(
|
|
1197
|
+
decorateGitOp(stateMachine.nextAction(runtime, profile), runtime, profile, projectRoot),
|
|
1198
|
+
runtime,
|
|
1199
|
+
profile,
|
|
1200
|
+
projectRoot,
|
|
1201
|
+
);
|
|
1141
1202
|
ledger.append({ kind: 'action_emitted', phase: runtime.phase, action }, { projectRoot });
|
|
1142
1203
|
// Persist any mutations done by lockUserBranchIfNeeded — without this
|
|
1143
1204
|
// every cmdNext under reuse_user_branch=true re-detects the branch and
|
|
@@ -1264,7 +1325,12 @@ function cmdRecord(opts) {
|
|
|
1264
1325
|
}
|
|
1265
1326
|
|
|
1266
1327
|
const payload = {
|
|
1267
|
-
action:
|
|
1328
|
+
action: decorateRunScript(
|
|
1329
|
+
decorateGitOp(result.nextAction, result.newState, result.newProfile, projectRoot),
|
|
1330
|
+
result.newState,
|
|
1331
|
+
result.newProfile,
|
|
1332
|
+
projectRoot,
|
|
1333
|
+
),
|
|
1268
1334
|
verdict: result.verdict,
|
|
1269
1335
|
phase: result.newState.phase,
|
|
1270
1336
|
profile: result.newProfile.name,
|
|
@@ -1353,4 +1419,4 @@ if (require.main === module) {
|
|
|
1353
1419
|
process.exit(main(process.argv.slice(2)));
|
|
1354
1420
|
}
|
|
1355
1421
|
|
|
1356
|
-
module.exports = { main, SUBCOMMANDS, decorateGitOp, composeRuntimeState };
|
|
1422
|
+
module.exports = { main, SUBCOMMANDS, decorateGitOp, decorateRunScript, composeRuntimeState };
|
|
@@ -592,6 +592,10 @@ function advanceState(state, profile, newPhase, signal) {
|
|
|
592
592
|
next.story_key = null;
|
|
593
593
|
next.story_file_path = null;
|
|
594
594
|
next.ac_summary = null;
|
|
595
|
+
// session_story_limit: increment per-session completion counter so
|
|
596
|
+
// state-machine.js#nextAction can emit the halt at the next
|
|
597
|
+
// emission. The counter resets on cmdStart (new session boundary).
|
|
598
|
+
next.session_stories_completed = (state.session_stories_completed || 0) + 1;
|
|
595
599
|
}
|
|
596
600
|
|
|
597
601
|
return next;
|
|
@@ -170,6 +170,37 @@ function nextAction(state, profile) {
|
|
|
170
170
|
handoff: 'sprint_finalize_pending',
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
+
// session_story_limit: when this session has completed >= limit
|
|
174
|
+
// stories, halt cleanly. The next /sprint-autopilot-on resets the
|
|
175
|
+
// counter and continues. Skipped when limit === 0 (unlimited per
|
|
176
|
+
// Sprintpilot.md) or limit is unset.
|
|
177
|
+
const sessionLimit = profile && profile.session_story_limit;
|
|
178
|
+
const sessionDone = state.session_stories_completed || 0;
|
|
179
|
+
if (
|
|
180
|
+
typeof sessionLimit === 'number' &&
|
|
181
|
+
sessionLimit > 0 &&
|
|
182
|
+
sessionDone >= sessionLimit &&
|
|
183
|
+
// Don't halt when we're at a story-start phase — that would
|
|
184
|
+
// create an infinite halt loop on resume. The limit check should
|
|
185
|
+
// fire at the boundary between stories (epic_boundary_check or
|
|
186
|
+
// before the next story is picked). Most natural is to halt
|
|
187
|
+
// before emitting the next story-start action.
|
|
188
|
+
(state.phase === STATES.EPIC_BOUNDARY_CHECK ||
|
|
189
|
+
state.phase === STATES.RETROSPECTIVE ||
|
|
190
|
+
state.phase === STATES.PREPARE_STORY_BRANCH ||
|
|
191
|
+
state.phase === STATES.CREATE_STORY ||
|
|
192
|
+
state.phase === STATES.NANO_QUICK_DEV)
|
|
193
|
+
) {
|
|
194
|
+
return {
|
|
195
|
+
type: 'halt',
|
|
196
|
+
reason: 'session_story_limit_reached',
|
|
197
|
+
prompt:
|
|
198
|
+
`Session story limit reached (${sessionDone}/${sessionLimit}). ` +
|
|
199
|
+
`Run /sprint-autopilot-on to start a new session and continue with the next pending story.`,
|
|
200
|
+
session_stories_completed: sessionDone,
|
|
201
|
+
session_story_limit: sessionLimit,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
173
204
|
|
|
174
205
|
switch (state.phase) {
|
|
175
206
|
case STATES.PREPARE_STORY_BRANCH: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikunin/sprintpilot",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.13",
|
|
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": {
|