ruvnet-brain 4.0.1 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -0
- package/README.md +4 -4
- package/bin/install.mjs +303 -24
- package/console/CONTRACT.md +172 -0
- package/console/activity.js +753 -0
- package/console/app.js +4189 -0
- package/console/architecture.html +1221 -0
- package/console/assets/depth-1.webp +0 -0
- package/console/assets/depth-2.webp +0 -0
- package/console/assets/depth-3.webp +0 -0
- package/console/assets/harness-vs-plain.svg +259 -0
- package/console/assets/hero.webp +0 -0
- package/console/assets/memory.webp +0 -0
- package/console/assets/metaharness.svg +247 -0
- package/console/index.html +777 -0
- package/console/install-architecture.html +162 -0
- package/console/install-mockup.html +543 -0
- package/console/style.css +2144 -0
- package/console/tips.css +926 -0
- package/console/tips.html +858 -0
- package/console/tips.js +128 -0
- package/docs/RELEASE-NOTES-4.0.md +88 -0
- package/kb/model-requirements.mjs +37 -6
- package/keys/ruvnet-brain-signing.pub.pem +3 -0
- package/package.json +8 -22
- package/plugin/.claude-plugin/marketplace.json +1 -0
- package/plugin/.claude-plugin/plugin.json +2 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/brain-console.md +2 -2
- package/plugin/commands/configure.md +3 -2
- package/plugin/commands/rvbc.md +4 -3
- package/plugin/commands/rvcb.md +2 -2
- package/plugin/commands/whats-new.md +6 -6
- package/plugin/docs/RELEASE-NOTES-4.0.md +88 -0
- package/plugin/hooks/hooks.json +1 -2
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +90 -32
- package/plugin/scripts/detach.mjs +14 -0
- package/plugin/scripts/first-session-worker.mjs +38 -0
- package/plugin/scripts/ground-ruvnet.sh +16 -6
- package/plugin/scripts/hook-shim.mjs +34 -29
- package/plugin/scripts/learn-capture.sh +22 -3
- package/plugin/scripts/learn-flush.mjs +21 -4
- package/plugin/scripts/runtime-preferences.mjs +269 -0
- package/plugin/scripts/session-start-core.mjs +503 -0
- package/plugin/scripts/session-start.sh +3 -858
- package/plugin/scripts/whats-new.mjs +42 -0
- package/plugin/skills/brain-console/SKILL.md +4 -2
- package/plugin/skills/release-proof/SKILL.md +98 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +44 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +286 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
- package/plugin/skills/ruvnet-brain/SKILL.md +22 -7
- package/plugin/skills/rvbc/SKILL.md +9 -6
- package/plugin/skills/whats-new/SKILL.md +4 -4
- package/scripts/adr-backfill.mjs +107 -0
- package/scripts/advocacy-outcomes.mjs +808 -0
- package/scripts/agentdb-context.mjs +216 -0
- package/scripts/agentdb-fleet-doctor.mjs +101 -0
- package/scripts/ascii-drift.mjs +236 -0
- package/scripts/behavioral-l1-l4.mjs +210 -0
- package/scripts/brain-capability-check.mjs +72 -0
- package/scripts/brain-grade-groundtruth.mjs +100 -0
- package/scripts/brain-latency-50.mjs +227 -0
- package/scripts/brain-novice-50.mjs +189 -0
- package/scripts/brain-stamp.mjs +94 -0
- package/scripts/brain-state.mjs +212 -0
- package/scripts/build-bundle.mjs +531 -0
- package/scripts/build-concepts.mjs +132 -0
- package/scripts/build-l2.mjs +71 -0
- package/scripts/build-primer.mjs +73 -0
- package/scripts/build-symbols.mjs +68 -0
- package/scripts/calibrate-router.mjs +97 -0
- package/scripts/capability-audit.mjs +321 -0
- package/scripts/capability-registry.mjs +876 -0
- package/scripts/check-indexation.mjs +108 -0
- package/scripts/check-legibility.mjs +189 -0
- package/scripts/ci/build-fixture-kb.mjs +67 -0
- package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
- package/scripts/ci/learning-replay-recorder.mjs +59 -0
- package/scripts/ci/mutate-hook-timeout.mjs +70 -0
- package/scripts/ci/stranger-fixture-stage.mjs +17 -0
- package/scripts/ci/stranger-scenario.mjs +228 -0
- package/scripts/ci/stranger-timeout.mjs +25 -0
- package/scripts/ci-verdict.mjs +29 -0
- package/scripts/claims-verify.mjs +710 -0
- package/scripts/clear-claude-tmp.sh +31 -0
- package/scripts/console-engine.mjs +434 -0
- package/scripts/console-engine.test.mjs +125 -0
- package/scripts/corpus-qa.mjs +250 -0
- package/scripts/correction-detect-embed.mjs +346 -0
- package/scripts/correction-detect-measure.mjs +270 -0
- package/scripts/correction-detect.mjs +686 -0
- package/scripts/count-chunks.mjs +54 -0
- package/scripts/described-questions.json +30 -0
- package/scripts/design-grade.mjs +58 -0
- package/scripts/dev-plugin-link.sh +105 -0
- package/scripts/distill-project.mjs +200 -0
- package/scripts/doc-currency.mjs +801 -0
- package/scripts/eval-brain.mjs +244 -0
- package/scripts/fix-metaharness-memretrieve.mjs +121 -0
- package/scripts/fix-workstream.mjs +291 -0
- package/scripts/full-hints.mjs +87 -0
- package/scripts/gate.sh +39 -0
- package/scripts/gates.mjs +146 -0
- package/scripts/gen-console-images.mjs +54 -0
- package/scripts/gen-images.mjs +47 -0
- package/scripts/git-clone-refresh.mjs +52 -0
- package/scripts/git-hooks/pre-push +126 -0
- package/scripts/goal-match.mjs +398 -0
- package/scripts/goldie-research.mjs +223 -0
- package/scripts/goldie-weekly.sh +67 -0
- package/scripts/health-repair.mjs +237 -0
- package/scripts/helix-scenario-questions.json +10 -0
- package/scripts/ingest-gists.mjs +230 -0
- package/scripts/ingest-meeting.mjs +115 -0
- package/scripts/ingest-repo.mjs +79 -0
- package/scripts/install-npx-witness.sh +49 -0
- package/scripts/issue-fix.mjs +558 -0
- package/scripts/issue-watch.mjs +276 -0
- package/scripts/issue4-close-note.md +31 -0
- package/scripts/key-canary.mjs +91 -0
- package/scripts/latency-to-surface.mjs +233 -0
- package/scripts/learning-enable.mjs +380 -0
- package/scripts/learning-replay.mjs +1570 -0
- package/scripts/learnings.mjs +62 -0
- package/scripts/lesson-gate.mjs +680 -0
- package/scripts/lesson-lifecycle.mjs +449 -0
- package/scripts/lesson-promote.mjs +262 -0
- package/scripts/lesson-ratify.mjs +98 -0
- package/scripts/lesson-seed.mjs +252 -0
- package/scripts/lesson-store.mjs +447 -0
- package/scripts/loop-checkpoint.mjs +86 -0
- package/scripts/memdb-health.sh +14 -0
- package/scripts/memory-doctor.mjs +326 -0
- package/scripts/model-catalog.mjs +79 -0
- package/scripts/nightly-controller.mjs +66 -0
- package/scripts/nightly-gists.sh +72 -0
- package/scripts/nightly-wrapper.sh +172 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2922 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -0
- package/scripts/protected-release-invocation.mjs +76 -0
- package/scripts/prove.mjs +95 -0
- package/scripts/proxy/claude-proxied.sh +57 -0
- package/scripts/proxy/proxy-revert.sh +59 -0
- package/scripts/proxy/proxy-up.sh +60 -0
- package/scripts/proxy/proxy-verify.mjs +142 -0
- package/scripts/publication-receipt.mjs +307 -0
- package/scripts/published-surface-probe.mjs +241 -0
- package/scripts/qe/card-lane-gate.mjs +162 -0
- package/scripts/qe/session-start-gate.mjs +229 -0
- package/scripts/qe/ux-suite.mjs +323 -0
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/record-lesson.mjs +113 -0
- package/scripts/refresh-model-catalog.mjs +99 -0
- package/scripts/release-authority.mjs +93 -0
- package/scripts/release-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +439 -0
- package/scripts/remedy-registry.mjs +247 -0
- package/scripts/rerank-cap-eval.mjs +265 -0
- package/scripts/rerank-cap-warm-ab.mjs +129 -0
- package/scripts/route-cheap.mjs +20 -15
- package/scripts/router-utilization.mjs +182 -0
- package/scripts/routing-flywheel.mjs +596 -0
- package/scripts/rvf-generation.mjs +104 -0
- package/scripts/rvf-index-audit.mjs +138 -0
- package/scripts/self-update.mjs +296 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -0
- package/scripts/stabilization-receipt.mjs +108 -0
- package/scripts/stack-sync.mjs +469 -0
- package/scripts/stamp-existing-rvf-generations.mjs +53 -0
- package/scripts/stamp-sweep.mjs +144 -0
- package/scripts/status-honesty.mjs +102 -0
- package/scripts/sync-version.mjs +217 -0
- package/scripts/token-report.mjs +102 -0
- package/scripts/top100-benchmark.mjs +479 -0
- package/scripts/top100-corpus.mjs +112 -0
- package/scripts/top100-semantic-assertions.mjs +449 -0
- package/scripts/update-apply.mjs +9 -0
- package/scripts/upgrade-notice.mjs +14 -0
- package/scripts/verify-bundle.mjs +51 -0
- package/scripts/verify-channels.mjs +184 -0
- package/scripts/verify-model-catalog.mjs +104 -0
- package/scripts/verify-nightly-close-issue4.sh +31 -0
- package/scripts/version.mjs +40 -0
- package/scripts/wired-check.mjs +867 -0
- package/plugin/scripts/finalize-token-meter.mjs +0 -25
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/signal-watch.mjs — the W2 POLLED half of the external-signal watch plane (ADR-058 §D3;
|
|
3
|
+
// DDD-0013 Context 2, aggregate SignalDebt). A maintainer-only dev tool, same class as
|
|
4
|
+
// scripts/ci-verdict.mjs and scripts/issue-watch.mjs — it is NOT part of the shipped plugin, so it
|
|
5
|
+
// only ever runs in this checkout (this repo's own session-start.sh looks for it by repo-relative
|
|
6
|
+
// path and simply finds nothing on a downloader's machine — see plugin/scripts/session-start.sh).
|
|
7
|
+
//
|
|
8
|
+
// THE FAILURE THIS CLOSES: 2026-07-27, GitHub CI was failing and the OWNER had to tell the model.
|
|
9
|
+
// plugin/scripts/signal-watch.mjs (the PostToolUse observer, the OTHER half — "W1 OBSERVED") opens a
|
|
10
|
+
// pending debt the moment a `git push` succeeds, keyed (repo, sha), appended to pending.jsonl. THIS
|
|
11
|
+
// file resolves that debt via `gh run list` — READ-ONLY VERBS ONLY, never a mutating `gh` command —
|
|
12
|
+
// and writes the result so plugin/scripts/session-start.sh can surface a transition with zero user
|
|
13
|
+
// input. This is 2026-07-27 replayed with the human removed (ADR-058 §D3, mutant M-W1).
|
|
14
|
+
//
|
|
15
|
+
// REUSE, NOT REIMPLEMENTATION (explicit ADR-058 instruction, enforced by this repo's own
|
|
16
|
+
// substitution:check gate): the success/refuse mapping is scripts/ci-verdict.mjs's assessCiGate —
|
|
17
|
+
// the SAME "unknown is red" law the remote-CI ship gate already uses. This file does not carry a
|
|
18
|
+
// second copy of that decision.
|
|
19
|
+
//
|
|
20
|
+
// SINGLE WRITER (DDD-0013 Context 2 invariant 3): this file is the ONLY writer of ci-status.json.
|
|
21
|
+
// plugin/scripts/signal-watch.mjs (the observer) only ever APPENDS to pending.jsonl and never
|
|
22
|
+
// touches ci-status.json — two files, two writers, on purpose (ADR-050's one-poisoned-predicate
|
|
23
|
+
// lesson: channels that share a write path fail together).
|
|
24
|
+
//
|
|
25
|
+
// DEGRADATION LADDER (DDD-0013 Context 2 invariant 1, "UNKNOWN STAYS OPEN"):
|
|
26
|
+
// no `gh` binary -> state 'unverifiable', reason names the fix. A typed CAPABILITY state,
|
|
27
|
+
// never an error.
|
|
28
|
+
// `gh` unauthenticated -> state 'unverifiable', reason names the fix. Same class as above.
|
|
29
|
+
// API error / rate-limit -> state stays 'pending' — a transient hiccup, retried next poll. NEVER
|
|
30
|
+
// invented as a green resolution (ci-verdict.mjs's law, reused, not
|
|
31
|
+
// reimplemented: `assessCiGate(null)` refuses).
|
|
32
|
+
// no run found yet -> state stays 'pending' (CI hasn't started; not a failure).
|
|
33
|
+
// Never fakes green, never silently disables (DDD invariant 6) — every non-resolved state is a
|
|
34
|
+
// distinct, named reason, not silence.
|
|
35
|
+
//
|
|
36
|
+
// FIXTURE INJECTION (ADR-058 §D3, mandatory): SIGNAL_WATCH_GH_FIXTURE points at a JSON file standing
|
|
37
|
+
// in for a real `gh run list` call, so CI needs no network and no auth. See
|
|
38
|
+
// tests/fixtures/signal-watch/gh-fixtures/*.json for the shapes:
|
|
39
|
+
// [ { "conclusion": "success" } ] — a real `gh run list --json ...` array (first element used)
|
|
40
|
+
// { "apiError": true } — simulates a rate-limit / network failure (state stays pending)
|
|
41
|
+
// { "unauthenticated": true } — simulates `gh auth login required`
|
|
42
|
+
// { "notInstalled": true } — simulates a missing `gh` binary
|
|
43
|
+
|
|
44
|
+
import fs from 'node:fs';
|
|
45
|
+
import path from 'node:path';
|
|
46
|
+
import os from 'node:os';
|
|
47
|
+
import { spawnSync } from 'node:child_process';
|
|
48
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
49
|
+
import { assessCiGate } from './ci-verdict.mjs';
|
|
50
|
+
|
|
51
|
+
const BRAIN_HOME = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
|
|
52
|
+
const SIGNAL_DIR = process.env.RUVNET_SIGNAL_DIR || path.join(BRAIN_HOME, 'external-signals');
|
|
53
|
+
const GH_BIN = process.env.GH_BIN || 'gh';
|
|
54
|
+
const GH_TIMEOUT_MS = 3000; // bounded — this poller may run synchronously inside session-start.sh's 5s hook budget
|
|
55
|
+
|
|
56
|
+
export function pendingPath() { return process.env.SIGNAL_WATCH_PENDING || path.join(SIGNAL_DIR, 'pending.jsonl'); }
|
|
57
|
+
export function statusPath() { return process.env.SIGNAL_WATCH_STATUS || path.join(SIGNAL_DIR, 'ci-status.json'); }
|
|
58
|
+
|
|
59
|
+
export function debtKey(source, repo, ref) { return `${source}:${repo}:${ref}`; }
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse pending.jsonl (append-only event log, written ONLY by the PostToolUse observer) into the
|
|
63
|
+
* list of open git-push CI-verdict debts. Dedup keeps the LATEST event per (repo, ref) — a debt's
|
|
64
|
+
* resolution STATE lives entirely in ci-status.json, never here; this only answers "what exists to
|
|
65
|
+
* check". One malformed line is skipped, never crashes the poller (matches issue-watch.mjs's own
|
|
66
|
+
* defensiveness against a corrupt state file).
|
|
67
|
+
*/
|
|
68
|
+
export function readPushDebts(path_ = pendingPath()) {
|
|
69
|
+
let raw;
|
|
70
|
+
try { raw = fs.readFileSync(path_, 'utf8'); } catch { return []; }
|
|
71
|
+
const byKey = new Map();
|
|
72
|
+
for (const line of raw.split('\n')) {
|
|
73
|
+
const t = line.trim();
|
|
74
|
+
if (!t) continue;
|
|
75
|
+
let ev;
|
|
76
|
+
try { ev = JSON.parse(t); } catch { continue; }
|
|
77
|
+
if (ev && ev.kind === 'git-push' && ev.repo && ev.ref) {
|
|
78
|
+
byKey.set(debtKey('gh-ci', ev.repo, ev.ref), ev);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [...byKey.entries()].map(([key, ev]) => ({ key, repo: ev.repo, ref: ev.ref, ts: ev.ts }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function loadJson(p) {
|
|
85
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return {}; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function saveJson(p, obj) {
|
|
89
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
90
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolve ONE debt's CI verdict. READ-ONLY `gh` verbs only (`--version`, `run list`) — a
|
|
95
|
+
* state-changing verb here is the exact boundary violation DDD-0013's anti-corruption section names
|
|
96
|
+
* ("adapters carry a read-only verb whitelist; a state-changing verb ... is a boundary violation the
|
|
97
|
+
* mesh lint fails"). Returns { state: 'resolved'|'unverifiable'|'pending', conclusion?, reason?, workflowName? }.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveVerdict({ repo, ref }, { ghBin = GH_BIN, fixturePath = process.env.SIGNAL_WATCH_GH_FIXTURE } = {}) {
|
|
100
|
+
let runs = null;
|
|
101
|
+
let capability = null;
|
|
102
|
+
|
|
103
|
+
if (fixturePath) {
|
|
104
|
+
let fixture;
|
|
105
|
+
try { fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); }
|
|
106
|
+
catch (e) { return { state: 'pending', reason: `unreadable SIGNAL_WATCH_GH_FIXTURE: ${e.message}` }; }
|
|
107
|
+
if (fixture && fixture.notInstalled) capability = 'not-installed';
|
|
108
|
+
else if (fixture && fixture.unauthenticated) capability = 'unauthenticated';
|
|
109
|
+
else if (fixture && fixture.apiError) runs = null;
|
|
110
|
+
else runs = Array.isArray(fixture) ? fixture : (fixture.runs || []);
|
|
111
|
+
} else {
|
|
112
|
+
let probe;
|
|
113
|
+
try { probe = spawnSync(ghBin, ['--version'], { encoding: 'utf8', timeout: GH_TIMEOUT_MS }); }
|
|
114
|
+
catch { probe = { error: { code: 'ENOENT' } }; }
|
|
115
|
+
if (probe.error && probe.error.code === 'ENOENT') {
|
|
116
|
+
capability = 'not-installed';
|
|
117
|
+
} else {
|
|
118
|
+
// READ-ONLY VERB: `gh run list`. Never anything that mutates (no `gh run rerun`, no `gh pr merge`, etc).
|
|
119
|
+
const r = spawnSync(ghBin, ['run', 'list', '--commit', ref, '--repo', repo, '--json', 'status,conclusion,workflowName'],
|
|
120
|
+
{ encoding: 'utf8', timeout: GH_TIMEOUT_MS });
|
|
121
|
+
if (r.status === 0) {
|
|
122
|
+
try { runs = JSON.parse(r.stdout || '[]'); } catch { runs = null; }
|
|
123
|
+
} else {
|
|
124
|
+
const err = String(r.stderr || r.stdout || '');
|
|
125
|
+
if (/auth login|not logged in|authentication|401|unauthorized/i.test(err)) capability = 'unauthenticated';
|
|
126
|
+
else runs = null; // API error, rate-limit, offline — stays UNKNOWN, never invented as green
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (capability === 'not-installed') return { state: 'unverifiable', reason: 'gh not installed — install: brew install gh (or see https://cli.github.com)' };
|
|
132
|
+
if (capability === 'unauthenticated') return { state: 'unverifiable', reason: 'gh auth login required' };
|
|
133
|
+
if (!Array.isArray(runs)) return { state: 'pending', reason: 'gh API error or unreachable — verdict UNKNOWN, debt stays open' };
|
|
134
|
+
if (!runs.length) return { state: 'pending', reason: 'no CI run found yet for this SHA' };
|
|
135
|
+
|
|
136
|
+
const run = runs[0];
|
|
137
|
+
// The SAME unknown-is-red law the remote-CI ship gate uses, reused rather than reimplemented.
|
|
138
|
+
const gate = assessCiGate(run.conclusion ?? null);
|
|
139
|
+
if (gate === 'ship') return { state: 'resolved', conclusion: 'success', workflowName: run.workflowName };
|
|
140
|
+
return { state: 'resolved', conclusion: run.conclusion || 'unknown', workflowName: run.workflowName };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Poll every open, not-yet-resolved debt once and persist the result. Append-only in spirit
|
|
145
|
+
* (DDD-0013 Context 1 invariant 3, applied here): a debt already 'resolved' is never re-resolved —
|
|
146
|
+
* closing state is a SurfacingLedger-layer transition (session-start.sh), not a poller re-check.
|
|
147
|
+
*/
|
|
148
|
+
export function pollOnce({ pendingFile = pendingPath(), statusFile = statusPath(), ghBin = GH_BIN, fixturePath = process.env.SIGNAL_WATCH_GH_FIXTURE } = {}) {
|
|
149
|
+
const debts = readPushDebts(pendingFile);
|
|
150
|
+
const status = loadJson(statusFile);
|
|
151
|
+
for (const debt of debts) {
|
|
152
|
+
const prior = status[debt.key];
|
|
153
|
+
if (prior && prior.state === 'resolved') continue;
|
|
154
|
+
const verdict = resolveVerdict({ repo: debt.repo, ref: debt.ref }, { ghBin, fixturePath });
|
|
155
|
+
status[debt.key] = { ...verdict, repo: debt.repo, ref: debt.ref, checkedAt: new Date().toISOString() };
|
|
156
|
+
}
|
|
157
|
+
saveJson(statusFile, status);
|
|
158
|
+
return status;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function main() {
|
|
162
|
+
const status = pollOnce({});
|
|
163
|
+
process.stdout.write(JSON.stringify(status, null, 2) + '\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isMain() {
|
|
167
|
+
try { return process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href; }
|
|
168
|
+
catch { return false; }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isMain()) main();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Produce the explicit maintenance-release seal used when the owner chooses to publish a
|
|
3
|
+
// stabilization build before the separate >=95 promotion program is complete. This receipt never
|
|
4
|
+
// claims 95 and cannot satisfy the strict candidate schema by accident.
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { evaluateStabilizationCandidateReceipt } from './release-proof.mjs';
|
|
11
|
+
|
|
12
|
+
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
13
|
+
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
14
|
+
|
|
15
|
+
function argument(args, name) {
|
|
16
|
+
const index = args.indexOf(name);
|
|
17
|
+
return index >= 0 ? args[index + 1] : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function packedVersion(artifact) {
|
|
21
|
+
const result = spawnSync('tar', ['-xOf', artifact, 'package/package.json'], { encoding: 'utf8' });
|
|
22
|
+
if (result.status !== 0) throw new Error(`cannot read packed package.json: ${String(result.stderr || '').trim()}`);
|
|
23
|
+
return JSON.parse(result.stdout).version;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createStabilizationReceipt({ root, artifactPath, qe, audit }) {
|
|
27
|
+
const git = (...args) => {
|
|
28
|
+
const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
|
|
29
|
+
if (result.status !== 0) throw new Error(`git ${args.join(' ')} failed`);
|
|
30
|
+
return result.stdout.trim();
|
|
31
|
+
};
|
|
32
|
+
const manifest = readJson(path.join(root, 'package.json'));
|
|
33
|
+
const claude = readJson(path.join(root, 'plugin/.claude-plugin/plugin.json'));
|
|
34
|
+
const codex = readJson(path.join(root, 'plugin/.codex-plugin/plugin.json'));
|
|
35
|
+
const sha = git('rev-parse', 'HEAD');
|
|
36
|
+
const digest = sha256(artifactPath);
|
|
37
|
+
const total = Number(qe?.numTotalTests ?? qe?.testResults?.reduce((sum, file) => sum + (file.assertionResults?.length || 0), 0) ?? 0);
|
|
38
|
+
const failed = Number(qe?.numFailedTests ?? 0);
|
|
39
|
+
const skipped = Number(qe?.numPendingTests ?? 0);
|
|
40
|
+
const passed = Number(qe?.numPassedTests ?? Math.max(0, total - failed - skipped));
|
|
41
|
+
const vulnerabilities = audit?.metadata?.vulnerabilities;
|
|
42
|
+
if (!vulnerabilities || !Number.isFinite(Number(vulnerabilities.critical))
|
|
43
|
+
|| !Number.isFinite(Number(vulnerabilities.high))) {
|
|
44
|
+
throw new Error('dependency audit evidence is missing critical/high counts');
|
|
45
|
+
}
|
|
46
|
+
const receipt = {
|
|
47
|
+
schemaVersion: 1,
|
|
48
|
+
phase: 'stabilization-candidate',
|
|
49
|
+
mode: 'stabilization',
|
|
50
|
+
reason: 'Owner-authorized 4.0.4 stabilization release; >=95 promotion remains open work',
|
|
51
|
+
targetScore: 95,
|
|
52
|
+
scoreClaimed: false,
|
|
53
|
+
sha,
|
|
54
|
+
tree: git('rev-parse', 'HEAD^{tree}'),
|
|
55
|
+
dirty: Boolean(git('status', '--porcelain')),
|
|
56
|
+
version: manifest.version,
|
|
57
|
+
tag: `v${manifest.version}`,
|
|
58
|
+
sourceVersions: { package: manifest.version, claudePlugin: claude.version, codexPlugin: codex.version },
|
|
59
|
+
artifact: {
|
|
60
|
+
path: `release-evidence/${path.basename(artifactPath)}`,
|
|
61
|
+
sha256: digest,
|
|
62
|
+
sourceSha: sha,
|
|
63
|
+
version: packedVersion(artifactPath),
|
|
64
|
+
},
|
|
65
|
+
security: {
|
|
66
|
+
status: Number(vulnerabilities.critical || 0) === 0 && Number(vulnerabilities.high || 0) === 0 ? 'PASS' : 'FAIL',
|
|
67
|
+
critical: Number(vulnerabilities.critical || 0),
|
|
68
|
+
high: Number(vulnerabilities.high || 0),
|
|
69
|
+
},
|
|
70
|
+
qe: { status: total > 0 && failed === 0 && skipped === 0 ? 'PASS' : 'FAIL', total, passed, failed, skipped },
|
|
71
|
+
limitations: [
|
|
72
|
+
'No >=95 score claimed',
|
|
73
|
+
'Strict promotion receipt remains unsatisfied',
|
|
74
|
+
'Clean-host and installed-Brain acceptance are required after publication',
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
const result = evaluateStabilizationCandidateReceipt(receipt);
|
|
78
|
+
if (result.verdict !== 'PASS') throw new Error(`stabilization seal failed: ${result.failures.map(({ code }) => code).join(',')}`);
|
|
79
|
+
return receipt;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function main(args = process.argv.slice(2)) {
|
|
83
|
+
try {
|
|
84
|
+
const rootArg = argument(args, '--root');
|
|
85
|
+
const artifactArg = argument(args, '--artifact');
|
|
86
|
+
const outArg = argument(args, '--out');
|
|
87
|
+
if (!artifactArg || !outArg) throw new Error('--artifact and --out are required');
|
|
88
|
+
const root = path.resolve(rootArg || process.cwd());
|
|
89
|
+
const artifactPath = path.resolve(artifactArg);
|
|
90
|
+
const outPath = path.resolve(outArg);
|
|
91
|
+
if (fs.existsSync(outPath)) throw new Error(`refusing to overwrite existing receipt: ${outPath}`);
|
|
92
|
+
const receipt = createStabilizationReceipt({
|
|
93
|
+
root,
|
|
94
|
+
artifactPath,
|
|
95
|
+
qe: readJson(argument(args, '--qe')),
|
|
96
|
+
audit: readJson(argument(args, '--audit')),
|
|
97
|
+
});
|
|
98
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
99
|
+
fs.writeFileSync(outPath, `${JSON.stringify(receipt, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
100
|
+
console.log(JSON.stringify({ verdict: 'PASS', sha: receipt.sha, artifactSha256: receipt.artifact.sha256, version: receipt.version }));
|
|
101
|
+
return 0;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(`stabilization-receipt: ${error.message}`);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) process.exitCode = main();
|