Files
thedotmack-claude-mem/plans/10-build-artifact-hygiene.md
Alex Newman cf450cec00 fix(build): enforce shipped dependency-closure boundary (plan-10, closes #2783) (#2800)
* plan-10 Phase 1: ship deterministic plugin runtime dependency closure

Approach A — commit & ship plugin/bun.lock so the plugin's runtime
node_modules install is deterministic, fixing the recurring
`Cannot find module 'zod/v3'` (#2730).

- align generated plugin zod range to root (^4.4.3) in build-hooks.js
- new scripts/gen-plugin-lockfile.cjs generates plugin/bun.lock as a
  build artifact after build-hooks.js writes plugin/package.json
- track & ship plugin/bun.lock (.gitignore negation, .npmignore, files allowlist)
- install with `bun install --frozen-lockfile --ignore-scripts` at runtime

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 2: fail loud at install time on a broken dependency closure

Strengthen verifyCriticalModules to assert each dependency is actually
importable via require.resolve (not merely a directory), and assert the
worker-required zod subpaths resolve: zod/v3, zod/v4, zod/v4-mini.
A partial/stale install now fails `npx claude-mem install` immediately
instead of surfacing later as a Stop-hook `Cannot find module 'zod/v3'`.

Bin-only packages (e.g. tree-sitter-cli, which has no bare-name entry
point) fall back to resolving <dep>/package.json so a healthy install
isn't falsely rejected.

Adds tests/cli/verify-critical-modules.test.ts covering a missing zod/v3
subpath (throws), a complete zod (passes), and a bin-only dep (passes).

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 3: clean-room install + import smoke test (#2730 backstop)

Add scripts/smoke-clean-room.cjs and a `smoke:clean-room` npm script.
Against fresh temp dirs (never the repo's node_modules) it:
- copies plugin/, runs `bun install --frozen-lockfile --ignore-scripts`,
  asserts zod, zod/v3, zod/v4, zod/v4-mini resolve, and boots the bundled
  worker asserting no `Cannot find module` — the direct #2730 regression guard;
- `npm pack`s, installs the tarball into a second temp dir, and load-tests
  the published bin entrypoint, warning loudly on any declared main/exports
  target missing from the tarball (latent #2537 gap).

Exits non-zero naming the missing module on any failure; cleans up all
temp dirs and the tarball in a finally.

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 4: gate CI and publish on the clean-room dependency closure

- ci.yml: new `clean-room-deps` job (between build and the docker e2e job)
  runs a frozen-lockfile drift check on the committed plugin lockfile, then
  `npm run build` + `npm run smoke:clean-room`. The drift step catches a
  contributor who changed plugin deps without regenerating plugin/bun.lock.
- npm-publish.yml: add setup-bun and run `npm run smoke:clean-room` between
  build and `npm publish`, so a broken runtime closure cannot be published
  on a tag push (ci.yml does not run on tags). Secrets block untouched.

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10: doc recluster note + Phase 0 execution slice for #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plans: backlog recluster (2026-06-04) — cross-cluster execution order + plan-13 doc

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10: gen-plugin-lockfile degrades gracefully when bun is absent

The Windows build CI job has no bun on PATH; regenerating the lockfile there
threw and failed the build. The committed plugin/bun.lock is already the
deterministic closure, so skip regeneration (non-fatal) when bun is missing
and a lockfile exists; fail loud only when neither is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 20:27:34 -07:00

3.3 KiB

[plan-10] Build / Bundle / CI Artifact Hygiene — enforce a boundary on what we ship

Defect

There is no enforced discipline on the contents, size, or correctness of published artifacts, so dead weight and maintainer files leak into what users install, and main can ship with a broken typecheck. The worker bundler reaches past the plugin's declared dependency boundary and pulls in code that is never used; there is no CI guard to catch the resulting bloat; the published npm tarball ships maintainer CLAUDE.md files because there is no files allowlist; and npm run typecheck is red on main. Each is a symptom of the same missing contract: the build must declare and enforce its boundaries — externals, size, tarball contents, and a green typecheck — in CI.

Children

  • #2584 — worker-service.cjs bundles unused better-auth (94 OAuth URLs, ~3.7MB); bundler reaches past the dep boundary
  • #2570 — no bundle-size guardrail in CI; bash-only marketplace-sync breaks on Windows (non-idempotent)
  • #2538 — 24 pre-existing TypeScript errors block npm run typecheck on main (Express 5 / React 19 / logger union drift)
  • #2537 — published npm tarball ships five CLAUDE.md files (no files allowlist / .npmignore)

Fix sequence

  1. Externalize / treeshake: mark better-auth (and any other server-only dep) external to the worker bundle, or gate it behind the server runtime so it never enters the worker artifact (#2584).
  2. Bundle-size canary in CI: record a baseline and fail CI when the worker bundle grows past a threshold; port the marketplace-sync step to a cross-platform, idempotent script (#2570).
  3. Green typecheck gate: fix the 24 drift errors (Express 5, React 19, logger union) and make npm run typecheck a required CI check so main can't go red again (#2538).
  4. Tarball allowlist: add a files allowlist (and/or .npmignore) so only intended artifacts publish; assert tarball contents in CI (#2537).

Test matrix

Artifact Check Required behavior
worker-service.cjs bundle size vs baseline no better-auth; size under threshold or CI fails
Repo main npm run typecheck exit 0; required check
npm tarball npm pack contents only allowlisted files; no maintainer CLAUDE.md
Marketplace sync run on Windows + POSIX idempotent; succeeds on both

The matrix lives in CI. An artifact-hygiene regression must fail CI before a user can install it.

Recluster note (2026-06-04)

Issue #2730 (Cannot find module 'zod/v3') is tracked here, not plan-04. Discovery (see plans/10-build-artifact-hygiene-EXECUTION.md Phase 0) found the root cause is a shipped-artifact defect — no lockfile is committed, zod is external to the worker bundle, and the unpinned bun install plus an auto-update that never re-installs leave node_modules/zod stale/missing. That is "what we ship," i.e. this master. The Wave-0 execution slice for #2730 lives in plans/10-build-artifact-hygiene-EXECUTION.md.

Out of scope

  • Missing-runtime-dependency-on-install (node_modules / zod not shipped) — moved into this plan (see Recluster note above); the original routing to plan-04 predated the no-lockfile root cause.
  • WHEN the dependency install runs on auto-update (hint-only vs. auto-reinstall) → plan-03.
  • Worker runtime crashes unrelated to dependency resolution → plan-03.