a33cbe72f569e75e72d94de85d0930296d1ca1df
124 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a33cbe72f5 |
fix(worktree): bound git subprocesses with timeout + surface degraded health (#3281) (#3283)
* test: red — bounded git subprocess + structured worktree warnings (#3281) Regression tests for #3281: worktree-related git subprocess calls have no timeout bound, and timeout/error outcomes are not surfaced as structured signals. Failing assertions: - planWorktreePrune / listLinkedWorktreePaths / snapshotWorktreeInventory must return reason=git_timed_out (not generic git_list_failed) when execGit returns timedOut:true — enables callers to distinguish timeout from auth failure - executeWorktreePrunePlan must include timedOut:true in result when the git prune call itself times out Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(worktree): bounded git subprocess + structured warning surfacing (#3281) Root cause (PRED.k014): execGit / execGitDefault called spawnSync with no timeout, so `git worktree list --porcelain` against a hung/locked repo blocked the parent process indefinitely. Downstream callers in core.cjs and verify.cjs then swallowed any resulting failure silently via catch { /* intentionally empty */ } (PRED.k302). Fix: - worktree-safety.cjs: execGitDefault now passes timeout:10000 to spawnSync. Detects SIGTERM+ETIMEDOUT and returns { timedOut:true } in the result shape. readWorktreeList maps timedOut:true -> reason:'git_timed_out' (distinct from generic git_list_failed) so callers can emit a structured warning. executeWorktreePrunePlan propagates timedOut:true as a first-class result field. - core.cjs: execGit receives the same timeout+timedOut treatment (PRED.k014 uniform-fix discipline). pruneOrphanedWorktrees now emits a [gsd-tools] WARNING to stderr when the git prune call times out instead of silent-catch. - verify.cjs: Check 11 branches on worktreeHealth.ok to surface W018 warning when the worktree list times out, instead of silent-catch on ok:false. Backward-compatible: exitCode/stdout/stderr continue to work for all existing callers; timedOut and error are additive new fields. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * changeset: pr=3283 for #3281 * fix(verify): rename W020 for worktree-timeout warning to avoid W018 collision W018 is already used for milestone archive drift (Check 12). The new worktree-health-degraded timeout warning was assigned W018, causing warning-code ambiguity in triage. Rename to W020 (next available code). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
8bc255c266 |
fix(workstream): normalize migration workstream names (#3269)
* fix(workstream): normalize migrate-name to valid slug * docs(context): record workstream migrate-name slug invariant * fix(catalog-cjs): balanced fallback for unknown profile (CR finding A) profiles[profile] could return undefined for any profile key absent from the catalog entry, causing downstream callers like formatAgentToModelMapAsTable to crash on .length. Add ?? profiles.balanced fallback to match the SDK adapter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(sdk): anchor path resolution on import.meta.url not cwd (CR finding B) resolve(process.cwd(), '..') breaks when Vitest is invoked from the repo root because cwd is already the repo root and '..' goes one level above. Replace with a file-relative path using fileURLToPath(new URL('../../../', import.meta.url)) anchored at the test file's location (sdk/src/query/). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: derive Group B runtime list from catalog (CR finding C) Hardcoded ['kilo', 'cline', ...] throws TypeError if a runtime name is removed from the catalog. Derive group B dynamically via Object.keys(catalog.runtimeTierDefaults).filter(r => !r.opus) so the test never goes stale and auto-covers future Group B additions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(workflow): add hermes to Step B runtime options (CR finding D) hermes appears in the Group A built-in defaults table but was missing from the AskUserQuestion options in Step B, forcing users to manually type it via 'Other (Group B or custom)'. Add explicit hermes entry for UI consistency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(config): refresh dynamic_routing tier table; fix stale L671 (findings E+F) Finding E: tier table was missing 6 heavy-tier agents and 15 standard/light agents added by this PR. Updated all three rows to match catalog routingTier assignments (33 agents total). Finding F: removed stale '18 of 31' claim and agent enumeration; replaced with accurate note that all 33 agents have explicit catalog entries. Updated authoritative source pointers to model-catalog.cjs / model-catalog.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(core): add profile-fallback unit tests for quality and budget (CR nitpick G) The PR introduced quality→opus and budget→haiku unknown-agent fallbacks but only balanced→sonnet and inherit→inherit were tested. Add two tests covering the remaining two branches to complete coverage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * adr: define planning workspace and worktree seam * refactor(worktree): extract worktree safety policy module * refactor(workstream): extract active workstream pointer store seam * test(worktree): cover policy branch paths and persist seam guardrails * refactor(worktree): centralize health inventory seam for W017 * fix(workspace): align SDK project path policy with CJS planningDir * refactor(query): unify SDK planning path projection seam * refactor(init): route workspace projection through planningPaths seam * docs(adr): add SDK architecture and planning path ADRs * refactor(worktree): deepen name, pointer, inventory, and config seams * docs(config): harmonize claude-opus-4-6 to 4-7 in resolve_model_ids example (CR finding 2) * fix(sdk): return undefined for model_profile='inherit' sentinel (CR finding 3) * docs(adr): renumber conflicting 0003-sdk-package-seam-module to 0007, update seam-map reference (CR finding 4) * fix(workstream): align CJS and SDK name validation to accept dots, guard path traversal via includes('..') (CR finding 5) * fix(sdk): guard writeActiveWorkstream against non-existent workstream directory, k014/k031 parity (CR finding 6) * chore(changeset): add #3269 changeset (CR finding 1 — proper changeset for this PR) * docs(inventory): register 3 new CLI modules in INVENTORY.md/MANIFEST (active-workstream-store, workstream-name-policy, worktree-safety) * fix(sdk): use relPlanningPath(workstream) in planningPaths, fix setActiveWorkstream/getActiveWorkstream name errors in workstream.ts * fix(sdk): validate GSD_WORKSTREAM in planningPaths before use (#3269 regression) planningPaths() called resolveWorkspaceContext() which returned GSD_WORKSTREAM raw (no validation). An invalid value like '../evil' was used as effectiveWorkstream, constructing a bad path; roadmapAnalyze() caught the ENOENT and returned a no-phase_count error object instead of the root ROADMAP result. Fix: validate envCtx.workstream with validateWorkstreamName() in planningPaths() before accepting it as effectiveWorkstream. Invalid env → null → root .planning/ fallback, preserving the bug-2791 contract: invalid GSD_WORKSTREAM is silently ignored and falls back to the root context (phase_count: 0 for empty root ROADMAP). The bug-2791 regression test now passes. No other call sites read GSD_WORKSTREAM without validation: query-runtime-context.ts already validates; cli.ts already validates; context-engine.ts takes a caller-validated workstream parameter. Closes #3268 (regression introduced by #3269 workstream-name-policy work). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
96806003c5 |
fix(#3229): shared model catalog source of truth for agent profiles + runtime tier defaults (#3230)
* docs(adr): add ADR-0003 model catalog module * fix(#3229): add shared model catalog as source of truth for agent profiles and runtime tier defaults Research / design (ADR-0003): - Existing drift came from 4 independent model truths: 1. CJS model-profiles.cjs 2. SDK config-query.ts stale copy (18 agents) 3. settings-advanced.md runtime tier table 4. session-runner Claude-only profile map - New design: one machine-readable Model Catalog Module in sdk/shared/ that both packages ship and consume. Implementation: - sdk/shared/model-catalog.json — canonical source of truth for: - full 33-agent registry - per-agent golden (quality) alias + balanced/budget aliases - adaptive derivation from routingTier - agent→phaseType map - agent→dynamic-routing default tier map - runtime tier defaults for all supported runtimes - get-shit-done/bin/lib/model-catalog.cjs — CJS adapter over the catalog - sdk/src/model-catalog.ts — SDK adapter over the same catalog - CJS model-profiles.cjs now re-exports derived data from model-catalog.cjs - SDK config-query.ts now re-exports MODEL_PROFILES/VALID_PROFILES from model-catalog.ts instead of maintaining its own list - sdk/src/query/helpers.ts runtime list now comes from the catalog (fixes hermes drift) - sdk/src/session-runner.ts Claude profile→model-id mapping now resolves via catalog - docs/CONFIGURATION.md + settings-advanced.md runtime tables updated to match catalog Behavior changes: - resolve-model now covers every shipped agent file on disk (33 agents) - unknown-agent fallback is profile-semantic, not hardcoded sonnet: quality→opus, budget→haiku, balanced/adaptive→sonnet, inherit→inherit - Group B runtimes remain known runtimes but do not get built-in tier defaults Tests (RED→GREEN): - root tests: shipped agent files must equal MODEL_PROFILES keys - sdk tests: shipped agent files must equal MODEL_PROFILES keys - direct fix assertion: gsd-code-reviewer resolves to opus under quality with no unknown_agent - runtime defaults parity test: settings-advanced.md + CONFIGURATION.md tables must match catalog - helper tests: hermes included in SUPPORTED_RUNTIMES and getRuntimeConfigDir() Closes #3229 * chore(changeset): update #3229 changeset pr field to 3230 * fix(ci): update inherit fallback expectations and inventory parity for model catalog |
||
|
|
aa64638176 |
Merge pull request #3112 from gsd-build/fix/3101-plan-summary-matcher-in-core-cjs-reports
fix: canonicalize plan-summary matching for suffixless summaries |
||
|
|
dbbc7f0942 |
Merge pull request #3117 from gsd-build/fix/3056-pruneorphanedworktrees-destroys-linked-w
fix: make orphaned worktree prune non-destructive by default |
||
|
|
ecd5d11b32 | fix(worktree): disable destructive orphaned-worktree removal by default | ||
|
|
c7886415c3 | fix(phase): canonicalize plan-summary matching for suffixless summaries | ||
|
|
19e580137d | fix: scope milestone complete stats to explicit version | ||
|
|
e1d661ece0 |
feat(#3024): dynamic routing with failure-tier escalation (#3031)
* feat(#3024): dynamic routing with failure-tier escalation Adds a `dynamic_routing` block to .planning/config.json that lets the resolver start agents on a cheap tier and escalate one tier up when the orchestrator detects a soft failure (verification inconclusive, plan-check FLAG, etc.). Solves the "pay Opus rates as insurance" anti-pattern by making escalation observed-quality-driven. Architecture: - AGENT_DEFAULT_TIERS map (light/standard/heavy) — every agent in MODEL_PROFILES declares a default tier; tests assert coverage so adding a new agent without updating the map fails CI. - nextTier(currentTier) helper — light → standard → heavy → heavy (heavy stays at heavy; can't go further). - resolveModelForTier(cwd, agentType, attempt) — new resolver. The orchestrator tracks the attempt counter and passes 0 for the first spawn, 1+ on escalation. The resolver caps internally at max_escalations so the orchestrator can blindly bump the counter. - Schema validation: dynamic_routing.enabled / escalate_on_failure / max_escalations / tier_models.<light|standard|heavy>. Unknown tiers and unknown sub-keys rejected at config-set time. - SDK schema mirror updated to keep CJS/SDK in lockstep (#2653). Resolution precedence (highest → lowest): 1. model_overrides[<agent>] (full IDs accepted) 2. dynamic_routing.tier_models[<tier>] (NEW; escalation-aware) 3. models[<phase_type>] (#3023 phase-type map) 4. model_profile (per-agent column) 5. Runtime default Backward compatibility: dynamic_routing is disabled by default (enabled: false or block omitted). resolveModelForTier short- circuits to resolveModelInternal in that case, so callers can adopt unconditionally without breaking existing behavior. This PR delivers the JS-layer infrastructure: schema + tier map + resolver. Orchestrator adoption (workflow markdown updates that detect soft failures and call resolveModelForTier with attempt+1) is incremental follow-up — verifier / plan-checker / integration- checker each adopt the protocol when ready. Tests (23 cases, all structural-IR — no stdout grep): - Schema invariants: AGENT_DEFAULT_TIERS coverage, VALID_AGENT_TIERS exact match, every assignment uses a valid tier - nextTier helper: light→standard→heavy→heavy, null on invalid input - Disabled mode: no block + enabled:false both no-op (back-compat) - Enabled mode: attempt=0 returns default tier model, attempt=1 escalates, beyond max_escalations caps, heavy agents stay heavy, default max_escalations=1 when omitted - Precedence: per-agent override beats dynamic_routing, dynamic_routing beats phase-type models - Validation: every settings key accepted, unknown tiers/sub-keys rejected, bare `dynamic_routing` rejected as config-set target Documentation: - get-shit-done/references/model-profiles.md — full reference section - docs/CONFIGURATION.md — full settings table + escalation flow - docs/USER-GUIDE.md — task-oriented "Cheap-by-default" section - docs/FEATURES.md — config row cross-link Verification: - 23/23 pass on regression test - 6843/6843 full suite (23 net new from 6820) - lint-no-source-grep clean (376 test files) - SDK schema mirror keeps CJS/SDK in sync per #2653 parity test Closes #3024 * fix(#3024): honor escalate_on_failure:false + 3 CR follow-ups CodeRabbit on PR #3031 (4 findings — 1 Major + 2 Minor + 1 Nitpick): 1. **Major (inline)** — get-shit-done/bin/lib/core.cjs:1668 resolveModelForTier ignored dynamic_routing.escalate_on_failure. When the user set it to false, escalation should be disabled, but the resolver only checked attempt/max_escalations. An orchestrator that always passes attempt+1 on retry would silently escalate despite the user opting out. Fix: gate effectiveAttempt on `dr.escalate_on_failure !== false` so false short-circuits every attempt back to the default tier. 2. **Minor (inline)** — docs/CONFIGURATION.md:123-126 The dynamic_routing rows in the Core Settings table had 4 cells instead of 5 (missing the Options column), breaking the table structure. Added explicit Options values for enabled / escalate_on_failure / max_escalations rows. 3. **Minor (outside-diff)** — references/model-profiles.md:179-195 "Resolution Logic" sketch was pre-#3024 and didn't include dynamic_routing in the precedence ladder. Updated to a 6-step block with dynamic_routing at step 3 (between override and phase-type). 4. **Nitpick** — tests/feat-3024-dynamic-routing.test.cjs:189+ Tests used `if (lightAgent) { ... }` guards that silent-pass when AGENT_DEFAULT_TIERS drifts. Replaced all 5 conditional skips with `assert.ok(lightAgent, '...')` preconditions so a tier-mapping change surfaces as a test failure. Plus: 2 new regression tests for the Major fix: - escalate_on_failure:false caps every attempt at default tier - escalate_on_failure:true (explicit) still escalates normally Verification: - 25/25 pass on regression test (23 prior + 2 escalate_on_failure) - 6845/6845 full suite (2 net new) - lint-no-source-grep clean * docs(#3024): align precedence + add fence language tags (CR follow-up) CodeRabbit (3 minor): 1. docs/CONFIGURATION.md:691 — "Per-Phase-Type Models → Resolution precedence" was a 4-step block written pre-#3024; readers got contradictory rules between the per-phase-type section and the later dynamic_routing section. Updated to the same 5-step ladder with dynamic_routing at step 2, and noted that dynamic_routing is disabled by default so this section's behavior is unchanged when the kill-switch is off. 2. docs/CONFIGURATION.md:770 — escalation-flow code fence missing language tag (MD040). Added `text`. 3. references/model-profiles.md:184 — resolution-ladder code fence missing language tag (MD040). Added `text`. No code changes; docs only. Verification: regression test still 25/25. * docs(#3024): clarify precedence prose — five layers, not four (CR nitpick) CodeRabbit nitpick: the "Per-Phase-Type Models → Resolution precedence" prose said "The four layers compose..." but the ladder above lists five (including Runtime default). Also "dynamic_routing escalates per-attempt above all of them" misreads as suggesting dynamic_routing wins over model_overrides — actually overrides still win at step 1. Reworded top-down so the precedence direction is unambiguous: - model_profile = base - models = phase-level override - dynamic_routing = per-attempt escalation - model_overrides = per-agent exception (top) - runtime default = fallback No code changes; docs only. * docs(#3024): note escalate_on_failure:false in escalation-flow diagram (CR) CodeRabbit nitpick: the escalation-flow diagram in docs/CONFIGURATION.md described the soft-failure → respawn → tier_models[next_tier_up] path, but didn't surface the `dynamic_routing.escalate_on_failure: false` kill-switch right next to it. Users reading the flow diagram (which is the canonical place to understand attempt behavior) wouldn't see that the kill-switch overrides the soft-failure branch. Added a one-paragraph note immediately after the flow listing, before the tier-sequence example, so the kill-switch is visible exactly where users decide whether escalation will happen. No code changes; docs only. |
||
|
|
d812c66020 |
feat(#3023): per-phase-type model map in .planning/config.json (#3030)
* feat(#3023): per-phase-type model map in .planning/config.json Adds a new `models` block to .planning/config.json with six phase-type slots (planning / discuss / research / execution / verification / completion). Lets users express coarse tuning ("Opus for planning, Sonnet for the rest") without learning the agent taxonomy. Resolution precedence (highest → lowest): 1. Per-agent `model_overrides[agent]` (full IDs; targeted exception) 2. Phase-type `models[phase_type]` (NEW; tier alias) 3. Profile table (`model_profile`) (per-agent column) 4. Runtime default The three layers compose: `models` defaults a phase, `model_overrides` carves an exception. Phase-type values are tier aliases (opus/sonnet/ haiku/inherit) so the runtime-resolution chain (#2517) stays correct end-to-end without further branching. Implementation: - model-profiles.cjs: new AGENT_TO_PHASE_TYPE map + VALID_PHASE_TYPES set. Each agent in MODEL_PROFILES gets one phase-type assignment; tests assert coverage so adding a new agent without updating the table fails CI. - core.cjs (resolveModelInternal): inserts phase-type tier lookup between per-agent override and profile-derived tier. Skips runtime resolution when the resolved tier is 'inherit' (was previously gated only on profile === 'inherit'; phase-type can now produce inherit independently). - core.cjs (loadConfig): pass `parsed.models` through both code paths so resolveModelInternal can read it. - config-schema.cjs + sdk/src/query/config-schema.ts: dynamic-pattern validator accepts only the six known phase-types; unknown slots rejected at config-set time. Backward compat: configs without `models` behave exactly as today. Tests (15 cases, all structural-IR — no stdout grep): - Schema: AGENT_TO_PHASE_TYPE coverage, VALID_PHASE_TYPES exact match - Resolver: phase-type alone; per-agent override beats phase-type; phase-type beats profile; issue's full example; "inherit"; empty block is no-op; no block is no-op - Validation: each of the 6 slots accepted; unknown slot rejected; bare `models` (no slot) rejected Verification: - 15/15 pass on new regression test - 6808/6808 full suite (5 net new), 0 fail - lint-no-source-grep clean across 375 test files Closes #3023 * docs(#3023): document `models` per-phase-type config in user-facing docs Adds `models` block coverage to the three user-facing docs that ship with each release: 1. docs/CONFIGURATION.md - New "Per-Phase-Type Models" section between "Per-Agent Overrides" and "Non-Claude Runtimes" with: * full example mixing models + model_overrides * phase-type → agent mapping table * resolution-precedence pseudocode * accepted values (tier alias only) * "When to use which" decision matrix * validation behavior + example error - Added `"models": {}` to the Full Schema snippet - Added a row for `models.<phase_type>` to the config keys table (next to model_profile_overrides for adjacency) 2. docs/FEATURES.md - Added a row for models.<phase_type> in the Configurable Settings table (right under model_profile) - Cross-link to CONFIGURATION.md for the full surface 3. docs/USER-GUIDE.md - New task-oriented "Tuning model cost by phase" section above "Using Non-Claude Runtimes" — leads with the concrete config and shows the override pattern (one-shot phase + targeted exception) - Cross-link to CONFIGURATION.md Verification: - 29/29 pass on config-schema-docs-parity + docs-update + new feature test (parity-check passes, so the config-schema entry I added in the feature commit is now matched by a docs row) - 6808/6808 full suite pass - lint-no-source-grep clean Doc style follows the same pattern used by the existing model_profile, model_overrides, and model_profile_overrides sections — example-led, table-backed, cross-referenced. Each doc surfaces the feature at the right depth (reference / settings table / task guide). * fix(#3023): mirror phase-type tier in resolveReasoningEffortInternal (CR Major) CodeRabbit caught a real Codex correctness bug + 3 minor docs/test issues: 1. **Major (outside-diff)** — resolveReasoningEffortInternal in core.cjs derived its tier exclusively from the profile table, ignoring the models.<phase_type> override added in #3023. Failure mode on Codex: Config: model_profile=balanced, models.execution=opus, agent=gsd-executor resolveModelInternal: tier=opus → gpt-5.4 resolveReasoningEffortInternal: tier=sonnet → reasoning_effort=medium ↑ WRONG — should be xhigh (opus tier on Codex) The runtime received a mismatched (model, effort) pair. Mirrored the phase-type lookup from resolveModelInternal so both functions derive from the same tier source. 'inherit' phase-type returns null effort (no runtime entry maps to 'inherit'; let runtime decide). 2. Minor — .changeset/per-phase-type-models.md `pr: TBD` → `pr: 3030`. 3. Minor (outside-diff) — model-profiles.md "Resolution Logic" section omitted the new phase-type tier. Updated the 4-step block to a 5-step block including `models[phase_type]` between override and profile, plus a paragraph noting that `model` and `reasoning_effort` derive from the same tier source. 4. Nitpick — added 2 typo-safety tests: - models.research = "haiku3" (typo) → falls through to profile - models.research = "openai/gpt-5" (full ID) → falls through to profile Plus 5 new reasoning_effort tests covering the Major fix: - exported correctly - phase-type override flips both model AND effort to same tier - inherit phase-type returns null effort - per-agent override still bypasses phase-type for effort - claude runtime ignores models.* (no effort propagation) Verification: - 24/24 pass on regression test (15 original + 2 typo-safety + 5 effort + 2 outside-diff related) - 6815/6815 full suite (7 net new from 6808) - lint-no-source-grep clean The reasoning_effort tests are written semantically (phase-type override must produce the SAME effort as a profile-only opus config) rather than hard-coding tier-specific effort strings, so changes to the runtime tier map don't break them. * fix(#3023): phase-type override beats profile=inherit (CR Major round 2) CodeRabbit caught another precedence inversion: when { model_profile: 'inherit', models: { execution: 'opus' } } both resolvers short-circuited on `profile === 'inherit'` BEFORE the phase-type override could be honored. Result: model returned 'inherit' and reasoning_effort returned null — both contradicting the documented precedence where models[phase_type] wins over model_profile. Fix in resolveModelInternal: - Compute tier from phase-type FIRST. If phase-type is a valid alias, it wins. Otherwise, fall back to profile-derived tier OR 'inherit' (when profile === 'inherit'). - Gate the runtime-resolution branch on `tier !== 'inherit'` (was `profile !== 'inherit'`) so phase-type=opus can flip runtime mapping on even when profile=inherit. - Gate the inherit-return on `tier === 'inherit'` (was `profile === 'inherit'`). Fix in resolveReasoningEffortInternal: - Remove the `if (profile === 'inherit') return null;` early-return. - Compute tier from phase-type first, fall back to profile. If phase-type is explicitly 'inherit' OR the resolved tier is 'inherit', return null (no runtime entry maps to inherit). Tests added (5 new): - model: phase-type wins over profile=inherit (with explicit opus, with haiku for one phase + planner-without-slot still inheriting) - model: profile=inherit + no models block → all agents inherit (no regression on existing inherit semantics) - model: profile=inherit + models block but agent has no slot → that agent inherits, agents with slots get phase-type tier - effort: phase-type opus + profile=inherit → produces opus-tier effort, NOT null (the original bug) Verification: - 27/27 pass on regression test (24 prior + 3 model + 1 effort) - 6820/6820 full suite (5 net new) - lint-no-source-grep clean The effort test reads the expected value by running a profile-only opus config and comparing — semantic check, not hard-coded effort string. So runtime tier map changes don't break the test. |
||
|
|
f55069ecbf |
test(#2974): migrate 8 test files to typed-IR assertions (#3016)
* test(#2974): migrate 8 test files to typed-IR assertions Replaces raw stdout/stderr substring matching with structured-field assertions per CONTRIBUTING.md "Prohibited: Raw Text Matching on Test Outputs". Adds shared infrastructure for typed error emission so this pattern is the easy path going forward. Shared infrastructure: - core.cjs: ERROR_REASON frozen enum + setJsonErrorMode/getJsonErrorMode - gsd-tools.cjs: --json-errors CLI flag, parsed before subcommand dispatch - config.cjs: typed reasons at all 7 error sites - graphify.cjs: GRAPHIFY_REASON enum + reason/timeout_ms in execGraphify result - bin/install.js: pure buildSdkFailFastReport() IR builder + renderer - hooks/gsd-session-state.sh, gsd-phase-boundary.sh: emit Claude Code hookSpecificOutput JSON envelope with typed state_present/config_mode/ planning_modified/file_path fields (no-op when hooks.community is off) Test migrations (all pass, 171 tests across the 8 files): - bug-2649-sdk-fail-fast: assert on ir.reason / ir.context / ir.fix_command - bug-2687-config-read-warning-parity: assert.equal stderr === '' - bug-2796-arg-parsing-regression: assert on result.json.updated/.phase - bug-2838-summary-rescue: parse rescue footer, assert mtime invariant - bug-2943-config-get-context-window: parse JSON, assert ERROR_REASON.CONFIG_KEY_NOT_FOUND - graphify: assert reason === GRAPHIFY_REASON.ENOENT/TIMEOUT - hooks-opt-in: parse hookSpecificOutput, assert typed fields - security-scan: reclassified as source-text-is-the-product (scan label output and CI workflow YAML ARE the deployed contract) Verification: lint-no-source-grep clean (0 violations), full suite 6741/6741 pass. Closes #2974 * test(#2974): address CR feedback — typed code field, robust idempotency Two CodeRabbit findings on #3016 addressed: 1. tests/hooks-opt-in.test.cjs:355 (Minor, inline) — parsed.reason.includes('Conventional Commits') was still substring matching after the typed-IR migration. Fixed at the source: the gsd-validate-commit hook now emits a typed `code` field ('CONVENTIONAL_COMMITS_VIOLATION', 'COMMIT_SUBJECT_TOO_LONG') alongside the human-readable `reason`. Test asserts strictEqual on the code; the prose copy is no longer part of the test contract. 2. tests/bug-2838-summary-rescue-gitignored-planning.test.cjs:224-250 (Outside-diff) — mtimeMs alone can stay unchanged on coarse-grained filesystems (HFS+, FAT) when two rewrites land within the same timestamp tick, falsely passing the idempotency assertion. Replaced with a full snapshot (mtimeMs, ctimeMs, size, ino, sha256 of contents) compared via assert.deepStrictEqual — the hash catches any rewrite the timestamp would miss. Verification: 30/30 pass on the two affected files; lint-no-source-grep clean (0 violations across 368 test files). |
||
|
|
b126c0579a |
feat(install): add Hermes Agent runtime support (#2841)
Adds Hermes Agent as a supported installation target. Users can run
\`npx get-shit-done-cc --hermes\` to install all 86 GSD commands as
skills under \`~/.hermes/skills/gsd-*/SKILL.md\`, following the same
open skill standard as Claude Code 2.1.88+, Qwen Code, Antigravity,
Trae, Augment, and Codebuddy.
Hermes Agent is an open-source AI agent framework by Nous Research
(NousResearch/hermes-agent, MIT). Its skill loader accepts the Claude
skill format as-is: frontmatter parsed with PyYAML SafeLoader (unknown
keys like \`allowed-tools\` / \`argument-hint\` ignored), body XML tags
(\`<objective>\`, \`<execution_context>\`, \`<process>\`) passed directly
to the model. Compatibility proven end-to-end with all 86 GSD skills
loading cleanly, \`skill_view()\` returning full bodies, and
\`build_skills_system_prompt()\` emitting them into the agent system
prompt — zero Hermes code changes required.
Changes:
- \`bin/install.js\`: --hermes flag, getDirName/getGlobalDir/getConfigDirFromHome
support, HERMES_HOME env var (native to Hermes — used for profile
mode / Docker deploys), install/uninstall pipelines, interactive
picker option 10 (alphabetical: between Gemini and Kilo), .hermes
path replacements in copyCommandsAsClaudeSkills and
copyWithPathReplacement, legacy commands/gsd cleanup, CLAUDE.md ->
HERMES.md and "Claude Code" -> "Hermes Agent" content rewrites in
skills/agents/hooks, runtime-appropriate finish message.
- \`get-shit-done/bin/lib/core.cjs\`: add hermes to KNOWN_RUNTIMES;
add RUNTIME_PROFILE_MAP.hermes with OpenRouter-slug defaults
(Hermes is provider-agnostic; these defaults resolve across
OpenRouter, native Anthropic, and Copilot via Hermes' aggregator-
aware resolver, and are overridable per-tier via
model_profile_overrides.hermes.{opus,sonnet,haiku}).
- \`README.md\`: Hermes Agent in tagline, runtime list, verification
command, install/uninstall examples, \`--hermes\` flag reference.
- \`tests/hermes-install.test.cjs\`: new, 14 tests covering directory
mapping, HERMES_HOME env var precedence, install/uninstall
lifecycle, user-skill preservation, engine cleanup.
- \`tests/hermes-skills-migration.test.cjs\`: new, 11 tests covering
frontmatter conversion, path replacement (~/.claude/ ->
\$HERMES_HOME/skills/), CLAUDE.md -> HERMES.md, "Claude Code" ->
"Hermes Agent", stale skill cleanup, SKILL.md format validation.
- \`tests/multi-runtime-select.test.cjs\`: updated for new option
numbering (hermes=10, kilo=11, opencode=12, qwen=13, trae=14,
windsurf=15, all=16).
- \`tests/kilo-install.test.cjs\`: updated assertions for Kilo having
moved from option 10 to option 11.
Closes #2841
Implementation notes:
- Zero custom code paths: Hermes reuses copyCommandsAsClaudeSkills()
identical to Qwen Code / Antigravity pattern.
- Path replacement: ~/.claude/, \$HOME/.claude/, ./.claude/ ->
.hermes equivalents in skill/agent/hook content.
- Config precedence: --config-dir > HERMES_HOME > ~/.hermes (matches
how Hermes itself resolves its home directory).
- Legacy cleanup: removes commands/gsd/ if present from a prior
install, preserving dev-preferences.md (same as Qwen).
- No external dependencies added.
Testing: 5841 / 5841 tests pass (0 failures, 0 regressions)
- 14 new tests in hermes-install.test.cjs
- 11 new tests in hermes-skills-migration.test.cjs
- multi-runtime-select.test.cjs renumbered + 1 new test (single choice for hermes)
|
||
|
|
abb2cb63f6 |
refactor: extract planning-workspace seam from core.cjs (#2901)
* refactor: extract planning workspace seam from core * docs: document planning-workspace module and inventory updates * fix: harden planning lock timeout and preserve workstream set contract --------- Co-authored-by: Tom Boucher <thomas.boucher@sas.com> |
||
|
|
c3a42d66f9 | Revert "feat(install): add Hermes Agent runtime support" (#2849) | ||
|
|
5a636bc90a |
feat(install): add Hermes Agent runtime support (#2841)
Adds Hermes Agent as a supported installation target. Users can run
\`npx get-shit-done-cc --hermes\` to install all 86 GSD commands as
skills under \`~/.hermes/skills/gsd-*/SKILL.md\`, following the same
open skill standard as Claude Code 2.1.88+, Qwen Code, Antigravity,
Trae, Augment, and Codebuddy.
Hermes Agent is an open-source AI agent framework by Nous Research
(NousResearch/hermes-agent, MIT). Its skill loader accepts the Claude
skill format as-is: frontmatter parsed with PyYAML SafeLoader (unknown
keys like \`allowed-tools\` / \`argument-hint\` ignored), body XML tags
(\`<objective>\`, \`<execution_context>\`, \`<process>\`) passed directly
to the model. Compatibility proven end-to-end with all 86 GSD skills
loading cleanly, \`skill_view()\` returning full bodies, and
\`build_skills_system_prompt()\` emitting them into the agent system
prompt — zero Hermes code changes required.
Changes:
- \`bin/install.js\`: --hermes flag, getDirName/getGlobalDir/getConfigDirFromHome
support, HERMES_HOME env var (native to Hermes — used for profile
mode / Docker deploys), install/uninstall pipelines, interactive
picker option 10 (alphabetical: between Gemini and Kilo), .hermes
path replacements in copyCommandsAsClaudeSkills and
copyWithPathReplacement, legacy commands/gsd cleanup, CLAUDE.md ->
HERMES.md and "Claude Code" -> "Hermes Agent" content rewrites in
skills/agents/hooks, runtime-appropriate finish message.
- \`get-shit-done/bin/lib/core.cjs\`: add hermes to KNOWN_RUNTIMES;
add RUNTIME_PROFILE_MAP.hermes with OpenRouter-slug defaults
(Hermes is provider-agnostic; these defaults resolve across
OpenRouter, native Anthropic, and Copilot via Hermes' aggregator-
aware resolver, and are overridable per-tier via
model_profile_overrides.hermes.{opus,sonnet,haiku}).
- \`README.md\`: Hermes Agent in tagline, runtime list, verification
command, install/uninstall examples, \`--hermes\` flag reference.
- \`tests/hermes-install.test.cjs\`: new, 14 tests covering directory
mapping, HERMES_HOME env var precedence, install/uninstall
lifecycle, user-skill preservation, engine cleanup.
- \`tests/hermes-skills-migration.test.cjs\`: new, 11 tests covering
frontmatter conversion, path replacement (~/.claude/ ->
\$HERMES_HOME/skills/), CLAUDE.md -> HERMES.md, "Claude Code" ->
"Hermes Agent", stale skill cleanup, SKILL.md format validation.
- \`tests/multi-runtime-select.test.cjs\`: updated for new option
numbering (hermes=10, kilo=11, opencode=12, qwen=13, trae=14,
windsurf=15, all=16).
- \`tests/kilo-install.test.cjs\`: updated assertions for Kilo having
moved from option 10 to option 11.
Closes #2841
Implementation notes:
- Zero custom code paths: Hermes reuses copyCommandsAsClaudeSkills()
identical to Qwen Code / Antigravity pattern.
- Path replacement: ~/.claude/, \$HOME/.claude/, ./.claude/ ->
.hermes equivalents in skill/agent/hook content.
- Config precedence: --config-dir > HERMES_HOME > ~/.hermes (matches
how Hermes itself resolves its home directory).
- Legacy cleanup: removes commands/gsd/ if present from a prior
install, preserving dev-preferences.md (same as Qwen).
- No external dependencies added.
Testing: 5841 / 5841 tests pass (0 failures, 0 regressions)
- 14 new tests in hermes-install.test.cjs
- 11 new tests in hermes-skills-migration.test.cjs
- multi-runtime-select.test.cjs renumbered + 1 new test (single choice for hermes)
|
||
|
|
eeaf9c556f |
fix(#2787): track fenced code blocks in extractCurrentMilestone (#2812)
* fix(#2787): track fenced code blocks in extractCurrentMilestone The milestone-end search used a multiline regex against the raw restContent string. Lines inside fenced code blocks (``` or ~~~) that matched the milestone-heading pattern (e.g. `# note v1.0`) prematurely set sectionEnd, hiding all phases after the block from roadmap analyze, roadmap get-phase, and every downstream command. Replace the regex match with a line-by-line scan that tracks fence state. Lines inside an open fence are skipped regardless of content. Adds three regression tests covering backtick fences, tilde fences, and the roadmap get-phase code path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2787): track fence delimiter instead of toggling bare boolean Replace the inFence boolean with fenceChar/fenceLen tracking so that indented fences (up to 3 leading spaces) and mixed-delimiter content (~~~ inside a backtick fence) are parsed correctly. A closing fence is only recognised when it uses the same character as the opening delimiter and has at least the same run length, matching the CommonMark spec. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2787): require fence-only closing line — reject info-string lines as closers A closing fence delimiter must contain only optional trailing whitespace. A line like \`\`\`js inside an open fence has an info string and must not close it. The previous regex /^\s{0,3}([`~]{3,})/ matched the opening of any such line, so the closing check could toggle fenceChar off on an info-string line and expose subsequent heading-like content to the milestone-end detector. Fix: capture the trailing portion of every fence-candidate line and only clear fenceChar when trailing matches /^\s*$/ (per CommonMark §4.5). Adds a regression test covering the ```text / ```js nesting scenario. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
3246810876 |
feat: extend RUNTIME_PROFILE_MAP to gemini/qwen/opencode/copilot + settings-advanced UI (#2754)
Closes #2612 - Add gemini, qwen, opencode, and copilot entries to RUNTIME_PROFILE_MAP in core.cjs - Group B runtimes (kilo, cline, cursor, windsurf, augment, trae, codebuddy, antigravity) intentionally have no built-in map and fall through to the existing unknown-runtime fallback - Add 40 new tests to tests/issue-2517-runtime-aware-profiles.test.cjs covering each new runtime's three tiers, Group B fall-through, and partial override merge semantics - Add Section 7 "Runtime Model Tiers" to settings-advanced.md with interactive UI to view and override built-in tier defaults per runtime - Update docs/CONFIGURATION.md built-in tier table to include all four new runtimes Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
2b95ccbddd |
Merge pull request #2743 from gsd-build/fix/2714-workstream-config-inheritance
feat(config): workstream config.json inherits from root .planning/config.json |
||
|
|
cb149383c1 |
fix(config): bump MODEL_ALIAS_MAP to claude-opus-4-7 (#2733)
* fix(config): bump MODEL_ALIAS_MAP and RUNTIME_PROFILE_MAP to claude-opus-4-7 Opus 4.7 shipped Q1 2026 but MODEL_ALIAS_MAP and RUNTIME_PROFILE_MAP.claude.opus were still pinned to claude-opus-4-6. Users with resolve_model_ids: true received stale model IDs in logs and agent-tool calls. Also adds a resolve_model_ids: true test suite — this path had zero coverage, which is why the stale ID survived undetected. Closes #2712 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(config): derive RUNTIME_PROFILE_MAP.claude from MODEL_ALIAS_MAP (coderabbit) RUNTIME_PROFILE_MAP.claude was duplicating model IDs that MODEL_ALIAS_MAP already owns. Future model bumps now only require updating MODEL_ALIAS_MAP. Also fixes stale test assertion (claude-opus-4-6 → claude-opus-4-7). * fix(tests): update stale claude-opus-4-6 refs to claude-opus-4-7; DRY: derive RUNTIME_PROFILE_MAP.claude from MODEL_ALIAS_MAP - Update 3 hardcoded `claude-opus-4-6` assertions in tests/issue-2517-runtime-aware-profiles.test.cjs to `claude-opus-4-7` - Update comment on line 128 that referenced the old model ID - Replace manual per-tier expansion of RUNTIME_PROFILE_MAP.claude with Object.fromEntries so future alias bumps only require updating MODEL_ALIAS_MAP Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1cb4bebcf5 |
feat(config): workstream config.json inherits from root .planning/config.json
- Add _deepMergeConfig() with correct null-override semantics - loadConfig() reads root config.json when GSD_WORKSTREAM is set, then deep-merges with workstream config (workstream wins on conflict) - Workstream without config.json falls back to root config entirely - Migrations and disk writes operate on fileData (on-disk content) only, never on the merged result, to prevent workstream pollution - Fixes null-override bug from PR #2717: explicit null in workstream now correctly overrides root value instead of falling back to root - Tests: inherit root model_overrides, workstream override, nested workflow.* deep merge, explicit null override, missing workstream config Closes #2714 |
||
|
|
b7ff14fe51 |
fix(#2687): derive KNOWN_TOP_LEVEL from DYNAMIC_KEY_PATTERNS to eliminate read-side drift (#2706)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b1a670e662 |
fix(#2697): replace retired /gsd: prefix with /gsd- in all user-facing text (#2699)
All workflow, command, reference, template, and tool-output files that surfaced /gsd:<cmd> as a user-typed slash command have been updated to use /gsd-<cmd>, matching the Claude Code skill directory name. Closes #2697 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c811792967 |
fix(#2660): capture prose after labeled bold in extractOneLinerFromBody (#2679)
* fix(#2660): capture prose after label in extractOneLinerFromBody The regex `\*\*([^*]+)\*\*` matched the first bold span, so for the new SUMMARY template `**One-liner:** Real prose here.` it captured the label `One-liner:` instead of the prose. MILESTONES.md then wrote bullets like `- One-liner:` with no content. Handle both template forms: - Labeled: `**One-liner:** prose` → prose - Bare: `**prose**` → prose (legacy) Empty prose after a label returns null so no bogus bullets are emitted. Note: existing MILESTONES.md entries generated under the bug are not regenerated here — that is a follow-up. Closes #2660 * fix(#2660): normalize CRLF before one-liner extraction Windows-authored SUMMARY files use CRLF line endings; the LF-only regex in extractOneLinerFromBody would fail to match. Normalize \r\n and \r to \n before stripping frontmatter and matching the one-liner pattern. Adds test case (h) covering CRLF input. |
||
|
|
06463860e4 |
fix(#2638): write sub_repos to canonical planning.sub_repos (#2668)
loadConfig's multiRepo migration and filesystem-sync writers targeted the top-level parsed.sub_repos, but KNOWN_TOP_LEVEL (the unknown-key validator's allowlist) only recognizes planning.sub_repos (canonical per #2561). Each migration/sync therefore persisted a key the next loadConfig call warned was unknown. Redirect both writers to parsed.planning.sub_repos, ensuring parsed.planning is initialized first. Also self-heal legacy/buggy installs by stripping any stale top-level sub_repos on load, preserving its value as the planning.sub_repos seed if that slot is empty. Tests cover: (a) canonical planning.sub_repos emits no warning, (b) multiRepo migration writes to planning.sub_repos with no top-level residue, (c) filesystem sync relocates to planning.sub_repos, (d) stale top-level sub_repos from older buggy installs is stripped on load. Closes #2638 |
||
|
|
74da61fb4a |
fix(#2619): prevent extractCurrentMilestone from truncating on phase-vX.Y headings (#2624)
* fix(#2619): prevent extractCurrentMilestone from truncating on phase-vX.Y headings extractCurrentMilestone sliced ROADMAP.md to the current milestone by looking for the next milestone heading with a greedy regex: ^#{1,N}\s+(?:.*v\d+\.\d+|✅|📋|🚧) Any heading that mentioned a version literal matched — including phase headings like "### Phase 12: v1.0 Tech-Debt Closure". When the current milestone was at the same heading level as the phases (### 🚧 v1.1 …), the slice terminated at the first such phase, hiding every phase that followed from phase.insert, validate.health W007, and other SDK commands. Fix: add a `(?!Phase\s+\S)` negative lookahead so phase headings can never be treated as milestone boundaries. Phase headings always start with the literal `Phase `, so this is a clean exclusion. Applied to: - get-shit-done/bin/lib/core.cjs (extractCurrentMilestone) - sdk/src/query/roadmap.ts (extractCurrentMilestone + extractNextMilestoneSection) Regression tests: - tests/roadmap-phase-fallback.test.cjs: extractCurrentMilestone does not truncate on phase heading containing vX.Y (#2619) - sdk/src/query/roadmap.test.ts: extractCurrentMilestone bug-2619: does not truncate at a phase heading containing vX.Y Closes #2619 * fix(#2619): make milestone-boundary Phase lookahead case-insensitive CodeRabbit follow-up on #2619: the negative lookahead `(?!Phase\s+\S)` in the SDK milestone-boundary regex was case-sensitive, so headings like `### PHASE 12: v1.0 Tech-Debt` or `### phase 12: …` still truncated the milestone slice. Add the `i` flag (now `gmi`). The sibling CJS regex in get-shit-done/bin/lib/core.cjs already uses the `mi` flag, so it is already case-insensitive; added a regression test to lock that in. - sdk/src/query/roadmap.ts: change flags from `gm` → `gmi` - sdk/src/query/roadmap.test.ts: add PHASE/phase regression test - tests/roadmap-phase-fallback.test.cjs: add PHASE/phase regression test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1a3d953767 |
feat: add unified post-planning gap checker (closes #2493) (#2610)
* feat: add unified post-planning gap checker (closes #2493) Adds a unified post-planning gap checker as Step 13e of plan-phase.md. After all plans are generated and committed, scans REQUIREMENTS.md and CONTEXT.md <decisions> against every PLAN.md in the phase directory and emits a single Source | Item | Status table. Why - The existing Requirements Coverage Gate (§13) blocks/re-plans on REQ gaps but emits two separate per-source signals. Issue #2493 asks for one unified report after planning so that requirements AND discuss-phase decisions slipping through are surfaced in one place before execution starts. What - New workflow.post_planning_gaps boolean config key, default true, added to VALID_CONFIG_KEYS, CONFIG_DEFAULTS, hardcoded.workflow, and cmdConfigSet (boolean validation). - New get-shit-done/bin/lib/decisions.cjs — shared parser for CONTEXT.md <decisions> blocks (D-NN entries). Designed for reuse by the related #2492 plan/verify decision gates. - New get-shit-done/bin/lib/gap-checker.cjs — parses REQUIREMENTS.md (checkbox + traceability table forms), reads CONTEXT.md decisions, walks PHASE_DIR/*-PLAN.md, runs word-boundary coverage detection (REQ-1 must not match REQ-10), formats a sorted report. - New gsd-tools gap-analysis CLI command wired through gsd-tools.cjs. - workflows/plan-phase.md gains §13e between §13d (commit plans) and §14 (Present Final Status). Existing §13 gate preserved — §13e is additive and non-blocking. - sdk/prompts/workflows/plan-phase.md gets an equivalent post_planning_gaps step for headless mode. - Docs: CONFIGURATION.md, references/planning-config.md, INVENTORY.md, INVENTORY-MANIFEST.json all updated. Tests - tests/post-planning-gaps-2493.test.cjs: 30 test cases covering step insertion position, decisions parser, gap detector behavior (covered/not-covered, false-positive guard, missing-file resilience, malformed-input resilience, gate on/off, deterministic natural sort), and full config integration. - Full suite: 5234 / 5234 pass. Design decisions - Numbered §13e (sub-step), not §14 — §14 already exists (Present Final Status); inserting before it preserves downstream auto-advance step numbers. - Existing §13 gate kept, not replaced — §13 blocks/re-plans on REQ gaps; §13e is the unified post-hoc report. Per spec: "default behavior MUST be backward compatible." - Word-boundary ID matching avoids REQ-1 matching REQ-10 and avoids brittle semantic/substring matching. - Shared decisions.cjs parser so #2492 can reuse the same regex. - Natural-sort keys (REQ-02 before REQ-10) for deterministic output. - Boolean validation in cmdConfigSet rejects non-boolean values matches the precedent set by drift_threshold/drift_action. Closes #2493 * fix(#2493): expose post_planning_gaps in loadConfig() + sync schema example Address CodeRabbit review on PR #2610: - core.cjs loadConfig(): return post_planning_gaps from both the config.json branch and the global ~/.gsd/defaults.json fallback so callers can rely on config.post_planning_gaps regardless of whether the key is present (comment 3127977404, Major). - docs/CONFIGURATION.md: add workflow.post_planning_gaps to the Full Schema JSON example so copy/paste users see the new toggle alongside security_block_on (comment 3127977392, Minor). - tests/post-planning-gaps-2493.test.cjs: regression coverage for loadConfig() — default true when key absent, honors explicit true/false from workflow.post_planning_gaps. |
||
|
|
cc17886c51 |
feat: make model profiles runtime-aware for Codex/non-Claude runtimes (closes #2517) (#2609)
* feat: make model profiles runtime-aware for Codex/non-Claude runtimes (closes #2517) Adds an optional top-level `runtime` config key plus a `model_profile_overrides[runtime][tier]` map. When `runtime` is set, profile tiers (opus/sonnet/haiku) resolve to runtime-native model IDs (and reasoning_effort where supported) instead of bare Claude aliases. Codex defaults from the spec: opus -> gpt-5.4 reasoning_effort: xhigh sonnet -> gpt-5.3-codex reasoning_effort: medium haiku -> gpt-5.4-mini reasoning_effort: medium Claude defaults mirror MODEL_ALIAS_MAP. Unknown runtimes fall back to the Claude-alias safe default rather than emit IDs the runtime cannot accept. reasoning_effort is only emitted into Codex install paths; never returned from resolveModelInternal and never written to Claude agent frontmatter. Backwards compatible: any user without `runtime` set sees identical behavior — the new branch is gated on `config.runtime != null`. Precedence (highest to lowest): 1. per-agent model_overrides 2. runtime-aware tier resolution (when `runtime` is set) 3. resolve_model_ids: "omit" 4. Claude-native default 5. inherit (literal passthrough) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#2517): address adversarial review of #2609 (findings 1-16) Addresses all 16 findings from the adversarial review of PR #2609. Each finding is enumerated below with its resolution. CRITICAL - F1: readGsdRuntimeProfileResolver(targetDir) now probes per-project .planning/config.json AND ~/.gsd/defaults.json with per-project winning, so the PR's headline claim ("set runtime in project config and Codex TOML emit picks it up") actually holds end-to-end. - F2: resolveTierEntry field-merges user overrides with built-in defaults. The CONFIGURATION.md string-shorthand example `{ codex: { opus: "gpt-5-pro" } }` now keeps reasoning_effort from the built-in entry. Partial-object overrides like `{ opus: { reasoning_effort: 'low' } }` keep the built-in model. Both paths regression-tested. MAJOR - F3: resolveReasoningEffortInternal gates strictly on the RUNTIMES_WITH_REASONING_EFFORT allowlist regardless of override presence. Override + unknown-runtime no longer leaks reasoning_effort. - F4: runtime:"claude" is now a no-op for resolution (it is the implicit default). It no longer hijacks resolve_model_ids:"omit". Existing tests for `runtime:"claude"` returning Claude IDs were rewritten to reflect the no-op semantics; new test asserts the omit case returns "". - F5: _readGsdConfigFile in install.js writes a stderr warning on JSON parse failure instead of silently returning null. Read failure and parse failure are warned separately. Library require is hoisted to top of install.js so it is not co-mingled with config-read failure modes. - F6: install.js requires for core.cjs / model-profiles.cjs are hoisted to the top of the file with __dirname-based absolute paths so global npm install works regardless of cwd. Test asserts both lib paths exist relative to install.js __dirname. - F7: docs/CONFIGURATION.md `runtime` row no longer lists `opencode` as a valid runtime — install-path emission for non-Codex runtimes is explicitly out of scope per #2517 / #2612, and the doc now points at #2612 for the follow-on work. resolveModelInternal still accepts any runtime string (back-compat) and falls back safely for unknown values. - F8: Tests now isolate HOME (and GSD_HOME) to a per-test tmpdir so the developer's real ~/.gsd/defaults.json cannot bleed into assertions. Same pattern CodeRabbit caught on PRs #2603 / #2604. - F9: `runtime` and `model_profile_overrides` documented as flat-only in core.cjs comments — not routed through `get()` because they are top-level keys per docs/CONFIGURATION.md and introducing nested resolution for two new keys was not worth the edge-case surface. - F10/F13: loadConfig now invokes _warnUnknownProfileOverrides on the raw parsed config so direct .planning/config.json edits surface unknown runtime values (e.g. typo `runtime: "codx"`) and unknown tier values (e.g. `model_profile_overrides.codex.banana`) at read time. Warnings only — preserves back-compat for runtimes added later. Per-process warning cache prevents log spam across repeated loadConfig calls. MINOR / NIT - F11: Removed dead `tier || 'sonnet'` defensive shortcut. The local is now `const alias = tier;` with a comment explaining why `tier` is guaranteed truthy at that point (every MODEL_PROFILES entry defines `balanced`, the fallback profile). - F12: Extracted resolveTierEntry() in core.cjs as the single source of truth for runtime-aware tier resolution. core.cjs and bin/install.js both consume it — no duplicated lookup logic between the two files. - F14: Added regression tests for findings #1, #2, #3, #4, #6, #10, #13 in tests/issue-2517-runtime-aware-profiles.test.cjs. Each must-fix path has a corresponding test that fails against the pre-fix code and passes against the post-fix code. - F15: docs/CONFIGURATION.md `model_profile` row cross-references #1713 / #1806 next to the `adaptive` enum value. - F16: RUNTIME_PROFILE_MAP remains in core.cjs as the single source of truth; install.js imports it through the exported resolveTierEntry helper rather than carrying its own copy. Doc files (CONFIGURATION.md, USER-GUIDE.md, settings.md) intentionally still embed the IDs as text — code comment in core.cjs flags that those doc files must be updated whenever the constant changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
73c1af5168 |
fix(#2543): replace legacy /gsd-<cmd> syntax with /gsd:<cmd> across all source files (#2595)
Commands are now installed as commands/gsd/<name>.md and invoked as /gsd:<name> in Claude Code. The old hyphen form /gsd-<name> was still hardcoded in hundreds of places across workflows, references, templates, lib modules, and command files — causing "Unknown command" errors whenever GSD suggested a command to the user. Replace all /gsd-<cmd> occurrences where <cmd> is a known command name (derived at runtime from commands/gsd/*.md) using a targeted Node.js script. Agent names, tool names (gsd-sdk, gsd-tools), directory names, and path fragments are not touched. Adds regression test tests/bug-2543-gsd-slash-namespace.test.cjs that enforces zero legacy occurrences going forward. Removes inverted tests/stale-colon-refs.test.cjs (bug #1748) which enforced the now-obsolete hyphen form; the new bug-2543 test supersedes it. Updates 5 assertion tests that hardcoded the old hyphen form to accept the new colon form. Closes #2543 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0d6349a6c1 |
fix(#2554): preserve leading zero in getMilestonePhaseFilter (#2585)
The normalization `replace(/^0+/, '')` over-stripped decimal phase IDs: `"00.1"` collapsed to `".1"`, while the disk-side extractor yielded `"0.1"` from `"00.1-<slug>"`. Set membership failed and inserted decimal phases were silently excluded from every disk scan inside `buildStateFrontmatter`, causing `state update` to rewind progress counters. Strip leading zeros only when followed by a digit (`replace(/^0+(?=\d)/, '')`), preserving the zero before the decimal point while keeping existing behavior for zero-padded integer IDs. Closes #2554 |
||
|
|
2b494407e5 |
feat(assembly): add link mode for CLAUDE.md @-reference sections (#2484)
* feat(assembly): add link mode for CLAUDE.md @-reference sections (#2415) Adds `claude_md_assembly.mode: "link"` config option that writes `@.planning/<source>` instead of inlining content between GSD markers, reducing typical CLAUDE.md size by ~65%. Per-block overrides available via `claude_md_assembly.blocks.<section>`. Falls back to embed for sections without a real source file (workflow, fallbacks). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): add positive assertion for embedded workflow content (CodeRabbit #2484) The negative assertion only confirmed @GSD defaults wasn't written. Add assert.ok(content.includes('GSD Workflow Enforcement')) to verify the workflow section is actually embedded inline when link mode falls back. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
5afcd5577e |
fix: zero-padded phase numbers bypass archived-phase guard; stale current_milestone (#2458)
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing Closes #2422 Closes #2420 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sdk): skip stateVersion early-return for shipped milestones When STATE.md has a stale `milestone: v1.0` entry but v1.0 is already shipped (heading contains ✅ in ROADMAP.md), the stateVersion early-return path in getMilestoneInfo was returning v1.0 instead of detecting the new active milestone. Two-part fix: 1. In the stateVersion block: skip the early-return when the matched heading line includes ✅ (shipped marker). Fall through to normal detection instead. 2. In the heading-format fallback regex: add a negative lookahead `(?!.*✅)` so the regex never matches a ✅ heading regardless of whether stateVersion was present. This handles the no-STATE.md case and ensures fallthrough from part 1 actually finds the next milestone. Adds two regression tests covering both ✅-suffix (`## v1.0 ✅ Name`) and ✅-prefix (`## ✅ v1.0 Name`) heading formats. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(core): allow padded-and-unpadded phase headings in getRoadmapPhaseInternal The zero-strip normalization (01→1) fixed the archived-phase guard but broke lookup against ROADMAP headings that still use zero-padded numbers like "Phase 01:". Change the regex to use 0*<normalized> so both formats match, making the fix robust regardless of ROADMAP heading style. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
fbf30792f3 |
docs: authoritative shipped-surface inventory with filesystem-backed parity tests (#2390)
* docs: finish trust-bug fixes in user guide and commands Correct load-bearing defects in the v1.36.0 docs corpus so readers stop acting on wrong defaults and stale exhaustiveness claims. - README.md: drop "Complete feature"/"Every command"/"All 18 agents" exhaustiveness claims; replace version-pinned "What's new in v1.32" bullet with a CHANGELOG pointer. - CONFIGURATION.md: fix `claude_md_path` default (null/none -> `./CLAUDE.md`) in both Full Schema and core settings table; correct `workflow.tdd_mode` provenance from "Added in v1.37" to "Added in v1.36". - USER-GUIDE.md: fix `workflow.discuss_mode` default (`standard` -> `discuss`) in the workflow-toggles table AND in the abbreviated Full Schema JSON block above it; align the Options cell with the shipped enum. - COMMANDS.md: drop "Complete command syntax" subtitle overclaim to match the README posture. - AGENTS.md: weaken "All 21 specialized agents" header to reflect that the `agents/` filesystem is authoritative (shipped roster is 31). Part 1 of a stacked docs refresh series (PR 1/4). * docs: refresh shipped surface coverage for v1.36 Close the v1.36.0 shipped-surface gaps in the docs corpus. - COMMANDS.md: add /gsd-graphify section (build/query/status/diff) and its config gate; expand /gsd-quick with --validate flag and list/ status/resume subcommands; expand /gsd-thread with list --open, list --resolved, close <slug>, status <slug>. - CLI-TOOLS.md: replace the hardcoded "15 domain modules" count with a pointer to the Module Architecture table; add a graphify verb-family section (build/query/status/diff/snapshot); add Graphify and Learnings rows to the Module Architecture table. - FEATURES.md: add TOC entries for #116 TDD Pipeline Mode and #117 Knowledge Graph Integration; add the #117 body with REQ-GRAPH-01..05. - CONFIGURATION.md: move security_enforcement / security_asvs_level / security_block_on from root into `workflow.*` in Full Schema to match templates/config.json and the gsd-sdk runtime reads; update Security Settings table to use the workflow.* prefix; add planning.sub_repos to Full Schema and description table; add a Graphify Settings section documenting graphify.enabled and graphify.build_timeout. Note: VALID_CONFIG_KEYS in bin/lib/config.cjs does not yet include workflow.security_* or planning.sub_repos, so config-set currently rejects them. That is a pre-existing validator gap that this PR does not attempt to fix; the docs now correctly describe where these keys live per the shipped template and runtime reads. Part 2 of a stacked docs refresh series (PR 2/5), based on PR 1. * docs: make inventory authoritative and reconcile architecture Upgrade docs/INVENTORY.md from "complete for agents, selective for others" to authoritative across all six shipped-surface families, and reconcile docs/ARCHITECTURE.md against the new inventory so the PR that introduces INVENTORY does not also introduce an INVENTORY/ARCHITECTURE contradiction. - docs/AGENTS.md: weaken "21 specialized agents" header to 21 primary + 10 advanced (31 shipped); add new "Advanced and Specialized Agents" section with concise role cards for the 10 previously-omitted shipped agents (pattern-mapper, debug-session-manager, code-reviewer, code-fixer, ai-researcher, domain-researcher, eval-planner, eval-auditor, framework-selector, intel-updater); footnote the Agent Tool Permissions Summary as primary-agents-only so it no longer misleads. - docs/INVENTORY.md (rewritten to be authoritative): * Full 31-agent roster with one-line role + spawner + primary-doc status per agent (unchanged from prior partial work). * Commands: full 75-row enumeration grouped by Core Workflow, Phase & Milestone Management, Session & Navigation, Codebase Intelligence, Review/Debug/Recovery, and Docs/Profile/Utilities — each row carries a one-line role derived from the command's frontmatter and a link to the source file. * Workflows: full 72-row enumeration covering every get-shit-done/workflows/*.md, with a one-line role per workflow and a column naming the user-facing command (or internal orchestrator) that invokes it. * References: full 41-row enumeration grouped by Core, Workflow, Thinking-Model clusters, and the Modular Planner decomposition, matching the groupings docs/ARCHITECTURE.md already uses; notes the few-shot-examples subdirectory separately. * CLI Modules and Hooks: unchanged — already full rosters. * Maintenance section rewritten to describe the drift-guard test suite that will land in PR4 (inventory-counts, commands-doc-parity, agents-doc-parity, cli-modules-doc-parity, hooks-doc-parity). - docs/ARCHITECTURE.md reconciled against INVENTORY: * References block: drop the stale "(35 total)" count; point at INVENTORY.md#references-41-shipped for the authoritative count. * CLI Tools block: drop the stale "19 domain modules" count; point at INVENTORY.md#cli-modules-24-shipped for the authoritative roster. * Agent Spawn Categories: relabel as "Primary Agent Spawn Categories" and add a footer naming the 10 advanced agents and pointing at INVENTORY.md#agents-31-shipped for the full 31-agent roster. - docs/CONFIGURATION.md: preserve the six model-profile rows added in the prior partial work, and tighten the fallback note so it names the 13 shipped agents without an explicit profile row, documents model_overrides as the escape hatch, and points at INVENTORY.md for the authoritative 31-agent roster. Part 3 of a stacked docs refresh series (PR 3/4). Remaining consistency work (USER-GUIDE config-section delete-and-link, FEATURES.md TOC reorder, ARCHITECTURE.md Hook-table expansion + installation-layout collapse, CLI-TOOLS.md module-row additions, workflow-discuss-mode invocation normalization, and the five doc-parity tests) lands in PR4. * test(docs): add consistency guards and remove duplicate refs Consolidates USER-GUIDE.md's command/config duplicates into pointers to COMMANDS.md and CONFIGURATION.md (kills a ghost `resolve_model_ids` key and a stale `discuss_mode: standard` default); reorders FEATURES.md TOC chronologically so v1.32 precedes v1.34/1.35/1.36; expands ARCHITECTURE.md's Hook table to the 11 shipped hooks (gsd-read-injection-scanner, gsd-check-update-worker) and collapses the installation-layout hook enumeration to the *.js/*.sh pattern form; adds audit/gsd2-import/intel rows and state signal-*, audit-open, from-gsd2 verbs to CLI-TOOLS.md; normalizes workflow-discuss-mode.md invocations to `node gsd-tools.cjs config-set`. Adds five drift guards anchored on docs/INVENTORY.md as the authoritative roster: inventory-counts (all six families), commands/agents/cli-modules/hooks parity checks that every shipped surface has a row somewhere. * fix(convergence): thread --ws to review agent; add stall and max-cycles behavioral tests - Thread GSD_WS through to review agent spawn in plan-review-convergence workflow (step 5a) so --ws scoping is symmetric with planning step - Add behavioral stall detection test: asserts workflow compares HIGH_COUNT >= prev_high_count and emits a stall warning - Add behavioral --max-cycles 1 test: asserts workflow reaches escalation gate when cycle >= MAX_CYCLES with HIGH > 0 after a single cycle - Include original PR files (commands, workflow, tests) as the branch predated the PR commits Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docs,config): PR #2390 review — security_* config keys and REQ-GRAPH-02 scope Addresses trek-e's review items that don't require rebase: - config.cjs: add workflow.security_enforcement, workflow.security_asvs_level, workflow.security_block_on to VALID_CONFIG_KEYS so gsd-sdk config-set accepts them (closed the gap where docs/CONFIGURATION.md listed keys the validator rejected). - core.cjs: add matching CONFIG_DEFAULTS entries (true / 1 / 'high') so the canonical defaults table matches the documented values. - config.cjs: wire the three keys into the new-project workflow defaults so fresh configs inherit them. - planning-config.md: document the three keys in the Workflow Fields table, keeping the CONFIG_DEFAULTS ↔ doc parity test happy. - config-field-docs.test.cjs: extend NAMESPACE_MAP so the flat keys in CONFIG_DEFAULTS resolve to their workflow.* doc rows. - FEATURES.md REQ-GRAPH-02: split the slash-command surface (build|query| status|diff) from the CLI surface which additionally exposes `snapshot` (invoked automatically at the tail of `graphify build`). The prior text overstated the slash-command surface. * docs(inventory): refresh rosters and counts for post-rebase drift origin/main accumulated surfaces since this PR was authored: - Agents: 31 → 33 (+ gsd-doc-classifier, gsd-doc-synthesizer) - Commands: 76 → 82 (+ ingest-docs, ultraplan-phase, spike, spike-wrap-up, sketch, sketch-wrap-up) - Workflows: 73 → 79 (same 6 names) - References: 41 → 49 (+ debugger-philosophy, doc-conflict-engine, mandatory-initial-read, project-skills-discovery, sketch-interactivity, sketch-theme-system, sketch-tooling, sketch-variant-patterns) Adds rows in the existing sub-groupings, introduces a Sketch References subsection, and bumps all four headline counts. Roles are pulled from source frontmatter / purpose blocks for each file. All 5 parity tests (inventory-counts, agents-doc-parity, commands-doc-parity, cli-modules-doc-parity, hooks-doc-parity) pass against this state — 156 assertions, 0 failures. Also updates the 'Coverage note' advanced-agent count 10 → 12 and the few-shot-examples footnote "41 top-level references" → "49" to keep the file internally consistent. * docs(agents): add advanced stubs for gsd-doc-classifier and gsd-doc-synthesizer Both agents ship on main (spawned by /gsd-ingest-docs) but had no coverage in docs/AGENTS.md. Adds the "advanced stub" entries (Role, property table, Key behaviors) following the template used by the other 10 advanced/specialized agents in the same section. Also updates the Agent Tool Permissions Summary scope note from "10 advanced/specialized agents" to 12 to reflect the two new stubs. * docs(commands): add entries for ingest-docs, ultraplan-phase, plan-review-convergence These three commands ship on main (plan-review-convergence via trek-e's 4b452d29 commit on this branch) but had no user-facing section in docs/COMMANDS.md — they lived only in INVENTORY.md. The commands-doc-parity test already passes via INVENTORY, but the user-facing doc was missing canonical explanations, argument tables, and examples. - /gsd-plan-review-convergence → Core Workflow (after /gsd-plan-phase) - /gsd-ultraplan-phase → Core Workflow (after plan-review-convergence) - /gsd-ingest-docs → Brownfield (after /gsd-import, since both consume the references/doc-conflict-engine.md contract) Content pulled from each command's frontmatter and workflow purpose block. * test: remove redundant ARCHITECTURE.md count tests tests/architecture-counts.test.cjs and tests/command-count-sync.test.cjs were added when docs/ARCHITECTURE.md carried hardcoded counts for commands/ workflows/agents. With the PR #2390 cleanup, ARCHITECTURE.md no longer owns those numbers — docs/INVENTORY.md does, enforced by tests/inventory-counts.test.cjs (scans the same filesystem directories with the same readdirSync filter). Keeping these ARCHITECTURE-specific tests would re-introduce the hardcoded counts they guard, defeating trek-e's review point. The single-source-of- truth parity tests already catch the same drift scenarios. Related: #2257 (the regression this replaced). --------- Co-authored-by: Tom Boucher <trekkie@nomorestars.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
3589f7b256 |
fix(worktrees): prune orphaned worktrees in code, not prose (#2367)
* feat: add /gsd-spec-phase — Socratic spec refinement with ambiguity scoring (#2213) Introduces `/gsd-spec-phase <phase>` as an optional pre-step before discuss-phase. Clarifies WHAT a phase delivers (requirements, boundaries, acceptance criteria) with quantitative ambiguity scoring before discuss-phase handles HOW to implement. - `commands/gsd/spec-phase.md` — slash command routing to workflow - `get-shit-done/workflows/spec-phase.md` — full Socratic interview loop (up to 6 rounds, 5 rotating perspectives: Researcher, Simplifier, Boundary Keeper, Failure Analyst, Seed Closer) with weighted 4-dimension ambiguity gate (≤ 0.20 to write SPEC.md) - `get-shit-done/templates/spec.md` — SPEC.md template with falsifiable requirements (Current/Target/Acceptance per requirement), Boundaries, Acceptance Criteria, Ambiguity Report, and Interview Log; includes two full worked examples - `get-shit-done/workflows/discuss-phase.md` — new `check_spec` step detects `{padded_phase}-SPEC.md` at startup; displays "Found SPEC.md — N requirements locked. Focusing on implementation decisions."; `analyze_phase` respects `spec_loaded` flag to skip "what/why" gray areas; `write_context` emits `<spec_lock>` section with boundary summary and canonical ref to SPEC.md - `docs/ARCHITECTURE.md` — update command/workflow counts (74→75, 71→72) Closes #2213 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(worktrees): auto-prune merged worktrees in code, not prose Adds pruneOrphanedWorktrees(repoRoot) to core.cjs. It runs on every cmdInitProgress call (the entry point for most GSD commands) and removes linked worktrees whose branch is fully merged into main, then runs git worktree prune to clear stale references. Guards prevent removal of the main worktree, the current process.cwd(), or any unmerged branch. Covered by 4 new real-git integration tests in tests/prune-orphaned-worktrees.test.cjs (TDD red→green). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
875b257c18 |
fix(init): embed auto_advance/auto_chain_active/mode in init plan-phase output (#2228)
Prevents infinite config-get loops on Kimi K2.5 and other models that re-execute bash tool calls when they encounter config-get subshell patterns. Values are now bundled into the init plan-phase JSON so step 15 of plan-phase.md can read them directly without separate shell calls. Closes #2192 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
72af8cd0f7 |
fix: display relative time in intel status output (#2132)
* fix: display relative time instead of UTC in intel status output
The `updated_at` timestamps in `gsd-tools intel status` were displayed
as raw ISO/UTC strings, making them appear to show the wrong time in
non-UTC timezones. Replace with fuzzy relative times ("5 minutes ago",
"1 day ago") which are timezone-agnostic and more useful for freshness.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression tests for timeAgo utility
Covers boundary values (seconds/minutes/hours/days/months/years),
singular vs plural formatting, and future-date edge case.
Addresses review feedback on #2132.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
8009b67e3e |
feat: expose tdd_mode in init JSON and add --tdd flag override (#2124)
* test(2123): add failing tests for TDD init JSON exposure and --tdd flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(2123): expose tdd_mode in init JSON and add --tdd flag override Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
23763f920b |
feat(config): add configurable claude_md_path setting (#2010) (#2102)
Allow users to control where GSD writes its managed CLAUDE.md sections via a `claude_md_path` setting in .planning/config.json, enabling separation of GSD content from team-shared CLAUDE.md in shared repos. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f34dc66fa9 |
fix(core): use dedicated temp subdirectory for GSD temp files (#1975) (#2100)
Move GSD temp file writes from os.tmpdir() root to os.tmpdir()/gsd subdirectory. This limits reapStaleTempFiles() scan to only GSD files instead of scanning the entire system temp directory. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f8526b5c01 |
fix: complete planningDir migration for config CRUD, template fill, and verify (#1986)
* fix(config): route CRUD through planningDir to honor GSD_PROJECT PR #1484 added planningDir(cwd) and the GSD_PROJECT env var so a workspace can host multiple projects under .planning/{project}/. loadConfig() in core.cjs (line 256) was migrated at the time, but the four CRUD entry points in config.cjs and the planningPaths() helper in core.cjs were left resolving against planningRoot(cwd). The result was a silent split-brain in any multi-project workspace: - cmdConfigGet, setConfigValue, ensureConfigFile, cmdConfigNewProject all wrote to and read from .planning/config.json - loadConfig read from .planning/{GSD_PROJECT}/config.json So `gsd-tools config-get workflow.discuss_mode` returned "unset" even when the value was correctly stored in the project-routed file, because the reader and writer pointed at different paths. planningPaths() carried a comment that "Shared paths (project, config) always resolve to the root .planning/" which described the original intent, but loadConfig() already contradicted that intent for config.json. project and config now both resolve through planningDir() so the contract matches the only function that successfully read config.json in the multi-project case. Single-project users (no GSD_PROJECT set) are unaffected: planningRoot() and planningDir() return the same path when no project is configured. Verification: in a workspace with .planning/projectA/config.json and GSD_PROJECT=projectA, `gsd-tools config-get workflow.discuss_mode` now returns the value instead of "Error: Key not found". Backward compat verified by running the same command without GSD_PROJECT in a single-project layout. Affected sites: - get-shit-done/bin/lib/config.cjs cmdConfigNewProject (line 199) - get-shit-done/bin/lib/config.cjs ensureConfigFile (line 244) - get-shit-done/bin/lib/config.cjs setConfigValue (line 294) - get-shit-done/bin/lib/config.cjs cmdConfigGet (line 367) - get-shit-done/bin/lib/core.cjs planningPaths.config (line 706) - get-shit-done/bin/lib/core.cjs planningPaths.project (line 705) * fix(template): emit project-aware references in template fill plan The template fill plan body hardcoded `@.planning/PROJECT.md`, `@.planning/ROADMAP.md`, and `@.planning/STATE.md` references. In a multi-project workspace these resolve to nothing because the actual project, roadmap, and state files live under .planning/{GSD_PROJECT}/. `gsd-tools verify references` reports them as missing on every PLAN.md generated by template fill in any GSD_PROJECT-routed workspace. Fix: route the references through planningDir(cwd), normalize via the existing toPosixPath helper for cross-platform path consistency, and embed them as `@<relative-path>` matching the phase-relative reference pattern used elsewhere in the file. Single-project users (no GSD_PROJECT set) get exactly the same output as before because planningDir() falls back to .planning/ when no project is active. Affected site: get-shit-done/bin/lib/template.cjs cmdTemplateFill plan branch (lines 142-145, the @.planning/ refs in the Context section). * fix(verify): planningDir for cmdValidateHealth and regenerateState cmdValidateHealth resolved projectPath and configPath via planningRoot(cwd) while ROADMAP/STATE/phases/requirements went through planningDir(cwd). The inconsistency reported "missing PROJECT.md" and "missing config.json" in multi-project layouts even when the project-routed copies existed and the config CRUD writers (now also routed by the previous commit in this PR) were writing to them. regenerateState (the /gsd:health --repair STATE.md regeneration path) hardcoded `See: .planning/PROJECT.md` in the generated body, which fails the same reference check it just regenerated for in any GSD_PROJECT-routed workspace. Fix: route both sites through planningDir(cwd). For regenerateState, derive a POSIX-style relative reference from the resolved path so the reference matches verify references' resolution rules. Also dropped the planningRoot import from verify.cjs since it is no longer used after this change. Single-project users (no GSD_PROJECT set) get the same paths as before: planningDir() falls back to .planning/ when no project is configured. Affected sites: - get-shit-done/bin/lib/verify.cjs cmdValidateHealth (lines 536-541) - get-shit-done/bin/lib/verify.cjs regenerateState repair (line 865) - get-shit-done/bin/lib/verify.cjs core.cjs import (line 8, dropped unused planningRoot) |
||
|
|
33575ba91d |
feat: /gsd-ai-integration-phase + /gsd-eval-review — AI framework selection and eval coverage layer (#1971)
* feat: /gsd:ai-phase + /gsd:eval-review — AI evals and framework selection layer Adds a structured AI development layer to GSD with 5 new agents, 2 new commands, 2 new workflows, 2 reference files, and 1 template. Commands: - /gsd:ai-phase [N] — pre-planning AI design contract (inserts between discuss-phase and plan-phase). Orchestrates 4 agents in sequence: framework-selector → ai-researcher → domain-researcher → eval-planner. Output: AI-SPEC.md with framework decision, implementation guidance, domain expert context, and evaluation strategy. - /gsd:eval-review [N] — retroactive eval coverage audit. Scores each planned eval dimension as COVERED/PARTIAL/MISSING. Output: EVAL-REVIEW.md with 0-100 score, verdict, and remediation plan. Agents: - gsd-framework-selector: interactive decision matrix (6 questions) → scored framework recommendation for CrewAI, LlamaIndex, LangChain, LangGraph, OpenAI Agents SDK, Claude Agent SDK, AutoGen/AG2, Haystack - gsd-ai-researcher: fetches official framework docs + writes AI systems best practices (Pydantic structured outputs, async-first, prompt discipline, context window management, cost/latency budget) - gsd-domain-researcher: researches business domain and use-case context — surfaces domain expert evaluation criteria, industry failure modes, regulatory constraints, and practitioner rubric ingredients before eval-planner writes measurable criteria - gsd-eval-planner: designs evaluation strategy grounded in domain context; defaults to Arize Phoenix (tracing) + RAGAS (RAG eval) with detect-first guard for existing tooling - gsd-eval-auditor: retroactive codebase scan → scores eval coverage Integration points: - plan-phase: non-blocking nudge (step 4.5) when AI keywords detected and no AI-SPEC.md present - settings: new workflow.ai_phase toggle (default on) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: refine ai-integration-phase layer — rename, house style, consistency fixes Amends the ai-evals framework layer (df8cb6c) with post-review improvements before opening upstream PR. Rename /gsd:ai-phase → /gsd:ai-integration-phase: - Renamed commands/gsd/ai-phase.md → ai-integration-phase.md - Renamed get-shit-done/workflows/ai-phase.md → ai-integration-phase.md - Updated config key: workflow.ai_phase → workflow.ai_integration_phase - Updated repair action: addAiPhaseKey → addAiIntegrationPhaseKey - Updated all 84 cross-references across agents, workflows, templates, tests Consistency fixes (same class as PR #1380 review): - commands/gsd: objective described 3-agent chain, missing gsd-domain-researcher - workflows/ai-integration-phase: purpose tag described 3-agent chain + "locks three things" — updated to 4 agents + 4 outputs - workflows/ai-integration-phase: missing DOMAIN_MODEL resolve-model call in step 1 (domain-researcher was spawned in step 7.5 with no model variable) - workflows/ai-integration-phase: fractional step ## 7.5 renumbered to integers (steps 8–12 shifted) Agent house style (GSD meta-prompting conformance): - All 5 new agents refactored to execution_flow + step name="" structure - Role blocks compressed to 2 lines (removed verbose "Core responsibilities") - Added skills: frontmatter to all 5 agents (agent-frontmatter tests) - Added # hooks: commented pattern to file-writing agents - Added ALWAYS use Write tool anti-heredoc instruction to file-writing agents - Line reductions: ai-researcher −41%, domain-researcher −25%, eval-planner −26%, eval-auditor −25%, framework-selector −9% Test coverage (tests/ai-evals.test.cjs — 48 tests): - CONFIG: workflow.ai_integration_phase defaults and config-set/get - HEALTH: W010 warning emission and addAiIntegrationPhaseKey repair - TEMPLATE: AI-SPEC.md section completeness (10 sections) - COMMAND: ai-integration-phase + eval-review frontmatter validity - AGENTS: all 5 new agent files exist - REFERENCES: ai-evals.md + ai-frameworks.md exist and are non-empty - WORKFLOW: plan-phase nudge integration, workflow files exist + agent coverage 603/603 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add Google ADK to framework selector and reference matrix Google ADK (released March 2025) was missing from the framework options. Adds Python + Java multi-agent framework optimised for Gemini / Vertex AI. - get-shit-done/references/ai-frameworks.md: add Google ADK profile (type, language, model support, best for, avoid if, strengths, weaknesses, eval concerns); update Quick Picks, By System Type, and By Model Commitment tables - agents/gsd-framework-selector.md: add "Google (Gemini)" to model provider interview question - agents/gsd-ai-researcher.md: add Google ADK docs URL to documentation_sources Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: adapt to upstream conventions post-rebase - Remove skills: frontmatter from all 5 new agents (upstream changed convention — skills: breaks Gemini CLI and must not be present) - Add workflow.ai_integration_phase to VALID_CONFIG_KEYS whitelist in config.cjs (config-set blocked unknown keys) - Add ai_integration_phase: true to CONFIG_DEFAULTS in core.cjs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: rephrase 4b.1 line to avoid false-positive in prompt-injection scan "contract as a Pydantic model" matched the `act as a` pattern case-insensitively. Rephrased to "output schema using a Pydantic model". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: adapt to upstream conventions (W016, colon refs, config docs) - Replace verify.cjs from upstream to restore W010-W015 + cmdValidateAgents, lost when rebase conflict was resolved with --theirs - Add W016 (workflow.ai_integration_phase absent) inside the config try block, avoids collision with upstream's W010 agent-installation check - Add addAiIntegrationPhaseKey repair case mirroring addNyquistKey pattern - Replace /gsd: colon format with /gsd- hyphen format across all new files (agents, workflows, templates, verify.cjs) per stale-colon-refs guard (#1748) - Add workflow.ai_integration_phase to planning-config.md reference table - Add ai_integration_phase → workflow.ai_integration_phase to NAMESPACE_MAP in config-field-docs.test.cjs so CONFIG_DEFAULTS coverage check passes - Update ai-evals tests to use W016 instead of W010 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add 5 new agents to E2E Copilot install expected list gsd-ai-researcher, gsd-domain-researcher, gsd-eval-auditor, gsd-eval-planner, gsd-framework-selector added to the hardcoded expected agent list in copilot-install.test.cjs (#1890). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
cb1eb7745a |
fix(core): preserve letter suffix case in normalizePhaseName (#1963)
* fix(core): preserve letter suffix case in normalizePhaseName (#1962) normalizePhaseName uppercased letter suffixes (e.g., "16c" → "16C"), causing directory/roadmap mismatches on case-sensitive filesystems. init progress couldn't match directory "16C-name" to roadmap "16c". Preserve original case — comparePhaseNum still uppercases for sorting (correct), but normalizePhaseName is used for display and directory creation where case must match the roadmap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(phase): update existing test to expect preserved letter case The 'uppercases letters' test asserted the old behavior (3a → 03A). With normalizePhaseName now preserving case, update expectations to match (3a → 03a) and rename the test to 'preserves letter case'. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
4dd35f6b69 |
fix(state): correct TOCTOU races, busy-wait, lock cleanup, and config locking (#1944)
cmdStateUpdateProgress, cmdStateAddDecision, cmdStateAddBlocker,
cmdStateResolveBlocker, cmdStateRecordSession, and cmdStateBeginPhase from
bare readFileSync+writeStateMd to readModifyWriteStateMd, eliminating the
TOCTOU window where two concurrent callers read the same content and the
second write clobbers the first.
Atomics.wait(), matching the pattern already used in withPlanningLock in
core.cjs.
and core.cjs and register a process.on('exit') handler to unlink them on
process exit. The exit event fires even when process.exit(1) is called
inside a locked region, eliminating stale lock files after errors.
read-modify-write body of setConfigValue in a planning lock, preventing
concurrent config-set calls from losing each other's writes.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
60fa2936dd |
fix(core): add atomicWriteFileSync to prevent truncated files on kill (#1943)
Replaces direct fs.writeFileSync calls for STATE.md, ROADMAP.md, and config.json with write-to-temp-then-rename so a process killed mid-write cannot leave an unparseable truncated file. Falls back to direct write if rename fails (e.g. cross-device). Adds regression tests for the helper. Closes #1915 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9679e18ef4 |
perf(config): cache isGitIgnored result per process lifetime (#1898)
loadConfig() calls isGitIgnored() which spawns a git check-ignore subprocess. The result is stable for the process lifetime but was being recomputed on every call. With 28+ loadConfig call sites, this could spawn multiple redundant git subprocesses per CLI invocation. A module-level Map cache keyed on (cwd, targetPath) ensures the subprocess fires at most once per unique pair per process. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
383007dca4 |
feat(workflows): add conditional thinking partner at decision points (#1816)
Integrate lightweight thinking partner analysis at two workflow decision points, controlled by features.thinking_partner config (default: false): 1. discuss-phase: when developer answers reveal competing priorities (detected via keyword/structural signals), offers brief tradeoff analysis before locking decisions 2. plan-phase: when plan-checker flags architectural tradeoffs, analyzes options and recommends an approach aligned with phase goals before entering the revision loop The thinking partner is opt-in, skippable (No, I have decided), and brief (3-5 bullets). A third integration point for /gsd-explore will be added when #1729 lands. Closes #1726 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
89c2469ff2 |
feat(config): apply ~/.gsd/defaults.json as fallback for pre-project commands (#1738)
* feat(config): apply ~/.gsd/defaults.json as fallback for pre-project commands (#1683) When .planning/config.json is missing (e.g., running GSD commands outside a project), loadConfig() now checks ~/.gsd/defaults.json before returning hardcoded defaults. This lets users set preferred model_profile, context_window, subagent_timeout, and other settings globally. Only whitelisted keys are merged — unknown keys in defaults.json are silently ignored. If defaults.json is missing or contains invalid JSON, the hardcoded defaults are returned as before. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(config): scope defaults.json fallback to pre-project context only Only consult ~/.gsd/defaults.json when .planning/ does not exist (truly pre-project). When .planning/ exists but config.json is missing, return hardcoded defaults — avoids interference with tests and initialized projects. Use GSD_HOME env var for test isolation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
771259597b |
refactor: deduplicate config defaults into CONFIG_DEFAULTS constant (#1708)
Fixes #1707 Extracts config defaults from loadConfig() into an exported CONFIG_DEFAULTS constant in core.cjs. config.cjs and verify.cjs now reference CONFIG_DEFAULTS instead of duplicating values, preventing future divergence. |
||
|
|
839ea22d06 |
fix: replace shell sleep with cross-platform Atomics.wait in planning lock (#1693)
Fixes #1692 spawnSync('sleep', ['0.1']) fails silently on Windows (ENOENT), causing a tight busy-loop during lock contention. Atomics.wait() provides a cross-platform 100ms blocking wait available in Node 22+. |
||
|
|
ade67cf9f9 |
fix: update MODEL_ALIAS_MAP to current Claude model IDs (#1691)
Fixes #1690 - opus: claude-opus-4-0 → claude-opus-4-6 - sonnet: claude-sonnet-4-5 → claude-sonnet-4-6 - haiku: claude-haiku-3-5 → claude-haiku-4-5 Also updates the stale haiku reference in sdk/src/session-runner.ts and documentation examples in CONFIGURATION.md (en, ja-JP, ko-KR). |
||
|
|
dc2afa299b |
feat(i18n): add response_language config for cross-phase language consistency (#1412)
* feat(i18n): add response_language config for cross-phase language consistency Adds `response_language` config key that propagates through all init outputs via withProjectRoot(). Workflows read this field and instruct agents to present user-facing questions in the configured language, solving the problem of language preference resetting at phase boundaries. Usage: gsd-tools config-set response_language "Portuguese" Closes #1399 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(security): allowlist discuss-phase.md for size threshold discuss-phase.md legitimately exceeds 50K chars due to power mode and i18n directives — not prompt stuffing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |