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.
- package/.claude-plugin/marketplace.json +1 -0
- package/README.md +4 -4
- package/bin/install.mjs +303 -24
- package/console/CONTRACT.md +172 -0
- package/console/activity.js +753 -0
- package/console/app.js +4189 -0
- package/console/architecture.html +1221 -0
- package/console/assets/depth-1.webp +0 -0
- package/console/assets/depth-2.webp +0 -0
- package/console/assets/depth-3.webp +0 -0
- package/console/assets/harness-vs-plain.svg +259 -0
- package/console/assets/hero.webp +0 -0
- package/console/assets/memory.webp +0 -0
- package/console/assets/metaharness.svg +247 -0
- package/console/index.html +777 -0
- package/console/install-architecture.html +162 -0
- package/console/install-mockup.html +543 -0
- package/console/style.css +2144 -0
- package/console/tips.css +926 -0
- package/console/tips.html +858 -0
- package/console/tips.js +128 -0
- package/docs/RELEASE-NOTES-4.0.md +88 -0
- package/kb/model-requirements.mjs +37 -6
- package/keys/ruvnet-brain-signing.pub.pem +3 -0
- package/package.json +8 -22
- package/plugin/.claude-plugin/marketplace.json +1 -0
- package/plugin/.claude-plugin/plugin.json +2 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/brain-console.md +2 -2
- package/plugin/commands/configure.md +3 -2
- package/plugin/commands/rvbc.md +4 -3
- package/plugin/commands/rvcb.md +2 -2
- package/plugin/commands/whats-new.md +6 -6
- package/plugin/docs/RELEASE-NOTES-4.0.md +88 -0
- package/plugin/hooks/hooks.json +1 -2
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +90 -32
- package/plugin/scripts/detach.mjs +14 -0
- package/plugin/scripts/first-session-worker.mjs +38 -0
- package/plugin/scripts/ground-ruvnet.sh +16 -6
- package/plugin/scripts/hook-shim.mjs +34 -29
- package/plugin/scripts/learn-capture.sh +22 -3
- package/plugin/scripts/learn-flush.mjs +21 -4
- package/plugin/scripts/runtime-preferences.mjs +269 -0
- package/plugin/scripts/session-start-core.mjs +503 -0
- package/plugin/scripts/session-start.sh +3 -858
- package/plugin/scripts/whats-new.mjs +42 -0
- package/plugin/skills/brain-console/SKILL.md +4 -2
- package/plugin/skills/release-proof/SKILL.md +98 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +44 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +286 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
- package/plugin/skills/ruvnet-brain/SKILL.md +22 -7
- package/plugin/skills/rvbc/SKILL.md +9 -6
- package/plugin/skills/whats-new/SKILL.md +4 -4
- package/scripts/adr-backfill.mjs +107 -0
- package/scripts/advocacy-outcomes.mjs +808 -0
- package/scripts/agentdb-context.mjs +216 -0
- package/scripts/agentdb-fleet-doctor.mjs +101 -0
- package/scripts/ascii-drift.mjs +236 -0
- package/scripts/behavioral-l1-l4.mjs +210 -0
- package/scripts/brain-capability-check.mjs +72 -0
- package/scripts/brain-grade-groundtruth.mjs +100 -0
- package/scripts/brain-latency-50.mjs +227 -0
- package/scripts/brain-novice-50.mjs +189 -0
- package/scripts/brain-stamp.mjs +94 -0
- package/scripts/brain-state.mjs +212 -0
- package/scripts/build-bundle.mjs +531 -0
- package/scripts/build-concepts.mjs +132 -0
- package/scripts/build-l2.mjs +71 -0
- package/scripts/build-primer.mjs +73 -0
- package/scripts/build-symbols.mjs +68 -0
- package/scripts/calibrate-router.mjs +97 -0
- package/scripts/capability-audit.mjs +321 -0
- package/scripts/capability-registry.mjs +876 -0
- package/scripts/check-indexation.mjs +108 -0
- package/scripts/check-legibility.mjs +189 -0
- package/scripts/ci/build-fixture-kb.mjs +67 -0
- package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
- package/scripts/ci/learning-replay-recorder.mjs +59 -0
- package/scripts/ci/mutate-hook-timeout.mjs +70 -0
- package/scripts/ci/stranger-fixture-stage.mjs +17 -0
- package/scripts/ci/stranger-scenario.mjs +228 -0
- package/scripts/ci/stranger-timeout.mjs +25 -0
- package/scripts/ci-verdict.mjs +29 -0
- package/scripts/claims-verify.mjs +710 -0
- package/scripts/clear-claude-tmp.sh +31 -0
- package/scripts/console-engine.mjs +434 -0
- package/scripts/console-engine.test.mjs +125 -0
- package/scripts/corpus-qa.mjs +250 -0
- package/scripts/correction-detect-embed.mjs +346 -0
- package/scripts/correction-detect-measure.mjs +270 -0
- package/scripts/correction-detect.mjs +686 -0
- package/scripts/count-chunks.mjs +54 -0
- package/scripts/described-questions.json +30 -0
- package/scripts/design-grade.mjs +58 -0
- package/scripts/dev-plugin-link.sh +105 -0
- package/scripts/distill-project.mjs +200 -0
- package/scripts/doc-currency.mjs +801 -0
- package/scripts/eval-brain.mjs +244 -0
- package/scripts/fix-metaharness-memretrieve.mjs +121 -0
- package/scripts/fix-workstream.mjs +291 -0
- package/scripts/full-hints.mjs +87 -0
- package/scripts/gate.sh +39 -0
- package/scripts/gates.mjs +146 -0
- package/scripts/gen-console-images.mjs +54 -0
- package/scripts/gen-images.mjs +47 -0
- package/scripts/git-clone-refresh.mjs +52 -0
- package/scripts/git-hooks/pre-push +126 -0
- package/scripts/goal-match.mjs +398 -0
- package/scripts/goldie-research.mjs +223 -0
- package/scripts/goldie-weekly.sh +67 -0
- package/scripts/health-repair.mjs +237 -0
- package/scripts/helix-scenario-questions.json +10 -0
- package/scripts/ingest-gists.mjs +230 -0
- package/scripts/ingest-meeting.mjs +115 -0
- package/scripts/ingest-repo.mjs +79 -0
- package/scripts/install-npx-witness.sh +49 -0
- package/scripts/issue-fix.mjs +558 -0
- package/scripts/issue-watch.mjs +276 -0
- package/scripts/issue4-close-note.md +31 -0
- package/scripts/key-canary.mjs +91 -0
- package/scripts/latency-to-surface.mjs +233 -0
- package/scripts/learning-enable.mjs +380 -0
- package/scripts/learning-replay.mjs +1570 -0
- package/scripts/learnings.mjs +62 -0
- package/scripts/lesson-gate.mjs +680 -0
- package/scripts/lesson-lifecycle.mjs +449 -0
- package/scripts/lesson-promote.mjs +262 -0
- package/scripts/lesson-ratify.mjs +98 -0
- package/scripts/lesson-seed.mjs +252 -0
- package/scripts/lesson-store.mjs +447 -0
- package/scripts/loop-checkpoint.mjs +86 -0
- package/scripts/memdb-health.sh +14 -0
- package/scripts/memory-doctor.mjs +326 -0
- package/scripts/model-catalog.mjs +79 -0
- package/scripts/nightly-controller.mjs +66 -0
- package/scripts/nightly-gists.sh +72 -0
- package/scripts/nightly-wrapper.sh +172 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2922 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -0
- package/scripts/protected-release-invocation.mjs +76 -0
- package/scripts/prove.mjs +95 -0
- package/scripts/proxy/claude-proxied.sh +57 -0
- package/scripts/proxy/proxy-revert.sh +59 -0
- package/scripts/proxy/proxy-up.sh +60 -0
- package/scripts/proxy/proxy-verify.mjs +142 -0
- package/scripts/publication-receipt.mjs +307 -0
- package/scripts/published-surface-probe.mjs +241 -0
- package/scripts/qe/card-lane-gate.mjs +162 -0
- package/scripts/qe/session-start-gate.mjs +229 -0
- package/scripts/qe/ux-suite.mjs +323 -0
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/record-lesson.mjs +113 -0
- package/scripts/refresh-model-catalog.mjs +99 -0
- package/scripts/release-authority.mjs +93 -0
- package/scripts/release-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +439 -0
- package/scripts/remedy-registry.mjs +247 -0
- package/scripts/rerank-cap-eval.mjs +265 -0
- package/scripts/rerank-cap-warm-ab.mjs +129 -0
- package/scripts/route-cheap.mjs +20 -15
- package/scripts/router-utilization.mjs +182 -0
- package/scripts/routing-flywheel.mjs +596 -0
- package/scripts/rvf-generation.mjs +104 -0
- package/scripts/rvf-index-audit.mjs +138 -0
- package/scripts/self-update.mjs +296 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -0
- package/scripts/stabilization-receipt.mjs +108 -0
- package/scripts/stack-sync.mjs +469 -0
- package/scripts/stamp-existing-rvf-generations.mjs +53 -0
- package/scripts/stamp-sweep.mjs +144 -0
- package/scripts/status-honesty.mjs +102 -0
- package/scripts/sync-version.mjs +217 -0
- package/scripts/token-report.mjs +102 -0
- package/scripts/top100-benchmark.mjs +479 -0
- package/scripts/top100-corpus.mjs +112 -0
- package/scripts/top100-semantic-assertions.mjs +449 -0
- package/scripts/update-apply.mjs +9 -0
- package/scripts/upgrade-notice.mjs +14 -0
- package/scripts/verify-bundle.mjs +51 -0
- package/scripts/verify-channels.mjs +184 -0
- package/scripts/verify-model-catalog.mjs +104 -0
- package/scripts/verify-nightly-close-issue4.sh +31 -0
- package/scripts/version.mjs +40 -0
- package/scripts/wired-check.mjs +867 -0
- package/plugin/scripts/finalize-token-meter.mjs +0 -25
|
@@ -0,0 +1,172 @@
|
|
|
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. self-update.mjs is dry-run by default, so dropping
|
|
45
|
+
# --apply exercises every link except the build itself. Publication is never part of this job.
|
|
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 rc
|
|
55
|
+
{ echo "===== nightly-wrapper rebuild attempt $1 — $(date -u +%FT%TZ) ====="
|
|
56
|
+
if [ "$SMOKE" = "1" ]; then
|
|
57
|
+
echo "[SMOKE] dry-run: exercising the full chain, NOT building"
|
|
58
|
+
/usr/local/bin/node scripts/self-update.mjs --fresh-window 60
|
|
59
|
+
else
|
|
60
|
+
/usr/local/bin/node scripts/self-update.mjs --apply --fresh-window 60
|
|
61
|
+
fi
|
|
62
|
+
} >> "$LOG" 2>&1
|
|
63
|
+
rc=$?
|
|
64
|
+
if [ "$rc" -ne 0 ]; then
|
|
65
|
+
echo "===== attempt $1: FAILED exit $rc — rebuild/candidate preparation did not complete =====" >> "$LOG"
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
if [ "$SMOKE" = "1" ]; then
|
|
69
|
+
# A smoke run must never claim a real outcome, touch the failure marker, or alter the tag story.
|
|
70
|
+
echo "===== attempt $1: SMOKE OK — chain intact (no build, no publish) =====" >> "$LOG"
|
|
71
|
+
return 0
|
|
72
|
+
fi
|
|
73
|
+
echo "===== attempt $1: REBUILD COMPLETE — candidate bytes prepared; no release channel was promoted =====" >> "$LOG"
|
|
74
|
+
rm -f "$MARKER"
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Memory distillation (ADR-174) had gone stale 3 days — same silent-death pattern as everything
|
|
79
|
+
# else tonight. Independent of rebuild success/failure: mine raw memory_entries into structured
|
|
80
|
+
# episodes/reasoning_patterns on every nightly run. Best-effort, never blocks the real job.
|
|
81
|
+
RUFLO_DAEMON_AUTOSTART=0 ~/.npm-global/bin/ruflo memory distill run --path .swarm/memory.db >> "$LOG" 2>&1 || true
|
|
82
|
+
# Durability: was a one-off manual snapshot before tonight. Now recurring — WAL-safe, rotates
|
|
83
|
+
# automatically (keeps last 14), zero risk to the live DB (reads only).
|
|
84
|
+
RUFLO_DAEMON_AUTOSTART=0 ~/.npm-global/bin/ruflo memory backup --db .swarm/memory.db --keep 14 >> "$LOG" 2>&1 || true
|
|
85
|
+
|
|
86
|
+
# AgentDB drift canary (2026-07-23, P7 wiring sweep) — scripts/memdb-health.sh existed unwired since
|
|
87
|
+
# 2026-07-09/10, the exact nights idx_bridge_key/idx_bridge_ns corruption recurred 3x on this very DB
|
|
88
|
+
# while in WAL mode. Best-effort, same shape as its brain-health/key-health siblings below: never
|
|
89
|
+
# blocks the real rebuild, just makes a WAL-mode/integrity regression loud instead of silent.
|
|
90
|
+
echo "===== memdb-health canary — $(date -u +%FT%TZ) =====" >> "$LOG"
|
|
91
|
+
sh scripts/memdb-health.sh .swarm/memory.db >> "$LOG" 2>&1 \
|
|
92
|
+
&& echo "===== memdb-health canary: OK =====" >> "$LOG" \
|
|
93
|
+
|| echo "===== memdb-health canary: UNHEALTHY (see line above) =====" >> "$LOG"
|
|
94
|
+
|
|
95
|
+
# ── LEARNING-REPLAY: the D4 counterfactual trap (ADR-058 §D4). THE ONE STANDING TOKEN SPEND, priced
|
|
96
|
+
# in the open: N=3 replay, two model arms per run, ~$0.09 and ~55s measured 2026-07-27 on haiku.
|
|
97
|
+
#
|
|
98
|
+
# It runs HERE, and not on a GitHub runner, because the trap needs credentials a bare runner does not
|
|
99
|
+
# have — a real model session AND the real global `ruflo` binary (it records into a fixture project's
|
|
100
|
+
# .swarm/memory.db and refreshes it with `ruflo memory distill`). .github/workflows/learning-replay.yml
|
|
101
|
+
# is the currency gate on the artifact this writes; if this step stops running, that workflow goes red
|
|
102
|
+
# on staleness rather than everything staying quietly green.
|
|
103
|
+
#
|
|
104
|
+
# Best-effort, same shape as the canaries below: it never blocks the rebuild. Its exit code is not
|
|
105
|
+
# thrown away though — it is written to the log by name, because 0/1/3/4 are four different facts
|
|
106
|
+
# (PASS / the lesson stopped transferring / the trap was invalidated / it could not be measured) and
|
|
107
|
+
# collapsing them into "failed" is how the seven prior silent-death bugs in this file happened.
|
|
108
|
+
echo "===== LEARNING-REPLAY counterfactual trap — $(date -u +%FT%TZ) =====" >> "$LOG"
|
|
109
|
+
/usr/local/bin/node scripts/learning-replay.mjs --n 3 --model haiku >> "$LOG" 2>&1
|
|
110
|
+
LR_RC=$?
|
|
111
|
+
case "$LR_RC" in
|
|
112
|
+
0) echo "===== LEARNING-REPLAY: PASS =====" >> "$LOG" ;;
|
|
113
|
+
1) echo "===== LEARNING-REPLAY: FAIL — a lesson recorded in project A stopped changing behaviour in project B =====" >> "$LOG" ;;
|
|
114
|
+
3) echo "===== LEARNING-REPLAY: INCONCLUSIVE — the control arm also produced the token; the trap measured nothing tonight =====" >> "$LOG" ;;
|
|
115
|
+
*) echo "===== LEARNING-REPLAY: UNKNOWN (exit $LR_RC) — never a pass; see the lines above =====" >> "$LOG" ;;
|
|
116
|
+
esac
|
|
117
|
+
|
|
118
|
+
# ── GONG LAYER 3: brain-health canary (Stuart, 2026-07-12 — the brain must NEVER be dark silently).
|
|
119
|
+
# One real query against the LIVE cache brain every night. forge-ask-all.mjs exits non-zero on a
|
|
120
|
+
# total retrieval failure (all repos erroring) and rings kb/brain-alarm.mjs itself; this adds the
|
|
121
|
+
# guaranteed-nightly cadence plus its own urgent push, independent of the rebuild job's outcome.
|
|
122
|
+
BRAIN_KB="$HOME/.cache/ruvnet-brain/kb"
|
|
123
|
+
if [ -f "$BRAIN_KB/forge-ask-all.mjs" ]; then
|
|
124
|
+
echo "===== brain-health canary — $(date -u +%FT%TZ) =====" >> "$LOG"
|
|
125
|
+
if (cd "$BRAIN_KB" && /usr/local/bin/node forge-ask-all.mjs --dir . --q "HNSW vector index" --k 1) >> "$LOG" 2>&1; then
|
|
126
|
+
echo "===== brain-health canary: OK =====" >> "$LOG"
|
|
127
|
+
else
|
|
128
|
+
echo "===== brain-health canary: DOWN — escalating =====" >> "$LOG"
|
|
129
|
+
sh scripts/notify.sh "🚨 RuvNet Brain DOWN (nightly canary)" \
|
|
130
|
+
"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." \
|
|
131
|
+
urgent "rotating_light" || true
|
|
132
|
+
fi
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# ── Key-health canary (Stuart, 2026-07-12: two provider keys found dead by accident, months late).
|
|
136
|
+
# Runs through `zsh -lc` ON PURPOSE: a login shell sources ~/.zshrc -> env.global + the openclaw
|
|
137
|
+
# SOPS secrets, so this probes the EXACT env every real shell inherits — the real door, not a copy.
|
|
138
|
+
# The canary itself handles urgent pushes on alive->DEAD transitions (and recovery notices), so a
|
|
139
|
+
# known-dead key doesn't re-alarm every night; here we only log.
|
|
140
|
+
echo "===== key-health canary — $(date -u +%FT%TZ) =====" >> "$LOG"
|
|
141
|
+
zsh -lc 'cd /Users/stuartkerr/Code/ruvnet-brain && /usr/local/bin/node scripts/key-canary.mjs --notify' >> "$LOG" 2>&1 \
|
|
142
|
+
&& echo "===== key-health canary: all present keys alive =====" >> "$LOG" \
|
|
143
|
+
|| echo "===== key-health canary: at least one key DEAD (push sent on new deaths) =====" >> "$LOG"
|
|
144
|
+
|
|
145
|
+
if run_once 1; then
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
echo "===== first attempt failed — waiting 3 min, retrying once (self-heal for transient issues) =====" >> "$LOG"
|
|
150
|
+
sleep 180
|
|
151
|
+
|
|
152
|
+
if run_once 2; then
|
|
153
|
+
echo "===== SELF-HEALED on retry — no escalation needed =====" >> "$LOG"
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Both attempts genuinely failed. Escalate loudly AND leave a marker the next session cannot miss.
|
|
158
|
+
AFTER=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "unknown")
|
|
159
|
+
TAIL=$(tail -8 "$LOG" | tr '\n' ' ' | cut -c1-600)
|
|
160
|
+
mkdir -p .ruvnet-brain
|
|
161
|
+
python3 -c "
|
|
162
|
+
import json, datetime
|
|
163
|
+
json.dump({
|
|
164
|
+
'at': datetime.datetime.utcnow().isoformat() + 'Z',
|
|
165
|
+
'last_release_tag_observed': '$AFTER',
|
|
166
|
+
'tail': '''$TAIL''',
|
|
167
|
+
'note': 'Nightly failed twice (immediate + 3min retry). Needs a live session to diagnose — see logs/nightly.log.'
|
|
168
|
+
}, open('$MARKER', 'w'), indent=2)
|
|
169
|
+
"
|
|
170
|
+
sh scripts/notify.sh "🔴 Nightly FAILED twice — needs you" "candidate rebuild failed; last release remains $AFTER. Last: $TAIL" urgent "rotating_light"
|
|
171
|
+
echo "===== ESCALATED: marker written at $MARKER =====" >> "$LOG"
|
|
172
|
+
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
|