fix(6): retire validate.ts/verify.cjs cooperating-sibling, fix W007/phaseVariants/W006 drift via generator (#156)

* test(6): reproduce W007 + phaseVariants + W006 drift between CJS verify and SDK validate

Adds tests/6-validate-cjs-drift-regression.test.cjs with 5 RED tests covering the
three drift items from issue #6 between verify.cjs (Check 8) and validate.ts (Check 8):

  1. W007 activeDiskPhases — verify.cjs uses diskPhases (includes archived) for W007;
     archived phase "1" absent from current ROADMAP fires false W007.
     validate.ts: activeDiskPhases (active phasesDir only) correctly excludes archives.

  2. phaseVariants() normalization — ROADMAP says "01A", disk has "1A-foo".
     verify.cjs parseInt("01A")=1 → padded "01" (drops letter suffix) → miss.
     validate.ts phaseVariants("01A") = {"01A","1A","01A"} → "1A" matched.
     Both W006 and W007 fire as false positives in verify.cjs.

  3. W006 letter-suffix padding mismatch — ROADMAP says "3B", disk has "03B-foo".
     verify.cjs parseInt("3B")=3 → padded "03" (drops "B") → diskPhases.has("03B") missed.
     W006 and W007 fire as false positives.

All 5 tests RED on origin/main. Will turn GREEN after generator + verify.cjs migration.

References:
  - Issue #6 (open-gsd/get-shit-done-redux) — maintainer acceptance criteria:
    "Port all three items to verify.cjs; add parity tests confirming identical output
    for all three cases on both paths"
  - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
  - PR #154 (issue #4) — precedent for the generator pattern

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

* chore(6): add sdk/scripts/gen-validate.mjs generator

Extracts phaseVariants() from sdk/dist/query/validate.js via brace-balanced
source-text parsing (phaseVariants is a closure inside validateHealth, not a
module export, so Function.prototype.toString() is unavailable).

Emits get-shit-done/bin/lib/validate.generated.cjs with three pure helpers:
  - phaseVariants(phase): normalized Set of padded/unpadded/letter-suffix variants
  - buildRoadmapPhaseVariants(content): {roadmapPhases, roadmapPhaseVariants}
  - buildNotStartedPhaseVariants(content): Set of unchecked-phase variants

These three helpers directly address the three drift items in issue #6.
Follows the gen-phase-lifecycle-policy.mjs extraction pattern from PR #154.

References:
  - Issue #6 (open-gsd/get-shit-done-redux)
  - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
  - PR #154 (issue #4) — generator pattern precedent

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

* chore(6): add sdk/scripts/check-validate-fresh.mjs freshness check

Mirrors check-phase-lifecycle-policy-fresh.mjs from PR #154: imports
buildValidateCjs() directly, regenerates in-memory, and diffs against the
committed validate.generated.cjs. Exits 1 if stale (CI gate).

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

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

* chore(6): emit validate.generated.cjs from validate.ts

Generated by: node sdk/scripts/gen-validate.mjs

Exports three pure helpers extracted from sdk/src/query/validate.ts Check 8:
  - phaseVariants(phase): Set of normalized variants {"01A","1A"} etc.
  - buildRoadmapPhaseVariants(content): {roadmapPhases, roadmapPhaseVariants}
  - buildNotStartedPhaseVariants(content): Set of unchecked-phase variants

Freshness check: node sdk/scripts/check-validate-fresh.mjs → FRESH

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

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

* fix(6): migrate verify.cjs to consume validate.generated.cjs helpers (GREEN)

Check 8 in verify.cjs now uses three generated helpers from validate.generated.cjs:

  1. buildRoadmapPhaseVariants(roadmapContent) — replaces hand-rolled roadmapPhases
     Set. Produces both roadmapPhases (raw, for W006 message) and roadmapPhaseVariants
     (all variants, for W007 membership check). Fixes false W007 for letter-suffix
     phases with padding mismatch.

  2. activeDiskPhases — now uses collectDiskPhases() WITHOUT forEachArchivedPhaseToken.
     W007 iterates activeDiskPhases, not diskPhases, so archived phases absent from
     current ROADMAP no longer trigger false W007.

  3. buildNotStartedPhaseVariants(roadmapContent) — replaces raw+parseInt-padded
     notStartedPhases population. Uses phaseVariants() expansion so zero-padded
     letter-suffix unchecked entries (e.g. "03B") correctly suppress W006 for
     their un-padded counterpart ("3B") and vice versa.

  4. phaseVariants() in W006 loop — replaces parseInt-padded disk-existence check.
     "3B" now matches disk dir "03B-foo" via variant expansion.

Also updates test fixture for drift item 1 to use two milestone archives (v1.0 + v1.1),
accurately reproducing the scenario where forEachArchivedPhaseToken walks ALL archives
while getActiveMilestoneArchiveDir returns only the most recent one.

All 5 tests GREEN. Confirmed RED on pre-fix code (git stash test).

References:
  - Issue #6 (open-gsd/get-shit-done-redux) — maintainer acceptance criteria:
    "Port all three items to verify.cjs; add parity tests confirming identical output"
  - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md)
  - PR #154 (issue #4) — generator pattern precedent

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

* ci(6): wire validate freshness check into test workflow

Adds 'SDK generated validate artifact drift check' step to .github/workflows/test.yml,
mirroring the pattern used by all PR #154 generator freshness checks.
Runs on ubuntu-latest/node-24 only (same as other artifact drift checks).

Placement: after workstream-name-policy check, before Shared Module hand-sync drift check.

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

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

* chore(6): wire gen:validate into sdk/package.json, root package.json, and allowlist

sdk/package.json: adds gen:validate and check:validate-fresh npm scripts.
package.json: adds check:validate-fresh script (mirrors other check:*-fresh entries).
scripts/shared-module-handsync-allowlist.json: updates verify.cjs justification to
  note that Check 8 W006/W007 helpers are now generated from validate.ts via
  gen-validate.mjs (issue #6), with freshness check at check-validate-fresh.mjs.

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

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

* docs(6): amend ADR-3524 — validate.ts now uses generator pattern

Adds 2026-05-23 amendment section to docs/adr/3524-cjs-sdk-hard-seam.md documenting:
  - Generator/artifact/freshness-check/CI paths
  - Three drift items resolved (W007 activeDiskPhases, phaseVariants normalization,
    W006 unchecked-phase variant skip)
  - phaseVariants extraction technique (brace-balanced source-text parsing)
  - Parity test coverage (5 tests, RED→GREEN)
  - Allowlist classification preserved (cooperating-sibling)

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

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

* chore(6): add changeset fragment for validate.ts/verify.cjs generator migration

Touches get-shit-done/bin/lib/validate.generated.cjs and verify.cjs which
match USER_FACING_PREFIXES. Required by the fix-template checklist + the
changeset-lint CI workflow.

Refs #6 #156

* docs(6): register validate.generated.cjs in INVENTORY + manifest

INVENTORY parity test demanded a row for the new generated CJS surface
and a matching entry in INVENTORY-MANIFEST.json. Headline count bumped
from 74 → 75.

Refs #6

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-23 15:51:34 -04:00
committed by GitHub
parent c439890e26
commit 5414da2ce5
13 changed files with 693 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
{
"generated": "2026-05-21",
"generated": "2026-05-23",
"families": {
"agents": [
"gsd-advisor-researcher",
@@ -325,6 +325,7 @@
"template.cjs",
"uat.cjs",
"validate-command-router.cjs",
"validate.generated.cjs",
"verify-command-router.cjs",
"verify.cjs",
"workstream-inventory-builder.generated.cjs",

View File

@@ -361,7 +361,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
---
## CLI Modules (74 shipped)
## CLI Modules (75 shipped)
Full listing: `get-shit-done/bin/lib/*.cjs`.
@@ -433,6 +433,7 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
| `template.cjs` | Template selection and filling with variable substitution |
| `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support |
| `validate-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools validate` |
| `validate.generated.cjs` | GENERATED — CJS artifact emitted from `sdk/src/query/validate.ts` via `sdk/scripts/gen-validate.mjs`; pure phase variant normalization helpers (`phaseVariants`, `buildRoadmapPhaseVariants`, `buildNotStartedPhaseVariants`) used by `verify.cjs` for W006/W007 checks; no I/O, no async; do not edit directly |
| `verify-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools verify` |
| `verify.cjs` | Plan structure, phase completeness, reference, commit validation |
| `workstream-inventory-builder.generated.cjs` | GENERATED — pure workstream inventory projection builder; CJS artifact emitted from `sdk/src/workstream-inventory/builder.ts` via `sdk/scripts/gen-workstream-inventory-builder.mjs`; do not edit directly |

View File

@@ -101,3 +101,48 @@ A top-of-file banner is auto-inserted by each generator into the emitted `.gener
## Amendments
_(Append-only. Use a dated header when the decision evolves.)_
### 2026-05-23 — validate.ts → verify.cjs generator pattern (issue #6)
Three pure helpers from `sdk/src/query/validate.ts` Check 8 are now generated into
`get-shit-done/bin/lib/validate.generated.cjs` via `sdk/scripts/gen-validate.mjs`,
following the same I/O adapter pattern established by PR #154 (issue #4):
**Generator:** `sdk/scripts/gen-validate.mjs`
**Artifact:** `get-shit-done/bin/lib/validate.generated.cjs`
**Freshness check:** `sdk/scripts/check-validate-fresh.mjs`
**CI:** `.github/workflows/test.yml` — "SDK generated validate artifact drift check"
**Three drift items resolved (issue #6):**
1. **W007 `activeDiskPhases`** — `verify.cjs` Check 8 previously iterated `diskPhases`
(which includes archived milestone phases via `forEachArchivedPhaseToken`) for the W007
check. Archived phases absent from the current ROADMAP produced false W007 warnings.
Fix: W007 now iterates `activeDiskPhases` (from `collectDiskPhases()` only, without
`forEachArchivedPhaseToken`), matching `validate.ts` Check 8 behavior.
2. **`phaseVariants()` normalization** — `verify.cjs` Check 8 used `parseInt(p).padStart(2,'0')`
for disk-existence and roadmap-membership checks, which drops letter suffixes (e.g. "3B" →
"03" instead of "03B"). Phase dirs with letter-suffix padding mismatches (ROADMAP "3B",
disk "03B-foo") produced false W006 and W007. Fix: both checks now use `phaseVariants(p)`
from the generated module, which returns the full normalized Set including letter-suffix forms.
3. **W006 unchecked-phase variant skip** — `verify.cjs` Check 8 built `notStartedPhases` with
raw + `parseInt`-padded forms (drops letter suffix). `phaseVariants()` is now used instead,
so unchecked entries like "3B" correctly suppress W006 for "03B" (and vice versa).
**`phaseVariants` extraction note:** `phaseVariants` is defined as a closure inside `validateHealth`
in the compiled output (not a module-level export). It is extracted via brace-balanced source-text
parsing from `sdk/dist/query/validate.js`, the same technique used for `escapeRegex` extraction in
`gen-phase-lifecycle-policy.mjs`. The function is deterministic and pure: no closures over external
state, no side effects.
**Parity tests:** `tests/6-validate-cjs-drift-regression.test.cjs` — 5 tests (all GREEN after fix,
all RED on pre-fix `origin/main`). Covers each drift item with concrete fixtures:
- Drift 1: two milestone archives (v1.0 old, v1.1 active); v1.0 phase absent from ROADMAP.
- Drift 2: ROADMAP "01A", disk "1A-foo" — padding mismatch.
- Drift 3: ROADMAP "3B", disk "03B-foo" — zero-padded letter-suffix mismatch.
**Allowlist:** `scripts/shared-module-handsync-allowlist.json` — `verify.cjs` entry updated to
reference the generator and freshness check. Classification remains `cooperating-sibling` (verify.cjs
is still a full implementation; only Check 8 helpers are generated).