When a session dies mid-execution (hang, token exhaustion, API drop), STATE.md's current_phase can be advanced past a phase that still has PLAN.md files without matching SUMMARY.md files. Without this fix, /gsd-next and /gsd-progress would route by current_phase and silently skip the partially-executed phase, producing a data-loss-shape outcome. Adds a Route 0 cross-phase incomplete-execution scan to both next.md and progress.md. Before any current_phase-based routing, the scan finds the lowest-numbered phase where plans outnumber summaries and routes to /gsd-execute-phase <that-phase> to resume it. Opt out with --no-resume to fall back to the prior-phase defer prompt; --force bypasses all gates as before. Rework (codex review): - Route 0 now ordered AFTER Gates 1-3 (repo/state validity always run) but BEFORE the prior-phase completeness-scan defer prompt — eliminating the double-decision where the default path would both prompt the user (C/S/F) AND resume the phase anyway. Prior-phase defer prompt moved to a new prior_phase_completeness step; only reached via --no-resume. - --force flow made coherent across all three steps: safety_gates jumps directly to determine_next_action, skipping Gates, Route 0, AND prior_phase_completeness. resume_incomplete_phase and prior_phase_completeness now correctly state --force never reaches them. success_criteria entry updated to reflect --force → determine_next_action (not prior_phase_completeness). - Scan uses $GSD_SDK (canonical resolver form) throughout next.md, matching the file's existing convention. progress.md uses $ROADMAP already loaded by analyze_roadmap. Neither file uses bare gsd-sdk. - Errors are surfaced rather than suppressed: removed 2>/dev/null on the main roadmap.analyze call; added explicit WARNING emission when the scan cannot run, so the invariant fails closed instead of failing open. - Predicate aligned to plans-without-summaries (plans.length > summaries.length) in both files, consistent with determine_next_action Route 4. - command references use canonical /gsd: namespace form Fixes #160 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,55 @@ When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` lin
|
||||
<step name="route">
|
||||
**Determine next action based on verified counts.**
|
||||
|
||||
**Step 0: Resume-incomplete-phase invariant (Route 0)**
|
||||
|
||||
Before any current-phase-scoped counting, scan ALL phases for incomplete execution. This catches the case where STATE.md's `current_phase` was advanced past the phase that actually has unfinished work (common after a mid-execution session death from hang, token exhaustion, or API disruption). Without this guard, the current-phase-scoped count in Step 1 would inspect the wrong phase and the routing would skip the unfinished work.
|
||||
|
||||
**Skip if `--no-resume` or `--force` is present in `$ARGUMENTS`.**
|
||||
|
||||
Scan all phases via the `$ROADMAP` JSON already loaded in `analyze_roadmap`. For each phase entry, compare `plans` length to `summaries` length using the same plans-without-summaries predicate as `determine_next_action` Route 4 (`plans.length > summaries.length`). Stop at the first (lowest-numbered) phase where the predicate is true. Record its phase number as `INCOMPLETE_PHASE`.
|
||||
|
||||
If `$ROADMAP` is empty or the query failed, surface a warning rather than silently proceeding:
|
||||
|
||||
```bash
|
||||
INCOMPLETE_PHASE=""
|
||||
if [ -z "$ROADMAP" ]; then
|
||||
echo "⚠ WARNING: resume-incomplete-phase scan could not run (\$ROADMAP is empty)." >&2
|
||||
echo " The incomplete-phase invariant (#160) could not be verified." >&2
|
||||
echo " Review project state carefully before continuing." >&2
|
||||
else
|
||||
for PHASE_NUM in $(echo "$ROADMAP" | jq -r '.phases[] | (.number // .phase_number)'); do
|
||||
PHASE_DATA=$(echo "$ROADMAP" | jq --arg n "$PHASE_NUM" '.phases[] | select((.number // .phase_number) == ($n | tonumber))')
|
||||
PLAN_COUNT=$(echo "$PHASE_DATA" | jq '(.plans // []) | length')
|
||||
SUMMARY_COUNT=$(echo "$PHASE_DATA" | jq '(.summaries // []) | length')
|
||||
if [ "${PLAN_COUNT:-0}" -gt "${SUMMARY_COUNT:-0}" ]; then
|
||||
INCOMPLETE_PHASE="$PHASE_NUM"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
```
|
||||
|
||||
**If `INCOMPLETE_PHASE` is non-empty:** emit a one-line resume notice in the routing output and route to `/gsd:execute-phase ${INCOMPLETE_PHASE}` instead of running Step 1's current-phase routing. The progress report (already displayed by the `report` step above) gives the user full project status before this routing decision is shown.
|
||||
|
||||
```
|
||||
---
|
||||
|
||||
## ▶ Next Up — Resuming incomplete Phase ${INCOMPLETE_PHASE}
|
||||
|
||||
`/clear` then:
|
||||
|
||||
`/gsd:execute-phase ${INCOMPLETE_PHASE} ${GSD_WS}`
|
||||
|
||||
(plans without summaries detected; use --no-resume to skip this check and route by current_phase instead; --force to skip all gates)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
Then exit the route step. Do NOT run Steps 1 through Routes A-F.
|
||||
|
||||
**If `INCOMPLETE_PHASE` is empty:** continue to Step 1.
|
||||
|
||||
**Step 1: Count plans, summaries, and issues in current phase**
|
||||
|
||||
List files in the current phase directory:
|
||||
|
||||
Reference in New Issue
Block a user