@opengsd/gsd-core 1.7.0-rc.4 → 1.7.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +20 -0
- package/agents/gsd-doc-classifier.md +105 -0
- package/agents/gsd-doc-synthesizer.md +61 -0
- package/agents/gsd-ui-checker.md +30 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +1568 -622
- package/gsd-core/bin/gsd-tools.cjs +40 -1
- package/gsd-core/bin/lib/api-coverage.cjs +466 -0
- package/gsd-core/bin/lib/audit.cjs +6 -3
- package/gsd-core/bin/lib/capability-loader.cjs +11 -9
- package/gsd-core/bin/lib/capability-registry.cjs +761 -84
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/capability-writer.cjs +10 -1
- package/gsd-core/bin/lib/check-command-router.cjs +242 -3
- package/gsd-core/bin/lib/commands.cjs +7 -5
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +96 -0
- package/gsd-core/bin/lib/core-utils.cjs +4 -1
- package/gsd-core/bin/lib/host-integration-adapters/cline-sdk-binding.cjs +234 -0
- package/gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs +145 -0
- package/gsd-core/bin/lib/host-integration.cjs +45 -4
- package/gsd-core/bin/lib/init.cjs +76 -39
- package/gsd-core/bin/lib/install-effort-resolver.cjs +213 -0
- package/gsd-core/bin/lib/install-engine.cjs +228 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +7 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +68 -17
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +50 -11
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/milestone.cjs +3 -3
- package/gsd-core/bin/lib/normalize-test-command.cjs +187 -0
- package/gsd-core/bin/lib/phase-id.cjs +132 -3
- package/gsd-core/bin/lib/phase.cjs +78 -16
- package/gsd-core/bin/lib/planning-workspace.cjs +17 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/roadmap-command-router.cjs +5 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -30
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +9 -9
- package/gsd-core/bin/lib/roadmap.cjs +42 -56
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +248 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +39 -23
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +30 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +534 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/security.cjs +6 -36
- package/gsd-core/bin/lib/shell-command-projection.cjs +115 -2
- package/gsd-core/bin/lib/spec-section.cjs +111 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/state-transition.cjs +1 -1
- package/gsd-core/bin/lib/state.cjs +24 -24
- package/gsd-core/bin/lib/surface.cjs +40 -6
- package/gsd-core/bin/lib/uat.cjs +4 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/lib/validate.cjs +15 -6
- package/gsd-core/bin/lib/verify.cjs +33 -37
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +14 -9
- package/gsd-core/references/api-coverage.md +104 -0
- package/gsd-core/references/model-profiles.md +2 -2
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/specless-probe-fallback.md +172 -0
- package/gsd-core/references/ui-consideration-probe.md +73 -0
- package/gsd-core/templates/UI-SPEC.md +25 -0
- package/gsd-core/templates/VALIDATION.md +2 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/workflows/audit-fix.md +9 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/code-review-fix.md +7 -3
- package/gsd-core/workflows/code-review.md +4 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +8 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +42 -0
- package/gsd-core/workflows/execute-phase.md +1 -25
- package/gsd-core/workflows/plan-phase.md +37 -2
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/review.md +59 -13
- package/gsd-core/workflows/settings-advanced.md +12 -9
- package/gsd-core/workflows/settings.md +2 -2
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-phase.md +3 -2
- package/gsd-core/workflows/verify-work.md +38 -0
- package/hooks/dist/gsd-cursor-pre-tool.js +76 -0
- package/hooks/dist/gsd-cursor-stop.js +48 -0
- package/hooks/dist/gsd-cursor-subagent-start.js +50 -0
- package/hooks/dist/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
- package/hooks/dist/managed-hooks-registry.cjs +6 -0
- package/hooks/gsd-cursor-pre-tool.js +76 -0
- package/hooks/gsd-cursor-stop.js +48 -0
- package/hooks/gsd-cursor-subagent-start.js +50 -0
- package/hooks/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +6 -0
- package/package.json +9 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +8 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +11 -1
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-phase-id-drift.cjs +150 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/run-tests.cjs +21 -1
- package/scripts/validate-registry.cjs +117 -0
- package/vscode/browser.js +197 -0
- package/vscode/extension.js +383 -0
- package/vscode/host-binding.js +113 -0
- package/vscode/package.json +96 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Step: regression_gate_run
|
|
2
|
+
|
|
3
|
+
Run the resolved prior-phase test command one-shot, bounded by a timeout, so a
|
|
4
|
+
watch-mode runner (vitest defaults to watch in a TTY; jest `--watch`) cannot
|
|
5
|
+
hang this gate forever (#1857). Uses the shared `normalize-test-command` helper
|
|
6
|
+
— the same one the post-merge gate uses — so the two gate paths cannot drift.
|
|
7
|
+
|
|
8
|
+
Expects `REGRESSION_FILES` (from the prior step) in scope for the pytest branch.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
12
|
+
# Resolve test command: project config > Makefile > language sniff
|
|
13
|
+
REG_TEST_CMD=$(gsd_run query config-get workflow.test_command --default "" 2>/dev/null || true)
|
|
14
|
+
if [ -z "$REG_TEST_CMD" ]; then
|
|
15
|
+
if [ -f "Makefile" ] && grep -q "^test:" Makefile; then
|
|
16
|
+
REG_TEST_CMD="make test"
|
|
17
|
+
elif [ -f "Justfile" ] || [ -f "justfile" ]; then
|
|
18
|
+
REG_TEST_CMD="just test"
|
|
19
|
+
elif [ -f "package.json" ]; then
|
|
20
|
+
REG_TEST_CMD="npm test"
|
|
21
|
+
elif [ -f "Cargo.toml" ]; then
|
|
22
|
+
REG_TEST_CMD="cargo test"
|
|
23
|
+
elif [ -f "go.mod" ]; then
|
|
24
|
+
REG_TEST_CMD="go test ./..."
|
|
25
|
+
elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
|
|
26
|
+
REG_TEST_CMD="python -m pytest ${REGRESSION_FILES} -q --tb=short"
|
|
27
|
+
else
|
|
28
|
+
REG_TEST_CMD="true"
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
# #1857: normalize to a one-shot form (defeat vitest/jest watch mode) and bound
|
|
32
|
+
# with a timeout so a watch-mode runner cannot hang the gate indefinitely.
|
|
33
|
+
REG_TEST_CMD=$(gsd_run query normalize-test-command "$REG_TEST_CMD" --cwd . 2>/dev/null || echo "$REG_TEST_CMD")
|
|
34
|
+
TEST_GATE_TIMEOUT=$(gsd_run query config-get workflow.test_gate_timeout 2>/dev/null || echo "600")
|
|
35
|
+
timeout "$TEST_GATE_TIMEOUT" bash -c "$REG_TEST_CMD" 2>&1
|
|
36
|
+
REG_TEST_EXIT=$?
|
|
37
|
+
if [ "$REG_TEST_EXIT" -eq 124 ]; then
|
|
38
|
+
echo "✗ REGRESSION GATE ABORTED — test runner did not exit within ${TEST_GATE_TIMEOUT}s, likely stuck in watch/dev mode (e.g. vitest without 'run'). Run tests one-shot (e.g. 'vitest run'), set workflow.test_command, or raise workflow.test_gate_timeout."
|
|
39
|
+
fi
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**On `REG_TEST_EXIT` 124 (`REGRESSION GATE ABORTED`):** HALT — do not proceed to verification. The runner did not exit within the budget (watch/dev mode is the likely cause). Surface the watch-mode cause and the recovery options; never silently continue.
|
|
@@ -1289,31 +1289,7 @@ For each VERIFICATION.md found, look for test file references:
|
|
|
1289
1289
|
|
|
1290
1290
|
Collect all unique test file paths into `REGRESSION_FILES`.
|
|
1291
1291
|
|
|
1292
|
-
**Step 3: Run regression tests (if any found)**
|
|
1293
|
-
|
|
1294
|
-
```bash
|
|
1295
|
-
# Resolve test command: project config > Makefile > language sniff
|
|
1296
|
-
REG_TEST_CMD=$(gsd_run query config-get workflow.test_command --default "" 2>/dev/null || true)
|
|
1297
|
-
if [ -z "$REG_TEST_CMD" ]; then
|
|
1298
|
-
if [ -f "Makefile" ] && grep -q "^test:" Makefile; then
|
|
1299
|
-
REG_TEST_CMD="make test"
|
|
1300
|
-
elif [ -f "Justfile" ] || [ -f "justfile" ]; then
|
|
1301
|
-
REG_TEST_CMD="just test"
|
|
1302
|
-
elif [ -f "package.json" ]; then
|
|
1303
|
-
REG_TEST_CMD="npm test"
|
|
1304
|
-
elif [ -f "Cargo.toml" ]; then
|
|
1305
|
-
REG_TEST_CMD="cargo test"
|
|
1306
|
-
elif [ -f "go.mod" ]; then
|
|
1307
|
-
REG_TEST_CMD="go test ./..."
|
|
1308
|
-
elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
|
|
1309
|
-
REG_TEST_CMD="python -m pytest ${REGRESSION_FILES} -q --tb=short"
|
|
1310
|
-
else
|
|
1311
|
-
REG_TEST_CMD="true"
|
|
1312
|
-
fi
|
|
1313
|
-
fi
|
|
1314
|
-
# Detect test runner and run prior phase tests
|
|
1315
|
-
eval "$REG_TEST_CMD" 2>&1
|
|
1316
|
-
```
|
|
1292
|
+
**Step 3: Run regression tests (if any found)** — Read and execute `gsd-core/workflows/execute-phase/steps/regression-gate.md`. It resolves the project test command, normalizes it to a one-shot form (defeating vitest/jest watch mode via the shared `normalize-test-command` helper), runs it under `workflow.test_gate_timeout`, and aborts on timeout with a watch-mode hint (#1857). On `REGRESSION GATE ABORTED` (exit 124), HALT — do not proceed to verification.
|
|
1317
1293
|
|
|
1318
1294
|
**Step 4: Report results**
|
|
1319
1295
|
|
|
@@ -610,6 +610,10 @@ SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/nul
|
|
|
610
610
|
PHASE_DIR_FOR_SPEC=$(_gsd_field "$INIT" phase_dir)
|
|
611
611
|
SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-SPEC.md 2>/dev/null | grep -Ev -- '-(AI|UI)-SPEC\.md$' | head -1)
|
|
612
612
|
SPEC_PATH="${SPEC_FILE}"
|
|
613
|
+
# Resolve the phase UI-SPEC separately (the glob above excludes -UI-SPEC.md); it carries the
|
|
614
|
+
# ## UI Considerations section the planner lifts by the same rule as ## Edge Coverage (#1867).
|
|
615
|
+
UI_SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
|
616
|
+
UI_SPEC_PATH="${UI_SPEC_FILE}"
|
|
613
617
|
```
|
|
614
618
|
|
|
615
619
|
## 7.5. Verify Nyquist Artifacts
|
|
@@ -693,6 +697,19 @@ echo "✓ API surface regenerated: ${API_SURFACE_PATH}" # injected into step 8
|
|
|
693
697
|
|
|
694
698
|
Continue to step 8.
|
|
695
699
|
|
|
700
|
+
## 7.95. Spec-less Probe Fallback (gate)
|
|
701
|
+
|
|
702
|
+
When the SPEC did not supply `## Edge Coverage` / `## Prohibitions`, plan-phase runs the probe protocol
|
|
703
|
+
and authors the predicates into PLAN.md `must_haves` (ADR-857 Phase 6 — the *else branch* of the
|
|
704
|
+
`<downstream_consumer>` lift below). Core workflow-body substrate, not a capability rail (D-03). Runs
|
|
705
|
+
after `$SPEC_FILE` (Step 7), before the gsd-planner spawn (Step 8).
|
|
706
|
+
|
|
707
|
+
**Read and run** the gate + edge probe in `~/.claude/gsd-core/references/specless-probe-fallback.md`
|
|
708
|
+
(§0 default-ON toggle + per-section absence via the `spec-section` helper, visibly skipping when
|
|
709
|
+
disabled or no requirement IDs; §A deterministic edge probe → `$COVERAGE` when `EDGE_ABSENT`; §B
|
|
710
|
+
prohibition recall in the planner). Pass `$COVERAGE` and `$SPECLESS_FALLBACK_DISABLED` into Step 8.
|
|
711
|
+
|
|
712
|
+
|
|
696
713
|
## 8. Spawn gsd-planner Agent
|
|
697
714
|
|
|
698
715
|
Display banner:
|
|
@@ -769,6 +786,22 @@ ${MVP_MODE === 'true' ? `
|
|
|
769
786
|
**MVP Mode is ENABLED.** Read `~/.claude/gsd-core/references/planner-mvp-mode.md` now and follow its vertical-slice planning rules. Each plan must deliver a complete vertical slice — thin end-to-end functionality rather than horizontal layers.
|
|
770
787
|
</mvp_mode_active>
|
|
771
788
|
` : ''}
|
|
789
|
+
|
|
790
|
+
<specless_probe_fallback>
|
|
791
|
+
**Spec-less probe fallback** (only when step 7.95 set `EDGE_ABSENT` and/or `PROHIB_ABSENT`). The SPEC
|
|
792
|
+
omitted that section — author its predicates into `must_haves` via the `<downstream_consumer>`
|
|
793
|
+
else-branch below, per §A/§B/§C of `~/.claude/gsd-core/references/specless-probe-fallback.md`
|
|
794
|
+
(descriptor-less prohibitions, never auto-dismiss, no silent drops).
|
|
795
|
+
|
|
796
|
+
Edge coverage report (`$COVERAGE`, present when `EDGE_ABSENT`):
|
|
797
|
+
|
|
798
|
+
```json
|
|
799
|
+
{COVERAGE}
|
|
800
|
+
```
|
|
801
|
+
${SPECLESS_FALLBACK_DISABLED ? `
|
|
802
|
+
**⚠ ${SPECLESS_FALLBACK_DISABLED}** — record this in the plan (a visible, recorded choice); do not generate probe predicates this run.
|
|
803
|
+
` : ''}
|
|
804
|
+
|
|
772
805
|
</planning_context>
|
|
773
806
|
|
|
774
807
|
<downstream_consumer>
|
|
@@ -777,8 +810,9 @@ Output consumed by /gsd:execute-phase. Plans need:
|
|
|
777
810
|
- Tasks in XML format with read_first and acceptance_criteria fields (MANDATORY on every task)
|
|
778
811
|
- Verification criteria
|
|
779
812
|
- must_haves for goal-backward verification
|
|
780
|
-
- If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: <the check>, verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them.
|
|
781
|
-
- If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3)
|
|
813
|
+
- If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: <the check>, verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run.
|
|
814
|
+
- If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) with `statement`+`status`+`verification`, via the single `projectProhibitions` serializer (Hyrum — no second serializer); unresolved -> flagged assumptions, don't drop; never put a must-NOT under `truths`. **Otherwise** (`PROHIB_ABSENT`), author the recalled prohibitions into the SAME block via the SAME `projectProhibitions` contract but **descriptor-less** (no `check_*`) so each disposes flagged-unverified; never auto-dismiss. Section-level precedence + no-silent-drop equality apply (§C).
|
|
815
|
+
- If a `-UI-SPEC.md` exists (resolved above as `UI_SPEC_PATH`) with a `## UI Considerations` section, lift it by the **identical rule** as `## Edge Coverage` above — `covered` → `must_haves.truths` string, `backstop` → flat scalar `{ statement, verification: backstop }`, `unresolved` → explicit planner assumption (no new verb — ADR-550 #1278/#1154; #1867). Read it from `UI_SPEC_PATH` (the SPEC glob excludes `-UI-SPEC.md`).
|
|
782
816
|
- **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement.
|
|
783
817
|
</downstream_consumer>
|
|
784
818
|
|
|
@@ -825,6 +859,7 @@ Every task MUST include these fields — they are NOT optional:
|
|
|
825
859
|
- [ ] must_haves derived from phase goal
|
|
826
860
|
- [ ] Every PLAN.md includes an "Artifacts this phase produces" section listing symbols created by this phase (decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths)
|
|
827
861
|
- [ ] Every SPEC ## Edge Coverage covered/backstop edge is represented in a plan's must_haves (no silent drops)
|
|
862
|
+
- [ ] Every UI-SPEC ## UI Considerations covered/backstop consideration is represented in a plan's must_haves (no silent drops)
|
|
828
863
|
- [ ] Every SPEC ## Prohibitions resolved item is represented in a plan's must_haves.prohibitions (no silent drops)
|
|
829
864
|
</quality_gate>
|
|
830
865
|
```
|
|
@@ -134,7 +134,7 @@ AGENT_SKILLS_CHECKER=$(gsd_run query agent-skills gsd-plan-checker)
|
|
|
134
134
|
AGENT_SKILLS_VERIFIER=$(gsd_run query agent-skills gsd-verifier)
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
|
137
|
+
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `reviewer_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
140
|
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
|
@@ -868,7 +868,7 @@ Agent(
|
|
|
868
868
|
Output: ${QUICK_DIR}/${quick_id}-REVIEW.md
|
|
869
869
|
Depth: quick",
|
|
870
870
|
subagent_type="gsd-code-reviewer",
|
|
871
|
-
model="{
|
|
871
|
+
model="{reviewer_model}"
|
|
872
872
|
)
|
|
873
873
|
```
|
|
874
874
|
|
|
@@ -234,7 +234,8 @@ GEMINI_MODEL=$(gsd_run query config-get review.models.gemini 2>/dev/null | jq -r
|
|
|
234
234
|
CLAUDE_MODEL=$(gsd_run query config-get review.models.claude 2>/dev/null | jq -r '.' 2>/dev/null || true)
|
|
235
235
|
CODEX_MODEL=$(gsd_run query config-get review.models.codex 2>/dev/null | jq -r '.' 2>/dev/null || true)
|
|
236
236
|
OPENCODE_MODEL=$(gsd_run query config-get review.models.opencode 2>/dev/null | jq -r '.' 2>/dev/null || true)
|
|
237
|
-
# review.models.agy is
|
|
237
|
+
# review.models.agy, when set, is passed to agy as --model (escape hatch for a
|
|
238
|
+
# pinned model that 404s server-side); otherwise agy uses its persisted default.
|
|
238
239
|
AGY_MODEL=$(gsd_run query config-get review.models.agy 2>/dev/null | jq -r '.' 2>/dev/null || true)
|
|
239
240
|
|
|
240
241
|
# #1115: `--dangerously-bypass-hook-trust` only exists on codex-cli >= 0.137.0.
|
|
@@ -373,7 +374,7 @@ fi
|
|
|
373
374
|
|
|
374
375
|
**Antigravity CLI:**
|
|
375
376
|
|
|
376
|
-
**Maintainer note — why this block has three layers (last updated against agy 1.0.
|
|
377
|
+
**Maintainer note — why this block has three layers (last updated against agy 1.0.16):**
|
|
377
378
|
|
|
378
379
|
`agy -p` (the `--print` non-interactive flag) works correctly on macOS and Linux: it sends the
|
|
379
380
|
prompt, receives the model response, and writes it to stdout. On **native Windows** it silently
|
|
@@ -408,7 +409,9 @@ and Step 3 fires with a clear error message in REVIEWS.md. No silent corruption.
|
|
|
408
409
|
Invocation specifics (verified agy 1.0.0, macOS arm64 and Linux amd64):
|
|
409
410
|
- `-p` takes the prompt as a **flag value** — `echo X | agy -p` errors with "flag needs an argument: -p"
|
|
410
411
|
- `--print-timeout` defaults to 5m, aligning with this workflow's global timeout
|
|
411
|
-
-
|
|
412
|
+
- `--model "<name>"` selects the model (available since agy ~1.0.3; `agy models` lists
|
|
413
|
+
them). When `review.models.agy` is set it is passed as `--model`; otherwise agy uses
|
|
414
|
+
its persisted default (`agy models`).
|
|
412
415
|
|
|
413
416
|
```bash
|
|
414
417
|
# Pre-flight: snapshot the transcript watermark before invoking agy.
|
|
@@ -432,14 +435,41 @@ if [ -f "$_AGY_CACHE" ]; then
|
|
|
432
435
|
fi
|
|
433
436
|
|
|
434
437
|
# Step 1 — primary invocation: stdout works on macOS, Linux, and WSL.
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
#
|
|
441
|
-
#
|
|
442
|
-
|
|
438
|
+
# Three hardening invariants (#2073), all mirroring the Cursor block's discipline:
|
|
439
|
+
# * FILE-REFERENCE prompt (not inline `$(cat …)`) — a large review prompt (≈197 KB
|
|
440
|
+
# for 6 plans + CONTEXT + RESEARCH + REQUIREMENTS) overflows the exec arg list
|
|
441
|
+
# (`bash: agy: Argument list too long`, rc 126), indistinguishable from a model
|
|
442
|
+
# failure when stderr is suppressed.
|
|
443
|
+
# * EXTERNAL `timeout` wrapper when available (GNU `timeout` / `gtimeout`) —
|
|
444
|
+
# `--print-timeout` is agy's native cap but it CANNOT fire before agy creates a
|
|
445
|
+
# session; under concurrent heavy runs one process can stall pre-session (no
|
|
446
|
+
# `brain/<conv-id>/` dir, alive at 583 s despite `--print-timeout 300s`). The
|
|
447
|
+
# external cap bounds wall-clock regardless. Stock macOS lacks `timeout`, so
|
|
448
|
+
# the block probes for it and falls back to --print-timeout alone there.
|
|
449
|
+
# * `--model` from `review.models.agy` when set — escape hatch for a pinned model
|
|
450
|
+
# that 404s server-side (exits 0 with empty stdout AND empty transcript).
|
|
451
|
+
# * stdin tied to /dev/null so agy never blocks on a tty.
|
|
452
|
+
# A non-zero exit (external timeout = 124, crash, etc.) discards any partial output
|
|
453
|
+
# so the Step 2 transcript fallback / Step 3 diagnostic take over.
|
|
454
|
+
if [ -n "$AGY_MODEL" ] && [ "$AGY_MODEL" != "null" ]; then
|
|
455
|
+
set -- --model "$AGY_MODEL"
|
|
456
|
+
else
|
|
457
|
+
set --
|
|
458
|
+
fi
|
|
459
|
+
_AGY_PROMPT="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files."
|
|
460
|
+
# Capability-probe an external wall-clock killer (GNU coreutils `timeout` or the
|
|
461
|
+
# macOS Homebrew `gtimeout`). Stock macOS ships NEITHER — a bare `timeout …` would
|
|
462
|
+
# fail with rc 127 ("command not found") and silently lose the reviewer, so fall
|
|
463
|
+
# back to agy's native --print-timeout alone in that case. The external cap, when
|
|
464
|
+
# available, is set HIGHER than --print-timeout so it only backstops a pre-session
|
|
465
|
+
# stall (which --print-timeout cannot bound — #2073 mode 3) and never pre-empts a
|
|
466
|
+
# healthy run. Mirrors the probe in scripts/base64-scan.sh.
|
|
467
|
+
_AGY_KILLER="$(command -v timeout 2>/dev/null || command -v gtimeout 2>/dev/null || true)"
|
|
468
|
+
if [ -n "$_AGY_KILLER" ]; then
|
|
469
|
+
"$_AGY_KILLER" 600 agy --print-timeout 540s "$@" -p "$_AGY_PROMPT" </dev/null 2>/dev/null > /tmp/gsd-review-antigravity-{phase}.md
|
|
470
|
+
else
|
|
471
|
+
agy --print-timeout 540s "$@" -p "$_AGY_PROMPT" </dev/null 2>/dev/null > /tmp/gsd-review-antigravity-{phase}.md
|
|
472
|
+
fi
|
|
443
473
|
_AGY_RC=$?
|
|
444
474
|
if [ "$_AGY_RC" -ne 0 ]; then
|
|
445
475
|
: > /tmp/gsd-review-antigravity-{phase}.md
|
|
@@ -473,9 +503,25 @@ if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
|
|
|
473
503
|
fi
|
|
474
504
|
fi
|
|
475
505
|
|
|
476
|
-
# Step 3 — final guard: both approaches yielded nothing (auth error, first-run setup,
|
|
506
|
+
# Step 3 — final guard: both approaches yielded nothing (auth error, first-run setup,
|
|
507
|
+
# path schema changed, 404'd pinned model, pre-session stall, etc.)
|
|
477
508
|
if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
|
|
478
|
-
|
|
509
|
+
{
|
|
510
|
+
echo "Antigravity review failed or returned empty output."
|
|
511
|
+
# #2073 mode 2: a pinned model that 404s exits 0 with empty stdout AND an empty
|
|
512
|
+
# transcript — the only evidence is in agy's own log. Surface it instead of a
|
|
513
|
+
# bare generic stub so the failure is diagnosable.
|
|
514
|
+
_AGY_LOG="$HOME/.gemini/antigravity-cli/cli.log"
|
|
515
|
+
if [ -f "$_AGY_LOG" ]; then
|
|
516
|
+
_AGY_ERR=$(grep -iE 'agent executor error|NOT_FOUND|Publisher model' "$_AGY_LOG" | tail -3)
|
|
517
|
+
if [ -n "$_AGY_ERR" ]; then
|
|
518
|
+
echo "agy log hint (pinned model may be unavailable — run 'agy models' and set review.models.agy):"
|
|
519
|
+
echo "$_AGY_ERR"
|
|
520
|
+
fi
|
|
521
|
+
fi
|
|
522
|
+
# #2073 mode 3: pre-session stall tell — no new conversation dir appeared.
|
|
523
|
+
echo "If no agy run started, that is the pre-session-stall case: check whether a new ~/.gemini/antigravity-cli/brain/<conv-id>/ dir appeared within ~30s of launch."
|
|
524
|
+
} > /tmp/gsd-review-antigravity-{phase}.md
|
|
479
525
|
fi
|
|
480
526
|
```
|
|
481
527
|
|
|
@@ -354,13 +354,15 @@ Built-in tier defaults by runtime:
|
|
|
354
354
|
| Runtime | `opus` | `sonnet` | `haiku` |
|
|
355
355
|
|------------|-------------------------------|---------------------------------|-------------------------------|
|
|
356
356
|
| `claude` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
|
357
|
-
| `codex` | `gpt-5.
|
|
357
|
+
| `codex` | `gpt-5.6-sol` | `gpt-5.6-terra` | `gpt-5.6-luna` |
|
|
358
358
|
| `gemini` | `gemini-3.1-pro-preview` | `gemini-3-flash` | `gemini-2.5-flash-lite` |
|
|
359
359
|
| `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` |
|
|
360
360
|
| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
|
361
361
|
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
|
362
362
|
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
|
363
|
-
|
|
|
363
|
+
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
|
364
|
+
| `pi` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
|
365
|
+
| Group B (`cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
|
364
366
|
|
|
365
367
|
Display a table to the user showing the effective configuration:
|
|
366
368
|
|
|
@@ -387,8 +389,8 @@ AskUserQuestion([
|
|
|
387
389
|
options: [
|
|
388
390
|
{ label: "Keep current (<runtime>)", description: "Configure overrides for the current runtime." },
|
|
389
391
|
{ label: "Common runtimes", description: "claude, codex, gemini, qwen" },
|
|
390
|
-
{ label: "Additional runtimes", description: "opencode, copilot, hermes" },
|
|
391
|
-
{ label: "Other (Group B or custom)", description: "
|
|
392
|
+
{ label: "Additional runtimes", description: "opencode, copilot, hermes, kilo" },
|
|
393
|
+
{ label: "Other (Group B or custom)", description: "cline, cursor, windsurf, augment, trae, codebuddy, antigravity, or a custom runtime string." }
|
|
392
394
|
]
|
|
393
395
|
}
|
|
394
396
|
])
|
|
@@ -423,7 +425,8 @@ AskUserQuestion([
|
|
|
423
425
|
options: [
|
|
424
426
|
{ label: "opencode", description: "OpenCode (uses anthropic/ prefix)." },
|
|
425
427
|
{ label: "copilot", description: "GitHub Copilot." },
|
|
426
|
-
{ label: "hermes", description: "Hermes (uses anthropic/ prefix)." }
|
|
428
|
+
{ label: "hermes", description: "Hermes (uses anthropic/ prefix)." },
|
|
429
|
+
{ label: "kilo", description: "Kilo Code (uses anthropic/ prefix)." }
|
|
427
430
|
]
|
|
428
431
|
}
|
|
429
432
|
])
|
|
@@ -627,7 +630,7 @@ AskUserQuestion([
|
|
|
627
630
|
options: [
|
|
628
631
|
{ label: "anthropic", description: "claude-opus-4-8 / claude-sonnet-5 / claude-haiku-4-5 (Anthropic / Claude)" },
|
|
629
632
|
{ label: "anthropic-fable", description: "claude-fable-5 / claude-sonnet-5 / claude-haiku-4-5 (Anthropic / Claude Fable opt-in)" },
|
|
630
|
-
{ label: "openai", description: "gpt-5.
|
|
633
|
+
{ label: "openai", description: "gpt-5.6-sol / gpt-5.6-terra / gpt-5.6-luna (OpenAI / Codex)" },
|
|
631
634
|
{ label: "Other known provider", description: "Type google or qwen; both still use the canonical tier mapping." }
|
|
632
635
|
]
|
|
633
636
|
}
|
|
@@ -662,9 +665,9 @@ Canonical tier mappings by provider and budget:
|
|
|
662
665
|
| anthropic-fable | high | claude-fable-5 | claude-fable-5 | claude-sonnet-5 |
|
|
663
666
|
| anthropic-fable | medium | claude-opus-4-8 | claude-sonnet-5 | claude-haiku-4-5 |
|
|
664
667
|
| anthropic-fable | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 |
|
|
665
|
-
| openai | high | gpt-5.
|
|
666
|
-
| openai | medium | gpt-5.
|
|
667
|
-
| openai | low | gpt-5.
|
|
668
|
+
| openai | high | gpt-5.6-sol | gpt-5.6-sol | gpt-5.6-sol |
|
|
669
|
+
| openai | medium | gpt-5.6-sol | gpt-5.6-terra | gpt-5.6-luna |
|
|
670
|
+
| openai | low | gpt-5.6-luna | gpt-5.6-luna | gpt-5.6-luna |
|
|
668
671
|
| google | high | gemini-3.1-pro-preview | gemini-3.1-pro-preview | gemini-3.1-pro-preview |
|
|
669
672
|
| google | medium | gemini-3.1-pro-preview | gemini-3-flash | gemini-2.5-flash-lite |
|
|
670
673
|
| google | low | gemini-2.5-flash-lite | gemini-2.5-flash-lite | gemini-2.5-flash-lite |
|
|
@@ -73,8 +73,8 @@ Parse current values (default to `true` if not present):
|
|
|
73
73
|
```
|
|
74
74
|
Note: Quality, Balanced, Budget, and Adaptive profiles assign semantic tiers
|
|
75
75
|
(Opus/Sonnet/Haiku) to each agent. When `runtime` is set in .planning/config.json,
|
|
76
|
-
tiers resolve to runtime-native model IDs — on Codex that's gpt-5.
|
|
77
|
-
gpt-5.
|
|
76
|
+
tiers resolve to runtime-native model IDs — on Codex that's gpt-5.6-sol / gpt-5.6-terra /
|
|
77
|
+
gpt-5.6-luna with appropriate reasoning effort. See "Runtime-Aware Profiles" in
|
|
78
78
|
docs/CONFIGURATION.md.
|
|
79
79
|
|
|
80
80
|
If `runtime` is unset on a non-Claude runtime, the profile tiers have no effect on
|
|
@@ -223,7 +223,7 @@ Agent(
|
|
|
223
223
|
## 8. Handle Checker Return
|
|
224
224
|
|
|
225
225
|
**If `## UI-SPEC VERIFIED`:**
|
|
226
|
-
Display dimension results. Proceed to step
|
|
226
|
+
Display dimension results. Proceed to step 9.5.
|
|
227
227
|
|
|
228
228
|
**If `## ISSUES FOUND`:**
|
|
229
229
|
Display blocking issues. Proceed to step 9.
|
|
@@ -264,6 +264,151 @@ Options:
|
|
|
264
264
|
|
|
265
265
|
Use AskUserQuestion for the choice.
|
|
266
266
|
|
|
267
|
+
**On "Force approve":** proceed to step 9.5 (the UI-consideration probe still runs on the accepted UI-SPEC, so state coverage is recorded even when quality FLAGs were accepted), then step 10. **On "Edit manually" / "Abandon":** exit without running the probe.
|
|
268
|
+
|
|
269
|
+
## 9.5. UI-Consideration Probe (post-verification)
|
|
270
|
+
|
|
271
|
+
Run AFTER the checker approves the UI-SPEC (VERIFIED, or force-approved at step 9) — never inline
|
|
272
|
+
during authoring, so a revision-loop researcher rewrite (step 9) cannot clobber the section and the
|
|
273
|
+
`## UI Considerations` block is committed with the FINAL UI-SPEC. This is the visual analog of
|
|
274
|
+
spec-phase Step 5.5's edge probe, retargeted to the UI element/state axis. Reference:
|
|
275
|
+
@~/.claude/gsd-core/references/ui-consideration-probe.md.
|
|
276
|
+
|
|
277
|
+
**Skip conditions:** if `--auto` and the UI-SPEC already carries a resolved `## UI Considerations`
|
|
278
|
+
section (re-run), the write-back is idempotent (it REPLACES that section, never appends). If the
|
|
279
|
+
runtime is non-Claude and the probe engine cannot be resolved, the shim FAILS LOUD (below) — it
|
|
280
|
+
never silently no-ops (a silent skip would drop the whole state-coverage axis).
|
|
281
|
+
|
|
282
|
+
**Runtime coverage compute — resolve and invoke ui-consideration-probe.cjs:**
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Resolve the compiled ui-consideration-probe.cjs against the GSD install dir via RUNTIME_DIR
|
|
286
|
+
# (#448) — NOT the consuming project's git root — falling back to git toplevel / $HOME/.claude.
|
|
287
|
+
# Mirrors spec-phase.md Step 5.5's edge-probe resolution idiom verbatim (same candidate paths).
|
|
288
|
+
_GSD_RT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
289
|
+
UI_PROBE_JS=$(for _c in \
|
|
290
|
+
"$_GSD_RT/gsd-core/bin/lib/ui-consideration-probe.cjs" \
|
|
291
|
+
"$_GSD_RT/bin/lib/ui-consideration-probe.cjs" \
|
|
292
|
+
"$_GSD_RT/.claude/bin/lib/ui-consideration-probe.cjs" \
|
|
293
|
+
"$HOME/.claude/gsd-core/bin/lib/ui-consideration-probe.cjs" \
|
|
294
|
+
"$HOME/.claude/bin/lib/ui-consideration-probe.cjs"; do
|
|
295
|
+
[ -f "$_c" ] && { echo "$_c"; break; }
|
|
296
|
+
done)
|
|
297
|
+
|
|
298
|
+
# Graceful degradation — never a silent skip. Build ONLY when $_GSD_RT is a verified GSD source
|
|
299
|
+
# checkout (has tsconfig.build.json + src/ui-consideration-probe.cts), pinned with --prefix so we
|
|
300
|
+
# never trigger the CONSUMING project's own build during a ui-phase. Real installs ship the
|
|
301
|
+
# compiled .cjs via prepublishOnly, so this path only matters in a GSD dev checkout.
|
|
302
|
+
if [ -z "$UI_PROBE_JS" ]; then
|
|
303
|
+
if [ -f "$_GSD_RT/tsconfig.build.json" ] && [ -f "$_GSD_RT/src/ui-consideration-probe.cts" ]; then
|
|
304
|
+
npm --prefix "$_GSD_RT" run build:lib 2>/dev/null || true
|
|
305
|
+
UI_PROBE_JS=$(for _c in \
|
|
306
|
+
"$_GSD_RT/gsd-core/bin/lib/ui-consideration-probe.cjs" \
|
|
307
|
+
"$_GSD_RT/bin/lib/ui-consideration-probe.cjs" \
|
|
308
|
+
"$_GSD_RT/.claude/bin/lib/ui-consideration-probe.cjs" \
|
|
309
|
+
"$HOME/.claude/gsd-core/bin/lib/ui-consideration-probe.cjs" \
|
|
310
|
+
"$HOME/.claude/bin/lib/ui-consideration-probe.cjs"; do
|
|
311
|
+
[ -f "$_c" ] && { echo "$_c"; break; }
|
|
312
|
+
done)
|
|
313
|
+
fi
|
|
314
|
+
if [ -z "$UI_PROBE_JS" ]; then
|
|
315
|
+
echo "ERROR: ui-consideration-probe.cjs not found — reinstall GSD or run \`npm run build:lib\` in your GSD checkout." >&2
|
|
316
|
+
exit 1
|
|
317
|
+
fi
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# Element extraction (MANUAL BY DESIGN — not an oversight): the agent reads the researcher-authored
|
|
321
|
+
# UI-SPEC prose (the described surfaces — the Design System / Copywriting rows and any element the
|
|
322
|
+
# researcher named) and writes ONE object per UI element/surface: {"id","text"} where text is the
|
|
323
|
+
# prose describing it. This mirrors spec-phase Step 5.5's edge-probe REQS_JSON step VERBATIM — a
|
|
324
|
+
# hand-populated heredoc guarded by the fail-loud <replace:> check below — the established, shipped
|
|
325
|
+
# pattern for feeding a probe from a prose spec. It is NOT mechanized on purpose: a UI-SPEC has no
|
|
326
|
+
# single machine-parseable "elements" column — surfaces are distributed across design-token tables
|
|
327
|
+
# (Design System / Typography / Color), the Copywriting section, and prose the researcher names, so a
|
|
328
|
+
# regex/table parse would fail-OPEN (miss a prose-named surface, or feed a design-token row as a bogus
|
|
329
|
+
# element). The agent-authored heredoc + fail-loud guard is the conservative choice, identical to the
|
|
330
|
+
# requirement-side edge-probe path (RR-04). If a future UI-SPEC gains a canonical element table,
|
|
331
|
+
# revisit to parse it. Populate the heredoc from the UI-SPEC; the guard below fails loud on a
|
|
332
|
+
# forgotten substitution (never a no-op).
|
|
333
|
+
ELEMENTS_JSON=$(mktemp "${TMPDIR:-/tmp}/ui-probe-elements-XXXXXX") && mv "$ELEMENTS_JSON" "${ELEMENTS_JSON}.json" && ELEMENTS_JSON="${ELEMENTS_JSON}.json" || exit 1
|
|
334
|
+
cat > "$ELEMENTS_JSON" <<'JSON'
|
|
335
|
+
[
|
|
336
|
+
{ "id": "E1", "text": "<replace: element/surface description from the UI-SPEC prose>" }
|
|
337
|
+
]
|
|
338
|
+
JSON
|
|
339
|
+
if ! node -e 'const a=require(process.argv[1]);if(!Array.isArray(a)||a.length===0)process.exit(1);if(a.some(e=>typeof e.text!=="string"||!e.text.trim()||e.text.includes("<replace:")))process.exit(1)' "$ELEMENTS_JSON" 2>/dev/null; then
|
|
340
|
+
rm -f "$ELEMENTS_JSON"
|
|
341
|
+
echo "ERROR: ui-probe elements JSON is empty/invalid or still holds the <replace: …> placeholder — populate \$ELEMENTS_JSON from the UI-SPEC's described surfaces before this step runs." >&2
|
|
342
|
+
exit 1
|
|
343
|
+
fi
|
|
344
|
+
# Invoke the compiled engine and CAPTURE its report. FATAL-INVOKE GUARD: use `if ! COVERAGE=$(…)`,
|
|
345
|
+
# NEVER a bare `COVERAGE=$(node …)` — a bare capture swallows the engine's exit 2 (invalid shape /
|
|
346
|
+
# bad input) and falls through to prose re-derivation: fail-OPEN at the exact boundary the engine
|
|
347
|
+
# validation protects.
|
|
348
|
+
if ! COVERAGE=$(node "$UI_PROBE_JS" "$ELEMENTS_JSON"); then
|
|
349
|
+
rm -f "$ELEMENTS_JSON"
|
|
350
|
+
echo "ERROR: ui-consideration-probe engine failed (invalid shapes or bad input) — fix the element(s) and re-run; never proceed with empty coverage." >&2
|
|
351
|
+
exit 1
|
|
352
|
+
fi
|
|
353
|
+
rm -f "$ELEMENTS_JSON"
|
|
354
|
+
# Malformed-report guard: exit 0 but garbage. The report must parse as { items[], coverage{} }.
|
|
355
|
+
if ! printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let r;try{r=JSON.parse(s)}catch{process.exit(1)}if(!r||!Array.isArray(r.items)||typeof r.coverage!=="object"||r.coverage===null)process.exit(1)})'; then
|
|
356
|
+
echo "ERROR: ui-consideration-probe produced an unparseable or malformed coverage report — refusing to proceed with the resolution loop." >&2
|
|
357
|
+
exit 1
|
|
358
|
+
fi
|
|
359
|
+
# Zero-applicable guard: a report where NO category applied across ANY element is far more likely a
|
|
360
|
+
# classification miss (or malformed elements) than a genuinely state-free UI. Surface it loudly.
|
|
361
|
+
APPLICABLE=$(printf '%s' "$COVERAGE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let n=0;try{n=JSON.parse(s).coverage.applicable}catch{n=0}process.stdout.write(String(n))})')
|
|
362
|
+
if [ "$APPLICABLE" = "0" ]; then
|
|
363
|
+
echo "WARNING: ui-consideration-probe proposed ZERO applicable categories across all elements — likely a classification miss or malformed elements, not a genuinely state-free UI. Do NOT silently write an empty UI Considerations section." >&2
|
|
364
|
+
fi
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
If `$APPLICABLE` is `0`, do NOT proceed silently: ask via AskUserQuestion ("The UI probe found no
|
|
368
|
+
applicable state considerations — is this genuinely a state-free surface, or should we revisit the
|
|
369
|
+
element descriptions?"). Only write an empty section after explicit confirmation.
|
|
370
|
+
|
|
371
|
+
**Propose-then-confirm (the partial-cue mitigation — load-bearing).** For each element, the engine
|
|
372
|
+
reports the DETECTED element kinds (`classifyElement` over the built `.cjs`). The prose classifier
|
|
373
|
+
is heuristic and LOSSY: a surface that is genuinely both a form and a list, but whose prose trips
|
|
374
|
+
only the form cue, under-covers — and because SOMETHING classified, no `unclassified` signal fires.
|
|
375
|
+
So SURFACE the detected kinds to the user (AskUserQuestion) and ask whether any real element kind
|
|
376
|
+
was missed. If the user ADDs a kind, re-run that element with an authored `elements` override
|
|
377
|
+
(the union of detected + added) so the missed categories are raised. A single tripped cue is a
|
|
378
|
+
SIGNAL, not proof the element is only that kind — the confirm step, not the heuristic, is what makes
|
|
379
|
+
coverage sound.
|
|
380
|
+
|
|
381
|
+
**Resolution loop** (mirror spec-phase 5.5): resolve each applicable consideration via
|
|
382
|
+
AskUserQuestion — **Specify** (→ `covered`, write a concrete truth) / **Dismiss (reason required)** /
|
|
383
|
+
**Backstop** (a held-out/visual UI-state test) / **Defer** (→ `unresolved`). An `unclassified` row is
|
|
384
|
+
a manual-review nudge, not a hard block. Text mode (`workflow.text_mode` / `--text`) → numbered lists.
|
|
385
|
+
|
|
386
|
+
**Kind-confirmation under `--auto`.** The propose-then-confirm step above is an AskUserQuestion, so
|
|
387
|
+
under `--auto` it follows the spec-phase 5.5 convention (replace AskUserQuestion with Claude's
|
|
388
|
+
recommended choice): Claude re-reads each element's prose and authors the `elements` override (the
|
|
389
|
+
union of the detected kinds + any kind it identifies as missed) instead of prompting — so `--auto`
|
|
390
|
+
recall rests on Claude's kind-identification, not the heuristic cue-match alone. This matters because
|
|
391
|
+
`autoResolve` (below) is a RESOLUTION floor only: it resolves the *detected* categories and cannot
|
|
392
|
+
recover a kind that was never surfaced, so recall is fixed HERE, at kind-confirmation, before
|
|
393
|
+
resolution runs.
|
|
394
|
+
|
|
395
|
+
**`--auto` mode (two layers).** The adapter's `autoResolve` is the CODE floor: every applicable
|
|
396
|
+
consideration auto-`backstop`s (carrying the taxonomy question as its resolution) and an
|
|
397
|
+
`unclassified` candidate stays `unresolved` — it NEVER auto-`dismiss`es and never auto-backstops an
|
|
398
|
+
unclassified item (#1110). On top of that floor the workflow MAY upgrade an item to `covered` when a
|
|
399
|
+
defensible acceptance criterion can be written (the same judgment spec-phase 5.5 applies in prose).
|
|
400
|
+
An auto `--auto` run therefore leaves un-upgraded backstops as `backstop`: at verify time each one
|
|
401
|
+
with no wired evidence routes to `insufficient_spec → human_needed` — never a silent pass (#1154).
|
|
402
|
+
That surfacing is the intended honest-verifier behavior, not over-flagging.
|
|
403
|
+
|
|
404
|
+
**Write-back.** Populate a `## UI Considerations` section in the UI-SPEC from the resolved
|
|
405
|
+
considerations, in the format the shipped plan-phase `## UI Considerations` lift rule reads:
|
|
406
|
+
`covered` → a truth string; `backstop` → a flat scalar `{ statement, verification: backstop }`;
|
|
407
|
+
`unresolved` → an explicit `⚠ unresolved — planner must treat as assumption` row. Empty-state and
|
|
408
|
+
error-state COPY stays in `## Copywriting Contract` — the considerations section covers shape-rooted
|
|
409
|
+
STATE coverage and REFERENCES those rows rather than restating the copy (de-dup). IDEMPOTENT: if a
|
|
410
|
+
`## UI Considerations` section already exists, REPLACE it — never append a duplicate.
|
|
411
|
+
|
|
267
412
|
## 10. Present Final Status
|
|
268
413
|
|
|
269
414
|
Display:
|
|
@@ -122,11 +122,11 @@ Handle return:
|
|
|
122
122
|
|
|
123
123
|
**State B (create):**
|
|
124
124
|
1. Read template from `~/.claude/gsd-core/templates/VALIDATION.md`
|
|
125
|
-
2. Fill: frontmatter, Test Infrastructure, Per-Task Map, Manual-Only, Sign-Off
|
|
125
|
+
2. Fill: frontmatter (**set `status: validated`**), Test Infrastructure, Per-Task Map, Manual-Only, Sign-Off
|
|
126
126
|
3. Write to `${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md`
|
|
127
127
|
|
|
128
128
|
**State A (update):**
|
|
129
|
-
1. Update Per-Task Map statuses, add escalated to Manual-Only, update frontmatter
|
|
129
|
+
1. Update Per-Task Map statuses, add escalated to Manual-Only, update frontmatter (**set `status: validated`**)
|
|
130
130
|
2. Append audit trail:
|
|
131
131
|
|
|
132
132
|
```markdown
|
|
@@ -288,14 +288,15 @@ if [ -z "$TEST_CMD" ]; then
|
|
|
288
288
|
echo "⚠ No test runner detected — skipping test suite"
|
|
289
289
|
fi
|
|
290
290
|
fi
|
|
291
|
-
#
|
|
291
|
+
# Run all tests (timeout: 5 min). #1857: normalize to one-shot so watch mode exits.
|
|
292
|
+
TEST_CMD=$(gsd_run query normalize-test-command "$TEST_CMD" --cwd . 2>/dev/null || echo "$TEST_CMD")
|
|
292
293
|
TEST_EXIT=0
|
|
293
294
|
timeout 300 bash -c "$TEST_CMD" 2>&1
|
|
294
295
|
TEST_EXIT=$?
|
|
295
296
|
if [ "${TEST_EXIT}" -eq 0 ]; then
|
|
296
297
|
echo "✓ Test suite passed"
|
|
297
298
|
elif [ "${TEST_EXIT}" -eq 124 ]; then
|
|
298
|
-
echo "⚠ Test suite timed out after 5 minutes"
|
|
299
|
+
echo "⚠ Test suite timed out after 5 minutes — likely watch/dev mode"
|
|
299
300
|
else
|
|
300
301
|
echo "✗ Test suite failed (exit code ${TEST_EXIT})"
|
|
301
302
|
fi
|
|
@@ -58,6 +58,44 @@ MVP_MODE=$(gsd_run query phase.mvp-mode "${phase_number}" ${GSD_WS} --pick activ
|
|
|
58
58
|
```
|
|
59
59
|
</step>
|
|
60
60
|
|
|
61
|
+
<step name="verify_pre_hooks">
|
|
62
|
+
**Verify:pre gate dispatch.** Before verification begins, dispatch every active
|
|
63
|
+
gate hook registered at the `verify:pre` loop extension point. Each gate is
|
|
64
|
+
data-driven — resolved from the capability registry, not hardcoded here.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
VERIFY_PRE_HOOKS_JSON=$(gsd_run loop render-hooks verify:pre --raw)
|
|
68
|
+
PHASE_DIR=$(printf '%s' "$INIT" | jq -r '.phase_dir // empty')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Resolve active gate hooks from `VERIFY_PRE_HOOKS_JSON` where `kind == "gate"`.
|
|
72
|
+
For each active gate hook, run its declared check (a `check.query` gate runs
|
|
73
|
+
`gsd_run check ${hook.check.query} "${PHASE_DIR}" --raw`; a `predicate` gate
|
|
74
|
+
runs `gsd_run check predicate --predicate '<hook.check.predicate as JSON>' --phase-dir "${PHASE_DIR}" --raw`):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
GATE_RESULT=$(gsd_run check "${hook_check_query}" "${PHASE_DIR}" --raw)
|
|
78
|
+
GATE_BLOCK=$(printf '%s' "$GATE_RESULT" | jq -r '.block // false' 2>/dev/null || echo "false")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Two-step gate contract (same as execute:wave:post / execute:post):**
|
|
82
|
+
|
|
83
|
+
- **Step 1 — command failure:** if the `gsd_run check ...` invocation itself
|
|
84
|
+
fails (non-zero exit, no JSON), route by the gate's `onError`. An `onError:
|
|
85
|
+
halt` gate HALTs; an `onError: skip` gate logs a warning and continues.
|
|
86
|
+
- **Step 2 — block evaluation:** parse `GATE_RESULT.block`. For a **blocking
|
|
87
|
+
gate** (`hook.blocking == true`) with `block == true`: HALT — do not begin UAT,
|
|
88
|
+
present the gate's `message`, and tell the user what artifact resolves it. For
|
|
89
|
+
a **non-blocking gate** with a non-empty `message`: print
|
|
90
|
+
`⚠ {hook.capId} advisory: {GATE_RESULT.message}` and continue. For any gate
|
|
91
|
+
with `block == false`: continue silently.
|
|
92
|
+
|
|
93
|
+
Example — the `ai-integration` capability's `api-coverage.verify-pre` gate
|
|
94
|
+
(when `workflow.api_coverage_gate` is on) blocks here if the phase integrates an
|
|
95
|
+
external API without a decided COVERAGE.md matrix. Present its `message` and
|
|
96
|
+
point the user at producing COVERAGE.md before re-running verification.
|
|
97
|
+
</step>
|
|
98
|
+
|
|
61
99
|
<step name="check_active_session">
|
|
62
100
|
**First: Check for active UAT sessions**
|
|
63
101
|
|