ruvnet-brain 4.3.21 → 4.3.26
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 +5 -5
- package/bin/install.mjs +275 -60
- package/console/app.js +189 -9
- package/console/index.html +70 -24
- package/console/scope.css +137 -0
- package/console/scope.html +144 -0
- package/console/scope.js +209 -0
- package/console/style.css +26 -0
- package/console/tips.html +1 -0
- package/kb/corpus-release-identity.mjs +239 -0
- package/kb/update-storage-transaction.mjs +20 -3
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/commands/checkpoint.md +61 -0
- package/plugin/hooks/codex-hooks.json +64 -1
- package/plugin/hooks/hook-contracts.json +299 -6
- package/plugin/hooks/hooks.json +81 -1
- package/plugin/mcp/server.mjs +23 -0
- package/plugin/scripts/advocacy-catalog.mjs +245 -0
- package/plugin/scripts/advocacy-route.mjs +460 -0
- package/plugin/scripts/continuation-gate.mjs +25 -2
- package/plugin/scripts/continuation-objective.mjs +7 -1
- package/plugin/scripts/continuity-hook-policy.mjs +190 -15
- package/plugin/scripts/coverage-integrity.mjs +7 -0
- package/plugin/scripts/gates.mjs +113 -10
- package/plugin/scripts/grounding-turn-gate.mjs +167 -0
- package/plugin/scripts/grounding-turn-mark.mjs +91 -0
- package/plugin/scripts/hook-shim.mjs +14 -0
- package/plugin/scripts/nightly-scheduler.mjs +37 -4
- package/plugin/scripts/project-progression-checkpoint.mjs +145 -0
- package/plugin/scripts/project-progression-contract.mjs +16 -0
- package/plugin/scripts/project-progression-hook.mjs +3 -0
- package/plugin/scripts/project-progression-producer.mjs +252 -0
- package/plugin/scripts/project-progression-reader.mjs +271 -0
- package/plugin/scripts/project-progression-session-start.mjs +93 -16
- package/plugin/scripts/project-progression-sources.mjs +220 -0
- package/plugin/scripts/project-progression-store.mjs +106 -13
- package/plugin/scripts/ruvnet-gate1-pattern.mjs +29 -0
- package/plugin/scripts/session-snapshot-hook.mjs +115 -7
- package/plugin/scripts/session-start-budget.mjs +59 -0
- package/plugin/scripts/session-start-core.mjs +234 -457
- package/plugin/scripts/session-start-fsutil.mjs +61 -0
- package/plugin/scripts/session-start-health.mjs +64 -0
- package/plugin/scripts/session-start-hook-description.mjs +45 -0
- package/plugin/scripts/session-start-issue-alert.mjs +77 -0
- package/plugin/scripts/session-start-repo-identity.mjs +54 -0
- package/plugin/scripts/session-start-signals.mjs +73 -0
- package/plugin/scripts/session-start-trace.mjs +86 -0
- package/plugin/scripts/session-start-update-plane.mjs +104 -0
- package/plugin/scripts/unprompted-runtime.mjs +32 -2
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +26 -2
- package/plugin/skills/ruvnet-brain/SKILL.md +67 -2
- package/scripts/adr-072-completion.mjs +1 -1
- package/scripts/agentdb-fleet-doctor.mjs +5 -1
- package/scripts/approved-runtime.mjs +197 -0
- package/scripts/brain-novice-50.mjs +16 -1
- package/scripts/brain-score.mjs +23 -5
- package/scripts/build-bundle.mjs +971 -530
- package/scripts/build-concepts.mjs +36 -116
- package/scripts/console-engine.test.mjs +8 -7
- package/scripts/console-runtime-identity.mjs +4 -0
- package/scripts/corpus-aggregates.mjs +94 -77
- package/scripts/corpus-candidate.mjs +475 -222
- package/scripts/corpus-next-seed.mjs +225 -0
- package/scripts/corpus-promotion.mjs +58 -0
- package/scripts/corpus-reconcile.mjs +411 -105
- package/scripts/doc-currency.mjs +16 -1
- package/scripts/dual-host-deliberation.mjs +25 -2
- package/scripts/dual-host-suggest.mjs +17 -1
- package/scripts/falsify.mjs +13 -3
- package/scripts/gist-receipts.mjs +482 -87
- package/scripts/github-health-watch.mjs +12 -2
- package/scripts/handoff-asset.mjs +34 -0
- package/scripts/hook-retirement-check.mjs +8 -1
- package/scripts/host-registry.mjs +1 -1
- package/scripts/ingest-gists.mjs +74 -101
- package/scripts/job-heartbeat.sh +77 -14
- package/scripts/learning-replay-execution.mjs +10 -4
- package/scripts/nightly-gists.sh +27 -13
- package/scripts/nightly-two-run-proof.mjs +1 -1
- package/scripts/nightly-watchdog.mjs +61 -4
- package/scripts/onboarding-console.mjs +364 -28
- package/scripts/oracle/produce-questions.mjs +293 -0
- package/scripts/oracle/producer-hosts.mjs +235 -0
- package/scripts/oracle/repo-recall.mjs +448 -0
- package/scripts/oracle/retrieval-accuracy.mjs +818 -0
- package/scripts/oracle/source-tree.mjs +165 -0
- package/scripts/oracle/source-units.mjs +391 -0
- package/scripts/oracle/spike-run.mjs +98 -0
- package/scripts/oracle/unit-inventory.mjs +141 -0
- package/scripts/oracle/unit-sampling.mjs +128 -0
- package/scripts/oracle/validate-labels.mjs +250 -0
- package/scripts/private-overlay.mjs +248 -0
- package/scripts/product-integrity-contract.mjs +1 -1
- package/scripts/proxy/claude-proxied.sh +6 -0
- package/scripts/proxy/proxy-revert.sh +5 -0
- package/scripts/proxy/proxy-up.sh +6 -0
- package/scripts/proxy/proxy-verify.mjs +4 -0
- package/scripts/public-inputs.mjs +409 -0
- package/scripts/public-verification-inputs.mjs +112 -26
- package/scripts/public-verification-lane.mjs +1 -1
- package/scripts/published-surface-probe.mjs +34 -4
- package/scripts/qe/card-lane-gate.mjs +16 -1
- package/scripts/qe/session-start-gate.mjs +16 -1
- package/scripts/rebuild-gists-from-receipts.mjs +58 -78
- package/scripts/record-lesson.mjs +4 -1
- package/scripts/rehearse-corpus-pipeline.mjs +994 -0
- package/scripts/release-abort-stale.mjs +5 -1
- package/scripts/release-authority.mjs +104 -12
- package/scripts/release-channel-kind.mjs +86 -0
- package/scripts/release-convergence-watchdog.mjs +7 -2
- package/scripts/release-projection.mjs +177 -72
- package/scripts/release-transaction-provider.mjs +47 -10
- package/scripts/release-transaction.mjs +40 -11
- package/scripts/release.mjs +252 -17
- package/scripts/retrieval-canary.mjs +87 -0
- package/scripts/rvf-index-audit.mjs +573 -13
- package/scripts/rvf-wire.mjs +269 -0
- package/scripts/seal-gist-receipt.mjs +65 -0
- package/scripts/selfcheck.mjs +42 -21
- package/scripts/source-coverage.mjs +253 -24
- package/scripts/status-honesty.mjs +25 -0
- package/scripts/sync-census.mjs +0 -0
- package/scripts/sync-version.mjs +2 -0
- package/scripts/trismart.mjs +42 -0
- package/scripts/updater-manifest.mjs +162 -0
- package/scripts/verify-channels.mjs +17 -5
- package/scripts/wired-check.mjs +48 -10
- package/tri-smart-skill/QUICKSTART.md +37 -0
- package/tri-smart-skill/README.md +92 -0
- package/tri-smart-skill/install.cmd +14 -0
- package/tri-smart-skill/install.command +13 -0
- package/tri-smart-skill/install.mjs +51 -0
- package/tri-smart-skill/install.sh +9 -0
- package/tri-smart-skill/tri-smart/SKILL.md +90 -0
- package/tri-smart-skill/tri-smart/evals/evals.json +25 -0
- package/tri-smart-skill/tri-smart/references/protocol.md +25 -0
- package/tri-smart-skill/tri-smart/references/provider-cli.md +18 -0
- package/tri-smart-skill/tri-smart/scripts/review.mjs +154 -0
- package/tri-smart-skill/tri-smart/scripts/setup.mjs +97 -0
- package/tri-smart-skill/tri-smart/scripts/verify-access.mjs +107 -0
- package/scripts/corpus-seed-publish.mjs +0 -110
|
@@ -53,6 +53,7 @@ import os from 'node:os';
|
|
|
53
53
|
import path from 'node:path';
|
|
54
54
|
import { spawnSync } from 'node:child_process';
|
|
55
55
|
import { fileURLToPath } from 'node:url';
|
|
56
|
+
import { describeLatestPointer, isCodeReleaseTag, latestCodeReleaseTag } from './release-channel-kind.mjs';
|
|
56
57
|
|
|
57
58
|
export const EXIT = Object.freeze({ PASS: 0, FAIL: 1, UNKNOWN: 4 });
|
|
58
59
|
|
|
@@ -61,6 +62,8 @@ const REPO = 'stuinfla/ruvnet-brain';
|
|
|
61
62
|
const ASSET = 'ruvnet-brain.zip';
|
|
62
63
|
const REGISTRY = `https://registry.npmjs.org/${PKG}`;
|
|
63
64
|
const RELEASE_API = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
65
|
+
// ADR-086 S1: latest may be a corpus generation; npm-vs-GitHub coherence is about the CODE release.
|
|
66
|
+
const RELEASES_API = `https://api.github.com/repos/${REPO}/releases?per_page=30`;
|
|
64
67
|
|
|
65
68
|
/**
|
|
66
69
|
* The bundle is ~736MB-845MB today. The floor is set FAR below that — this is a truncation
|
|
@@ -209,6 +212,27 @@ async function probeRelease() {
|
|
|
209
212
|
return { tag, s: 'PASS' };
|
|
210
213
|
}
|
|
211
214
|
|
|
215
|
+
/**
|
|
216
|
+
* The newest CODE release. Separate request from releases/latest on purpose: latest answers "what
|
|
217
|
+
* does a customer download", this answers "which product generation is published", and since
|
|
218
|
+
* ADR-086 S1 those are two different releases on most nights.
|
|
219
|
+
*/
|
|
220
|
+
async function latestCodeReleaseTag_(rel) {
|
|
221
|
+
const headers = process.env.GITHUB_TOKEN ? { authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {};
|
|
222
|
+
const r = await get(RELEASES_API, { headers });
|
|
223
|
+
if (r.ok && r.status === 200) {
|
|
224
|
+
try {
|
|
225
|
+
const tag = latestCodeReleaseTag(await r.res.json());
|
|
226
|
+
if (tag) return tag;
|
|
227
|
+
} catch { /* fall through to the pointer below */ }
|
|
228
|
+
}
|
|
229
|
+
// Degrade to the latest POINTER, but only when the pointer is itself a code release. That is
|
|
230
|
+
// byte-for-byte the answer this probe gave before corpus promotion existed, so adding a request
|
|
231
|
+
// never turns a previously-answerable check into UNKNOWN — and it still refuses to compare an npm
|
|
232
|
+
// semver against a corpus digest, which is the whole point.
|
|
233
|
+
return isCodeReleaseTag(rel?.tag) ? rel.tag : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
212
236
|
export async function main() {
|
|
213
237
|
say('PUBLISHED-SURFACE probe — the artifact a stranger receives, not the one in this checkout\n');
|
|
214
238
|
|
|
@@ -217,12 +241,18 @@ export async function main() {
|
|
|
217
241
|
else record('B-npx-exec', 'SKIPPED', '--no-exec');
|
|
218
242
|
const rel = await probeRelease();
|
|
219
243
|
|
|
220
|
-
|
|
221
|
-
|
|
244
|
+
// C1-C4 above deliberately stay on `releases/latest`: that IS the stranger's download, and a
|
|
245
|
+
// corpus generation carries the identical asset names (ruvnet-brain.zip + .sha256 + .sig), so
|
|
246
|
+
// those checks remain exactly as meaningful. Version COHERENCE is a different question — npm only
|
|
247
|
+
// ever carries code generations, so it must be compared against the latest CODE release.
|
|
248
|
+
const codeTag = await latestCodeReleaseTag_(rel);
|
|
249
|
+
if (reg.latest && codeTag) {
|
|
250
|
+
const githubVersion = String(codeTag).replace(/^v/, '');
|
|
222
251
|
if (reg.latest === githubVersion) {
|
|
223
|
-
record('D-version-coherence', 'PASS', `npm ${reg.latest} == GitHub ${
|
|
252
|
+
record('D-version-coherence', 'PASS', `npm ${reg.latest} == GitHub code release ${codeTag}`
|
|
253
|
+
+ (rel.tag && rel.tag !== codeTag ? ` · ${describeLatestPointer(rel.tag)}` : ''));
|
|
224
254
|
} else {
|
|
225
|
-
record('D-version-coherence', 'FAIL', `npm ${reg.latest} != GitHub ${
|
|
255
|
+
record('D-version-coherence', 'FAIL', `npm ${reg.latest} != GitHub code release ${codeTag} — published surfaces identify different Brain generations`);
|
|
226
256
|
}
|
|
227
257
|
} else {
|
|
228
258
|
record('D-version-coherence', 'UNKNOWN', 'npm or GitHub version unavailable; equality cannot be proven');
|
|
@@ -158,5 +158,20 @@ async function main() {
|
|
|
158
158
|
process.exit(1);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
// Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
|
|
162
|
+
// NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
|
|
163
|
+
// shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
|
|
164
|
+
// never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
|
|
165
|
+
// from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
|
|
166
|
+
// archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
|
|
167
|
+
function isDirectInvocation() {
|
|
168
|
+
try {
|
|
169
|
+
if (!process.argv[1]) return false;
|
|
170
|
+
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const invokedDirectly = isDirectInvocation();
|
|
162
177
|
if (invokedDirectly) main();
|
|
@@ -225,5 +225,20 @@ async function main() {
|
|
|
225
225
|
process.exit(1);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
// Entry-point guard. Compares REALPATHS on both sides: path.resolve() normalizes a path but does
|
|
229
|
+
// NOT follow symlinks, while import.meta.url IS symlink-resolved by Node. Through a symlink (npm bin
|
|
230
|
+
// shims, wrapper scripts, and every os.tmpdir() path on macOS) the two sides disagree, so main()
|
|
231
|
+
// never runs -- and because nothing throws, the process exits 0. A silent exit 0 is indistinguishable
|
|
232
|
+
// from "ran, found nothing", which is how prepareCorpusCandidate once reported SUCCESS with no
|
|
233
|
+
// archive on disk. Reproduced live 2026-07-27; pinned by tests/unit/entrypoint-symlink.test.mjs.
|
|
234
|
+
function isDirectInvocation() {
|
|
235
|
+
try {
|
|
236
|
+
if (!process.argv[1]) return false;
|
|
237
|
+
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const invokedDirectly = isDirectInvocation();
|
|
229
244
|
if (invokedDirectly) main();
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// rebuild-gists-from-receipts.mjs — a thin staging/replay wrapper over the canonical gist renderer
|
|
3
|
+
// (renderGistPassages) and the full aggregate builder (buildGistAggregate), both in gist-receipts.mjs.
|
|
4
|
+
//
|
|
5
|
+
// This file owns exactly two things gist-receipts.mjs deliberately does not: (1) accepting a
|
|
6
|
+
// possibly-LEGACY (schema 1/2/3) receipt read from disk and replaying it deterministically from raw
|
|
7
|
+
// gist content alone — no GitHub detail-endpoint auth, no vector build, no publication — and
|
|
8
|
+
// (2) resolving WHICH installed receipt to replay. Rendering itself (the banner/chunk/JSONL
|
|
9
|
+
// assembly) is never reimplemented here; renderGistPassages is the sole implementation.
|
|
10
|
+
//
|
|
11
|
+
// node scripts/rebuild-gists-from-receipts.mjs [--sources <file>] [--out-dir <dir>] [--concurrency N]
|
|
4
12
|
|
|
5
13
|
import crypto from 'node:crypto';
|
|
6
14
|
import fs from 'node:fs';
|
|
7
15
|
import os from 'node:os';
|
|
8
16
|
import path from 'node:path';
|
|
9
17
|
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import {
|
|
18
|
+
import { canonicalJson } from './coverage-integrity.mjs';
|
|
19
|
+
import {
|
|
20
|
+
buildGistAggregate,
|
|
21
|
+
paragraphChunks,
|
|
22
|
+
provenanceBanner,
|
|
23
|
+
rawUrlFor,
|
|
24
|
+
renderGistPassages,
|
|
25
|
+
sealGistReceipt,
|
|
26
|
+
sealGistReceiptSet,
|
|
27
|
+
} from './gist-receipts.mjs';
|
|
28
|
+
|
|
29
|
+
export { buildGistAggregate, paragraphChunks, provenanceBanner, rawUrlFor };
|
|
11
30
|
|
|
12
31
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
32
|
const NAME = 'ruv-gists';
|
|
@@ -20,7 +39,6 @@ function fail(message) {
|
|
|
20
39
|
}
|
|
21
40
|
|
|
22
41
|
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
|
23
|
-
const jsonLine = (value) => JSON.stringify(value).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
24
42
|
|
|
25
43
|
function validDate(value) {
|
|
26
44
|
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value) && Number.isFinite(Date.parse(value));
|
|
@@ -33,18 +51,22 @@ function validateFilename(filename, label) {
|
|
|
33
51
|
}
|
|
34
52
|
}
|
|
35
53
|
|
|
54
|
+
// Accepts a receipt read from disk, which may be an OLDER schema than anything buildGistAggregate
|
|
55
|
+
// produces today (schema 1/2 predate the source-byte binding this pipeline now requires for every
|
|
56
|
+
// NEW write). This is a raw-input structural sanity check on a file this tool does not control the
|
|
57
|
+
// production of, not the "one shared validator" (validateGistReceipt) — that validator always
|
|
58
|
+
// requires real, already-rendered passage bytes on disk, which do not exist yet at this point in the
|
|
59
|
+
// replay. Schema 3 gets ONE extra guard on top of the shape checks below: the receipt must re-seal
|
|
60
|
+
// to itself byte-for-byte (sealGistReceipt/sealGistReceiptSet round-trip), which is exactly the
|
|
61
|
+
// internal-consistency half of what the shared validator checks — the half that needs no live
|
|
62
|
+
// observation and no passages file to prove.
|
|
36
63
|
export function validateSourceReceipts(source) {
|
|
37
64
|
if (source?.schemaVersion === 3) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
id, updated_at: receipt?.updatedAt,
|
|
44
|
-
})) },
|
|
45
|
-
};
|
|
46
|
-
try { validateGistReceiptSet(source, observation); }
|
|
47
|
-
catch (error) { fail(`schema-3 source receipt is invalid: ${error.message}`); }
|
|
65
|
+
if (source?.kind !== 'ruvnet-brain-gist-source-receipts') fail('schema-3 source receipt has the wrong kind');
|
|
66
|
+
const resealed = sealGistReceiptSet({ ...source, gists: Object.fromEntries(
|
|
67
|
+
Object.entries(source.gists || {}).map(([id, row]) => [id, sealGistReceipt(row)]),
|
|
68
|
+
) });
|
|
69
|
+
if (canonicalJson(resealed) !== canonicalJson(source)) fail('schema-3 source receipt does not re-seal to itself');
|
|
48
70
|
return source;
|
|
49
71
|
}
|
|
50
72
|
if (![1, 2].includes(source?.schemaVersion)) fail('source receipt schemaVersion must be 1 or 2');
|
|
@@ -85,37 +107,6 @@ export function validateSourceReceipts(source) {
|
|
|
85
107
|
return source;
|
|
86
108
|
}
|
|
87
109
|
|
|
88
|
-
export function rawUrlFor({ owner, gistId, versionSha, filename }) {
|
|
89
|
-
if (!/^[A-Za-z0-9-]{1,39}$/.test(String(owner || '')) || !HEX_GIST.test(String(gistId || ''))
|
|
90
|
-
|| !HEX40.test(String(versionSha || ''))) fail('cannot construct raw URL from malformed source identity');
|
|
91
|
-
validateFilename(filename, `gist ${gistId}`);
|
|
92
|
-
const encodedFile = filename.split('/').map(encodeURIComponent).join('/');
|
|
93
|
-
return `https://gist.githubusercontent.com/${owner}/${gistId}/raw/${versionSha}/${encodedFile}`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function paragraphChunks(text, size = 3200) {
|
|
97
|
-
if (typeof text !== 'string') fail('passage content must be text');
|
|
98
|
-
if (!Number.isSafeInteger(size) || size < 1) fail('chunk size must be a positive integer');
|
|
99
|
-
const out = [];
|
|
100
|
-
let buffer = '';
|
|
101
|
-
for (const paragraph of text.split(/\n\n+/)) {
|
|
102
|
-
if (buffer && buffer.length + paragraph.length + 2 > size) {
|
|
103
|
-
out.push(buffer);
|
|
104
|
-
buffer = '';
|
|
105
|
-
}
|
|
106
|
-
buffer = buffer ? `${buffer}\n\n${paragraph}` : paragraph;
|
|
107
|
-
}
|
|
108
|
-
if (buffer.trim()) out.push(buffer);
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function provenanceBanner({ owner, gistId, filename, updatedAt }) {
|
|
113
|
-
return `SOURCE: GitHub gist by @${owner} — "${filename.replace(/\s+/g, ' ').trim().slice(0, 160)}"\n`
|
|
114
|
-
+ `GIST STATUS: rUv's own notes / release announcement — may describe PROPOSED or UNRELEASED work.\n`
|
|
115
|
-
+ `Treat as intent, not as confirmed shipped behavior: verify against repo source before asserting.\n`
|
|
116
|
-
+ `updated: ${updatedAt.slice(0, 10)} · https://gist.github.com/${owner}/${gistId}\n\n`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
110
|
async function mapBounded(items, concurrency, operation) {
|
|
120
111
|
const results = new Array(items.length);
|
|
121
112
|
let next = 0;
|
|
@@ -129,6 +120,11 @@ async function mapBounded(items, concurrency, operation) {
|
|
|
129
120
|
return results;
|
|
130
121
|
}
|
|
131
122
|
|
|
123
|
+
// Deterministically REPLAY passages from a durable, exact-version source receipt. Intentionally does
|
|
124
|
+
// not call the GitHub detail API, build vectors, or publish artifacts — every included file's raw
|
|
125
|
+
// bytes are fetched once (unauthenticated, versioned raw URL) and verified byte-for-byte and
|
|
126
|
+
// sha256-for-sha256 against the receipt already on disk; rendering itself is renderGistPassages, the
|
|
127
|
+
// same implementation buildGistAggregate uses.
|
|
132
128
|
export async function reconstructGists(source, { fetchFn = globalThis.fetch, concurrency = 6 } = {}) {
|
|
133
129
|
validateSourceReceipts(source);
|
|
134
130
|
if (typeof fetchFn !== 'function') fail('fetch implementation is unavailable');
|
|
@@ -152,51 +148,35 @@ export async function reconstructGists(source, { fetchFn = globalThis.fetch, con
|
|
|
152
148
|
if (bytes.length !== file.bytes) fail(`${gistId}/${file.filename} byte count ${bytes.length} differs from receipt ${file.bytes}`);
|
|
153
149
|
const digest = sha256(bytes);
|
|
154
150
|
if (digest !== file.sha256) fail(`${gistId}/${file.filename} sha256 ${digest} differs from receipt ${file.sha256}`);
|
|
155
|
-
let body;
|
|
156
151
|
try {
|
|
157
|
-
|
|
152
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
158
153
|
} catch {
|
|
159
154
|
fail(`${gistId}/${file.filename} is not valid UTF-8 text`);
|
|
155
|
+
return undefined; // unreachable; keeps linters happy about a missing return on every path
|
|
160
156
|
}
|
|
161
|
-
return body;
|
|
162
157
|
});
|
|
163
158
|
|
|
164
|
-
const passages = [];
|
|
165
|
-
const entries = {};
|
|
166
159
|
let bodyIndex = 0;
|
|
167
|
-
|
|
160
|
+
const capturedGists = {};
|
|
168
161
|
for (const [gistId, receipt] of Object.entries(source.gists)) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
for (const [chunkIndex, chunk] of chunks.entries()) {
|
|
176
|
-
const passageId = String(id++);
|
|
177
|
-
const passagePath = `${gistId.slice(0, 8)}/${file.filename}${chunks.length > 1 ? `#${chunkIndex}` : ''}`;
|
|
178
|
-
const text = banner + chunk;
|
|
179
|
-
passages.push({ id: passageId, text, path: passagePath, title });
|
|
180
|
-
entries[passageId] = { path: passagePath, kind: 'doc', title, chunk: chunkIndex, preview: text.slice(0, 200) };
|
|
181
|
-
}
|
|
182
|
-
}
|
|
162
|
+
capturedGists[gistId] = {
|
|
163
|
+
gistId,
|
|
164
|
+
versionSha: receipt.versionSha,
|
|
165
|
+
updatedAt: receipt.updatedAt,
|
|
166
|
+
files: receipt.files.map((file) => (file.included ? { ...file, body: bodies[bodyIndex++] } : file)),
|
|
167
|
+
};
|
|
183
168
|
}
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
metric: 'cosine',
|
|
189
|
-
name: NAME,
|
|
190
|
-
generated: source.generated,
|
|
191
|
-
repo: `gists/${source.owner}`,
|
|
192
|
-
note: "rUv's public gists — announcements and thinking, PROPOSED unless confirmed in repo source.",
|
|
193
|
-
entries,
|
|
194
|
-
};
|
|
169
|
+
const { passageBytes, metadata } = renderGistPassages({
|
|
170
|
+
captured: { owner: source.owner, gists: capturedGists }, generatedAt: source.generated,
|
|
171
|
+
});
|
|
172
|
+
const passageBody = passageBytes.toString('utf8');
|
|
195
173
|
const passagesSha256 = sha256(passageBody);
|
|
174
|
+
const trimmed = passageBody.trim();
|
|
175
|
+
const passages = trimmed.length ? trimmed.split('\n').map((line) => JSON.parse(line)) : [];
|
|
196
176
|
return {
|
|
197
177
|
passages,
|
|
198
178
|
passageBody,
|
|
199
|
-
meta,
|
|
179
|
+
meta: metadata,
|
|
200
180
|
sources: source.schemaVersion === 3
|
|
201
181
|
? sealGistReceiptSet({ ...source, passagesSha256 })
|
|
202
182
|
: { ...source, schemaVersion: 2, passagesSha256, gists: source.gists },
|
|
@@ -283,7 +263,7 @@ export async function main(argv = process.argv.slice(2), {
|
|
|
283
263
|
return 0;
|
|
284
264
|
}
|
|
285
265
|
|
|
286
|
-
if (
|
|
266
|
+
if (((() => { try { return process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } })())) {
|
|
287
267
|
main().then((code) => { process.exitCode = code; }).catch((error) => {
|
|
288
268
|
console.error(error.message);
|
|
289
269
|
process.exitCode = 1;
|
|
@@ -62,9 +62,12 @@ const value = [
|
|
|
62
62
|
`OUTCOME: ${outcome}`,
|
|
63
63
|
].filter(Boolean).join(' ');
|
|
64
64
|
|
|
65
|
+
// Every `ruflo` invocation auto-starts a project background daemon unless this is set (verified
|
|
66
|
+
// live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/services/
|
|
67
|
+
// daemon-autostart.js:85) — recording a lesson has no business leaving one running.
|
|
65
68
|
const ruflo = (args) =>
|
|
66
69
|
execFileSync(RUFLO, args, { cwd: dir, encoding: 'utf8', timeout: 60000,
|
|
67
|
-
shell: process.platform === 'win32' });
|
|
70
|
+
shell: process.platform === 'win32', env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0' } });
|
|
68
71
|
|
|
69
72
|
console.log(`\nRecording lesson into ${path.basename(dir)}/.swarm/memory.db (namespace: ${ns})`);
|
|
70
73
|
console.log(` key: ${key}`);
|