ruvnet-brain 4.0.1 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/.claude-plugin/marketplace.json +1 -0
  2. package/README.md +4 -4
  3. package/bin/install.mjs +303 -24
  4. package/console/CONTRACT.md +172 -0
  5. package/console/activity.js +753 -0
  6. package/console/app.js +4189 -0
  7. package/console/architecture.html +1221 -0
  8. package/console/assets/depth-1.webp +0 -0
  9. package/console/assets/depth-2.webp +0 -0
  10. package/console/assets/depth-3.webp +0 -0
  11. package/console/assets/harness-vs-plain.svg +259 -0
  12. package/console/assets/hero.webp +0 -0
  13. package/console/assets/memory.webp +0 -0
  14. package/console/assets/metaharness.svg +247 -0
  15. package/console/index.html +777 -0
  16. package/console/install-architecture.html +162 -0
  17. package/console/install-mockup.html +543 -0
  18. package/console/style.css +2144 -0
  19. package/console/tips.css +926 -0
  20. package/console/tips.html +858 -0
  21. package/console/tips.js +128 -0
  22. package/docs/RELEASE-NOTES-4.0.md +88 -0
  23. package/kb/model-requirements.mjs +37 -6
  24. package/keys/ruvnet-brain-signing.pub.pem +3 -0
  25. package/package.json +8 -22
  26. package/plugin/.claude-plugin/marketplace.json +1 -0
  27. package/plugin/.claude-plugin/plugin.json +2 -3
  28. package/plugin/.codex-plugin/plugin.json +1 -1
  29. package/plugin/commands/brain-console.md +2 -2
  30. package/plugin/commands/configure.md +3 -2
  31. package/plugin/commands/rvbc.md +4 -3
  32. package/plugin/commands/rvcb.md +2 -2
  33. package/plugin/commands/whats-new.md +6 -6
  34. package/plugin/docs/RELEASE-NOTES-4.0.md +88 -0
  35. package/plugin/hooks/hooks.json +1 -2
  36. package/plugin/mcp/managed-cli-interface.mjs +47 -4
  37. package/plugin/mcp/server.mjs +90 -32
  38. package/plugin/scripts/detach.mjs +14 -0
  39. package/plugin/scripts/first-session-worker.mjs +38 -0
  40. package/plugin/scripts/ground-ruvnet.sh +16 -6
  41. package/plugin/scripts/hook-shim.mjs +34 -29
  42. package/plugin/scripts/learn-capture.sh +22 -3
  43. package/plugin/scripts/learn-flush.mjs +21 -4
  44. package/plugin/scripts/runtime-preferences.mjs +269 -0
  45. package/plugin/scripts/session-start-core.mjs +503 -0
  46. package/plugin/scripts/session-start.sh +3 -858
  47. package/plugin/scripts/whats-new.mjs +42 -0
  48. package/plugin/skills/brain-console/SKILL.md +4 -2
  49. package/plugin/skills/release-proof/SKILL.md +98 -0
  50. package/plugin/skills/release-proof/agents/openai.yaml +4 -0
  51. package/plugin/skills/release-proof/references/receipt-contract.md +44 -0
  52. package/plugin/skills/release-proof/scripts/release-proof.mjs +286 -0
  53. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
  54. package/plugin/skills/ruvnet-brain/SKILL.md +22 -7
  55. package/plugin/skills/rvbc/SKILL.md +9 -6
  56. package/plugin/skills/whats-new/SKILL.md +4 -4
  57. package/scripts/adr-backfill.mjs +107 -0
  58. package/scripts/advocacy-outcomes.mjs +808 -0
  59. package/scripts/agentdb-context.mjs +216 -0
  60. package/scripts/agentdb-fleet-doctor.mjs +101 -0
  61. package/scripts/ascii-drift.mjs +236 -0
  62. package/scripts/behavioral-l1-l4.mjs +210 -0
  63. package/scripts/brain-capability-check.mjs +72 -0
  64. package/scripts/brain-grade-groundtruth.mjs +100 -0
  65. package/scripts/brain-latency-50.mjs +227 -0
  66. package/scripts/brain-novice-50.mjs +189 -0
  67. package/scripts/brain-stamp.mjs +94 -0
  68. package/scripts/brain-state.mjs +212 -0
  69. package/scripts/build-bundle.mjs +531 -0
  70. package/scripts/build-concepts.mjs +132 -0
  71. package/scripts/build-l2.mjs +71 -0
  72. package/scripts/build-primer.mjs +73 -0
  73. package/scripts/build-symbols.mjs +68 -0
  74. package/scripts/calibrate-router.mjs +97 -0
  75. package/scripts/capability-audit.mjs +321 -0
  76. package/scripts/capability-registry.mjs +876 -0
  77. package/scripts/check-indexation.mjs +108 -0
  78. package/scripts/check-legibility.mjs +189 -0
  79. package/scripts/ci/build-fixture-kb.mjs +67 -0
  80. package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
  81. package/scripts/ci/learning-replay-recorder.mjs +59 -0
  82. package/scripts/ci/mutate-hook-timeout.mjs +70 -0
  83. package/scripts/ci/stranger-fixture-stage.mjs +17 -0
  84. package/scripts/ci/stranger-scenario.mjs +228 -0
  85. package/scripts/ci/stranger-timeout.mjs +25 -0
  86. package/scripts/ci-verdict.mjs +29 -0
  87. package/scripts/claims-verify.mjs +710 -0
  88. package/scripts/clear-claude-tmp.sh +31 -0
  89. package/scripts/console-engine.mjs +434 -0
  90. package/scripts/console-engine.test.mjs +125 -0
  91. package/scripts/corpus-qa.mjs +250 -0
  92. package/scripts/correction-detect-embed.mjs +346 -0
  93. package/scripts/correction-detect-measure.mjs +270 -0
  94. package/scripts/correction-detect.mjs +686 -0
  95. package/scripts/count-chunks.mjs +54 -0
  96. package/scripts/described-questions.json +30 -0
  97. package/scripts/design-grade.mjs +58 -0
  98. package/scripts/dev-plugin-link.sh +105 -0
  99. package/scripts/distill-project.mjs +200 -0
  100. package/scripts/doc-currency.mjs +801 -0
  101. package/scripts/eval-brain.mjs +244 -0
  102. package/scripts/fix-metaharness-memretrieve.mjs +121 -0
  103. package/scripts/fix-workstream.mjs +291 -0
  104. package/scripts/full-hints.mjs +87 -0
  105. package/scripts/gate.sh +39 -0
  106. package/scripts/gates.mjs +146 -0
  107. package/scripts/gen-console-images.mjs +54 -0
  108. package/scripts/gen-images.mjs +47 -0
  109. package/scripts/git-clone-refresh.mjs +52 -0
  110. package/scripts/git-hooks/pre-push +126 -0
  111. package/scripts/goal-match.mjs +398 -0
  112. package/scripts/goldie-research.mjs +223 -0
  113. package/scripts/goldie-weekly.sh +67 -0
  114. package/scripts/health-repair.mjs +237 -0
  115. package/scripts/helix-scenario-questions.json +10 -0
  116. package/scripts/ingest-gists.mjs +230 -0
  117. package/scripts/ingest-meeting.mjs +115 -0
  118. package/scripts/ingest-repo.mjs +79 -0
  119. package/scripts/install-npx-witness.sh +49 -0
  120. package/scripts/issue-fix.mjs +558 -0
  121. package/scripts/issue-watch.mjs +276 -0
  122. package/scripts/issue4-close-note.md +31 -0
  123. package/scripts/key-canary.mjs +91 -0
  124. package/scripts/latency-to-surface.mjs +233 -0
  125. package/scripts/learning-enable.mjs +380 -0
  126. package/scripts/learning-replay.mjs +1570 -0
  127. package/scripts/learnings.mjs +62 -0
  128. package/scripts/lesson-gate.mjs +680 -0
  129. package/scripts/lesson-lifecycle.mjs +449 -0
  130. package/scripts/lesson-promote.mjs +262 -0
  131. package/scripts/lesson-ratify.mjs +98 -0
  132. package/scripts/lesson-seed.mjs +252 -0
  133. package/scripts/lesson-store.mjs +447 -0
  134. package/scripts/loop-checkpoint.mjs +86 -0
  135. package/scripts/memdb-health.sh +14 -0
  136. package/scripts/memory-doctor.mjs +326 -0
  137. package/scripts/model-catalog.mjs +79 -0
  138. package/scripts/nightly-controller.mjs +66 -0
  139. package/scripts/nightly-gists.sh +72 -0
  140. package/scripts/nightly-wrapper.sh +172 -0
  141. package/scripts/notify.sh +12 -0
  142. package/scripts/npx-witness.sh +56 -0
  143. package/scripts/onboarding-console.mjs +2922 -0
  144. package/scripts/private-fence.mjs +69 -0
  145. package/scripts/proactivity-metrics.mjs +118 -0
  146. package/scripts/proof-questions.json +56 -0
  147. package/scripts/protected-release-invocation.mjs +76 -0
  148. package/scripts/prove.mjs +95 -0
  149. package/scripts/proxy/claude-proxied.sh +57 -0
  150. package/scripts/proxy/proxy-revert.sh +59 -0
  151. package/scripts/proxy/proxy-up.sh +60 -0
  152. package/scripts/proxy/proxy-verify.mjs +142 -0
  153. package/scripts/publication-receipt.mjs +307 -0
  154. package/scripts/published-surface-probe.mjs +241 -0
  155. package/scripts/qe/card-lane-gate.mjs +162 -0
  156. package/scripts/qe/session-start-gate.mjs +229 -0
  157. package/scripts/qe/ux-suite.mjs +323 -0
  158. package/scripts/reconcile-project.mjs +0 -0
  159. package/scripts/record-lesson.mjs +113 -0
  160. package/scripts/refresh-model-catalog.mjs +99 -0
  161. package/scripts/release-authority.mjs +93 -0
  162. package/scripts/release-proof.mjs +9 -0
  163. package/scripts/release-vector.mjs +281 -0
  164. package/scripts/release.mjs +439 -0
  165. package/scripts/remedy-registry.mjs +247 -0
  166. package/scripts/rerank-cap-eval.mjs +265 -0
  167. package/scripts/rerank-cap-warm-ab.mjs +129 -0
  168. package/scripts/route-cheap.mjs +20 -15
  169. package/scripts/router-utilization.mjs +182 -0
  170. package/scripts/routing-flywheel.mjs +596 -0
  171. package/scripts/rvf-generation.mjs +104 -0
  172. package/scripts/rvf-index-audit.mjs +138 -0
  173. package/scripts/self-update.mjs +296 -0
  174. package/scripts/selfcheck.mjs +7 -1
  175. package/scripts/sign-bundle.mjs +69 -0
  176. package/scripts/signal-watch.mjs +171 -0
  177. package/scripts/stabilization-receipt.mjs +108 -0
  178. package/scripts/stack-sync.mjs +469 -0
  179. package/scripts/stamp-existing-rvf-generations.mjs +53 -0
  180. package/scripts/stamp-sweep.mjs +144 -0
  181. package/scripts/status-honesty.mjs +102 -0
  182. package/scripts/sync-version.mjs +217 -0
  183. package/scripts/token-report.mjs +102 -0
  184. package/scripts/top100-benchmark.mjs +479 -0
  185. package/scripts/top100-corpus.mjs +112 -0
  186. package/scripts/top100-semantic-assertions.mjs +449 -0
  187. package/scripts/update-apply.mjs +9 -0
  188. package/scripts/upgrade-notice.mjs +14 -0
  189. package/scripts/verify-bundle.mjs +51 -0
  190. package/scripts/verify-channels.mjs +184 -0
  191. package/scripts/verify-model-catalog.mjs +104 -0
  192. package/scripts/verify-nightly-close-issue4.sh +31 -0
  193. package/scripts/version.mjs +40 -0
  194. package/scripts/wired-check.mjs +867 -0
  195. package/plugin/scripts/finalize-token-meter.mjs +0 -25
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ // ingest-meeting.mjs — turn a Read.ai meeting transcript into brain passages (the ruv-meetings store).
3
+ //
4
+ // WHY (2026-07-17, Stuart): rUv's community calls are where he explains HOW HE THINKS — the
5
+ // doctrine behind the repos (meta-proxy load balancing, flywheel economics, oracle memory,
6
+ // harness-vs-model philosophy). The repos hold the WHAT; the calls hold the WHY. This makes them
7
+ // citable: search_ruvnet returns "ruv-meetings/<date>-<slug>" passages alongside source code.
8
+ //
9
+ // PRIVACY — this store is PRIVATE BY CONSTRUCTION and that is load-bearing:
10
+ // • kb/ruv-meetings.* is gitignored (never committed to the public repo)
11
+ // • "ruv-meetings" is listed in kb/PRIVATE-STORES.json, so scripts/build-bundle.mjs excludes it
12
+ // from every publishable bundle (same fail-closed fence as the cognitum stores)
13
+ // Participants of a community call did not consent to public redistribution. Local brain only.
14
+ //
15
+ // Output contract mirrors ingest-gists.mjs: kb/ruv-meetings.passages.jsonl (+ .meta.json), then:
16
+ // node kb/forge-big.mjs both --dir kb --name ruv-meetings
17
+ //
18
+ // Usage:
19
+ // node scripts/ingest-meeting.mjs --file "docs/Ruv_advice/Ruv hackerspace Transcript.txt" \
20
+ // --title "Ruv hackerspace" --date 2026-07-16 [--append]
21
+ // --append keeps existing passages (multi-meeting store grows over time); default rebuilds.
22
+
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
26
+
27
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
+ const KB = path.join(path.dirname(__dirname), 'kb');
29
+ const OUT_PASSAGES = path.join(KB, 'ruv-meetings.passages.jsonl');
30
+ const OUT_META = path.join(KB, 'ruv-meetings.meta.json');
31
+
32
+ const args = process.argv.slice(2);
33
+ const get = (k) => { const i = args.indexOf('--' + k); return i >= 0 ? args[i + 1] : null; };
34
+ const file = get('file');
35
+ const title = get('title') || 'community call';
36
+ const date = get('date') || 'unknown-date';
37
+ const APPEND = args.includes('--append');
38
+ if (!file || !fs.existsSync(file)) { console.error('need --file <transcript.txt>'); process.exit(2); }
39
+
40
+ const raw = fs.readFileSync(file, 'utf8');
41
+
42
+ // Read.ai format: blocks of "H:MM(:SS) - Speaker Name\n<text>\n". Parse into turns, then pack turns
43
+ // into passages of ~2,200 chars, never splitting inside a turn — a speaker's thought stays whole
44
+ // (the same clean-thought law the explainer's line breaks follow).
45
+ const turnRe = /^(\d{1,2}:\d{2}(?::\d{2})?) - (.+)$/;
46
+ const lines = raw.split('\n');
47
+ const turns = [];
48
+ let cur = null;
49
+ for (const line of lines) {
50
+ const m = line.match(turnRe);
51
+ if (m) {
52
+ if (cur && cur.text.trim()) turns.push(cur);
53
+ cur = { ts: m[1], speaker: m[2].trim(), text: '' };
54
+ } else if (cur) {
55
+ cur.text += (cur.text ? '\n' : '') + line;
56
+ }
57
+ }
58
+ if (cur && cur.text.trim()) turns.push(cur);
59
+
60
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
61
+ const HEADER = `SOURCE: rUv community call — "${title}" (${date}), Read.ai transcript.\n`
62
+ + `STATUS: spoken, informal — rUv's live thinking and practice. Treat as INTENT and doctrine;\n`
63
+ + `verify shipped behavior against repo source before asserting a capability exists.\n`;
64
+
65
+ const TARGET = 2200;
66
+ const passages = [];
67
+ let buf = [];
68
+ let bufLen = 0;
69
+ let firstTs = null;
70
+ const flush = () => {
71
+ if (!buf.length) return;
72
+ const seg = String(passages.length).padStart(4, '0');
73
+ passages.push({
74
+ id: String(passages.length),
75
+ // UNIQUE path per segment (verified fix, 2026-07-19). A shared path made forge-ask.mjs doc-collapse
76
+ // crush every segment of a meeting into ONE ~12k window — the 317-passages→4-paths collapse that
77
+ // made meeting recall unretrievable (facts were IN the corpus but never surfaced; meeting recall
78
+ // 3/28). One path = one segment, the same granular convention ingest-gists.mjs uses (gist-id/file#N).
79
+ // With this + the reader's transcript-store BM25 candidates, meeting recall went 3/28 → 25/28. See
80
+ // docs/adr/0025.
81
+ path: `ruv-meetings/${date}/${slug}-seg-${seg}`,
82
+ title: `${title} — ${firstTs}`,
83
+ text: `${HEADER}segment starting ${firstTs} —\n\n` + buf.join('\n\n'),
84
+ meeting: slug, date, ts: firstTs,
85
+ });
86
+ buf = []; bufLen = 0; firstTs = null;
87
+ };
88
+ for (const t of turns) {
89
+ const block = `[${t.ts}] ${t.speaker}:\n${t.text.trim()}`;
90
+ if (bufLen + block.length > TARGET && buf.length) flush();
91
+ if (!buf.length) firstTs = t.ts;
92
+ buf.push(block); bufLen += block.length;
93
+ }
94
+ flush();
95
+
96
+ let existing = [];
97
+ if (APPEND && fs.existsSync(OUT_PASSAGES)) {
98
+ existing = fs.readFileSync(OUT_PASSAGES, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
99
+ // Re-ingesting the same meeting replaces its old passages instead of duplicating them.
100
+ existing = existing.filter((p) => p.meeting !== slug);
101
+ }
102
+ const all = [...existing, ...passages].map((p, i) => ({ ...p, id: String(i) }));
103
+ fs.writeFileSync(OUT_PASSAGES, all.map((p) => JSON.stringify(p)).join('\n') + '\n');
104
+ fs.writeFileSync(OUT_META, JSON.stringify({
105
+ builder: 'ingest-meeting',
106
+ builtUtc: new Date().toISOString(),
107
+ private: true,
108
+ fence: 'kb/PRIVATE-STORES.json + .gitignore — never bundle, never commit',
109
+ meetings: [...new Set(all.map((p) => `${p.date} ${p.meeting}`))],
110
+ passages: all.length,
111
+ }, null, 2) + '\n');
112
+
113
+ console.log(`ingest-meeting: ${turns.length} turns → ${passages.length} passages for "${title}" (${date})`);
114
+ console.log(`total store: ${all.length} passages → ${path.relative(process.cwd(), OUT_PASSAGES)}`);
115
+ console.log(`next: node kb/forge-big.mjs both --dir kb --name ruv-meetings (embed → ruv-meetings.big.rvf)`);
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // ingest-repo.mjs — pull ANY github.com/ruvnet/<name> repo into the brain ON DEMAND.
3
+ //
4
+ // clone (shallow) -> transactional incremental bge-768 refresh -> symbol index
5
+ //
6
+ // After it finishes the new repo is searchable immediately: search_ruvnet / forge-ask-all discover
7
+ // every <name>.rvf in the kb dir at query time, so no server restart is needed. Use this when a
8
+ // project needs a RuvNet repo the brain doesn't cover yet — load it first, don't guess about it.
9
+ //
10
+ // node scripts/ingest-repo.mjs --name rvm
11
+ //
12
+ // For full capability-confidence on the new repo (so it's never wrongly doubted), also build its
13
+ // prose primer afterwards:
14
+ // node scripts/build-primer.mjs --name <name> --variant big
15
+ // node scripts/build-concepts.mjs && node kb/forge-big.mjs both --dir kb --name concepts
16
+ //
17
+ // Portable: paths resolve relative to this file; model cache via KB_MODEL_CACHE (or auto-download).
18
+ import path from 'node:path';
19
+ import fs from 'node:fs';
20
+ import { execFileSync } from 'node:child_process';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { FULL_HINTS, KEEP_DIRS } from './full-hints.mjs';
23
+
24
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
25
+ const arg = (f, d) => { const i = process.argv.indexOf(f); return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : d; };
26
+ const NAME = arg('--name');
27
+ // --org lets us ingest ecosystem repos that live in a rUv COLLABORATOR org (e.g. the QE fleet at
28
+ // proffesor-for-testing/agentic-qe), not just github.com/ruvnet/*. Defaults to ruvnet.
29
+ const ORG = arg('--org', 'ruvnet');
30
+ const SOURCE = arg('--source');
31
+ if (!NAME) {
32
+ console.error('Usage: node scripts/ingest-repo.mjs --name <repo> [--org <github-org>] [--source <local-checkout>]');
33
+ process.exit(2);
34
+ }
35
+
36
+ const KB = path.join(ROOT, 'kb');
37
+ const CLONES = path.join(ROOT, 'clones');
38
+ const kb = NAME.toLowerCase();
39
+ const dir = SOURCE ? path.resolve(SOURCE) : path.join(CLONES, NAME);
40
+ const env = { ...process.env };
41
+ const run = (cmd, args, opts) => execFileSync(cmd, args, { stdio: 'inherit', ...opts });
42
+
43
+ fs.mkdirSync(CLONES, { recursive: true });
44
+ if (SOURCE) {
45
+ if (!fs.existsSync(path.join(dir, '.git'))) {
46
+ console.error(`[source] ${dir} is not a git checkout`);
47
+ process.exit(2);
48
+ }
49
+ console.log(`[source] local candidate ${dir}`);
50
+ } else if (!fs.existsSync(path.join(dir, '.git'))) {
51
+ console.log(`[clone] ${ORG}/${NAME}`);
52
+ run('git', ['clone', '--depth', '1', `https://github.com/${ORG}/${NAME}`, dir]);
53
+ } else {
54
+ console.log(`[update] ${ORG}/${NAME}`);
55
+ run('git', ['-C', dir, 'fetch', '--depth', '1', 'origin']);
56
+ run('git', ['-C', dir, 'reset', '--hard', 'origin/HEAD']);
57
+ }
58
+
59
+ const url = process.env.RUVNET_CANONICAL_URL || 'https://raw.githubusercontent.com/stuinfla/ruvnet-brain/main/kb';
60
+ // Depth config (--full / --keep) comes from the SHARED per-repo map, overridable per-invocation.
61
+ // Before this, ingest-repo.mjs never passed --full: any repo rebuilt through it silently lost its
62
+ // full-body source indexing (the 2026-07-10 depth-restore run zeroed ruvector this way).
63
+ const FULL = arg('--full', FULL_HINTS[kb] || '');
64
+ const KEEP = arg('--keep', KEEP_DIRS[kb] || '');
65
+ const buildArgs = ['forge-refresh.mjs', '--repo', dir, '--out', '.', '--name', kb, '--canonical-url', url];
66
+ if (FULL) buildArgs.push('--full', FULL);
67
+ if (KEEP) buildArgs.push('--keep', KEEP);
68
+ console.log(`[refresh bge-768] ${kb}${FULL ? ' (--full: ' + FULL.split(',').length + ' prefixes)' : ''}${KEEP ? ' (--keep: ' + KEEP + ')' : ''}`);
69
+ run('node', buildArgs, { cwd: KB, env });
70
+ console.log(`[symbols] ${kb}`);
71
+ try { run('node', ['scripts/build-symbols.mjs', '--name', kb], { cwd: ROOT, env }); } catch { console.log(' (symbols skipped — sparse repo)'); }
72
+
73
+ const ok = fs.existsSync(path.join(KB, `${kb}.big.rvf`))
74
+ && fs.existsSync(path.join(KB, `${kb}.passages.jsonl`))
75
+ && fs.existsSync(path.join(KB, `${kb}.meta.json`));
76
+ console.log(ok
77
+ ? `\n[done] ${NAME} ingested → searchable now via search_ruvnet (no restart). For capability-confidence, build its primer next.`
78
+ : `\n[FAIL] ${NAME}: expected stores missing after build.`);
79
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # install-npx-witness.sh — install the npx witness as a launchd WatchPaths job. Idempotent.
3
+ #
4
+ # The job fires the instant ~/.npm/_npx changes (any creation/deletion, by anything), runs
5
+ # scripts/npx-witness.sh, and is wrapped in job-heartbeat.sh so it PROVES it ran (standing order:
6
+ # launchd reports exit 0 for a job that never ran — silence is not health).
7
+ #
8
+ # Zero sudo. Zero interference: the job only appends to ~/.claude/logs/npx-witness.log.
9
+ # Uninstall: launchctl bootout gui/$(id -u)/com.ruvnet.npx-witness && rm the plist.
10
+
11
+ set -euo pipefail
12
+
13
+ REPO="$(cd "$(dirname "$0")/.." && pwd)"
14
+ LABEL="com.ruvnet.npx-witness"
15
+ PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
16
+ HB="$REPO/scripts/job-heartbeat.sh"
17
+ WITNESS="$REPO/scripts/npx-witness.sh"
18
+
19
+ [ -f "$HB" ] || { echo "FAIL: $HB missing"; exit 1; }
20
+ [ -f "$WITNESS" ] || { echo "FAIL: $WITNESS missing"; exit 1; }
21
+ chmod +x "$WITNESS"
22
+
23
+ cat > "$PLIST" <<EOF
24
+ <?xml version="1.0" encoding="UTF-8"?>
25
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
26
+ <plist version="1.0"><dict>
27
+ <key>Label</key><string>$LABEL</string>
28
+ <key>ProgramArguments</key><array>
29
+ <string>/bin/sh</string>
30
+ <string>$HB</string>
31
+ <string>$LABEL</string>
32
+ <string>--</string>
33
+ <string>/bin/bash</string>
34
+ <string>$WITNESS</string>
35
+ </array>
36
+ <key>WatchPaths</key><array><string>$HOME/.npm/_npx</string></array>
37
+ <key>RunAtLoad</key><false/>
38
+ <key>StandardOutPath</key><string>$HOME/Library/Logs/npx-witness.out</string>
39
+ <key>StandardErrorPath</key><string>$HOME/Library/Logs/npx-witness.err</string>
40
+ </dict></plist>
41
+ EOF
42
+
43
+ launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
44
+ launchctl bootstrap "gui/$(id -u)" "$PLIST"
45
+
46
+ echo "installed: $PLIST"
47
+ echo "loaded: $(launchctl list | grep -c "$LABEL") (1 = live)"
48
+ echo "log: ~/.claude/logs/npx-witness.log"
49
+ echo "heartbeat: ~/.cache/ruvnet-brain/heartbeats/$LABEL.json"