chore: consolidate duplicated runtime dependencies into the root package.json

Every runtime dependency core/ imports is now declared once, in the repo-root
package.json, and nowhere else. Deleting the client-side copies does not merely
align them: a package installs only into an install root that declares it, so
with no second declaration a second copy cannot be created at all — the failure
class #1896 was opened for and #1965/#2029 built verify:dep-lockstep to detect.

- clients/{web,cli,tui,launcher}: dropped ajv, atomically, chokidar, commander,
  open, pino, zod, @napi-rs/keyring, hono, @hono/node-server and react, as each
  applied. clients/cli and clients/launcher now declare no runtime dependencies.
- Root hono floor raised ^4.12.18 -> ^4.13.1, keeping the higher of the two
  ranges that were in the tree rather than silently lowering web's.
- tsup external lists: added ajv and zod to all three bundles, plus atomically
  and open (cli) and atomically (tui). tsup externalizes only what the nearest
  manifest declares, so a package that moves to the root is inlined unless it is
  named explicitly.
- vitest.shared.mts: repointed the pino, hono, @hono/node-server, atomically,
  chokidar and @napi-rs/keyring pins from <client>/node_modules to the repo
  root, joining express, yaml and proper-lockfile. Left behind they would
  resolve to a directory that no longer exists, or to a transitive copy an
  unrelated dependency dragged in. react and react-dom stay client-pinned so
  the pair stays version-matched within an install.
- Docs: AGENTS.md dependency placement, the root README and the local-dev skill
  describe the consolidated layout.

verify:dep-lockstep candidate set: 4 -> 2 (hono and pino fell out; only
@standard-schema/spec and zod still reach one tsc program from two installs).
npm run local:gate and npm run pack:verify both pass.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-31 19:08:24 -04:00
commit 1f5c32940d

View file

@ -50,43 +50,15 @@ export function vitestSharedPaths(clientDir: string) {
find: /^react-dom\/client$/,
replacement: path.resolve(dirname, "node_modules/react-dom/client.js"),
},
{ find: /^pino$/, replacement: path.resolve(dirname, "node_modules/pino") },
{
find: /^pino\/browser\.js$/,
replacement: path.resolve(dirname, "node_modules/pino/browser.js"),
},
{
find: /^hono$/,
replacement: path.resolve(dirname, "node_modules/hono/dist/index.js"),
},
{
find: /^hono\/streaming$/,
replacement: path.resolve(
dirname,
"node_modules/hono/dist/helper/streaming/index.js",
),
},
{
find: /^@hono\/node-server$/,
replacement: path.resolve(dirname, "node_modules/@hono/node-server"),
},
{
find: /^atomically$/,
replacement: path.resolve(dirname, "node_modules/atomically"),
},
{
find: /^chokidar$/,
replacement: path.resolve(dirname, "node_modules/chokidar"),
},
{
find: /^@napi-rs\/keyring$/,
replacement: path.resolve(dirname, "node_modules/@napi-rs/keyring"),
},
// `express` and `yaml` resolve from the **repo root**, unlike every pin
// above. Both are reached only through `test-servers/src` — express by the
// http/oauth servers, yaml by `load-config.ts` — which is root-owned code
// with no manifest of its own, so the root is where they are declared and
// the only place a client's resolution chain is guaranteed to find them.
// Everything below resolves from the **repo root**, unlike the `react` /
// `react-dom` pins above. These are the runtime dependencies declared in
// the root manifest and nowhere else, so the root install is the only place
// a client's resolution chain is guaranteed to find them.
//
// `express` and `yaml` are reached only through `test-servers/src` —
// express by the http/oauth servers, yaml by `load-config.ts` — which is
// root-owned code with no manifest of its own.
//
// Pointing these at `<client>/node_modules` is what broke when the MCP
// packages moved to the root (#1970): express was never declared by a client
// at all, it arrived in `clients/cli` as a peer of `express-rate-limit`
@ -112,6 +84,49 @@ export function vitestSharedPaths(clientDir: string) {
find: /^proper-lockfile$/,
replacement: path.resolve(repoRoot, "node_modules/proper-lockfile"),
},
// The rest of `core/`'s runtime dependencies, consolidated into the root
// manifest by #2195. Each of these used to be declared by the clients that
// reached it and was pinned to `<client>/node_modules` accordingly; once
// the declarations went away those paths stopped existing, so the pin has
// to follow the package to the root. Left un-repointed they would resolve
// to a directory that is not there — or, worse, to a transitive copy some
// unrelated dependency happened to drag in, which is the duplicate this
// whole pin list exists to prevent.
{
find: /^pino$/,
replacement: path.resolve(repoRoot, "node_modules/pino"),
},
{
find: /^pino\/browser\.js$/,
replacement: path.resolve(repoRoot, "node_modules/pino/browser.js"),
},
{
find: /^hono$/,
replacement: path.resolve(repoRoot, "node_modules/hono/dist/index.js"),
},
{
find: /^hono\/streaming$/,
replacement: path.resolve(
repoRoot,
"node_modules/hono/dist/helper/streaming/index.js",
),
},
{
find: /^@hono\/node-server$/,
replacement: path.resolve(repoRoot, "node_modules/@hono/node-server"),
},
{
find: /^atomically$/,
replacement: path.resolve(repoRoot, "node_modules/atomically"),
},
{
find: /^chokidar$/,
replacement: path.resolve(repoRoot, "node_modules/chokidar"),
},
{
find: /^@napi-rs\/keyring$/,
replacement: path.resolve(repoRoot, "node_modules/@napi-rs/keyring"),
},
];
const projectResolve = {