* 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>
10 KiB
Model Profiles
Model profiles control which Claude model each GSD agent uses. This allows balancing quality vs token spend, or inheriting the currently selected session model.
Profile Definitions
| Agent | quality |
balanced |
budget |
adaptive |
inherit |
|---|---|---|---|---|---|
| gsd-planner | opus | opus | sonnet | opus | inherit |
| gsd-roadmapper | opus | sonnet | sonnet | sonnet | inherit |
| gsd-executor | opus | sonnet | sonnet | sonnet | inherit |
| gsd-phase-researcher | opus | sonnet | haiku | sonnet | inherit |
| gsd-project-researcher | opus | sonnet | haiku | sonnet | inherit |
| gsd-research-synthesizer | sonnet | sonnet | haiku | haiku | inherit |
| gsd-debugger | opus | sonnet | sonnet | opus | inherit |
| gsd-codebase-mapper | sonnet | haiku | haiku | haiku | inherit |
| gsd-verifier | sonnet | sonnet | haiku | sonnet | inherit |
| gsd-plan-checker | sonnet | sonnet | haiku | haiku | inherit |
| gsd-integration-checker | sonnet | sonnet | haiku | haiku | inherit |
| gsd-nyquist-auditor | sonnet | sonnet | haiku | haiku | inherit |
Per-Phase-Type Model Map (#3023)
.planning/config.json accepts a coarse per-phase-type map under the models key. Use this when you want tuning at the phase level ("Opus for planning and execution, Sonnet for the rest") without learning the agent taxonomy.
{
"model_profile": "balanced",
"models": {
"planning": "opus",
"discuss": "opus",
"research": "sonnet",
"execution": "opus",
"verification": "sonnet",
"completion": "sonnet"
},
"model_overrides": {
"gsd-codebase-mapper": "haiku"
}
}
Phase-type → agent mapping
| Phase type | Agents |
|---|---|
planning |
gsd-planner, gsd-roadmapper, gsd-pattern-mapper |
discuss |
(reserved — no subagent today) |
research |
gsd-phase-researcher, gsd-project-researcher, gsd-research-synthesizer, gsd-codebase-mapper, gsd-ui-researcher |
execution |
gsd-executor, gsd-debugger, gsd-doc-writer |
verification |
gsd-verifier, gsd-plan-checker, gsd-integration-checker, gsd-nyquist-auditor, gsd-ui-checker, gsd-ui-auditor, gsd-doc-verifier |
completion |
(reserved — no subagent today) |
Resolution precedence (highest to lowest)
- Per-agent
model_overrides[agent]— full IDs accepted; targeted exceptions - Phase-type
models[phase_type]— tier alias only (opus/sonnet/haiku/inherit) - Profile table — the per-agent column from the active
model_profile - Runtime default — when nothing else applies
Why two layers above the profile?
- Profile is a global tier strategy (everyone runs balanced).
modelsis coarse phase-level tuning without learning agent names.model_overridesis per-agent precision (e.g. forcehaikuongsd-codebase-mapperfor a fan-out).
The three layers compose: models defaults a phase, model_overrides carves an exception out of it.
Profile Philosophy
quality - Maximum reasoning power
- Opus for all decision-making agents
- Sonnet for read-only verification
- Use when: quota available, critical architecture work
balanced (default) - Smart allocation
- Opus only for planning (where architecture decisions happen)
- Sonnet for execution and research (follows explicit instructions)
- Sonnet for verification (needs reasoning, not just pattern matching)
- Use when: normal development, good balance of quality and cost
budget - Minimal Opus usage
- Sonnet for anything that writes code
- Haiku for research and verification
- Use when: conserving quota, high-volume work, less critical phases
adaptive — Role-based cost optimization
- Opus for planning and debugging (where reasoning quality has highest impact)
- Sonnet for execution, research, and verification (follows explicit instructions)
- Haiku for mapping, checking, and auditing (high volume, structured output)
- Use when: optimizing cost without sacrificing plan quality, solo development on paid API tiers
inherit - Follow the current session model
- All agents resolve to
inherit - Best when you switch models interactively (for example OpenCode or Kilo
/model) - Required when using non-Anthropic providers (OpenRouter, local models, etc.) — otherwise GSD may call Anthropic models directly, incurring unexpected costs
- Use when: you want GSD to follow your currently selected runtime model
Using Non-Claude Runtimes (Codex, OpenCode, Gemini CLI, Kilo)
When installed for a non-Claude runtime, the GSD installer sets resolve_model_ids: "omit" in ~/.gsd/defaults.json. This returns an empty model parameter for all agents, so each agent uses the runtime's default model. No manual setup is needed.
To assign different models to different agents, add model_overrides with model IDs your runtime recognizes:
{
"resolve_model_ids": "omit",
"model_overrides": {
"gsd-planner": "o3",
"gsd-executor": "o4-mini",
"gsd-debugger": "o3",
"gsd-codebase-mapper": "o4-mini"
}
}
The same tiering logic applies: stronger models for planning and debugging, cheaper models for execution and mapping.
Using Claude Code with Non-Anthropic Providers (OpenRouter, Local)
If you're using Claude Code with OpenRouter, a local model, or any non-Anthropic provider, set the inherit profile to prevent GSD from calling Anthropic models for subagents:
# Via settings command
/gsd:settings
# → Select "Inherit" for model profile
# Or manually in .planning/config.json
{
"model_profile": "inherit"
}
Without inherit, GSD's default balanced profile spawns specific Anthropic models (opus, sonnet, haiku) for each agent type, which can result in additional API costs through your non-Anthropic provider.
Dynamic Routing with Failure-Tier Escalation (#3024)
When dynamic_routing.enabled = true in .planning/config.json, the resolver picks a model from a tier-mapped table based on the agent's default tier (light / standard / heavy) and escalates to the next tier up on orchestrator-detected soft failure.
{
"dynamic_routing": {
"enabled": true,
"tier_models": {
"light": "haiku",
"standard": "sonnet",
"heavy": "opus"
},
"escalate_on_failure": true,
"max_escalations": 1
}
}
Agent default tiers (each agent in MODEL_PROFILES declares one):
| Tier | Agents | Use case |
|---|---|---|
light |
gsd-codebase-mapper, gsd-pattern-mapper, gsd-research-synthesizer, gsd-plan-checker, gsd-integration-checker, gsd-nyquist-auditor, gsd-ui-checker, gsd-ui-auditor, gsd-doc-verifier | Cheap/fast — pure mappers, scanners, low-stakes audits |
standard |
gsd-executor, gsd-phase-researcher, gsd-project-researcher, gsd-verifier, gsd-doc-writer, gsd-ui-researcher | Default workhorse — research, writing, primary verification |
heavy |
gsd-planner, gsd-roadmapper, gsd-debugger | Deep reasoning — already at top, can't escalate further |
Escalation flow (orchestrator-driven):
- Orchestrator spawns agent with
attempt: 0→ resolver returnstier_models[default_tier] - If orchestrator marks the result a soft failure, it re-spawns with
attempt: 1→ resolver returnstier_models[next_tier_up] max_escalationscaps total retries (default 1). Beyond the cap the resolver returns the cap-tier model so the orchestrator can log without burning further budget.- Hard failures (exceptions) bypass escalation and surface immediately.
Precedence with other tier sources (highest → lowest):
model_overrides[<agent>]— full ID, always winsdynamic_routing.tier_models[escalated_tier]— whenenabled: truemodels[<phase_type>]— coarse phase-level (#3023)model_profile— global tier strategy
When dynamic_routing.enabled = false (default), behavior is identical to today.
Resolution Logic
Orchestrators resolve model before spawning. The full precedence ladder is (highest → lowest):
1. Read .planning/config.json
2. Check model_overrides[<agent>] (full IDs accepted; targeted exceptions)
3. If dynamic_routing.enabled, return tier_models[escalated_tier]
(see §Dynamic Routing — escalation steps tier up per attempt counter)
4. If no dynamic_routing match, check models[phase_type] for a phase-type tier
(see §Per-Phase-Type Model Map for the agent → phase-type mapping)
5. If no phase-type slot, look up agent in profile table
6. Pass model parameter to Task call
The same precedence applies to reasoning_effort resolution on runtimes
that support it (Codex), so model and reasoning_effort always derive
from the same tier source — a models[phase_type] or
dynamic_routing override flips both.
Per-Agent Overrides
Override specific agents without changing the entire profile:
{
"model_profile": "balanced",
"model_overrides": {
"gsd-executor": "opus",
"gsd-planner": "haiku"
}
}
Overrides take precedence over the profile. Valid values: opus, sonnet, haiku, inherit, or any fully-qualified model ID (e.g., "o3", "openai/o3", "google/gemini-2.5-pro").
Switching Profiles
Runtime: /gsd-set-profile <profile>
Per-project default: Set in .planning/config.json:
{
"model_profile": "balanced"
}
Design Rationale
Why Opus for gsd-planner? Planning involves architecture decisions, goal decomposition, and task design. This is where model quality has the highest impact.
Why Sonnet for gsd-executor? Executors follow explicit PLAN.md instructions. The plan already contains the reasoning; execution is implementation.
Why Sonnet (not Haiku) for verifiers in balanced? Verification requires goal-backward reasoning - checking if code delivers what the phase promised, not just pattern matching. Sonnet handles this well; Haiku may miss subtle gaps.
Why Haiku for gsd-codebase-mapper? Read-only exploration and pattern extraction. No reasoning required, just structured output from file contents.
Why inherit instead of passing opus directly?
Claude Code's "opus" alias maps to a specific model version. Organizations may block older opus versions while allowing newer ones. GSD returns "inherit" for opus-tier agents, causing them to use whatever opus version the user has configured in their session. This avoids version conflicts and silent fallbacks to Sonnet.
Why inherit profile?
Some runtimes (including OpenCode) let users switch models at runtime (/model). The inherit profile keeps all GSD subagents aligned to that live selection.