* chore(#604): rename get-shit-done/ runtime directory to gsd-core/ Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary (`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers are unaffected. Mechanical (bulk, ~90% of the diff): - `git mv get-shit-done gsd-core` - Swept path/identifier references across the repo via `perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead preserves the five legitimate slug variants that are NOT the directory: get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names). - Build/manifest wiring: package.json (bin, files, coverage globs), tsconfig.build.json (outDir), ~86 .gitignore build-output entries, stryker.config.mjs, scan-ignore files, install.js path strings. - Frozen (not rewritten): CHANGELOG.md history; translated docs (README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/). New logic (review here): - src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper ADR-0008 installer migration. On upgrade it walks the legacy `~/.claude/get-shit-done/` tree, classifies each file via the prior install manifest, and emits remove-managed / backup-and-remove for managed files while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked root and symlinked entries; bounds-checks every path under configDir). The framework rolls back on install failure. Emptied dirs may remain (framework has no recursive dir-removal primitive) — documented. - scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare `get-shit-done` directory token (split token to avoid self-match; case- insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines). Wired into the lint-tests CI job. - Restored scripts/lint-package-identity-drift.cjs detection regexes (the mechanical sweep had wrongly rewritten the old-name patterns it exists to detect) and marked them as intentional legacy references. - TDD tests for the migration and the guard; do.md slash-command guard regex tightened so a `/gsd-core/bin` path segment is not mistaken for a command; changeset + docs/installer-migrations.md row added. Breaking: the installed runtime path moves `~/.claude/get-shit-done/` -> `~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed files (preserving user files) on upgrade. Users with custom hooks/configs hardcoding the old path must update them. Closes #604 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): unsweep pending changesets + allowlist injection-example docs CI fixes for the rename PR: - Do not sweep pending .changeset/*.md (ephemeral release-note fragments, like CHANGELOG); reverted those body edits so 5 pre-existing malformed fragments (missing type/pr) no longer enter the PR diff and trip docs-lint. Allowlisted .changeset/ in the legacy-name guard accordingly. - Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in prompt-injection-scan.sh: they contain intentional injection examples / security-model prose; the path-reference rewrites are kept. CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR; none in the new migration/guard) and are out of scope for the rename. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): resolve CodeQL alerts surfaced on this PR The rename diff touched files carrying pre-existing CodeQL findings; per the no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving them off. All behavior-preserving: - scripts/ci-test-scope.cjs: build the config-path match from string .includes() instead of a RegExp over an arg-derived value (js/regex-injection). - src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName so the table-cell escape is complete (js/incomplete-sanitization). - tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization). - tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace, keep the meaningful POSIX-class conversion (js/identity-replacement). Verified: build:lib green; the touched test files + ci-test-scope + profile-output suites pass; lint:legacy-name clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization) The prior commit's fixes for two alerts were ineffective: - ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file` reaching static regex `.test(file)` calls (not the config rule). Removed ALL regex over file/t — startsWith/includes/=== string checks + an isWindowsHint helper — so there is no regex sink for the tainted value. - js/incomplete-multi-character-sanitization (3 test files): a single `.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint loop (replace until stable) plus a final bare-opener strip. Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass; lint:legacy-name clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL CodeQL flags the regex PATTERNS syntactically (regex-injection on the --files arg split; incomplete-multi-character-sanitization on the <!--...--> replace), so loop fixes do not satisfy it. Made these paths regex-free: - ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/). - 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)). Behavior preserved; ci-test-scope + the 3 suites pass; guard clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): unblock security base64 scan on the large rename diff The security job hit its 10m timeout: base64-scan.sh choked on the binary test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/ non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings), and the ~800-file rename diff is slow to scan regardless. - scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they can't carry base64-obfuscated *text* and feeding NUL bytes through the per-line scanner is pathologically slow. collect_files already filtered binary *extensions*; this catches binary *content* in text extensions. - .github/workflows/security-scan.yml: raise the security job timeout 10m->30m to accommodate very large diffs (the scan itself is unchanged). Verified locally: scan skips the fixture, 0 "ignored null byte" warnings, 0 findings, exit 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): sweep get-shit-done refs introduced by merging next The branch was updated with next (#614/#384/#618 etc.), which reference the get-shit-done/ dir (still named that on next). Swept the stale references in the merged files to gsd-core so the rename stays consistent and lint:legacy-name passes: - commands/gsd/discuss-phase.md (runtime-launcher shim paths) - src/core.cts (getAgentsDir layout comments) - tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib) Verified: guard 0 violations; build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant The #614 runtime-launcher shim added to discuss-phase.md references `${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it mis-read the directory path as a dangling `/gsd-core` command ref (same class as the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path segments are not treated as slash-command references. Verified locally on BOTH platforms before pushing: - mac (node 26) full suite: 0 failures - gsd-test-runner (linux, node22 image) full suite: 0 failures - bug-3683 + bug-2954 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI) CI intermittently failed state.test's gsd-tools subprocess with "findProjectRoot is not a function" (flip-flopping across legs; not reproducible on mac full suite, gsd-test linux full suite, test:unit, or state.test x8). findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs); binding it via destructure at module-load can be undefined under a load-ordering edge. Resolve it lazily at call time via a small wrapper so the lookup happens after core.cjs is fully initialized. Verified green on BOTH platforms before pushing: - mac (node 26) full suite: 0 failures - gsd-test-runner (linux, node22) full suite: 0 failures - state.test.cjs: 106/106; gsd-tools loads cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#604): allowlist verification-patterns.md placeholder examples in secret scan The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var examples (illustrative Stripe test-key / database-URL / API-key placeholders) — not real credentials. Added it to .secretscanignore with the strict annotation, mirroring the existing gsd-core/workflows/plan-phase.md exception. Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next exits 0 with 0 findings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
810
gsd-core/workflows/settings-advanced.md
Normal file
810
gsd-core/workflows/settings-advanced.md
Normal file
@@ -0,0 +1,810 @@
|
||||
<purpose>
|
||||
Interactive configuration of GSD power-user knobs — plan bounce, node repair, subagent timeouts,
|
||||
inline plan threshold, cross-AI execution, base branch, branch templates, response language,
|
||||
context window, gitignored search, graphify build timeout, runtime model tier overrides, and
|
||||
model policy configuration (provider + budget → canonical tier mapping, or manual model ID
|
||||
assignment per cost tier).
|
||||
|
||||
This is a companion to `/gsd:settings` — the common-case prompt there covers model profile,
|
||||
research/plan_check/verifier toggles, branching strategy, UI/AI phase gates, and worktree
|
||||
isolation. This advanced command covers everything else that is user-settable, grouped into
|
||||
eight sections so each prompt batch stays cognitively scoped. Every answer pre-selects the
|
||||
current value; numeric-input answers that are non-numeric are rejected and re-prompted.
|
||||
</purpose>
|
||||
|
||||
<required_reading>
|
||||
Read all files referenced by the invoking prompt's execution_context before starting.
|
||||
</required_reading>
|
||||
|
||||
<process>
|
||||
|
||||
<step name="ensure_and_load_config">
|
||||
Ensure config exists and resolve the workstream-aware config path (mirrors `settings.md`):
|
||||
|
||||
```bash
|
||||
_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 command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/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
|
||||
gsd_run query config-ensure-section
|
||||
if [[ -z "${GSD_CONFIG_PATH:-}" ]]; then
|
||||
if [[ -f .planning/active-workstream ]]; then
|
||||
WS=$(tr -d '\n\r' < .planning/active-workstream)
|
||||
GSD_CONFIG_PATH=".planning/workstreams/${WS}/config.json"
|
||||
else
|
||||
GSD_CONFIG_PATH=".planning/config.json"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
All subsequent reads and writes go through `$GSD_CONFIG_PATH`. Never hardcode
|
||||
`.planning/config.json` — workstream installs must route to their own config file.
|
||||
</step>
|
||||
|
||||
<step name="read_current">
|
||||
```bash
|
||||
cat "$GSD_CONFIG_PATH"
|
||||
```
|
||||
|
||||
Parse the following current values. If a key is absent, fall back to the documented default
|
||||
shown in parentheses:
|
||||
|
||||
Planning Tuning:
|
||||
- `workflow.plan_bounce` (default: `false`)
|
||||
- `workflow.plan_bounce_passes` (default: `2`)
|
||||
- `workflow.plan_bounce_script` (default: `null`)
|
||||
- `workflow.subagent_timeout` (default: `600`)
|
||||
- `workflow.inline_plan_threshold` (default: `3`)
|
||||
|
||||
Execution Tuning:
|
||||
- `workflow.node_repair` (default: `true`)
|
||||
- `workflow.node_repair_budget` (default: `2`)
|
||||
- `workflow.auto_prune_state` (default: `false`)
|
||||
|
||||
Discussion Tuning:
|
||||
- `workflow.max_discuss_passes` (default: `3`)
|
||||
|
||||
Cross-AI Execution:
|
||||
- `workflow.cross_ai_execution` (default: `false`)
|
||||
- `workflow.cross_ai_command` (default: `null`)
|
||||
- `workflow.cross_ai_timeout` (default: `300`)
|
||||
|
||||
Git Customization:
|
||||
- `git.base_branch` (default: `main`)
|
||||
- `git.phase_branch_template` (default: `gsd/phase-{phase}-{slug}`)
|
||||
- `git.milestone_branch_template` (default: `gsd/{milestone}-{slug}`)
|
||||
|
||||
Runtime / Output:
|
||||
- `response_language` (default: `null`)
|
||||
- `context_window` (default: `200000`)
|
||||
- `search_gitignored` (default: `false`)
|
||||
- `graphify.build_timeout` (default: `300`)
|
||||
|
||||
Runtime Model Tiers:
|
||||
- `runtime` (default: `null` — reads as `"claude"`)
|
||||
- `model_profile_overrides.<runtime>.opus` (default: built-in for the runtime, or absent)
|
||||
- `model_profile_overrides.<runtime>.sonnet` (default: built-in for the runtime, or absent)
|
||||
- `model_profile_overrides.<runtime>.haiku` (default: built-in for the runtime, or absent)
|
||||
|
||||
Model Policy:
|
||||
- `model_policy.provider` (default: `null` — known values: anthropic, openai, google, qwen)
|
||||
- `model_policy.budget` (default: `null` — known values: high, medium, low)
|
||||
- `model_policy.high` (default: `null` — model ID for the high-cost tier; used by generic provider path)
|
||||
- `model_policy.medium` (default: `null` — model ID for the medium-cost tier; used by generic provider path)
|
||||
- `model_policy.low` (default: `null` — model ID for the low-cost tier; used by generic provider path)
|
||||
|
||||
Each field's **current value is pre-selected** in the prompt rendering below. When the
|
||||
current value is absent from the config, render the documented default as the pre-selected
|
||||
option so the user sees what the effective value is.
|
||||
</step>
|
||||
|
||||
<step name="present_settings">
|
||||
|
||||
**Text mode (`workflow.text_mode: true` or `--text` flag):** Set `TEXT_MODE=true` if `--text` is
|
||||
in `$ARGUMENTS` OR `text_mode` is true in config. When `TEXT_MODE=true`, replace every
|
||||
`AskUserQuestion` call below with a plain-text numbered list and ask the user to type the
|
||||
choice number or free-text value.
|
||||
|
||||
**Numeric-input validation.** For any numeric field (`*_passes`, `*_budget`, `*_timeout`,
|
||||
`*_threshold`, `context_window`, `graphify.build_timeout`), if the user types a value that
|
||||
is not a non-negative integer, the workflow MUST reject it, state which value was invalid,
|
||||
and re-prompt that single field. The minimum accepted value is field-specific and is stated
|
||||
in each field's prompt below — `workflow.plan_bounce_passes` and `workflow.max_discuss_passes`
|
||||
require `>= 1`; all other numeric fields accept `>= 0`. An empty input means "keep current"
|
||||
— the existing value is retained. Non-numeric input is never silently coerced.
|
||||
|
||||
**Free-text validation.** For branch template fields (`git.phase_branch_template`,
|
||||
`git.milestone_branch_template`), if the user supplies a non-default value, it MUST be
|
||||
non-empty and SHOULD contain at least one `{placeholder}`. A template missing placeholders
|
||||
is rejected with a message explaining the available variables (`{phase}`, `{slug}`,
|
||||
`{milestone}`) and re-prompted. An empty input means "keep current."
|
||||
|
||||
**Null-allowed fields.** For `response_language`, `workflow.plan_bounce_script`,
|
||||
`workflow.cross_ai_command`: an empty input clears the field (`null`). A non-empty input is
|
||||
stored verbatim as a string.
|
||||
|
||||
---
|
||||
|
||||
### Section 1 — Planning Tuning
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Run external plan-bounce validator against generated PLAN.md? (current: <value or false>)",
|
||||
header: "Plan Bounce",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "No (default: false)", description: "Skip external plan validation." },
|
||||
{ label: "Yes", description: "Pipe each PLAN.md through `plan_bounce_script` and block on non-zero exit." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "How many plan-bounce passes? (current: <value or 2>)",
|
||||
header: "Bounce Passes",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave the existing value unchanged." },
|
||||
{ label: "Enter number", description: "Type an integer >= 1. Non-numeric input is rejected and re-prompted. Default: 2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Path to plan-bounce validation script? (current: <value or null>)",
|
||||
header: "Bounce Script",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave existing path unchanged." },
|
||||
{ label: "Clear (null)", description: "Unset the script path." },
|
||||
{ label: "Enter path", description: "Type an absolute or repo-relative path. Receives PLAN.md path as first argument." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Subagent timeout (seconds)? (current: <value or 600>)",
|
||||
header: "Subagent Timeout",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave timeout unchanged." },
|
||||
{ label: "Enter seconds", description: "Integer number of seconds. Non-numeric rejected. Default: 600" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Inline plan threshold — tasks allowed inline before splitting to PLAN.md? (current: <value or 3>)",
|
||||
header: "Inline Plan Threshold",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave threshold unchanged." },
|
||||
{ label: "Enter number", description: "Integer count. Non-numeric rejected. Default: 3" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### Section 2 — Execution Tuning
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Enable autonomous node repair on verification failure? (current: <value or true>)",
|
||||
header: "Node Repair",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (default: true)", description: "Executor retries failed tasks up to the repair budget." },
|
||||
{ label: "No", description: "Stop on first verification failure." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Maximum node-repair attempts per failed task? (current: <value or 2>)",
|
||||
header: "Repair Budget",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave existing budget unchanged." },
|
||||
{ label: "Enter number", description: "Integer >= 0. Non-numeric rejected. Default: 2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Auto-prune stale STATE.md entries at phase boundaries? (current: <value or false>)",
|
||||
header: "Auto Prune",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "No (default: false)", description: "Prompt before pruning." },
|
||||
{ label: "Yes", description: "Prune stale entries without prompting." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### Section 3 — Discussion Tuning
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Maximum discuss-phase question rounds? (current: <value or 3>)",
|
||||
header: "Max Discuss Passes",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave existing value unchanged." },
|
||||
{ label: "Enter number", description: "Integer >= 1. Non-numeric rejected. Default: 3. Prevents infinite discussion loops in headless mode." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### Section 4 — Cross-AI Execution
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Delegate phase execution to an external AI CLI? (current: <value or false>)",
|
||||
header: "Cross-AI",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "No (default: false)", description: "Use local executor agents." },
|
||||
{ label: "Yes", description: "Pipe phase prompt to `cross_ai_command` via stdin. Requires command to be set." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Cross-AI command template? (current: <value or null>)",
|
||||
header: "Cross-AI Command",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave command unchanged." },
|
||||
{ label: "Clear (null)", description: "Unset the command." },
|
||||
{ label: "Enter command", description: "Shell command receiving phase prompt via stdin. Must produce SUMMARY.md-compatible output." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Cross-AI timeout (seconds)? (current: <value or 300>)",
|
||||
header: "Cross-AI Timeout",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave timeout unchanged." },
|
||||
{ label: "Enter seconds", description: "Integer seconds. Non-numeric rejected. Default: 300" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### Section 5 — Git Customization
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Git base branch? (current: <value or main>)",
|
||||
header: "Base Branch",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave base branch unchanged." },
|
||||
{ label: "Enter branch name", description: "e.g., main, master, develop. Integration branch for phase/milestone branches." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Phase branch template? (current: <value or gsd/phase-{phase}-{slug}>)",
|
||||
header: "Phase Template",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave template unchanged." },
|
||||
{ label: "Enter template", description: "Non-empty string with at least one placeholder. Available: {phase}, {slug}. Non-default values missing placeholders are rejected." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Milestone branch template? (current: <value or gsd/{milestone}-{slug}>)",
|
||||
header: "Milestone Template",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave template unchanged." },
|
||||
{ label: "Enter template", description: "Non-empty string. Available placeholders: {milestone}, {slug}. Non-default values missing placeholders are rejected." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### Section 6 — Runtime / Output
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Response language for agent output? (current: <value or null>)",
|
||||
header: "Language",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged." },
|
||||
{ label: "Clear (null)", description: "Use Claude default (English)." },
|
||||
{ label: "Enter language", description: "Free-text language name or code (e.g., Japanese, pt, ko). Propagates to spawned agents." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Context window size (tokens)? (current: <value or 200000>)",
|
||||
header: "Context Window",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged." },
|
||||
{ label: "Enter number", description: "Integer. Non-numeric rejected. Default: 200000. Use 1000000 for 1M-context models. Values >= 500000 enable adaptive enrichment." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Include gitignored files in broad searches? (current: <value or false>)",
|
||||
header: "Search Gitignored",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "No (default: false)", description: "Respect .gitignore during searches." },
|
||||
{ label: "Yes", description: "Add --no-ignore to broad searches (includes .planning/)." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Graphify build timeout (seconds)? (current: <value or 300>)",
|
||||
header: "Graphify Timeout",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave timeout unchanged." },
|
||||
{ label: "Enter seconds", description: "Integer seconds. Non-numeric rejected. Default: 300" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
### Section 7 — Runtime Model Tiers
|
||||
|
||||
This section lets the user inspect and override the built-in model IDs GSD resolves for each
|
||||
profile tier (`opus` / `sonnet` / `haiku`) on their configured runtime.
|
||||
|
||||
**Step A — Show current runtime and built-in defaults:**
|
||||
|
||||
Read `runtime` from the config (or treat as `"claude"` if absent). Look up the built-in
|
||||
tier map from the table below. For each tier, also read the current override from
|
||||
`model_profile_overrides.<runtime>.<tier>` if present.
|
||||
|
||||
Built-in tier defaults by runtime:
|
||||
|
||||
| Runtime | `opus` | `sonnet` | `haiku` |
|
||||
|------------|-------------------------------|---------------------------------|-------------------------------|
|
||||
| `claude` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` |
|
||||
| `codex` | `gpt-5.5` | `gpt-5.3-codex` | `gpt-5.4-mini` |
|
||||
| `gemini` | `gemini-3-pro` | `gemini-3-flash` | `gemini-2.5-flash-lite` |
|
||||
| `qwen` | `qwen3-max-2026-01-23` | `qwen3-coder-plus` | `qwen3-coder-next` |
|
||||
| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` |
|
||||
| `copilot` | `claude-opus-4-8` | `claude-sonnet-4-6` | `claude-haiku-4-5` |
|
||||
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-4-6` | `anthropic/claude-haiku-4-5` |
|
||||
| Group B (`kilo`, `cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
||||
|
||||
Display a table to the user showing the effective configuration:
|
||||
|
||||
```text
|
||||
Runtime model tiers — runtime: <current runtime or "claude (default)">
|
||||
|
||||
| Tier | Built-in default | Current override (if any) |
|
||||
|--------|-----------------------------------|-----------------------------------|
|
||||
| opus | <built-in or "(no built-in)"> | <override value or "(none)"> |
|
||||
| sonnet | <built-in or "(no built-in)"> | <override value or "(none)"> |
|
||||
| haiku | <built-in or "(no built-in)"> | <override value or "(none)"> |
|
||||
```
|
||||
|
||||
For Group B runtimes (those without a built-in default), show `(no built-in default — your runtime handles model selection)` in the built-in column.
|
||||
|
||||
**Step B — Let the user choose a runtime (optional):**
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Which runtime group do you want to configure tier overrides for? (current: <runtime or 'claude'>)",
|
||||
header: "Runtime Group",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current (<runtime>)", description: "Configure overrides for the current runtime." },
|
||||
{ label: "Common runtimes", description: "claude, codex, gemini, qwen" },
|
||||
{ label: "Additional runtimes", description: "opencode, copilot, hermes" },
|
||||
{ label: "Other (Group B or custom)", description: "kilo, cline, cursor, windsurf, augment, trae, codebuddy, antigravity, or a custom runtime string." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
If "Common runtimes" is selected, ask:
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Choose the runtime:",
|
||||
header: "Common",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "claude", description: "Claude Code / Anthropic CLI." },
|
||||
{ label: "codex", description: "OpenAI Codex CLI." },
|
||||
{ label: "gemini", description: "Gemini CLI." },
|
||||
{ label: "qwen", description: "Qwen CLI." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
If "Additional runtimes" is selected, ask:
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Choose the runtime:",
|
||||
header: "Additional",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "opencode", description: "OpenCode (uses anthropic/ prefix)." },
|
||||
{ label: "copilot", description: "GitHub Copilot." },
|
||||
{ label: "hermes", description: "Hermes (uses anthropic/ prefix)." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
If "Other (Group B or custom)" is selected, prompt the user to enter the runtime name as a free-text string.
|
||||
If the selected runtime differs from the stored `runtime` key, update `runtime` via
|
||||
`gsd-tools.cjs query config-set runtime <value>` before proceeding to Step C.
|
||||
|
||||
**Step C — Configure tier overrides for the selected runtime:**
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Override for opus tier? Built-in: <opus default or '(no built-in)'> Current: <override or '(none)'>",
|
||||
header: "Opus Override",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged (uses built-in default if no override)." },
|
||||
{ label: "Clear override", description: "Remove any existing override; fall back to built-in." },
|
||||
{ label: "Enter model ID", description: "Type the exact model ID string to use for opus-tier agents on this runtime." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Override for sonnet tier? Built-in: <sonnet default or '(no built-in)'> Current: <override or '(none)'>",
|
||||
header: "Sonnet Override",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged." },
|
||||
{ label: "Clear override", description: "Remove any existing override; fall back to built-in." },
|
||||
{ label: "Enter model ID", description: "Type the exact model ID string to use for sonnet-tier agents on this runtime." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Override for haiku tier? Built-in: <haiku default or '(no built-in)'> Current: <override or '(none)'>",
|
||||
header: "Haiku Override",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged." },
|
||||
{ label: "Clear override", description: "Remove any existing override; fall back to built-in." },
|
||||
{ label: "Enter model ID", description: "Type the exact model ID string to use for haiku-tier agents on this runtime." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
**Step D — Apply the changes:**
|
||||
|
||||
For each tier where the user chose "Enter model ID":
|
||||
```bash
|
||||
gsd_run query config-set model_profile_overrides.<runtime>.<tier> "<model-id>"
|
||||
```
|
||||
|
||||
For each tier where the user chose "Clear override", remove the key by setting it to null:
|
||||
```bash
|
||||
gsd_run query config-set model_profile_overrides.<runtime>.<tier> null
|
||||
```
|
||||
|
||||
"Keep current" selections are skipped entirely. Never write a key the user did not explicitly
|
||||
change.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="update_config">
|
||||
Merge the new settings into the existing config at `$GSD_CONFIG_PATH`. This merge is the
|
||||
core correctness invariant: **preserve every unrelated key** — do not clobber siblings.
|
||||
|
||||
Apply each selected value via `gsd-tools.cjs query config-set <key> <value>` so the central
|
||||
validator (`isValidConfigKey`) accepts the write and the deep-merge preserves unrelated
|
||||
keys and sibling sub-objects.
|
||||
|
||||
```bash
|
||||
# Example — only write keys the user changed. "Keep current" selections are skipped.
|
||||
gsd_run query config-set workflow.plan_bounce_passes 5
|
||||
gsd_run query config-set workflow.subagent_timeout 900
|
||||
gsd_run query config-set git.base_branch main
|
||||
gsd_run query config-set context_window 1000000
|
||||
# Runtime model tier examples:
|
||||
gsd_run query config-set runtime gemini
|
||||
gsd_run query config-set model_profile_overrides.gemini.opus gemini-3-ultra
|
||||
gsd_run query config-set model_profile_overrides.gemini.haiku null
|
||||
```
|
||||
|
||||
Conceptual shape after merge (unchanged top-level keys like `model_profile`,
|
||||
`granularity`, `mode`, `brave_search`, `agent_skills.*`, `hooks.context_warnings`, and
|
||||
anything not listed in Sections 1–8 MUST survive the update):
|
||||
|
||||
```json
|
||||
{
|
||||
...existing_config,
|
||||
"workflow": {
|
||||
...existing_workflow,
|
||||
"plan_bounce": <new|existing>,
|
||||
"plan_bounce_passes": <new|existing>,
|
||||
"plan_bounce_script": <new|existing|null>,
|
||||
"subagent_timeout": <new|existing>,
|
||||
"inline_plan_threshold": <new|existing>,
|
||||
"node_repair": <new|existing>,
|
||||
"node_repair_budget": <new|existing>,
|
||||
"auto_prune_state": <new|existing>,
|
||||
"max_discuss_passes": <new|existing>,
|
||||
"cross_ai_execution": <new|existing>,
|
||||
"cross_ai_command": <new|existing|null>,
|
||||
"cross_ai_timeout": <new|existing>
|
||||
},
|
||||
"git": {
|
||||
...existing_git,
|
||||
"base_branch": <new|existing>,
|
||||
"phase_branch_template": <new|existing>,
|
||||
"milestone_branch_template": <new|existing>
|
||||
},
|
||||
"response_language": <new|existing|null>,
|
||||
"context_window": <new|existing>,
|
||||
"search_gitignored": <new|existing>,
|
||||
"graphify": {
|
||||
...existing_graphify,
|
||||
"build_timeout": <new|existing>
|
||||
},
|
||||
"runtime": <new|existing|null>,
|
||||
"model_profile_overrides": {
|
||||
...existing_model_profile_overrides,
|
||||
"<runtime>": {
|
||||
...existing_runtime_overrides,
|
||||
"opus": <new|existing|null>,
|
||||
"sonnet": <new|existing|null>,
|
||||
"haiku": <new|existing|null>
|
||||
}
|
||||
},
|
||||
"model_policy": {
|
||||
...existing_model_policy,
|
||||
"provider": <new|existing|null>,
|
||||
"budget": <new|existing|null>,
|
||||
"high": <new|existing|null>,
|
||||
"medium": <new|existing|null>,
|
||||
"low": <new|existing|null>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Never emit a full overwrite of the file that omits keys the user did not touch. Always
|
||||
route each write through `gsd-tools.cjs query config-set` so sibling preservation is handled by
|
||||
the central setter.
|
||||
</step>
|
||||
|
||||
### Section 8 — Model Policy
|
||||
|
||||
This section configures the `model_policy` key in `.planning/config.json`. Model policy
|
||||
defines which AI models GSD uses at each cost tier (low / medium / high), independently
|
||||
of the `runtime` and `model_profile` selections above. Two paths are offered:
|
||||
|
||||
- **Known provider:** choose a provider and a budget level; GSD materializes the canonical
|
||||
tier mapping for that provider.
|
||||
- **Generic provider:** enter low / medium / high model IDs manually.
|
||||
|
||||
**Step A — Read and display the current model policy:**
|
||||
|
||||
```bash
|
||||
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 "{}"
|
||||
```
|
||||
|
||||
Display the current values (or "(unset)" for any absent field) before asking:
|
||||
|
||||
```text
|
||||
Current model_policy:
|
||||
provider : <value or "(unset)">
|
||||
budget : <value or "(unset)">
|
||||
low : <value or "(unset)">
|
||||
medium : <value or "(unset)">
|
||||
high : <value or "(unset)">
|
||||
```
|
||||
|
||||
**Step B — Choose configuration path:**
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "How do you want to configure the model policy?",
|
||||
header: "Model Policy",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Known provider", description: "Choose a provider (Claude / OpenAI / Gemini / Qwen) and a budget level — GSD writes the canonical tier mapping automatically." },
|
||||
{ label: "Generic provider", description: "Enter low / medium / high model IDs manually for any provider or custom deployment." },
|
||||
{ label: "Keep current", description: "Leave model_policy unchanged." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
**If "Keep current" is selected:** skip Steps C–E and move on to the confirm step.
|
||||
|
||||
**Step C — Known-provider path:**
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Which provider?",
|
||||
header: "Provider",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "anthropic", description: "claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5 (Anthropic / Claude)" },
|
||||
{ label: "openai", description: "gpt-5.5 / gpt-5.3-codex / gpt-5.4-mini (OpenAI / Codex)" },
|
||||
{ label: "google", description: "gemini-3-pro / gemini-3-flash / gemini-2.5-flash-lite (Google Gemini)" },
|
||||
{ label: "qwen", description: "qwen3-max-2026-01-23 / qwen3-coder-plus / qwen3-coder-next (Qwen)" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
After the user picks a provider, ask:
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Which budget level?",
|
||||
header: "Budget",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "high", description: "All tiers use the highest-quality model for the chosen provider. Highest cost." },
|
||||
{ label: "medium", description: "High tier → top model; medium → mid model; low → cheapest model. Best cost/quality ratio." },
|
||||
{ label: "low", description: "All tiers use the cheapest model for the chosen provider. Lowest cost." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
Canonical tier mappings by provider and budget:
|
||||
|
||||
| Provider | Budget | high | medium | low |
|
||||
|-----------|--------|----------------------------|----------------------------|----------------------------|
|
||||
| anthropic | high | claude-opus-4-8 | claude-opus-4-8 | claude-opus-4-8 |
|
||||
| anthropic | medium | claude-opus-4-8 | claude-sonnet-4-6 | claude-haiku-4-5 |
|
||||
| anthropic | low | claude-haiku-4-5 | claude-haiku-4-5 | claude-haiku-4-5 |
|
||||
| openai | high | gpt-5.5 | gpt-5.5 | gpt-5.5 |
|
||||
| openai | medium | gpt-5.5 | gpt-5.3-codex | gpt-5.4-mini |
|
||||
| openai | low | gpt-5.4-mini | gpt-5.4-mini | gpt-5.4-mini |
|
||||
| google | high | gemini-3-pro | gemini-3-pro | gemini-3-pro |
|
||||
| google | medium | gemini-3-pro | gemini-3-flash | gemini-2.5-flash-lite |
|
||||
| google | low | gemini-2.5-flash-lite | gemini-2.5-flash-lite | gemini-2.5-flash-lite |
|
||||
| qwen | high | qwen3-max-2026-01-23 | qwen3-max-2026-01-23 | qwen3-max-2026-01-23 |
|
||||
| qwen | medium | qwen3-max-2026-01-23 | qwen3-coder-plus | qwen3-coder-next |
|
||||
| qwen | low | qwen3-coder-next | qwen3-coder-next | qwen3-coder-next |
|
||||
|
||||
Look up the selected (provider, budget) row and proceed to Step E to write those values.
|
||||
|
||||
**Step D — Generic-provider path:**
|
||||
|
||||
Prompt the user to enter each model ID as a free-text input. An empty input means "keep
|
||||
the current value for that tier." Validate that non-empty inputs are non-blank strings
|
||||
(no whitespace-only values); if validation fails, re-prompt that single field.
|
||||
|
||||
```text
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Model ID for the HIGH-cost tier? (most capable model — used for heavy reasoning tasks)",
|
||||
header: "High-tier model",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged (current: <model_policy.high or '(unset)'>)." },
|
||||
{ label: "Enter model ID", description: "Type the exact model identifier. Non-blank string required." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Model ID for the MEDIUM-cost tier? (balanced model — used for most agents)",
|
||||
header: "Medium-tier model",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged (current: <model_policy.medium or '(unset)'>)." },
|
||||
{ label: "Enter model ID", description: "Type the exact model identifier." }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "Model ID for the LOW-cost tier? (cheapest model — used for lightweight/fast tasks)",
|
||||
header: "Low-tier model",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Keep current", description: "Leave unchanged (current: <model_policy.low or '(unset)'>)." },
|
||||
{ label: "Enter model ID", description: "Type the exact model identifier." }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
Set `provider = "custom"` and `budget = null` when writing the generic-provider result.
|
||||
Proceed to Step E.
|
||||
|
||||
**Step E — Write model_policy to config:**
|
||||
|
||||
```bash
|
||||
# Known-provider path — write all four keys atomically:
|
||||
gsd_run query config-set model_policy.provider "<provider>" # e.g., anthropic / openai / google / qwen
|
||||
gsd_run query config-set model_policy.budget "<budget>" # high / medium / low
|
||||
gsd_run query config-set model_policy.high "<high-id>"
|
||||
gsd_run query config-set model_policy.medium "<medium-id>"
|
||||
gsd_run query config-set model_policy.low "<low-id>"
|
||||
|
||||
# Generic-provider path — write only tiers the user changed ("Keep current" skipped):
|
||||
gsd_run query config-set model_policy.provider "custom"
|
||||
gsd_run query config-set model_policy.budget null
|
||||
# Per-tier writes for each non-"Keep current" answer:
|
||||
gsd_run query config-set model_policy.high "<high-id>" # omit if user chose "Keep current"
|
||||
gsd_run query config-set model_policy.medium "<medium-id>" # omit if user chose "Keep current"
|
||||
gsd_run query config-set model_policy.low "<low-id>" # omit if user chose "Keep current"
|
||||
```
|
||||
|
||||
Never write a tier the user explicitly chose to keep; the existing value must survive.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="confirm">
|
||||
Display:
|
||||
|
||||
```text
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► ADVANCED SETTINGS UPDATED
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
| Setting | Value |
|
||||
|--------------------------------------------|-------|
|
||||
| workflow.plan_bounce | {on/off} |
|
||||
| workflow.plan_bounce_passes | {n} |
|
||||
| workflow.plan_bounce_script | {path/null} |
|
||||
| workflow.subagent_timeout | {seconds} |
|
||||
| workflow.inline_plan_threshold | {n} |
|
||||
| workflow.node_repair | {on/off} |
|
||||
| workflow.node_repair_budget | {n} |
|
||||
| workflow.auto_prune_state | {on/off} |
|
||||
| workflow.max_discuss_passes | {n} |
|
||||
| workflow.cross_ai_execution | {on/off} |
|
||||
| workflow.cross_ai_command | {cmd/null} |
|
||||
| workflow.cross_ai_timeout | {seconds} |
|
||||
| git.base_branch | {branch} |
|
||||
| git.phase_branch_template | {template} |
|
||||
| git.milestone_branch_template | {template} |
|
||||
| response_language | {lang/null} |
|
||||
| context_window | {tokens} |
|
||||
| search_gitignored | {on/off} |
|
||||
| graphify.build_timeout | {seconds} |
|
||||
| runtime | {runtime/null} |
|
||||
| model_profile_overrides.<runtime>.opus | {model/built-in/null} |
|
||||
| model_profile_overrides.<runtime>.sonnet | {model/built-in/null} |
|
||||
| model_profile_overrides.<runtime>.haiku | {model/built-in/null} |
|
||||
| effort.default | {low/medium/high/xhigh/max} |
|
||||
| effort.routing_tier_defaults.light | {low/medium/high/xhigh/max} |
|
||||
| effort.routing_tier_defaults.standard | {low/medium/high/xhigh/max} |
|
||||
| effort.routing_tier_defaults.heavy | {low/medium/high/xhigh/max} |
|
||||
| effort.agent_overrides.<agent-id> | {low/medium/high/xhigh/max} |
|
||||
| fast_mode.enabled | {true/false} |
|
||||
| fast_mode.routing_tier_defaults.light | {true/false} |
|
||||
| fast_mode.routing_tier_defaults.standard | {true/false} |
|
||||
| fast_mode.routing_tier_defaults.heavy | {true/false} |
|
||||
| fast_mode.agent_overrides.<agent-id> | {true/false} |
|
||||
| model_policy.provider | {anthropic/openai/google/qwen/custom/null} |
|
||||
| model_policy.budget | {high/medium/low/null} |
|
||||
| model_policy.high | {model-id/null} |
|
||||
| model_policy.medium | {model-id/null} |
|
||||
| model_policy.low | {model-id/null} |
|
||||
|
||||
These settings apply to future /gsd:plan-phase, /gsd:execute-phase, /gsd:discuss-phase,
|
||||
and /gsd:ship runs.
|
||||
|
||||
For common-case toggles (model profile, research/plan_check/verifier, branching strategy,
|
||||
UI/AI phase gates), use /gsd:settings.
|
||||
```
|
||||
</step>
|
||||
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Current config read from resolved `$GSD_CONFIG_PATH`
|
||||
- [ ] Eight sections rendered (Planning, Execution, Discussion, Cross-AI, Git, Runtime/Output, Runtime Model Tiers, Model Policy)
|
||||
- [ ] Every field pre-selected to its current value (or documented default if absent)
|
||||
- [ ] Numeric inputs validated — non-numeric rejected and re-prompted
|
||||
- [ ] Branch-template inputs validated — non-default must contain a placeholder
|
||||
- [ ] Null-allowed fields accept an empty input as a clear
|
||||
- [ ] Writes routed through `gsd-tools.cjs query config-set` so unrelated keys are preserved
|
||||
- [ ] Section 7 shows current runtime and built-in tier table
|
||||
- [ ] Group B runtimes display "(no built-in default — your runtime handles model selection)"
|
||||
- [ ] Override set/clear/keep paths all work correctly for each tier
|
||||
- [ ] Section 8 (Model Policy) offers three top-level choices: Known provider, Generic provider, Keep current
|
||||
- [ ] Known-provider path: provider + budget → canonical tier mapping written to model_policy.{provider,budget,high,medium,low}
|
||||
- [ ] Generic-provider path: per-tier manual model IDs; "Keep current" tiers are never written; provider=custom budget=null
|
||||
- [ ] model_policy written under the model_policy key in config.json, never as a top-level flat key
|
||||
- [ ] Confirmation table rendered listing all fields including model_policy.{provider,budget,high,medium,low}
|
||||
</success_criteria>
|
||||
Reference in New Issue
Block a user