Commit Graph

3539 Commits

Author SHA1 Message Date
Ben Lamm
4b66ca5800 fix(#2641): harden <details> fallback per trek-e review
Address trek-e's adversarial review on PR #3046. Two critical merge-blockers
plus four hardening items, all now covered with tests.

CRITICAL #1 — substring-version trap:
  `[^<]*${escapedVersion}[^<]*` did substring containment, so
  `milestone: v0.1` matched <summary>v0.10 …</summary> and returned the
  v0.10 block's body as the active milestone — confidently-wrong content
  worse than the pre-PR fall-through. Add `(?![\d.])` non-version-character
  lookahead, mirroring the same boundary protection used by the existing
  `currentVersionStr` logic on the heading path. Test asserts v0.1 active
  with v0.10 sibling block returns v0.1's phases, not v0.10's.

CRITICAL #2 — nested <details> silent truncation:
  The lazy `[\s\S]*?</details>` terminates on the FIRST </details>, which
  is the inner closer when nesting is present. Prior comment claimed
  "would mis-anchor (acceptable; falls through)" — factually wrong: the
  match succeeds with truncated body and is returned with a confident
  `## ${summary}` heading. Future maintainer investigating a "missing
  phase" report would be misled. Add `!detailsMatch[2].includes('<details')`
  guard so nesting falls through to stripShippedMilestones (loud failure)
  instead of returning truncated content (silent failure). Test locks
  the contract: no synthesized v0.9 heading anchored to truncated body.

HARDENING:
  - Empty-body guard: `<details><summary>v0.9</summary></details>` would
    synthesize `## v0.9\n` (phantom milestone, zero phases, no error
    signal). Treat as no-match.
  - Inline-HTML in <summary>: rejected by `[^<]*` capture. Widen to
    `(?:(?!</summary>).)*?` (non-greedy until close tag) and strip tags
    + leading `#` from the captured summary before promoting to a `##`
    heading. Covers GitHub-rendered <em>(active)</em>, <code>v0.9</code>,
    <strong>...</strong> patterns.
  - JSDoc: rewrote to describe both anchoring strategies and the
    synthesized-heading contract; demoted stale "Port of core.cjs lines
    1102-1170" to historical context with the divergence list.
  - Comment block: rewrote in contract style ("any consumer scanning
    /##\s*.*vX.Y/ sees the active milestone") instead of coupling to
    specific call sites (roadmapAnalyze, "later in this file"). Adds
    explicit regex anatomy + hardening-guards section so future readers
    can audit each guard.

OUT OF SCOPE (per trek-e's "Recommended action" tier):
  - Debug logging on fall-through paths (Suggestion #10) — adds tracing
    surface to a function that doesn't currently use logger; appropriate
    for a follow-up if/when other extraction bugs surface.
  - Uppercase <DETAILS>/<SUMMARY> + extended attribute coverage
    (Test gap #7 last two rows) — already covered by the documented `i`
    flag and the existing <details open> test; adding redundant cases
    inflates the test set without locking new contracts.

Verification: 45/45 roadmap.test.ts tests pass (was 41/41; added 4
hardening tests). FAMP end-to-end smoke unchanged: roadmap.get-phase 3
returns "Claude Code integration polish", roadmap.analyze surfaces
v0.9 Local-First Bus in data.milestones with phase_count: 4.
2026-05-06 15:41:27 -04:00
Ben Lamm
c8239f67f8 fix(#2641): inject normalized ## heading from <details><summary> capture
Address CodeRabbit review on PR #3046: the prior commit returned only the
body inside <details>...</details>, which fixed the `roadmapGetPhase`
miss but left `roadmapAnalyze`'s downstream `data.milestones` scan
(`/##\s*(.*v(\d+(?:\.\d+)+)[^(\n]*)/gi` at the bottom of roadmap.ts)
without an active-milestone anchor in the returned slice.

Now capture the <summary> text and prepend it as a synthesized `##`
heading on the returned slice. This makes both `data.phases` (the
original bug) AND `data.milestones` (the downstream consumer) surface
the active milestone correctly for <details>-wrapped ROADMAPs.

Also widened the inner tag to `<summary\b[^>]*>` for symmetry with the
outer `<details\b[^>]*>` — both now tolerate attributes.

Verified end-to-end against FAMP's v0.9 ROADMAP:
- Before this commit (after PR #3046 base):
    milestones: [{heading: '# Phase 1: ... (v0.5.2 atomic bump)', version: 'v0.5.2'}]
- After this commit:
    milestones: [{heading: 'v0.9 Local-First Bus', version: 'v0.9'},
                 {heading: '# Phase 1: ... (v0.5.2 ...)', version: 'v0.5.2'}]

(The v0.5.2 entry is pre-existing noise from the loose `##\s*` regex
matching the `### Phase 1: famp-bus (v0.5.2 atomic bump)` body heading;
unrelated to this fix and out of scope for this PR.)

Tests:
- Updated the two `<details><summary>` tests to assert the synthesized
  `## v0.9 Local-First Bus` heading is present on the returned slice.
- Added a 4th regression test (`roadmapAnalyze`) confirming
  `data.milestones` now contains the active milestone for
  <details>-wrapped ROADMAPs.
- All 40 roadmap.test.ts tests pass.
2026-05-06 15:41:27 -04:00
Ben Lamm
ba6a3efc3e fix(#2641): strip YAML quotes from STATE.md milestone version
Address CodeRabbit review on PR #3046: extractCurrentMilestone read the
`milestone:` value from STATE.md frontmatter via `.trim()` only, while
parseMilestoneFromState() and getMilestoneInfo() both also strip
surrounding YAML quotes via `.replace(/^["']|["']$/g, '')`.

For projects whose STATE.md uses quoted YAML (`milestone: "v0.9"`),
`version` carried literal quotes, `escapedVersion` became `\"v0\.9\"`,
and neither the markdown-heading regex nor the new <details><summary>
fallback could match anything — falling through to
stripShippedMilestones() and reintroducing the same archived-milestone
misrouting this PR addresses.

Strip quotes for parity. Three-line addition + one new test.
All 41 roadmap.test.ts tests pass.
2026-05-06 15:41:27 -04:00
Ben Lamm
592b676414 fix(#2641): recognize <details><summary> as active-milestone anchor
`extractCurrentMilestone` only matched markdown headings (## v0.9, ### v0.9)
to find the active milestone slice. Projects that wrap their active
milestone's phase details inside `<details><summary>vX.Y …</summary>`
(a common GitHub-friendly collapse pattern, e.g. FAMP) fell through to
`stripShippedMilestones`, which strips ALL `<details>` blocks indiscriminately.

Net effect: `roadmapGetPhase` returned `{found:false}` for phases that ARE in
the active ROADMAP. The `init.phase-op` safety guard at `init.ts:133`
('drop archived disk match when phase is in current ROADMAP') depends on
`roadmapPhase.found`, so it didn't fire. `init.phase-op` then returned a
`phase_dir` pointing at an ARCHIVED milestone's same-numbered phase —
silently routing downstream workflows (e.g. /gsd-discuss-phase) into
completed phases.

Fix: when no markdown heading matches the active version, try matching
`<details\b[^>]*><summary>...vX.Y...</summary>`. Returns the inner content
of the matching block. Purely additive — `stripShippedMilestones` behavior
and its tests are unchanged.

The `\b[^>]*>` form tolerates attributes like `<details open>` or
`<details class="...">` (GitHub commonly emits `<details open>` for
default-expanded sections). Lazy `[\s\S]*?` matches up to the first
`</details>`; nested `<details>` inside the active milestone are not
expected and would mis-anchor (acceptable; falls through to the existing
`stripShippedMilestones` path with no regression vs. today's behavior).

Closes #2641. Distinct from the closed #2642 which bundled three orthogonal
changes (parser fix + checkbox-scan fix + STATE.md counting auth) into one
PR; this PR addresses only the parser anchoring bug, leaving
`stripShippedMilestones`, `roadmapAnalyze`, and `initMilestoneOp` untouched.

Tests added (3, all in `roadmap.test.ts`):
- `bug-2641: finds active milestone wrapped in <details><summary>vX.Y …</summary>`
- `bug-2641: finds active milestone in <details open><summary>vX.Y …</summary>`
- `bug-2641: returns found:true for phase inside <details>-wrapped active milestone` (end-to-end via `roadmapGetPhase`)

All existing `roadmap.test.ts` tests pass (39/39). Real-world repro
verified against an FAMP-style ROADMAP: before the fix,
`gsd-sdk query roadmap.get-phase 3` returned `{found:false}` despite the
phase being at line 113 of the active ROADMAP; after the fix, it returns
the correct phase metadata, and `init.phase-op 3` no longer returns the
v0.8 archived `phase_dir`.
2026-05-06 15:41:27 -04:00
Tom Boucher
99b2bddd14 fix(milestone-archive): expose searched roots and parse canonical STATE milestone 2026-05-06 15:38:53 -04:00
Tom Boucher
6b9ee44e19 test(rename): align Copilot and ingest-docs assertions with Agent tool 2026-05-06 15:34:29 -04:00
Tom Boucher
56737c057b fix(verify): use active milestone phase roots for consistency checks 2026-05-06 15:30:45 -04:00
Tom Boucher
a4cb7451ff docs(workflows): address CodeRabbit markdownlint and wording findings 2026-05-06 15:30:28 -04:00
Tom Boucher
a0d95176db chore: add changeset fragment for #3191 2026-05-06 15:21:30 -04:00
Tom Boucher
e53c5e6865 chore: add changeset fragment for #3189 2026-05-06 15:21:29 -04:00
Tom Boucher
b4894323e5 chore: add changeset fragment for #3188 2026-05-06 15:21:24 -04:00
Tom Boucher
a1a81eec90 fix(config): align SDK runtime-state key validation with CJS 2026-05-06 15:19:34 -04:00
Tom Boucher
019f114787 fix(dispatcher): finish Task→Agent prose rename in workflows 2026-05-06 15:19:16 -04:00
Tom Boucher
4847277082 fix(gsd-tools): support .planning/milestones/v*-phases/ layout in validators and find-phase
Fixes #3164

Validators and find-phase hardcoded phasesDir = .planning/phases/, so projects
using the milestone-archive layout (.planning/milestones/v*-phases/) had an empty
diskPhases set, triggering W006 for every active phase and find-phase returning
found:false.

Add collectDiskPhases(planBase) helper that scans both flat layout and all
.planning/milestones/v*-phases/ subdirs. Wire it into cmdValidateConsistency,
cmdValidateHealth (both the Check 4 validPhases set and Check 8 diskPhases),
and refactor cmdFindPhase to iterate candidate search dirs so it also searches
milestone-archive dirs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:10:15 -04:00
Tom Boucher
bb858e0e11 docs(changelog): add #3168 Task→Agent dispatcher rename entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:00:31 -04:00
Tom Boucher
1452b1275b fix(dispatcher): rename Task→Agent in allowed-tools, workflow prose, and agent tools frontmatter
Fixes #3168

The Claude Code subagent dispatcher tool is named `Agent` (with `subagent_type`
parameter). The `Task*` namespace (TaskCreate, TaskList, TaskGet, TaskUpdate,
TaskOutput, TaskStop) is the separate task-tracker. GSD's commands, workflows,
and agents were partially migrated and still referenced `- Task` / `Task(` in
55 files, causing orchestrators to silently fall back to inline execution when
no `Task` tool appeared on their tool surface.

Changes:
- `commands/gsd/*.md` allowed-tools: replaced `- Task` with `- Agent` in 24
  files; removed duplicate `- Task` from autonomous.md (already had `- Agent`)
- `get-shit-done/workflows/*.md`: replaced dispatcher `Task(` → `Agent(` in
  29 workflow files (~133 call sites); TaskCreate/List/Get/Update/Output/Stop
  left untouched
- `agents/gsd-debug-session-manager.md`: replaced `Task` → `Agent` in tools
  frontmatter (the only remaining agent with the wrong name)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:00:08 -04:00
Tom Boucher
9ae4426ebb docs(changelog): add #3162 fixed entries for resolve_model_ids and workflow._auto_chain_active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:57:31 -04:00
Tom Boucher
96ce608ee6 fix(config): add resolve_model_ids to VALID_CONFIG_KEYS; accept workflow._auto_chain_active via RUNTIME_STATE_KEYS
Fixes #3162

`resolve_model_ids` is a documented top-level config key (CONFIGURATION.md)
read by core.cjs and session-runner.ts, but was missing from the CJS and SDK
VALID_CONFIG_KEYS allowlists — causing config-set to reject it with
"Unknown config key".

`workflow._auto_chain_active` is internal runtime state intentionally excluded
from VALID_CONFIG_KEYS by #2530, but plan-phase, execute-phase, discuss-phase,
transition, and new-project workflows all write it via `config-set`. Without
a valid write path these calls emit spurious errors (silenced with `|| true`
but noisy in logs). A new RUNTIME_STATE_KEYS set in config-schema.cjs holds
keys that isValidConfigKey() accepts without exposing them as user-settable
options — preserving the #2530 intent while fixing the runtime error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:56:34 -04:00
Tom Boucher
94f835af40 docs: add Prerelease editions install guidance (Next/Nightly/Insiders/Preview) (#3173)
* docs: add Prerelease editions install guidance (Next/Nightly/Insiders/Preview)

Documents the existing <RUNTIME>_CONFIG_DIR override pattern for users on
prerelease runtime editions (Windsurf Next, Cursor Nightly, VS Code Insiders,
Codex preview, JetBrains EAP, etc.) and explicitly states they are best-effort
and not separately tested under release CI — consistent with the free-string
runtime policy in #2517.

Resolves the discoverability gap behind issue #3161 without enumerating each
prerelease channel as a named runtime. Future "add <runtime>-next/-nightly"
requests can be redirected to the new section.

Closes #3172

* chore(changeset): set PR number for prerelease docs fragment
2026-05-06 12:44:48 -04:00
Fabio
3c1b91609f test(hooks): structural test for Windows npm spawn platform gate
Locks the contract from PR #3102 / issue #3103: the shell option on the
execFileSync('npm', ...) call must be `process.platform === 'win32'`,
never an unconditional `shell: true`.

A regression to `shell: true` would silently change POSIX behavior
(spawn /bin/sh -c, signal/exit-code semantics shift, windowsHide can
lose effect on some Node versions) — exactly the cross-platform risk
flagged in the adversarial review.

Test approach:
- Reads worker source via readFileSync (hooks/*.js, outside the
  lint-no-source-grep .cjs scope; allow-test-rule annotated with reason).
- Strips comments before checking for `shell: true` so prose mentions
  in the JSDoc-style block comment do not trigger the regression check.
- Asserts execFileSync is still the spawn primitive (a swap to execSync
  would silently shell-spawn on POSIX and defeat the gate).
- Why structural, not runtime: the win32 branch only manifests on a
  Windows runner and the repo's CI is POSIX-only.

All 4 subtests pass. Lint-no-source-grep: clean.
2026-05-06 18:34:18 +02:00
Tom Boucher
29eb8be06d feat(graphify): commit-based staleness from built_at_commit (#3170) (#3171)
* test(graphify): TDD-red design contract for #3170 commit-staleness signal

Captures the proposed extension to graphifyStatus() as 8 failing
assertions across 3 groups (git-aware, non-git, back-compat). Suite is
describe.skip()'d so npm test stays green on the branch — removing
.skip is the green-light moment when the enhancement is approved and
implementation lands.

Verified against safishamsi/graphify v0.7.0 release notes: the field
on graph.json is built_at_commit (full git HEAD), not commit_hash as
originally guessed in #3170. Tests assert against the verified name.

Design highlights captured in the file's docstring:
- Tri-state commit_stale (true/false/null) — null means "we don't
  know" (pre-v0.7 graph or no git), distinct from false ("known fresh")
- Argument-injection fence /^[0-9a-f]{4,40}$/i validates built_at_commit
  before it reaches `git` as an argv element
- Existing graphifyStatus() fields (node_count, edge_count, stale,
  age_hours, etc.) are unchanged — back-compat fenced

Per the issue's enhancement template: no PR will be opened until the
issue is labeled `approved-enhancement`.

Refs #3170
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(graphify): surface commit-based staleness from graphify v0.7+ built_at_commit

Closes #3170

graphify v0.7+ embeds built_at_commit (full git HEAD) into graph.json at
write time. GSD's existing graphifyStatus() ignored it; staleness was
mtime-only, which is a poor proxy for "does this graph reflect the
current code." A CI-built graph rebuilt minutes ago against an old
checkout reads as FRESH on mtime but is materially stale.

graphifyStatus() now returns four additional fields on the success path:

  built_at_commit   short hash from graph.built_at_commit, or null
  current_commit    short hash of git HEAD, or null when no git
  commits_behind    git rev-list --count <built>..HEAD, or null
  commit_stale      true | false | null

Tri-state on commit_stale is load-bearing. null means "we don't know"
(pre-v0.7 graph, non-git cwd, unreachable commit) — semantically
distinct from false ("known fresh"). Agents reading null should fall
back to mtime; reading false can confidently skip a rebuild.

Security: built_at_commit is on-disk and user-influenceable. Without
validation, a hostile value (e.g. "--upload-pack=evil") would reach git
as an argv element and be interpreted as an option. The
/^[0-9a-f]{4,40}$/i fence rejects anything else as absent. spawnSync's
array args (no shell) is defense in depth, not the boundary.

Skill (commands/gsd/graphify.md) Step 2b renders one conditional line:

  Source commit: abc1234 (5 commits behind HEAD)
  Source commit: abc1234 (current)
  Source commit: abc1234 (freshness unknown)

Pre-v0.7 graphs omit the line entirely — no confusing "Source commit:
unknown" rendered.

Also documents `graphify hook install` in docs/CONFIGURATION.md for
multi-dev teams who would otherwise hit graph.json merge conflicts on
parallel rebuilds (sub-enhancement 2 from #3170).

TDD red→green: tests/enh-3170-graphify-commit-staleness.test.cjs
(8 assertions across git-aware, non-git, back-compat) was committed
describe.skip()'d in c567f23d when the issue was filed; this commit
removes .skip and lands the implementation that makes them green.
Full suite 7503/7503 passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:59:53 -04:00
Tom Boucher
41dc9bc060 fix(graphify): run /gsd-graphify build inline (with regression fence) (#3169)
* fix(graphify): run /gsd-graphify build inline instead of spawning a sub-agent

Closes #3166

graphify v0.7+ split the build into a fast AST-extraction phase (cached)
followed by a separate clustering + report-write phase. The cached
extraction phase survived sub-agent isolation, but the post-extraction
phase was SIGTERM'd when the agent exited, leaving the cache populated
and no graph.json / graph.html / GRAPH_REPORT.md artifacts written to
.planning/graphs/.

The skill now runs `graphify update .`, the three artifact copies, the
snapshot, and the status report as a single foreground Bash call so the
entire pipeline survives to completion. The CLI's `graphify build`
pre-flight still returns `action: "spawn_agent"` so external callers
and existing tests in tests/graphify.test.cjs keep working.

Regression test (tests/bug-3166-graphify-inline-build.test.cjs) parses
the skill's YAML frontmatter and body structurally to fence against
re-introducing Task to allowed-tools or `Task(` invocation syntax — a
future edit cannot regress the fix without tripping the fence.

Verified against safishamsi/graphify v0.7.0–v0.7.8 release notes:
`graphify update .` invocation and output filenames are unchanged in
v0.7+; no GSD-side interface migration is required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): drop yaml dep from bug-3166 fence — replace with inline parser

CI failed with MODULE_NOT_FOUND on `require('yaml')` — the package
resolved locally as a transitive dep but isn't declared in package.json.
The project pattern (see tests/helpers.cjs `parseFrontmatter`) deliberately
avoids pulling in yaml/js-yaml.

Replace with a narrow inline parser that handles the scalar + block-list
subset used in this skill's frontmatter. Verified the fence still trips
when Task is reintroduced to allowed-tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): parse fenced blocks structurally for #3166 fence

Address CodeRabbit nitpicks on PR #3169: the body assertions used raw
markdown text regex (\bTask\s*\(/, /graphify\s+update\s+\./) which
violates the project's "parse, never grep" testing convention and risks
false-positives on prose.

Replace with extractFencedBlocks(body) which returns
[{lang, content}, ...] tuples per markdown code fence. Body assertions
now run against parsed blocks:

  - "no fenced code block contains Task("
    → deepEqual offending blocks to [] (vs. regex on raw body)
  - "a bash block invokes graphify update . / build snapshot"
    → filter to lang === 'bash', then substring-check inside parsed content

Substring checks within already-parsed fenced content are structural —
prose mentioning the word "Task" can no longer false-positive, and a
future prose reference to graphify cannot satisfy the positive assertions
either. The frontmatter side already used a parser; both sides now match.

Verified: re-introducing Task( inside a code fence still trips the
assertion. Full suite 7499/7499 passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): rename readFileSync-bound var to satisfy lint-no-source-grep

The structural-parse refactor introduced `b.content.includes(...)` calls
on parsed fenced-block records, but `loadSkill()` had also bound
`const content = fs.readFileSync(...)` for the markdown text. The
lint-no-source-grep regex scanner cannot distinguish scopes — it sees
"variable `content` is bound from readFileSync" and "`content.includes`
is called" and flags it as a source-grep test, even though the two
`content`s are different lexical entities.

Rename the readFileSync-bound local to `markdown`. Now `b.content` is
unambiguously a property access on a parsed-block record. Lint passes
(0 violations across 401 test files); behavior unchanged (4/4 tests
still pass, including the negative regression case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): tighten snapshot assertion to gsd-tools.cjs prefix

CodeRabbit nitpick on bug-3166 fence: the snapshot bash assertion accepted
any 'graphify build snapshot' substring. Tighten to require it follows
'gsd-tools.cjs', matching the actual fenced invocation in
commands/gsd/graphify.md (which uses node "$HOME/.../gsd-tools.cjs" graphify
build snapshot — note the closing quote, so a literal 'gsd-tools graphify build
snapshot' substring would not match).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:56:27 -04:00
Tom Boucher
3579a48d76 Merge pull request #3158 from gsd-build/feat/sdk-runtime-bridge-seam
feat(sdk): deepen runtime bridge seam for native-first SDK dispatch
2026-05-05 20:50:26 -04:00
Tom Boucher
3785c09307 fix(sdk): include hotpath fallback reason in bridge observability 2026-05-05 20:29:43 -04:00
Tom Boucher
fe16143e29 fix(sdk): align hotpath observability with actual dispatch mode 2026-05-05 20:22:03 -04:00
Tom Boucher
8ad2e3877f fix(sdk): address CodeRabbit runtime bridge and docs findings 2026-05-05 19:59:56 -04:00
Tom Boucher
fb58731008 chore(changeset): add changelog fragment for sdk runtime bridge seam 2026-05-05 19:40:33 -04:00
Tom Boucher
51b809e8e9 feat(sdk): expose runtime bridge controls via GSD options 2026-05-05 19:36:47 -04:00
Tom Boucher
00ba404b60 test(sdk): enforce runtime bridge seam and explicit no-fallback behavior 2026-05-05 19:31:03 -04:00
Tom Boucher
54b06e653e docs(sdk): document runtime bridge seam, strict mode, and fallback policy 2026-05-05 19:29:59 -04:00
Tom Boucher
1bd11ab699 feat(sdk): emit runtime bridge dispatch observability events 2026-05-05 19:26:01 -04:00
Tom Boucher
0026065c7a feat(sdk): add strict mode and explicit fallback policy to runtime bridge 2026-05-05 19:23:39 -04:00
Tom Boucher
98dd9e4afb refactor(sdk): add runtime bridge seam for query dispatch 2026-05-05 19:21:31 -04:00
Tom Boucher
a7ce59f0fc Merge pull request #3155 from gsd-build/fix/3150-stats-json-omits-phases-when-a-same-majo
test(stats): lock decimal phase continuity when .10 exists
2026-05-05 18:57:33 -04:00
Tom Boucher
b694d31239 chore(3155): address CodeRabbit changeset ref and test diagnostics 2026-05-05 18:53:35 -04:00
Tom Boucher
a0f2dde4d3 Merge pull request #3152 from gsd-build/adr/0002-command-contract-validation-module
enh(#3151): command contract validation module + prose @-ref cleanup + workflow extraction (ADR-0002)
2026-05-05 18:41:56 -04:00
Tom Boucher
386a27e733 Merge pull request #3154 from gsd-build/fix/3153-statusline-percent-string-only-check-ski
fix(statusline): accept numeric 100 and block-list next_phases parsing
2026-05-05 18:41:04 -04:00
Tom Boucher
53879d8c93 test(3150): add stats.json regression for 06.10 decimal-gap sequence 2026-05-05 18:38:37 -04:00
coderabbitai[bot]
96d2556209 fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
2026-05-05 22:36:38 +00:00
Tom Boucher
dbc09d21a6 fix(3153): handle numeric 100 percent and block-list next_phases 2026-05-05 18:34:42 -04:00
Tom Boucher
8adfe4de01 docs(context): add ADR-0002 PR review learnings (CodeRabbit findings synthesis) 2026-05-05 16:07:08 -04:00
Tom Boucher
a411e08e88 fix(coderabbit): resolve all 12 findings on PR #3152
MAJOR (security/correctness):
- commands/gsd/debug.md: add Write to allowed-tools (session file creation
  requires it — workflow explicitly says 'use Write tool, never heredoc')
- workflows/debug.md: add SLUG sanitization guard to steps 1b+1c (status/
  continue subcommands used raw user input in file paths — path traversal)
- workflows/thread.md: sanitize $ARGUMENTS in RESUME mode before file path
  construction (was bypassing the sanitization guard in CLOSE/STATUS modes)

MINOR (consistency/correctness):
- docs/INVENTORY-MANIFEST.json: remove stale top-level 'workflows' array
  (duplicate of families.workflows introduced in earlier update)
- commands/gsd/resume-work.md: normalize process to 'Execute end-to-end.'
- commands/gsd/settings.md: normalize process to 'Execute end-to-end.'
- commands/gsd/update.md: normalize otherwise branch to 'execute end-to-end.'
- docs/adr/0002: add Status: Accepted + Date header (ADR convention)
- workflows/extract-learnings.md: rename step extract_learnings → extract-learnings
- tests/extract-learnings.test.cjs: tighten step-name assertion to exact name

ARCHITECTURE:
- scripts/command-contract-helpers.cjs: extract CANONICAL_TOOLS, parseFrontmatter,
  executionContextRefs as shared module — single source of truth consumed by
  both lint script and test suite (prevents silent lint/test disagreement)
- scripts/lint-command-contract.cjs: require() helpers instead of duplicating
- tests/command-contract.test.cjs: require() helpers; move readFileSync calls
  inside test() callbacks (registration-time throws surface as named failures)
2026-05-05 16:06:29 -04:00
Tom Boucher
b752a9aae7 fix(tests): redirect implementation tests to workflow files after extraction
After extracting debug.md and thread.md implementations to workflow files
and renaming extract_learnings.md, existing tests still referenced the
old locations:

- debug-session-management.test.cjs: commands/gsd/debug.md → workflows/debug.md
- thread-session-management.test.cjs: commands/gsd/thread.md → workflows/thread.md
- extract-learnings.test.cjs: extract_learnings.md → extract-learnings.md
- enh-2430-learnings-consumption.test.cjs: extract_learnings.md → extract-learnings.md

Also adds <available_agent_types> block and TEXT_MODE fallback note to
get-shit-done/workflows/debug.md to satisfy the spawn-type-consistency
(#1357) and AskUserQuestion text-mode fallback (#2012) contract tests
that scan all workflow files.
2026-05-05 15:44:59 -04:00
Tom Boucher
ecf3510511 chore(changeset): add changeset for ADR-0002 enhancement (#3151) 2026-05-05 15:36:45 -04:00
Tom Boucher
81f9534b5a feat(adr-0002): command contract validation module + prose @-ref cleanup + workflow extraction
ADR-0002: commands/gsd/*.md contract now enforced at two layers:

LINT (scripts/lint-command-contract.cjs — new CI step):
- name: present, starts with gsd: or gsd-
- description: non-empty
- allowed-tools: non-empty, all entries canonical
- execution_context @-refs: resolve on disk, no trailing prose on same line
- handles both @~/ and $HOME/ path prefixes

TEST (tests/command-contract.test.cjs — 361 assertions):
- Behavioral contract for all 65 command files
- Replaces scattered coverage in enh-2790 + bug-3135
- Per-command per-rule test — one failure names the exact file + rule

CI (.github/workflows/test.yml):
- 'Lint — command contract (ADR-0002)' step added to lint-tests job

PROSE @-REF CLEANUP (39 command files, ~900 tokens/invocation recovered):
- Removed redundant @~/.claude/get-shit-done/... paths from <process> prose
- execution_context block is now the single authoritative load declaration
- Routing commands (sketch, spike, update, pause-work, etc.) keep routing
  instructions; only the inert path token is stripped

WORKFLOW EXTRACTION (debug.md + thread.md, ~15,000 chars / ~3,750 tokens):
- get-shit-done/workflows/debug.md: full process extracted from commands/gsd/debug.md
- get-shit-done/workflows/thread.md: full process extracted from commands/gsd/thread.md
- Command files reduced to frontmatter + objective + execution_context + context
- debug.md: 9,603 → 1,703 chars; thread.md: 7,868 → 585 chars

RENAME:
- get-shit-done/workflows/extract_learnings.md → extract-learnings.md
  (aligns with hyphen convention of all other workflow files)

DOCS:
- docs/INVENTORY.md: count 85→87, new rows, rename row, fix add-todo --backlog attribution
- docs/INVENTORY-MANIFEST.json: +debug.md +thread.md +extract-learnings.md -extract_learnings.md

Closes ADR-0002 implementation.
2026-05-05 15:18:13 -04:00
Fabio
ad0747ccac fix(hooks): scope shell:true to Windows + add changeset
Address adversarial review feedback on PR #3102:

1. shell:true is now conditional (process.platform === 'win32')
   - POSIX path unchanged: no shell spawn, no overhead, original
     signal/exit-code semantics and windowsHide effect preserved
   - Windows path: still routes through cmd.exe to resolve npm.cmd
     via PATHEXT (the actual fix for ENOENT)

2. Added .changeset/windows-npm-shell-fix.md (Fixed type)

Reviewed feedback resolved:
- Cross-platform regression risk → shell now Windows-only
- Missing changeset → added
2026-05-05 21:16:36 +02:00
Tom Boucher
695ad986c0 docs(adr): add ADR-0002 command contract validation module 2026-05-05 15:09:24 -04:00
Tom Boucher
519de8a91d docs(context): add workflow learnings from 2026-05-05 triage + PR cycle
- Skill consolidation gap class: missing workflow files, detection via regression test
- CodeRabbit stale thread resolution pattern after allow-test-rule fixes
- PR discipline: split unrelated changes, one concern per PR
- INVENTORY.md must stay in sync with workflow filesystem on every add/remove
- README: storyline-only target, MD001/MD040 markdownlint rules to watch
- Issue triage: always check local branches for crash-recovery before re-implementing
- SDK-only verbs: golden-policy NO_CJS_SUBPROCESS_REASON exemption required
2026-05-05 15:03:38 -04:00
Tom Boucher
c2b3f02d41 fix(#3135): restore workflows/add-backlog.md — capture --backlog had no workflow to load (#3147)
* fix(#3121): implement commands verb in SDK native registry

- Add commandsList handler — returns sorted JSON array of all registered
  verb strings; satisfies workstream-flag.md + agent tooling discoverability
- Register ['commands', commandsList] in DECISION_ROUTING_STATIC_CATALOG
- Add golden-policy exemption (SDK-only, no CJS mirror needed)
- check.decision-coverage-plan/verify were already registered; commands was the remaining gap

Closes #3121

* fix(#3135): restore workflows/add-backlog.md — capture --backlog had no workflow to load

Root cause: PR #2824 consolidated add-backlog into gsd-capture --backlog and
wired capture.md to delegate to workflows/add-backlog.md via execution_context.
The workflow file was never created (same gap class as reapply-patches.md which
was caught and fixed in the same PR). With no file to load, the agent had no
implementation steps to follow when --backlog was invoked.

Fix:
- Restore get-shit-done/workflows/add-backlog.md with full process from deleted
  commands/gsd/add-backlog.md (phase.next-decimal, ROADMAP write, mkdir, commit)
- Preserve #2280 ordering invariant: ROADMAP entry written before directory
- Fix docs/INVENTORY.md: remove incorrect attribution of --backlog to add-todo.md,
  add add-backlog.md row, bump workflow count 84→85
- Update docs/INVENTORY-MANIFEST.json
- Add regression test: every execution_context @-reference in commands/gsd/*.md
  must resolve to an existing workflow file on disk

Closes #3135
2026-05-05 15:02:38 -04:00
Tom Boucher
9811782e6d fix(#3121): implement commands verb in SDK native registry (#3146)
- Add commandsList handler — returns sorted JSON array of all registered
  verb strings; satisfies workstream-flag.md + agent tooling discoverability
- Register ['commands', commandsList] in DECISION_ROUTING_STATIC_CATALOG
- Add golden-policy exemption (SDK-only, no CJS mirror needed)
- check.decision-coverage-plan/verify were already registered; commands was the remaining gap

Closes #3121
2026-05-05 15:02:34 -04:00