mirror of
https://github.com/ruvnet/RuView
synced 2026-08-04 19:31:42 +00:00
e6f26e9ac9
* docs(adr): deep review of the RuView npm surface — ADR-263/264/265 optimization strategies
ADR-263 — @ruvnet/ruview@0.1.0 harness review (O1–O9):
- HIGH: claim-check CLI fails open on empty input (no --text/--file -> PASS exit 0)
- HIGH: MCP stdio server head-of-line blocking (spawnSync verify/calibrate up to 600s)
- MEASURED: optionalDependencies triple the cold npx install (4 pkgs/620kB/71 files
vs 1 pkg/172kB/22 files with --omit=optional) for a path that never imports them
- maxBuffer truncation, python -c port interpolation, version drift, duplicate skills,
guardrail METRIC_TERMS substring false positives ('map'/'F1' — found by dogfooding
claim-check on these very ADRs), zero CI
ADR-264 — @ruvnet/rvagent@0.1.0 + @ruv/ruview-cli review (O1–O9), verified against
the published registry tarball:
- HIGH: exports.require -> dist/index.cjs which is never built nor published
- MEASURED: 44 dead source-map files = 62,698B of the 188kB unpacked payload
- stdio-only server described as dual-transport; mixed dot/underscore tool names;
double Zod validation + hand-duplicated advertised schemas; 2-fd leak per training
job; unbounded body in the unwired HTTP scaffold; dead detectCogBinary candidates;
ruview bin-name collision
ADR-265 — cross-cutting npm distribution strategy: npm-packages.yml CI matrix
(test + pack-content/size gate + tarball-install smoke test), publish-from-CI-only
with npm provenance, version single-sourcing from package.json, bin/namespace
ownership (ruview bin belongs to @ruvnet/ruview), claim-check on package READMEs.
Docs only — no runtime code changed. Index/CHANGELOG/CLAUDE.md/README counts updated.
Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WrGfTGKv1oWZ6iwXZACULz
* fix(npm): implement ADR-263/264/265 — harness fail-closed + async MCP, rvagent packaging/transport/naming, npm CI+provenance gate
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
* perf(rvagent): lazy-load HTTP transport + memoize generated tool schemas
stdio time-to-first-response ~242ms -> ~189ms (-22%; MEASURED, median of
repeated initialize round-trips against dist/index.js in this container).
- ./http-transport.js now imported lazily inside the RVAGENT_HTTP_PORT
branch: it chain-loads the MCP SDK streamableHttp module (~48ms MEASURED
via per-module import() timing) which the default stdio path never uses
- toolInputJsonSchema memoized per tool: schemas are static for the process
lifetime; under the session-per-server HTTP model every session calls
tools/list, so stop re-walking the Zod tree each time
No behavior change: 99/99 jest tests; HTTP session flow re-smoke-tested
through the lazy import path (initialize -> 200 + mcp-session-id).
Profiled @ruvnet/ruview too and left it alone: 50ms CLI startup vs ~29ms
bare 'node -e ""' floor on the same box (MEASURED) — already near the
interpreter floor with zero dependencies.
Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WrGfTGKv1oWZ6iwXZACULz
* ci(ruview-cli): pass jest --passWithNoTests so the private no-test package doesn't fail the npm-packages matrix
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(npm): address 10 verified review findings in harness + rvagent before 0.2.0 publish
harness/ruview (@ruvnet/ruview):
- guardrails: digit gate now sees numbers inside code spans; F1-style
metric tokens followed by ':' or a nearby number are no longer scrubbed
(fail-open regressions in the honesty gate)
- mcp-server: tools/call requests serialize through a FIFO promise chain
(hardware/mutating tools never overlap) while ping/tools/list stay
immediate; stdin close drains in-flight responses before exit
- tools: which() no longer memoizes negative lookups
tools/ruview-mcp (@ruvnet/rvagent):
- index: realpath invoked-directly guard — library import no longer
connects a stdio transport to the consumer's process
- http-transport: explicit allowedOrigins is exact-match only (localhost
any-port convenience applies only with no configured allowlist);
session map gains maxSessions=64 + 5min idle TTL sweep
- train-count: job records persist the child pid and reconcile stale
'running' status after a server restart (exit-code marker or dead pid)
- config: cog binary candidates ordered by process.arch
.github/workflows/ruview-npm-release.yml: port the full ADR-265 D1 gate
(version-literal check, unpacked-size budget, tarball-install smoke test)
from npm-packages.yml so the publish path enforces what the header claims.
Tests: harness 30→36, rvagent 99→112, all passing.
Co-Authored-By: claude-flow <ruv@ruv.net>
---------
Co-authored-by: Claude <noreply@anthropic.com>
125 lines
6.9 KiB
Markdown
125 lines
6.9 KiB
Markdown
# 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.yml` pins `NODE_VERSION: '18'`** while all three packages declare
|
||
`engines.node >= 20`.
|
||
- **Version triplication.** Each package hardcodes its version in source at
|
||
least once beyond package.json (harness `SERVER_INFO`, rvagent
|
||
`PACKAGE_VERSION`, cli `.version("0.0.1")`).
|
||
- **Bin-name collision.** Two packages claim the `ruview` bin.
|
||
- **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 `require` target, 33% dead map weight — MEASURED, tarball listing — and a phantom
|
||
`CHANGELOG.md` in `files`) are exactly the defect class an
|
||
`npm pack --dry-run` assertion 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]`:
|
||
|
||
1. `npm ci` where a lockfile is committed (the TS packages); the harness
|
||
installs with `npm install` — repo policy gitignores lockfiles under
|
||
`harness/`, and the package is dependency-free after ADR-263 O3 so there is
|
||
nothing to pin.
|
||
2. `npm test` (harness: `node --test test/*.test.mjs` — pin the glob form,
|
||
the directory form fails on Node 22; TS packages: build + jest or `node:test`
|
||
per ADR-264 O8).
|
||
3. **Pack gate:** `npm pack --dry-run --json` asserted 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.
|
||
4. **Tarball smoke test:** install the packed tarball into a temp dir; run
|
||
`ruview --version`, `ruview doctor`, `rvagent` `--help`-equivalent, and a
|
||
Node `import()` of each declared export condition — this is the test that
|
||
would have caught ADR-264 F1 (`require` → nonexistent `dist/index.cjs`).
|
||
5. Bump `ci.yml` `NODE_VERSION` to `'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/ruview` **owns the `ruview` bin** (it is the published front door,
|
||
ADR-182). `@ruv/ruview-cli` renames its bin or folds into `rvagent`
|
||
(ADR-264 O9) — decided here so neither package ADR relitigates it.
|
||
- New Node packages in this repo use the `@ruvnet/` scope (the `@ruv/` scope
|
||
holds `rvcsi` legacies; 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 `require` condition, 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.
|