ruvnet-brain 3.9.134-dev → 4.0.2

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 (218) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/README.md +5 -5
  3. package/bin/install.mjs +382 -36
  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/kb/zip-extract.mjs +53 -14
  25. package/keys/ruvnet-brain-signing.pub.pem +3 -0
  26. package/package.json +14 -22
  27. package/plugin/.claude-plugin/marketplace.json +14 -0
  28. package/plugin/.claude-plugin/plugin.json +22 -0
  29. package/plugin/.codex-plugin/plugin.json +21 -0
  30. package/plugin/.mcp.json +8 -0
  31. package/plugin/commands/brain-console.md +16 -0
  32. package/plugin/commands/configure.md +33 -0
  33. package/plugin/commands/rvbc.md +79 -0
  34. package/plugin/commands/rvcb.md +16 -0
  35. package/plugin/commands/whats-new.md +57 -0
  36. package/plugin/hooks/codex-hooks.json +160 -0
  37. package/plugin/hooks/hook-contracts.json +77 -0
  38. package/plugin/hooks/hooks.json +202 -0
  39. package/plugin/mcp/managed-cli-interface.mjs +47 -4
  40. package/plugin/mcp/server.mjs +56 -6
  41. package/plugin/scripts/anticipate.sh +534 -0
  42. package/plugin/scripts/codex-hook-adapter.mjs +96 -0
  43. package/plugin/scripts/continuation-gate.mjs +267 -0
  44. package/plugin/scripts/design-wall.sh +137 -0
  45. package/plugin/scripts/detach.mjs +182 -0
  46. package/plugin/scripts/first-session-worker.mjs +38 -0
  47. package/plugin/scripts/gate-receipt.sh +35 -0
  48. package/plugin/scripts/ground-before-write.sh +199 -0
  49. package/plugin/scripts/ground-ruvnet.sh +517 -0
  50. package/plugin/scripts/grounding-stamp.sh +113 -0
  51. package/plugin/scripts/grounding-substance.mjs +595 -0
  52. package/plugin/scripts/hijack-ruvnet.sh +81 -0
  53. package/plugin/scripts/hook-input.mjs +558 -0
  54. package/plugin/scripts/hook-shim-bash.mjs +55 -0
  55. package/plugin/scripts/hook-shim.mjs +303 -0
  56. package/plugin/scripts/host-update.mjs +58 -0
  57. package/plugin/scripts/kling-preflight.sh +146 -0
  58. package/plugin/scripts/learn-capture.sh +173 -0
  59. package/plugin/scripts/learn-flush.mjs +155 -0
  60. package/plugin/scripts/lesson-hooks.sh +213 -0
  61. package/plugin/scripts/md-stamp.mjs +219 -0
  62. package/plugin/scripts/protect-brain-state.sh +84 -0
  63. package/plugin/scripts/route-dispatch.sh +147 -0
  64. package/plugin/scripts/routing-outcome-capture.mjs +89 -0
  65. package/plugin/scripts/runtime-preferences.mjs +269 -0
  66. package/plugin/scripts/session-start-core.mjs +477 -0
  67. package/plugin/scripts/session-start.sh +13 -0
  68. package/plugin/scripts/signal-watch.mjs +193 -0
  69. package/plugin/scripts/unprompted-runtime.mjs +377 -0
  70. package/plugin/scripts/update-apply.mjs +419 -0
  71. package/plugin/scripts/verify-interface.sh +53 -0
  72. package/plugin/scripts/version-bump-gate.sh +112 -0
  73. package/plugin/skills/brain-build/SKILL.md +123 -0
  74. package/plugin/skills/brain-console/SKILL.md +22 -0
  75. package/plugin/skills/brain-prompt/SKILL.md +83 -0
  76. package/plugin/skills/brain-score/SKILL.md +101 -0
  77. package/plugin/skills/release-proof/SKILL.md +81 -0
  78. package/plugin/skills/release-proof/agents/openai.yaml +4 -0
  79. package/plugin/skills/release-proof/references/receipt-contract.md +38 -0
  80. package/plugin/skills/release-proof/scripts/release-proof.mjs +210 -0
  81. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +121 -0
  82. package/plugin/skills/ruvnet-brain/SKILL.md +234 -0
  83. package/plugin/skills/rvbc/SKILL.md +23 -0
  84. package/plugin/skills/savings/SKILL.md +46 -0
  85. package/plugin/skills/whats-new/SKILL.md +22 -0
  86. package/scripts/adr-backfill.mjs +107 -0
  87. package/scripts/advocacy-outcomes.mjs +808 -0
  88. package/scripts/agentdb-context.mjs +216 -0
  89. package/scripts/agentdb-fleet-doctor.mjs +101 -0
  90. package/scripts/ascii-drift.mjs +236 -0
  91. package/scripts/behavioral-l1-l4.mjs +210 -0
  92. package/scripts/brain-capability-check.mjs +72 -0
  93. package/scripts/brain-grade-groundtruth.mjs +100 -0
  94. package/scripts/brain-latency-50.mjs +227 -0
  95. package/scripts/brain-novice-50.mjs +189 -0
  96. package/scripts/brain-stamp.mjs +94 -0
  97. package/scripts/brain-state.mjs +212 -0
  98. package/scripts/build-bundle.mjs +522 -0
  99. package/scripts/build-concepts.mjs +132 -0
  100. package/scripts/build-l2.mjs +71 -0
  101. package/scripts/build-primer.mjs +73 -0
  102. package/scripts/build-symbols.mjs +68 -0
  103. package/scripts/calibrate-router.mjs +97 -0
  104. package/scripts/capability-audit.mjs +321 -0
  105. package/scripts/capability-registry.mjs +876 -0
  106. package/scripts/check-indexation.mjs +108 -0
  107. package/scripts/check-legibility.mjs +189 -0
  108. package/scripts/ci/build-fixture-kb.mjs +67 -0
  109. package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
  110. package/scripts/ci/learning-replay-recorder.mjs +59 -0
  111. package/scripts/ci/mutate-hook-timeout.mjs +70 -0
  112. package/scripts/ci/stranger-fixture-stage.mjs +17 -0
  113. package/scripts/ci/stranger-scenario.mjs +228 -0
  114. package/scripts/ci/stranger-timeout.mjs +25 -0
  115. package/scripts/ci-verdict.mjs +29 -0
  116. package/scripts/claims-verify.mjs +710 -0
  117. package/scripts/clear-claude-tmp.sh +31 -0
  118. package/scripts/console-engine.mjs +434 -0
  119. package/scripts/console-engine.test.mjs +125 -0
  120. package/scripts/corpus-qa.mjs +250 -0
  121. package/scripts/correction-detect-embed.mjs +346 -0
  122. package/scripts/correction-detect-measure.mjs +270 -0
  123. package/scripts/correction-detect.mjs +686 -0
  124. package/scripts/count-chunks.mjs +54 -0
  125. package/scripts/described-questions.json +30 -0
  126. package/scripts/design-grade.mjs +58 -0
  127. package/scripts/dev-plugin-link.sh +105 -0
  128. package/scripts/distill-project.mjs +200 -0
  129. package/scripts/doc-currency.mjs +801 -0
  130. package/scripts/eval-brain.mjs +244 -0
  131. package/scripts/fix-metaharness-memretrieve.mjs +121 -0
  132. package/scripts/full-hints.mjs +87 -0
  133. package/scripts/gate.sh +39 -0
  134. package/scripts/gates.mjs +146 -0
  135. package/scripts/gen-console-images.mjs +54 -0
  136. package/scripts/gen-images.mjs +47 -0
  137. package/scripts/git-clone-refresh.mjs +52 -0
  138. package/scripts/git-hooks/pre-push +126 -0
  139. package/scripts/goal-match.mjs +398 -0
  140. package/scripts/goldie-research.mjs +223 -0
  141. package/scripts/goldie-weekly.sh +67 -0
  142. package/scripts/health-repair.mjs +250 -0
  143. package/scripts/helix-scenario-questions.json +10 -0
  144. package/scripts/ingest-gists.mjs +230 -0
  145. package/scripts/ingest-meeting.mjs +115 -0
  146. package/scripts/ingest-repo.mjs +79 -0
  147. package/scripts/install-npx-witness.sh +49 -0
  148. package/scripts/issue-fix.mjs +639 -0
  149. package/scripts/issue-watch.mjs +276 -0
  150. package/scripts/issue4-close-note.md +31 -0
  151. package/scripts/key-canary.mjs +91 -0
  152. package/scripts/latency-to-surface.mjs +233 -0
  153. package/scripts/learning-enable.mjs +380 -0
  154. package/scripts/learning-replay.mjs +1570 -0
  155. package/scripts/learnings.mjs +62 -0
  156. package/scripts/lesson-gate.mjs +680 -0
  157. package/scripts/lesson-lifecycle.mjs +449 -0
  158. package/scripts/lesson-promote.mjs +262 -0
  159. package/scripts/lesson-ratify.mjs +98 -0
  160. package/scripts/lesson-seed.mjs +252 -0
  161. package/scripts/lesson-store.mjs +447 -0
  162. package/scripts/loop-checkpoint.mjs +86 -0
  163. package/scripts/memdb-health.sh +14 -0
  164. package/scripts/memory-doctor.mjs +271 -0
  165. package/scripts/model-catalog.mjs +79 -0
  166. package/scripts/nightly-controller.mjs +66 -0
  167. package/scripts/nightly-gists.sh +72 -0
  168. package/scripts/nightly-wrapper.sh +180 -0
  169. package/scripts/notify.sh +12 -0
  170. package/scripts/npx-witness.sh +56 -0
  171. package/scripts/onboarding-console.mjs +2749 -0
  172. package/scripts/private-fence.mjs +69 -0
  173. package/scripts/proactivity-metrics.mjs +118 -0
  174. package/scripts/proof-questions.json +56 -0
  175. package/scripts/prove.mjs +95 -0
  176. package/scripts/proxy/claude-proxied.sh +57 -0
  177. package/scripts/proxy/proxy-revert.sh +59 -0
  178. package/scripts/proxy/proxy-up.sh +60 -0
  179. package/scripts/proxy/proxy-verify.mjs +142 -0
  180. package/scripts/published-surface-probe.mjs +241 -0
  181. package/scripts/qe/card-lane-gate.mjs +162 -0
  182. package/scripts/qe/session-start-gate.mjs +229 -0
  183. package/scripts/qe/ux-suite.mjs +323 -0
  184. package/scripts/reconcile-project.mjs +0 -0
  185. package/scripts/record-lesson.mjs +113 -0
  186. package/scripts/refresh-model-catalog.mjs +99 -0
  187. package/scripts/release-proof.mjs +9 -0
  188. package/scripts/release-vector.mjs +281 -0
  189. package/scripts/release.mjs +395 -0
  190. package/scripts/remedy-registry.mjs +247 -0
  191. package/scripts/rerank-cap-eval.mjs +265 -0
  192. package/scripts/rerank-cap-warm-ab.mjs +129 -0
  193. package/scripts/route-cheap.mjs +20 -15
  194. package/scripts/router-utilization.mjs +182 -0
  195. package/scripts/routing-flywheel.mjs +596 -0
  196. package/scripts/rvf-generation.mjs +104 -0
  197. package/scripts/rvf-index-audit.mjs +138 -0
  198. package/scripts/self-update.mjs +508 -0
  199. package/scripts/selfcheck.mjs +7 -1
  200. package/scripts/sign-bundle.mjs +69 -0
  201. package/scripts/signal-watch.mjs +171 -0
  202. package/scripts/stack-sync.mjs +469 -0
  203. package/scripts/stamp-existing-rvf-generations.mjs +53 -0
  204. package/scripts/stamp-sweep.mjs +144 -0
  205. package/scripts/status-honesty.mjs +102 -0
  206. package/scripts/sync-version.mjs +217 -0
  207. package/scripts/token-report.mjs +102 -0
  208. package/scripts/top100-benchmark.mjs +479 -0
  209. package/scripts/top100-corpus.mjs +112 -0
  210. package/scripts/top100-semantic-assertions.mjs +449 -0
  211. package/scripts/update-apply.mjs +9 -0
  212. package/scripts/upgrade-notice.mjs +14 -0
  213. package/scripts/verify-bundle.mjs +51 -0
  214. package/scripts/verify-channels.mjs +184 -0
  215. package/scripts/verify-model-catalog.mjs +104 -0
  216. package/scripts/verify-nightly-close-issue4.sh +31 -0
  217. package/scripts/version.mjs +40 -0
  218. package/scripts/wired-check.mjs +864 -0
@@ -0,0 +1,180 @@
1
+ #!/bin/bash
2
+ # nightly-wrapper.sh — the ONLY thing launchd invokes for the nightly. Per Stuart's standing order
3
+ # (2026-07-12): no noise for success or no-op — only ACTIVE alerts for real failure, and the system
4
+ # must attempt to self-heal before escalating, then make failure impossible to miss next session.
5
+ #
6
+ # Flow: run once -> if it fails, wait and retry ONCE (catches transient network/API blips, the only
7
+ # class a blind retry can fix) -> if it fails AGAIN, loud phone alert + a durable marker file that
8
+ # ground-ruvnet.sh surfaces at the top of the very next session, unprompted. Success or a legitimate
9
+ # "nothing was due tonight" no-op: log only, phone stays silent.
10
+ set -u
11
+ # kb/models-cache (the fallback) starts cold every time -> every nightly re-downloads the ONNX
12
+ # embedder from HuggingFace. Point at the already-warm cache instead (verified present).
13
+ export KB_MODEL_CACHE="/Users/stuartkerr/Code/PowerPlatePulse/scripts/models-cache"
14
+ cd /Users/stuartkerr/Code/ruvnet-brain 2>/dev/null || {
15
+ curl -sS --max-time 10 -H "Title: 🔴 Nightly CRASHED before it could even start" \
16
+ -H "Priority: urgent" -H "Tags: rotating_light" \
17
+ -d "cd into the repo failed — filesystem or mount problem. Investigate the machine directly." \
18
+ "https://ntfy.sh/$(grep -m1 '^NTFY_TOPIC=' /Users/stuartkerr/Code/ruvnet-brain/.env 2>/dev/null | cut -d= -f2)" >/dev/null 2>&1
19
+ exit 1
20
+ }
21
+ mkdir -p logs .ruvnet-brain
22
+ LOG=logs/nightly.log
23
+ MARKER=.ruvnet-brain/nightly-failure.json
24
+ LOCK=.ruvnet-brain/nightly.lock
25
+
26
+ # Single-instance guard (2026-07-12): the plist has no built-in one. The updater is incremental now,
27
+ # but a very large upstream change, a slow model, or a stalled gate can still outlive the window and
28
+ # overlap the NEXT scheduled 3:15am fire. A stale lock from a crashed run (PID no longer alive) is
29
+ # treated as no lock.
30
+ if [ -f "$LOCK" ]; then
31
+ OLD_PID=$(cat "$LOCK" 2>/dev/null)
32
+ if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
33
+ echo "===== $(date -u +%FT%TZ) — SKIPPED: previous run (pid $OLD_PID) still in progress =====" >> "$LOG"
34
+ # 75 = the reserved skip code: job-heartbeat.sh restores the live run's receipt instead of
35
+ # stamping ok/0s over it (F3). launchd still sees success — a skip is not a failure.
36
+ exit 75
37
+ fi
38
+ echo "===== $(date -u +%FT%TZ) — stale lock from pid $OLD_PID (not running) — clearing =====" >> "$LOG"
39
+ fi
40
+ echo $$ > "$LOCK"
41
+ trap 'rm -f "$LOCK"' EXIT
42
+
43
+ # NIGHTLY_SMOKE=1 — prove this whole chain (launchd -> heartbeat wrapper -> here -> node -> the repo
44
+ # scan -> the log) WITHOUT a multi-hour rebuild or a real Release publish. self-update.mjs is dry-run
45
+ # by default, so dropping --apply --publish exercises every link except the build itself.
46
+ # WHY IT EXISTS (2026-07-13): the 03:15 fire on 07-12 died before it could even write its own log
47
+ # ("/bin/bash: logs/nightly.log: No such file or directory" — relative path, no working directory) and
48
+ # NOBODY KNEW, because the only way to test this chain was to wait until 03:15 and hope. A scheduled job
49
+ # you can only test by waiting for it is a job you are not testing. Now: `NIGHTLY_SMOKE=1 <the exact
50
+ # plist command>` proves it on demand, any time, in seconds.
51
+ SMOKE="${NIGHTLY_SMOKE:-0}"
52
+
53
+ run_once() {
54
+ local before after rc tail
55
+ before=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "unknown")
56
+ { echo "===== nightly-wrapper attempt $1 — $(date -u +%FT%TZ) — before: $before ====="
57
+ if [ "$SMOKE" = "1" ]; then
58
+ echo "[SMOKE] dry-run: exercising the full chain, NOT building or publishing"
59
+ /usr/local/bin/node scripts/self-update.mjs --fresh-window 60
60
+ else
61
+ /usr/local/bin/node scripts/self-update.mjs --apply --publish --fresh-window 60
62
+ fi
63
+ } >> "$LOG" 2>&1
64
+ rc=$?
65
+ if [ "$SMOKE" = "1" ]; then
66
+ # A smoke run must never claim a real outcome, touch the failure marker, or alter the tag story.
67
+ if [ "$rc" -eq 0 ]; then echo "===== attempt $1: SMOKE OK — chain intact (no build, no publish) =====" >> "$LOG"; return 0; fi
68
+ echo "===== attempt $1: SMOKE FAILED exit $rc — the chain is BROKEN, fix before 03:15 =====" >> "$LOG"
69
+ return 1
70
+ fi
71
+ after=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "unknown")
72
+ if [ "$after" != "$before" ] && [ -n "$after" ] && [ "$after" != "unknown" ]; then
73
+ echo "===== attempt $1: VERIFIED SUCCESS $before -> $after =====" >> "$LOG"
74
+ rm -f "$MARKER"
75
+ return 0
76
+ elif [ "$rc" -eq 0 ]; then
77
+ echo "===== attempt $1: CLEAN NO-OP, tag unchanged at $after =====" >> "$LOG"
78
+ rm -f "$MARKER"
79
+ return 0
80
+ else
81
+ echo "===== attempt $1: FAILED exit $rc, tag stuck at $after =====" >> "$LOG"
82
+ return 1
83
+ fi
84
+ }
85
+
86
+ # Memory distillation (ADR-174) had gone stale 3 days — same silent-death pattern as everything
87
+ # else tonight. Independent of publish success/failure: mine raw memory_entries into structured
88
+ # episodes/reasoning_patterns on every nightly run. Best-effort, never blocks the real job.
89
+ RUFLO_DAEMON_AUTOSTART=0 ~/.npm-global/bin/ruflo memory distill run --path .swarm/memory.db >> "$LOG" 2>&1 || true
90
+ # Durability: was a one-off manual snapshot before tonight. Now recurring — WAL-safe, rotates
91
+ # automatically (keeps last 14), zero risk to the live DB (reads only).
92
+ RUFLO_DAEMON_AUTOSTART=0 ~/.npm-global/bin/ruflo memory backup --db .swarm/memory.db --keep 14 >> "$LOG" 2>&1 || true
93
+
94
+ # AgentDB drift canary (2026-07-23, P7 wiring sweep) — scripts/memdb-health.sh existed unwired since
95
+ # 2026-07-09/10, the exact nights idx_bridge_key/idx_bridge_ns corruption recurred 3x on this very DB
96
+ # while in WAL mode. Best-effort, same shape as its brain-health/key-health siblings below: never
97
+ # blocks the real publish, just makes a WAL-mode/integrity regression loud instead of silent.
98
+ echo "===== memdb-health canary — $(date -u +%FT%TZ) =====" >> "$LOG"
99
+ sh scripts/memdb-health.sh .swarm/memory.db >> "$LOG" 2>&1 \
100
+ && echo "===== memdb-health canary: OK =====" >> "$LOG" \
101
+ || echo "===== memdb-health canary: UNHEALTHY (see line above) =====" >> "$LOG"
102
+
103
+ # ── LEARNING-REPLAY: the D4 counterfactual trap (ADR-058 §D4). THE ONE STANDING TOKEN SPEND, priced
104
+ # in the open: N=3 replay, two model arms per run, ~$0.09 and ~55s measured 2026-07-27 on haiku.
105
+ #
106
+ # It runs HERE, and not on a GitHub runner, because the trap needs credentials a bare runner does not
107
+ # have — a real model session AND the real global `ruflo` binary (it records into a fixture project's
108
+ # .swarm/memory.db and refreshes it with `ruflo memory distill`). .github/workflows/learning-replay.yml
109
+ # is the currency gate on the artifact this writes; if this step stops running, that workflow goes red
110
+ # on staleness rather than everything staying quietly green.
111
+ #
112
+ # Best-effort, same shape as the canaries below: it never blocks the publish. Its exit code is not
113
+ # thrown away though — it is written to the log by name, because 0/1/3/4 are four different facts
114
+ # (PASS / the lesson stopped transferring / the trap was invalidated / it could not be measured) and
115
+ # collapsing them into "failed" is how the seven prior silent-death bugs in this file happened.
116
+ echo "===== LEARNING-REPLAY counterfactual trap — $(date -u +%FT%TZ) =====" >> "$LOG"
117
+ /usr/local/bin/node scripts/learning-replay.mjs --n 3 --model haiku >> "$LOG" 2>&1
118
+ LR_RC=$?
119
+ case "$LR_RC" in
120
+ 0) echo "===== LEARNING-REPLAY: PASS =====" >> "$LOG" ;;
121
+ 1) echo "===== LEARNING-REPLAY: FAIL — a lesson recorded in project A stopped changing behaviour in project B =====" >> "$LOG" ;;
122
+ 3) echo "===== LEARNING-REPLAY: INCONCLUSIVE — the control arm also produced the token; the trap measured nothing tonight =====" >> "$LOG" ;;
123
+ *) echo "===== LEARNING-REPLAY: UNKNOWN (exit $LR_RC) — never a pass; see the lines above =====" >> "$LOG" ;;
124
+ esac
125
+
126
+ # ── GONG LAYER 3: brain-health canary (Stuart, 2026-07-12 — the brain must NEVER be dark silently).
127
+ # One real query against the LIVE cache brain every night. forge-ask-all.mjs exits non-zero on a
128
+ # total retrieval failure (all repos erroring) and rings kb/brain-alarm.mjs itself; this adds the
129
+ # guaranteed-nightly cadence plus its own urgent push, independent of the publish job's outcome.
130
+ BRAIN_KB="$HOME/.cache/ruvnet-brain/kb"
131
+ if [ -f "$BRAIN_KB/forge-ask-all.mjs" ]; then
132
+ echo "===== brain-health canary — $(date -u +%FT%TZ) =====" >> "$LOG"
133
+ if (cd "$BRAIN_KB" && /usr/local/bin/node forge-ask-all.mjs --dir . --q "HNSW vector index" --k 1) >> "$LOG" 2>&1; then
134
+ echo "===== brain-health canary: OK =====" >> "$LOG"
135
+ else
136
+ echo "===== brain-health canary: DOWN — escalating =====" >> "$LOG"
137
+ sh scripts/notify.sh "🚨 RuvNet Brain DOWN (nightly canary)" \
138
+ "The live brain at $BRAIN_KB failed a real query — retrieval is broken for every session on this machine. Fix: cd $BRAIN_KB && npm i, then npx github:stuinfla/ruvnet-brain --doctor. See logs/nightly.log." \
139
+ urgent "rotating_light" || true
140
+ fi
141
+ fi
142
+
143
+ # ── Key-health canary (Stuart, 2026-07-12: two provider keys found dead by accident, months late).
144
+ # Runs through `zsh -lc` ON PURPOSE: a login shell sources ~/.zshrc -> env.global + the openclaw
145
+ # SOPS secrets, so this probes the EXACT env every real shell inherits — the real door, not a copy.
146
+ # The canary itself handles urgent pushes on alive->DEAD transitions (and recovery notices), so a
147
+ # known-dead key doesn't re-alarm every night; here we only log.
148
+ echo "===== key-health canary — $(date -u +%FT%TZ) =====" >> "$LOG"
149
+ zsh -lc 'cd /Users/stuartkerr/Code/ruvnet-brain && /usr/local/bin/node scripts/key-canary.mjs --notify' >> "$LOG" 2>&1 \
150
+ && echo "===== key-health canary: all present keys alive =====" >> "$LOG" \
151
+ || echo "===== key-health canary: at least one key DEAD (push sent on new deaths) =====" >> "$LOG"
152
+
153
+ if run_once 1; then
154
+ exit 0
155
+ fi
156
+
157
+ echo "===== first attempt failed — waiting 3 min, retrying once (self-heal for transient issues) =====" >> "$LOG"
158
+ sleep 180
159
+
160
+ if run_once 2; then
161
+ echo "===== SELF-HEALED on retry — no escalation needed =====" >> "$LOG"
162
+ exit 0
163
+ fi
164
+
165
+ # Both attempts genuinely failed. Escalate loudly AND leave a marker the next session cannot miss.
166
+ AFTER=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "unknown")
167
+ TAIL=$(tail -8 "$LOG" | tr '\n' ' ' | cut -c1-600)
168
+ mkdir -p .ruvnet-brain
169
+ python3 -c "
170
+ import json, datetime
171
+ json.dump({
172
+ 'at': datetime.datetime.utcnow().isoformat() + 'Z',
173
+ 'tag_stuck_at': '$AFTER',
174
+ 'tail': '''$TAIL''',
175
+ 'note': 'Nightly failed twice (immediate + 3min retry). Needs a live session to diagnose — see logs/nightly.log.'
176
+ }, open('$MARKER', 'w'), indent=2)
177
+ "
178
+ sh scripts/notify.sh "🔴 Nightly FAILED twice — needs you" "tag stuck at $AFTER after retry. Last: $TAIL" urgent "rotating_light"
179
+ echo "===== ESCALATED: marker written at $MARKER =====" >> "$LOG"
180
+ exit 1
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+ # notify.sh "<title>" "<message>" [priority] [tags] — push alert to Stuart's phone via ntfy.sh.
3
+ # Topic from $NTFY_TOPIC or the project .env. Fail-silent: alerting must never break a pipeline.
4
+ DIR=$(dirname "$0")
5
+ TOPIC="${NTFY_TOPIC:-$(grep -m1 '^NTFY_TOPIC=' "$DIR/../.env" 2>/dev/null | cut -d= -f2)}"
6
+ [ -z "$TOPIC" ] && exit 0
7
+ curl -sS --max-time 10 \
8
+ -H "Title: ${1:-RuvNet Brain}" \
9
+ -H "Priority: ${3:-default}" \
10
+ -H "Tags: ${4:-brain}" \
11
+ -d "${2:-(no message)}" \
12
+ "https://ntfy.sh/$TOPIC" >/dev/null 2>&1 || true
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # npx-witness.sh — the instrument at the door. Fired by launchd the instant ~/.npm/_npx changes.
3
+ #
4
+ # WHY (2026-07-14 — the day the same failure happened seven times):
5
+ # Stale npx shadow copies of RuvNet packages kept "coming back" after every purge. Each time, the
6
+ # cleanup was verified with a check built from what had already been found — grep for claude-flow
7
+ # because claude-flow was what we'd seen. A check built from what you found can only confirm what
8
+ # you found; it is structurally incapable of revealing what you missed. There were at least FIVE
9
+ # families of npx producers (claude-flow@latest, claude-flow@alpha, ruflo, ruvector, aqe); exactly
10
+ # one was ever fixed, and the shadows returned twice while "the producer was disarmed".
11
+ #
12
+ # THE RULE THIS ENCODES: you cannot enumerate the callers of a thing — you can instrument the thing.
13
+ # This script does not search for callers. launchd (WatchPaths on ~/.npm/_npx) runs it the moment
14
+ # the cache CHANGES, and it snapshots who is running right then, with full parent chains. A sixth
15
+ # family we never imagined gets logged exactly like the five we know.
16
+ #
17
+ # It is an OBSERVER: append-only log, no deletions, no fixes, never blocks anything, always exit 0.
18
+ # Preferred placement was a shim at /usr/local/bin/npx (catches cache HITS too), but that is
19
+ # root-owned; this watcher needs no sudo and catches every cache CREATION — which is precisely the
20
+ # event that manufactures a shadow. Log: ~/.claude/logs/npx-witness.log (plain text, greppable).
21
+
22
+ LOG="$HOME/.claude/logs/npx-witness.log"
23
+ NPXDIR="$HOME/.npm/_npx"
24
+ mkdir -p "$(dirname "$LOG")" 2>/dev/null || exit 0
25
+
26
+ {
27
+ echo "═══ $(date -u +%Y-%m-%dT%H:%M:%SZ) — ~/.npm/_npx changed ═══"
28
+
29
+ # What is in the cache right now, and what package does each dir hold?
30
+ for d in "$NPXDIR"/*/; do
31
+ [ -d "$d" ] || continue
32
+ deps="?"
33
+ [ -f "$d/package.json" ] && deps=$(node -p "Object.keys(require('$d/package.json').dependencies||{}).join(',')" 2>/dev/null || echo "?")
34
+ echo " dir: $(basename "$d") mtime: $(stat -f '%Sm' -t '%H:%M:%S' "$d" 2>/dev/null) pkgs: $deps"
35
+ done
36
+ [ -d "$NPXDIR" ] || echo " (cache dir was deleted)"
37
+
38
+ # Who is running npx/npm-exec RIGHT NOW — the culprit is alive at creation time (installs take
39
+ # seconds). Walk each suspect's parent chain so we learn who CALLED it, not just that npx ran.
40
+ echo " --- live npx/npm processes at this instant ---"
41
+ ps -Ao pid,ppid,command | grep -E "npx|npm exec|npm-cli.*exec|_npx" | grep -vE "grep|npx-witness" | head -8 | cut -c1-180 | sed 's/^/ /'
42
+ for pid in $(ps -Ao pid,command | grep -E "npx|npm exec" | grep -vE "grep|npx-witness" | awk '{print $1}' | head -4); do
43
+ echo " chain for pid $pid:"
44
+ p=$pid
45
+ for _ in 1 2 3 4 5 6; do
46
+ line=$(ps -o pid=,ppid=,command= -p "$p" 2>/dev/null) || break
47
+ [ -z "$line" ] && break
48
+ echo " $line" | cut -c1-170
49
+ p=$(echo "$line" | awk '{print $2}')
50
+ [ "$p" -le 1 ] 2>/dev/null && break
51
+ done
52
+ done
53
+ echo ""
54
+ } >> "$LOG" 2>/dev/null
55
+
56
+ exit 0