@claude-flow/cli 3.32.26 → 3.32.29
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 +2 -2
- package/catalog-manifest.json +4 -4
- package/dist/src/commands/index.d.ts +1 -0
- package/dist/src/commands/index.js +5 -3
- package/dist/src/commands/memory.js +49 -5
- package/dist/src/commands/metaharness.js +25 -5
- package/dist/src/commands/policy.d.ts +4 -0
- package/dist/src/commands/policy.js +107 -0
- package/dist/src/index.js +18 -0
- package/dist/src/mcp-client.js +25 -1
- package/dist/src/mcp-tools/capability-brain.d.ts +134 -0
- package/dist/src/mcp-tools/capability-brain.js +697 -0
- package/dist/src/mcp-tools/guidance-tools.d.ts +2 -0
- package/dist/src/mcp-tools/guidance-tools.js +369 -37
- package/dist/src/mcp-tools/index.d.ts +4 -1
- package/dist/src/mcp-tools/index.js +3 -1
- package/dist/src/mcp-tools/memory-tools.js +26 -0
- package/dist/src/mcp-tools/metaharness-tools.js +27 -1
- package/dist/src/mcp-tools/policy-tools.d.ts +3 -0
- package/dist/src/mcp-tools/policy-tools.js +121 -0
- package/dist/src/memory/memory-bridge.d.ts +11 -0
- package/dist/src/memory/memory-bridge.js +100 -21
- package/dist/src/memory/memory-initializer.d.ts +22 -1
- package/dist/src/memory/memory-initializer.js +184 -39
- package/dist/src/services/bounded-worker-pool.d.ts +28 -0
- package/dist/src/services/bounded-worker-pool.js +90 -0
- package/dist/src/services/harness-flywheel-runtime.d.ts +2 -0
- package/dist/src/services/harness-flywheel-runtime.js +18 -0
- package/dist/src/services/harness-flywheel.d.ts +4 -1
- package/dist/src/services/harness-flywheel.js +27 -6
- package/dist/src/services/policy-runtime.d.ts +38 -0
- package/dist/src/services/policy-runtime.js +340 -0
- package/package.json +22 -6
- package/plugins/ruflo-metaharness/scripts/smoke.sh +22 -14
- package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
3.32.
|
|
1
|
+
3.32.29
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.29",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "68be7e9a9eba7bf9c4e8a230db7bf61a243b965639f8504842799d6c6ca28762",
|
|
6
6
|
"hook-handler.cjs": "50ea92a72651bdc95634f7588d56a5870963168eef5226f66ed14af3b47c8d9a",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"statusline.cjs": "d2a0eac56d1267d8dbed2d70b09feb592299469196ec4b215909f2b052144882"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "jF/krskPhEF+3m0rkY3my1qEuLZec5hzo3YoV7xUl8/7xZqlpTctkB9QlYACViu2GCK3AGkWS7BRf5xR6WK7Aw==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generation":
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"gitSha": "
|
|
3
|
+
"generation": 3,
|
|
4
|
+
"generatedAt": "2026-07-29T04:47:25.187Z",
|
|
5
|
+
"gitSha": "39bc5fd9",
|
|
6
6
|
"catalog": {
|
|
7
7
|
"agents": 164,
|
|
8
|
-
"tools":
|
|
8
|
+
"tools": 395,
|
|
9
9
|
"skills": 34
|
|
10
10
|
},
|
|
11
11
|
"benchmark": null
|
|
@@ -33,6 +33,7 @@ export declare function getProvidersCommand(): Promise<Command | undefined>;
|
|
|
33
33
|
export declare function getPluginsCommand(): Promise<Command | undefined>;
|
|
34
34
|
export declare function getDeploymentCommand(): Promise<Command | undefined>;
|
|
35
35
|
export declare function getClaimsCommand(): Promise<Command | undefined>;
|
|
36
|
+
export declare function getPolicyCommand(): Promise<Command | undefined>;
|
|
36
37
|
export declare function getEmbeddingsCommand(): Promise<Command | undefined>;
|
|
37
38
|
export declare function getCompletionsCommand(): Promise<Command | undefined>;
|
|
38
39
|
export declare function getAnalyzeCommand(): Promise<Command | undefined>;
|
|
@@ -40,6 +40,7 @@ const commandLoaders = {
|
|
|
40
40
|
plugins: () => import('./plugins.js'),
|
|
41
41
|
deployment: () => import('./deployment.js'),
|
|
42
42
|
claims: () => import('./claims.js'),
|
|
43
|
+
policy: () => import('./policy.js'),
|
|
43
44
|
embeddings: () => import('./embeddings.js'),
|
|
44
45
|
// P0 Commands
|
|
45
46
|
completions: () => import('./completions.js'),
|
|
@@ -175,6 +176,7 @@ export async function getProvidersCommand() { return loadCommand('providers'); }
|
|
|
175
176
|
export async function getPluginsCommand() { return loadCommand('plugins'); }
|
|
176
177
|
export async function getDeploymentCommand() { return loadCommand('deployment'); }
|
|
177
178
|
export async function getClaimsCommand() { return loadCommand('claims'); }
|
|
179
|
+
export async function getPolicyCommand() { return loadCommand('policy'); }
|
|
178
180
|
export async function getEmbeddingsCommand() { return loadCommand('embeddings'); }
|
|
179
181
|
export async function getCompletionsCommand() { return loadCommand('completions'); }
|
|
180
182
|
export async function getAnalyzeCommand() { return loadCommand('analyze'); }
|
|
@@ -230,14 +232,14 @@ export const commandsByCategory = {
|
|
|
230
232
|
* Use this for help display and full command listings.
|
|
231
233
|
*/
|
|
232
234
|
export async function getCommandsByCategory() {
|
|
233
|
-
const [daemonCmd, doctorCmd, embeddingsCmd, neuralCmd, performanceCmd, securityCmd, ruvectorCmd, hiveMindCmd, configCmd, completionsCmd, migrateCmd, workflowCmd, analyzeCmd, routeCmd, progressCmd, providersCmd, pluginsCmd, deploymentCmd, claimsCmd, issuesCmd, updateCmd, processCmd, guidanceCmd, applianceCmd, cleanupCmd, autopilotCmd,] = await Promise.all([
|
|
235
|
+
const [daemonCmd, doctorCmd, embeddingsCmd, neuralCmd, performanceCmd, securityCmd, ruvectorCmd, hiveMindCmd, configCmd, completionsCmd, migrateCmd, workflowCmd, analyzeCmd, routeCmd, progressCmd, providersCmd, pluginsCmd, deploymentCmd, claimsCmd, issuesCmd, updateCmd, processCmd, guidanceCmd, applianceCmd, cleanupCmd, autopilotCmd, policyCmd,] = await Promise.all([
|
|
234
236
|
loadCommand('daemon'), loadCommand('doctor'), loadCommand('embeddings'), loadCommand('neural'),
|
|
235
237
|
loadCommand('performance'), loadCommand('security'), loadCommand('ruvector'), loadCommand('hive-mind'),
|
|
236
238
|
loadCommand('config'), loadCommand('completions'), loadCommand('migrate'), loadCommand('workflow'),
|
|
237
239
|
loadCommand('analyze'), loadCommand('route'), loadCommand('progress'), loadCommand('providers'),
|
|
238
240
|
loadCommand('plugins'), loadCommand('deployment'), loadCommand('claims'), loadCommand('issues'),
|
|
239
241
|
loadCommand('update'), loadCommand('process'), loadCommand('guidance'), loadCommand('appliance'),
|
|
240
|
-
loadCommand('cleanup'), loadCommand('autopilot'),
|
|
242
|
+
loadCommand('cleanup'), loadCommand('autopilot'), loadCommand('policy'),
|
|
241
243
|
]);
|
|
242
244
|
return {
|
|
243
245
|
primary: [
|
|
@@ -246,7 +248,7 @@ export async function getCommandsByCategory() {
|
|
|
246
248
|
mcpCommand, hooksCommand,
|
|
247
249
|
],
|
|
248
250
|
advanced: [
|
|
249
|
-
neuralCmd, securityCmd, performanceCmd, embeddingsCmd,
|
|
251
|
+
neuralCmd, securityCmd, policyCmd, performanceCmd, embeddingsCmd,
|
|
250
252
|
hiveMindCmd, ruvectorCmd, guidanceCmd, autopilotCmd,
|
|
251
253
|
].filter(Boolean),
|
|
252
254
|
utility: [
|
|
@@ -86,12 +86,23 @@ const storeCommand = {
|
|
|
86
86
|
description: 'Scan the value for injection payloads before persisting (dream-cycle #2752 MemPoison gate)',
|
|
87
87
|
type: 'boolean',
|
|
88
88
|
},
|
|
89
|
+
{
|
|
90
|
+
// ADR-323 — typed memory provenance. Distinguishes who/what produced
|
|
91
|
+
// this entry so shared-namespace retrieval can filter by trust level
|
|
92
|
+
// instead of conflating a user's stated claim with an agent's own
|
|
93
|
+
// output or a raw tool/system observation.
|
|
94
|
+
name: 'provenance',
|
|
95
|
+
description: 'Who/what produced this value: user_claim | agent_output | system_observation | tool_result | unknown (default: unknown) — ADR-323',
|
|
96
|
+
type: 'string',
|
|
97
|
+
choices: ['user_claim', 'agent_output', 'system_observation', 'tool_result', 'unknown'],
|
|
98
|
+
},
|
|
89
99
|
DB_PATH_OPTION
|
|
90
100
|
],
|
|
91
101
|
examples: [
|
|
92
102
|
{ command: 'claude-flow memory store -k "api/auth" -v "JWT implementation"', description: 'Store text' },
|
|
93
103
|
{ command: 'claude-flow memory store -k "pattern/singleton" --vector', description: 'Store vector' },
|
|
94
|
-
{ command: 'claude-flow memory store -k "pattern" -v "new" --no-upsert', description: 'Strict insert (fail if key exists)' }
|
|
104
|
+
{ command: 'claude-flow memory store -k "pattern" -v "new" --no-upsert', description: 'Strict insert (fail if key exists)' },
|
|
105
|
+
{ command: 'claude-flow memory store -k "user/goal" -v "ship by Friday" --provenance user_claim', description: 'Store with typed provenance (ADR-323)' }
|
|
95
106
|
],
|
|
96
107
|
action: async (ctx) => {
|
|
97
108
|
const key = ctx.flags.key;
|
|
@@ -114,6 +125,10 @@ const storeCommand = {
|
|
|
114
125
|
// regardless of the parser gap: only an explicit `--no-upsert` (which
|
|
115
126
|
// arrives as `false`) turns it off.
|
|
116
127
|
const upsert = ctx.flags.upsert !== false;
|
|
128
|
+
// ADR-323: leave undefined (not 'unknown') when omitted so storeEntry's
|
|
129
|
+
// own default applies uniformly across the CLI, MCP tool, and direct
|
|
130
|
+
// callers — one place decides the default, not three.
|
|
131
|
+
const provenance = ctx.flags.provenance;
|
|
117
132
|
if (!key) {
|
|
118
133
|
output.printError('Key is required. Use --key or -k');
|
|
119
134
|
return { success: false, exitCode: 1 };
|
|
@@ -174,6 +189,7 @@ const storeCommand = {
|
|
|
174
189
|
tags,
|
|
175
190
|
ttl,
|
|
176
191
|
upsert,
|
|
192
|
+
provenanceType: provenance,
|
|
177
193
|
dbPath
|
|
178
194
|
});
|
|
179
195
|
if (!result.success) {
|
|
@@ -193,12 +209,13 @@ const storeCommand = {
|
|
|
193
209
|
{ property: 'TTL', val: ttl ? `${ttl}s` : 'None' },
|
|
194
210
|
{ property: 'Tags', val: tags.length > 0 ? tags.join(', ') : 'None' },
|
|
195
211
|
{ property: 'Vector', val: result.embedding ? `Yes (${result.embedding.dimensions}-dim)` : 'No' },
|
|
212
|
+
{ property: 'Provenance', val: provenance || 'unknown' },
|
|
196
213
|
{ property: 'ID', val: result.id.substring(0, 20) }
|
|
197
214
|
]
|
|
198
215
|
});
|
|
199
216
|
output.writeln();
|
|
200
217
|
output.printSuccess('Data stored successfully');
|
|
201
|
-
return { success: true, data: { ...storeData, id: result.id, embedding: result.embedding } };
|
|
218
|
+
return { success: true, data: { ...storeData, id: result.id, embedding: result.embedding, provenanceType: provenance || 'unknown' } };
|
|
202
219
|
}
|
|
203
220
|
catch (error) {
|
|
204
221
|
output.printError(`Failed to store: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -361,6 +378,15 @@ const searchCommand = {
|
|
|
361
378
|
choices: ['auto', 'mixed', 'episodic', 'semantic', 'procedural'],
|
|
362
379
|
default: 'mixed',
|
|
363
380
|
},
|
|
381
|
+
{
|
|
382
|
+
// ADR-323 — restrict results to entries with a matching provenance
|
|
383
|
+
// type (e.g. exclude user_claim when retrieving for fact-checking,
|
|
384
|
+
// per MemSyco-Bench's memory-induced sycophancy finding). Enforced
|
|
385
|
+
// across semantic, keyword, hybrid, and SmartRetrieval paths.
|
|
386
|
+
name: 'provenance-filter',
|
|
387
|
+
description: 'Comma-separated provenance types to restrict results to: user_claim,agent_output,system_observation,tool_result,unknown — ADR-323',
|
|
388
|
+
type: 'string',
|
|
389
|
+
},
|
|
364
390
|
DB_PATH_OPTION
|
|
365
391
|
],
|
|
366
392
|
examples: [
|
|
@@ -369,11 +395,18 @@ const searchCommand = {
|
|
|
369
395
|
{ command: 'claude-flow memory search -q "test" --build-hnsw', description: 'Build HNSW index and search' },
|
|
370
396
|
{ command: 'claude-flow memory search -q "auth patterns" --smart', description: 'SmartRetrieval with RRF + MMR' },
|
|
371
397
|
{ command: 'claude-flow memory search -q "when did we last touch auth" --intent auto', description: 'SCM-routed retrieval (dream-cycle #2760)' },
|
|
398
|
+
{ command: 'claude-flow memory search -q "deploy plan" --provenance-filter agent_output,tool_result', description: 'Only trust agent/tool-sourced entries (ADR-323)' },
|
|
372
399
|
],
|
|
373
400
|
action: async (ctx) => {
|
|
374
401
|
const query = ctx.flags.query || ctx.args[0];
|
|
375
402
|
let namespace = ctx.flags.namespace || 'all';
|
|
376
403
|
const limit = ctx.flags.limit || 10;
|
|
404
|
+
// Matches the --build-hnsw / buildHnsw dual-check convention already used
|
|
405
|
+
// below: the parser doesn't consistently camelCase every hyphenated flag.
|
|
406
|
+
const provenanceFilterRaw = (ctx.flags['provenance-filter'] || ctx.flags.provenanceFilter);
|
|
407
|
+
const provenanceFilter = provenanceFilterRaw
|
|
408
|
+
? provenanceFilterRaw.split(',').map(s => s.trim()).filter(Boolean)
|
|
409
|
+
: undefined;
|
|
377
410
|
// #2790 fix — `||` discards an explicit `--threshold 0` (falsy) and
|
|
378
411
|
// silently uses the fallback; that made `--threshold 0` return
|
|
379
412
|
// FEWER results than `--threshold 0.01` (non-monotonic). Nullish
|
|
@@ -456,8 +489,13 @@ const searchCommand = {
|
|
|
456
489
|
limit: 5000,
|
|
457
490
|
includeContent: true,
|
|
458
491
|
dbPath: dbPathSearch,
|
|
492
|
+
provenanceFilter,
|
|
459
493
|
});
|
|
460
|
-
if (list.success) {
|
|
494
|
+
if (!list.success) {
|
|
495
|
+
output.printError(list.error || 'Keyword search failed');
|
|
496
|
+
return { success: false, exitCode: 1 };
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
461
499
|
const q = query.toLowerCase();
|
|
462
500
|
keywordResults = list.entries
|
|
463
501
|
.filter((e) => {
|
|
@@ -473,6 +511,7 @@ const searchCommand = {
|
|
|
473
511
|
score: inKey ? 1.0 : 0.7,
|
|
474
512
|
namespace: e.namespace,
|
|
475
513
|
preview: c.slice(0, 200),
|
|
514
|
+
provenanceType: e.provenanceType,
|
|
476
515
|
};
|
|
477
516
|
})
|
|
478
517
|
.sort((a, b) => b.score - a.score)
|
|
@@ -526,6 +565,7 @@ const searchCommand = {
|
|
|
526
565
|
limit: req.limit || limit * 3,
|
|
527
566
|
threshold: req.threshold ?? threshold,
|
|
528
567
|
dbPath: dbPathSearch,
|
|
568
|
+
provenanceFilter,
|
|
529
569
|
});
|
|
530
570
|
return {
|
|
531
571
|
results: r.results.map(e => ({
|
|
@@ -534,6 +574,7 @@ const searchCommand = {
|
|
|
534
574
|
content: e.content,
|
|
535
575
|
score: e.score,
|
|
536
576
|
namespace: e.namespace,
|
|
577
|
+
provenanceType: e.provenanceType,
|
|
537
578
|
})),
|
|
538
579
|
};
|
|
539
580
|
};
|
|
@@ -548,6 +589,7 @@ const searchCommand = {
|
|
|
548
589
|
score: r.score,
|
|
549
590
|
namespace: r.namespace,
|
|
550
591
|
preview: r.content,
|
|
592
|
+
provenanceType: r.provenanceType,
|
|
551
593
|
}));
|
|
552
594
|
searchTimeMs = smartResult.stats.durationMs;
|
|
553
595
|
smartStats = smartResult.stats;
|
|
@@ -559,7 +601,8 @@ const searchCommand = {
|
|
|
559
601
|
namespace,
|
|
560
602
|
limit,
|
|
561
603
|
threshold,
|
|
562
|
-
dbPath: dbPathSearch
|
|
604
|
+
dbPath: dbPathSearch,
|
|
605
|
+
provenanceFilter
|
|
563
606
|
});
|
|
564
607
|
if (!searchResult.success) {
|
|
565
608
|
output.printError(searchResult.error || 'Search failed');
|
|
@@ -569,7 +612,8 @@ const searchCommand = {
|
|
|
569
612
|
key: r.key,
|
|
570
613
|
score: r.score,
|
|
571
614
|
namespace: r.namespace,
|
|
572
|
-
preview: r.content
|
|
615
|
+
preview: r.content,
|
|
616
|
+
provenanceType: r.provenanceType,
|
|
573
617
|
}));
|
|
574
618
|
searchTimeMs = searchResult.searchTime;
|
|
575
619
|
}
|
|
@@ -40,6 +40,7 @@ import { join, dirname, resolve } from 'path';
|
|
|
40
40
|
import { fileURLToPath } from 'url';
|
|
41
41
|
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
42
42
|
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
43
|
+
import { evaluatePolicyRequest } from '../services/policy-runtime.js';
|
|
43
44
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
44
45
|
// Subcommand → plugin script filename
|
|
45
46
|
const SUBCOMMANDS = {
|
|
@@ -99,6 +100,8 @@ async function dispatchFlywheel(operation, positional, flags) {
|
|
|
99
100
|
proposer: String(flywheelFlag(flags, 'proposer', 'auto')),
|
|
100
101
|
receiptPrivateKeyPem: privateKeyPath ? readFileSync(resolve(privateKeyPath), 'utf8') : undefined,
|
|
101
102
|
receiptPublicKeyPem: publicKeyPath ? readFileSync(resolve(publicKeyPath), 'utf8') : undefined,
|
|
103
|
+
maxConcurrency: Number(flywheelFlag(flags, 'maxConcurrency', 2)),
|
|
104
|
+
evaluationTimeoutMs: Number(flywheelFlag(flags, 'timeoutMs', 120_000)),
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
}
|
|
@@ -124,11 +127,28 @@ async function dispatchFlywheel(operation, positional, flags) {
|
|
|
124
127
|
data = { success: false, reason: 'promote requires <receipt-id> and --public-key <PEM path>' };
|
|
125
128
|
}
|
|
126
129
|
else {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const policy = await evaluatePolicyRequest({
|
|
131
|
+
identity: { id: process.env.CLAUDE_FLOW_PRINCIPAL_ID ?? 'metaharness-local', type: 'agent', roles: ['optimizer'] },
|
|
132
|
+
action: {
|
|
133
|
+
type: 'metaharness.candidate.promote',
|
|
134
|
+
resource: receiptId,
|
|
135
|
+
environment: 'production',
|
|
136
|
+
destructive: true,
|
|
137
|
+
},
|
|
138
|
+
context: {
|
|
139
|
+
approvalIds: String(flywheelFlag(flags, 'approvalId', '')).split(',').filter(Boolean),
|
|
140
|
+
},
|
|
141
|
+
}, projectRoot);
|
|
142
|
+
if (policy.enforcedOutcome !== 'allowed') {
|
|
143
|
+
data = { success: false, reason: `policy-${policy.enforcedOutcome}:${policy.reason}`, policyReceiptId: policy.receiptId };
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const publicKey = readFileSync(resolve(publicKeyPath), 'utf8');
|
|
147
|
+
data = await promoteFlywheelCandidate(projectRoot, receiptId, {
|
|
148
|
+
confirm: flywheelFlag(flags, 'confirm', false) === true,
|
|
149
|
+
trustedPublicKeys: new Set([publicKey]),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
132
152
|
}
|
|
133
153
|
}
|
|
134
154
|
else {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { output } from '../output.js';
|
|
2
|
+
import { autoMigratePolicyStateIfNeeded, evaluatePolicyRequest, loadPolicyState, revokePolicyApproval, setPolicyBudget, setPolicyMode, upsertPolicyRule, verifyPolicyLedger, } from '../services/policy-runtime.js';
|
|
3
|
+
function argJson(value, label) {
|
|
4
|
+
if (!value)
|
|
5
|
+
throw new Error(`${label} requires a JSON argument`);
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(value);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new Error(`${label} must be valid JSON`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function print(data) {
|
|
14
|
+
output.writeln(JSON.stringify(data, null, 2));
|
|
15
|
+
return { success: true, exitCode: 0, data };
|
|
16
|
+
}
|
|
17
|
+
function requireInteractiveAdministrator() {
|
|
18
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
19
|
+
throw new Error('policy administration requires an interactive local terminal');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export const policyCommand = {
|
|
23
|
+
name: 'policy',
|
|
24
|
+
description: 'Agentic policy engine — evaluate actions, manage rules/approvals, and verify the decision ledger (ADR-324)',
|
|
25
|
+
options: [
|
|
26
|
+
{ name: 'mode', type: 'string', description: 'Policy mode: legacy | observe | enforce' },
|
|
27
|
+
{ name: 'project-root', type: 'string', description: 'Project root containing .claude-flow/policy' },
|
|
28
|
+
],
|
|
29
|
+
async action(context) {
|
|
30
|
+
const args = context.args ?? [];
|
|
31
|
+
const flags = context.flags ?? {};
|
|
32
|
+
const root = String(flags.projectRoot ?? process.cwd());
|
|
33
|
+
const operation = args[0] ?? 'status';
|
|
34
|
+
try {
|
|
35
|
+
if (operation === 'init' || operation === 'migrate') {
|
|
36
|
+
if (flags.mode || args[1])
|
|
37
|
+
requireInteractiveAdministrator();
|
|
38
|
+
const migration = await autoMigratePolicyStateIfNeeded(root);
|
|
39
|
+
const mode = String(flags.mode ?? args[1] ?? '');
|
|
40
|
+
if (mode) {
|
|
41
|
+
if (!['legacy', 'observe', 'enforce'].includes(mode))
|
|
42
|
+
throw new Error('mode must be legacy, observe, or enforce');
|
|
43
|
+
await setPolicyMode(mode, root);
|
|
44
|
+
}
|
|
45
|
+
return print({ ...migration, state: loadPolicyState(root) });
|
|
46
|
+
}
|
|
47
|
+
if (operation === 'status') {
|
|
48
|
+
const state = loadPolicyState(root);
|
|
49
|
+
return print({
|
|
50
|
+
version: state.version,
|
|
51
|
+
mode: state.mode,
|
|
52
|
+
migratedFrom: state.migratedFrom,
|
|
53
|
+
rules: state.rules.length,
|
|
54
|
+
budgets: state.budgets.length,
|
|
55
|
+
approvals: state.approvals.length,
|
|
56
|
+
receipts: state.receipts.length,
|
|
57
|
+
ledger: await verifyPolicyLedger(root),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (operation === 'evaluate') {
|
|
61
|
+
return print(await evaluatePolicyRequest(argJson(args[1], 'evaluate'), root));
|
|
62
|
+
}
|
|
63
|
+
if (operation === 'rule' && args[1] === 'add') {
|
|
64
|
+
requireInteractiveAdministrator();
|
|
65
|
+
const rule = argJson(args[2], 'rule add');
|
|
66
|
+
await upsertPolicyRule(rule, root);
|
|
67
|
+
return print({ success: true, ruleId: rule.id });
|
|
68
|
+
}
|
|
69
|
+
if (operation === 'budget' && args[1] === 'set') {
|
|
70
|
+
requireInteractiveAdministrator();
|
|
71
|
+
const budget = argJson(args[2], 'budget set');
|
|
72
|
+
await setPolicyBudget(budget, root);
|
|
73
|
+
return print({ success: true, budgetId: budget.id });
|
|
74
|
+
}
|
|
75
|
+
if (operation === 'approve') {
|
|
76
|
+
throw new Error('approval issuance requires an authenticated human identity adapter; '
|
|
77
|
+
+ 'the local TTY is not an identity credential');
|
|
78
|
+
}
|
|
79
|
+
if (operation === 'revoke') {
|
|
80
|
+
requireInteractiveAdministrator();
|
|
81
|
+
if (!args[1])
|
|
82
|
+
throw new Error('revoke requires an approval id');
|
|
83
|
+
return print({ success: await revokePolicyApproval(args[1], root), approvalId: args[1] });
|
|
84
|
+
}
|
|
85
|
+
if (operation === 'audit') {
|
|
86
|
+
const state = loadPolicyState(root);
|
|
87
|
+
return print({ receipts: state.receipts });
|
|
88
|
+
}
|
|
89
|
+
if (operation === 'verify')
|
|
90
|
+
return print(await verifyPolicyLedger(root));
|
|
91
|
+
throw new Error(`unknown policy operation: ${operation}`);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
output.printError(message);
|
|
96
|
+
return { success: false, exitCode: 1, data: { error: message } };
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
examples: [
|
|
100
|
+
{ command: 'ruflo policy status', description: 'Show policy mode and ledger health' },
|
|
101
|
+
{ command: 'ruflo policy init --mode observe', description: 'Migrate an existing install without blocking legacy actions' },
|
|
102
|
+
{ command: 'ruflo policy budget set \'{"id":"daily-model","action":"model.call","maxCostUsd":10,"periodMs":86400000}\'', description: 'Set an atomic policy budget ceiling' },
|
|
103
|
+
{ command: 'ruflo policy evaluate \'{"identity":{"id":"agent-1","type":"agent"},"action":{"type":"deploy","environment":"production"}}\'', description: 'Evaluate an action and write a decision receipt' },
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
export default policyCommand;
|
|
107
|
+
//# sourceMappingURL=policy.js.map
|
package/dist/src/index.js
CHANGED
|
@@ -120,6 +120,24 @@ export class CLI {
|
|
|
120
120
|
// stamp read + string compare (sub-ms), and the copy runs at most once per
|
|
121
121
|
// version bump. Best-effort + silent — never blocks or fails a command.
|
|
122
122
|
if (commandPath[0] !== 'init' && commandPath[0] !== 'update') {
|
|
123
|
+
// ADR-324: existing installations acquire a versioned policy state on
|
|
124
|
+
// first use. Migration is additive and starts in legacy mode, so older
|
|
125
|
+
// AgentDB, MCP, hooks, and swarm workflows keep their exact behavior.
|
|
126
|
+
try {
|
|
127
|
+
const { autoMigratePolicyStateIfNeeded } = await import('./services/policy-runtime.js');
|
|
128
|
+
const migration = await autoMigratePolicyStateIfNeeded(process.cwd());
|
|
129
|
+
if (migration.migrated && this.output.isVerbose()) {
|
|
130
|
+
this.output.printDebug(`Initialized agentic policy compatibility state (${migration.mode})`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// Legacy compatibility requires startup to remain available if the
|
|
135
|
+
// policy state cannot be initialized. Enforce-mode actions still
|
|
136
|
+
// fail at their authoritative dispatcher when state is unreadable.
|
|
137
|
+
if (this.output.isVerbose()) {
|
|
138
|
+
this.output.printDebug(`Policy compatibility migration skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
123
141
|
try {
|
|
124
142
|
const { autoRefreshHelpersIfStale } = await import('./init/helper-refresh.js');
|
|
125
143
|
// alsoRefreshGlobal:true — refresh ~/.claude/helpers too, not just
|
package/dist/src/mcp-client.js
CHANGED
|
@@ -21,6 +21,8 @@ import { analyzeTools } from './mcp-tools/analyze-tools.js';
|
|
|
21
21
|
import { progressTools } from './mcp-tools/progress-tools.js';
|
|
22
22
|
import { embeddingsTools } from './mcp-tools/embeddings-tools.js';
|
|
23
23
|
import { claimsTools } from './mcp-tools/claims-tools.js';
|
|
24
|
+
import { policyTools } from './mcp-tools/policy-tools.js';
|
|
25
|
+
import { authorizeMcpTool, classifyMcpTool } from './services/policy-runtime.js';
|
|
24
26
|
import { securityTools } from './mcp-tools/security-tools.js';
|
|
25
27
|
import { transferTools } from './mcp-tools/transfer-tools.js';
|
|
26
28
|
// V2 Compatibility tools
|
|
@@ -47,7 +49,7 @@ import { wasmAgentTools } from './mcp-tools/wasm-agent-tools.js';
|
|
|
47
49
|
// the local WASM-sandboxed `wasm_agent_*` (rvagent) tools. Lives in the
|
|
48
50
|
// `ruflo-agent` plugin.
|
|
49
51
|
import { managedAgentTools } from './mcp-tools/managed-agent-tools.js';
|
|
50
|
-
import { guidanceTools } from './mcp-tools/guidance-tools.js';
|
|
52
|
+
import { configureGuidanceToolProvider, guidanceTools, } from './mcp-tools/guidance-tools.js';
|
|
51
53
|
import { autopilotTools } from './mcp-tools/autopilot-tools.js';
|
|
52
54
|
// ADR-150 — MetaHarness MCP tools (score / genome / mcp-scan / threat-model / oia-audit)
|
|
53
55
|
import { metaharnessTools } from './mcp-tools/metaharness-tools.js';
|
|
@@ -127,6 +129,10 @@ registerTools([
|
|
|
127
129
|
...progressTools,
|
|
128
130
|
...embeddingsTools,
|
|
129
131
|
...claimsTools,
|
|
132
|
+
// Remote MCP exposes evaluation/status only. Administrative mutation is a
|
|
133
|
+
// local CLI bootstrap/recovery operation until the server has authenticated
|
|
134
|
+
// human identity propagation.
|
|
135
|
+
...policyTools.filter((tool) => ['policy_evaluate', 'policy_status'].includes(tool.name)),
|
|
130
136
|
...securityTools,
|
|
131
137
|
...transferTools,
|
|
132
138
|
// V2 Compatibility tools
|
|
@@ -167,6 +173,15 @@ registerTools([
|
|
|
167
173
|
// ADR-164 Phase 4 §5.1.8 — http_fetch (1 tool, secure-by-default HTTP probe)
|
|
168
174
|
...httpFetchTools,
|
|
169
175
|
]);
|
|
176
|
+
// The capability brain consumes the completed live registry. This is injected
|
|
177
|
+
// after registration to keep guidance honest and avoid a registry import cycle.
|
|
178
|
+
configureGuidanceToolProvider(() => Array.from(TOOL_REGISTRY.values()).map((tool) => ({
|
|
179
|
+
name: tool.name,
|
|
180
|
+
description: tool.description,
|
|
181
|
+
category: tool.category,
|
|
182
|
+
tags: tool.tags,
|
|
183
|
+
version: tool.version,
|
|
184
|
+
})));
|
|
170
185
|
/**
|
|
171
186
|
* MCP Client Error
|
|
172
187
|
*/
|
|
@@ -211,6 +226,15 @@ export async function callMCPTool(toolName, input = {}, context) {
|
|
|
211
226
|
throw new MCPClientError(`MCP tool not found: ${toolName}`, toolName);
|
|
212
227
|
}
|
|
213
228
|
try {
|
|
229
|
+
// ADR-324: one policy chokepoint for every local CLI/MCP invocation.
|
|
230
|
+
// Policy administration is not exempt: authorization calls the engine
|
|
231
|
+
// directly, so there is no recursive MCP dispatch. In enforce mode an
|
|
232
|
+
// administrator must explicitly allow policy.* actions or use the local
|
|
233
|
+
// CLI bootstrap path.
|
|
234
|
+
const decision = await authorizeMcpTool(toolName, input, context, classifyMcpTool(toolName));
|
|
235
|
+
if (decision.enforcedOutcome !== 'allowed') {
|
|
236
|
+
throw new Error(`policy-${decision.enforcedOutcome}:${decision.reason}; receipt=${decision.receiptId}`);
|
|
237
|
+
}
|
|
214
238
|
// Call the tool handler
|
|
215
239
|
const result = await tool.handler(input, context);
|
|
216
240
|
// ADR-146 P2: scan every tool result for indirect-injection before it
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruflo capability brain.
|
|
3
|
+
*
|
|
4
|
+
* This module is deliberately data-only: it describes the complete MCP surface
|
|
5
|
+
* without importing the MCP registry. The registry injects its live tool list
|
|
6
|
+
* into guidance-tools after registration, which avoids a circular dependency
|
|
7
|
+
* and prevents documentation from being mistaken for runtime availability.
|
|
8
|
+
*/
|
|
9
|
+
export type CapabilityMaturity = 'stable' | 'experimental' | 'compatibility';
|
|
10
|
+
export type CapabilityAuthority = 'advisory' | 'capability-plane' | 'control-plane';
|
|
11
|
+
export type CapabilityRisk = 'read-only' | 'reversible' | 'privileged';
|
|
12
|
+
export type AvailabilityState = boolean | 'unknown';
|
|
13
|
+
export type CapabilityRiskFlag = 'read' | 'local-write' | 'memory-poisoning' | 'process-exec' | 'network' | 'credential-pii' | 'spend' | 'concurrency' | 'destructive' | 'approval' | 'promotion';
|
|
14
|
+
export interface CapabilityToolMetadata {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
category?: string;
|
|
18
|
+
tags?: string[];
|
|
19
|
+
version?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CapabilityHealth {
|
|
22
|
+
/** Present in this running MCP server's registry. */
|
|
23
|
+
registered: boolean;
|
|
24
|
+
/** Requires an operator/provider configuration check. */
|
|
25
|
+
configured: AvailabilityState;
|
|
26
|
+
/** Requires a non-mutating dependency probe. */
|
|
27
|
+
reachable: AvailabilityState;
|
|
28
|
+
/** Requires a service-specific health check. */
|
|
29
|
+
healthy: AvailabilityState;
|
|
30
|
+
/** Requires a policy evaluation for the current subject and action. */
|
|
31
|
+
authorized: AvailabilityState;
|
|
32
|
+
}
|
|
33
|
+
export interface CapabilityDomainDefinition {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
prefixes: string[];
|
|
38
|
+
exactTools?: string[];
|
|
39
|
+
taskSignals: string[];
|
|
40
|
+
commands: string[];
|
|
41
|
+
skills: string[];
|
|
42
|
+
agents: string[];
|
|
43
|
+
maturity: CapabilityMaturity;
|
|
44
|
+
authority: CapabilityAuthority;
|
|
45
|
+
risk: CapabilityRisk;
|
|
46
|
+
optionalRuntime?: boolean;
|
|
47
|
+
packageOwners?: string[];
|
|
48
|
+
pluginOwners?: string[];
|
|
49
|
+
availabilityMode?: 'always-local' | 'conditional-registration' | 'registered-degraded' | 'configured-remote' | 'policy-filtered';
|
|
50
|
+
/**
|
|
51
|
+
* Conservative union of effects possible anywhere in this domain.
|
|
52
|
+
* Individual tool descriptors may narrow or expand this reviewed profile.
|
|
53
|
+
*/
|
|
54
|
+
riskFlags?: CapabilityRiskFlag[];
|
|
55
|
+
loopPhases?: Array<'discover' | 'recall' | 'authorize-claim' | 'route-plan' | 'branch-propose' | 'execute' | 'validate-critique' | 'commit-validated' | 'observe' | 'publish'>;
|
|
56
|
+
useWhen: string;
|
|
57
|
+
verifyBeforeUse: string[];
|
|
58
|
+
}
|
|
59
|
+
export interface CapabilityDomain extends CapabilityDomainDefinition {
|
|
60
|
+
packageOwners: string[];
|
|
61
|
+
pluginOwners: string[];
|
|
62
|
+
availabilityMode: NonNullable<CapabilityDomainDefinition['availabilityMode']>;
|
|
63
|
+
riskFlags: NonNullable<CapabilityDomainDefinition['riskFlags']>;
|
|
64
|
+
loopPhases: NonNullable<CapabilityDomainDefinition['loopPhases']>;
|
|
65
|
+
tools: Array<CapabilityToolMetadata & {
|
|
66
|
+
domain: string;
|
|
67
|
+
packageOwner: string;
|
|
68
|
+
pluginOwner?: string;
|
|
69
|
+
availabilityMode: NonNullable<CapabilityDomainDefinition['availabilityMode']>;
|
|
70
|
+
authority: CapabilityAuthority;
|
|
71
|
+
risk: CapabilityRisk;
|
|
72
|
+
riskFlags: NonNullable<CapabilityDomainDefinition['riskFlags']>;
|
|
73
|
+
loopPhases: NonNullable<CapabilityDomainDefinition['loopPhases']>;
|
|
74
|
+
}>;
|
|
75
|
+
health: CapabilityHealth;
|
|
76
|
+
}
|
|
77
|
+
export interface CapabilityBrain {
|
|
78
|
+
schemaVersion: 'ruflo.capability-brain/v1';
|
|
79
|
+
generatedAt: string;
|
|
80
|
+
truthModel: {
|
|
81
|
+
catalogued: string;
|
|
82
|
+
registered: string;
|
|
83
|
+
configured: string;
|
|
84
|
+
reachable: string;
|
|
85
|
+
healthy: string;
|
|
86
|
+
authorized: string;
|
|
87
|
+
};
|
|
88
|
+
domains: CapabilityDomain[];
|
|
89
|
+
cliCommands: string[];
|
|
90
|
+
implementationLoop: ImplementationLoopStep[];
|
|
91
|
+
coverage: {
|
|
92
|
+
registeredToolCount: number;
|
|
93
|
+
classifiedToolCount: number;
|
|
94
|
+
coveragePercent: number;
|
|
95
|
+
fallbackClassifiedTools: string[];
|
|
96
|
+
duplicateToolNames: string[];
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Top-level commands from the CLI command loader. Kept as a checked manifest. */
|
|
100
|
+
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "announcements"];
|
|
101
|
+
export interface ImplementationLoopStep {
|
|
102
|
+
id: string;
|
|
103
|
+
name: string;
|
|
104
|
+
purpose: string;
|
|
105
|
+
preferredTools: string[];
|
|
106
|
+
requiredEvidence: string;
|
|
107
|
+
mutation: 'none' | 'workspace' | 'external';
|
|
108
|
+
}
|
|
109
|
+
export declare const IMPLEMENTATION_LOOP: readonly ImplementationLoopStep[];
|
|
110
|
+
/**
|
|
111
|
+
* Domain order is significant: narrow/specialized matches precede broad
|
|
112
|
+
* compatibility prefixes. Every registered tool is eventually classified by
|
|
113
|
+
* the final runtime-services domain, but fallback classifications are reported.
|
|
114
|
+
*/
|
|
115
|
+
export declare const CAPABILITY_DOMAINS: readonly CapabilityDomainDefinition[];
|
|
116
|
+
export declare function classifyCapabilityTool(tool: CapabilityToolMetadata): CapabilityDomainDefinition;
|
|
117
|
+
export declare function buildCapabilityBrain(registeredTools: readonly CapabilityToolMetadata[], now?: Date): CapabilityBrain;
|
|
118
|
+
export interface CapabilityRecommendation {
|
|
119
|
+
task: string;
|
|
120
|
+
domains: Array<{
|
|
121
|
+
id: string;
|
|
122
|
+
name: string;
|
|
123
|
+
score: number;
|
|
124
|
+
registeredTools: string[];
|
|
125
|
+
health: CapabilityHealth;
|
|
126
|
+
authority: CapabilityAuthority;
|
|
127
|
+
risk: CapabilityRisk;
|
|
128
|
+
verifyBeforeUse: string[];
|
|
129
|
+
}>;
|
|
130
|
+
implementationLoop: ImplementationLoopStep[];
|
|
131
|
+
guardrails: string[];
|
|
132
|
+
}
|
|
133
|
+
export declare function recommendCapabilities(brain: CapabilityBrain, task: string): CapabilityRecommendation;
|
|
134
|
+
//# sourceMappingURL=capability-brain.d.ts.map
|