@claude-flow/cli 3.32.35 → 3.32.37
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 +5 -5
- package/.claude/helpers/hook-handler.cjs +30 -0
- package/.claude/helpers/intelligence.cjs +83 -5
- package/.claude/helpers/statusline.cjs +169 -6
- package/bin/cli.js +25 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/agent.js +1 -1
- package/dist/src/commands/doctor.js +159 -11
- package/dist/src/commands/hooks.js +39 -4
- package/dist/src/commands/init.js +52 -8
- package/dist/src/commands/mcp.js +1 -1
- package/dist/src/commands/memory-distill.js +1 -0
- package/dist/src/commands/swarm.js +28 -0
- package/dist/src/init/executor.js +45 -10
- package/dist/src/init/helpers-generator.js +15 -4
- package/dist/src/init/statusline-generator.js +18 -5
- package/dist/src/mcp-server.d.ts +25 -0
- package/dist/src/mcp-server.js +57 -2
- package/dist/src/mcp-tools/embeddings-tools.js +51 -10
- package/dist/src/mcp-tools/hooks-tools.js +34 -8
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/memory/memory-bridge.d.ts +12 -0
- package/dist/src/memory/memory-bridge.js +29 -6
- package/dist/src/memory/memory-initializer.js +11 -8
- package/dist/src/services/memory-distillation.d.ts +1 -0
- package/dist/src/services/memory-distillation.js +81 -5
- package/dist/src/services/worker-daemon.js +1 -0
- package/node_modules/@claude-flow/codex/.agents/skills/github-automation/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/.agents/skills/performance-analysis/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +20 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +18 -2
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts +11 -5
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +84 -849
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts +5 -0
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.js +54 -20
- package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
- package/package.json +1 -1
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +4 -2
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +5 -1
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +12 -13
- package/plugins/ruflo-metaharness/scripts/genome.mjs +29 -4
- package/plugins/ruflo-metaharness/scripts/mcp-scan.mjs +7 -8
- package/plugins/ruflo-metaharness/scripts/smoke.sh +58 -2
- package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +25 -1
- package/plugins/ruflo-metaharness/scripts/test-similarity.mjs +38 -0
- package/plugins/ruflo-metaharness/scripts/threat-model.mjs +4 -1
- package/plugins/ruflo-metaharness/skills/harness-genome/SKILL.md +5 -2
|
@@ -53,6 +53,16 @@ function getInstalledCliVersionLocal() {
|
|
|
53
53
|
return '0.0.0';
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
function compareVersionsLocal(a, b) {
|
|
57
|
+
const pa = a.split(/[.-]/).map(part => Number.parseInt(part, 10) || 0);
|
|
58
|
+
const pb = b.split(/[.-]/).map(part => Number.parseInt(part, 10) || 0);
|
|
59
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
60
|
+
const delta = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
61
|
+
if (delta !== 0)
|
|
62
|
+
return delta;
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
56
66
|
/**
|
|
57
67
|
* Generate optimized statusline script
|
|
58
68
|
* Output format:
|
|
@@ -91,10 +101,12 @@ export function generateStatuslineScript(options) {
|
|
|
91
101
|
// getInstalledCliVersionLocal() above — reuse the pattern for the
|
|
92
102
|
// same reason it exists there.
|
|
93
103
|
let helperContent = null;
|
|
104
|
+
let helperPackageRoot = '';
|
|
94
105
|
try {
|
|
95
106
|
const esmRequire = createRequire(import.meta.url);
|
|
96
107
|
const pkgJsonPath = esmRequire.resolve('@claude-flow/cli/package.json');
|
|
97
|
-
|
|
108
|
+
helperPackageRoot = path.dirname(pkgJsonPath);
|
|
109
|
+
const helperPath = path.join(helperPackageRoot, '.claude', 'helpers', 'statusline.cjs');
|
|
98
110
|
helperContent = fs.readFileSync(helperPath, 'utf-8');
|
|
99
111
|
}
|
|
100
112
|
catch {
|
|
@@ -105,6 +117,7 @@ export function generateStatuslineScript(options) {
|
|
|
105
117
|
if (pkg && pkg.name === '@claude-flow/cli') {
|
|
106
118
|
const candidate = path.join(dir, '.claude', 'helpers', 'statusline.cjs');
|
|
107
119
|
if (fs.existsSync(candidate)) {
|
|
120
|
+
helperPackageRoot = dir;
|
|
108
121
|
helperContent = fs.readFileSync(candidate, 'utf-8');
|
|
109
122
|
break;
|
|
110
123
|
}
|
|
@@ -128,15 +141,15 @@ export function generateStatuslineScript(options) {
|
|
|
128
141
|
// whatever the helper hard-codes) ships. Add a paired test in
|
|
129
142
|
// statusline-cost-display.test.ts before changing either token.
|
|
130
143
|
helperContent = helperContent.replace(/maxAgents: \d+,/, `maxAgents: ${maxAgents},`);
|
|
144
|
+
helperContent = helperContent.replace(/const BAKED_INSTALL_ROOT = "[^"]*";/, `const BAKED_INSTALL_ROOT = ${JSON.stringify(helperPackageRoot)};`);
|
|
131
145
|
// Only overwrite the helper's baked version if OURS resolves higher.
|
|
132
146
|
// Otherwise the substitution could DOWNGRADE (test environments where
|
|
133
147
|
// esmRequire.resolve happens to hit an older node_modules install would
|
|
134
|
-
// clobber a fresh committed helper).
|
|
135
|
-
//
|
|
136
|
-
// reliable at this stage of the version space.
|
|
148
|
+
// clobber a fresh committed helper). Compare numeric components:
|
|
149
|
+
// lexicographic comparison freezes 3.32.24 below 3.32.8 (#2811).
|
|
137
150
|
const helperVerMatch = helperContent.match(/let ver = "([^"]+)";/);
|
|
138
151
|
const helperVer = helperVerMatch ? helperVerMatch[1] : '';
|
|
139
|
-
if (!helperVer || bakedVersion >
|
|
152
|
+
if (!helperVer || compareVersionsLocal(bakedVersion, helperVer) > 0) {
|
|
140
153
|
helperContent = helperContent.replace(/let ver = "[^"]+";/, `let ver = ${JSON.stringify(bakedVersion)};`);
|
|
141
154
|
}
|
|
142
155
|
return helperContent;
|
package/dist/src/mcp-server.d.ts
CHANGED
|
@@ -48,6 +48,31 @@ export interface MCPServerStatus {
|
|
|
48
48
|
metrics?: Record<string, number>;
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
export declare function parseMcpToolSelection(value: string | undefined): string[] | 'all';
|
|
52
|
+
/**
|
|
53
|
+
* Apply the existing `--tools` contract to advertised schemas. A selector can
|
|
54
|
+
* be an exact tool name, a category, or a namespace prefix (`memory` matches
|
|
55
|
+
* `memory_store`). Execution remains registered internally; only the fixed
|
|
56
|
+
* per-request schema catalogue is reduced.
|
|
57
|
+
*/
|
|
58
|
+
export declare function filterAdvertisedMcpTools<T extends {
|
|
59
|
+
name: string;
|
|
60
|
+
category?: string;
|
|
61
|
+
}>(tools: T[], selection: string[] | 'all'): T[];
|
|
62
|
+
export interface McpSchemaOverhead {
|
|
63
|
+
toolCount: number;
|
|
64
|
+
bytes: number;
|
|
65
|
+
estimatedTokens: number;
|
|
66
|
+
contextWindowTokens?: number;
|
|
67
|
+
ratio?: number;
|
|
68
|
+
risk: 'normal' | 'high';
|
|
69
|
+
}
|
|
70
|
+
/** Conservative JSON-size estimate for the fixed tools/list catalogue. */
|
|
71
|
+
export declare function assessMcpSchemaOverhead(tools: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
inputSchema?: unknown;
|
|
75
|
+
}>, contextWindowTokens?: number): McpSchemaOverhead;
|
|
51
76
|
/**
|
|
52
77
|
* MCP Server Manager
|
|
53
78
|
*
|
package/dist/src/mcp-server.js
CHANGED
|
@@ -42,6 +42,54 @@ const DEFAULT_OPTIONS = {
|
|
|
42
42
|
daemonize: false,
|
|
43
43
|
timeout: 30000,
|
|
44
44
|
};
|
|
45
|
+
export function parseMcpToolSelection(value) {
|
|
46
|
+
if (!value || value.trim().toLowerCase() === 'all')
|
|
47
|
+
return 'all';
|
|
48
|
+
const selectors = value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
49
|
+
return selectors.length > 0 ? selectors : 'all';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Apply the existing `--tools` contract to advertised schemas. A selector can
|
|
53
|
+
* be an exact tool name, a category, or a namespace prefix (`memory` matches
|
|
54
|
+
* `memory_store`). Execution remains registered internally; only the fixed
|
|
55
|
+
* per-request schema catalogue is reduced.
|
|
56
|
+
*/
|
|
57
|
+
export function filterAdvertisedMcpTools(tools, selection) {
|
|
58
|
+
if (selection === 'all')
|
|
59
|
+
return tools;
|
|
60
|
+
const selectors = new Set(selection.map((item) => item.toLowerCase()));
|
|
61
|
+
return tools.filter((tool) => {
|
|
62
|
+
const name = tool.name.toLowerCase();
|
|
63
|
+
const category = tool.category?.toLowerCase();
|
|
64
|
+
return selectors.has(name)
|
|
65
|
+
|| (category !== undefined && selectors.has(category))
|
|
66
|
+
|| Array.from(selectors).some((selector) => name.startsWith(`${selector}_`));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Conservative JSON-size estimate for the fixed tools/list catalogue. */
|
|
70
|
+
export function assessMcpSchemaOverhead(tools, contextWindowTokens) {
|
|
71
|
+
const catalogue = tools.map((tool) => ({
|
|
72
|
+
name: tool.name,
|
|
73
|
+
description: tool.description,
|
|
74
|
+
inputSchema: tool.inputSchema,
|
|
75
|
+
}));
|
|
76
|
+
const bytes = Buffer.byteLength(JSON.stringify(catalogue), 'utf8');
|
|
77
|
+
const estimatedTokens = Math.ceil(bytes / 4);
|
|
78
|
+
const validWindow = Number.isFinite(contextWindowTokens) && Number(contextWindowTokens) > 0
|
|
79
|
+
? Number(contextWindowTokens)
|
|
80
|
+
: undefined;
|
|
81
|
+
const ratio = validWindow ? estimatedTokens / validWindow : undefined;
|
|
82
|
+
return {
|
|
83
|
+
toolCount: tools.length,
|
|
84
|
+
bytes,
|
|
85
|
+
estimatedTokens,
|
|
86
|
+
...(validWindow === undefined ? {} : { contextWindowTokens: validWindow }),
|
|
87
|
+
...(ratio === undefined ? {} : { ratio }),
|
|
88
|
+
risk: (ratio !== undefined ? ratio >= 0.2 : estimatedTokens >= 8_000)
|
|
89
|
+
? 'high'
|
|
90
|
+
: 'normal',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
45
93
|
/**
|
|
46
94
|
* MCP Server Manager
|
|
47
95
|
*
|
|
@@ -55,7 +103,14 @@ export class MCPServerManager extends EventEmitter {
|
|
|
55
103
|
healthCheckInterval;
|
|
56
104
|
constructor(options = {}) {
|
|
57
105
|
super();
|
|
58
|
-
|
|
106
|
+
// `options.tools`, populated by the `mcp start --tools` CLI flag, is
|
|
107
|
+
// spread last below and therefore takes precedence over this env fallback.
|
|
108
|
+
const environmentTools = parseMcpToolSelection(process.env.CLAUDE_FLOW_MCP_TOOLS);
|
|
109
|
+
this.options = {
|
|
110
|
+
...DEFAULT_OPTIONS,
|
|
111
|
+
...(environmentTools === 'all' ? {} : { tools: environmentTools }),
|
|
112
|
+
...options,
|
|
113
|
+
};
|
|
59
114
|
}
|
|
60
115
|
/**
|
|
61
116
|
* Start the MCP server
|
|
@@ -420,7 +475,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
420
475
|
},
|
|
421
476
|
};
|
|
422
477
|
case 'tools/list':
|
|
423
|
-
const tools = listMCPTools();
|
|
478
|
+
const tools = filterAdvertisedMcpTools(listMCPTools(), this.options.tools);
|
|
424
479
|
return {
|
|
425
480
|
jsonrpc: '2.0',
|
|
426
481
|
id: message.id,
|
|
@@ -50,13 +50,16 @@ async function getRealEmbeddingFunction() {
|
|
|
50
50
|
}
|
|
51
51
|
return realEmbeddingFn;
|
|
52
52
|
}
|
|
53
|
-
// Generate real ONNX embedding (falls back to deterministic hash if ONNX unavailable)
|
|
54
53
|
async function generateRealEmbedding(text, dimension) {
|
|
55
54
|
const realFn = await getRealEmbeddingFunction();
|
|
56
55
|
if (realFn) {
|
|
57
56
|
try {
|
|
58
57
|
const result = await realFn(text);
|
|
59
|
-
return
|
|
58
|
+
return {
|
|
59
|
+
embedding: result.embedding,
|
|
60
|
+
backend: result.backend ?? 'onnx',
|
|
61
|
+
model: result.model,
|
|
62
|
+
};
|
|
60
63
|
}
|
|
61
64
|
catch {
|
|
62
65
|
// Fall through to fallback
|
|
@@ -75,7 +78,11 @@ async function generateRealEmbedding(text, dimension) {
|
|
|
75
78
|
}
|
|
76
79
|
// L2 normalize
|
|
77
80
|
const norm = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0));
|
|
78
|
-
return
|
|
81
|
+
return {
|
|
82
|
+
embedding: embedding.map(x => x / norm),
|
|
83
|
+
backend: 'mock',
|
|
84
|
+
model: 'hash-fallback',
|
|
85
|
+
};
|
|
79
86
|
}
|
|
80
87
|
// Convert Euclidean embedding to Poincaré ball
|
|
81
88
|
function toPoincare(euclidean, curvature) {
|
|
@@ -238,7 +245,8 @@ export const embeddingsTools = [
|
|
|
238
245
|
}
|
|
239
246
|
const useHyperbolic = input.hyperbolic === true && config.hyperbolic.enabled;
|
|
240
247
|
// Generate real ONNX embedding
|
|
241
|
-
const
|
|
248
|
+
const generated = await generateRealEmbedding(text, config.dimension);
|
|
249
|
+
const embedding = generated.embedding;
|
|
242
250
|
let result;
|
|
243
251
|
let geometry;
|
|
244
252
|
if (useHyperbolic) {
|
|
@@ -254,6 +262,8 @@ export const embeddingsTools = [
|
|
|
254
262
|
embedding: result,
|
|
255
263
|
metadata: {
|
|
256
264
|
model: config.model,
|
|
265
|
+
embeddingBackend: generated.backend,
|
|
266
|
+
semanticGrounded: generated.backend === 'onnx',
|
|
257
267
|
dimension: config.dimension,
|
|
258
268
|
geometry,
|
|
259
269
|
curvature: useHyperbolic ? config.hyperbolic.curvature : null,
|
|
@@ -309,10 +319,13 @@ export const embeddingsTools = [
|
|
|
309
319
|
return { success: false, error: v.error };
|
|
310
320
|
}
|
|
311
321
|
// Generate real ONNX embeddings for both texts
|
|
312
|
-
const [
|
|
322
|
+
const [generated1, generated2] = await Promise.all([
|
|
313
323
|
generateRealEmbedding(text1, config.dimension),
|
|
314
324
|
generateRealEmbedding(text2, config.dimension)
|
|
315
325
|
]);
|
|
326
|
+
const emb1 = generated1.embedding;
|
|
327
|
+
const emb2 = generated2.embedding;
|
|
328
|
+
const embeddingBackend = generated1.backend === 'onnx' && generated2.backend === 'onnx' ? 'onnx' : 'mock';
|
|
316
329
|
let similarity;
|
|
317
330
|
let distance;
|
|
318
331
|
switch (metric) {
|
|
@@ -341,14 +354,21 @@ export const embeddingsTools = [
|
|
|
341
354
|
similarity,
|
|
342
355
|
distance,
|
|
343
356
|
metric,
|
|
357
|
+
embeddingBackend,
|
|
358
|
+
semanticGrounded: embeddingBackend === 'onnx',
|
|
344
359
|
texts: {
|
|
345
360
|
text1: { length: text1.length, preview: text1.slice(0, 50) },
|
|
346
361
|
text2: { length: text2.length, preview: text2.slice(0, 50) },
|
|
347
362
|
},
|
|
348
|
-
interpretation:
|
|
349
|
-
similarity > 0.
|
|
350
|
-
similarity > 0.
|
|
351
|
-
similarity > 0.
|
|
363
|
+
interpretation: embeddingBackend === 'onnx'
|
|
364
|
+
? similarity > 0.8 ? 'very similar'
|
|
365
|
+
: similarity > 0.6 ? 'similar'
|
|
366
|
+
: similarity > 0.4 ? 'somewhat similar'
|
|
367
|
+
: similarity > 0.2 ? 'different' : 'very different'
|
|
368
|
+
: null,
|
|
369
|
+
warning: embeddingBackend === 'mock'
|
|
370
|
+
? 'Hash fallback scores are deterministic but not semantically meaningful.'
|
|
371
|
+
: undefined,
|
|
352
372
|
};
|
|
353
373
|
},
|
|
354
374
|
},
|
|
@@ -426,6 +446,8 @@ export const embeddingsTools = [
|
|
|
426
446
|
})),
|
|
427
447
|
metadata: {
|
|
428
448
|
model: config.model,
|
|
449
|
+
embeddingBackend: queryEmbedding.backend,
|
|
450
|
+
semanticGrounded: queryEmbedding.backend === 'onnx',
|
|
429
451
|
topK,
|
|
430
452
|
threshold,
|
|
431
453
|
namespace: namespace || 'all',
|
|
@@ -433,6 +455,9 @@ export const embeddingsTools = [
|
|
|
433
455
|
indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
|
|
434
456
|
resultCount: searchResult.results.length
|
|
435
457
|
},
|
|
458
|
+
warning: queryEmbedding.backend === 'mock'
|
|
459
|
+
? 'Results use a hash fallback and are not semantically ranked.'
|
|
460
|
+
: undefined,
|
|
436
461
|
};
|
|
437
462
|
}
|
|
438
463
|
catch {
|
|
@@ -444,12 +469,17 @@ export const embeddingsTools = [
|
|
|
444
469
|
results: [],
|
|
445
470
|
metadata: {
|
|
446
471
|
model: config.model,
|
|
472
|
+
embeddingBackend: queryEmbedding.backend,
|
|
473
|
+
semanticGrounded: queryEmbedding.backend === 'onnx',
|
|
447
474
|
topK,
|
|
448
475
|
threshold,
|
|
449
476
|
namespace: namespace || 'all',
|
|
450
477
|
searchTime: `${searchTime}ms`,
|
|
451
478
|
indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
|
|
452
479
|
},
|
|
480
|
+
warning: queryEmbedding.backend === 'mock'
|
|
481
|
+
? 'Hash fallback is active; semantic search is unavailable.'
|
|
482
|
+
: undefined,
|
|
453
483
|
message: 'No embeddings indexed yet. Use memory store to add documents.',
|
|
454
484
|
};
|
|
455
485
|
}
|
|
@@ -795,6 +825,8 @@ export const embeddingsTools = [
|
|
|
795
825
|
}
|
|
796
826
|
catch { /* not installed */ }
|
|
797
827
|
const ruvectorEnabled = config.neural.ruvector?.enabled ?? false;
|
|
828
|
+
const backendProbe = await generateRealEmbedding('ruflo embedding backend probe', config.dimension);
|
|
829
|
+
const semanticGrounded = backendProbe.backend === 'onnx';
|
|
798
830
|
return {
|
|
799
831
|
success: true,
|
|
800
832
|
initialized: true,
|
|
@@ -822,11 +854,20 @@ export const embeddingsTools = [
|
|
|
822
854
|
models: config.modelPath,
|
|
823
855
|
},
|
|
824
856
|
initializedAt: config.initialized,
|
|
857
|
+
embeddingBackend: backendProbe.backend,
|
|
858
|
+
semanticGrounded,
|
|
859
|
+
warning: semanticGrounded
|
|
860
|
+
? undefined
|
|
861
|
+
: 'Hash fallback is active. Similarity scores are deterministic but not semantically meaningful.',
|
|
825
862
|
capabilities: {
|
|
826
863
|
onnxModels: ['Xenova/all-MiniLM-L6-v2', 'Xenova/all-mpnet-base-v2'],
|
|
827
864
|
geometries: ['euclidean', 'poincare'],
|
|
828
865
|
normalizations: ['L2', 'L1', 'minmax', 'zscore'],
|
|
829
|
-
features: [
|
|
866
|
+
features: [
|
|
867
|
+
...(semanticGrounded ? ['semantic search'] : []),
|
|
868
|
+
'hyperbolic projection',
|
|
869
|
+
'neural substrate',
|
|
870
|
+
],
|
|
830
871
|
},
|
|
831
872
|
};
|
|
832
873
|
},
|
|
@@ -439,6 +439,20 @@ function getIntelligenceStatsFromMemory() {
|
|
|
439
439
|
const category = e.metadata?.category || 'general';
|
|
440
440
|
categories[category] = (categories[category] || 0) + 1;
|
|
441
441
|
});
|
|
442
|
+
const successfulPatterns = patternEntries.filter(e => e.metadata?.success === true).length;
|
|
443
|
+
const failedPatterns = patternEntries.filter(e => e.metadata?.success === false).length;
|
|
444
|
+
const describedPatterns = patternEntries.filter(e => {
|
|
445
|
+
if (typeof e.metadata?.task === 'string' && e.metadata.task.trim())
|
|
446
|
+
return true;
|
|
447
|
+
try {
|
|
448
|
+
const value = typeof e.value === 'string' ? JSON.parse(e.value) : e.value;
|
|
449
|
+
return typeof value?.task === 'string' &&
|
|
450
|
+
Boolean(String(value.task).trim());
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}).length;
|
|
442
456
|
// Count routing decisions
|
|
443
457
|
const routingEntries = entries.filter(e => e.key.includes('routing') ||
|
|
444
458
|
e.metadata?.type === 'routing-decision');
|
|
@@ -472,6 +486,10 @@ function getIntelligenceStatsFromMemory() {
|
|
|
472
486
|
},
|
|
473
487
|
patterns: {
|
|
474
488
|
learned: patternEntries.length,
|
|
489
|
+
successful: successfulPatterns,
|
|
490
|
+
failed: failedPatterns,
|
|
491
|
+
unknown: Math.max(0, patternEntries.length - successfulPatterns - failedPatterns),
|
|
492
|
+
described: describedPatterns,
|
|
475
493
|
categories,
|
|
476
494
|
},
|
|
477
495
|
memory: {
|
|
@@ -1074,21 +1092,28 @@ export const hooksMetrics = {
|
|
|
1074
1092
|
agentCounts[o.agent] = (agentCounts[o.agent] || 0) + 1;
|
|
1075
1093
|
}
|
|
1076
1094
|
const topAgent = Object.entries(agentCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? null;
|
|
1077
|
-
const successful = stats.trajectories.successful;
|
|
1078
|
-
const total = stats.trajectories.total;
|
|
1079
|
-
const failed = Math.max(0, total - successful);
|
|
1080
1095
|
return {
|
|
1081
1096
|
_real: true,
|
|
1082
1097
|
_dataSource: 'intelligence-stats + routing-outcomes',
|
|
1083
1098
|
period,
|
|
1084
1099
|
patterns: {
|
|
1085
1100
|
total: stats.patterns.learned,
|
|
1086
|
-
successful,
|
|
1087
|
-
failed,
|
|
1101
|
+
successful: stats.patterns.successful,
|
|
1102
|
+
failed: stats.patterns.failed,
|
|
1103
|
+
unknown: stats.patterns.unknown,
|
|
1104
|
+
described: stats.patterns.described,
|
|
1105
|
+
descriptionCoverage: stats.patterns.learned > 0
|
|
1106
|
+
? stats.patterns.described / stats.patterns.learned
|
|
1107
|
+
: null,
|
|
1088
1108
|
avgConfidence: stats.routing.avgConfidence || null,
|
|
1089
1109
|
},
|
|
1090
1110
|
agents: {
|
|
1091
|
-
|
|
1111
|
+
// Confidence is a model self-score, not observed routing accuracy
|
|
1112
|
+
// (#2809). Keep the old key as an explicit null for compatibility
|
|
1113
|
+
// while exposing the truthful additive field.
|
|
1114
|
+
routingAccuracy: null,
|
|
1115
|
+
averageConfidence: stats.routing.avgConfidence || null,
|
|
1116
|
+
outcomeSuccessRate: successRate,
|
|
1092
1117
|
totalRoutes: stats.routing.decisions,
|
|
1093
1118
|
topAgent,
|
|
1094
1119
|
},
|
|
@@ -1097,7 +1122,7 @@ export const hooksMetrics = {
|
|
|
1097
1122
|
successRate,
|
|
1098
1123
|
avgRiskScore: null,
|
|
1099
1124
|
},
|
|
1100
|
-
_note:
|
|
1125
|
+
_note: stats.patterns.learned === 0 && totalCommands === 0
|
|
1101
1126
|
? 'No metrics data collected yet. Run hooks_post-task / hooks_intelligence_trajectory-end / hooks_route to populate.'
|
|
1102
1127
|
: undefined,
|
|
1103
1128
|
lastUpdated: new Date().toISOString(),
|
|
@@ -1311,6 +1336,7 @@ export const hooksPostTask = {
|
|
|
1311
1336
|
const bridge = await import('../memory/memory-bridge.js');
|
|
1312
1337
|
feedbackResult = await bridge.bridgeRecordFeedback({
|
|
1313
1338
|
taskId,
|
|
1339
|
+
task: params.task || undefined,
|
|
1314
1340
|
success,
|
|
1315
1341
|
quality,
|
|
1316
1342
|
agent,
|
|
@@ -3136,7 +3162,7 @@ export const hooksIntelligenceStats = {
|
|
|
3136
3162
|
catch {
|
|
3137
3163
|
memoryStats = {
|
|
3138
3164
|
trajectories: { total: 0, successful: 0 },
|
|
3139
|
-
patterns: { learned: 0, categories: {} },
|
|
3165
|
+
patterns: { learned: 0, successful: 0, failed: 0, unknown: 0, described: 0, categories: {} },
|
|
3140
3166
|
memory: { indexSize: 0, totalAccessCount: 0, memorySizeBytes: 0 },
|
|
3141
3167
|
routing: { decisions: 0, avgConfidence: 0 },
|
|
3142
3168
|
};
|
|
@@ -199,7 +199,7 @@ export const metaharnessTools = [
|
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
name: 'metaharness_genome',
|
|
202
|
-
description: 'ADR-150 — 7-section categorical readiness report from `metaharness genome <path>` (repo_type / agent_topology / risk_score / mcp_surface / test_confidence / publish_readiness). Use when you need the categorical view (vs numeric score). Pair with metaharness_score for the full readiness picture — score-alone is wrong because two harnesses with the same harnessFit can have very different agent_topology and mcp_surface. ' + MCP_SUCCESS_SEMANTIC,
|
|
202
|
+
description: 'ADR-150 — 7-section categorical readiness report from `metaharness genome <path>` (repo_type / agent_topology / risk_score / mcp_surface / test_confidence / publish_readiness). Upstream needs-work/blocked exit statuses are valid verdicts and return successfully as data.verdict + data.verdictExitCode; only a missing or malformed report is an error. Use when you need the categorical view (vs numeric score). Pair with metaharness_score for the full readiness picture — score-alone is wrong because two harnesses with the same harnessFit can have very different agent_topology and mcp_surface. ' + MCP_SUCCESS_SEMANTIC,
|
|
203
203
|
category: 'metaharness',
|
|
204
204
|
inputSchema: {
|
|
205
205
|
type: 'object',
|
|
@@ -275,6 +275,16 @@ export declare function getControllerRegistry(dbPath?: string): Promise<any | nu
|
|
|
275
275
|
* operator learns the cause instead of only the symptom.
|
|
276
276
|
*/
|
|
277
277
|
export declare function getBridgeFailureReason(): string | null;
|
|
278
|
+
/**
|
|
279
|
+
* Install a pre-initialized registry for deterministic bridge tests.
|
|
280
|
+
*
|
|
281
|
+
* The CLI test runner intentionally externalizes the optional
|
|
282
|
+
* `@claude-flow/memory` package so an unbuilt workspace can still exercise
|
|
283
|
+
* fallback paths. That also makes module-level mocking of ControllerRegistry
|
|
284
|
+
* environment-dependent. This narrow seam keeps native SQL regression tests
|
|
285
|
+
* independent of package build order without changing production startup.
|
|
286
|
+
*/
|
|
287
|
+
export declare function __setMemoryBridgeRegistryForTests(registry: any | null): void;
|
|
278
288
|
/**
|
|
279
289
|
* Shutdown the bridge and release resources.
|
|
280
290
|
*
|
|
@@ -322,6 +332,8 @@ export declare function bridgeSearchPatterns(options: {
|
|
|
322
332
|
*/
|
|
323
333
|
export declare function bridgeRecordFeedback(options: {
|
|
324
334
|
taskId: string;
|
|
335
|
+
/** Human-readable task text used as the semantic learning signal. */
|
|
336
|
+
task?: string;
|
|
325
337
|
success: boolean;
|
|
326
338
|
quality: number;
|
|
327
339
|
agent?: string;
|
|
@@ -23,6 +23,10 @@ import { createRequire } from 'node:module';
|
|
|
23
23
|
let registryPromise = null;
|
|
24
24
|
let registryInstance = null;
|
|
25
25
|
let bridgeAvailable = null;
|
|
26
|
+
// #2652/#2120: rows created before the status column existed receive NULL
|
|
27
|
+
// during migration. They are live rows, not tombstones. Every user-facing
|
|
28
|
+
// read/delete path must agree with list() about their visibility.
|
|
29
|
+
const ACTIVE_MEMORY_ROW_SQL = `(status = 'active' OR status IS NULL)`;
|
|
26
30
|
/**
|
|
27
31
|
* Why the bridge is unavailable, when it is.
|
|
28
32
|
*
|
|
@@ -994,7 +998,7 @@ export async function bridgeSearchEntries(options) {
|
|
|
994
998
|
const stmt = ctx.db.prepare(`
|
|
995
999
|
SELECT id, key, namespace, content, embedding, provenance_type
|
|
996
1000
|
FROM memory_entries
|
|
997
|
-
WHERE
|
|
1001
|
+
WHERE ${ACTIVE_MEMORY_ROW_SQL} ${whereExtra}
|
|
998
1002
|
LIMIT 1000
|
|
999
1003
|
`);
|
|
1000
1004
|
rows = filterParams.length > 0 ? stmt.all(...filterParams) : stmt.all();
|
|
@@ -1114,7 +1118,7 @@ export async function bridgeListEntries(options) {
|
|
|
1114
1118
|
// the `status = 'active'` filter matched zero. Treat NULL as
|
|
1115
1119
|
// "legacy-active" — the safe default for any entry that predates the
|
|
1116
1120
|
// status column.
|
|
1117
|
-
const statusFilter =
|
|
1121
|
+
const statusFilter = ACTIVE_MEMORY_ROW_SQL;
|
|
1118
1122
|
// Count
|
|
1119
1123
|
let total = 0;
|
|
1120
1124
|
try {
|
|
@@ -1206,7 +1210,7 @@ export async function bridgeGetEntry(options) {
|
|
|
1206
1210
|
const stmt = ctx.db.prepare(`
|
|
1207
1211
|
SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, tags
|
|
1208
1212
|
FROM memory_entries
|
|
1209
|
-
WHERE
|
|
1213
|
+
WHERE ${ACTIVE_MEMORY_ROW_SQL} AND key = ? AND namespace = ?
|
|
1210
1214
|
LIMIT 1
|
|
1211
1215
|
`);
|
|
1212
1216
|
row = stmt.get(key, namespace);
|
|
@@ -1274,7 +1278,7 @@ export async function bridgeDeleteEntry(options) {
|
|
|
1274
1278
|
const result = ctx.db.prepare(`
|
|
1275
1279
|
UPDATE memory_entries
|
|
1276
1280
|
SET status = 'deleted', updated_at = ?
|
|
1277
|
-
WHERE key = ? AND namespace = ? AND
|
|
1281
|
+
WHERE key = ? AND namespace = ? AND ${ACTIVE_MEMORY_ROW_SQL}
|
|
1278
1282
|
`).run(Date.now(), key, namespace);
|
|
1279
1283
|
changes = result?.changes ?? 0;
|
|
1280
1284
|
}
|
|
@@ -1306,7 +1310,7 @@ export async function bridgeDeleteEntry(options) {
|
|
|
1306
1310
|
}
|
|
1307
1311
|
let remaining = 0;
|
|
1308
1312
|
try {
|
|
1309
|
-
const row = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE
|
|
1313
|
+
const row = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE ${ACTIVE_MEMORY_ROW_SQL}`).get();
|
|
1310
1314
|
remaining = row?.cnt ?? 0;
|
|
1311
1315
|
}
|
|
1312
1316
|
catch {
|
|
@@ -1659,6 +1663,21 @@ export async function getControllerRegistry(dbPath) {
|
|
|
1659
1663
|
export function getBridgeFailureReason() {
|
|
1660
1664
|
return bridgeFailureReason;
|
|
1661
1665
|
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Install a pre-initialized registry for deterministic bridge tests.
|
|
1668
|
+
*
|
|
1669
|
+
* The CLI test runner intentionally externalizes the optional
|
|
1670
|
+
* `@claude-flow/memory` package so an unbuilt workspace can still exercise
|
|
1671
|
+
* fallback paths. That also makes module-level mocking of ControllerRegistry
|
|
1672
|
+
* environment-dependent. This narrow seam keeps native SQL regression tests
|
|
1673
|
+
* independent of package build order without changing production startup.
|
|
1674
|
+
*/
|
|
1675
|
+
export function __setMemoryBridgeRegistryForTests(registry) {
|
|
1676
|
+
registryInstance = registry;
|
|
1677
|
+
registryPromise = registry ? Promise.resolve(registry) : null;
|
|
1678
|
+
bridgeAvailable = registry ? true : null;
|
|
1679
|
+
bridgeFailureReason = null;
|
|
1680
|
+
}
|
|
1662
1681
|
/**
|
|
1663
1682
|
* Shutdown the bridge and release resources.
|
|
1664
1683
|
*
|
|
@@ -1840,11 +1859,15 @@ export async function bridgeRecordFeedback(options) {
|
|
|
1840
1859
|
try {
|
|
1841
1860
|
const intelligence = await import('./intelligence.js');
|
|
1842
1861
|
const verdict = options.success ? 'success' : 'failure';
|
|
1862
|
+
const taskContext = options.task?.trim();
|
|
1843
1863
|
const recorded = await intelligence.recordTrajectory([{
|
|
1844
1864
|
type: 'action',
|
|
1845
|
-
|
|
1865
|
+
// Put the task description first so the embedding represents the
|
|
1866
|
+
// work, not just the bookkeeping suffix (#2812).
|
|
1867
|
+
content: `${taskContext ? `${taskContext} — ` : ''}Task ${options.taskId} completed by ${options.agent || 'unknown'} — success=${options.success}, quality=${options.quality.toFixed(3)}`,
|
|
1846
1868
|
metadata: {
|
|
1847
1869
|
taskId: options.taskId,
|
|
1870
|
+
task: taskContext,
|
|
1848
1871
|
agent: options.agent,
|
|
1849
1872
|
quality: options.quality,
|
|
1850
1873
|
duration: options.duration,
|
|
@@ -153,6 +153,10 @@ export function resolveDbPath(cliFlag) {
|
|
|
153
153
|
}
|
|
154
154
|
return path.join(getMemoryRoot(), 'memory.db');
|
|
155
155
|
}
|
|
156
|
+
// #2652/#2120: legacy rows with NULL status predate soft-delete semantics and
|
|
157
|
+
// are active. Keep fallback retrieve/delete aligned with list and the native
|
|
158
|
+
// bridge instead of making a row visible to one command but not another.
|
|
159
|
+
const ACTIVE_MEMORY_ROW_SQL = `(status = 'active' OR status IS NULL)`;
|
|
156
160
|
// ADR-053: Lazy import of AgentDB v3 bridge
|
|
157
161
|
let _bridge;
|
|
158
162
|
async function getBridge() {
|
|
@@ -1608,8 +1612,7 @@ export async function repairVectorIndexes(dbPath, opts = {}) {
|
|
|
1608
1612
|
*/
|
|
1609
1613
|
export async function initializeMemoryDatabase(options) {
|
|
1610
1614
|
const { backend = 'hybrid', dbPath: customPath, force = false, verbose = false, migrate = true } = options;
|
|
1611
|
-
const
|
|
1612
|
-
const dbPath = customPath || path.join(swarmDir, 'memory.db');
|
|
1615
|
+
const dbPath = resolveDbPath(customPath);
|
|
1613
1616
|
const dbDir = path.dirname(dbPath);
|
|
1614
1617
|
try {
|
|
1615
1618
|
// Create directory if needed
|
|
@@ -2857,7 +2860,7 @@ export async function listEntries(options) {
|
|
|
2857
2860
|
// that predate the status column may have NULL after migration.
|
|
2858
2861
|
// See memory-bridge.ts:bridgeListEntries for full context.
|
|
2859
2862
|
// Get total count
|
|
2860
|
-
const whereClauses = [
|
|
2863
|
+
const whereClauses = [ACTIVE_MEMORY_ROW_SQL];
|
|
2861
2864
|
const whereParams = [];
|
|
2862
2865
|
if (namespace) {
|
|
2863
2866
|
whereClauses.push('namespace = ?');
|
|
@@ -2967,7 +2970,7 @@ export async function getEntry(options) {
|
|
|
2967
2970
|
const getStmt = db.prepare(`
|
|
2968
2971
|
SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, tags
|
|
2969
2972
|
FROM memory_entries
|
|
2970
|
-
WHERE
|
|
2973
|
+
WHERE ${ACTIVE_MEMORY_ROW_SQL}
|
|
2971
2974
|
AND key = ?
|
|
2972
2975
|
AND namespace = ?
|
|
2973
2976
|
LIMIT 1
|
|
@@ -3081,7 +3084,7 @@ export async function deleteEntry(options) {
|
|
|
3081
3084
|
// Check if entry exists first
|
|
3082
3085
|
const checkStmt = db.prepare(`
|
|
3083
3086
|
SELECT id FROM memory_entries
|
|
3084
|
-
WHERE
|
|
3087
|
+
WHERE ${ACTIVE_MEMORY_ROW_SQL}
|
|
3085
3088
|
AND key = ?
|
|
3086
3089
|
AND namespace = ?
|
|
3087
3090
|
LIMIT 1
|
|
@@ -3095,7 +3098,7 @@ export async function deleteEntry(options) {
|
|
|
3095
3098
|
const checkResult = checkRows.length > 0 ? [{ values: checkRows }] : [];
|
|
3096
3099
|
if (!checkResult[0]?.values?.[0]) {
|
|
3097
3100
|
// Get remaining count before closing
|
|
3098
|
-
const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE
|
|
3101
|
+
const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE ${ACTIVE_MEMORY_ROW_SQL}`);
|
|
3099
3102
|
const remainingEntries = countResult[0]?.values?.[0]?.[0] || 0;
|
|
3100
3103
|
db.close();
|
|
3101
3104
|
return {
|
|
@@ -3116,10 +3119,10 @@ export async function deleteEntry(options) {
|
|
|
3116
3119
|
updated_at = strftime('%s', 'now') * 1000
|
|
3117
3120
|
WHERE key = ?
|
|
3118
3121
|
AND namespace = ?
|
|
3119
|
-
AND
|
|
3122
|
+
AND ${ACTIVE_MEMORY_ROW_SQL}
|
|
3120
3123
|
`, [key, namespace]);
|
|
3121
3124
|
// Get remaining count
|
|
3122
|
-
const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE
|
|
3125
|
+
const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE ${ACTIVE_MEMORY_ROW_SQL}`);
|
|
3123
3126
|
const remainingEntries = countResult[0]?.values?.[0]?.[0] || 0;
|
|
3124
3127
|
// Save updated database
|
|
3125
3128
|
const data = db.export();
|