ruvnet-brain 4.3.26 β 4.3.27
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 +19 -4
- package/data/model-catalog.json +1 -1
- package/docs/RELEASE-NOTES-4.0.md +4 -3
- package/kb/capability-only.mjs +27 -0
- package/kb/capability-summaries/cognitum-ruos/CAPABILITIES.md +26 -0
- package/kb/verify-citation.mjs +16 -4
- package/package.json +5 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/docs/RELEASE-NOTES-4.0.md +4 -3
- package/plugin/hooks/codex-hooks.json +6 -1
- package/plugin/hooks/hook-contracts.json +24 -2
- package/plugin/hooks/hooks.json +6 -1
- package/plugin/scripts/capacity-aware-parallel-work.mjs +200 -0
- package/plugin/scripts/codex-hook-adapter.mjs +37 -0
- package/plugin/scripts/continuity-hook-policy.mjs +4 -0
- package/plugin/scripts/coverage-integrity.mjs +17 -0
- package/plugin/scripts/hook-shim.mjs +1 -0
- package/plugin/scripts/lesson-gate.mjs +4 -1
- package/plugin/scripts/project-progression-reader.mjs +12 -1
- package/plugin/scripts/project-progression-session-start.mjs +24 -6
- package/plugin/scripts/project-progression-store.mjs +76 -4
- package/plugin/skills/release-proof/SKILL.md +28 -4
- package/plugin/skills/release-proof/scripts/release-proof.mjs +48 -24
- package/scripts/brain-novice-50.mjs +14 -16
- package/scripts/build-bundle.mjs +2 -0
- package/scripts/corpus-dispatch-receipt.mjs +22 -0
- package/scripts/corpus-reconcile.mjs +26 -4
- package/scripts/doc-currency.mjs +12 -4
- package/scripts/eval-brain.mjs +7 -5
- package/scripts/gist-git-transport.mjs +218 -0
- package/scripts/gist-receipts.mjs +168 -38
- package/scripts/ingest-gists.mjs +5 -2
- package/scripts/installed-brain-health.mjs +99 -0
- package/scripts/public-inputs.mjs +2 -1
- package/scripts/public-verification-inputs.mjs +14 -6
- package/scripts/refresh-capability-only-store.mjs +143 -0
- package/scripts/release-vector.mjs +44 -20
- package/scripts/run-operational-benchmark.mjs +151 -0
- package/scripts/run-operational-benchmark.v3.mjs +194 -0
- package/scripts/self-update.mjs +2 -0
- package/scripts/source-coverage.mjs +6 -1
- package/scripts/sync-version.mjs +10 -2
- package/scripts/wired-check.mjs +73 -45
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Created: 2026-06-29 22:36:38 EDT
|
|
|
7
7
|
|
|
8
8
|
# π§ RuvNet Brain
|
|
9
9
|
|
|
10
|
-
### π§ RuvNet Brain β [](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
|
|
11
11
|
|
|
12
12
|
**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack β delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
|
|
13
13
|
|
|
@@ -562,7 +562,7 @@ node forge-ask-all.mjs --dir . --q "How does RuVector implement HNSW vector sear
|
|
|
562
562
|
|
|
563
563
|
This project versions in the open (see the live badge up top for the exact plugin version; the downloadable knowledge bundle is a separate track) β we don't claim βdone,β βcomplete,β or βzero hallucinations.β Where it stands:
|
|
564
564
|
|
|
565
|
-
- β
**The grounding brain is real and proven** β 182 public stores Β· 143,
|
|
565
|
+
- β
**The grounding brain is real and proven** β 182 public stores Β· 143,682 public source chunks, dual embeddings, cross-encoder rerank, plugin (MCP tool + explicit skills; automatic hooks retired), all re-runnable.
|
|
566
566
|
- β
**Code-level depth** β the code-rich repos are indexed to full function bodies; βhow is it implemented?β returns the implementation. Verified in the shipped bundle (clean-room 3/3).
|
|
567
567
|
- β
**Routing holds** β named 47/48, described 26/28, scenario 7/8; behavioral L1βL3 all pass (**L4 downgraded β it measures that the brain spoke, not that anything listened**); private stores fenced out of the public bundle (zero-leak verified).
|
|
568
568
|
- β οΈ **Two routing residuals** (above) β surfaced, not hidden.
|
package/bin/install.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from '../kb/model-requirements.mjs';
|
|
31
31
|
import { applyManagedCatalogUpdate } from '../scripts/model-router-catalog.mjs';
|
|
32
32
|
import { cmpVersion } from '../scripts/stack-sync.mjs';
|
|
33
|
+
import { inspectInstalledBrain, classifySmokeEvidence, DOCTOR_SMOKE_QUERY, doctorSmokeArgs } from '../scripts/installed-brain-health.mjs';
|
|
33
34
|
import { validateCoverageDirectory } from '../plugin/scripts/coverage-integrity.mjs';
|
|
34
35
|
import {
|
|
35
36
|
continuityContractIds,
|
|
@@ -2402,9 +2403,9 @@ async function smokeQuery(cacheDir) {
|
|
|
2402
2403
|
if (!fs.existsSync(ask)) return { ran: false };
|
|
2403
2404
|
step(
|
|
2404
2405
|
'Asking the brain a real question',
|
|
2405
|
-
'this warms the local model
|
|
2406
|
+
'this warms the local model and checks that retrieval returns usable, cited evidence',
|
|
2406
2407
|
);
|
|
2407
|
-
const Q =
|
|
2408
|
+
const Q = DOCTOR_SMOKE_QUERY;
|
|
2408
2409
|
info(`Q: ${c.cyan(`"${Q}"`)}`);
|
|
2409
2410
|
info(c.dim('(first run downloads a small local model once β this can take a minute)'));
|
|
2410
2411
|
const started = Date.now();
|
|
@@ -2415,7 +2416,7 @@ async function smokeQuery(cacheDir) {
|
|
|
2415
2416
|
// absolute path via spawnSync (no shell involved) that identity check silently fails on this
|
|
2416
2417
|
// machine, so main() never runs β exit 0, zero stdout, zero stderr, no exception. Looks like a
|
|
2417
2418
|
// clean success; is actually a total no-op. Verified: switching to a relative name + cwd fixes it.
|
|
2418
|
-
r = spawnSync('node',
|
|
2419
|
+
r = spawnSync('node', doctorSmokeArgs(cacheDir), {
|
|
2419
2420
|
cwd: cacheDir,
|
|
2420
2421
|
encoding: 'utf8',
|
|
2421
2422
|
timeout: 240000,
|
|
@@ -2476,6 +2477,11 @@ async function smokeQuery(cacheDir) {
|
|
|
2476
2477
|
}
|
|
2477
2478
|
|
|
2478
2479
|
const v = await verifier.verifyGrounding(out, cacheDir);
|
|
2480
|
+
const evidence = classifySmokeEvidence(v, out);
|
|
2481
|
+
if (v.grounded && !evidence.usable) {
|
|
2482
|
+
warn(`the citation resolves, but the question was not answered with sufficient evidence (${evidence.reason})`);
|
|
2483
|
+
return { ran: true, grounded: false, citationResolved: true, reason: evidence.reason, secs };
|
|
2484
|
+
}
|
|
2479
2485
|
if (v.grounded) {
|
|
2480
2486
|
ok(`grounded in rUv's real source β verified in ${secs}s, not guessed β¦`);
|
|
2481
2487
|
console.log(` ${c.dim('cited:')} ${c.bold(v.receipt.path)}`);
|
|
@@ -2693,6 +2699,13 @@ async function doctor() {
|
|
|
2693
2699
|
// Two independent version streams (KB bundle vs plugin wrapper) β see checkVersionDrift()'s
|
|
2694
2700
|
// header comment for the full story. Silent unless they've genuinely diverged.
|
|
2695
2701
|
reportVersionDrift(cacheDir);
|
|
2702
|
+
const installedIdentity = inspectInstalledBrain(cacheDir, PACKAGE_VERSION);
|
|
2703
|
+
info(`installed identities: package ${installedIdentity.packageVersion || 'unknown'}; search engine ${installedIdentity.searchVersion || 'unknown'}; validator ${installedIdentity.validatorVersion || 'unknown'}`);
|
|
2704
|
+
if (installedIdentity.corpusTag) info(`corpus generation: ${installedIdentity.corpusTag}`);
|
|
2705
|
+
if (!installedIdentity.healthy) {
|
|
2706
|
+
for (const issue of installedIdentity.issues) warn(`installed identity: ${issue}`);
|
|
2707
|
+
info(`Repair the installed generation: ${c.bold('npx ruvnet-brain@latest --update')}`);
|
|
2708
|
+
}
|
|
2696
2709
|
// Extraction no longer needs an external binary at all β kb/zip-extract.mjs does it with node:zlib
|
|
2697
2710
|
// (see unzipInto()). So this reports the file's PRESENCE, not a PATH lookup: if it is missing from
|
|
2698
2711
|
// the install, extraction on Windows silently loses its primary method, which is exactly the class
|
|
@@ -2883,6 +2896,8 @@ async function doctor() {
|
|
|
2883
2896
|
const codexWiringFailed = Boolean(cx.host && !cx.wired);
|
|
2884
2897
|
const codexReadinessFailed = Boolean(codexMcp?.blocking);
|
|
2885
2898
|
const failed = (hookResult ? hookResult.exitCode !== 0 : !allGreen)
|
|
2899
|
+
|| !installedIdentity.healthy
|
|
2900
|
+
|| smoke.grounded !== true
|
|
2886
2901
|
|| groundingUnprovenPersisted
|
|
2887
2902
|
|| (codexLifecycleFailed && !codexTrustBypassed)
|
|
2888
2903
|
|| codexWiringFailed
|
|
@@ -3056,7 +3071,7 @@ function reportVersionDrift(cacheDir) {
|
|
|
3056
3071
|
const state = checkVersionDrift(cacheDir);
|
|
3057
3072
|
if (!state.drift) return state;
|
|
3058
3073
|
warn(`the brain (${c.bold(state.kb)}) and the Claude Code plugin (${c.bold(state.wrapper)}) have drifted apart β`);
|
|
3059
|
-
info(`
|
|
3074
|
+
info(`they update on separate schedules; this comparison does not prove either is healthy. To bring the`);
|
|
3060
3075
|
info(`plugin up to date: ${c.bold('claude plugin marketplace update ruvnet-brain')} ${c.dim('(body updates go live without a restart; boot-surface changes are called out)')}`);
|
|
3061
3076
|
return state;
|
|
3062
3077
|
}
|
package/data/model-catalog.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"generated": "2026-07-15",
|
|
5
5
|
"schema_version": 1,
|
|
6
6
|
"sources": {
|
|
7
|
-
"prices": "OpenRouter /api/v1/models live catalog, pulled 2026-09-
|
|
7
|
+
"prices": "OpenRouter /api/v1/models live catalog, pulled 2026-09-19 (in/out USD per Mtok).",
|
|
8
8
|
"rankings": "Artificial Analysis Intelligence Index (artificialanalysis.ai) + Arena/LMArena (arena.ai) β the ONLY independent evaluators carrying current-generation models as of 2026-07-15; each figure cross-verified twice.",
|
|
9
9
|
"provenance_rule": "rUv ADR-206: vendor-reported scores are optimistic and harness-confounded β trust independent (AA/Arena) numbers, treat vendor self-scaffold SWE-bench/LiveCodeBench figures as noisy features, never as truth.",
|
|
10
10
|
"benchmark_lag": "The canonical hard coding benchmarks (SWE-bench Verified standardized harness, LiveCodeBench, Aider polyglot) were ALL months stale on 2026-07-15 and carry NONE of these models. The '88.6% / 95% SWE-bench' figures in the press are vendor self-scaffold scores, not the standardized harness β excluded here.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RuvNet-Brain 4.0 line β what's new (the major-release highlights)
|
|
2
2
|
|
|
3
|
-
Updated: 2026-
|
|
3
|
+
Updated: 2026-09-20
|
|
4
4
|
|
|
5
5
|
> **Source of truth** for the `/whats-new` command and the first-run upgrade message. Curated, honest,
|
|
6
6
|
> major-only β not the point-release churn. If a claim here isn't true of the shipping build, it does not
|
|
@@ -19,10 +19,11 @@ it's landing now.
|
|
|
19
19
|
|
|
20
20
|
### Release proof is fail-closed
|
|
21
21
|
The 4.0 release path now separates a clean candidate seal from a post-publication seal. Dirty
|
|
22
|
-
lineage, zero/skipped/todo tests, open issues, red or pending exact-SHA workflows, a missing
|
|
22
|
+
lineage, zero/skipped/todo tests, open `release-blocker` issues, red or pending exact-SHA workflows, a missing
|
|
23
23
|
`ruvnet-brain` self-RVF store, weak query-deadline margin, missing independent graders,
|
|
24
24
|
host/artifact mismatches, and public-byte drift are release failures rather than warnings.
|
|
25
|
-
|
|
25
|
+
The local `release:proof --status --quick` command is only a diagnostic and cannot qualify a
|
|
26
|
+
candidate; use the exact-SHA preflight receipt and protected publisher evidence.
|
|
26
27
|
|
|
27
28
|
### 1. The Console is the front door
|
|
28
29
|
Type `/rvbc` and your whole RuvNet stack is on one live local page: what's installed, what the AI has
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const isCapabilityOnly = name => String(name).toLowerCase() === 'cognitum-ruos';
|
|
5
|
+
export const CAPABILITY_RETIRED_SUFFIXES = [
|
|
6
|
+
'-primer.md', '.symbols.json', '.rvf', '.rvf.idmap.json', '.rvf.embed.json', '.big.passages.jsonl', '.big.meta.json',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// Reject historical source-bearing stores at the final packaging boundary, even
|
|
10
|
+
// when their upstream SHA and signed generation receipt are otherwise current.
|
|
11
|
+
export function assertCapabilityOnlyStore(dir, name) {
|
|
12
|
+
if (!isCapabilityOnly(name)) return;
|
|
13
|
+
const expected = fs.readFileSync(new URL('./capability-summaries/cognitum-ruos/CAPABILITIES.md', import.meta.url), 'utf8');
|
|
14
|
+
const rows = fs.readFileSync(path.join(dir, `${name}.passages.jsonl`), 'utf8')
|
|
15
|
+
.trim().split('\n').map(line => JSON.parse(line));
|
|
16
|
+
if (rows.length !== 1 || rows[0].path !== 'CAPABILITIES.md' || rows[0].text !== expected) {
|
|
17
|
+
throw new Error(`${name}: capability-only policy requires the current curated summary, without source passages`);
|
|
18
|
+
}
|
|
19
|
+
const meta = JSON.parse(fs.readFileSync(path.join(dir, `${name}.meta.json`), 'utf8'));
|
|
20
|
+
const entries = Object.values(meta.entries || {});
|
|
21
|
+
if (entries.length !== 1 || entries[0].path !== 'CAPABILITIES.md' || entries[0].kind !== 'doc') {
|
|
22
|
+
throw new Error(`${name}: capability-only metadata contains unexpected source paths`);
|
|
23
|
+
}
|
|
24
|
+
if (CAPABILITY_RETIRED_SUFFIXES.some(suffix => fs.existsSync(path.join(dir, `${name}${suffix}`)))) {
|
|
25
|
+
throw new Error(`${name}: capability-only store must not carry a symbol index or legacy source sidecar`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Updated: 2026-09-19 | Version 1.0.0
|
|
2
|
+
Created: 2026-09-19
|
|
3
|
+
|
|
4
|
+
# Cognitum ruOS capabilities
|
|
5
|
+
|
|
6
|
+
Cognitum ruOS is an agentic operating system for AI workstations. It is designed
|
|
7
|
+
to observe workstation conditions, reason about what needs attention, and take
|
|
8
|
+
appropriate actions with less manual intervention.
|
|
9
|
+
|
|
10
|
+
Its documented capabilities include:
|
|
11
|
+
|
|
12
|
+
- Workstation health monitoring and recovery from service failures.
|
|
13
|
+
- Adapting performance profiles to workload and user context.
|
|
14
|
+
- Local AI assistance and persistent knowledge for workstation tasks.
|
|
15
|
+
- Learning from prior sessions and evaluating knowledge quality.
|
|
16
|
+
- Backup, recovery, and software updates.
|
|
17
|
+
- Voice interaction and optional sensing with user consent.
|
|
18
|
+
- Security checks around AI interactions.
|
|
19
|
+
|
|
20
|
+
It is relevant when a user wants a workstation that helps maintain itself and
|
|
21
|
+
supports ongoing AI-assisted work. These are documented capabilities, not a claim
|
|
22
|
+
that ruOS is installed, provisioned, or independently benchmarked on this machine.
|
|
23
|
+
|
|
24
|
+
This Brain entry intentionally covers capabilities and use cases only. Proprietary
|
|
25
|
+
source code, internal architecture, algorithms, configuration, and implementation
|
|
26
|
+
instructions are outside its published knowledge scope.
|
package/kb/verify-citation.mjs
CHANGED
|
@@ -42,7 +42,7 @@ import readline from 'node:readline';
|
|
|
42
42
|
export function parseCitations(stdout) {
|
|
43
43
|
const out = [];
|
|
44
44
|
const text = String(stdout ?? '');
|
|
45
|
-
const blockRe = /^#(\d+)\
|
|
45
|
+
const blockRe = /^#(\d+)[ \t]+repo=(\S+)([^\r\n]*)/gm;
|
|
46
46
|
const nextHeaderRe = /^#\d+\s+repo=\S+/gm;
|
|
47
47
|
let m;
|
|
48
48
|
let expectedRank = 1;
|
|
@@ -63,15 +63,27 @@ export function parseCitations(stdout) {
|
|
|
63
63
|
if (!pathM) continue;
|
|
64
64
|
expectedRank = rank + 1;
|
|
65
65
|
const repo = m[2];
|
|
66
|
+
// Metadata comes only from the header, never from a retrieved document body.
|
|
67
|
+
// A proof label is descriptive; consumers must independently validate its evidence.
|
|
68
|
+
const field = (name) => new RegExp(`(?:^|[ \\t])${name}=([^ \\t]+)`).exec(m[3])?.[1] ?? null;
|
|
69
|
+
const score = (name) => {
|
|
70
|
+
const value = field(name);
|
|
71
|
+
return value !== null && /^-?\d+(?:\.\d+)?$/.test(value) ? Number(value) : null;
|
|
72
|
+
};
|
|
66
73
|
const fullPath = pathM[1].trim();
|
|
67
74
|
// Strip the repo prefix the reader adds, so the remainder can be matched against the store.
|
|
68
75
|
const docPath = fullPath.startsWith(`${repo}/`) ? fullPath.slice(repo.length + 1) : fullPath;
|
|
76
|
+
// Retain only the reader's bounded document body. Missing/truncated delimiters fail
|
|
77
|
+
// closed, so evaluators cannot borrow claims from headers, diagnostics, or later hits.
|
|
78
|
+
const body = /^----- full document -----\r?\n([\s\S]*?)\r?\n={67}(?:\r?\n|$)/m.exec(block);
|
|
69
79
|
out.push({
|
|
70
80
|
rank,
|
|
71
81
|
repo,
|
|
72
|
-
ce:
|
|
73
|
-
vec:
|
|
74
|
-
kind:
|
|
82
|
+
ce: score('ce'),
|
|
83
|
+
vec: score('vec'),
|
|
84
|
+
kind: field('kind'),
|
|
85
|
+
proofMethod: field('proof'),
|
|
86
|
+
returnedText: body ? body[1] : null,
|
|
75
87
|
fullPath,
|
|
76
88
|
docPath,
|
|
77
89
|
title: titleM ? titleM[1].trim() : null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.27",
|
|
4
4
|
"description": "One-command installer for RuvNet Brain β a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"release:proof": "node scripts/release-proof.mjs",
|
|
13
13
|
"benchmark:brain50": "node scripts/brain-latency-50.mjs",
|
|
14
14
|
"benchmark:novice50": "node scripts/brain-novice-50.mjs",
|
|
15
|
+
"benchmark:operational": "node scripts/run-operational-benchmark.v3.mjs",
|
|
16
|
+
"benchmark:operational:v2": "node scripts/run-operational-benchmark.mjs",
|
|
15
17
|
"version:check": "node scripts/sync-version.mjs --check",
|
|
16
18
|
"version:set": "node scripts/set-version.mjs",
|
|
17
19
|
"qa:pr": "node scripts/qa-runner.mjs",
|
|
@@ -94,6 +96,8 @@
|
|
|
94
96
|
"kb/verify-citation.mjs",
|
|
95
97
|
"kb/retrieval-result.mjs",
|
|
96
98
|
"kb/corpus-release-identity.mjs",
|
|
99
|
+
"kb/capability-only.mjs",
|
|
100
|
+
"kb/capability-summaries/",
|
|
97
101
|
"kb/zip-extract.mjs",
|
|
98
102
|
"kb/brain-profile.mjs",
|
|
99
103
|
"kb/lifecycle-evidence-retention.mjs",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
3
|
"description": "RuvNet brain transplant for Claude Code β grounds every RuvNet decision in real source across 77 rUv repositories, prefers Ruflo / RuVector-RVF / AgentDB over training-prior defaults (pgvector, Pinecone, hand-rolled cosine), and can pull in any RuvNet repo on demand. Ships a UserPromptSubmit retrieve-and-inject grounding hook and a PreToolUse write gate that refuses ungrounded rUv-product code until search_ruvnet has been consulted (ADR-0012 / ADR-067).",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.27",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RuvNet-Brain 4.0 line β what's new (the major-release highlights)
|
|
2
2
|
|
|
3
|
-
Updated: 2026-
|
|
3
|
+
Updated: 2026-09-20
|
|
4
4
|
|
|
5
5
|
> **Source of truth** for the `/whats-new` command and the first-run upgrade message. Curated, honest,
|
|
6
6
|
> major-only β not the point-release churn. If a claim here isn't true of the shipping build, it does not
|
|
@@ -19,10 +19,11 @@ it's landing now.
|
|
|
19
19
|
|
|
20
20
|
### Release proof is fail-closed
|
|
21
21
|
The 4.0 release path now separates a clean candidate seal from a post-publication seal. Dirty
|
|
22
|
-
lineage, zero/skipped/todo tests, open issues, red or pending exact-SHA workflows, a missing
|
|
22
|
+
lineage, zero/skipped/todo tests, open `release-blocker` issues, red or pending exact-SHA workflows, a missing
|
|
23
23
|
`ruvnet-brain` self-RVF store, weak query-deadline margin, missing independent graders,
|
|
24
24
|
host/artifact mismatches, and public-byte drift are release failures rather than warnings.
|
|
25
|
-
|
|
25
|
+
The local `release:proof --status --quick` command is only a diagnostic and cannot qualify a
|
|
26
|
+
candidate; use the exact-SHA preflight receipt and protected publisher evidence.
|
|
26
27
|
|
|
27
28
|
### 1. The Console is the front door
|
|
28
29
|
Type `/rvbc` and your whole RuvNet stack is on one live local page: what's installed, what the AI has
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "RuvNet Brain continuity plane for Codex. Broad legacy automatic gates remain retired. Registrations here are MEASURED, not assumed: a probe hook was registered on all twelve event names the installed codex-cli 0.154.0 binary declares and a real `codex exec` run was observed (2026-09-11). SessionStart, UserPromptSubmit and SessionEnd FIRED and carry handlers. Stop keeps only the pre-existing, project-scoped continuation gate. PreCompact is DECLARED ABSENT here: the binary declares the event but the probe never observed it, and a capture registered on an unobserved event would look symmetrical while capturing nothing. All handlers are bounded and fail open. Amended 2026-09-11: UserPromptSubmit also runs ground-ruvnet (grounding injection, ADR-040 Β§Amendment). Amended 2026-09-12: the 2026-09-11 PreToolUse/PostToolUse \"not observed\" note was measured with a prompt (`codex exec \"reply OK\"`) that never invoked a tool, so neither event had anything to fire on β that was an untested path, not a failing one. Re-probed with prompts that actually call a tool: a real apply_patch write fired PreToolUse/PostToolUse with tool_name \"apply_patch\", and a real MCP call to this repo's own search_ruvnet server fired both with tool_name \"mcp__ruvnet_brain__search_ruvnet\". Both are now registered below: decision-gate's write route (matcher includes apply_patch, Codex's raw write-tool name) and grounding-stamp (unchanged matcher already recognizes the mcp__..__search_ruvnet shape). The bash route (exec_command) remains unregistered β today's measurement covered a write and an MCP call, not exec_command, and extending on that evidence would be the same unproven leap this note replaces. Also added 2026-09-12: grounding-turn-mark (UserPromptSubmit) and grounding-turn-gate (Stop), the \"answered without searching\" pair β a prompt-level grounding directive is advisory, so this records whether it fired and forces continuation at Stop if no search_ruvnet call was recorded since (reusing grounding-stamp's own stamp evidence). Both are registered on Codex identically to Claude: the Stop-block contract (hookSpecificOutput.additionalContext) already has a proven Codex translation via codex-hook-adapter.mjs's Stop branch (see tests/unit/codex-lifecycle-hooks.test.mjs), the same path continuation-gate already uses.",
|
|
2
|
+
"description": "RuvNet Brain continuity plane for Codex. Broad legacy automatic gates remain retired. Registrations here are MEASURED, not assumed: a probe hook was registered on all twelve event names the installed codex-cli 0.154.0 binary declares and a real `codex exec` run was observed (2026-09-11). SessionStart, UserPromptSubmit and SessionEnd FIRED and carry handlers. Stop keeps only the pre-existing, project-scoped continuation gate. PreCompact is DECLARED ABSENT here: the binary declares the event but the probe never observed it, and a capture registered on an unobserved event would look symmetrical while capturing nothing. All handlers are bounded and fail open. Amended 2026-09-11: UserPromptSubmit also runs ground-ruvnet (grounding injection, ADR-040 Β§Amendment). Amended 2026-09-12: the 2026-09-11 PreToolUse/PostToolUse \"not observed\" note was measured with a prompt (`codex exec \"reply OK\"`) that never invoked a tool, so neither event had anything to fire on β that was an untested path, not a failing one. Re-probed with prompts that actually call a tool: a real apply_patch write fired PreToolUse/PostToolUse with tool_name \"apply_patch\", and a real MCP call to this repo's own search_ruvnet server fired both with tool_name \"mcp__ruvnet_brain__search_ruvnet\". Both are now registered below: decision-gate's write route (matcher includes apply_patch, Codex's raw write-tool name) and grounding-stamp (unchanged matcher already recognizes the mcp__..__search_ruvnet shape). The bash route (exec_command) remains unregistered β today's measurement covered a write and an MCP call, not exec_command, and extending on that evidence would be the same unproven leap this note replaces. Also added 2026-09-12: grounding-turn-mark (UserPromptSubmit) and grounding-turn-gate (Stop), the \"answered without searching\" pair β a prompt-level grounding directive is advisory, so this records whether it fired and forces continuation at Stop if no search_ruvnet call was recorded since (reusing grounding-stamp's own stamp evidence). Both are registered on Codex identically to Claude: the Stop-block contract (hookSpecificOutput.additionalContext) already has a proven Codex translation via codex-hook-adapter.mjs's Stop branch (see tests/unit/codex-lifecycle-hooks.test.mjs), the same path continuation-gate already uses. Capacity-aware parallel-work guidance is also registered at UserPromptSubmit: it advises the coordinator to launch only real independent workers within sampled resource headroom and the live runtime/tool cap; it never spawns workers or claims execution.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -68,6 +68,11 @@
|
|
|
68
68
|
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 9000 ground-ruvnet",
|
|
69
69
|
"timeout": 10
|
|
70
70
|
},
|
|
71
|
+
{
|
|
72
|
+
"type": "command",
|
|
73
|
+
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 1500 capacity-aware-parallel-work",
|
|
74
|
+
"timeout": 2
|
|
75
|
+
},
|
|
71
76
|
{
|
|
72
77
|
"type": "command",
|
|
73
78
|
"command": "node -e \"const f=require('node:fs'),o=require('node:os'),p=require('node:path'),c=require('node:child_process'),d=process.env.CODEX_HOME||p.join(o.homedir(),'.codex'),b=process.env.RUVNET_BRAIN_HOME||p.join(p.dirname(d),'.cache','ruvnet-brain'),w=p.join(b,'codex-hook.mjs');let s;try{s=f.statSync(w)}catch{}if(!s?.isFile())process.exit(0);const r=c.spawnSync(process.execPath,[w,...process.argv.slice(2)],{stdio:['inherit','pipe','pipe'],encoding:'utf8',env:process.env,timeout:Number(process.argv[1]),killSignal:'SIGKILL'});if(r.status===0||r.status===2){if(r.stdout)process.stdout.write(r.stdout);if(r.stderr)process.stderr.write(r.stderr)}process.exit(r.status===2?2:0)\" 4500 grounding-turn-mark",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_note": "The legacy automatic gate collection remains retired. What is permitted is the continuity plane below and nothing else: SessionStart restores the canonical project checkpoint; UserPromptSubmit runs the single unprompted-speech chokepoint; Stop may nudge one explicitly authorized project-scoped objective AND capture a project snapshot; PreCompact and SessionEnd capture a project snapshot. Version 3 of this file permitted exactly two handlers, which was not a safety property but a contradiction: it demanded a restore while forbidding any event that could WRITE the journal the restore reads, so the canonical store held zero progression rows. Adding to this list is still a deliberate act that must pass `npm run hooks:check`; what changed is that the shape can now express the plane that actually works. Amended 2026-09-11 (Stuart): the continuity-only charter had retired the ONLY enforcement of ADR-0012 β never write rUv-product code the brain has not seen β and the failure it exists to prevent recurred the day it was measured absent. The plane now also carries three grounding registrations: ground-ruvnet (UserPromptSubmit, grounding injection β a second owner of that event, scoped by ADR-040 Β§Amendment 2026-09-11 to directives rather than speech), decision-gateβs write route (PreToolUse, the one refuser, ADR-067), and grounding-stamp (PostToolUse on a successful search_ruvnet, the receipt that opens the write gate). Amended 2026-09-12: the 2026-09-11 claim that Codex PreToolUse/PostToolUse delivery had never been observed was measured with a prompt that never invoked a tool (`codex exec \"reply OK\"`), so it was an untested path, not a failing one. Re-measured with prompts that actually call a tool (a real apply_patch write, a real MCP search_ruvnet call against this repo's own server): both events FIRED on codex-cli 0.154.0 with real payloads. decision-gate's write route and grounding-stamp are now dual-host (see _codexCapture and contracts below); the bash route (exec_command) remains Claude-only β today's measurement did not exercise it. Also amended 2026-09-12: added grounding-turn-mark (UserPromptSubmit) and grounding-turn-gate (Stop), the \"answered without searching\" pair β ground-ruvnet's Gate 1 directive is advisory, so nothing previously checked whether the model complied before the turn ended. grounding-turn-mark records that Gate 1 fired for a turn; grounding-turn-gate forces continuation at Stop if grounding-stamp's own evidence shows no search_ruvnet call happened since. Both dual-host from the start (the Stop-block contract is already proven on Codex via continuation-gate).",
|
|
3
|
-
"_version":
|
|
2
|
+
"_note": "The legacy automatic gate collection remains retired. What is permitted is the continuity plane below and nothing else: SessionStart restores the canonical project checkpoint; UserPromptSubmit runs the single unprompted-speech chokepoint; Stop may nudge one explicitly authorized project-scoped objective AND capture a project snapshot; PreCompact and SessionEnd capture a project snapshot. Version 3 of this file permitted exactly two handlers, which was not a safety property but a contradiction: it demanded a restore while forbidding any event that could WRITE the journal the restore reads, so the canonical store held zero progression rows. Adding to this list is still a deliberate act that must pass `npm run hooks:check`; what changed is that the shape can now express the plane that actually works. Amended 2026-09-11 (Stuart): the continuity-only charter had retired the ONLY enforcement of ADR-0012 β never write rUv-product code the brain has not seen β and the failure it exists to prevent recurred the day it was measured absent. The plane now also carries three grounding registrations: ground-ruvnet (UserPromptSubmit, grounding injection β a second owner of that event, scoped by ADR-040 Β§Amendment 2026-09-11 to directives rather than speech), decision-gateβs write route (PreToolUse, the one refuser, ADR-067), and grounding-stamp (PostToolUse on a successful search_ruvnet, the receipt that opens the write gate). Amended 2026-09-12: the 2026-09-11 claim that Codex PreToolUse/PostToolUse delivery had never been observed was measured with a prompt that never invoked a tool (`codex exec \"reply OK\"`), so it was an untested path, not a failing one. Re-measured with prompts that actually call a tool (a real apply_patch write, a real MCP search_ruvnet call against this repo's own server): both events FIRED on codex-cli 0.154.0 with real payloads. decision-gate's write route and grounding-stamp are now dual-host (see _codexCapture and contracts below); the bash route (exec_command) remains Claude-only β today's measurement did not exercise it. Also amended 2026-09-12: added grounding-turn-mark (UserPromptSubmit) and grounding-turn-gate (Stop), the \"answered without searching\" pair β ground-ruvnet's Gate 1 directive is advisory, so nothing previously checked whether the model complied before the turn ended. grounding-turn-mark records that Gate 1 fired for a turn; grounding-turn-gate forces continuation at Stop if grounding-stamp's own evidence shows no search_ruvnet call happened since. Both dual-host from the start (the Stop-block contract is already proven on Codex via continuation-gate). Added capacity-aware-parallel-work at UserPromptSubmit on both measured hosts: it is context-only, resource-bounded, and requires the coordinator to check actual tool/runtime slots; it does not spawn or claim workers.",
|
|
3
|
+
"_version": 8,
|
|
4
4
|
"_eventOwners": [
|
|
5
5
|
{
|
|
6
6
|
"event": "SessionStart",
|
|
@@ -70,6 +70,16 @@
|
|
|
70
70
|
],
|
|
71
71
|
"responsibility": "Injects a grounding directive into the modelβs context when the prompt names the rUv stack, reaches for a classical default, or asks to build: call search_ruvnet before you assert. Not speech β emits no advocacy, promotion, lesson or alarm and reads no dial; silenced by the brain switch (ADR-054); scoped by ADR-040 Β§Amendment 2026-09-11."
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
"event": "UserPromptSubmit",
|
|
75
|
+
"owner": "capacity-aware-parallel-work",
|
|
76
|
+
"class": "capacity-aware coordination guidance",
|
|
77
|
+
"hosts": [
|
|
78
|
+
"claude",
|
|
79
|
+
"codex"
|
|
80
|
+
],
|
|
81
|
+
"responsibility": "For clearly substantial, independently splittable work, adds context-only guidance based on bounded memory-pressure, swap, compression, and normalized-load signals. It never creates workers, claims they are running, or overrides the coordinatorβs live agent-tool/runtime cap; unknown capacity recommends serial execution."
|
|
82
|
+
},
|
|
73
83
|
{
|
|
74
84
|
"event": "PreToolUse",
|
|
75
85
|
"owner": "decision-gate",
|
|
@@ -228,6 +238,18 @@
|
|
|
228
238
|
"matcher": "*",
|
|
229
239
|
"timeout": 10
|
|
230
240
|
},
|
|
241
|
+
{
|
|
242
|
+
"id": "capacity-aware-parallel-work",
|
|
243
|
+
"event": "UserPromptSubmit",
|
|
244
|
+
"hosts": [
|
|
245
|
+
"claude",
|
|
246
|
+
"codex"
|
|
247
|
+
],
|
|
248
|
+
"mode": "advisory",
|
|
249
|
+
"offBehavior": "run",
|
|
250
|
+
"matcher": "*",
|
|
251
|
+
"timeout": 2
|
|
252
|
+
},
|
|
231
253
|
{
|
|
232
254
|
"id": "decision-gate",
|
|
233
255
|
"event": "PreToolUse",
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "RuvNet Brain lifecycle plane. The broad legacy routing, learning, and release interceptors remain retired. What is automatic is exactly: SessionStart restores the canonical project checkpoint; UserPromptSubmit runs the single unprompted-speech chokepoint
|
|
2
|
+
"description": "RuvNet Brain lifecycle plane. The broad legacy routing, learning, and release interceptors remain retired. What is automatic is exactly: SessionStart restores the canonical project checkpoint; UserPromptSubmit runs the single unprompted-speech chokepoint, ground-ruvnet grounding injection, and a capacity-aware context hint for clearly large independent work. The capacity hook uses bounded CPU/memory-pressure evidence, never starts agents, and tells the coordinator to check live tools and clamp to the runtime cap; it is not user-facing speech. ground-ruvnet remains a directive to the model, not speech β ADR-040 Β§Amendment 2026-09-11. PreToolUse runs decision-gate's write route, the ONE process that may refuse a Write/Edit/MultiEdit/NotebookEdit/apply_patch (ADR-067, composing ground-before-write per ADR-0012); PostToolUse on a successful search_ruvnet mints the grounding stamp that opens that gate. grounding-turn-mark (UserPromptSubmit) records that the grounding directive fired this turn, and grounding-turn-gate (Stop) forces continuation if no search_ruvnet call was recorded since. Stop also runs the ledger-scoped continuation handler and captures a project snapshot; PreCompact and SessionEnd capture a project snapshot. Every capture is bounded and fails open; only decision-gate may block on exit code, and it fails open on its own errors β the Stop stdout envelopes are advisory at the shim boundary but keep the agent working.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" ground-ruvnet || true",
|
|
28
28
|
"timeout": 10
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"type": "command",
|
|
32
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" capacity-aware-parallel-work || true",
|
|
33
|
+
"timeout": 2
|
|
34
|
+
},
|
|
30
35
|
{
|
|
31
36
|
"type": "command",
|
|
32
37
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-shim.mjs\" grounding-turn-mark || true",
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UserPromptSubmit advisory for substantial work with independent workstreams.
|
|
4
|
+
*
|
|
5
|
+
* This hook classifies only the submitted prompt and samples local pressure signals. It NEVER
|
|
6
|
+
* creates agents or claims that agents are running. The coordinator still has to check the live
|
|
7
|
+
* agent tools and runtime concurrency ceiling, then spawn real workers and inspect their results.
|
|
8
|
+
* Missing or malformed input, unavailable probes, and unexpected errors are silent and fail open.
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
export const UNKNOWN_RUNTIME_TOTAL_AGENT_CEILING = 4;
|
|
17
|
+
const INPUT_LIMIT = 32 * 1024;
|
|
18
|
+
const PROBE_TIMEOUT_MS = 450;
|
|
19
|
+
const GIB = 1024 ** 3;
|
|
20
|
+
|
|
21
|
+
const ACTION = /\b(?:build|implement|refactor|migrate|investigate|audit|review|design|fix|add|remove|optimi[sz]e|plan|execute|ship)\b/i;
|
|
22
|
+
const EXPLICIT_FANOUT = /\b(?:parallel(?:ize|ise)?|swarm|delegate|spawn (?:real )?agents?|independent workstreams?|separate owners?)\b/i;
|
|
23
|
+
const BROAD_SCOPE = /\b(?:cross[- ]cutting|end[- ]to[- ]end|multi[- ]step|large[- ]scale|whole (?:repo|repository|codebase|system)|entire (?:repo|repository|codebase|system)|full (?:repo|repository|codebase|system)|across (?:the )?(?:repo|repository|codebase|system)|multiple (?:modules|files|packages|components|services)|several (?:modules|files|packages|components|services|workstreams))\b/i;
|
|
24
|
+
const TRIVIAL_SCOPE = /\b(?:tiny|trivial|simple|single[- ]line|one[- ]line|small typo|rename (?:one|a|single) variable|format one file|just (?:a )?quick fix)\b/i;
|
|
25
|
+
const COMPONENTS = [
|
|
26
|
+
/\bapi\b/i, /\bcli\b/i, /\bui\b|\bfront[- ]end\b|\binterface\b/i,
|
|
27
|
+
/\btests?\b|\bqa\b/i, /\bdocs?\b|\bdocumentation\b/i,
|
|
28
|
+
/\bdata(?:base| layer| model)?\b|\bschema\b/i, /\bsecurity\b/i,
|
|
29
|
+
/\binfra(?:structure)?\b|\bdeployment\b/i, /\bhooks?\b/i,
|
|
30
|
+
/\binstaller\b|\bpackaging\b/i, /\bruntime\b/i,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function estimateWorkUnitCount(prompt) {
|
|
34
|
+
const componentCount = COMPONENTS.reduce((n, re) => n + Number(re.test(prompt)), 0);
|
|
35
|
+
if (componentCount >= 2) return componentCount;
|
|
36
|
+
const numberedItems = [...prompt.matchAll(/(?:^|\n)\s*(?:\d+[.)]|[-*])\s+\S/g)].length;
|
|
37
|
+
return numberedItems >= 2 ? numberedItems : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isSubstantialParallelWork(prompt) {
|
|
41
|
+
const text = typeof prompt === 'string' ? prompt.trim() : '';
|
|
42
|
+
if (!text || TRIVIAL_SCOPE.test(text) || !ACTION.test(text)) return false;
|
|
43
|
+
if (EXPLICIT_FANOUT.test(text)) return true;
|
|
44
|
+
const breadth = BROAD_SCOPE.test(text);
|
|
45
|
+
const componentCount = COMPONENTS.reduce((n, re) => n + Number(re.test(text)), 0);
|
|
46
|
+
const numberedItems = [...text.matchAll(/(?:^|\n)\s*(?:\d+[.)]|[-*])\s+\S/g)].length;
|
|
47
|
+
return (breadth && componentCount >= 2) || componentCount >= 3 || numberedItems >= 3;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseUsedBytes(text) {
|
|
51
|
+
const match = String(text).match(/\bused\s*=\s*([\d.]+)\s*([KMGT]?)B?\b/i);
|
|
52
|
+
if (!match) return null;
|
|
53
|
+
const scale = { '': 1, K: 1024, M: 1024 ** 2, G: GIB, T: 1024 ** 4 }[match[2].toUpperCase()];
|
|
54
|
+
const value = Number(match[1]) * scale;
|
|
55
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseMacPressureOutput(text, { totalMemoryBytes, normalizedLoad }) {
|
|
59
|
+
const source = String(text || '');
|
|
60
|
+
const freeMatch = source.match(/System-wide memory free percentage:\s*(\d+(?:\.\d+)?)%/i);
|
|
61
|
+
const pageSizeMatch = source.match(/page size of\s+([\d,]+)\s+bytes/i);
|
|
62
|
+
const compressorMatch = source.match(/Pages occupied by compressor:\s*([\d,]+)/i);
|
|
63
|
+
const swapMatch = source.match(/vm\.swapusage:.*?used\s*=\s*([\d.]+)\s*([KMGT]?)B?\b/i);
|
|
64
|
+
if (!freeMatch || !pageSizeMatch || !compressorMatch || !swapMatch) return null;
|
|
65
|
+
const pageSize = Number(pageSizeMatch[1].replaceAll(',', ''));
|
|
66
|
+
const compressedPages = Number(compressorMatch[1].replaceAll(',', ''));
|
|
67
|
+
const swapUsedBytes = parseUsedBytes(`used = ${swapMatch[1]}${swapMatch[2]}B`);
|
|
68
|
+
const freePct = Number(freeMatch[1]);
|
|
69
|
+
const compressorBytes = pageSize * compressedPages;
|
|
70
|
+
if (![pageSize, compressedPages, swapUsedBytes, freePct, totalMemoryBytes, normalizedLoad]
|
|
71
|
+
.every(Number.isFinite) || pageSize <= 0 || compressedPages < 0 || totalMemoryBytes <= 0
|
|
72
|
+
|| freePct < 0 || freePct > 100 || normalizedLoad < 0) return null;
|
|
73
|
+
return { freePct, swapUsedBytes, compressorBytes, totalMemoryBytes, normalizedLoad };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Classify measured pressure. Unknown or incomplete measurements recommend serial work. */
|
|
77
|
+
export function pressureRecommendation(sample) {
|
|
78
|
+
if (!sample || ![
|
|
79
|
+
sample.freePct, sample.swapUsedBytes, sample.compressorBytes,
|
|
80
|
+
sample.totalMemoryBytes, sample.normalizedLoad,
|
|
81
|
+
].every(Number.isFinite) || sample.totalMemoryBytes <= 0) {
|
|
82
|
+
return { tier: 'unknown', totalAgents: 1, reason: 'capacity signals unavailable' };
|
|
83
|
+
}
|
|
84
|
+
const compressedRatio = sample.compressorBytes / sample.totalMemoryBytes;
|
|
85
|
+
if (sample.freePct < 45 || sample.swapUsedBytes > 0 || compressedRatio >= 0.4
|
|
86
|
+
|| sample.normalizedLoad >= 1) {
|
|
87
|
+
return { tier: 'constrained', totalAgents: 1, reason: 'resource pressure is high' };
|
|
88
|
+
}
|
|
89
|
+
// The local load-per-logical-core signal is a bounded CPU-pressure proxy, not a claim to a
|
|
90
|
+
// precise CPU utilization sample. Above 75% it trims fan-out to three total agents.
|
|
91
|
+
if (sample.normalizedLoad >= 0.75) {
|
|
92
|
+
return { tier: 'high-cpu', totalAgents: 3, reason: 'CPU pressure is high' };
|
|
93
|
+
}
|
|
94
|
+
if (sample.freePct < 75 || compressedRatio >= 0.2 || sample.normalizedLoad >= 0.55) {
|
|
95
|
+
return { tier: 'moderate', totalAgents: 2, reason: 'resource headroom is partial' };
|
|
96
|
+
}
|
|
97
|
+
return { tier: 'available', totalAgents: null, reason: 'measured headroom is available' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Apply lower configured/runtime caps after pressure sizing. A configured worker ceiling never
|
|
102
|
+
* proves runtime availability; an authoritative runtime cap, when supplied by the host, wins.
|
|
103
|
+
*/
|
|
104
|
+
export function effectiveAgentRecommendation(sample, {
|
|
105
|
+
configuredMaxChildren = null,
|
|
106
|
+
runtimeTotalAgentCap = null,
|
|
107
|
+
workUnitCount = null,
|
|
108
|
+
} = {}) {
|
|
109
|
+
const pressure = pressureRecommendation(sample);
|
|
110
|
+
const runtimeCapKnown = Number.isInteger(runtimeTotalAgentCap) && runtimeTotalAgentCap >= 1;
|
|
111
|
+
const limits = [pressure.totalAgents ?? (runtimeCapKnown ? runtimeTotalAgentCap : UNKNOWN_RUNTIME_TOTAL_AGENT_CEILING)];
|
|
112
|
+
if (Number.isInteger(configuredMaxChildren) && configuredMaxChildren >= 0) {
|
|
113
|
+
limits.push(configuredMaxChildren + 1); // configured workers plus the coordinating agent
|
|
114
|
+
}
|
|
115
|
+
if (runtimeCapKnown) {
|
|
116
|
+
limits.push(runtimeTotalAgentCap);
|
|
117
|
+
}
|
|
118
|
+
if (Number.isInteger(workUnitCount) && workUnitCount >= 0) limits.push(workUnitCount + 1);
|
|
119
|
+
const totalAgents = Math.min(...limits);
|
|
120
|
+
return {
|
|
121
|
+
...pressure,
|
|
122
|
+
totalAgents,
|
|
123
|
+
workers: Math.max(0, totalAgents - 1),
|
|
124
|
+
runtimeCapKnown,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readInput() {
|
|
129
|
+
const chunks = [];
|
|
130
|
+
const buffer = Buffer.alloc(4096);
|
|
131
|
+
let total = 0;
|
|
132
|
+
while (total < INPUT_LIMIT) {
|
|
133
|
+
const count = fs.readSync(0, buffer, 0, Math.min(buffer.length, INPUT_LIMIT - total), null);
|
|
134
|
+
if (!count) break;
|
|
135
|
+
chunks.push(Buffer.from(buffer.subarray(0, count)));
|
|
136
|
+
total += count;
|
|
137
|
+
}
|
|
138
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function collectMacPressure() {
|
|
142
|
+
if (process.platform !== 'darwin') return null;
|
|
143
|
+
try {
|
|
144
|
+
// One bounded subprocess gathers pressure, swap, and compressor bytes. Never use raw free RAM
|
|
145
|
+
// as the capacity signal; memory_pressure, actual swap, compression, and normalized load drive
|
|
146
|
+
// the tier. The hook intentionally does not wait for a timed CPU sample.
|
|
147
|
+
const probe = spawnSync('/bin/sh', ['-c', '/usr/bin/memory_pressure -Q; /usr/sbin/sysctl vm.swapusage; /usr/bin/vm_stat'], {
|
|
148
|
+
encoding: 'utf8', timeout: PROBE_TIMEOUT_MS, maxBuffer: 24 * 1024,
|
|
149
|
+
env: { PATH: '/usr/bin:/bin:/usr/sbin:/sbin' },
|
|
150
|
+
});
|
|
151
|
+
if (probe.error || probe.status !== 0) return null;
|
|
152
|
+
const cpuCount = os.cpus().length;
|
|
153
|
+
const load = os.loadavg()[0];
|
|
154
|
+
if (!cpuCount || !Number.isFinite(load) || load < 0) return null;
|
|
155
|
+
return parseMacPressureOutput(probe.stdout, {
|
|
156
|
+
totalMemoryBytes: os.totalmem(), normalizedLoad: load / cpuCount,
|
|
157
|
+
});
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function formatAdvisory(recommendation) {
|
|
164
|
+
const n = recommendation.totalAgents;
|
|
165
|
+
const runtime = recommendation.runtimeCapKnown
|
|
166
|
+
? 'Do not exceed the host-reported runtime cap; configured concurrency is only a ceiling.'
|
|
167
|
+
: `This hook cannot see the live runtime/tool cap; ${UNKNOWN_RUNTIME_TOTAL_AGENT_CEILING} total is only a conservative ceiling until the coordinator checks it. Configured concurrency is not proof of available slots.`;
|
|
168
|
+
const workerPlan = recommendation.workers > 0
|
|
169
|
+
? `At most ${recommendation.workers} worker agent${recommendation.workers === 1 ? '' : 's'}`
|
|
170
|
+
: 'No additional worker agents';
|
|
171
|
+
return [
|
|
172
|
+
'Capacity-aware parallel-work advisory (context only; no workers were started).',
|
|
173
|
+
`This prompt appears to contain independent work. Resource tier: ${recommendation.tier}; recommend no more than ${n} total agent${n === 1 ? '' : 's'} including the coordinator (${workerPlan}).`,
|
|
174
|
+
`${runtime} Treat configured concurrency as a ceiling only; never infer that configured slots are available.`,
|
|
175
|
+
'If a real agent-spawn/task tool is available and slots remain, launch actual workers now with non-overlapping deliverables and collect their results. If tools or slots are unavailable, continue serially and do not claim parallel workers exist.',
|
|
176
|
+
].join('\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function runCapacityHook(rawInput, sample) {
|
|
180
|
+
let input;
|
|
181
|
+
try { input = JSON.parse(String(rawInput || '')); } catch { return ''; }
|
|
182
|
+
const prompt = input?.prompt ?? input?.user_prompt ?? input?.input;
|
|
183
|
+
if (!isSubstantialParallelWork(prompt)) return '';
|
|
184
|
+
return formatAdvisory(effectiveAgentRecommendation(sample === undefined ? collectMacPressure() : sample, {
|
|
185
|
+
configuredMaxChildren: input?.configured_max_children,
|
|
186
|
+
runtimeTotalAgentCap: input?.runtime_total_agent_cap,
|
|
187
|
+
workUnitCount: input?.independent_workstream_count ?? estimateWorkUnitCount(prompt),
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function main() {
|
|
192
|
+
try {
|
|
193
|
+
const output = runCapacityHook(readInput());
|
|
194
|
+
if (output) process.stdout.write(`${output}\n`);
|
|
195
|
+
} catch {
|
|
196
|
+
// This advisory has no authority to block or delay user work when anything is unavailable.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main();
|