From 352876ff0cffd597dbbe77a1e57a1b79b94c6e28 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 20 Jul 2026 10:55:28 -0400 Subject: [PATCH] fix(#2315): respect review.default_reviewers in bare convergence invocation (#2451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(#2315): regression test for review.default_reviewers precedence A bare /gsd-plan-review-convergence invocation (no reviewer flags) is supposed to let users configure a persistent reviewer lineup via review.default_reviewers and just run the loop. Instead, the orchestrator's argument parser silently discards that configuration and forces --codex on every no-flag invocation — with no warning that the configured reviewers were ignored. This commit adds a regression test that fails against the pre-fix workflow (the buggy unconditional --codex fallback is still present at this commit) and passes after the fix lands: - Structural: the workflow must NOT contain an unconditional 'if [ -z "$REVIEWER_FLAGS" ]; then REVIEWER_FLAGS="--codex"; fi' line before the workflow.plan_review_convergence config gate. - Structural: the workflow must query review.default_reviewers AFTER the config gate and document that empty REVIEWER_FLAGS lets gsd-review apply the default. - Behavioral: matrix across {configured, unset, empty-array, explicit-flag} invoking the actual deployed parse + resolution blocks with a stubbed gsd_run. Also updates two existing tests whose assertions the fix makes stale: - #2293 behavioral: endMarker was the buggy unconditional fallback line; the bare invocation assertion was 'run("5") === "--codex"'. Both flip post-fix (endMarker is now the last --all grep line; bare invocation returns empty from the parse block, default applied later in step 1.5). - command-default-claim: the pre-fix command documented '--codex (default if no reviewer specified)' which was the user-facing mirror of the bug. The assertion now requires the command to document the review.default_reviewers precedence. * fix(#2315): respect review.default_reviewers in bare convergence invocation Root cause: plan-review-convergence.md step 1 (Parse and Normalize Arguments) contained an unconditional fallback that set REVIEWER_FLAGS=\"--codex\" whenever no explicit reviewer flag was supplied. This value was then interpolated verbatim into the gsd-review args, so gsd-review saw --codex as an explicit flag (precedence rule 1) and never reached rule 3 (review.default_reviewers). The same path silently dropped any configured review.reviewer_instances (instances participate ONLY via review.default_reviewers per ADR-1517). Fix: - Remove the unconditional --codex fallback from step 1. - Add a config-gated resolution in step 1.5 (after CONVERGENCE_ENABLED check) that queries review.default_reviewers and either leaves REVIEWER_FLAGS empty (letting gsd-review apply its own rule-3 default) or falls back to --codex when no default is configured — preserving the pre-fix default for unconfigured users (AC3). - Replace the banner {REVIEWER_FLAGS} token with {REVIEWER_DISPLAY} so the startup banner reflects what will actually run (AC4), not a hardcoded value. The fix upholds the documented precedence contract (ADR-0011, ADR-0015) that the bug was actively violating. Explicit-flag invocations (--gemini, --all, etc.) are unaffected (AC5). References: #2315; ADR-0011 (review.default_reviewers precedence); ADR-0015 (autonomous cross-AI convergence); ADR-1517 (reviewer instances). * chore(#2315): bump plan-review-convergence.md baseline + changeset - Bump plan-review-convergence.md size baseline 23713 → 25536 (the new step-1.5 default-resolution block). - Add .changeset/plucky-yaks-roar.md documenting the user-visible change. * fix(#2315): restore /gsd: colon syntax + skip behavioral test when jq missing Two follow-ups to the #2315 fix discovered by gsd-test: 1. The fix commit accidentally regressed the slash-command namespace in the disabled-feature exit message: /gsd:plan-review-convergence (correct, from PR #3452) became /gsd-plan-review-convergence (retired dash syntax). The slash-command-namespace invariant test caught this. Restored the colon form. 2. The behavioral test exercises the deployed reviewer-resolution block, which pipes through jq. jq is a documented production dependency (review.md:244 "install jq if missing") and is present in every production deployment, but is NOT on PATH in the gsd-test linux-node{22,24} containers (same constraint as tests/opencode-review-reconstruction. property.test.cjs). Without jq, the printf|jq pipeline fails silently, the ||echo 0 fallback yields DEFAULT_REVIEWERS_COUNT=0, and the resolution falls through to the --codex branch — producing a false negative. Added a jqAvailable guard at module load (matching the existing pattern) and skip the behavioral test when jq is absent. The structural tests (no bash execution) still run and validate the fix. * chore(#2315): regenerate golden-install-parity fixtures Source changes to plan-review-convergence.md (workflow + skill mirror + command doc) changed the install-tree hashes. Regenerated via 'npm run gen:golden' after rebuilding gsd-core/bin/lib/install-engine.cjs ('npm run build:lib') — the on-disk lib was stale relative to src/install-engine.cts (isSymlinkedDestOptIn) and blocked fixture gen. * test(#2315): address review findings — strengthen structural tests + property test Code-review + security-review (isolated subagent passes) surfaced Low/Nit findings; this commit addresses the test-side findings: - Structural test 1 ("unconditional --codex one-liner") now asserts the buggy line is absent EVERYWHERE, not just before the config gate. The earlier assertion allowed a maintainer to re-add the line after the gate (passing the structural test) while the bug would still bite at runtime before the gate runs. - Structural test 4 ("banner uses REVIEWER_DISPLAY") now asserts the LITERAL banner placeholder "Reviewers: {REVIEWER_DISPLAY}" and forbids "Reviewers: {REVIEWER_FLAGS}". The earlier workflow.includes( "REVIEWER_DISPLAY") was satisfied by a comment mention. - New structural test for command/skill content parity: both files must document the review.default_reviewers precedence on the --codex flag (catches a manual edit to one that the gen:plugin-skills mirror misses). - Behavioral test stub now passes default_reviewers via env var ($GSD_TEST_DEFAULT_REVIEWERS) instead of inline-interpolating into a bash single-quoted string. Removes the (currently-safe) fragility where a future test input containing a single quote would close the bash quote and execute as bash under execFileSync. - New property test (fast-check, numRuns=25) for the JSON-classification contract: non-empty arrays of slugs -> empty REVIEWER_FLAGS; empty array / scalar JSON / malformed JSON -> --codex fallback. Locks the parser contract per CLAUDE.md mandate. * fix(#2315): address review findings — defensive jq-missing warning + banner cleanup Code-review surfaced two Low-severity workflow-side findings: - Defensive jq-missing warning: if jq is not on PATH in production (it is a documented dependency per review.md:244, but the dependency can be absent in degraded environments), the printf|jq pipeline fails silently to "0" and a user with review.default_reviewers configured gets --codex with no indication their configured default was unreadable. Added a command -v jq guard at the top of the resolution that falls back to --codex AND emits a stderr warning explaining the reason. This makes the failure diagnosable instead of silently reproducing the #2315 override. - Banner leading-space cleanup: REVIEWER_FLAGS accumulates with a leading space ("$REVIEWER_FLAGS --gemini" from ""), so the explicit-flag branch of REVIEWER_DISPLAY="$REVIEWER_FLAGS" rendered "Reviewers: --gemini" (double space). Pre-existing but worth fixing alongside the AC4 banner work. Strip one leading space with the ${VAR# } parameter expansion in the explicit-flag branch only (the configured-default and --codex branches already produce clean strings). * chore(#2315): bump plan-review-convergence.md baseline + regen golden fixtures The defensive jq-missing warning grew plan-review-convergence.md (25536 -> 26285 bytes). Bumps the per-file baseline snapshot and regenerates the golden-install-parity fixtures for the resulting install-tree hash changes. * chore(#2315): backfill pr:2451 in .changeset/plucky-yaks-roar.md --- .changeset/plucky-yaks-roar.md | 5 + commands/gsd/plan-review-convergence.md | 2 +- gsd-core/workflows/plan-review-convergence.md | 47 ++- skills/gsd-plan-review-convergence/SKILL.md | 2 +- .../golden-install-parity/antigravity.json | 4 +- .../golden-install-parity/augment.json | 6 +- .../golden-install-parity/claude-local.json | 4 +- .../golden-install-parity/claude.json | 4 +- .../fixtures/golden-install-parity/cline.json | 4 +- .../golden-install-parity/codebuddy.json | 6 +- .../fixtures/golden-install-parity/codex.json | 4 +- .../golden-install-parity/copilot.json | 4 +- .../golden-install-parity/cursor.json | 6 +- .../golden-install-parity/hermes.json | 4 +- .../fixtures/golden-install-parity/kilo.json | 6 +- .../fixtures/golden-install-parity/kimi.json | 4 +- .../golden-install-parity/opencode.json | 6 +- tests/fixtures/golden-install-parity/pi.json | 2 +- .../fixtures/golden-install-parity/qwen.json | 4 +- .../fixtures/golden-install-parity/trae.json | 4 +- .../golden-install-parity/windsurf.json | 2 +- .../fixtures/golden-install-parity/zcode.json | 6 +- tests/plan-review-convergence.test.cjs | 284 +++++++++++++++++- tests/workflow-size-baseline.json | 2 +- 24 files changed, 362 insertions(+), 60 deletions(-) create mode 100644 .changeset/plucky-yaks-roar.md diff --git a/.changeset/plucky-yaks-roar.md b/.changeset/plucky-yaks-roar.md new file mode 100644 index 000000000..21234eb73 --- /dev/null +++ b/.changeset/plucky-yaks-roar.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2451 +--- +**`/gsd-plan-review-convergence` no longer silently overrides configured reviewers with Codex** — a bare invocation (no reviewer flags) now respects `review.default_reviewers` (and, transitively, `review.reviewer_instances`) per ADR-0011/ADR-0015, instead of always injecting `--codex` and bypassing the configured default. Users without `review.default_reviewers` configured still get `--codex` as before. The startup banner now shows what will actually run. diff --git a/commands/gsd/plan-review-convergence.md b/commands/gsd/plan-review-convergence.md index 86a9b9086..4070620e1 100644 --- a/commands/gsd/plan-review-convergence.md +++ b/commands/gsd/plan-review-convergence.md @@ -40,7 +40,7 @@ Replaces gsd-plan-phase's internal gsd-plan-checker with external AI reviewers ( Phase number: extracted from $ARGUMENTS (required) **Flags:** -- `--codex` — Use Codex CLI as reviewer (default if no reviewer specified) +- `--codex` — Use Codex CLI as reviewer (default if no reviewer flag given AND `review.default_reviewers` is unset; otherwise `review.default_reviewers` wins per ADR-0011 — #2315) - `--gemini` — Use Gemini CLI as reviewer - `--agy` / `--antigravity` — Use Antigravity CLI as reviewer (successor to the discontinued Gemini CLI) - `--claude` — Use Claude CLI as reviewer (separate session) diff --git a/gsd-core/workflows/plan-review-convergence.md b/gsd-core/workflows/plan-review-convergence.md index 4e629db35..396032e1e 100644 --- a/gsd-core/workflows/plan-review-convergence.md +++ b/gsd-core/workflows/plan-review-convergence.md @@ -34,7 +34,9 @@ echo "$ARGUMENTS" | grep -q '\-\-ollama' && REVIEWER_FLAGS="$REVIEWER_FLAGS --ol echo "$ARGUMENTS" | grep -q '\-\-lm-studio' && REVIEWER_FLAGS="$REVIEWER_FLAGS --lm-studio" echo "$ARGUMENTS" | grep -q '\-\-llama-cpp' && REVIEWER_FLAGS="$REVIEWER_FLAGS --llama-cpp" echo "$ARGUMENTS" | grep -q '\-\-all' && REVIEWER_FLAGS="$REVIEWER_FLAGS --all" -if [ -z "$REVIEWER_FLAGS" ]; then REVIEWER_FLAGS="--codex"; fi +# #2315: do NOT default REVIEWER_FLAGS to --codex here. The default is resolved +# against review.default_reviewers in step 1.5 (after the config gate) so a bare +# invocation respects the configured reviewer lineup per ADR-0011 / ADR-0015. MAX_CYCLES=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}') if [ -z "$MAX_CYCLES" ]; then MAX_CYCLES=3; fi @@ -63,6 +65,47 @@ Enable it with: Then re-run: /gsd:plan-review-convergence {PHASE} ``` +```bash +# #2315: Resolve reviewer selection when no explicit flag was given. +# The pre-fix bug unconditionally set REVIEWER_FLAGS="--codex" in step 1, BEFORE +# the config gate — silently overriding any configured review.default_reviewers +# (and, transitively, review.reviewer_instances). gsd-review sees the injected +# --codex as an explicit flag (precedence rule 1) and never reaches rule 3 +# (review.default_reviewers). ADR-0011 and ADR-0015 both assume convergence +# respects review.default_reviewers on the no-flag path. +# +# After the fix: leave REVIEWER_FLAGS empty when default_reviewers is configured +# so gsd-review applies review.default_reviewers itself (rule 3). Only fall back +# to --codex when no default is configured, preserving the pre-fix default for +# unconfigured users (#2315 AC3). REVIEWER_DISPLAY mirrors the resolved value +# so the startup banner reflects what will actually run (#2315 AC4). +if [ -z "$REVIEWER_FLAGS" ]; then + DEFAULT_REVIEWERS_JSON=$(gsd_run query config-get review.default_reviewers 2>/dev/null || echo "") + if ! command -v jq >/dev/null 2>&1; then + # jq is a documented production dependency (review.md:244 — "install jq if + # missing"). If it is absent we cannot inspect the configured default, so + # fail safe with --codex and surface the reason rather than silently + # reproducing the #2315 override under degraded conditions. + echo "WARNING: jq not on PATH — cannot read review.default_reviewers; falling back to --codex (#2315)" >&2 + REVIEWER_FLAGS="--codex" + REVIEWER_DISPLAY="--codex (jq missing; cannot read review.default_reviewers)" + else + DEFAULT_REVIEWERS_COUNT=$(printf '%s' "$DEFAULT_REVIEWERS_JSON" | jq 'if type=="array" then length else 0 end' 2>/dev/null || echo 0) + if [ "${DEFAULT_REVIEWERS_COUNT:-0}" -gt 0 ] 2>/dev/null; then + : # leave REVIEWER_FLAGS empty — gsd-review applies review.default_reviewers itself + REVIEWER_DISPLAY="review.default_reviewers ($(printf '%s' "$DEFAULT_REVIEWERS_JSON" | jq -r 'join(", ")' 2>/dev/null))" + else + REVIEWER_FLAGS="--codex" + REVIEWER_DISPLAY="--codex (default; configure review.default_reviewers to change)" + fi + fi +else + # Strip the leading space accumulated by the parse block so the banner renders + # "Reviewers: --gemini" not "Reviewers: --gemini" (#2315 review nit). + REVIEWER_DISPLAY="${REVIEWER_FLAGS# }" +fi +``` + ## 2. Initialize ```bash @@ -91,7 +134,7 @@ Display startup banner: GSD ► PLAN CONVERGENCE — Phase {phase_number} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Reviewers: {REVIEWER_FLAGS} + Reviewers: {REVIEWER_DISPLAY} Max cycles: {MAX_CYCLES} ``` diff --git a/skills/gsd-plan-review-convergence/SKILL.md b/skills/gsd-plan-review-convergence/SKILL.md index c82fdb17b..58b5c878b 100644 --- a/skills/gsd-plan-review-convergence/SKILL.md +++ b/skills/gsd-plan-review-convergence/SKILL.md @@ -40,7 +40,7 @@ Replaces gsd-plan-phase's internal gsd-plan-checker with external AI reviewers ( Phase number: extracted from $ARGUMENTS (required) **Flags:** -- `--codex` — Use Codex CLI as reviewer (default if no reviewer specified) +- `--codex` — Use Codex CLI as reviewer (default if no reviewer flag given AND `review.default_reviewers` is unset; otherwise `review.default_reviewers` wins per ADR-0011 — #2315) - `--gemini` — Use Gemini CLI as reviewer - `--agy` / `--antigravity` — Use Antigravity CLI as reviewer (successor to the discontinued Gemini CLI) - `--claude` — Use Claude CLI as reviewer (separate session) diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index ff948068e..698884413 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -285,7 +285,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "ab0b22244c3389aa", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", - "gsd-core/workflows/plan-review-convergence.md": "2d71ce5e1d327b5f", + "gsd-core/workflows/plan-review-convergence.md": "b06546c4bf47e521", "gsd-core/workflows/plant-seed.md": "6cf726be6427a35a", "gsd-core/workflows/pr-branch.md": "dc5598ae8accdecd", "gsd-core/workflows/profile-user.md": "355af92ac285567f", @@ -408,7 +408,7 @@ "skills/gsd-pause-work/SKILL.md": "9d3cc6bd70b03df1", "skills/gsd-phase/SKILL.md": "00676bbea61410bf", "skills/gsd-plan-phase/SKILL.md": "03a8f71d4385a50d", - "skills/gsd-plan-review-convergence/SKILL.md": "e4d52b4473c6234b", + "skills/gsd-plan-review-convergence/SKILL.md": "79d264b132ca360d", "skills/gsd-pr-branch/SKILL.md": "5e050db73988f9a8", "skills/gsd-profile-user/SKILL.md": "10f2ff4be2e7d55f", "skills/gsd-progress/SKILL.md": "df0d06cf9d5963d0", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index babef7572..6b22a833b 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -80,7 +80,7 @@ "commands/gsd-pause-work.md": "4fb032f72238fe33", "commands/gsd-phase.md": "4920d15d779329eb", "commands/gsd-plan-phase.md": "6b05898d574f3666", - "commands/gsd-plan-review-convergence.md": "87def0355116452a", + "commands/gsd-plan-review-convergence.md": "4523045c95d4cd52", "commands/gsd-pr-branch.md": "e168fcd545d72d0d", "commands/gsd-profile-user.md": "ffd9c2feb4c69f11", "commands/gsd-progress.md": "0b4d5b73a6c5e958", @@ -356,7 +356,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", - "gsd-core/workflows/plan-review-convergence.md": "8b23bf9142cf7ec7", + "gsd-core/workflows/plan-review-convergence.md": "1c31d4905c1556c8", "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "14263db831230142", @@ -499,7 +499,7 @@ "skills/gsd-ns-workflow/skills/next/SKILL.md": "e7409245f1a0f9de", "skills/gsd-ns-workflow/skills/phase/SKILL.md": "fe5b26417ee466be", "skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "0dda686ce26d9171", - "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "8181db9842a79620", + "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "32b4344ec427afce", "skills/gsd-ns-workflow/skills/progress/SKILL.md": "68bda87136db9fb3", "skills/gsd-ns-workflow/skills/quick/SKILL.md": "014dec52d85dcb0e", "skills/gsd-ns-workflow/skills/spec-phase/SKILL.md": "2f88d0fc0c1abefe", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index 210bdc0f6..176d0a5fa 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -79,7 +79,7 @@ "commands/gsd-pause-work.md": "01dbaebfefacd252", "commands/gsd-phase.md": "e8c226d2694692a5", "commands/gsd-plan-phase.md": "7d14158eb377a248", - "commands/gsd-plan-review-convergence.md": "c24bc978d9d743dc", + "commands/gsd-plan-review-convergence.md": "cb6a8e074c0ab0cd", "commands/gsd-pr-branch.md": "382c23a6a644c0e4", "commands/gsd-profile-user.md": "adbc5b025b30e836", "commands/gsd-progress.md": "75a6dc71b74c54c2", @@ -355,7 +355,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "b810f9f2374e23a5", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", - "gsd-core/workflows/plan-review-convergence.md": "71ba7c20f15bd5f6", + "gsd-core/workflows/plan-review-convergence.md": "7db990f90908a5e4", "gsd-core/workflows/plant-seed.md": "fbe964fcdb244802", "gsd-core/workflows/pr-branch.md": "513f6cff722eff2d", "gsd-core/workflows/profile-user.md": "3b34dcb337d50f4b", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 82e264ef9..6ba17b956 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -284,7 +284,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", - "gsd-core/workflows/plan-review-convergence.md": "b80df3a60c2d3f7c", + "gsd-core/workflows/plan-review-convergence.md": "35fd32d839a2e4ba", "gsd-core/workflows/plant-seed.md": "af50e9f10d3cc6e1", "gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd", "gsd-core/workflows/profile-user.md": "ff3820a27731ceb8", @@ -406,7 +406,7 @@ "skills/gsd-pause-work/SKILL.md": "35e8a148e44f5361", "skills/gsd-phase/SKILL.md": "00be96e7ae36c6f0", "skills/gsd-plan-phase/SKILL.md": "f64953d1af7b0f34", - "skills/gsd-plan-review-convergence/SKILL.md": "a42e226d17b69108", + "skills/gsd-plan-review-convergence/SKILL.md": "53242a6abe90c405", "skills/gsd-pr-branch/SKILL.md": "c5e26f2c6dff1355", "skills/gsd-profile-user/SKILL.md": "894eb2850ecd2dde", "skills/gsd-progress/SKILL.md": "9b288501db99c7ea", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 5b1a5b73b..a87ac4b09 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -288,7 +288,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "4b0a2cb0f4f28179", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "090c31e22b1508fe", - "gsd-core/workflows/plan-review-convergence.md": "08de1c65837a8146", + "gsd-core/workflows/plan-review-convergence.md": "cb03bd703057f395", "gsd-core/workflows/plant-seed.md": "a2cdd513663226f1", "gsd-core/workflows/pr-branch.md": "9923878a4f6a2d91", "gsd-core/workflows/profile-user.md": "26f74db0a7fcd268", @@ -403,7 +403,7 @@ "skills/gsd-ns-workflow/skills/next/SKILL.md": "3856471d0f64bf09", "skills/gsd-ns-workflow/skills/phase/SKILL.md": "4e1363db6013a1e5", "skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "5796e8744314dcf5", - "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "d44c5d4d78cbee13", + "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "a7be67e1bd14b5fb", "skills/gsd-ns-workflow/skills/progress/SKILL.md": "493f467c22d55b6b", "skills/gsd-ns-workflow/skills/quick/SKILL.md": "605e596c680cbb1c", "skills/gsd-ns-workflow/skills/spec-phase/SKILL.md": "19726b83ca2c3d2d", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 0fd58adc1..2348c9015 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -80,7 +80,7 @@ "commands/gsd-pause-work.md": "6caa75a7c2b4dd2d", "commands/gsd-phase.md": "e3ca4958ea20a935", "commands/gsd-plan-phase.md": "ad988a271bf20e9c", - "commands/gsd-plan-review-convergence.md": "3d8837015534969f", + "commands/gsd-plan-review-convergence.md": "da885320edff822b", "commands/gsd-pr-branch.md": "f5be514b9f69eaf5", "commands/gsd-profile-user.md": "7a9289910719d828", "commands/gsd-progress.md": "80f75f428fc0949d", @@ -356,7 +356,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", - "gsd-core/workflows/plan-review-convergence.md": "8b23bf9142cf7ec7", + "gsd-core/workflows/plan-review-convergence.md": "1c31d4905c1556c8", "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "4fa910d15dea5695", @@ -478,7 +478,7 @@ "skills/gsd-pause-work/SKILL.md": "e2de20b7539e78d4", "skills/gsd-phase/SKILL.md": "f87211f779315ced", "skills/gsd-plan-phase/SKILL.md": "5f00d90224127bd5", - "skills/gsd-plan-review-convergence/SKILL.md": "bbad9a21eacdbfcf", + "skills/gsd-plan-review-convergence/SKILL.md": "1b1526f5d7a34306", "skills/gsd-pr-branch/SKILL.md": "87bb3306488565fb", "skills/gsd-profile-user/SKILL.md": "ee8e8547298006b8", "skills/gsd-progress/SKILL.md": "88c8229b673004e3", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 2f24fd60c..cb12f061b 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -44,7 +44,7 @@ ".agents/skills/gsd-pause-work/SKILL.md": "b379469eed78a196", ".agents/skills/gsd-phase/SKILL.md": "25477edc97a90c91", ".agents/skills/gsd-plan-phase/SKILL.md": "0a7a22d226bcec30", - ".agents/skills/gsd-plan-review-convergence/SKILL.md": "9a7ce3391c33139d", + ".agents/skills/gsd-plan-review-convergence/SKILL.md": "c7d8a4db99e4f4b9", ".agents/skills/gsd-pr-branch/SKILL.md": "6901da15e321913e", ".agents/skills/gsd-profile-user/SKILL.md": "6259fabfb6afe7be", ".agents/skills/gsd-progress/SKILL.md": "85d76286162b9189", @@ -391,7 +391,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "d838b87563feedf6", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "f10975692cbd036e", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "f5edc589cab52a7b", - "gsd-core/workflows/plan-review-convergence.md": "cfba777405d34c52", + "gsd-core/workflows/plan-review-convergence.md": "6f904fb1befa8991", "gsd-core/workflows/plant-seed.md": "5b07de07e4593281", "gsd-core/workflows/pr-branch.md": "d13e1cc81de40896", "gsd-core/workflows/profile-user.md": "05828c8cc61ef384", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index e9c434d70..d93a2599a 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -286,7 +286,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "bb052483744f0a6d", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", - "gsd-core/workflows/plan-review-convergence.md": "a258ddcf713699ee", + "gsd-core/workflows/plan-review-convergence.md": "0afd8eb4b0689bc9", "gsd-core/workflows/plant-seed.md": "21e461cd39e5181b", "gsd-core/workflows/pr-branch.md": "2833905f119b5722", "gsd-core/workflows/profile-user.md": "5cc032206c99ef71", @@ -381,7 +381,7 @@ "skills/gsd-pause-work/SKILL.md": "4f0caa008a8001ff", "skills/gsd-phase/SKILL.md": "d38c7f9b1d0d2360", "skills/gsd-plan-phase/SKILL.md": "d1ba21e500bf62a8", - "skills/gsd-plan-review-convergence/SKILL.md": "d6b850077d62d723", + "skills/gsd-plan-review-convergence/SKILL.md": "29e903e25047ebd1", "skills/gsd-pr-branch/SKILL.md": "9cd9740db385d95a", "skills/gsd-profile-user/SKILL.md": "052a8e17ecda40f1", "skills/gsd-progress/SKILL.md": "28b94a19f2bccb78", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index a121fb627..de47f3489 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -80,7 +80,7 @@ "commands/gsd-pause-work.md": "59630f05f95fff68", "commands/gsd-phase.md": "9a073dcd0f934f90", "commands/gsd-plan-phase.md": "d9bdd3726e9c3dff", - "commands/gsd-plan-review-convergence.md": "04d89318e0e23a1b", + "commands/gsd-plan-review-convergence.md": "ea3bec0ae494ecc8", "commands/gsd-pr-branch.md": "ef2eedb0ed4295da", "commands/gsd-profile-user.md": "0e99de36619c3b7d", "commands/gsd-progress.md": "44a7c784dc98f735", @@ -356,7 +356,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "3bed01c3c906ac52", - "gsd-core/workflows/plan-review-convergence.md": "86e18a63ab7a0a6f", + "gsd-core/workflows/plan-review-convergence.md": "68139431c21ef46d", "gsd-core/workflows/plant-seed.md": "7bccd151ce7b69f2", "gsd-core/workflows/pr-branch.md": "c67d90c65da47168", "gsd-core/workflows/profile-user.md": "8c943983241260b5", @@ -456,7 +456,7 @@ "skills/gsd-pause-work/SKILL.md": "7d310e7329b7ae7d", "skills/gsd-phase/SKILL.md": "49fca93e5f2ccc70", "skills/gsd-plan-phase/SKILL.md": "c5e1a4ab2ede399a", - "skills/gsd-plan-review-convergence/SKILL.md": "c2793cacf3f40d70", + "skills/gsd-plan-review-convergence/SKILL.md": "3e13dd1bf82f74c0", "skills/gsd-pr-branch/SKILL.md": "a8f8838a6aa5e1dd", "skills/gsd-profile-user/SKILL.md": "3be8d50399836ef2", "skills/gsd-progress/SKILL.md": "1245ea9e6af6ae25", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 25c1c87dd..6e7dc71ec 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -285,7 +285,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "9607e6d03e93c1c2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "62f8e4f3b475fe5f", - "gsd-core/workflows/plan-review-convergence.md": "545fa880b6c3501f", + "gsd-core/workflows/plan-review-convergence.md": "d9c282754c05eb81", "gsd-core/workflows/plant-seed.md": "f862f77fca983749", "gsd-core/workflows/pr-branch.md": "ecabd55e4eabf229", "gsd-core/workflows/profile-user.md": "de5030437226cf2c", @@ -429,7 +429,7 @@ "skills/gsd/gsd-ns-workflow/skills/next/SKILL.md": "1d30cf6061d15168", "skills/gsd/gsd-ns-workflow/skills/phase/SKILL.md": "610bd06d71198849", "skills/gsd/gsd-ns-workflow/skills/plan-phase/SKILL.md": "e564bd88707a0e69", - "skills/gsd/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "87e5f3a0c1f2a6af", + "skills/gsd/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "9f67e59967b3f5eb", "skills/gsd/gsd-ns-workflow/skills/progress/SKILL.md": "6ec0e4715dde7ee2", "skills/gsd/gsd-ns-workflow/skills/quick/SKILL.md": "65e377345e9c49a8", "skills/gsd/gsd-ns-workflow/skills/spec-phase/SKILL.md": "848cec32c341713c", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 138a004d7..93199cd73 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -80,7 +80,7 @@ "command/gsd-pause-work.md": "04e993b1c9f8322b", "command/gsd-phase.md": "8f0e98dc6c223229", "command/gsd-plan-phase.md": "8c39408407b6bf9f", - "command/gsd-plan-review-convergence.md": "45060e2c490fcae7", + "command/gsd-plan-review-convergence.md": "b957f86414b6c73f", "command/gsd-pr-branch.md": "68e724607de3c480", "command/gsd-profile-user.md": "9704158b2d79cad2", "command/gsd-progress.md": "cba4805469307416", @@ -356,7 +356,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "f10975692cbd036e", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", - "gsd-core/workflows/plan-review-convergence.md": "396dd0dbc4d7e405", + "gsd-core/workflows/plan-review-convergence.md": "232c93a9aed224fe", "gsd-core/workflows/plant-seed.md": "ffa5774304243649", "gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd", "gsd-core/workflows/profile-user.md": "203ebe3f8f3876a8", @@ -480,7 +480,7 @@ "skills/gsd-pause-work/SKILL.md": "34366b18a392a717", "skills/gsd-phase/SKILL.md": "64a241d4f8665aa2", "skills/gsd-plan-phase/SKILL.md": "c95d9376440c4725", - "skills/gsd-plan-review-convergence/SKILL.md": "fb7355db56f5eaec", + "skills/gsd-plan-review-convergence/SKILL.md": "db311b1dbb0024d1", "skills/gsd-pr-branch/SKILL.md": "a80da6aa95efc50d", "skills/gsd-profile-user/SKILL.md": "4ac2c5ea45d17a9d", "skills/gsd-progress/SKILL.md": "493f467c22d55b6b", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index b4ae2dd8d..bbe9a69e2 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -349,7 +349,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", - "gsd-core/workflows/plan-review-convergence.md": "8b23bf9142cf7ec7", + "gsd-core/workflows/plan-review-convergence.md": "1c31d4905c1556c8", "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "5abfae83739fa978", @@ -443,7 +443,7 @@ "skills/gsd-pause-work/SKILL.md": "95017c70ae9dca0d", "skills/gsd-phase/SKILL.md": "31578c329cc2583e", "skills/gsd-plan-phase/SKILL.md": "83362394f648deeb", - "skills/gsd-plan-review-convergence/SKILL.md": "ab63cb338105d27f", + "skills/gsd-plan-review-convergence/SKILL.md": "fcac3c24e566438e", "skills/gsd-pr-branch/SKILL.md": "67f468db29ff2cf1", "skills/gsd-profile-user/SKILL.md": "19a1d3aba57f6c7c", "skills/gsd-progress/SKILL.md": "cc46a92b92b989ce", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 835a78da5..508f66cb6 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -80,7 +80,7 @@ "commands/gsd-pause-work.md": "bb5bf91a2e3e480e", "commands/gsd-phase.md": "6bcda1539f949d5a", "commands/gsd-plan-phase.md": "4e19ddc0ca22fbf6", - "commands/gsd-plan-review-convergence.md": "88c4284aa8b3dd7e", + "commands/gsd-plan-review-convergence.md": "348d02775fd62977", "commands/gsd-pr-branch.md": "31fca4f1d6c4ee62", "commands/gsd-profile-user.md": "725c14ae7203b5b6", "commands/gsd-progress.md": "001f2754c427a0a9", @@ -356,7 +356,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "3a09141de7f3dedb", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261", - "gsd-core/workflows/plan-review-convergence.md": "4d51320e4e3caddc", + "gsd-core/workflows/plan-review-convergence.md": "f91c297dce1253a2", "gsd-core/workflows/plant-seed.md": "b8dad652e31c2318", "gsd-core/workflows/pr-branch.md": "929b7cb0c99c7b9e", "gsd-core/workflows/profile-user.md": "248d59a31948e0ed", @@ -480,7 +480,7 @@ "skills/gsd-pause-work/SKILL.md": "c7e9ba4f242c4648", "skills/gsd-phase/SKILL.md": "ee78c0c56c814d84", "skills/gsd-plan-phase/SKILL.md": "4b65f0a9d7d42ec7", - "skills/gsd-plan-review-convergence/SKILL.md": "78820e21dd05d5d4", + "skills/gsd-plan-review-convergence/SKILL.md": "8338cb7e018e3713", "skills/gsd-pr-branch/SKILL.md": "8fa5a8fa217fe913", "skills/gsd-profile-user/SKILL.md": "3e6155a64523d59f", "skills/gsd-progress/SKILL.md": "1dfd9327510e500c", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index 71a5230cb..8e9a3045f 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -252,7 +252,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", - "gsd-core/workflows/plan-review-convergence.md": "8b23bf9142cf7ec7", + "gsd-core/workflows/plan-review-convergence.md": "1c31d4905c1556c8", "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "1bac7f69142801ef", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 9c09d3f1f..756d4866e 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -285,7 +285,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "c22ff5ea46de665a", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "d050d8d551ed1756", - "gsd-core/workflows/plan-review-convergence.md": "f30e8ce168471fcd", + "gsd-core/workflows/plan-review-convergence.md": "7b887180677f11ac", "gsd-core/workflows/plant-seed.md": "0a92ba12993ac261", "gsd-core/workflows/pr-branch.md": "cef0f65b16d500b4", "gsd-core/workflows/profile-user.md": "263c0693563d98da", @@ -428,7 +428,7 @@ "skills/gsd-ns-workflow/skills/next/SKILL.md": "13e394affe675498", "skills/gsd-ns-workflow/skills/phase/SKILL.md": "1ed640e5f06c7be6", "skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "ff46b5450c9c4313", - "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "a42e226d17b69108", + "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "53242a6abe90c405", "skills/gsd-ns-workflow/skills/progress/SKILL.md": "943538c4ac6bde19", "skills/gsd-ns-workflow/skills/quick/SKILL.md": "bd5e4cb79bc41611", "skills/gsd-ns-workflow/skills/spec-phase/SKILL.md": "7e4dfa2070b7d9d1", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 395c98df5..3a073eea8 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -285,7 +285,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "778b73a8db6f7c32", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "619946c879f33b9d", - "gsd-core/workflows/plan-review-convergence.md": "7a527920ad606595", + "gsd-core/workflows/plan-review-convergence.md": "610d816af89b1b16", "gsd-core/workflows/plant-seed.md": "856ad565b2eb0c47", "gsd-core/workflows/pr-branch.md": "79fd55b88ea2db9c", "gsd-core/workflows/profile-user.md": "672821e6b1266645", @@ -400,7 +400,7 @@ "skills/gsd-ns-workflow/skills/next/SKILL.md": "faadd9e2817e7324", "skills/gsd-ns-workflow/skills/phase/SKILL.md": "df3efcd61f7cc796", "skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "31962f06bc124c89", - "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "5c4475320977818f", + "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "b27aad9196476875", "skills/gsd-ns-workflow/skills/progress/SKILL.md": "37a37d2cdfba46ea", "skills/gsd-ns-workflow/skills/quick/SKILL.md": "6fd1b96274b23a9f", "skills/gsd-ns-workflow/skills/spec-phase/SKILL.md": "9291f835233fa8da", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index fcb707c10..16e45ac47 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -285,7 +285,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "e06ccd4d4c0703fb", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "80b1ba493a9a967f", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "3fed4740a91d0443", - "gsd-core/workflows/plan-review-convergence.md": "a7fde3dbf83628cc", + "gsd-core/workflows/plan-review-convergence.md": "27dcce3dbca22e3d", "gsd-core/workflows/plant-seed.md": "6cf61f540cdbd8b9", "gsd-core/workflows/pr-branch.md": "acd59f915d018ad4", "gsd-core/workflows/profile-user.md": "c4313672b81b5bcd", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 0f1b6104b..292e518ab 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -80,7 +80,7 @@ "commands/gsd-pause-work.md": "40a953fcddbedb5d", "commands/gsd-phase.md": "5dd3d40e3461a973", "commands/gsd-plan-phase.md": "d6ba79bf0ede5bcd", - "commands/gsd-plan-review-convergence.md": "551597f2add981cb", + "commands/gsd-plan-review-convergence.md": "f3aab422e0708dae", "commands/gsd-pr-branch.md": "ab1fcffe92129061", "commands/gsd-profile-user.md": "37c9ef202669bfd3", "commands/gsd-progress.md": "13d7e5ba8acc2605", @@ -356,7 +356,7 @@ "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072", "gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2", "gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5", - "gsd-core/workflows/plan-review-convergence.md": "8b23bf9142cf7ec7", + "gsd-core/workflows/plan-review-convergence.md": "1c31d4905c1556c8", "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "e23bea0a69c0bb4b", @@ -471,7 +471,7 @@ "skills/gsd-ns-workflow/skills/next/SKILL.md": "63659cd48a0276f9", "skills/gsd-ns-workflow/skills/phase/SKILL.md": "b377b03d13db8573", "skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": "4564f415845d6c15", - "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "12d1696eaaf60043", + "skills/gsd-ns-workflow/skills/plan-review-convergence/SKILL.md": "84af9aa192ec349f", "skills/gsd-ns-workflow/skills/progress/SKILL.md": "fb24eab4a0e5f6a3", "skills/gsd-ns-workflow/skills/quick/SKILL.md": "48629fef2b9ac5b0", "skills/gsd-ns-workflow/skills/spec-phase/SKILL.md": "052cfb21eb0ee3d4", diff --git a/tests/plan-review-convergence.test.cjs b/tests/plan-review-convergence.test.cjs index 6ed6d25c8..df611f4af 100644 --- a/tests/plan-review-convergence.test.cjs +++ b/tests/plan-review-convergence.test.cjs @@ -31,6 +31,7 @@ const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); +const { execFileSync } = require('node:child_process'); 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'); @@ -39,6 +40,15 @@ const PLAN_PHASE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'pla 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'); +// #2315: the workflow's reviewer-resolution block pipes through `jq`, which is +// a documented production dependency (review.md:244 "install jq if missing") +// but is NOT present in every test container (gsd-test's linux-node{22,24} +// images lack it; see tests/opencode-review-reconstruction.property.test.cjs +// for the same skip pattern). Behavioral tests that exercise the deployed jq +// pipeline skip when jq is absent; structural tests still run. +let jqAvailable = false; +try { execFileSync('jq', ['--version'], { stdio: 'ignore', timeout: 10000, killSignal: 'SIGKILL' }); jqAvailable = true; } catch { /* no jq on PATH */ } + // ─── Command source ──────────────────────────────────────────────────────── describe('plan-review-convergence command source (#2306)', () => { @@ -123,12 +133,16 @@ describe('plan-review-convergence command source (#2306)', () => { ); }); - test('--codex is the default reviewer when no flag is specified', () => { + test('--codex is the default reviewer when no flag is given AND review.default_reviewers is unset (#2315)', () => { + // #2315: a bare invocation now respects review.default_reviewers per + // ADR-0011. The command must document that --codex is the default ONLY + // when review.default_reviewers is unset; otherwise the configured default + // wins. The pre-fix claim ("default if no reviewer specified") was the + // user-facing mirror of the #2315 bug. assert.ok( - command.includes('default if no reviewer specified') || - command.includes('default: --codex') || - command.includes('(default if no reviewer specified)'), - '--codex must be documented as the default reviewer' + command.includes('default if no reviewer flag given') && + command.includes('review.default_reviewers'), + 'command must document that --codex is the default ONLY when review.default_reviewers is unset (#2315)' ); }); @@ -159,17 +173,25 @@ describe('plan-review-convergence: --agy/--antigravity reviewer whitelist (#2293 assert.ok(skill.includes('--agy'), 'generated SKILL.md must document --agy (regenerate via gen:plugin-skills)'); }); - // Behavioral: execute the ACTUAL deployed REVIEWER_FLAGS accumulation block and - // assert --antigravity passes through instead of being dropped to the --codex - // default. POSIX-only — the block is /bin/sh-style grep pipework; skip on Windows - // where a bash shim is not guaranteed on PATH. - test('[behavioral] the deployed extraction passes --antigravity through (not the --codex fallback)', (t) => { + // Behavioral: execute the ACTUAL deployed REVIEWER_FLAGS parse block and + // assert --antigravity passes through instead of being dropped. POSIX-only — + // the block is /bin/sh-style grep pipework; skip on Windows where a bash + // shim is not guaranteed on PATH. + // + // #2315: the parse block no longer applies a --codex default. The endMarker + // was previously the unconditional `if [ -z "$REVIEWER_FLAGS" ]; then + // REVIEWER_FLAGS="--codex"; fi` line; that line was the #2315 bug and is now + // gone. Default resolution moved to step 1.5 after the config gate (see the + // #2315 describe block below). The bare-invocation assertion now expects an + // empty REVIEWER_FLAGS from the parse block — the default is applied later, + // respecting review.default_reviewers. + test('[behavioral] the deployed parse block passes --antigravity through (parse block no longer applies a --codex default — #2315)', (t) => { if (process.platform === 'win32') { t.skip('POSIX shell extraction; not run on Windows'); return; } const { execFileSync } = require('node:child_process'); const startIdx = workflow.indexOf('REVIEWER_FLAGS=""'); - const endMarker = 'if [ -z "$REVIEWER_FLAGS" ]; then REVIEWER_FLAGS="--codex"; fi'; + const endMarker = "echo \"$ARGUMENTS\" | grep -q '\\-\\-all' && REVIEWER_FLAGS=\"$REVIEWER_FLAGS --all\""; const endIdx = workflow.indexOf(endMarker); - assert.ok(startIdx !== -1 && endIdx !== -1, 'the REVIEWER_FLAGS accumulation block must exist in the workflow'); + assert.ok(startIdx !== -1 && endIdx !== -1, 'the REVIEWER_FLAGS parse block must exist in the workflow'); const block = workflow.slice(startIdx, endIdx + endMarker.length) + '\nprintf "%s" "$REVIEWER_FLAGS"'; const run = (args) => execFileSync('bash', ['-c', block], { env: { ...process.env, ARGUMENTS: args }, @@ -179,12 +201,15 @@ describe('plan-review-convergence: --agy/--antigravity reviewer whitelist (#2293 const agy = run('5 --antigravity'); assert.ok(agy.split(/\s+/).includes('--antigravity'), `--antigravity must pass through, got: "${agy}"`); - assert.notStrictEqual(agy, '--codex', 'must NOT collapse to the --codex-only fallback when --antigravity is given'); + // The parse block must NOT inject --codex when an explicit flag is present. + assert.notStrictEqual(agy, '--codex', 'must NOT produce --codex-only when --antigravity is given'); assert.ok(run('5 --agy').split(/\s+/).includes('--agy'), '--agy short form must pass through'); - // Regressions: default + existing flags unchanged. - assert.strictEqual(run('5'), '--codex', 'no reviewer flag → --codex default preserved'); + // #2315 regression: the parse block no longer applies a default. The bare + // invocation (no flag) MUST yield an empty REVIEWER_FLAGS here; the default + // is resolved later in step 1.5 against review.default_reviewers. + assert.strictEqual(run('5'), '', 'no reviewer flag → empty REVIEWER_FLAGS from parse (default applied in step 1.5 per #2315)'); const mixed = run('5 --codex --gemini'); assert.ok(mixed.includes('--codex') && mixed.includes('--gemini'), 'existing flags still recognized'); // --agy must not be spuriously matched by an unrelated flag (independence). @@ -192,6 +217,235 @@ describe('plan-review-convergence: --agy/--antigravity reviewer whitelist (#2293 }); }); +// ─── #2315: bare invocation respects review.default_reviewers ────────────── + +describe('plan-review-convergence: #2315 respects review.default_reviewers (no-flag default)', () => { + const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8'); + const command = fs.readFileSync(COMMAND_PATH, 'utf8'); + const SKILL_PATH = path.join(__dirname, '..', 'skills', 'gsd-plan-review-convergence', 'SKILL.md'); + const skill = fs.readFileSync(SKILL_PATH, 'utf8'); + + // Pre-fix #2315: the workflow unconditionally set REVIEWER_FLAGS="--codex" in + // step 1 (line 37) BEFORE the workflow.plan_review_convergence config gate. + // gsd-review sees the injected --codex as an explicit flag (precedence rule 1) + // and never reaches rule 3 (review.default_reviewers), silently overriding + // any configured default — a violation of ADR-0011 and ADR-0015. + // + // The buggy one-liner must not appear ANYWHERE in the workflow — the post-fix + // resolution lives in step 1.5 as a config-gated if/else/fi block, never as + // the bare one-liner. (Earlier versions of this test only asserted the line + // was not BEFORE the gate, which still allowed a re-introduction after the + // gate; the assertion is now unconditional per review.) + test('workflow does NOT contain the unconditional REVIEWER_FLAGS=--codex one-liner anywhere', () => { + const buggyLine = 'if [ -z "$REVIEWER_FLAGS" ]; then REVIEWER_FLAGS="--codex"; fi'; + assert.strictEqual( + workflow.indexOf(buggyLine), + -1, + 'the unconditional --codex one-liner must not appear anywhere in the workflow (#2315); ' + + 'default resolution is conditional on review.default_reviewers in step 1.5 (if/else/fi block).' + ); + }); + + test('workflow resolves REVIEWER_FLAGS against review.default_reviewers AFTER the config gate', () => { + const resolutionIdx = workflow.indexOf('gsd_run query config-get review.default_reviewers'); + const configGateIdx = workflow.indexOf('CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence'); + assert.ok(resolutionIdx !== -1, 'workflow must query review.default_reviewers to resolve the no-flag default (#2315)'); + assert.ok( + resolutionIdx > configGateIdx, + 'review.default_reviewers resolution must come AFTER the config gate (only runs when convergence is enabled)' + ); + }); + + test('workflow documents that empty REVIEWER_FLAGS lets gsd-review apply review.default_reviewers', () => { + // After the fix, an empty REVIEWER_FLAGS is INTENTIONAL — it signals "let + // gsd-review apply its own precedence (rule 3: review.default_reviewers)". + // A comment must document this so a future maintainer does not re-add the + // unconditional --codex fallback and resurrect the #2315 bug. + assert.ok( + /gsd-review applies.*review\.default_reviewers|review\.default_reviewers.*gsd-review applies/i.test(workflow), + 'workflow must document that empty REVIEWER_FLAGS lets gsd-review apply review.default_reviewers (prevent #2315 regression)' + ); + }); + + test('startup banner uses REVIEWER_DISPLAY (not raw REVIEWER_FLAGS) so users see what will actually run', () => { + // AC4 of #2315: banner must reflect actual reviewers, not a hardcoded value. + // When REVIEWER_FLAGS is empty (because default_reviewers is configured), + // the banner must show the resolved default — not an empty string and not + // a misleading "--codex". Assert the literal banner placeholder so a + // maintainer cannot satisfy this by defining REVIEWER_DISPLAY in a comment + // while leaving the banner pointing at REVIEWER_FLAGS. + assert.ok( + workflow.includes('Reviewers: {REVIEWER_DISPLAY}'), + 'startup banner must use the {REVIEWER_DISPLAY} placeholder, not {REVIEWER_FLAGS} (#2315 AC4)' + ); + assert.ok( + !/\bReviewers:\s*\{REVIEWER_FLAGS\}/.test(workflow), + 'startup banner must NOT reference {REVIEWER_FLAGS} directly (#2315 AC4)' + ); + }); + + test('command and skill doc both document the review.default_reviewers precedence (content parity)', () => { + // The #2315 fix updated the --codex flag description in BOTH the command + // (commands/gsd/plan-review-convergence.md) and the generated skill mirror + // (skills/gsd-plan-review-convergence/SKILL.md). The two are kept in sync + // by `npm run gen:plugin-skills`; this assertion catches a manual edit to + // one that the other doesn't mirror. Both must mention review.default_reviewers + // alongside the --codex default claim. + const expected = 'review.default_reviewers'; + assert.ok( + command.includes(expected), + `command file must document the review.default_reviewers precedence on the --codex flag (#2315)` + ); + assert.ok( + skill.includes(expected), + `skill file must mirror the command file's review.default_reviewers precedence (#2315)` + ); + }); + + // Behavioral: extract the parse block + the post-config-gate resolution block + // and execute them with a stubbed gsd_run. Proves the matrix: + // - bare + default_reviewers configured → empty REVIEWER_FLAGS (gsd-review applies default) + // - bare + default_reviewers unset → --codex fallback (pre-fix behavior preserved) + // - bare + empty-array default → --codex fallback (defensive — schema would reject) + // - explicit --gemini + default set → --gemini wins (explicit flags unaffected, #2315 AC5) + test('[behavioral] no-flag invocation resolves to default_reviewers when configured, --codex otherwise', (t) => { + if (process.platform === 'win32') { t.skip('POSIX shell extraction; not run on Windows'); return; } + if (!jqAvailable) { t.skip('jq not on PATH — workflow resolution block pipes through jq (production dependency, review.md:244); structural tests above still validate the fix'); return; } + const { execFileSync } = require('node:child_process'); + + // Parse block: from REVIEWER_FLAGS="" to the last grep line (--all). + const parseStart = workflow.indexOf('REVIEWER_FLAGS=""'); + const parseEndMarker = "echo \"$ARGUMENTS\" | grep -q '\\-\\-all' && REVIEWER_FLAGS=\"$REVIEWER_FLAGS --all\""; + const parseEnd = workflow.indexOf(parseEndMarker); + assert.ok(parseStart !== -1 && parseEnd !== -1, 'parse block must exist'); + const parseBlock = workflow.slice(parseStart, parseEnd + parseEndMarker.length); + + // Resolution block: the `if [ -z "$REVIEWER_FLAGS" ]; then` that appears + // AFTER the config gate (CONVERGENCE_ENABLED=). This is the #2315 fix. + // Extract to the closing fence of the enclosing ```bash block — the block + // contains a nested if/else/fi, so a naive "first \nfi\n" match would stop + // at the inner fi and yield unbalanced bash. + const configGateIdx = workflow.indexOf('CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence'); + const resolutionStart = workflow.indexOf('if [ -z "$REVIEWER_FLAGS" ]; then', configGateIdx); + assert.ok(resolutionStart !== -1, 'post-config-gate REVIEWER_FLAGS resolution block must exist (#2315)'); + const closingFence = workflow.indexOf('\n```\n', resolutionStart); + assert.ok(closingFence !== -1, 'could not locate closing fence of resolution block'); + const resolutionBlock = workflow.slice(resolutionStart, closingFence); + + const run = ({ args, defaultReviewers }) => { + // Stub gsd_run: only `query config-get review.default_reviewers` is exercised. + // Empty/default → unset key (gsd_run returns nothing → empty stdout). + // + // The default_reviewers value is passed via env var ($GSD_TEST_DEFAULT_REVIEWERS) + // rather than inline-interpolated into the bash script. This avoids a quoting + // fragility: a future test input containing a single quote would otherwise + // break the bash single-quoted string and execute as bash. With env-var + // handoff, the value never crosses an interpreting shell context. + const stub = `gsd_run() { case "$*" in *"config-get review.default_reviewers"*) printf '%s' "$GSD_TEST_DEFAULT_REVIEWERS";; *) return 0;; esac; }`; + const script = `${stub}\n${parseBlock}\n${resolutionBlock}\nprintf 'REVIEWER_FLAGS=[%s] REVIEWER_DISPLAY=[%s]' "$REVIEWER_FLAGS" "$REVIEWER_DISPLAY"`; + return execFileSync('bash', ['-c', script], { + env: { ...process.env, ARGUMENTS: args, GSD_TEST_DEFAULT_REVIEWERS: defaultReviewers ?? '' }, + encoding: 'utf8', + timeout: 5000, + }); + }; + + // AC1: bare invocation with default_reviewers configured → empty REVIEWER_FLAGS + // (gsd-review applies configured default per its rule 3) and banner shows the resolved default. + let r = run({ args: '5', defaultReviewers: '["gemini","claude"]' }); + assert.ok(/REVIEWER_FLAGS=\[\s*\]/.test(r), `configured default → REVIEWER_FLAGS empty, got: "${r}"`); + assert.ok(/review\.default_reviewers \(gemini, claude\)/.test(r), `banner shows configured default, got: "${r}"`); + + // AC2: reviewer instances participate via default_reviewers — same path. + r = run({ args: '5', defaultReviewers: '["opencode-deepseek","opencode-mimo"]' }); + assert.ok(/REVIEWER_FLAGS=\[\s*\]/.test(r), `instance default → REVIEWER_FLAGS empty, got: "${r}"`); + + // AC3: bare invocation with default_reviewers unset → --codex fallback preserved. + r = run({ args: '5', defaultReviewers: '' }); + assert.ok(/REVIEWER_FLAGS=\[--codex\]/.test(r), `unset default → --codex fallback, got: "${r}"`); + + // AC3 defensive: empty-array default → --codex fallback (schema rejects this, but be safe). + r = run({ args: '5', defaultReviewers: '[]' }); + assert.ok(/REVIEWER_FLAGS=\[--codex\]/.test(r), `empty-array default → --codex fallback, got: "${r}"`); + + // AC5 (out of scope but must not regress): explicit --gemini overrides configured default. + r = run({ args: '5 --gemini', defaultReviewers: '["claude"]' }); + assert.ok(/REVIEWER_FLAGS=\[.*--gemini.*\]/.test(r), `explicit flag wins over configured default, got: "${r}"`); + }); + + // Property test — CLAUDE.md mandates at least one fast-check (fc) property + // test for parsers and bijective contracts. The resolution block parses the + // configured review.default_reviewers JSON and classifies it into one of two + // outcomes: "non-empty array → delegate to gsd-review" (REVIEWER_FLAGS empty) + // or "anything else → fall back to --codex" (defensive — schema rejects most + // of these at config-set time, but corruption/edge cases must not crash or + // misclassify). Locks the contract so a future change can't subtly narrow or + // widen the accepted shape. + const fc = require('fast-check'); + test('[property] resolution classifies arbitrary JSON values: non-empty array → empty flags, anything else → --codex fallback', (t) => { + if (process.platform === 'win32') { t.skip('POSIX shell extraction; not run on Windows'); return; } + if (!jqAvailable) { t.skip('jq not on PATH — workflow resolution block pipes through jq; structural tests above still validate the fix'); return; } + + // Re-extract the blocks (the test above proved extraction works; we re-use + // the same logic rather than promoting to a helper to keep the test scope local). + const parseStart = workflow.indexOf('REVIEWER_FLAGS=""'); + const parseEndMarker = "echo \"$ARGUMENTS\" | grep -q '\\-\\-all' && REVIEWER_FLAGS=\"$REVIEWER_FLAGS --all\""; + const parseEnd = workflow.indexOf(parseEndMarker); + const parseBlock = workflow.slice(parseStart, parseEnd + parseEndMarker.length); + const configGateIdx = workflow.indexOf('CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence'); + const resolutionStart = workflow.indexOf('if [ -z "$REVIEWER_FLAGS" ]; then', configGateIdx); + const closingFence = workflow.indexOf('\n```\n', resolutionStart); + const resolutionBlock = workflow.slice(resolutionStart, closingFence); + const { execFileSync } = require('node:child_process'); + const run = ({ args, defaultReviewers }) => { + const stub = `gsd_run() { case "$*" in *"config-get review.default_reviewers"*) printf '%s' "$GSD_TEST_DEFAULT_REVIEWERS";; *) return 0;; esac; }`; + const script = `${stub}\n${parseBlock}\n${resolutionBlock}\nprintf 'REVIEWER_FLAGS=[%s] REVIEWER_DISPLAY=[%s]' "$REVIEWER_FLAGS" "$REVIEWER_DISPLAY"`; + return execFileSync('bash', ['-c', script], { + env: { ...process.env, ARGUMENTS: args, GSD_TEST_DEFAULT_REVIEWERS: defaultReviewers ?? '' }, + encoding: 'utf8', + timeout: 5000, + }); + }; + + // Slug pattern mirrors the schema (ADR-0011: ^[a-zA-Z0-9_-]+$). + const slug = fc.stringMatching(/^[a-zA-Z0-9_-]{1,8}$/); + // Non-empty arrays of slugs → MUST classify as "use default" (REVIEWER_FLAGS empty). + const nonEmptyArray = fc.array(slug, { minLength: 1, maxLength: 4 }).map((a) => JSON.stringify(a)); + // Defensive-corpus: empty array, scalar JSON, malformed JSON. All MUST fall + // back to --codex. The schema rejects the first two at config-set time, but + // a corruption/typo landing in the file directly would still reach this code. + const emptyArray = fc.constant('[]'); + const scalarJson = fc.oneof( + fc.string({ maxLength: 8 }).filter((s) => !s.includes('"')).map((s) => JSON.stringify(s)), + fc.integer({ min: -10, max: 10 }).map((n) => JSON.stringify(n)), + fc.constant('null'), + fc.constant('true'), + fc.constant('false') + ); + const malformedJson = fc.oneof( + fc.constant('[unclosed'), + fc.constant('{bad json'), + fc.constant('not json at all'), + fc.constant('{"k":'), + fc.string({ maxLength: 12 }).filter((s) => { + try { JSON.parse(s); return false; } catch { return true; } + }) + ); + + // Property A: any non-empty array of slugs → empty REVIEWER_FLAGS. + fc.assert( + fc.property(nonEmptyArray, (dr) => /REVIEWER_FLAGS=\[\s*\]/.test(run({ args: '5', defaultReviewers: dr }))), + { numRuns: 25 } + ); + + // Property B: anything in the defensive corpus → --codex fallback. + fc.assert( + fc.property(fc.oneof(emptyArray, scalarJson, malformedJson), (dr) => /REVIEWER_FLAGS=\[--codex\]/.test(run({ args: '5', defaultReviewers: dr }))), + { numRuns: 25 } + ); + }); +}); + // ─── Workflow: initialization ────────────────────────────────────────────── describe('plan-review-convergence workflow: initialization (#2306)', () => { diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 0d921c7a6..b135e87da 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -54,7 +54,7 @@ "pause-work.md": 14441, "plan-milestone-gaps.md": 11809, "plan-phase.md": 94391, - "plan-review-convergence.md": 23713, + "plan-review-convergence.md": 26285, "plant-seed.md": 11785, "pr-branch.md": 15963, "profile-user.md": 21246,