Skip to content

Fix core release coupling, declaration build, and CI gates - #847

Merged
iamtoruk merged 6 commits into
getagentseal:feat/core-extractionfrom
laulpogan:harden/core-release
Jul 27, 2026
Merged

Fix core release coupling, declaration build, and CI gates#847
iamtoruk merged 6 commits into
getagentseal:feat/core-extractionfrom
laulpogan:harden/core-release

Conversation

@laulpogan

@laulpogan laulpogan commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Makes @codeburn/core buildable and publishable from a clean checkout. Build, release, and CI plumbing only — no src/ changes, no schema changes, no behavior changes.

Relates to #796 and #809.

Baseline on feat/core-extraction @ 88f298c

Gate Before After
npm ci passes, but lockfile records packages/core@0.9.19 against a manifest saying 0.9.20 passes, versions agree
npm run check:workspace-versions script did not exist passes at 0.9.20
npm run typecheck -w @codeburn/core passes passes
npm test -w @codeburn/core 1 file fails, 507 passed / 2 skipped 36 files pass, 509 passed
npm run build -w @codeburn/core fails: ERR_WORKER_OUT_OF_MEMORY passes
npm pack -w @codeburn/core --dry-run unreachable (build fails) 389 files, 376.2 kB

The build failure:

ESM ⚡️ Build success in 48ms
DTS Build start
Error [ERR_WORKER_OUT_OF_MEMORY]: Worker terminated due to reaching memory limit: JS heap out of memory
    at [kOnExit] (node:internal/worker:379:26)

tests/import-smoke.test.ts shells out to npm run build in beforeAll, so the DTS crash took the test suite down with it. That is the whole delta between 507 and 509 tests.

Changes

1. Couple core and CLI releases (c52c567)

Root, CLI, and core were at 0.9.19 / 0.9.19 / 0.9.20, and the CLI depended on "@codeburn/core": "*". A published CLI could resolve any core version, including ones it was never tested against.

All three now sit at 0.9.20, the CLI pins the exact core version, and scripts/check-workspace-versions.mjs fails the build if they drift. The gate was tested against fixtures covering *, ^0.9.20, CLI drift, core drift, and the aligned case — it rejects the first four and accepts the last.

2. Emit declarations with tsc (0054c9c)

tsup's DTS worker bundles types for all 41 entries in one pass and exhausts the heap. Reproduced on Node 22.13, 24, 25, and 26 — this is not a local-environment artifact.

dts: false in tsup.config.ts; declarations now come from tsc -p tsconfig.build.json (emitDeclarationOnly). tsup still emits the ESM JavaScript. All 398 relative imports in src/ already carry .js extensions, so unbundled .d.ts resolve correctly under node16/nodenext.

Side effect: the core build went from crashing to ~40 ms, and the test suite from 34 s to 4 s.

3. Gate core in CI (7e29455)

CI ran Semgrep only — never a clean install, core build, typecheck, test, or pack. Adds a core job on Node 22.13.x / 24.x / 26.x running npm ci, check:workspace-versions, typecheck, test, build, and npm pack --dry-run. The Semgrep job is unchanged. Also adds "sideEffects": false to core so bundlers can tree-shake the 41 subpaths.

Verification

From a fresh clone, not the working tree:

npm ci                                        PASS
npm run check:workspace-versions              workspace versions agree at 0.9.20
npm run typecheck --workspace=@codeburn/core  PASS
npm test --workspace=@codeburn/core           36 files / 509 tests passed
npm run build --workspace=@codeburn/core      PASS
npm pack --workspace=@codeburn/core --dry-run 389 files, 376.2 kB

The same six gates were run in Docker on node:22.13 (npm 10.9.2), node:24 (npm 11.16.0), and node:26 (npm 11.17.0). All pass on all three.

Consumer smoke — the real tarball installed into an empty project:

  • all 41 exports-map subpaths import by name under a hook that throws on any node:fs / child_process / net / http import
  • all 41 subpaths type-resolve under moduleResolution: node16 with skipLibCheck: false — no diagnostics

The existing no-I/O import guard and content-minimization tests are untouched and still pass.

Notes for review

Three things worth a maintainer's eye, none blocking:

  1. The npm ci failure did not reproduce. The audit behind this work recorded a clean npm ci failing on lock/manifest disagreement. It passes on npm 10.9.2, 11.12.1, 11.16.0, and 11.17.0. The underlying drift was real and is fixed; the reported symptom was not reproducible.

  2. declarationMap: true ships 151 .d.ts.map files (~135 kB unpacked) that point at src/, which files does not publish. They resolve to nothing for consumers. Dropping declarationMap, or adding src to files, would both be defensible — happy to do either.

  3. The Node 22.13 leg skips tests/providers/zed-decode.test.ts (6 tests). The suite gates it on zlib.zstdCompressSync, which landed in Node 22.15, while engines.node is >=22.13.0. Testing the declared floor is correct, but zed decoding has no coverage on that leg. Raising the floor to >=22.15.0 would close the gap.

No publish is included here. npm publication stays a maintainer action.


Update: two defects found in review, both fixed

A multi-model review pass over this branch surfaced two release hazards. Both are now closed by the last two commits, and both were reproduced before being fixed.

4. Check package-lock.json in the version gate (6f1f8a4)

scripts/check-workspace-versions.mjs compared only the three manifests, so it passed while the lockfile still recorded a stale workspace version — and npm ci does not reject that either (checked on npm 10.9.2, 11.12.1, 11.16.0, 11.17.0). The gate was blind to the exact drift it exists to catch. Reproduced with a fixture whose manifests all read 0.9.20 while the lockfile recorded packages/core at 0.9.19 plus a "*" CLI dep: the gate exited 0. It now exits 1.

5. Guard publish against a half-built dist (0ec9ea9)

The build is tsup && tsc, and tsup runs with clean: true. If tsup succeeds and tsc fails, dist holds .js with no declarations. The build exits non-zero — but packages/core declared no prepublishOnly, so nothing rebuilt at publish time.

Reproduced: strip the declarations from a copy of dist and npm pack --dry-run still succeeds, with all 41 exports subpaths pointing at files absent from the tarball. npm pack was never the guard.

Adds prepublishOnly (build, then verify) and packages/core/scripts/verify-dist.mjs, which asserts every exports target exists. CI runs verify-dist too, so the guard is exercised on every push rather than only on the rare publish. Negative-tested: it accepts an intact dist and rejects the half-built one.

This hazard is pre-existing, not introduced here — the old single-tsup build had the same window. It is cheap to close, so it is closed.

Two review claims that did not survive checking

  • Older-TypeScript consumers. Claim: TS<4.7 consumers using moduleResolution: "node" would lose types now that dist/index.d.ts is a thin re-export rather than a tsup bundle. Refuted — a real node10 consumer (module: commonjs, moduleResolution: node, skipLibCheck: false) compiles with no diagnostics. TypeScript maps ./schema.js to ./schema.d.ts under node10 as well. Also verified clean under bundler and nodenext across all 41 subpaths.
  • The Node 22.13 zed skip. Claim: engines.node: ">=22.13.0" is wrong because zed decoding needs zstd from 22.15. Refuted by source — packages/core/src/providers/zed/decode.ts:6-7 documents that the host only calls the decoder after confirming zstdDecompressSync exists. Deliberate, documented degradation. It remains a CI visibility nit: those 6 tests skip silently on the 22.13 leg while CI reports green.

declarationMap is now dropped (ed4f84d). It was emitting 151 .d.ts.map files pointing at src/, which files does not ship, so they resolved to nothing for consumers. The tarball goes from 389 files / 376.2 kB to 238 files / 354.4 kB. Declarations are unchanged: 151 .d.ts, all 41 export targets still verified present.

The gate compared only the three package.json manifests, so it passed while
package-lock.json still recorded a stale workspace version. npm ci does not
reject that either (checked on npm 10.9.2, 11.12.1, 11.16.0, 11.17.0), so
nothing caught the exact drift the script exists to catch.

Reproduced with a fixture whose manifests all read 0.9.20 while the lockfile
recorded packages/core at 0.9.19 and a "*" CLI dependency: the gate exited 0.
It now exits 1.
The build is `tsup && tsc`. tsup runs with clean:true, so it wipes dist and
writes JavaScript; if tsc then fails, dist holds .js with no declarations. The
build exits non-zero, but packages/core declared no prepublishOnly, so nothing
rebuilt at publish time and a later npm publish would ship it.

Reproduced: remove the declarations from a copy of dist and npm pack --dry-run
still succeeds, with all 41 exports subpaths pointing at files absent from the
tarball. npm pack was never the guard.

Adds prepublishOnly (build then verify) and scripts/verify-dist.mjs, which
asserts every exports target exists. CI runs verify-dist as well, so the guard
is exercised on every push rather than only on the rare publish.
package.json#files ships dist and schemas but not src, so every emitted
.d.ts.map pointed at a source file the consumer never receives. That was 151
files and roughly 135 kB of maps that resolve to nothing.

Dropping declarationMap takes the tarball from 389 files to 238 and 376 kB to
354 kB. The declarations themselves are unchanged: 151 .d.ts, all 41 export
targets still verified present.
@iamtoruk
iamtoruk merged commit 0a57aa5 into getagentseal:feat/core-extraction Jul 27, 2026
7 checks passed
@laulpogan
laulpogan deleted the harden/core-release branch July 28, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants