@ikunin/sprintpilot 2.2.8 → 2.2.10
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.
|
@@ -499,15 +499,50 @@ function composeRuntimeState(persisted, profile, projectRoot) {
|
|
|
499
499
|
projectRoot,
|
|
500
500
|
);
|
|
501
501
|
if (rejection) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
502
|
+
// Phase-aware rejection gate. The "marked done" rejection is NOT
|
|
503
|
+
// a poisoned-state signal when state.phase is a story-bound phase
|
|
504
|
+
// (CHECK_READINESS through STORY_LAND) — at STORY_DONE the story
|
|
505
|
+
// IS expected to be marked done in sprint-status (verifyStoryDone
|
|
506
|
+
// enforces it). Pre-2.2.9 fix: any "done" rejection nulled
|
|
507
|
+
// story_key mid-record, producing branch "story/unknown" on
|
|
508
|
+
// commit_and_push_story.
|
|
509
|
+
//
|
|
510
|
+
// Epic-rollup-header / retrospective / not-in-sprint-status
|
|
511
|
+
// rejections are ALWAYS poison and fire regardless of phase.
|
|
512
|
+
const STORY_BOUND_PHASES = new Set([
|
|
513
|
+
STATES.CHECK_READINESS,
|
|
514
|
+
STATES.DEV_RED,
|
|
515
|
+
STATES.DEV_GREEN,
|
|
516
|
+
STATES.CODE_REVIEW,
|
|
517
|
+
STATES.PATCH_APPLY,
|
|
518
|
+
STATES.PATCH_RETEST,
|
|
519
|
+
STATES.STORY_DONE,
|
|
520
|
+
STATES.STORY_LAND,
|
|
521
|
+
]);
|
|
522
|
+
const isDoneRejection = /already complete/.test(rejection);
|
|
523
|
+
const skipDoneRejection = isDoneRejection && STORY_BOUND_PHASES.has(phase);
|
|
524
|
+
if (!skipDoneRejection) {
|
|
525
|
+
process.stderr.write(
|
|
526
|
+
`[autopilot] WARN persisted current_story "${persistedCurrentStory}" rejected: ${rejection}. ` +
|
|
527
|
+
'Treating as null and falling through to queue / sprint-status resolution. ' +
|
|
528
|
+
'This typically means state was poisoned by an older orchestrator version (v2.1.3 / v2.1.4 pre-filter); ' +
|
|
529
|
+
'next emission will clean it up.\n',
|
|
530
|
+
);
|
|
531
|
+
resolvedStoryKey = null;
|
|
532
|
+
resolvedEpic = null;
|
|
533
|
+
resolvedStoryFilePath = null;
|
|
534
|
+
// When the rejected story was at a phase that REQUIRES a story_key
|
|
535
|
+
// to emit a coherent action, also reset state.phase to flowStart.
|
|
536
|
+
// Otherwise the next emission produces a story-bound action (e.g.,
|
|
537
|
+
// commit_and_push_story) with null story_key → branch resolves to
|
|
538
|
+
// "story/unknown" → execution fails or corrupts the working tree.
|
|
539
|
+
if (STORY_BOUND_PHASES.has(phase)) {
|
|
540
|
+
process.stderr.write(
|
|
541
|
+
`[autopilot] WARN phase was "${phase}" (requires story_key) — resetting to ${flowStart} so next emission re-enters story-start.\n`,
|
|
542
|
+
);
|
|
543
|
+
phase = flowStart;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
511
546
|
}
|
|
512
547
|
}
|
|
513
548
|
// Explicit queue (populated by `autopilot start --stories` / `--epic`)
|
|
@@ -597,6 +632,39 @@ function composeRuntimeState(persisted, profile, projectRoot) {
|
|
|
597
632
|
remainingStoriesInEpic = epicStories.length;
|
|
598
633
|
}
|
|
599
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
|
+
|
|
600
668
|
return {
|
|
601
669
|
phase,
|
|
602
670
|
story_key: resolvedStoryKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikunin/sprintpilot",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.10",
|
|
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": {
|