Files
ruvnet--RuView/harness/ruview/scripts/sync-skills.mjs
T
Claude 9109269329 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
2026-07-02 13:03:20 +00:00

38 lines
1.5 KiB
JavaScript

#!/usr/bin/env node
// SPDX-License-Identifier: MIT
// ADR-263 O7: skills/*.md is the single source of truth; the host-projected
// copies (.claude/skills/<name>/SKILL.md) are GENERATED here at pack time.
// Run with --check to verify without writing (used by tests/CI).
import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
const SRC = join(ROOT, 'skills');
const DST = join(ROOT, '.claude', 'skills');
const checkOnly = process.argv.includes('--check');
let drift = 0;
for (const f of readdirSync(SRC).filter((f) => f.endsWith('.md'))) {
const name = f.replace(/\.md$/, '');
const src = readFileSync(join(SRC, f), 'utf8');
const dstDir = join(DST, name);
const dstFile = join(dstDir, 'SKILL.md');
const current = existsSync(dstFile) ? readFileSync(dstFile, 'utf8') : null;
if (current === src) continue;
drift++;
if (checkOnly) {
console.error(`DRIFT: .claude/skills/${name}/SKILL.md != skills/${f}`);
} else {
mkdirSync(dstDir, { recursive: true });
writeFileSync(dstFile, src);
console.error(`synced .claude/skills/${name}/SKILL.md`);
}
}
if (checkOnly && drift > 0) {
console.error(`sync-skills --check: ${drift} file(s) out of sync — run \`npm run sync-skills\`.`);
process.exit(1);
}
console.error(`sync-skills: ${drift === 0 ? 'all in sync' : `${drift} file(s) ${checkOnly ? 'OUT OF SYNC' : 'synced'}`}`);