@claude-flow/cli 3.44.0 → 3.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/README.md +2 -0
- package/bin/cli.js +13 -0
- package/bin/mcp-server.js +16 -0
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.d.ts +12 -0
- package/dist/src/commands/doctor.js +200 -3
- package/dist/src/commands/embeddings.js +50 -7
- package/dist/src/commands/init.js +5 -2
- package/dist/src/commands/mcp.js +27 -4
- package/dist/src/mcp-server.d.ts +50 -6
- package/dist/src/mcp-server.js +267 -33
- package/dist/src/mcp-tools/agent-execute-core.js +7 -0
- package/dist/src/mcp-tools/agentdb-tools.js +20 -8
- package/dist/src/mcp-tools/browser-tools.js +30 -23
- package/dist/src/mcp-tools/memory-tools.js +9 -4
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/policy-enforcer.d.ts +4 -4
- package/dist/src/mcp-tools/policy-enforcer.js +4 -4
- package/dist/src/mcp-tools/terminal-tools.js +11 -1
- package/dist/src/mcp-tools/workflow-tools.js +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts +16 -1
- package/dist/src/memory/ewc-consolidation.js +63 -13
- package/dist/src/memory/intelligence.d.ts +6 -2
- package/dist/src/memory/intelligence.js +18 -11
- package/dist/src/memory/memory-bridge.js +84 -0
- package/dist/src/ruvector/model-router.js +19 -1
- package/dist/src/services/distill-oracle.js +2 -1
- package/dist/src/services/git-workspace-identity.js +5 -8
- package/dist/src/services/policy-runtime.js +114 -4
- package/dist/src/services/worker-daemon.js +8 -3
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js +2 -6
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +14 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts +40 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js +100 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -0
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts +22 -0
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts.map +1 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js +63 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js.map +1 -0
- package/package.json +5 -5
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +1 -1
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +84 -12
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +10 -4
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +120 -6
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +33 -6
- package/plugins/ruflo-metaharness/scripts/audit-list.mjs +11 -10
- package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +2 -3
- package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/similarity.mjs +9 -8
- package/plugins/ruflo-metaharness/scripts/smoke.sh +31 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +319 -0
- package/plugins/ruflo-metaharness/scripts/test-pin-alignment.mjs +182 -0
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +4 -2
- package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +3 -1
- package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +1 -1
|
@@ -1015,13 +1015,16 @@ export const memoryTools = [
|
|
|
1015
1015
|
if (nameMatch)
|
|
1016
1016
|
name = nameMatch[1].trim();
|
|
1017
1017
|
}
|
|
1018
|
-
// Split into sections for granular search
|
|
1018
|
+
// Split into sections for granular search. Persist each full section:
|
|
1019
|
+
// clipping at 4096 characters silently discarded its searchable tail (#3217).
|
|
1019
1020
|
const sections = body.split(/^(?=## )/m).filter(s => s.trim().length > 20);
|
|
1020
1021
|
if (sections.length === 0 && body.length > 10) {
|
|
1021
1022
|
// #1884 — sanitize key so memory_delete can later remove it. Without
|
|
1022
1023
|
// this, dangerous chars from frontmatter `name` strand the key.
|
|
1023
1024
|
const key = sanitizeMemoryKey(`claude:${memFile.project}:${name}`);
|
|
1024
|
-
await storeEntry({ key, value: body
|
|
1025
|
+
const stored = await storeEntry({ key, value: body, namespace: ns, generateEmbeddingFlag: true });
|
|
1026
|
+
if (!stored.success)
|
|
1027
|
+
throw new Error(stored.error || `Failed to store ${key}`);
|
|
1025
1028
|
imported++;
|
|
1026
1029
|
}
|
|
1027
1030
|
else {
|
|
@@ -1034,7 +1037,9 @@ export const memoryTools = [
|
|
|
1034
1037
|
// #1884 — sanitize so any dangerous chars in the heading don't
|
|
1035
1038
|
// produce keys memory_delete will reject.
|
|
1036
1039
|
const key = sanitizeMemoryKey(`claude:${memFile.project}:${name}:${sectionTitle.slice(0, 50)}`);
|
|
1037
|
-
await storeEntry({ key, value: sectionBody
|
|
1040
|
+
const stored = await storeEntry({ key, value: sectionBody, namespace: ns, generateEmbeddingFlag: true });
|
|
1041
|
+
if (!stored.success)
|
|
1042
|
+
throw new Error(stored.error || `Failed to store ${key}`);
|
|
1038
1043
|
imported++;
|
|
1039
1044
|
}
|
|
1040
1045
|
}
|
|
@@ -1054,7 +1059,7 @@ export const memoryTools = [
|
|
|
1054
1059
|
}
|
|
1055
1060
|
catch { /* probe failed — leave 'unknown' */ }
|
|
1056
1061
|
return {
|
|
1057
|
-
success:
|
|
1062
|
+
success: skipped === 0,
|
|
1058
1063
|
imported,
|
|
1059
1064
|
skipped,
|
|
1060
1065
|
duplicatesSkipped,
|
|
@@ -402,7 +402,7 @@ export const metaharnessTools = [
|
|
|
402
402
|
},
|
|
403
403
|
// ───────────────────────────────────────────────────────────────────────
|
|
404
404
|
// ADR-153 — @metaharness/darwin integration (3 tools).
|
|
405
|
-
// Backed by the separate `@metaharness/darwin@~0.
|
|
405
|
+
// Backed by the separate `@metaharness/darwin@~0.10.2` npm package, NOT
|
|
406
406
|
// the umbrella `metaharness`. Plugin scripts shell out via _darwin.mjs.
|
|
407
407
|
// Same {success, data, degraded, exitCode} contract.
|
|
408
408
|
// ───────────────────────────────────────────────────────────────────────
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
* misconfiguration defeats the feature. See `evaluateToolCall()`.
|
|
31
31
|
*
|
|
32
32
|
* Known scope limits (disclosed, not fixed here):
|
|
33
|
-
* -
|
|
34
|
-
*
|
|
35
|
-
* path (`startHttpServer()`, via `@claude-flow/mcp`)
|
|
36
|
-
* this module and is unaffected even when this flag is set.
|
|
33
|
+
* - Wired into all three stdio `tools/call` dispatchers: the CLI fast
|
|
34
|
+
* path, the standalone MCP launcher, and MCPServerManager. The separate
|
|
35
|
+
* HTTP/websocket path (`startHttpServer()`, via `@claude-flow/mcp`)
|
|
36
|
+
* does not call this module and is unaffected even when this flag is set.
|
|
37
37
|
*
|
|
38
38
|
* `maxToolCallsPerTurn` reset semantics (dream-cycle 2026-09-01, follow-up
|
|
39
39
|
* to 2026-08-31 review round 1): despite the field's name, the original
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
* misconfiguration defeats the feature. See `evaluateToolCall()`.
|
|
31
31
|
*
|
|
32
32
|
* Known scope limits (disclosed, not fixed here):
|
|
33
|
-
* -
|
|
34
|
-
*
|
|
35
|
-
* path (`startHttpServer()`, via `@claude-flow/mcp`)
|
|
36
|
-
* this module and is unaffected even when this flag is set.
|
|
33
|
+
* - Wired into all three stdio `tools/call` dispatchers: the CLI fast
|
|
34
|
+
* path, the standalone MCP launcher, and MCPServerManager. The separate
|
|
35
|
+
* HTTP/websocket path (`startHttpServer()`, via `@claude-flow/mcp`)
|
|
36
|
+
* does not call this module and is unaffected even when this flag is set.
|
|
37
37
|
*
|
|
38
38
|
* `maxToolCallsPerTurn` reset semantics (dream-cycle 2026-09-01, follow-up
|
|
39
39
|
* to 2026-08-31 review round 1): despite the field's name, the original
|
|
@@ -153,6 +153,16 @@ export const terminalTools = [
|
|
|
153
153
|
const startTime = Date.now();
|
|
154
154
|
let output;
|
|
155
155
|
let exitCode;
|
|
156
|
+
// dream-cycle 2026-09-21 (post-review): session.env was validated once
|
|
157
|
+
// at terminal_create time and trusted forever after via the on-disk
|
|
158
|
+
// store — a session persisted before a denylist update (or a store.json
|
|
159
|
+
// edited/restored out of band) could carry an env that the *current*
|
|
160
|
+
// validateEnv() would reject, silently bypassing the denylist for every
|
|
161
|
+
// execute on that pre-existing session. Re-validate at execution time
|
|
162
|
+
// too; drop (not fail the whole call) on now-invalid env so a stale
|
|
163
|
+
// session degrades to a clean environment rather than blocking.
|
|
164
|
+
const vSessionEnv = validateEnv(session.env, 'session.env');
|
|
165
|
+
const effectiveEnv = vSessionEnv.valid ? vSessionEnv.sanitized : {};
|
|
156
166
|
try {
|
|
157
167
|
output = execSync(command, {
|
|
158
168
|
cwd,
|
|
@@ -160,7 +170,7 @@ export const terminalTools = [
|
|
|
160
170
|
timeout,
|
|
161
171
|
maxBuffer: 5 * 1024 * 1024,
|
|
162
172
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
163
|
-
env: { ...process.env, ...
|
|
173
|
+
env: { ...process.env, ...effectiveEnv },
|
|
164
174
|
});
|
|
165
175
|
exitCode = 0;
|
|
166
176
|
}
|
|
@@ -256,7 +256,7 @@ export const workflowTools = [
|
|
|
256
256
|
const stepResults = [];
|
|
257
257
|
// Variable substitution: {{name}} → workflow.variables[name] OR steps[stepId].output
|
|
258
258
|
const interp = (text) => {
|
|
259
|
-
return text.replace(/\{\{\s*([a-zA-Z_][\w
|
|
259
|
+
return text.replace(/\{\{\s*([a-zA-Z_][\w.-]*)\s*\}\}/g, (_, key) => {
|
|
260
260
|
// Direct variable
|
|
261
261
|
if (key in workflow.variables)
|
|
262
262
|
return String(workflow.variables[key]);
|
|
@@ -212,7 +212,13 @@ export declare class EWCConsolidator {
|
|
|
212
212
|
* Called by SONA after distillLearning to track which patterns
|
|
213
213
|
* are important and should be protected from forgetting.
|
|
214
214
|
*
|
|
215
|
-
* Uses online averaging: F_new =
|
|
215
|
+
* Uses online averaging: F_new = (1 - decay) * F_old + decay * F_current
|
|
216
|
+
* (same convention as computeFisherMatrix()/recordGradient() above: decay
|
|
217
|
+
* is the weight given to the new batch, so a small decay — the default,
|
|
218
|
+
* 0.01 — means the accumulated Fisher decays slowly and survives many
|
|
219
|
+
* subsequent low-signal calls, per the standard online-EWC/EWC++ update
|
|
220
|
+
* rule, e.g. Chaudhry et al., "Riemannian Walk for Incremental Learning",
|
|
221
|
+
* ECCV 2018, arXiv:1801.10112).
|
|
216
222
|
*
|
|
217
223
|
* @param confidenceChanges - Array of {id, embedding, oldConf, newConf}
|
|
218
224
|
*/
|
|
@@ -235,6 +241,15 @@ export declare class EWCConsolidator {
|
|
|
235
241
|
* Clear all patterns and history (full reset)
|
|
236
242
|
*/
|
|
237
243
|
clear(): void;
|
|
244
|
+
/**
|
|
245
|
+
* Validate a loaded globalFisher candidate before trusting it: must be an
|
|
246
|
+
* array of exactly `dimensions` finite, non-negative numbers. Fisher
|
|
247
|
+
* values are sums of squares (see computeFisherMatrix/recordGradient/
|
|
248
|
+
* updateFisherFromConfidences above), so a negative or non-finite entry
|
|
249
|
+
* means the persisted file is corrupted or from an incompatible layout,
|
|
250
|
+
* not a legitimate state to resume from.
|
|
251
|
+
*/
|
|
252
|
+
private isValidFisherArray;
|
|
238
253
|
/**
|
|
239
254
|
* Calculate importance score for a pattern based on usage
|
|
240
255
|
*/
|
|
@@ -357,38 +357,68 @@ export class EWCConsolidator {
|
|
|
357
357
|
* Called by SONA after distillLearning to track which patterns
|
|
358
358
|
* are important and should be protected from forgetting.
|
|
359
359
|
*
|
|
360
|
-
* Uses online averaging: F_new =
|
|
360
|
+
* Uses online averaging: F_new = (1 - decay) * F_old + decay * F_current
|
|
361
|
+
* (same convention as computeFisherMatrix()/recordGradient() above: decay
|
|
362
|
+
* is the weight given to the new batch, so a small decay — the default,
|
|
363
|
+
* 0.01 — means the accumulated Fisher decays slowly and survives many
|
|
364
|
+
* subsequent low-signal calls, per the standard online-EWC/EWC++ update
|
|
365
|
+
* rule, e.g. Chaudhry et al., "Riemannian Walk for Incremental Learning",
|
|
366
|
+
* ECCV 2018, arXiv:1801.10112).
|
|
361
367
|
*
|
|
362
368
|
* @param confidenceChanges - Array of {id, embedding, oldConf, newConf}
|
|
363
369
|
*/
|
|
364
370
|
updateFisherFromConfidences(confidenceChanges) {
|
|
365
371
|
if (confidenceChanges.length === 0)
|
|
366
372
|
return;
|
|
367
|
-
const
|
|
373
|
+
const decay = this.config.fisherDecayRate;
|
|
368
374
|
const currentFisher = new Array(this.config.dimensions).fill(0);
|
|
369
375
|
let sampleCount = 0;
|
|
370
376
|
for (const change of confidenceChanges) {
|
|
371
377
|
if (!change.embedding || change.embedding.length === 0)
|
|
372
378
|
continue;
|
|
379
|
+
if (!Number.isFinite(change.oldConf) || !Number.isFinite(change.newConf))
|
|
380
|
+
continue;
|
|
373
381
|
const confDelta = Math.abs(change.newConf - change.oldConf);
|
|
374
382
|
if (confDelta === 0)
|
|
375
383
|
continue;
|
|
376
|
-
sampleCount++;
|
|
377
384
|
const len = Math.min(change.embedding.length, this.config.dimensions);
|
|
378
|
-
// Squared gradient proxy: embedding scaled by confidence change magnitude
|
|
385
|
+
// Squared gradient proxy: embedding scaled by confidence change magnitude.
|
|
386
|
+
// Validate the whole sample before folding any of it in -- a single
|
|
387
|
+
// non-finite embedding dimension (NaN/Infinity) must not silently poison
|
|
388
|
+
// the shared accumulator for every other pattern in this batch.
|
|
389
|
+
const sampleFisher = new Array(len);
|
|
390
|
+
let sampleValid = true;
|
|
379
391
|
for (let i = 0; i < len; i++) {
|
|
380
392
|
const grad = change.embedding[i] * confDelta;
|
|
381
|
-
|
|
393
|
+
const sq = grad * grad;
|
|
394
|
+
if (!Number.isFinite(sq)) {
|
|
395
|
+
sampleValid = false;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
sampleFisher[i] = sq;
|
|
382
399
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
400
|
+
if (!sampleValid)
|
|
401
|
+
continue;
|
|
402
|
+
sampleCount++;
|
|
403
|
+
for (let i = 0; i < len; i++) {
|
|
404
|
+
currentFisher[i] += sampleFisher[i];
|
|
387
405
|
}
|
|
388
406
|
}
|
|
389
|
-
//
|
|
407
|
+
// No sample contributed usable signal (all filtered out, or every delta was
|
|
408
|
+
// zero): leave globalFisher and persisted state untouched rather than
|
|
409
|
+
// decaying accumulated importance toward an empty/no-op batch.
|
|
410
|
+
if (sampleCount === 0)
|
|
411
|
+
return;
|
|
412
|
+
for (let i = 0; i < this.config.dimensions; i++) {
|
|
413
|
+
currentFisher[i] /= sampleCount;
|
|
414
|
+
}
|
|
415
|
+
// Online EMA: F_new = (1 - decay) * F_old + decay * F_current
|
|
390
416
|
for (let i = 0; i < this.config.dimensions; i++) {
|
|
391
|
-
|
|
417
|
+
const next = (1 - decay) * this.globalFisher[i] + decay * currentFisher[i];
|
|
418
|
+
// Defensive: never let a non-finite result (e.g. from a corrupted prior
|
|
419
|
+
// globalFisher[i]) propagate into the live signal -- keep the last known
|
|
420
|
+
// finite value instead of poisoning it further.
|
|
421
|
+
this.globalFisher[i] = Number.isFinite(next) ? next : this.globalFisher[i];
|
|
392
422
|
}
|
|
393
423
|
this.saveToDisk();
|
|
394
424
|
}
|
|
@@ -432,6 +462,19 @@ export class EWCConsolidator {
|
|
|
432
462
|
// ============================================================================
|
|
433
463
|
// Private Methods
|
|
434
464
|
// ============================================================================
|
|
465
|
+
/**
|
|
466
|
+
* Validate a loaded globalFisher candidate before trusting it: must be an
|
|
467
|
+
* array of exactly `dimensions` finite, non-negative numbers. Fisher
|
|
468
|
+
* values are sums of squares (see computeFisherMatrix/recordGradient/
|
|
469
|
+
* updateFisherFromConfidences above), so a negative or non-finite entry
|
|
470
|
+
* means the persisted file is corrupted or from an incompatible layout,
|
|
471
|
+
* not a legitimate state to resume from.
|
|
472
|
+
*/
|
|
473
|
+
isValidFisherArray(value) {
|
|
474
|
+
return (Array.isArray(value) &&
|
|
475
|
+
value.length === this.config.dimensions &&
|
|
476
|
+
value.every((v) => typeof v === 'number' && Number.isFinite(v) && v >= 0));
|
|
477
|
+
}
|
|
435
478
|
/**
|
|
436
479
|
* Calculate importance score for a pattern based on usage
|
|
437
480
|
*/
|
|
@@ -524,8 +567,15 @@ export class EWCConsolidator {
|
|
|
524
567
|
if (state.version !== '1.0.0') {
|
|
525
568
|
throw new Error(`Unsupported state version: ${state.version}`);
|
|
526
569
|
}
|
|
527
|
-
// Restore
|
|
528
|
-
|
|
570
|
+
// Restore the Fisher matrix, quarantining anything malformed -- wrong
|
|
571
|
+
// length, non-numeric/non-finite entries (e.g. JSON's lossy NaN/Infinity
|
|
572
|
+
// -> null round-trip), or negative values (Fisher values are sums of
|
|
573
|
+
// squares and can never legitimately be negative) -- rather than
|
|
574
|
+
// silently absorbing corrupted or truncated state into live importance
|
|
575
|
+
// tracking on restart.
|
|
576
|
+
this.globalFisher = this.isValidFisherArray(state.globalFisher)
|
|
577
|
+
? state.globalFisher
|
|
578
|
+
: new Array(this.config.dimensions).fill(0);
|
|
529
579
|
// Restore patterns
|
|
530
580
|
this.patterns.clear();
|
|
531
581
|
if (state.patterns) {
|
|
@@ -248,6 +248,7 @@ export interface UnifiedLearningStats {
|
|
|
248
248
|
signalsProcessed: number;
|
|
249
249
|
lastAdaptation: number | null;
|
|
250
250
|
source: string;
|
|
251
|
+
scope: 'project-persisted';
|
|
251
252
|
};
|
|
252
253
|
sona: {
|
|
253
254
|
trajectoriesTotal: number;
|
|
@@ -256,6 +257,8 @@ export interface UnifiedLearningStats {
|
|
|
256
257
|
avgAdaptationTimeMs: number;
|
|
257
258
|
source: string;
|
|
258
259
|
available: boolean;
|
|
260
|
+
scope: 'process-local';
|
|
261
|
+
metric: 'recent-buffered-trajectories';
|
|
259
262
|
};
|
|
260
263
|
memoryBridge: {
|
|
261
264
|
totalEntries: number;
|
|
@@ -270,8 +273,9 @@ export interface UnifiedLearningStats {
|
|
|
270
273
|
source: string;
|
|
271
274
|
};
|
|
272
275
|
consistency: {
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
/** These counters measure different lifetimes and cannot be compared. */
|
|
277
|
+
sonaTracksGlobal: null;
|
|
278
|
+
sonaTracksGlobalDelta: null;
|
|
275
279
|
notes: string[];
|
|
276
280
|
};
|
|
277
281
|
generatedAt: string;
|
|
@@ -701,12 +701,15 @@ export async function getUnifiedLearningStats() {
|
|
|
701
701
|
let sonaStats = { trajectoriesTotal: 0, patternsLearned: 0, reasoningBankSize: 0, avgAdaptationTimeMs: 0 };
|
|
702
702
|
if (sonaCoord) {
|
|
703
703
|
try {
|
|
704
|
-
const s = sonaCoord.stats
|
|
704
|
+
const s = sonaCoord.stats();
|
|
705
705
|
sonaStats = {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
706
|
+
// LocalSonaCoordinator.stats() reports trajectoryCount, a bounded
|
|
707
|
+
// recent buffer. Reading nonexistent trajectoriesTotal / processed
|
|
708
|
+
// fields made this metric permanently 0, even after learning.
|
|
709
|
+
trajectoriesTotal: s.trajectoryCount,
|
|
710
|
+
patternsLearned: 0,
|
|
711
|
+
reasoningBankSize: bank?.stats().patternCount ?? 0,
|
|
712
|
+
avgAdaptationTimeMs: s.avgAdaptationMs,
|
|
710
713
|
};
|
|
711
714
|
}
|
|
712
715
|
catch { /* SONA not yet initialised */ }
|
|
@@ -729,11 +732,12 @@ export async function getUnifiedLearningStats() {
|
|
|
729
732
|
neuralStats = nt.getNeuralStoreStats();
|
|
730
733
|
}
|
|
731
734
|
catch { /* neural module not loadable */ }
|
|
732
|
-
//
|
|
733
|
-
|
|
735
|
+
// SONA's coordinator is recreated on each process start, while globalStats
|
|
736
|
+
// is restored from disk. Their counts can differ by thousands after a
|
|
737
|
+
// restart even when both learning paths work correctly (#3198).
|
|
734
738
|
const notes = [];
|
|
735
|
-
if (sonaAvailable &&
|
|
736
|
-
notes.push(
|
|
739
|
+
if (sonaAvailable && sonaStats.trajectoriesTotal !== intel.trajectoriesRecorded) {
|
|
740
|
+
notes.push('sona.trajectoriesTotal is a process-local, bounded recent trajectory buffer; global.trajectoriesRecorded is project-persisted. These counters have different lifetimes and are not comparable.');
|
|
737
741
|
}
|
|
738
742
|
if (intel.patternsLearned > 0 && neuralStats.patternCount === 0) {
|
|
739
743
|
notes.push(`globalStats reports ${intel.patternsLearned} patterns learned but neural_patterns store is empty — pretrain has not written here, or trajectory-end isn't promoting patterns to the neural store yet`);
|
|
@@ -748,17 +752,20 @@ export async function getUnifiedLearningStats() {
|
|
|
748
752
|
signalsProcessed: intel.signalsProcessed,
|
|
749
753
|
lastAdaptation: intel.lastAdaptation,
|
|
750
754
|
source: '.claude-flow/neural/stats.json (globalStats)',
|
|
755
|
+
scope: 'project-persisted',
|
|
751
756
|
},
|
|
752
757
|
sona: {
|
|
753
758
|
...sonaStats,
|
|
754
759
|
source: 'sonaCoordinator (in-memory, resets per process)',
|
|
755
760
|
available: sonaAvailable,
|
|
761
|
+
scope: 'process-local',
|
|
762
|
+
metric: 'recent-buffered-trajectories',
|
|
756
763
|
},
|
|
757
764
|
memoryBridge: bridgeStats,
|
|
758
765
|
neuralPatterns: neuralStats,
|
|
759
766
|
consistency: {
|
|
760
|
-
sonaTracksGlobal:
|
|
761
|
-
sonaTracksGlobalDelta,
|
|
767
|
+
sonaTracksGlobal: null,
|
|
768
|
+
sonaTracksGlobalDelta: null,
|
|
762
769
|
notes,
|
|
763
770
|
},
|
|
764
771
|
generatedAt: new Date().toISOString(),
|
|
@@ -565,6 +565,89 @@ async function cacheGet(registry, cacheKey) {
|
|
|
565
565
|
return null;
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Cross-process cache coherence.
|
|
570
|
+
*
|
|
571
|
+
* The TieredCache lives in this process, but the database file is shared:
|
|
572
|
+
* Claude Code, Codex and Grok each run their own `mcp start` against the
|
|
573
|
+
* same `.swarm/` store, and each process only invalidates entries on its
|
|
574
|
+
* OWN writes. A key another process updated or deleted kept coming back
|
|
575
|
+
* from this process's cache — old value, `found: true` — until the cached
|
|
576
|
+
* entry expired (TieredCache TTL, 5 minutes by default; reads don't extend
|
|
577
|
+
* it).
|
|
578
|
+
*
|
|
579
|
+
* SQLite's `PRAGMA data_version` changes whenever a DIFFERENT connection has
|
|
580
|
+
* committed to the database file since this connection last asked, and never
|
|
581
|
+
* for this connection's own writes (https://sqlite.org/pragma.html#pragma_data_version).
|
|
582
|
+
* Checking it before a cached read is an exact "has anyone else written?"
|
|
583
|
+
* test that costs one pragma call. When it moves, drop the whole cache:
|
|
584
|
+
* every cached shape was derived from the old database state. The first
|
|
585
|
+
* check on a handle records its baseline and clears: a no-op for a fresh
|
|
586
|
+
* handle, and the right thing if a handle is ever replaced under a live
|
|
587
|
+
* cache.
|
|
588
|
+
*
|
|
589
|
+
* Native better-sqlite3 handles only. agentdb's sql.js fallback works on a
|
|
590
|
+
* private in-memory image that never sees another process's commits, so
|
|
591
|
+
* there is nothing to detect; it is skipped rather than probed. A handle
|
|
592
|
+
* that cannot prepare the check is remembered and never probed again; a
|
|
593
|
+
* check that fails on a given read (e.g. SQLITE_BUSY) clears the cache for
|
|
594
|
+
* safety and is simply retried on the next read.
|
|
595
|
+
*/
|
|
596
|
+
const _cacheDataVersionStmts = new WeakMap();
|
|
597
|
+
const _cacheDataVersions = new WeakMap();
|
|
598
|
+
const _cacheDataVersionUnsupported = new WeakSet();
|
|
599
|
+
function dropCacheIfDbChangedElsewhere(registry, ctx) {
|
|
600
|
+
// Never let this check break the read it guards: any throw falls back to
|
|
601
|
+
// the pre-fix behaviour (a possibly stale hit).
|
|
602
|
+
try {
|
|
603
|
+
const db = ctx.db;
|
|
604
|
+
if (!db || ctx.agentdb?.isWasm === true || !('inTransaction' in db))
|
|
605
|
+
return;
|
|
606
|
+
if (_cacheDataVersionUnsupported.has(db))
|
|
607
|
+
return;
|
|
608
|
+
let stmt = _cacheDataVersionStmts.get(db);
|
|
609
|
+
if (!stmt) {
|
|
610
|
+
try {
|
|
611
|
+
stmt = db.prepare('PRAGMA data_version');
|
|
612
|
+
_cacheDataVersionStmts.set(db, stmt);
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
_cacheDataVersionUnsupported.add(db); // structural: this handle can't answer
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
let cache = null;
|
|
620
|
+
try {
|
|
621
|
+
cache = registry.get('tieredCache');
|
|
622
|
+
}
|
|
623
|
+
catch { /* no cache, nothing to drop */ }
|
|
624
|
+
try {
|
|
625
|
+
const version = stmt.get()?.data_version;
|
|
626
|
+
if (typeof version !== 'number') {
|
|
627
|
+
// #3360: an unusable answer is not evidence the store is unchanged.
|
|
628
|
+
// Same posture as the catch below — a check we cannot validate drops
|
|
629
|
+
// the cache rather than trusting it, and leaves the baseline alone so
|
|
630
|
+
// the next read re-checks.
|
|
631
|
+
if (cache && typeof cache.clear === 'function')
|
|
632
|
+
cache.clear();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (_cacheDataVersions.get(db) === version)
|
|
636
|
+
return;
|
|
637
|
+
if (cache && typeof cache.clear === 'function')
|
|
638
|
+
cache.clear();
|
|
639
|
+
_cacheDataVersions.set(db, version);
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
// Transient (e.g. SQLITE_BUSY): don't trust the cache for this read.
|
|
643
|
+
if (cache && typeof cache.clear === 'function')
|
|
644
|
+
cache.clear();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// Non-fatal.
|
|
649
|
+
}
|
|
650
|
+
}
|
|
568
651
|
/**
|
|
569
652
|
* Write to TieredCache after DB write.
|
|
570
653
|
*/
|
|
@@ -1323,6 +1406,7 @@ export async function bridgeGetEntry(options) {
|
|
|
1323
1406
|
const safeNs = String(namespace).replace(/:/g, '_');
|
|
1324
1407
|
const safeKey = String(key).replace(/:/g, '_');
|
|
1325
1408
|
const cacheKey = `entry:${safeNs}:${safeKey}`;
|
|
1409
|
+
dropCacheIfDbChangedElsewhere(registry, ctx);
|
|
1326
1410
|
const cached = await cacheGet(registry, cacheKey);
|
|
1327
1411
|
if (cached && cached.content) {
|
|
1328
1412
|
return {
|
|
@@ -296,6 +296,24 @@ function envMaxUncertainty() {
|
|
|
296
296
|
return undefined;
|
|
297
297
|
return n;
|
|
298
298
|
}
|
|
299
|
+
// Dream Cycle 2026-09-17 — env override for priorDecay, mirroring
|
|
300
|
+
// envMaxUncertainty() above. The discounted-Thompson-sampling primitive
|
|
301
|
+
// (see priorDecay's doc comment) was built, tested, and benchmarked in
|
|
302
|
+
// #3049 but shipped with no way to actually turn it on — DEFAULT_CONFIG
|
|
303
|
+
// hardcoded 1 (disabled) with no env/config knob, unlike every sibling
|
|
304
|
+
// tunable in this file. Parsed once at module load; invalid/out-of-range
|
|
305
|
+
// values fall through to the constructor's own priorDecay<=0||>1 guard,
|
|
306
|
+
// which already normalizes to 1 (disabled) — so an unset or malformed env
|
|
307
|
+
// var is fully backward compatible with existing deployments.
|
|
308
|
+
function envPriorDecay() {
|
|
309
|
+
const raw = process.env.CLAUDE_FLOW_PRIOR_DECAY;
|
|
310
|
+
if (!raw)
|
|
311
|
+
return undefined;
|
|
312
|
+
const n = Number(raw);
|
|
313
|
+
if (!Number.isFinite(n) || n <= 0 || n > 1)
|
|
314
|
+
return undefined;
|
|
315
|
+
return n;
|
|
316
|
+
}
|
|
299
317
|
const DEFAULT_CONFIG = {
|
|
300
318
|
confidenceThreshold: 0.85,
|
|
301
319
|
maxUncertainty: envMaxUncertainty() ?? 0.15,
|
|
@@ -305,7 +323,7 @@ const DEFAULT_CONFIG = {
|
|
|
305
323
|
autoSaveInterval: 1, // Save after every decision for CLI persistence
|
|
306
324
|
enableCostOptimization: true,
|
|
307
325
|
preferSpeed: true,
|
|
308
|
-
priorDecay: 1,
|
|
326
|
+
priorDecay: envPriorDecay() ?? 1,
|
|
309
327
|
};
|
|
310
328
|
// Beta shape parameters must stay well clear of 0 for sampleGamma/sampleBeta
|
|
311
329
|
// to remain numerically stable across a persisted, long-running state file —
|
|
@@ -43,7 +43,8 @@ import { FableHarness, } from './fable-harness.js';
|
|
|
43
43
|
// behavior mid-run (the #142 pin-drift failure mode). Pin it, and let
|
|
44
44
|
// scripts/check-metaharness-pins.mjs watch this constant for drift. Kept in
|
|
45
45
|
// lock-step with the optionalDependencies pin in package.json and the plugin
|
|
46
|
-
// darwin cache (versioned by the plugin's own `~0.
|
|
46
|
+
// darwin cache (versioned by the plugin's own `~0.10.2` pin in _darwin.mjs;
|
|
47
|
+
// both are checked by scripts/check-metaharness-pins.mjs — #3366).
|
|
47
48
|
export const MH_DARWIN_PIN = '0.10.2';
|
|
48
49
|
// ── Public API ───────────────────────────────────────────────────────────
|
|
49
50
|
/**
|
|
@@ -21,20 +21,17 @@
|
|
|
21
21
|
* of the resolved directory path (prefixed `dir:`-style via isGit=false), so
|
|
22
22
|
* callers never need a special case.
|
|
23
23
|
*/
|
|
24
|
-
import { execFileSync } from 'child_process';
|
|
25
24
|
import { createHash } from 'crypto';
|
|
26
25
|
import { resolve } from 'path';
|
|
27
26
|
import * as fs from 'fs';
|
|
27
|
+
import { safeGitTextSync } from '@claude-flow/security/safe-git';
|
|
28
28
|
const GIT_TIMEOUT_MS = 3000;
|
|
29
29
|
function git(cwd, ...args) {
|
|
30
30
|
try {
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
36
|
-
windowsHide: true,
|
|
37
|
-
}).trim();
|
|
31
|
+
return safeGitTextSync(cwd, args, {
|
|
32
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
33
|
+
stderr: 'ignore',
|
|
34
|
+
});
|
|
38
35
|
}
|
|
39
36
|
catch {
|
|
40
37
|
return null;
|