ruvnet-brain 4.0.1 → 4.0.2
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 +100 -5
- 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/hooks/hooks.json +1 -2
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +21 -0
- 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 +7 -7
- 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 +477 -0
- package/plugin/scripts/session-start.sh +3 -858
- package/plugin/skills/brain-console/SKILL.md +4 -2
- package/plugin/skills/release-proof/SKILL.md +81 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +38 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +210 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
- package/plugin/skills/rvbc/SKILL.md +9 -6
- 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 +522 -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/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 +250 -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 +639 -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 +271 -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 +180 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2749 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -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/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-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +395 -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 +508 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -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 +864 -0
- package/plugin/scripts/finalize-token-meter.mjs +0 -25
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// rvf-generation.mjs — bind each canonical RVF byte generation to the Brain product version.
|
|
2
|
+
//
|
|
3
|
+
// @ruvector/rvf's Node API does not currently expose a supported custom-manifest writer for an
|
|
4
|
+
// existing store. This checksum-bound companion is therefore the fail-closed identity record:
|
|
5
|
+
// changing even one RVF byte changes sha256, while brainVersion/releaseTag identify the npm and
|
|
6
|
+
// GitHub release that owns those exact bytes.
|
|
7
|
+
|
|
8
|
+
import crypto from 'node:crypto';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { getVersion, getVersionTag } from './version.mjs';
|
|
12
|
+
|
|
13
|
+
export const RVF_GENERATIONS_FILE = 'RVF-GENERATIONS.json';
|
|
14
|
+
|
|
15
|
+
export function canonicalRvfStores(dir) {
|
|
16
|
+
return fs.readdirSync(dir)
|
|
17
|
+
.map((file) => file.match(/^(.+)\.big\.rvf$/)?.[1])
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.sort();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasCanonicalRvfStore(dir, name) {
|
|
23
|
+
const wanted = String(name).toLowerCase();
|
|
24
|
+
return canonicalRvfStores(dir).some((store) => store.toLowerCase() === wanted);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function sha256File(file) {
|
|
28
|
+
const hash = crypto.createHash('sha256');
|
|
29
|
+
const fd = fs.openSync(file, 'r');
|
|
30
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
31
|
+
try {
|
|
32
|
+
let bytes;
|
|
33
|
+
while ((bytes = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) {
|
|
34
|
+
hash.update(buffer.subarray(0, bytes));
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
fs.closeSync(fd);
|
|
38
|
+
}
|
|
39
|
+
return hash.digest('hex');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function readRvfGenerations(dir) {
|
|
43
|
+
const file = path.join(dir, RVF_GENERATIONS_FILE);
|
|
44
|
+
if (!fs.existsSync(file)) {
|
|
45
|
+
return { schemaVersion: 1, brainVersion: getVersion(), releaseTag: getVersionTag(), stores: {} };
|
|
46
|
+
}
|
|
47
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
48
|
+
parsed.stores ||= {};
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writeRvfGeneration({
|
|
53
|
+
dir,
|
|
54
|
+
store,
|
|
55
|
+
rvfFile = `${store}.big.rvf`,
|
|
56
|
+
model,
|
|
57
|
+
dimensions,
|
|
58
|
+
sourceCommit = null,
|
|
59
|
+
builtUtc = new Date().toISOString(),
|
|
60
|
+
previousDir = dir,
|
|
61
|
+
}) {
|
|
62
|
+
const rvfPath = path.join(dir, rvfFile);
|
|
63
|
+
if (!fs.existsSync(rvfPath)) throw new Error(`cannot stamp missing RVF: ${rvfPath}`);
|
|
64
|
+
const manifest = readRvfGenerations(previousDir);
|
|
65
|
+
manifest.schemaVersion = 1;
|
|
66
|
+
manifest.brainVersion = getVersion();
|
|
67
|
+
manifest.releaseTag = getVersionTag();
|
|
68
|
+
manifest.stores[store] = {
|
|
69
|
+
file: rvfFile,
|
|
70
|
+
sha256: sha256File(rvfPath),
|
|
71
|
+
bytes: fs.statSync(rvfPath).size,
|
|
72
|
+
model,
|
|
73
|
+
dimensions,
|
|
74
|
+
sourceCommit,
|
|
75
|
+
builtUtc,
|
|
76
|
+
};
|
|
77
|
+
fs.writeFileSync(path.join(dir, RVF_GENERATIONS_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
78
|
+
return manifest.stores[store];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function verifyRvfGenerations(dir, {
|
|
82
|
+
version = getVersion(),
|
|
83
|
+
releaseTag = getVersionTag(),
|
|
84
|
+
requiredStores = [],
|
|
85
|
+
allowMissingFiles = false,
|
|
86
|
+
} = {}) {
|
|
87
|
+
const manifest = readRvfGenerations(dir);
|
|
88
|
+
const failures = [];
|
|
89
|
+
if (manifest.brainVersion !== version) failures.push(`brainVersion=${manifest.brainVersion}, expected ${version}`);
|
|
90
|
+
if (manifest.releaseTag !== releaseTag) failures.push(`releaseTag=${manifest.releaseTag}, expected ${releaseTag}`);
|
|
91
|
+
for (const store of requiredStores) {
|
|
92
|
+
if (!manifest.stores[store]) failures.push(`${store}: no generation record`);
|
|
93
|
+
}
|
|
94
|
+
for (const [store, generation] of Object.entries(manifest.stores)) {
|
|
95
|
+
const file = path.join(dir, generation.file || `${store}.big.rvf`);
|
|
96
|
+
if (!fs.existsSync(file)) {
|
|
97
|
+
if (!allowMissingFiles) failures.push(`${store}: missing ${path.basename(file)}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const actual = sha256File(file);
|
|
101
|
+
if (actual !== generation.sha256) failures.push(`${store}: sha256=${actual}, recorded ${generation.sha256}`);
|
|
102
|
+
}
|
|
103
|
+
return { manifest, failures };
|
|
104
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { loadRvf, closeReadonlyRvf } from '../kb/resolve-deps.mjs';
|
|
6
|
+
import { persistAndVerifyRvfIndex } from '../kb/rvf-index.mjs';
|
|
7
|
+
import {
|
|
8
|
+
readRvfGenerations,
|
|
9
|
+
sha256File,
|
|
10
|
+
writeRvfGeneration,
|
|
11
|
+
} from './rvf-generation.mjs';
|
|
12
|
+
|
|
13
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
export const RVF_HNSW_THRESHOLD = 1024;
|
|
15
|
+
|
|
16
|
+
function dimensionsFor(rvfPath) {
|
|
17
|
+
const embedPath = `${rvfPath}.embed.json`;
|
|
18
|
+
const config = JSON.parse(fs.readFileSync(embedPath, 'utf8'));
|
|
19
|
+
const dimensions = Number(config.dimensions);
|
|
20
|
+
if (!Number.isInteger(dimensions) || dimensions < 1) {
|
|
21
|
+
throw new Error(`invalid dimensions in ${embedPath}`);
|
|
22
|
+
}
|
|
23
|
+
return dimensions;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function inspectRvfIndex(rvfPath, RvfDatabase) {
|
|
27
|
+
const db = await RvfDatabase.openReadonly(rvfPath);
|
|
28
|
+
try {
|
|
29
|
+
const [status, segments] = await Promise.all([db.status(), db.segments()]);
|
|
30
|
+
const hasIndex = segments.some(({ segType }) => segType === 'index');
|
|
31
|
+
return {
|
|
32
|
+
path: rvfPath,
|
|
33
|
+
totalVectors: status.totalVectors,
|
|
34
|
+
indexRequired: status.totalVectors >= RVF_HNSW_THRESHOLD,
|
|
35
|
+
hasIndex,
|
|
36
|
+
state: status.totalVectors >= RVF_HNSW_THRESHOLD && !hasIndex ? 'FAIL' : 'PASS',
|
|
37
|
+
};
|
|
38
|
+
} finally {
|
|
39
|
+
await closeReadonlyRvf(db);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function auditRvfIndexes(rvfPaths) {
|
|
44
|
+
const { mod: { RvfDatabase } } = loadRvf();
|
|
45
|
+
return Promise.all(rvfPaths.map((rvfPath) => inspectRvfIndex(rvfPath, RvfDatabase)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function repairRvfIndex(rvfPath, RvfDatabase) {
|
|
49
|
+
const before = await inspectRvfIndex(rvfPath, RvfDatabase);
|
|
50
|
+
if (before.state === 'PASS') return { ...before, repaired: false, elapsedMs: 0 };
|
|
51
|
+
|
|
52
|
+
const started = Date.now();
|
|
53
|
+
const db = await RvfDatabase.open(rvfPath);
|
|
54
|
+
const proof = await persistAndVerifyRvfIndex({
|
|
55
|
+
db,
|
|
56
|
+
dimensions: dimensionsFor(rvfPath),
|
|
57
|
+
rvfPath,
|
|
58
|
+
RvfDatabase,
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
...before,
|
|
62
|
+
hasIndex: proof.hasIndex,
|
|
63
|
+
state: proof.hasIndex ? 'PASS' : 'FAIL',
|
|
64
|
+
repaired: proof.hasIndex,
|
|
65
|
+
elapsedMs: Date.now() - started,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function restampChangedRvfGenerations(kbDir, results) {
|
|
70
|
+
const ledger = readRvfGenerations(kbDir);
|
|
71
|
+
let stamped = 0;
|
|
72
|
+
for (const result of results) {
|
|
73
|
+
if (result.state !== 'PASS' || !result.path.endsWith('.big.rvf')) continue;
|
|
74
|
+
const store = path.basename(result.path, '.big.rvf');
|
|
75
|
+
const prior = ledger.stores?.[store];
|
|
76
|
+
if (!prior || prior.sha256 === sha256File(result.path)) continue;
|
|
77
|
+
const embed = JSON.parse(fs.readFileSync(`${result.path}.embed.json`, 'utf8'));
|
|
78
|
+
writeRvfGeneration({
|
|
79
|
+
dir: kbDir,
|
|
80
|
+
store,
|
|
81
|
+
rvfFile: path.basename(result.path),
|
|
82
|
+
model: prior.model || embed.model || null,
|
|
83
|
+
dimensions: prior.dimensions || Number(embed.dimensions),
|
|
84
|
+
sourceCommit: prior.sourceCommit ?? null,
|
|
85
|
+
});
|
|
86
|
+
stamped++;
|
|
87
|
+
}
|
|
88
|
+
return stamped;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function main() {
|
|
92
|
+
const repair = process.argv.includes('--repair');
|
|
93
|
+
const concurrencyArg = process.argv.indexOf('--concurrency');
|
|
94
|
+
const concurrency = Math.max(1, Number(concurrencyArg >= 0 ? process.argv[concurrencyArg + 1] : 3));
|
|
95
|
+
const namesAt = process.argv.indexOf('--names');
|
|
96
|
+
const names = namesAt >= 0
|
|
97
|
+
? new Set(String(process.argv[namesAt + 1] || '').split(',').filter(Boolean))
|
|
98
|
+
: null;
|
|
99
|
+
const kbDirAt = process.argv.indexOf('--dir');
|
|
100
|
+
const kbDir = path.resolve(ROOT, kbDirAt >= 0 ? process.argv[kbDirAt + 1] : 'kb');
|
|
101
|
+
const rvfPaths = fs.readdirSync(kbDir)
|
|
102
|
+
.filter((name) => name.endsWith('.rvf') && (!names || names.has(name.replace(/\.rvf$/, ''))))
|
|
103
|
+
.sort()
|
|
104
|
+
.map((name) => path.join(kbDir, name));
|
|
105
|
+
const { mod: { RvfDatabase } } = loadRvf();
|
|
106
|
+
const results = new Array(rvfPaths.length);
|
|
107
|
+
let next = 0;
|
|
108
|
+
|
|
109
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, rvfPaths.length || 1) }, async () => {
|
|
110
|
+
for (let index = next++; index < rvfPaths.length; index = next++) {
|
|
111
|
+
const rvfPath = rvfPaths[index];
|
|
112
|
+
try {
|
|
113
|
+
results[index] = repair
|
|
114
|
+
? await repairRvfIndex(rvfPath, RvfDatabase)
|
|
115
|
+
: await inspectRvfIndex(rvfPath, RvfDatabase);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
results[index] = { path: rvfPath, state: 'FAIL', error: error.message };
|
|
118
|
+
}
|
|
119
|
+
const row = results[index];
|
|
120
|
+
console.log(JSON.stringify({ ...row, path: path.relative(ROOT, row.path) }));
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
const failed = results.filter(({ state }) => state !== 'PASS');
|
|
125
|
+
const repaired = results.filter(({ repaired }) => repaired).length;
|
|
126
|
+
const stamped = repair && !failed.length
|
|
127
|
+
? restampChangedRvfGenerations(kbDir, results)
|
|
128
|
+
: 0;
|
|
129
|
+
console.log(`SUMMARY checked=${results.length} repaired=${repaired} stamped=${stamped} failed=${failed.length}`);
|
|
130
|
+
if (failed.length) process.exitCode = 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
134
|
+
main().catch((error) => {
|
|
135
|
+
console.error(error.stack || error.message);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
});
|
|
138
|
+
}
|