@ikunin/sprintpilot 2.2.9 → 2.2.11
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.
|
@@ -632,6 +632,39 @@ function composeRuntimeState(persisted, profile, projectRoot) {
|
|
|
632
632
|
remainingStoriesInEpic = epicStories.length;
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
+
// Catch-all guard: if state.phase REQUIRES a story_key to emit a
|
|
636
|
+
// coherent action AND we still don't have one after every resolution
|
|
637
|
+
// path (queue / validator / sprint-status), reset phase to flowStart.
|
|
638
|
+
//
|
|
639
|
+
// Real-world scenario: a previous orchestrator version nulled
|
|
640
|
+
// current_story (e.g., v2.2.4's overzealous rejection) but didn't
|
|
641
|
+
// reset state.phase. Persisted state ends up with current_story: null
|
|
642
|
+
// at story_done. v2.2.9's reset only fires inside the rejection branch,
|
|
643
|
+
// so a NULL story_key doesn't trigger it (no rejection to fire). This
|
|
644
|
+
// guard catches that case + any future bug class where story_key
|
|
645
|
+
// ends up null at a story-bound phase.
|
|
646
|
+
//
|
|
647
|
+
// The reset is safe: the next emission re-enters story-start (or
|
|
648
|
+
// PREPARE_STORY_BRANCH per the migration rule) and picks the next
|
|
649
|
+
// pending story from queue / sprint-status.
|
|
650
|
+
const STORY_BOUND_PHASES_CATCH_ALL = new Set([
|
|
651
|
+
STATES.CHECK_READINESS,
|
|
652
|
+
STATES.DEV_RED,
|
|
653
|
+
STATES.DEV_GREEN,
|
|
654
|
+
STATES.CODE_REVIEW,
|
|
655
|
+
STATES.PATCH_APPLY,
|
|
656
|
+
STATES.PATCH_RETEST,
|
|
657
|
+
STATES.STORY_DONE,
|
|
658
|
+
STATES.STORY_LAND,
|
|
659
|
+
]);
|
|
660
|
+
if (!resolvedStoryKey && STORY_BOUND_PHASES_CATCH_ALL.has(phase)) {
|
|
661
|
+
process.stderr.write(
|
|
662
|
+
`[autopilot] WARN phase "${phase}" requires a story_key but none resolved (queue empty, sprint-status lookup didn't fire for this phase). ` +
|
|
663
|
+
`Resetting to ${flowStart} so next emission re-enters story-start.\n`,
|
|
664
|
+
);
|
|
665
|
+
phase = flowStart;
|
|
666
|
+
}
|
|
667
|
+
|
|
635
668
|
return {
|
|
636
669
|
phase,
|
|
637
670
|
story_key: resolvedStoryKey,
|
|
@@ -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.
|
|
3
|
+
"version": "2.2.11",
|
|
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": {
|