ruvnet-brain 4.3.21 → 4.3.25

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.
Files changed (141) hide show
  1. package/README.md +5 -5
  2. package/bin/install.mjs +275 -60
  3. package/console/app.js +141 -9
  4. package/console/index.html +51 -24
  5. package/console/scope.css +137 -0
  6. package/console/scope.html +144 -0
  7. package/console/scope.js +209 -0
  8. package/console/tips.html +1 -0
  9. package/kb/corpus-release-identity.mjs +239 -0
  10. package/kb/update-storage-transaction.mjs +20 -3
  11. package/package.json +9 -2
  12. package/plugin/.claude-plugin/plugin.json +2 -2
  13. package/plugin/.codex-plugin/plugin.json +1 -1
  14. package/plugin/commands/checkpoint.md +61 -0
  15. package/plugin/hooks/codex-hooks.json +64 -1
  16. package/plugin/hooks/hook-contracts.json +299 -6
  17. package/plugin/hooks/hooks.json +81 -1
  18. package/plugin/mcp/server.mjs +23 -0
  19. package/plugin/scripts/advocacy-catalog.mjs +245 -0
  20. package/plugin/scripts/advocacy-route.mjs +460 -0
  21. package/plugin/scripts/continuation-gate.mjs +25 -2
  22. package/plugin/scripts/continuation-objective.mjs +7 -1
  23. package/plugin/scripts/continuity-hook-policy.mjs +190 -15
  24. package/plugin/scripts/coverage-integrity.mjs +7 -0
  25. package/plugin/scripts/gates.mjs +113 -10
  26. package/plugin/scripts/grounding-turn-gate.mjs +167 -0
  27. package/plugin/scripts/grounding-turn-mark.mjs +91 -0
  28. package/plugin/scripts/hook-shim.mjs +14 -0
  29. package/plugin/scripts/nightly-scheduler.mjs +37 -4
  30. package/plugin/scripts/project-progression-checkpoint.mjs +145 -0
  31. package/plugin/scripts/project-progression-contract.mjs +16 -0
  32. package/plugin/scripts/project-progression-hook.mjs +3 -0
  33. package/plugin/scripts/project-progression-producer.mjs +252 -0
  34. package/plugin/scripts/project-progression-reader.mjs +271 -0
  35. package/plugin/scripts/project-progression-session-start.mjs +93 -16
  36. package/plugin/scripts/project-progression-sources.mjs +220 -0
  37. package/plugin/scripts/project-progression-store.mjs +106 -13
  38. package/plugin/scripts/ruvnet-gate1-pattern.mjs +29 -0
  39. package/plugin/scripts/session-snapshot-hook.mjs +115 -7
  40. package/plugin/scripts/session-start-budget.mjs +59 -0
  41. package/plugin/scripts/session-start-core.mjs +234 -457
  42. package/plugin/scripts/session-start-fsutil.mjs +61 -0
  43. package/plugin/scripts/session-start-health.mjs +64 -0
  44. package/plugin/scripts/session-start-hook-description.mjs +45 -0
  45. package/plugin/scripts/session-start-issue-alert.mjs +77 -0
  46. package/plugin/scripts/session-start-repo-identity.mjs +54 -0
  47. package/plugin/scripts/session-start-signals.mjs +73 -0
  48. package/plugin/scripts/session-start-trace.mjs +86 -0
  49. package/plugin/scripts/session-start-update-plane.mjs +104 -0
  50. package/plugin/scripts/unprompted-runtime.mjs +32 -2
  51. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +26 -2
  52. package/plugin/skills/ruvnet-brain/SKILL.md +67 -2
  53. package/scripts/adr-072-completion.mjs +1 -1
  54. package/scripts/agentdb-fleet-doctor.mjs +5 -1
  55. package/scripts/approved-runtime.mjs +197 -0
  56. package/scripts/brain-novice-50.mjs +16 -1
  57. package/scripts/brain-score.mjs +23 -5
  58. package/scripts/build-bundle.mjs +971 -530
  59. package/scripts/build-concepts.mjs +36 -116
  60. package/scripts/console-engine.test.mjs +8 -7
  61. package/scripts/console-runtime-identity.mjs +4 -0
  62. package/scripts/corpus-aggregates.mjs +94 -77
  63. package/scripts/corpus-candidate.mjs +475 -222
  64. package/scripts/corpus-next-seed.mjs +225 -0
  65. package/scripts/corpus-promotion.mjs +58 -0
  66. package/scripts/corpus-reconcile.mjs +411 -105
  67. package/scripts/doc-currency.mjs +16 -1
  68. package/scripts/dual-host-deliberation.mjs +25 -2
  69. package/scripts/dual-host-suggest.mjs +17 -1
  70. package/scripts/falsify.mjs +13 -3
  71. package/scripts/gist-receipts.mjs +482 -87
  72. package/scripts/github-health-watch.mjs +12 -2
  73. package/scripts/handoff-asset.mjs +34 -0
  74. package/scripts/hook-retirement-check.mjs +8 -1
  75. package/scripts/host-registry.mjs +1 -1
  76. package/scripts/ingest-gists.mjs +74 -101
  77. package/scripts/job-heartbeat.sh +77 -14
  78. package/scripts/learning-replay-execution.mjs +10 -4
  79. package/scripts/nightly-gists.sh +27 -13
  80. package/scripts/nightly-two-run-proof.mjs +1 -1
  81. package/scripts/nightly-watchdog.mjs +61 -4
  82. package/scripts/onboarding-console.mjs +319 -27
  83. package/scripts/oracle/produce-questions.mjs +293 -0
  84. package/scripts/oracle/producer-hosts.mjs +235 -0
  85. package/scripts/oracle/repo-recall.mjs +448 -0
  86. package/scripts/oracle/retrieval-accuracy.mjs +818 -0
  87. package/scripts/oracle/source-tree.mjs +165 -0
  88. package/scripts/oracle/source-units.mjs +391 -0
  89. package/scripts/oracle/spike-run.mjs +98 -0
  90. package/scripts/oracle/unit-inventory.mjs +141 -0
  91. package/scripts/oracle/unit-sampling.mjs +128 -0
  92. package/scripts/oracle/validate-labels.mjs +250 -0
  93. package/scripts/private-overlay.mjs +248 -0
  94. package/scripts/product-integrity-contract.mjs +1 -1
  95. package/scripts/proxy/claude-proxied.sh +6 -0
  96. package/scripts/proxy/proxy-revert.sh +5 -0
  97. package/scripts/proxy/proxy-up.sh +6 -0
  98. package/scripts/proxy/proxy-verify.mjs +4 -0
  99. package/scripts/public-inputs.mjs +409 -0
  100. package/scripts/public-verification-inputs.mjs +112 -26
  101. package/scripts/public-verification-lane.mjs +1 -1
  102. package/scripts/published-surface-probe.mjs +34 -4
  103. package/scripts/qe/card-lane-gate.mjs +16 -1
  104. package/scripts/qe/session-start-gate.mjs +16 -1
  105. package/scripts/rebuild-gists-from-receipts.mjs +58 -78
  106. package/scripts/record-lesson.mjs +4 -1
  107. package/scripts/rehearse-corpus-pipeline.mjs +994 -0
  108. package/scripts/release-abort-stale.mjs +5 -1
  109. package/scripts/release-authority.mjs +104 -12
  110. package/scripts/release-channel-kind.mjs +86 -0
  111. package/scripts/release-convergence-watchdog.mjs +7 -2
  112. package/scripts/release-projection.mjs +177 -72
  113. package/scripts/release-transaction-provider.mjs +23 -6
  114. package/scripts/release.mjs +252 -17
  115. package/scripts/retrieval-canary.mjs +87 -0
  116. package/scripts/rvf-index-audit.mjs +573 -13
  117. package/scripts/rvf-wire.mjs +269 -0
  118. package/scripts/seal-gist-receipt.mjs +65 -0
  119. package/scripts/selfcheck.mjs +42 -21
  120. package/scripts/source-coverage.mjs +253 -24
  121. package/scripts/status-honesty.mjs +25 -0
  122. package/scripts/sync-census.mjs +0 -0
  123. package/scripts/sync-version.mjs +2 -0
  124. package/scripts/trismart.mjs +42 -0
  125. package/scripts/updater-manifest.mjs +162 -0
  126. package/scripts/verify-channels.mjs +17 -5
  127. package/scripts/wired-check.mjs +48 -10
  128. package/tri-smart-skill/QUICKSTART.md +37 -0
  129. package/tri-smart-skill/README.md +92 -0
  130. package/tri-smart-skill/install.cmd +14 -0
  131. package/tri-smart-skill/install.command +13 -0
  132. package/tri-smart-skill/install.mjs +51 -0
  133. package/tri-smart-skill/install.sh +9 -0
  134. package/tri-smart-skill/tri-smart/SKILL.md +90 -0
  135. package/tri-smart-skill/tri-smart/evals/evals.json +25 -0
  136. package/tri-smart-skill/tri-smart/references/protocol.md +25 -0
  137. package/tri-smart-skill/tri-smart/references/provider-cli.md +18 -0
  138. package/tri-smart-skill/tri-smart/scripts/review.mjs +154 -0
  139. package/tri-smart-skill/tri-smart/scripts/setup.mjs +97 -0
  140. package/tri-smart-skill/tri-smart/scripts/verify-access.mjs +107 -0
  141. 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
- if (reg.latest && rel.tag) {
221
- const githubVersion = String(rel.tag).replace(/^v/, '');
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 ${rel.tag}`);
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 ${rel.tag} — published surfaces identify different Brain generations`);
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
- const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
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
- const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
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
- // Deterministically reconstruct gist passages from durable, exact-version source receipts.
3
- // This intentionally does not call the GitHub API, build vectors, or publish artifacts.
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 { sealGistReceiptSet, validateGistReceiptSet } from './gist-receipts.mjs';
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
- const observation = {
39
- owner: source.owner,
40
- observedAt: source.observedAt,
41
- observationSha256: source.sourceObservationSha256,
42
- gists: { rows: Object.entries(source.gists || {}).map(([id, receipt]) => ({
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
- body = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
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
- let id = 0;
160
+ const capturedGists = {};
168
161
  for (const [gistId, receipt] of Object.entries(source.gists)) {
169
- for (const file of receipt.files) {
170
- if (!file.included) continue;
171
- const body = bodies[bodyIndex++];
172
- const chunks = paragraphChunks(body);
173
- const title = file.filename.replace(/\s+/g, ' ').trim().slice(0, 180) || file.filename;
174
- const banner = provenanceBanner({ owner: source.owner, gistId, filename: file.filename, updatedAt: receipt.updatedAt });
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 passageBody = passages.map(jsonLine).join('\n') + '\n';
185
- const meta = {
186
- model: NAME,
187
- dimensions: 0,
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 (path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) {
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}`);