@opengsd/gsd-core 1.2.0 → 1.3.0-rc.1

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.
Files changed (92) hide show
  1. package/README.ja-JP.md +6 -5
  2. package/README.ko-KR.md +6 -5
  3. package/README.md +10 -45
  4. package/README.pt-BR.md +5 -4
  5. package/README.zh-CN.md +6 -5
  6. package/agents/gsd-ai-researcher.md +1 -1
  7. package/agents/gsd-debug-session-manager.md +1 -1
  8. package/agents/gsd-doc-writer.md +7 -6
  9. package/agents/gsd-domain-researcher.md +1 -1
  10. package/agents/gsd-eval-planner.md +1 -1
  11. package/agents/gsd-phase-researcher.md +1 -1
  12. package/agents/gsd-ui-researcher.md +1 -1
  13. package/agents/gsd-verifier.md +1 -1
  14. package/assets/gsd-logo-2000-transparent.png +0 -0
  15. package/assets/gsd-logo-2000-transparent.svg +17 -0
  16. package/assets/gsd-logo-2000.png +0 -0
  17. package/assets/gsd-logo-2000.svg +21 -0
  18. package/assets/terminal.svg +68 -0
  19. package/bin/install.js +86 -44
  20. package/commands/gsd/review.md +2 -1
  21. package/get-shit-done/bin/gsd-tools.cjs +37 -1
  22. package/get-shit-done/bin/lib/command-aliases.cjs +16 -0
  23. package/get-shit-done/bin/lib/commands.cjs +109 -5
  24. package/get-shit-done/bin/lib/config-types.cjs +19 -0
  25. package/get-shit-done/bin/lib/core.cjs +455 -63
  26. package/get-shit-done/bin/lib/gap-checker.cjs +56 -7
  27. package/get-shit-done/bin/lib/installer-migration-report.cjs +1 -0
  28. package/get-shit-done/bin/lib/milestone.cjs +59 -1
  29. package/get-shit-done/bin/lib/model-catalog.cjs +17 -0
  30. package/get-shit-done/bin/lib/phase-lifecycle.cjs +0 -1
  31. package/get-shit-done/bin/lib/review-reviewer-selection.cjs +1 -0
  32. package/get-shit-done/bin/lib/roadmap-command-router.cjs +134 -0
  33. package/get-shit-done/bin/lib/roadmap-upgrade.cjs +569 -0
  34. package/get-shit-done/bin/lib/roadmap.cjs +6 -5
  35. package/get-shit-done/bin/lib/semver-compare.cjs +34 -27
  36. package/get-shit-done/bin/lib/shell-command-projection.cjs +35 -0
  37. package/get-shit-done/bin/lib/state-document.cjs +0 -4
  38. package/get-shit-done/bin/lib/state.cjs +41 -6
  39. package/get-shit-done/bin/lib/ui-safety-gate.cjs +111 -0
  40. package/get-shit-done/bin/lib/validate.cjs +43 -17
  41. package/get-shit-done/bin/lib/verify.cjs +107 -4
  42. package/get-shit-done/bin/shared/config-defaults.manifest.json +1 -0
  43. package/get-shit-done/bin/shared/config-schema.manifest.json +12 -1
  44. package/get-shit-done/bin/shared/model-catalog.json +27 -0
  45. package/get-shit-done/references/checkpoints.md +1 -1
  46. package/get-shit-done/references/planner-human-verify-mode.md +1 -1
  47. package/get-shit-done/references/ui-brand.md +4 -2
  48. package/get-shit-done/workflows/audit-fix.md +1 -1
  49. package/get-shit-done/workflows/audit-milestone.md +2 -0
  50. package/get-shit-done/workflows/autonomous.md +7 -5
  51. package/get-shit-done/workflows/cleanup.md +43 -3
  52. package/get-shit-done/workflows/code-review-fix.md +3 -3
  53. package/get-shit-done/workflows/code-review.md +2 -0
  54. package/get-shit-done/workflows/debug.md +6 -1
  55. package/get-shit-done/workflows/diagnose-issues.md +2 -0
  56. package/get-shit-done/workflows/discuss-phase/modes/advisor.md +1 -1
  57. package/get-shit-done/workflows/discuss-phase-assumptions.md +2 -2
  58. package/get-shit-done/workflows/docs-update.md +18 -4
  59. package/get-shit-done/workflows/eval-review.md +1 -1
  60. package/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  61. package/get-shit-done/workflows/execute-phase.md +21 -11
  62. package/get-shit-done/workflows/execute-plan.md +1 -1
  63. package/get-shit-done/workflows/explore.md +2 -0
  64. package/get-shit-done/workflows/help/modes/default.md +3 -3
  65. package/get-shit-done/workflows/help/modes/full.md +5 -5
  66. package/get-shit-done/workflows/import.md +2 -0
  67. package/get-shit-done/workflows/ingest-docs.md +2 -2
  68. package/get-shit-done/workflows/manager.md +2 -2
  69. package/get-shit-done/workflows/map-codebase.md +2 -0
  70. package/get-shit-done/workflows/new-milestone.md +2 -2
  71. package/get-shit-done/workflows/new-project.md +2 -2
  72. package/get-shit-done/workflows/plan-phase.md +16 -15
  73. package/get-shit-done/workflows/plan-review-convergence.md +3 -3
  74. package/get-shit-done/workflows/quick.md +5 -3
  75. package/get-shit-done/workflows/review.md +109 -1
  76. package/get-shit-done/workflows/scan.md +2 -0
  77. package/get-shit-done/workflows/secure-phase.md +2 -0
  78. package/get-shit-done/workflows/settings-advanced.md +195 -5
  79. package/get-shit-done/workflows/settings.md +15 -1
  80. package/get-shit-done/workflows/ui-phase.md +2 -2
  81. package/get-shit-done/workflows/ui-review.md +1 -1
  82. package/get-shit-done/workflows/validate-phase.md +2 -0
  83. package/get-shit-done/workflows/verify-work.md +2 -2
  84. package/hooks/dist/gsd-worktree-path-guard.js +169 -0
  85. package/hooks/gsd-worktree-path-guard.js +169 -0
  86. package/hooks/managed-hooks-registry.cjs +1 -0
  87. package/package.json +9 -6
  88. package/scripts/build-hooks.js +1 -0
  89. package/scripts/ci-test-scope.cjs +10 -0
  90. package/scripts/run-tests.cjs +25 -1
  91. package/scripts/lint-shared-module-handsync.cjs +0 -388
  92. package/scripts/shared-module-handsync-allowlist.json +0 -183
@@ -98,7 +98,7 @@ Display startup banner:
98
98
 
99
99
  **If `has_plans` is false:**
100
100
 
101
- Display: `◆ No plans found — spawning initial planning agent...`
101
+ Display: `◆ No plans found — spawning initial planning agent... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
102
102
 
103
103
  ```text
104
104
  Agent(
@@ -134,7 +134,7 @@ prev_high_count = Infinity
134
134
 
135
135
  Increment `cycle`.
136
136
 
137
- Display: `◆ Cycle {cycle}/{MAX_CYCLES} — spawning review agent...`
137
+ Display: `◆ Cycle {cycle}/{MAX_CYCLES} — spawning review agent... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
138
138
 
139
139
  ```text
140
140
  Agent(
@@ -308,7 +308,7 @@ Exit workflow.
308
308
 
309
309
  Update `prev_high_count = HIGH_COUNT`.
310
310
 
311
- Display: `◆ Spawning replan agent with review feedback...`
311
+ Display: `◆ Spawning replan agent with review feedback... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
312
312
 
313
313
  ```text
314
314
  Agent(
@@ -392,7 +392,7 @@ Display banner:
392
392
  GSD ► RESEARCHING QUICK TASK
393
393
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
394
394
 
395
- ◆ Investigating approaches for: ${DESCRIPTION}
395
+ ◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
396
396
  ```
397
397
 
398
398
  Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
@@ -455,6 +455,8 @@ If research file not found, warn but continue: "Research agent did not produce o
455
455
 
456
456
  **If NOT `$VALIDATE_MODE`:** Use standard `quick` mode.
457
457
 
458
+ Display: `◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
459
+
458
460
  ```
459
461
  Agent(
460
462
  prompt="
@@ -518,7 +520,7 @@ Display banner:
518
520
  GSD ► CHECKING PLAN
519
521
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
520
522
 
521
- ◆ Spawning plan checker...
523
+ ◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
522
524
  ```
523
525
 
524
526
  Checker prompt:
@@ -855,7 +857,7 @@ Display banner:
855
857
  GSD ► VERIFYING RESULTS
856
858
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
857
859
 
858
- ◆ Spawning verifier...
860
+ ◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
859
861
  ```
860
862
 
861
863
  ```
@@ -23,6 +23,7 @@ command -v coderabbit >/dev/null 2>&1 && echo "coderabbit:available" || echo "co
23
23
  command -v opencode >/dev/null 2>&1 && echo "opencode:available" || echo "opencode:missing"
24
24
  command -v qwen >/dev/null 2>&1 && echo "qwen:available" || echo "qwen:missing"
25
25
  command -v cursor >/dev/null 2>&1 && echo "cursor:available" || echo "cursor:missing"
26
+ command -v agy >/dev/null 2>&1 && echo "antigravity:available" || echo "antigravity:missing"
26
27
 
27
28
  # Check local model servers (OpenAI-compatible HTTP API — no CLI binary required)
28
29
  OLLAMA_HOST=$(gsd_run query config-get review.ollama_host 2>/dev/null | jq -r '.' 2>/dev/null || echo "")
@@ -46,6 +47,7 @@ Parse flags from `$ARGUMENTS`:
46
47
  - `--opencode` → include OpenCode
47
48
  - `--qwen` → include Qwen Code
48
49
  - `--cursor` → include Cursor
50
+ - `--agy` or `--antigravity` → include Antigravity CLI
49
51
  - `--ollama` → include Ollama (local server, OpenAI-compatible)
50
52
  - `--lm-studio` → include LM Studio (local server, OpenAI-compatible)
51
53
  - `--llama-cpp` → include llama.cpp (local server, OpenAI-compatible)
@@ -73,6 +75,7 @@ No external AI CLIs found. Install at least one:
73
75
  - opencode: https://opencode.ai (leverages GitHub Copilot subscription models)
74
76
  - qwen: https://github.com/nicepkg/qwen-code (Alibaba Qwen models)
75
77
  - cursor: https://cursor.com (Cursor IDE agent mode)
78
+ - agy: curl -fsSL https://antigravity.google/cli/install.sh | bash (Antigravity CLI — free with Google credentials)
76
79
 
77
80
  Then run /gsd:review again.
78
81
  ```
@@ -218,6 +221,8 @@ GEMINI_MODEL=$(gsd_run query config-get review.models.gemini 2>/dev/null | jq -r
218
221
  CLAUDE_MODEL=$(gsd_run query config-get review.models.claude 2>/dev/null | jq -r '.' 2>/dev/null || true)
219
222
  CODEX_MODEL=$(gsd_run query config-get review.models.codex 2>/dev/null | jq -r '.' 2>/dev/null || true)
220
223
  OPENCODE_MODEL=$(gsd_run query config-get review.models.opencode 2>/dev/null | jq -r '.' 2>/dev/null || true)
224
+ # review.models.agy is reserved for future model-pinning support; agy selects its model internally
225
+ AGY_MODEL=$(gsd_run query config-get review.models.agy 2>/dev/null | jq -r '.' 2>/dev/null || true)
221
226
  ```
222
227
 
223
228
  For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
@@ -285,6 +290,103 @@ if [ ! -s /tmp/gsd-review-cursor-{phase}.md ]; then
285
290
  fi
286
291
  ```
287
292
 
293
+ **Antigravity CLI:**
294
+
295
+ **Maintainer note — why this block has three layers (last updated against agy 1.0.2):**
296
+
297
+ `agy -p` (the `--print` non-interactive flag) works correctly on macOS and Linux: it sends the
298
+ prompt, receives the model response, and writes it to stdout. On **native Windows** it silently
299
+ produces no stdout output despite the API call succeeding — a bug in `text_drip.go`'s non-TTY
300
+ flush path, tracked at https://github.com/google-antigravity/antigravity-cli/issues/27466 and
301
+ still open as of agy 1.0.2.
302
+
303
+ Regardless of platform, `agy` always persists the full exchange to a transcript file on disk.
304
+ The transcript fallback (Step 2 below) reads that file directly, giving Windows users full review
305
+ coverage without any extra tooling. This pattern was first documented by the community MCP bridge
306
+ at https://github.com/SinanTufekci/Claude-Code-Antigravity-CLI-MCP-Server — we inline the same
307
+ logic here in pure bash/jq so no additional dependency is required.
308
+
309
+ **Stale-response guard (why the pre-flight watermark matters):**
310
+ Without a watermark, the fallback would read the last `PLANNER_RESPONSE` entry in the transcript
311
+ regardless of when it was written — including entries from a previous invocation in the same
312
+ workspace. To prevent that, we record the transcript's line count *before* calling `agy -p`. In
313
+ the fallback, we only read lines appended after that count. If no new lines were written (agy
314
+ failed before producing a response), `_AGY_RESULT` is empty and Step 3 fires — never stale. If
315
+ the conv-id changed (agy started a fresh session), all lines in the new file are new and we use
316
+ skip=0.
317
+
318
+ **If the upstream stdout bug is fixed** (check the issue above): Step 2 silently becomes
319
+ unreachable; stdout is non-empty and Step 1 handles it. No code change needed.
320
+
321
+ **If the transcript paths change** in a future `agy` release: Step 2 silently becomes a no-op
322
+ and Step 3 fires with a clear error message in REVIEWS.md. No silent corruption. To debug:
323
+ - `~/.gemini/antigravity-cli/cache/last_conversations.json` — workspace → conv-id map
324
+ - `~/.gemini/antigravity-cli/brain/<id>/.system_generated/logs/transcript.jsonl`
325
+ Filter: `source=="MODEL"`, `status=="DONE"`, `type=="PLANNER_RESPONSE"`, take the last match's `content` field.
326
+
327
+ Invocation specifics (verified agy 1.0.0, macOS arm64 and Linux amd64):
328
+ - `-p` takes the prompt as a **flag value** — `echo X | agy -p` errors with "flag needs an argument: -p"
329
+ - `--print-timeout` defaults to 5m, aligning with this workflow's global timeout
330
+ - No `-m` / `--model` flag — agy selects the model internally
331
+
332
+ ```bash
333
+ # Pre-flight: snapshot the transcript watermark before invoking agy.
334
+ # Must run BEFORE agy -p — this is what prevents the fallback from reading a stale prior response.
335
+ _AGY_WS=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
336
+ _AGY_CACHE="$HOME/.gemini/antigravity-cli/cache/last_conversations.json"
337
+ _AGY_MARK_CONV=""
338
+ _AGY_MARK_LINES=0
339
+ if [ -f "$_AGY_CACHE" ]; then
340
+ _AGY_MARK_CONV=$(jq -r --arg ws "$_AGY_WS" '
341
+ .[$ws] //
342
+ (to_entries
343
+ | map(select(.key | ascii_downcase == ($ws | ascii_downcase)))
344
+ | first | .value) //
345
+ empty
346
+ ' "$_AGY_CACHE" 2>/dev/null)
347
+ if [ -n "$_AGY_MARK_CONV" ] && [ "$_AGY_MARK_CONV" != "null" ]; then
348
+ _AGY_MARK_TX="$HOME/.gemini/antigravity-cli/brain/${_AGY_MARK_CONV}/.system_generated/logs/transcript.jsonl"
349
+ [ -f "$_AGY_MARK_TX" ] && _AGY_MARK_LINES=$(wc -l < "$_AGY_MARK_TX" | tr -d ' ')
350
+ fi
351
+ fi
352
+
353
+ # Step 1 — primary invocation: stdout works on macOS, Linux, and WSL
354
+ agy -p "$(cat /tmp/gsd-review-prompt-{phase}.md)" 2>/dev/null > /tmp/gsd-review-antigravity-{phase}.md
355
+
356
+ # Step 2 — transcript fallback: catches Windows agy -p stdout bug (and any future stdout-silent edge cases).
357
+ # Reads only lines appended AFTER the pre-flight watermark. If agy failed before writing a new response,
358
+ # _AGY_RESULT is empty and Step 3 fires — no stale content can leak through.
359
+ # Undocumented paths, verified agy 1.0.0–1.0.2. See maintainer note above if these break.
360
+ if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
361
+ if [ -f "$_AGY_CACHE" ]; then
362
+ _AGY_CONV=$(jq -r --arg ws "$_AGY_WS" '
363
+ .[$ws] //
364
+ (to_entries
365
+ | map(select(.key | ascii_downcase == ($ws | ascii_downcase)))
366
+ | first | .value) //
367
+ empty
368
+ ' "$_AGY_CACHE" 2>/dev/null)
369
+ if [ -n "$_AGY_CONV" ] && [ "$_AGY_CONV" != "null" ]; then
370
+ _AGY_TX="$HOME/.gemini/antigravity-cli/brain/${_AGY_CONV}/.system_generated/logs/transcript.jsonl"
371
+ if [ -f "$_AGY_TX" ]; then
372
+ # If conv-id changed, agy started a new session — all lines are new, skip 0.
373
+ # If same conv-id, only read lines beyond the watermark.
374
+ [ "$_AGY_CONV" = "$_AGY_MARK_CONV" ] && _AGY_SKIP=$_AGY_MARK_LINES || _AGY_SKIP=0
375
+ _AGY_RESULT=$(tail -n +"$((_AGY_SKIP + 1))" "$_AGY_TX" 2>/dev/null | \
376
+ jq -r 'select(.source=="MODEL" and .status=="DONE" and .type=="PLANNER_RESPONSE") | .content' \
377
+ 2>/dev/null | tail -1)
378
+ [ -n "$_AGY_RESULT" ] && echo "$_AGY_RESULT" > /tmp/gsd-review-antigravity-{phase}.md
379
+ fi
380
+ fi
381
+ fi
382
+ fi
383
+
384
+ # Step 3 — final guard: both approaches yielded nothing (auth error, first-run setup, path schema changed, etc.)
385
+ if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
386
+ echo "Antigravity review failed or returned empty output." > /tmp/gsd-review-antigravity-{phase}.md
387
+ fi
388
+ ```
389
+
288
390
  **Ollama (local, OpenAI-compatible):**
289
391
 
290
392
  Read host and model from config. All three local backends share the same `/v1/chat/completions` endpoint — only host and model differ. Use `jq --rawfile` to safely encode the multi-line prompt as JSON without shell-escaping issues.
@@ -493,7 +595,7 @@ After all reviewers complete, collect trim metadata files written during the run
493
595
  ```markdown
494
596
  ---
495
597
  phase: {N}
496
- reviewers: [gemini, claude, codex, coderabbit, opencode, qwen, cursor, ollama, lm_studio, llama_cpp] # populate at runtime with only the reviewers actually invoked
598
+ reviewers: [gemini, claude, codex, coderabbit, opencode, qwen, cursor, antigravity, ollama, lm_studio, llama_cpp] # populate at runtime with only the reviewers actually invoked
497
599
  reviewed_at: {ISO timestamp}
498
600
  plans_reviewed: [{list of PLAN.md files}]
499
601
  trimmed_reviewers: # only present if at least one reviewer was trimmed
@@ -552,6 +654,12 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
552
654
 
553
655
  ---
554
656
 
657
+ ## Antigravity Review
658
+
659
+ {antigravity review content}
660
+
661
+ ---
662
+
555
663
  ## Ollama Review
556
664
 
557
665
  {ollama review content}
@@ -72,6 +72,8 @@ mkdir -p .planning/codebase
72
72
 
73
73
  Spawn a single `gsd-codebase-mapper` agent with the selected focus area:
74
74
 
75
+ Print: `◆ Spawning scanner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
76
+
75
77
  ```
76
78
  Agent(
77
79
  prompt="Scan this codebase with focus: {focus}. Write results to .planning/codebase/. Produce only: {document_list}",
@@ -93,6 +93,8 @@ Call AskUserQuestion with threat table and options:
93
93
  - `register_authored_at_plan_time: true` — **Verify mitigations exist** — do not scan for new threats. The register is complete; verify each threat's mitigation is present in the implementation.
94
94
  - `register_authored_at_plan_time: false` (retroactive-STRIDE mode) — **Retroactive-STRIDE: build a STRIDE register from implementation files first, then verify mitigations.** The phase was authored before formal threat modelling; the auditor must construct the register from scratch before verifying.
95
95
 
96
+ Print: `◆ Spawning security auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
97
+
96
98
  ```
97
99
  Agent(
98
100
  prompt="Read ~/.claude/agents/gsd-security-auditor.md for instructions.\n\n" +
@@ -1,12 +1,14 @@
1
1
  <purpose>
2
2
  Interactive configuration of GSD power-user knobs — plan bounce, node repair, subagent timeouts,
3
3
  inline plan threshold, cross-AI execution, base branch, branch templates, response language,
4
- context window, gitignored search, graphify build timeout, and runtime model tier overrides.
4
+ context window, gitignored search, graphify build timeout, runtime model tier overrides, and
5
+ model policy configuration (provider + budget → canonical tier mapping, or manual model ID
6
+ assignment per cost tier).
5
7
 
6
8
  This is a companion to `/gsd:settings` — the common-case prompt there covers model profile,
7
9
  research/plan_check/verifier toggles, branching strategy, UI/AI phase gates, and worktree
8
10
  isolation. This advanced command covers everything else that is user-settable, grouped into
9
- seven sections so each prompt batch stays cognitively scoped. Every answer pre-selects the
11
+ eight sections so each prompt batch stays cognitively scoped. Every answer pre-selects the
10
12
  current value; numeric-input answers that are non-numeric are rejected and re-prompted.
11
13
  </purpose>
12
14
 
@@ -81,6 +83,13 @@ Runtime Model Tiers:
81
83
  - `model_profile_overrides.<runtime>.sonnet` (default: built-in for the runtime, or absent)
82
84
  - `model_profile_overrides.<runtime>.haiku` (default: built-in for the runtime, or absent)
83
85
 
86
+ Model Policy:
87
+ - `model_policy.provider` (default: `null` — known values: anthropic, openai, google, qwen)
88
+ - `model_policy.budget` (default: `null` — known values: high, medium, low)
89
+ - `model_policy.high` (default: `null` — model ID for the high-cost tier; used by generic provider path)
90
+ - `model_policy.medium` (default: `null` — model ID for the medium-cost tier; used by generic provider path)
91
+ - `model_policy.low` (default: `null` — model ID for the low-cost tier; used by generic provider path)
92
+
84
93
  Each field's **current value is pre-selected** in the prompt rendering below. When the
85
94
  current value is absent from the config, render the documented default as the pre-selected
86
95
  option so the user sees what the effective value is.
@@ -500,7 +509,7 @@ gsd_run query config-set model_profile_overrides.gemini.haiku null
500
509
 
501
510
  Conceptual shape after merge (unchanged top-level keys like `model_profile`,
502
511
  `granularity`, `mode`, `brave_search`, `agent_skills.*`, `hooks.context_warnings`, and
503
- anything not listed in Sections 1–7 MUST survive the update):
512
+ anything not listed in Sections 1–8 MUST survive the update):
504
513
 
505
514
  ```json
506
515
  {
@@ -542,6 +551,14 @@ anything not listed in Sections 1–7 MUST survive the update):
542
551
  "sonnet": <new|existing|null>,
543
552
  "haiku": <new|existing|null>
544
553
  }
554
+ },
555
+ "model_policy": {
556
+ ...existing_model_policy,
557
+ "provider": <new|existing|null>,
558
+ "budget": <new|existing|null>,
559
+ "high": <new|existing|null>,
560
+ "medium": <new|existing|null>,
561
+ "low": <new|existing|null>
545
562
  }
546
563
  }
547
564
  ```
@@ -551,6 +568,170 @@ route each write through `gsd-tools.cjs query config-set` so sibling preservatio
551
568
  the central setter.
552
569
  </step>
553
570
 
571
+ ### Section 8 — Model Policy
572
+
573
+ This section configures the `model_policy` key in `.planning/config.json`. Model policy
574
+ defines which AI models GSD uses at each cost tier (low / medium / high), independently
575
+ of the `runtime` and `model_profile` selections above. Two paths are offered:
576
+
577
+ - **Known provider:** choose a provider and a budget level; GSD materializes the canonical
578
+ tier mapping for that provider.
579
+ - **Generic provider:** enter low / medium / high model IDs manually.
580
+
581
+ **Step A — Read and display the current model policy:**
582
+
583
+ ```bash
584
+ cat "$GSD_CONFIG_PATH" | python3 -c "import sys,json; c=json.load(sys.stdin); mp=c.get('model_policy',{}); print(json.dumps(mp,indent=2))" 2>/dev/null || echo "{}"
585
+ ```
586
+
587
+ Display the current values (or "(unset)" for any absent field) before asking:
588
+
589
+ ```text
590
+ Current model_policy:
591
+ provider : <value or "(unset)">
592
+ budget : <value or "(unset)">
593
+ low : <value or "(unset)">
594
+ medium : <value or "(unset)">
595
+ high : <value or "(unset)">
596
+ ```
597
+
598
+ **Step B — Choose configuration path:**
599
+
600
+ ```text
601
+ AskUserQuestion([
602
+ {
603
+ question: "How do you want to configure the model policy?",
604
+ header: "Model Policy",
605
+ multiSelect: false,
606
+ options: [
607
+ { label: "Known provider", description: "Choose a provider (Claude / OpenAI / Gemini / Qwen) and a budget level — GSD writes the canonical tier mapping automatically." },
608
+ { label: "Generic provider", description: "Enter low / medium / high model IDs manually for any provider or custom deployment." },
609
+ { label: "Keep current", description: "Leave model_policy unchanged." }
610
+ ]
611
+ }
612
+ ])
613
+ ```
614
+
615
+ **If "Keep current" is selected:** skip Steps C–E and move on to the confirm step.
616
+
617
+ **Step C — Known-provider path:**
618
+
619
+ ```text
620
+ AskUserQuestion([
621
+ {
622
+ question: "Which provider?",
623
+ header: "Provider",
624
+ multiSelect: false,
625
+ options: [
626
+ { label: "anthropic", description: "claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5 (Anthropic / Claude)" },
627
+ { label: "openai", description: "gpt-5.5 / gpt-5.3-codex / gpt-5.4-mini (OpenAI / Codex)" },
628
+ { label: "google", description: "gemini-3-pro / gemini-3-flash / gemini-2.5-flash-lite (Google Gemini)" },
629
+ { label: "qwen", description: "qwen3-max-2026-01-23 / qwen3-coder-plus / qwen3-coder-next (Qwen)" }
630
+ ]
631
+ }
632
+ ])
633
+ ```
634
+
635
+ After the user picks a provider, ask:
636
+
637
+ ```text
638
+ AskUserQuestion([
639
+ {
640
+ question: "Which budget level?",
641
+ header: "Budget",
642
+ multiSelect: false,
643
+ options: [
644
+ { label: "high", description: "All tiers use the highest-quality model for the chosen provider. Highest cost." },
645
+ { label: "medium", description: "High tier → top model; medium → mid model; low → cheapest model. Best cost/quality ratio." },
646
+ { label: "low", description: "All tiers use the cheapest model for the chosen provider. Lowest cost." }
647
+ ]
648
+ }
649
+ ])
650
+ ```
651
+
652
+ Canonical tier mappings by provider and budget:
653
+
654
+ | Provider | Budget | high | medium | low |
655
+ |-----------|--------|----------------------------|----------------------------|----------------------------|
656
+ | anthropic | high | claude-opus-4-8 | claude-opus-4-8 | claude-opus-4-8 |
657
+ | anthropic | medium | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 |
658
+ | anthropic | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 |
659
+ | openai | high | gpt-5.5 | gpt-5.5 | gpt-5.5 |
660
+ | openai | medium | gpt-5.5 | gpt-5.3-codex | gpt-5.4-mini |
661
+ | openai | low | gpt-5.4-mini | gpt-5.4-mini | gpt-5.4-mini |
662
+ | google | high | gemini-3-pro | gemini-3-pro | gemini-3-pro |
663
+ | google | medium | gemini-3-pro | gemini-3-flash | gemini-2.5-flash-lite |
664
+ | google | low | gemini-2.5-flash-lite | gemini-2.5-flash-lite | gemini-2.5-flash-lite |
665
+ | qwen | high | qwen3-max-2026-01-23 | qwen3-max-2026-01-23 | qwen3-max-2026-01-23 |
666
+ | qwen | medium | qwen3-max-2026-01-23 | qwen3-coder-plus | qwen3-coder-next |
667
+ | qwen | low | qwen3-coder-next | qwen3-coder-next | qwen3-coder-next |
668
+
669
+ Look up the selected (provider, budget) row and proceed to Step E to write those values.
670
+
671
+ **Step D — Generic-provider path:**
672
+
673
+ Prompt the user to enter each model ID as a free-text input. An empty input means "keep
674
+ the current value for that tier." Validate that non-empty inputs are non-blank strings
675
+ (no whitespace-only values); if validation fails, re-prompt that single field.
676
+
677
+ ```text
678
+ AskUserQuestion([
679
+ {
680
+ question: "Model ID for the HIGH-cost tier? (most capable model — used for heavy reasoning tasks)",
681
+ header: "High-tier model",
682
+ multiSelect: false,
683
+ options: [
684
+ { label: "Keep current", description: "Leave unchanged (current: <model_policy.high or '(unset)'>)." },
685
+ { label: "Enter model ID", description: "Type the exact model identifier. Non-blank string required." }
686
+ ]
687
+ },
688
+ {
689
+ question: "Model ID for the MEDIUM-cost tier? (balanced model — used for most agents)",
690
+ header: "Medium-tier model",
691
+ multiSelect: false,
692
+ options: [
693
+ { label: "Keep current", description: "Leave unchanged (current: <model_policy.medium or '(unset)'>)." },
694
+ { label: "Enter model ID", description: "Type the exact model identifier." }
695
+ ]
696
+ },
697
+ {
698
+ question: "Model ID for the LOW-cost tier? (cheapest model — used for lightweight/fast tasks)",
699
+ header: "Low-tier model",
700
+ multiSelect: false,
701
+ options: [
702
+ { label: "Keep current", description: "Leave unchanged (current: <model_policy.low or '(unset)'>)." },
703
+ { label: "Enter model ID", description: "Type the exact model identifier." }
704
+ ]
705
+ }
706
+ ])
707
+ ```
708
+
709
+ Set `provider = "custom"` and `budget = null` when writing the generic-provider result.
710
+ Proceed to Step E.
711
+
712
+ **Step E — Write model_policy to config:**
713
+
714
+ ```bash
715
+ # Known-provider path — write all four keys atomically:
716
+ gsd_run query config-set model_policy.provider "<provider>" # e.g., anthropic / openai / google / qwen
717
+ gsd_run query config-set model_policy.budget "<budget>" # high / medium / low
718
+ gsd_run query config-set model_policy.high "<high-id>"
719
+ gsd_run query config-set model_policy.medium "<medium-id>"
720
+ gsd_run query config-set model_policy.low "<low-id>"
721
+
722
+ # Generic-provider path — write only tiers the user changed ("Keep current" skipped):
723
+ gsd_run query config-set model_policy.provider "custom"
724
+ gsd_run query config-set model_policy.budget null
725
+ # Per-tier writes for each non-"Keep current" answer:
726
+ gsd_run query config-set model_policy.high "<high-id>" # omit if user chose "Keep current"
727
+ gsd_run query config-set model_policy.medium "<medium-id>" # omit if user chose "Keep current"
728
+ gsd_run query config-set model_policy.low "<low-id>" # omit if user chose "Keep current"
729
+ ```
730
+
731
+ Never write a tier the user explicitly chose to keep; the existing value must survive.
732
+
733
+ </step>
734
+
554
735
  <step name="confirm">
555
736
  Display:
556
737
 
@@ -594,6 +775,11 @@ Display:
594
775
  | fast_mode.routing_tier_defaults.standard | {true/false} |
595
776
  | fast_mode.routing_tier_defaults.heavy | {true/false} |
596
777
  | fast_mode.agent_overrides.<agent-id> | {true/false} |
778
+ | model_policy.provider | {anthropic/openai/google/qwen/custom/null} |
779
+ | model_policy.budget | {high/medium/low/null} |
780
+ | model_policy.high | {model-id/null} |
781
+ | model_policy.medium | {model-id/null} |
782
+ | model_policy.low | {model-id/null} |
597
783
 
598
784
  These settings apply to future /gsd:plan-phase, /gsd:execute-phase, /gsd:discuss-phase,
599
785
  and /gsd:ship runs.
@@ -607,7 +793,7 @@ UI/AI phase gates), use /gsd:settings.
607
793
 
608
794
  <success_criteria>
609
795
  - [ ] Current config read from resolved `$GSD_CONFIG_PATH`
610
- - [ ] Seven sections rendered (Planning, Execution, Discussion, Cross-AI, Git, Runtime/Output, Runtime Model Tiers)
796
+ - [ ] Eight sections rendered (Planning, Execution, Discussion, Cross-AI, Git, Runtime/Output, Runtime Model Tiers, Model Policy)
611
797
  - [ ] Every field pre-selected to its current value (or documented default if absent)
612
798
  - [ ] Numeric inputs validated — non-numeric rejected and re-prompted
613
799
  - [ ] Branch-template inputs validated — non-default must contain a placeholder
@@ -616,5 +802,9 @@ UI/AI phase gates), use /gsd:settings.
616
802
  - [ ] Section 7 shows current runtime and built-in tier table
617
803
  - [ ] Group B runtimes display "(no built-in default — your runtime handles model selection)"
618
804
  - [ ] Override set/clear/keep paths all work correctly for each tier
619
- - [ ] Confirmation table rendered listing all 23 fields (19 + runtime + 3 tier overrides)
805
+ - [ ] Section 8 (Model Policy) offers three top-level choices: Known provider, Generic provider, Keep current
806
+ - [ ] Known-provider path: provider + budget → canonical tier mapping written to model_policy.{provider,budget,high,medium,low}
807
+ - [ ] Generic-provider path: per-tier manual model IDs; "Keep current" tiers are never written; provider=custom budget=null
808
+ - [ ] model_policy written under the model_policy key in config.json, never as a top-level flat key
809
+ - [ ] Confirmation table rendered listing all fields including model_policy.{provider,budget,high,medium,low}
620
810
  </success_criteria>
@@ -57,6 +57,11 @@ Parse current values (default to `true` if not present):
57
57
  - `model_profile` — which model each agent uses (default: `balanced`)
58
58
  - `git.branching_strategy` — branching approach (default: `"none"`)
59
59
  - `workflow.use_worktrees` — whether parallel executor agents run in worktree isolation (default: `true`)
60
+ - `model_policy.provider` — provider slug for model policy (default: `null`; known values: anthropic, openai, google, qwen; set via /gsd:config --advanced)
61
+ - `model_policy.budget` — budget level for model policy (default: `null`; known values: high, medium, low; set via /gsd:config --advanced)
62
+ - `model_policy.high` — model ID for high-cost tier (default: `null`; set via /gsd:config --advanced)
63
+ - `model_policy.medium` — model ID for medium-cost tier (default: `null`; set via /gsd:config --advanced)
64
+ - `model_policy.low` — model ID for low-cost tier (default: `null`; set via /gsd:config --advanced)
60
65
  </step>
61
66
 
62
67
  <step name="present_settings">
@@ -423,6 +428,15 @@ Merge new settings into existing config.json:
423
428
  "hooks": {
424
429
  "context_warnings": true/false,
425
430
  "workflow_guard": true/false
431
+ },
432
+ "model_policy": {
433
+ // Read-only in this flow — written only by /gsd:config --advanced (Section 8).
434
+ // Listed here so safe-merge never clobbers an existing model_policy object.
435
+ "provider": <existing|null>,
436
+ "budget": <existing|null>,
437
+ "high": <existing|null>,
438
+ "medium": <existing|null>,
439
+ "low": <existing|null>
426
440
  }
427
441
  }
428
442
  ```
@@ -537,7 +551,7 @@ Quick commands:
537
551
  - /gsd:plan-phase --research — force research
538
552
  - /gsd:plan-phase --skip-research — skip research
539
553
  - /gsd:plan-phase --skip-verify — skip plan check
540
- - /gsd:config --advanced — power-user tuning (plan bounce, timeouts, branch templates, cross-AI, context window)
554
+ - /gsd:config --advanced — power-user tuning (plan bounce, timeouts, branch templates, cross-AI, context window, model policy)
541
555
  ```
542
556
  </step>
543
557
 
@@ -118,7 +118,7 @@ Display:
118
118
  GSD ► UI DESIGN CONTRACT — PHASE {N}
119
119
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120
120
 
121
- ◆ Spawning UI researcher...
121
+ ◆ Spawning UI researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
122
122
  ```
123
123
 
124
124
  Build prompt:
@@ -183,7 +183,7 @@ Display:
183
183
  GSD ► VERIFYING UI-SPEC
184
184
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
185
185
 
186
- ◆ Spawning UI checker...
186
+ ◆ Spawning UI checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
187
187
  ```
188
188
 
189
189
  Build prompt:
@@ -68,7 +68,7 @@ Build file list for auditor:
68
68
  ## 3. Spawn gsd-ui-auditor
69
69
 
70
70
  ```
71
- ◆ Spawning UI auditor...
71
+ ◆ Spawning UI auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
72
72
  ```
73
73
 
74
74
  Build prompt:
@@ -93,6 +93,8 @@ Call AskUserQuestion with gap table and options:
93
93
 
94
94
  ## 5. Spawn gsd-nyquist-auditor
95
95
 
96
+ Print: `◆ Spawning nyquist auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
97
+
96
98
  ```
97
99
  Agent(
98
100
  prompt="Read ~/.claude/agents/gsd-nyquist-auditor.md for instructions.\n\n" +
@@ -552,7 +552,7 @@ Display:
552
552
  GSD ► PLANNING FIXES
553
553
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
554
554
 
555
- ◆ Spawning planner for gap closure...
555
+ ◆ Spawning planner for gap closure... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
556
556
  ```
557
557
 
558
558
  Spawn gsd-planner in --gaps mode:
@@ -602,7 +602,7 @@ Display:
602
602
  GSD ► VERIFYING FIX PLANS
603
603
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
604
604
 
605
- ◆ Spawning plan checker...
605
+ ◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
606
606
  ```
607
607
 
608
608
  Initialize: `iteration_count = 1`