a33cbe72f569e75e72d94de85d0930296d1ca1df
1015 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
199083777a |
Merge pull request #3111 from gsd-build/fix/3094-progress-md-still-recommends-deleted-gsd
fix: remove stale /gsd-list-phase-assumptions guidance from progress routing |
||
|
|
dbbc7f0942 |
Merge pull request #3117 from gsd-build/fix/3056-pruneorphanedworktrees-destroys-linked-w
fix: make orphaned worktree prune non-destructive by default |
||
|
|
2113902daf |
Merge pull request #3119 from gsd-build/fix/3072-gsd-sdk-query-resolve-model-error-when-i
fix: guard optional sketch-findings probes from non-zero ls exits |
||
|
|
f01f6b76dd |
Merge pull request #3122 from gsd-build/fix/3088-gsd-complete-milestone-leaves-state-md-n
fix: normalize stale STATE narrative tails on milestone completion |
||
|
|
67684626d8 | fix(3088): append missing STATE narrative sections on milestone close | ||
|
|
59fd17251a | fix(phase): clarify insert preconditions and reject unsupported dry-run flag | ||
|
|
efa642a078 | fix(update): skip unreadable custom files during backup | ||
|
|
120113c42b | fix(sdk-guidance): point quick install hint and agent fallbacks to query-capable CLI | ||
|
|
2dcf374da0 | fix(milestone): normalize STATE narrative after milestone completion | ||
|
|
50f714cdd5 | fix(workflows): make optional findings-skill probes non-fatal | ||
|
|
471df09242 | fix(state): harden complete-phase resolution and add explicit override | ||
|
|
ecd5d11b32 | fix(worktree): disable destructive orphaned-worktree removal by default | ||
|
|
65024683fd | fix(init): count plans/ summaries from nested plans/ layout | ||
|
|
72f4c3b362 | fix(docs): replace stale /gsd-next references with /gsd-progress --next | ||
|
|
538ef683be | fix(resume): remove clear prefix from resume routing | ||
|
|
c7886415c3 | fix(phase): canonicalize plan-summary matching for suffixless summaries | ||
|
|
a54dda3837 | fix(progress): remove stale list-phase-assumptions routing | ||
|
|
19e580137d | fix: scope milestone complete stats to explicit version | ||
|
|
1e6737cd8e |
feat(plan-phase): --research-phase flag + scrub stale slash-command refs (#3042, #3044) (#3045)
* feat(plan-phase): --research-phase flag absorbs deleted /gsd-research-phase + scrub stale refs (#3042, #3044) #3042 (orphaned research-phase): /gsd-research-phase had a workflow file but no slash-command stub. Rather than restore the orphan, the research- only capability is now a flag on /gsd-plan-phase: /gsd-plan-phase --research-phase <N> When set, the workflow scopes to phase N, runs the research step (Section 5 of the existing plan-phase workflow), then early-exits before the planner/plan-checker/verifier chain. Per RCA against the deleted standalone, the flag adds two modifiers to fully cover the original surface (Option B from the RCA discussion): - --view : print existing RESEARCH.md to stdout, no spawn. Cheapest mode for the correction-without-replanning loop the issue reporter explicitly called out. Errors with a clear hint if RESEARCH.md is missing. - --research : reuse the existing "force re-research" semantics. In research-only mode this skips the existing-RESEARCH.md prompt and re-spawns unconditionally. - Neither flag, RESEARCH.md exists : prompt update/view/skip. Mirrors the deleted standalone's existing-artifact menu (#3042 RCA). #3044 (stale slash-command refs): scrubbed five deleted commands from all user-facing surfaces, including English docs, 4 localized doc sets (ja-JP, ko-KR, zh-CN, pt-BR), workflows, templates, and references. /gsd-check-todos → /gsd-capture --list /gsd-new-workspace → /gsd-workspace --new /gsd-status → /gsd-progress /gsd-plan-milestone-gaps → table rows / orphan sections removed (PR #3038 only scrubbed workflows/agent; missed the docs surfaces this PR covers) /gsd-research-phase → /gsd-plan-phase --research-phase Includes a fix to docs/issue-driven-orchestration.md (PR #3036) which itself referenced /gsd-new-workspace 4 times — self-correction. Removed: - get-shit-done/workflows/research-phase.md (orphan, capability absorbed into --research-phase flag) Tests: - tests/bug-3042-3044-research-flag-and-stale-refs.test.cjs — 46 structural-IR tests across both bugs: - argument-hint advertises --research-phase + --view - workflow parses --research-phase, sets RESEARCH_ONLY, early-exits before planner - --view prints RESEARCH.md without spawning - --research forces refresh in research-only mode - existing-RESEARCH.md prompt path with update/view/skip - workflows/research-phase.md is removed - 5 deleted slash-commands absent from 17 English user-facing surfaces + 16 localized doc surfaces (4 locales × 4 docs each) - replacement command tokens present where deleted ones lived 6950/6950 full suite pass. Lints clean. Closes #3042 Closes #3044 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address all 8 CR findings on PR #3045 Major (3): - get-shit-done/workflows/plan-phase.md:344 — added explicit early-exit guard at Section 5.1: "Skip if RESEARCH_ONLY=true". Without it, an LLM could fall through "use existing, skip to step 6" → planner spawn, violating the research-only contract. The guard makes the early-exit unreachable from any non-research-only branch. - get-shit-done/references/continuation-format.md (3 examples) + zh-CN/.../continuation-format.md (3 examples) — pointed to `/gsd-plan-phase --research-phase` but docs/COMMANDS.md didn't document the flag. Added a full --research-phase + --view + --research modifier section to the /gsd-plan-phase flag table in COMMANDS.md so the canonical reference matches the continuation examples. Minor (5): - docs/FEATURES.md:1632 — `/gsd-plan-phase --research-phase` → `/gsd-plan-phase --research-phase <N>` (include required arg). - get-shit-done/templates/README.md:46 — NN-VALIDATION.md producer reverted from `/gsd-plan-phase --research-phase` (Nyquist) to plain `/gsd-plan-phase` (Nyquist). VALIDATION.md is created during normal Nyquist flow, not research-only mode — the bulk replacement was wrong for that line. - get-shit-done/workflows/help.md:89 — signature line was missing `--research`; added it alongside `--research-phase` and `--view`. - tests/bug-3042-3044-...:197 — promptHasView/promptHasSkip were tautological (matched anywhere in 1700-line workflow). Tightened to a proximity check anchored on "RESEARCH.md already exists" prompt header within a 600-char window. Updated workflow to emit that literal phrase. - tests/feat-2840-...:95 — workspace assertion used `/gsd-workspace` but the documented replacement is `/gsd-workspace --new`. Tightened to require both tokens (in 3 places: requiredCommands list, regex in conceptPairs, error message). 6950/6950 full suite pass. Lint clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
7714b5244b |
fix(workflows,docs): scrub stale /gsd-code-review-fix and /gsd-plan-milestone-gaps refs (#3029, #3034) (#3038)
* fix(workflows,docs): scrub stale /gsd-code-review-fix and /gsd-plan-milestone-gaps refs (#3029, #3034) #2790 consolidated /gsd-code-review-fix into /gsd-code-review --fix and deleted /gsd-plan-milestone-gaps in favor of inline gap planning as part of /gsd-audit-milestone's output. The deletion was propagated through some surfaces (#2950 covered help/do/settings/discuss-phase/etc.) but several user-facing surfaces still emitted the old forms: #3029 — /gsd-code-review-fix references in: - agents/gsd-code-fixer.md (description, "Spawned by", recovery prose) - get-shit-done/workflows/code-review.md (offer text) - get-shit-done/workflows/execute-phase.md (offer text) - get-shit-done/workflows/code-review-fix.md (internal retry hints) - docs/INVENTORY.md (agent + workflow rows) - docs/CONFIGURATION.md (workflow.code_review row) - docs/USER-GUIDE.md (3 occurrences in walkthrough) - docs/AGENTS.md (gsd-code-fixer agent stub) - docs/FEATURES.md (commands list + REQ-REVIEW-04) All replaced with /gsd-code-review --fix. Internal retry hints in the workflow file itself updated to point at the new form. Release notes (docs/RELEASE-*.md) and gsd-ns-review's "absorbed by" deletion note left unchanged — historical/explanatory content. #3034 — /gsd-plan-milestone-gaps references in: - get-shit-done/workflows/audit-milestone.md (<offer_next> blocks for gaps_found and tech_debt: lines 281, 323) - commands/gsd/complete-milestone.md (gaps_found pre-flight: lines 46, 57) Replaced with inline closure path: /gsd-phase --insert <N> "Close gap: <REQ-ID> ..." /gsd-discuss-phase <N> /gsd-plan-phase <N> /gsd-execute-phase <N> Plus a Nyquist-coverage hint pointing at /gsd-validate-phase / /gsd-secure-phase for retroactive audit-chain hygiene gaps. The gsd-ns-project SKILL.md "deleted by #2790" note is preserved (it's the canonical pointer for future readers asking what happened to the command). Tests: - tests/bug-3029-3034-stale-command-routes.test.cjs — parser-based assertions per fixed surface, plus a structural cross-check that gsd-ns-project keeps the deletion note. 15 tests, all green. - 6905/6905 full suite passes. Closes #3029 Closes #3034 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address CR feedback on PR #3038 — argument order, structural tests, agent count CR findings on PR #3038: 1. **docs/USER-GUIDE.md (Major)** — `--fix` examples used flag-first form (`/gsd-code-review --fix 3`), but the supported CLI grammar is phase-first (`/gsd-code-review 3 --fix`). The original sed-based replacement preserved the position of the `gsd-code-review-fix` token, producing the wrong order. Fixed in USER-GUIDE.md (3 occurrences) and the same drift in the workflow surfaces: - get-shit-done/workflows/code-review-fix.md (2 retry hints) - get-shit-done/workflows/code-review.md (offer text) - get-shit-done/workflows/execute-phase.md (offer text) 2. **docs/AGENTS.md (Minor)** — internal count drift: line 483 said "Ten additional agents" but line 725 said "12 advanced/specialized". Filesystem reality: 33 agents total, 21 primary, 12 specialized (count of `### ` stubs in the Advanced and Specialized section). Updated lines 3, 13, 483 to use 12/33 and added the two missing names (doc-classifier, doc-synthesizer) to the inline list at line 13. 3. **tests:94 (Major refactor suggestion)** — `.includes()` token checks were source-grep style. Refactored to a typed-IR pattern: extract the SET of slash-command tokens via regex, assert membership on the parsed Set instead of substring scanning the raw file text. Added the `allow-test-rule` comment explaining the IR-build vs IR-assertion split per scripts/lint-no-source-grep.cjs convention. 4. **tests:130 (Major)** — replacement-path assertion was file-wide and could false-pass on generic mentions of "inline" elsewhere in the file. Refactored: `extractOfferBlocks(content)` returns the typed list of `<offer_next>` and "Pre-flight" blocks where the deleted command previously lived, and the assertion runs against those blocks specifically. Now requires `/gsd-phase --insert` or inline-audit prose to appear in the same offer block, not just somewhere in the file. 15/15 targeted tests pass. 6905/6905 full suite pass. Lints clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
8c43ba7301 |
docs(#3025): MCP tool schema as a context-budget concern (#3032)
* docs(#3025): MCP tool schema as a context-budget concern Adds documentation covering the largest GSD cost lever that GSD itself does not own: MCP tool schema injection. Every enabled MCP server adds its schema to every turn (often 20k+ tokens for heavyweight servers like browser/playwright, mac-tools, etc.), which can dwarf whatever `model_profile` tuning saves. Two doc surfaces (per the bundle-docs-with-code skill depth gradient): 1. get-shit-done/references/context-budget.md - New "MCP Tool Schema Cost (Harness Concern)" section. - Explains schemas-per-turn cost framing. - Names enabledMcpjsonServers / disabledMcpjsonServers and .claude/settings.json explicitly. - Pre-phase audit checklist: browser/playwright, platform-specific, cross-project/stale, duplicate/shadow. - Explicit "GSD does not manage MCP enablement — harness concern" statement so users don't hunt for a GSD setting. - Links to Anthropic Claude Code MCP docs as canonical reference. - Notes compounding interaction with model_profile (additive levers). 2. docs/USER-GUIDE.md - New task-oriented "Trim MCP servers to reduce per-turn cost" section above "Using Non-Claude Runtimes". - Same checklist condensed. - Cross-link to context-budget.md for the full reference. Tests: - tests/feat-3025-mcp-token-budget-docs.test.cjs (12 cases) parses both docs into typed semantic-flag records and asserts behavioral invariants (mentions key, includes audit, names harness, etc.) rather than substring-matching prose. Adheres to CONTRIBUTING.md no-source-grep — section can be reworded freely as long as the required semantics survive. - Markdownlint pre-flight tests (MD040 fence language, MD056 table column count) per the bundle-docs-with-code skill so CR can't ratchet on prose nitpicks across multiple review rounds. Verification: - 12/12 pass on regression test - 6857/6857 full suite (12 net new) - lint-no-source-grep clean (377 test files) Companion to #3023 (per-phase-type model map) and #3024 (dynamic routing). Together they cover the three biggest cost levers users ask about; this issue covers the one GSD does not own. Closes #3025 * docs(#3025): batch 3 CR fixes — pr id, relative link, named flag CodeRabbit on PR #3032 (3 minor — 2 inline + 1 nitpick), all in one push per the bundle-docs-with-code skill (avoid per-round nitpick ratchet): 1. Inline (Minor) — .changeset/mcp-token-budget-docs.md:3 `pr: TBD` → `pr: 3032` so changeset tooling can link the entry. 2. Inline (Minor) — docs/USER-GUIDE.md:1101 Used a hardcoded `https://github.com/.../blob/main/...` URL for the cross-link to `context-budget.md`. Rest of USER-GUIDE.md uses relative links. Switched to `../get-shit-done/references/context- budget.md#mcp-tool-schema-cost-harness-concern` so feature-branch work shows the right content and rename-resilience is preserved. 3. Nitpick — tests/feat-3025-mcp-token-budget-docs.test.cjs:234 The cross-link assertion used an inline `/context-budget/i.test(...)` while every other invariant in the file lived as a named flag in `parseMcpBudgetSection`. Per CONTRIBUTING.md no-source-grep, added `crossLinksContextBudget` to the parser and asserted on `parsed.crossLinksContextBudget` so the cross-link rule sits next to its siblings. Verification: - 12/12 pass on regression test (no count change; refactor only) - No source code changes, only docs + tests * test(#3025): strip inline markdown before phrase-match (CR nitpick) CodeRabbit caught that the `explainsHarnessNotGsd` primary regex branch couldn't match "GSD does **not** manage" in context-budget.md because the markdown bold markers (`**`) sit between contiguous words. The test passed today only via the fallback `harness (concern|setting|controlled)` branch — the primary branch was effectively dead code. Fix: strip inline markdown emphasis (`**`, `*`, `~~`) and inline- code backticks before any phrase-matching in `parseMcpBudgetSection`. All seven flag computations now run against the stripped text so markdown formatting can't silently invalidate any invariant. Underscores are intentionally NOT stripped — `model_profile` and other snake_case identifiers must survive intact for the mentionsModelProfileInteraction check to find them. Verification: 12/12 still pass; primary branches now fire on real markdown content rather than relying on fallbacks. * test(#3025): guard markdownlint tests against null section (CR nitpick) CodeRabbit caught that the MD040 and MD056 markdownlint pre-flight tests called `section.match(...)` and `section.split('\n')` directly on the value returned by `extractSection`, which returns null when no matching header is found. If the MCP section is ever removed (regression), both tests would throw `TypeError: Cannot read properties of null` instead of producing a clean assertion failure naming the actual problem. The semantic tests above are protected because parseMcpBudgetSection short-circuits to a typed-falsy record on null input. The markdownlint tests bypassed that guard since they need raw section text, not parsed flags. Added `assert.ok(section, ...)` preconditions to both so a missing section produces a meaningful failure message. No content changes; defensive programming only. Verification: 12/12 still pass. |
||
|
|
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. |
||
|
|
e3b64b39f8 |
fix(#3019): query --help reaches handler instead of short-circuiting (#3026)
* fix(#3019): query --help reaches handler instead of short-circuiting to top-level usage The query argv parser in sdk/src/cli.ts harvested -h/--help as a global flag and main() short-circuited dispatch when args.help was true. Net effect: every `gsd-sdk query <anything> --help` printed top-level USAGE instead of contextual subcommand help. There was no path for users to discover what arguments a query subcommand accepts — they had to trigger "required" errors by trial and error. Two-layer fix: 1. sdk/src/cli.ts (parseCliArgsQueryPermissive) - Push -h / --help onto queryArgv instead of consuming them silently, so the registered handler / gsd-tools.cjs fallback gets to interpret the flag and render contextual help. - Only honor the global help flag when there is NO real subcommand to dispatch to (i.e. queryArgv contains only help flags). Preserves `gsd-sdk query --help` → top-level USAGE while letting `gsd-sdk query phase add --help` reach the handler. 2. get-shit-done/bin/gsd-tools.cjs - Render top-level usage on --help / -h / -? / --usage instead of erroring with "Unknown flag". The discovery hint in the usage text points users at the working method (run without args → error names required arguments) and references #3019 for tracking subcommand- level help printers. - --version remains rejected (no discovery use-case). #1818 anti-hallucination invariant preserved: the destructive command NEVER executes when --help is present. The new shape returns success:true + usage on stdout instead of the old success:false + error on stderr — both satisfy "destructive command did not run", and the new shape also restores discoverability. Tests: - sdk/src/cli.test.ts: 4 new vitest cases covering #3019 — query argv parser keeps --help with subcommand, parses -h short flag, preserves bare `query --help` top-level behavior, preserves --help position when intermixed with other query flags. - tests/bug-3019-help-passthrough.test.cjs: 5 node:test cases on the fallback — bare gsd-tools (no args) errors with usage; --help renders usage on stdout exit 0; -h same; subcommand --help renders usage; usage hint mentions discovery method (without prose substring matching — parses into typed sections). - tests/bug-1818-unknown-flags.test.cjs: rewritten to assert the new invariant ("destructive command did not run" + "usage was rendered") instead of the old shape ("--help is rejected with non-zero exit"). Each destructive test seeds a sentinel artifact (phase dir, slug output) and asserts it survives. Verification: - 47/47 vitest pass on sdk/src/cli.test.ts - 5/5 pass on tests/bug-3019-help-passthrough.test.cjs - 8/8 pass on tests/bug-1818-unknown-flags.test.cjs (rewritten) - 6763/6763 pass on full node:test suite - lint-no-source-grep clean (0 violations) Closes #3019 * fix(#3019): SDK fallback forwards plain-text help, broader usage list (CR) CodeRabbit on PR #3026 (4 findings — 1 Major outside-diff, 2 inline, 1 nitpick): 1. **Major outside-diff** — sdk/src/cli.ts:442-454. The fallback path that delegates to gsd-tools.cjs called parseCliQueryJsonOutput (JSON.parse) on stdout. Now that gsd-tools renders plain-text usage on --help, JSON.parse threw "Unexpected token 'U'". Wrapped the parse in try/catch — on parse failure, forward the plain stdout verbatim so subcommand help reaches the user. Regression test: tests/bug-3019-help-passthrough.test.cjs spawns the built SDK and asserts `gsd-sdk query phase --help` exits 0, stdout contains the gsd-tools usage, and stderr does NOT contain a JSON-parse error. 2. .changeset/help-passthrough.md:3 — `pr: TBD` → `pr: 3026`. 3. gsd-tools.cjs:346 (TOP_LEVEL_USAGE): - Removed self-referencing `#3019` link (immediately stale after this PR merges). - Expanded Commands list from 17 → all 47 dispatcher cases: agent-skills, audit-open, audit-uat, check-commit, commit, … phase, phases, roadmap, milestone, validate, progress, intel, graphify, learnings, etc. — the bulk of the surface that was previously unreachable via --help discovery. 4. Nitpick: `isUsageOutput` was duplicated in bug-1818 and bug-3019-help-passthrough tests. Moved to tests/helpers.cjs with structural-comment, removed both duplicates. Verification: 47/47 vitest pass, 14/14 regression tests pass, 6764/6764 full suite, lint clean. * test(#3019): use t.skip() instead of bare return when SDK not built (CR) CodeRabbit follow-up on PR #3026: The integration test guarded against missing sdk/dist/cli.js with a bare `return;` — node:test counts that as a passing test (0 assertions exercised, 0 failures). On a CI checkout that hasn't run the SDK build, the #3026 regression test silently green-lit and no signal ever surfaced that the integration check was skipped. Switched to `t.skip(...)` via the test context parameter so the omission shows up in the test report. The unit-level fix (sdk/src/cli.ts) is still covered by vitest, so the skip only affects the end-to-end spawn-built-SDK check. Verification: 6/6 pass when SDK is built; 5 pass + 1 skip when not. |
||
|
|
f2decefede |
fix(#3010): post-install message and docs use /gsd-update --reapply (#3012)
* fix(#3010): post-install message and docs use /gsd-update --reapply PR #2824 consolidated 86 skills into ~58, removing the standalone /gsd-reapply-patches command and folding it into a flag on /gsd-update (/gsd-update --reapply). The 1.39.1 hotfix (#2954) updated help.md but missed three other surfaces that still recommended the dead form: 1. bin/install.js reportLocalPatches() — runtime emitter shown after every install with backed-up patches. All branches updated: - claude/opencode/kilo/copilot: /gsd-update --reapply - gemini: /gsd:update --reapply - codex: $gsd-update --reapply - cursor: gsd-update --reapply (mention the skill name) 2. get-shit-done/workflows/update.md — Step 4 prose and the check_local_patches block both referenced /gsd-reapply-patches. Replaced with /gsd-update --reapply (with backticks around the command per CR feedback for copy/paste UX). 3. Localized docs (en/ja-JP/ko-KR/zh-CN) — 14 files across ARCHITECTURE.md / COMMANDS.md / FEATURES.md / INVENTORY.md / USER-GUIDE.md / manual-update.md still listed the removed command. Tests: - bug-3010-reapply-patches-references.test.cjs (4 tests): scans bin/install.js's reportLocalPatches body, every workflow file, and every doc (excluding CHANGELOG history and help.md's deprecation notice) for the removed command form, and verifies each runtime branch emits the consolidated form via captured console output. - tests/copilot-install.test.cjs:1081-1115 — stale assertions that hard-coded the removed string updated to assert /gsd-update --reapply. Verification: 115/115 pass across both files. Co-authored-by: Patrick Clery <patrick@patrickclery.com> Closes #3010 * test(#3010): broaden dead-command scan + tighten runtime exact-match CodeRabbit follow-up findings on #3012: 1. Workflow + docs scans only matched "/gsd-reapply-patches", missing the gemini ("/gsd:reapply-patches") and codex ("$gsd-reapply-patches") spellings. A regression that re-introduced either form in localized docs would have passed silently. Extracted a DEAD_COMMAND_PATTERNS array + findDeadCommands() helper used by both scans, so all three removed forms are checked uniformly. Match output also reports which spellings hit, for faster diagnosis. 2. reportLocalPatches runtime test asserted output.includes('update --reapply'), which is too loose — a malformed prefix like '/gsd:update --reapply' on the claude branch would have passed. Replaced with an exact {runtime → expected token} map covering all 7 branches: claude/opencode/kilo/copilot → /gsd-update --reapply gemini → /gsd:update --reapply codex → $gsd-update --reapply cursor → gsd-update --reapply Negative assertion also runs DEAD_COMMAND_PATTERNS against output for every runtime, so dead forms can't slip in regardless of branch. Verification: 4/4 pass on bug-3010-reapply-patches-references.test.cjs. * test(#3010): add prefix-absence guard for cursor runtime (CR follow-up) CodeRabbit (Minor): the cursor expected token "gsd-update --reapply" is a substring of every prefixed form ("/gsd-update --reapply" for claude/ opencode/kilo/copilot, "\$gsd-update --reapply" for codex). The positive output.includes(expectedToken) check therefore can't distinguish correct cursor output from a regression where the installer emits a prefixed form for cursor — both pass the substring check. Add an explicit prefix-absence assertion for cursor that fails if any of /, \$, or : appears immediately before "gsd-update --reapply" in output. The gemini form ("/gsd:update --reapply") doesn't share the substring (gsd:update vs gsd-update) so it's already caught by the positive includes failing on cursor's expected bare token. Verification: 4/4 pass. --------- Co-authored-by: Patrick Clery <patrick@patrickclery.com> |
||
|
|
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) |
||
|
|
4277f7d7e8 |
fix(#2994): move verify-reapply-patches.cjs to get-shit-done/bin/ so it ships to user installs (#3000)
* fix(#2994): move verify-reapply-patches.cjs to get-shit-done/bin/ so installer ships it scripts/verify-reapply-patches.cjs (added in #2972 to close the verified-yes-without-checking gap from #2969) shipped in the npm tarball but never reached user installs: bin/install.js copies get-shit-done/ recursively but does not copy the top-level scripts/ directory. Effect: every fresh install hit `Cannot find module …/scripts/verify-reapply-patches.cjs` on Step 5 of /gsd-reapply-patches. The whole point of moving verification out of LLM-driven prose into a deterministic script is undone if the script does not resolve at runtime. Fix: move the script to get-shit-done/bin/verify-reapply-patches.cjs (same pattern as gsd-tools.cjs and other runtime bin scripts that the installer ships) and update reapply-patches.md Step 5 to invoke ${GSD_HOME}/get-shit-done/bin/verify-reapply-patches.cjs. Tests: - bug-2969 SCRIPT path updated to the new location - New bug-2994-verify-reapply-patches-installed-path.test.cjs parses reapply-patches.md into structured invocation records and asserts every node ${GSD_HOME}/... reference lives under get-shit-done/ (the installed tree). Catches future regressions where someone moves a runtime-needed script back to scripts/. Closes #2994 * chore(#2994): add changeset fragment for PR #3000 * chore(#2994): add changeset fragment for PR #3000 * docs(#2994): update verifier-script-location comment to reflect new path (CR) CodeRabbit on PR #3000: the parenthetical at line 278 still said the script ships under scripts/, but this PR moved it to get-shit-done/bin/. Updated the prose to reference the new location and the installer target path. * chore(#3000): 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. |
||
|
|
cde793f1f0 |
fix(#2992): deterministic latest-version check — package name is a constant, not LLM choice (#2993)
* fix(#2992): deterministic latest-version check — package name is a constant, not LLM choice The /gsd-update workflow's check_latest_version step was prescribed in LLM-driven prose: "run `npm view get-shit-done-cc version`". The executing model could and did shortcut the prescription and invent npm queries against name-shaped guesses — `@get-shit-done/cli`, `get-shit-done-cli`, `gsd` — all of which 404 or, worse, return an unrelated typosquat (the 2016 `get-shit-done` timer package). Same architectural anti-pattern as #2969 (Hunk Verification Gate where the LLM filled `verified: yes` without checking). Implementation built TDD per #2992: get-shit-done/bin/check-latest-version.cjs - PACKAGE_NAME = 'get-shit-done-cc' as a module constant; not parameterised, not exposed for override. - checkLatestVersion({ spawn? }) returns { ok: bool, version?: string, reason: CHECK_REASON.X, detail? } via a frozen enum: OK / FAIL_NPM_FAILED / FAIL_INVALID_OUTPUT. - --json mode emits the structured record on stdout for the workflow to parse via jq. - Windows-aware: uses { shell: process.platform === 'win32' } since npm is npm.cmd on Windows (same lesson as #2962). - Stored under get-shit-done/bin/ (not top-level scripts/) because that path IS in the user's installed config dir; top-level scripts/ ships in the npm tarball but is not copied into ~/.claude/ at install time. tests/bug-2992-check-latest-version.test.cjs - 7 tests, all assertions on the typed CHECK_REASON enum + the structured record. Injectable spawn function so no real npm process is invoked. Covers OK, npm-non-zero, invalid-output, empty-output, pre-release semver, PACKAGE_NAME constant lock, enum-shape lock. get-shit-done/workflows/update.md - check_latest_version step rewritten to call the script via `node "${GSD_HOME}/get-shit-done/bin/check-latest-version.cjs" --json` and parse the structured response with jq. Explicit "Do NOT run `npm view` or `npm search` directly" guidance cites #2992 so future contributors understand why. Closes #2992 * fix(#2992): trailing slash on GSD_HOME default to satisfy bare-path lint The bug-2470 regression test scans update.md for bare `$HOME/.claude` references (no trailing slash). The PR added one in the new check_latest_version step. Fix: trailing slash on the default value (`${GSD_HOME:-$HOME/.claude/}`). Bash POSIX collapses the resulting double slash; the lint pattern's negative lookahead is now satisfied. * fix(#2992): emit GSD_DIR from get_installed_version, use it in check_latest_version Addresses CodeRabbit feedback: the previous `${GSD_HOME:-$HOME/.claude/}` fallback hardcoded the Claude runtime path, which silently breaks for non-Claude runtimes (gemini, codex, opencode, kilo). Fix: - get_installed_version now emits a 4th line with the resolved config dir ($LOCAL_DIR or $GLOBAL_DIR), captured by callers as GSD_DIR. - check_latest_version uses $GSD_DIR/get-shit-done/bin/check-latest-version.cjs. Empty GSD_DIR (UNKNOWN scope) skips the version check and falls through to fresh-install path. This keeps the package name deterministic (#2992) AND respects the detected runtime, instead of assuming Claude. * chore(#2992): add changeset fragment for PR #2993 * chore(#2992): add changeset fragment for PR #2993 * fix(#2992): consolidate LATEST_RESULT parsing inside the GSD_DIR guard CodeRabbit on PR #2993: the previous structure separated the GSD_DIR guard from the jq parsing, so when GSD_DIR was empty the parsing block ran against an unset LATEST_RESULT and produced misleading 'couldn't check for updates' diagnostics instead of clean 'no_install_detected'. Move all field assignments inside the conditional so the skip path seeds LATEST_OK=false, LATEST_VERSION='', LATEST_REASON='no_install_detected', and LATEST_STATUS=0 atomically. * fix(#2992): emit GSD_DIR in early-return; add code-block lang and spawnSync timeout (CR) CodeRabbit on PR #2993 caught three issues: 1. (Major) The early-return path in get_installed_version (PREFERRED_CONFIG_DIR fast path) only echoed 3 lines, but PR #2993 changed the contract to 4 (GSD_DIR is now line 4). Downstream check_latest_version misread valid installs as UNKNOWN. Added `echo "$PREFERRED_CONFIG_DIR"` before exit 0. 2. (Minor) Markdown MD040: fenced code block at line 310 was missing a language identifier. Added ```text. 3. (Quick win) spawnSync('npm view ...') had no timeout, so a hung network could block /gsd-update indefinitely. Added 15s timeout; on timeout spawnSync returns with signal !== null and the existing failure path emits FAIL_NPM_FAILED. * fix(#3008): kill cross-process race in install-minimal:307 mid-copy test Old shape compared listTmpStageDirs() snapshots before/after the mid-copy throw. Under scripts/run-tests.cjs --test-concurrency=4, tests/install-minimal-all-runtimes.test.cjs runs in a parallel subprocess and also creates gsd-minimal-skills-* dirs in shared os.tmpdir(). The parallel process's create/remove activity between this test's two snapshots caused deterministic failure when timing aligned -- presented as 'flaky' but is a real race. CI failure data (PR #2993 run 25238555786): expected (before): ['gsd-minimal-skills-km1O1O'] actual (after): [] Both processes behaved correctly in isolation. The test was wrong: it observed a shared filesystem state across processes. Fix: stub fs.mkdtempSync inside this test to record THIS call's stage dir path. After the throw, assert fs.existsSync(stagedDir) === false. Direct observation of the function's own behavior; no global tmpdir scan; no parallel-process interference. Closes #3008 * fix(#2992): distinguish timeout from npm failure; guard empty LATEST_RESULT (CR) CodeRabbit on PR #2993 (post-fix-up review) caught two improvements: 1. (Low value) check-latest-version.cjs:55-61 — when spawnSync times out, r.status is null and r.signal is set (e.g. 'SIGTERM'), but r.stderr is empty. Without the signal-first branch, both timeouts and genuine npm failures shaped as 'npm exited non-zero' in detail, making logs ambiguous. Added explicit signal-first branch: 'npm timed out (signal: SIGTERM)'. 2. (Quick win) update.md:284-315 — when node is missing or the script doesn't exist, LATEST_RESULT is empty. Piping empty to jq parses without error but leaves LATEST_OK / LATEST_REASON as empty strings, producing the user-visible diagnostic 'Couldn\'t check for updates (reason: , exit: N)' with a blank reason. Added an explicit guard that sets LATEST_REASON to 'script_not_found_or_node_unavailable' when LATEST_RESULT is empty, so operators see a meaningful failure message. Tests: bug-2992 grows by 2 cases (timeout signal detail + empty stderr fallback). |
||
|
|
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. |
||
|
|
ef43f5161f |
fix(#2969): deterministic Step 5 verification gate for /gsd-reapply-patches (#2972)
* fix(#2969): deterministic Step 5 verification gate for /gsd-reapply-patches The prior Step 5 "Hunk Verification Gate" was prescribed correctly in the workflow text — but executed laxly by the LLM, which filled in `verified: yes` without actually checking content presence. The reporter observed three distinct files (skills/gsd-discuss-phase/SKILL.md, skills/gsd-autonomous/ SKILL.md, get-shit-done/workflows/new-project.md) where archives contained substantive user-added blocks that did not survive into the merged result, yet the gate reported clean. Move verification from LLM-driven prose into a deterministic Node script the workflow calls. The script can't be shortcut. Changes: - scripts/verify-reapply-patches.cjs (new): pure Node, no external deps. For each file in the patches dir, computes user-added significant lines as the line-set diff between backup and pristine baseline (when available; falls back to "every significant backup line" when no pristine — over-broad but the safe direction for this bug class). Asserts each line appears literally in the merged installed file via String.prototype.includes. Filters trivial lines (length < 12 chars, pure punctuation, decorative comments) so harmless drift doesn't trigger false failures. Exits 0 on pass, 1 on any miss with per-file diagnostic, 2 on usage error. Supports --json for workflow consumption. - get-shit-done/workflows/reapply-patches.md: rewrite Step 5 to call the script and parse its JSON output. The Step 4 Hunk Verification Table remains as advisory Claude-readable summary, but the gate is now the script's exit code. - tests/bug-2969-verify-reapply-patches.test.cjs (new): 6 tests covering (a) pass when every line survives, (b) fail when a line is missing, (c) fail when the merged file is deleted entirely, (d) --json structured report shape, (e) backup-meta.json is correctly skipped as metadata, (f) no-pristine-dir fallback exercises the safe over-broad path. All pass. Out of scope: the manifest-baseline tightening described in #2969 Failure 1 (saveLocalPatches comparing against the wrong baseline so prior silent wipes poison subsequent updates). That's a separate, bigger architectural change involving pristine-content infrastructure; this PR addresses the gate fidelity half so users at least see the diagnostic when content goes missing. Closes #2969 (partial — Failure 2 only) * fix(#2969): preserve #1999 Hunk Verification Table assertions alongside new script gate CI failure on PR #2972 surfaced that tests/reapply-patches.test.cjs (the #1999 contract) asserts Step 5 references: - "Hunk Verification Table" - `verified: no` failure condition - explicit STOP/halt/abort directive - "table absent / missing" halt path My initial Step 5 rewrite for #2969 substituted the deterministic script for the table-based gate entirely, stripping those references. The script is the strictly stronger gate, but the existing #1999 test enforces the table-based safety net as a defense-in-depth contract. Restore both gates as a layered Step 5: - 5a (binding): deterministic verifier script — script gate, exits non-zero on any miss, cannot be shortcut by the LLM - 5b (advisory): Hunk Verification Table review — preserved as redundant safety net for the case where the script has a bug or the pristine baseline is unavailable Both gates must pass. Verified: tests/reapply-patches.test.cjs (5 tests in the #1999 suite) and tests/bug-2969-verify-reapply-patches.test.cjs (6 tests in the #2969 suite) all pass — 21/21 total in this fixture. * fix(#2969): address CodeRabbit findings on workflow + script Five CR findings on PR #2972, all valid; addressed in this commit: 1. (Major) Stderr was merged into VERIFY_OUTPUT via `2>&1`, so any Node warning, deprecation notice, or stack trace would corrupt the JSON parse downstream. Capture stdout only; stderr remains on the controlling terminal for operator visibility. 2. (Major) verifyFile() crashed with EISDIR/EACCES instead of producing a structured diagnostic when the installed path was a directory or unreadable. Wrap statSync/readFileSync in try/catch and emit a per-file fail row; the whole-run gate continues with structured output. Added test case asserting the directory-at-installed-path case fails with `not a regular file` diagnostic instead of crashing. 3. (Minor) PRISTINE_FLAG built as a single string + unquoted expansion would split paths with spaces. Switched to a bash array (VERIFY_ARGS) that preserves whitespace through expansion. 4. (Minor) Fenced code block missing language tag (markdownlint MD040). Added `text` tag to the error message block. 5. (Minor) Usage comment said pristine fallback was "backup-meta lookup" but the actual code path falls back to significant-line checks from backup content. Corrected the comment to match implementation. Verified all 21 tests in tests/reapply-patches.test.cjs (#1999 contract) + tests/bug-2969-verify-reapply-patches.test.cjs (now 7 tests with the new directory case) pass. * test(#2969): structured JSON assertions, no substring matching on script output Replace every assert.match(r.stdout, /pattern/) call with structured assertions on the parsed JSON report from the script's own --json mode. The script's --json contract IS the structured shape we test against — the test author should never depend on the human-readable formatter output, just as no test should depend on substring presence in source. Changes: - All 7 tests now run the verifier with --json (via a runVerifier() helper) and parse the resulting JSON document into { status, report, stderr }. Diagnostic stderr is preserved as a separate channel for debug output but is not used for assertions. - Each previously substring-matched diagnostic ("Failures: 1", "not a regular file", "installed file missing after merge", file path, dropped line) is now a deepEqual / equal / Array.includes against typed report fields: report.failures, report.results[i].status, report.results[i].reason, report.results[i].file, report.results[i].missing[]. - Added an explicit "documented shape" test asserting the JSON output has exactly the keys { file, missing, reason, status } per result — locks the public contract of the --json mode. - DRY'd up fixture reset into a resetFixture() helper since every test starts with a fresh patches/installed/pristine triple. Linter: scripts/lint-no-source-grep.cjs reports 0 violations across 348 test files. Combined run of bug-2969-...test.cjs (7 tests) + reapply-patches.test.cjs (5 tests in the #1999 suite) all pass — 22/22 in the relevant fixture. * fix(#2969): typed REASON enum + raw-text-matching rule shipped repo-wide This commit closes the loop on the no-source-grep discipline: 1. scripts/verify-reapply-patches.cjs: - Frozen REASON enum exposes the diagnostic surface as stable codes: OK_NO_USER_LINES_VS_PRISTINE, OK_NO_SIGNIFICANT_BACKUP_LINES, FAIL_INSTALLED_MISSING, FAIL_INSTALLED_NOT_REGULAR_FILE, FAIL_READ_ERROR, FAIL_USER_LINES_MISSING. - Each result.reason is now a code from this enum, not free text. Tests assert via REASON.X equality, not regex on prose. - REASON exported from module.exports. 2. tests/bug-2969-verify-reapply-patches.test.cjs: - Full rewrite. Every assertion on typed structured fields: report.results[0].status === 'fail', report.results[0].reason === REASON.FAIL_INSTALLED_NOT_REGULAR_FILE, report.results[0].missing.includes(droppedLine) (Array set membership, not String substring). - Locks the REASON enum surface via Object.keys(REASON).sort() deepEqual. - Locks the JSON report shape via Object.keys(report).sort() deepEqual. - Zero regex, zero String#includes, zero startsWith/endsWith on text. 3. CONTRIBUTING.md: - New section "Prohibited: Raw Text Matching on Test Outputs" with concrete BAD/GOOD examples (substring on file content; assert.match on stdout; "structured parser" hiding string ops; regex on free-form reason fields). - The rule statement: "Tests assert on typed structured values. If the code under test produces text, the code under test must also expose a structured intermediate representation, and the test must assert on that IR — never on the rendered text." - Required structured-surface table: file IR, --json mode, frozen enum, fs facts. - "Hiding grep behind a function is still grep" callout — the parser-wrapper anti-pattern. - New `pre-existing-text-matching` exemption category for the 8 grandfathered files. Marked Transitional; new tests cannot use it. 4. scripts/lint-no-source-grep.cjs: - Three new patterns enforced (in addition to the existing .cjs-source readFileSync rule): - assert.match/doesNotMatch on .stdout/.stderr - .stdout/.stderr.<includes|startsWith|endsWith>( - readFileSync(...).<includes|startsWith|endsWith>( - Aggregated violations per file (multiple findings now report together). - Updated diagnostic message references both CONTRIBUTING.md sections. 5. 8 pre-existing tests annotated with `// allow-test-rule: pre-existing-text-matching` so the lint passes on this commit; each carries the prose "Tracked for migration to typed-IR assertions; do not copy this pattern." Files: bug-2649, bug-2687, bug-2796, bug-2838, bug-2943, graphify, hooks-opt-in, security-scan. Verification: lint 0 violations across 348 test files; full suite passes. * fix(#2969): rename exemption category to pending-migration-to-typed-ir + cite tracking issue Per maintainer feedback: 1. "Grandfathered" / "legacy" framing is wrong — both terms imply permanent or condoned exemption. The 8 files are tracked for correction, not exempted. 2. Each annotated file must cite the tracking issue so the migration work is auditable. Changes: - CONTRIBUTING.md: rename exemption category from `pre-existing-text-matching` to `pending-migration-to-typed-ir`. Update prose to "Tracked for correction, not exempted" and require each annotation to cite the open migration issue (e.g. `// allow-test-rule: pending-migration-to-typed-ir [#NNNN]`). - 8 test files: update annotation to cite #2974 (the tracking issue opened for migrating these files to typed-IR assertions). |
||
|
|
0d6abb87ac | fix(#2954): align help.md with post-#2824 skill consolidation (#2959) | ||
|
|
3ba17e872e |
fix(#2950): update stale deleted-command references in workflow files (#2952)
* fix(#2950): update stale deleted-command references in workflow files Eight workflow files (help.md, do.md, settings.md, discuss-phase.md, new-project.md, plan-phase.md, spike.md, sketch.md) referenced command names removed in #2790. Updated all occurrences to canonical new forms: /gsd-phase (--insert / --remove), /gsd-capture, /gsd-config (--profile / --integrations / --advanced), /gsd-spike --wrap-up, /gsd-sketch --wrap-up, /gsd-code-review --fix. Adds regression test (124 assertions) in tests/bug-2950-stale-command-refs.test.cjs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2950): update pre-existing assertions to accept new consolidated command forms gsd-settings-advanced.test.cjs and settings-integrations.test.cjs were checking settings.md for the old micro-skill names (/gsd-settings-advanced, /gsd-settings-integrations). Now that #2950 updates settings.md to use the consolidated equivalents, broaden the assertions to accept both old and new forms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2950): require canonical command forms and forbid legacy variants The broadened OR assertions added to unblock CI were too permissive — they could pass with legacy names still present. Now assert the canonical form is present (gsd-config --advanced / gsd-config --integrations) AND the legacy forms are absent (gsd-settings-advanced, gsd:settings-advanced, /gsd-settings-integrations). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
4d628b306a |
fix(#2949): wire sketch --wrap-up flag dispatch (#2953)
* fix(#2949): wire sketch --wrap-up flag dispatch Add dispatch logic to commands/gsd/sketch.md so --wrap-up routes to the sketch-wrap-up workflow instead of silently falling through to the normal sketch workflow. Also adds sketch-wrap-up.md to execution_context and updates companion references in workflows/sketch.md from the deleted /gsd-sketch-wrap-up command to /gsd-sketch --wrap-up. Fixes #2949 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2949): use exact-match "If it is" instead of "If it contains" for --wrap-up dispatch Aligns with the established pattern across all consolidated commands (workspace.md, update.md, progress.md) where the first-token check uses "If it is `--flag`" for exact equality, not substring matching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e2792536d9 |
feat(workflows): atomic Write+commit ordering for SUMMARY.md (#2806) (#2939)
* feat(workflows): add atomic Write+commit ordering directive for SUMMARY.md Adds explicit prompt-ordering language to executor spawn prompts and plan-execution steps so agents commit SUMMARY.md before emitting any concluding narrative. Mitigates the truncation-between-Write-and-commit failure mode that has made the #2070 rescue net load-bearing. Refs #2806 * fix(workflows): condense REQUIRED ORDER blocks to fit XL budget The two REQUIRED ORDER directives added in bd1956df pushed execute-phase.md to 1712 lines, exceeding the 1700-line XL budget. Collapse each 6-line block into a single line that preserves the semantic intent (Write SUMMARY.md → commit → narration; no text between Write and commit; #2070 rescue is not primary defense). File is now exactly 1700 lines; workflow-size-budget test passes. * fix(execute-plan): move self-check before commit to preserve atomic Write+commit (#2939) |
||
|
|
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> |
||
|
|
faee0287a0 |
fix(detect-custom-files): add skills/ to GSD_MANAGED_DIRS (#2942) (#2945)
After v1.39.0 skill consolidation (#2790), skills/ became a GSD-managed root that the installer wipes on update. GSD_MANAGED_DIRS in gsd-tools.cjs was missing 'skills', so user-added skill directories (e.g. skills/custom-skill/SKILL.md) were never walked and silently destroyed during /gsd-update. - Add 'skills' to GSD_MANAGED_DIRS so the directory is walked - Add tests/bug-2942-detect-custom-skills.test.cjs with 5 targeted tests - Update tests/update-custom-backup.test.cjs: replace the now-incorrect "skills/ must NOT be scanned" assertion (written pre-#2790) with a test that verifies custom skills ARE detected and GSD-owned skills are not falsely flagged Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
5abf46ac1c |
Merge pull request #2920 from gsd-build/feat/hermes-runtime-2841
feat(install): add Hermes Agent runtime support |
||
|
|
1168e9f59a |
Merge pull request #2921 from gsd-build/fix/2916-handle-branching-default-base
fix(#2916): branch new phases off origin/HEAD instead of current HEAD |
||
|
|
3ed8980519 |
fix(#2916): drop unreachable post-creation merge-base guard
CodeRabbit pointed out the post-creation guard is structurally unreachable: immediately after `git checkout -b X origin/$DEFAULT_BRANCH`, HEAD == origin/$DEFAULT_BRANCH, so both the merge-base form (`MB == DT`) and the alternative "ahead-of" count form (`AHEAD == 0`) are sentinels that always pass on a successful fresh checkout. With the explicit base arg + fail-fast on the checkout, the guard cannot catch anything new. Removing it (rather than swapping in another no-op that satisfies the linter but adds no actual coverage) is the honest fix. Comment retained to explain why no post-creation guard is needed: the explicit base argument to `git checkout -b` is the single source of correctness for #2916. Same simplification mirrored in get-shit-done/workflows/quick.md. Full suite: 6102/6102. |
||
|
|
c3aef27aa6 |
fix(#2916): fail-fast on switch/checkout, gate fork-point warning to fresh branches
Two CodeRabbit findings on PR #2921 (review 4209533909 + comment 3171721073, both still unresolved): A. Branch switch and create steps now abort on non-zero exit. Previously `git switch "$BRANCH_NAME"` and `git checkout -b "$BRANCH_NAME" "origin/$DEFAULT_BRANCH"` could fail (locked worktree, dirty tree refusing the checkout, etc.) and the workflow would silently continue on the wrong branch — sending the phase's later commits to the wrong place. Both calls now `|| { echo "ERROR: …" >&2; exit 1; }`. B. The fork-point base-warning is now scoped to the creation arm of the if/else. Previously it ran for the resume path too, so a legitimate resumed branch where origin/$DEFAULT_BRANCH had advanced since first creation would falsely warn ("does not fork from origin/<DEFAULT_BRANCH>"). Moving the check inside the else arm means it only runs immediately after a fresh `git checkout -b`, when the merge-base check is meaningful. Same fix mirrored in get-shit-done/workflows/quick.md. execute-phase.md stays at the 1700-line XL budget. Full suite: 6102/6102. |
||
|
|
2256e4c9a3 |
fix(#2916): use fork-point detection for non-default-base warning
Replace the "ahead-of" heuristic with a structural check that compares the HEAD↔origin/$DEFAULT_BRANCH merge-base to origin/$DEFAULT_BRANCH itself. The previous count-based warning fired on legitimate WIP that was simply ahead of the default branch — the correct signal is that the branch did not fork from the default branch in the first place. Addresses CodeRabbit review on PR #2921. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4a5f36df5e |
Merge pull request #2919 from gsd-build/fix/2911-audit-open-output-references
fix(#2911): audit-open emits raw human report and parseable JSON |
||
|
|
4e2f1105d9 |
fix(#2916): pin new-branch base to origin/$DEFAULT_BRANCH explicitly
Address CodeRabbit HIGH findings on PR #2921. The previous fix had three unconditional code paths where `git checkout -b "$BRANCH_NAME"` would run from the *current* HEAD when the upstream sync failed silently: - the dirty-tree warn-and-continue path, - the clean path where `git switch` / `git merge --ff-only` errors were swallowed by `2>/dev/null` (still falling through to checkout -b), - any case where `git fetch` failed but the script continued. This rewrites both `execute-phase.md` (handle_branching) and `quick.md` (Step 2.5) to: 1. Fetch origin/$DEFAULT_BRANCH; if fetch fails AND no local copy of origin/$DEFAULT_BRANCH exists, abort with a clear ERROR (exit 1) rather than create the branch off arbitrary HEAD. 2. Always create the new branch with an explicit start point: `git checkout -b "$BRANCH_NAME" "origin/$DEFAULT_BRANCH"`. The base is now deterministic regardless of which branch is currently checked out, regardless of whether the optional local fast-forward succeeded, and regardless of dirty-tree state. 3. Carry uncommitted changes onto the new (origin-pinned) branch instead of inheriting the previous-phase HEAD as a fallback base. The post-creation INHERITED check now references origin/$DEFAULT_BRANCH rather than the (possibly-stale) local default branch, so the warning fires accurately even when the local fast-forward was skipped. |
||
|
|
294564b951 |
fix(#2916): branch new phases off origin/HEAD instead of current HEAD
handle_branching in execute-phase.md (and the equivalent step in quick.md) created the per-phase branch from whatever branch happened to be checked out — typically the previous phase's still-unmerged feature branch — so consecutive phases compounded on top of each other and stayed unpushed. Detect the default branch via git symbolic-ref refs/remotes/origin/HEAD, fast-forward it from origin, and fork the new phase branch off that tip. Existing branches are still reused as-is. Dirty working trees fall back to current HEAD with a loud warning, and a post-creation guard reports any inherited commits. Regression test extracts the bash from the <step name="handle_branching"> block structurally and runs it against a fixture repo where HEAD sits on a previous-phase branch with extra commits. |
||
|
|
9a13d2fc0b |
fix(#2911): audit-open emits raw human report and parseable JSON
Two bugs in the audit-open dispatch case in bin/gsd-tools.cjs:
1. Bare output(...) calls (only core.output is in scope) threw
ReferenceError: output is not defined on every invocation,
blocking the first step of /gsd-complete-milestone.
2. Even after switching to core.output(formattedReport, raw), the
human-readable branch JSON-stringified the formatted text because
core.output only bypasses JSON encoding when called as
core.output(null, true, rawValue).
Fix:
- --json path: core.output(result, raw) — pass the object,
let core.output JSON-stringify (don't pre-stringify).
- text path: core.output(null, true, formatAuditReport(result))
— use the rawValue form to emit verbatim section dividers and
item lists.
Adds tests/bug-2911-audit-open-output-shape.test.cjs which parses
both modes structurally — line-by-line for text mode (asserting the
report headers exist as standalone lines, not as escaped \n inside a
JSON quoted string), and JSON.parse + key-by-key shape assertions for
--json mode (matching the contract returned by auditOpenArtifacts).
|
||
|
|
d29822c1da |
fix(progress): add explicit context-authority directive to report step
The report step in workflows/progress.md had no directive establishing PROJECT.md/STATE.md/ROADMAP.md as the authoritative sources for the progress report. When init.progress returned project_exists: false (e.g. invoked from a subdirectory without .planning/), the model fell back to whatever was in its session context — including stale CLAUDE.md ## Project blocks — and produced routing output citing the wrong milestone/phase. Add a blockquote directive at the top of the report step that names PROJECT.md, STATE.md, and ROADMAP.md as authoritative and forbids using the CLAUDE.md ## Project block as a source for any progress report field. Fixes #2912 |
||
|
|
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. |