d137ce86ec29d7fcd8cfc975982f75d8802fa9ca
354 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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). |
||
|
|
ca78b65de7 |
fix(#2973): /gsd-profile-user writes dev-preferences.md to skills/, not legacy commands/gsd/ (#3003)
* fix(#2973): /gsd-profile-user writes dev-preferences.md to skills/ not legacy commands/gsd/ v1.39.0's install summary claimed the legacy ~/.claude/commands/gsd/ directory had been removed in favor of skills-only architecture, but the cmdGenerateDevPreferences writer at profile-output.cjs:781 still defaulted to the legacy path. Every /gsd-profile-user --refresh deterministically re-created the legacy directory. Missed in PR #1540's migration because dev-preferences is a runtime-generated user artifact, not a GSD-shipped command file. Fix: - Writer default: ~/.claude/skills/gsd-dev-preferences/SKILL.md - profile-user.md Display message + artifact list reference new path - New migrateLegacyDevPreferencesToSkill(targetDir, saved) installer helper. Called at all 5 skills-aware install branches. Copies preserved legacy dev-preferences.md into skills/gsd-dev-preferences/ SKILL.md, but ONLY if no SKILL.md already exists -- never clobbers user-customized skill content. Tests: bug-2973-profile-user-skills-path.test.cjs runs the writer in a subprocess (core.cjs:output uses fs.writeSync(1, ...) which bypasses in-process stubbing), asserts the writer's command_path field is the skills location, the file is on disk at that path, the legacy path is NOT created. Tests for migration helper assert it writes when no skill exists and skips when one does. Closes #2973 * chore(#2973): add changeset fragment for PR #3003 * fix(#2973): rephrase comment to avoid cline-install leaked-path lint The new comment at line 780 of profile-output.cjs literally contained the string '~/.claude/commands/gsd/' which the cline-install leaked-path regression test (tests/cline-install.test.cjs:175) correctly flagged. Cline transforms .claude/skills/ -> .cline/skills/ in installed .cjs files but does not transform .claude/commands/. The new comment talks about the legacy 'commands/gsd' subdirectory without the ~/.claude/ prefix, so the lint passes. The path semantics are unchanged -- the runtime construction at line 787 still uses path.join(os.homedir(), '.claude', 'skills', ...) which the lint regex does not match. * test(#2973): add timeout to spawnSync to prevent CI hangs (CR feedback) CodeRabbit on PR #3003: without a timeout, a regression that hangs the writer or dispatcher would block CI indefinitely. Added a 30s timeout (generous for what should complete in <1s) and an explicit signal assertion so a timeout trip surfaces as a clear test failure with context rather than a hung worker. * test(#2973): add allow-test-rule annotation for legitimate product-text parsing The new var-binding lint from #2982/#2985 caught readFileSync(...).match() and readFileSync(...).includes() calls in this test. Both are legitimate structural assertions against the product workflow markdown, not source-grep: - match() extracts the path from a structured Display: "..." line and asserts on the typed path value (same pattern as bug-2470's installer scanForLeakedPaths regex test). - includes() asserts the absence of a legacy path literal. profile-user.md IS the shipped workflow artifact, and its Display: line IS what the user sees. Per the existing test-rigor convention, this is the source-text-is-the-product justification category. Annotated with allow-test-rule citing that category. * chore(#3003): drop direct CHANGELOG.md edit; release entry now lives in .changeset/ The changeset-fragment workflow (#2975) renders fragments into CHANGELOG.md at release time. Direct edits to [Unreleased] on each PR caused merge conflicts on every concurrent PR. This commit restores CHANGELOG.md to match origin/main; the release entry for this fix is preserved in the .changeset/*.md fragment(s) on this branch, which the release workflow consolidates. * fix(#2973): preserve user-owned gsd-dev-preferences skill across wipe (CR) CodeRabbit on PR #3003 caught a real bug: copyCommandsAsClaudeSkills() wipes ALL gsd-* skill directories at the top of every install, then reinstalls from the package source. Since gsd-dev-preferences is user-generated (written by /gsd-profile-user --refresh) and NOT shipped by the npm package, the wipe deletes the user's customized SKILL.md with nothing to restore from. Fix: USER_OWNED_SKILLS allow-list in copyCommandsAsClaudeSkills. Snapshot files under skills/gsd-dev-preferences/ before the wipe, restore after. Same preserve/restore pattern as PR #1924. Tests: bug-2973 grows by 2 cases: - user-customized SKILL.md survives the wipe - non-user-owned gsd-* skills are still wiped (preservation is opt-in) |
||
|
|
ffeeb92c14 |
fix(#2997): mask SECRET_CONFIG_KEYS in SDK config-set/get and init responses (#2999)
* fix(#2997): mask SECRET_CONFIG_KEYS in SDK config-set/get and init responses The CJS→TS port at sdk/src/query/config-mutation.ts:240,243 and config-query.ts:122,128,132 dropped the masking layer that secrets.cjs spec defines for brave_search/firecrawl/exa_search. Result: the SDK echoed plaintext API keys into machine-readable JSON output (stdout, transcripts, CI logs). Adjacent leak in init.ts:673-675 / init.cjs:728-730: the init bundle passed config.brave_search through raw, leaking the API key whenever the user had stored one. Fix: - New sdk/src/query/secrets.ts ports SECRET_CONFIG_KEYS, isSecretKey, maskSecret, maskIfSecret. Exact CJS parity (verified by 17 tests in secrets.test.ts that import secrets.cjs and compare). - config-set masks value + previousValue in response; on-disk plaintext intact (key stays usable). - config-get masks read response. --default flows through unmasked (user's own input, not stored secret). - init.ts/init.cjs mask string values only; booleans (availability flags) pass through unchanged so the typed contract is preserved. Tests: 17 in secrets.test.ts (including CJS parity), 5 in config-mutation.test.ts (#2997 block — covers on-disk-preserved, previousValue masking, short-value, unset, non-secret pass-through), 4 in config-query.test.ts. Closes #2997 * chore(#2997): add changeset fragment for PR #2999 * chore(#2997): add changeset fragment for PR #2999 * chore(#2999): drop direct CHANGELOG.md edit; release entry now lives in .changeset/ The changeset-fragment workflow (#2975) renders fragments into CHANGELOG.md at release time. Direct edits to [Unreleased] on each PR caused merge conflicts on every concurrent PR. This commit restores CHANGELOG.md to match origin/main; the release entry for this fix is preserved in the .changeset/*.md fragment(s) on this branch, which the release workflow consolidates. |
||
|
|
8de8acee46 |
fix(workflows): assert HEAD on per-agent branch before worktree commits (#2924) (#2941)
* fix(workflows): assert HEAD on per-agent branch before worktree commits Worktree-mode setup could leave HEAD attached to a protected branch (master), causing agent commits to land there. The previous response was a destructive self-recovery via 'git update-ref refs/heads/master <sha>', which silently rewinds the protected branch and destroys concurrent commits in multi-active scenarios (parallel agents, user committing while agent runs). - Reorder <worktree_branch_check> in execute-phase.md and quick.md to assert HEAD via 'git symbolic-ref' BEFORE any 'git reset --hard'. HALT with a blocker if HEAD is on main/master/develop/trunk/release/* or detached. - Add a per-commit HEAD assertion (step 0) to gsd-executor.md <task_commit_protocol>; HEAD attachment can drift after 'git checkout <sha>'. - Forbid 'git update-ref refs/heads/<protected>' in <destructive_git_prohibition>; surface the blocker rather than self-heal. - Remove '--no-verify' as the worktree-mode default in execute-phase.md, execute-plan.md, quick.md, and references/git-integration.md. Hooks now run on every executor commit; opt out only via workflow.worktree_skip_hooks. - Add regression test that parses the worktree_branch_check blocks structurally and asserts the symbolic-ref check precedes the reset --hard, no workflow performs update-ref on a protected ref, and --no-verify is no longer the default in any parallel-execution prompt. * fix(#2924): address CodeRabbit review findings on worktree HEAD PR - Add positive worktree-agent-* allow-list to <task_commit_protocol> step 0 in gsd-executor.md and to <worktree_branch_check> in execute-phase.md and quick.md. The deny-list (main|master|develop|trunk|release/*) silently allowed feature/* and other arbitrary branches outside the agent namespace. - Register workflow.worktree_skip_hooks in both config schemas (sdk/src/query/config-schema.ts and get-shit-done/bin/lib/config-schema.cjs) and document it in docs/CONFIGURATION.md so config-set accepts it. - Fix stash lifecycle in execute-phase.md post-wave hook validation: stash under a named ref and pop after the hook run; warn on pop failure. - Pre-dispatch PLAN.md commit in quick.md: gate on git diff --cached --quiet for idempotency and exit 1 with a clear error on commit failure (both the --no-verify and the normal branches) — no more swallowing real errors. - Test fixes (tests/bug-2924-worktree-head-attachment.test.cjs): - Parse the protected-branch alternation structurally and require main, master, develop, trunk, release/.* (release/* was previously skipped by the \\b...\\b regex). - Use fs.readdirSync(dir, { recursive: true }) so workflows in nested subdirectories are also asserted against the update-ref ban. - Add allow-list assertions for execute-phase.md, quick.md, and gsd-executor.md to lock in the new positive namespace check. * test(#2924): assert sub-section end marker exists before slicing * test(#2924): use section boundary instead of fixed window for parallel-agents slice |
||
|
|
2cc8796265 |
fix(config-get): return schema default for context_window when absent (#2944)
* fix(config-get): return schema default for context_window when absent (#2943) cmdConfigGet in bin/lib/config.cjs now consults a SCHEMA_DEFAULTS map before emitting "Key not found", so context_window (and any future schema-defaulted keys) return their default value (exit 0) when not set in config.json. Also updates the stale subagent-timeout.test.cjs assertion that expected the old broken behavior (exit 1 / "Key not found") to match the corrected behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: use distinct sentinel to prove --default wins over schema default (#2943) * docs: update CHANGELOG.md for #2943 fix --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
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)
|
||
|
|
444db1714b |
refactor(query): manifest-backed routing seam + family adapters (#2908)
Merging validated command-seam foundation. |
||
|
|
6dce1de4a7 |
fix: gap-analysis parses mixed requirement prefixes and skips table headers (#2902)
* fix: parse non-REQ IDs in gap-analysis and ignore table headers * fix: parse requirement IDs from first traceability column only --------- Co-authored-by: Tom Boucher <thomas.boucher@sas.com> |
||
|
|
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> |
||
|
|
951d5bf7c0 |
fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans (#2896)
* fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five plan files existed on disk. Investigation showed the planner had written files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them silently — collapsing two distinct states into the same `plans: []` return: - directory truly has no plans (legit empty) - directory has plans but the filter rejected them (user/agent error) The canonical contract is documented in three places: - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080) - `commands/gsd/plan-phase.md` - `references/universal-anti-patterns.md` (rule 26) It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids `PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is correct per that contract. The bug is that the executor never tells the user when the contract was violated — they just see `plan_count: 0` with no signal. Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans the phase directory for files matching `*PLAN*.md` (the diagnostic net) that the canonical filter rejected, excluding legit derivatives like `*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist, return a `warning` field naming each one and citing the canonical pattern so the user knows what to rename to. Wired into the three filter sites: - `phase-plan-index` (the executor's main entry point) - `phases list --type plans` - `find-phase` The strict filter itself is unchanged — existing canonical plans behave identically. This is purely a diagnostic that converts silent-empty into loud-with-actionable-error. Tests: - `phase-plan-index returns warning for reporter's exact filename pattern (`01-PLAN-01-foundation.md`)` - `truly empty dir does not emit a warning` - `canonical plans + outline + pre-bounce files do not emit a warning` Closes #2893 * test(#2893): add parity tests for find-phase and phases list --type plans warnings CodeRabbit's only finding on the prior commit: I wired the warning into three filter sites (`phase-plan-index`, `find-phase`, `phases list --type plans`) but only `phase-plan-index` had test coverage for the warning shape. The other two paths could silently diverge during future refactors — exactly the silent-drift class of bug this fix exists to prevent. Add four parity tests mirroring the existing two: - find-phase: non-canonical filenames produce a warning naming each offender + citing the canonical pattern. - find-phase: canonical plan + derivative files (PLAN-OUTLINE, pre-bounce) produce no warning. - phases list --type plans: same non-canonical case, but assert the warning is prefixed with `${dir}: ` (this path aggregates across phase directories so each offender is tagged with its dir). - phases list --type plans: canonical case, no warning. `node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new). |
||
|
|
5fdc950eb7 |
feat(#2792): namespace meta-skills + keyword-tag descriptions + context utilization guard (#2825)
* feat(#2792): namespace meta-skills retargeted at the post-#2790 surface This branch is now based on #2790's HEAD (the consolidation PR) instead of main, and every routing table targets the consolidated surface so a user routed by a namespace meta-skill never lands at a deleted / folded sub-skill. Cross-PR inconsistencies the original PR #2825 carried (vs #2790): - ns-ideate routed to gsd-note / gsd-add-todo / gsd-add-backlog / gsd-plant-seed → all folded into gsd-capture by #2790. Now routes to gsd-capture (the parent picks the mode from the user's intent). - ns-context routed to gsd-scan and gsd-intel → folded into gsd-map-codebase --fast / --query by #2790. Now routes to those flag forms. - ns-manage routed all workspace intent to gsd-list-workspaces (a list-only entry) → CR also flagged the over-narrow target. #2790 folds into gsd-workspace; routing now points there. - ns-workflow routed to gsd-research-phase → deleted outright by #2790. Removed. - ns-project routed to gsd-plan-milestone-gaps → deleted outright by #2790. Removed. - None of the namespaces previously surfaced #2790's new consolidated skills (gsd-capture, gsd-phase, gsd-config, gsd-workspace, gsd-progress). All five are now reachable through the routers. - extract_learnings → extract-learnings (canonicalized by #2858). Defect fixes within the namespace skills: - Hyphen-form `name:` (gsd-workflow, …) per the canonical naming contract — the colon-form addressed CR's drift complaint. - `Skill` added to allowed-tools on every router. The body instructs "Invoke the matched skill directly using the Skill tool" — without Skill in the permission list the meta-skill cannot route at all. New regression guard in tests/enh-2792-namespace-skills.test.cjs: every gsd-* token in any namespace router's table column resolves to a surviving commands/gsd/*.md file (or to a known consolidated parent for flag-form targets like gsd-map-codebase --fast). This single test would have caught every dead-end route the original PR shipped with. Skill-count cap in tests/enh-2790-skill-consolidation.test.cjs now filters out ns-*.md from its <= 63 cap. Namespace routers are descriptor-only entries, not part of the consolidation surface that cap is policing — they have their own contract in tests/enh-2792-namespace-skills.test.cjs. INVENTORY.md gains a "Namespace Meta-Skills" section with the 6 router rows; INVENTORY-MANIFEST.json gains 6 entries; the headline count moves 59 → 65 to match. Out of scope for this rebase: the gsd-health --context flag (PR #2825 advertised the contract but didn't implement it). That's a separate feature concern and is left untouched here. 5908/5908 on `npm test`. * feat(#2792): implement gsd-health --context utilization guard The original PR #2825 advertised a `--context` flag on gsd-health with a 60%/70% utilization threshold table but never implemented the workflow logic — CR caught it as a contract leak, the rebase deferred it. This commit closes the gap with TDD red/green/refactor. Math layer (pure): - get-shit-done/bin/lib/context-utilization.cjs classifyContextUtilization(tokensUsed, contextWindow) → { percent, state } State boundaries use the exact ratio: < 60% healthy / 60–70% warning / ≥ 70% critical (fracture point) Display percent rounded for humans. Throws TypeError on non-integer or out-of-range inputs. - STATES = Object.freeze({ HEALTHY, WARNING, CRITICAL }) exported so callers reference the names by symbol, not by literal string. SDK CLI integration: - get-shit-done/bin/gsd-tools.cjs `validate context --tokens-used N --context-window M [--json]` routes to the classifier, owns the recommendation copy (the classifier intentionally does not — keeps the renderer free to evolve without touching the math layer or its tests), and uses core.output's rawValue path for the sync-flush guarantee. - sdk/src/query/validate.ts + sdk/src/query/index.ts TypeScript validateContext handler registered at 'validate.context' and 'validate context'. Mirrors the CJS classifier inline (15 lines of arithmetic; not worth a shared cross-language module). User-facing wiring: - commands/gsd/health.md frontmatter advertises --context, body documents the three-state threshold table. - get-shit-done/workflows/health.md adds a `context_check` step that's reached only when --context is set. Step calls `gsd-sdk query validate.context` with self-reported tokensUsed and contextWindow, prints the SDK output verbatim, and ends. Includes a TEXT_MODE plain-text fallback for non-Claude runtimes per #2012. Tests: - tests/context-utilization.test.cjs (17 tests) — pure-function contract: state thresholds at every boundary, percent rounding, input validation, return-shape (no recommendation field — that's the renderer's job). - tests/validate-context.test.cjs (9 tests) — SDK CLI plumbing: arg parsing errors, JSON vs human rendering, recommendation copy pinned per state. - tests/enh-2792-namespace-skills.test.cjs (4 new tests) — markdown contract: --context advertised in argument-hint, threshold table in command body, context_check step exists in workflow, step invokes gsd-sdk query validate.context with both flags. Inventory bookkeeping: - docs/INVENTORY.md "CLI Modules" 31 → 32; new row for context-utilization.cjs. - docs/INVENTORY-MANIFEST.json mirror. 5939/5939 on `npm test`. |
||
|
|
74b81379cf |
fix(#2836): audit-open quick SUMMARY filename + UAT terminal-status drift (#2847)
* fix(#2836): audit-open quick SUMMARY filename + UAT terminal-status drift Fixes two convention drifts in bin/lib/audit.cjs that produced false-positive "open" items at every milestone close: 1. scanQuickTasks: looked for bare `SUMMARY.md`, but workflows/quick.md mandates `${quick_id}-SUMMARY.md`. Now matches either filename so quick tasks created via the documented workflow are recognized. 2. scanUatGaps: only treated `status: complete` as terminal, but workflows/execute-phase.md uses `status: resolved` post-gap-closure. Now treats both `complete` and `resolved` as terminal, with `result: all_pass` as a fallback when status is absent. Also reconciles workflows/help.md one-liner that referenced bare `SUMMARY.md` so docs match the authoritative quick.md workflow. Adds tests/bug-2836-audit-open-summary-uat-drift.test.cjs with 6 structural regression tests covering both fixes plus no-regression cases. * refactor(#2836): hoist TERMINAL_UAT_STATUSES outside scanUatGaps loop Address CodeRabbit nitpick: the Set was being recreated on each UAT file iteration. Hoist to module scope so it is constructed once. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.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> |
||
|
|
eddb2a205b |
fix(#2801): add ingest-docs handler to gsd-tools init dispatch (#2820)
* fix(#2801): add ingest-docs handler to gsd-tools init dispatch The `/gsd-ingest-docs` workflow was broken because `workflows/ingest-docs.md` called `gsd-sdk query init.ingest-docs` but the installed binary is `gsd-tools`, and `gsd-tools init` had no `ingest-docs` case in its dispatch switch. - Added `cmdInitIngestDocs` function to `init.cjs` and exported it; returns `project_exists`, `planning_exists`, `has_git`, `project_path`, `commit_docs` - Added `case 'ingest-docs'` to the `init` switch in `gsd-tools.cjs` - Updated `workflows/ingest-docs.md` to call `gsd-tools init ingest-docs` (line 55) and `gsd-tools commit` (line 292) instead of `gsd-sdk query ...` - Regression test: `tests/bug-2801-ingest-docs-handler.test.cjs` Closes #2801 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2801): address CodeRabbit — commit_docs assertion, broader gsd-sdk detection, bash fence --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
936cf26706 |
fix(#2769): tolerate Requirements header with colon inside bold delimiters (#2782)
* fix(#2769): tolerate Requirements header with colon inside bold delimiters extractReqIds in sdk/src/query/init.ts and the legacy init.cjs port only matched `**Requirements**:` (colon outside bold), so phases declared with the equally-valid markdown form `**Requirements:**` (colon inside bold, which is what the project's own templates emit) returned phase_req_ids: null for both `init plan-phase` and `init execute-phase`. The mirror-image bug in `phase complete`'s REQUIREMENTS.md traceability sweep at get-shit-done/bin/lib/phase.cjs:871 only matched the inside-bold form, silently skipping the REQ-ID checkbox flips for any roadmap that used the outside-bold form. Both parsers now share the same canonical regex that accepts all three rendered-identical variants: **Requirements:** (colon inside bold) **Requirements**: (colon outside bold) **Requirements** : (space before outside colon) Tests: - tests/init.test.cjs — parameterized over the three header variants for both init plan-phase and init execute-phase (6 new behavioral cases). - sdk/src/query/init.test.ts — describe.each over the same variants exercising initPlanPhase through the SDK. - tests/bug-2769-requirements-header-variants.test.cjs — phase complete flips REQ-001 in REQUIREMENTS.md across all three header variants. Closes #2769 * refactor(#2769): centralize REQUIREMENTS_HEADER_RE constant per CodeRabbit |
||
|
|
3ac3a2ae70 |
fix(#2770): coerce non-string depends_on YAML values to preserve dependencies (#2780)
* fix(#2770): coerce non-string truths to preserve cross-cutting constraints `cmdRoadmapAnnotateDependencies` skipped non-string truth entries via `if (typeof t !== 'string') continue`. That avoided the TypeError reported in #2770 but silently dropped legitimate constraints — numeric YAML scalars (`- 3`) and kv-shaped truths from parseMustHavesBlock's continuation-kv path (#2757) — from the cross-cutting analysis, leaving ROADMAP.md under-annotated. Replace the skip-guard with a `coerceTruthToString` helper that: * passes strings through * `String()`-coerces numbers, booleans, bigints * extracts a string field (title, text, name, rule, path, provides) from object-shaped items Composes cleanly with #2757 (objects from kv continuation lines now contribute their title rather than being dropped) and the existing `splitInlineArray` quote-aware parser. Tests: tests/bug-2770-annotate-deps-int-coerce.test.cjs - numeric scalar truth shared across plans surfaces as constraint - kv-shaped truth surfaces via title field - bare-int depends_on regression guards on extractFrontmatter Full suite: 5678 pass, 0 fail. Closes #2770 * test(#2770): use array join() for multi-line fixtures per CONTRIBUTING * refactor(#2770): cache trim() and avoid no-op truthCounts.set in aggregation |
||
|
|
dc9b712967 |
refactor(state): drop unused args + lift currentPhase in cmdStateCompletePhase (#2761)
* refactor(state): drop unused args param and lift currentPhase in cmdStateCompletePhase Two cleanup items surfaced by CodeRabbit review of PR #2759: 1. cmdStateCompletePhase(cwd, args, raw) — args is never read inside the function. All sibling state subcommands use the leaner (cwd, raw) shape. Remove the unused parameter and update the dispatch call in gsd-tools.cjs. 2. output() at line 1754 called fs.readFileSync(statePath) after readModifyWriteStateMd had already released the lock, re-extracting Current Phase via an extra fs read. The closure already computed currentPhase at line 1704; lifting resolvedPhase into outer scope and capturing it in the callback eliminates the post-lock read and closes the small race window. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2761): apply CodeRabbit nitpicks with regression tests Two CodeRabbit nitpicks from PR #2761 review, each landed with a regression test so a future refactor can't unwind them. 1. tests/dispatcher.test.cjs — pin the enumerated subcommand list: the 'state unknown subcommand errors' test now also asserts that the dispatcher's error string includes 'complete-phase'. Without this, a future reformat of the available-subcommands enumeration could silently drop entries and the existing 'Unknown state subcommand' substring check would still pass. 2. get-shit-done/bin/lib/state.cjs — tighten the Phase fallback in cmdStateCompletePhase: when STATE.md is missing the canonical '**Current Phase:**' field and the only phase signal is the decorated body line under '## Current Position' (e.g. 'Phase: 01 (Foo) — EXECUTING'), the previous fallback returned the entire decorated string, producing messy downstream output: Status: Phase 01 (Foo) — EXECUTING complete Phase: 01 (Foo) — EXECUTING — COMPLETE The fallback now strips everything past the leading numeric/decimal token via /^\\s*([\\w.-]+)/ so degraded inputs produce clean output identical to the canonical path. 3. tests/state.test.cjs — two new tests in a dedicated describe block: - decorated Phase line writes clean Phase identifier - canonical Current Phase wins over Current Position decoration Both run real `gsd state complete-phase` against synthetic STATE.md fixtures and assert on the rendered Status field. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9472f343db |
feat(#2762): --minimal install profile (≥94% cold-start token reduction) (#2764)
* feat(#2762): add --minimal install profile to cut cold-start token cost Eager system-prompt load from 86 gsd-* skill descriptions plus 33 subagent descriptions costs ~12k tokens per turn even in directories with no .planning/. Frontier models (Sonnet 4.6 / Opus 4.7) with 200K-1M context don't feel it; local LLMs with 32K-128K do. --minimal (alias --core-only) installs only the main GSD loop: new-project, discuss-phase, plan-phase, execute-phase, plus help/update. Zero gsd-* subagents are written. Re-running gsd update without --minimal expands to the full surface. Default install behavior is unchanged. DRY: a single stageSkillsForMode() helper filters the source dir; all 13 runtime-specific copy fns are unchanged because they recurse the staged dir. Allowlist + helpers live in get-shit-done/bin/lib/install- profiles.cjs as the single source of truth. Manifest now records mode: 'minimal' | 'full' so future commands can detect install profile. Tested end-to-end: --minimal yields 6 skill folders + 0 agents; default yields 86 + 33 (unchanged). * docs(#2762): document --minimal install in README Adds a collapsible 'Minimal Install' section under Getting Started covering: who it's for (local LLMs, token-billed APIs), what you get (6 skills, 0 subagents, ~700 token floor vs ~12k), and the critical caveat that re-installing without --minimal restores the full surface and erases the savings. Includes a comparison table, the manifest inspection one-liner, and the use-case decision matrix. * fix(#2762): address CodeRabbit review + CI failures CodeRabbit findings: 1. Temp dir leak (Minor): stageSkillsForMode created tmp dirs that were never cleaned up. Added a module-level Set tracking every staged dir plus a process.on('exit') handler that rm -rf's them. Also wrap the copy loop in try/catch to remove a partially-populated tmp dir on mid-flight failure. Verified end-to-end: 0 leaked dirs in /tmp after a real install. 2. Codex full -> minimal stale state (Major): a previous full Codex install left agents/gsd-*.toml files plus [agents.gsd-*] sections in config.toml. The original cleanup only removed .md files, so a switch to --minimal would leave Codex still advertising the full agent surface. Cleanup now also handles .toml under isCodex, and minimal mode strips GSD sections from config.toml via the existing stripGsdFromCodexConfig helper (same path used by --uninstall). 3. Nitpick — Codex downgrade regression test: added a spawnSync-based end-to-end test that fakes a previous full install (stale gsd-*.md + gsd-*.toml + GSD-marked config.toml + a user-owned agent/setting), runs install.js --codex --minimal, and asserts stale GSD files + sections are gone while user content is preserved. CI failures (inventory parity): - docs/INVENTORY.md CLI Modules table now lists install-profiles.cjs with the correct headline count (30 -> 31). - docs/INVENTORY-MANIFEST.json regenerated via gen-inventory-manifest.cjs. Test count: 149 pass (was 116 in last commit; +14 new install-minimal + all previously-failing inventory tests now green). * test(#2762): expand install-minimal test coverage for future-proofing Each new test pins a specific guarantee that closes off a future regression class — turning every CodeRabbit finding (including the nitpicky one) into a permanent guard. cleanupStagedSkills suite (+3 tests): - 'full mode does not register a staged dir' — catches a future regression where someone forgets the early-return in stageSkillsForMode and starts polluting STAGED_DIRS in default installs. - 'exit handler registers exactly once across many calls' — catches removal of the exitHandlerRegistered guard. install.js has 13 dispatch sites, so a missing guard would attach 13 listeners. - 'mid-copy failure removes partial staged dir and re-throws' — intercepts fs.copyFileSync to throw mid-loop and asserts the staged dir count in /tmp is unchanged after the throw. Pins the exact CodeRabbit-flagged leak. Claude full -> minimal downgrade (+1 test): - Mirrors the Codex downgrade test for the .md-only path that the other 12 runtimes share. Asserts user-owned agents are preserved. Manifest mode round-trip (+3 tests): - Default install -> mode: 'full' with >6 skills and >0 agents - --minimal -> mode: 'minimal' with exactly 6 skills and 0 agents - --core-only alias produces identical manifest to --minimal Allowlist scope guards (+3 tests): - Every main-loop command IS in allowlist (positive) - Off-loop commands (autonomous, ship, do, progress, next, fast, quick, debug, code-review, verify-work) are NOT (guards against silent scope creep — future contributor adds 'autonomous' to core and the floor erodes) - Unknown mode strings fall through to full behavior — pre-emptive guard for future 'compact'/'tier2' modes that might forget to update the predicate. Total: 25 tests in this file (was 15), 159/159 passing across the install + inventory suites. * fix(#2762): clean up staged tmp dirs on SIGINT/SIGTERM/SIGHUP CodeRabbit follow-up review on c727bf5f flagged that process.on('exit') does not fire on signal-driven termination. An installer is exactly the kind of process users abort mid-run with Ctrl+C, so without explicit signal handlers the staged tmp dirs in STAGED_DIRS would be left behind until the OS reaps tmpdir. Fix: ensureExitCleanup now also registers process.once handlers for SIGINT, SIGTERM, SIGHUP. Each handler runs cleanupStagedSkills then re-raises the same signal via process.kill(pid, sig) so the OS-default handler takes over and the parent shell sees the correct exit code (130 for SIGINT, etc.) — CI scripts and interactive users see the abort the way they expect. Test: spawns a child that stages a tmp dir then blocks; parent captures the staged path from stdout, sends SIGINT, asserts (a) the staged dir is gone after child exit, (b) child exits via the signal not via code 0. Skipped on Windows (signal semantics differ; the natural-exit cleanup test covers the Windows CI matrix). Total: 26 tests in install-minimal.test.cjs (was 25). |
||
|
|
ab5ad6c8bc |
fix(#2757): unquoted truths with colons crash annotate-dependencies (#2759)
parseMustHavesBlock dispatched on `includes(':')` to detect key-value pairs,
but unquoted YAML strings like `GET /foo/:id resolves...` and
`Class::Method is idempotent` also contain colons. When the KV regex failed
to match, `current` was left as `{}` (the empty object initialized before
the branch), which then caused `t.trim()` in roadmap.cjs to throw
`TypeError: t.trim is not a function`.
Two fixes:
- frontmatter.cjs: tighten the KV regex to require at least one space after
the colon (`\s+` instead of `\s*`), matching YAML convention. When the
regex still fails to match, fall back to treating the item as a plain
string instead of leaving `current` as `{}`.
- roadmap.cjs: add `typeof t !== 'string'` guard before `.trim()` as a
cheap safety net against any future parser anomaly.
Closes #2757
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
1405728292 |
fix: parseMustHavesBlock quoted strings + gsd state complete-phase (#2744)
* fix: parseMustHavesBlock quoted strings + gsd state complete-phase Bug #2734: parseMustHavesBlock dropped quoted truths containing ':' because fully-quoted strings like `"App-side UUIDv4: generated locally"` fell into the kv-parse branch, the regex failed (value starts with '"'), and current stayed as empty {}. Fix: detect fully-quoted strings before the ':' check and extract them directly. Two regression tests added to frontmatter.test.cjs. Bug #2735: `gsd state complete-phase` subcommand was missing — unknown subcommands fell through to cmdStateLoad. Added cmdStateCompletePhase to state.cjs (updates Status, Last Activity, and Current Position to COMPLETE), exported it, and wired it into the case 'state': dispatch in gsd-tools.cjs. Closes #2734 Closes #2735 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(state): unknown subcommand returns explicit error instead of silent fallthrough Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- 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 |
||
|
|
0eef943f0a |
Merge pull request #2739 from gsd-build/fix/2732-graphify-cli-invocation
fix(graphify): update CLI invocation from legacy flag form to subcommand |
||
|
|
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 |
||
|
|
f89a56eb55 |
fix(graphify): update CLI invocation from legacy flag form to subcommand
graphify . --update was removed in favor of graphify update . in v0.4.x. Also improves version detection to try `graphify --version` before falling back to python3 importlib query. Closes #2732 |
||
|
|
b40110111d |
feat(#2306): plan-review-convergence v2 — CYCLE_SUMMARY contract, config gate, local model reviewers (#2718)
* feat(#2306): plan-review-convergence v2 — CYCLE_SUMMARY contract, config gate, local model reviewers Fixes the false-stall detection bug in the plan→review→replan convergence loop. REVIEWS.md accumulates history across cycles so raw grep inflated HIGH counts; HIGH count now comes from a per-cycle CYCLE_SUMMARY contract emitted in the review agent's return message. Key changes: - workflow.plan_review_convergence config gate (disabled by default, same pattern as workflow.code_review / workflow.nyquist_validation) - Review agent prompt defines CYCLE_SUMMARY: current_high=<N> contract with PARTIALLY RESOLVED / FULLY RESOLVED counting rules - Orchestrator aborts on absent/malformed CYCLE_SUMMARY (distinguishes both) - Warns when HIGH_COUNT > 0 but ## Current HIGH Concerns section is missing - Stall detection and --ws forwarding preserved and tested - Local model reviewers: --ollama, --lm-studio, --llama-cpp flags added to convergence workflow and review workflow; all three use OpenAI-compatible /v1/chat/completions endpoint with jq --rawfile for safe JSON encoding - review.ollama_host / review.lm_studio_host / review.llama_cpp_host config keys registered and documented (default to localhost:11434/1234/8080) - review.models.ollama / .lm_studio / .llama_cpp model-name config support - 58 tests (up from 29 in PR #2339), all passing Closes #2306 Closes #2339 Co-authored-by: Tom Boucher <trekkie@nomorestars.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): sync sdk/src/query/config-schema.ts with CJS schema (#2306) Add workflow.plan_review_convergence, review.ollama_host, review.lm_studio_host, and review.llama_cpp_host to the SDK-side TypeScript mirror — required by the CJS↔SDK parity test (#2653). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2306): resolve CodeRabbit review findings - Anchor HIGH_COUNT extraction with head -1 to prevent multi-match when agent return message contains multiple CYCLE_SUMMARY lines (e.g. quoted back from prompt context) - Replace hardcoded reviewers list in REVIEWS.md frontmatter template with runtime-derived placeholder — the static list did not reflect which reviewers were actually invoked - Broaden workflow.plan_review_convergence docs to include local reviewers (Ollama, LM Studio, llama.cpp) alongside cloud reviewers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): restore reviewers frontmatter list with runtime note The cursor-reviewer.test.cjs (and equivalent per-reviewer tests) assert that each supported reviewer appears on the reviewers: line — these are wiring tests that catch when a new reviewer is added to invocation but not to the REVIEWS.md template. Replacing the list with a placeholder broke those tests. Restore the full static list and add an inline comment clarifying that the actual committed frontmatter should be filtered to only the reviewers invoked that run — satisfying both the per-reviewer tests and the CodeRabbit correctness note. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
8393f4b355 |
fix(#2601): config-set-model-profile now accepts 'inherit' value (#2707)
VALID_PROFILES was derived solely from Object.keys(MODEL_PROFILES['gsd-planner']), which only contained the named tiers (quality/balanced/budget/adaptive). The cmdConfigSetModelProfile validator rejected 'inherit' even though the runtime has supported it since #1829. Fix: append 'inherit' to VALID_PROFILES and handle it in getAgentToModelMapForProfile so the agent→model table shows 'inherit' instead of undefined. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
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. |
||
|
|
7b470f2625 |
fix(#2633): ROADMAP.md is the authority for current-milestone phase counts (#2665)
* fix(#2633): use ROADMAP.md as authority for current-milestone phase counts initMilestoneOp (SDK + CJS) derives phase_count and completed_phases from the current milestone section of ROADMAP.md instead of counting on-disk `.planning/phases/` directories. After `phases clear` at the start of a new milestone the on-disk set is a subset of the roadmap, causing premature `all_phases_complete: true`. validateHealth W002 now unions ROADMAP.md phase declarations (all milestones — current, shipped, backlog) with on-disk dirs when checking STATE.md phase refs. Eliminates false positives for future-phase refs in the current milestone and history-phase refs from shipped milestones. Falls back to legacy on-disk counting when ROADMAP.md is missing or unparseable so no-roadmap fixtures still work. Adds vitest regressions for both handlers; all 66 SDK + 118 CJS tests pass. * fix(#2633): preserve full phase tokens in W002 + completion lookup CodeRabbit flagged that the parseInt-based normalization collapses distinct phase IDs (3, 3A, 3.1) into the same integer bucket, masking real STATE/ROADMAP mismatches and miscounting completions in milestones with inserted/sub-phases. Index disk dirs and validate STATE.md refs by canonical full phase token — strip leading zeros from the integer head only, preserve [A-Z] suffix and dotted segments, and accept just the leading-zero variant of the integer prefix as a tolerated alias. 3A and 3 never share a bucket. Also widens the disk and STATE.md regexes to accept [A-Z]? suffix tokens. |
||
|
|
a6e692f789 |
fix(#2646): honor ROADMAP [x] checkboxes when no phases/ directory exists (#2669)
initProgress (and its CJS twin) hardcoded `not_started` for ROADMAP-only phases, so `completed_count` stayed at 0 even when the ROADMAP showed `- [x] Phase N`. Extract ROADMAP checkbox states into a shared helper and use `- [x]` as the completion signal when no phase directory is present. Disk status continues to win when both exist. Adds a regression test that reproduces the bug with no phases/ dir and one `[x]` / one `[ ]` phase, asserting completed_count===1. Fixes #2646 |
||
|
|
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 |
||
|
|
387c8a1f9c |
fix(#2653): eliminate SDK↔CJS config-schema drift (#2670)
The SDK's config-set kept its own hand-maintained allowlist (28-key drift vs. get-shit-done/bin/lib/config-schema.cjs), so documented keys accepted by the CJS config-set — planning.sub_repos, workflow.code_review_command, workflow.security_*, review.models.*, model_profile_overrides.*, etc. — were rejected with "Unknown config key" when routed through the SDK. Changes: - New sdk/src/query/config-schema.ts mirrors the CJS schema exactly (exact-match keys + dynamic regex sources). - config-mutation.ts imports VALID_CONFIG_KEYS / DYNAMIC_KEY_PATTERNS from the shared module instead of rolling its own set and regex branches. - Drop hand-coded agent_skills.* / features.* regex branches — now schema-driven so claude_md_assembly.blocks.*, review.models.*, and model_profile_overrides.<runtime>.<tier> are also accepted. - Add tests/config-schema-sdk-parity.test.cjs (node:test) as the CI drift guard: asserts CJS VALID_CONFIG_KEYS set-equals the literal set parsed from config-schema.ts, and that every CJS dynamic pattern source has an identical counterpart in the SDK. Parallel to the CJS↔docs parity added in #2479. - Vitest #2653 specs iterate every CJS key through the SDK validator, spot-check each dynamic pattern, and lock in planning.sub_repos. - While here: add workflow.context_coverage_gate to the CJS schema (already in docs and SDK; CJS previously rejected it) and sync the missing curated typo-suggestions (review.model, sub_repos, plan_checker, workflow.review_command) into the SDK. Fixes #2653. |
||
|
|
e973ff4cb6 |
fix(#2630): reset STATE.md frontmatter atomically on milestone switch (#2666)
The /gsd:new-milestone workflow Step 5 rewrote STATE.md's Current Position body but never touched the YAML frontmatter, so every downstream reader (state.json, getMilestoneInfo, progress bars) kept reporting the stale milestone until the first phase advance forced a resync. Asymmetric with milestone.complete, which uses readModifyWriteStateMdFull. Add a new `state milestone-switch` handler (both SDK and CJS) that atomically: - Stomps frontmatter milestone/milestone_name with caller-supplied values - Resets status to 'planning' and progress counters to zero - Rewrites the ## Current Position section to the new-milestone template - Preserves Accumulated Context (decisions, blockers, todos) Wire the workflow Step 5 to invoke `state.milestone-switch` instead of the manual body rewrite. Note the flag is `--milestone` not `--version`: gsd-tools reserves `--version` as a globally-invalid help flag. Red vitest in sdk/src/query/state-mutation.test.ts asserts the frontmatter reset. Regression guard via node:test in tests/bug-2630-*.test.cjs runs through gsd-tools end-to-end. Fixes #2630 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
220da8e487 |
feat: /gsd-settings-integrations — configure third-party search and review integrations (closes #2529) (#2604)
* feat(#2529): /gsd-settings-integrations — third-party integrations command Adds /gsd-settings-integrations for configuring API keys, code-review CLI routing, and agent-skill injection. Distinct from /gsd-settings (workflow toggles) because these are connectivity, not pipeline shape. Three sections: - Search Integrations: brave_search / firecrawl / exa_search API keys, plus search_gitignored toggle. - Code Review CLI Routing: review.models.{claude,codex,gemini,opencode} shell-command strings. - Agent Skills Injection: agent_skills.<agent-type> free-text input, validated against [a-zA-Z0-9_-]+. Security: - New secrets.cjs module with ****<last-4> masking convention. - cmdConfigSet now masks value/previousValue in CLI output for secret keys. - Plaintext is written only to .planning/config.json; never echoed to stdout/stderr, never written to audit/log files by this flow. - Slug validators reject path separators, whitespace, shell metacharacters. Tests (tests/settings-integrations.test.cjs — 25 cases): - Artifact presence / frontmatter. - Field round-trips via gsd-tools config-set for all four search keys, review.models.<cli>, agent_skills.<agent-type>. - Config-merge safety: unrelated keys preserved across writes. - Masking: config-set output never contains plaintext sentinel. - Logging containment: plaintext secret sentinel appears only in config.json under .planning/, nowhere else on disk. - Negative: path-traversal, shell-metachar, and empty-slug rejected. - /gsd:settings workflow mentions /gsd:settings-integrations. Docs: - docs/COMMANDS.md: new command entry with security note. - docs/CONFIGURATION.md: integration settings section (keys, routing, skills injection) with masking documentation. - docs/CLI-TOOLS.md: reviewer CLI routing and secret-handling sections. - docs/INVENTORY.md + INVENTORY-MANIFEST.json regenerated. Closes #2529 * fix(#2529): mask secrets in config-get; address CodeRabbit review cmdConfigGet was emitting plaintext for brave_search/firecrawl/exa_search. Apply the same isSecretKey/maskSecret treatment used by config-set so the CLI surface never echoes raw API keys; plaintext still lives only in config.json on disk. Also addresses CodeRabbit review items in the same PR area: - #3127146188: config-get plaintext leak (root fix above) - #3127146211: rename test sentinels to concat-built markers so secret scanners stop flagging the test file. Behavior preserved. - #3127146207: add explicit 'text' language to fenced code blocks (MD040). - nitpick: unify masked-value wording in read_current legend ('****<last-4>' instead of '**** already set'). - nitpick: extend round-trip test to cover search_gitignored toggle. New regression test 'config-get masks secrets and never echoes plaintext' verifies the fix for all three secret keys. * docs(#2529): bump INVENTORY counts post-rebase (commands 84→85, workflows 82→83) * fix(test): bump CLI Modules count 27→28 after rebase onto main (CI #24811455435) PR #2604 was rebased onto main before #2605 (drift.cjs) merged. The pull_request CI runs against the merge ref (refs/pull/2604/merge), which now contains 28 .cjs files in get-shit-done/bin/lib/, but docs/INVENTORY.md headline still said "(27 shipped)". inventory-counts.test.cjs failed with: AssertionError: docs/INVENTORY.md "CLI Modules (27 shipped)" disagrees with get-shit-done/bin/lib/ file count (28) Rebased branch onto current origin/main (picks up drift.cjs row, which was already added by #2605) and bumped the headline to 28. Full suite: 5200/5200 pass. |
||
|
|
1a694fcac3 |
feat: auto-remap codebase after significant phase execution (closes #2003) (#2605)
* feat: auto-remap codebase after significant phase execution (#2003) Adds a post-phase structural drift detector that compares the committed tree against `.planning/codebase/STRUCTURE.md` and either warns or auto-remaps the affected subtrees when drift exceeds a configurable threshold. ## Summary - New `bin/lib/drift.cjs` — pure detector covering four drift categories: new directories outside mapped paths, new barrel exports at `(packages|apps)/*/src/index.*`, new migration files, and new route modules. Prioritizes the most-specific category per file. - New `verify codebase-drift` CLI subcommand + SDK handler, registered as `gsd-sdk query verify.codebase-drift`. - New `codebase_drift_gate` step in `execute-phase` between `schema_drift_gate` and `verify_phase_goal`. Non-blocking by contract — any error logs and the phase continues. - Two new config keys: `workflow.drift_threshold` (int, default 3) and `workflow.drift_action` (`warn` | `auto-remap`, default `warn`), with enum/integer validation in `config-set`. - `gsd-codebase-mapper` learns an optional `--paths <p1,p2,...>` scope hint for incremental remapping; agent/workflow docs updated. - `last_mapped_commit` lives in YAML frontmatter on each `.planning/codebase/*.md` file; `readMappedCommit`/`writeMappedCommit` round-trip helpers ship in `drift.cjs`. ## Tests - 55 new tests in `tests/drift-detection.test.cjs` covering: classification, threshold gating at 2/3/4 elements, warn vs. auto-remap routing, affected-path scoping, `--paths` sanitization (traversal, absolute, shell metacharacter rejection), frontmatter round-trip, defensive paths (missing STRUCTURE.md, malformed input, non-git repos), CLI JSON output, and documentation parity. - Full suite: 5044 pass / 0 fail. ## Documentation - `docs/CONFIGURATION.md` — rows for both new keys. - `docs/ARCHITECTURE.md` — section on the post-execute drift gate. - `docs/AGENTS.md` — `--paths` flag on `gsd-codebase-mapper`. - `docs/USER-GUIDE.md` — user-facing behavior note + toggle commands. - `docs/FEATURES.md` — new 27a section with REQ-DRIFT-01..06. - `docs/INVENTORY.md` + `docs/INVENTORY-MANIFEST.json` — drift.cjs listed. - `get-shit-done/workflows/execute-phase.md` — `codebase_drift_gate` step. - `get-shit-done/workflows/map-codebase.md` — `parse_paths_flag` step. - `agents/gsd-codebase-mapper.md` — `--paths` directive under parse_focus. ## Design decisions - **Frontmatter over sidecar JSON** for `last_mapped_commit`: keeps the baseline attached to the file, survives git moves, survives per-doc regeneration, no extra file lifecycle. - **Substring match against STRUCTURE.md** for `isPathMapped`: the map is free-form markdown, not a structured manifest; any mention of a path prefix counts as "mapped territory". Cheap, no parser, zero false negatives on reasonable maps. - **Category priority migration > route > barrel > new_dir** so a file matching multiple rules counts exactly once at the most specific level. - **Empty-tree SHA fallback** (`4b825dc6…`) when `last_mapped_commit` is absent — semantically correct (no baseline means everything is drift) and deterministic across repos. - **Four layers of non-blocking** — detector try/catch, CLI try/catch, SDK handler try/catch, and workflow `|| echo` shell fallback. Any single layer failing still returns a valid skipped result. - **SDK handler delegates to `gsd-tools.cjs`** rather than re-porting the detector to TypeScript, keeping drift logic in one canonical place. Closes #2003 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(mapper): tag --paths fenced block as text (CodeRabbit MD040) Comment 3127255172. * docs(config): use /gsd- dash command syntax in drift_action row (CodeRabbit) Comment 3127255180. Matches the convention used by every other command reference in docs/CONFIGURATION.md. * fix(execute-phase): initialize AGENT_SKILLS_MAPPER + tag fenced blocks Two CodeRabbit findings on the auto-remap branch of the drift gate: - 3127255186 (must-fix): the mapper Task prompt referenced ${AGENT_SKILLS_MAPPER} but only AGENT_SKILLS (for gsd-executor) is loaded at init_context (line 72). Without this fix the literal placeholder string would leak into the spawned mapper's prompt. Add an explicit gsd-sdk query agent-skills gsd-codebase-mapper step right before the Task spawn. - 3127255183: tag the warn-message and Task() fenced code blocks as text to satisfy markdownlint MD040. * docs(map-codebase): wire PATH_SCOPE_HINT through every mapper prompt CodeRabbit (review id 4158286952, comment 3127255190) flagged that the parse_paths_flag step defined incremental-remap semantics but did not inject a normalized variable into the spawn_agents and sequential_mapping mapper prompts, so incremental remap could silently regress to a whole-repo scan. - Define SCOPED_PATHS / PATH_SCOPE_HINT in parse_paths_flag. - Inject ${PATH_SCOPE_HINT} into all four spawn_agents Task prompts. - Document the same scope contract for sequential_mapping mode. * fix(drift): writeMappedCommit tolerates missing target file CodeRabbit (review id 4158286952, drift.cjs:349-355 nitpick) noted that readMappedCommit returns null on ENOENT but writeMappedCommit threw — an asymmetry that breaks first-time stamping of a freshly produced doc that the caller has not yet written. - Catch ENOENT on the read; treat absent file as empty content. - Add a regression test that calls writeMappedCommit on a non-existent path and asserts the file is created with correct frontmatter. Test was authored to fail before the fix (ENOENT) and passes after. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9c0a153a5f |
feat: /gsd-settings-advanced — power-user config tuning command (closes #2528) (#2603)
* feat: /gsd-settings-advanced — power-user config tuning command (closes #2528) Adds a second-tier interactive configuration command covering the power-user knobs that don't belong in the common-case /gsd-settings prompt. Six sectioned AskUserQuestion batches cover planning, execution, discussion, cross-AI, git, and runtime settings (19 config keys total). Current values are pre-selected; numeric fields reject non-numeric input; writes route through gsd-sdk query config-set so unrelated keys are preserved. - commands/gsd/settings-advanced.md — command entry - get-shit-done/workflows/settings-advanced.md — six-section workflow - get-shit-done/workflows/settings.md — advertise advanced command - get-shit-done/bin/lib/config-schema.cjs — add context_window to VALID_CONFIG_KEYS - docs/COMMANDS.md, docs/CONFIGURATION.md, docs/INVENTORY.md — docs + inventory - tests/gsd-settings-advanced.test.cjs — 81 tests (files, frontmatter, field coverage, pre-selection, merge-preserves-siblings, VALID_CONFIG_KEYS membership, confirmation table, /gsd-settings cross-link, negative scenarios) All 5073 tests pass; coverage 88.66% (>= 70% threshold). * docs(settings-advanced): clarify per-field numeric bounds and label fenced blocks Addresses CodeRabbit review on PR #2603: - Numeric-input rule now states min is field-specific: plan_bounce_passes and max_discuss_passes require >= 1; other numeric fields accept >= 0. Resolves the inconsistency between the global rule and the field-level prompts (CodeRabbit comment 3127136557). - Adds 'text' fence language to seven previously unlabeled code blocks in the workflow (six AskUserQuestion sections plus the confirmation banner) to satisfy markdownlint MD040 (CodeRabbit comment 3127136561). * test(settings-advanced): tighten section assertion, fix misleading test name, add executable numeric-input coverage Addresses CodeRabbit review on PR #2603: - Required section list now asserts the full 'Runtime / Output' heading rather than the looser 'Runtime' substring (comment 3127136564). - Renames the subagent_timeout coercion test to match the actual key under test (was titled 'context_window' but exercised workflow.subagent_timeout — comment 3127136573). - Adds two executable behavioral tests at the config-set boundary (comment 3127136579): * Non-numeric input on a numeric key currently lands as a string — locks in that the workflow's AskUserQuestion re-prompt loop is the layer responsible for type rejection. If a future change adds CLI-side numeric validation, the assertion flips and the test surfaces it. * Numeric string on workflow.max_discuss_passes is coerced to Number — locks in the parser invariant for a second numeric key. |
||
|
|
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> |
||
|
|
533973700c |
feat(#2538): add last: /cmd suffix to statusline (opt-in) (#2594)
Adds a `statusline.show_last_command` config toggle (default: false) that appends ` │ last: /<cmd>` to the statusline, showing the most recently invoked slash command in the current session. The suffix is derived by tailing the active Claude Code transcript (provided as transcript_path in the hook input) and extracting the last <command-name> tag. Reads only the final 256 KiB to stay cheap per render. Graceful degradation: missing transcript, no recorded command, unreadable config, or parse errors all silently omit the suffix without breaking the statusline. Closes #2538 |
||
|
|
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 |
||
|
|
c47a6a2164 |
fix: correct VALID_CONFIG_KEYS — remove internal state key, add missing public keys, migration hints (#2561)
* fix(#2530-2535): correct VALID_CONFIG_KEYS set — remove internal state key, add missing public keys, add migration hints - Remove workflow._auto_chain_active from VALID_CONFIG_KEYS (internal runtime state, not user-settable) (#2530) - Add hooks.workflow_guard to VALID_CONFIG_KEYS (read by gsd-workflow-guard.js hook, already documented) (#2531) - Add workflow.ui_review to VALID_CONFIG_KEYS (read in autonomous.md via config-get) (#2532) - Add workflow.max_discuss_passes to VALID_CONFIG_KEYS (read in discuss-phase.md via config-get) (#2533) - Add CONFIG_KEY_SUGGESTIONS entries for sub_repos → planning.sub_repos and plan_checker → workflow.plan_check (#2535) - Document workflow.ui_review and workflow.max_discuss_passes in docs/CONFIGURATION.md - Clear INTERNAL_KEYS exemption in parity test (workflow._auto_chain_active removed from schema entirely) - Add regression test file tests/bug-2530-valid-config-keys.test.cjs covering all 6 bugs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: align SDK VALID_CONFIG_KEYS with CJS — remove internal key, add missing public keys - Remove workflow._auto_chain_active from SDK (internal runtime state, not user-settable) - Add workflow.ui_review, workflow.max_discuss_passes, hooks.workflow_guard to SDK - Add ui_review and max_discuss_passes to Full Schema example in CONFIGURATION.md Resolves CodeRabbit review on #2561. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |