ruvnet-brain 4.7.20 → 4.7.22
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/README.md +2 -2
- package/bin/install.mjs +618 -104
- package/console/activity.js +122 -38
- package/kb/download-retry.mjs +93 -21
- package/kb/forge-guard-injection.mjs +277 -0
- package/kb/forge-update.mjs +166 -38
- package/kb/lifecycle-evidence-retention.mjs +151 -14
- package/kb/model-requirements.mjs +4 -1
- package/kb/refresh-run.mjs +175 -27
- package/kb/verify-citation.mjs +15 -2
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/mcp/managed-cli-interface.mjs +16 -8
- package/plugin/scripts/adr-currency-gate.mjs +2 -1
- package/plugin/scripts/advisory-contract.mjs +21 -0
- package/plugin/scripts/advocacy-route.mjs +5 -4
- package/plugin/scripts/agentdb-recall-process.mjs +115 -5
- package/plugin/scripts/agentdb-recall-reader.mjs +44 -2
- package/plugin/scripts/agentdb-recall.mjs +49 -17
- package/plugin/scripts/automatic-update.mjs +9 -1
- package/plugin/scripts/brain-confirmation.mjs +2 -1
- package/plugin/scripts/brain-footprint.mjs +72 -11
- package/plugin/scripts/capability-registry.mjs +1 -1
- package/plugin/scripts/capacity-aware-parallel-work.mjs +2 -1
- package/plugin/scripts/codex-overnight-notice.mjs +33 -0
- package/plugin/scripts/completion-claim-evidence.mjs +241 -23
- package/plugin/scripts/continuation-gate.mjs +75 -11
- package/plugin/scripts/decision-gate.mjs +13 -63
- package/plugin/scripts/decision-outcomes.mjs +36 -0
- package/plugin/scripts/decision-recall-gate.mjs +40 -5
- package/plugin/scripts/decision-record.mjs +8 -3
- package/plugin/scripts/degradation-watch.mjs +2 -1
- package/plugin/scripts/developer-update-lock.mjs +172 -33
- package/plugin/scripts/developer-update-owner.mjs +8 -5
- package/plugin/scripts/developer-update-policy.mjs +4 -1
- package/plugin/scripts/developer-update.mjs +155 -53
- package/plugin/scripts/doc-currency.mjs +2 -16
- package/plugin/scripts/duplicate-gate.mjs +2 -1
- package/plugin/scripts/entrypoint.mjs +34 -0
- package/plugin/scripts/footprint-io.mjs +135 -8
- package/plugin/scripts/grok-hook-adapter.mjs +2 -1
- package/plugin/scripts/ground-ruvnet.sh +6 -3
- package/plugin/scripts/grounding-answer.mjs +2 -1
- package/plugin/scripts/grounding-code-projection.mjs +2 -1
- package/plugin/scripts/grounding-substance.mjs +2 -1
- package/plugin/scripts/grounding-turn-gate.mjs +2 -2
- package/plugin/scripts/grounding-turn-mark.mjs +2 -2
- package/plugin/scripts/hook-context-budget.mjs +2 -1
- package/plugin/scripts/hook-input.mjs +2 -2
- package/plugin/scripts/hook-shim.mjs +9 -2
- package/plugin/scripts/host-update.mjs +29 -5
- package/plugin/scripts/identifier-preflight.mjs +2 -1
- package/plugin/scripts/kb-copy-proof.mjs +57 -9
- package/plugin/scripts/learn-capture.mjs +2 -1
- package/plugin/scripts/learning-store.mjs +62 -16
- package/plugin/scripts/lesson-bridge.mjs +2 -2
- package/plugin/scripts/lesson-gate.mjs +7 -39
- package/plugin/scripts/lesson-promote.mjs +83 -22
- package/plugin/scripts/lesson-scope.mjs +51 -0
- package/plugin/scripts/lesson-store.mjs +1 -1
- package/plugin/scripts/md-stamp.mjs +2 -1
- package/plugin/scripts/native-turn-transcript.mjs +98 -6
- package/plugin/scripts/nightly-scheduler.mjs +10 -5
- package/plugin/scripts/package-recommender.mjs +2 -2
- package/plugin/scripts/project-store-resolver.mjs +19 -0
- package/plugin/scripts/routing-outcome-capture.mjs +2 -1
- package/plugin/scripts/ruflo-bin.mjs +14 -6
- package/plugin/scripts/ruflo-memory-hooks.mjs +157 -19
- package/plugin/scripts/session-snapshot-hook.mjs +25 -5
- package/plugin/scripts/session-start-core.mjs +7 -16
- package/plugin/scripts/session-start-health.mjs +98 -10
- package/plugin/scripts/session-start-issue-alert.mjs +63 -0
- package/plugin/scripts/session-start-update-plane.mjs +15 -1
- package/plugin/scripts/signal-watch.mjs +2 -1
- package/plugin/scripts/spend-guard.mjs +2 -1
- package/plugin/scripts/substitution-wall.mjs +2 -1
- package/plugin/scripts/turn-outcome-capture.mjs +7 -3
- package/plugin/scripts/unprompted-runtime.mjs +20 -3
- package/plugin/scripts/update-apply.mjs +186 -15
- package/plugin/skills/release-proof/scripts/release-proof.mjs +2 -1
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +2 -2
- package/plugin/skills/ruvnet-brain/SKILL.md +3 -2
- package/scripts/adr-072-completion.mjs +2 -1
- package/scripts/agentdb-context.mjs +2 -1
- package/scripts/agentdb-fleet-doctor.mjs +30 -8
- package/scripts/approved-runtime.mjs +2 -1
- package/scripts/architecture-review-lock.mjs +2 -1
- package/scripts/ascii-drift.mjs +2 -1
- package/scripts/brain-latency-50.mjs +2 -1
- package/scripts/brain-novice-50.mjs +2 -16
- package/scripts/brain-score.mjs +13 -2
- package/scripts/build-bundle.mjs +2 -1
- package/scripts/build-concepts.mjs +2 -1
- package/scripts/candidate-ci-receipt.mjs +2 -1
- package/scripts/candidate-host-evidence.mjs +2 -1
- package/scripts/card-from-source.mjs +2 -1
- package/scripts/ci/warm-brain-models.mjs +2 -1
- package/scripts/claims-verify.mjs +7 -3
- package/scripts/claude-controlled-terminal.mjs +2 -1
- package/scripts/claude-terminal-mod.mjs +2 -1
- package/scripts/code-release-corpus.mjs +2 -1
- package/scripts/codex-boot-surface-proof.mjs +208 -0
- package/scripts/codex-hook-trust-reconcile.mjs +67 -1
- package/scripts/codex-hook-trust.mjs +40 -1
- package/scripts/codex-hook-wrapper-history.mjs +2 -1
- package/scripts/codex-host-execution-proof.mjs +2 -1
- package/scripts/completion-claim-replay.mjs +2 -1
- package/scripts/console-engine.mjs +8 -3
- package/scripts/corpus-canary.mjs +2 -1
- package/scripts/corpus-candidate.mjs +2 -2
- package/scripts/corpus-coverage-sidecar.mjs +2 -1
- package/scripts/corpus-dispatch-decision.mjs +2 -1
- package/scripts/corpus-dispatch-receipt.mjs +2 -1
- package/scripts/corpus-next-seed.mjs +2 -1
- package/scripts/corpus-qa.mjs +2 -1
- package/scripts/corpus-reconcile.mjs +2 -2
- package/scripts/corpus-watchdog.mjs +2 -1
- package/scripts/count-chunks.mjs +2 -1
- package/scripts/customer-state-matrix.mjs +2 -1
- package/scripts/derive-passage-content-map.mjs +2 -1
- package/scripts/development-push-check.mjs +2 -1
- package/scripts/dispatch-receipt.mjs +2 -1
- package/scripts/doc-currency.mjs +2 -1
- package/scripts/dream-issue-gate.mjs +2 -1
- package/scripts/dual-host-deliberation.mjs +2 -16
- package/scripts/dual-host-suggest.mjs +2 -16
- package/scripts/eval-brain.mjs +7 -3
- package/scripts/execution-policy.mjs +2 -1
- package/scripts/execution-preflight.mjs +2 -1
- package/scripts/falsify.mjs +2 -1
- package/scripts/fix-metaharness-memretrieve.mjs +2 -1
- package/scripts/fix-workstream.mjs +2 -1
- package/scripts/full-hints.mjs +2 -1
- package/scripts/full-suite-gate.mjs +2 -1
- package/scripts/grok-host.mjs +68 -0
- package/scripts/hook-qualify-core.mjs +3 -1
- package/scripts/hook-qualify.mjs +2 -1
- package/scripts/hook-retirement-check.mjs +2 -1
- package/scripts/host-registry.mjs +2 -1
- package/scripts/independent-review-receipt.mjs +2 -1
- package/scripts/ingest-new-repos.mjs +2 -1
- package/scripts/ingest-repo.mjs +12 -2
- package/scripts/integration-evidence.mjs +2 -1
- package/scripts/issue-fix.mjs +7 -24
- package/scripts/issue-watch.mjs +116 -40
- package/scripts/job-heartbeat.sh +54 -19
- package/scripts/key-canary.mjs +9 -3
- package/scripts/latency-to-surface.mjs +2 -1
- package/scripts/learning-replay-cli.mjs +97 -19
- package/scripts/learning-replay-execution.mjs +2 -0
- package/scripts/learning-replay-fixture.mjs +3 -0
- package/scripts/learning-replay-proof.mjs +14 -13
- package/scripts/learning-replay.mjs +3 -3
- package/scripts/learnings.mjs +2 -1
- package/scripts/lesson-ratify.mjs +88 -40
- package/scripts/loop-checkpoint.mjs +2 -1
- package/scripts/metaharness-receipts.mjs +2 -1
- package/scripts/model-catalog.mjs +2 -1
- package/scripts/model-currency-evidence.mjs +6 -4
- package/scripts/model-currency.mjs +33 -3
- package/scripts/model-native-catalog.mjs +2 -1
- package/scripts/model-router-agent-hook.mjs +2 -1
- package/scripts/model-router-dispatch.mjs +2 -1
- package/scripts/model-router-engine.mjs +2 -1
- package/scripts/model-router-outcome.mjs +2 -1
- package/scripts/model-router-setup.mjs +2 -1
- package/scripts/model-routing-eval.mjs +2 -1
- package/scripts/model-routing-gateway.mjs +2 -1
- package/scripts/model-routing-launchers.mjs +4 -1
- package/scripts/model-terminal-gateway.mjs +2 -1
- package/scripts/model-terminal-launchers.mjs +2 -1
- package/scripts/model-weekly-analyst.mjs +2 -1
- package/scripts/model-weekly-cycle.mjs +2 -1
- package/scripts/model-weekly-qualification.mjs +2 -1
- package/scripts/nightly-gists.sh +26 -20
- package/scripts/nightly-two-run-proof.mjs +2 -1
- package/scripts/nightly-watchdog.mjs +158 -47
- package/scripts/no-silent-substitution.mjs +2 -1
- package/scripts/notify.sh +11 -6
- package/scripts/ntfy-inbox.mjs +41 -18
- package/scripts/onboarding-console.mjs +123 -23
- package/scripts/oracle/abstain-threshold-sweep.mjs +2 -1
- package/scripts/oracle/abstain-trace.mjs +2 -1
- package/scripts/oracle/doc2query-generate.mjs +2 -1
- package/scripts/oracle/doc2query-reach.mjs +2 -1
- package/scripts/oracle/judge-train.mjs +2 -1
- package/scripts/oracle/need-set-split.mjs +2 -1
- package/scripts/oracle/produce-questions.mjs +2 -16
- package/scripts/oracle/repo-recall.mjs +2 -2
- package/scripts/oracle/retrieval-accuracy.mjs +2 -2
- package/scripts/oracle/sona-query-adapter-eval.mjs +2 -1
- package/scripts/oracle/source-tree.mjs +2 -15
- package/scripts/oracle/source-units.mjs +2 -16
- package/scripts/oracle/spike-run.mjs +2 -16
- package/scripts/oracle/validate-labels.mjs +2 -16
- package/scripts/org-repo-count.mjs +2 -1
- package/scripts/package-cards.mjs +2 -1
- package/scripts/prepublication-evidence.mjs +2 -1
- package/scripts/private-overlay.mjs +2 -1
- package/scripts/product-integrity-contract.mjs +2 -1
- package/scripts/protected-release-invocation.mjs +2 -1
- package/scripts/public-verification-abandon.mjs +2 -1
- package/scripts/public-verification-aggregate.mjs +2 -1
- package/scripts/public-verification-finalizer.mjs +2 -1
- package/scripts/public-verification-inputs.mjs +2 -1
- package/scripts/public-verification-lane.mjs +2 -1
- package/scripts/publication-receipt.mjs +2 -1
- package/scripts/published-surface-probe.mjs +2 -1
- package/scripts/qe/card-lane-gate.mjs +2 -16
- package/scripts/qe/session-start-gate.mjs +2 -16
- package/scripts/qe/ux-suite.mjs +2 -1
- package/scripts/qualified-candidate-check.mjs +2 -1
- package/scripts/rebuild-gists-from-receipts.mjs +2 -1
- package/scripts/recall-latency-probe.mjs +48 -0
- package/scripts/recommendation-eval.mjs +2 -1
- package/scripts/recommendation-floor.mjs +2 -1
- package/scripts/recommendation-judge-score.mjs +2 -1
- package/scripts/recommendation-real-host-score.mjs +2 -1
- package/scripts/recommendation-real-host.mjs +2 -1
- package/scripts/refresh-capability-only-store.mjs +2 -1
- package/scripts/refresh-model-catalog.mjs +2 -1
- package/scripts/rehearse-corpus-pipeline.mjs +2 -1
- package/scripts/release-authority.mjs +2 -1
- package/scripts/release-evidence-aggregate.mjs +2 -1
- package/scripts/release-payload.mjs +2 -1
- package/scripts/release-projection.mjs +2 -1
- package/scripts/release-proof.mjs +2 -1
- package/scripts/release-qualification-contract.mjs +82 -28
- package/scripts/release-qualification.mjs +8 -1
- package/scripts/release-vector.mjs +2 -1
- package/scripts/restore-local-ingests.mjs +2 -1
- package/scripts/retrieval-canary.mjs +2 -1
- package/scripts/route-cheap.mjs +2 -1
- package/scripts/route-gold-rank.mjs +2 -1
- package/scripts/route-index-memory.mjs +2 -1
- package/scripts/route-latency-warm.mjs +2 -1
- package/scripts/router-utilization.mjs +2 -1
- package/scripts/routing-flywheel.mjs +2 -1
- package/scripts/run-operational-benchmark.mjs +2 -1
- package/scripts/run-operational-benchmark.v3.mjs +2 -1
- package/scripts/rvf-index-audit.mjs +2 -16
- package/scripts/seal-gist-receipt.mjs +2 -1
- package/scripts/security-guidance-codex-compat.mjs +2 -1
- package/scripts/selfcheck.mjs +2 -1
- package/scripts/signal-watch.mjs +2 -1
- package/scripts/source-coverage.mjs +2 -1
- package/scripts/stabilization-receipt.mjs +2 -1
- package/scripts/stamp-sweep.mjs +2 -1
- package/scripts/status-honesty.mjs +2 -1
- package/scripts/sync-commands.mjs +2 -2
- package/scripts/top100-benchmark.mjs +2 -1
- package/scripts/top100-corpus.mjs +2 -1
- package/scripts/user-model-hook-install.mjs +2 -1
- package/scripts/user-model-prompt-hook.mjs +2 -1
- package/scripts/verify-bundle.mjs +3 -1
- package/scripts/verify-model-catalog.mjs +2 -1
- package/scripts/version.mjs +2 -1
- package/scripts/wired-check.mjs +13 -5
- package/scripts/worktree-integrity.mjs +2 -2
package/scripts/notify.sh
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
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.
|
|
3
|
+
# Topic from $NTFY_TOPIC or the project .env. Alerting must never break a pipeline (no caller runs under
|
|
4
|
+
# `set -e`), but the exit code now tells the truth so a caller can record delivery (4.7.21):
|
|
5
|
+
# 0 delivered (2xx) · 1 not delivered (network or HTTP error; curl --fail) · 2 paging off (NTFY_TOPIC="" or no topic)
|
|
6
|
+
# NTFY_TOPIC set-but-EMPTY is an explicit opt-out (job-heartbeat.sh's convention): it never falls through to .env.
|
|
4
7
|
DIR=$(dirname "$0")
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
if [ "${NTFY_TOPIC+set}" = "set" ]; then TOPIC="$NTFY_TOPIC"
|
|
9
|
+
else TOPIC="$(grep -m1 '^NTFY_TOPIC=' "$DIR/../.env" 2>/dev/null | cut -d= -f2)"; fi
|
|
10
|
+
[ -z "$TOPIC" ] && exit 2
|
|
11
|
+
curl -sS --fail --max-time 10 \
|
|
8
12
|
-H "Title: ${1:-RuvNet Brain}" \
|
|
9
13
|
-H "Priority: ${3:-default}" \
|
|
10
14
|
-H "Tags: ${4:-brain}" \
|
|
11
|
-
-d "${2
|
|
12
|
-
"https://ntfy.sh/$TOPIC" >/dev/null 2>&1 ||
|
|
15
|
+
-d "${2:-(no message)}" \
|
|
16
|
+
"${NTFY_URL:-https://ntfy.sh}/$TOPIC" >/dev/null 2>&1 || exit 1
|
|
17
|
+
exit 0
|
package/scripts/ntfy-inbox.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
23
23
|
import { spawn, spawnSync } from 'node:child_process';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
25
|
import { isPersonIssue, judgeIssue } from './issue-watch.mjs';
|
|
26
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
26
27
|
|
|
27
28
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
28
29
|
const REPO = 'stuinfla/ruvnet-brain';
|
|
@@ -61,14 +62,27 @@ export function classify(msg) {
|
|
|
61
62
|
let m;
|
|
62
63
|
// job-heartbeat.sh pages "SCHEDULED JOB FAILED: X" / "Recovered: X"; nightly-watchdog.mjs pages "🔴 <STATE>: X" /
|
|
63
64
|
// "✅ X is healthy again". Same cause, same key, so either recovery closes either failure.
|
|
64
|
-
if ((m = /SCHEDULED JOB FAILED: (\S+)/.exec(title)) || (m = /^\W*(?:FAILING|NEVER-RAN|STALE|MISSING|STALLED): (\S+)/.exec(title))) return { kind: 'job', key: `job:${m[1]}`, label: m[1], actionable: true };
|
|
65
|
+
if ((m = /SCHEDULED JOB FAILED: (\S+)/.exec(title)) || (m = /^\W*(?:FAILING|NEVER-RAN|STALE|MISSING|STALLED|STUCK): (\S+)/.exec(title))) return { kind: 'job', key: `job:${m[1]}`, label: m[1], actionable: true };
|
|
65
66
|
if ((m = /Recovered: (\S+)/.exec(title)) || (m = /^\W*(\S+) is healthy again/.exec(title))) return { kind: 'job-recovered', key: `job:${m[1]}`, label: m[1], actionable: false, resolves: true };
|
|
66
67
|
if ((m = /CI \w+: (.+)$/.exec(title))) {
|
|
67
68
|
const b = /branch ([\w./-]{1,100})/.exec(body);
|
|
68
69
|
// Keyed by workflow AND branch: a green main must never close a still-red release branch (review round 4).
|
|
69
70
|
return { kind: 'ci', key: `ci:${m[1].trim()}@${b ? b[1] : 'main'}`, workflow: m[1].trim(), ...(b ? { branch: b[1] } : {}), actionable: true };
|
|
70
71
|
}
|
|
71
|
-
if ((m = /(?:New issue|SLA breach: issue) #(\d+)/.exec(title)))
|
|
72
|
+
if ((m = /(?:New issue|SLA breach: issue) #(\d+)/.exec(title))) {
|
|
73
|
+
// ONE combined SLA page names every breaching issue (scripts/issue-watch.mjs: a "#N (age) title" line each, and
|
|
74
|
+
// "…and K more: #a #b" past 8). Each named issue is its own alert, so a 17-issue breach counts 17, not 1.
|
|
75
|
+
const numbers = [Number(m[1])];
|
|
76
|
+
if (/^SLA breach/.test(title)) {
|
|
77
|
+
for (const line of body.split('\n')) {
|
|
78
|
+
const listed = /^#(\d+) \(/.exec(line);
|
|
79
|
+
if (listed) numbers.push(Number(listed[1]));
|
|
80
|
+
else if (/^…and \d+ more:/.test(line)) for (const x of line.matchAll(/#(\d+)/g)) numbers.push(Number(x[1]));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const unique = [...new Set(numbers.filter((n) => Number.isSafeInteger(n) && n > 0))].slice(0, MAX_OPEN_ALERTS);
|
|
84
|
+
return { kind: 'issue', key: `issue:${unique[0]}`, number: unique[0], actionable: true, ...(unique.length > 1 ? { numbers: unique } : {}) };
|
|
85
|
+
}
|
|
72
86
|
if ((m = /New PR #(\d+)/.exec(title))) return { kind: 'pr', key: `pr:${m[1]}`, number: Number(m[1]), actionable: true };
|
|
73
87
|
if (/^Waiting on a reply/.test(title)) return { kind: 'digest', key: 'digest:issues', actionable: false };
|
|
74
88
|
if (/New star/.test(title)) return { kind: 'info', key: `info:${msg.id}`, actionable: false };
|
|
@@ -115,23 +129,32 @@ export function fold(open, messages, { now = Date.now(), resolver = resolvedNow,
|
|
|
115
129
|
for (const msg of messages) {
|
|
116
130
|
if (!msg || typeof msg.id !== 'string' || folded.has(msg.id)) continue;
|
|
117
131
|
folded.add(msg.id);
|
|
118
|
-
const
|
|
132
|
+
const classified = classify(msg);
|
|
119
133
|
const at = new Date((msg.time || now / 1000) * 1000).toISOString();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
continue;
|
|
134
|
+
// A combined SLA page becomes one alert per issue it names; the first keeps the message id, the rest get id-N.
|
|
135
|
+
const parts = classified.numbers ? classified.numbers.map((n, i) => {
|
|
136
|
+
const one = { ...classified, key: `issue:${n}`, number: n, alertId: i === 0 ? msg.id : `${msg.id}-${n}` };
|
|
137
|
+
delete one.numbers;
|
|
138
|
+
return one;
|
|
139
|
+
}) : [classified];
|
|
140
|
+
for (const c of parts) {
|
|
141
|
+
if (c.resolves) {
|
|
142
|
+
const a = byKey.get(c.key);
|
|
143
|
+
if (a && !a.closedAt && Date.parse(at) >= Date.parse(a.lastAt) && resolver(a)) { a.closedAt = at; a.closedBy = 'recovered notice (confirmed)'; a.closedByMsg = msg.id; }
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!c.actionable) continue;
|
|
147
|
+
const existing = byKey.get(c.key);
|
|
148
|
+
if (existing && !existing.closedAt) {
|
|
149
|
+
// lastAt only moves forward: a delayed older message must not make a newer failure look resolved.
|
|
150
|
+
existing.count += 1;
|
|
151
|
+
if (Date.parse(at) > Date.parse(existing.lastAt)) { existing.lastAt = at; existing.lastTitle = msg.title; }
|
|
152
|
+
existing.msgIds = [...(existing.msgIds || []), msg.id].slice(-10);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const { alertId, ...fields } = c;
|
|
156
|
+
byKey.set(c.key, { id: alertId || msg.id, ...fields, firstAt: at, lastAt: at, count: 1, title: msg.title || '', body: String(msg.message || '').slice(0, 200), msgIds: [msg.id], attempts: 0 });
|
|
133
157
|
}
|
|
134
|
-
byKey.set(c.key, { id: msg.id, ...c, firstAt: at, lastAt: at, count: 1, title: msg.title || '', body: String(msg.message || '').slice(0, 200), msgIds: [msg.id], attempts: 0 });
|
|
135
158
|
}
|
|
136
159
|
const byOldestCheck = [...byKey.values()].sort((x, y) => Date.parse(x.lastCheckedAt || 0) - Date.parse(y.lastCheckedAt || 0));
|
|
137
160
|
for (const a of byOldestCheck) {
|
|
@@ -506,7 +529,7 @@ export function formatOpen(alerts) {
|
|
|
506
529
|
+ `${a.count > 1 ? ` (×${a.count}, since ${clean(a.firstAt, 30)})` : ''}\n ${clean(String(a.body || '').split('\n')[0], 160)}`).join('\n') + tail;
|
|
507
530
|
}
|
|
508
531
|
|
|
509
|
-
if (
|
|
532
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
510
533
|
const args = process.argv.slice(2);
|
|
511
534
|
try {
|
|
512
535
|
if (args[0] === '--open') {
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
} from '../kb/brain-profile.mjs';
|
|
65
65
|
// Lessons: read model + the two user verbs. Every mutation goes through lesson-store's own
|
|
66
66
|
// updateLessons/ratify/demote/restore — this file adds a SURFACE, never a second writer.
|
|
67
|
-
import { loadLessons, updateLessons, ratify, demote, restore, pending, weightOf, TRIGGERS, ENFORCEMENT, ORIGIN, SOURCE_CLASS, STATUS } from './lesson-store.mjs';
|
|
67
|
+
import { loadLessons, lessonApplies, projectNameFor, STORE_PATH as LESSON_STORE_PATH, updateLessons, ratify, demote, restore, pending, weightOf, TRIGGERS, ENFORCEMENT, ORIGIN, SOURCE_CLASS, STATUS } from './lesson-store.mjs';
|
|
68
68
|
import {
|
|
69
69
|
openRouterCredentialStatus,
|
|
70
70
|
saveOpenRouterCredential,
|
|
@@ -430,8 +430,31 @@ function wiringSurvey() {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// ── Memory health (read-only probes for the project the console was launched from) ────────────────
|
|
433
|
+
// Dream Cycle 2026-09-18 (memory-durability, round-trip-proof): this used to also pass on ANY
|
|
434
|
+
// `.claude/hooks` directory existing, so a machine whose hooks folder holds only an unrelated
|
|
435
|
+
// script (a different plugin, a stale leftover) scored `sessionSurfacing: ok` and printed "the
|
|
436
|
+
// global SessionStart hook surfaces project state at launch" — a claim about a hook that was never
|
|
437
|
+
// installed. Only the specific recall hook's presence can support that claim.
|
|
438
|
+
//
|
|
439
|
+
// Dream Cycle 2026-09-19 (memory-durability, managed-boundary): the fix above still checked only
|
|
440
|
+
// the LEGACY standalone `agentdb-ensure.sh` global hook. It ignored the mechanism this very repo
|
|
441
|
+
// ships and documents as the automatic one — `plugin/hooks/hooks.json`'s own SessionStart entry,
|
|
442
|
+
// which every session restores via `project-progression-session-start.mjs` (wired through
|
|
443
|
+
// `session-start-core.mjs`) whenever the `ruvnet-brain` plugin itself is enabled. A machine that
|
|
444
|
+
// installed this plugin the real, current way (Claude Code's marketplace flow) and never separately
|
|
445
|
+
// placed the legacy global shell hook scored `sessionSurfacing: warn` — a false negative, flagged
|
|
446
|
+
// as an open scope question by PR #296's independent critic, not chased there to keep that change
|
|
447
|
+
// to one conceptual fix. `pluginEnabled()` mirrors the exact `k.startsWith('ruvnet-brain@')` check
|
|
448
|
+
// `capability-registry.mjs`'s `wiredForRoute()` already uses for the same "is our plugin the one
|
|
449
|
+
// Claude Code will actually load" question, rather than re-deriving a second way to ask it.
|
|
450
|
+
function pluginEnabled() {
|
|
451
|
+
const settings = readJSON(path.join(CONSOLE_ROOT, '.claude', 'settings.json'));
|
|
452
|
+
return Object.entries(settings?.enabledPlugins || {}).some(([k, v]) => k.startsWith('ruvnet-brain@') && v === true);
|
|
453
|
+
}
|
|
433
454
|
function sessionHookExists() {
|
|
434
|
-
|
|
455
|
+
if (fs.existsSync(path.join(CONSOLE_ROOT, '.claude/hooks/agentdb-ensure.sh'))) return 'agentdb-ensure';
|
|
456
|
+
if (pluginEnabled()) return 'ruvnet-brain-plugin';
|
|
457
|
+
return null;
|
|
435
458
|
}
|
|
436
459
|
// WHICH FILE IS THE PROJECT'S MEMORY STORE IS NOT A CONSTANT (issue #127).
|
|
437
460
|
//
|
|
@@ -496,7 +519,12 @@ function probeMemory(projectDir, { now = Date.now() } = {}) {
|
|
|
496
519
|
// Compaction survival is a ruflo write at PreCompact (project-state-auto-* + exact readback); a refusal
|
|
497
520
|
// reaches the hook-failures summary. It is not re-probed at render.
|
|
498
521
|
probes.compactionSurvival = { status: 'notTested', detail: 'PreCompact stores a project-state-auto-* checkpoint through ruflo and reads it back; failures appear in the SessionStart hook-failures summary' };
|
|
499
|
-
|
|
522
|
+
const sessionHookVia = sessionHookExists();
|
|
523
|
+
probes.sessionSurfacing = sessionHookVia === 'agentdb-ensure'
|
|
524
|
+
? { status: 'ok', detail: 'the global SessionStart hook surfaces project state at launch', via: sessionHookVia }
|
|
525
|
+
: sessionHookVia === 'ruvnet-brain-plugin'
|
|
526
|
+
? { status: 'ok', detail: 'the ruvnet-brain plugin\'s own SessionStart hook surfaces project state at launch', via: sessionHookVia }
|
|
527
|
+
: { status: 'warn', detail: 'no SessionStart recall hook found', via: null };
|
|
500
528
|
// recall quality: honestly NOT probed at render (a true probe needs an embedding query; left for an explicit deep test)
|
|
501
529
|
probes.recallQuality = { status: 'notTested', detail: 'not checked this session — a real recall probe needs an embedding round-trip, which render deliberately avoids' };
|
|
502
530
|
|
|
@@ -1373,6 +1401,8 @@ function setLesson(body) {
|
|
|
1373
1401
|
// --refresh-cache child. A request never scans — not inline on a first-ever run, and not via
|
|
1374
1402
|
// setImmediate either (deferring synchronous work still blocks the loop when it runs). A machine with
|
|
1375
1403
|
// no fleet scan yet is reported `warming`, never as a fabricated zero.
|
|
1404
|
+
// #500: bump when the meaning of the cached fleet changes. 2 = one entry per resolved store; 3 = + stable `id`.
|
|
1405
|
+
const ACTIVITY_CACHE_SCHEMA = 3; // 3 = each project also carries `id`, the store's dev:inode identity
|
|
1376
1406
|
let ACTIVITY_MACHINE_CACHE = null;
|
|
1377
1407
|
const CONSOLE_CACHE_PATH = path.join(CONSOLE_ROOT, '.cache/ruvnet-brain/console-cache.json');
|
|
1378
1408
|
|
|
@@ -1834,7 +1864,9 @@ function loadConsoleCache() {
|
|
|
1834
1864
|
let restored = false;
|
|
1835
1865
|
try {
|
|
1836
1866
|
const j = JSON.parse(fs.readFileSync(CONSOLE_CACHE_PATH, 'utf8'));
|
|
1837
|
-
|
|
1867
|
+
// #500: a cache written before the fleet scan deduped by store identity can hold one database
|
|
1868
|
+
// counted twice (a nested docs/adr/.swarm beside its parent). Never trust it — re-measure.
|
|
1869
|
+
if (j.activity && j.activity.at && j.activity.schema === ACTIVITY_CACHE_SCHEMA) { ACTIVITY_MACHINE_CACHE = j.activity; restored = true; }
|
|
1838
1870
|
if (j.trust && j.trust.at) { TRUST_CACHE = j.trust; restored = true; }
|
|
1839
1871
|
} catch { /* no cache yet — first ever boot */ }
|
|
1840
1872
|
return restored;
|
|
@@ -1868,7 +1900,7 @@ function saveConsoleCache() {
|
|
|
1868
1900
|
function adoptFleetFromDisk() {
|
|
1869
1901
|
try {
|
|
1870
1902
|
const j = readJSON(CONSOLE_CACHE_PATH);
|
|
1871
|
-
if (j && j.activity && j.activity.at && (!ACTIVITY_MACHINE_CACHE || j.activity.at > ACTIVITY_MACHINE_CACHE.at)) {
|
|
1903
|
+
if (j && j.activity && j.activity.at && j.activity.schema === ACTIVITY_CACHE_SCHEMA && (!ACTIVITY_MACHINE_CACHE || j.activity.at > ACTIVITY_MACHINE_CACHE.at)) {
|
|
1872
1904
|
ACTIVITY_MACHINE_CACHE = j.activity;
|
|
1873
1905
|
}
|
|
1874
1906
|
} catch { /* no cache yet — the caller kicks a child and reports `warming` */ }
|
|
@@ -1884,8 +1916,10 @@ function refreshFleetCache() {
|
|
|
1884
1916
|
// pathIdentity, not path.resolve: resolve() normalises `.`/`..` and nothing else, so a project
|
|
1885
1917
|
// reached through a symlink OR through the other capitalisation of a case-insensitive volume
|
|
1886
1918
|
// was two distinct strings for one directory, and its memories were summed twice (#107).
|
|
1887
|
-
|
|
1888
|
-
|
|
1919
|
+
// #500: dedupe on the DATABASE, which is what holds the memories. Two directories (a repo and its
|
|
1920
|
+
// nested docs/adr/.swarm, or a linked worktree) can resolve to one store; project paths differ.
|
|
1921
|
+
const resolved = pathIdentity(s.db) ?? path.resolve(s.db);
|
|
1922
|
+
if (seen.has(resolved)) continue; // a store visible twice counts once
|
|
1889
1923
|
seen.add(resolved);
|
|
1890
1924
|
const n = Number(robustRead(s.db, "SELECT COUNT(*) FROM memory_entries WHERE status='active'").value || 0);
|
|
1891
1925
|
if (n > 0) {
|
|
@@ -1894,17 +1928,25 @@ function refreshFleetCache() {
|
|
|
1894
1928
|
const lastTouched = Number(robustRead(s.db, 'SELECT MAX(updated_at) FROM memory_entries').value || 0);
|
|
1895
1929
|
// rel = the root-relative path — the SAME key reconcile:<id> recommendations use (wiringSurvey
|
|
1896
1930
|
// computes projName the same way, relative to whichever root the project was found under).
|
|
1897
|
-
|
|
1931
|
+
// Name from the RESOLVED project root. A linked worktree whose main checkout lies outside this
|
|
1932
|
+
// scan root would give a '..'-prefixed rel, but reconcile:<id> keys (wiringSurvey) are relative to
|
|
1933
|
+
// the directory found under the root — so fall back to the found directory in that case.
|
|
1934
|
+
const inRoot = path.relative(root, s.project);
|
|
1935
|
+
const rel = (inRoot === '' || inRoot.startsWith('..') || path.isAbsolute(inRoot)) ? path.relative(root, s.found || s.project) : inRoot;
|
|
1936
|
+
// `id` = the store's own dev:inode identity (pathIdentity follows symlinks and sees hard links), so a
|
|
1937
|
+
// client can tell WHICH project is "you" without comparing folder names (two repos can share one).
|
|
1938
|
+
projects.push({ id: resolved, name: path.basename(s.project), rel, memories: n, lastTouched });
|
|
1898
1939
|
total += n;
|
|
1899
1940
|
}
|
|
1900
1941
|
}
|
|
1901
1942
|
}
|
|
1902
1943
|
projects.sort((a, b) => b.memories - a.memories);
|
|
1903
|
-
ACTIVITY_MACHINE_CACHE = { at: Date.now(), projects, totalMemories: total };
|
|
1944
|
+
ACTIVITY_MACHINE_CACHE = { schema: ACTIVITY_CACHE_SCHEMA, at: Date.now(), projects, totalMemories: total };
|
|
1904
1945
|
saveConsoleCache();
|
|
1905
1946
|
}
|
|
1906
|
-
function findMemoryStores(root) {
|
|
1947
|
+
export function findMemoryStores(root) {
|
|
1907
1948
|
const out = [];
|
|
1949
|
+
const seenDb = new Set();
|
|
1908
1950
|
const walk = (dir, depth) => {
|
|
1909
1951
|
if (depth > 3) return;
|
|
1910
1952
|
let ents; try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
@@ -1916,8 +1958,12 @@ function findMemoryStores(root) {
|
|
|
1916
1958
|
// Fleet activity is project memory, so resolve the canonical store rather than selecting a
|
|
1917
1959
|
// coordination database by size. The latter is diagnostic-only and can contain unrelated rows.
|
|
1918
1960
|
try {
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1961
|
+
const store = resolveProjectStore({ projectDir: dir });
|
|
1962
|
+
const resolved = store.canonicalAgentDbPath;
|
|
1963
|
+
// #500: record the RESOLVED project root and one entry per database. A nested docs/adr/.swarm
|
|
1964
|
+
// resolves to its parent's store; recording `dir` listed that one database twice.
|
|
1965
|
+
const id = pathIdentity(resolved) ?? path.resolve(resolved);
|
|
1966
|
+
if (fs.existsSync(resolved) && !seenDb.has(id)) { seenDb.add(id); out.push({ project: store.projectRoot, found: dir, db: resolved }); }
|
|
1921
1967
|
} catch { /* an unresolvable directory is not a project store */ }
|
|
1922
1968
|
continue;
|
|
1923
1969
|
}
|
|
@@ -1928,6 +1974,38 @@ function findMemoryStores(root) {
|
|
|
1928
1974
|
walk(root, 0);
|
|
1929
1975
|
return out;
|
|
1930
1976
|
}
|
|
1977
|
+
// #499: approved lessons are the ratified rules in the lesson store (the SAME active rule as
|
|
1978
|
+
// gatherLessons().counts.active). `available:false` means the store could not be read — unknown, never 0.
|
|
1979
|
+
// count = machine-wide (every ratified/active, non-demoted lesson)
|
|
1980
|
+
// applicable = the subset that applies to THIS project, by the SAME rule plugin/scripts/lesson-gate.mjs
|
|
1981
|
+
// uses (lesson-store.lessonApplies): unscoped, or scoped to this project, or proven in >=2 projects.
|
|
1982
|
+
// items = per-lesson id/title/scope/enforcement/status for the applicable ones, bounded (the metadata
|
|
1983
|
+
// the reporter asked to keep), `itemsTruncated` says when the bound cut the list.
|
|
1984
|
+
const APPROVED_ITEMS_MAX = 25;
|
|
1985
|
+
function approvedLessonsFigure(here) {
|
|
1986
|
+
try {
|
|
1987
|
+
// loadLessons() swallows a parse failure and returns [] — which would read as "0 approved". A file
|
|
1988
|
+
// that exists but does not parse is UNKNOWN; only an absent file is a true zero.
|
|
1989
|
+
if (fs.existsSync(LESSON_STORE_PATH)) JSON.parse(fs.readFileSync(LESSON_STORE_PATH, 'utf8'));
|
|
1990
|
+
const approved = loadLessons().filter((l) => (l.status === STATUS.RATIFIED || l.status === STATUS.ACTIVE) && !l.demoted);
|
|
1991
|
+
const mine = approved.filter((l) => lessonApplies(l, here)); // the gate's own rule (lesson-store.lessonApplies)
|
|
1992
|
+
const items = mine.slice(0, APPROVED_ITEMS_MAX).map((l) => {
|
|
1993
|
+
const ps = Array.isArray(l.projects) ? l.projects : [];
|
|
1994
|
+
const text = String(l.statement || '');
|
|
1995
|
+
return { id: l.id, title: text.length > 140 ? `${text.slice(0, 139)}…` : text,
|
|
1996
|
+
scope: ps.length ? ps.join(', ') : 'all projects', enforcement: l.enforcement ?? null,
|
|
1997
|
+
ratified: l.status === STATUS.RATIFIED || l.status === STATUS.ACTIVE, status: l.status };
|
|
1998
|
+
});
|
|
1999
|
+
return { available: true, count: approved.length, applicable: mine.length, items, itemsTruncated: mine.length > items.length };
|
|
2000
|
+
} catch { return { available: false, count: null, applicable: null, items: [], itemsTruncated: false }; }
|
|
2001
|
+
}
|
|
2002
|
+
// #499: a count that could not be read is null, not 0 — "0" would claim the store is empty.
|
|
2003
|
+
function readCount(db, sql) {
|
|
2004
|
+
const r = robustRead(db, sql);
|
|
2005
|
+
if (!r.ok) return null;
|
|
2006
|
+
const n = Number(r.value ?? 0);
|
|
2007
|
+
return Number.isFinite(n) ? n : null;
|
|
2008
|
+
}
|
|
1931
2009
|
function gatherActivity(cwd) {
|
|
1932
2010
|
let project;
|
|
1933
2011
|
let db;
|
|
@@ -1937,19 +2015,27 @@ function gatherActivity(cwd) {
|
|
|
1937
2015
|
db = resolved.canonicalAgentDbPath;
|
|
1938
2016
|
} catch {
|
|
1939
2017
|
const measuredAt = new Date().toISOString();
|
|
1940
|
-
return { generatedAt: measuredAt, project: path.basename(cwd), hasStore: false, continuity: 'unavailable', ...freshnessOf(measuredAt) };
|
|
2018
|
+
return { generatedAt: measuredAt, project: path.basename(cwd), hasStore: false, continuity: 'unavailable', approvedLessons: approvedLessonsFigure(projectNameFor(cwd)), ...freshnessOf(measuredAt) };
|
|
1941
2019
|
}
|
|
1942
2020
|
if (!fs.existsSync(db)) {
|
|
1943
2021
|
// No store to read — the existence check above IS the entire measurement, so it IS the
|
|
1944
2022
|
// observation instant. Stamped here, not with a value taken before the check ran.
|
|
1945
2023
|
const measuredAt = new Date().toISOString();
|
|
1946
|
-
return { generatedAt: measuredAt, project: path.basename(project), hasStore: false, ...freshnessOf(measuredAt) };
|
|
2024
|
+
return { generatedAt: measuredAt, project: path.basename(project), hasStore: false, approvedLessons: approvedLessonsFigure(path.basename(project)), ...freshnessOf(measuredAt) };
|
|
1947
2025
|
}
|
|
1948
|
-
|
|
2026
|
+
// projectId = the same dev:inode identity the fleet entries carry, so the client matches on identity, not the folder name.
|
|
2027
|
+
const out = { project: path.basename(project), projectId: pathIdentity(db) ?? path.resolve(db), hasStore: true, approvedLessons: approvedLessonsFigure(path.basename(project)) };
|
|
1949
2028
|
const rows = (sql) => robustReadJSON(db, sql).rows;
|
|
2029
|
+
const agentdbLessons = readCount(db, "SELECT COUNT(*) FROM memory_entries WHERE namespace='lessons' AND status='active'");
|
|
1950
2030
|
out.totals = {
|
|
1951
|
-
memories:
|
|
1952
|
-
|
|
2031
|
+
memories: readCount(db, "SELECT COUNT(*) FROM memory_entries WHERE status='active'"),
|
|
2032
|
+
agentdbLessons,
|
|
2033
|
+
lessons: agentdbLessons, // legacy alias of agentdbLessons
|
|
2034
|
+
// #499: the learning pipeline's OWN measurements (null = unreadable, shown as Unknown, never 0). Distillation
|
|
2035
|
+
// writes patterns/episodes, never `lessons` rows; observations are the namespace it mines.
|
|
2036
|
+
observations: readCount(db, "SELECT COUNT(*) FROM memory_entries WHERE namespace='learning-observations' AND status='active'"),
|
|
2037
|
+
patterns: readCount(db, 'SELECT COUNT(*) FROM reasoning_patterns'),
|
|
2038
|
+
episodes: readCount(db, 'SELECT COUNT(*) FROM episodes'),
|
|
1953
2039
|
};
|
|
1954
2040
|
out.lessons = rows("SELECT key, access_count, date(created_at/1000,'unixepoch') AS learned, substr(content,1,600) AS excerpt FROM memory_entries WHERE namespace='lessons' AND status='active' ORDER BY created_at DESC");
|
|
1955
2041
|
out.recent = rows("SELECT key, namespace, type, datetime(updated_at/1000,'unixepoch','localtime') AS at FROM memory_entries WHERE status='active' ORDER BY updated_at DESC LIMIT 18");
|
|
@@ -2613,12 +2699,8 @@ function apply(ids) {
|
|
|
2613
2699
|
} : {});
|
|
2614
2700
|
if (res.ok && learningBefore) {
|
|
2615
2701
|
const after = observeLearning();
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
if (!progressed) {
|
|
2619
|
-
res.ok = false;
|
|
2620
|
-
res.log += `\nNo measured learning progress: queue ${after.queueDir}; learner ${after.learnerCwd}. Reload to inspect the remaining evidence.`;
|
|
2621
|
-
}
|
|
2702
|
+
res.ok = learningProgressed(learningBefore, after);
|
|
2703
|
+
if (!res.ok) res.log += `\nNo measured learning progress: ${learningProgressDetail(learningBefore, after)}. Reload to inspect the remaining evidence.`;
|
|
2622
2704
|
}
|
|
2623
2705
|
phaseMs.childRemedyMs += elapsedMs(remedyStartedAt);
|
|
2624
2706
|
results.push({ id, ...res, undoToken });
|
|
@@ -2628,6 +2710,21 @@ function apply(ids) {
|
|
|
2628
2710
|
return { results, timings: { ...phaseMs, totalMs: elapsedMs(startedAt) } };
|
|
2629
2711
|
}
|
|
2630
2712
|
|
|
2713
|
+
/**
|
|
2714
|
+
* #377: Apply on "Your learner has gone quiet" counts as progress only when `ruflo memory distill
|
|
2715
|
+
* status` shows the learner's last distillation moved FORWARD. A readable store alone is not progress.
|
|
2716
|
+
*/
|
|
2717
|
+
export function learningProgressed(before, after) {
|
|
2718
|
+
const was = Date.parse(before?.lastDistillAt || '');
|
|
2719
|
+
const now = Date.parse(after?.lastDistillAt || '');
|
|
2720
|
+
return !!(before?.statusKnown && after?.statusKnown && after.distillKnown === true
|
|
2721
|
+
&& Number.isFinite(now) && (!Number.isFinite(was) || now > was));
|
|
2722
|
+
}
|
|
2723
|
+
export function learningProgressDetail(before, after) {
|
|
2724
|
+
return `learner ${after?.learningDb || before?.learningDb || 'unresolved'}; last distillation `
|
|
2725
|
+
+ `${before?.lastDistillAt || 'not recorded'} → ${after?.distillKnown === true ? (after.lastDistillAt || 'not recorded') : 'unknown'}`;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2631
2728
|
function autoEligibleIds(recommendations = []) {
|
|
2632
2729
|
return recommendations
|
|
2633
2730
|
.filter((rec) => rec?.scope === 'project')
|
|
@@ -3511,6 +3608,9 @@ export {
|
|
|
3511
3608
|
saveBrainProfile,
|
|
3512
3609
|
setLesson,
|
|
3513
3610
|
gatherLessons,
|
|
3611
|
+
gatherActivity,
|
|
3612
|
+
refreshFleetCache,
|
|
3613
|
+
loadConsoleCache,
|
|
3514
3614
|
gatherRouterEngine,
|
|
3515
3615
|
autoEligibleIds,
|
|
3516
3616
|
gatherConfig,
|
|
@@ -20,6 +20,7 @@ import fs from 'node:fs';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { wilson } from '../eval-brain.mjs';
|
|
23
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
23
24
|
|
|
24
25
|
const rate = (k, n) => { const w = wilson(k, n); return { k, n, p: +w.p.toFixed(4), lo: +w.lo.toFixed(4), hi: +w.hi.toFixed(4) }; };
|
|
25
26
|
const within5 = (r) => (r.fileRank != null && r.fileRank <= 5) || (r.altFileRank != null && r.altFileRank <= 5);
|
|
@@ -59,4 +60,4 @@ async function main() {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
if (
|
|
63
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -28,6 +28,7 @@ import fs from 'node:fs';
|
|
|
28
28
|
import path from 'node:path';
|
|
29
29
|
import readline from 'node:readline';
|
|
30
30
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
31
32
|
|
|
32
33
|
const args = process.argv.slice(2);
|
|
33
34
|
const arg = (n, d) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : d; };
|
|
@@ -136,4 +137,4 @@ async function main() {
|
|
|
136
137
|
process.exit(0);
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
if (
|
|
140
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -23,6 +23,7 @@ import os from 'node:os';
|
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { claudeArgs, spawnHost, isQuotaRefusal } from './producer-hosts.mjs';
|
|
25
25
|
import { subscriptionOnlyEnv } from '../subscription-hosts.mjs';
|
|
26
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
26
27
|
|
|
27
28
|
export const EXCERPT_CHARS = 1500;
|
|
28
29
|
export const D2Q_SCHEMA = Object.freeze({
|
|
@@ -159,4 +160,4 @@ async function main() {
|
|
|
159
160
|
console.log(JSON.stringify({ files: docs.length, written, stopped: stop }));
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
if (
|
|
163
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -22,6 +22,7 @@ import fs from 'node:fs';
|
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
import { wilson } from '../eval-brain.mjs';
|
|
25
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
25
26
|
|
|
26
27
|
/** Collapse ranked entry hits (each pointing at a file) to the first `files` distinct files. */
|
|
27
28
|
export function filesFromEntries(hits, pathOfId, files) {
|
|
@@ -107,4 +108,4 @@ async function main() {
|
|
|
107
108
|
process.exit(0);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
if (
|
|
111
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -21,6 +21,7 @@ import fs from 'node:fs';
|
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
import { wilson } from '../eval-brain.mjs';
|
|
24
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
24
25
|
|
|
25
26
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
26
27
|
const { JUDGE_FEATURES, judgeFeatures, judgeLogit } = await import(pathToFileURL(path.join(ROOT, 'kb/judge-rank.mjs')).href);
|
|
@@ -155,4 +156,4 @@ async function main() {
|
|
|
155
156
|
console.log(JSON.stringify({ threshold: tau, trainedOn: model.trainedOn, heldout: report.heldout }, null, 1));
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
if (
|
|
159
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -13,6 +13,7 @@ import { createHash } from 'node:crypto';
|
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
16
17
|
|
|
17
18
|
export function splitNeedSet(questions, { salt = 'v1', trainFraction = 0.5 } = {}) {
|
|
18
19
|
const byRepo = new Map();
|
|
@@ -45,4 +46,4 @@ async function main() {
|
|
|
45
46
|
console.log(JSON.stringify({ train: split.train.length, heldout: split.heldout.length, contentHash: set.contentHash }));
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
if (
|
|
49
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
DEFAULT_ROLES, LABEL_SCHEMA, PRODUCER_MODELS, VERDICT_SCHEMA, generatorPrompt, hostAdapters, isQuotaRefusal,
|
|
35
35
|
judgePrompt, spawnHost,
|
|
36
36
|
} from './producer-hosts.mjs';
|
|
37
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
37
38
|
|
|
38
39
|
export {
|
|
39
40
|
CLAUDE_SYSTEM_PROMPT, CLAUDE_TIMEOUT_MS, CODEX_TIMEOUT_MS, DEFAULT_ROLES, JUDGE_SYSTEM_PROMPT, LABEL_SCHEMA,
|
|
@@ -275,19 +276,4 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
275
276
|
return labels.suspended ? 75 : 0;
|
|
276
277
|
}
|
|
277
278
|
|
|
278
|
-
|
|
279
|
-
// NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
|
|
280
|
-
// shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
|
|
281
|
-
// never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
|
|
282
|
-
// from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
|
|
283
|
-
// archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
|
|
284
|
-
function isDirectInvocation() {
|
|
285
|
-
try {
|
|
286
|
-
if (!process.argv[1]) return false;
|
|
287
|
-
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
288
|
-
} catch {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (isDirectInvocation()) process.exitCode = await main();
|
|
279
|
+
if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
|
|
@@ -36,6 +36,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
36
36
|
import { extractZip } from '../../kb/zip-extract.mjs';
|
|
37
37
|
import { validateCoverageLedger } from '../coverage-integrity.mjs';
|
|
38
38
|
import { retiredFixtureStores } from '../fixture-denominator.mjs';
|
|
39
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
39
40
|
|
|
40
41
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
41
42
|
|
|
@@ -556,8 +557,7 @@ export async function main(argv = process.argv.slice(2), { searchAll = null } =
|
|
|
556
557
|
// archive on disk (measured 2026-09-14).
|
|
557
558
|
function isMain() {
|
|
558
559
|
try {
|
|
559
|
-
|
|
560
|
-
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
560
|
+
return isDirectInvocation(import.meta.url);
|
|
561
561
|
} catch { return false; }
|
|
562
562
|
}
|
|
563
563
|
|
|
@@ -54,6 +54,7 @@ import os from 'node:os';
|
|
|
54
54
|
import path from 'node:path';
|
|
55
55
|
import { fileURLToPath } from 'node:url';
|
|
56
56
|
import { extractZip } from '../../kb/zip-extract.mjs';
|
|
57
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
57
58
|
|
|
58
59
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
59
60
|
const ROOT = path.resolve(HERE, '..', '..');
|
|
@@ -871,8 +872,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
871
872
|
// with no archive and no receipt on disk. Same defect, same fix as plugin/scripts/hook-input.mjs:518.
|
|
872
873
|
function isMain() {
|
|
873
874
|
try {
|
|
874
|
-
|
|
875
|
-
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
875
|
+
return isDirectInvocation(import.meta.url);
|
|
876
876
|
} catch {
|
|
877
877
|
return false;
|
|
878
878
|
}
|
|
@@ -22,6 +22,7 @@ import path from 'node:path';
|
|
|
22
22
|
import { createRequire } from 'node:module';
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
import { wilson } from '../eval-brain.mjs';
|
|
25
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
25
26
|
|
|
26
27
|
/** Rank (1-based) of the first hit whose path is `gold`, or null within the list. */
|
|
27
28
|
export function rankOf(paths, gold) {
|
|
@@ -136,4 +137,4 @@ async function main() {
|
|
|
136
137
|
process.exit(0);
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
if (
|
|
140
|
+
if (isDirectInvocation(import.meta.url)) await main();
|
|
@@ -20,6 +20,7 @@ import fs from 'node:fs';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
23
24
|
|
|
24
25
|
export const SOURCE_TREE_VERSION = 'oracle-source-tree/1';
|
|
25
26
|
export const LFS_POINTER_PREFIX = 'version https://git-lfs.github.com/spec/v1';
|
|
@@ -148,18 +149,4 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
148
149
|
return 0;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
// NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
|
|
153
|
-
// shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
|
|
154
|
-
// never runs -- and because nothing throws, the process exits 0. Pinned by
|
|
155
|
-
// tests/unit/entrypoint-symlink.test.mjs.
|
|
156
|
-
function isDirectInvocation() {
|
|
157
|
-
try {
|
|
158
|
-
if (!process.argv[1]) return false;
|
|
159
|
-
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
160
|
-
} catch {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (isDirectInvocation()) process.exitCode = main();
|
|
152
|
+
if (isDirectInvocation(import.meta.url)) process.exitCode = main();
|
|
@@ -25,6 +25,7 @@ import { createHash } from 'node:crypto';
|
|
|
25
25
|
import { execFileSync } from 'node:child_process';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
import { parse as babelParse } from '@babel/parser';
|
|
28
|
+
import { isDirectInvocation } from '../../plugin/scripts/entrypoint.mjs';
|
|
28
29
|
|
|
29
30
|
export const RULES_VERSION = 'oracle-source-units/1';
|
|
30
31
|
export const MAX_SELECTED = 100;
|
|
@@ -373,19 +374,4 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
373
374
|
return 0;
|
|
374
375
|
}
|
|
375
376
|
|
|
376
|
-
|
|
377
|
-
// NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
|
|
378
|
-
// shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
|
|
379
|
-
// never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
|
|
380
|
-
// from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
|
|
381
|
-
// archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
|
|
382
|
-
function isDirectInvocation() {
|
|
383
|
-
try {
|
|
384
|
-
if (!process.argv[1]) return false;
|
|
385
|
-
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
386
|
-
} catch {
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (isDirectInvocation()) process.exitCode = main();
|
|
377
|
+
if (isDirectInvocation(import.meta.url)) process.exitCode = main();
|