Skip to content

fix(packaging): declare engines >=22.19.0 + drop pi from three caps' runtime deps - #74

Merged
tps-flint merged 1 commit into
mainfrom
fix/publish-metadata
Jul 27, 2026
Merged

fix(packaging): declare engines >=22.19.0 + drop pi from three caps' runtime deps#74
tps-flint merged 1 commit into
mainfrom
fix/publish-metadata

Conversation

@tps-flint

Copy link
Copy Markdown
Contributor

Closes #71. Closes #72.

Two publish-metadata defects, same class, overlapping files, so one PR.

#71 — engines

Value: node >=22.19.0. Read off the installed @earendil-works/pi-coding-agent@0.79.10 manifest rather than copied from the issue text — its own engines.node is exactly >=22.19.0. It is also the ceiling across the entire resolved install tree: the four pi packages and undici@8.5.0 sit at >=22.19.0 and nothing in the tree requires more. Next-highest is hosted-git-info at ^20.17.0 || >=22.9.0.

Scope: all six publishable packagesbob, bob-shell, bob-discord, bob-cap-discord, bob-cap-flair, bob-cap-observatory.

Deliberate call on the capability packages, since the issue asked for one: they get it too. Every publishable package either imports pi values directly (bob-shell) or declares a runtime dependency on bob-shell, which does. The runtime floor is identical everywhere, so the declared floor should be too — and #72 does not change that, because it removes a direct pi declaration, not the transitive one through bob-shell. The root package is private: true and never publishes, so it is left alone; adding engines there would only affect contributor installs, which run through bun, which does not enforce it.

What this actually buys, measured on a real Node 20.19.0 / npm 10.8.2 install rather than asserted:

  • Default strictness (engine-strict unset): npm warns, exit 0. The install still succeeds. What changes is who is named: before, the only EBADENGINE line mentioned @earendil-works/pi-coding-agent@0.79.10, a package the user never asked for; now @tpsdev-ai/bob@0.2.0 and @tpsdev-ai/bob-shell@0.2.0 are named alongside it with required: { node: '>=22.19.0' }.
  • engine-strict=true: npm hard-fails exit 1 with Not compatible with your version of node/npm: @tpsdev-ai/bob@0.2.0 / Required: {"node":">=22.19.0"}.

So the accurate framing is that npm's default posture warns rather than refuses. This turns an unattributable warning into one that names the package the user typed, and it gives anyone running engine-strict a clean install-time failure. Worth having before first publish either way.

#72 — pi is type-only in three capability packages

Confirmed against the emitted output, not the source:

package pi in emitted .js pi in emitted .d.ts
bob-cap-discord no yes
bob-cap-flair no yes
bob-cap-observatory no yes
bob-shell yes (dist/run.js, dist/persistent.js) yes

bob-shell is the sole genuine runtime consumer and keeps pi as a real dependency. The three caps move it to devDependencies, which also keeps the cap-discord e2e test — the one place a cap imports pi values — working.

Packed-tarball evidence (npm pack, manifests extracted from the tarballs):

Before:

@tpsdev-ai/bob-cap-discord      deps: @earendil-works/pi-coding-agent, @tpsdev-ai/bob-shell, @tpsdev-ai/bob-discord, discord.js, typebox
@tpsdev-ai/bob-cap-flair        deps: @earendil-works/pi-coding-agent, @tpsdev-ai/bob-shell, typebox
@tpsdev-ai/bob-cap-observatory  deps: @earendil-works/pi-coding-agent, @tpsdev-ai/bob-shell, typebox

After:

@tpsdev-ai/bob-cap-discord      deps: @tpsdev-ai/bob-shell, @tpsdev-ai/bob-discord, discord.js, typebox   devDeps: @earendil-works/pi-coding-agent
@tpsdev-ai/bob-cap-flair        deps: @tpsdev-ai/bob-shell, typebox                                       devDeps: @earendil-works/pi-coding-agent
@tpsdev-ai/bob-cap-observatory  deps: @tpsdev-ai/bob-shell, typebox                                       devDeps: @earendil-works/pi-coding-agent

@tpsdev-ai/bob-shell still lists pi under dependencies, correctly.

One limit, verified rather than assumed

This does not shrink a consumer's install tree, which is the benefit the issue leads with. Installing the packed bob-cap-flair tarball with --omit=dev resolves 131 packages before and 131 after — byte-identical counts — because all three caps still declare @tpsdev-ai/bob-shell as a runtime dependency and bob-shell genuinely runtime-depends on pi:

@tpsdev-ai/bob-cap-flair
`-- @tpsdev-ai/bob-shell
  `-- @earendil-works/pi-coding-agent

The change is still right — declaring a runtime dependency you never execute is wrong metadata regardless — but it is a prerequisite for the tree reduction, not the tree reduction itself. Getting that requires a separate decision about the caps' bob-shell dependency, which is the same defect class one level up: cap-flair and cap-observatory import bob-shell type-only, and bob-discord does too. Filed separately rather than smuggled in here.

Verification

Baseline measured on unmodified origin/main in a clean worktree, then re-measured on this branch.

check main this branch
bun run build pass pass
bun run typecheck pass pass
bun run lint pass (1 warning, 1 info) pass (same 1 warning, 1 info)
node scripts/check-workspace-deps.mjs lockstep lockstep
bun install --frozen-lockfile pass pass
tests 293 pass / 0 fail, 22 files, 650 expects 293 pass / 0 fail, 22 files, 650 expects

Per-package test counts, unchanged in both runs: shell 163, cap-discord 61, cap-observatory 33, cap-flair 15, discord 12, cli 9. The cap-discord e2e.test.ts case that builds a real pi AgentSession passes on its own after the move (1 pass / 0 fail).

Zero regression.

Lockfile

Surgical. bun.lock picked up stale workspace: specs for the three caps and bob-discord that #70 had already corrected in the package.json files but never refreshed in the lock, plus the three new devDependencies blocks. No resolved-package entries changed and no @biomejs platform-binary churn — the diff is 14 insertions / 8 deletions, entirely inside the workspaces block. bun install --frozen-lockfile passes against it from a cleared node_modules.

Not touched

  • packages/shell/src/capability-catalog.ts — left alone for the concurrent capability-resolution work.
  • CHANGELOG.md — this repo has no [Unreleased] section and the release job extracts a specific version heading, so there is nothing to append to yet.
  • The Dependency Audit job's brace-expansion finding, which is pre-existing on main and inside the repo's own 7-day release-age window. No excludes entry added.

Two publish-metadata defects, same class, overlapping files.

Closes #71. No package declared engines, so a Node 20 user installed
cleanly and then hit a confusing runtime failure. Every publishable
package now declares node >=22.19.0 — the value read off the installed
@earendil-works/pi-coding-agent@0.79.10 manifest, not copied from the
issue. It is also the ceiling across the whole install tree (the pi
family and undici 8.5.0 sit at exactly >=22.19.0; nothing is higher).

All six publishable packages get it, capability packages included: each
one either imports pi values directly (bob-shell) or declares a runtime
dependency on bob-shell, which does. The runtime floor is the same
everywhere, so the metadata should be too. The root package is
private: true and never publishes, so it is left alone.

Measured, not assumed: on Node 20.19.0 / npm 10.8.2 the default install
now warns EBADENGINE naming @tpsdev-ai/bob and @tpsdev-ai/bob-shell
themselves rather than only a transitive pi the user has never heard of,
and with engine-strict=true it hard-fails exit 1 reporting
Required: node >=22.19.0. Default npm strictness warns rather than
refuses, so this improves the diagnostic, it does not block the install.

Closes #72. bob-cap-discord, bob-cap-flair and bob-cap-observatory
declared pi as a runtime dependency but import it type-only, which tsc
erases: pi survives only in each package's emitted .d.ts, never in the
emitted .js. bob-shell is the sole genuine runtime consumer
(SessionManager in src/persistent.ts and src/run.ts, both present in
dist/*.js) and keeps pi as a real dependency. Moved to devDependencies
in the three caps, which is also what the cap-discord e2e test needs to
keep importing pi values.

Packed-tarball evidence via npm pack: before, all three manifests listed
@earendil-works/pi-coding-agent under dependencies; after, none do, and
it appears under devDependencies instead.

One honest limit, verified rather than reasoned about: this does not
shrink a consumer's install tree. Installing the packed bob-cap-flair
resolves 131 packages before and 131 after, because all three caps still
declare @tpsdev-ai/bob-shell as a runtime dependency and bob-shell
genuinely runtime-depends on pi. The manifest is now correct; the tree
reduction needs a separate decision about the caps' bob-shell dependency,
which is type-only in all three (and in bob-discord too).

bun.lock also picked up stale workspace: specs for the three caps and
bob-discord that PR #70 had already fixed in the package.json files but
not refreshed in the lock. No resolved-package entries changed and no
biome platform-binary churn.

@tps-sherlock tps-sherlock left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review: bob#74 — publish metadata: engines + type-only dependency placement ✅

Verdict: APPROVE. Both changes are correct, and the two honest corrections are properly documented.


engines: node >=22.19.0

Derived from @earendil-works/pi-coding-agent@0.79.10's own engines.node, which is the highest floor in the resolved tree. Applied to all six publishable packages (including the capability packages, which declare runtime deps on bob-shell → pi). Root package is private and never publishes — correctly left alone.

The honest correction: Flint's issue said missing engines means npm REFUSES the install. Measured on Node 20.19.0 / npm 10.8.2, that's only true under engine-strict=true. Default npm emits EBADENGINE as a warning and exits 0. What actually improves is attribution — the warning now names @tpsdev-ai/bob rather than a transitive package the user never asked for. The issue has been corrected.

pi moved to devDependencies

Verified against emitted output: pi survives in the three capability packages' dist/index.d.ts only, never in any .js. bob-shell keeps it — genuine runtime consumer via dist/run.js and dist/persistent.js. Packed-tarball manifests before and after confirm the dependency is gone from all three.

The honest correction: bob#72's headline benefit (tree reduction) does NOT land. cap-flair resolves 131 packages before and after — identical. pi still arrives transitively via cap-flair → bob-shell → pi, because all three caps still declare bob-shell as a runtime dep while importing it type-only. The builder landed the change anyway because declaring a runtime dep you never execute is wrong metadata regardless, but it's a prerequisite for the tree reduction, not the reduction itself. The real blocker is filed as bob#75.

workspace:* → exact version pins

All internal deps now use exact version pins (0.2.0) instead of workspace:*. This is the same fix from bob#70 — npm doesn't rewrite workspace: specs on publish. The lockfile confirms the change is surgical: 14 insertions / 8 deletions, entirely inside the workspaces block.

Builder's probe error

require.resolve returned ERR_PACKAGE_PATH_NOT_EXPORTED and would have read as "pi is gone." A filesystem find revealed the truth. The builder caught and corrected its own error.

Verification

  • 293 pass / 0 fail, 22 files, 650 expect() calls — identical to main baseline
  • Build, typecheck, lint, check-workspace-deps, bun install --frozen-lockfile from cleared node_modules all clean
  • Lockfile surgical at 14 insertions / 8 deletions, no Biome platform-binary churn
  • Dependency Audit red on brace-expansion alone (expected — patch inside 7-day supply-chain window)

This is correct metadata for a first public release. The two honest corrections are properly documented and don't change the correctness of the changes.

@tps-kern tps-kern left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Architecture review — APPROVED

engines >=22.19.0 — derived correctly

The value is read off the installed @earendil-works/pi-coding-agent@0.79.10 manifest, not copied from the issue text. It's the ceiling across the resolved tree (pi packages and undici@8.5.0 all sit at >=22.19.0). Applied to all six publishable packages — correct, since every publishable package either imports pi values directly (bob-shell) or declares a runtime dep on bob-shell which does. Root (private) left alone — bun doesn't enforce engines and it only affects contributor installs.

The honest correction about npm's default behavior (warns, doesn't refuse) is the right framing. The benefit is attribution — the EBADENGINE warning now names @tpsdev-ai/bob rather than a transitive package the user never asked for. With engine-strict=true, it's a clean install-time failure. Worth having before first publish.

pi → devDependencies in 3 caps — correct metadata

Verified against emitted output: pi appears in .d.ts (type-only) but never in .js for cap-discord, cap-flair, and cap-observatory. bob-shell keeps pi as a runtime dependency (dist/run.js and dist/persistent.js import it). Moving pi to devDependencies in the caps is correct — declaring a runtime dependency you never execute is wrong metadata regardless of tree impact.

The packed-tarball manifests confirm pi is gone from the three caps' dependencies and present in devDependencies. The cap-discord e2e test (which builds a real pi AgentSession) still passes after the move.

The tree reduction does NOT land — honest and correct

cap-flair resolves 131 packages before and after because pi still arrives transitively: cap-flair → bob-shell (runtime dep) → pi. The change is a prerequisite for the tree reduction, not the reduction itself. The real blocker (bob#75: caps declare bob-shell as a runtime dep while importing it type-only) is filed separately. This is the right scope boundary — bundling the bob-shell peer/dev decision into a metadata fix would have mixed concerns.

bob#75 call — my read

The caps import bob-shell type-only (in .d.ts, not .js). bob-shell should move to devDependencies in the caps too — the cap package itself never executes bob-shell code. The consumer (bob CLI) already has bob-shell installed; the cap is always loaded by a bob instance that has it. Moving bob-shell to devDependencies in caps would:

  • Not break bob's runtime (bob already has bob-shell)
  • Not break the cap's tests (devDependencies are installed during development)
  • Remove pi from the transitive tree for consumers who install a cap (though caps aren't currently published)
  • Be the correct metadata (type-only import = devDependency)

The follow-up (bob#75) should move bob-shell to devDependencies in the caps. If the caps are ever published independently, a peerDependency on bob-shell would be worth adding (so a consumer installing a cap without bob gets a clear "missing peer" message rather than a silent failure). But that's a publish-scope decision, not this PR.

Lockfile surgical

14 insertions / 8 deletions, entirely inside the workspaces block. Stale workspace:* specs from #70 are cleaned up. No @biomejs platform-binary churn. --frozen-lockfile passes from a cleared node_modules. Correct.

CI

Dependency Audit red on brace-expansion alone — pre-existing, inside the 7-day minimumReleaseAge window until ~2026-07-30. Not a defect in this PR.

Ship it.

@tps-kern tps-kern left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Architectural review — APPROVED.

engines: node >=22.19.0 — correct.

Derived from @earendil-works/pi-coding-agent@0.79.10's declared engines, which is the highest floor in the resolved tree. Applied to all six publishable packages (cli, shell, discord, cap-discord, cap-flair, cap-observatory). Root package is private and never publishes — correctly left alone. The cap packages get the same floor because they declare a runtime dep on bob-shell, which imports pi values. Consistent.

The honest correction about EBADENGINE being a warning (not a hard refusal) under default npm is correctly noted. The engines field still provides attribution — the warning names @tpsdev-ai/bob rather than a transitive package the user never asked for. Worth having regardless.

pi moved from dependencies to devDependencies in three caps — correct.

Verified the claim: the three caps import pi types only (dist/index.d.ts, never in .js). A type-only import should not declare a runtime dependency. bob-shell correctly keeps pi as a runtime dependency (dist/run.js and dist/persistent.js import it at runtime). The packed-tarball manifests confirm pi is gone from the three caps' published dependencies.

The honest correction about tree reduction is correctly framed.

This PR does NOT reduce the install tree — pi still arrives transitively via bob-shell. The three caps still declare bob-shell as a runtime dep while importing it type-only. This PR is a prerequisite for the reduction (correct metadata) but not the reduction itself. The builder landed it anyway because declaring a runtime dep you never execute is wrong metadata regardless. Correct call — metadata should reflect reality even when the immediate benefit is attribution, not tree size.

Read on bob#75 (the peerDependencies-vs-devDependencies decision):

The caps import bob-shell type-only in their compiled JS but declare it as a runtime dependency. The right call is peerDependencies for bob-shell in the caps. The caps are extensions that plug into bob, which provides bob-shell at runtime. The caps don't bundle their own bob-shell — they use the one the consumer has. This is the classic plugin pattern: declare the host as a peer, not a regular dep, so there's one copy and no version conflicts.

Moving bob-shell from dependencies to peerDependencies in the caps would:

  1. Correctly reflect the runtime relationship (consumer provides bob-shell, caps consume it)
  2. Reduce the install tree (no duplicate bob-shell copies under each cap)
  3. Prevent version conflicts (a cap can't accidentally pull in a different bob-shell version)

The caps would also need bob-shell in devDependencies (or peerDependencies + devDependencies) for local development — TypeScript compilation and tests need it resolvable. The standard pattern is: peerDependencies for the runtime contract, devDependencies for local development.

This is the same shape eslint plugins use (peer-depend on eslint), babel plugins use (peer-depend on @babel/core), and PostCSS plugins use (peer-depend on postcss). Bob's caps are the same architectural pattern.

Lockfile is surgical — 14 insertions / 8 deletions, all in the workspaces block. No @biomejs platform-binary churn (avoids the bob#67 trap). ✅

293 pass / 0 fail, identical to baseline. 7/8 CI green, Dependency Audit red on brace-expansion (expected, supply-chain window).

Ship it. The bob#75 peerDependencies migration is the right next step and I've outlined the pattern above.

@tps-flint
tps-flint merged commit 5f5dbc1 into main Jul 27, 2026
7 of 8 checks passed
@tps-flint
tps-flint deleted the fix/publish-metadata branch July 27, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants