claude-flow 3.56.9 → 3.57.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/.proven-config-version +1 -0
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/proven-config.json +42 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +17 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +99 -24
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +14 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.js +3 -1
- package/node_modules/@claude-flow/security/dist/policy/types.js.map +1 -1
- package/package.json +2 -2
- package/v3/@claude-flow/cli/README.md +164 -62
- package/v3/@claude-flow/cli/catalog-manifest.json +5 -5
- package/v3/@claude-flow/cli/dist/src/autopilot-state.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/autopilot-state.js +36 -8
- package/v3/@claude-flow/cli/dist/src/command-resolution.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/command-resolution.js +25 -0
- package/v3/@claude-flow/cli/dist/src/commands/agent.js +17 -17
- package/v3/@claude-flow/cli/dist/src/commands/claims.js +6 -8
- package/v3/@claude-flow/cli/dist/src/commands/daemon.js +94 -33
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +95 -18
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +14 -2
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +6 -1
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.js +61 -3
- package/v3/@claude-flow/cli/dist/src/commands/security.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +81 -35
- package/v3/@claude-flow/cli/dist/src/commands/tui.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/commands/tui.js +29 -3
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +24 -3
- package/v3/@claude-flow/cli/dist/src/funnel/local-signals.js +2 -1
- package/v3/@claude-flow/cli/dist/src/index.js +11 -47
- package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +4 -58
- package/v3/@claude-flow/cli/dist/src/init/types.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +45 -22
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/v3/@claude-flow/cli/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +100 -25
- package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +48 -54
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-tools.js +5 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/security-tools.js +55 -17
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.d.ts +5 -1
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +158 -35
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +10 -3
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +2 -1
- package/v3/@claude-flow/cli/dist/src/memory/search-defaults.d.ts +54 -0
- package/v3/@claude-flow/cli/dist/src/memory/search-defaults.js +37 -0
- package/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.js +59 -2
- package/v3/@claude-flow/cli/dist/src/memory/stats-file-lock.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/memory/stats-file-lock.js +53 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.js +27 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.js +202 -69
- package/v3/@claude-flow/cli/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +2 -2
- package/v3/@claude-flow/cli/dist/src/services/policy-anchor-log.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-anchor-log.js +242 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-archive-scan.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-archive-scan.js +50 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-anchor.d.ts +14 -8
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-anchor.js +356 -40
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-audit.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-audit.js +260 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-record.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-ledger-record.js +22 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +2 -1
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +267 -40
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +37 -0
- package/v3/@claude-flow/cli/dist/src/transfer/store/discovery.js +16 -17
- package/v3/@claude-flow/cli/dist/src/transfer/store/types.d.ts +4 -0
- package/v3/@claude-flow/cli/package.json +5 -5
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
|
|
13
13
|
import { homedir } from 'os';
|
|
14
|
-
import { join, resolve } from 'path';
|
|
14
|
+
import { basename, dirname, join, resolve } from 'path';
|
|
15
|
+
import { execFileSync } from 'node:child_process';
|
|
15
16
|
import { createHash } from 'crypto';
|
|
17
|
+
import { getProjectCwd } from './types.js';
|
|
16
18
|
import { validateIdentifier } from './validate-input.js';
|
|
19
|
+
import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.js';
|
|
17
20
|
// #1604: Align with memory-initializer.ts — single source of truth is .swarm/memory.db
|
|
18
21
|
const MEMORY_DIR = '.swarm';
|
|
19
22
|
const LEGACY_MEMORY_FILE = 'store.json';
|
|
@@ -145,7 +148,24 @@ function resolveProjectMemoryDir(claudeProjectsDir, projectPathOverride) {
|
|
|
145
148
|
sources.push(projectPathOverride);
|
|
146
149
|
}
|
|
147
150
|
else {
|
|
148
|
-
|
|
151
|
+
const project = getProjectCwd();
|
|
152
|
+
sources.push(project);
|
|
153
|
+
// Claude Code shares auto-memory across linked Git worktrees under the
|
|
154
|
+
// main checkout's project slug. Preserve the direct-path match first,
|
|
155
|
+
// then try the common Git directory's parent. Explicit overrides above
|
|
156
|
+
// remain authoritative, and non-Git projects need no Git dependency.
|
|
157
|
+
try {
|
|
158
|
+
const commonDir = execFileSync('git', ['rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
159
|
+
cwd: project,
|
|
160
|
+
encoding: 'utf-8',
|
|
161
|
+
timeout: 1000,
|
|
162
|
+
windowsHide: true,
|
|
163
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
164
|
+
}).trim();
|
|
165
|
+
if (basename(commonDir) === '.git')
|
|
166
|
+
sources.push(dirname(resolve(project, commonDir)));
|
|
167
|
+
}
|
|
168
|
+
catch { /* Git unavailable or not a repository: use the project path */ }
|
|
149
169
|
}
|
|
150
170
|
for (const source of sources) {
|
|
151
171
|
// Candidate 1: legacy POSIX hash — what shipped before #1883
|
|
@@ -231,6 +251,19 @@ async function getMemoryFunctions() {
|
|
|
231
251
|
checkMemoryInitialization,
|
|
232
252
|
};
|
|
233
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Path of the CLI's own store (.swarm/memory.db or CLAUDE_FLOW_DB_PATH), or
|
|
256
|
+
* undefined when the memory module cannot say (#4148). Never throws.
|
|
257
|
+
*/
|
|
258
|
+
async function resolveCliStorePath() {
|
|
259
|
+
try {
|
|
260
|
+
const mod = await import('../memory/memory-initializer.js');
|
|
261
|
+
return typeof mod.resolveDbPath === 'function' ? mod.resolveDbPath() : undefined;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
234
267
|
/**
|
|
235
268
|
* #2922: every `backend:` field in this file's tool responses used to be the
|
|
236
269
|
* hardcoded literal `'sql.js + HNSW'` regardless of which search path was
|
|
@@ -538,7 +571,7 @@ export const memoryTools = [
|
|
|
538
571
|
query: { type: 'string', description: 'Search query (semantic similarity)' },
|
|
539
572
|
namespace: { type: 'string', description: 'Namespace to search (default: all namespaces — omit to search across every namespace)' },
|
|
540
573
|
limit: { type: 'number', description: 'Maximum results (default: 10)' },
|
|
541
|
-
threshold: { type: 'number', description: 'Minimum raw retrieval relevance 0-1 for candidate admission, applied per query before SmartRetrieval ranking; not a floor on rankingScore (default: 0.
|
|
574
|
+
threshold: { type: 'number', description: 'Minimum raw retrieval relevance 0-1 for candidate admission, applied per query before SmartRetrieval ranking; not a floor on rankingScore (default: 0.2; calibrated for the bundled embedding model, pass 0 to see every match)' },
|
|
542
575
|
smart: { type: 'boolean', description: 'Enable SmartRetrieval — query expansion, RRF fusion, recency boost, MMR diversity; preserves raw similarity and adds rankingScore (default: false)' },
|
|
543
576
|
provenance_filter: {
|
|
544
577
|
type: 'array',
|
|
@@ -567,7 +600,8 @@ export const memoryTools = [
|
|
|
567
600
|
// in the search layer actually fires.
|
|
568
601
|
const namespace = input.namespace;
|
|
569
602
|
const limit = input.limit ?? 10;
|
|
570
|
-
const threshold = input.threshold ??
|
|
603
|
+
const threshold = input.threshold ?? DEFAULT_SEARCH_THRESHOLD;
|
|
604
|
+
const thresholdWasDefault = input.threshold === undefined || input.threshold === null;
|
|
571
605
|
validateMemoryInput(undefined, undefined, query, namespace);
|
|
572
606
|
const startTime = performance.now();
|
|
573
607
|
try {
|
|
@@ -638,10 +672,14 @@ export const memoryTools = [
|
|
|
638
672
|
provenanceType: r.provenanceType,
|
|
639
673
|
};
|
|
640
674
|
});
|
|
675
|
+
const smartNearMisses = results.length === 0 && thresholdWasDefault
|
|
676
|
+
? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
|
|
677
|
+
: undefined;
|
|
641
678
|
return {
|
|
642
679
|
query,
|
|
643
680
|
results,
|
|
644
681
|
total: results.length,
|
|
682
|
+
...(smartNearMisses ?? {}),
|
|
645
683
|
searchTime: `${duration.toFixed(2)}ms`,
|
|
646
684
|
backend: 'SmartRetrieval (RRF + MMR + Recency)',
|
|
647
685
|
stats: smartResult.stats,
|
|
@@ -685,10 +723,14 @@ export const memoryTools = [
|
|
|
685
723
|
provenanceType: r.provenanceType,
|
|
686
724
|
};
|
|
687
725
|
});
|
|
726
|
+
const nearMissInfo = results.length === 0 && thresholdWasDefault
|
|
727
|
+
? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
|
|
728
|
+
: undefined;
|
|
688
729
|
return {
|
|
689
730
|
query,
|
|
690
731
|
results,
|
|
691
732
|
total: results.length,
|
|
733
|
+
...(nearMissInfo ?? {}),
|
|
692
734
|
searchTime: `${duration.toFixed(2)}ms`,
|
|
693
735
|
backend: 'HNSW + sql.js',
|
|
694
736
|
...(smartFallbackReason ? { smartFallback: smartFallbackReason } : {}),
|
|
@@ -1043,6 +1085,7 @@ export const memoryTools = [
|
|
|
1043
1085
|
}
|
|
1044
1086
|
let imported = 0;
|
|
1045
1087
|
let skipped = 0;
|
|
1088
|
+
const importErrors = [];
|
|
1046
1089
|
// #1791.8 — Claude Code's `~/.claude/projects/` accumulates historical
|
|
1047
1090
|
// project_id directories (truncated forms, sandbox cwds, renamed
|
|
1048
1091
|
// workspaces) that all contain copies of the same memory files. The
|
|
@@ -1082,7 +1125,7 @@ export const memoryTools = [
|
|
|
1082
1125
|
// #1884 — sanitize key so memory_delete can later remove it. Without
|
|
1083
1126
|
// this, dangerous chars from frontmatter `name` strand the key.
|
|
1084
1127
|
const key = sanitizeMemoryKey(`claude:${memFile.project}:${name}`);
|
|
1085
|
-
const stored = await storeEntry({ key, value: body, namespace: ns, generateEmbeddingFlag: true });
|
|
1128
|
+
const stored = await storeEntry({ key, value: body, namespace: ns, generateEmbeddingFlag: true, upsert: true });
|
|
1086
1129
|
if (!stored.success)
|
|
1087
1130
|
throw new Error(stored.error || `Failed to store ${key}`);
|
|
1088
1131
|
imported++;
|
|
@@ -1097,15 +1140,19 @@ export const memoryTools = [
|
|
|
1097
1140
|
// #1884 — sanitize so any dangerous chars in the heading don't
|
|
1098
1141
|
// produce keys memory_delete will reject.
|
|
1099
1142
|
const key = sanitizeMemoryKey(`claude:${memFile.project}:${name}:${sectionTitle.slice(0, 50)}`);
|
|
1100
|
-
const stored = await storeEntry({ key, value: sectionBody, namespace: ns, generateEmbeddingFlag: true });
|
|
1143
|
+
const stored = await storeEntry({ key, value: sectionBody, namespace: ns, generateEmbeddingFlag: true, upsert: true });
|
|
1101
1144
|
if (!stored.success)
|
|
1102
1145
|
throw new Error(stored.error || `Failed to store ${key}`);
|
|
1103
1146
|
imported++;
|
|
1104
1147
|
}
|
|
1105
1148
|
}
|
|
1106
1149
|
}
|
|
1107
|
-
catch {
|
|
1150
|
+
catch (err) {
|
|
1108
1151
|
skipped++;
|
|
1152
|
+
// Say why a file was skipped instead of returning a bare failure (#4197).
|
|
1153
|
+
if (importErrors.length < 5) {
|
|
1154
|
+
importErrors.push(`${memFile.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1155
|
+
}
|
|
1109
1156
|
}
|
|
1110
1157
|
}
|
|
1111
1158
|
// AUDIT #3: report the embedding backend truthfully — a hash-fallback
|
|
@@ -1124,6 +1171,7 @@ export const memoryTools = [
|
|
|
1124
1171
|
skipped,
|
|
1125
1172
|
duplicatesSkipped,
|
|
1126
1173
|
excludedByPattern,
|
|
1174
|
+
...(importErrors.length > 0 ? { errors: importErrors } : {}),
|
|
1127
1175
|
files: memoryFiles.length,
|
|
1128
1176
|
projects: projects.size,
|
|
1129
1177
|
namespace: ns,
|
|
@@ -1497,25 +1545,50 @@ export const memoryTools = [
|
|
|
1497
1545
|
if (!v.valid)
|
|
1498
1546
|
throw new Error(v.error);
|
|
1499
1547
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1548
|
+
const readStore = async (dbPath) => {
|
|
1549
|
+
const out = [];
|
|
1550
|
+
let total;
|
|
1551
|
+
do {
|
|
1552
|
+
const page = await listEntries({ limit: 10000, offset: out.length, namespace, includeContent: true, ...(dbPath ? { dbPath } : {}) });
|
|
1553
|
+
if (!page.success)
|
|
1554
|
+
return { error: page.error || 'Could not read memory for export' };
|
|
1555
|
+
total ??= page.total;
|
|
1556
|
+
if (page.entries.length === 0 && out.length < total) {
|
|
1557
|
+
return { error: `Incomplete memory export: read ${out.length} of ${total} entries` };
|
|
1558
|
+
}
|
|
1559
|
+
out.push(...page.entries);
|
|
1560
|
+
} while (out.length < total);
|
|
1561
|
+
return { entries: out };
|
|
1562
|
+
};
|
|
1563
|
+
const primary = await readStore();
|
|
1564
|
+
if ('error' in primary)
|
|
1565
|
+
return { error: primary.error };
|
|
1566
|
+
// #4148: the default reader is the AgentDB bridge, but the CLI's own store
|
|
1567
|
+
// (.swarm/memory.db, possibly encrypted) can hold rows the bridge never
|
|
1568
|
+
// sees, so an export silently covered one store and reported success. Read
|
|
1569
|
+
// the CLI store too and merge, newest copy of a (namespace, key) winning.
|
|
1570
|
+
const warnings = [];
|
|
1571
|
+
const stores = [{ store: 'default', entries: primary.entries.length }];
|
|
1572
|
+
const merged = new Map();
|
|
1573
|
+
const keep = (e) => {
|
|
1574
|
+
const id = `${e.namespace}\u0000${e.key}`;
|
|
1575
|
+
const have = merged.get(id);
|
|
1576
|
+
if (!have || String(e.updatedAt) > String(have.updatedAt))
|
|
1577
|
+
merged.set(id, e);
|
|
1578
|
+
};
|
|
1579
|
+
primary.entries.forEach(keep);
|
|
1580
|
+
const cliStore = await resolveCliStorePath();
|
|
1581
|
+
if (cliStore && existsSync(cliStore)) {
|
|
1582
|
+
const secondary = await readStore(cliStore);
|
|
1583
|
+
if ('error' in secondary) {
|
|
1584
|
+
warnings.push(`Could not read the CLI store ${cliStore} (${secondary.error}); the export covers the AgentDB store only.`);
|
|
1515
1585
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1586
|
+
else {
|
|
1587
|
+
stores.push({ store: cliStore, entries: secondary.entries.length });
|
|
1588
|
+
secondary.entries.forEach(keep);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const all = { entries: [...merged.values()] };
|
|
1519
1592
|
const payload = {
|
|
1520
1593
|
schema: 'ruflo-memory-export/v1',
|
|
1521
1594
|
exportedAt: new Date().toISOString(),
|
|
@@ -1542,6 +1615,8 @@ export const memoryTools = [
|
|
|
1542
1615
|
outputPath,
|
|
1543
1616
|
format: input.format || 'json',
|
|
1544
1617
|
exported: { entries: all.entries.length, vectors: vectorsWithEmb, patterns: 0 },
|
|
1618
|
+
stores,
|
|
1619
|
+
...(warnings.length ? { warnings } : {}),
|
|
1545
1620
|
fileSize: `${Buffer.byteLength(JSON.stringify(payload))}B`,
|
|
1546
1621
|
note: input.format === 'csv' ? 'CSV not implemented yet — wrote JSON' : undefined,
|
|
1547
1622
|
};
|
|
@@ -399,7 +399,7 @@ export const metaharnessTools = [
|
|
|
399
399
|
},
|
|
400
400
|
// ───────────────────────────────────────────────────────────────────────
|
|
401
401
|
// ADR-153 — @metaharness/darwin integration (3 tools).
|
|
402
|
-
// Backed by the separate `@metaharness/darwin@~0.
|
|
402
|
+
// Backed by the separate `@metaharness/darwin@~0.12.0` npm package, NOT
|
|
403
403
|
// the umbrella `metaharness`. Plugin scripts shell out via _darwin.mjs.
|
|
404
404
|
// Same {success, data, degraded, exitCode} contract.
|
|
405
405
|
// ───────────────────────────────────────────────────────────────────────
|
|
@@ -452,8 +452,24 @@ export const neuralTools = [
|
|
|
452
452
|
patternsStored++;
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
|
+
// #4156: nothing stored means nothing was trained. Reporting success and "ready" made
|
|
456
|
+
// callers (the neural-train skill) believe the pattern store had been filled.
|
|
457
|
+
if (patternsStored === 0) {
|
|
458
|
+
model.status = 'untrained';
|
|
459
|
+
model.accuracy = 0;
|
|
460
|
+
saveNeuralStore(store);
|
|
461
|
+
return {
|
|
462
|
+
success: false,
|
|
463
|
+
status: 'empty',
|
|
464
|
+
modelId,
|
|
465
|
+
type: modelType,
|
|
466
|
+
patternsStored: 0,
|
|
467
|
+
totalPatterns: Object.keys(store.patterns).length,
|
|
468
|
+
error: 'neural_train stored nothing: pass `data` (an array of strings or {text, label} entries). It does not read trajectories, SONA or the ReasoningBank: those (hooks_intelligence_*) are a separate store that neural_predict and neural_patterns do not see.',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
455
471
|
model.status = 'ready';
|
|
456
|
-
model.accuracy =
|
|
472
|
+
model.accuracy = 1.0; // accuracy = data stored, not simulated
|
|
457
473
|
model.trainedAt = new Date().toISOString();
|
|
458
474
|
saveNeuralStore(store);
|
|
459
475
|
return {
|
|
@@ -484,6 +500,7 @@ export const neuralTools = [
|
|
|
484
500
|
modelId: { type: 'string', description: 'Model ID to use' },
|
|
485
501
|
input: { type: 'string', description: 'Input text or data' },
|
|
486
502
|
topK: { type: 'number', description: 'Number of top predictions' },
|
|
503
|
+
minSimilarity: { type: 'number', description: 'Minimum cosine similarity for a stored pattern to count as a match (default 0.5). A softmax over a single candidate is always 1.0, so without a floor any input matched the only stored pattern with confidence 1' },
|
|
487
504
|
},
|
|
488
505
|
required: ['input'],
|
|
489
506
|
},
|
|
@@ -502,6 +519,7 @@ export const neuralTools = [
|
|
|
502
519
|
const modelId = input.modelId;
|
|
503
520
|
const inputText = input.input;
|
|
504
521
|
const topK = input.topK || 3;
|
|
522
|
+
const minSimilarity = typeof input.minSimilarity === 'number' ? input.minSimilarity : 0.5;
|
|
505
523
|
// Find model or use default
|
|
506
524
|
const model = modelId ? store.models[modelId] : Object.values(store.models).find(m => m.status === 'ready');
|
|
507
525
|
if (model && model.status !== 'ready') {
|
|
@@ -519,9 +537,10 @@ export const neuralTools = [
|
|
|
519
537
|
// to 1, and we surface enough metadata to trust the result.
|
|
520
538
|
const storedPatterns = Object.values(store.patterns);
|
|
521
539
|
let predictions;
|
|
540
|
+
let bestSimilarity = null;
|
|
522
541
|
if (storedPatterns.length > 0) {
|
|
523
542
|
// Step 1: k-NN with cosine
|
|
524
|
-
|
|
543
|
+
let scored = storedPatterns
|
|
525
544
|
.map(p => {
|
|
526
545
|
const sim = cosineSimilarity(embedding, p.embedding);
|
|
527
546
|
return {
|
|
@@ -530,8 +549,10 @@ export const neuralTools = [
|
|
|
530
549
|
cosineSimilarity: sim,
|
|
531
550
|
};
|
|
532
551
|
})
|
|
533
|
-
.sort((a, b) => b.cosineSimilarity - a.cosineSimilarity)
|
|
534
|
-
|
|
552
|
+
.sort((a, b) => b.cosineSimilarity - a.cosineSimilarity);
|
|
553
|
+
bestSimilarity = scored[0]?.cosineSimilarity ?? null;
|
|
554
|
+
// #4156: only patterns that reach the similarity floor are candidates.
|
|
555
|
+
scored = scored.filter((c) => c.cosineSimilarity >= minSimilarity).slice(0, topK);
|
|
535
556
|
// Step 2: temperature-softmax over the top-K so confidence sums to 1.
|
|
536
557
|
// Temperature 0.1 sharpens differences between similar candidates.
|
|
537
558
|
const tau = 0.1;
|
|
@@ -569,8 +590,11 @@ export const neuralTools = [
|
|
|
569
590
|
embedding: embedding.slice(0, 8), // Preview of embedding
|
|
570
591
|
embeddingDims: embedding.length,
|
|
571
592
|
latency,
|
|
593
|
+
...(bestSimilarity !== null ? { bestSimilarity: Number(bestSimilarity.toFixed(4)), minSimilarity } : {}),
|
|
572
594
|
...(storedPatterns.length === 0 ? {
|
|
573
595
|
_note: 'No patterns stored. Train with neural_train(modelType, trainingData) before predicting.',
|
|
596
|
+
} : predictions.length === 0 ? {
|
|
597
|
+
_note: `No stored pattern reached the similarity floor (best ${bestSimilarity === null ? 'n/a' : bestSimilarity.toFixed(3)}, floor ${minSimilarity}); pass minSimilarity to lower it.`,
|
|
574
598
|
} : {}),
|
|
575
599
|
};
|
|
576
600
|
},
|
|
@@ -884,9 +908,6 @@ export const neuralTools = [
|
|
|
884
908
|
if (!v.valid)
|
|
885
909
|
return { success: false, error: v.error };
|
|
886
910
|
}
|
|
887
|
-
// Resolve provider so embeddingProvider in the result matches the old
|
|
888
|
-
// eager-init reporting.
|
|
889
|
-
await ensureEmbeddings();
|
|
890
911
|
const store = loadNeuralStore();
|
|
891
912
|
const method = input.method || 'quantize';
|
|
892
913
|
const targetReduction = input.targetSize || 0.5;
|
|
@@ -895,38 +916,25 @@ export const neuralTools = [
|
|
|
895
916
|
return { success: false, error: 'No patterns to compress. Train patterns first with neural_train.' };
|
|
896
917
|
}
|
|
897
918
|
const beforeCount = patterns.length;
|
|
898
|
-
const beforeSize = patterns.reduce((s, p) => s + (p.embedding?.length || 0) * 4, 0); // Float32 = 4 bytes
|
|
899
919
|
if (method === 'quantize') {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
916
|
-
saveNeuralStore(store);
|
|
917
|
-
return {
|
|
918
|
-
success: true, _real: true, method,
|
|
919
|
-
embeddingProvider: embeddingServiceName,
|
|
920
|
-
patternsCompressed: totalCompressed,
|
|
921
|
-
compressionRatio: '3.92x (Int8)',
|
|
922
|
-
beforeBytes: beforeSize,
|
|
923
|
-
afterBytes: Math.round(beforeSize / 3.92),
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
catch {
|
|
927
|
-
return { success: false, error: 'Quantization requires memory-initializer. Run `memory init` first.' };
|
|
928
|
-
}
|
|
920
|
+
// Neural-store readers consume `embedding` as a full-precision number[]
|
|
921
|
+
// (search, prediction, deduplication, and optimization). The former
|
|
922
|
+
// implementation discarded the Int8 vector, persisted only metadata,
|
|
923
|
+
// and reported a calculated 3.92x reduction. Refuse until the store has
|
|
924
|
+
// a persisted representation with a reader-side decode contract.
|
|
925
|
+
const storedBytes = readFileSync(getNeuralPath()).byteLength;
|
|
926
|
+
return {
|
|
927
|
+
success: false,
|
|
928
|
+
error: 'Quantized embeddings are not persisted: neural-store readers require full-precision embedding arrays.',
|
|
929
|
+
method,
|
|
930
|
+
patternsCompressed: 0,
|
|
931
|
+
beforeBytes: storedBytes,
|
|
932
|
+
afterBytes: storedBytes,
|
|
933
|
+
compressionRatio: '1.00x (unchanged JSON storage)',
|
|
934
|
+
};
|
|
929
935
|
}
|
|
936
|
+
// Resolve provider only for operations that actually use embeddings.
|
|
937
|
+
await ensureEmbeddings();
|
|
930
938
|
if (method === 'prune') {
|
|
931
939
|
// Prune patterns with low usage count below threshold (targetReduction as min usage)
|
|
932
940
|
const threshold = targetReduction;
|
|
@@ -1112,24 +1120,10 @@ export const neuralTools = [
|
|
|
1112
1120
|
if (duplicatesRemoved > 0)
|
|
1113
1121
|
actions.push(`Removed ${duplicatesRemoved} near-duplicate patterns`);
|
|
1114
1122
|
}
|
|
1115
|
-
// memory / balanced:
|
|
1123
|
+
// memory / balanced: report the unsupported persisted representation
|
|
1124
|
+
// instead of attaching metadata for a discarded Int8 vector.
|
|
1116
1125
|
if (target === 'memory' || target === 'balanced') {
|
|
1117
|
-
|
|
1118
|
-
const { quantizeInt8, getQuantizationStats } = await import('../memory/memory-initializer.js');
|
|
1119
|
-
for (const p of Object.values(store.patterns)) {
|
|
1120
|
-
if (p.embedding && p.embedding.length > 0 && !p._quantized) {
|
|
1121
|
-
const stats = getQuantizationStats(p.embedding);
|
|
1122
|
-
const q = quantizeInt8(p.embedding);
|
|
1123
|
-
p._quantized = { scale: q.scale, zeroPoint: q.zeroPoint, compressionRatio: stats.compressionRatio };
|
|
1124
|
-
patternsQuantized++;
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
if (patternsQuantized > 0)
|
|
1128
|
-
actions.push(`Quantized ${patternsQuantized} pattern embeddings (Int8, ~3.92x)`);
|
|
1129
|
-
}
|
|
1130
|
-
catch {
|
|
1131
|
-
actions.push('Quantization skipped (memory-initializer not available)');
|
|
1132
|
-
}
|
|
1126
|
+
actions.push('Quantization skipped: neural-store readers require persisted full-precision embedding arrays.');
|
|
1133
1127
|
}
|
|
1134
1128
|
// accuracy / balanced: prune low-usage, zero-embedding patterns
|
|
1135
1129
|
if (target === 'accuracy' || target === 'balanced') {
|
|
@@ -24,9 +24,11 @@ export const policyTools = [
|
|
|
24
24
|
category: 'security',
|
|
25
25
|
inputSchema: {
|
|
26
26
|
type: 'object',
|
|
27
|
-
properties: {
|
|
27
|
+
properties: {
|
|
28
|
+
full: { type: 'boolean', description: 'Also replay the archived receipts to verify the complete historical ledger, not just the retained hot tail (#3164)' },
|
|
29
|
+
},
|
|
28
30
|
},
|
|
29
|
-
handler: async (
|
|
31
|
+
handler: async (input, context) => {
|
|
30
32
|
const root = typeof context?.projectRoot === 'string' ? context.projectRoot : process.cwd();
|
|
31
33
|
const state = loadPolicyState(root);
|
|
32
34
|
return {
|
|
@@ -39,7 +41,7 @@ export const policyTools = [
|
|
|
39
41
|
approvals: state.approvals.length,
|
|
40
42
|
receipts: state.receipts.length,
|
|
41
43
|
},
|
|
42
|
-
ledger: await verifyPolicyLedger(root),
|
|
44
|
+
ledger: await verifyPolicyLedger(root, { full: input.full === true }),
|
|
43
45
|
};
|
|
44
46
|
},
|
|
45
47
|
},
|
|
@@ -43,7 +43,13 @@ export const ruvllmWasmTools = [
|
|
|
43
43
|
nativeBackend = {
|
|
44
44
|
available: iStats._ruvllmBackend === 'active',
|
|
45
45
|
coordinator: iStats._ruvllmBackend || 'unavailable',
|
|
46
|
+
// `trajectories` is the in-process SonaCoordinator buffer — it is
|
|
47
|
+
// 0 in any process that did not itself record (e.g. this MCP
|
|
48
|
+
// server while the daemon / hook processes do the recording).
|
|
46
49
|
trajectories: iStats._ruvllmTrajectories || 0,
|
|
50
|
+
// Project-persisted total across all processes
|
|
51
|
+
// (.claude-flow/neural/stats.json).
|
|
52
|
+
trajectoriesRecorded: iStats.trajectoriesRecorded || 0,
|
|
47
53
|
contrastiveTrainer: sStats._contrastiveTrainer !== 'unavailable' ? 'active' : 'unavailable',
|
|
48
54
|
trainingBackend: iStats._trainingBackend || 'unknown',
|
|
49
55
|
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { validateText, validateIdentifier } from './validate-input.js';
|
|
13
13
|
import { autoInstallPackage } from './auto-install.js';
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
|
+
import { findKnownMiss, recordKnownMiss } from './aidefence-known-misses.js';
|
|
15
16
|
// Create require for resolving module paths
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
// Lazy-loaded AIDefence instance
|
|
@@ -152,7 +153,8 @@ const aidefenceScanTool = {
|
|
|
152
153
|
piiPresent = !!defender.hasPII(input);
|
|
153
154
|
}
|
|
154
155
|
catch { /* hasPII unavailable */ }
|
|
155
|
-
const
|
|
156
|
+
const knownMiss = findKnownMiss(input);
|
|
157
|
+
const threatDetected = result.threat || piiPresent || !!knownMiss;
|
|
156
158
|
wrapperStats.quickScanCalls++;
|
|
157
159
|
if (threatDetected)
|
|
158
160
|
wrapperStats.quickScanThreats++;
|
|
@@ -164,24 +166,29 @@ const aidefenceScanTool = {
|
|
|
164
166
|
threatDetected,
|
|
165
167
|
confidence: result.confidence,
|
|
166
168
|
piiDetected: piiPresent,
|
|
167
|
-
promptInjectionDetected: result.threat,
|
|
169
|
+
promptInjectionDetected: result.threat || !!knownMiss,
|
|
170
|
+
...(knownMiss ? { knownMiss: { threatType: knownMiss.threatType, reportedAt: knownMiss.reportedAt } } : {}),
|
|
168
171
|
mode: 'quick',
|
|
169
172
|
}, null, 2),
|
|
170
173
|
}],
|
|
171
174
|
};
|
|
172
175
|
}
|
|
173
176
|
const result = await defender.detect(input);
|
|
177
|
+
const knownMiss = findKnownMiss(input);
|
|
174
178
|
return {
|
|
175
179
|
content: [{
|
|
176
180
|
type: 'text',
|
|
177
181
|
text: JSON.stringify({
|
|
178
|
-
safe: result.safe,
|
|
179
|
-
threats:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
safe: result.safe && !knownMiss,
|
|
183
|
+
threats: [
|
|
184
|
+
...result.threats.map(t => ({
|
|
185
|
+
type: t.type,
|
|
186
|
+
severity: t.severity,
|
|
187
|
+
confidence: t.confidence,
|
|
188
|
+
description: t.description,
|
|
189
|
+
})),
|
|
190
|
+
...(knownMiss ? [{ type: knownMiss.threatType, severity: 'high', confidence: 1, description: 'Matches an input previously reported as a missed threat (aidefence_learn)' }] : []),
|
|
191
|
+
],
|
|
185
192
|
piiFound: result.piiFound,
|
|
186
193
|
detectionTimeMs: result.detectionTimeMs,
|
|
187
194
|
}, null, 2),
|
|
@@ -398,11 +405,33 @@ const aidefenceLearnTool = {
|
|
|
398
405
|
const defender = await getAIDefence();
|
|
399
406
|
// Re-detect to get result for learning
|
|
400
407
|
const result = await defender.detect(input);
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
// A false negative (wasAccurate=false, nothing detected) gives
|
|
409
|
+
// learnFromDetection() no threat to store, so record the miss itself.
|
|
410
|
+
const falseNegative = !wasAccurate && result.threats.length === 0;
|
|
411
|
+
const missType = threatType || 'prompt_injection';
|
|
412
|
+
let learned = result.threats.length;
|
|
413
|
+
let newlyRecorded = false;
|
|
414
|
+
if (falseNegative) {
|
|
415
|
+
newlyRecorded = recordKnownMiss(input, missType);
|
|
416
|
+
if (newlyRecorded) {
|
|
417
|
+
const synthetic = {
|
|
418
|
+
...result,
|
|
419
|
+
safe: false,
|
|
420
|
+
threats: [{
|
|
421
|
+
type: missType,
|
|
422
|
+
severity: 'high',
|
|
423
|
+
confidence: 1,
|
|
424
|
+
pattern: 'user-reported-miss',
|
|
425
|
+
description: verdict || 'Reported by user as a missed threat',
|
|
426
|
+
}],
|
|
427
|
+
};
|
|
428
|
+
await defender.learnFromDetection(input, synthetic, { wasAccurate, userVerdict: verdict });
|
|
429
|
+
}
|
|
430
|
+
learned = 1;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
await defender.learnFromDetection(input, result, { wasAccurate, userVerdict: verdict });
|
|
434
|
+
}
|
|
406
435
|
// Record mitigation if provided
|
|
407
436
|
if (threatType && mitigationStrategy && mitigationSuccess !== undefined) {
|
|
408
437
|
await defender.recordMitigation(threatType, mitigationStrategy, mitigationSuccess);
|
|
@@ -412,7 +441,14 @@ const aidefenceLearnTool = {
|
|
|
412
441
|
type: 'text',
|
|
413
442
|
text: JSON.stringify({
|
|
414
443
|
success: true,
|
|
415
|
-
message:
|
|
444
|
+
message: falseNegative
|
|
445
|
+
? (newlyRecorded
|
|
446
|
+
? 'Missed threat recorded: this exact input (normalised for case/whitespace) will now be flagged by aidefence_scan and aidefence_is_safe. Detection is pattern-based and does not generalise from it.'
|
|
447
|
+
: 'This input was already recorded as a missed threat')
|
|
448
|
+
: (learned === 0
|
|
449
|
+
? 'Feedback noted, but no threat was detected so no pattern was learned. To teach a missed threat, pass wasAccurate=false.'
|
|
450
|
+
: 'Feedback recorded for pattern learning'),
|
|
451
|
+
learned,
|
|
416
452
|
learnedFrom: {
|
|
417
453
|
input: input.slice(0, 50) + (input.length > 50 ? '...' : ''),
|
|
418
454
|
wasAccurate,
|
|
@@ -468,7 +504,8 @@ const aidefenceIsSafeTool = {
|
|
|
468
504
|
piiPresent = !!defender.hasPII(input);
|
|
469
505
|
}
|
|
470
506
|
catch { /* hasPII unavailable */ }
|
|
471
|
-
const
|
|
507
|
+
const knownMiss = findKnownMiss(input);
|
|
508
|
+
const safe = promptSafe && !piiPresent && !knownMiss;
|
|
472
509
|
wrapperStats.isSafeCalls++;
|
|
473
510
|
if (!safe)
|
|
474
511
|
wrapperStats.isSafeUnsafeVerdicts++;
|
|
@@ -477,8 +514,9 @@ const aidefenceIsSafeTool = {
|
|
|
477
514
|
type: 'text',
|
|
478
515
|
text: JSON.stringify({
|
|
479
516
|
safe,
|
|
480
|
-
promptSafe,
|
|
517
|
+
promptSafe: promptSafe && !knownMiss,
|
|
481
518
|
piiPresent,
|
|
519
|
+
...(knownMiss ? { knownMiss: { threatType: knownMiss.threatType, reportedAt: knownMiss.reportedAt } } : {}),
|
|
482
520
|
}, null, 2),
|
|
483
521
|
}],
|
|
484
522
|
};
|