@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-verify before training (ADR-503 §2.3). Opt-in: `.claude-flow/config.json`
|
|
3
|
+
* `learningLoop.reverify: true`. Off by default because it runs project code.
|
|
4
|
+
*
|
|
5
|
+
* - The command comes only from a fixed allowlist built from the project's own
|
|
6
|
+
* manifest (`package.json` scripts named test/typecheck/build/lint, or
|
|
7
|
+
* `cargo test`/`cargo check` when `Cargo.toml` exists). A record's text is
|
|
8
|
+
* never executed: it only selects, by its verifier *kind*, which allowlisted
|
|
9
|
+
* check NOT to run.
|
|
10
|
+
* - Checks rotate round-robin across ingests, preferring one the record did
|
|
11
|
+
* not use, so no single check is the one that counts.
|
|
12
|
+
* - The real runner works in a temporary detached `git worktree` at HEAD,
|
|
13
|
+
* with a hard timeout; timeout, error or a non-zero exit downgrades the
|
|
14
|
+
* record to `unknown`.
|
|
15
|
+
*/
|
|
16
|
+
import { execFile, spawn } from 'node:child_process';
|
|
17
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
/** package.json scripts that may be re-run, in rotation order, and the kind each verifies. */
|
|
21
|
+
const NPM_SCRIPTS = [
|
|
22
|
+
['test', 'test'],
|
|
23
|
+
['typecheck', 'typecheck'],
|
|
24
|
+
['build', 'build'],
|
|
25
|
+
['lint', 'lint'],
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* The allowlist for a project. Only the script NAMES are read from the
|
|
29
|
+
* manifest; the argv is fixed here (`npm run <name>`, `cargo test`, ...).
|
|
30
|
+
*/
|
|
31
|
+
export function reverifyChecks(root) {
|
|
32
|
+
const checks = [];
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
35
|
+
const scripts = pkg && typeof pkg.scripts === 'object' && pkg.scripts !== null ? pkg.scripts : {};
|
|
36
|
+
for (const [name, kind] of NPM_SCRIPTS) {
|
|
37
|
+
if (typeof scripts[name] === 'string' && scripts[name].trim())
|
|
38
|
+
checks.push({ name: `npm:${name}`, kind, argv: ['npm', 'run', name] });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// no or unreadable package.json: no npm checks
|
|
43
|
+
}
|
|
44
|
+
if (existsSync(join(root, 'Cargo.toml'))) {
|
|
45
|
+
checks.push({ name: 'cargo:test', kind: 'test', argv: ['cargo', 'test'] });
|
|
46
|
+
checks.push({ name: 'cargo:check', kind: 'typecheck', argv: ['cargo', 'check'] });
|
|
47
|
+
}
|
|
48
|
+
return checks;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The check to run for a record decided by a `used` verifier: round-robin by
|
|
52
|
+
* `cursor` over the checks of another kind, or over all of them when every
|
|
53
|
+
* check is that kind. Null when the project has none.
|
|
54
|
+
*/
|
|
55
|
+
export function pickCheck(checks, used, cursor) {
|
|
56
|
+
if (checks.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const others = checks.filter(c => c.kind !== used);
|
|
59
|
+
const pool = others.length > 0 ? others : checks;
|
|
60
|
+
return pool[Math.abs(Math.floor(cursor)) % pool.length];
|
|
61
|
+
}
|
|
62
|
+
const COMMIT = /^[0-9a-f]{40,64}$/;
|
|
63
|
+
function git(args, cwd, timeout = 60_000) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
execFile('git', args, { cwd, timeout, maxBuffer: 1024 * 1024 }, (error, stdout) => (error ? reject(error) : resolve(String(stdout))));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Runs argv (no shell) in `cwd`; the whole process group is killed at the timeout. */
|
|
69
|
+
function runCheck(argv, cwd, timeoutMs) {
|
|
70
|
+
return new Promise(resolve => {
|
|
71
|
+
let settled = false;
|
|
72
|
+
const finish = (status) => {
|
|
73
|
+
if (settled)
|
|
74
|
+
return;
|
|
75
|
+
settled = true;
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
resolve(status);
|
|
78
|
+
};
|
|
79
|
+
let child;
|
|
80
|
+
try {
|
|
81
|
+
child = spawn(argv[0], argv.slice(1), { cwd, shell: false, stdio: 'ignore', detached: process.platform !== 'win32', env: { ...process.env, CI: '1' } });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
resolve('error');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
try {
|
|
89
|
+
if (child.pid && process.platform !== 'win32')
|
|
90
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
91
|
+
else
|
|
92
|
+
child.kill('SIGKILL');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// already gone
|
|
96
|
+
}
|
|
97
|
+
finish('timeout');
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
child.on('error', () => finish('error'));
|
|
100
|
+
child.on('exit', code => finish(code === 0 ? 'pass' : 'fail'));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The real runner: a detached `git worktree` at HEAD in a temp directory, the
|
|
105
|
+
* root's `node_modules` linked in (a fresh worktree has none), the check run,
|
|
106
|
+
* the worktree removed. Any failure along the way is `error`.
|
|
107
|
+
*/
|
|
108
|
+
export const gitWorktreeRunner = async (root, check, timeoutMs) => {
|
|
109
|
+
let commit = null;
|
|
110
|
+
const base = mkdtempSync(join(tmpdir(), 'ruflo-reverify-'));
|
|
111
|
+
const dir = join(base, 'wt');
|
|
112
|
+
let added = false;
|
|
113
|
+
try {
|
|
114
|
+
const head = (await git(['rev-parse', 'HEAD'], root, 10_000)).trim();
|
|
115
|
+
if (!COMMIT.test(head))
|
|
116
|
+
return { status: 'error', commit: null };
|
|
117
|
+
commit = head;
|
|
118
|
+
await git(['worktree', 'add', '--detach', dir, head], root);
|
|
119
|
+
added = true;
|
|
120
|
+
if (existsSync(join(root, 'node_modules')) && !existsSync(join(dir, 'node_modules')))
|
|
121
|
+
symlinkSync(join(root, 'node_modules'), join(dir, 'node_modules'), 'dir');
|
|
122
|
+
return { status: await runCheck(check.argv, dir, timeoutMs), commit };
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return { status: 'error', commit };
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (added)
|
|
129
|
+
await git(['worktree', 'remove', '--force', dir], root).catch(() => undefined);
|
|
130
|
+
rmSync(base, { recursive: true, force: true });
|
|
131
|
+
if (added)
|
|
132
|
+
await git(['worktree', 'prune'], root).catch(() => undefined);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/** A runner that never settles still ends: past the deadline its outcome is `timeout`. */
|
|
136
|
+
function withDeadline(run, ms) {
|
|
137
|
+
let timer;
|
|
138
|
+
const deadline = new Promise(resolve => {
|
|
139
|
+
timer = setTimeout(() => resolve({ status: 'timeout', commit: null }), ms);
|
|
140
|
+
timer.unref?.();
|
|
141
|
+
});
|
|
142
|
+
return Promise.race([run, deadline]).finally(() => clearTimeout(timer));
|
|
143
|
+
}
|
|
144
|
+
/** One ingest's re-verification: picks per record, runs each distinct check at most once, never throws. */
|
|
145
|
+
export class ReverifySession {
|
|
146
|
+
root;
|
|
147
|
+
cursor;
|
|
148
|
+
timeoutMs;
|
|
149
|
+
runner;
|
|
150
|
+
results = new Map();
|
|
151
|
+
checks;
|
|
152
|
+
ran = 0;
|
|
153
|
+
constructor(root, cursor, timeoutMs, runner, checks) {
|
|
154
|
+
this.root = root;
|
|
155
|
+
this.cursor = cursor;
|
|
156
|
+
this.timeoutMs = timeoutMs;
|
|
157
|
+
this.runner = runner;
|
|
158
|
+
this.checks = checks ?? reverifyChecks(root);
|
|
159
|
+
}
|
|
160
|
+
/** Re-verifies a success decided by a `used` verifier. The record's own command and task are never passed. */
|
|
161
|
+
async verify(used) {
|
|
162
|
+
const check = pickCheck(this.checks, used, this.cursor);
|
|
163
|
+
if (!check)
|
|
164
|
+
return { check: null, outcome: { status: 'error', commit: null } };
|
|
165
|
+
let pending = this.results.get(check.name);
|
|
166
|
+
if (!pending) {
|
|
167
|
+
this.ran++;
|
|
168
|
+
pending = withDeadline(Promise.resolve().then(() => this.runner(this.root, check, this.timeoutMs)), this.timeoutMs + Math.min(this.timeoutMs, 30_000)).then(o => (o && ['pass', 'fail', 'timeout', 'error'].includes(o.status) ? { status: o.status, commit: typeof o.commit === 'string' && COMMIT.test(o.commit) ? o.commit : null } : { status: 'error', commit: null }), () => ({ status: 'error', commit: null }));
|
|
169
|
+
this.results.set(check.name, pending);
|
|
170
|
+
}
|
|
171
|
+
return { check: check.name, outcome: await pending };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=verified-reverify.js.map
|
|
@@ -45,12 +45,12 @@ export declare const missionRecordSchema: z.ZodObject<{
|
|
|
45
45
|
digest: z.ZodString;
|
|
46
46
|
taskGraphRef: z.ZodString;
|
|
47
47
|
}, "strict", z.ZodTypeAny, {
|
|
48
|
-
revision: number;
|
|
49
48
|
digest: string;
|
|
49
|
+
revision: number;
|
|
50
50
|
taskGraphRef: string;
|
|
51
51
|
}, {
|
|
52
|
-
revision: number;
|
|
53
52
|
digest: string;
|
|
53
|
+
revision: number;
|
|
54
54
|
taskGraphRef: string;
|
|
55
55
|
}>;
|
|
56
56
|
policyRef: z.ZodString;
|
|
@@ -74,54 +74,54 @@ export declare const missionRecordSchema: z.ZodObject<{
|
|
|
74
74
|
updatedAt: z.ZodString;
|
|
75
75
|
}, "strict", z.ZodTypeAny, {
|
|
76
76
|
createdAt: string;
|
|
77
|
-
|
|
77
|
+
updatedAt: string;
|
|
78
78
|
schemaVersion: 1;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
plan: {
|
|
79
|
+
state: "completed" | "cancelled" | "failed" | "blocked" | "running" | "paused" | "queued" | "draft" | "planned" | "awaitingAuthorization" | "pauseRequested" | "verifying" | "cancelRequested";
|
|
80
|
+
acceptance: {
|
|
82
81
|
revision: number;
|
|
82
|
+
criteriaDigest: string;
|
|
83
|
+
};
|
|
84
|
+
plan: {
|
|
83
85
|
digest: string;
|
|
86
|
+
revision: number;
|
|
84
87
|
taskGraphRef: string;
|
|
85
88
|
};
|
|
89
|
+
objective: string;
|
|
86
90
|
missionId: string;
|
|
87
|
-
updatedAt: string;
|
|
88
91
|
tenantId: string;
|
|
89
92
|
workspaceId: string;
|
|
90
93
|
ownerPrincipalId: string;
|
|
94
|
+
revision: number;
|
|
91
95
|
policyRef: string;
|
|
92
96
|
budgetRef: string;
|
|
93
97
|
executionMode: "durable-executor" | "session-bound";
|
|
94
98
|
evidenceRefs: string[];
|
|
95
|
-
acceptance: {
|
|
96
|
-
revision: number;
|
|
97
|
-
criteriaDigest: string;
|
|
98
|
-
};
|
|
99
99
|
lastEventSequence: number;
|
|
100
100
|
authorizationRef?: string | undefined;
|
|
101
101
|
}, {
|
|
102
102
|
createdAt: string;
|
|
103
|
-
|
|
103
|
+
updatedAt: string;
|
|
104
104
|
schemaVersion: 1;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
plan: {
|
|
105
|
+
state: "completed" | "cancelled" | "failed" | "blocked" | "running" | "paused" | "queued" | "draft" | "planned" | "awaitingAuthorization" | "pauseRequested" | "verifying" | "cancelRequested";
|
|
106
|
+
acceptance: {
|
|
108
107
|
revision: number;
|
|
108
|
+
criteriaDigest: string;
|
|
109
|
+
};
|
|
110
|
+
plan: {
|
|
109
111
|
digest: string;
|
|
112
|
+
revision: number;
|
|
110
113
|
taskGraphRef: string;
|
|
111
114
|
};
|
|
115
|
+
objective: string;
|
|
112
116
|
missionId: string;
|
|
113
|
-
updatedAt: string;
|
|
114
117
|
tenantId: string;
|
|
115
118
|
workspaceId: string;
|
|
116
119
|
ownerPrincipalId: string;
|
|
120
|
+
revision: number;
|
|
117
121
|
policyRef: string;
|
|
118
122
|
budgetRef: string;
|
|
119
123
|
executionMode: "durable-executor" | "session-bound";
|
|
120
124
|
evidenceRefs: string[];
|
|
121
|
-
acceptance: {
|
|
122
|
-
revision: number;
|
|
123
|
-
criteriaDigest: string;
|
|
124
|
-
};
|
|
125
125
|
lastEventSequence: number;
|
|
126
126
|
authorizationRef?: string | undefined;
|
|
127
127
|
}>;
|
|
@@ -207,15 +207,15 @@ export declare const budgetSpecSchema: z.ZodObject<{
|
|
|
207
207
|
}, "strict", z.ZodTypeAny, {
|
|
208
208
|
currency: string;
|
|
209
209
|
ceilingMinor: number;
|
|
210
|
-
tokens?: number | undefined;
|
|
211
210
|
concurrency?: number | undefined;
|
|
211
|
+
tokens?: number | undefined;
|
|
212
212
|
wallTimeSeconds?: number | undefined;
|
|
213
213
|
retries?: number | undefined;
|
|
214
214
|
}, {
|
|
215
215
|
currency: string;
|
|
216
216
|
ceilingMinor: number;
|
|
217
|
-
tokens?: number | undefined;
|
|
218
217
|
concurrency?: number | undefined;
|
|
218
|
+
tokens?: number | undefined;
|
|
219
219
|
wallTimeSeconds?: number | undefined;
|
|
220
220
|
retries?: number | undefined;
|
|
221
221
|
}>;
|
|
@@ -301,15 +301,15 @@ export declare const planBodySchema: z.ZodObject<{
|
|
|
301
301
|
}, "strict", z.ZodTypeAny, {
|
|
302
302
|
currency: string;
|
|
303
303
|
ceilingMinor: number;
|
|
304
|
-
tokens?: number | undefined;
|
|
305
304
|
concurrency?: number | undefined;
|
|
305
|
+
tokens?: number | undefined;
|
|
306
306
|
wallTimeSeconds?: number | undefined;
|
|
307
307
|
retries?: number | undefined;
|
|
308
308
|
}, {
|
|
309
309
|
currency: string;
|
|
310
310
|
ceilingMinor: number;
|
|
311
|
-
tokens?: number | undefined;
|
|
312
311
|
concurrency?: number | undefined;
|
|
312
|
+
tokens?: number | undefined;
|
|
313
313
|
wallTimeSeconds?: number | undefined;
|
|
314
314
|
retries?: number | undefined;
|
|
315
315
|
}>;
|
|
@@ -340,8 +340,8 @@ export declare const planBodySchema: z.ZodObject<{
|
|
|
340
340
|
budget: {
|
|
341
341
|
currency: string;
|
|
342
342
|
ceilingMinor: number;
|
|
343
|
-
tokens?: number | undefined;
|
|
344
343
|
concurrency?: number | undefined;
|
|
344
|
+
tokens?: number | undefined;
|
|
345
345
|
wallTimeSeconds?: number | undefined;
|
|
346
346
|
retries?: number | undefined;
|
|
347
347
|
};
|
|
@@ -372,8 +372,8 @@ export declare const planBodySchema: z.ZodObject<{
|
|
|
372
372
|
budget: {
|
|
373
373
|
currency: string;
|
|
374
374
|
ceilingMinor: number;
|
|
375
|
-
tokens?: number | undefined;
|
|
376
375
|
concurrency?: number | undefined;
|
|
376
|
+
tokens?: number | undefined;
|
|
377
377
|
wallTimeSeconds?: number | undefined;
|
|
378
378
|
retries?: number | undefined;
|
|
379
379
|
};
|
|
@@ -397,11 +397,11 @@ export declare const createInputSchema: z.ZodObject<{
|
|
|
397
397
|
requestId: z.ZodString;
|
|
398
398
|
objective: z.ZodString;
|
|
399
399
|
}, "strict", z.ZodTypeAny, {
|
|
400
|
-
objective: string;
|
|
401
400
|
requestId: string;
|
|
402
|
-
}, {
|
|
403
401
|
objective: string;
|
|
402
|
+
}, {
|
|
404
403
|
requestId: string;
|
|
404
|
+
objective: string;
|
|
405
405
|
}>;
|
|
406
406
|
export declare const planInputSchema: z.ZodObject<{
|
|
407
407
|
requestId: z.ZodString;
|
|
@@ -489,15 +489,15 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
489
489
|
}, "strict", z.ZodTypeAny, {
|
|
490
490
|
currency: string;
|
|
491
491
|
ceilingMinor: number;
|
|
492
|
-
tokens?: number | undefined;
|
|
493
492
|
concurrency?: number | undefined;
|
|
493
|
+
tokens?: number | undefined;
|
|
494
494
|
wallTimeSeconds?: number | undefined;
|
|
495
495
|
retries?: number | undefined;
|
|
496
496
|
}, {
|
|
497
497
|
currency: string;
|
|
498
498
|
ceilingMinor: number;
|
|
499
|
-
tokens?: number | undefined;
|
|
500
499
|
concurrency?: number | undefined;
|
|
500
|
+
tokens?: number | undefined;
|
|
501
501
|
wallTimeSeconds?: number | undefined;
|
|
502
502
|
retries?: number | undefined;
|
|
503
503
|
}>;
|
|
@@ -528,8 +528,8 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
528
528
|
budget: {
|
|
529
529
|
currency: string;
|
|
530
530
|
ceilingMinor: number;
|
|
531
|
-
tokens?: number | undefined;
|
|
532
531
|
concurrency?: number | undefined;
|
|
532
|
+
tokens?: number | undefined;
|
|
533
533
|
wallTimeSeconds?: number | undefined;
|
|
534
534
|
retries?: number | undefined;
|
|
535
535
|
};
|
|
@@ -560,8 +560,8 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
560
560
|
budget: {
|
|
561
561
|
currency: string;
|
|
562
562
|
ceilingMinor: number;
|
|
563
|
-
tokens?: number | undefined;
|
|
564
563
|
concurrency?: number | undefined;
|
|
564
|
+
tokens?: number | undefined;
|
|
565
565
|
wallTimeSeconds?: number | undefined;
|
|
566
566
|
retries?: number | undefined;
|
|
567
567
|
};
|
|
@@ -601,8 +601,8 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
601
601
|
budget: {
|
|
602
602
|
currency: string;
|
|
603
603
|
ceilingMinor: number;
|
|
604
|
-
tokens?: number | undefined;
|
|
605
604
|
concurrency?: number | undefined;
|
|
605
|
+
tokens?: number | undefined;
|
|
606
606
|
wallTimeSeconds?: number | undefined;
|
|
607
607
|
retries?: number | undefined;
|
|
608
608
|
};
|
|
@@ -638,8 +638,8 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
638
638
|
budget: {
|
|
639
639
|
currency: string;
|
|
640
640
|
ceilingMinor: number;
|
|
641
|
-
tokens?: number | undefined;
|
|
642
641
|
concurrency?: number | undefined;
|
|
642
|
+
tokens?: number | undefined;
|
|
643
643
|
wallTimeSeconds?: number | undefined;
|
|
644
644
|
retries?: number | undefined;
|
|
645
645
|
};
|
|
@@ -687,13 +687,13 @@ export declare const requestActionInputSchema: z.ZodObject<{
|
|
|
687
687
|
action: z.ZodEnum<["pause", "resume", "cancel", "requestAuthorization", "admit"]>;
|
|
688
688
|
reason: z.ZodOptional<z.ZodString>;
|
|
689
689
|
}, "strict", z.ZodTypeAny, {
|
|
690
|
-
action: "pause" | "resume" | "cancel" | "
|
|
690
|
+
action: "pause" | "resume" | "cancel" | "requestAuthorization" | "admit";
|
|
691
691
|
requestId: string;
|
|
692
692
|
missionId: string;
|
|
693
693
|
expectedRevision: number;
|
|
694
694
|
reason?: string | undefined;
|
|
695
695
|
}, {
|
|
696
|
-
action: "pause" | "resume" | "cancel" | "
|
|
696
|
+
action: "pause" | "resume" | "cancel" | "requestAuthorization" | "admit";
|
|
697
697
|
requestId: string;
|
|
698
698
|
missionId: string;
|
|
699
699
|
expectedRevision: number;
|
package/dist/src/mods/apply.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface ApplyOptions {
|
|
|
18
18
|
skipInTestEnv?: boolean;
|
|
19
19
|
/** The marketplace to declare when the settings have none (default github ruvnet/ruflo). */
|
|
20
20
|
marketplace?: MarketplaceEntry;
|
|
21
|
+
/** ADR-509 §2.2: plugins (name or id) to install past a high scan finding; each override is recorded. */
|
|
22
|
+
acceptRisk?: readonly string[];
|
|
21
23
|
/** Injectable for tests. */
|
|
22
24
|
exec?: Exec;
|
|
23
25
|
}
|
package/dist/src/mods/apply.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { output } from '../output.js';
|
|
10
|
-
import { installMod, MARKETPLACE_NAME, MARKETPLACE_SOURCE, MOD_PLUGINS, readRecord, recordClaudeInstalled, scopeOfSettingsFile, uninstallMod, } from './install.js';
|
|
10
|
+
import { disableBlockedPlugins, installMod, MARKETPLACE_NAME, MARKETPLACE_SOURCE, MOD_PLUGIN_IDS, MOD_PLUGINS, readRecord, readSettingsFile, settingsFileFor, recordClaudeInstalled, scopeOfSettingsFile, uninstallMod, } from './install.js';
|
|
11
11
|
import { findClaudeBinary, installArgv, marketplaceArgv, repairPluginInstall, uninstallArgv, uninstallPlugins } from './plugin-repair.js';
|
|
12
12
|
import { claudeConfigDir, describeSource, installedState, marketplaceState, repairCommands, resolveFindings, sameSource } from './plugin-resolve.js';
|
|
13
|
+
import { scanBeforeInstall, verifyAndRecord } from './install-trust.js';
|
|
13
14
|
/** One line per thing a run added; empty on a re-run. */
|
|
14
15
|
export function describeAdded(added) {
|
|
15
16
|
return [
|
|
@@ -68,14 +69,39 @@ function noteRewrite(file, before) {
|
|
|
68
69
|
// Not ours to judge further.
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
/** The marketplace the settings file already declares, else the requested one. */
|
|
73
|
+
function declaredMarketplace(root, scope, requested) {
|
|
74
|
+
try {
|
|
75
|
+
const markets = readSettingsFile(settingsFileFor(root, scope)).extraKnownMarketplaces;
|
|
76
|
+
return markets?.[MARKETPLACE_NAME] ?? requested;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return requested;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
71
82
|
export async function applyMods(root, opts) {
|
|
72
|
-
const install = installMod(root, opts.scope, opts.dryRun === true, undefined, opts.marketplace ?? MARKETPLACE_SOURCE);
|
|
73
83
|
const configDir = claudeConfigDir(process.env, homedir());
|
|
84
|
+
const acceptRisk = new Set(opts.acceptRisk ?? []);
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
const blocked = new Map();
|
|
87
|
+
const told = new Set();
|
|
88
|
+
// ADR-509 §2.2: Claude Code loads an enabled plugin straight from the
|
|
89
|
+
// marketplace clone, install or not. So where a clone already exists, scan it
|
|
90
|
+
// BEFORE enabling anything from it, and never enable what the scan blocks.
|
|
91
|
+
const preClone = opts.dryRun ? null : marketplaceState(configDir).location;
|
|
92
|
+
const scannable = declaredMarketplace(root, opts.scope, opts.marketplace ?? MARKETPLACE_SOURCE).source.source !== 'directory';
|
|
93
|
+
if (preClone && scannable) {
|
|
94
|
+
for (const [id, why] of scanBeforeInstall(root, preClone, MOD_PLUGINS, acceptRisk, undefined, seen).blocked)
|
|
95
|
+
blocked.set(id, why);
|
|
96
|
+
}
|
|
97
|
+
const install = installMod(root, opts.scope, opts.dryRun === true, MOD_PLUGIN_IDS.filter((id) => !blocked.has(id)), opts.marketplace ?? MARKETPLACE_SOURCE);
|
|
98
|
+
// A blocked plugin enabled by an earlier run (or by hand) is turned off too.
|
|
99
|
+
turnOff(root, install, blocked, told);
|
|
74
100
|
const lines = describeAdded(install.added);
|
|
75
101
|
// Only what is enabled in the file: `claude plugin install` would flip a
|
|
76
102
|
// plugin someone set to false back to true.
|
|
77
103
|
const enabled = (install.next.enabledPlugins ?? {});
|
|
78
|
-
const plugins = MOD_PLUGINS.filter((p) => enabled[p.id] === true);
|
|
104
|
+
const plugins = MOD_PLUGINS.filter((p) => enabled[p.id] === true && !blocked.has(p.id));
|
|
79
105
|
// The marketplace the file declares wins: a person's own declaration is kept and used.
|
|
80
106
|
const declared = install.next.extraKnownMarketplaces?.[MARKETPLACE_NAME] ?? MARKETPLACE_SOURCE;
|
|
81
107
|
const market = marketplaceState(configDir);
|
|
@@ -111,16 +137,25 @@ export async function applyMods(root, opts) {
|
|
|
111
137
|
for (const line of repairCommands(root, opts.scope, k, plugins, declared))
|
|
112
138
|
output.writeln(` ${line}`);
|
|
113
139
|
};
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
// The `claude` step may not run below: say what is enabled without a scan.
|
|
141
|
+
const unscanned = () => {
|
|
142
|
+
if (!preClone && scannable && plugins.length)
|
|
143
|
+
output.printWarning(`No ruflo marketplace clone yet, so nothing was scanned (ADR-509): ${plugins.map((p) => p.id).join(', ')} enabled unscanned; Claude Code clones it on the first trusted start. Run ruflo mods install again after that to scan it.`);
|
|
144
|
+
};
|
|
145
|
+
if (opts.pluginInstall === false) {
|
|
146
|
+
unscanned();
|
|
147
|
+
return { install, resolvable: blocked.size === 0, skipped: '--no-plugin-install' };
|
|
148
|
+
}
|
|
116
149
|
if (opts.skipInTestEnv && (process.env.VITEST || process.env.CI)) {
|
|
117
150
|
output.writeln(output.dim(`claude plugin step skipped (${process.env.VITEST ? 'VITEST' : 'CI'} set).`));
|
|
151
|
+
unscanned();
|
|
118
152
|
manual();
|
|
119
153
|
return { install, resolvable: false, skipped: 'test or CI environment' };
|
|
120
154
|
}
|
|
121
155
|
const claude = findClaudeBinary(process.env, homedir());
|
|
122
156
|
if (!claude) {
|
|
123
157
|
output.printWarning('No runnable claude binary on PATH: the plugins are enabled in settings, not installed.');
|
|
158
|
+
unscanned();
|
|
124
159
|
manual();
|
|
125
160
|
return { install, resolvable: false, skipped: 'no claude on PATH' };
|
|
126
161
|
}
|
|
@@ -130,8 +165,21 @@ export async function applyMods(root, opts) {
|
|
|
130
165
|
// Never claim (and so never uninstall) a plugin that was installed before, or one ruflo did not enable.
|
|
131
166
|
const before = new Set(plugins.filter((p) => installedState(root, configDir, p.id).installed).map((p) => p.id));
|
|
132
167
|
const textBefore = readText(install.settingsFile);
|
|
133
|
-
const repair = await repairPluginInstall({
|
|
168
|
+
const repair = await repairPluginInstall({
|
|
169
|
+
projectRoot: root, scope: opts.scope, configDir, claude, plugins, marketplace: declared, exec: opts.exec,
|
|
170
|
+
// ADR-509 §2.2: scan the refreshed clone before any install (a directory marketplace has no install step to gate).
|
|
171
|
+
beforeInstall: (clone) => {
|
|
172
|
+
const gate = scanBeforeInstall(root, clone, plugins, acceptRisk, undefined, seen);
|
|
173
|
+
for (const [id, why] of gate.blocked)
|
|
174
|
+
blocked.set(id, why);
|
|
175
|
+
return gate;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
134
178
|
printSteps(repair.steps);
|
|
179
|
+
// The refresh may have brought a finding the first scan did not see: that plugin is turned off as well.
|
|
180
|
+
turnOff(root, install, blocked, told);
|
|
181
|
+
// ADR-509 §2.1: verify what Claude Code will load and leave the result for the mod's trust gate.
|
|
182
|
+
verifyAndRecord(configDir, marketplaceState(configDir).location, plugins.map((p) => p.id.split('@')[0]));
|
|
135
183
|
if (declared.source.source === 'directory' && repair.ok)
|
|
136
184
|
output.writeln(output.dim(`Directory marketplace: plugins load live from ${declared.source.path} (no clone, no install).`));
|
|
137
185
|
const enabledByRuflo = new Set(readRecord(root)?.files[install.settingsFile]?.plugins ?? []);
|
|
@@ -141,8 +189,8 @@ export async function applyMods(root, opts) {
|
|
|
141
189
|
.filter((id) => !before.has(id) && enabledByRuflo.has(id));
|
|
142
190
|
recordClaudeInstalled(root, install.settingsFile, ours);
|
|
143
191
|
noteRewrite(install.settingsFile, textBefore);
|
|
144
|
-
const failed = resolveFindings(root, opts.scope, configDir, undefined, plugins.map((p) => p.id), declared).filter((f) => f.status === 'fail');
|
|
145
|
-
if (repair.ok && failed.length === 0)
|
|
192
|
+
const failed = resolveFindings(root, opts.scope, configDir, undefined, plugins.filter((p) => !blocked.has(p.id)).map((p) => p.id), declared).filter((f) => f.status === 'fail');
|
|
193
|
+
if (repair.ok && failed.length === 0 && blocked.size === 0)
|
|
146
194
|
return { install, resolvable: true };
|
|
147
195
|
for (const f of failed)
|
|
148
196
|
output.writeln(`${output.error('✗')} ${f.name}: ${f.message}`);
|
|
@@ -150,6 +198,18 @@ export async function applyMods(root, opts) {
|
|
|
150
198
|
manual(now.known && sameSource(now.source, declared.source));
|
|
151
199
|
return { install, resolvable: false };
|
|
152
200
|
}
|
|
201
|
+
/** ADR-509 §2.2: turn off (enabledPlugins + install record) each blocked plugin, and say so. */
|
|
202
|
+
function turnOff(root, install, blocked, told) {
|
|
203
|
+
if (install.dryRun || blocked.size === 0)
|
|
204
|
+
return;
|
|
205
|
+
const off = disableBlockedPlugins(root, install.settingsFile, [...blocked.keys()]);
|
|
206
|
+
for (const [id, why] of blocked) {
|
|
207
|
+
if (told.has(id) && !off.includes(id))
|
|
208
|
+
continue; // already said, and nothing changed since
|
|
209
|
+
told.add(id);
|
|
210
|
+
output.writeln(`${output.error('✗')} ${id}: ${off.includes(id) ? `enabledPlugins["${id}"] removed from ${install.settingsFile}` : 'not enabled'}; ${why}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
153
213
|
/**
|
|
154
214
|
* `ruflo mods uninstall`: `claude plugin uninstall` exactly the plugins ruflo
|
|
155
215
|
* installed, then remove exactly the settings keys it added.
|