@claude-flow/cli 3.56.8 → 3.56.10
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-version +1 -1
- package/.claude/helpers/helpers.manifest.json +4 -4
- package/.claude/helpers/hook-handler.cjs +109 -23
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/helpers/router.cjs +0 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/autopilot-state.d.ts +2 -0
- package/dist/src/autopilot-state.js +36 -8
- package/dist/src/command-resolution.d.ts +15 -0
- package/dist/src/command-resolution.js +25 -0
- package/dist/src/commands/agent.js +17 -17
- package/dist/src/commands/analyze.js +40 -29
- package/dist/src/commands/claims.js +6 -8
- package/dist/src/commands/daemon.js +86 -31
- package/dist/src/commands/memory.js +14 -2
- package/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/dist/src/commands/security-code-patterns.js +61 -3
- package/dist/src/commands/security.d.ts +24 -0
- package/dist/src/commands/security.js +81 -35
- package/dist/src/commands/tui.d.ts +9 -0
- package/dist/src/commands/tui.js +29 -3
- package/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/dist/src/config/proven-config-refresh.js +24 -3
- package/dist/src/funnel/local-signals.js +2 -1
- package/dist/src/index.js +6 -42
- package/dist/src/init/helpers-generator.js +109 -23
- package/dist/src/init/types.js +1 -1
- package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/dist/src/mcp-tools/agent-tools.js +45 -22
- package/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/dist/src/mcp-tools/memory-tools.js +70 -20
- package/dist/src/mcp-tools/neural-tools.js +28 -4
- package/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/dist/src/mcp-tools/security-tools.js +55 -17
- package/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/dist/src/memory/intelligence.d.ts +5 -1
- package/dist/src/memory/intelligence.js +105 -29
- package/dist/src/memory/memory-bridge.js +10 -3
- package/dist/src/memory/memory-initializer.js +2 -1
- package/dist/src/memory/search-defaults.d.ts +54 -0
- package/dist/src/memory/search-defaults.js +37 -0
- package/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/dist/src/memory/sona-optimizer.js +59 -2
- package/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/dist/src/parser.js +32 -22
- package/dist/src/production/monitoring.d.ts +1 -0
- package/dist/src/production/monitoring.js +19 -6
- package/dist/src/production/rate-limiter.js +5 -4
- package/dist/src/production/retry.js +3 -0
- package/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/dist/src/ruvector/agent-wasm.js +27 -1
- package/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/dist/src/ruvector/diff-classifier.js +202 -69
- package/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/dist/src/services/config-file-manager.js +3 -3
- package/dist/src/services/policy-runtime.d.ts +1 -1
- package/dist/src/services/policy-runtime.js +7 -1
- package/dist/src/services/worker-daemon.d.ts +11 -0
- package/dist/src/services/worker-daemon.js +37 -0
- package/dist/src/transfer/store/discovery.js +16 -17
- package/dist/src/transfer/store/types.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +1 -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 +3 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
- package/node_modules/@claude-flow/codex/dist/index.js +1 -1
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/node_modules/@claude-flow/security/dist/policy/envelope.js +6 -6
- package/node_modules/@claude-flow/security/dist/policy/envelope.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-console/dist/tui.mjs +162 -160
- package/plugins/ruflo-metaharness/.claude-flow/daemon-state.json +179 -0
- package/plugins/ruflo-metaharness/.claude-flow/daemon.pid +1 -0
- package/plugins/ruflo-metaharness/.claude-flow/logs/daemon.log +32 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/backup.json +23 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/codebase-map.json +11 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/consolidation.json +16 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/harness-loop.json +91 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/performance.json +17 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/security-audit.json +12 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/test-gaps.json +8 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/patterns.json +2809 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +6 -0
- package/plugins/ruflo-metaharness/.claude-flow/policy/state.json +11 -0
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-shm +0 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-wal +0 -0
- package/plugins/ruflo-metaharness/.swarm/backups/memory-2026-10-14T16-09-38-164Z.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/backups/store-agentdb-memory.db-2026-10-14T16-09-38-166Z.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/ewc-fisher.json +397 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db-shm +0 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db-wal +0 -0
- package/plugins/ruflo-metaharness/CHANGELOG.md +3 -0
- package/plugins/ruflo-metaharness/agentdb.rvf +0 -0
- package/plugins/ruflo-metaharness/agentdb.rvf.lock +0 -0
- package/plugins/ruflo-metaharness/hooks/screen.ts +10 -3
- package/plugins/ruflo-metaharness/ruvector.db +0 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
|
@@ -14,6 +14,7 @@ import { homedir } from 'os';
|
|
|
14
14
|
import { join, resolve } from 'path';
|
|
15
15
|
import { createHash } from 'crypto';
|
|
16
16
|
import { validateIdentifier } from './validate-input.js';
|
|
17
|
+
import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.js';
|
|
17
18
|
// #1604: Align with memory-initializer.ts — single source of truth is .swarm/memory.db
|
|
18
19
|
const MEMORY_DIR = '.swarm';
|
|
19
20
|
const LEGACY_MEMORY_FILE = 'store.json';
|
|
@@ -231,6 +232,19 @@ async function getMemoryFunctions() {
|
|
|
231
232
|
checkMemoryInitialization,
|
|
232
233
|
};
|
|
233
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Path of the CLI's own store (.swarm/memory.db or CLAUDE_FLOW_DB_PATH), or
|
|
237
|
+
* undefined when the memory module cannot say (#4148). Never throws.
|
|
238
|
+
*/
|
|
239
|
+
async function resolveCliStorePath() {
|
|
240
|
+
try {
|
|
241
|
+
const mod = await import('../memory/memory-initializer.js');
|
|
242
|
+
return typeof mod.resolveDbPath === 'function' ? mod.resolveDbPath() : undefined;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
234
248
|
/**
|
|
235
249
|
* #2922: every `backend:` field in this file's tool responses used to be the
|
|
236
250
|
* hardcoded literal `'sql.js + HNSW'` regardless of which search path was
|
|
@@ -538,7 +552,7 @@ export const memoryTools = [
|
|
|
538
552
|
query: { type: 'string', description: 'Search query (semantic similarity)' },
|
|
539
553
|
namespace: { type: 'string', description: 'Namespace to search (default: all namespaces — omit to search across every namespace)' },
|
|
540
554
|
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.
|
|
555
|
+
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
556
|
smart: { type: 'boolean', description: 'Enable SmartRetrieval — query expansion, RRF fusion, recency boost, MMR diversity; preserves raw similarity and adds rankingScore (default: false)' },
|
|
543
557
|
provenance_filter: {
|
|
544
558
|
type: 'array',
|
|
@@ -567,7 +581,8 @@ export const memoryTools = [
|
|
|
567
581
|
// in the search layer actually fires.
|
|
568
582
|
const namespace = input.namespace;
|
|
569
583
|
const limit = input.limit ?? 10;
|
|
570
|
-
const threshold = input.threshold ??
|
|
584
|
+
const threshold = input.threshold ?? DEFAULT_SEARCH_THRESHOLD;
|
|
585
|
+
const thresholdWasDefault = input.threshold === undefined || input.threshold === null;
|
|
571
586
|
validateMemoryInput(undefined, undefined, query, namespace);
|
|
572
587
|
const startTime = performance.now();
|
|
573
588
|
try {
|
|
@@ -638,10 +653,14 @@ export const memoryTools = [
|
|
|
638
653
|
provenanceType: r.provenanceType,
|
|
639
654
|
};
|
|
640
655
|
});
|
|
656
|
+
const smartNearMisses = results.length === 0 && thresholdWasDefault
|
|
657
|
+
? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
|
|
658
|
+
: undefined;
|
|
641
659
|
return {
|
|
642
660
|
query,
|
|
643
661
|
results,
|
|
644
662
|
total: results.length,
|
|
663
|
+
...(smartNearMisses ?? {}),
|
|
645
664
|
searchTime: `${duration.toFixed(2)}ms`,
|
|
646
665
|
backend: 'SmartRetrieval (RRF + MMR + Recency)',
|
|
647
666
|
stats: smartResult.stats,
|
|
@@ -685,10 +704,14 @@ export const memoryTools = [
|
|
|
685
704
|
provenanceType: r.provenanceType,
|
|
686
705
|
};
|
|
687
706
|
});
|
|
707
|
+
const nearMissInfo = results.length === 0 && thresholdWasDefault
|
|
708
|
+
? await nearMisses(searchEntries, { query, namespace, provenanceFilter })
|
|
709
|
+
: undefined;
|
|
688
710
|
return {
|
|
689
711
|
query,
|
|
690
712
|
results,
|
|
691
713
|
total: results.length,
|
|
714
|
+
...(nearMissInfo ?? {}),
|
|
692
715
|
searchTime: `${duration.toFixed(2)}ms`,
|
|
693
716
|
backend: 'HNSW + sql.js',
|
|
694
717
|
...(smartFallbackReason ? { smartFallback: smartFallbackReason } : {}),
|
|
@@ -1497,25 +1520,50 @@ export const memoryTools = [
|
|
|
1497
1520
|
if (!v.valid)
|
|
1498
1521
|
throw new Error(v.error);
|
|
1499
1522
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1523
|
+
const readStore = async (dbPath) => {
|
|
1524
|
+
const out = [];
|
|
1525
|
+
let total;
|
|
1526
|
+
do {
|
|
1527
|
+
const page = await listEntries({ limit: 10000, offset: out.length, namespace, includeContent: true, ...(dbPath ? { dbPath } : {}) });
|
|
1528
|
+
if (!page.success)
|
|
1529
|
+
return { error: page.error || 'Could not read memory for export' };
|
|
1530
|
+
total ??= page.total;
|
|
1531
|
+
if (page.entries.length === 0 && out.length < total) {
|
|
1532
|
+
return { error: `Incomplete memory export: read ${out.length} of ${total} entries` };
|
|
1533
|
+
}
|
|
1534
|
+
out.push(...page.entries);
|
|
1535
|
+
} while (out.length < total);
|
|
1536
|
+
return { entries: out };
|
|
1537
|
+
};
|
|
1538
|
+
const primary = await readStore();
|
|
1539
|
+
if ('error' in primary)
|
|
1540
|
+
return { error: primary.error };
|
|
1541
|
+
// #4148: the default reader is the AgentDB bridge, but the CLI's own store
|
|
1542
|
+
// (.swarm/memory.db, possibly encrypted) can hold rows the bridge never
|
|
1543
|
+
// sees, so an export silently covered one store and reported success. Read
|
|
1544
|
+
// the CLI store too and merge, newest copy of a (namespace, key) winning.
|
|
1545
|
+
const warnings = [];
|
|
1546
|
+
const stores = [{ store: 'default', entries: primary.entries.length }];
|
|
1547
|
+
const merged = new Map();
|
|
1548
|
+
const keep = (e) => {
|
|
1549
|
+
const id = `${e.namespace}\u0000${e.key}`;
|
|
1550
|
+
const have = merged.get(id);
|
|
1551
|
+
if (!have || String(e.updatedAt) > String(have.updatedAt))
|
|
1552
|
+
merged.set(id, e);
|
|
1553
|
+
};
|
|
1554
|
+
primary.entries.forEach(keep);
|
|
1555
|
+
const cliStore = await resolveCliStorePath();
|
|
1556
|
+
if (cliStore && existsSync(cliStore)) {
|
|
1557
|
+
const secondary = await readStore(cliStore);
|
|
1558
|
+
if ('error' in secondary) {
|
|
1559
|
+
warnings.push(`Could not read the CLI store ${cliStore} (${secondary.error}); the export covers the AgentDB store only.`);
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
stores.push({ store: cliStore, entries: secondary.entries.length });
|
|
1563
|
+
secondary.entries.forEach(keep);
|
|
1515
1564
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
const all = { entries };
|
|
1565
|
+
}
|
|
1566
|
+
const all = { entries: [...merged.values()] };
|
|
1519
1567
|
const payload = {
|
|
1520
1568
|
schema: 'ruflo-memory-export/v1',
|
|
1521
1569
|
exportedAt: new Date().toISOString(),
|
|
@@ -1542,6 +1590,8 @@ export const memoryTools = [
|
|
|
1542
1590
|
outputPath,
|
|
1543
1591
|
format: input.format || 'json',
|
|
1544
1592
|
exported: { entries: all.entries.length, vectors: vectorsWithEmb, patterns: 0 },
|
|
1593
|
+
stores,
|
|
1594
|
+
...(warnings.length ? { warnings } : {}),
|
|
1545
1595
|
fileSize: `${Buffer.byteLength(JSON.stringify(payload))}B`,
|
|
1546
1596
|
note: input.format === 'csv' ? 'CSV not implemented yet — wrote JSON' : undefined,
|
|
1547
1597
|
};
|
|
@@ -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
|
},
|
|
@@ -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
|
};
|
|
@@ -12,6 +12,35 @@ import { executeAgentTask } from './agent-execute-core.js';
|
|
|
12
12
|
const STORAGE_DIR = '.claude-flow';
|
|
13
13
|
const WORKFLOW_DIR = 'workflows';
|
|
14
14
|
const WORKFLOW_FILE = 'store.json';
|
|
15
|
+
function parseCondition(when) {
|
|
16
|
+
if (typeof when !== 'string' || !when.trim()) {
|
|
17
|
+
return new Error('condition step requires a non-empty config.when');
|
|
18
|
+
}
|
|
19
|
+
const expression = when.trim();
|
|
20
|
+
if (expression === 'true' || expression === 'false') {
|
|
21
|
+
return { constant: expression === 'true' };
|
|
22
|
+
}
|
|
23
|
+
const match = expression.match(/^([a-zA-Z_]\w*)\s*===?\s*(?:'([^']*)'|"([^"]*)"|(-?\d+(?:\.\d+)?)|(true|false))$/);
|
|
24
|
+
if (!match) {
|
|
25
|
+
return new Error('condition config.when must compare a variable to a quoted string, number, or boolean using == or ===');
|
|
26
|
+
}
|
|
27
|
+
const expected = match[2] ?? match[3] ?? (match[4] !== undefined ? Number(match[4]) : match[5] === 'true');
|
|
28
|
+
return { variable: match[1], expected };
|
|
29
|
+
}
|
|
30
|
+
function validateExecutableStep(step) {
|
|
31
|
+
if (step.type === 'parallel' || step.type === 'loop') {
|
|
32
|
+
return `step type '${step.type}' is not implemented by the workflow runtime`;
|
|
33
|
+
}
|
|
34
|
+
if (step.type === 'condition') {
|
|
35
|
+
const parsed = parseCondition(step.config?.when);
|
|
36
|
+
if (parsed instanceof Error)
|
|
37
|
+
return parsed.message;
|
|
38
|
+
}
|
|
39
|
+
else if (step.type !== 'task' && step.type !== 'wait') {
|
|
40
|
+
return `unsupported workflow step type '${step.type}'`;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
15
44
|
function getWorkflowDir() {
|
|
16
45
|
return join(getProjectCwd(), STORAGE_DIR, WORKFLOW_DIR);
|
|
17
46
|
}
|
|
@@ -181,8 +210,8 @@ export const workflowTools = [
|
|
|
181
210
|
type: 'object',
|
|
182
211
|
properties: {
|
|
183
212
|
name: { type: 'string' },
|
|
184
|
-
type: { type: 'string', enum: ['task', 'condition', '
|
|
185
|
-
config: { type: 'object' },
|
|
213
|
+
type: { type: 'string', enum: ['task', 'condition', 'wait'] },
|
|
214
|
+
config: { type: 'object', description: 'Condition steps require config.when; parallel and loop steps are not yet supported' },
|
|
186
215
|
},
|
|
187
216
|
},
|
|
188
217
|
},
|
|
@@ -209,6 +238,11 @@ export const workflowTools = [
|
|
|
209
238
|
config: s.config || {},
|
|
210
239
|
status: 'pending',
|
|
211
240
|
}));
|
|
241
|
+
for (const step of steps) {
|
|
242
|
+
const error = validateExecutableStep(step);
|
|
243
|
+
if (error)
|
|
244
|
+
return { success: false, error: `${step.stepId} (${step.name}): ${error}` };
|
|
245
|
+
}
|
|
212
246
|
const workflow = {
|
|
213
247
|
workflowId,
|
|
214
248
|
name: input.name,
|
|
@@ -257,6 +291,20 @@ export const workflowTools = [
|
|
|
257
291
|
if (workflow.status === 'running') {
|
|
258
292
|
return { workflowId, error: 'Workflow already running' };
|
|
259
293
|
}
|
|
294
|
+
// Older stored workflows may predate creation-time validation. Reject
|
|
295
|
+
// unsupported steps before dispatching any task with side effects.
|
|
296
|
+
for (const step of workflow.steps) {
|
|
297
|
+
const error = validateExecutableStep(step);
|
|
298
|
+
if (error) {
|
|
299
|
+
workflow.status = 'failed';
|
|
300
|
+
workflow.error = `${step.stepId} (${step.name}): ${error}`;
|
|
301
|
+
step.status = 'failed';
|
|
302
|
+
step.result = { error: workflow.error };
|
|
303
|
+
workflow.completedAt = new Date().toISOString();
|
|
304
|
+
saveWorkflowStore(store);
|
|
305
|
+
return { workflowId, status: 'failed', failedStep: step.stepId, error: workflow.error };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
260
308
|
// Indices may name a step or the end boundary (used when resuming
|
|
261
309
|
// after the final step). Reject invalid control flow before any writes.
|
|
262
310
|
const validIndex = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= workflow.steps.length;
|
|
@@ -382,19 +430,14 @@ export const workflowTools = [
|
|
|
382
430
|
stepEntry = { stepId: step.stepId, type: 'wait', status: 'completed', durationMs: ms };
|
|
383
431
|
}
|
|
384
432
|
else if (step.type === 'condition') {
|
|
385
|
-
// Simple condition: config.when is a JS expression evaluated against workflow.variables.
|
|
386
|
-
// For safety, we only support `var === 'value'` or `var === number`.
|
|
387
433
|
const cfg = step.config;
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
else if (expr === 'true')
|
|
397
|
-
truthy = true;
|
|
434
|
+
const parsed = parseCondition(cfg.when);
|
|
435
|
+
if (parsed instanceof Error)
|
|
436
|
+
throw parsed;
|
|
437
|
+
const truthy = 'constant' in parsed
|
|
438
|
+
? parsed.constant
|
|
439
|
+
: workflow.variables[parsed.variable] === parsed.expected;
|
|
440
|
+
const expr = String(cfg.when).trim();
|
|
398
441
|
step.result = { conditionExpr: expr, truthy };
|
|
399
442
|
// condition can declare a target step index to jump to via cfg.thenStep / cfg.elseStep
|
|
400
443
|
if (typeof cfg.thenStep === 'number' && truthy)
|
|
@@ -404,9 +447,7 @@ export const workflowTools = [
|
|
|
404
447
|
stepEntry = { stepId: step.stepId, type: 'condition', status: 'completed' };
|
|
405
448
|
}
|
|
406
449
|
else {
|
|
407
|
-
|
|
408
|
-
step.result = { _note: `step type '${step.type}' not yet implemented in runtime` };
|
|
409
|
-
stepEntry = { stepId: step.stepId, type: step.type, status: 'skipped' };
|
|
450
|
+
throw new Error(`step type '${step.type}' is not implemented by the workflow runtime`);
|
|
410
451
|
}
|
|
411
452
|
step.status = stepEntry.status === 'skipped' ? 'skipped' : 'completed';
|
|
412
453
|
step.completedAt = new Date().toISOString();
|
|
@@ -906,6 +947,14 @@ export const workflowTools = [
|
|
|
906
947
|
const agentSet = new Set();
|
|
907
948
|
steps.forEach((s, i) => {
|
|
908
949
|
const step = (s ?? {});
|
|
950
|
+
if (typeof step.type === 'string') {
|
|
951
|
+
const error = validateExecutableStep({
|
|
952
|
+
type: step.type,
|
|
953
|
+
config: (step.config ?? {}),
|
|
954
|
+
});
|
|
955
|
+
if (error)
|
|
956
|
+
errors.push({ line: i + 1, message: `step ${i + 1}: ${error}`, severity: 'error' });
|
|
957
|
+
}
|
|
909
958
|
// Match workflow_execute: control steps do not dispatch agents,
|
|
910
959
|
// and task steps use config.agentId or the workflow default.
|
|
911
960
|
const config = step.config;
|
|
@@ -175,8 +175,12 @@ declare class LocalReasoningBank {
|
|
|
175
175
|
* Deduplicates by content: if a pattern with the same content already
|
|
176
176
|
* exists, the existing entry is updated (bumped usageCount, higher
|
|
177
177
|
* confidence wins, refreshed lastUsedAt) instead of adding a duplicate.
|
|
178
|
+
*
|
|
179
|
+
* Returns true only when a NEW pattern was added (#3979 item 4): callers that
|
|
180
|
+
* keep a "patterns learned" counter must not count a merge or an update of an
|
|
181
|
+
* existing pattern as a newly learned one.
|
|
178
182
|
*/
|
|
179
|
-
store(pattern: Omit<StoredPattern, 'usageCount' | 'createdAt' | 'lastUsedAt'> & Partial<StoredPattern>):
|
|
183
|
+
store(pattern: Omit<StoredPattern, 'usageCount' | 'createdAt' | 'lastUsedAt'> & Partial<StoredPattern>): boolean;
|
|
180
184
|
/**
|
|
181
185
|
* Find similar patterns by embedding.
|
|
182
186
|
*
|