ADR-263 (@ruvnet/ruview 0.2.0), O1-O9: - claim-check fails closed on empty input (CLI exit 2, empty_text tool error) - MCP stdio server dispatches tools/call asynchronously (promise-based spawn); ping answers while a 3s fake verify runs — pinned by new e2e test - optionalDependencies dropped: cold npx installs exactly 1 package (MEASURED: was 4 pkgs/620kB/71 files via npm i in a clean prefix) - bounded rolling output tails replace spawnSync 1MiB maxBuffer - node_monitor port passed via sys.argv, never spliced into python -c source - serverInfo.version read from package.json; resources/prompts stubs - skills single-sourced: prepack sync script generates .claude/skills/ copies - which() = memoized dep-free PATH scan - tools underscore-canonical (ruview_claim_check, ...) + dotted aliases - guardrail precision: word-boundary map/f1/auc/iou, code-span + F1/O2 label scrubbing, quantitative-claims-only; packaging reproducer hints - 30/30 tests (was 17), incl. concurrency e2e + fail-open regression pins ADR-264 (@ruvnet/rvagent 0.2.0), O1-O9: - exports fixed: types-first, phantom dist/index.cjs require target removed - tarball map-free: 127,704B unpacked / 46 files / 0 maps (MEASURED, npm pack --dry-run; was 188kB incl. 44 maps referencing unshipped src) - Streamable HTTP actually wired behind RVAGENT_HTTP_PORT: one transport + one MCP server per session (mcp-session-id routing), 1MiB body cap (413), port-aware localhost origin gate; dual-transport description now true - tools renamed underscore-canonical with dotted router-only aliases - single Zod validation gate; advertised inputSchema generated from the same Zod source (zod-to-json-schema) - train_count: parent log fds closed (was leaking 2/job); job records persisted to <jobsDir>/<id>.json (job_status survives restarts); bounded log-tail reads - detectCogBinary probes its candidates instead of dead-coding them - version from package.json; @types/express dropped; @types/jest -> 29 - README rewritten to match reality (no phantom subcommands/policy layer) - 99/99 jest tests (incl. new session/body-cap suite + previously-broken manifest suite); stdio handshake + HTTP session flow smoke-tested live ADR-265 D1-D4: - .github/workflows/npm-packages.yml: 3-package x Node 20/22 gate — tests, version-literal grep (D3), pack-content/size gate, tarball-install smoke test (catches the ADR-264 F1 class), README claim-check (D4) - .github/workflows/ruview-npm-release.yml: publish from CI only with npm publish --provenance - @ruv/ruview-cli bin renamed ruview-cli (ruview bin belongs to @ruvnet/ruview); version single-sourced - ci.yml NODE_VERSION 18 -> 20 ADR statuses updated to Accepted/implemented; harness manifest re-pinned; ADR-263/264/265 + both package READMEs pass claim-check. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01WrGfTGKv1oWZ6iwXZACULz
6.9 KiB
ADR-265: RuView npm Distribution Strategy — CI Gate, Provenance, Version Single-Sourcing, Namespace
| Field | Value |
|---|---|
| Status | Accepted — D1–D4 implemented: .github/workflows/npm-packages.yml (matrix gate: tests, version-literal grep, pack-content/size gate, tarball-install smoke test, README claim-check), .github/workflows/ruview-npm-release.yml (publish-from-CI with npm publish --provenance), version single-sourcing (all three packages read package.json), ruview bin owned by @ruvnet/ruview (@ruv/ruview-cli bin renamed ruview-cli), ci.yml NODE_VERSION 18→20. D5 (no workspace) stands as recorded |
| Date | 2026-07-02 |
| Deciders | ruv |
| Codename | RUVIEW-NPM-DIST |
| Supersedes / amends | none (cross-cutting layer above ADR-263 and ADR-264; complements ADR-182 P3/P4) |
Context
The monorepo now ships (or stages) three Node packages with no shared distribution engineering:
| Package | Dir | Published | Bin(s) | Tests in CI |
|---|---|---|---|---|
@ruvnet/ruview |
harness/ruview/ |
0.1.0 (live) | ruview |
none |
@ruvnet/rvagent |
tools/ruview-mcp/ |
0.1.0 (live) | rvagent, ruview-mcp |
none |
@ruv/ruview-cli |
tools/ruview-cli/ |
private | ruview (collides) |
none |
Cross-cutting facts established during the ADR-263/264 reviews:
- Zero CI coverage. No workflow under
.github/workflows/references any of the three directories. Two of the packages are live on the registry and were published from a laptop state CI never saw. Meanwhile the Rust side has a 1,031+-test gate and a witness-bundle culture (ADR-028) — the npm surface is the only shipped artifact class with no verification gate at all. ci.ymlpinsNODE_VERSION: '18'while all three packages declareengines.node >= 20.- Version triplication. Each package hardcodes its version in source at
least once beyond package.json (harness
SERVER_INFO, rvagentPACKAGE_VERSION, cli.version("0.0.1")). - Bin-name collision. Two packages claim the
ruviewbin. - No provenance. Neither published package carries npm provenance attestations, in a project whose differentiator is signed, reproducible evidence (ADR-028 witness bundles, ADR-182 P4 ed25519/SLSA design).
- No pack-content gate. ADR-264 F1/F2 (broken
requiretarget, 33% dead map weight — MEASURED, tarball listing — and a phantomCHANGELOG.mdinfiles) are exactly the defect class annpm pack --dry-runassertion catches in seconds.
Decision
Adopt one distribution layer for all Node packages. Per-package code fixes live in ADR-263/264; this ADR fixes the machinery around them.
D1 — One npm-packages.yml CI workflow (the gate)
Matrix over [harness/ruview, tools/ruview-mcp, tools/ruview-cli] ×
Node [20, 22]:
npm ciwhere a lockfile is committed (the TS packages); the harness installs withnpm install— repo policy gitignores lockfiles underharness/, and the package is dependency-free after ADR-263 O3 so there is nothing to pin.npm test(harness:node --test test/*.test.mjs— pin the glob form, the directory form fails on Node 22; TS packages: build + jest ornode:testper ADR-264 O8).- Pack gate:
npm pack --dry-run --jsonasserted against a checked-in expected file list + a max unpacked-size budget per package (harness ≤ 60 kB; rvagent ≤ 130 kB post ADR-264 O2). Any new/missing/renamed shipped file is a reviewed diff, not a surprise. - Tarball smoke test: install the packed tarball into a temp dir; run
ruview --version,ruview doctor,rvagent--help-equivalent, and a Nodeimport()of each declared export condition — this is the test that would have caught ADR-264 F1 (require→ nonexistentdist/index.cjs). - Bump
ci.ymlNODE_VERSIONto'20'(independent of the matrix above).
D2 — Publish only from CI, with provenance
Manual npm publish from laptops stops. A tag-triggered workflow
(ruview-npm-release.yml, mirroring the firmware release discipline) runs the
D1 gate, then npm publish --provenance --access public under the GitHub OIDC
token. Consequence: every published version is attested to a public commit +
workflow run — the npm-side analogue of the ADR-028 witness bundle. The
prepublishOnly script in each package runs the pack gate locally as a
belt-and-braces (publishing outside CI fails loudly, not silently).
D3 — Version single-sourcing
Rule: package.json is the only place a version string lives. Runtime code
reads it (createRequire(import.meta.url)('./package.json').version or a
build-time define for the TS packages). CI greps for \d+\.\d+\.\d+ literals in
src/ of each package and fails on match (allowlist: test fixtures). This
retires ADR-263 F6 and ADR-264 F9 permanently instead of per-incident.
D4 — Namespace and bin ownership
@ruvnet/ruviewowns theruviewbin (it is the published front door, ADR-182).@ruv/ruview-clirenames its bin or folds intorvagent(ADR-264 O9) — decided here so neither package ADR relitigates it.- New Node packages in this repo use the
@ruvnet/scope (the@ruv/scope holdsrvcsilegacies; do not grow it). - Every package README + description must pass
npx ruview claim-check— enforced in the D1 gate. The guardrail package linting its sibling packages' claims is the cheapest dogfooding we have (ADR-264 F3 is the standing example of why).
D5 — Shared-code policy (bounded)
Do not introduce an npm workspace or a shared runtime package yet: three
packages, two of which may merge (ADR-264 O9), do not justify workspace
machinery, and the harness's zero-dep property is load-bearing. Revisit if a
fourth package appears or if the http/cog/config duplication survives the
ADR-264 O9 fold. Record the duplication as intentional in each file header (the
CLI already does this).
Consequences
- The npm artifacts get the same class of gate the Rust workspace has had since
ADR-028: no publish without tests, no shipped file set without an asserted
manifest, no version without provenance. The two defects that reached the
registry (broken
requirecondition, dead maps) become CI-impossible. - Cold-path costs stay near zero: the D1 matrix is 6 fast jobs (the harness suite runs in ~108 ms MEASURED; TS builds dominate at a few tens of seconds).
- Publishing gains one constraint (must go through CI) and loses one failure mode (laptop-state publishes) — the right trade for a project whose brand is reproducible evidence.
- D3's grep gate is blunt but cheap; if it over-fires, scope it to
version-adjacent identifiers before weakening it. - Follow-ups tracked elsewhere: per-package code fixes (ADR-263 O1–O8, ADR-264 O1–O9); ADR-182 P4 (metaharness router + ed25519 provenance chain) remains the deeper provenance story that D2's npm attestations complement, not replace.