ruvnet-brain 4.3.27 → 4.3.29
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 +1 -1
- package/bin/install.mjs +74 -7
- package/console/app.js +7 -2
- package/kb/corpus-release-identity.mjs +73 -0
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/mcp/managed-cli-interface.mjs +83 -9
- package/plugin/scripts/advocacy-route.mjs +4 -1
- package/plugin/scripts/capacity-aware-parallel-work.mjs +4 -0
- package/plugin/scripts/continuation-gate.mjs +71 -0
- package/plugin/scripts/decision-gate.mjs +29 -57
- package/plugin/scripts/ground-ruvnet.sh +77 -4
- package/plugin/scripts/grounding-stamp.sh +34 -5
- package/plugin/scripts/grounding-turn-gate.mjs +8 -2
- package/plugin/scripts/grounding-turn-mark.mjs +6 -1
- package/plugin/scripts/hook-input.mjs +54 -0
- package/plugin/scripts/hook-shim.mjs +10 -0
- package/plugin/scripts/memory-doctor.mjs +43 -0
- package/plugin/scripts/nightly-controller.mjs +6 -1
- package/plugin/scripts/project-progression-contract.mjs +17 -0
- package/plugin/scripts/project-progression-hook.mjs +18 -5
- package/plugin/scripts/project-progression-producer.mjs +16 -12
- package/plugin/scripts/ruvnet-gate1-pattern.mjs +17 -0
- package/plugin/scripts/session-start-core.mjs +11 -5
- package/plugin/scripts/session-start-update-plane.mjs +5 -1
- package/plugin/scripts/unprompted-runtime.mjs +29 -8
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +1 -1
- package/scripts/adr-072-completion.mjs +2 -1
- package/scripts/calibrate-router.mjs +6 -6
- package/scripts/candidate-host-evidence.mjs +44 -16
- package/scripts/corpus-reconcile.mjs +32 -3
- package/scripts/correction-detect.mjs +10 -11
- package/scripts/dispatch-receipt.mjs +2 -2
- package/scripts/dual-host-deliberation.mjs +3 -2
- package/scripts/execution-policy.mjs +10 -2
- package/scripts/gen-console-images.mjs +0 -1
- package/scripts/gen-images.mjs +0 -4
- package/scripts/host-install-matrix.mjs +60 -14
- package/scripts/independent-review-receipt.mjs +7 -8
- package/scripts/ingest-repo.mjs +45 -1
- package/scripts/learning-replay-cli.mjs +2 -1
- package/scripts/learning-replay-fixture.mjs +2 -1
- package/scripts/learnings.mjs +19 -5
- package/scripts/lesson-migrate-agentdb.mjs +635 -0
- package/scripts/loop-checkpoint.mjs +37 -1
- package/scripts/metaharness-receipts.mjs +3 -2
- package/scripts/nightly-two-run-proof.mjs +6 -6
- package/scripts/nightly-watchdog.mjs +10 -2
- package/scripts/onboarding-console.mjs +98 -49
- package/scripts/oracle/producer-hosts.mjs +2 -1
- package/scripts/prepublication-evidence.mjs +8 -0
- package/scripts/private-overlay.mjs +31 -9
- package/scripts/public-verification-aggregate.mjs +4 -3
- package/scripts/publication-receipt.mjs +80 -30
- package/scripts/qe/agentic-qe-4.3.mjs +0 -1
- package/scripts/qe/ux-suite.mjs +17 -5
- package/scripts/rebuild-gists-from-receipts.mjs +1 -1
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/release-qualification-contract.mjs +11 -1
- package/scripts/release.mjs +34 -82
- package/scripts/retrieval-canary.mjs +3 -2
- package/scripts/review-model-defaults.mjs +26 -0
- package/scripts/route-cheap.mjs +20 -7
- package/scripts/router-utilization.mjs +5 -5
- package/scripts/rvf-generation.mjs +68 -2
- package/scripts/single-source-check.mjs +270 -0
- package/scripts/staged-host-verifier.mjs +2 -1
- package/scripts/subscription-hosts.mjs +4 -0
- package/scripts/sync-version.mjs +22 -0
- package/scripts/trismart.mjs +3 -3
- package/scripts/wired-check.mjs +40 -4
- package/console/assets/memory.webp +0 -0
- package/plugin/scripts/version-bump-gate.sh +0 -124
- package/scripts/qe/aggregate-4.3.mjs +0 -42
- package/scripts/release-convergence-watchdog.mjs +0 -112
- package/scripts/stamp-existing-rvf-generations.mjs +0 -53
- package/scripts/verify-channels.mjs +0 -196
|
@@ -32,6 +32,14 @@ import { runRetrievalCanaries, validateRetrievalCanaryReceipt, resolveInstalledC
|
|
|
32
32
|
|
|
33
33
|
/** The three host shapes a release must survive. ONE name each, for every consumer. */
|
|
34
34
|
export const HOST_MODES = Object.freeze(['claude', 'codex', 'dual']);
|
|
35
|
+
export const SELF_STORE_PROOF_QUERY = 'repo:ruvnet-brain What is the RuvNet Brain release evidence workflow?';
|
|
36
|
+
export const SELF_STORE_PROOF_K = 1;
|
|
37
|
+
export const RELEASE_SEARCH_QUERY = 'repo:ruvnet-brain How does RuvNet Brain prove a public release artifact?';
|
|
38
|
+
export const HOST_WARMUP_TIMEOUT_MS = 300_000;
|
|
39
|
+
export const RELEASE_SEARCH_DEADLINE_MS = 30_000;
|
|
40
|
+
// The shared model-cache prewarm's own candidate pool size, named so the test asserting on it
|
|
41
|
+
// derives from this constant instead of restating the digit as a second, driftable literal.
|
|
42
|
+
export const PREWARM_POOL_SIZE = 8;
|
|
35
43
|
|
|
36
44
|
/** Which CLIs each mode is allowed to see. A codex-only box genuinely has no `claude`. */
|
|
37
45
|
export const MODE_HOSTS = Object.freeze({
|
|
@@ -204,6 +212,7 @@ export async function runHostMatrixAsync({
|
|
|
204
212
|
verifyGrounding = verifyInstalledGrounding,
|
|
205
213
|
resolveMcpServer = resolveInstalledMcpServer,
|
|
206
214
|
retrieval,
|
|
215
|
+
sequentialSearches = false,
|
|
207
216
|
}) {
|
|
208
217
|
const spec = VARIANTS[variant];
|
|
209
218
|
if (!spec) throw new Error(`unknown host-matrix variant: ${variant}`);
|
|
@@ -251,54 +260,91 @@ export async function runHostMatrixAsync({
|
|
|
251
260
|
|
|
252
261
|
const prewarmContext = contexts[0];
|
|
253
262
|
const prewarmReader = path.join(prewarmContext.env.RUVNET_BRAIN_KB, 'forge-ask-all.mjs');
|
|
263
|
+
// This query only primes the shared model cache. Keep it in the Brain's own bounded store so
|
|
264
|
+
// Mac/Windows/Linux runner speed does not determine whether the later real host searches run.
|
|
265
|
+
// The following MCP matrix remains the measured, source-grounded candidate acceptance.
|
|
254
266
|
const prewarm = await runCommand(process.execPath, [prewarmReader, '--dir', prewarmContext.env.RUVNET_BRAIN_KB,
|
|
255
|
-
'--q',
|
|
267
|
+
'--q', RELEASE_SEARCH_QUERY, '--k', '1', '--pool', String(PREWARM_POOL_SIZE),
|
|
268
|
+
'--repos', 'ruvnet-brain', '--bounded'], {
|
|
256
269
|
cwd: prewarmContext.env.RUVNET_BRAIN_KB, env: prewarmContext.env, timeout: 300_000,
|
|
257
270
|
});
|
|
258
271
|
if (prewarm.error || prewarm.status !== 0) {
|
|
259
|
-
|
|
272
|
+
const detail = String(prewarm.stderr || prewarm.stdout || '').trim().split('\n').slice(-8).join(' | ').slice(-1600);
|
|
273
|
+
return { verdict: 'FAIL', fixtures: {}, error: `shared model prewarm failed (${processDiagnostic(prewarm)})${detail ? `; ${detail}` : ''}` };
|
|
260
274
|
}
|
|
261
275
|
const prewarmGrounding = await verifyGrounding(String(prewarm.stdout || ''), prewarmContext.env.RUVNET_BRAIN_KB);
|
|
262
276
|
if (!prewarmGrounding?.grounded) {
|
|
263
277
|
return { verdict: 'FAIL', fixtures: {}, error: 'shared model prewarm returned no grounded source receipt' };
|
|
264
278
|
}
|
|
265
279
|
|
|
266
|
-
const
|
|
280
|
+
const searchOne = async (context) => {
|
|
267
281
|
let session;
|
|
268
282
|
try {
|
|
269
283
|
const serverPath = resolveMcpServer(context);
|
|
270
284
|
// One installed worker per host: model/store state survives smoke and every sealed case.
|
|
271
285
|
session = runMcpSearch === runInstalledMcpSearch ? createInstalledMcpSession({ serverPath, env: context.env }) : null;
|
|
272
286
|
const searchMcp = session ? (args) => session.search(args) : runMcpSearch;
|
|
273
|
-
|
|
287
|
+
// The installed shell warms its in-process models and stores asynchronously from MCP
|
|
288
|
+
// initialize. Candidate qualification must give that same worker the same cited readiness
|
|
289
|
+
// probe used by the public verifier before timing the first user search; a separate CLI
|
|
290
|
+
// prewarm only primes disk cache and cannot warm this worker's process memory.
|
|
291
|
+
const warmupResult = await searchMcp({ mode: context.mode, serverPath, env: context.env,
|
|
292
|
+
query: SELF_STORE_PROOF_QUERY, k: SELF_STORE_PROOF_K, timeoutMs: HOST_WARMUP_TIMEOUT_MS });
|
|
293
|
+
const warmupOutput = `${warmupResult.stdout || ''}${warmupResult.stderr || ''}`;
|
|
294
|
+
if (warmupResult.error || warmupResult.status !== 0) {
|
|
295
|
+
return { context, processResult: warmupResult,
|
|
296
|
+
error: `MCP warmup failed for ${context.mode} (${processDiagnostic(warmupResult)}): ${warmupOutput.slice(-2000)}` };
|
|
297
|
+
}
|
|
298
|
+
const warmupGrounding = await verifyGrounding(warmupOutput, context.env.RUVNET_BRAIN_KB);
|
|
299
|
+
if (!warmupGrounding?.grounded || warmupGrounding.receipt?.repo !== 'ruvnet-brain') {
|
|
300
|
+
return { context, processResult: warmupResult, warmupMs: warmupResult.broadMs,
|
|
301
|
+
error: `MCP self-store warmup grounding unproven for ${context.mode}` };
|
|
302
|
+
}
|
|
303
|
+
const processResult = await searchMcp({ mode: context.mode, serverPath, env: context.env,
|
|
304
|
+
query: RELEASE_SEARCH_QUERY, k: 5,
|
|
305
|
+
timeoutMs: RELEASE_SEARCH_DEADLINE_MS });
|
|
274
306
|
const output = `${processResult.stdout || ''}${processResult.stderr || ''}`;
|
|
275
307
|
if (processResult.error || processResult.status !== 0) {
|
|
276
|
-
return { context, processResult,
|
|
308
|
+
return { context, processResult, warmupMs: warmupResult.broadMs, warmupGrounding,
|
|
309
|
+
error: `MCP search failed for ${context.mode} (${processDiagnostic(processResult)}): ${output.slice(-2000)}` };
|
|
277
310
|
}
|
|
278
311
|
const grounding = await verifyGrounding(output, context.env.RUVNET_BRAIN_KB);
|
|
279
|
-
if (!grounding?.grounded) return { context, processResult,
|
|
312
|
+
if (!grounding?.grounded) return { context, processResult, warmupMs: warmupResult.broadMs,
|
|
313
|
+
warmupGrounding, error: `MCP search grounding unproven for ${context.mode}` };
|
|
280
314
|
let receipt;
|
|
281
315
|
if (retrieval) {
|
|
282
|
-
receipt = await runRetrievalCanaries({ ...retrieval,
|
|
283
|
-
search: async ({ query, k }) => {
|
|
284
|
-
const result = await searchMcp({ mode: context.mode, serverPath, env: context.env, query, k });
|
|
316
|
+
receipt = await runRetrievalCanaries({ ...retrieval, searchTimeoutMs: RELEASE_SEARCH_DEADLINE_MS,
|
|
317
|
+
search: async ({ query, k, timeoutMs }) => {
|
|
318
|
+
const result = await searchMcp({ mode: context.mode, serverPath, env: context.env, query, k, timeoutMs });
|
|
285
319
|
if (result.error || result.status !== 0) throw new Error(`canary MCP search failed: ${processDiagnostic(result)}`);
|
|
286
320
|
return parseRetrievalResult(result.mcpResult, { query, k });
|
|
287
321
|
},
|
|
288
322
|
citationResolver: (matched, expected) => resolveInstalledCanaryCitation({ kbDir: context.env.RUVNET_BRAIN_KB, matched, expected }),
|
|
289
323
|
});
|
|
290
324
|
try { validateRetrievalCanaryReceipt(receipt, { plan: retrieval.plan }); }
|
|
291
|
-
catch (error) { return { context, processResult,
|
|
325
|
+
catch (error) { return { context, processResult, warmupMs: warmupResult.broadMs,
|
|
326
|
+
warmupGrounding, grounding, retrieval: receipt, error: `${context.mode} canary rejected: ${error.message}` }; }
|
|
292
327
|
}
|
|
293
|
-
return { context, processResult,
|
|
328
|
+
return { context, processResult, warmupMs: warmupResult.broadMs, warmupGrounding,
|
|
329
|
+
grounding, ...(receipt ? { retrieval: receipt } : {}) };
|
|
294
330
|
} catch (error) {
|
|
295
331
|
return { context, processResult: { status: null, error }, error: `${context.mode} host search failed: ${error.message}` };
|
|
296
332
|
} finally { await session?.close(); }
|
|
297
|
-
}
|
|
298
|
-
const
|
|
333
|
+
};
|
|
334
|
+
const searches = [];
|
|
335
|
+
if (sequentialSearches) {
|
|
336
|
+
for (const context of contexts) searches.push(await searchOne(context));
|
|
337
|
+
} else {
|
|
338
|
+
searches.push(...await Promise.all(contexts.map(searchOne)));
|
|
339
|
+
}
|
|
340
|
+
const fixtures = Object.fromEntries(searches.map(({ context, processResult, warmupMs, warmupGrounding,
|
|
341
|
+
grounding, retrieval, error }) => [context.mode, {
|
|
299
342
|
status: error ? 'FAIL' : 'PASS',
|
|
300
343
|
version,
|
|
301
344
|
process: processIdentity(processResult),
|
|
345
|
+
warmupMs: Number.isFinite(warmupMs) ? warmupMs : null,
|
|
346
|
+
...(warmupGrounding?.receipt ? { warmupGrounding: warmupGrounding.receipt } : {}),
|
|
347
|
+
searchMs: Number.isFinite(processResult.broadMs) ? processResult.broadMs : null,
|
|
302
348
|
...(grounding?.receipt ? { grounding: grounding.receipt } : {}),
|
|
303
349
|
...(retrieval ? { retrieval } : {}),
|
|
304
350
|
...(error ? { error } : {}),
|
|
@@ -395,7 +441,7 @@ export function createInstalledMcpSession({ serverPath, env, timeout = 300_000,
|
|
|
395
441
|
pending.set(id, { resolve, reject });
|
|
396
442
|
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
|
397
443
|
});
|
|
398
|
-
const execute = async ({ query =
|
|
444
|
+
const execute = async ({ query = RELEASE_SEARCH_QUERY, k = 5,
|
|
399
445
|
timeoutMs = timeout } = {}) => {
|
|
400
446
|
if (terminalError) return { status: null, error: terminalError, signal: exitSignal, stdout: '', stderr };
|
|
401
447
|
stderr = '';
|
|
@@ -5,12 +5,11 @@ import path from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { canonicalJson, digest } from './coverage-integrity.mjs';
|
|
7
7
|
import { transactionIdFor } from './release-transaction.mjs';
|
|
8
|
+
import { CLAUDE_FABLE_5_ID, GPT_5_6_SOL_ID, LEGACY_REVIEWER_IDENTITIES } from './review-model-defaults.mjs';
|
|
8
9
|
const HEX40 = /^[a-f0-9]{40}$/;
|
|
9
10
|
const HEX64 = /^[a-f0-9]{64}$/;
|
|
10
11
|
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
11
|
-
export const ALLOWED_INDEPENDENT_REVIEWERS =
|
|
12
|
-
Object.freeze({ identity: 'claude-fable-5', model: 'claude-fable-5', provider: 'firstParty' }),
|
|
13
|
-
Object.freeze({ identity: 'gpt-5.6-sol', model: 'gpt-5.6-sol', provider: 'openai' })]);
|
|
12
|
+
export const ALLOWED_INDEPENDENT_REVIEWERS = LEGACY_REVIEWER_IDENTITIES;
|
|
14
13
|
|
|
15
14
|
const INPUT_KEYS = Object.freeze([
|
|
16
15
|
'artifactSha256', 'deductions', 'execution', 'findings', 'id', 'independent', 'model', 'payloadId',
|
|
@@ -222,17 +221,17 @@ function reviewerFor(input) {
|
|
|
222
221
|
}
|
|
223
222
|
|
|
224
223
|
function normalizeExecution(execution, reviewer) {
|
|
225
|
-
const required = reviewer.identity ===
|
|
224
|
+
const required = reviewer.identity === GPT_5_6_SOL_ID
|
|
226
225
|
? ['catalogRowSha256', 'invocationDigest', 'subscriptionAuthenticated', 'threadId']
|
|
227
226
|
: ['invocationDigest', 'subscriptionAuthenticated'];
|
|
228
|
-
if (reviewer.identity ===
|
|
227
|
+
if (reviewer.identity === GPT_5_6_SOL_ID
|
|
229
228
|
&& (!Object.hasOwn(execution || {}, 'threadId') || !Object.hasOwn(execution || {}, 'catalogRowSha256'))) {
|
|
230
229
|
throw new Error('GPT review thread and catalog evidence are required');
|
|
231
230
|
}
|
|
232
231
|
exactKeys(execution, required, 'review execution');
|
|
233
232
|
if (execution.subscriptionAuthenticated !== true) throw new Error('review execution is not subscription authenticated');
|
|
234
233
|
hex(execution.invocationDigest, HEX64, 'review invocation digest');
|
|
235
|
-
if (reviewer.identity ===
|
|
234
|
+
if (reviewer.identity === GPT_5_6_SOL_ID) {
|
|
236
235
|
text(execution.threadId, 'GPT review thread');
|
|
237
236
|
hex(execution.catalogRowSha256, HEX64, 'GPT review catalog row');
|
|
238
237
|
}
|
|
@@ -479,8 +478,8 @@ export function main(args = process.argv.slice(2), runtime = {}) {
|
|
|
479
478
|
readJson(options['--sol'], 'Sol review receipt')];
|
|
480
479
|
const ordered = validateIndependentReviewPair(receipts, {
|
|
481
480
|
publicKeysByReviewer: {
|
|
482
|
-
|
|
483
|
-
|
|
481
|
+
[CLAUDE_FABLE_5_ID]: readRegular(options['--fable-public-key'], 'Fable review public key'),
|
|
482
|
+
[GPT_5_6_SOL_ID]: readRegular(options['--sol-public-key'], 'Sol review public key'),
|
|
484
483
|
},
|
|
485
484
|
expectedIdentity: options['--expected-identity']
|
|
486
485
|
? readJson(options['--expected-identity'], 'expected review identity') : null,
|
package/scripts/ingest-repo.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { FULL_HINTS, KEEP_DIRS } from './full-hints.mjs';
|
|
23
23
|
import { storeRoot } from '../kb/store-root.mjs';
|
|
24
|
+
import { applyPrivateOverlay } from './private-overlay.mjs';
|
|
24
25
|
|
|
25
26
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
27
|
const arg = (f, d) => { const i = process.argv.indexOf(f); return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : d; };
|
|
@@ -118,15 +119,58 @@ const carded = (() => {
|
|
|
118
119
|
// The stores themselves cannot be committed. The FACT that they were ingested can, so a wipe
|
|
119
120
|
// becomes detectable and replayable instead of silent and permanent. This record is the recipe.
|
|
120
121
|
if (ok) {
|
|
122
|
+
let rest = [];
|
|
121
123
|
try {
|
|
122
124
|
const ledger = path.join(ROOT, 'kb', 'local-ingests.json');
|
|
123
125
|
const prior = fs.existsSync(ledger) ? JSON.parse(fs.readFileSync(ledger, 'utf8')) : { ingests: [] };
|
|
124
|
-
|
|
126
|
+
rest = (prior.ingests || []).filter((e) => e.name !== NAME);
|
|
125
127
|
rest.push({ name: NAME, org: ORG, at: new Date().toISOString(), store: kb });
|
|
126
128
|
rest.sort((a, b) => a.name.localeCompare(b.name));
|
|
127
129
|
fs.writeFileSync(ledger, `${JSON.stringify({ ingests: rest }, null, 2)}\n`);
|
|
128
130
|
console.log(`[record] kb/local-ingests.json — ${rest.length} local ingest(s) recorded. COMMIT THIS.`);
|
|
129
131
|
} catch (e) { console.log(`[record] could not record the ingest: ${e.message}`); }
|
|
132
|
+
|
|
133
|
+
// S3 (explicit local ownership): stamp updateManaged:false + origin:'local-ingest' into the LIVE
|
|
134
|
+
// store root's own SOURCE.json (via scripts/private-overlay.mjs's writer — --from equals --root
|
|
135
|
+
// here, since forge-refresh.mjs already wrote the bytes directly into KB; the writer degenerates
|
|
136
|
+
// to registry-stamping only, see its own header). Without this a store built through THIS script
|
|
137
|
+
// reads exactly like a normal public store to kb/forge-update.mjs — unmarked, and therefore
|
|
138
|
+
// silently deleted the moment it is absent from an incoming public bundle.
|
|
139
|
+
//
|
|
140
|
+
// ONE-TIME MIGRATION for stores this script ingested BEFORE this fix shipped: every OTHER name
|
|
141
|
+
// this ledger already recorded is retroactively re-stamped too, provided its files still exist and
|
|
142
|
+
// it is not already an update-managed (public) SOURCE.json entry. This runs HERE — where the
|
|
143
|
+
// recipe ledger (kb/local-ingests.json, git-tracked in the checkout) and the live store root (KB,
|
|
144
|
+
// ~/.cache/ruvnet-brain/kb by default — a DIFFERENT, non-git-tracked directory, see kb/store-
|
|
145
|
+
// root.mjs) are both actually in scope in the same process — rather than inside kb/forge-update.mjs,
|
|
146
|
+
// which runs against an installed KB tree with no reliable path back to this checkout's ledger.
|
|
147
|
+
const candidateNames = [...new Set([kb, ...rest.map((e) => e.store).filter(Boolean)])];
|
|
148
|
+
const sourceFile = path.join(KB, 'SOURCE.json');
|
|
149
|
+
let liveSourceStores = {};
|
|
150
|
+
try { liveSourceStores = JSON.parse(fs.readFileSync(sourceFile, 'utf8')).stores || {}; } catch { /* handled by applyPrivateOverlay below */ }
|
|
151
|
+
const toStamp = candidateNames.filter((name) => {
|
|
152
|
+
const existing = liveSourceStores[name];
|
|
153
|
+
if (existing && existing.updateManaged !== false) return false; // a real public store — never shadow it
|
|
154
|
+
return [`${name}.big.rvf`, `${name}.big.rvf.embed.json`, `${name}.big.rvf.idmap.json`, `${name}.meta.json`, `${name}.passages.jsonl`]
|
|
155
|
+
.every((file) => fs.existsSync(path.join(KB, file)));
|
|
156
|
+
});
|
|
157
|
+
if (toStamp.length) {
|
|
158
|
+
try {
|
|
159
|
+
const fenceFile = path.join(KB, 'PRIVATE-STORES.json');
|
|
160
|
+
const fence = fs.existsSync(fenceFile) ? JSON.parse(fs.readFileSync(fenceFile, 'utf8')) : { privateStores: [] };
|
|
161
|
+
const fenceNames = new Set((fence.privateStores || []).map((n) => String(n).toLowerCase()));
|
|
162
|
+
const additions = toStamp.filter((name) => !fenceNames.has(name.toLowerCase()));
|
|
163
|
+
if (additions.length) {
|
|
164
|
+
fence.privateStores = [...new Set([...(fence.privateStores || []), ...additions])].sort();
|
|
165
|
+
fs.writeFileSync(fenceFile, `${JSON.stringify(fence, null, 2)}\n`);
|
|
166
|
+
}
|
|
167
|
+
const receipt = applyPrivateOverlay({ root: KB, from: KB, stores: toStamp, origin: 'local-ingest' });
|
|
168
|
+
const stamped = receipt.stores.filter((entry) => entry.changes.includes('SOURCE.json')).map((entry) => entry.name);
|
|
169
|
+
if (stamped.length) console.log(`[local-ownership] marked updateManaged:false (origin: local-ingest) — ${stamped.join(', ')}`);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
console.log(`[local-ownership] could not stamp local ownership (${e.message}); the next forge-update.mjs --apply may remove ${toStamp.join(', ')} if they are absent from the incoming bundle`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
130
174
|
}
|
|
131
175
|
|
|
132
176
|
console.log(!ok
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
writeArtifact,
|
|
36
36
|
} from './learning-replay-proof.mjs';
|
|
37
37
|
import { ROOT } from './learning-replay-contract.mjs';
|
|
38
|
+
import { GPT_5_6_SOL_ID } from './review-model-defaults.mjs';
|
|
38
39
|
|
|
39
40
|
const usage = () => `Usage:
|
|
40
41
|
node scripts/learning-replay.mjs [--trap ${TRAP.MEMORY_SEARCH}|${TRAP.POST_TASK}] [--n N] [--host codex|claude-code] [--model MODEL]
|
|
@@ -94,7 +95,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
94
95
|
const mutant = arg('--mutant', null);
|
|
95
96
|
const trap = arg('--trap', TRAP.MEMORY_SEARCH);
|
|
96
97
|
const host = arg('--host', 'codex');
|
|
97
|
-
const model = arg('--model', host === 'codex' ?
|
|
98
|
+
const model = arg('--model', host === 'codex' ? GPT_5_6_SOL_ID : 'haiku');
|
|
98
99
|
const n = Math.max(1, parseInt(arg('--n', mutant ? '1' : '3'), 10) || 1);
|
|
99
100
|
const outFile = arg('--out', mutant
|
|
100
101
|
? MUTANT_RESULT_FILES[trap]?.[mutant]
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
PROJECT_B_MEMORY_VALUE,
|
|
20
20
|
RUFLO_BIN,
|
|
21
21
|
} from './learning-replay-execution.mjs';
|
|
22
|
+
import { GPT_5_6_SOL_ID } from './review-model-defaults.mjs';
|
|
22
23
|
|
|
23
24
|
const CLAUDE_BIN = process.env.RUVNET_CLAUDE_BIN
|
|
24
25
|
|| path.join(os.homedir(), '.npm-global', 'bin', 'claude');
|
|
@@ -266,7 +267,7 @@ export function codexReplayInstrumentationError(events, sequence, attempts) {
|
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
export function buildCodexArgv({
|
|
269
|
-
model =
|
|
270
|
+
model = GPT_5_6_SOL_ID,
|
|
270
271
|
prompt = REPLAY_PROMPT,
|
|
271
272
|
appendSystemPrompt = null,
|
|
272
273
|
brainHome,
|
package/scripts/learnings.mjs
CHANGED
|
@@ -8,13 +8,22 @@ import fs from 'node:fs';
|
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { learningScope, learnerCwd } from '../plugin/scripts/runtime-preferences.mjs';
|
|
11
12
|
|
|
12
13
|
const HOME = os.homedir();
|
|
13
14
|
|
|
14
|
-
/** @param {{statsPath?:string, queueDir?:string, now?:number}} [opts] */
|
|
15
|
-
export function learnings({ statsPath, queueDir, now = Date.now() } = {}) {
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/** @param {{statsPath?:string, queueDir?:string, now?:number, cwd?:string, env?:object, home?:string}} [opts] */
|
|
16
|
+
export function learnings({ statsPath, queueDir, now = Date.now(), cwd, env, home } = {}) {
|
|
17
|
+
// The flusher and the console must inspect the same learner. A fixed HOME path made the panel
|
|
18
|
+
// report a stale global learner while project-scoped captures were fed under the active project.
|
|
19
|
+
// Keep injectable paths for callers/tests, but derive both defaults from the shared scope owner.
|
|
20
|
+
const project = cwd || process.env.RUVNET_BRAIN_PROJECT_DIR || process.cwd();
|
|
21
|
+
const scope = learningScope({ cwd: project, env: env || process.env });
|
|
22
|
+
const learner = learnerCwd({ cwd: project, env: env || process.env, home: home || HOME });
|
|
23
|
+
const sp = statsPath || path.join(learner, '.claude-flow', 'neural', 'stats.json');
|
|
24
|
+
const qd = queueDir || (scope === 'user'
|
|
25
|
+
? path.join(home || HOME, '.cache', 'ruvnet-brain', 'learn')
|
|
26
|
+
: path.join(project, '.swarm', 'ruvnet-brain-learn'));
|
|
18
27
|
|
|
19
28
|
let stats = {};
|
|
20
29
|
try { stats = JSON.parse(fs.readFileSync(sp, 'utf8')); } catch { /* no learner yet */ }
|
|
@@ -53,7 +62,12 @@ export function learnings({ statsPath, queueDir, now = Date.now() } = {}) {
|
|
|
53
62
|
lastAdaptation: lastMs ? new Date(lastMs).toISOString() : null,
|
|
54
63
|
daysSinceLastAdaptation: daysSince,
|
|
55
64
|
recentWorkflow,
|
|
56
|
-
note:
|
|
65
|
+
note: scope === 'user'
|
|
66
|
+
? 'Learnings are how you work — shared across your projects and getting smarter over time. Project facts stay isolated per project; nothing here is project data.'
|
|
67
|
+
: 'Learnings are how you work in this project and getting smarter over time. User-scoped learning remains isolated from this project; nothing here is project data.',
|
|
68
|
+
scope,
|
|
69
|
+
statsPath: sp,
|
|
70
|
+
queueDir: qd,
|
|
57
71
|
};
|
|
58
72
|
}
|
|
59
73
|
|