Commit Graph

3244 Commits

Author SHA1 Message Date
Tom Boucher
2214394f11 Merge pull request #255 from open-gsd/fix/185-migrate-hub-errors-manifest
refactor(#185): migrate hub, errors, and manifest to canonical src seams
2026-05-24 22:18:33 -04:00
Tom Boucher
c66b542f71 refactor(#185): migrate hub, errors, and manifest to canonical src seams 2026-05-24 22:17:20 -04:00
Tom Boucher
3517f65fa7 Merge pull request #254 from open-gsd/fix/184-migrate-init-composer
refactor(#184): migrate init composer handlers to sdk/src/handlers/init
2026-05-24 22:12:57 -04:00
Tom Boucher
f657ab972e fix(#184): correct runtime alias manifest path after relocation 2026-05-24 22:09:29 -04:00
Tom Boucher
5e0d804d8f fix(#184): preserve query init compatibility while migrating handlers 2026-05-24 22:03:54 -04:00
Tom Boucher
01cd00c080 refactor(#184): migrate init composer handlers to sdk/src/handlers/init 2026-05-24 21:58:44 -04:00
Tom Boucher
5e8e677328 Merge pull request #253 from open-gsd/fix/183-migrate-runtime-name-policy
refactor(#183): migrate runtime name policy module to sdk/src/runtime
2026-05-24 21:53:25 -04:00
Tom Boucher
c5f9d95333 refactor(#183): migrate runtime name policy module to runtime path 2026-05-24 21:52:18 -04:00
Tom Boucher
8442762ca0 Merge pull request #252 from open-gsd/fix/182-migrate-project-root-module
refactor(#182): migrate project-root resolution module to sdk/src/runtime
2026-05-24 21:45:57 -04:00
Tom Boucher
c46dfc4a7f refactor(#182): migrate project-root module to runtime path 2026-05-24 21:38:49 -04:00
Tom Boucher
22e9c1de62 refactor(#181): migrate workstream inventory builder to sdk/src/workstream (#250) 2026-05-24 21:32:53 -04:00
Tom Boucher
59bcdf03b6 refactor(#180): migrate STATE.md Document Module to sdk/src/state (#249)
* refactor(#180): migrate state document module to sdk/src/state

* test(#180): ratchet lint allowlists for state module relocation
2026-05-24 21:06:50 -04:00
Tom Boucher
9aae41f22d refactor(#179): migrate Configuration Module to sdk/src/config (#244)
* refactor(#179): migrate configuration module to sdk/src/config

* chore(#179): add changeset for config module path migration
2026-05-24 20:49:35 -04:00
Tom Boucher
b2a8411e4d fix(#17): cap AskUserQuestion options at 4 across workflows (#243)
* fix(#17): enforce AskUserQuestion 4-option cap across workflows

* chore(changeset): add fixed entry for #17
2026-05-24 20:26:53 -04:00
Tom Boucher
81a4d1c091 fix(#16): renumber canonical phases above 999 on remove (#241) 2026-05-24 20:02:44 -04:00
Tom Boucher
a512bd79d7 fix(#222): enforce research synthesizer write-only summary contract (#240) 2026-05-24 19:19:50 -04:00
Tom Boucher
e95fa8ad7d fix(13): emit raw string scalars for sdk --pick output (#239) 2026-05-24 19:14:05 -04:00
Tom Boucher
247596079f Merge pull request #238 from open-gsd/fix/11-bug-hand-sync-allowlist-lint-misses-cros
fix(11): detect cross-name hand-sync pairs from allowlist
2026-05-24 19:01:14 -04:00
Tom Boucher
5042ec9d4f fix(11): support cross-name hand-sync pair detection 2026-05-24 18:54:34 -04:00
Tom Boucher
08f075e91e Merge pull request #237 from open-gsd/fix/10-bug-codebase-has-6-divergent-semver-comp
fix(10): consolidate semver comparison policy
2026-05-24 18:45:48 -04:00
Tom Boucher
22136ae19d chore(changeset): add fragment for issue 10 semver consolidation 2026-05-24 18:32:50 -04:00
Tom Boucher
5434c8c4cb docs: update inventory surfaces for semver compare module 2026-05-24 18:25:21 -04:00
Tom Boucher
6913dbcdb1 fix(10): centralize semver comparison policy across hooks and changeset 2026-05-24 18:14:56 -04:00
Solvely-Colin
7170120325 Make next Discord changelog release-only 2026-05-24 17:56:28 -04:00
Tom Boucher
fc4cb75819 Merge pull request #236 from open-gsd/fix/critical-release-flow-next-aware
fix(critical): make hotfix + auto-branch next-aware (Phase 3)
2026-05-24 17:35:12 -04:00
Tom Boucher
7d3c9f6a83 fix(critical): make hotfix + auto-branch next-aware (Phase 3)
- hotfix.yml: cherry-pick from origin/next (fallback origin/main) instead
  of hardcoded origin/main. Under the next-branch model day-to-day fixes
  land on next first; main only moves on release back-merges, so the old
  source would miss every fix between releases.

- auto-branch.yml: create issue branches from heads/next (fallback
  heads/main). Contributors should branch from where current work lives.

Phase 3 of the next-branch rollout per docs/adr/230-introduce-next-integration-branch.md.
2026-05-24 17:35:07 -04:00
Tom Boucher
faebb81187 Merge pull request #234 from open-gsd/fix/critical-flags-to-main
fix: bring next-branch automation flag flips to main
2026-05-24 17:28:03 -04:00
Tom Boucher
ff933ee890 chore: enable next-branch automation (Phase 2 flips) (#232)
- auto-backmerge.yml: enable the job (was if: false in Phase 1)
- pr-target-validator.yml: enforce instead of warn-only

These flips are the operational gate for the next-branch model. The
next branch and branch protection were created/applied before this PR.
2026-05-24 17:17:42 -04:00
Tom Boucher
1bc7d61294 chore: introduce next integration branch (Phase 1 — additive) (#231)
Adds:
  - docs/branching.md              — beginner contributor guide
  - docs/adr/XXXX-...md            — ADR (will be renamed with issue#)
  - .github/workflows/auto-backmerge.yml      — disabled in Phase 1
  - .github/workflows/pr-target-validator.yml — warn-only in Phase 1
  - scripts/setup-branch-protection.sh        — idempotent gh api script

Modifies:
  - .github/workflows/branch-naming.yml  — recognize 'next'
  - CONTRIBUTING.md                       — 'Where Do I Open My PR?' section

Phase 1 is additive: nothing operational changes until Phase 2 flips
auto-backmerge.yml's if:false→true, flips pr-target-validator.yml's
WARN_ONLY→false, creates the next branch, and switches the default
branch. See the ADR for the migration plan.
2026-05-24 17:11:31 -04:00
Tom Boucher
cf7e65e18c fix(#224): return byte counts for --pick stdout capture (#226) 2026-05-24 16:34:02 -04:00
Tom Boucher
5f3eb42864 feat(observability): propagate parentTraceId on DispatchEvent — ADR-0174 SDK retirement Phase 1.4 (#178) (#225)
* test(#178): update DispatchEvent factory tests to propagate parentTraceId

P1.3 test 'parentTraceId is always undefined' replaced with four P1.4
contracts: absent → undefined, string → propagated, null → undefined,
non-string → undefined (defensive normalization policy).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(#178): propagate parentTraceId through DispatchEvent factory

Stop ignoring the parentTraceId parameter added as a forward-compat hook
in P1.3. Defensive normalization: only non-null strings are propagated;
null, non-string values, and absent callers all yield undefined, keeping
P1.3 behavior intact for all existing dispatch call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#178): add Hub-level parentTraceId propagation tests

Four new assertions: req.parentTraceId propagates to event, absent →
undefined (P1.3 regression), shared parentTraceId across multiple
dispatches, and unique traceId invariant despite shared parentTraceId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(#178): plumb parentTraceId through Hub dispatch and _notifyLogger

dispatch() now reads req.parentTraceId and passes it to _notifyLogger,
which forwards it to makeDispatchEvent. Backward-compatible: callers
that omit parentTraceId emit events with parentTraceId: undefined,
identical to P1.3 behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#178): add trace correlation end-to-end test

Dispatches a root command then 3 children with parentTraceId=rootTraceId.
Reads the real .gsd-trace.jsonl audit file and verifies: 4 events total,
root has no parentTraceId, all children carry rootTraceId, all traceIds
unique, JS filter returns exactly the 3 children given the root's traceId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#178): document traceId/parentTraceId in audit file

Update Observability section to note that audit events now carry both
traceId and parentTraceId, and explain the correlation filter pattern.
Note that leaf dispatches emit parentTraceId: undefined until the Phase 2
composer wires it automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(#178): add changeset for trace correlation seam

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#178): cover invalid parentTraceId values in DispatchEvent factory

Adds 9 new test cases for UUID v4 validation of parentTraceId:
empty string, whitespace, non-UUID, oversized, UUID v1, missing-hyphen,
extra-char (all dropped to undefined), plus UPPERCASE and lowercase v4
(both propagated). Tests are intentionally red until the implementation
commit that follows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(#178): validate parentTraceId against UUID v4 before propagation

Adds UUID_V4_REGEX constant and isValidParentTraceId() helper to
event.cjs. makeDispatchEvent now silently coerces any parentTraceId that
fails the UUID v4 check (wrong version nibble, wrong variant, missing
hyphens, oversized, empty, etc.) to undefined. No stderr warn is emitted
— the factory remains pure and side-effect-free. Closes the correlation-
poisoning vector identified in the Codex adversarial review of PR #225.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#178): assert Hub silently drops invalid parentTraceId at the seam

Adds two tests to hub-logger-integration.test.cjs:
1. dispatch with 'junk' parentTraceId emits event with parentTraceId===undefined.
2. The logger-failure warn path is NOT triggered — the factory coerces the bad
   value before onEvent is called, confirmed by zero stderr output even when a
   logger that would throw on non-undefined parentTraceId is installed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#178): assert invalid parentTraceId does not poison correlation siblings

Adds one test to trace-correlation.test.cjs: dispatches a root, a valid
child (parentTraceId = rootTraceId), and an invalid child (parentTraceId =
'junk'). Asserts: valid child carries correct parentTraceId, invalid child
has parentTraceId dropped to undefined, filtering by rootTraceId yields
exactly 1 event (the valid child only), and all 3 events have unique
traceIds. Uses an isolated Hub + tmpdir to avoid shared fixture interference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#178): document UUID v4 contract for parentTraceId

Appends one sentence to the Observability audit-trail paragraph in
CONFIGURATION.md: parentTraceId must be canonical UUID v4 (RFC 4122);
values that don't match are silently dropped from audit output. No section
restructuring — single sentence addition only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:32:28 -04:00
Tom Boucher
7c539cb86a docs(227): ADR on input-validation checking semantic shape, not just type (#228)
* docs(227): create ADR for input-validation-shape-not-just-type

Captures the architectural standard that defensive normalization at trust
boundaries must validate both type and semantic shape, with silent
coercion on failure. Concrete cases: parentTraceId UUID v4 fix in
PR #225 and release-version validation in ADR 218.

Closes #227

* docs(227): cross-reference new ADR from ADR 218

Appends a "See also" section at the end of ADR 218 pointing forward to
ADR 227, which generalises the type+semantic-shape validation principle
documented in ADR 218's narrower release-workflow context.

* docs(227): add CONTRIBUTING pointer to new ADR

Adds a "Code Review Lessons → Input validation" section after the
Reviewer Standards block, linking to ADR 227 as the citable reference
for the type+semantic-shape validation standard.
2026-05-24 16:32:01 -04:00
Tom Boucher
2d14eb8873 feat(observability): add DispatchLogger seam — ADR-0174 SDK retirement Phase 1.3 (#177) (#223)
* test(#177): add DispatchEvent factory failing tests

Red tests for makeDispatchEvent shape, traceId UUID v4, uniqueness,
parentTraceId-always-undefined (P1.3), args redaction toggle, ISO 8601
timestamp, and all result variant passthrough.

* feat(#177): introduce DispatchEvent factory

makeDispatchEvent produces an immutable event record per dispatch:
- traceId: crypto.randomUUID() (UUID v4)
- parentTraceId: always undefined (P1.4 wires composer)
- command, result, timestamp (ISO 8601)
- args only included when includeArgs === true (default: omitted)

* test(#177): add arg redaction policy failing tests

Red tests for shouldIncludeArgs (GSD_AUDIT_ARGS env gating) and
redactEvent (strips args from frozen events, preserves all other
fields, returns a new object, never mutates the source).

* feat(#177): introduce arg redaction policy

shouldIncludeArgs(): only GSD_AUDIT_ARGS==='1' opts in; all other
values (unset, '', '0', 'true') default to omitting args.

redactEvent(event): returns a shallow copy of the event, dropping the
args field unless opted in. Never mutates the (frozen) source event.

* test(#177): add DispatchLogger interface failing tests

Red tests covering:
- no-op logger: silent on all events, never throws
- default logger: silent on ok, one flattened JSON line to stderr on error
- default logger: audit file creation + append-only + redaction + config gate
- GSD_AUDIT env var and config.audit.enabled config gate
- GSD_AUDIT_ARGS opt-in for args inclusion
All tests use real fs under os.tmpdir() — no mocked appendFileSync.

* feat(#177): introduce DispatchLogger with default and no-op implementations

createNoOpLogger(): silent on all events — Hub default when no logger injected.
createDefaultLogger({ cwd, config }):
  - Silent on ok result
  - Flattened JSON line to stderr on error: { kind, traceId, ...typedPayload }
  - Append-only audit at .planning/.gsd-trace.jsonl when GSD_AUDIT=1 or config.audit.enabled
  - Args redacted by default; GSD_AUDIT_ARGS=1 opts in
  - Logger errors caught internally; never break dispatch callers

* test(#177): add Hub+logger integration failing tests

Red tests verifying:
- onEvent called exactly once per dispatch (ok, error, handler-throw, unknown)
- DispatchEvent shape: traceId uniqueness, command, result.kind, parentTraceId
- Logger errors contained (dispatch still returns Result, warn line to stderr)
- Hub defaults to no-op when no logger injected
- End-to-end with createDefaultLogger: silent on success, stderr on error, audit file

* feat(#177): wire DispatchLogger into CommandRoutingHub

Add optional logger param to createHub({ ..., logger }).
Defaults to createNoOpLogger() — silent, no behaviour change for callers
that don't inject a logger.

After every dispatch (success and error):
- Normalises HubResult { ok } to DispatchEvent { kind: 'ok'|error-kind }
- Calls makeDispatchEvent({ command, args, result }) to mint the event
- Calls logger.onEvent(event) exactly once
- Wraps in try/catch: logger errors emit { level:'warn', source:'DispatchLogger' }
  to stderr but never propagate to dispatch callers

* chore(#177): gitignore .planning/.gsd-trace.jsonl audit file

The audit trail is local-only, append-only, and must never be committed.
Slotted under the existing "Local scratch + Claude-test artifacts" block.

* docs(#177): document GSD_AUDIT, GSD_AUDIT_ARGS, config.audit.enabled

New ## Observability section at end of CONFIGURATION.md covering:
- Default silent/stderr behaviour overview
- Stderr error JSON format
- Audit file opt-in (env var and config key)
- Args redaction policy and GSD_AUDIT_ARGS opt-in

Also slots GSD_AUDIT and GSD_AUDIT_ARGS into the existing
## Environment Variables table (alphabetical order).

* chore(#177): add changeset for observability seam

type: Added — new DispatchLogger seam with default silent/stderr/audit behaviour.
2026-05-24 15:22:36 -04:00
Tom Boucher
4bc3653578 fix(#167): support query meta-command in gsd-tools (#202)
* fix(#167): support query meta-command in gsd-tools

* chore(#167): add changeset for query meta-command fix

* fix(#167): pin claude runtime in local-agent regression tests

* test(#3751): stabilize local-agent CI assertions
2026-05-24 14:37:10 -04:00
Solvely-Colin
3d82761f9a Add Discord changelog workflow 2026-05-24 14:33:49 -04:00
Tom Boucher
d011a6fac2 refactor(hub): tighten Result<T> typed payload — ADR-0174 SDK retirement Phase 1.2 (#176) (#221)
* refactor(hub): tighten Result<T> to typed-payload-per-kind discriminated union (#176)

Each Hub error variant now carries only its own typed payload. The generic
`errorKind` field is renamed to `kind`; `message`/`details` escape hatches
are removed from Hub-emitted errors. Factory functions (makeUnknownCommand,
makeInvalidArgs, makeHandlerRefusal, makeHandlerFailure) are exported and
used in phase-command-router.cjs. Callers switch on `result.kind`.

Part of ADR-0174 P1.2.

<!-- docs-exempt: no docs/ changes; API is internal to Hub callers -->

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(hub): act on P1.2 review findings (#176)

Addresses 4 review findings on PR #221:
- Hub now runtime-validates ok:false variants against the typed shape
  and coerces malformed returns to HandlerFailure with a contract-
  violation message (codex finding #1, code-review finding #1)
- catch path now preserves the original throwable for non-Error
  throws via an Error wrapper with .thrown attached (codex finding #2)
- All 4 factory returns are Object.freeze'd (review finding #9)
- makeHandlerFailure validates cause is Error; non-Error causes are
  wrapped with .thrown attached (review finding #10)

Tests added for each finding (TDD red → green).

Refs #176. Part of #174.

* fix(docs-lint): add docs-exempt markers to both P1.2 changeset fragments

Both `176-typed-result-discriminated-union.md` and `176-hub-p1.2-review-findings.md`
carry `type: Changed` which triggers the docs-required lint. Neither fragment had
a `<!-- docs-exempt: <reason> -->` marker, causing `docs-lint` to fail with
`FAIL_DOCS_MISSING`. Added the per-fragment exemption marker to both (the repo has
no `no-docs` label). This is a purely internal SDK refactor (ADR-0174 P1.2) with
no public docs surface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:20:28 -04:00
Tom Boucher
5b3646d6c8 fix(#213): support antigravity 2.x config directory split (#217)
* fix(#213): support antigravity 2.x config directory split

* fix(#213): harden antigravity tests for windows parity
2026-05-24 14:17:03 -04:00
Tom Boucher
aaba233f83 fix(#170): migrate workflow fallback hints to @opengsd package (#204)
* fix(#170): update workflow fallback install hint package

* chore(#170): add changeset for workflow fallback hint migration

* fix(#170): mark workflow-hint test as structural text contract
2026-05-24 14:16:30 -04:00
Tom Boucher
0dc3fd604c fix(#166): omit bash.exe wrapper for windows claude sh hooks (#203)
* fix(#166): omit bash.exe wrapper for windows claude sh hooks

* chore(#166): add changeset for windows claude sh hook fix

* fix(#166): reset exitCode in sdk bridge integration test
2026-05-24 14:16:16 -04:00
Tom Boucher
7bf06f7a68 fix(#33): add regression test for model_profile adaptive option (#149)
* fix(#33): add regression test asserting adaptive is reachable in settings.md UI

The schema (sdk/shared/model-catalog.json) defines 5 model_profile values
(quality, balanced, budget, adaptive, inherit). The settings.md AskUserQuestion
previously omitted `adaptive`; the fix (two-question split, #3784) is already
applied. This test locks the schema/UI contract so the gap cannot regress.

Closes #33

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(#33): add changeset fragment

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#33): address review feedback — presence not count, drop structural caps

Replace exact-count assertion (profiles.length === 5) with presence
assertion (profiles.includes('adaptive')). Remove the options-per-question
≤4 cap guard — that is a structural implementation detail, not a
behavioural regression guard for issue #33.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:30:00 -04:00
Tom Boucher
21e3ce53c0 feat(hub): drop mode/sdkLoader/SdkDispatchFailed (#175) (#220)
* feat(hub): drop mode/sdkLoader/SdkDispatchFailed (#175)

The Command Routing Hub no longer carries dual-runtime selection.
Removes `mode` and `sdkLoader` constructor parameters and the
`SdkDispatchFailed` and `SdkLoadFailed` errorKind values. The Hub
now routes exclusively through the CJS registry / handler resolution
path. ERROR_KINDS enum shrinks from 6 to 4 values.

phase-command-router.cjs updated to construct the Hub without the
removed params (removes tryLoadSdk, getExecuteForCjs, sdkLoader fn,
mode variable, and the post-dispatch SDK output branch). The 7
remaining family routers (init, phases, roadmap, state, validate,
verify, cjs-command-router-adapter) do not use createHub directly
and require no changes.

The CJS↔SDK bridge (bin/lib/cjs-sdk-bridge.cjs) is unchanged and
remains separately invokable; its removal is tracked in Phase 4 (#190).

ADR-0012 is no longer amended in this PR — the decision is captured
in ADR-0174 (which supersedes ADR-0012 entirely as part of the
SDK-retirement migration). Amending a superseded ADR would be
redundant noise.

Tests:
- Added assertions that Hub rejects/ignores `mode` and `sdkLoader`
- Removed obsolete mode-selection branching tests
- 57/57 local tests pass

Closes #175.
Part of #174 (ADR-0174).

* chore(changeset): add docs-exempt marker (#175)

P1.1's CommandRoutingHub work has no docs/ touchpoints — the
architectural decision is captured in ADR-0174 (merged via PR #198).
Per-phase ADR amendments would create noise; the SDK-retirement
migration's docs land in Phase 6 PRs (#193-#196) once the relevant
state is removed.

Adds the standard <!-- docs-exempt: <reason> --> marker inside the
changeset fragment so lint:docs accepts the PR without forcing a
docs/ touch that would be redundant.

Refs #175. Part of #174 (ADR-0174).
2026-05-24 13:04:11 -04:00
Tom Boucher
6fc46db49a fix(release): reject leading-zero versions and pre-check npm before publish (#219)
* fix(release): reject leading-zero versions and pre-check npm before publish

The validate-version job used ^[0-9]+\.[0-9]+\.0$ which accepted leading
zeros (e.g. 1.01.0). npm version silently normalises such inputs to their
canonical semver form (1.1.0), creating divergent state across npm, git
tags, GitHub releases, and release branches — leaving orphaned artefacts
that require manual surgery to clean up.

Two changes to the validate-version job only:

1. Replace the format regex with ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.0$
   so any segment with a leading zero is rejected in under 5 seconds with
   an error message that includes the offending value.

2. Add a "Reject already-published versions" step that calls
   `npm view $pkg@$VERSION` for both packages before any branch, install,
   or build work begins. Duplicate-version requests now fail fast instead
   of burning ~10 minutes before dying at the dry-run publish step.

Adds ADR-0175 documenting the incident, the decisions, and the recovery
runbook for the orphaned v1.01.0 / v1.03.0 artefacts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(adr): rename ADR to issue-number format per CONTRIBUTING.md policy

Renames docs/adr/0175-release-version-validation.md to
docs/adr/218-release-version-validation.md to match the issue-number
prefix convention required by CONTRIBUTING.md (section: Proposing an
ADR or PRD). Issue #218 was opened to track this CI hardening work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 11:21:43 -04:00
Tom Boucher
4a03ada279 docs(#209): refresh README continuity and audit messaging (#210) 2026-05-24 01:53:18 -04:00
Tom Boucher
fa4bba478b chore(ci): adopt npm Trusted Publishers (OIDC) for release workflow (#207) (#208)
Replace long-lived GETSHITDONEREDUXNPMTOKEN secret with short-lived
OIDC tokens via npm Trusted Publishers. Remove NODE_AUTH_TOKEN env
blocks from rc and finalize jobs; add npm install -g npm@latest steps
to guarantee CLI >= 11.5.1 required for OIDC exchange. Trusted
Publisher already configured on npmjs.com for this workflow/environment.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:23:25 -04:00
Tom Boucher
b00cae7699 fix(ci): reference NPM publish secret as GETSHITDONEREDUXNPMTOKEN (#205) (#206)
release-finalize, hotfix, and release-sdk workflows referenced
\`secrets.NPM_TOKEN\`, but the actual environment secret is named
\`GETSHITDONEREDUXNPMTOKEN\`. Resolves the ENEEDAUTH failure on
run 26351934927.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 00:55:16 -04:00
Tom Boucher
39d8502752 fix(ci): raise Node heap to 6144 MB in release finalize job (#200)
c8 coverage aggregation OOMs at ~4085 MB under Node's default 4 GB cap
after all 466 tests pass. Add NODE_OPTIONS: --max-old-space-size=6144
to the Install and test step in the finalize job, matching the existing
precedent in test.yml line 354.

Fixes #199

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:09:45 -04:00
Tom Boucher
6e31630f2a docs(adr): ADR-0174 — retire @opengsd/gsd-sdk package boundary (#198)
* docs(adr): retire @opengsd/gsd-sdk package boundary

Authors ADR-XXXX (Single-runtime collapse onto src/ TypeScript)
and marks ADR-0005, 0007, 0012, 3524 as Superseded.

The dual-runtime SDK design accumulated ~120 files of scaffolding
(worker pool, generators, parity tests, transition shims, two
release pipelines) for a feature set CJS provides in-process.
This ADR records the decision to collapse onto a single TS source
tree in src/ within get-shit-done-cc.

Implementation tracked separately in the umbrella tracking issue
and per-phase sub-issues (referenced in the PR body).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(adr): assign ADR-0174 number and finalize supersedes references

Replaces XXXX placeholder with real ADR number (0174 = umbrella
tracking issue #174 per project convention, zero-padded to 4 digits).
Updates Status lines in ADR-0005, ADR-0007, ADR-0012, ADR-3524 to
reference ADR-0174. Adds README.md index entry for ADR-0174 and
marks the four superseded ADRs accordingly.

Refs #174.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:38:35 -04:00
Tom Boucher
d022dab470 fix(#105): skip strategy-branch auto-switch when use_worktrees=false (#150)
* fix(#105): skip strategy branch auto-switch when use_worktrees is false

When workflow.use_worktrees is false, the primary checkout is shared or
pinned to a base branch. Auto-switching HEAD in that mode silently moves
the shared checkout and allows concurrent commits to land on the wrong
branch. ensureStrategyBranch now returns ok:true with an explicit skip
reason instead of calling git checkout in this configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#105): replace source-grep tests with behavioral coverage

Remove the root tests/bug-105-*.test.cjs file which used source-text
structural assertions (reading commit.ts as a string) — tests that pass
even when the runtime behavior is broken. Replace with a Vitest test at
sdk/src/query/commit.bug-105.test.ts that invokes ensureStrategyBranch
directly with use_worktrees:false and asserts the guard fires before any
git invocation, and that the guard does NOT fire when use_worktrees is
true or absent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(#105): add changeset fragment

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(#105): address review — type WorkflowConfig.use_worktrees, accept "false" string, stub git in tests

- Add `use_worktrees?: boolean | string` to WorkflowConfig interface with doc comment
- Remove `(config.workflow as unknown as Record<string, unknown>)` double cast; use typed `config.workflow?.use_worktrees`
- Accept string `"false"` alongside boolean `false` via `isExplicitlyFalse` guard (YAML/JSON parser resilience)
- Add `vi.mock('node:child_process')` stub in commit.bug-105.test.ts; assert no-call on skip-path tests
- Add new test case for string `"false"` coercion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:10:25 -04:00
Tom Boucher
3169d5cda6 fix(ci): reduce Windows test concurrency 4→2 to prevent synckit worker exhaustion on Node 24 (#173)
Under Node 24 on Windows, running node --test with --test-concurrency=4
causes 4 concurrent gsd-tools subprocesses to each spawn a synckit
worker_threads worker for the SDK bridge. The 4 workers simultaneously
contend on SharedArrayBuffer + Atomics.wait under Windows Defender
scanning and NTFS latency, triggering OS-level resource exhaustion that
kills worker processes with empty stderr before any output is flushed.

The symptom: intermittent exit 1 with 0 test failures, varying affected
test files per run, all sharing the pattern of invoking gsd-tools as a
subprocess. Empty stderr distinguishes OS crash from gsd-tools app error
(the error() path writes to stderr before exiting).

Fix: platform-aware concurrency default — 2 on win32, 4 on Linux/macOS.
The existing TEST_CONCURRENCY env-var override is preserved. Also adds
a [stderr: (empty) exit:N] diagnostic note in helpers.cjs runGsdTools
catch block so future empty-stderr crashes are visible in CI logs.

Fixes gsd-build/get-shit-done#3869

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:10:06 -04:00
Tom Boucher
6177e3a5f5 fix(4): retire cooperating-sibling for phase.*, introduce generator + I/O adapter, fix cmdPhaseComplete (#154)
* test(4): reproduce non-idempotent phase complete + unclamped percent in CJS CLI

RED regression tests for issue #4:
- T1: double invocation of cmdPhaseComplete double-increments **Completed Phases:**
  in STATE.md body (blind parseInt+1 instead of deriving from ROADMAP)
- T2: progress percent can exceed 100% when Completed Phases > Total Phases

The CJS path (bin/lib/phase.cjs:cmdPhaseComplete) has the bug; the SDK path
(phase-lifecycle.ts:phaseComplete, fixed in ~PR#3520) already derives
completed_phases from ROADMAP Complete-row count, making it idempotent.

References:
- Issue #4 (open-gsd/get-shit-done-redux)
- ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
- /tmp/adr-3524-review-findings.md (architectural justification)

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

* chore(4): add sdk/scripts/gen-phase-lifecycle-policy.mjs generator + freshness check placeholder

Generates phase-lifecycle-policy.generated.cjs from sdk/src/query/phase-lifecycle-policy.ts.
All functions in phase-lifecycle-policy.ts are pure transforms (no I/O), directly
serializable via Function.prototype.toString(). The GSDError dependency is replaced
with a lightweight stub that throws plain Error objects — CJS callers that need
process.exit(1) behavior catch these and delegate to error().

This is the "I/O adapter pattern" from ADR-3524 Section 4 applied to pure helpers.

References:
- ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
- /tmp/adr-3524-review-findings.md (architectural justification)
- Issue #4 (open-gsd/get-shit-done-redux)

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

* chore(4): add sdk/scripts/gen-phase.mjs generator

Generates phase.generated.cjs from sdk/src/query/phase.ts.
Only pure helpers (isCanonicalPlanFile, describeNonCanonicalPlans) are generated;
async query handlers (findPhase, phasePlanIndex) are I/O-bound and remain per-side
per ADR-3524 Section 4.

References:
- ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
- /tmp/adr-3524-review-findings.md (architectural justification)
- Issue #4 (open-gsd/get-shit-done-redux)

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

* chore(4): add sdk/scripts/gen-phase-lifecycle.mjs + core idempotency fix logic

Generates phase-lifecycle.generated.cjs providing two pure functions that are the
root-cause fix for issue #4:

1. deriveProgressFromRoadmap(roadmapContent): counts Complete rows in ROADMAP
   progress table — makes completed_phases idempotent (derived from ground truth
   instead of blind +1). Direct transcription of the SDK's "Root cause 1 fix"
   block in phase-lifecycle.ts (~line 1644).

2. clampPercent(completed, total): percent capped at 100 — prevents >100% progress
   when Completed Phases exceeds Total Phases.

Design note: the full phase lifecycle mutations (add, insert, remove, complete) are
inherently async and I/O-bound. Per ADR-3524 Section 4 ("I/O stays per-side"), those
are NOT generated. Only the pure-computation kernel is extracted, following the
I/O adapter pattern: pure logic shared; each side (CJS sync, SDK async) supplies
its own I/O adapter.

The pure functions are defined in the generator as real JS functions and serialized
via Function.prototype.toString() — same technique as gen-project-root.mjs — rather
than embedded in template literals (which would require double-escaping all regex
backslashes).

References:
- ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
- /tmp/adr-3524-review-findings.md (architectural justification)
- Issue #4 (open-gsd/get-shit-done-redux)

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

* chore(4): emit phase.generated.cjs, phase-lifecycle.generated.cjs, phase-lifecycle-policy.generated.cjs

Three generated CJS artifacts from their respective generator scripts:

- phase.generated.cjs (1.7K): isCanonicalPlanFile + describeNonCanonicalPlans
  from sdk/src/query/phase.ts
- phase-lifecycle.generated.cjs (3.6K): deriveProgressFromRoadmap + clampPercent
  — the idempotency+clamp fix for issue #4
- phase-lifecycle-policy.generated.cjs (7.0K): 14 pure phase naming/directory
  helpers from sdk/src/query/phase-lifecycle-policy.ts

Run to regenerate:
  node sdk/scripts/gen-phase.mjs
  node sdk/scripts/gen-phase-lifecycle.mjs
  node sdk/scripts/gen-phase-lifecycle-policy.mjs

References:
- ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
- Issue #4 (open-gsd/get-shit-done-redux)

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

* fix(4): migrate bin/lib/phase.cjs cmdPhaseComplete to use generated helpers

Replace the blind-increment + unclamped percent bug in cmdPhaseComplete with
the idempotent ROADMAP-derived approach: read freshly-updated ROADMAP, call
deriveProgressFromRoadmap() from phase-lifecycle.generated.cjs, fall back to
existing value when ROADMAP is unavailable. clampPercent() prevents >100%.

Root cause fix for issue #4: the original parseInt(completedRaw) + 1 on every
call made phase complete non-idempotent; the missing Math.min(100, ...) clamp
allowed Progress to exceed 100%.

I/O adapter pattern (ADR-3524 §4): pure computation in generated module;
CJS supplies sync readFileSync; SDK supplies async readFile. Same logic, two adapters.

Closes: Tests in 4-phase-complete-cjs-regression.test.cjs go GREEN.

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

* chore(4): add freshness checks + npm scripts for phase generated artifacts (D4/D6)

Add check-phase-fresh.mjs, check-phase-lifecycle-fresh.mjs, and
check-phase-lifecycle-policy-fresh.mjs (same pattern as check-project-root-fresh.mjs:
import buildXCjs() from the generator, regenerate in-memory, byte-compare to committed
file, exit 1 if stale).

Add gen:phase, check:phase-fresh, gen:phase-lifecycle, check:phase-lifecycle-fresh,
gen:phase-lifecycle-policy, check:phase-lifecycle-policy-fresh to sdk/package.json.
Note: gen:phase-lifecycle / check:phase-lifecycle-fresh do not require 'npm run build'
because the generator defines pure functions directly rather than importing dist.

Update shared-module-handsync-allowlist.json: reclassify phase.cjs justification to
reflect that it now consumes phase-lifecycle.generated.cjs for cmdPhaseComplete. The
*.generated.cjs files are excluded by the lint scanner (excludes *.generated.cjs) so
no new allowlist entries are required for the generated artifacts.

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

* ci(4): add freshness-check CI steps for phase, phase-lifecycle, phase-lifecycle-policy

Add three drift-check steps to .github/workflows/test.yml following the same
pattern as the existing freshness checks (ubuntu-latest + node 24 only):
  - SDK generated phase artifact drift check
  - SDK generated phase-lifecycle artifact drift check
  - SDK generated phase-lifecycle-policy artifact drift check

These guard against editors modifying the generated *.cjs files directly.
They run check-phase-fresh.mjs, check-phase-lifecycle-fresh.mjs, and
check-phase-lifecycle-policy-fresh.mjs respectively (added in D4).

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

* docs(4): amend ADR-3524 — phase * I/O adapter pattern for issue #4 (D8)

Append a 2026-05-23 amendment to docs/adr/3524-cjs-sdk-hard-seam.md documenting
the Phase * cooperating-sibling retirement: three new generator scripts extract
pure-computation helpers from phase.ts / phase-lifecycle.ts / phase-lifecycle-policy.ts,
cmdPhaseComplete migrates to deriveProgressFromRoadmap + clampPercent for idempotency,
freshness checks + CI steps added.

Clarifies what is NOT generated (async I/O-bound mutation handlers stay per-side per
Section 4) and notes open drift bugs #6 and #26 for traceability.

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

* chore(4): add changeset fragment for cmdPhaseComplete fix

Refs #4

* fix(154): use canonical /gsd:plan-phase form in phase-lifecycle-policy.ts

Replaces the retired /gsd-plan-phase slash command reference with the
canonical colon-namespaced /gsd:plan-phase in the TS source template
string that feeds the generated CJS roadmap entry helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(154): regenerate phase-lifecycle-policy.generated.cjs after slash-namespace fix

Regenerated via node sdk/scripts/gen-phase-lifecycle-policy.mjs after
fixing /gsd-plan-phase → /gsd:plan-phase in the TS source. Generated
file now contains the canonical colon form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(154): cross-platform frontmatter regex anchor in 4-phase-complete-cjs-regression.test.cjs

Replaces /^---\n/ with /^---\r?\n/ so the frontmatter extraction helper
in the regression test tolerates Windows CRLF line endings (autocrlf=true
checkout leaves \r before \n, causing /^---\n/ to never match).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(154): add new generated CJS modules to INVENTORY.md and regenerate manifest

Adds three missing rows to the CLI Modules table:
  - phase-lifecycle-policy.generated.cjs
  - phase-lifecycle.generated.cjs
  - phase.generated.cjs

Bumps the headline count from 74 to 77 to match the filesystem.
Also regenerates docs/INVENTORY-MANIFEST.json via
node scripts/gen-inventory-manifest.cjs --write.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(154): regenerate phase-lifecycle-policy.generated.cjs with hyphen form

Root cause: commit 6cd701f4 regenerated the CJS artifact but at that point
sdk/dist/query/phase-lifecycle-policy.js already had the correct /gsd-plan-phase
(hyphen) form while sdk/src/query/phase-lifecycle-policy.ts still had /gsd:plan-phase
(colon). The generator uses Function.prototype.toString() on the compiled dist, so
the CJS picked up the wrong string from the stale TS source that was compiled into
dist at some earlier point.

Fix: correct the TS source to /gsd-plan-phase and re-run gen-phase-lifecycle-policy.mjs
so that buildPhaseRoadmapEntry in the CJS emits the hyphen form, satisfying the
bug-3584-runtime-slash-emitters.test.cjs assertion at line 179.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(154): restore /gsd:plan-phase canonical form in phase-lifecycle-policy

Commit 0c3a9c75 incorrectly reverted the slash-namespace fix by misreading
sdk/dist/ (a build artifact in hyphen form for non-Claude runtimes) as the
authoritative source. The canonical form for Claude-facing source is
/gsd:plan-phase (colon-namespaced).

Fix: revert TS source back to /gsd:plan-phase, rebuild dist, regenerate
phase-lifecycle-policy.generated.cjs.

Fixes bug-2543-gsd-slash-namespace test failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(154): update INVENTORY.md CLI Modules count to 79 after rebase onto main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(154): use hyphen form /gsd-plan-phase in persisted phase section template

- sdk/src/query/phase-lifecycle-policy.ts: use /gsd-plan-phase (routable
  hyphen form) in the phase scaffold template that gets persisted to
  ROADMAP.md; bug-3584 requires persisted artifacts use the hyphen form
- docs/INVENTORY.md: add missing runtime-name-policy.cjs row in CLI
  Modules table
- tests/4-phase-complete-cjs-regression.test.cjs: add maxRetries/retryDelay
  to rmSync calls to satisfy Windows parity ratchet (baseline was 95)
- Regenerate phase-lifecycle-policy.generated.cjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 21:47:11 -04:00