ruvnet-brain 4.7.20 → 4.7.21
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 +494 -97
- 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.mjs +15 -5
- 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 +56 -12
- package/plugin/scripts/continuation-gate.mjs +28 -9
- 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-promote.mjs +83 -22
- 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/session-snapshot-hook.mjs +24 -4
- package/plugin/scripts/session-start-core.mjs +5 -15
- package/plugin/scripts/session-start-health.mjs +97 -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-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 +47 -8
- 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/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 +64 -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 +11 -5
- package/scripts/worktree-integrity.mjs +2 -2
|
@@ -220,14 +220,14 @@ function currency(artifact, repo, maxAgeDays) {
|
|
|
220
220
|
const changed = diff.status === 0
|
|
221
221
|
? diff.stdout.split('\n').map((value) => value.trim()).filter(Boolean)
|
|
222
222
|
: [];
|
|
223
|
-
if (changed.length) return { ok: false, why: `load-bearing files changed: ${changed.join(', ')}` };
|
|
223
|
+
if (changed.length) return { ok: false, stale: true, why: `load-bearing files changed: ${changed.join(', ')}` };
|
|
224
224
|
}
|
|
225
225
|
const age = artifact.at ? (Date.now() - Date.parse(artifact.at)) / 86_400_000 : Infinity;
|
|
226
|
-
if (!(age <= maxAgeDays)) return { ok: false, why: `artifact is older than ${maxAgeDays} days` };
|
|
226
|
+
if (!(age <= maxAgeDays)) return { ok: false, stale: true, why: `artifact is older than ${maxAgeDays} days` };
|
|
227
227
|
return { ok: true };
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
export function checkArtifact({ file = RESULT_FILE, repo = ROOT, maxAgeDays = 14 } = {}) {
|
|
230
|
+
export function checkArtifact({ file = RESULT_FILE, repo = ROOT, maxAgeDays = 14, tolerateStale = false } = {}) {
|
|
231
231
|
if (!fs.existsSync(file)) return { status: VERDICT.UNKNOWN, why: 'result artifact does not exist' };
|
|
232
232
|
let artifact;
|
|
233
233
|
try { artifact = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
@@ -236,7 +236,8 @@ export function checkArtifact({ file = RESULT_FILE, repo = ROOT, maxAgeDays = 14
|
|
|
236
236
|
const source = checkSourceIdentity({ repo });
|
|
237
237
|
if (!source.clean) return { status: VERDICT.UNKNOWN, why: source.why, artifact };
|
|
238
238
|
const current = currency(artifact, repo, maxAgeDays);
|
|
239
|
-
|
|
239
|
+
// `stale`: the one UNKNOWN a re-measurement fixes. Waiving it still verifies and recomputes everything below.
|
|
240
|
+
if (!current.ok && !(tolerateStale && current.stale)) return { status: VERDICT.UNKNOWN, why: current.why, ...(current.stale ? { stale: true } : {}), artifact };
|
|
240
241
|
const evidence = verifyRuns(artifact, repo);
|
|
241
242
|
if (!evidence.ok) return { status: VERDICT.UNKNOWN, why: evidence.why, artifact };
|
|
242
243
|
const recomputed = aggregate(evidence.inputs);
|
|
@@ -244,7 +245,7 @@ export function checkArtifact({ file = RESULT_FILE, repo = ROOT, maxAgeDays = 14
|
|
|
244
245
|
return { status: VERDICT.UNKNOWN, why: recomputed.why, artifact, recomputed };
|
|
245
246
|
}
|
|
246
247
|
const mismatch = aggregateMismatch(artifact, recomputed);
|
|
247
|
-
if (mismatch) return { status: VERDICT.FAIL, why: mismatch, artifact, recomputed };
|
|
248
|
+
if (mismatch) return { status: VERDICT.FAIL, inconsistent: true, why: mismatch, artifact, recomputed }; // claims contradict own evidence
|
|
248
249
|
return {
|
|
249
250
|
status: recomputed.verdict,
|
|
250
251
|
why: `${recomputed.verdict} — ${recomputed.passes}/${recomputed.n} recomputed runs on ${artifact.sha.slice(0, 8)}`,
|
|
@@ -279,18 +280,18 @@ function causalMutantCheck(artifact, recomputed, mutant) {
|
|
|
279
280
|
export function checkMutantArtifacts({
|
|
280
281
|
files = MUTANT_RESULT_FILES,
|
|
281
282
|
repo = ROOT,
|
|
282
|
-
maxAgeDays = 14,
|
|
283
|
+
maxAgeDays = 14, tolerateStale = false,
|
|
283
284
|
} = {}) {
|
|
284
285
|
const checked = [];
|
|
285
286
|
for (const trap of [TRAP.MEMORY_SEARCH, TRAP.POST_TASK]) {
|
|
286
287
|
for (const mutant of ['delete-lesson', 'brain-off-treated']) {
|
|
287
288
|
const file = files[trap]?.[mutant];
|
|
288
289
|
if (!file) return { status: VERDICT.UNKNOWN, why: `${trap}/${mutant}: artifact absent`, checked };
|
|
289
|
-
const result = checkArtifact({ file, repo, maxAgeDays });
|
|
290
|
+
const result = checkArtifact({ file, repo, maxAgeDays, tolerateStale });
|
|
290
291
|
if (result.status === VERDICT.UNKNOWN) return { ...result, why: `${trap}/${mutant}: ${result.why}`, checked };
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
292
|
+
// Only a FAIL recomputed from self-consistent evidence is the expected one; claims that contradict the hashed transcripts are a forged receipt (Codex 4.7.21 r5 #1).
|
|
293
|
+
if (result.inconsistent) return { status: VERDICT.FAIL, why: `${trap}/${mutant}: mutant evidence is inconsistent: ${result.why}`, checked };
|
|
294
|
+
if (result.artifact?.mutant !== mutant || result.artifact?.trap !== trap) return { status: VERDICT.FAIL, why: `${trap}/${mutant}: artifact identity mismatch`, checked };
|
|
294
295
|
const causal = causalMutantCheck(result.artifact, result.recomputed, mutant);
|
|
295
296
|
if (causal) return { status: VERDICT.FAIL, why: `${trap}/${mutant}: ${causal}`, checked };
|
|
296
297
|
checked.push(`${trap}/${mutant}`);
|
|
@@ -303,11 +304,11 @@ export function checkPortfolio({
|
|
|
303
304
|
files = PORTFOLIO_RESULT_FILES,
|
|
304
305
|
mutantFiles = MUTANT_RESULT_FILES,
|
|
305
306
|
repo = ROOT,
|
|
306
|
-
maxAgeDays = 14,
|
|
307
|
+
maxAgeDays = 14, tolerateStale = false,
|
|
307
308
|
} = {}) {
|
|
308
309
|
const artifacts = [];
|
|
309
310
|
for (const trap of [TRAP.MEMORY_SEARCH, TRAP.POST_TASK]) {
|
|
310
|
-
const result = checkArtifact({ file: files[trap], repo, maxAgeDays });
|
|
311
|
+
const result = checkArtifact({ file: files[trap], repo, maxAgeDays, tolerateStale });
|
|
311
312
|
if (result.status !== VERDICT.PASS) return { ...result, why: `${trap}: ${result.why}`, artifacts };
|
|
312
313
|
const metrics = result.recomputed;
|
|
313
314
|
const artifact = result.artifact;
|
|
@@ -328,7 +329,7 @@ export function checkPortfolio({
|
|
|
328
329
|
|| artifacts[0].artifact.taskHash === artifacts[1].artifact.taskHash) {
|
|
329
330
|
return { status: VERDICT.FAIL, why: 'portfolio traps are not independent', artifacts };
|
|
330
331
|
}
|
|
331
|
-
const mutants = checkMutantArtifacts({ files: mutantFiles, repo, maxAgeDays });
|
|
332
|
+
const mutants = checkMutantArtifacts({ files: mutantFiles, repo, maxAgeDays, tolerateStale });
|
|
332
333
|
if (mutants.status !== VERDICT.PASS) return { ...mutants, why: `portfolio mutants: ${mutants.why}`, artifacts };
|
|
333
334
|
return { status: VERDICT.PASS, why: 'portfolio recomputed both traps and all causal mutants', artifacts, mutants };
|
|
334
335
|
}
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { main } from './learning-replay-cli.mjs';
|
|
6
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
6
7
|
|
|
7
8
|
export * from './learning-replay-contract.mjs';
|
|
8
9
|
export * from './learning-replay-execution.mjs';
|
|
9
10
|
export * from './learning-replay-fixture.mjs';
|
|
10
11
|
export * from './learning-replay-proof.mjs';
|
|
11
|
-
export { main, measurePortfolio } from './learning-replay-cli.mjs';
|
|
12
|
+
export { main, measurePortfolio, checkWire } from './learning-replay-cli.mjs';
|
|
12
13
|
|
|
13
|
-
const invokedDirectly =
|
|
14
|
-
&& path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
14
|
+
const invokedDirectly = isDirectInvocation(import.meta.url);
|
|
15
15
|
if (invokedDirectly) process.exit(await main());
|
package/scripts/learnings.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import os from 'node:os';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { learningScope, learnerCwd } from '../plugin/scripts/runtime-preferences.mjs';
|
|
12
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
12
13
|
|
|
13
14
|
const HOME = os.homedir();
|
|
14
15
|
|
|
@@ -73,4 +74,4 @@ export function learnings({ statsPath, queueDir, now = Date.now(), cwd, env, hom
|
|
|
73
74
|
|
|
74
75
|
export function printLearnings(l) { console.log(JSON.stringify(l, null, 2)); }
|
|
75
76
|
|
|
76
|
-
if (
|
|
77
|
+
if (isDirectInvocation(import.meta.url)) printLearnings(learnings());
|
|
@@ -24,23 +24,27 @@
|
|
|
24
24
|
* Demotion is STICKY — it survives every future mining run. A one-click reject that the next
|
|
25
25
|
* nightly quietly undoes is worse than no control at all, because the user stops trusting it and,
|
|
26
26
|
* correctly, stops using it.
|
|
27
|
+
*
|
|
28
|
+
* MUTATIONS GO THROUGH `updateLessons()`, NOT `loadLessons()` + `saveLessons()`. The earlier version
|
|
29
|
+
* read `lessons` once at module load, then handed that same in-memory snapshot to `saveLessons()`
|
|
30
|
+
* after transforming it — the exact unlocked read-modify-write `lesson-store.mjs`'s own header
|
|
31
|
+
* documents destroying three ratified rules on 2026-07-22, and the exact race `updateLessons()` was
|
|
32
|
+
* written to close. `saveLessons()` taking a lock around the WRITE never protected the READ that
|
|
33
|
+
* preceded it: two ratify/demote invocations overlapping (a double-click, a script driving two calls
|
|
34
|
+
* back to back) could both load the same pre-change snapshot, and the second writer's save would
|
|
35
|
+
* silently discard the first writer's change — on the one file this CLI exists to make trustworthy.
|
|
36
|
+
* `updateLessons()` holds the lock across read → transform → write, so a concurrent writer's change
|
|
37
|
+
* is never lost.
|
|
27
38
|
*/
|
|
39
|
+
import fs from 'node:fs';
|
|
28
40
|
import os from 'node:os';
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const arg = (f) => { const i = argv.indexOf(f); return i >= 0 && argv[i + 1] ? argv[i + 1] : null; };
|
|
33
|
-
const has = (f) => argv.includes(f);
|
|
34
|
-
|
|
35
|
-
const lessons = loadLessons();
|
|
36
|
-
if (!lessons.length) {
|
|
37
|
-
console.log('\n No personal lessons stored yet. They appear after an explicit user correction is captured.\n');
|
|
38
|
-
process.exit(0);
|
|
39
|
-
}
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
import { loadLessons, updateLessons, ratify, demote, weightOf, pending, ENFORCEMENT, STATUS, ORIGIN, SOURCE_CLASS, TRIGGERS } from './lesson-store.mjs';
|
|
43
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
40
44
|
|
|
41
45
|
const FORCE = { block: '⛔ BLOCKS', checklist: '☑ checklist', inject: '· context', review: '👁 review only' };
|
|
42
46
|
|
|
43
|
-
function list() {
|
|
47
|
+
export function list(lessons) {
|
|
44
48
|
const pend = pending(lessons);
|
|
45
49
|
console.log(`\n ${lessons.length} lessons — ${pend.length} awaiting your decision.\n`);
|
|
46
50
|
console.log(' Nothing here refuses your work until YOU ratify it. The model does not get to');
|
|
@@ -67,35 +71,79 @@ function list() {
|
|
|
67
71
|
console.log(' node scripts/lesson-ratify.mjs --ratify-all-user-stated\n');
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Apply `mutate(id, lessons)` to the lesson `id`, holding the store lock across the read and the
|
|
76
|
+
* write (via `updateLessons`) so a concurrent ratify/demote can never be read as stale and clobbered.
|
|
77
|
+
* Returns the lesson's new state, or null if no lesson with `id` exists (the store is left untouched).
|
|
78
|
+
*/
|
|
79
|
+
export function applyMutation(id, mutate, file) {
|
|
80
|
+
let result = null;
|
|
81
|
+
updateLessons((fresh) => {
|
|
82
|
+
if (!fresh.find((x) => x.id === id)) return fresh;
|
|
83
|
+
const next = mutate(id, fresh);
|
|
84
|
+
result = next.find((x) => x.id === id);
|
|
85
|
+
return next;
|
|
86
|
+
}, file);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Bulk convenience, deliberately scoped: it can only touch lessons the USER stated. Model-inferred
|
|
92
|
+
* lessons are never swept up by a bulk action — that would be exactly the hole the boundary closes.
|
|
93
|
+
* Also lock-protected end to end, for the same reason as `applyMutation`.
|
|
94
|
+
*/
|
|
95
|
+
export function applyRatifyAllUserStated(file) {
|
|
96
|
+
let ratifiedIds = [];
|
|
97
|
+
let nowBlocking = 0;
|
|
98
|
+
updateLessons((fresh) => {
|
|
99
|
+
const targets = fresh.filter((l) => l.origin === ORIGIN.USER_STATED
|
|
100
|
+
&& l.sourceClass === SOURCE_CLASS.CURRENT_USER
|
|
101
|
+
&& l.status === STATUS.CANDIDATE
|
|
102
|
+
&& !l.demoted);
|
|
103
|
+
let next = fresh;
|
|
104
|
+
for (const l of targets) next = ratify(l.id, next);
|
|
105
|
+
ratifiedIds = targets.map((l) => l.id);
|
|
106
|
+
nowBlocking = next.filter((l) => l.enforcement === ENFORCEMENT.BLOCK).length;
|
|
107
|
+
return next;
|
|
108
|
+
}, file);
|
|
109
|
+
return { ratifiedCount: ratifiedIds.length, nowBlocking };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function printResult(result, id, verb) {
|
|
113
|
+
if (!result) { console.log(`\n No lesson with id "${id}". Run --list to see them.\n`); process.exit(1); }
|
|
114
|
+
console.log(`\n ✓ ${verb} ${result.id}`);
|
|
115
|
+
console.log(` now: ${FORCE[result.enforcement] || result.enforcement}${result.demoted ? ' (demoted — will never fire again, including after future mining runs)' : ''}`);
|
|
76
116
|
console.log(` stored at ${(process.env.RUVNET_LESSON_STORE || '~/.config/ruvnet-brain/lessons.json').replace(os.homedir(), '~')}\n`);
|
|
77
117
|
}
|
|
78
118
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
/** am I the entrypoint? — `fileURLToPath`, never `.pathname` (Windows); `realpathSync` guarded (vitest). */
|
|
120
|
+
function isEntrypoint() {
|
|
121
|
+
return isDirectInvocation(import.meta.url);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isEntrypoint()) {
|
|
125
|
+
const argv = process.argv.slice(2);
|
|
126
|
+
const arg = (f) => { const i = argv.indexOf(f); return i >= 0 && argv[i + 1] ? argv[i + 1] : null; };
|
|
127
|
+
const has = (f) => argv.includes(f);
|
|
128
|
+
|
|
129
|
+
const lessons = loadLessons();
|
|
130
|
+
if (!lessons.length) {
|
|
131
|
+
console.log('\n No personal lessons stored yet. They appear after an explicit user correction is captured.\n');
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (has('--ratify')) {
|
|
136
|
+
const id = arg('--ratify');
|
|
137
|
+
printResult(applyMutation(id, ratify), id, 'ratified');
|
|
138
|
+
} else if (has('--demote')) {
|
|
139
|
+
const id = arg('--demote');
|
|
140
|
+
printResult(applyMutation(id, demote), id, 'demoted');
|
|
141
|
+
} else if (has('--ratify-all-user-stated')) {
|
|
142
|
+
const { ratifiedCount, nowBlocking } = applyRatifyAllUserStated();
|
|
143
|
+
console.log(`\n ✓ ratified ${ratifiedCount} lesson(s) you stated yourself.`);
|
|
144
|
+
console.log(` ${nowBlocking} now BLOCK at their decision point. Model-inferred lessons were left`);
|
|
145
|
+
console.log(` as candidates — a bulk action may never promote something the model inferred.\n`);
|
|
146
|
+
} else {
|
|
147
|
+
list(lessons);
|
|
148
|
+
}
|
|
101
149
|
}
|
|
@@ -26,6 +26,7 @@ import fs from 'node:fs';
|
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { spawnSync } from 'node:child_process';
|
|
28
28
|
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
29
30
|
|
|
30
31
|
const argv = process.argv.slice(2);
|
|
31
32
|
const cmd = argv[0];
|
|
@@ -91,7 +92,7 @@ export function checkCheckpoint(file = FILE, runner = (c) => spawnSync('sh', ['-
|
|
|
91
92
|
return { code: 0, verdict: `continue — iteration ${cp.iteration}, next: ${cp.next}` };
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const invokedDirectly =
|
|
95
|
+
const invokedDirectly = isDirectInvocation(import.meta.url);
|
|
95
96
|
if (invokedDirectly) {
|
|
96
97
|
if (cmd === 'read') {
|
|
97
98
|
console.log(JSON.stringify(readCheckpoint() ?? {}, null, 2));
|
|
@@ -12,6 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
14
|
import { CLAUDE_MODEL_IDS } from './route-cheap.mjs';
|
|
15
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
15
16
|
|
|
16
17
|
export function receiptsPath() {
|
|
17
18
|
return (
|
|
@@ -165,4 +166,4 @@ function main() {
|
|
|
165
166
|
if (skipped) console.log(`(${skipped} corrupt line(s) skipped)`);
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
if (
|
|
169
|
+
if (isDirectInvocation(import.meta.url)) main();
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
11
12
|
|
|
12
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const ROOT = path.resolve(__dirname, '..');
|
|
@@ -70,7 +71,7 @@ export function providerChoices(catalog) {
|
|
|
70
71
|
return Object.entries(catalog.providers || {}).map(([id, p]) => ({ id, label: p.label || id }));
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
if (
|
|
74
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
74
75
|
const c = loadCatalog();
|
|
75
76
|
const det = detectProvider(c, {});
|
|
76
77
|
console.log(`detected house: ${det.provider} (${det.source})`);
|
|
@@ -96,7 +96,9 @@ export function parseCodingAgentEvidence(html, methodology, { url, checkedAt, id
|
|
|
96
96
|
const walk = (value) => {
|
|
97
97
|
if (Array.isArray(value)) { for (const child of value) walk(child); return; }
|
|
98
98
|
if (!value || typeof value !== 'object') return;
|
|
99
|
-
|
|
99
|
+
// A row is an agent/configuration record: harness name + index score. AA dropped `hostModelSlug` and
|
|
100
|
+
// `modelRelease` from the page in October 2026, so neither may be a key; both stay optional evidence.
|
|
101
|
+
if (typeof value.agentName === 'string' && typeof value.id === 'string' && value.indexScore !== undefined) { rows.set(value.id, value); return; }
|
|
100
102
|
for (const child of Object.values(value)) walk(child);
|
|
101
103
|
};
|
|
102
104
|
for (const match of html.matchAll(/self\.__next_f\.push\((.*?)\)<\/script>/gs)) {
|
|
@@ -104,12 +106,12 @@ export function parseCodingAgentEvidence(html, methodology, { url, checkedAt, id
|
|
|
104
106
|
const envelope = JSON.parse(match[1]);
|
|
105
107
|
if (typeof envelope[1] !== 'string') continue;
|
|
106
108
|
for (const line of envelope[1].split('\n')) {
|
|
107
|
-
if (!line.includes('
|
|
109
|
+
if (!line.includes('agentName')) continue;
|
|
108
110
|
try { walk(JSON.parse(line.slice(line.indexOf(':') + 1))); } catch { /* unrelated frame */ }
|
|
109
111
|
}
|
|
110
112
|
} catch { /* required contract checked below */ }
|
|
111
113
|
}
|
|
112
|
-
const source = { url, checkedAt, sha256: digest(html), parser: 'aa-coding-agents-flight-
|
|
114
|
+
const source = { url, checkedAt, sha256: digest(html), parser: 'aa-coding-agents-flight-v2' };
|
|
113
115
|
const records = [];
|
|
114
116
|
const skippedIncomplete = [];
|
|
115
117
|
for (const row of rows.values()) {
|
|
@@ -129,7 +131,7 @@ export function parseCodingAgentEvidence(html, methodology, { url, checkedAt, id
|
|
|
129
131
|
if (binding && (!binding.model || !binding.evidence || !binding.checkedAt)) throw new Error('Invalid coding-agent native identity binding');
|
|
130
132
|
const effort = nativeHost ? row.display.model.match(/\((low|medium|high|xhigh|max|minimal|none)\)/i)?.[1]?.toLowerCase() ?? null : null;
|
|
131
133
|
records.push({ sourceId: row.id, harness: row.agentName, provider: row.provider, nativeHost,
|
|
132
|
-
sourceModelSlug: row.hostModelSlug, sourceReleaseSlug: row.modelRelease?.slug ?? null,
|
|
134
|
+
sourceModelSlug: typeof row.hostModelSlug === 'string' ? row.hostModelSlug : null, sourceReleaseSlug: row.modelRelease?.slug ?? null,
|
|
133
135
|
displayModel: row.display.model, configurationLabel: row.displayLabel, effort,
|
|
134
136
|
model: binding?.model ?? null, identityEvidence: binding ?? null,
|
|
135
137
|
fallback: /fallback/i.test(row.displayLabel ?? row.display.model),
|
|
@@ -8,6 +8,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
8
8
|
import { buildWeeklyAssessment } from './model-weekly-assessment.mjs';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { WEEK_MS, digest, parseInventory, parseArtificialAnalysis, parseCodingAgentEvidence, currencyStatus } from './model-currency-evidence.mjs';
|
|
11
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
11
12
|
|
|
12
13
|
export const DEFAULT_ROUTER_DIR = path.join(os.homedir(), '.claude', 'model-router');
|
|
13
14
|
export const AA_URLS = [
|
|
@@ -218,13 +219,42 @@ export async function refreshModelCurrency({ routerDir = DEFAULT_ROUTER_DIR, now
|
|
|
218
219
|
} finally { release(routerDir, lock); }
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
|
|
222
|
+
/** The weekly CI verdict. Core evidence is the inventory, the effort evaluations and the local instruction; a
|
|
223
|
+
* third-party scrape that only feeds the advisory sections (coding agents, official docs, extra benchmark
|
|
224
|
+
* archives) failing on its own is `partial`: exit 0 with a warning, because the router's own facts are fresh. */
|
|
225
|
+
const ANCILLARY_ERROR = /^(coding agents|official |coding benchmark )/;
|
|
226
|
+
export function refreshOutcome(result) {
|
|
227
|
+
const errors = result?.lastAttempt?.errors ?? result?.errors ?? [];
|
|
228
|
+
const core = errors.filter((error) => !ANCILLARY_ERROR.test(error));
|
|
229
|
+
if (result?.action !== 'refreshed' || result.inventoryFresh !== true || result.evaluationsFresh !== true || core.length) {
|
|
230
|
+
return { status: 'failed', exitCode: 1, errors: core.length ? core : [...errors, result?.reason ?? 'core evidence not fresh'] };
|
|
231
|
+
}
|
|
232
|
+
return errors.length ? { status: 'partial', exitCode: 0, warnings: errors } : { status: 'complete', exitCode: 0, warnings: [] };
|
|
233
|
+
}
|
|
234
|
+
export function outcomeSummary(outcome) {
|
|
235
|
+
const lines = [`### model-currency refresh: ${outcome.status}`, ''];
|
|
236
|
+
for (const message of outcome.errors ?? []) lines.push(`- error: ${message}`);
|
|
237
|
+
for (const message of outcome.warnings ?? []) lines.push(`- warning: ${message}`);
|
|
238
|
+
if (outcome.status === 'partial') lines.push('', 'Inventory and evaluations are fresh; only a third-party advisory source failed.');
|
|
239
|
+
return `${lines.join('\n')}\n`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
222
243
|
const dirIndex = process.argv.indexOf('--router-dir');
|
|
223
244
|
const routerDir = dirIndex >= 0 ? process.argv[dirIndex + 1] : DEFAULT_ROUTER_DIR;
|
|
224
245
|
if (!routerDir || !path.isAbsolute(routerDir)) throw new Error('--router-dir must be absolute');
|
|
225
|
-
if (process.argv.includes('--
|
|
246
|
+
if (process.argv.includes('--summarize')) {
|
|
247
|
+
// Markdown for the CI job summary from a captured `--refresh` stdout; a missing or unreadable capture says so.
|
|
248
|
+
let outcome;
|
|
249
|
+
try { outcome = JSON.parse(fs.readFileSync(process.argv[process.argv.indexOf('--summarize') + 1], 'utf8').split('\n')[0]).outcome; }
|
|
250
|
+
catch { outcome = null; }
|
|
251
|
+
process.stdout.write(outcome ? outcomeSummary(outcome) : '### model-currency refresh: no result (the refresh step crashed before reporting)\n');
|
|
252
|
+
} else if (process.argv.includes('--refresh')) {
|
|
226
253
|
refreshModelCurrency({ routerDir, claimToken: process.argv.includes('--claim-token') ? process.argv[process.argv.indexOf('--claim-token') + 1] : null }).then((result) => {
|
|
227
|
-
|
|
254
|
+
const outcome = refreshOutcome(result);
|
|
255
|
+
console.log(JSON.stringify({ ...result, outcome }));
|
|
256
|
+
if (outcome.status === 'partial') console.log(`::warning title=model-currency partial::${outcome.warnings.join('; ').replace(/[\r\n]+/g, ' ')}`);
|
|
257
|
+
process.exitCode = outcome.exitCode;
|
|
228
258
|
}).catch((error) => { console.error(error.message); process.exitCode = 1; });
|
|
229
259
|
} else console.log(JSON.stringify(process.argv.includes('--catch-up') ? maybeLaunchCurrencyRefresh({ routerDir }) : readCurrencyStatus({ routerDir })));
|
|
230
260
|
}
|
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import { randomUUID } from 'node:crypto';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
8
9
|
const HOSTS = { codex: 'openai', 'claude-code': 'anthropic' };
|
|
9
10
|
const EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
10
11
|
const read = (file, optional = false) => {
|
|
@@ -104,7 +105,7 @@ export async function refreshNativeCatalog({ routerDir = path.join(os.homedir(),
|
|
|
104
105
|
if (acquired) { try { fs.rmdirSync(guard); } catch { /* not acquired */ } }
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
|
-
if (
|
|
108
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
108
109
|
const option = name => { const at = process.argv.indexOf(name); return at < 0 ? undefined : process.argv[at + 1]; };
|
|
109
110
|
refreshNativeCatalog({ routerDir: option('--router-dir'), deadline: option('--deadline') === undefined ? undefined : Number(option('--deadline')) })
|
|
110
111
|
.then(result => { console.log(JSON.stringify(result)); if (result.status !== 'current') process.exitCode = 1; });
|
|
@@ -10,6 +10,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
11
|
|
|
12
12
|
import { readCodexAllowance } from './native-subscription-usage.mjs';
|
|
13
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
13
14
|
|
|
14
15
|
const DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
const TASK_CLASSES = new Set(['fast', 'medium', 'substantial', 'hard', 'exceptional']);
|
|
@@ -126,7 +127,7 @@ async function main(argv) {
|
|
|
126
127
|
// The host's normal transcript records its tool input. This hook persists no task text.
|
|
127
128
|
process.stdout.write(JSON.stringify(output) + '\n');
|
|
128
129
|
}
|
|
129
|
-
if (
|
|
130
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
130
131
|
main(process.argv.slice(2)).catch(() => {
|
|
131
132
|
const args = process.argv.slice(2); const harness = args[args.indexOf('--harness') + 1];
|
|
132
133
|
const result = harness === 'codex' && loadScope(harness).strict ? deny('Agent routing hook failed; managed dispatch required.')
|
|
@@ -9,6 +9,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
9
9
|
import { assertCurrentSelection, selectionEvidenceStatus, loadSelection, loadCatalog, loadProfile, applyProfile, eligibleCandidates, TASK_CLASSES } from './model-router-engine.mjs';
|
|
10
10
|
|
|
11
11
|
import { readCodexAllowance } from './native-subscription-usage.mjs';
|
|
12
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
12
13
|
|
|
13
14
|
const DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const BILLING_ENV = /^(OPENAI_API_KEY|OPENAI_BASE_URL|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_BASE_URL|ANTHROPIC_CUSTOM_HEADERS|CLAUDE_CODE_USE_BEDROCK|CLAUDE_CODE_USE_VERTEX|CLAUDE_CODE_USE_FOUNDRY|CODEX_API_KEY)$/;
|
|
@@ -156,6 +157,6 @@ async function main(argv) {
|
|
|
156
157
|
taskClass: decision.taskClass, executed: false }) + '\n');
|
|
157
158
|
} else process.exitCode = await dispatch(decision, prompt, { interactive });
|
|
158
159
|
}
|
|
159
|
-
if (
|
|
160
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
160
161
|
main(process.argv.slice(2)).catch((e) => { process.stderr.write(`model-router-dispatch: ${e.message}\n`); process.exitCode = 1; });
|
|
161
162
|
}
|
|
@@ -12,6 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import crypto from 'node:crypto';
|
|
14
14
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
15
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
15
16
|
import { estTokens } from './route-cheap.mjs'; // reuse the verified char/4 estimator (DRY)
|
|
16
17
|
|
|
17
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -309,6 +310,6 @@ async function main() {
|
|
|
309
310
|
}
|
|
310
311
|
}
|
|
311
312
|
|
|
312
|
-
if (
|
|
313
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
313
314
|
main().catch((e) => { process.stderr.write(`model-router-engine: ${e.stack || e.message}\n`); process.exit(1); });
|
|
314
315
|
}
|
|
@@ -25,6 +25,7 @@ import fs from 'node:fs';
|
|
|
25
25
|
import path from 'node:path';
|
|
26
26
|
import os from 'node:os';
|
|
27
27
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
28
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
28
29
|
|
|
29
30
|
const OUTCOMES_LOG =
|
|
30
31
|
process.env.MODEL_ROUTER_OUTCOMES ||
|
|
@@ -105,6 +106,6 @@ function main() {
|
|
|
105
106
|
console.log(`[model-router-outcome] logged: ${JSON.stringify(entry)}`);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
if (
|
|
109
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
109
110
|
try { main(); } catch (e) { process.stderr.write(`model-router-outcome: ${e.message}\n`); process.exit(1); }
|
|
110
111
|
}
|
|
@@ -27,6 +27,7 @@ import path from 'node:path';
|
|
|
27
27
|
import os from 'node:os';
|
|
28
28
|
import { execFileSync } from 'node:child_process';
|
|
29
29
|
import readline from 'node:readline/promises';
|
|
30
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
30
31
|
|
|
31
32
|
const CONFIG_DIR = path.join(os.homedir(), '.claude', 'model-router');
|
|
32
33
|
export const PROFILE_PATH = process.env.MODEL_ROUTER_PROFILE || path.join(CONFIG_DIR, 'profile.json');
|
|
@@ -136,6 +137,6 @@ async function main() {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
const { pathToFileURL } = await import('node:url');
|
|
139
|
-
if (
|
|
140
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
140
141
|
main().catch((e) => { console.error(`model-router-setup: ${e.message}`); process.exit(1); });
|
|
141
142
|
}
|
|
@@ -8,6 +8,7 @@ import { performance } from 'node:perf_hooks';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { classify } from '../config/model-router/policy.default.mjs';
|
|
10
10
|
import { extractFeatures } from './model-router-engine.mjs';
|
|
11
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
11
12
|
|
|
12
13
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
14
|
const DEFAULT_CASES = path.join(ROOT, 'config/model-router/routing-eval-cases.json');
|
|
@@ -96,7 +97,7 @@ export function runEvaluation({ caseFile = DEFAULT_CASES, generatedAt } = {}) {
|
|
|
96
97
|
return report;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
if (
|
|
100
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
100
101
|
const args = process.argv.slice(2);
|
|
101
102
|
if (args.length && (args.length !== 2 || args[0] !== '--out')) throw new Error('Usage: node scripts/model-routing-eval.mjs [--out <report.json>]');
|
|
102
103
|
const report = runEvaluation();
|
|
@@ -11,6 +11,7 @@ import { StringDecoder } from 'node:string_decoder';
|
|
|
11
11
|
import { spawn, execFileSync } from 'node:child_process';
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
13
|
import { subscriptionEnvironment, assertSubscriptionAuth, validateDispatchDecision, loadNativeCodexModels } from './model-router-dispatch.mjs';
|
|
14
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
14
15
|
|
|
15
16
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const REFUSED = 'Current reviewed native model/effort allocation unavailable; new turn was not forwarded.';
|
|
@@ -448,6 +449,6 @@ async function main(argv) {
|
|
|
448
449
|
child.once('exit', (code, signal) => { process.stdin.pause(); process.stdin.unref?.(); process.exitCode = code ?? (signal ? 1 : 0); });
|
|
449
450
|
child.once('error', () => { process.stderr.write('[native-model-routing] Native host transport unavailable.\n'); process.exitCode = 1; });
|
|
450
451
|
}
|
|
451
|
-
if (
|
|
452
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
452
453
|
main(process.argv.slice(2)).catch(() => { process.stderr.write('[native-model-routing] Explicit native executable or subscription authentication unavailable.\n'); process.exitCode = 1; });
|
|
453
454
|
}
|
|
@@ -218,7 +218,10 @@ export function updateLauncherSettings(file, requested, { apply = false } = {})
|
|
|
218
218
|
return receipt;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
// installNativeLaunchers() copies THIS file alone to ~/.cache/ruvnet-brain/model-routing/ as the
|
|
222
|
+
// launcher runner, so it cannot import plugin/scripts/entrypoint.mjs; this is that helper's
|
|
223
|
+
// comparison, inline: real paths on both sides.
|
|
224
|
+
if ((() => { try { return Boolean(process.argv[1]) && fs.realpathSync(process.argv[1]) === fs.realpathSync(SELF); } catch { return false; } })()) {
|
|
222
225
|
const argv = process.argv.slice(2);
|
|
223
226
|
if (argv[0] === '--launch') {
|
|
224
227
|
try {
|
|
@@ -11,6 +11,7 @@ import { spawn } from 'node:child_process';
|
|
|
11
11
|
import { pathToFileURL } from 'node:url';
|
|
12
12
|
import { connectNativeGateway, decideNativeTurn, nativeGatewayLaunch } from './model-routing-gateway.mjs';
|
|
13
13
|
import { subscriptionEnvironment, assertSubscriptionAuth } from './model-router-dispatch.mjs';
|
|
14
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
14
15
|
|
|
15
16
|
export const MAX_TERMINAL_BYTES = 64 * 1024 * 1024;
|
|
16
17
|
const REFUSED = 'Native terminal routing unavailable; request was not sent.';
|
|
@@ -302,7 +303,7 @@ function exitOf(child) {
|
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
305
|
|
|
305
|
-
if (
|
|
306
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
306
307
|
runTerminalGateway(parseTerminalInvocation(process.argv.slice(2))).then(({ code, signal }) => {
|
|
307
308
|
if (signal) { process.removeAllListeners(signal); process.kill(process.pid, signal); }
|
|
308
309
|
else process.exitCode = code ?? 1;
|
|
@@ -9,6 +9,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
9
9
|
import { classifyTerminalArguments, validateUpstreamSocket, terminalTempRoot } from './model-terminal-gateway.mjs';
|
|
10
10
|
import { nativeGatewayLaunch } from './model-routing-gateway.mjs';
|
|
11
11
|
import { subscriptionEnvironment, assertSubscriptionAuth } from './model-router-dispatch.mjs';
|
|
12
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
12
13
|
|
|
13
14
|
const SELF = fileURLToPath(import.meta.url);
|
|
14
15
|
const MARKER = '# Managed RuvNet Brain terminal launcher';
|
|
@@ -343,7 +344,7 @@ export async function runTerminalLauncher({ host, args = [], config, env = proce
|
|
|
343
344
|
const unforward = forwardSignals(child, signalSource);
|
|
344
345
|
try { return await exitOf(child); } finally { unforward(); }
|
|
345
346
|
}
|
|
346
|
-
if (
|
|
347
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
347
348
|
const argv = process.argv.slice(2);
|
|
348
349
|
Promise.resolve().then(() => {
|
|
349
350
|
if (argv[0] !== '--launch' || argv[2] !== '--config' || argv[4] !== '--') throw new Error('Explicit host/config terminal launcher required');
|
|
@@ -11,6 +11,7 @@ import { subscriptionOnlyEnv } from './subscription-hosts.mjs';
|
|
|
11
11
|
import { applyProfile, loadCatalog, selectionEvidenceStatus } from './model-router-engine.mjs';
|
|
12
12
|
import { createAnalystHome, trustAnalystDenial } from './model-analyst-sandbox.mjs';
|
|
13
13
|
import { digest, currencyStatus, WEEK_MS } from './model-currency-evidence.mjs';
|
|
14
|
+
import { isDirectInvocation } from '../plugin/scripts/entrypoint.mjs';
|
|
14
15
|
|
|
15
16
|
const text = { type: 'string', maxLength: 320 };
|
|
16
17
|
const sourceId = { type: 'string', maxLength: 64 };
|
|
@@ -328,7 +329,7 @@ export function maybeLaunchWeeklyAnalyst({ routerDir = path.join(os.homedir(), '
|
|
|
328
329
|
} catch (error) { release(routerDir, token); throw error; }
|
|
329
330
|
} catch (error) { return { status: 'blocked', launched: false, reason: error.message.slice(0, 240) }; }
|
|
330
331
|
}
|
|
331
|
-
if (
|
|
332
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
332
333
|
const index = process.argv.indexOf('--router-dir'); const routerDir = index >= 0 ? process.argv[index + 1] : undefined;
|
|
333
334
|
const claimIndex = process.argv.indexOf('--claim-token');
|
|
334
335
|
if (!process.argv.includes('--run')) { console.log(JSON.stringify(maybeLaunchWeeklyAnalyst({ routerDir }))); } else runWeeklyAnalyst({ routerDir, claimToken: claimIndex >= 0 ? process.argv[claimIndex + 1] : null, ...(process.argv.includes('--timeout-ms') ? { timeoutMs: Number(process.argv[process.argv.indexOf('--timeout-ms') + 1]) } : {}) }).then((result) => { console.log(JSON.stringify(result)); if (result.status === 'failed') process.exitCode = 1; }).catch((error) => { console.error(error.message); process.exitCode = 1; });
|