@claude-flow/cli 3.64.0 → 3.65.1
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 +2 -2
- 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 +21 -3
- 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 +8 -2
- 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.js +2 -1
- package/dist/src/mcp-tools/http-fetch-pinned.d.ts +44 -0
- package/dist/src/mcp-tools/http-fetch-pinned.js +148 -0
- package/dist/src/mcp-tools/http-fetch-tools.d.ts +11 -2
- package/dist/src/mcp-tools/http-fetch-tools.js +19 -70
- 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 +68 -3
- package/dist/src/memory/verified-outcomes.js +88 -19
- 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 +4 -4
- 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 +36 -36
- 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.1",
|
|
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": "vAyxpgZ8P+tdc0y57OdMmpY1skgNclrdXaKIl8ZFcQzujKG9+Tix+tLhr9XKhgu9Wbt8Fx6ttlPetWN0cKFUAA==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -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,11 +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
|
+
}
|
|
2325
2332
|
// #4581 / ADR-502: ruflo-mods learning loop — is it recording, and do its verified outcomes reach SONA? Warn-only.
|
|
2326
2333
|
async function checkLearningLoop() {
|
|
2327
2334
|
const { learningLoopHealth } = await import('../memory/verified-outcomes.js');
|
|
2328
2335
|
return { name: 'Learning loop (#4581)', ...learningLoopHealth(process.cwd()) };
|
|
2329
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
|
+
}
|
|
2330
2342
|
// Opt-in @ruvector/typesafe task router (optional peer). `--component typesafe` only.
|
|
2331
2343
|
async function checkTypesafeRouter() {
|
|
2332
2344
|
const name = '@ruvector/typesafe router';
|
|
@@ -2546,7 +2558,7 @@ export const doctorCommand = {
|
|
|
2546
2558
|
{
|
|
2547
2559
|
name: 'component',
|
|
2548
2560
|
short: 'c',
|
|
2549
|
-
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)',
|
|
2550
2562
|
type: 'string'
|
|
2551
2563
|
},
|
|
2552
2564
|
{
|
|
@@ -2619,6 +2631,7 @@ export const doctorCommand = {
|
|
|
2619
2631
|
'browser': checkAgentBrowserVersion,
|
|
2620
2632
|
'config': checkConfigFile,
|
|
2621
2633
|
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
2634
|
+
'auto-approve': () => checkProjectAutoApproveSettings(), // ADR-509
|
|
2622
2635
|
'helpers': helperCheck, // #3565
|
|
2623
2636
|
'daemon': checkDaemonStatus,
|
|
2624
2637
|
get memory() {
|
|
@@ -2659,6 +2672,8 @@ export const doctorCommand = {
|
|
|
2659
2672
|
'typesafe': checkTypesafeRouter, // opt-in @ruvector/typesafe task router
|
|
2660
2673
|
'mods': checkMods, // ADR-404 — ruflo as a Claude Code mod
|
|
2661
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)
|
|
2662
2677
|
};
|
|
2663
2678
|
if (component !== undefined && !Object.hasOwn(componentMap, component)) {
|
|
2664
2679
|
const message = `Unknown doctor component: ${JSON.stringify(component)}. Valid components: ${Object.keys(componentMap).join(', ')}`;
|
|
@@ -2746,6 +2761,7 @@ export const doctorCommand = {
|
|
|
2746
2761
|
checkGitRepo,
|
|
2747
2762
|
checkConfigFile,
|
|
2748
2763
|
checkStaleSettingsNpx, // #2448/#2677 — runaway `npx @latest` in settings
|
|
2764
|
+
() => checkProjectAutoApproveSettings(), // ADR-509 — project auto-approve keys
|
|
2749
2765
|
helperCheck, // #3565 — installed signed helpers vs manifest
|
|
2750
2766
|
checkDaemonStatus,
|
|
2751
2767
|
checkMemoryDatabase,
|
|
@@ -2772,6 +2788,7 @@ export const doctorCommand = {
|
|
|
2772
2788
|
checkAuth, // ADR-306 — Cognitum identity (warn-only; never fails bare `ruflo doctor`)
|
|
2773
2789
|
checkMods, // ADR-404 — Claude Code mod path (warn-only)
|
|
2774
2790
|
checkLearningLoop, // #4581 — verified outcomes reach SONA (warn-only)
|
|
2791
|
+
checkCompactSnapshots, // ADR-505 — compaction snapshots gitignored (warn-only)
|
|
2775
2792
|
];
|
|
2776
2793
|
let checksToRun = allChecks;
|
|
2777
2794
|
if (component !== undefined) {
|
|
@@ -2785,7 +2802,7 @@ export const doctorCommand = {
|
|
|
2785
2802
|
[checkNpmVersion, 'npm'], [checkClaudeCode, 'claude'],
|
|
2786
2803
|
[checkAgentBrowserVersion, 'browser'], [checkGit, 'git'],
|
|
2787
2804
|
[checkGitRepo, 'git-repo'], [checkConfigFile, 'config'],
|
|
2788
|
-
[checkStaleSettingsNpx, 'stale-settings'], [helperCheck, 'helpers'],
|
|
2805
|
+
[checkStaleSettingsNpx, 'stale-settings'], [() => checkProjectAutoApproveSettings(), 'auto-approve'], [helperCheck, 'helpers'],
|
|
2789
2806
|
[checkDaemonStatus, 'daemon'], [checkMemoryDatabase, 'memory-presence'],
|
|
2790
2807
|
[checkMemoryStructuralIntegrity, 'memory-structural'],
|
|
2791
2808
|
[checkMemoryIntegrity, 'memory-integrity'],
|
|
@@ -2807,7 +2824,8 @@ export const doctorCommand = {
|
|
|
2807
2824
|
[checkProxyBinary, 'proxy-binary'], [checkProxyProcess, 'proxy-process'],
|
|
2808
2825
|
[checkProxyBindAddress, 'proxy-bind'], [checkAuth, 'auth'],
|
|
2809
2826
|
[checkTypesafeRouter, 'typesafe'], [checkMods, 'mods'],
|
|
2810
|
-
[checkLearningLoop, 'learning-loop'],
|
|
2827
|
+
[checkLearningLoop, 'learning-loop'], [checkHeadlessPlugins, 'headless-plugins'],
|
|
2828
|
+
[checkCompactSnapshots, 'compact-snapshots'],
|
|
2811
2829
|
]);
|
|
2812
2830
|
nativeAgentDbChecks.forEach((check, index) => {
|
|
2813
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();
|
|
@@ -4675,9 +4675,15 @@ const verifiedIngestCommand = {
|
|
|
4675
4675
|
if (!result.sonaAvailable)
|
|
4676
4676
|
output.printWarning('SONA unavailable: verified outcomes wait for the next ingest');
|
|
4677
4677
|
const i = result.ingested;
|
|
4678
|
-
output.printSuccess(`Ingested ${i.verifiedSuccess} verified success, ${i.verifiedFailure} verified failure; ${i.unknown} unverified (not trained), ${i.rejected} malformed${result.skipped === 'unchanged' ? ' (file unchanged)' : ''}`);
|
|
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
4679
|
output.writeln(`Lessons at or above the 0.6 gate: ${result.lessons}; cautions: ${result.cautions}; verified patterns below the gate: ${result.belowGate}`);
|
|
4680
|
-
|
|
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`));
|
|
4681
4687
|
return { success: true, data: result };
|
|
4682
4688
|
}
|
|
4683
4689
|
catch (error) {
|
|
@@ -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.
|