claude-flow 3.20.0 → 3.21.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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.d.ts +148 -0
- package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.js +218 -0
- package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +45 -0
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +309 -1
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +9 -82
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
- package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
- package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
- package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
- package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
- package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
- package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
- package/v3/@claude-flow/cli/package.json +5 -4
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* distill-oracle.ts — Tiered `resolved` oracle for distill / weight-EFT SFT data.
|
|
3
|
+
*
|
|
4
|
+
* THE PROBLEM: weight-EFT's SFT data wants a gold `resolved: boolean` per
|
|
5
|
+
* trajectory. Ruflo has no SWE-bench oracle, so today `resolved` is
|
|
6
|
+
* structural-confidence — a proxy that risks distilling plausible-but-wrong
|
|
7
|
+
* completions. This module replaces that single proxy with a TIERED labeler,
|
|
8
|
+
* where every label carries HONEST PROVENANCE (ADR-169 reporting integrity — a
|
|
9
|
+
* proxy is never presented as ground truth).
|
|
10
|
+
*
|
|
11
|
+
* ── TIERS (tried in order, per trajectory) ──────────────────────────────────
|
|
12
|
+
* TIER 1 — MECHANICAL ORACLE (provenance `oracle:test-exec`, real ground truth)
|
|
13
|
+
* When a trajectory carries a test spec (SWE-bench FAIL_TO_PASS shape) or
|
|
14
|
+
* maps to a metaharness/darwin bench-suite case, EXECUTE the real eval and
|
|
15
|
+
* set resolved = tests-pass. This needs Docker/compute, so it runs on a
|
|
16
|
+
* REMOTE host over SSH (host parameterized via `remote` / env
|
|
17
|
+
* RUFLO_DISTILL_REMOTE — never hard-coded). DRY-RUN by default: it prints
|
|
18
|
+
* the ssh/darwin-bench/eval commands + a preflight plan and does NOT touch
|
|
19
|
+
* the network; only `execute: true` runs the real eval (with a wrapped,
|
|
20
|
+
* non-fatal preflight probe first).
|
|
21
|
+
*
|
|
22
|
+
* TIER 2 — FABLE JUDGE (provenance `judge:fable`, smarter proxy)
|
|
23
|
+
* For trajectories with no mechanical spec, judge via the cost-disciplined
|
|
24
|
+
* headless Fable harness (fable-harness.ts). Opt-in and OFF by default;
|
|
25
|
+
* spends nothing unless `fableJudge: true` AND a `maxBudgetUsd` cap is set.
|
|
26
|
+
*
|
|
27
|
+
* TIER 3 — STRUCTURAL PROXY (provenance `proxy:structural`, weakest)
|
|
28
|
+
* The existing output-verifier structural confidence. Always available,
|
|
29
|
+
* $0, no external calls. Explicitly the weakest signal, clearly labeled.
|
|
30
|
+
*
|
|
31
|
+
* DEFAULT (no opts): dry-run oracle preflight + structural-proxy fallback.
|
|
32
|
+
* ZERO spend, no SSH exec, no Fable call. Everything degrades gracefully
|
|
33
|
+
* (ADR-150) — a missing remote or a failed probe is reported, never fatal.
|
|
34
|
+
*
|
|
35
|
+
* @module services/distill-oracle
|
|
36
|
+
*/
|
|
37
|
+
import { type VerifyTaskKind } from '../ruvector/output-verifier.js';
|
|
38
|
+
import { FableHarness, type ReflectItem, type ReflectResult } from './fable-harness.js';
|
|
39
|
+
export type ResolvedProvenance = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural';
|
|
40
|
+
/** SWE-bench-shaped / bench-suite-mapped test spec that Tier 1 can execute. */
|
|
41
|
+
export interface TestSpec {
|
|
42
|
+
/** SWE-bench FAIL_TO_PASS tests — must go red→green for `resolved`. */
|
|
43
|
+
failToPass?: string[];
|
|
44
|
+
/** SWE-bench PASS_TO_PASS tests — must stay green (no regressions). */
|
|
45
|
+
passToPass?: string[];
|
|
46
|
+
/** Repo identifier (for the remote checkout). */
|
|
47
|
+
repo?: string;
|
|
48
|
+
/** Base commit the patch applies onto. */
|
|
49
|
+
baseCommit?: string;
|
|
50
|
+
/** Candidate patch/diff to apply before evaluating. */
|
|
51
|
+
patch?: string;
|
|
52
|
+
/** An explicit eval command that returns 0 iff the task is resolved. */
|
|
53
|
+
evalCommand?: string;
|
|
54
|
+
/** metaharness/darwin bench-suite this case belongs to. */
|
|
55
|
+
benchSuite?: string;
|
|
56
|
+
/** Case id within the bench suite. */
|
|
57
|
+
benchCase?: string;
|
|
58
|
+
/** Working directory on the remote host (default derived). */
|
|
59
|
+
workdir?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Minimal trajectory contract. Extra fields are preserved verbatim on output
|
|
63
|
+
* (the labeler spreads `...trajectory`). `task`/`output` feed the fable judge
|
|
64
|
+
* and structural proxy; `testSpec` (when present) unlocks Tier 1.
|
|
65
|
+
*/
|
|
66
|
+
export interface Trajectory {
|
|
67
|
+
id: string;
|
|
68
|
+
task?: string;
|
|
69
|
+
output?: string;
|
|
70
|
+
testSpec?: TestSpec;
|
|
71
|
+
/** Hint for the structural verifier's task-kind detection. */
|
|
72
|
+
taskKind?: VerifyTaskKind;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
}
|
|
75
|
+
export interface LabelOptions {
|
|
76
|
+
/** SSH host for Tier-1 execution. Falls back to env RUFLO_DISTILL_REMOTE. Never hard-coded. */
|
|
77
|
+
remote?: string;
|
|
78
|
+
/** Run the REAL Tier-1 eval over SSH. Default false → dry-run preflight only. */
|
|
79
|
+
execute?: boolean;
|
|
80
|
+
/** Enable the Tier-2 Fable judge. Default false. Requires maxBudgetUsd to spend. */
|
|
81
|
+
fableJudge?: boolean;
|
|
82
|
+
/** Hard budget cap (USD) for the Fable tier. No cap ⇒ no Fable spend. */
|
|
83
|
+
maxBudgetUsd?: number;
|
|
84
|
+
/** Items per Fable call (default 20 — see FABLE_COST_MODEL). */
|
|
85
|
+
fableBatchSize?: number;
|
|
86
|
+
/** Structural verifier: min score to call a trajectory resolved (default: strict — confident only). */
|
|
87
|
+
minStructuralConfidence?: number;
|
|
88
|
+
/** Injected Tier-1 runner (tests). Defaults to a real SSH command runner. */
|
|
89
|
+
runner?: OracleRunner;
|
|
90
|
+
/** Injected Tier-2 harness (tests). Defaults to a real FableHarness. */
|
|
91
|
+
harness?: FableHarness;
|
|
92
|
+
}
|
|
93
|
+
/** A trajectory with its resolved label + honest provenance. */
|
|
94
|
+
export type LabeledTrajectory<T extends Trajectory = Trajectory> = T & {
|
|
95
|
+
resolved: boolean;
|
|
96
|
+
resolvedBy: ResolvedProvenance;
|
|
97
|
+
resolvedConfidence?: number;
|
|
98
|
+
resolvedReason?: string;
|
|
99
|
+
/** Present for trajectories that had a mechanical spec — the Tier-1 plan/outcome. */
|
|
100
|
+
oraclePreflight?: OraclePreflight;
|
|
101
|
+
};
|
|
102
|
+
export type OracleKind = 'ssh-darwin-bench' | 'ssh-swebench-eval' | 'ssh-eval' | 'local-eval';
|
|
103
|
+
/** The concrete command plan for executing a trajectory's eval on the remote. */
|
|
104
|
+
export interface OraclePlan {
|
|
105
|
+
kind: OracleKind;
|
|
106
|
+
/** Resolved remote host, or null when none is configured. */
|
|
107
|
+
remote: string | null;
|
|
108
|
+
/** Preflight probe commands (reachability, docker/darwin presence). */
|
|
109
|
+
probeCommands: string[];
|
|
110
|
+
/** The eval commands whose success ⇒ resolved. */
|
|
111
|
+
evalCommands: string[];
|
|
112
|
+
/** Human note describing how the resolved boolean is derived. */
|
|
113
|
+
parseHint: string;
|
|
114
|
+
}
|
|
115
|
+
/** Preflight/dry-run record attached to labeled trajectories that had a spec. */
|
|
116
|
+
export interface OraclePreflight {
|
|
117
|
+
/** True when nothing was executed (default path). */
|
|
118
|
+
dryRun: boolean;
|
|
119
|
+
plan: OraclePlan;
|
|
120
|
+
/** Probe outcome when executed (execute:true); absent in dry-run. */
|
|
121
|
+
probe?: {
|
|
122
|
+
ok: boolean;
|
|
123
|
+
reason: string;
|
|
124
|
+
};
|
|
125
|
+
/** Why Tier 1 did not produce the final label (e.g. dry-run, no remote, probe failed). */
|
|
126
|
+
fellThroughBecause?: string;
|
|
127
|
+
}
|
|
128
|
+
/** Outcome of a real Tier-1 eval. */
|
|
129
|
+
export interface OracleOutcome {
|
|
130
|
+
resolved: boolean;
|
|
131
|
+
reason: string;
|
|
132
|
+
}
|
|
133
|
+
/** Result of a shelled command. */
|
|
134
|
+
export interface CommandResult {
|
|
135
|
+
stdout: string;
|
|
136
|
+
stderr: string;
|
|
137
|
+
code: number | null;
|
|
138
|
+
}
|
|
139
|
+
/** Injectable command executor (tests mock this instead of touching SSH). */
|
|
140
|
+
export type CommandExec = (cmd: string, args: string[], opts: {
|
|
141
|
+
timeoutMs: number;
|
|
142
|
+
}) => Promise<CommandResult>;
|
|
143
|
+
/** Tier-1 runner contract: probe the remote, then run the eval. */
|
|
144
|
+
export interface OracleRunner {
|
|
145
|
+
preflight(plan: OraclePlan): Promise<{
|
|
146
|
+
ok: boolean;
|
|
147
|
+
reason: string;
|
|
148
|
+
}>;
|
|
149
|
+
runEval(plan: OraclePlan, spec: TestSpec): Promise<OracleOutcome>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Label each trajectory with `resolved` + honest provenance, trying the tiers
|
|
153
|
+
* in order per trajectory. See the module header for tier semantics and the
|
|
154
|
+
* zero-spend default guarantee.
|
|
155
|
+
*/
|
|
156
|
+
export declare function labelResolved<T extends Trajectory>(trajectories: T[], opts?: LabelOptions): Promise<Array<LabeledTrajectory<T>>>;
|
|
157
|
+
/**
|
|
158
|
+
* Reflective failure analysis (GEPA/evolve mutation input). Thin, cost-
|
|
159
|
+
* disciplined pass-through to the Fable harness. Opt-in: returns [] unless a
|
|
160
|
+
* harness with a budget cap is provided/configured.
|
|
161
|
+
*/
|
|
162
|
+
export declare function reflectFailures(items: ReflectItem[], opts?: {
|
|
163
|
+
maxBudgetUsd?: number;
|
|
164
|
+
fableBatchSize?: number;
|
|
165
|
+
harness?: FableHarness;
|
|
166
|
+
}): Promise<ReflectResult[]>;
|
|
167
|
+
/** A trajectory can be mechanically evaluated iff it carries an actionable spec. */
|
|
168
|
+
export declare function hasMechanicalSpec(t: Trajectory): boolean;
|
|
169
|
+
/** Resolve the remote host from opts → env, never a hard-coded host. */
|
|
170
|
+
export declare function resolveRemote(remote?: string): string | null;
|
|
171
|
+
/**
|
|
172
|
+
* Build the concrete command plan for a spec. Pure — constructs command strings
|
|
173
|
+
* only; executes nothing. `remote` is always substituted from the parameter,
|
|
174
|
+
* never hard-coded.
|
|
175
|
+
*/
|
|
176
|
+
export declare function buildOraclePlan(spec: TestSpec, remote: string | null): OraclePlan;
|
|
177
|
+
/**
|
|
178
|
+
* Default command executor: shells out, piping any heredoc/stdin via argv only
|
|
179
|
+
* (commands are self-contained strings run through `sh -c`). Never invoked on
|
|
180
|
+
* the default (dry-run) path.
|
|
181
|
+
*/
|
|
182
|
+
export declare const defaultCommandExec: CommandExec;
|
|
183
|
+
/**
|
|
184
|
+
* Build a real SSH-backed oracle runner. `exec` is injectable so tests assert
|
|
185
|
+
* command construction without ever touching a network. Each plan command is
|
|
186
|
+
* run via `sh -c <command-string>` so the fully-rendered ssh line executes as-is.
|
|
187
|
+
*/
|
|
188
|
+
export declare function createSshOracleRunner(exec?: CommandExec, timeoutMs?: number): OracleRunner;
|
|
189
|
+
export default labelResolved;
|
|
190
|
+
//# sourceMappingURL=distill-oracle.d.ts.map
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* distill-oracle.ts — Tiered `resolved` oracle for distill / weight-EFT SFT data.
|
|
3
|
+
*
|
|
4
|
+
* THE PROBLEM: weight-EFT's SFT data wants a gold `resolved: boolean` per
|
|
5
|
+
* trajectory. Ruflo has no SWE-bench oracle, so today `resolved` is
|
|
6
|
+
* structural-confidence — a proxy that risks distilling plausible-but-wrong
|
|
7
|
+
* completions. This module replaces that single proxy with a TIERED labeler,
|
|
8
|
+
* where every label carries HONEST PROVENANCE (ADR-169 reporting integrity — a
|
|
9
|
+
* proxy is never presented as ground truth).
|
|
10
|
+
*
|
|
11
|
+
* ── TIERS (tried in order, per trajectory) ──────────────────────────────────
|
|
12
|
+
* TIER 1 — MECHANICAL ORACLE (provenance `oracle:test-exec`, real ground truth)
|
|
13
|
+
* When a trajectory carries a test spec (SWE-bench FAIL_TO_PASS shape) or
|
|
14
|
+
* maps to a metaharness/darwin bench-suite case, EXECUTE the real eval and
|
|
15
|
+
* set resolved = tests-pass. This needs Docker/compute, so it runs on a
|
|
16
|
+
* REMOTE host over SSH (host parameterized via `remote` / env
|
|
17
|
+
* RUFLO_DISTILL_REMOTE — never hard-coded). DRY-RUN by default: it prints
|
|
18
|
+
* the ssh/darwin-bench/eval commands + a preflight plan and does NOT touch
|
|
19
|
+
* the network; only `execute: true` runs the real eval (with a wrapped,
|
|
20
|
+
* non-fatal preflight probe first).
|
|
21
|
+
*
|
|
22
|
+
* TIER 2 — FABLE JUDGE (provenance `judge:fable`, smarter proxy)
|
|
23
|
+
* For trajectories with no mechanical spec, judge via the cost-disciplined
|
|
24
|
+
* headless Fable harness (fable-harness.ts). Opt-in and OFF by default;
|
|
25
|
+
* spends nothing unless `fableJudge: true` AND a `maxBudgetUsd` cap is set.
|
|
26
|
+
*
|
|
27
|
+
* TIER 3 — STRUCTURAL PROXY (provenance `proxy:structural`, weakest)
|
|
28
|
+
* The existing output-verifier structural confidence. Always available,
|
|
29
|
+
* $0, no external calls. Explicitly the weakest signal, clearly labeled.
|
|
30
|
+
*
|
|
31
|
+
* DEFAULT (no opts): dry-run oracle preflight + structural-proxy fallback.
|
|
32
|
+
* ZERO spend, no SSH exec, no Fable call. Everything degrades gracefully
|
|
33
|
+
* (ADR-150) — a missing remote or a failed probe is reported, never fatal.
|
|
34
|
+
*
|
|
35
|
+
* @module services/distill-oracle
|
|
36
|
+
*/
|
|
37
|
+
import { spawn } from 'child_process';
|
|
38
|
+
import { verifyAndEscalate } from '../ruvector/output-verifier.js';
|
|
39
|
+
import { FableHarness, } from './fable-harness.js';
|
|
40
|
+
// ── Public API ───────────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Label each trajectory with `resolved` + honest provenance, trying the tiers
|
|
43
|
+
* in order per trajectory. See the module header for tier semantics and the
|
|
44
|
+
* zero-spend default guarantee.
|
|
45
|
+
*/
|
|
46
|
+
export async function labelResolved(trajectories, opts = {}) {
|
|
47
|
+
const results = new Array(trajectories.length);
|
|
48
|
+
const preflights = new Map();
|
|
49
|
+
const remote = resolveRemote(opts.remote);
|
|
50
|
+
const fableActive = Boolean(opts.fableJudge) && Number(opts.maxBudgetUsd) > 0;
|
|
51
|
+
// ── Tier 1: mechanical oracle (real exec only under execute:true) ──────────
|
|
52
|
+
const fableQueue = [];
|
|
53
|
+
for (let i = 0; i < trajectories.length; i++) {
|
|
54
|
+
const t = trajectories[i];
|
|
55
|
+
if (hasMechanicalSpec(t)) {
|
|
56
|
+
const plan = buildOraclePlan(t.testSpec, remote);
|
|
57
|
+
if (opts.execute) {
|
|
58
|
+
const runner = opts.runner ?? createSshOracleRunner();
|
|
59
|
+
// Wrapped probe: a failure is REPORTED, not thrown.
|
|
60
|
+
const probe = await safeProbe(runner, plan);
|
|
61
|
+
if (probe.ok) {
|
|
62
|
+
const outcome = await safeRunEval(runner, plan, t.testSpec);
|
|
63
|
+
results[i] = {
|
|
64
|
+
...t,
|
|
65
|
+
resolved: outcome.resolved,
|
|
66
|
+
resolvedBy: 'oracle:test-exec',
|
|
67
|
+
resolvedConfidence: 1,
|
|
68
|
+
resolvedReason: outcome.reason,
|
|
69
|
+
oraclePreflight: { dryRun: false, plan, probe },
|
|
70
|
+
};
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Probe failed → report and fall through to next tier.
|
|
74
|
+
preflights.set(i, {
|
|
75
|
+
dryRun: false,
|
|
76
|
+
plan,
|
|
77
|
+
probe,
|
|
78
|
+
fellThroughBecause: `preflight failed: ${probe.reason}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// DRY-RUN: emit the plan/commands, touch nothing, fall through.
|
|
83
|
+
preflights.set(i, {
|
|
84
|
+
dryRun: true,
|
|
85
|
+
plan,
|
|
86
|
+
fellThroughBecause: remote
|
|
87
|
+
? 'dry-run (pass execute:true to run the real eval over SSH)'
|
|
88
|
+
: 'dry-run and no remote configured (set remote / RUFLO_DISTILL_REMOTE)',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ── Route to Tier 2 (batched later) or Tier 3 now ──
|
|
93
|
+
if (fableActive) {
|
|
94
|
+
fableQueue.push({ index: i, item: toJudgeItem(t) });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
results[i] = await proxyLabel(t, opts, preflights.get(i));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ── Tier 2: batched Fable judge for queued items ──────────────────────────
|
|
101
|
+
if (fableActive && fableQueue.length > 0) {
|
|
102
|
+
const harness = opts.harness ??
|
|
103
|
+
new FableHarness({ maxBudgetUsd: opts.maxBudgetUsd, batchSize: opts.fableBatchSize });
|
|
104
|
+
let verdicts = [];
|
|
105
|
+
try {
|
|
106
|
+
verdicts = await harness.judgeBatch(fableQueue.map((q) => q.item));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
verdicts = []; // graceful: any harness failure ⇒ proxy fallback for all
|
|
110
|
+
}
|
|
111
|
+
const byId = new Map(verdicts.map((v) => [v.id, v]));
|
|
112
|
+
for (const { index, item } of fableQueue) {
|
|
113
|
+
const v = byId.get(item.id);
|
|
114
|
+
const t = trajectories[index];
|
|
115
|
+
if (v) {
|
|
116
|
+
results[index] = {
|
|
117
|
+
...t,
|
|
118
|
+
resolved: v.resolved,
|
|
119
|
+
resolvedBy: 'judge:fable',
|
|
120
|
+
resolvedConfidence: v.confidence,
|
|
121
|
+
resolvedReason: v.reason,
|
|
122
|
+
...(preflights.has(index) ? { oraclePreflight: preflights.get(index) } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Model omitted this id / budget exhausted → Tier-3 proxy fallback.
|
|
127
|
+
results[index] = await proxyLabel(t, opts, preflights.get(index));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ── Safety net: nothing should be undefined, but fall back to proxy if so ──
|
|
132
|
+
for (let i = 0; i < results.length; i++) {
|
|
133
|
+
if (results[i] === undefined) {
|
|
134
|
+
results[i] = await proxyLabel(trajectories[i], opts, preflights.get(i));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Reflective failure analysis (GEPA/evolve mutation input). Thin, cost-
|
|
141
|
+
* disciplined pass-through to the Fable harness. Opt-in: returns [] unless a
|
|
142
|
+
* harness with a budget cap is provided/configured.
|
|
143
|
+
*/
|
|
144
|
+
export async function reflectFailures(items, opts = {}) {
|
|
145
|
+
const harness = opts.harness ??
|
|
146
|
+
new FableHarness({ maxBudgetUsd: opts.maxBudgetUsd, batchSize: opts.fableBatchSize });
|
|
147
|
+
if (!harness.isEnabled())
|
|
148
|
+
return [];
|
|
149
|
+
return harness.reflectFailures(items);
|
|
150
|
+
}
|
|
151
|
+
// ── Tier-3 structural proxy ──────────────────────────────────────────────
|
|
152
|
+
async function proxyLabel(t, opts, preflight) {
|
|
153
|
+
const verdict = await verifyAndEscalate({
|
|
154
|
+
task: t.task ?? '',
|
|
155
|
+
output: t.output ?? '',
|
|
156
|
+
taskKind: t.taskKind,
|
|
157
|
+
});
|
|
158
|
+
const threshold = opts.minStructuralConfidence;
|
|
159
|
+
const resolved = typeof threshold === 'number' ? verdict.score >= threshold : verdict.confident;
|
|
160
|
+
return {
|
|
161
|
+
...t,
|
|
162
|
+
resolved,
|
|
163
|
+
resolvedBy: 'proxy:structural',
|
|
164
|
+
resolvedConfidence: verdict.score,
|
|
165
|
+
resolvedReason: verdict.confident
|
|
166
|
+
? 'structural signals confident'
|
|
167
|
+
: `structural signals weak: ${verdict.reasons.slice(0, 3).join('; ')}`,
|
|
168
|
+
...(preflight ? { oraclePreflight: preflight } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// ── Tier-1 planning ──────────────────────────────────────────────────────
|
|
172
|
+
/** A trajectory can be mechanically evaluated iff it carries an actionable spec. */
|
|
173
|
+
export function hasMechanicalSpec(t) {
|
|
174
|
+
const s = t.testSpec;
|
|
175
|
+
if (!s)
|
|
176
|
+
return false;
|
|
177
|
+
return Boolean((s.failToPass && s.failToPass.length > 0) ||
|
|
178
|
+
s.evalCommand ||
|
|
179
|
+
s.benchSuite ||
|
|
180
|
+
s.benchCase);
|
|
181
|
+
}
|
|
182
|
+
/** Resolve the remote host from opts → env, never a hard-coded host. */
|
|
183
|
+
export function resolveRemote(remote) {
|
|
184
|
+
const r = (remote ?? process.env.RUFLO_DISTILL_REMOTE ?? '').trim();
|
|
185
|
+
return r.length > 0 ? r : null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Build the concrete command plan for a spec. Pure — constructs command strings
|
|
189
|
+
* only; executes nothing. `remote` is always substituted from the parameter,
|
|
190
|
+
* never hard-coded.
|
|
191
|
+
*/
|
|
192
|
+
export function buildOraclePlan(spec, remote) {
|
|
193
|
+
const host = remote; // may be null; commands render `<remote>` placeholder then
|
|
194
|
+
const r = host ?? '<remote>';
|
|
195
|
+
const sshLocal = host ? `ssh ${host} ` : `ssh <remote> `;
|
|
196
|
+
const workdir = spec.workdir ?? (spec.repo ? `~/ruflo-distill/${sanitize(spec.repo)}` : '~/ruflo-distill/work');
|
|
197
|
+
// Preflight probes — reachability + the tools Tier-1 needs on the remote.
|
|
198
|
+
const probeCommands = [
|
|
199
|
+
`ssh -o BatchMode=yes -o ConnectTimeout=8 ${r} 'echo ok'`,
|
|
200
|
+
`${sshLocal}'command -v docker >/dev/null 2>&1 && echo docker-present || echo docker-missing'`,
|
|
201
|
+
`${sshLocal}'command -v darwin >/dev/null 2>&1 || npx --yes @metaharness/darwin --version'`,
|
|
202
|
+
];
|
|
203
|
+
let kind;
|
|
204
|
+
const evalCommands = [];
|
|
205
|
+
let parseHint;
|
|
206
|
+
if (spec.benchSuite || spec.benchCase) {
|
|
207
|
+
kind = 'ssh-darwin-bench';
|
|
208
|
+
const suite = spec.benchSuite ? `--suite ${shq(spec.benchSuite)}` : '';
|
|
209
|
+
const kase = spec.benchCase ? `--case ${shq(spec.benchCase)}` : '';
|
|
210
|
+
evalCommands.push(`${sshLocal}'cd ${workdir} && npx --yes @metaharness/darwin bench run ${suite} ${kase} --json'`.replace(/\s+/g, ' ').trim());
|
|
211
|
+
parseHint = 'resolved = darwin bench run reports the case scored PASS (exit 0, json.passed=true)';
|
|
212
|
+
}
|
|
213
|
+
else if (spec.evalCommand) {
|
|
214
|
+
kind = 'ssh-eval';
|
|
215
|
+
evalCommands.push(`${sshLocal}'cd ${workdir} && ${spec.evalCommand}'`);
|
|
216
|
+
parseHint = 'resolved = evalCommand exits 0';
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
kind = 'ssh-swebench-eval';
|
|
220
|
+
if (spec.patch) {
|
|
221
|
+
evalCommands.push(`${sshLocal}'cd ${workdir} && git apply - <<"PATCH"\n${spec.patch}\nPATCH'`);
|
|
222
|
+
}
|
|
223
|
+
const f2p = (spec.failToPass ?? []).map(shq).join(' ');
|
|
224
|
+
const p2p = (spec.passToPass ?? []).map(shq).join(' ');
|
|
225
|
+
evalCommands.push(`${sshLocal}'cd ${workdir} && npx --yes @metaharness/darwin swebench-eval` +
|
|
226
|
+
`${spec.repo ? ` --repo ${shq(spec.repo)}` : ''}` +
|
|
227
|
+
`${spec.baseCommit ? ` --base ${shq(spec.baseCommit)}` : ''}` +
|
|
228
|
+
`${f2p ? ` --fail-to-pass ${f2p}` : ''}` +
|
|
229
|
+
`${p2p ? ` --pass-to-pass ${p2p}` : ''} --json'`);
|
|
230
|
+
parseHint = 'resolved = all FAIL_TO_PASS now pass AND all PASS_TO_PASS still pass';
|
|
231
|
+
}
|
|
232
|
+
return { kind, remote: host, probeCommands, evalCommands, parseHint };
|
|
233
|
+
}
|
|
234
|
+
// ── Default SSH runner (real exec; injectable for tests) ─────────────────────
|
|
235
|
+
/**
|
|
236
|
+
* Default command executor: shells out, piping any heredoc/stdin via argv only
|
|
237
|
+
* (commands are self-contained strings run through `sh -c`). Never invoked on
|
|
238
|
+
* the default (dry-run) path.
|
|
239
|
+
*/
|
|
240
|
+
export const defaultCommandExec = (cmd, args, opts) => new Promise((resolve) => {
|
|
241
|
+
let child;
|
|
242
|
+
try {
|
|
243
|
+
child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
resolve({ stdout: '', stderr: err instanceof Error ? err.message : String(err), code: null });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
let stdout = '';
|
|
250
|
+
let stderr = '';
|
|
251
|
+
let done = false;
|
|
252
|
+
const finish = (r) => { if (!done) {
|
|
253
|
+
done = true;
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
resolve(r);
|
|
256
|
+
} };
|
|
257
|
+
const timer = setTimeout(() => {
|
|
258
|
+
try {
|
|
259
|
+
child.kill('SIGTERM');
|
|
260
|
+
}
|
|
261
|
+
catch { /* dead */ }
|
|
262
|
+
finish({ stdout, stderr: stderr || `timed out after ${opts.timeoutMs}ms`, code: null });
|
|
263
|
+
}, opts.timeoutMs);
|
|
264
|
+
timer.unref?.();
|
|
265
|
+
child.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
266
|
+
child.stderr?.on('data', (d) => { stderr += d.toString(); });
|
|
267
|
+
child.on('error', (e) => finish({ stdout, stderr: e.message, code: null }));
|
|
268
|
+
child.on('close', (code) => finish({ stdout, stderr, code }));
|
|
269
|
+
});
|
|
270
|
+
/**
|
|
271
|
+
* Build a real SSH-backed oracle runner. `exec` is injectable so tests assert
|
|
272
|
+
* command construction without ever touching a network. Each plan command is
|
|
273
|
+
* run via `sh -c <command-string>` so the fully-rendered ssh line executes as-is.
|
|
274
|
+
*/
|
|
275
|
+
export function createSshOracleRunner(exec = defaultCommandExec, timeoutMs = 15 * 60 * 1000) {
|
|
276
|
+
const run = (command) => exec('sh', ['-c', command], { timeoutMs });
|
|
277
|
+
return {
|
|
278
|
+
async preflight(plan) {
|
|
279
|
+
if (!plan.remote)
|
|
280
|
+
return { ok: false, reason: 'no remote configured' };
|
|
281
|
+
for (const c of plan.probeCommands) {
|
|
282
|
+
const res = await run(c);
|
|
283
|
+
if (res.code !== 0) {
|
|
284
|
+
return { ok: false, reason: `probe failed (${c.slice(0, 60)}…): ${(res.stderr || '').slice(0, 120)}` };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { ok: true, reason: 'remote reachable; docker/darwin present' };
|
|
288
|
+
},
|
|
289
|
+
async runEval(plan) {
|
|
290
|
+
let lastReason = 'no eval commands';
|
|
291
|
+
for (const c of plan.evalCommands) {
|
|
292
|
+
const res = await run(c);
|
|
293
|
+
if (res.code !== 0) {
|
|
294
|
+
return { resolved: false, reason: `eval failed (exit ${res.code}): ${(res.stderr || res.stdout || '').slice(0, 160)}` };
|
|
295
|
+
}
|
|
296
|
+
lastReason = interpretEvalStdout(res.stdout);
|
|
297
|
+
if (lastReason.startsWith('NOT_RESOLVED')) {
|
|
298
|
+
return { resolved: false, reason: lastReason };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return { resolved: true, reason: `${plan.parseHint} — ${lastReason}` };
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** Interpret an eval command's JSON stdout for an explicit pass/fail signal. */
|
|
306
|
+
function interpretEvalStdout(stdout) {
|
|
307
|
+
const trimmed = (stdout ?? '').trim();
|
|
308
|
+
const m = trimmed.match(/\{[\s\S]*\}/);
|
|
309
|
+
if (m) {
|
|
310
|
+
try {
|
|
311
|
+
const o = JSON.parse(m[0]);
|
|
312
|
+
if (o.passed === false || o.resolved === false)
|
|
313
|
+
return 'NOT_RESOLVED (json.passed=false)';
|
|
314
|
+
if (o.passed === true || o.resolved === true)
|
|
315
|
+
return 'json.passed=true';
|
|
316
|
+
}
|
|
317
|
+
catch { /* fall through to exit-code semantics */ }
|
|
318
|
+
}
|
|
319
|
+
return 'exit 0';
|
|
320
|
+
}
|
|
321
|
+
// ── small helpers ────────────────────────────────────────────────────────
|
|
322
|
+
async function safeProbe(runner, plan) {
|
|
323
|
+
try {
|
|
324
|
+
return await runner.preflight(plan);
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
return { ok: false, reason: `probe threw: ${err instanceof Error ? err.message : String(err)}` };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function safeRunEval(runner, plan, spec) {
|
|
331
|
+
try {
|
|
332
|
+
return await runner.runEval(plan, spec);
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
return { resolved: false, reason: `eval threw: ${err instanceof Error ? err.message : String(err)}` };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function toJudgeItem(t) {
|
|
339
|
+
return { id: t.id, task: t.task ?? '', output: t.output ?? '' };
|
|
340
|
+
}
|
|
341
|
+
function sanitize(s) {
|
|
342
|
+
return s.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
343
|
+
}
|
|
344
|
+
/** Single-quote a token for safe interpolation into a shell command string. */
|
|
345
|
+
function shq(s) {
|
|
346
|
+
return `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
347
|
+
}
|
|
348
|
+
export default labelResolved;
|
|
349
|
+
//# sourceMappingURL=distill-oracle.js.map
|