From 22e9c1de620eeab0e1fc56635a7c503297bc1ca9 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 24 May 2026 21:32:53 -0400 Subject: [PATCH] refactor(#181): migrate workstream inventory builder to sdk/src/workstream (#250) --- .changeset/quick-otters-run.md | 5 +++++ CONTEXT.md | 2 +- docs/INVENTORY.md | 2 +- docs/adr/3524-cjs-sdk-hard-seam.md | 2 +- docs/agents/cjs-sdk-seam.md | 2 +- docs/prd/3524-cjs-sdk-hard-seam.md | 2 +- .../bin/lib/workstream-inventory-builder.generated.cjs | 2 +- sdk/scripts/gen-workstream-inventory-builder.mjs | 8 ++++---- sdk/src/query/workstream-inventory.ts | 8 ++++---- .../{workstream-inventory => workstream}/builder.test.ts | 0 sdk/src/{workstream-inventory => workstream}/builder.ts | 0 tests/gen-staleness-check.test.cjs | 4 ++-- tests/workstream-inventory-builder-generator.test.cjs | 2 +- 13 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 .changeset/quick-otters-run.md rename sdk/src/{workstream-inventory => workstream}/builder.test.ts (100%) rename sdk/src/{workstream-inventory => workstream}/builder.ts (100%) diff --git a/.changeset/quick-otters-run.md b/.changeset/quick-otters-run.md new file mode 100644 index 000000000..6e6c9186d --- /dev/null +++ b/.changeset/quick-otters-run.md @@ -0,0 +1,5 @@ +--- +type: Changed +pr: 181 +--- +migrated the Workstream Inventory Builder from sdk/src/workstream-inventory/builder.ts to sdk/src/workstream/builder.ts and rewired generator/test references without behavior changes. diff --git a/CONTEXT.md b/CONTEXT.md index 2109aa308..3eb142b65 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -77,7 +77,7 @@ Shared CJS/SDK Module owning config load, legacy-key normalization, defaults mer Module owning `.planning` path resolution, active workstream pointer policy (`session-scoped > shared`), pointer self-heal behavior, and planning lock semantics for workstream-aware execution. ### Workstream Inventory Module -Shared CJS/SDK Module owning workstream directory discovery, per-workstream state projection, phase/plan/summary counting, roadmap-declared phase count, active marker projection, and active-workstream collision inputs. Command handlers render list/status/progress outputs from this inventory instead of rescanning `.planning/workstreams/*` directly. Source of truth for the pure projection is `sdk/src/workstream-inventory/builder.ts` (a Builder Module emitted to `get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs` via the generator pattern); per-side Reader Adapters (`bin/lib/workstream-inventory.cjs` sync, `sdk/src/query/workstream-inventory.ts` async-ready) collect filesystem inputs and delegate projection to the Builder. +Shared CJS/SDK Module owning workstream directory discovery, per-workstream state projection, phase/plan/summary counting, roadmap-declared phase count, active marker projection, and active-workstream collision inputs. Command handlers render list/status/progress outputs from this inventory instead of rescanning `.planning/workstreams/*` directly. Source of truth for the pure projection is `sdk/src/workstream/builder.ts` (a Builder Module emitted to `get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs` via the generator pattern); per-side Reader Adapters (`bin/lib/workstream-inventory.cjs` sync, `sdk/src/query/workstream-inventory.ts` async-ready) collect filesystem inputs and delegate projection to the Builder. ### Project-Root Resolution Module Shared CJS/SDK Module owning project-root resolution from any starting directory. Walks the ancestor chain (bounded by `FIND_PROJECT_ROOT_MAX_DEPTH = 10`) applying four heuristics in order: (0) own `.planning/` guard (#1362), (1) parent `.planning/config.json` `sub_repos` traversal, (2) legacy `multiRepo: true` boolean + ancestor `.git`, (3) `.git` heuristic with parent `.planning/`. Returns `startDir` when no ancestor qualifies. Sync `node:fs` I/O. Source of truth: `sdk/src/project-root/index.ts`; CJS callers consume the generator-emitted `get-shit-done/bin/lib/project-root.generated.cjs` via thin re-exports at `get-shit-done/bin/lib/core.cjs` and `sdk/src/query/helpers.ts`. diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index d99a133db..603a3cd4e 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -442,7 +442,7 @@ Full listing: `get-shit-done/bin/lib/*.cjs`. | `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 | +| `workstream-inventory-builder.generated.cjs` | GENERATED — pure workstream inventory projection builder; CJS artifact emitted from `sdk/src/workstream/builder.ts` via `sdk/scripts/gen-workstream-inventory-builder.mjs`; do not edit directly | | `workstream-inventory.cjs` | Shared workstream inventory projection: state fields, phase/plan/summary counts, roadmap phase count, and active marker — thin orchestrator that delegates pure projection to `workstream-inventory-builder.generated.cjs` | | `workstream-name-policy.cjs` | CJS shim adapter — re-exports from `workstream-name-policy.generated.cjs` (Phase 6/#3575 Shared Module migration) | | `workstream-name-policy.generated.cjs` | GENERATED — CJS artifact emitted from `sdk/src/workstream-name-policy.ts` via `sdk/scripts/gen-workstream-name-policy.mjs`; canonical workstream name validation (`isValidActiveWorkstreamName`, `hasInvalidPathSegment`, `validateWorkstreamName`) and slug normalization (`toWorkstreamSlug`); do not edit directly | diff --git a/docs/adr/3524-cjs-sdk-hard-seam.md b/docs/adr/3524-cjs-sdk-hard-seam.md index 1dbf270d6..dd2321461 100644 --- a/docs/adr/3524-cjs-sdk-hard-seam.md +++ b/docs/adr/3524-cjs-sdk-hard-seam.md @@ -33,7 +33,7 @@ The table below indexes by Module, not by physical layer. Each row names the sou |---|---|---|---|---| | **STATE.md Document Module** | New under this ADR (Phase 1) — see CONTEXT.md "STATE.md Document Module" | `sdk/src/state/index.ts` (promoted from `sdk/src/query/state-document.ts`) | `sdk/src/query/state-document.generated.ts`, `get-shit-done/bin/lib/state-document.generated.cjs` | `bin/lib/state.cjs` and `sdk/src/query/state*.ts` import the generated form | | **Configuration Module** | New under this ADR (Phase 2) — definition added to CONTEXT.md as part of Phase 2 | `sdk/src/config/index.ts` plus data manifests `sdk/shared/config-schema.manifest.json` and `sdk/shared/config-defaults.manifest.json` | `sdk/src/query/config-schema.generated.ts`, `get-shit-done/bin/lib/config-schema.generated.cjs`, `get-shit-done/bin/lib/configuration.generated.cjs` | `bin/lib/config.cjs`, `bin/lib/core.cjs:loadConfig`, `sdk/src/config.ts` | -| **Workstream Inventory Module** (Builder) | Amended under this ADR (Phase 3) — Builder split documented in CONTEXT.md update | `sdk/src/workstream-inventory/builder.ts` (pure projection from directory entries + STATE.md text + plan scan results → typed inventory) | `sdk/src/query/workstream-inventory-builder.generated.ts`, `get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs` | Per-side fs Readers (`workstream-inventory.cjs` sync, `workstream-inventory.ts` async) call the Builder. Readers stay hand-authored because the fs idiom legitimately differs. | +| **Workstream Inventory Module** (Builder) | Amended under this ADR (Phase 3) — Builder split documented in CONTEXT.md update | `sdk/src/workstream/builder.ts` (pure projection from directory entries + STATE.md text + plan scan results → typed inventory) | `sdk/src/query/workstream-inventory-builder.generated.ts`, `get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs` | Per-side fs Readers (`workstream-inventory.cjs` sync, `workstream-inventory.ts` async) call the Builder. Readers stay hand-authored because the fs idiom legitimately differs. | | **Project-Root Resolution Module** | New under this ADR (Phase 4) — short CONTEXT.md entry, behavior already de-facto shared | `sdk/src/project-root/index.ts` | `get-shit-done/bin/lib/project-root.generated.cjs` | `bin/lib/core.cjs` (`findProjectRoot`, `findEffectiveRoot`), `sdk/src/helpers.ts` | | **Frontmatter Module** | Conditional (Phase 3, only if drift catalogue confirms pair duplication) | `sdk/src/frontmatter/index.ts` | `get-shit-done/bin/lib/frontmatter.generated.cjs` | Existing handler call sites | | **Plan Scan Module** | Conditional (Phase 3 or later) | `sdk/src/plan-scan/index.ts` | `get-shit-done/bin/lib/plan-scan.generated.cjs` | Phase/roadmap routers | diff --git a/docs/agents/cjs-sdk-seam.md b/docs/agents/cjs-sdk-seam.md index 689259b42..ba797fb62 100644 --- a/docs/agents/cjs-sdk-seam.md +++ b/docs/agents/cjs-sdk-seam.md @@ -11,7 +11,7 @@ The CJS↔SDK hard-seam migration (#3524) eliminates a class of config-schema dr |-------|----|---------| | Phase 1 | [#3531](https://github.com/open-gsd/get-shit-done-redux/pull/3531) | `state-document` Shared Module — source-of-truth at `sdk/src/state/` (`index.ts`), generator, freshness check, CJS Adapter (`state-document.generated.cjs`). Worked example for the pattern. | | Phase 2 | [#3540](https://github.com/open-gsd/get-shit-done-redux/pull/3540) | `configuration` Shared Module — `sdk/shared/config-schema.manifest.json` + `sdk/shared/config-defaults.manifest.json` as data manifests; generator + freshness check + CJS Adapter. | -| Phase 3 | [#3548](https://github.com/open-gsd/get-shit-done-redux/pull/3548) | `workstream-inventory` Shared Module — source-of-truth at `sdk/src/workstream-inventory/`, builder, generator, freshness check, CJS Adapter. | +| Phase 3 | [#3548](https://github.com/open-gsd/get-shit-done-redux/pull/3548) | `workstream-inventory` Shared Module — source-of-truth at `sdk/src/workstream/`, builder, generator, freshness check, CJS Adapter. | | Phase 4 | [#3554](https://github.com/open-gsd/get-shit-done-redux/pull/3554) | `project-root` Shared Module — source-of-truth at `sdk/src/project-root/`, generator, freshness check, CJS Adapter. | | Phase 5.0 | [#3558](https://github.com/open-gsd/get-shit-done-redux/pull/3558) | `runtime-bridge-sync` worker — enables CJS-side execution of SDK native handlers; state.* family initial router delegation via `executeForCjs`. | | Phase 5.1 | [#3574](https://github.com/open-gsd/get-shit-done-redux/pull/3574) | `state.*` router delegation complete — all known state subcommands delegated via `executeForCjs`; Phase 5.0 worker bug fix. | diff --git a/docs/prd/3524-cjs-sdk-hard-seam.md b/docs/prd/3524-cjs-sdk-hard-seam.md index 6a564ef7d..1e135f607 100644 --- a/docs/prd/3524-cjs-sdk-hard-seam.md +++ b/docs/prd/3524-cjs-sdk-hard-seam.md @@ -112,7 +112,7 @@ Phases are sized to ship in one to two PRs each. Each phase has its own GitHub i **Why third.** Phase 1 proves the pattern for pure transforms. Phase 2 proves it for data-manifest-backed logic. Phase 3 generalizes across the remaining hand-synced pairs surfaced by the audit. The Workstream Inventory Module is the headline because it requires the **Builder/Reader split** — the projection logic is pure and shareable, but the directory traversal is legitimately sync (CJS) vs async (SDK). This is the pattern for every paired Module with mixed pure-and-I/O concerns. **Scope:** -- Write the Workstream Inventory Builder source at `sdk/src/workstream-inventory/builder.ts`. Pure function: takes a list of directory entries plus per-workstream STATE.md text plus plan-scan results and returns the typed `WorkstreamPhaseInventory`/`WorkstreamInventory` projection. No fs reads. +- Write the Workstream Inventory Builder source at `sdk/src/workstream/builder.ts`. Pure function: takes a list of directory entries plus per-workstream STATE.md text plus plan-scan results and returns the typed `WorkstreamPhaseInventory`/`WorkstreamInventory` projection. No fs reads. - Write `sdk/scripts/gen-workstream-inventory-builder.ts` to emit `get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs` and `sdk/src/query/workstream-inventory-builder.generated.ts`. - Write `sdk/scripts/check-workstream-inventory-builder-fresh.mjs`. - Refactor `bin/lib/workstream-inventory.cjs` to a sync Reader Adapter: does `fs.readdirSync` + `readFileSync` of STATE.md, calls the Builder. The projection logic is removed. diff --git a/get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs b/get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs index c7c4eca16..16b7c6733 100644 --- a/get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs +++ b/get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs @@ -3,7 +3,7 @@ /** * GENERATED FILE — DO NOT EDIT. * - * Source: sdk/src/workstream-inventory/builder.ts + * Source: sdk/src/workstream/builder.ts * Regenerate: cd sdk && npm run gen:workstream-inventory-builder * * Workstream Inventory Builder — pure projection from pre-collected diff --git a/sdk/scripts/gen-workstream-inventory-builder.mjs b/sdk/scripts/gen-workstream-inventory-builder.mjs index 61b5793a2..1b1a74da4 100644 --- a/sdk/scripts/gen-workstream-inventory-builder.mjs +++ b/sdk/scripts/gen-workstream-inventory-builder.mjs @@ -2,7 +2,7 @@ /** * Generator for the Workstream Inventory Builder CJS artifact. * - * Reads the compiled ESM output from sdk/dist/workstream-inventory/builder.js, + * Reads the compiled ESM output from sdk/dist/workstream/builder.js, * extracts function source via Function.prototype.toString() for exports * and via source-text extraction for internal helpers, then emits * get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs. @@ -15,14 +15,14 @@ import { readFile, writeFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { requireFreshDist } from './_gen-helpers.mjs'; -requireFreshDist('sdk/dist/workstream-inventory/builder.js', 'sdk/src/workstream-inventory/builder.ts'); +requireFreshDist('sdk/dist/workstream/builder.js', 'sdk/src/workstream/builder.ts'); export const BANNER = `'use strict'; /** * GENERATED FILE — DO NOT EDIT. * - * Source: sdk/src/workstream-inventory/builder.ts + * Source: sdk/src/workstream/builder.ts * Regenerate: cd sdk && npm run gen:workstream-inventory-builder * * Workstream Inventory Builder — pure projection from pre-collected @@ -66,7 +66,7 @@ export function extractFunctionFromSource(source, name) { export async function buildWorkstreamInventoryBuilderCjs() { // Load the compiled ESM module to get exports via Function.prototype.toString() - const distUrl = new URL('../dist/workstream-inventory/builder.js', import.meta.url); + const distUrl = new URL('../dist/workstream/builder.js', import.meta.url); const { buildWorkstreamInventory, isCompletedInventory, diff --git a/sdk/src/query/workstream-inventory.ts b/sdk/src/query/workstream-inventory.ts index 678064dff..502e6e397 100644 --- a/sdk/src/query/workstream-inventory.ts +++ b/sdk/src/query/workstream-inventory.ts @@ -5,7 +5,7 @@ * Query handlers should render outputs from this inventory instead of * rescanning workstream directories directly. * - * Pure projection logic lives in ../workstream-inventory/builder.ts. + * Pure projection logic lives in ../workstream/builder.ts. * This module handles I/O orchestration only. */ @@ -14,16 +14,16 @@ import { join } from 'node:path'; import { scanPhasePlans } from './plan-scan.js'; import { stateExtractField } from '../state/index.js'; import { readActiveWorkstream } from './active-workstream-store.js'; -import { buildWorkstreamInventory } from '../workstream-inventory/builder.js'; +import { buildWorkstreamInventory } from '../workstream/builder.js'; // Re-export types from the builder so downstream consumers can import from here. export type { WorkstreamPhaseInventory, WorkstreamInventory, WorkstreamInventoryList, -} from '../workstream-inventory/builder.js'; +} from '../workstream/builder.js'; -import type { WorkstreamInventory, WorkstreamInventoryList } from '../workstream-inventory/builder.js'; +import type { WorkstreamInventory, WorkstreamInventoryList } from '../workstream/builder.js'; export const planningRoot = (projectDir: string): string => join(projectDir, '.planning'); diff --git a/sdk/src/workstream-inventory/builder.test.ts b/sdk/src/workstream/builder.test.ts similarity index 100% rename from sdk/src/workstream-inventory/builder.test.ts rename to sdk/src/workstream/builder.test.ts diff --git a/sdk/src/workstream-inventory/builder.ts b/sdk/src/workstream/builder.ts similarity index 100% rename from sdk/src/workstream-inventory/builder.ts rename to sdk/src/workstream/builder.ts diff --git a/tests/gen-staleness-check.test.cjs b/tests/gen-staleness-check.test.cjs index d8cc3e054..bd9c7094b 100644 --- a/tests/gen-staleness-check.test.cjs +++ b/tests/gen-staleness-check.test.cjs @@ -58,8 +58,8 @@ const GENERATORS = [ }, { script: 'gen-workstream-inventory-builder.mjs', - dist: 'sdk/dist/workstream-inventory/builder.js', - ts: 'sdk/src/workstream-inventory/builder.ts', + dist: 'sdk/dist/workstream/builder.js', + ts: 'sdk/src/workstream/builder.ts', }, { script: 'gen-workstream-name-policy.mjs', diff --git a/tests/workstream-inventory-builder-generator.test.cjs b/tests/workstream-inventory-builder-generator.test.cjs index feb60da59..fd4fe1f79 100644 --- a/tests/workstream-inventory-builder-generator.test.cjs +++ b/tests/workstream-inventory-builder-generator.test.cjs @@ -125,7 +125,7 @@ describe('workstream-inventory-builder generator parity (ESM dist vs generated C // Dynamic import of the ESM SDK dist (use pathToFileURL since we're in CJS context) const path = require('path'); const { pathToFileURL } = require('url'); - const distPath = path.resolve(__dirname, '..', 'sdk', 'dist', 'workstream-inventory', 'builder.js'); + const distPath = path.resolve(__dirname, '..', 'sdk', 'dist', 'workstream', 'builder.js'); sdkBuild = await import(pathToFileURL(distPath).href); // CJS require of the generated artifact cjsModule = require('../get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs');