Skip to content

chore: consolidate duplicated dependency declarations into the root package.json #2030

Description

@cliffhall

Follow-up to #1896 and #1965 / PR #2029, from a question raised reviewing that PR: since the clients are not published separately and all ship inside one flat package, do the dependencies need to be split across five manifests at all?

The finding

Almost every client dependency is already a verbatim duplicate of a root one — same package, same range:

Declared in root and in one or more clients Ranges
ajv, atomically, chokidar, commander, open, pino, undici, zod, @napi-rs/keyring, hono, @hono/node-server identical strings
react root ^19.0.0, clients ^19.2.4 — the one real divergence

These are exactly the packages core/ imports at runtime, which AGENTS.md already requires to be root-only. The rule simply was never applied beyond the SDK packages that #1970 cleaned up.

Why this is the structural fix, not a cleanup

A package can only be installed into an install root that declares it. Deleting the redundant client-side declarations does not merely align the two copies — it makes the second copy impossible to create. That is the failure class #1896 was opened for (two structurally-distinct copies of one type in a single tsc program; zod 4.3.6 vs 4.4.3 exhausted the 4GB tsc heap in clients/web) and that #1965 / #2029 built a guard to detect.

After consolidation the guard in #2029 should find almost nothing. That is the correct end state for it: it stops being the thing holding the line and becomes the thing that keeps us honest if a declaration creeps back.

The recorded rationale does not argue against it

specification/v2_cli_tui_launcher.md:25 rejects npm workspaces, on three grounds: core/ is consumed by bundling rather than symlinked linking, the published package is one flat fat package, and hoisting fights the dual-instance pins. None of those is a reason to declare zod in four manifests — this issue is not proposing workspaces.

That line's third clause is in fact backwards today: the dedupe + alias pins in vitest.shared.mts exist because duplicate installs happen. Consolidating should let some of them retire — worth checking as part of the work, but not assumed here.

Scope

Move to root — runtime dependencies (step 1):
ajv, atomically, chokidar, commander, open, pino, undici, zod, @napi-rs/keyring, hono, @hono/node-server. Delete the client-side declarations; they resolve from the root install by walk-up.

Move to root — shared toolchain devDependencies (step 2, separate PR):
eslint, prettier, typescript, typescript-eslint, globals, @eslint/js are declared in all five manifests; vitest, @vitest/coverage-v8, @types/node in four. Verified that npm run from a client directory has the repo root's node_modules/.bin on PATH, so each client's scripts still resolve their binaries from a root-only install. This also strengthens the exact-pin rule on prettier (#1790) — one pin instead of five that can drift independently.

Leave where they are — both must NOT become root dependencies:

  • ink-form, ink-scroll-view — deliberately inlined into the TUI bundle (TUI Error when pressing Enter to call a tool #1952). A root dependency would make consumers install unused copies; clients/tui/__tests__/tsupConfig.test.ts enforces this.
  • Web's UI stack (@mantine/*, @dnd-kit/*, @emotion/react, react-dom, react-icons, react-markdown, react-syntax-highlighter, remark-gfm, papaparse) — bundled into dist, so shipping them to consumers is pointless.

Constraints

  • react must stay ^19.0.0 at the root. Narrowing it reopens TUI Error when pressing Enter to call a tool #1952 for the ink renderer itself, which breaks TUI startup, not just its forms. The clients' ^19.2.4 is the declaration that goes away, not the root's open range.
  • Regenerate the lockfiles, and gate with npm run ci and npm run pack:verify — this changes what the published tarball resolves against, which ci alone does not cover.
  • Re-run npm run verify:dep-lockstep afterward and record the new candidate set in the PR. TOLERATED_SKEW is empty as of chore(scripts): derive dep-lockstep candidates from what enters each tsc program #2029 and should stay empty.
  • Confirm the postinstall cascade (scripts/install-clients.mjs) still behaves when a client manifest declares few or no dependencies.

Done when

  • The duplicated runtime deps are declared once, at the root.
  • npm run ci and npm run pack:verify both pass.
  • AGENTS.md's dependency-placement section and the root README's matching section describe the consolidated layout, with the .github/copilot-instructions.md mirror updated in the same PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    choreMaintenance: deps, build tooling, CI, cleanup — no user-facing behavior changev2Issues and PRs for v2

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions