* fix(#724): block convergence on actionable review findings * merge: integrate clean next (#936 inline) onto author tip + re-apply cursor fixes (Mode field, REVIEWS.md extraction) and review hardening (#724) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/nimble-jays-fly.md
Normal file
5
.changeset/nimble-jays-fly.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 728
|
||||
---
|
||||
**`/gsd-plan-review-convergence` now blocks on actionable review findings outside PLAN.md (#724).** The convergence summary contract includes current_actionable alongside current_high, and reviews-mode planning/checking requires actionable MEDIUM/LOW feedback to be incorporated or explicitly deferred in executable PLAN.md content.
|
||||
@@ -76,6 +76,15 @@ If CONTEXT.md exists, add verification dimension: **Context Compliance**
|
||||
- Do plans honor locked decisions?
|
||||
- Are deferred ideas excluded?
|
||||
- Are discretion areas handled appropriately?
|
||||
|
||||
**REVIEWS.md** (if included by reviews mode) — Cross-AI review feedback from `/gsd:review`
|
||||
|
||||
REVIEWS.md is audit trail and feedback input, not a hidden execution contract. /gsd:execute-phase primarily consumes PLAN.md plus normal phase context. Add verification dimension: **Review Incorporation**.
|
||||
|
||||
- Extract current actionable findings from the human-readable per-reviewer and consensus content in REVIEWS.md. Do NOT look for a `CYCLE_SUMMARY: current_high=<N> current_actionable=<M>` line or `## Current HIGH Concerns` / `## Current Actionable Non-HIGH Concerns` section headers — those machine-readable fields exist only in the convergence orchestrator's return message, never in REVIEWS.md (which contains only human-readable review content).
|
||||
- Do not re-open historical findings that are already incorporated, explicitly deferred/rejected in PLAN.md, or marked fully resolved.
|
||||
- Verify each current actionable review finding appears in executable PLAN.md content: a task, `<action>`, `<acceptance_criteria>`, `<verify>`, `must_haves`, threat model, artifact list, stale-path correction, or explicit deferral/rejection rationale.
|
||||
- If a current actionable finding remains only in REVIEWS.md and would be invisible to /gsd:execute-phase, return `## ISSUES FOUND`. Use WARNING by default; use BLOCKER when the missing incorporation can prevent the phase goal, create unsafe execution, or invalidate verification.
|
||||
</upstream_input>
|
||||
|
||||
<core_principle>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: gsd:plan-review-convergence
|
||||
description: "Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain."
|
||||
description: "Cross-AI plan convergence - replan until review concerns are resolved."
|
||||
argument-hint: "<phase> [--codex] [--gemini] [--claude] [--opencode] [--ollama] [--lm-studio] [--llama-cpp] [--text] [--ws <name>] [--all] [--max-cycles N]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -9,19 +9,20 @@ allowed-tools:
|
||||
- Glob
|
||||
- Grep
|
||||
- Agent
|
||||
- Skill
|
||||
- AskUserQuestion
|
||||
requires: [phase, review]
|
||||
---
|
||||
|
||||
<objective>
|
||||
Cross-AI plan convergence loop — an outer revision gate around gsd-review and gsd-planner.
|
||||
Repeatedly: review plans with external AI CLIs → if HIGH concerns found → replan with --reviews feedback → re-review. Stops when no HIGH concerns remain or max cycles reached.
|
||||
Repeatedly: review plans with external AI CLIs → if HIGH or actionable non-HIGH concerns remain → replan with --reviews feedback → re-review. Stops when no unresolved HIGH concerns or actionable MEDIUM/LOW findings remain outside PLAN.md, or when max cycles is reached.
|
||||
|
||||
**Flow:** Skill("gsd-plan-phase") → Agent→Skill("gsd-review") → check HIGHs → Skill("gsd-plan-phase --reviews") → Agent→Skill("gsd-review") → ... → Converge or escalate
|
||||
**Flow:** Skill("gsd-plan-phase") → Agent→Skill("gsd-review") → check unresolved HIGH + actionable non-HIGH → Skill("gsd-plan-phase --reviews") → Agent→Skill("gsd-review") → ... → Converge or escalate
|
||||
|
||||
Replaces gsd-plan-phase's internal gsd-plan-checker with external AI reviewers (codex, gemini, etc.). Plan-phase runs **inline** (bare Skill at depth 0) so it can spawn gsd-planner/gsd-plan-checker at depth 1. Review runs inside an isolated Agent (gsd-review is a Bash leaf — no sub-agents needed). Orchestrator only does loop control.
|
||||
|
||||
**Orchestrator role:** Parse arguments, validate phase, run plan-phase inline (Skill at depth 0), spawn an Agent for gsd-review, check HIGHs, stall detection, escalation gate.
|
||||
**Orchestrator role:** Parse arguments, validate phase, run plan-phase inline (Skill at depth 0), spawn an Agent for gsd-review, check unresolved HIGH and actionable non-HIGH counts, stall detection, escalation gate.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
||||
@@ -205,7 +205,7 @@ See [Package Legitimacy Gate in the User Guide](USER-GUIDE.md#package-legitimacy
|
||||
|
||||
### `/gsd-plan-review-convergence`
|
||||
|
||||
Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain. Runs `plan-phase → review → replan → re-review` cycles (max 3 cycles by default). Spawns isolated agents for planning and review; orchestrator handles loop control, HIGH-concern counting, stall detection, and escalation.
|
||||
Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain and no actionable MEDIUM/LOW findings remain outside `PLAN.md`. Runs `plan-phase → review → replan → re-review` cycles (max 3 cycles by default). Plan-phase runs inline (bare Skill at depth 0 so it can spawn gsd-planner/gsd-plan-checker at depth 1); only gsd-review runs in an isolated Agent. Orchestrator handles loop control, unresolved review counting (HIGH + actionable non-HIGH), stall detection, and escalation.
|
||||
|
||||
| Argument / Flag | Required | Description |
|
||||
|-----------------|----------|-------------|
|
||||
@@ -214,7 +214,7 @@ Cross-AI plan convergence loop — replan with review feedback until no HIGH con
|
||||
| `--all` | No | Run every configured reviewer in parallel |
|
||||
| `--max-cycles N` | No | Override cycle cap (default 3) |
|
||||
|
||||
**Exit behavior:** Loop exits when HIGH count hits zero. Stall detection warns when HIGH count is not decreasing across cycles. Escalation gate asks the user to proceed or review manually when `--max-cycles` is hit with HIGH concerns still open.
|
||||
**Exit behavior:** Loop exits when both `current_high` and `current_actionable` hit zero. Stall detection warns when the total unresolved review count is not decreasing across cycles. Escalation gate asks the user to proceed or review manually when `--max-cycles` is hit with HIGH or actionable non-HIGH concerns still open.
|
||||
|
||||
```bash
|
||||
/gsd-plan-review-convergence 3 # Default reviewers, 3 cycles
|
||||
|
||||
@@ -256,7 +256,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
|
||||
| `workflow.plan_bounce_script` | string | (none) | Path to the external script invoked for plan bounce validation. Receives the PLAN.md path as its first argument. Required when `plan_bounce` is `true`. Added in v1.36 |
|
||||
| `workflow.plan_bounce_passes` | number | `2` | Number of sequential bounce passes to run. Each pass feeds the previous pass's output back into the validator. Higher values increase rigor at the cost of latency. Added in v1.36 |
|
||||
| `workflow.post_planning_gaps` | boolean | `true` | Unified post-planning gap report (#2493). After all plans are generated and committed, scans REQUIREMENTS.md and CONTEXT.md `<decisions>` against every PLAN.md in the phase directory, then prints one `Source \| Item \| Status` table. Word-boundary matching (REQ-1 vs REQ-10) and natural sort (REQ-02 before REQ-10). Non-blocking — informational report only. Set to `false` to skip Step 13e of plan-phase. |
|
||||
| `workflow.plan_review_convergence` | boolean | `false` | Enable the `/gsd-plan-review-convergence` command. Disabled by default — the command exits with an enable instruction when this key is `false`. The command automates the manual plan→review→replan loop: it spawns configured reviewers (Codex, Gemini, Claude, OpenCode, Ollama, LM Studio, llama.cpp), counts unresolved HIGH concerns via the CYCLE_SUMMARY contract, replans with `--reviews` feedback, and repeats until converged or max cycles reached. Enable with `gsd config-set workflow.plan_review_convergence true`. Added in v1.39 |
|
||||
| `workflow.plan_review_convergence` | boolean | `false` | Enable the `/gsd-plan-review-convergence` command. Disabled by default — the command exits with an enable instruction when this key is `false`. The command automates the manual plan→review→replan loop: it spawns configured reviewers (Codex, Gemini, Claude, OpenCode, Ollama, LM Studio, llama.cpp), counts unresolved HIGH concerns and actionable MEDIUM/LOW findings via the CYCLE_SUMMARY contract, replans with `--reviews` feedback, and repeats until converged or max cycles reached. Enable with `gsd config-set workflow.plan_review_convergence true`. Added in v1.39 |
|
||||
| `workflow.plan_chunked` | boolean | `false` | Enable chunked planning mode. When `true` (or when `--chunked` flag is passed to `/gsd-plan-phase`), the orchestrator splits the single long-lived planner Task into a short outline Task followed by N short per-plan Tasks (~3-5 min each). Each plan is committed individually for crash resilience. If a Task hangs and the terminal is force-killed, rerunning with `--chunked` resumes from the last completed plan. Particularly useful on Windows where long-lived Tasks may hang on stdio. Added in v1.38 |
|
||||
| `workflow.code_review_command` | string | (none) | Shell command for external code review integration in `/gsd-ship`. Receives changed file paths via stdin. Non-zero exit blocks the ship workflow. Added in v1.36 |
|
||||
| `workflow.tdd_mode` | boolean | `false` | Enable TDD pipeline as a first-class execution mode. When `true`, the planner aggressively applies `type: tdd` to eligible tasks (business logic, APIs, validations, algorithms) and the executor enforces RED/GREEN/REFACTOR gate sequence. An end-of-phase collaborative review checkpoint verifies gate compliance. Added in v1.36 |
|
||||
|
||||
@@ -85,7 +85,7 @@ These six routers are descriptor-only entries that the model picks first; the bo
|
||||
| `/gsd-ui-phase` | Generate UI design contract (UI-SPEC.md) for frontend phases. | [commands/gsd/ui-phase.md](../commands/gsd/ui-phase.md) |
|
||||
| `/gsd-ai-integration-phase` | Generate AI design contract (AI-SPEC.md) via framework selection, research, and eval planning. | [commands/gsd/ai-integration-phase.md](../commands/gsd/ai-integration-phase.md) |
|
||||
| `/gsd-plan-phase` | Create detailed phase plan (PLAN.md) with verification loop. | [commands/gsd/plan-phase.md](../commands/gsd/plan-phase.md) |
|
||||
| `/gsd-plan-review-convergence` | Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain (max 3 cycles). | [commands/gsd/plan-review-convergence.md](../commands/gsd/plan-review-convergence.md) |
|
||||
| `/gsd-plan-review-convergence` | Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns or actionable non-HIGH findings remain (max 3 cycles). | [commands/gsd/plan-review-convergence.md](../commands/gsd/plan-review-convergence.md) |
|
||||
| `/gsd-ultraplan-phase` | [BETA] Offload plan phase to Claude Code's ultraplan cloud — drafts remotely, review in browser, import back via `/gsd-import`. Claude Code only. | [commands/gsd/ultraplan-phase.md](../commands/gsd/ultraplan-phase.md) |
|
||||
| `/gsd-spike` | Rapidly spike an idea with throwaway experiments; use `--wrap-up` to package findings as a persistent skill. | [commands/gsd/spike.md](../commands/gsd/spike.md) |
|
||||
| `/gsd-sketch` | Rapidly sketch UI/design ideas using throwaway HTML mockups; use `--wrap-up` to package findings. | [commands/gsd/sketch.md](../commands/gsd/sketch.md) |
|
||||
@@ -224,7 +224,7 @@ Full roster at `gsd-core/workflows/*.md`. Workflows are thin orchestrators that
|
||||
| `note.md` | Zero-friction idea capture — one Write call, one confirmation line. | `/gsd-capture --note` |
|
||||
| `pause-work.md` | Create structured `.planning/HANDOFF.json` and `.continue-here.md` handoff files. | `/gsd-pause-work` |
|
||||
| `plan-phase.md` | Create executable PLAN.md files with integrated research and verification loop. | `/gsd-plan-phase`, `/gsd-quick` |
|
||||
| `plan-review-convergence.md` | Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain. | `/gsd-plan-review-convergence` |
|
||||
| `plan-review-convergence.md` | Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns or actionable non-HIGH findings remain. | `/gsd-plan-review-convergence` |
|
||||
| `plant-seed.md` | Capture a forward-looking idea as a structured seed file with trigger conditions. | `/gsd-capture --seed` |
|
||||
| `pr-branch.md` | Create a clean branch for pull requests by filtering `.planning/` commits. | `/gsd-pr-branch` |
|
||||
| `profile-user.md` | Orchestrate the full developer profiling flow — consent, session scan, profile generation. | `/gsd-profile-user` |
|
||||
|
||||
@@ -4,6 +4,8 @@ Triggered when orchestrator sets Mode to `reviews`. Replanning from scratch with
|
||||
|
||||
**Mindset:** Fresh planner with review insights — not a surgeon making patches, but an architect who has read peer critiques.
|
||||
|
||||
**Execution contract:** REVIEWS.md is audit trail and feedback input, not a second execution contract. /gsd:execute-phase primarily consumes PLAN.md plus the normal phase context. Every current actionable review finding must therefore be incorporated into the relevant PLAN.md or explicitly deferred/rejected in that PLAN.md.
|
||||
|
||||
### Step 1: Load REVIEWS.md
|
||||
Read the reviews file from `<files_to_read>`. Parse:
|
||||
- Per-reviewer feedback (strengths, concerns, suggestions)
|
||||
@@ -13,13 +15,14 @@ Read the reviews file from `<files_to_read>`. Parse:
|
||||
### Step 2: Categorize Feedback
|
||||
Group review feedback into:
|
||||
- **Must address**: HIGH severity consensus concerns
|
||||
- **Should address**: MEDIUM severity concerns from 2+ reviewers
|
||||
- **Must represent in PLAN.md**: actionable MEDIUM/LOW findings that require task, action, acceptance criteria, verify command, must_haves, threat-model, artifact, stale-path, or execution-contract changes
|
||||
- **Should address**: MEDIUM severity concerns from 2+ reviewers that improve quality but do not change the executable contract
|
||||
- **Consider**: Individual reviewer suggestions, LOW severity items
|
||||
|
||||
### Step 3: Plan Fresh with Review Context
|
||||
Create new plans following the standard planning process, but with review feedback as additional constraints:
|
||||
- Each HIGH severity consensus concern MUST have a task that addresses it
|
||||
- MEDIUM concerns should be addressed where feasible without over-engineering
|
||||
- Each current actionable MEDIUM/LOW finding MUST either appear in the relevant PLAN.md executable content or have a deferral/rejection rationale in that PLAN.md
|
||||
- Note in task actions: "Addresses review concern: {concern}" for traceability
|
||||
|
||||
### Step 4: Return
|
||||
|
||||
@@ -934,7 +934,7 @@ Planner prompt:
|
||||
- {PATTERNS_PATH} (Pattern Map — analog files and code excerpts, if exists)
|
||||
- {verification_path} (Verification Gaps - if --gaps)
|
||||
- {uat_path} (UAT Gaps - if --gaps)
|
||||
- {reviews_path} (Cross-AI Review Feedback - if --reviews)
|
||||
- {reviews_path} (Cross-AI Review Feedback - if --reviews; actionable findings must be incorporated or explicitly deferred/rejected in PLAN.md)
|
||||
- {UI_SPEC_PATH} (UI Design Contract — visual/interaction specs, if exists)
|
||||
- {SPIKE_FINDINGS_PATH} (Spike Findings — validated patterns, constraints, landmines from experiments, if exists)
|
||||
- {SKETCH_FINDINGS_PATH} (Sketch Findings — validated design decisions, CSS patterns, visual direction, if exists)
|
||||
@@ -955,6 +955,16 @@ ${API_SURFACE_PATH ? `
|
||||
` : ''}
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
<review_incorporation_contract>
|
||||
**If Mode is reviews:** REVIEWS.md is feedback input, not a hidden execution contract. /gsd:execute-phase primarily consumes PLAN.md plus the normal phase context, so every current actionable review finding must become visible in the relevant PLAN.md before planning can pass.
|
||||
|
||||
For each current actionable finding in REVIEWS.md, the planner MUST either:
|
||||
- incorporate it into a PLAN.md task, `<action>`, `<acceptance_criteria>`, `<verify>`, `must_haves`, threat model, or artifact list; or
|
||||
- explicitly document a deferral/rejection rationale in the relevant PLAN.md so the executor and reviewer can see the decision.
|
||||
|
||||
Historical findings already incorporated, explicitly deferred/rejected in PLAN.md, or marked fully resolved do not require new plan changes.
|
||||
</review_incorporation_contract>
|
||||
|
||||
**Phase requirement IDs (every ID MUST appear in a plan's `requirements` field):** {phase_req_ids}
|
||||
|
||||
**Project instructions:** Read ./CLAUDE.md if exists — follow project-specific guidelines
|
||||
@@ -1273,6 +1283,7 @@ Checker prompt:
|
||||
<verification_context>
|
||||
**Phase:** {phase_number}
|
||||
**Phase Goal:** {goal from ROADMAP}
|
||||
**Mode:** {standard | gap_closure | reviews}
|
||||
|
||||
<files_to_read>
|
||||
- {PHASE_DIR}/*-PLAN.md (Plans to verify)
|
||||
@@ -1280,10 +1291,17 @@ Checker prompt:
|
||||
- {requirements_path} (Requirements)
|
||||
- {context_path} (USER DECISIONS from /gsd:discuss-phase)
|
||||
- {research_path} (Technical Research — includes Validation Architecture)
|
||||
- {reviews_path} (Cross-AI Review Feedback - if --reviews; verify actionable findings are represented in PLAN.md)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_CHECKER}
|
||||
|
||||
<review_incorporation_verification>
|
||||
**If Mode is reviews:** Read REVIEWS.md and verify each current actionable review finding is visible in executable PLAN.md content or explicitly deferred/rejected in the relevant PLAN.md. A finding remains actionable if it requires a concrete plan task, `<action>`, `<acceptance_criteria>`, `<verify>`, `must_haves`, threat-model item, stale-path correction, or execution contract change before /gsd:execute-phase runs.
|
||||
|
||||
If an actionable finding remains only in REVIEWS.md and would be invisible to /gsd:execute-phase, return `## ISSUES FOUND`. Use WARNING by default; use BLOCKER when the missing incorporation can prevent the phase goal, create unsafe execution, or invalidate verification.
|
||||
</review_incorporation_verification>
|
||||
|
||||
**Phase requirement IDs (MUST ALL be covered):** {phase_req_ids}
|
||||
|
||||
**Project instructions:** Read ./CLAUDE.md if exists — verify plans honor project guidelines
|
||||
|
||||
@@ -3,7 +3,7 @@ Cross-AI plan convergence loop — automates the manual chain:
|
||||
gsd-plan-phase N → gsd-review N --codex → gsd-plan-phase N --reviews → gsd-review N --codex → ...
|
||||
Plan-phase runs inline (bare Skill at depth 0) so it can spawn gsd-planner/gsd-plan-checker at depth 1.
|
||||
Review runs inside an isolated Agent (leaf skill — Bash only, no sub-agents needed).
|
||||
Orchestrator only does: init, loop control, parse CYCLE_SUMMARY for HIGH count, stall detection, escalation.
|
||||
Orchestrator only does: init, loop control, parse CYCLE_SUMMARY for HIGH and actionable non-HIGH counts, stall detection, escalation.
|
||||
</purpose>
|
||||
|
||||
<required_reading>
|
||||
@@ -122,7 +122,7 @@ Initialize loop variables:
|
||||
|
||||
```text
|
||||
cycle = 0
|
||||
prev_high_count = Infinity
|
||||
prev_unresolved_count = Infinity
|
||||
```
|
||||
|
||||
### 5a. Review (Spawn Agent)
|
||||
@@ -143,9 +143,10 @@ Complete the full review workflow. Do NOT return until REVIEWS.md is committed.
|
||||
IMPORTANT — CYCLE_SUMMARY contract (required):
|
||||
Your final response MUST include a machine-readable line of exactly this form:
|
||||
|
||||
CYCLE_SUMMARY: current_high=<N>
|
||||
CYCLE_SUMMARY: current_high=<N> current_actionable=<M>
|
||||
|
||||
Where <N> is the integer count of HIGH-severity concerns that REMAIN UNRESOLVED in this cycle's findings.
|
||||
Where <M> is the integer count of actionable MEDIUM/LOW concerns that REMAIN UNRESOLVED because the latest PLAN.md files do not yet incorporate them or explicitly defer/reject them.
|
||||
|
||||
Counting rules:
|
||||
INCLUDE in the count:
|
||||
@@ -157,16 +158,23 @@ Counting rules:
|
||||
- FULLY RESOLVED HIGHs: concern addressed with verification complete (closed ticket, verification log, or reviewer sign-off)
|
||||
- HIGH mentions in retrospective/summary tables comparing cycles
|
||||
- Quoted excerpts from prior reviews referencing past HIGH items
|
||||
- MEDIUM/LOW concerns that are already incorporated into a PLAN.md task, action, acceptance_criteria, verify command, must_haves item, threat model, artifact list, or explicit deferral/rejection rationale
|
||||
|
||||
Definitions:
|
||||
PARTIALLY RESOLVED — concern acknowledged and mitigation is in progress but not yet verified/completed (e.g., open ticket exists but fix not landed).
|
||||
FULLY RESOLVED — concern addressed with verification complete (closed ticket, verification log, or explicit reviewer sign-off confirming closure).
|
||||
ACTIONABLE — a non-HIGH review finding that would be invisible to /gsd:execute-phase unless it is incorporated into PLAN.md or explicitly deferred/rejected in PLAN.md.
|
||||
|
||||
Your final response MUST also include this section immediately after the CYCLE_SUMMARY line:
|
||||
|
||||
## Current HIGH Concerns
|
||||
[List each unresolved HIGH with a brief description, one per bullet]
|
||||
[If none: write exactly 'None.']",
|
||||
[If none: write exactly 'None.']
|
||||
|
||||
## Current Actionable Non-HIGH Concerns
|
||||
[List each unresolved actionable MEDIUM/LOW with a brief description and the PLAN.md change still needed, one per bullet]
|
||||
[If none: write exactly 'None.']
|
||||
These two sections MUST be the final content of your response, in this exact order, with no additional "## " headings after them (the source-grounding "Verification coverage" block is appended to REVIEWS.md, not to this return message).",
|
||||
mode="auto"
|
||||
)
|
||||
```
|
||||
@@ -194,35 +202,49 @@ REVIEWS_FILE=$(ls ${phase_dir}/${padded_phase}-REVIEWS.md 2>/dev/null)
|
||||
|
||||
If REVIEWS_FILE is empty: Error — review agent did not produce REVIEWS.md. Exit.
|
||||
|
||||
### 5b. Extract HIGH Count from CYCLE_SUMMARY Contract
|
||||
### 5b. Extract unresolved counts from CYCLE_SUMMARY Contract
|
||||
|
||||
**Do NOT grep REVIEWS.md for HIGH count.** REVIEWS.md accumulates history across cycles — resolved HIGHs from prior cycles remain in the file as audit trail, inflating a raw grep count and causing false stall detection.
|
||||
**Do NOT grep REVIEWS.md for HIGH or actionable counts.** REVIEWS.md accumulates history across cycles — resolved findings from prior cycles remain in the file as audit trail, inflating a raw grep count and causing false stall detection.
|
||||
|
||||
Parse HIGH_COUNT from the review agent's return message via the CYCLE_SUMMARY contract:
|
||||
Parse HIGH_COUNT and ACTIONABLE_COUNT from the review agent's return message via the CYCLE_SUMMARY contract:
|
||||
|
||||
```bash
|
||||
# Extract the integer from "CYCLE_SUMMARY: current_high=N" in the agent's return message
|
||||
HIGH_COUNT=$(echo "$REVIEW_AGENT_RETURN" | grep -oE 'CYCLE_SUMMARY:\s*current_high=[0-9]+' | head -1 | grep -oE '[0-9]+$')
|
||||
# Extract integers from "CYCLE_SUMMARY: current_high=N current_actionable=M" in the agent's return message
|
||||
SUMMARY_LINE=$(echo "$REVIEW_AGENT_RETURN" | grep -oE 'CYCLE_SUMMARY:.*' | head -1)
|
||||
HIGH_COUNT=$(echo "$SUMMARY_LINE" | grep -oE 'current_high=[0-9]+' | head -1 | grep -oE '[0-9]+$')
|
||||
ACTIONABLE_COUNT=$(echo "$SUMMARY_LINE" | grep -oE 'current_actionable=[0-9]+' | head -1 | grep -oE '[0-9]+$')
|
||||
|
||||
if [ -z "$HIGH_COUNT" ]; then
|
||||
# Distinguish malformed contract from completely absent contract
|
||||
if echo "$REVIEW_AGENT_RETURN" | grep -q 'CYCLE_SUMMARY:'; then
|
||||
echo "CYCLE_SUMMARY present but current_high is malformed — expected integer, got non-numeric value. Retry or switch reviewer."
|
||||
else
|
||||
echo "Review agent did not honor the CYCLE_SUMMARY contract — cannot determine HIGH count. Retry or switch reviewer."
|
||||
fi
|
||||
if [ -z "$SUMMARY_LINE" ]; then
|
||||
echo "Review agent did not honor the CYCLE_SUMMARY contract — cannot determine unresolved review counts. Retry or switch reviewer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$HIGH_COUNT" ]; then
|
||||
echo "CYCLE_SUMMARY present but current_high is missing or malformed — expected integer, got non-numeric or absent value. Retry or switch reviewer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$ACTIONABLE_COUNT" ]; then
|
||||
echo "CYCLE_SUMMARY present but current_actionable is missing or malformed — expected integer, got non-numeric or absent value. Retry or switch reviewer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UNRESOLVED_COUNT=$((HIGH_COUNT + ACTIONABLE_COUNT))
|
||||
|
||||
# Extract the ## Current HIGH Concerns section from the agent's return message
|
||||
HIGH_LINES=$(echo "$REVIEW_AGENT_RETURN" | awk '/^## Current HIGH Concerns/{found=1; next} found && /^##/{exit} found{print}')
|
||||
ACTIONABLE_LINES=$(echo "$REVIEW_AGENT_RETURN" | awk '/^## Current Actionable Non-HIGH Concerns/{found=1; next} found && /^##/{exit} found{print}')
|
||||
|
||||
if [ "${HIGH_COUNT}" -gt 0 ] && [ -z "${HIGH_LINES}" ]; then
|
||||
echo "⚠ Review agent's CYCLE_SUMMARY reports ${HIGH_COUNT} HIGHs but did not provide ## Current HIGH Concerns section — continuing with incomplete escalation details."
|
||||
fi
|
||||
|
||||
if [ "${ACTIONABLE_COUNT}" -gt 0 ] && [ -z "${ACTIONABLE_LINES}" ]; then
|
||||
echo "⚠ Review agent's CYCLE_SUMMARY reports ${ACTIONABLE_COUNT} actionable non-HIGH concerns but did not provide ## Current Actionable Non-HIGH Concerns section — continuing with incomplete escalation details."
|
||||
fi
|
||||
```
|
||||
|
||||
**If HIGH_COUNT == 0 (converged):**
|
||||
**If HIGH_COUNT == 0 and ACTIONABLE_COUNT == 0 (converged):**
|
||||
|
||||
```bash
|
||||
gsd_run state planned-phase --phase "${PHASE}" --name "${phase_name}" --plans "${PLAN_COUNT}"
|
||||
@@ -236,6 +258,7 @@ Display:
|
||||
|
||||
Phase {phase_number} converged in {cycle} cycle(s).
|
||||
No HIGH concerns remaining.
|
||||
No actionable MEDIUM/LOW review findings remain outside PLAN.md.
|
||||
|
||||
REVIEWS.md: {REVIEWS_FILE}
|
||||
Next: /gsd:execute-phase {PHASE}
|
||||
@@ -243,16 +266,16 @@ Display:
|
||||
|
||||
Exit — convergence achieved.
|
||||
|
||||
**If HIGH_COUNT > 0:** Continue to 5c.
|
||||
**If HIGH_COUNT > 0 or ACTIONABLE_COUNT > 0:** Continue to 5c.
|
||||
|
||||
### 5c. Stall Detection + Escalation Check
|
||||
|
||||
Display: `◆ Cycle {cycle}/{MAX_CYCLES} — {HIGH_COUNT} HIGH concerns found`
|
||||
Display: `◆ Cycle {cycle}/{MAX_CYCLES} — {HIGH_COUNT} HIGH, {ACTIONABLE_COUNT} actionable non-HIGH review concerns found`
|
||||
|
||||
**Stall detection:** If `HIGH_COUNT >= prev_high_count`:
|
||||
**Stall detection:** If `UNRESOLVED_COUNT >= prev_unresolved_count`:
|
||||
```text
|
||||
⚠ Convergence stalled — HIGH concern count not decreasing
|
||||
({HIGH_COUNT} HIGH concerns, previous cycle had {prev_high_count})
|
||||
⚠ Convergence stalled — unresolved review concern count not decreasing
|
||||
({UNRESOLVED_COUNT} unresolved concerns, previous cycle had {prev_unresolved_count})
|
||||
```
|
||||
|
||||
**Max cycles check:** If `cycle >= MAX_CYCLES`:
|
||||
@@ -260,13 +283,15 @@ Display: `◆ Cycle {cycle}/{MAX_CYCLES} — {HIGH_COUNT} HIGH concerns found`
|
||||
If `TEXT_MODE` is true, present as plain-text numbered list:
|
||||
```text
|
||||
Plan convergence did not complete after {MAX_CYCLES} cycles.
|
||||
{HIGH_COUNT} HIGH concerns remain:
|
||||
{HIGH_COUNT} HIGH concerns and {ACTIONABLE_COUNT} actionable non-HIGH concerns remain:
|
||||
|
||||
{HIGH_LINES}
|
||||
|
||||
{ACTIONABLE_LINES}
|
||||
|
||||
How would you like to proceed?
|
||||
|
||||
1. Proceed anyway — Accept plans with remaining HIGH concerns and move to execution
|
||||
1. Proceed anyway — Accept plans with remaining review concerns and move to execution
|
||||
2. Manual review — Stop here, review REVIEWS.md and address concerns manually
|
||||
|
||||
Enter number:
|
||||
@@ -276,11 +301,11 @@ Otherwise use AskUserQuestion:
|
||||
```js
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Plan convergence did not complete after {MAX_CYCLES} cycles. {HIGH_COUNT} HIGH concerns remain:\n\n{HIGH_LINES}\n\nHow would you like to proceed?",
|
||||
question: "Plan convergence did not complete after {MAX_CYCLES} cycles. {HIGH_COUNT} HIGH concerns and {ACTIONABLE_COUNT} actionable non-HIGH concerns remain:\n\n{HIGH_LINES}\n\n{ACTIONABLE_LINES}\n\nHow would you like to proceed?",
|
||||
header: "Convergence",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Proceed anyway", description: "Accept plans with remaining HIGH concerns and move to execution" },
|
||||
{ label: "Proceed anyway", description: "Accept plans with remaining review concerns and move to execution" },
|
||||
{ label: "Manual review", description: "Stop here — review REVIEWS.md and address concerns manually" }
|
||||
]
|
||||
}
|
||||
@@ -301,7 +326,7 @@ Exit workflow.
|
||||
|
||||
**If under max cycles:**
|
||||
|
||||
Update `prev_high_count = HIGH_COUNT`.
|
||||
Update `prev_unresolved_count = UNRESOLVED_COUNT`.
|
||||
|
||||
Display: `◆ Replanning inline with review feedback... (plan-phase runs here in the orchestrator — no output until replanning is complete, ~1–5 min; expected, not a freeze)`
|
||||
|
||||
@@ -309,7 +334,7 @@ Display: `◆ Replanning inline with review feedback... (plan-phase runs here in
|
||||
Skill(skill="gsd-plan-phase", args="{PHASE} --reviews --skip-research {GSD_WS}")
|
||||
```
|
||||
|
||||
Run plan-phase **inline** (do NOT wrap it in Agent()). Same rationale as step 4: the convergence orchestrator runs at depth 0 with Agent available, so inline plan-phase can spawn gsd-planner and gsd-plan-checker at depth 1. Wrapping in Agent() pushes plan-phase to depth 1 where the Agent tool is absent — the replan loop can never produce a revised plan when HIGHs are found. This is the root cause of bug #936. Wait until plan-phase completes (outputs '## PLANNING COMPLETE') and updated PLAN.md files are committed before continuing.
|
||||
Run plan-phase **inline** (do NOT wrap it in Agent()). Same rationale as step 4: the convergence orchestrator runs at depth 0 with Agent available, so inline plan-phase can spawn gsd-planner and gsd-plan-checker at depth 1. Wrapping in Agent() pushes plan-phase to depth 1 where the Agent tool is absent — the replan loop can never produce a revised plan when HIGHs are found. This is the root cause of bug #936. Actionable MEDIUM/LOW findings must be incorporated into executable PLAN.md content or explicitly deferred/rejected in the relevant PLAN.md before convergence can complete. Wait until plan-phase completes (outputs '## PLANNING COMPLETE') and updated PLAN.md files are committed before continuing.
|
||||
|
||||
After plan-phase completes → go back to **step 5a** (review again).
|
||||
|
||||
@@ -320,13 +345,15 @@ After plan-phase completes → go back to **step 5a** (review again).
|
||||
- [ ] Initial planning via inline Skill("gsd-plan-phase") if no plans exist — NOT wrapped in Agent() (bug #936: depth-1 Agent has no Agent tool)
|
||||
- [ ] Review via Agent → Skill("gsd-review") — isolated Agent is correct; gsd-review is a Bash leaf with no sub-agent spawns; {GSD_WS} forwarded
|
||||
- [ ] Replan via inline Skill("gsd-plan-phase --reviews") — NOT wrapped in Agent(); inline lets plan-phase spawn gsd-planner/gsd-plan-checker at depth 1
|
||||
- [ ] Orchestrator only does: init, config gate, loop control, parse CYCLE_SUMMARY for HIGH count, stall detection, escalation
|
||||
- [ ] HIGH count extracted from review agent's CYCLE_SUMMARY return message (not by grepping REVIEWS.md)
|
||||
- [ ] Review agent prompt defines CYCLE_SUMMARY: current_high=<N> contract with PARTIALLY/FULLY RESOLVED definitions
|
||||
- [ ] Orchestrator only does: init, config gate, loop control, parse CYCLE_SUMMARY for HIGH and actionable non-HIGH counts, stall detection, escalation
|
||||
- [ ] HIGH and actionable non-HIGH counts extracted from review agent's CYCLE_SUMMARY return message (not by grepping REVIEWS.md)
|
||||
- [ ] Review agent prompt defines CYCLE_SUMMARY: current_high=<N> current_actionable=<M> contract with PARTIALLY/FULLY RESOLVED/ACTIONABLE definitions
|
||||
- [ ] Abort with clear error if CYCLE_SUMMARY is absent; distinguish malformed from absent
|
||||
- [ ] Warn if HIGH_COUNT > 0 but ## Current HIGH Concerns section is absent from return message
|
||||
- [ ] Abort with clear error if current_actionable is absent or malformed
|
||||
- [ ] Warn if ACTIONABLE_COUNT > 0 but ## Current Actionable Non-HIGH Concerns section is absent from return message
|
||||
- [ ] The review Agent fully completes gsd-review before returning (plan-phase runs inline — no Agent wrap)
|
||||
- [ ] Loop exits on: no HIGH concerns (converged) OR max cycles (escalation)
|
||||
- [ ] Stall detection reported when HIGH count not decreasing
|
||||
- [ ] Loop exits on: no HIGH concerns and no actionable non-HIGH concerns (converged) OR max cycles (escalation)
|
||||
- [ ] Stall detection reported when total unresolved review concern count is not decreasing
|
||||
- [ ] STATE.md updated on convergence completion
|
||||
</success_criteria>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Validates that the command source and workflow contain the key structural
|
||||
* elements required for correct cross-AI plan convergence loop behavior:
|
||||
* initial planning gate, review agent spawning, CYCLE_SUMMARY contract for
|
||||
* HIGH count extraction, stall detection, escalation gate, and STATE.md update
|
||||
* unresolved review count extraction, stall detection, escalation gate, and STATE.md update
|
||||
* on convergence.
|
||||
*
|
||||
* v2 additions (#2306-v2):
|
||||
@@ -15,6 +15,11 @@
|
||||
* - PARTIALLY RESOLVED / FULLY RESOLVED definitions in contract
|
||||
* - HIGH_LINES validation warning when HIGH_COUNT > 0 but section absent
|
||||
* - Success criteria updated to reflect CYCLE_SUMMARY parsing
|
||||
*
|
||||
* v3 additions (#724):
|
||||
* - CYCLE_SUMMARY includes current_actionable for unresolved actionable MEDIUM/LOW findings
|
||||
* - convergence requires HIGH_COUNT == 0 and ACTIONABLE_COUNT == 0
|
||||
* - reviews-mode planner/checker prompts require REVIEWS.md feedback to land in PLAN.md
|
||||
*/
|
||||
|
||||
// allow-test-rule: source-text-is-the-product
|
||||
@@ -30,6 +35,9 @@ const path = require('path');
|
||||
const COMMAND_PATH = path.join(__dirname, '..', 'commands', 'gsd', 'plan-review-convergence.md');
|
||||
const WORKFLOW_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'plan-review-convergence.md');
|
||||
const CONFIG_DOC_PATH = path.join(__dirname, '..', 'docs', 'CONFIGURATION.md');
|
||||
const PLAN_PHASE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'plan-phase.md');
|
||||
const PLANNER_REVIEWS_PATH = path.join(__dirname, '..', 'gsd-core', 'references', 'planner-reviews.md');
|
||||
const PLAN_CHECKER_PATH = path.join(__dirname, '..', 'agents', 'gsd-plan-checker.md');
|
||||
|
||||
// ─── Command source ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -80,10 +88,17 @@ describe('plan-review-convergence command source (#2306)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('command declares Agent in allowed-tools (required for spawning sub-agents)', () => {
|
||||
test('command declares Agent in allowed-tools (required for spawning review sub-agents)', () => {
|
||||
assert.ok(
|
||||
command.includes('- Agent'),
|
||||
'Agent must be in allowed-tools — command spawns isolated agents for planning and reviewing'
|
||||
'Agent must be in allowed-tools — command spawns isolated agents for reviewing'
|
||||
);
|
||||
});
|
||||
|
||||
test('command declares Skill in allowed-tools (required for inline plan-phase invocations)', () => {
|
||||
assert.ok(
|
||||
command.includes('- Skill'),
|
||||
'Skill must be in allowed-tools — command invokes gsd-plan-phase inline via Skill() at depth 0 (#936 fix)'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -182,7 +197,7 @@ describe('plan-review-convergence workflow: initial planning gate (#2306)', () =
|
||||
test('workflow skips initial planning when plans already exist', () => {
|
||||
assert.ok(
|
||||
workflow.includes('has_plans') || workflow.includes('plan_count'),
|
||||
'workflow must check whether plans already exist before spawning planning agent'
|
||||
'workflow must check whether plans already exist before running inline planning'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -213,10 +228,10 @@ describe('plan-review-convergence workflow: convergence loop (#2306)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow extracts HIGH count from CYCLE_SUMMARY contract, NOT from grepping REVIEWS.md', () => {
|
||||
test('workflow extracts HIGH and actionable counts from CYCLE_SUMMARY contract, NOT from grepping REVIEWS.md', () => {
|
||||
// Critical regression guard: REVIEWS.md accumulates history across cycles;
|
||||
// resolved HIGHs from cycle N remain in the file during cycle N+1 as audit trail,
|
||||
// inflating raw grep counts and causing false stalls. HIGH count must come from
|
||||
// inflating raw grep counts and causing false stalls. Counts must come from
|
||||
// the review agent's CYCLE_SUMMARY return message, not from the file.
|
||||
assert.ok(
|
||||
workflow.includes('CYCLE_SUMMARY'),
|
||||
@@ -226,6 +241,10 @@ describe('plan-review-convergence workflow: convergence loop (#2306)', () => {
|
||||
workflow.includes('current_high'),
|
||||
'workflow must parse current_high from CYCLE_SUMMARY line'
|
||||
);
|
||||
assert.ok(
|
||||
workflow.includes('ACTIONABLE_COUNT') && workflow.includes('current_actionable'),
|
||||
'workflow must parse current_actionable from CYCLE_SUMMARY line (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow aborts if review agent omits CYCLE_SUMMARY contract', () => {
|
||||
@@ -245,6 +264,13 @@ describe('plan-review-convergence workflow: convergence loop (#2306)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow fails closed when current_actionable is missing or malformed', () => {
|
||||
assert.ok(
|
||||
workflow.includes('current_actionable is missing or malformed'),
|
||||
'missing or malformed current_actionable must abort instead of silently treating actionable findings as zero (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('review agent spawn forwards --ws via GSD_WS (symmetric with replan agent)', () => {
|
||||
// Critical correctness bug: if GSD_WS is not forwarded to the review agent,
|
||||
// the review reads from the wrong workspace while replanning reads from the correct one.
|
||||
@@ -257,12 +283,14 @@ describe('plan-review-convergence workflow: convergence loop (#2306)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow exits loop when HIGH_COUNT == 0 (converged)', () => {
|
||||
test('workflow exits loop only when HIGH_COUNT and ACTIONABLE_COUNT are zero', () => {
|
||||
assert.ok(
|
||||
workflow.includes('HIGH_COUNT == 0') ||
|
||||
workflow.includes('HIGH_COUNT === 0') ||
|
||||
workflow.includes('converged'),
|
||||
'workflow must exit the loop when no HIGH concerns remain'
|
||||
workflow.includes('HIGH_COUNT == 0 and ACTIONABLE_COUNT == 0'),
|
||||
'workflow must require both HIGH_COUNT and ACTIONABLE_COUNT to be zero before convergence (#724)'
|
||||
);
|
||||
assert.ok(
|
||||
workflow.includes('If HIGH_COUNT > 0 or ACTIONABLE_COUNT > 0'),
|
||||
'current_high=0 with current_actionable>0 must continue to replan/escalation instead of converging (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -273,17 +301,17 @@ describe('plan-review-convergence workflow: convergence loop (#2306)', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow spawns replan agent with --reviews flag', () => {
|
||||
test('workflow invokes inline replan with --reviews flag', () => {
|
||||
assert.ok(
|
||||
workflow.includes('--reviews'),
|
||||
'replan agent must pass --reviews so gsd-plan-phase incorporates review feedback'
|
||||
'inline replan must pass --reviews so gsd-plan-phase incorporates review feedback'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow passes --skip-research to replan agent (research already done)', () => {
|
||||
test('workflow passes --skip-research to inline replan (research already done)', () => {
|
||||
assert.ok(
|
||||
workflow.includes('--skip-research'),
|
||||
'replan agent must skip research — only initial planning needs research'
|
||||
'inline replan must skip research — only initial planning needs research'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -293,10 +321,10 @@ describe('plan-review-convergence workflow: convergence loop (#2306)', () => {
|
||||
describe('plan-review-convergence workflow: CYCLE_SUMMARY contract definition (#2306-v2)', () => {
|
||||
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
|
||||
|
||||
test('review agent prompt defines CYCLE_SUMMARY: current_high=<N> format', () => {
|
||||
test('review agent prompt defines CYCLE_SUMMARY current_high/current_actionable format', () => {
|
||||
assert.ok(
|
||||
workflow.includes('CYCLE_SUMMARY: current_high='),
|
||||
'review agent spawn prompt must define the CYCLE_SUMMARY: current_high=<N> output format (#2306-v2)'
|
||||
workflow.includes('CYCLE_SUMMARY: current_high=<N> current_actionable=<M>'),
|
||||
'review agent spawn prompt must define the CYCLE_SUMMARY current_high/current_actionable output format (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -320,6 +348,13 @@ describe('plan-review-convergence workflow: CYCLE_SUMMARY contract definition (#
|
||||
'review agent must provide ## Current HIGH Concerns section so escalation gate can show specific issues (#2306-v2)'
|
||||
);
|
||||
});
|
||||
|
||||
test('CYCLE_SUMMARY contract defines ACTIONABLE non-HIGH findings and requires their section', () => {
|
||||
assert.ok(
|
||||
workflow.includes('ACTIONABLE') && workflow.includes('Current Actionable Non-HIGH Concerns'),
|
||||
'review agent must define actionable non-HIGH findings and list current unresolved actionable items (#724)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Workflow: HIGH_LINES validation ──────────────────────────────────────
|
||||
@@ -335,6 +370,14 @@ describe('plan-review-convergence workflow: HIGH_LINES validation (#2306-v2)', (
|
||||
'workflow must warn when HIGH_COUNT > 0 but HIGH_LINES is empty (contract partially violated) (#2306-v2)'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow warns when ACTIONABLE_COUNT > 0 but actionable section is absent', () => {
|
||||
assert.ok(
|
||||
workflow.includes('ACTIONABLE_LINES') &&
|
||||
workflow.includes('Current Actionable Non-HIGH Concerns'),
|
||||
'workflow must warn when ACTIONABLE_COUNT > 0 but actionable details are empty (#724)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Workflow: stall detection ─────────────────────────────────────────────
|
||||
@@ -342,17 +385,17 @@ describe('plan-review-convergence workflow: HIGH_LINES validation (#2306-v2)', (
|
||||
describe('plan-review-convergence workflow: stall detection (#2306)', () => {
|
||||
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
|
||||
|
||||
test('workflow tracks previous HIGH count to detect stalls', () => {
|
||||
test('workflow tracks previous unresolved count to detect stalls', () => {
|
||||
assert.ok(
|
||||
workflow.includes('prev_high_count') || workflow.includes('prev_HIGH'),
|
||||
'workflow must track the previous cycle HIGH count for stall detection'
|
||||
workflow.includes('prev_unresolved_count'),
|
||||
'workflow must track the previous total unresolved review count for stall detection (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('workflow warns when HIGH count is not decreasing', () => {
|
||||
test('workflow warns when unresolved count is not decreasing', () => {
|
||||
assert.ok(
|
||||
workflow.includes('stall') || workflow.includes('Stall') || workflow.includes('not decreasing'),
|
||||
'workflow must warn user when HIGH count is not decreasing between cycles'
|
||||
'workflow must warn user when unresolved review count is not decreasing between cycles'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -397,20 +440,19 @@ describe('plan-review-convergence workflow: escalation gate (#2306)', () => {
|
||||
describe('plan-review-convergence workflow: stall detection behavioral (#2306)', () => {
|
||||
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
|
||||
|
||||
test('workflow surfaces stall warning when prev_high_count equals current HIGH_COUNT', () => {
|
||||
test('workflow surfaces stall warning when unresolved count stops decreasing', () => {
|
||||
assert.ok(
|
||||
workflow.includes('prev_high_count') || workflow.includes('prev_HIGH'),
|
||||
'workflow must track prev_high_count across cycles'
|
||||
workflow.includes('prev_unresolved_count'),
|
||||
'workflow must track prev_unresolved_count across cycles (#724)'
|
||||
);
|
||||
assert.ok(
|
||||
workflow.includes('HIGH_COUNT >= prev_high_count') ||
|
||||
workflow.includes('HIGH_COUNT >= prev_HIGH') ||
|
||||
workflow.includes('UNRESOLVED_COUNT >= prev_unresolved_count') ||
|
||||
workflow.includes('not decreasing'),
|
||||
'workflow must compare current HIGH count against previous to detect stall'
|
||||
'workflow must compare current unresolved count against previous to detect stall (#724)'
|
||||
);
|
||||
assert.ok(
|
||||
workflow.includes('stall') || workflow.includes('Stall') || workflow.includes('not decreasing'),
|
||||
'workflow must emit a stall warning when HIGH count is not decreasing'
|
||||
'workflow must emit a stall warning when unresolved review count is not decreasing'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -429,9 +471,10 @@ describe('plan-review-convergence workflow: --max-cycles 1 immediate escalation
|
||||
);
|
||||
assert.ok(
|
||||
workflow.includes('HIGH_COUNT > 0') ||
|
||||
workflow.includes('ACTIONABLE_COUNT > 0') ||
|
||||
workflow.includes('HIGH concerns remain') ||
|
||||
workflow.includes('Proceed anyway'),
|
||||
'escalation gate must be reachable when HIGH_COUNT > 0 after a single cycle'
|
||||
'escalation gate must be reachable when unresolved findings remain after a single cycle'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -461,11 +504,12 @@ describe('plan-review-convergence workflow: artifact verification (#2306)', () =
|
||||
describe('plan-review-convergence workflow: success criteria (#2306-v2)', () => {
|
||||
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
|
||||
|
||||
test('success criteria references CYCLE_SUMMARY parsing, not grep HIGHs', () => {
|
||||
test('success criteria references CYCLE_SUMMARY parsing, not grep findings', () => {
|
||||
const successBlock = workflow.slice(workflow.lastIndexOf('<success_criteria>'));
|
||||
assert.ok(
|
||||
successBlock.includes('CYCLE_SUMMARY') || successBlock.includes('parse'),
|
||||
'success_criteria must reflect that orchestrator parses CYCLE_SUMMARY, not greps REVIEWS.md (#2306-v2)'
|
||||
(successBlock.includes('CYCLE_SUMMARY') || successBlock.includes('parse')) &&
|
||||
successBlock.includes('actionable non-HIGH'),
|
||||
'success_criteria must reflect that orchestrator parses HIGH and actionable CYCLE_SUMMARY counts, not greps REVIEWS.md (#724)'
|
||||
);
|
||||
assert.ok(
|
||||
!successBlock.includes('grep HIGHs'),
|
||||
@@ -511,6 +555,70 @@ describe('plan-review-convergence CONFIGURATION.md documentation (#2306-v2)', ()
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Reviews-mode incorporation contract (#724) ────────────────────────────
|
||||
|
||||
describe('plan-review-convergence reviews-mode incorporation contract (#724)', () => {
|
||||
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
|
||||
const planPhase = fs.readFileSync(PLAN_PHASE_PATH, 'utf8');
|
||||
const plannerReviews = fs.readFileSync(PLANNER_REVIEWS_PATH, 'utf8');
|
||||
const planChecker = fs.readFileSync(PLAN_CHECKER_PATH, 'utf8');
|
||||
|
||||
test('workflow replans while actionable non-HIGH findings remain', () => {
|
||||
assert.ok(
|
||||
workflow.includes('Actionable MEDIUM/LOW findings must be incorporated into executable PLAN.md content'),
|
||||
'inline replan must route actionable non-HIGH findings back through plan-phase --reviews (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('plan-phase planner prompt says REVIEWS.md is feedback input, not the execution contract', () => {
|
||||
assert.ok(
|
||||
planPhase.includes('<review_incorporation_contract>') &&
|
||||
planPhase.includes('REVIEWS.md is feedback input') &&
|
||||
planPhase.includes('/gsd:execute-phase primarily consumes PLAN.md'),
|
||||
'planner prompt must explain that actionable review feedback must land in PLAN.md for execute-phase (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('plan-phase checker prompt reads REVIEWS.md in reviews mode and fails hidden actionable findings', () => {
|
||||
assert.ok(
|
||||
planPhase.includes('{reviews_path}') &&
|
||||
planPhase.includes('<review_incorporation_verification>') &&
|
||||
planPhase.includes('return `## ISSUES FOUND`'),
|
||||
'checker prompt must read REVIEWS.md and fail if actionable findings remain only there (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('planner reviews reference requires actionable findings to appear in PLAN.md or be deferred there', () => {
|
||||
assert.ok(
|
||||
plannerReviews.includes('/gsd:execute-phase primarily consumes PLAN.md') &&
|
||||
plannerReviews.includes('Every current actionable review finding') &&
|
||||
plannerReviews.includes('deferral/rejection rationale in that PLAN.md'),
|
||||
'planner reviews reference must keep REVIEWS.md from becoming a hidden execution contract (#724)'
|
||||
);
|
||||
});
|
||||
|
||||
test('gsd-plan-checker has a Review Incorporation dimension for reviews mode', () => {
|
||||
assert.ok(
|
||||
planChecker.includes('Review Incorporation') &&
|
||||
planChecker.includes('current_actionable=<M>') &&
|
||||
planChecker.includes('remains only in REVIEWS.md'),
|
||||
'plan checker must validate review incorporation when REVIEWS.md is present (#724)'
|
||||
);
|
||||
// The current_actionable=<M> reference must appear in a prohibition context,
|
||||
// not as an instruction to parse machine-readable fields from REVIEWS.md.
|
||||
// The CYCLE_SUMMARY line exists only in the convergence orchestrator's return message.
|
||||
assert.ok(
|
||||
planChecker.includes('Do NOT look for') || planChecker.includes('do NOT look for'),
|
||||
'plan checker must explicitly prohibit looking for CYCLE_SUMMARY/current_actionable=<M> in REVIEWS.md — those machine-readable fields are only on the orchestrator return message, never in the file'
|
||||
);
|
||||
assert.ok(
|
||||
planChecker.includes('CYCLE_SUMMARY') &&
|
||||
(planChecker.includes('Do NOT look for') || planChecker.includes('do NOT look for')),
|
||||
'CYCLE_SUMMARY must appear in plan-checker only as a prohibited pattern, not as a parsing instruction'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Local model reviewer support ────────────────────────────────────────
|
||||
|
||||
describe('plan-review-convergence local model reviewer flags (#2306-local)', () => {
|
||||
|
||||
Reference in New Issue
Block a user