- docs/adr/452-eslint-lint-harness.md (Accepted): adopt ESLint flat config with typescript-eslint, eslint-plugin-n, eslint-plugin-no-only-tests, and local AST-rule plugin; retire homegrown scripts/lint-*.cjs regex checkers; three test-rigor rules ship at warn, promoted to error after #453 cleanup - docs/adr/456-test-rigor-architecture.md (Accepted): deterministic-over-racing via injectable clock seam + node:test mock.timers; antagonistic tier with fast-check + Stryker at 80% threshold; typed-surface mandate; delete-bad-tests policy with no-permanent-quarantine - docs/adr/457-generated-cjs-single-source.md (Proposed): future direction to collapse ~59 hand-written bin/lib/*.cjs to TS-generated single source; eliminates tsconfig.lint.json stopgap; marked Proposed / not yet executed - TESTING-STANDARDS.md: orients to existing docs; codifies six test-rigor contracts; adds new policies (no-timing-assertion, clock-seam, property-based, mutation-score, delete-bad-tests); pairs each with exact ESLint rule names; markdownlint-clean (MD040 fences, MD056 table columns) - CONTEXT.md: adds six RULESET.TESTS.* predicates (no-timing-assertion, clock-seam, property-based-testing, mutation-score, delete-bad-tests, eslint-harness) and five glossary terms (clock seam, deterministic scheduler, property-based test, mutation testing/score, ESLint harness) - docs/adr/README.md: adds index rows for ADRs 452, 456, 457 Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.1 KiB
ADR 457: Collapse hand-written CJS to generated single-source [Proposed]
- Status: Proposed
- Date: 2026-05-28
Not yet executed. This ADR records the agreed direction and rationale. No code has been changed under this decision. Implementation is tracked separately and requires the ESLint harness (ADR 452) to be in place first.
This ADR proposes collapsing the ~59 hand-written get-shit-done/bin/lib/*.cjs files into TypeScript sources compiled (generated) into .cjs output, eliminating the hand-written/generated split, enabling type-aware linting and CJS/TS parity as first-class CI signals, and removing tsconfig.lint.json as a stopgap.
Context
Today's split
The get-shit-done/bin/lib/ directory currently holds two kinds of files:
- Hand-written
.cjs(~59 files) — authored directly as CommonJS. These are the runtime entry points for CLI commands, library seams, and utilities. Type checking relies ontsconfig.lint.jsonand@ts-checkcomments; coverage is uneven. - Generated
.cjs(~13 files) — compiled from.tssources insdk/src/orget-shit-done/src/viatsc. These files carry a// @generatedheader; they must never be hand-edited. The CJS/TS parity tests (tests/cjs-ts-parity.test.cjs) assert that the generated surface matches the TS declarations.
This split creates three structural problems:
-
Type-aware linting gap.
tsconfig.lint.jsonis not wired into an ESLint project reference.typescript-eslinttype-aware rules (@typescript-eslint/no-floating-promises,@typescript-eslint/strict-boolean-expressions, etc.) do not run on hand-written.cjsfiles. ADR 452 introduces the ESLint harness as a prerequisite but cannot close the type-aware gap until the sources are TypeScript. -
CJS/TS parity is partial. The parity tests only cover the generated surface (~13 files). The hand-written surface (~59 files) has no equivalent parity signal. Regressions on the hand-written surface are caught only by behavioral tests, not by type or surface comparison.
-
tsconfig.lint.jsonas a permanent stopgap. The file was introduced with an explicit// stopgapannotation in its header comment. It adds a non-standard compilation path that must be kept in sync withtsconfig.jsonandtsconfig.build.json. Every time a new.cjsfile is added,tsconfig.lint.jsonmust be manually updated.
Relationship to the retired SDK boundary
ADR 0174 (0174-retire-gsd-sdk-package-boundary.md) retired the @opengsd/gsd-sdk package boundary and collapsed the runtime onto a single src/ TS surface. The generated .cjs files are the downstream artifact of that collapse. This ADR extends the same direction to the hand-written files.
Decision [Proposed]
-
Single TS source. Each hand-written
get-shit-done/bin/lib/*.cjsmodule is rewritten asget-shit-done/src/<module>.ts(or the equivalent path inside the unifiedsrc/tree established by ADR 0174). The TS source is the canonical artifact;.cjsoutput is generated bytscand checked in as part of the build step. -
No hand-written runtime
.cjs. After the collapse, the only.cjsfiles inget-shit-done/bin/lib/are generated. Hand-authoring a.cjsfile in that directory is a policy violation caught by a newscripts/lint-no-hand-written-cjs.cjscheck (or equivalent ESLint rule). -
tsconfig.lint.jsondeleted. Once all sources are TypeScript,tsconfig.lint.jsonis no longer needed. The ESLint project reference ineslint.config.mjs(ADR 452) points directly totsconfig.json.@ts-checkcomments in.cjsfiles are removed as part of the migration. -
CJS/TS parity becomes total. The parity tests (
tests/cjs-ts-parity.test.cjs) expand to cover the fullbin/lib/surface. A generated.cjsfile that drifts from its TS source fails CI. -
Migration is incremental. Files are migrated module by module in separate PRs. Each PR: (a) rewrites one hand-written
.cjsas TS, (b) adds it to thetscbuild, (c) updates parity tests, (d) removestsconfig.lint.jsonincludes for the migrated file. The final PR removestsconfig.lint.jsonentirely. -
Prerequisites. This ADR is not implemented until:
- ADR 452 (ESLint harness) is merged and CI-green.
- A migration tracking issue is opened with the full list of ~59 files and a per-module plan.
- At least one pilot migration (chosen for low coupling) is completed and reviewed.
Consequences
Positive
- Type-aware
typescript-eslintrules apply to the full runtime surface, not just the generated subset. - CJS/TS parity is a total, automated signal rather than a partial one.
tsconfig.lint.jsonstopgap is removed; one fewer compilation path to maintain.- Contributors write TypeScript for all new runtime code; no new hand-authored
.cjsfiles.
Negative
- Migration cost. ~59 files must be migrated. Each migration may surface latent type errors that require fixes before the module can compile as TypeScript.
- Build step added. Currently, hand-written
.cjsfiles are runtime-ready without compilation. After migration, every change to a source.tsfile requires atscstep before the.cjsoutput is updated. CI must gate on build output being up to date. - Generated file commits. If
.cjsoutputs are checked in (as is current practice for the generated subset), contributors must remember to commit both the.tssource and the generated.cjs. A pre-commit hook or CI check enforces this.
For testing
- Tests that currently import hand-written
.cjsfiles directly (require('../bin/lib/foo.cjs')) continue to work unchanged; only the source behind the.cjschanges. - No test file changes are required as part of the migration itself, though new type-aware lint rules may surface existing test-code issues.
Rejected Alternatives
(a) Keep the hand-written/generated split indefinitely. Rejected. The split creates a permanent type-aware linting gap and requires tsconfig.lint.json as an indefinite stopgap. The direction established by ADR 0174 is single-runtime collapse; this ADR extends that to the remaining hand-written files.
(b) Full ESM rewrite. Rewrite all .cjs files as .mjs ES modules instead of TypeScript-compiled CJS. Rejected. The package currently ships CommonJS to support require() consumers. An ESM rewrite would be a breaking change requiring a semver-major bump and coordination with downstream consumers. The TS-compiled CJS approach achieves type safety without the compatibility break.
(c) Keep tsconfig.lint.json and wire it into ESLint. Extend the ADR 452 ESLint harness to use tsconfig.lint.json as the project reference for hand-written files. Rejected as a permanent solution. tsconfig.lint.json is explicitly a stopgap; wiring it into ESLint makes the stopgap permanent. The correct solution is to make the files TypeScript so the standard tsconfig.json is sufficient.
References
- Tracking issue: #457
- ESLint harness prerequisite:
452-eslint-lint-harness.md - Single-runtime collapse:
0174-retire-gsd-sdk-package-boundary.md - CJS/TS parity tests:
tests/cjs-ts-parity.test.cjs - Stopgap:
tsconfig.lint.json