claude-flow 3.47.1 → 3.49.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/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/hook-handler.cjs +7 -4
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/memory.cjs +1 -1
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/.claude/helpers/router.cjs +1 -1
- package/.claude/helpers/session.cjs +1 -1
- package/.claude/proven-config.json +42 -0
- package/.claude-plugin/marketplace.json +16 -1
- package/README.md +1 -53
- package/README.zh-CN.md +1 -53
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +7 -7
- package/v3/@claude-flow/cli/README.md +1 -53
- package/v3/@claude-flow/cli/bin/cli.js +4 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
- package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +69 -8
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +22 -9
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +119 -34
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
- package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
- package/v3/@claude-flow/cli/dist/src/commands/session.js +132 -25
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
- package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
- package/v3/@claude-flow/cli/dist/src/index.js +10 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +14 -12
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -7
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +226 -51
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +131 -40
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +270 -58
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +47 -34
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
- package/v3/@claude-flow/cli/package.json +9 -9
- package/v3/@claude-flow/guidance/package.json +5 -6
- package/v3/@claude-flow/shared/package.json +6 -3
|
@@ -9,6 +9,10 @@ import { distillCommand } from './memory-distill.js';
|
|
|
9
9
|
import { backupCommand } from './memory-backup.js';
|
|
10
10
|
import { countSiblingStoreRows } from '../memory/sibling-store.js';
|
|
11
11
|
import { resolveDbPath } from '../memory/memory-initializer.js';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { siblingAgentDbPath } from '../memory/memory-bridge.js';
|
|
14
|
+
import { validateIdentifier } from '../mcp-tools/validate-input.js';
|
|
15
|
+
import { memoryKeyError } from '../mcp-tools/memory-tools.js';
|
|
12
16
|
/**
|
|
13
17
|
* #3228: a miss in one store is not a miss in the memory.
|
|
14
18
|
*
|
|
@@ -19,16 +23,39 @@ import { resolveDbPath } from '../memory/memory-initializer.js';
|
|
|
19
23
|
* `found:false`. A confident negative is worse than an error, because nothing
|
|
20
24
|
* prompts anyone to look further.
|
|
21
25
|
*/
|
|
22
|
-
async function warnIfSiblingHasRows(pathFlag) {
|
|
26
|
+
async function warnIfSiblingHasRows(pathFlag, hits) {
|
|
23
27
|
const unread = await countSiblingStoreRows(resolveDbPath(pathFlag));
|
|
24
28
|
if (unread && unread.rows > 0) {
|
|
25
|
-
|
|
29
|
+
// #3566: disclose on hits too. A partial positive ("Found 1 results") invites
|
|
30
|
+
// no second look, so it is the more dangerous case, not the safer one.
|
|
31
|
+
const lead = hits === undefined
|
|
32
|
+
? 'This read covered one store.'
|
|
33
|
+
: `Partial result: ${hits} ${hits === 1 ? 'match' : 'matches'} came from the store read here.`;
|
|
34
|
+
output.printWarning(`${lead} ${unread.rows} entries are in ${unread.path} and were not searched. ` +
|
|
26
35
|
`That store is written by the MCP/AgentDB path; read it with --path ${unread.path}.`);
|
|
27
36
|
}
|
|
28
37
|
}
|
|
38
|
+
/** Default CLI writes may be mirrored into AgentDB; remove both copies. */
|
|
39
|
+
function removalDbTargets(pathFlag) {
|
|
40
|
+
const primary = resolveDbPath(pathFlag);
|
|
41
|
+
// An explicit file selection is a single-store operation, as documented by
|
|
42
|
+
// --path. Do not widen a caller's requested destructive scope.
|
|
43
|
+
if (pathFlag || process.env.CLAUDE_FLOW_DB_PATH)
|
|
44
|
+
return [{ dbPath: primary }];
|
|
45
|
+
const sibling = siblingAgentDbPath(primary);
|
|
46
|
+
// The AgentDB mirror is consumed by native SQLite and must stay plaintext.
|
|
47
|
+
// Pass this per operation, including preview migrations; never toggle the
|
|
48
|
+
// process encryption setting while asynchronous operations are in flight.
|
|
49
|
+
const targets = [
|
|
50
|
+
{ dbPath: primary },
|
|
51
|
+
...(sibling ? [{ dbPath: sibling, encryptWrites: false }] : []),
|
|
52
|
+
].filter(target => existsSync(target.dbPath));
|
|
53
|
+
// Preserve the existing missing-database error when neither store exists.
|
|
54
|
+
return targets.length ? targets : [{ dbPath: primary }];
|
|
55
|
+
}
|
|
29
56
|
// Memory backends
|
|
30
57
|
const BACKENDS = [
|
|
31
|
-
{ value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing
|
|
58
|
+
{ value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing' },
|
|
32
59
|
{ value: 'sqlite', label: 'SQLite', hint: 'Lightweight local storage' },
|
|
33
60
|
{ value: 'hybrid', label: 'Hybrid', hint: 'SQLite + AgentDB (recommended)' },
|
|
34
61
|
{ value: 'memory', label: 'In-Memory', hint: 'Fast but non-persistent' }
|
|
@@ -162,6 +189,18 @@ const storeCommand = {
|
|
|
162
189
|
output.printError('Value is required. Use --value');
|
|
163
190
|
return { success: false, exitCode: 1 };
|
|
164
191
|
}
|
|
192
|
+
// #3570: reject a traversal namespace before persisting, as export/purge do.
|
|
193
|
+
const vNs = validateIdentifier(namespace, 'namespace');
|
|
194
|
+
if (!vNs.valid) {
|
|
195
|
+
output.printError(vNs.error);
|
|
196
|
+
return { success: false, exitCode: 1 };
|
|
197
|
+
}
|
|
198
|
+
// #3570 follow-up: the same key rule MCP memory_store enforces.
|
|
199
|
+
const keyError = memoryKeyError(key);
|
|
200
|
+
if (keyError) {
|
|
201
|
+
output.printError(keyError);
|
|
202
|
+
return { success: false, exitCode: 1 };
|
|
203
|
+
}
|
|
165
204
|
// #2752 MemPoison gate — scan before persist when opted in.
|
|
166
205
|
// CLI flag ctx.flags.scanContent takes precedence over RUFLO_MEMORY_SCAN_ON_WRITE env var
|
|
167
206
|
// (ADR-125 §"CLI flag wins" / ADR-130 §env-var-config-precedence — fix for #2794).
|
|
@@ -308,6 +347,9 @@ const retrieveCommand = {
|
|
|
308
347
|
return { success: false, exitCode: 1, data: { key, found: false } };
|
|
309
348
|
}
|
|
310
349
|
const entry = result.entry;
|
|
350
|
+
// #3566: the same key may also live in the sibling store. Goes to stderr,
|
|
351
|
+
// so --value-only / --format json stdout stays parseable.
|
|
352
|
+
await warnIfSiblingHasRows(ctx.flags.path, 1);
|
|
311
353
|
// #2073: --value-only emits just the raw value (no decoration) for
|
|
312
354
|
// piping into JSON.parse / jq / other downstream parsers without
|
|
313
355
|
// any cleanup.
|
|
@@ -391,7 +433,7 @@ const searchCommand = {
|
|
|
391
433
|
},
|
|
392
434
|
{
|
|
393
435
|
name: 'build-hnsw',
|
|
394
|
-
description: 'Build/rebuild HNSW index before searching
|
|
436
|
+
description: 'Build/rebuild HNSW index before searching',
|
|
395
437
|
type: 'boolean',
|
|
396
438
|
default: false
|
|
397
439
|
},
|
|
@@ -492,7 +534,6 @@ const searchCommand = {
|
|
|
492
534
|
const status = getHNSWStatus();
|
|
493
535
|
output.printSuccess(`HNSW index built (${status.entryCount} vectors, ${buildTime}ms)`);
|
|
494
536
|
output.writeln(output.dim(` Dimensions: ${status.dimensions}, Metric: cosine`));
|
|
495
|
-
output.writeln(output.dim(` Search speedup: ${status.entryCount > 10000 ? '12,500x' : status.entryCount > 1000 ? '150x' : '10x'}`));
|
|
496
537
|
}
|
|
497
538
|
else {
|
|
498
539
|
output.printWarning('HNSW index not available (install @ruvector/core for acceleration)');
|
|
@@ -556,6 +597,7 @@ const searchCommand = {
|
|
|
556
597
|
// Pure-keyword mode returns directly; skip the semantic path entirely.
|
|
557
598
|
if (ctx.flags.format === 'json') {
|
|
558
599
|
output.printJson({ query, searchType, results: keywordResults, searchTime: '0ms' });
|
|
600
|
+
await warnIfSiblingHasRows(ctx.flags.path, keywordResults.length);
|
|
559
601
|
return { success: true, data: keywordResults };
|
|
560
602
|
}
|
|
561
603
|
output.writeln();
|
|
@@ -563,6 +605,7 @@ const searchCommand = {
|
|
|
563
605
|
for (const r of keywordResults) {
|
|
564
606
|
output.writeln(` ${r.key} (${r.namespace}, score=${r.score.toFixed(2)}) — ${r.preview.slice(0, 80)}${r.preview.length > 80 ? '…' : ''}`);
|
|
565
607
|
}
|
|
608
|
+
await warnIfSiblingHasRows(ctx.flags.path, keywordResults.length);
|
|
566
609
|
return { success: true, data: keywordResults };
|
|
567
610
|
}
|
|
568
611
|
// Hybrid mode: keyword hits will be MERGED after semantic runs below.
|
|
@@ -674,6 +717,7 @@ const searchCommand = {
|
|
|
674
717
|
}
|
|
675
718
|
if (ctx.flags.format === 'json') {
|
|
676
719
|
output.printJson({ query, searchType, results, searchTime: `${searchTimeMs}ms`, ...(smartStats ? { stats: smartStats } : {}) });
|
|
720
|
+
await warnIfSiblingHasRows(ctx.flags.path, results.length);
|
|
677
721
|
return { success: true, data: results };
|
|
678
722
|
}
|
|
679
723
|
// Performance stats
|
|
@@ -700,6 +744,7 @@ const searchCommand = {
|
|
|
700
744
|
});
|
|
701
745
|
output.writeln();
|
|
702
746
|
output.printInfo(`Found ${results.length} results`);
|
|
747
|
+
await warnIfSiblingHasRows(ctx.flags.path, results.length);
|
|
703
748
|
return { success: true, data: results };
|
|
704
749
|
}
|
|
705
750
|
catch (error) {
|
|
@@ -820,7 +865,7 @@ function formatRelativeTime(isoDate) {
|
|
|
820
865
|
const deleteCommand = {
|
|
821
866
|
name: 'delete',
|
|
822
867
|
aliases: ['rm'],
|
|
823
|
-
description: 'Delete memory entry',
|
|
868
|
+
description: 'Delete a memory entry from the default store and its AgentDB mirror (use --path to select one store)',
|
|
824
869
|
options: [
|
|
825
870
|
{
|
|
826
871
|
name: 'key',
|
|
@@ -870,16 +915,24 @@ const deleteCommand = {
|
|
|
870
915
|
}
|
|
871
916
|
// Use sql.js directly for consistent data access (Issue #980)
|
|
872
917
|
try {
|
|
873
|
-
const { deleteEntry
|
|
874
|
-
const
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
918
|
+
const { deleteEntry } = await import('../memory/memory-initializer.js');
|
|
919
|
+
const paths = removalDbTargets(ctx.flags.path);
|
|
920
|
+
const stores = [];
|
|
921
|
+
for (const target of paths) {
|
|
922
|
+
const { dbPath } = target;
|
|
923
|
+
const entry = await deleteEntry({ key, namespace, ...target });
|
|
924
|
+
if (!entry.success)
|
|
925
|
+
throw new Error(`${dbPath}: ${entry.error || 'Failed to delete'}`);
|
|
926
|
+
stores.push({ dbPath, ...entry });
|
|
879
927
|
}
|
|
928
|
+
const result = {
|
|
929
|
+
success: true, key, namespace, stores,
|
|
930
|
+
deleted: stores.some(entry => entry.deleted),
|
|
931
|
+
remainingEntries: stores.reduce((total, entry) => total + entry.remainingEntries, 0),
|
|
932
|
+
};
|
|
880
933
|
if (result.deleted) {
|
|
881
934
|
output.printSuccess(`Deleted "${key}" from namespace "${namespace}"`);
|
|
882
|
-
output.printInfo(`Remaining entries: ${result.remainingEntries}`);
|
|
935
|
+
output.printInfo(`Remaining entries across ${paths.length} store(s): ${result.remainingEntries}`);
|
|
883
936
|
}
|
|
884
937
|
else {
|
|
885
938
|
output.printWarning(`Key not found: "${key}" in namespace "${namespace}"`);
|
|
@@ -901,7 +954,7 @@ const deleteCommand = {
|
|
|
901
954
|
// either interactive confirmation or --force.
|
|
902
955
|
const purgeCommand = {
|
|
903
956
|
name: 'purge',
|
|
904
|
-
description: 'Permanently delete
|
|
957
|
+
description: 'Permanently delete a namespace from the default store and its AgentDB mirror (use --path to select one store)',
|
|
905
958
|
options: [
|
|
906
959
|
{
|
|
907
960
|
name: 'namespace',
|
|
@@ -939,17 +992,22 @@ const purgeCommand = {
|
|
|
939
992
|
return { success: false, exitCode: 1 };
|
|
940
993
|
}
|
|
941
994
|
try {
|
|
942
|
-
const { listEntries, purgeNamespace
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
const
|
|
995
|
+
const { listEntries, purgeNamespace } = await import('../memory/memory-initializer.js');
|
|
996
|
+
const paths = removalDbTargets(dbPath);
|
|
997
|
+
let previewCount = 0;
|
|
998
|
+
for (const target of paths) {
|
|
999
|
+
const preview = await listEntries({ namespace, limit: 1, ...target });
|
|
1000
|
+
if (!preview.success)
|
|
1001
|
+
throw new Error(`${target.dbPath}: ${preview.error || 'Failed to read purge target'}`);
|
|
1002
|
+
previewCount += preview.total ?? preview.entries?.length ?? 0;
|
|
1003
|
+
}
|
|
946
1004
|
if (dryRun) {
|
|
947
|
-
output.printInfo(`Would permanently delete ${previewCount} entr${previewCount === 1 ? 'y' : 'ies'} from namespace "${namespace}" (dry run — nothing deleted)`);
|
|
1005
|
+
output.printInfo(`Would permanently delete ${previewCount} entr${previewCount === 1 ? 'y' : 'ies'} from namespace "${namespace}" across ${paths.length} store(s) (dry run — nothing deleted)`);
|
|
948
1006
|
return { success: true, data: { namespace, wouldDelete: previewCount } };
|
|
949
1007
|
}
|
|
950
1008
|
if (!force && ctx.interactive) {
|
|
951
1009
|
const confirmed = await confirm({
|
|
952
|
-
message: `Permanently delete ${previewCount} entr${previewCount === 1 ? 'y' : 'ies'} from namespace "${namespace}"? This is a hard delete — not reversible with \`memory delete\`'s soft-undo.`,
|
|
1010
|
+
message: `Permanently delete ${previewCount} entr${previewCount === 1 ? 'y' : 'ies'} from namespace "${namespace}" across ${paths.length} store(s)? This is a hard delete — not reversible with \`memory delete\`'s soft-undo.`,
|
|
953
1011
|
default: false
|
|
954
1012
|
});
|
|
955
1013
|
if (!confirmed) {
|
|
@@ -961,13 +1019,20 @@ const purgeCommand = {
|
|
|
961
1019
|
output.printError(`Refusing to purge namespace "${namespace}" without --force in non-interactive mode`);
|
|
962
1020
|
return { success: false, exitCode: 1 };
|
|
963
1021
|
}
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1022
|
+
const stores = [];
|
|
1023
|
+
for (const target of paths) {
|
|
1024
|
+
const purged = await purgeNamespace({ namespace, ...target });
|
|
1025
|
+
if (!purged.success)
|
|
1026
|
+
throw new Error(`${target.dbPath}: ${purged.error || 'Failed to purge'}`);
|
|
1027
|
+
stores.push({ dbPath: target.dbPath, ...purged });
|
|
968
1028
|
}
|
|
1029
|
+
const result = {
|
|
1030
|
+
success: true, stores,
|
|
1031
|
+
deletedCount: stores.reduce((total, store) => total + store.deletedCount, 0),
|
|
1032
|
+
remainingEntries: stores.reduce((total, store) => total + store.remainingEntries, 0),
|
|
1033
|
+
};
|
|
969
1034
|
output.printSuccess(`Purged ${result.deletedCount} entr${result.deletedCount === 1 ? 'y' : 'ies'} from namespace "${namespace}"`);
|
|
970
|
-
output.printInfo(`Remaining entries (all namespaces): ${result.remainingEntries}`);
|
|
1035
|
+
output.printInfo(`Remaining entries (all namespaces, ${paths.length} store(s)): ${result.remainingEntries}`);
|
|
971
1036
|
return { success: true, data: result };
|
|
972
1037
|
}
|
|
973
1038
|
catch (error) {
|
|
@@ -984,8 +1049,16 @@ const statsCommand = {
|
|
|
984
1049
|
action: async (ctx) => {
|
|
985
1050
|
// Call MCP memory/stats tool for real statistics
|
|
986
1051
|
try {
|
|
987
|
-
const
|
|
1052
|
+
const dbPath = resolveDbPath(ctx.flags.path);
|
|
1053
|
+
const statsResult = await callMCPTool('memory_stats', { dbPath });
|
|
1054
|
+
if (statsResult.available === false) {
|
|
1055
|
+
output.printError(`Failed to get stats: ${statsResult.error || 'Store unavailable'}`);
|
|
1056
|
+
return { success: false, exitCode: 1 };
|
|
1057
|
+
}
|
|
1058
|
+
const unreadStore = await countSiblingStoreRows(dbPath);
|
|
988
1059
|
const stats = {
|
|
1060
|
+
...(unreadStore ? { unreadStore } : {}),
|
|
1061
|
+
...(statsResult.truncated ? { truncated: true } : {}),
|
|
989
1062
|
backend: statsResult.backend,
|
|
990
1063
|
entries: {
|
|
991
1064
|
total: statsResult.totalEntries,
|
|
@@ -993,7 +1066,7 @@ const statsCommand = {
|
|
|
993
1066
|
text: statsResult.totalEntries
|
|
994
1067
|
},
|
|
995
1068
|
storage: {
|
|
996
|
-
total: statsResult.totalSize,
|
|
1069
|
+
total: statsResult.totalSize ?? 'unknown',
|
|
997
1070
|
location: statsResult.location
|
|
998
1071
|
},
|
|
999
1072
|
version: statsResult.version,
|
|
@@ -1004,6 +1077,9 @@ const statsCommand = {
|
|
|
1004
1077
|
output.printJson(stats);
|
|
1005
1078
|
return { success: true, data: stats };
|
|
1006
1079
|
}
|
|
1080
|
+
if (unreadStore) {
|
|
1081
|
+
output.printWarning(`${unreadStore.rows} entries are in ${unreadStore.path} and were not read here. Read them with --path ${unreadStore.path}.`);
|
|
1082
|
+
}
|
|
1007
1083
|
output.writeln();
|
|
1008
1084
|
output.writeln(output.bold('Memory Statistics'));
|
|
1009
1085
|
output.writeln();
|
|
@@ -1109,8 +1185,6 @@ const statsCommand = {
|
|
|
1109
1185
|
output.writeln(output.bold('Embedding'));
|
|
1110
1186
|
output.printInfo(`Provider info unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
1111
1187
|
}
|
|
1112
|
-
output.writeln();
|
|
1113
|
-
output.printInfo('V3 Performance: 150x-12,500x faster search with HNSW indexing');
|
|
1114
1188
|
return { success: true, data: stats };
|
|
1115
1189
|
}
|
|
1116
1190
|
catch (error) {
|
|
@@ -1245,19 +1319,27 @@ const cleanupCommand = {
|
|
|
1245
1319
|
{ command: 'claude-flow memory cleanup --expired-only', description: 'Clean expired entries' }
|
|
1246
1320
|
],
|
|
1247
1321
|
action: async (ctx) => {
|
|
1248
|
-
const dryRun = ctx.flags.dryRun;
|
|
1249
|
-
const force = ctx.flags.force;
|
|
1322
|
+
const dryRun = ctx.flags.dryRun === true;
|
|
1323
|
+
const force = ctx.flags.force === true;
|
|
1324
|
+
if (ctx.flags.format === 'json' && !dryRun && !force) {
|
|
1325
|
+
output.printJson({ success: false, error: 'Use --dry-run to preview or --force to authorize cleanup with --format json.' });
|
|
1326
|
+
return { success: false, exitCode: 1 };
|
|
1327
|
+
}
|
|
1250
1328
|
if (dryRun) {
|
|
1251
1329
|
output.writeln(output.warning('DRY RUN - No changes will be made'));
|
|
1252
1330
|
}
|
|
1253
1331
|
output.printInfo('Analyzing memory for cleanup...');
|
|
1254
1332
|
try {
|
|
1255
|
-
const
|
|
1256
|
-
dryRun,
|
|
1333
|
+
const selectors = {
|
|
1257
1334
|
olderThan: ctx.flags.olderThan,
|
|
1258
1335
|
expiredOnly: ctx.flags.expiredOnly,
|
|
1259
1336
|
lowQualityThreshold: ctx.flags.lowQuality,
|
|
1260
1337
|
namespace: ctx.flags.namespace,
|
|
1338
|
+
};
|
|
1339
|
+
// Preview before asking: dryRun:false executes deletion inside the tool.
|
|
1340
|
+
let result = await callMCPTool('memory_cleanup', {
|
|
1341
|
+
...selectors,
|
|
1342
|
+
dryRun: dryRun || !force,
|
|
1261
1343
|
});
|
|
1262
1344
|
if (ctx.flags.format === 'json') {
|
|
1263
1345
|
output.printJson(result);
|
|
@@ -1286,6 +1368,7 @@ const cleanupCommand = {
|
|
|
1286
1368
|
output.printInfo('Cleanup cancelled');
|
|
1287
1369
|
return { success: true, data: result };
|
|
1288
1370
|
}
|
|
1371
|
+
result = await callMCPTool('memory_cleanup', { ...selectors, dryRun: false });
|
|
1289
1372
|
}
|
|
1290
1373
|
if (!dryRun) {
|
|
1291
1374
|
output.writeln();
|
|
@@ -1525,7 +1608,8 @@ const importCommand = {
|
|
|
1525
1608
|
short: 'n',
|
|
1526
1609
|
description: 'Import into specific namespace',
|
|
1527
1610
|
type: 'string'
|
|
1528
|
-
}
|
|
1611
|
+
},
|
|
1612
|
+
DB_PATH_OPTION
|
|
1529
1613
|
],
|
|
1530
1614
|
examples: [
|
|
1531
1615
|
{ command: 'claude-flow memory import -i ./backup.json', description: 'Import from file' },
|
|
@@ -1543,6 +1627,7 @@ const importCommand = {
|
|
|
1543
1627
|
inputPath,
|
|
1544
1628
|
merge: ctx.flags.merge ?? true,
|
|
1545
1629
|
namespace: ctx.flags.namespace,
|
|
1630
|
+
dbPath: resolveDbPath(ctx.flags.path),
|
|
1546
1631
|
});
|
|
1547
1632
|
output.printSuccess(`Imported from ${result.inputPath}`);
|
|
1548
1633
|
output.printList([
|
|
@@ -279,6 +279,7 @@ export const metaharnessCommand = {
|
|
|
279
279
|
'verbose', 'v',
|
|
280
280
|
'quiet', 'q',
|
|
281
281
|
'help', 'h',
|
|
282
|
+
'interactive', 'i', // CLI-only default; plugin scripts are non-interactive
|
|
282
283
|
]);
|
|
283
284
|
// Parser normalizes kebab-case → camelCase (--per-dimension → perDimension).
|
|
284
285
|
// Plugin scripts expect kebab-case at argv, so we re-kebab here.
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { output } from '../output.js';
|
|
9
9
|
import { createPluginDiscoveryService, searchPlugins, getPluginSearchSuggestions, getFeaturedPlugins, getOfficialPlugins, } from '../plugins/store/index.js';
|
|
10
10
|
import { getPluginManager } from '../plugins/manager.js';
|
|
11
|
+
import { DEFAULT_PLUGIN_PERMISSIONS } from '../plugins/trust-policy.js';
|
|
11
12
|
import { getBulkRatings } from '../services/registry-api.js';
|
|
12
13
|
// List subcommand - Now uses IPFS-based registry
|
|
13
14
|
const listCommand = {
|
|
@@ -185,18 +186,29 @@ const installCommand = {
|
|
|
185
186
|
{ name: 'version', short: 'v', type: 'string', description: 'Specific version to install' },
|
|
186
187
|
{ name: 'global', short: 'g', type: 'boolean', description: 'Install globally' },
|
|
187
188
|
{ name: 'dev', short: 'd', type: 'boolean', description: 'Install as dev dependency' },
|
|
188
|
-
{
|
|
189
|
+
{
|
|
190
|
+
name: 'verify',
|
|
191
|
+
type: 'boolean',
|
|
192
|
+
description: 'Verify before registering (default: true). Registry installs must match the registry sha256 checksum. ' +
|
|
193
|
+
'Hooks and commands are withheld from a plugin that declares trustLevel "untrusted"/"unverified" or ' +
|
|
194
|
+
`permissions beyond the default set (${DEFAULT_PLUGIN_PERMISSIONS.join(', ')}) unless --trust is given. ` +
|
|
195
|
+
'--no-verify skips all of this.',
|
|
196
|
+
default: true,
|
|
197
|
+
},
|
|
198
|
+
{ name: 'trust', type: 'boolean', description: 'Register hooks and commands even if the plugin is untrusted or declares elevated permissions', default: false },
|
|
189
199
|
{ name: 'registry', short: 'r', type: 'string', description: 'Registry to use' },
|
|
190
200
|
],
|
|
191
201
|
examples: [
|
|
192
202
|
{ command: 'claude-flow plugins install -n community-analytics', description: 'Install plugin from IPFS' },
|
|
193
203
|
{ command: 'claude-flow plugins install -n ./my-plugin --dev', description: 'Install local plugin' },
|
|
204
|
+
{ command: 'claude-flow plugins install -n ./my-plugin --trust', description: 'Install a local plugin that declares elevated permissions, registering its hooks' },
|
|
194
205
|
],
|
|
195
206
|
action: async (ctx) => {
|
|
196
207
|
const name = ctx.flags.name;
|
|
197
208
|
const version = ctx.flags.version || 'latest';
|
|
198
209
|
const registryName = ctx.flags.registry;
|
|
199
210
|
const verify = ctx.flags.verify !== false;
|
|
211
|
+
const trust = ctx.flags.trust === true;
|
|
200
212
|
if (!name) {
|
|
201
213
|
output.printError('Plugin name is required');
|
|
202
214
|
return { success: false, exitCode: 1 };
|
|
@@ -227,7 +239,7 @@ const installCommand = {
|
|
|
227
239
|
if (isLocalPath) {
|
|
228
240
|
// Install from local path
|
|
229
241
|
spinner.setText(`Installing from ${name}...`);
|
|
230
|
-
result = await manager.installFromLocal(name);
|
|
242
|
+
result = await manager.installFromLocal(name, { verify, trust });
|
|
231
243
|
}
|
|
232
244
|
else {
|
|
233
245
|
// First, try to find in registry for metadata
|
|
@@ -242,7 +254,13 @@ const installCommand = {
|
|
|
242
254
|
}
|
|
243
255
|
// Install from npm (since IPFS is demo mode)
|
|
244
256
|
spinner.setText(`Installing ${name} from npm...`);
|
|
245
|
-
result = await manager.installFromNpm(name, version !== 'latest' ? version : undefined
|
|
257
|
+
result = await manager.installFromNpm(name, version !== 'latest' ? version : undefined, {
|
|
258
|
+
verify,
|
|
259
|
+
trust,
|
|
260
|
+
expectedChecksum: plugin?.checksum,
|
|
261
|
+
registryTrustLevel: plugin?.trustLevel,
|
|
262
|
+
registryPermissions: plugin?.permissions,
|
|
263
|
+
});
|
|
246
264
|
}
|
|
247
265
|
if (!result.success) {
|
|
248
266
|
spinner.fail(`Installation failed: ${result.error}`);
|
|
@@ -251,20 +269,39 @@ const installCommand = {
|
|
|
251
269
|
const installed = result.plugin;
|
|
252
270
|
spinner.succeed(`Installed ${installed.name}@${installed.version}`);
|
|
253
271
|
output.writeln();
|
|
272
|
+
if (result.decision?.verificationSkipped) {
|
|
273
|
+
output.printWarning('Verification skipped (--no-verify): hooks and commands were registered without a trust or checksum check.');
|
|
274
|
+
}
|
|
275
|
+
for (const warning of result.warnings ?? []) {
|
|
276
|
+
output.printWarning(warning);
|
|
277
|
+
}
|
|
254
278
|
const boxContent = [
|
|
255
279
|
`Plugin: ${installed.name}`,
|
|
256
280
|
`Version: ${installed.version}`,
|
|
257
281
|
`Source: ${installed.source}`,
|
|
258
282
|
`Path: ${installed.path || 'N/A'}`,
|
|
259
283
|
``,
|
|
284
|
+
`Trust: ${installed.trustLevel ?? 'not declared'}`,
|
|
285
|
+
`Permissions: ${installed.permissions?.join(', ') || 'none declared'}`,
|
|
286
|
+
`Verification: ${installed.verification ?? 'n/a'}`,
|
|
287
|
+
`Install scripts: ${installed.source === 'local' ? 'n/a (local link)' : installed.scriptsRun ? 'ran' : 'skipped (use --trust to run)'}`,
|
|
288
|
+
``,
|
|
260
289
|
`Hooks registered: ${installed.hooks?.length || 0}`,
|
|
261
290
|
`Commands added: ${installed.commands?.length || 0}`,
|
|
262
291
|
];
|
|
263
|
-
if (plugin) {
|
|
264
|
-
boxContent.push(`Trust: ${plugin.trustLevel}`);
|
|
265
|
-
boxContent.push(`Permissions: ${plugin.permissions.join(', ') || 'none'}`);
|
|
266
|
-
}
|
|
267
292
|
output.printBox(boxContent.join('\n'), 'Installation Complete');
|
|
293
|
+
if (installed.withheld) {
|
|
294
|
+
output.writeln();
|
|
295
|
+
output.printWarning(`Withheld ${installed.withheld.hooks.length} hook(s) and ${installed.withheld.commands.length} command(s) ` +
|
|
296
|
+
`from ${installed.name} because it ${installed.withheld.reasons.join('; ')}.`);
|
|
297
|
+
if (installed.withheld.hooks.length > 0) {
|
|
298
|
+
output.writeln(output.dim(` Hooks not registered: ${installed.withheld.hooks.join(', ')}`));
|
|
299
|
+
}
|
|
300
|
+
if (installed.withheld.commands.length > 0) {
|
|
301
|
+
output.writeln(output.dim(` Commands not added: ${installed.withheld.commands.join(', ')}`));
|
|
302
|
+
}
|
|
303
|
+
output.writeln(output.dim(` To register them, uninstall and reinstall with --trust.`));
|
|
304
|
+
}
|
|
268
305
|
return { success: true, data: installed };
|
|
269
306
|
}
|
|
270
307
|
catch (error) {
|
|
@@ -86,8 +86,11 @@ export const policyCommand = {
|
|
|
86
86
|
const state = loadPolicyState(root);
|
|
87
87
|
return print({ receipts: state.receipts });
|
|
88
88
|
}
|
|
89
|
-
if (operation === 'verify')
|
|
90
|
-
|
|
89
|
+
if (operation === 'verify') {
|
|
90
|
+
const ledger = await verifyPolicyLedger(root);
|
|
91
|
+
const result = print(ledger);
|
|
92
|
+
return ledger.valid ? result : { ...result, success: false, exitCode: 1 };
|
|
93
|
+
}
|
|
91
94
|
throw new Error(`unknown policy operation: ${operation}`);
|
|
92
95
|
}
|
|
93
96
|
catch (error) {
|