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
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ // private-overlay.mjs — stamp pre-built PRIVATE stores into a live brain root so the product's own
3
+ // updater preserves them.
4
+ //
5
+ // node scripts/private-overlay.mjs --root <kbDir> --from <sidecarDir> --store <name> [--store ...]
6
+ // [--dry-run] [--force] [--alias <name>=<nick,nick>] [--card <name>=<file>]
7
+ //
8
+ // WHY THIS FILE EXISTS (measured 2026-09-11/12). kb/forge-update.mjs preserves exactly one thing
9
+ // across a public bundle apply: SOURCE.json store entries flagged `updateManaged:false`
10
+ // (capturePrivateOverlayState, kb/forge-update.mjs:356-358) — the capture then needs a matching
11
+ // RVF-GENERATIONS.json row with a `file` (:369-372) and picks up `## <name>` cards and alias rows
12
+ // (:402-408). restoreTreeExact (:570-591) deletes every name the bundle lacks. Nothing anywhere
13
+ // WROTE that flag: forge-refresh's writeSourceManifest records public repos only, and
14
+ // ingest-repo.mjs builds from a git checkout with a canonical URL. So private stores that arrive as
15
+ // pre-built sidecars had no writer, sat in the root unflagged, and 0 of 8 survived the 2026-09-10
16
+ // tree replacement. This is that writer. It ADDS entries and never rebuilds top-level identity:
17
+ // the live ledger's brainVersion/releaseTag/sourceSnapshot are checked against COVERAGE.json by
18
+ // plugin/scripts/coverage-integrity.mjs, so writeRvfGeneration (which restamps the repo's version)
19
+ // is deliberately not reused here — only sha256File is.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import { pathToFileURL } from 'node:url';
23
+ import { sha256File } from './rvf-generation.mjs';
24
+
25
+ export const REQUIRED_SIDECARS = ['.big.rvf', '.big.rvf.embed.json', '.big.rvf.idmap.json', '.meta.json', '.passages.jsonl'];
26
+ export const OPTIONAL_SIDECARS = ['.symbols.json', '-primer.md'];
27
+ export const RECEIPT_FILE = 'private-overlay-receipt.json';
28
+ const NAME_RE = /^[a-z0-9][a-z0-9_-]*$/i;
29
+ const REGISTRY_FILES = ['SOURCE.json', 'RVF-GENERATIONS.json', 'repo-aliases.json', 'capability-cards.md'];
30
+
31
+ const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
32
+ const jsonText = (value) => `${JSON.stringify(value, null, 2)}\n`;
33
+ const sameJson = (left, right) => JSON.stringify(left) === JSON.stringify(right);
34
+ const isRegular = (file) => { try { return fs.lstatSync(file).isFile(); } catch { return false; } };
35
+ function writeAtomic(file, content) {
36
+ const temp = `${file}.tmp-${process.pid}`;
37
+ fs.writeFileSync(temp, content);
38
+ fs.renameSync(temp, file);
39
+ }
40
+ function copyAtomic(from, to) {
41
+ const temp = `${to}.tmp-${process.pid}`;
42
+ fs.copyFileSync(from, temp);
43
+ fs.renameSync(temp, to);
44
+ }
45
+
46
+ /** `## name` → full section text, same parse as kb/forge-update.mjs cardSections. */
47
+ function cardSections(markdown) {
48
+ const sections = new Map();
49
+ const matches = [...String(markdown || '').matchAll(/^## ([^\n]+)\n/gm)];
50
+ for (let index = 0; index < matches.length; index++) {
51
+ const end = matches[index + 1]?.index ?? markdown.length;
52
+ sections.set(matches[index][1].trim(), markdown.slice(matches[index].index, end).trimEnd());
53
+ }
54
+ return sections;
55
+ }
56
+
57
+ /** First prose section of a generated primer, flattened, so the card is grounded in the store's own text. */
58
+ function primerSummary(text) {
59
+ const section = String(text).split(/^## /m)[1];
60
+ if (!section) return '';
61
+ const plain = section.split('\n').slice(1).join('\n')
62
+ .replace(/<!--[\s\S]*?-->/g, '').replace(/^\s*[-*]\s+/gm, '').replace(/[*_`#>]/g, '').replace(/\s+/g, ' ').trim();
63
+ if (plain.length <= 600) return plain;
64
+ return `${plain.slice(0, plain.lastIndexOf(' ', 600))} …`;
65
+ }
66
+
67
+ /** Facts the store's own meta.json states — used only when no primer exists; never invented prose. */
68
+ function metaFacts(meta) {
69
+ const parts = [];
70
+ if (typeof meta.description === 'string' && meta.description.trim()) parts.push(meta.description.trim());
71
+ const census = meta.census || meta.corpusCounts;
72
+ if (census && typeof census === 'object' && !Array.isArray(census)) {
73
+ parts.push(`corpus census: ${Object.entries(census).map(([kind, count]) => `${kind} ${count}`).join(', ')}`);
74
+ }
75
+ if (Array.isArray(meta.meetings) && meta.meetings.length) parts.push(`meetings: ${meta.meetings.join('; ')}`);
76
+ if (Array.isArray(meta.sources) && meta.sources.length) parts.push(`sources: ${meta.sources.join(', ')}`);
77
+ if (Number.isInteger(meta.passages)) parts.push(`${meta.passages} passages`);
78
+ return parts.length ? `Private store (not in any public bundle). ${parts.join('. ')}.` : '';
79
+ }
80
+
81
+ function cardFor({ from, name, meta, cardFile }) {
82
+ if (cardFile) return { origin: `file:${cardFile}`, text: `## ${name}\n${fs.readFileSync(cardFile, 'utf8').trim()}` };
83
+ const primer = path.join(from, `${name}-primer.md`);
84
+ if (isRegular(primer)) {
85
+ const body = primerSummary(fs.readFileSync(primer, 'utf8'));
86
+ if (body) {
87
+ return { origin: 'primer', text: `## ${name}\n${body}\n(Auto-derived from the store's own primer; a hand-written card saying when to reach for it, and when not to, would be better.)` };
88
+ }
89
+ }
90
+ const facts = metaFacts(meta);
91
+ if (facts) {
92
+ return { origin: 'meta.json', text: `## ${name}\n${facts}\n(Auto-derived from the store's own meta.json — the sidecar set has no primer; a hand-written card saying when to reach for it would be better.)` };
93
+ }
94
+ throw new Error(`${name}: no card source — add ${name}-primer.md to ${from}, or pass --card ${name}=<file>`);
95
+ }
96
+
97
+ function planStore({ root, from, name, force, aliases, cardFile }) {
98
+ if (!NAME_RE.test(name)) throw new Error(`invalid store name: ${name}`);
99
+ const missing = REQUIRED_SIDECARS.filter((suffix) => !isRegular(path.join(from, name + suffix)));
100
+ if (missing.length) throw new Error(`${name}: missing sidecar(s) in ${from}: ${missing.map((suffix) => name + suffix).join(', ')}`);
101
+ const files = [...REQUIRED_SIDECARS, ...OPTIONAL_SIDECARS].map((suffix) => name + suffix)
102
+ .filter((file) => isRegular(path.join(from, file)));
103
+ const embed = readJson(path.join(from, `${name}.big.rvf.embed.json`));
104
+ if (typeof embed.model !== 'string' || !embed.model.trim() || !Number.isInteger(embed.dimensions) || embed.dimensions <= 0) {
105
+ throw new Error(`${name}: ${name}.big.rvf.embed.json lacks a model string / integer dimensions`);
106
+ }
107
+ let meta;
108
+ try { meta = readJson(path.join(from, `${name}.meta.json`)); } catch (error) { throw new Error(`${name}: ${name}.meta.json is unreadable: ${error.message}`); }
109
+ if (!meta || typeof meta !== 'object') meta = {};
110
+ const builtUtc = [meta.builtUtc, meta.generated, embed.generated]
111
+ .find((value) => typeof value === 'string' && Number.isFinite(Date.parse(value))) || new Date().toISOString();
112
+ const copies = [];
113
+ for (const file of files) {
114
+ const source = path.join(from, file);
115
+ const target = path.join(root, file);
116
+ if (!fs.existsSync(target)) { copies.push(file); continue; }
117
+ if (fs.lstatSync(target).isSymbolicLink() || !fs.lstatSync(target).isFile()) throw new Error(`${name}: ${target} is not a regular file — refusing to write through it`);
118
+ if (sha256File(target) === sha256File(source)) continue;
119
+ if (!force) throw new Error(`${name}: ${target} already exists with different bytes — pass --force to overwrite it`);
120
+ copies.push(file);
121
+ }
122
+ const rvf = path.join(from, `${name}.big.rvf`);
123
+ const card = cardFor({ from, name, meta, cardFile });
124
+ return {
125
+ name, files, copies, card, aliases,
126
+ generation: { file: `${name}.big.rvf`, sha256: sha256File(rvf), bytes: fs.statSync(rvf).size, model: embed.model, dimensions: embed.dimensions, sourceCommit: null, builtUtc },
127
+ source: { kbName: name, updateManaged: false, builtUtc, sourceCommit: null, sourceRepo: 'private', canonicalManifestUrl: null },
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Add private stores to a live brain root. Idempotent; refuses before writing anything; snapshots
133
+ * every registry it is about to change to `<file>.pre-overlay-<epoch>` first; temp+rename writes.
134
+ */
135
+ export function applyPrivateOverlay({ root, from, stores, dryRun = false, force = false, aliases = {}, cards = {}, now = Date.now }) {
136
+ root = path.resolve(String(root || ''));
137
+ from = path.resolve(String(from || ''));
138
+ if (!Array.isArray(stores) || !stores.length) throw new Error('at least one --store <name> is required');
139
+ if (!isRegular(path.join(root, 'SOURCE.json'))) throw new Error(`${root} is not a brain root (no SOURCE.json)`);
140
+ if (!isRegular(path.join(root, 'RVF-GENERATIONS.json'))) throw new Error(`${root} has no RVF-GENERATIONS.json — refusing to invent a ledger identity`);
141
+ if (!fs.existsSync(from) || !fs.statSync(from).isDirectory()) throw new Error(`--from ${from} is not a directory`);
142
+ const source = readJson(path.join(root, 'SOURCE.json'));
143
+ if (!source.stores || typeof source.stores !== 'object' || Array.isArray(source.stores)) throw new Error('SOURCE.json stores must be an object keyed by store name');
144
+ const ledger = readJson(path.join(root, 'RVF-GENERATIONS.json'));
145
+ if (!ledger.stores || typeof ledger.stores !== 'object' || Array.isArray(ledger.stores)) throw new Error('RVF-GENERATIONS.json stores must be an object keyed by store name');
146
+ const fenceFile = path.join(root, 'PRIVATE-STORES.json');
147
+ const fence = new Set((isRegular(fenceFile) ? readJson(fenceFile).privateStores || [] : []).map((name) => String(name).toLowerCase()));
148
+ const aliasFile = path.join(root, 'repo-aliases.json');
149
+ const currentAliases = isRegular(aliasFile) ? readJson(aliasFile) : {};
150
+ const cardsFile = path.join(root, 'capability-cards.md');
151
+ const currentCards = isRegular(cardsFile) ? fs.readFileSync(cardsFile, 'utf8') : '';
152
+
153
+ const nextSource = { ...source, stores: { ...source.stores } };
154
+ const nextLedger = { ...ledger, stores: { ...ledger.stores } };
155
+ const nextAliases = { ...currentAliases };
156
+ let nextCards = currentCards;
157
+ const receipt = {
158
+ schemaVersion: 1, kind: 'ruvnet-brain-private-overlay-receipt', at: new Date(now()).toISOString(),
159
+ root, from, dryRun, force, stores: [],
160
+ };
161
+ const plans = stores.map((name) => planStore({ root, from, name, force, aliases: aliases[name], cardFile: cards[name] }));
162
+ for (const plan of plans) {
163
+ const { name } = plan;
164
+ if (!fence.has(name.toLowerCase())) {
165
+ throw new Error(`${name}: not listed in ${fenceFile} — the coverage validator would count it as an unclassified PUBLIC store; add it to PRIVATE-STORES.json first`);
166
+ }
167
+ const existing = source.stores[name];
168
+ if (existing && existing.updateManaged !== false) throw new Error(`${name}: SOURCE.json already lists this name as an update-managed (public) store — refusing to shadow it`);
169
+ const changes = [];
170
+ if (!sameJson(existing, plan.source)) { nextSource.stores[name] = plan.source; changes.push('SOURCE.json'); }
171
+ if (!sameJson(ledger.stores[name], plan.generation)) { nextLedger.stores[name] = plan.generation; changes.push('RVF-GENERATIONS.json'); }
172
+ if (Array.isArray(plan.aliases) && plan.aliases.length && !sameJson(currentAliases[name], plan.aliases)) {
173
+ nextAliases[name] = plan.aliases; changes.push('repo-aliases.json');
174
+ }
175
+ if (!cardSections(nextCards).has(name)) {
176
+ nextCards = `${nextCards.trimEnd()}${nextCards.trim() ? '\n\n' : ''}${plan.card.text}\n`;
177
+ changes.push('capability-cards.md');
178
+ }
179
+ receipt.stores.push({
180
+ name, changes, copied: plan.copies, card: cardSections(currentCards).has(name) ? 'existing' : plan.card.origin,
181
+ sha256: plan.generation.sha256, bytes: plan.generation.bytes, model: plan.generation.model,
182
+ dimensions: plan.generation.dimensions, builtUtc: plan.generation.builtUtc,
183
+ });
184
+ }
185
+ if (dryRun) return receipt;
186
+
187
+ const changed = new Set(receipt.stores.flatMap((entry) => entry.changes));
188
+ const epoch = now();
189
+ receipt.snapshots = [];
190
+ // Snapshot ONLY registries this run rewrites, before the first write. SOURCE.json and
191
+ // RVF-GENERATIONS.json are always paired: the flag and the row are one fact in two files.
192
+ const snapshotTargets = changed.size ? new Set([...changed, 'SOURCE.json', 'RVF-GENERATIONS.json']) : new Set();
193
+ for (const file of REGISTRY_FILES) {
194
+ if (!snapshotTargets.has(file) || !isRegular(path.join(root, file))) continue;
195
+ const snapshot = path.join(root, `${file}.pre-overlay-${epoch}`);
196
+ if (fs.existsSync(snapshot)) throw new Error(`snapshot already exists: ${snapshot}`);
197
+ fs.copyFileSync(path.join(root, file), snapshot);
198
+ receipt.snapshots.push(path.basename(snapshot));
199
+ }
200
+ // Bytes first, then the ledger row, then aliases/cards, then the SOURCE flag LAST: a crash before
201
+ // the flag leaves extra files the coverage validator already tolerates (private fence), never a
202
+ // flag that points at a missing artifact.
203
+ for (const plan of plans) for (const file of plan.copies) copyAtomic(path.join(from, file), path.join(root, file));
204
+ if (changed.has('RVF-GENERATIONS.json')) writeAtomic(path.join(root, 'RVF-GENERATIONS.json'), jsonText(nextLedger));
205
+ if (changed.has('repo-aliases.json')) writeAtomic(aliasFile, jsonText(nextAliases));
206
+ if (changed.has('capability-cards.md')) writeAtomic(cardsFile, nextCards);
207
+ if (changed.has('SOURCE.json')) writeAtomic(path.join(root, 'SOURCE.json'), jsonText(nextSource));
208
+ writeAtomic(path.join(root, RECEIPT_FILE), jsonText(receipt));
209
+ return receipt;
210
+ }
211
+
212
+ function parseArgs(argv) {
213
+ const options = { stores: [], aliases: {}, cards: {}, dryRun: false, force: false };
214
+ const pair = (value, flag) => {
215
+ const index = String(value).indexOf('=');
216
+ if (index <= 0) throw new Error(`${flag} expects <name>=<value>`);
217
+ return [value.slice(0, index), value.slice(index + 1)];
218
+ };
219
+ for (let index = 0; index < argv.length; index++) {
220
+ const flag = argv[index];
221
+ const next = () => { if (argv[index + 1] == null) throw new Error(`${flag} needs a value`); return argv[++index]; };
222
+ if (flag === '--root') options.root = next();
223
+ else if (flag === '--from') options.from = next();
224
+ else if (flag === '--store') options.stores.push(next());
225
+ else if (flag === '--dry-run') options.dryRun = true;
226
+ else if (flag === '--force') options.force = true;
227
+ else if (flag === '--alias') { const [name, list] = pair(next(), flag); options.aliases[name] = list.split(',').map((s) => s.trim()).filter(Boolean); }
228
+ else if (flag === '--card') { const [name, file] = pair(next(), flag); options.cards[name] = path.resolve(file); }
229
+ else throw new Error(`unknown argument: ${flag}`);
230
+ }
231
+ if (!options.root || !options.from || !options.stores.length) {
232
+ throw new Error('usage: private-overlay.mjs --root <kbDir> --from <sidecarDir> --store <name> [--store ...] [--dry-run] [--force] [--alias name=a,b] [--card name=file]');
233
+ }
234
+ return options;
235
+ }
236
+
237
+ function main() {
238
+ let receipt;
239
+ try {
240
+ receipt = applyPrivateOverlay(parseArgs(process.argv.slice(2)));
241
+ } catch (error) {
242
+ console.error(`[private-overlay] REFUSED — nothing written: ${error.message}`);
243
+ process.exit(1);
244
+ }
245
+ console.log(JSON.stringify(receipt, null, 2));
246
+ }
247
+
248
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) main();
@@ -173,4 +173,4 @@ export function runProductIntegrityCli(argv = process.argv.slice(2), io = { stdo
173
173
  if (verify && sourceSha) { const trace = JSON.parse(fs.readFileSync(path.resolve(verify), 'utf8')); validateProductIntegrityTrace(trace, { root, sourceSha }); io.stdout.write(`${canonicalJson({ verdict: 'PASS', traceSha256: trace.traceSha256 })}\n`); return 0; }
174
174
  throw new Error('invalid product integrity argument combination');
175
175
  }
176
- if (path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) { try { process.exitCode = runProductIntegrityCli(); } catch (error) { console.error(`[product-integrity-contract] ${error.message}`); process.exitCode = 1; } }
176
+ if (((() => { try { return process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } })())) { try { process.exitCode = runProductIntegrityCli(); } catch (error) { console.error(`[product-integrity-contract] ${error.message}`); process.exitCode = 1; } }
@@ -21,6 +21,12 @@
21
21
  # assumed.
22
22
  set -euo pipefail
23
23
 
24
+ # Every `ruflo` invocation auto-starts a project background daemon unless this is set (verified
25
+ # live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/services/
26
+ # daemon-autostart.js:85). This script only checks/starts the proxy — it has no business leaving a
27
+ # swarm daemon running as a side effect.
28
+ export RUFLO_DAEMON_AUTOSTART=0
29
+
24
30
  TOKEN_FILE="$HOME/.ruflo/proxy-token"
25
31
 
26
32
  if ! ruflo proxy status --json 2>/dev/null | grep -q '"running":true'; then
@@ -9,6 +9,11 @@
9
9
  # proxy-verify.mjs check 3), so there is nothing to undo.
10
10
  set -uo pipefail
11
11
 
12
+ # Every `ruflo` invocation auto-starts a project background daemon unless this is set (verified
13
+ # live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/services/
14
+ # daemon-autostart.js:85). A teardown script has no business leaving one running.
15
+ export RUFLO_DAEMON_AUTOSTART=0
16
+
12
17
  echo "Reverting the Meta LLM Proxy trial..."
13
18
  echo
14
19
 
@@ -6,6 +6,12 @@
6
6
  # It adds no logic of its own except the macOS workaround documented below.
7
7
  set -uo pipefail
8
8
 
9
+ # Every `ruflo` invocation auto-starts a project background daemon unless this is set (verified
10
+ # live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/services/
11
+ # daemon-autostart.js:85). Installing/starting the proxy has no business leaving a swarm daemon
12
+ # running as a side effect.
13
+ export RUFLO_DAEMON_AUTOSTART=0
14
+
9
15
  REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
10
16
 
11
17
  # --- macOS workaround: upstream bug in @claude-flow/security's PathValidator --
@@ -57,6 +57,10 @@ try {
57
57
  doctorOut = execFileSync('ruflo', ['doctor', '--component', 'proxy'], {
58
58
  encoding: 'utf8',
59
59
  timeout: 60_000,
60
+ // Every `ruflo` invocation auto-starts a project background daemon unless this is set
61
+ // (verified live: ~/.npm-global/lib/node_modules/ruflo/node_modules/@claude-flow/cli/dist/src/
62
+ // services/daemon-autostart.js:85) — a health-check probe must not leave one running.
63
+ env: { ...process.env, RUFLO_DAEMON_AUTOSTART: '0' },
60
64
  });
61
65
  doctorOk = /Summary:.*passed/.test(doctorOut) && !/\b\d+ failed/.test(doctorOut);
62
66
  } catch (err) {