@claude-flow/cli 3.32.25 → 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/auto-memory-hook.mjs +430 -430
- package/.claude/helpers/helpers.manifest.json +6 -6
- package/.claude/helpers/hook-handler.cjs +565 -565
- package/.claude/helpers/intelligence.cjs +1058 -1058
- package/.claude/helpers/statusline.cjs +1060 -1060
- 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 +100 -2
- 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 +106 -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/flywheel-proposer.d.ts +87 -0
- package/dist/src/services/flywheel-proposer.js +165 -0
- package/dist/src/services/flywheel-receipt.d.ts +136 -0
- package/dist/src/services/flywheel-receipt.js +309 -0
- package/dist/src/services/flywheel-transaction.d.ts +77 -0
- package/dist/src/services/flywheel-transaction.js +378 -0
- package/dist/src/services/harness-flywheel-runtime.d.ts +11 -0
- package/dist/src/services/harness-flywheel-runtime.js +85 -2
- package/dist/src/services/harness-flywheel.d.ts +27 -1
- package/dist/src/services/harness-flywheel.js +138 -27
- package/dist/src/services/policy-runtime.d.ts +38 -0
- package/dist/src/services/policy-runtime.js +340 -0
- package/package.json +23 -5
- package/plugins/ruflo-metaharness/scripts/smoke.sh +22 -14
- package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +3 -1
- package/.claude/.proven-config-version +0 -1
- package/.claude/proven-config.json +0 -42
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
|
}
|
|
@@ -35,9 +35,12 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { output } from '../output.js';
|
|
37
37
|
import { spawnSync } from 'child_process';
|
|
38
|
-
import { existsSync } from 'fs';
|
|
38
|
+
import { existsSync, readFileSync } from 'fs';
|
|
39
39
|
import { join, dirname, resolve } from 'path';
|
|
40
40
|
import { fileURLToPath } from 'url';
|
|
41
|
+
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
42
|
+
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
43
|
+
import { evaluatePolicyRequest } from '../services/policy-runtime.js';
|
|
41
44
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
45
|
// Subcommand → plugin script filename
|
|
43
46
|
const SUBCOMMANDS = {
|
|
@@ -65,7 +68,96 @@ const SUBCOMMANDS = {
|
|
|
65
68
|
// @metaharness/darwin@0.8.0 — GEPA library surface (genome ops; the
|
|
66
69
|
// gepaOptimize loop itself stays library-only / behind evolve)
|
|
67
70
|
gepa: 'gepa.mjs',
|
|
71
|
+
// ADR-153 Darwin evolution and stable benchmark-corpus operations.
|
|
72
|
+
evolve: 'evolve.mjs',
|
|
73
|
+
bench: 'bench.mjs',
|
|
74
|
+
// ADR-322 evaluation receipt + atomic promotion loop; handled in-process.
|
|
75
|
+
flywheel: '__internal__',
|
|
68
76
|
};
|
|
77
|
+
function flywheelFlag(flags, name, fallback) {
|
|
78
|
+
const value = flags[name];
|
|
79
|
+
return (value === undefined ? fallback : value);
|
|
80
|
+
}
|
|
81
|
+
async function dispatchFlywheel(operation, positional, flags) {
|
|
82
|
+
const projectRoot = resolve(String(flywheelFlag(flags, 'projectRoot', process.cwd())));
|
|
83
|
+
let data;
|
|
84
|
+
if (!operation || operation === 'status') {
|
|
85
|
+
data = {
|
|
86
|
+
state: readFlywheelTransactionState(projectRoot),
|
|
87
|
+
ledger: verifyFlywheelLedger(projectRoot),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
else if (operation === 'run') {
|
|
91
|
+
const privateKeyPath = flywheelFlag(flags, 'privateKey');
|
|
92
|
+
const publicKeyPath = flywheelFlag(flags, 'publicKey');
|
|
93
|
+
if (!!privateKeyPath !== !!publicKeyPath) {
|
|
94
|
+
data = { success: false, reason: '--private-key and --public-key must be supplied together' };
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
data = await runFlywheelWorker(projectRoot, {
|
|
98
|
+
optInOverride: true,
|
|
99
|
+
sample: Number(flywheelFlag(flags, 'sample', 40)),
|
|
100
|
+
proposer: String(flywheelFlag(flags, 'proposer', 'auto')),
|
|
101
|
+
receiptPrivateKeyPem: privateKeyPath ? readFileSync(resolve(privateKeyPath), 'utf8') : undefined,
|
|
102
|
+
receiptPublicKeyPem: publicKeyPath ? readFileSync(resolve(publicKeyPath), 'utf8') : undefined,
|
|
103
|
+
maxConcurrency: Number(flywheelFlag(flags, 'maxConcurrency', 2)),
|
|
104
|
+
evaluationTimeoutMs: Number(flywheelFlag(flags, 'timeoutMs', 120_000)),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (operation === 'receipts') {
|
|
109
|
+
data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
|
|
110
|
+
receiptId: receipt.payload.receiptId,
|
|
111
|
+
candidateId: receipt.payload.candidateId,
|
|
112
|
+
baselineRef: receipt.payload.baselineRef,
|
|
113
|
+
decision: receipt.payload.decision,
|
|
114
|
+
signed: !!receipt.signature,
|
|
115
|
+
issuedAt: receipt.payload.issuedAt,
|
|
116
|
+
state,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
else if (operation === 'history') {
|
|
120
|
+
const state = readFlywheelTransactionState(projectRoot);
|
|
121
|
+
data = { ledgerHead: state.ledgerHead, commits: state.commits };
|
|
122
|
+
}
|
|
123
|
+
else if (operation === 'promote') {
|
|
124
|
+
const receiptId = positional[0];
|
|
125
|
+
const publicKeyPath = flywheelFlag(flags, 'publicKey');
|
|
126
|
+
if (!receiptId || !publicKeyPath) {
|
|
127
|
+
data = { success: false, reason: 'promote requires <receipt-id> and --public-key <PEM path>' };
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
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
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
data = { success: false, reason: `unknown flywheel operation: ${operation}` };
|
|
156
|
+
}
|
|
157
|
+
output.writeln(JSON.stringify(data, null, 2));
|
|
158
|
+
const success = !(data && typeof data === 'object' && data.success === false);
|
|
159
|
+
return { success, exitCode: success ? 0 : 1, data };
|
|
160
|
+
}
|
|
69
161
|
/**
|
|
70
162
|
* Walk up from the current dirname to find the ruflo repo root that
|
|
71
163
|
* contains plugins/ruflo-metaharness/. Handles three install layouts:
|
|
@@ -127,7 +219,7 @@ export const metaharnessCommand = {
|
|
|
127
219
|
// iter 73 — list reflects all dispatchable subcommands (was
|
|
128
220
|
// stale at the iter-3 list of 5). Keep this synced with the
|
|
129
221
|
// SUBCOMMANDS map above.
|
|
130
|
-
description: 'One of: score | genome | mcp-scan | threat-model | oia-audit | audit-list | audit-trend | similarity | drift-from-history | mint | redblue | learn | gepa',
|
|
222
|
+
description: 'One of: score | genome | mcp-scan | threat-model | oia-audit | audit-list | audit-trend | similarity | drift-from-history | mint | redblue | learn | gepa | evolve | bench | flywheel',
|
|
131
223
|
type: 'string',
|
|
132
224
|
},
|
|
133
225
|
],
|
|
@@ -190,6 +282,9 @@ export const metaharnessCommand = {
|
|
|
190
282
|
output.writeln(' drift-from-history iter 53 — diff current state against most recent audit (1-command drift)');
|
|
191
283
|
output.writeln(' mint scaffold a custom harness (dry-run by default)');
|
|
192
284
|
output.writeln(' redblue adversarial red/blue LLM testing (init|run|patch|attack|report)');
|
|
285
|
+
output.writeln(' evolve Darwin candidate evolution');
|
|
286
|
+
output.writeln(' bench create or verify a stable benchmark suite');
|
|
287
|
+
output.writeln(' flywheel receipt loop: run | status | receipts | history | promote');
|
|
193
288
|
output.writeln('');
|
|
194
289
|
output.writeln('Each subcommand accepts --format json|table and --help.');
|
|
195
290
|
output.writeln('');
|
|
@@ -201,6 +296,9 @@ export const metaharnessCommand = {
|
|
|
201
296
|
output.writeln(`Valid: ${Object.keys(SUBCOMMANDS).join(', ')}`);
|
|
202
297
|
return { success: false, exitCode: 2, data: { subcommand } };
|
|
203
298
|
}
|
|
299
|
+
if (subcommand === 'flywheel') {
|
|
300
|
+
return dispatchFlywheel(positionalRest[0], positionalRest.slice(1), ctxFlags);
|
|
301
|
+
}
|
|
204
302
|
const scriptDir = locatePluginScripts(SUBCOMMANDS[subcommand]);
|
|
205
303
|
if (!scriptDir) {
|
|
206
304
|
output.writeln(output.warning('metaharness: plugins/ruflo-metaharness/scripts/ not found. Install ruflo with `npm i ruflo` or run from the ruflo repo.'));
|
|
@@ -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
|