@claude-flow/cli 3.64.0 → 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 +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/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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-509 §2.3: the stop `ruflo init` / `ruflo init upgrade --settings` makes
|
|
3
|
+
* before carrying a project settings file's auto-approve keys forward.
|
|
4
|
+
*
|
|
5
|
+
* The check runs BEFORE anything is written, so a stop leaves the project as
|
|
6
|
+
* it was. `keep` proceeds (the keys are still reported); `drop` proceeds and
|
|
7
|
+
* the merged file is written without them.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { isRufloHelperCommand } from './helper-commands.js';
|
|
12
|
+
import { CRITICAL_HELPERS } from './helper-refresh.js';
|
|
13
|
+
import { HELPERS_MANIFEST_FILE, sha256Hex, verifyHelpersManifest } from './helper-signing.js';
|
|
14
|
+
import { dropAutoApproveKeys, formatRiskFindingsAsWarnings, scanProjectAutoApproveKeys, } from './settings-risk-scanner.js';
|
|
15
|
+
export function scanAutoApprove(settings) {
|
|
16
|
+
return scanProjectAutoApproveKeys(settings, isRufloHelperCommand);
|
|
17
|
+
}
|
|
18
|
+
/** Reads a settings file and judges it; null when the file is absent, unreadable or not a JSON object. */
|
|
19
|
+
export function checkRiskySettings(settingsPath, mode = 'stop') {
|
|
20
|
+
if (!fs.existsSync(settingsPath))
|
|
21
|
+
return null;
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
30
|
+
return null;
|
|
31
|
+
const findings = scanAutoApprove(parsed);
|
|
32
|
+
if (findings.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
return { mode, findings, lines: formatRiskFindingsAsWarnings(findings), stop: mode === 'stop' };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ADR-509 §2.3: the helpers ruflo's hooks launch, as the PROJECT ships them.
|
|
38
|
+
* `init` keeps an existing `.claude/helpers/<name>` unless --force, and the
|
|
39
|
+
* project-scope hook commands prefer the project's copy, so a repository that
|
|
40
|
+
* ships its own hook-handler.cjs runs it under an exact ruflo hook command. A
|
|
41
|
+
* helper is trusted when its bytes are the copy this ruflo ships, or its
|
|
42
|
+
* SHA-256 is in a helpers manifest signed with ruflo's key (this ruflo's, or
|
|
43
|
+
* the project's own, which an older signed release left). Anything else is a
|
|
44
|
+
* finding. A `.LOCKED` helpers dir (deliberate local edits) is skipped, and with
|
|
45
|
+
* no ground truth at all this fails open. `pubkeyPem` is injectable for tests.
|
|
46
|
+
*/
|
|
47
|
+
export function untrustedProjectHelpers(targetDir, sourceHelpersDir, pubkeyPem) {
|
|
48
|
+
const helpersDir = path.join(targetDir, '.claude', 'helpers');
|
|
49
|
+
// .LOCKED is the sanctioned marker for deliberately edited helpers (helper-refresh honours it too).
|
|
50
|
+
if (fs.existsSync(path.join(helpersDir, '.LOCKED')))
|
|
51
|
+
return [];
|
|
52
|
+
const signed = (dir) => {
|
|
53
|
+
if (!dir)
|
|
54
|
+
return null;
|
|
55
|
+
try {
|
|
56
|
+
return verifyHelpersManifest(fs.readFileSync(path.join(dir, HELPERS_MANIFEST_FILE), 'utf-8'), pubkeyPem);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const manifests = [signed(sourceHelpersDir), signed(helpersDir)].filter((m) => m !== null);
|
|
63
|
+
// No ground truth at all (no shipped copy, no signed manifest): fail open, as
|
|
64
|
+
// helper-integrity does, rather than flag every install.
|
|
65
|
+
if (!sourceHelpersDir && manifests.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const name of CRITICAL_HELPERS) {
|
|
69
|
+
const file = path.join(helpersDir, name);
|
|
70
|
+
let bytes;
|
|
71
|
+
try {
|
|
72
|
+
if (!fs.lstatSync(file).isFile())
|
|
73
|
+
throw new Error('not a regular file');
|
|
74
|
+
bytes = fs.readFileSync(file);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code === 'ENOENT')
|
|
78
|
+
continue;
|
|
79
|
+
out.push({ target: { kind: 'helper', name }, location: `.claude/helpers/${name}`, snippet: '(unreadable or not a regular file)', reason: "is not ruflo's signed helper; ruflo's hooks would run it" });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (sourceHelpersDir) {
|
|
83
|
+
try {
|
|
84
|
+
if (bytes.equals(fs.readFileSync(path.join(sourceHelpersDir, name))))
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// not shipped by this ruflo: fall through to the signed manifests
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const hash = sha256Hex(bytes);
|
|
92
|
+
if (manifests.some((m) => m.files[name] === hash))
|
|
93
|
+
continue;
|
|
94
|
+
out.push({
|
|
95
|
+
target: { kind: 'helper', name },
|
|
96
|
+
location: `.claude/helpers/${name}`,
|
|
97
|
+
snippet: `sha256 ${hash.slice(0, 16)}…`,
|
|
98
|
+
reason: "differs from ruflo's signed helper; init keeps it and ruflo's hooks would run it",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The init gate: the settings file's auto-approve keys plus the project's
|
|
105
|
+
* untrusted helpers. Null when there is nothing to report.
|
|
106
|
+
*/
|
|
107
|
+
export function checkInitTrust(targetDir, mode, sourceHelpersDir) {
|
|
108
|
+
const settings = checkRiskySettings(path.join(targetDir, '.claude', 'settings.json'), mode);
|
|
109
|
+
const helpers = untrustedProjectHelpers(targetDir, sourceHelpersDir);
|
|
110
|
+
const findings = [...(settings?.findings ?? []), ...helpers];
|
|
111
|
+
if (findings.length === 0)
|
|
112
|
+
return null;
|
|
113
|
+
return { mode, findings, lines: formatRiskFindingsAsWarnings(findings), stop: mode === 'stop' };
|
|
114
|
+
}
|
|
115
|
+
/** `drop`: the settings without the auto-approve keys; otherwise the settings unchanged. */
|
|
116
|
+
export function applyRiskyMode(settings, mode) {
|
|
117
|
+
if (mode !== 'drop')
|
|
118
|
+
return { settings, dropped: 0 };
|
|
119
|
+
const findings = scanAutoApprove(settings);
|
|
120
|
+
return findings.length ? { settings: dropAutoApproveKeys(settings, findings), dropped: findings.length } : { settings, dropped: 0 };
|
|
121
|
+
}
|
|
122
|
+
/** Reads `--keep-risky-settings` / `--drop-risky-settings` (camel or kebab); both at once is an error. */
|
|
123
|
+
export function riskyModeFromFlags(flags) {
|
|
124
|
+
const keep = flags.keepRiskySettings === true || flags['keep-risky-settings'] === true;
|
|
125
|
+
const drop = flags.dropRiskySettings === true || flags['drop-risky-settings'] === true;
|
|
126
|
+
if (keep && drop)
|
|
127
|
+
return { error: '--keep-risky-settings and --drop-risky-settings cannot be used together' };
|
|
128
|
+
return keep ? 'keep' : drop ? 'drop' : 'stop';
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=risky-settings-gate.js.map
|
|
@@ -44,4 +44,39 @@ export declare function scanAllowRuleForRisk(rule: string): string[];
|
|
|
44
44
|
export declare function scanSettingsForRisk(settings: Record<string, unknown>): SettingsRiskFinding[];
|
|
45
45
|
/** Formats findings as short, human-readable CLI warning lines. */
|
|
46
46
|
export declare function formatRiskFindingsAsWarnings(findings: SettingsRiskFinding[]): string[];
|
|
47
|
+
/** What a finding points at, precisely enough for dropAutoApproveKeys to remove only that. */
|
|
48
|
+
export type AutoApproveTarget = {
|
|
49
|
+
kind: 'enableAllProjectMcpServers';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'enabledMcpjsonServers';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'defaultMode';
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'allow';
|
|
56
|
+
index: number;
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'hook';
|
|
59
|
+
event: string;
|
|
60
|
+
group: number;
|
|
61
|
+
hook: number;
|
|
62
|
+
}
|
|
63
|
+
/** A project `.claude/helpers/<name>` that is not ruflo's signed copy (init keeps it without --force). */
|
|
64
|
+
| {
|
|
65
|
+
kind: 'helper';
|
|
66
|
+
name: string;
|
|
67
|
+
};
|
|
68
|
+
export interface AutoApproveFinding extends SettingsRiskFinding {
|
|
69
|
+
target: AutoApproveTarget;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The auto-approve keys of a PROJECT settings file (`.claude/settings.json` or
|
|
73
|
+
* `.claude/settings.local.json`). `isRufloHook` decides which hook commands are
|
|
74
|
+
* ruflo's own helpers (injected so this module stays dependency-free).
|
|
75
|
+
*/
|
|
76
|
+
export declare function scanProjectAutoApproveKeys(settings: Record<string, unknown>, isRufloHook: (command: string) => boolean): AutoApproveFinding[];
|
|
77
|
+
/** A deep copy of `settings` without the keys/entries the findings point at. Never mutates its input. */
|
|
78
|
+
export declare function dropAutoApproveKeys(settings: Record<string, unknown>, findings: readonly AutoApproveFinding[]): Record<string, unknown>;
|
|
79
|
+
/** What `init` does when the existing settings carry auto-approve keys. */
|
|
80
|
+
export type RiskySettingsMode = 'stop' | 'keep' | 'drop';
|
|
81
|
+
export declare const RISKY_SETTINGS_HINT = "Re-run with --drop-risky-settings to write the merged settings without these keys (and replace any flagged helper with ruflo's copy), or --keep-risky-settings to keep them.";
|
|
47
82
|
//# sourceMappingURL=settings-risk-scanner.d.ts.map
|
|
@@ -177,4 +177,89 @@ export function scanSettingsForRisk(settings) {
|
|
|
177
177
|
export function formatRiskFindingsAsWarnings(findings) {
|
|
178
178
|
return findings.map((f) => `${f.location}: ${f.reason} — "${f.snippet}"`);
|
|
179
179
|
}
|
|
180
|
+
const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
181
|
+
/** A bare `Bash`, `Bash(*)`, `Bash(*:*)` or a `*` wildcard pre-approves every command. */
|
|
182
|
+
function isBlanketAllow(rule) {
|
|
183
|
+
const t = rule.trim();
|
|
184
|
+
return t === '*' || /^Bash$/i.test(t) || /^Bash\(\s*\*\s*(:\s*\*\s*)?\)$/i.test(t) || /^\*\s*\(.*\)$/.test(t);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The auto-approve keys of a PROJECT settings file (`.claude/settings.json` or
|
|
188
|
+
* `.claude/settings.local.json`). `isRufloHook` decides which hook commands are
|
|
189
|
+
* ruflo's own helpers (injected so this module stays dependency-free).
|
|
190
|
+
*/
|
|
191
|
+
export function scanProjectAutoApproveKeys(settings, isRufloHook) {
|
|
192
|
+
const out = [];
|
|
193
|
+
if (settings.enableAllProjectMcpServers === true) {
|
|
194
|
+
out.push({ target: { kind: 'enableAllProjectMcpServers' }, location: 'enableAllProjectMcpServers', snippet: 'true', reason: 'starts every .mcp.json server in the project without asking' });
|
|
195
|
+
}
|
|
196
|
+
const enabled = settings.enabledMcpjsonServers;
|
|
197
|
+
if (Array.isArray(enabled) && enabled.length > 0) {
|
|
198
|
+
out.push({ target: { kind: 'enabledMcpjsonServers' }, location: 'enabledMcpjsonServers', snippet: buildSnippet(enabled.map(String).join(', ')), reason: 'pre-approves .mcp.json servers before the trust dialog' });
|
|
199
|
+
}
|
|
200
|
+
const permissions = isRecord(settings.permissions) ? settings.permissions : undefined;
|
|
201
|
+
if (permissions?.defaultMode === 'bypassPermissions') {
|
|
202
|
+
out.push({ target: { kind: 'defaultMode' }, location: 'permissions.defaultMode', snippet: 'bypassPermissions', reason: 'turns off every permission prompt' });
|
|
203
|
+
}
|
|
204
|
+
if (Array.isArray(permissions?.allow)) {
|
|
205
|
+
permissions.allow.forEach((rule, index) => {
|
|
206
|
+
if (typeof rule === 'string' && isBlanketAllow(rule)) {
|
|
207
|
+
out.push({ target: { kind: 'allow', index }, location: `permissions.allow[${index}]`, snippet: buildSnippet(rule), reason: 'pre-approves every command (bare Bash or * wildcard)' });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const hooks = isRecord(settings.hooks) ? settings.hooks : undefined;
|
|
212
|
+
for (const [event, groups] of Object.entries(hooks ?? {})) {
|
|
213
|
+
if (!Array.isArray(groups))
|
|
214
|
+
continue;
|
|
215
|
+
groups.forEach((g, group) => {
|
|
216
|
+
const list = isRecord(g) && Array.isArray(g.hooks) ? g.hooks : [];
|
|
217
|
+
list.forEach((h, hook) => {
|
|
218
|
+
if (!isRecord(h) || typeof h.command !== 'string' || isRufloHook(h.command))
|
|
219
|
+
return;
|
|
220
|
+
out.push({ target: { kind: 'hook', event, group, hook }, location: `hooks.${event}[${group}].hooks[${hook}]`, snippet: buildSnippet(h.command), reason: 'runs a command that is not a ruflo helper' });
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
/** A deep copy of `settings` without the keys/entries the findings point at. Never mutates its input. */
|
|
227
|
+
export function dropAutoApproveKeys(settings, findings) {
|
|
228
|
+
const next = JSON.parse(JSON.stringify(settings));
|
|
229
|
+
const perms = isRecord(next.permissions) ? next.permissions : undefined;
|
|
230
|
+
const allowDrop = new Set();
|
|
231
|
+
const hookDrop = new Set();
|
|
232
|
+
for (const { target: t } of findings) {
|
|
233
|
+
if (t.kind === 'enableAllProjectMcpServers')
|
|
234
|
+
delete next.enableAllProjectMcpServers;
|
|
235
|
+
else if (t.kind === 'enabledMcpjsonServers')
|
|
236
|
+
delete next.enabledMcpjsonServers;
|
|
237
|
+
else if (t.kind === 'defaultMode' && perms)
|
|
238
|
+
delete perms.defaultMode;
|
|
239
|
+
else if (t.kind === 'allow')
|
|
240
|
+
allowDrop.add(t.index);
|
|
241
|
+
else if (t.kind === 'hook')
|
|
242
|
+
hookDrop.add(`${t.event}\u0000${t.group}\u0000${t.hook}`);
|
|
243
|
+
}
|
|
244
|
+
if (perms && Array.isArray(perms.allow))
|
|
245
|
+
perms.allow = perms.allow.filter((_, i) => !allowDrop.has(i));
|
|
246
|
+
if (isRecord(next.hooks) && hookDrop.size) {
|
|
247
|
+
for (const [event, groups] of Object.entries(next.hooks)) {
|
|
248
|
+
if (!Array.isArray(groups))
|
|
249
|
+
continue;
|
|
250
|
+
const kept = groups
|
|
251
|
+
.map((g, gi) => (isRecord(g) && Array.isArray(g.hooks) ? { ...g, hooks: g.hooks.filter((_, hi) => !hookDrop.has(`${event}\u0000${gi}\u0000${hi}`)) } : g))
|
|
252
|
+
.filter((g) => !(isRecord(g) && Array.isArray(g.hooks) && g.hooks.length === 0));
|
|
253
|
+
if (kept.length)
|
|
254
|
+
next.hooks[event] = kept;
|
|
255
|
+
else
|
|
256
|
+
delete next.hooks[event];
|
|
257
|
+
}
|
|
258
|
+
// No hooks left: drop the key, so init writes its own hooks as it does for a file without any.
|
|
259
|
+
if (Object.keys(next.hooks).length === 0)
|
|
260
|
+
delete next.hooks;
|
|
261
|
+
}
|
|
262
|
+
return next;
|
|
263
|
+
}
|
|
264
|
+
export const RISKY_SETTINGS_HINT = 'Re-run with --drop-risky-settings to write the merged settings without these keys (and replace any flagged helper with ruflo\'s copy), or --keep-risky-settings to keep them.';
|
|
180
265
|
//# sourceMappingURL=settings-risk-scanner.js.map
|
package/dist/src/init/types.d.ts
CHANGED
|
@@ -296,6 +296,14 @@ export interface InitOptions {
|
|
|
296
296
|
* GitHub contributor graph. Pass `--attribution` to opt in.
|
|
297
297
|
*/
|
|
298
298
|
attribution?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* ADR-509 §2.3 — what to do when the existing project settings.json carries
|
|
301
|
+
* auto-approve keys (enableAllProjectMcpServers, enabledMcpjsonServers,
|
|
302
|
+
* bypassPermissions, blanket Bash allows, non-ruflo hooks): `stop`
|
|
303
|
+
* (default — nothing is written), `keep` (--keep-risky-settings) or `drop`
|
|
304
|
+
* (--drop-risky-settings).
|
|
305
|
+
*/
|
|
306
|
+
riskySettings?: 'stop' | 'keep' | 'drop';
|
|
299
307
|
}
|
|
300
308
|
/**
|
|
301
309
|
* Default init options - full V3 setup
|
|
@@ -334,5 +342,9 @@ export interface InitResult {
|
|
|
334
342
|
* init — surfaced so a user can review before trusting it.
|
|
335
343
|
*/
|
|
336
344
|
warnings?: string[];
|
|
345
|
+
/** ADR-509 §2.3 — auto-approve keys found in the existing project settings.json. */
|
|
346
|
+
riskySettings?: string[];
|
|
347
|
+
/** ADR-509 §2.3 — init stopped before writing anything because of riskySettings. */
|
|
348
|
+
stoppedOnRiskySettings?: boolean;
|
|
337
349
|
}
|
|
338
350
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -97,7 +97,7 @@ export interface CapabilityBrain {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
/** Top-level commands from the CLI command loader. Kept as a checked manifest. */
|
|
100
|
-
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "announcements", "tui", "harness"];
|
|
100
|
+
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "cost", "announcements", "tui", "harness"];
|
|
101
101
|
export interface ImplementationLoopStep {
|
|
102
102
|
id: string;
|
|
103
103
|
name: string;
|
|
@@ -15,7 +15,7 @@ export const RUFLO_CLI_COMMANDS = [
|
|
|
15
15
|
'doctor', 'verify', 'analyze', 'route', 'progress', 'issues', 'update',
|
|
16
16
|
'ruvector', 'benchmark', 'guidance', 'appliance', 'appliance-advanced',
|
|
17
17
|
'transfer-store', 'cleanup', 'autopilot', 'gaia-bench', 'metaharness',
|
|
18
|
-
'eject', 'funnel', 'settings', 'auth', 'proxy', 'advisor', 'spinner',
|
|
18
|
+
'eject', 'funnel', 'settings', 'auth', 'proxy', 'advisor', 'spinner', 'cost',
|
|
19
19
|
'announcements', 'tui', 'harness',
|
|
20
20
|
];
|
|
21
21
|
export const IMPLEMENTATION_LOOP = [
|
|
@@ -5482,7 +5482,8 @@ export const hooksVerifiedIngest = {
|
|
|
5482
5482
|
handler: async (params) => {
|
|
5483
5483
|
const { ingestVerifiedOutcomes } = await import('../memory/verified-outcomes.js');
|
|
5484
5484
|
const sona = await getSONAOptimizer();
|
|
5485
|
-
|
|
5485
|
+
// ADR-503 §2.3: only this explicit ingest may re-verify (run project code), and only when learningLoop.reverify is on.
|
|
5486
|
+
const result = await ingestVerifiedOutcomes({ dryRun: params.dryRun === true, force: params.force === true || params.dryRun === true, sona: sona, allowReverify: true });
|
|
5486
5487
|
return { success: true, ...result };
|
|
5487
5488
|
},
|
|
5488
5489
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gaming defences for the verified-outcome learning loop (ADR-503).
|
|
3
|
+
*
|
|
4
|
+
* Every rule here can only downgrade: a tainted record does not train, a
|
|
5
|
+
* pattern that looks gamed is held back from the prompt. Nothing here can
|
|
6
|
+
* turn an `unknown` into a verified outcome.
|
|
7
|
+
*
|
|
8
|
+
* - §2.1/§2.2 taints: the mod marks a `verified-success` whose turn edited a
|
|
9
|
+
* test file or a verifier config, or narrowed its check. Ingest never
|
|
10
|
+
* trains a tainted record unless `.claude-flow/config.json`
|
|
11
|
+
* `learningLoop.allowTaint` lists every one of its classes.
|
|
12
|
+
* - §2.3 re-verify settings (the runner lives in verified-reverify.ts).
|
|
13
|
+
* - §2.4 the anomaly gate on lessons.
|
|
14
|
+
*/
|
|
15
|
+
export type Taint = 'edited-tests' | 'edited-verifier-config' | 'narrowed-verifier';
|
|
16
|
+
export declare const TAINTS: readonly Taint[];
|
|
17
|
+
/** A record's `taint` field, validated: known classes only, each once. Anything else is dropped. */
|
|
18
|
+
export declare function parseTaint(raw: unknown): Taint[];
|
|
19
|
+
export interface LoopConfig {
|
|
20
|
+
/** Taint classes a project lets train (default none). */
|
|
21
|
+
readonly allowTaint: readonly Taint[];
|
|
22
|
+
/** Re-run a verifier at HEAD before a success trains (default off: it runs project code). */
|
|
23
|
+
readonly reverify: boolean;
|
|
24
|
+
readonly reverifyTimeoutMs: number;
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_REVERIFY_TIMEOUT_MS = 300000;
|
|
27
|
+
export declare const DEFAULT_LOOP_CONFIG: LoopConfig;
|
|
28
|
+
/**
|
|
29
|
+
* `learningLoop` from `.claude-flow/config.json`. A missing, unreadable or
|
|
30
|
+
* malformed file means the defaults: no taint allowed, re-verify off. Only
|
|
31
|
+
* `reverify: true` (the boolean) turns re-verification on.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readLoopConfig(root: string): LoopConfig;
|
|
34
|
+
export declare function parseLoopConfig(raw: unknown): LoopConfig;
|
|
35
|
+
/** The taint classes of a record that its project did not allow; non-empty means it never trains. */
|
|
36
|
+
export declare const blockedTaints: (taint: readonly Taint[], config: LoopConfig) => Taint[];
|
|
37
|
+
/**
|
|
38
|
+
* What ingest knows about a verified pattern, from verified records only
|
|
39
|
+
* (SONA's own counts include other feeders). Sessions are capped: the gate
|
|
40
|
+
* only asks whether there were two.
|
|
41
|
+
*/
|
|
42
|
+
export interface PatternStats {
|
|
43
|
+
successes: number;
|
|
44
|
+
failures: number;
|
|
45
|
+
/** Successes that carried an allowed taint. */
|
|
46
|
+
tainted: number;
|
|
47
|
+
/** Successes decided by a narrowed command. */
|
|
48
|
+
narrowed: number;
|
|
49
|
+
sessions: string[];
|
|
50
|
+
}
|
|
51
|
+
export declare const MAX_PATTERN_SESSIONS = 4;
|
|
52
|
+
export declare const emptyStats: () => PatternStats;
|
|
53
|
+
export declare function parseStats(raw: unknown): PatternStats;
|
|
54
|
+
/** Folds one trained record into its pattern's stats. */
|
|
55
|
+
export declare function addOutcome(stats: PatternStats, success: boolean, taint: readonly Taint[], session: string | null): void;
|
|
56
|
+
export type HoldReason = 'perfect-and-tainted' | 'only-narrowed' | 'one-session';
|
|
57
|
+
/** §2.4 case 1: records at or above which a perfect record with half its successes tainted is held. */
|
|
58
|
+
export declare const PERFECT_MIN_RECORDS = 8;
|
|
59
|
+
/**
|
|
60
|
+
* Why a would-be lesson is held back from the prompt (ADR-503 §2.4), or null.
|
|
61
|
+
* A held pattern stays in SONA; it only never becomes a prompt lesson.
|
|
62
|
+
*
|
|
63
|
+
* 1. 100% success over 8 or more records, half or more of them tainted (allowed).
|
|
64
|
+
* 2. Every success came from a narrowed command.
|
|
65
|
+
* 3. Every success came from one session: no independent repetition. A pattern
|
|
66
|
+
* with no recorded sessions (a record from before ADR-503, or one without a
|
|
67
|
+
* session) counts as one session.
|
|
68
|
+
*/
|
|
69
|
+
export declare function holdReason(stats: PatternStats, sonaSuccesses: number): HoldReason | null;
|
|
70
|
+
//# sourceMappingURL=verified-guards.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gaming defences for the verified-outcome learning loop (ADR-503).
|
|
3
|
+
*
|
|
4
|
+
* Every rule here can only downgrade: a tainted record does not train, a
|
|
5
|
+
* pattern that looks gamed is held back from the prompt. Nothing here can
|
|
6
|
+
* turn an `unknown` into a verified outcome.
|
|
7
|
+
*
|
|
8
|
+
* - §2.1/§2.2 taints: the mod marks a `verified-success` whose turn edited a
|
|
9
|
+
* test file or a verifier config, or narrowed its check. Ingest never
|
|
10
|
+
* trains a tainted record unless `.claude-flow/config.json`
|
|
11
|
+
* `learningLoop.allowTaint` lists every one of its classes.
|
|
12
|
+
* - §2.3 re-verify settings (the runner lives in verified-reverify.ts).
|
|
13
|
+
* - §2.4 the anomaly gate on lessons.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
export const TAINTS = ['edited-tests', 'edited-verifier-config', 'narrowed-verifier'];
|
|
18
|
+
/** A record's `taint` field, validated: known classes only, each once. Anything else is dropped. */
|
|
19
|
+
export function parseTaint(raw) {
|
|
20
|
+
if (!Array.isArray(raw))
|
|
21
|
+
return [];
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const t of raw.slice(0, 8))
|
|
24
|
+
if (TAINTS.includes(t) && !out.includes(t))
|
|
25
|
+
out.push(t);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
export const DEFAULT_REVERIFY_TIMEOUT_MS = 300_000;
|
|
29
|
+
export const DEFAULT_LOOP_CONFIG = { allowTaint: [], reverify: false, reverifyTimeoutMs: DEFAULT_REVERIFY_TIMEOUT_MS };
|
|
30
|
+
/**
|
|
31
|
+
* `learningLoop` from `.claude-flow/config.json`. A missing, unreadable or
|
|
32
|
+
* malformed file means the defaults: no taint allowed, re-verify off. Only
|
|
33
|
+
* `reverify: true` (the boolean) turns re-verification on.
|
|
34
|
+
*/
|
|
35
|
+
export function readLoopConfig(root) {
|
|
36
|
+
const path = join(root, '.claude-flow', 'config.json');
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(path))
|
|
39
|
+
return DEFAULT_LOOP_CONFIG;
|
|
40
|
+
const raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
41
|
+
return parseLoopConfig(raw?.learningLoop);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return DEFAULT_LOOP_CONFIG;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function parseLoopConfig(raw) {
|
|
48
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
49
|
+
return DEFAULT_LOOP_CONFIG;
|
|
50
|
+
const r = raw;
|
|
51
|
+
const sec = typeof r.reverifyTimeoutSec === 'number' && Number.isFinite(r.reverifyTimeoutSec) ? Math.min(Math.max(Math.floor(r.reverifyTimeoutSec), 10), 3600) : DEFAULT_REVERIFY_TIMEOUT_MS / 1000;
|
|
52
|
+
return { allowTaint: parseTaint(r.allowTaint), reverify: r.reverify === true, reverifyTimeoutMs: sec * 1000 };
|
|
53
|
+
}
|
|
54
|
+
/** The taint classes of a record that its project did not allow; non-empty means it never trains. */
|
|
55
|
+
export const blockedTaints = (taint, config) => taint.filter(t => !config.allowTaint.includes(t));
|
|
56
|
+
export const MAX_PATTERN_SESSIONS = 4;
|
|
57
|
+
const SESSION = /^[A-Za-z0-9_-]{1,40}$/;
|
|
58
|
+
const count = (v) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.min(Math.floor(v), 1_000_000) : 0);
|
|
59
|
+
export const emptyStats = () => ({ successes: 0, failures: 0, tainted: 0, narrowed: 0, sessions: [] });
|
|
60
|
+
export function parseStats(raw) {
|
|
61
|
+
const r = (raw && typeof raw === 'object' ? raw : {});
|
|
62
|
+
const sessions = Array.isArray(r.sessions) ? [...new Set(r.sessions.filter((s) => typeof s === 'string' && SESSION.test(s)))].slice(0, MAX_PATTERN_SESSIONS) : [];
|
|
63
|
+
return { successes: count(r.successes), failures: count(r.failures), tainted: count(r.tainted), narrowed: count(r.narrowed), sessions };
|
|
64
|
+
}
|
|
65
|
+
/** Folds one trained record into its pattern's stats. */
|
|
66
|
+
export function addOutcome(stats, success, taint, session) {
|
|
67
|
+
if (!success) {
|
|
68
|
+
stats.failures++;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
stats.successes++;
|
|
72
|
+
if (taint.length > 0)
|
|
73
|
+
stats.tainted++;
|
|
74
|
+
if (taint.includes('narrowed-verifier'))
|
|
75
|
+
stats.narrowed++;
|
|
76
|
+
if (session && SESSION.test(session) && !stats.sessions.includes(session) && stats.sessions.length < MAX_PATTERN_SESSIONS)
|
|
77
|
+
stats.sessions.push(session);
|
|
78
|
+
}
|
|
79
|
+
/** §2.4 case 1: records at or above which a perfect record with half its successes tainted is held. */
|
|
80
|
+
export const PERFECT_MIN_RECORDS = 8;
|
|
81
|
+
/**
|
|
82
|
+
* Why a would-be lesson is held back from the prompt (ADR-503 §2.4), or null.
|
|
83
|
+
* A held pattern stays in SONA; it only never becomes a prompt lesson.
|
|
84
|
+
*
|
|
85
|
+
* 1. 100% success over 8 or more records, half or more of them tainted (allowed).
|
|
86
|
+
* 2. Every success came from a narrowed command.
|
|
87
|
+
* 3. Every success came from one session: no independent repetition. A pattern
|
|
88
|
+
* with no recorded sessions (a record from before ADR-503, or one without a
|
|
89
|
+
* session) counts as one session.
|
|
90
|
+
*/
|
|
91
|
+
export function holdReason(stats, sonaSuccesses) {
|
|
92
|
+
const s = stats.successes;
|
|
93
|
+
if (s >= PERFECT_MIN_RECORDS && stats.failures === 0 && stats.tainted * 2 >= s)
|
|
94
|
+
return 'perfect-and-tainted';
|
|
95
|
+
if (s > 0 && stats.narrowed === s)
|
|
96
|
+
return 'only-narrowed';
|
|
97
|
+
if ((s > 0 || sonaSuccesses > 0) && stats.sessions.length < 2)
|
|
98
|
+
return 'one-session';
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=verified-guards.js.map
|
|
@@ -19,7 +19,15 @@
|
|
|
19
19
|
* verified outcomes produced, those at or above the SONA routing gate as
|
|
20
20
|
* lessons, and repeated verified failures as cautions. The mod reads that
|
|
21
21
|
* file and adds at most three cited lessons to a prompt.
|
|
22
|
+
*
|
|
23
|
+
* ADR-503 hardens it against gamed signals (verified-guards.ts,
|
|
24
|
+
* verified-reverify.ts): a tainted success does not train unless the project
|
|
25
|
+
* allows its taint class, an opt-in re-verify can downgrade a success before
|
|
26
|
+
* it trains, and an anomaly gate holds suspicious patterns back from the
|
|
27
|
+
* prompt. Every one of these can only downgrade.
|
|
22
28
|
*/
|
|
29
|
+
import { type HoldReason, type LoopConfig, type PatternStats, type Taint } from './verified-guards.js';
|
|
30
|
+
import { type ReverifyRunner } from './verified-reverify.js';
|
|
23
31
|
export declare const VERIFIED_PATH: string;
|
|
24
32
|
export declare const LEDGER_PATH: string;
|
|
25
33
|
export declare const LESSONS_PATH: string;
|
|
@@ -49,6 +57,10 @@ export interface VerifiedRecord {
|
|
|
49
57
|
readonly ok: boolean;
|
|
50
58
|
}>;
|
|
51
59
|
readonly edits: number;
|
|
60
|
+
/** ADR-503 §2.1: what the turn did to its own check; only a `verified-success` carries any. */
|
|
61
|
+
readonly taint: readonly Taint[];
|
|
62
|
+
/** The mod process that recorded it, when the mod wrote one (ADR-503 §2.4). */
|
|
63
|
+
readonly session: string | null;
|
|
52
64
|
readonly startedAt: number;
|
|
53
65
|
readonly endedAt: number;
|
|
54
66
|
}
|
|
@@ -62,13 +74,31 @@ export interface IngestLedger {
|
|
|
62
74
|
verifiedFailure: number;
|
|
63
75
|
unknown: number;
|
|
64
76
|
rejected: number;
|
|
77
|
+
tainted: number;
|
|
65
78
|
};
|
|
66
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Pattern keys verified outcomes trained, with the last verifier command that decided them (the lesson's evidence)
|
|
81
|
+
* and what the anomaly gate needs (ADR-503 §2.4).
|
|
82
|
+
*/
|
|
67
83
|
patterns: Record<string, {
|
|
68
84
|
evidence: string;
|
|
69
85
|
at: number;
|
|
86
|
+
stats: PatternStats;
|
|
70
87
|
}>;
|
|
88
|
+
/** ADR-503 §2.3: the rotation cursor and the latest re-verifications (which commit, which check, what happened). */
|
|
89
|
+
reverify: {
|
|
90
|
+
cursor: number;
|
|
91
|
+
log: ReverifyLogEntry[];
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export interface ReverifyLogEntry {
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly check: string | null;
|
|
97
|
+
readonly commit: string | null;
|
|
98
|
+
readonly status: 'pass' | 'fail' | 'timeout' | 'error';
|
|
99
|
+
readonly at: number;
|
|
71
100
|
}
|
|
101
|
+
export declare const MAX_REVERIFY_LOG = 50;
|
|
72
102
|
export interface Lesson {
|
|
73
103
|
readonly id: string;
|
|
74
104
|
readonly kind: 'lesson' | 'caution';
|
|
@@ -86,19 +116,38 @@ export interface LessonsFile {
|
|
|
86
116
|
lessons: Lesson[];
|
|
87
117
|
/** Verified patterns still below the gate, for the cold-start progress line. */
|
|
88
118
|
belowGate: number;
|
|
119
|
+
/** Patterns at or above the gate that the anomaly gate kept out of the prompt (ADR-503 §2.4). */
|
|
120
|
+
held: Array<{
|
|
121
|
+
id: string;
|
|
122
|
+
reason: HoldReason;
|
|
123
|
+
}>;
|
|
89
124
|
}
|
|
90
125
|
export interface IngestResult {
|
|
126
|
+
/** `tainted`: successes whose taint the project did not allow, counted and never trained (ADR-503 §2.2). */
|
|
91
127
|
ingested: {
|
|
92
128
|
verifiedSuccess: number;
|
|
93
129
|
verifiedFailure: number;
|
|
94
130
|
unknown: number;
|
|
95
131
|
rejected: number;
|
|
132
|
+
tainted: number;
|
|
96
133
|
};
|
|
97
134
|
trained: number;
|
|
98
135
|
skipped: 'no-file' | 'unchanged' | 'too-large' | null;
|
|
99
136
|
lessons: number;
|
|
100
137
|
cautions: number;
|
|
101
138
|
belowGate: number;
|
|
139
|
+
held: LessonsFile['held'];
|
|
140
|
+
/**
|
|
141
|
+
* ADR-503 §2.3: checks run, successes that passed or were downgraded to unknown, and successes left for an explicit
|
|
142
|
+
* ingest because this caller may not run project code.
|
|
143
|
+
*/
|
|
144
|
+
reverify: {
|
|
145
|
+
enabled: boolean;
|
|
146
|
+
ran: number;
|
|
147
|
+
passed: number;
|
|
148
|
+
downgraded: number;
|
|
149
|
+
deferred: number;
|
|
150
|
+
};
|
|
102
151
|
totals: IngestLedger['totals'];
|
|
103
152
|
sonaAvailable: boolean;
|
|
104
153
|
}
|
|
@@ -137,8 +186,15 @@ export declare function emptyLedger(): IngestLedger;
|
|
|
137
186
|
export declare function readLedger(raw: string | null): IngestLedger;
|
|
138
187
|
/** A lesson's citation: a short hash of the SONA pattern key, so the key's own text never reaches a prompt. */
|
|
139
188
|
export declare const lessonId: (patternKey: string) => string;
|
|
140
|
-
/**
|
|
141
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Builds the lessons file from SONA's patterns, keeping only the ones verified outcomes trained. A would-be lesson the
|
|
191
|
+
* anomaly gate holds (ADR-503 §2.4) is listed under `held` instead: it stays in SONA, never in a prompt.
|
|
192
|
+
*/
|
|
193
|
+
export declare function buildLessons(patterns: ReturnType<SonaLike['exportPatterns']>, verified: Record<string, {
|
|
194
|
+
evidence: string;
|
|
195
|
+
at: number;
|
|
196
|
+
stats?: PatternStats;
|
|
197
|
+
}>, now: number): LessonsFile;
|
|
142
198
|
export interface IngestOptions {
|
|
143
199
|
/** Project root; defaults to process.cwd(), where SONA keeps `.swarm/`. */
|
|
144
200
|
root?: string;
|
|
@@ -149,6 +205,15 @@ export interface IngestOptions {
|
|
|
149
205
|
/** SONA to train; defaults to the process's optimizer. Null means unavailable. */
|
|
150
206
|
sona?: SonaLike | null;
|
|
151
207
|
now?: number;
|
|
208
|
+
/** `learningLoop` settings; defaults to `.claude-flow/config.json` under `root` (ADR-503). */
|
|
209
|
+
config?: LoopConfig;
|
|
210
|
+
/**
|
|
211
|
+
* Whether this caller may run project code to re-verify (ADR-503 §2.3). Only an explicit ingest passes true; a hot
|
|
212
|
+
* caller (hooks_route) leaves successes un-ingested when re-verify is on, instead of blocking for the timeout.
|
|
213
|
+
*/
|
|
214
|
+
allowReverify?: boolean;
|
|
215
|
+
/** Re-verify runner; defaults to the detached-git-worktree runner. Tests inject one. */
|
|
216
|
+
reverifier?: ReverifyRunner;
|
|
152
217
|
}
|
|
153
218
|
export declare function ingestVerifiedOutcomes(options?: IngestOptions): Promise<IngestResult>;
|
|
154
219
|
/**
|