The problem
@loopover/contract is not on npm:
$ curl -s -o /dev/null -w "%{http_code}" https://registry.npmjs.org/@loopover%2Fcontract
404
$ npm install "@loopover/contract@^0.1.0" --dry-run
npm error 404 '@loopover/contract@^0.1.0' is not in this registry.
But both published CLI packages now declare it as a runtime dependency, not a dev one:
packages/loopover-mcp/package.json → "@loopover/contract": "^0.1.0" in dependencies
packages/loopover-miner/package.json → same
And both have real runtime imports in code that ships, built with plain tsc (no bundler, so the specifiers survive into the emitted JS):
packages/loopover-mcp/lib/telemetry.ts → @loopover/contract
packages/loopover-mcp/bin/loopover-mcp.ts → @loopover/contract, /api-schemas, /tools
packages/loopover-miner/lib/tenant-credential-resolution.ts → @loopover/contract/orb-broker
packages/loopover-miner/lib/github-token-resolution.ts → @loopover/contract/cli-config
Why it hasn't broken yet
The currently-published versions predate the dependency. @loopover/mcp@3.14.0 declares only @loopover/engine, @modelcontextprotocol/sdk, posthog-node, zod; @loopover/miner@3.14.1 likewise. Both still install cleanly today — verified.
It fires on the next release of either package. That publish will carry "@loopover/contract": "^0.1.0", and npm install -g @loopover/mcp@latest will fail with E404 for every external user.
Worth noting this also breaks the documented contributor workflow: .claude/skills/contributing-to-loopover/SKILL.md instructs npm install -g @loopover/mcp@latest as the pre-submit gate oracle.
Supporting evidence that publishing was intended
packages/loopover-contract/package.json is already shaped for it — no private: true, "publishConfig": { "access": "public" }, and a files: ["dist", "CHANGELOG.md"] allowlist. It is the only workspace package with that shape and no publish-*.yml workflow (engine, mcp, miner, ui-kit all have one). It looks like the publish step was simply never added when the package was split out.
Options
A. Publish it (add publish-contract.yml mirroring publish-engine.yml). Matches the package's existing shape and the other four packages' pattern. Contract must publish before any mcp/miner release, so release ordering becomes a real constraint.
B. Bundle it into the consumers — switch mcp/miner from plain tsc to a bundler that inlines @loopover/contract, and demote it to a devDependency. Keeps one less public package, but changes both build pipelines and their dist shape.
C. Vendor the shared types into each consumer. Cheapest to ship, worst for drift — it re-creates exactly the duplication #9521 consolidated into this package.
Recommend A: it is the smallest change, matches how every sibling package already ships, and preserves the single-source-of-truth the contract package exists to provide.
Guard worth adding either way
Nothing currently catches this. test:mcp-pack / test:miner-pack pack the tarball but never install it from a clean registry-only context, so an unresolvable dependency passes. A check that installs the packed tarball with --omit=dev against the public registry would have caught it before the dependency ever landed.
The problem
@loopover/contractis not on npm:But both published CLI packages now declare it as a runtime dependency, not a dev one:
packages/loopover-mcp/package.json→"@loopover/contract": "^0.1.0"independenciespackages/loopover-miner/package.json→ sameAnd both have real runtime imports in code that ships, built with plain
tsc(no bundler, so the specifiers survive into the emitted JS):packages/loopover-mcp/lib/telemetry.ts→@loopover/contractpackages/loopover-mcp/bin/loopover-mcp.ts→@loopover/contract,/api-schemas,/toolspackages/loopover-miner/lib/tenant-credential-resolution.ts→@loopover/contract/orb-brokerpackages/loopover-miner/lib/github-token-resolution.ts→@loopover/contract/cli-configWhy it hasn't broken yet
The currently-published versions predate the dependency.
@loopover/mcp@3.14.0declares only@loopover/engine,@modelcontextprotocol/sdk,posthog-node,zod;@loopover/miner@3.14.1likewise. Both still install cleanly today — verified.It fires on the next release of either package. That publish will carry
"@loopover/contract": "^0.1.0", andnpm install -g @loopover/mcp@latestwill fail with E404 for every external user.Worth noting this also breaks the documented contributor workflow:
.claude/skills/contributing-to-loopover/SKILL.mdinstructsnpm install -g @loopover/mcp@latestas the pre-submit gate oracle.Supporting evidence that publishing was intended
packages/loopover-contract/package.jsonis already shaped for it — noprivate: true,"publishConfig": { "access": "public" }, and afiles: ["dist", "CHANGELOG.md"]allowlist. It is the only workspace package with that shape and nopublish-*.ymlworkflow (engine,mcp,miner,ui-kitall have one). It looks like the publish step was simply never added when the package was split out.Options
A. Publish it (add
publish-contract.ymlmirroringpublish-engine.yml). Matches the package's existing shape and the other four packages' pattern. Contract must publish before any mcp/miner release, so release ordering becomes a real constraint.B. Bundle it into the consumers — switch mcp/miner from plain
tscto a bundler that inlines@loopover/contract, and demote it to adevDependency. Keeps one less public package, but changes both build pipelines and theirdistshape.C. Vendor the shared types into each consumer. Cheapest to ship, worst for drift — it re-creates exactly the duplication
#9521consolidated into this package.Recommend A: it is the smallest change, matches how every sibling package already ships, and preserves the single-source-of-truth the contract package exists to provide.
Guard worth adding either way
Nothing currently catches this.
test:mcp-pack/test:miner-packpack the tarball but never install it from a clean registry-only context, so an unresolvable dependency passes. A check that installs the packed tarball with--omit=devagainst the public registry would have caught it before the dependency ever landed.