* feat(3530): STATE.md Document Module via generator (Phase 1 of #3524)
Phase 1 of the CJS↔SDK hard-seam migration (parent #3524).
Converts the hand-synced state-document.cjs/state-document.ts pair
into a generator-driven seam, modeled on the existing
command-aliases.generated.* precedent.
What landed:
- sdk/src/query/state-document.ts is the source of truth.
- sdk/scripts/gen-state-document.ts emits
get-shit-done/bin/lib/state-document.generated.cjs from the
compiled SDK dist via Function.prototype.toString() inspection
for the 7 public exports and 3 internal helpers.
- sdk/scripts/check-state-document-fresh.mjs is the CI freshness
gate; pre-commit hook also runs it when relevant files change.
- get-shit-done/bin/lib/state-document.cjs is reduced to a one-line
re-export from state-document.generated.cjs so existing callers
(state.cjs, workstream-inventory.cjs, init.cjs) need no changes.
- New CI step in .github/workflows/test.yml after the existing alias
drift check.
- sdk/package.json: gen:state-document, check:state-document-fresh
scripts. tsx added as devDep.
- Root package.json: proxy script for the freshness check.
- CONTEXT.md: one-sentence amendment on STATE.md Document Module
recording the source-of-truth file path.
Tests:
- sdk/src/query/state-document.test.ts: 34 vitest fixtures across
the 7 public exports (TDD pinning safety net).
- tests/state-document-generator.test.cjs: 31 node:test parity
assertions comparing SDK source vs generated CJS for every
fixture.
- Full suite: 9177/9177 pass (baseline was 9146; +31 new tests).
One subtle behavior change worth flagging: the old hand-written
state-document.cjs used String(str) coercion inside escapeRegex,
which the SDK source does not. The generator faithfully matches
the SDK (the source of truth per ADR-3524), so the new CJS no
longer coerces non-string input to string before regex-escaping.
No current caller passes non-string input, so no observable
regression in the test suite. Flagged in the PR body for
reviewers.
Closes#3530.
* fix(3530): address state-document review findings
\s* after **Plans:** matches newlines, causing [^\n]+ to consume the first
plan checkbox when the **Plans:** field has no value on the same line.
Additionally, the lazy [\s\S]*? could cross section boundaries when the
current section had no **Plans:** value, corrupting a later section.
Fix 1: replace \s* with [ \t]* to restrict post-colon match to horizontal
whitespace only.
Fix 2: replace [\s\S]*? with (?:(?!\n#{2,4})[\s\S])*? to prevent the
pattern from crossing into a new section heading.
Closes#2728
PR #2386 v1 installed the published @gsd-build/sdk from npm, which ships an
older version that lacks query handlers needed by current workflows. Every
GSD release would drift further from what the installer put on PATH.
This commit rewires installSdkIfNeeded() to build from the in-repo sdk/
source tree instead:
1. cd sdk && npm install (build-time deps incl. tsc)
2. npm run build (tsc → sdk/dist/)
3. npm install -g . (global install; gsd-sdk on PATH)
Each step is a hard gate — failures warn loudly and point users at the
manual equivalent command. No more silent drift between installed SDK and
the rest of the GSD system.
Root package.json `files` now ships sdk/src, sdk/prompts, sdk/package.json,
sdk/package-lock.json, and sdk/tsconfig.json so npm-registry installs also
carry the source tree needed to build gsd-sdk locally.
Also fixes a blocking tsc error in sdk/src/event-stream.ts:313 — the cast
to `Array<{ type: string; [key: string]: unknown }>` needed a double-cast
via `unknown` because BetaContentBlock's variants don't carry an index
signature. Runtime-neutral type-widening; sdk vitest suite unchanged
(1256 passing; the lone failure is a pre-existing integration test that
requires external API access).
Updates the #1657/#2385 regression test to assert the new build-from-source
path (path.resolve(__dirname, '..', 'sdk') + `npm run build` + `npm install
-g .`) plus a new assertion that root package.json files array ships sdk
source.
Refs #2385
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>