@claude-flow/cli 3.63.9 → 3.65.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/helpers/helpers.manifest.json +2 -2
- package/catalog-manifest.json +3 -3
- package/dist/src/commands/cost.d.ts +18 -0
- package/dist/src/commands/cost.js +195 -0
- package/dist/src/commands/doctor-auto-approve.d.ts +9 -0
- package/dist/src/commands/doctor-auto-approve.js +32 -0
- package/dist/src/commands/doctor.js +28 -2
- package/dist/src/commands/hive-mind-worker.d.ts +2 -0
- package/dist/src/commands/hive-mind-worker.js +9 -3
- package/dist/src/commands/hive-mind.js +15 -1
- package/dist/src/commands/hooks.js +45 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init.js +76 -2
- package/dist/src/commands/mods.js +13 -0
- package/dist/src/commands/plugins-paths.d.ts +13 -0
- package/dist/src/commands/plugins-paths.js +55 -0
- package/dist/src/commands/plugins-verify.d.ts +9 -0
- package/dist/src/commands/plugins-verify.js +92 -0
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/cost/attribution.d.ts +35 -0
- package/dist/src/cost/attribution.js +101 -0
- package/dist/src/cost/budget-file.d.ts +57 -0
- package/dist/src/cost/budget-file.js +113 -0
- package/dist/src/cost/litellm.d.ts +69 -0
- package/dist/src/cost/litellm.js +135 -0
- package/dist/src/init/executor.d.ts +7 -2
- package/dist/src/init/executor.js +64 -10
- package/dist/src/init/helper-commands.d.ts +16 -0
- package/dist/src/init/helper-commands.js +73 -0
- package/dist/src/init/risky-settings-gate.d.ts +39 -0
- package/dist/src/init/risky-settings-gate.js +130 -0
- package/dist/src/init/settings-risk-scanner.d.ts +35 -0
- package/dist/src/init/settings-risk-scanner.js +85 -0
- package/dist/src/init/types.d.ts +12 -0
- package/dist/src/mcp-tools/capability-brain.d.ts +1 -1
- package/dist/src/mcp-tools/capability-brain.js +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts +1 -0
- package/dist/src/mcp-tools/hooks-tools.js +38 -0
- package/dist/src/memory/verified-guards.d.ts +70 -0
- package/dist/src/memory/verified-guards.js +101 -0
- package/dist/src/memory/verified-outcomes.d.ts +228 -0
- package/dist/src/memory/verified-outcomes.js +372 -0
- package/dist/src/memory/verified-reverify.d.ts +63 -0
- package/dist/src/memory/verified-reverify.js +174 -0
- package/dist/src/missions/schemas.d.ts +36 -36
- package/dist/src/mods/apply.d.ts +2 -0
- package/dist/src/mods/apply.js +68 -8
- package/dist/src/mods/command-registry/schema.d.ts +222 -222
- package/dist/src/mods/compact-snapshots.d.ts +21 -0
- package/dist/src/mods/compact-snapshots.js +45 -0
- package/dist/src/mods/headless-check.d.ts +58 -0
- package/dist/src/mods/headless-check.js +137 -0
- package/dist/src/mods/headless-plugins.d.ts +44 -0
- package/dist/src/mods/headless-plugins.js +92 -0
- package/dist/src/mods/install-trust.d.ts +28 -0
- package/dist/src/mods/install-trust.js +127 -0
- package/dist/src/mods/install.d.ts +19 -0
- package/dist/src/mods/install.js +38 -1
- package/dist/src/mods/plugin-paths.d.ts +60 -0
- package/dist/src/mods/plugin-paths.js +131 -0
- package/dist/src/mods/plugin-repair.d.ts +8 -0
- package/dist/src/mods/plugin-repair.js +7 -0
- package/dist/src/plugins/marketplace-trust/scan.d.ts +23 -0
- package/dist/src/plugins/marketplace-trust/scan.js +255 -0
- package/dist/src/plugins/marketplace-trust/signature.d.ts +53 -0
- package/dist/src/plugins/marketplace-trust/signature.js +140 -0
- package/dist/src/plugins/marketplace-trust/verify-status.d.ts +42 -0
- package/dist/src/plugins/marketplace-trust/verify-status.js +110 -0
- package/dist/src/services/headless-worker-executor.d.ts +2 -0
- package/dist/src/services/headless-worker-executor.js +13 -3
- package/package.json +2 -1
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +8 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +6 -0
- package/plugins/ruflo-metaharness/README.md +14 -1
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +2 -0
- package/plugins/ruflo-metaharness/hooks/hooks.json +16 -2
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +12 -2
- package/plugins/ruflo-metaharness/scripts/_secret-env.cjs +50 -0
- package/plugins/ruflo-metaharness/scripts/_secret.mjs +35 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +7 -7
- package/plugins/ruflo-metaharness/skills/harness-bench/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-mint/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-oia-audit/SKILL.md +1 -0
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.65.0",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
|
|
6
6
|
"hook-handler.cjs": "24502a3bcb78770971147940515c992e35b6736ae14ee2dc54e07d4cdef6a1d8",
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"signature": "
|
|
12
|
+
"signature": "cwZNQ9x3YdIZ8qur+qzjhgVAubgv3FSXoWEfhkPtNc86sTPBOg6zlIb8Z2bt8ogZ8PnVmNgKas6Ft6QswIBiBg==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generation": 7,
|
|
4
|
-
"generatedAt": "2026-10-
|
|
5
|
-
"gitSha": "
|
|
4
|
+
"generatedAt": "2026-10-30T02:51:18.963Z",
|
|
5
|
+
"gitSha": "d000e27e",
|
|
6
6
|
"catalog": {
|
|
7
7
|
"agents": 177,
|
|
8
|
-
"tools":
|
|
8
|
+
"tools": 445,
|
|
9
9
|
"skills": 34
|
|
10
10
|
},
|
|
11
11
|
"benchmark": null
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo cost` — the budget file and gateway export (ADR-508).
|
|
3
|
+
*
|
|
4
|
+
* ruflo cost budget set --daily 10 --monthly 100 [--hard-stop] [--fallback-model haiku]
|
|
5
|
+
* ruflo cost budget get
|
|
6
|
+
* ruflo cost budget export --gateway litellm [--format json] [--model <gateway model>] (YAML unless the global --format is json)
|
|
7
|
+
* ruflo cost budget export --gateway litellm --apply --url https://proxy --key-env LITELLM_MASTER_KEY
|
|
8
|
+
* ruflo cost report [--days 30] [--json]
|
|
9
|
+
*
|
|
10
|
+
* Attribution stays in the ruflo-mods cost ledger; enforcement across
|
|
11
|
+
* sessions and machines belongs to the gateway. The budget file holds no
|
|
12
|
+
* credential, and the gateway's master key is only ever read from the
|
|
13
|
+
* environment variable `--key-env` names.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command } from '../types.js';
|
|
16
|
+
export declare const costCommand: Command;
|
|
17
|
+
export default costCommand;
|
|
18
|
+
//# sourceMappingURL=cost.d.ts.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo cost` — the budget file and gateway export (ADR-508).
|
|
3
|
+
*
|
|
4
|
+
* ruflo cost budget set --daily 10 --monthly 100 [--hard-stop] [--fallback-model haiku]
|
|
5
|
+
* ruflo cost budget get
|
|
6
|
+
* ruflo cost budget export --gateway litellm [--format json] [--model <gateway model>] (YAML unless the global --format is json)
|
|
7
|
+
* ruflo cost budget export --gateway litellm --apply --url https://proxy --key-env LITELLM_MASTER_KEY
|
|
8
|
+
* ruflo cost report [--days 30] [--json]
|
|
9
|
+
*
|
|
10
|
+
* Attribution stays in the ruflo-mods cost ledger; enforcement across
|
|
11
|
+
* sessions and machines belongs to the gateway. The budget file holds no
|
|
12
|
+
* credential, and the gateway's master key is only ever read from the
|
|
13
|
+
* environment variable `--key-env` names.
|
|
14
|
+
*/
|
|
15
|
+
import { output } from '../output.js';
|
|
16
|
+
import { BUDGET_PATH, budgetFromFlags, ladderWindow, readBudget, writeBudget } from '../cost/budget-file.js';
|
|
17
|
+
import { applyToLiteLLM, formatBody, gatewayRefusal, keyFromEnv, keyGenerateUrl, maskKey, toLiteLLMKeyBody } from '../cost/litellm.js';
|
|
18
|
+
import { ATTRIBUTION_PATH, readAttribution } from '../cost/attribution.js';
|
|
19
|
+
/** A flag by its camelCase name (the parser's) or its kebab-case spelling. */
|
|
20
|
+
function flag(ctx, name) {
|
|
21
|
+
const camel = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
22
|
+
return ctx.flags[camel] ?? ctx.flags[name];
|
|
23
|
+
}
|
|
24
|
+
const describe = (b) => `${b.windows.map((w) => `$${w.usd} per ${w.period}`).join(', ')}; hard stop ${b.hardStop ? 'on' : 'off'}${b.fallbackModel ? `; fallback ${b.fallbackModel}` : ''}`;
|
|
25
|
+
function loadOrFail(cwd) {
|
|
26
|
+
const read = readBudget(cwd);
|
|
27
|
+
if (read.kind === 'ok')
|
|
28
|
+
return { budget: read.budget };
|
|
29
|
+
const message = read.kind === 'absent' ? `no budget file at ${BUDGET_PATH}; write one with: ruflo cost budget set --daily 10 --monthly 100` : read.reason;
|
|
30
|
+
output.printError(message);
|
|
31
|
+
return { result: { success: false, message, exitCode: 1 } };
|
|
32
|
+
}
|
|
33
|
+
const setSub = {
|
|
34
|
+
name: 'set',
|
|
35
|
+
description: 'Write .claude-flow/cost/budget.json (read by the ruflo-mods cost ladder and by export)',
|
|
36
|
+
options: [
|
|
37
|
+
{ name: 'daily', description: 'USD per 24h window', type: 'string' },
|
|
38
|
+
{ name: 'weekly', description: 'USD per 7d window', type: 'string' },
|
|
39
|
+
{ name: 'monthly', description: 'USD per 30d window', type: 'string' },
|
|
40
|
+
{ name: 'hard-stop', description: 'At 100% of the smallest window, the mod refuses new subagents', type: 'boolean', default: false },
|
|
41
|
+
{ name: 'fallback-model', description: 'Model a gateway falls back to when a per-model budget runs out', type: 'string' },
|
|
42
|
+
],
|
|
43
|
+
action: async (ctx) => {
|
|
44
|
+
const { budget, error } = budgetFromFlags({
|
|
45
|
+
daily: flag(ctx, 'daily'), weekly: flag(ctx, 'weekly'), monthly: flag(ctx, 'monthly'),
|
|
46
|
+
hardStop: flag(ctx, 'hard-stop'), fallbackModel: flag(ctx, 'fallback-model'),
|
|
47
|
+
});
|
|
48
|
+
if (!budget) {
|
|
49
|
+
output.printError(error ?? 'invalid budget');
|
|
50
|
+
return { success: false, message: error, exitCode: 2 };
|
|
51
|
+
}
|
|
52
|
+
const file = writeBudget(ctx.cwd, budget);
|
|
53
|
+
output.printSuccess(`Budget written to ${file}: ${describe(budget)}`);
|
|
54
|
+
output.writeln(`The ruflo-mods ladder uses the $${ladderWindow(budget).usd} ${ladderWindow(budget).period} window per session when costBudgetUsd is 0.`);
|
|
55
|
+
output.writeln('Enforce it across sessions at a gateway: ruflo cost budget export --gateway litellm');
|
|
56
|
+
return { success: true, data: budget };
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const getSub = {
|
|
60
|
+
name: 'get',
|
|
61
|
+
description: 'Show the budget file',
|
|
62
|
+
options: [{ name: 'json', description: 'Print JSON', type: 'boolean', default: false }],
|
|
63
|
+
action: async (ctx) => {
|
|
64
|
+
const { budget, result } = loadOrFail(ctx.cwd);
|
|
65
|
+
if (!budget)
|
|
66
|
+
return result;
|
|
67
|
+
if (flag(ctx, 'json'))
|
|
68
|
+
output.printJson(budget);
|
|
69
|
+
else
|
|
70
|
+
output.writeln(`${BUDGET_PATH}: ${describe(budget)}`);
|
|
71
|
+
return { success: true, data: budget };
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const exportSub = {
|
|
75
|
+
name: 'export',
|
|
76
|
+
description: 'Print the budget as gateway key settings (LiteLLM), or --apply it to a proxy',
|
|
77
|
+
options: [
|
|
78
|
+
{ name: 'gateway', description: 'Gateway: litellm (meta-llm has no documented budget API yet)', type: 'string', required: true },
|
|
79
|
+
{ name: 'model', description: 'Gateway model name for model_max_budget / budget_fallbacks', type: 'string' },
|
|
80
|
+
{ name: 'apply', description: 'POST the budget fields to <url>/key/generate', type: 'boolean', default: false },
|
|
81
|
+
{ name: 'url', description: 'Proxy base URL (https, or http to a loopback host only)', type: 'string' },
|
|
82
|
+
{ name: 'key-env', description: 'Name of the environment variable holding the proxy master key', type: 'string' },
|
|
83
|
+
{ name: 'print-key', description: 'Print the generated virtual key in full (masked by default)', type: 'boolean', default: false },
|
|
84
|
+
],
|
|
85
|
+
action: async (ctx) => {
|
|
86
|
+
const refusal = gatewayRefusal(flag(ctx, 'gateway'));
|
|
87
|
+
if (refusal) {
|
|
88
|
+
output.printError(refusal);
|
|
89
|
+
return { success: false, message: refusal, exitCode: 2 };
|
|
90
|
+
}
|
|
91
|
+
const { budget, result } = loadOrFail(ctx.cwd);
|
|
92
|
+
if (!budget)
|
|
93
|
+
return result;
|
|
94
|
+
const model = flag(ctx, 'model');
|
|
95
|
+
let body;
|
|
96
|
+
try {
|
|
97
|
+
body = toLiteLLMKeyBody(budget, model === undefined ? undefined : String(model));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const message = String(error.message);
|
|
101
|
+
output.printError(message);
|
|
102
|
+
return { success: false, message, exitCode: 2 };
|
|
103
|
+
}
|
|
104
|
+
if (!flag(ctx, 'apply')) {
|
|
105
|
+
// The global --format (text|json|table): json prints the /key/generate body, anything else the YAML key settings.
|
|
106
|
+
const format = flag(ctx, 'format') === 'json' ? 'json' : 'yaml';
|
|
107
|
+
output.write(formatBody(body, format));
|
|
108
|
+
if (model === undefined && budget.fallbackModel) {
|
|
109
|
+
output.writeErrorln(`note: pass --model <gateway model> to add model_max_budget and budget_fallbacks (-> ${budget.fallbackModel})`);
|
|
110
|
+
}
|
|
111
|
+
return { success: true, data: body };
|
|
112
|
+
}
|
|
113
|
+
// --apply: every check before anything is sent.
|
|
114
|
+
const target = keyGenerateUrl(flag(ctx, 'url'));
|
|
115
|
+
if (!target.url) {
|
|
116
|
+
output.printError(target.error);
|
|
117
|
+
return { success: false, message: target.error, exitCode: 2 };
|
|
118
|
+
}
|
|
119
|
+
const master = keyFromEnv(flag(ctx, 'key-env'));
|
|
120
|
+
if (!master.key) {
|
|
121
|
+
output.printError(master.error);
|
|
122
|
+
return { success: false, message: master.error, exitCode: 2 };
|
|
123
|
+
}
|
|
124
|
+
const applied = await applyToLiteLLM(target.url, master.key, body);
|
|
125
|
+
if (!applied.ok) {
|
|
126
|
+
const message = `LiteLLM refused /key/generate (${applied.status || 'no response'}): ${applied.error ?? ''}`;
|
|
127
|
+
output.printError(message);
|
|
128
|
+
return { success: false, message, exitCode: 1 };
|
|
129
|
+
}
|
|
130
|
+
output.printSuccess(`Budget applied at ${target.url.origin}: a virtual key with ${body.budget_limits.length} budget window(s).`);
|
|
131
|
+
if (applied.generatedKey) {
|
|
132
|
+
output.writeln(`Virtual key: ${flag(ctx, 'print-key') ? applied.generatedKey : maskKey(applied.generatedKey)}`);
|
|
133
|
+
if (!flag(ctx, 'print-key'))
|
|
134
|
+
output.writeln('(masked; it is shown once by LiteLLM; use --print-key to see it, or look it up in the LiteLLM UI)');
|
|
135
|
+
}
|
|
136
|
+
return { success: true, data: { status: applied.status } };
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const budgetCommand = {
|
|
140
|
+
name: 'budget',
|
|
141
|
+
description: 'The shared budget file and its export to a gateway (ADR-508)',
|
|
142
|
+
subcommands: [setSub, getSub, exportSub],
|
|
143
|
+
action: getSub.action,
|
|
144
|
+
};
|
|
145
|
+
const reportSub = {
|
|
146
|
+
name: 'report',
|
|
147
|
+
description: 'Which subagent types and tools spent the money (from the ruflo-mods cost ledger)',
|
|
148
|
+
options: [
|
|
149
|
+
{ name: 'days', description: 'Only sessions that ended in the last N days (0: all)', type: 'string', default: '0' },
|
|
150
|
+
{ name: 'json', description: 'Print JSON', type: 'boolean', default: false },
|
|
151
|
+
],
|
|
152
|
+
action: async (ctx) => {
|
|
153
|
+
const days = Number(flag(ctx, 'days') ?? 0);
|
|
154
|
+
const since = Number.isFinite(days) && days > 0 ? Date.now() - days * 86_400_000 : 0;
|
|
155
|
+
const { report, error } = readAttribution(ctx.cwd, since);
|
|
156
|
+
if (!report) {
|
|
157
|
+
output.printError(error ?? 'cannot read the attribution file');
|
|
158
|
+
return { success: false, message: error, exitCode: 1 };
|
|
159
|
+
}
|
|
160
|
+
if (flag(ctx, 'json')) {
|
|
161
|
+
output.printJson(report);
|
|
162
|
+
return { success: true, data: report };
|
|
163
|
+
}
|
|
164
|
+
if (report.sessions === 0) {
|
|
165
|
+
output.writeln(`No attributed sessions in ${ATTRIBUTION_PATH}. The ruflo-mods costLedger option (default on) writes one line per session.`);
|
|
166
|
+
return { success: true, data: report };
|
|
167
|
+
}
|
|
168
|
+
const pct = (v) => (report.totalUsd > 0 ? `${((v / report.totalUsd) * 100).toFixed(1)}%` : '-');
|
|
169
|
+
output.writeln(`${report.sessions} session(s), $${report.totalUsd.toFixed(4)} attributed${report.refusals ? `, ${report.refusals} gateway budget refusal(s)` : ''}${report.skipped ? ` (${report.skipped} unreadable line(s) skipped)` : ''}`);
|
|
170
|
+
output.writeln('Estimate: each turn\'s cost is split evenly across the tool calls made in it (a turn without tool calls goes to main / (model)).');
|
|
171
|
+
for (const [title, rows] of [['By agent', report.byAgent], ['By tool', report.byTool]]) {
|
|
172
|
+
output.writeln('');
|
|
173
|
+
output.writeln(output.bold(title));
|
|
174
|
+
output.printTable({
|
|
175
|
+
columns: [{ key: 'name', header: 'Name', width: 28 }, { key: 'usd', header: 'USD', width: 12, align: 'right' }, { key: 'share', header: 'Share', width: 8, align: 'right' }],
|
|
176
|
+
data: rows.slice(0, 20).map(([name, usd]) => ({ name, usd: usd.toFixed(4), share: pct(usd) })),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { success: true, data: report };
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
export const costCommand = {
|
|
183
|
+
name: 'cost',
|
|
184
|
+
description: 'Cost budgets, gateway budget export and per-agent/tool attribution (ADR-508)',
|
|
185
|
+
subcommands: [budgetCommand, reportSub],
|
|
186
|
+
examples: [
|
|
187
|
+
{ command: 'ruflo cost budget set --daily 10 --monthly 100', description: 'Write the shared budget file' },
|
|
188
|
+
{ command: 'ruflo cost budget export --gateway litellm', description: 'Print LiteLLM key budget settings (YAML)' },
|
|
189
|
+
{ command: 'ruflo cost budget export --gateway litellm --apply --url https://proxy.example --key-env LITELLM_MASTER_KEY', description: 'Create a budgeted LiteLLM virtual key' },
|
|
190
|
+
{ command: 'ruflo cost report', description: 'Spend by subagent type and by tool' },
|
|
191
|
+
],
|
|
192
|
+
action: reportSub.action,
|
|
193
|
+
};
|
|
194
|
+
export default costCommand;
|
|
195
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AutoApproveCheck {
|
|
2
|
+
name: string;
|
|
3
|
+
status: 'pass' | 'warn' | 'fail';
|
|
4
|
+
message: string;
|
|
5
|
+
fix?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const AUTO_APPROVE_CHECK_NAME = "Project auto-approve settings (ADR-509)";
|
|
8
|
+
export declare function checkProjectAutoApproveSettings(cwd?: string): Promise<AutoApproveCheck>;
|
|
9
|
+
//# sourceMappingURL=doctor-auto-approve.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-509 §2.3 — `ruflo doctor` reports project-level auto-approve keys
|
|
3
|
+
* (enableAllProjectMcpServers, enabledMcpjsonServers, bypassPermissions,
|
|
4
|
+
* blanket Bash allows, non-ruflo hooks) in `.claude/settings.json` and
|
|
5
|
+
* `.claude/settings.local.json`, and project `.claude/helpers` that are not
|
|
6
|
+
* ruflo's signed copies, as a warning, in any project.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { findPackageHelpersDir } from '../init/helper-refresh.js';
|
|
10
|
+
import { checkRiskySettings, untrustedProjectHelpers } from '../init/risky-settings-gate.js';
|
|
11
|
+
import { formatRiskFindingsAsWarnings } from '../init/settings-risk-scanner.js';
|
|
12
|
+
export const AUTO_APPROVE_CHECK_NAME = 'Project auto-approve settings (ADR-509)';
|
|
13
|
+
export async function checkProjectAutoApproveSettings(cwd = process.cwd()) {
|
|
14
|
+
const hits = [];
|
|
15
|
+
for (const file of ['.claude/settings.json', '.claude/settings.local.json']) {
|
|
16
|
+
const gate = checkRiskySettings(join(cwd, file), 'keep');
|
|
17
|
+
if (gate)
|
|
18
|
+
hits.push(...gate.lines.map((l) => `${file}: ${l}`));
|
|
19
|
+
}
|
|
20
|
+
// A project-shipped helper that is not ruflo's signed copy runs under ruflo's own hook commands.
|
|
21
|
+
hits.push(...formatRiskFindingsAsWarnings(untrustedProjectHelpers(cwd, findPackageHelpersDir())));
|
|
22
|
+
if (hits.length === 0)
|
|
23
|
+
return { name: AUTO_APPROVE_CHECK_NAME, status: 'pass', message: 'no auto-approve keys in project settings' };
|
|
24
|
+
const shown = hits.slice(0, 3).join(' | ');
|
|
25
|
+
return {
|
|
26
|
+
name: AUTO_APPROVE_CHECK_NAME,
|
|
27
|
+
status: 'warn',
|
|
28
|
+
message: `${hits.length} auto-approve key(s): ${shown}${hits.length > 3 ? ` | … ${hits.length - 3} more` : ''}`,
|
|
29
|
+
fix: 'Review them; `ruflo init upgrade --settings --drop-risky-settings` rewrites settings.json without them',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=doctor-auto-approve.js.map
|
|
@@ -11,6 +11,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
|
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
12
|
import { ownInstallAncestors } from '../plugins/own-install.js';
|
|
13
13
|
import { checkNativeAddonHeaders } from './doctor-native-addon.js';
|
|
14
|
+
import { checkProjectAutoApproveSettings } from './doctor-auto-approve.js';
|
|
14
15
|
import { execSync, exec, execFile } from 'child_process';
|
|
15
16
|
import { promisify } from 'util';
|
|
16
17
|
import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
|
|
@@ -2322,6 +2323,22 @@ async function checkMods() {
|
|
|
2322
2323
|
? { name: 'ruflo mods (ADR-404)', status: 'pass', message: findings.find((f) => f.name === 'last mod start')?.message ?? 'enabled' }
|
|
2323
2324
|
: { name: 'ruflo mods (ADR-404)', status: 'warn', message: warnings.map((f) => `${f.name}: ${f.message}`).join('; '), fix: 'ruflo mods doctor' };
|
|
2324
2325
|
}
|
|
2326
|
+
// ADR-510: do ruflo mods run under `claude -p --plugin-dir` here? Spawns a
|
|
2327
|
+
// real (cheap) model call, so `--component headless-plugins` only.
|
|
2328
|
+
async function checkHeadlessPlugins() {
|
|
2329
|
+
const { checkHeadlessPlugins: run } = await import('../mods/headless-check.js');
|
|
2330
|
+
return run();
|
|
2331
|
+
}
|
|
2332
|
+
// #4581 / ADR-502: ruflo-mods learning loop — is it recording, and do its verified outcomes reach SONA? Warn-only.
|
|
2333
|
+
async function checkLearningLoop() {
|
|
2334
|
+
const { learningLoopHealth } = await import('../memory/verified-outcomes.js');
|
|
2335
|
+
return { name: 'Learning loop (#4581)', ...learningLoopHealth(process.cwd()) };
|
|
2336
|
+
}
|
|
2337
|
+
// ADR-505: ruflo-mods compaction snapshots hold conversation text; warn when they exist and git does not ignore them.
|
|
2338
|
+
async function checkCompactSnapshots() {
|
|
2339
|
+
const { compactSnapshotHealth } = await import('../mods/compact-snapshots.js');
|
|
2340
|
+
return { name: 'Compaction snapshots (ADR-505)', ...compactSnapshotHealth(process.cwd()) };
|
|
2341
|
+
}
|
|
2325
2342
|
// Opt-in @ruvector/typesafe task router (optional peer). `--component typesafe` only.
|
|
2326
2343
|
async function checkTypesafeRouter() {
|
|
2327
2344
|
const name = '@ruvector/typesafe router';
|
|
@@ -2541,7 +2558,7 @@ export const doctorCommand = {
|
|
|
2541
2558
|
{
|
|
2542
2559
|
name: 'component',
|
|
2543
2560
|
short: 'c',
|
|
2544
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, mods, metaharness)',
|
|
2561
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, browser, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, typesafe, mods, headless-plugins, metaharness)',
|
|
2545
2562
|
type: 'string'
|
|
2546
2563
|
},
|
|
2547
2564
|
{
|
|
@@ -2614,6 +2631,7 @@ export const doctorCommand = {
|
|
|
2614
2631
|
'browser': checkAgentBrowserVersion,
|
|
2615
2632
|
'config': checkConfigFile,
|
|
2616
2633
|
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
2634
|
+
'auto-approve': () => checkProjectAutoApproveSettings(), // ADR-509
|
|
2617
2635
|
'helpers': helperCheck, // #3565
|
|
2618
2636
|
'daemon': checkDaemonStatus,
|
|
2619
2637
|
get memory() {
|
|
@@ -2653,6 +2671,9 @@ export const doctorCommand = {
|
|
|
2653
2671
|
'auth': checkAuth, // ADR-306
|
|
2654
2672
|
'typesafe': checkTypesafeRouter, // opt-in @ruvector/typesafe task router
|
|
2655
2673
|
'mods': checkMods, // ADR-404 — ruflo as a Claude Code mod
|
|
2674
|
+
'learning-loop': checkLearningLoop, // #4581 — verified-outcome learning loop
|
|
2675
|
+
'compact-snapshots': checkCompactSnapshots, // ADR-505 — snapshots must be gitignored
|
|
2676
|
+
'headless-plugins': checkHeadlessPlugins, // ADR-510 — mods under claude -p (spawns claude; not in bare doctor)
|
|
2656
2677
|
};
|
|
2657
2678
|
if (component !== undefined && !Object.hasOwn(componentMap, component)) {
|
|
2658
2679
|
const message = `Unknown doctor component: ${JSON.stringify(component)}. Valid components: ${Object.keys(componentMap).join(', ')}`;
|
|
@@ -2740,6 +2761,7 @@ export const doctorCommand = {
|
|
|
2740
2761
|
checkGitRepo,
|
|
2741
2762
|
checkConfigFile,
|
|
2742
2763
|
checkStaleSettingsNpx, // #2448/#2677 — runaway `npx @latest` in settings
|
|
2764
|
+
() => checkProjectAutoApproveSettings(), // ADR-509 — project auto-approve keys
|
|
2743
2765
|
helperCheck, // #3565 — installed signed helpers vs manifest
|
|
2744
2766
|
checkDaemonStatus,
|
|
2745
2767
|
checkMemoryDatabase,
|
|
@@ -2765,6 +2787,8 @@ export const doctorCommand = {
|
|
|
2765
2787
|
checkProxySponsoredConsent, // ADR-313 — Meta LLM Proxy sponsored-downtime health
|
|
2766
2788
|
checkAuth, // ADR-306 — Cognitum identity (warn-only; never fails bare `ruflo doctor`)
|
|
2767
2789
|
checkMods, // ADR-404 — Claude Code mod path (warn-only)
|
|
2790
|
+
checkLearningLoop, // #4581 — verified outcomes reach SONA (warn-only)
|
|
2791
|
+
checkCompactSnapshots, // ADR-505 — compaction snapshots gitignored (warn-only)
|
|
2768
2792
|
];
|
|
2769
2793
|
let checksToRun = allChecks;
|
|
2770
2794
|
if (component !== undefined) {
|
|
@@ -2778,7 +2802,7 @@ export const doctorCommand = {
|
|
|
2778
2802
|
[checkNpmVersion, 'npm'], [checkClaudeCode, 'claude'],
|
|
2779
2803
|
[checkAgentBrowserVersion, 'browser'], [checkGit, 'git'],
|
|
2780
2804
|
[checkGitRepo, 'git-repo'], [checkConfigFile, 'config'],
|
|
2781
|
-
[checkStaleSettingsNpx, 'stale-settings'], [helperCheck, 'helpers'],
|
|
2805
|
+
[checkStaleSettingsNpx, 'stale-settings'], [() => checkProjectAutoApproveSettings(), 'auto-approve'], [helperCheck, 'helpers'],
|
|
2782
2806
|
[checkDaemonStatus, 'daemon'], [checkMemoryDatabase, 'memory-presence'],
|
|
2783
2807
|
[checkMemoryStructuralIntegrity, 'memory-structural'],
|
|
2784
2808
|
[checkMemoryIntegrity, 'memory-integrity'],
|
|
@@ -2800,6 +2824,8 @@ export const doctorCommand = {
|
|
|
2800
2824
|
[checkProxyBinary, 'proxy-binary'], [checkProxyProcess, 'proxy-process'],
|
|
2801
2825
|
[checkProxyBindAddress, 'proxy-bind'], [checkAuth, 'auth'],
|
|
2802
2826
|
[checkTypesafeRouter, 'typesafe'], [checkMods, 'mods'],
|
|
2827
|
+
[checkLearningLoop, 'learning-loop'], [checkHeadlessPlugins, 'headless-plugins'],
|
|
2828
|
+
[checkCompactSnapshots, 'compact-snapshots'],
|
|
2803
2829
|
]);
|
|
2804
2830
|
nativeAgentDbChecks.forEach((check, index) => {
|
|
2805
2831
|
// The path distinguishes simultaneous authority files; normalize separators.
|
|
@@ -51,6 +51,8 @@ export interface RunOutcome {
|
|
|
51
51
|
sessionId?: string;
|
|
52
52
|
costUsd?: number;
|
|
53
53
|
durationMs: number;
|
|
54
|
+
/** ADR-510 — plugins in the worker profile that could not be found; the run went ahead without them. */
|
|
55
|
+
missingPlugins?: string[];
|
|
54
56
|
}
|
|
55
57
|
export type TaskRunner = (prompt: string, opts: {
|
|
56
58
|
mcpConfig: string;
|
|
@@ -14,6 +14,7 @@ import { spawn as childSpawn } from 'child_process';
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
15
|
import { join } from 'path';
|
|
16
16
|
import { resolveClaudeLaunchCommand } from '../runtime/claude-command.js';
|
|
17
|
+
import { withHeadlessPlugins } from '../mods/headless-plugins.js';
|
|
17
18
|
/** The server key the hive-mind prompt addresses (`mcp__ruflo__*`). */
|
|
18
19
|
const RUFLO_SERVER = 'ruflo';
|
|
19
20
|
function readServers(path, cwd) {
|
|
@@ -97,7 +98,10 @@ export const runWithClaude = (prompt, opts) => new Promise((resolve) => {
|
|
|
97
98
|
}
|
|
98
99
|
// The prompt goes on stdin, not argv: a task description has no length
|
|
99
100
|
// bound and `--mcp-config` is variadic (#1780).
|
|
100
|
-
|
|
101
|
+
// ADR-510: pass the worker profile's plugins (default ruflo-mods) as --plugin-dir.
|
|
102
|
+
const staged = withHeadlessPlugins([...launch.argsPrefix, '-p', '--output-format', 'json', `--mcp-config=${opts.mcpConfig}`], { ...process.env }, { projectRoot: opts.cwd });
|
|
103
|
+
const missing = staged.missing.length > 0 ? { missingPlugins: staged.missing } : {};
|
|
104
|
+
const child = childSpawn(launch.command, staged.argv, { cwd: opts.cwd, env: staged.env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
101
105
|
let stdout = '';
|
|
102
106
|
let stderr = '';
|
|
103
107
|
child.stdout.on('data', (d) => (stdout += d));
|
|
@@ -105,7 +109,7 @@ export const runWithClaude = (prompt, opts) => new Promise((resolve) => {
|
|
|
105
109
|
const timer = setTimeout(() => child.kill('SIGTERM'), opts.timeoutMs);
|
|
106
110
|
child.on('error', (e) => {
|
|
107
111
|
clearTimeout(timer);
|
|
108
|
-
resolve({ ok: false, output: e.message, exitCode: null, durationMs: Date.now() - started });
|
|
112
|
+
resolve({ ok: false, output: e.message, exitCode: null, durationMs: Date.now() - started, ...missing });
|
|
109
113
|
});
|
|
110
114
|
child.on('close', (code, signal) => {
|
|
111
115
|
clearTimeout(timer);
|
|
@@ -119,11 +123,12 @@ export const runWithClaude = (prompt, opts) => new Promise((resolve) => {
|
|
|
119
123
|
sessionId: r.session_id,
|
|
120
124
|
costUsd: r.total_cost_usd,
|
|
121
125
|
durationMs,
|
|
126
|
+
...missing,
|
|
122
127
|
});
|
|
123
128
|
}
|
|
124
129
|
catch {
|
|
125
130
|
const why = signal === 'SIGTERM' ? `timed out after ${opts.timeoutMs}ms` : (stderr || stdout).trim().slice(-2000);
|
|
126
|
-
resolve({ ok: false, output: why, exitCode: code, durationMs });
|
|
131
|
+
resolve({ ok: false, output: why, exitCode: code, durationMs, ...missing });
|
|
127
132
|
}
|
|
128
133
|
});
|
|
129
134
|
child.stdin.end(prompt);
|
|
@@ -173,6 +178,7 @@ export async function workOnce(opts) {
|
|
|
173
178
|
durationMs: outcome.durationMs,
|
|
174
179
|
...(outcome.sessionId ? { sessionId: outcome.sessionId } : {}),
|
|
175
180
|
...(outcome.costUsd !== undefined ? { costUsd: outcome.costUsd } : {}),
|
|
181
|
+
...(outcome.missingPlugins?.length ? { missingPlugins: outcome.missingPlugins } : {}),
|
|
176
182
|
};
|
|
177
183
|
if (outcome.ok) {
|
|
178
184
|
await callMCPTool('task_complete', { taskId, result });
|
|
@@ -13,6 +13,7 @@ import { mkdir, writeFile } from 'fs/promises';
|
|
|
13
13
|
import { resolveWorkerMcpConfig, currentRufloServer, workCommand } from './hive-mind-worker.js';
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
import { resolveClaudeLaunchCommand } from '../runtime/claude-command.js';
|
|
16
|
+
import { withHeadlessPlugins } from '../mods/headless-plugins.js';
|
|
16
17
|
import { getHiveTokenForCli } from '../mcp-tools/hive-mind-tools.js';
|
|
17
18
|
// Hive topologies
|
|
18
19
|
const TOPOLOGIES = [
|
|
@@ -248,6 +249,18 @@ async function spawnClaudeCodeInstance(swarmId, swarmName, objective, workers, f
|
|
|
248
249
|
output.printWarning('Using --dangerously-skip-permissions for seamless hive-mind execution');
|
|
249
250
|
}
|
|
250
251
|
}
|
|
252
|
+
// ADR-510 §2.2: a non-interactive (-p) run is a headless worker and loads
|
|
253
|
+
// no marketplace plugins on a fresh config, so stage the worker profile
|
|
254
|
+
// (default ruflo-mods) as --plugin-dir before the prompt. The interactive
|
|
255
|
+
// path keeps loading plugins the normal, trusted way.
|
|
256
|
+
let spawnEnv;
|
|
257
|
+
let missingPlugins = [];
|
|
258
|
+
if (isNonInteractive) {
|
|
259
|
+
const staged = withHeadlessPlugins(claudeArgs, { ...process.env }, { projectRoot: process.cwd() });
|
|
260
|
+
claudeArgs.splice(0, claudeArgs.length, ...staged.argv);
|
|
261
|
+
spawnEnv = staged.env;
|
|
262
|
+
missingPlugins = staged.missing;
|
|
263
|
+
}
|
|
251
264
|
// Add the prompt as the LAST argument
|
|
252
265
|
claudeArgs.push(hiveMindPrompt);
|
|
253
266
|
output.writeln();
|
|
@@ -260,6 +273,7 @@ async function spawnClaudeCodeInstance(swarmId, swarmName, objective, workers, f
|
|
|
260
273
|
], {
|
|
261
274
|
stdio: 'inherit',
|
|
262
275
|
shell: false,
|
|
276
|
+
...(spawnEnv ? { env: spawnEnv } : {}),
|
|
263
277
|
});
|
|
264
278
|
// Set up SIGINT handler for session management
|
|
265
279
|
let isExiting = false;
|
|
@@ -312,7 +326,7 @@ async function spawnClaudeCodeInstance(swarmId, swarmName, objective, workers, f
|
|
|
312
326
|
claudeProcess.on('exit', (c) => resolve(c ?? 0));
|
|
313
327
|
claudeProcess.on('error', () => resolve(1));
|
|
314
328
|
});
|
|
315
|
-
return { success: claudeExitCode === 0, promptFile };
|
|
329
|
+
return { success: claudeExitCode === 0, promptFile, ...(missingPlugins.length > 0 ? { missingPlugins } : {}) };
|
|
316
330
|
}
|
|
317
331
|
else if (dryRun) {
|
|
318
332
|
output.writeln();
|
|
@@ -4649,6 +4649,49 @@ const refreshAdvisorCommand = {
|
|
|
4649
4649
|
}
|
|
4650
4650
|
}
|
|
4651
4651
|
};
|
|
4652
|
+
// #4581 / ADR-502: fold the ruflo-mods learning loop's verified outcomes into SONA.
|
|
4653
|
+
const verifiedIngestCommand = {
|
|
4654
|
+
name: 'verified-ingest',
|
|
4655
|
+
description: 'Train SONA from the learning loop\'s verified turns (tests/build exit status); unverified turns never train',
|
|
4656
|
+
options: [
|
|
4657
|
+
{ name: 'dry-run', description: 'Count only: train nothing and write nothing', type: 'boolean', default: false },
|
|
4658
|
+
{ name: 'force', description: 'Re-read the file even if unchanged since the last ingest', type: 'boolean', default: false },
|
|
4659
|
+
],
|
|
4660
|
+
examples: [
|
|
4661
|
+
{ command: 'ruflo hooks verified-ingest', description: 'Ingest new verified outcomes and refresh learned-lessons.json' },
|
|
4662
|
+
{ command: 'ruflo hooks verified-ingest --dry-run', description: 'Show what would be trained' },
|
|
4663
|
+
],
|
|
4664
|
+
action: async (ctx) => {
|
|
4665
|
+
try {
|
|
4666
|
+
const result = await callMCPTool('hooks_intelligence_verified-ingest', { dryRun: ctx.flags['dry-run'] === true || ctx.flags.dryRun === true, force: ctx.flags.force === true });
|
|
4667
|
+
if (ctx.flags.format === 'json') {
|
|
4668
|
+
output.printJson(result);
|
|
4669
|
+
return { success: true, data: result };
|
|
4670
|
+
}
|
|
4671
|
+
if (result.skipped === 'no-file') {
|
|
4672
|
+
output.printInfo('No recorded turns yet (.claude-flow/data/verified-trajectories.jsonl). Turn on the ruflo-mods learningLoop option.');
|
|
4673
|
+
return { success: true, data: result };
|
|
4674
|
+
}
|
|
4675
|
+
if (!result.sonaAvailable)
|
|
4676
|
+
output.printWarning('SONA unavailable: verified outcomes wait for the next ingest');
|
|
4677
|
+
const i = result.ingested;
|
|
4678
|
+
output.printSuccess(`Ingested ${i.verifiedSuccess} verified success, ${i.verifiedFailure} verified failure; ${i.unknown} unverified (not trained), ${i.tainted ?? 0} tainted (not trained), ${i.rejected} malformed${result.skipped === 'unchanged' ? ' (file unchanged)' : ''}`);
|
|
4679
|
+
output.writeln(`Lessons at or above the 0.6 gate: ${result.lessons}; cautions: ${result.cautions}; verified patterns below the gate: ${result.belowGate}`);
|
|
4680
|
+
// ADR-503 §2.4: patterns the anomaly gate kept out of the prompt (they stay in SONA).
|
|
4681
|
+
for (const h of result.held ?? [])
|
|
4682
|
+
output.writeln(`held: ${h.id} (${h.reason})`);
|
|
4683
|
+
const rv = result.reverify;
|
|
4684
|
+
if (rv?.enabled)
|
|
4685
|
+
output.writeln(`Re-verify: ${rv.ran} check(s) run, ${rv.passed} passed, ${rv.downgraded} downgraded to unverified${rv.deferred ? `, ${rv.deferred} waiting` : ''}`);
|
|
4686
|
+
output.writeln(output.dim(`All time: ${result.totals.verifiedSuccess} success / ${result.totals.verifiedFailure} failure trained, ${result.totals.unknown} unverified, ${result.totals.tainted ?? 0} tainted`));
|
|
4687
|
+
return { success: true, data: result };
|
|
4688
|
+
}
|
|
4689
|
+
catch (error) {
|
|
4690
|
+
output.printError(`verified-ingest failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4691
|
+
return { success: false, exitCode: 1 };
|
|
4692
|
+
}
|
|
4693
|
+
},
|
|
4694
|
+
};
|
|
4652
4695
|
// Main hooks command
|
|
4653
4696
|
export const hooksCommand = {
|
|
4654
4697
|
name: 'hooks',
|
|
@@ -4696,6 +4739,8 @@ export const hooksCommand = {
|
|
|
4696
4739
|
refreshFunnelCommand,
|
|
4697
4740
|
// Advisor co-pilot tip background refresh — ADR-316
|
|
4698
4741
|
refreshAdvisorCommand,
|
|
4742
|
+
// Verified-outcome learning loop — #4581
|
|
4743
|
+
verifiedIngestCommand,
|
|
4699
4744
|
],
|
|
4700
4745
|
options: [],
|
|
4701
4746
|
examples: [
|
|
@@ -92,6 +92,8 @@ const commandLoaders = {
|
|
|
92
92
|
advisor: () => import('./advisor.js'),
|
|
93
93
|
// Ruflo verbs in Claude Code's spinnerVerbs rotation (ADR-318)
|
|
94
94
|
spinner: () => import('./spinner.js'),
|
|
95
|
+
// Budget file, gateway budget export and per-agent/tool cost attribution (ADR-508)
|
|
96
|
+
cost: () => import('./cost.js'),
|
|
95
97
|
// ruflo as a Claude Code mod: function hooks, early access (ADR-404)
|
|
96
98
|
mods: () => import('./mods.js'),
|
|
97
99
|
// ADR-489: the console in a terminal of its own (read-only Overview and Cost); loads a bundle from this CLI's own install.
|