refactor(#181): migrate workstream inventory builder to sdk/src/workstream (#250)

This commit is contained in:
Tom Boucher
2026-05-24 21:32:53 -04:00
committed by GitHub
parent 59bcdf03b6
commit 22e9c1de62
13 changed files with 22 additions and 17 deletions

View File

@@ -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.

View File

@@ -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`.

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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. |

View File

@@ -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.

View File

@@ -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

View File

@@ -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,

View File

@@ -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');

View File

@@ -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',

View File

@@ -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');