@claude-flow/cli 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/dist/src/agenticow/speculative-exploration.d.ts +148 -0
- package/dist/src/agenticow/speculative-exploration.d.ts.map +1 -0
- package/dist/src/agenticow/speculative-exploration.js +218 -0
- package/dist/src/agenticow/speculative-exploration.js.map +1 -0
- package/dist/src/commands/autopilot.d.ts.map +1 -1
- package/dist/src/commands/autopilot.js +45 -0
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/neural.d.ts.map +1 -1
- package/dist/src/commands/neural.js +309 -1
- package/dist/src/commands/neural.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +6 -1
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-tools/agent-execute-core.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-execute-core.js +33 -0
- package/dist/src/mcp-tools/agent-execute-core.js.map +1 -1
- package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-tools.js +47 -1
- package/dist/src/mcp-tools/agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
- package/dist/src/mcp-tools/agenticow-loader.d.ts.map +1 -0
- package/dist/src/mcp-tools/agenticow-loader.js +105 -0
- package/dist/src/mcp-tools/agenticow-loader.js.map +1 -0
- package/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
- package/dist/src/mcp-tools/agenticow-speculate-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
- package/dist/src/mcp-tools/agenticow-speculate-tools.js.map +1 -0
- package/dist/src/mcp-tools/agenticow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agenticow-tools.js +9 -82
- package/dist/src/mcp-tools/agenticow-tools.js.map +1 -1
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +2 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/ruvector/router-trajectory.d.ts +33 -0
- package/dist/src/ruvector/router-trajectory.d.ts.map +1 -1
- package/dist/src/ruvector/router-trajectory.js +31 -0
- package/dist/src/ruvector/router-trajectory.js.map +1 -1
- package/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
- package/dist/src/ruvector/run-transcript-recorder.d.ts.map +1 -0
- package/dist/src/ruvector/run-transcript-recorder.js +209 -0
- package/dist/src/ruvector/run-transcript-recorder.js.map +1 -0
- package/dist/src/services/checkpoint-gate.d.ts +140 -0
- package/dist/src/services/checkpoint-gate.d.ts.map +1 -0
- package/dist/src/services/checkpoint-gate.js +223 -0
- package/dist/src/services/checkpoint-gate.js.map +1 -0
- package/dist/src/services/distill-oracle.d.ts +190 -0
- package/dist/src/services/distill-oracle.d.ts.map +1 -0
- package/dist/src/services/distill-oracle.js +349 -0
- package/dist/src/services/distill-oracle.js.map +1 -0
- package/dist/src/services/fable-harness.d.ts +168 -0
- package/dist/src/services/fable-harness.d.ts.map +1 -0
- package/dist/src/services/fable-harness.js +347 -0
- package/dist/src/services/fable-harness.js.map +1 -0
- package/dist/src/services/swarm-memory-branches.d.ts +135 -0
- package/dist/src/services/swarm-memory-branches.d.ts.map +1 -0
- package/dist/src/services/swarm-memory-branches.js +213 -0
- package/dist/src/services/swarm-memory-branches.js.map +1 -0
- package/dist/src/services/weight-eft.d.ts +305 -0
- package/dist/src/services/weight-eft.d.ts.map +1 -0
- package/dist/src/services/weight-eft.js +296 -0
- package/dist/src/services/weight-eft.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
|
@@ -3983,11 +3983,319 @@ const routerCommand = {
|
|
|
3983
3983
|
return { success: true };
|
|
3984
3984
|
},
|
|
3985
3985
|
};
|
|
3986
|
+
// ============================================================================
|
|
3987
|
+
// ADR-150 weight-eft slice — `neural distill export | plan | eval | train`
|
|
3988
|
+
//
|
|
3989
|
+
// Turns ruflo's captured run transcripts into AUDITED TRAINING DATA + a
|
|
3990
|
+
// COST-PARETO measurement + a GPU TRAINING PLAN via the optional
|
|
3991
|
+
// `@metaharness/weight-eft` dependency. HARD honesty rule: this ships training
|
|
3992
|
+
// DATA + a cost audit + a GPU plan — it does NOT train a model and does NOT
|
|
3993
|
+
// "reduce escalation". weight-eft's own `train` never spawns; `resolved` in the
|
|
3994
|
+
// captured archive is a PROXY (no SWE-bench gold oracle). Every path degrades
|
|
3995
|
+
// gracefully ({degraded:true}) when the optional dep is absent (ADR-150).
|
|
3996
|
+
// ============================================================================
|
|
3997
|
+
const distillExportCommand = {
|
|
3998
|
+
name: 'export',
|
|
3999
|
+
description: 'Export captured run transcripts → SFT (OpenAI chat) + DPO (TRL preference) JSONL + a guard report (contamination / reward-hack / long-context). $0, offline. Does NOT train.',
|
|
4000
|
+
options: [
|
|
4001
|
+
{ name: 'archive', short: 'a', type: 'string', description: 'Run-transcript JSONL to read (default: $CLAUDE_FLOW_RUN_TRANSCRIPTS_PATH or .swarm/run-transcripts.jsonl)' },
|
|
4002
|
+
{ name: 'out-dir', short: 'o', type: 'string', description: 'Output dir for sft.jsonl / dpo.jsonl / export-report.json', default: '.claude-flow/neural/weft-export' },
|
|
4003
|
+
{ name: 'eval-holdout', type: 'string', description: 'Comma-separated instance_ids reserved for eval (contamination guard). Excluded + asserted-disjoint.' },
|
|
4004
|
+
{ name: 'max-tokens', type: 'number', description: 'Per-trajectory token budget (default weight-eft 28000)' },
|
|
4005
|
+
{ name: 'truncate', type: 'boolean', description: 'Truncate over-length trajectories instead of dropping', default: 'false' },
|
|
4006
|
+
{ name: 'keep-reward-hacked', type: 'boolean', description: 'Disable the reward-hacking filter (debug only; NOT recommended)', default: 'false' },
|
|
4007
|
+
{ name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
|
|
4008
|
+
],
|
|
4009
|
+
examples: [
|
|
4010
|
+
{ command: 'claude-flow neural distill export', description: 'Export from the default captured .swarm/run-transcripts.jsonl' },
|
|
4011
|
+
{ command: 'claude-flow neural distill export -a runs.jsonl -o ./out --eval-holdout astropy__astropy-1', description: 'Export a specific archive, holding out one instance' },
|
|
4012
|
+
],
|
|
4013
|
+
action: async (ctx) => {
|
|
4014
|
+
const fs = await import('node:fs');
|
|
4015
|
+
const path = await import('node:path');
|
|
4016
|
+
const { readRunTranscripts } = await import('../ruvector/run-transcript-recorder.js');
|
|
4017
|
+
const { buildArchiveFromRecords, runExport } = await import('../services/weight-eft.js');
|
|
4018
|
+
const fmt = ctx.flags.format || 'table';
|
|
4019
|
+
const archivePath = ctx.flags.archive
|
|
4020
|
+
?? process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS_PATH
|
|
4021
|
+
?? path.resolve(process.cwd(), '.swarm', 'run-transcripts.jsonl');
|
|
4022
|
+
const { records, malformed } = readRunTranscripts(archivePath);
|
|
4023
|
+
if (records.length === 0) {
|
|
4024
|
+
const msg = `No run transcripts at ${archivePath}. Enable capture with CLAUDE_FLOW_RUN_TRANSCRIPTS=1, or pass --archive <file>.`;
|
|
4025
|
+
if (fmt === 'json')
|
|
4026
|
+
output.writeln(JSON.stringify({ ok: false, archivePath, records: 0, malformed, error: msg }, null, 2));
|
|
4027
|
+
else
|
|
4028
|
+
output.printError(msg);
|
|
4029
|
+
return { success: false, exitCode: 1, data: { archivePath, records: 0 } };
|
|
4030
|
+
}
|
|
4031
|
+
const { trajectories, stats, proxyNote } = buildArchiveFromRecords(records);
|
|
4032
|
+
const holdout = (ctx.flags['eval-holdout'] ?? ctx.flags.evalHoldout)?.split(',').map((s) => s.trim()).filter(Boolean) ?? [];
|
|
4033
|
+
const maxTokens = (ctx.flags['max-tokens'] ?? ctx.flags.maxTokens) != null ? parseInt(String((ctx.flags['max-tokens'] ?? ctx.flags.maxTokens)), 10) : undefined;
|
|
4034
|
+
const res = await runExport({
|
|
4035
|
+
archive: trajectories,
|
|
4036
|
+
evalHoldout: holdout,
|
|
4037
|
+
maxTokens,
|
|
4038
|
+
truncateOverLength: ctx.flags.truncate === true,
|
|
4039
|
+
dropRewardHacked: (ctx.flags['keep-reward-hacked'] ?? ctx.flags.keepRewardHacked) === true ? false : undefined,
|
|
4040
|
+
});
|
|
4041
|
+
if (res.degraded) {
|
|
4042
|
+
// ADR-150 graceful degradation: dep absent → not a runtime failure.
|
|
4043
|
+
const payload = { degraded: true, reason: res.reason, archiveTrajectories: trajectories.length };
|
|
4044
|
+
if (fmt === 'json')
|
|
4045
|
+
output.writeln(JSON.stringify(payload, null, 2));
|
|
4046
|
+
else {
|
|
4047
|
+
output.writeln(output.warning(`weight-eft unavailable (${res.reason}).`));
|
|
4048
|
+
output.writeln(output.dim('Install the optional dep: npm i @metaharness/weight-eft. Archive was built (' + trajectories.length + ' trajectories) but not exported.'));
|
|
4049
|
+
}
|
|
4050
|
+
return { success: true, exitCode: 0, data: payload };
|
|
4051
|
+
}
|
|
4052
|
+
const outDir = path.resolve(process.cwd(), (ctx.flags['out-dir'] ?? ctx.flags.outDir) || '.claude-flow/neural/weft-export');
|
|
4053
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
4054
|
+
const sftPath = path.join(outDir, 'sft.jsonl');
|
|
4055
|
+
const dpoPath = path.join(outDir, 'dpo.jsonl');
|
|
4056
|
+
const reportPath = path.join(outDir, 'export-report.json');
|
|
4057
|
+
fs.writeFileSync(sftPath, res.sftJsonl);
|
|
4058
|
+
fs.writeFileSync(dpoPath, res.dpoJsonl);
|
|
4059
|
+
fs.writeFileSync(reportPath, JSON.stringify({ report: res.report, archiveStats: stats, proxyNote }, null, 2));
|
|
4060
|
+
const payload = {
|
|
4061
|
+
ok: true, archivePath, outDir, sftPath, dpoPath, reportPath,
|
|
4062
|
+
sftRows: res.sftRows, dpoRows: res.dpoRows, malformed,
|
|
4063
|
+
archiveStats: stats, report: res.report, proxyNote,
|
|
4064
|
+
};
|
|
4065
|
+
if (fmt === 'json') {
|
|
4066
|
+
output.writeln(JSON.stringify(payload, null, 2));
|
|
4067
|
+
return { success: true, data: payload };
|
|
4068
|
+
}
|
|
4069
|
+
output.writeln();
|
|
4070
|
+
output.writeln(output.bold('weight-eft export — audited training data ($0, no model trained)'));
|
|
4071
|
+
output.writeln(` archive: ${archivePath} (${records.length} records, ${malformed} malformed skipped)`);
|
|
4072
|
+
output.writeln(` trajectories: ${stats.total} (cheap ${stats.byTier.cheap} / frontier ${stats.byTier.frontier}), resolved ${stats.resolved}`);
|
|
4073
|
+
output.writeln(` SFT rows: ${res.sftRows} → ${sftPath}`);
|
|
4074
|
+
output.writeln(` DPO rows: ${res.dpoRows} → ${dpoPath}`);
|
|
4075
|
+
output.writeln(` guards: holdout=${res.report.excludedByHoldout} reward-hacked=${res.report.droppedRewardHacked} over-length=${res.report.droppedOverLength} truncated=${res.report.truncatedOverLength}`);
|
|
4076
|
+
output.writeln(` report: ${reportPath}`);
|
|
4077
|
+
output.writeln();
|
|
4078
|
+
output.writeln(output.warning('resolved provenance: ' + JSON.stringify(stats.byResolvedSource)));
|
|
4079
|
+
output.writeln(output.dim(proxyNote));
|
|
4080
|
+
return { success: true, data: payload };
|
|
4081
|
+
},
|
|
4082
|
+
};
|
|
4083
|
+
const distillPlanCommand = {
|
|
4084
|
+
name: 'plan',
|
|
4085
|
+
description: 'Print the two-stage (SFT → on-policy DPO) GPU training plan + the exact `ruvllm microlora` commands a GPU host would run. $0 dry-run — NEVER spawns a tune.',
|
|
4086
|
+
options: [
|
|
4087
|
+
{ name: 'sft', type: 'string', description: 'Path to sft.jsonl (default: .claude-flow/neural/weft-export/sft.jsonl)' },
|
|
4088
|
+
{ name: 'dpo', type: 'string', description: 'Path to dpo.jsonl (default: .claude-flow/neural/weft-export/dpo.jsonl)' },
|
|
4089
|
+
{ name: 'base', short: 'b', type: 'string', description: 'Base model id to tune (7-14B band). Default Qwen2.5-Coder-7B-Instruct' },
|
|
4090
|
+
{ name: 'params-b', type: 'number', description: 'Base model param count in billions (gate [1,14]). Default 7' },
|
|
4091
|
+
{ name: 'adapter-prefix', type: 'string', description: 'Adapter output prefix', default: 'ruflo-weft' },
|
|
4092
|
+
{ name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
|
|
4093
|
+
],
|
|
4094
|
+
examples: [
|
|
4095
|
+
{ command: 'claude-flow neural distill plan', description: 'Print the GPU plan for the last export ($0 dry-run)' },
|
|
4096
|
+
{ command: 'claude-flow neural distill plan --base Qwen/Qwen2.5-Coder-7B-Instruct --params-b 7', description: 'Plan for a specific base model' },
|
|
4097
|
+
],
|
|
4098
|
+
action: async (ctx) => {
|
|
4099
|
+
const path = await import('node:path');
|
|
4100
|
+
const { runPlan, DEFAULT_BASE_MODEL } = await import('../services/weight-eft.js');
|
|
4101
|
+
const fmt = ctx.flags.format || 'table';
|
|
4102
|
+
const sftPath = ctx.flags.sft || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/sft.jsonl');
|
|
4103
|
+
const dpoPath = ctx.flags.dpo || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/dpo.jsonl');
|
|
4104
|
+
const base = ctx.flags.base
|
|
4105
|
+
? { id: String(ctx.flags.base), paramsB: (ctx.flags['params-b'] ?? ctx.flags.paramsB) != null ? parseInt(String((ctx.flags['params-b'] ?? ctx.flags.paramsB)), 10) : 7 }
|
|
4106
|
+
: DEFAULT_BASE_MODEL;
|
|
4107
|
+
const res = await runPlan({ base, sftPath, dpoPath, adapterPrefix: String((ctx.flags['adapter-prefix'] ?? ctx.flags.adapterPrefix) || 'ruflo-weft') });
|
|
4108
|
+
if (res.degraded) {
|
|
4109
|
+
const payload = { degraded: true, reason: res.reason };
|
|
4110
|
+
if (fmt === 'json')
|
|
4111
|
+
output.writeln(JSON.stringify(payload, null, 2));
|
|
4112
|
+
else {
|
|
4113
|
+
output.writeln(output.warning(`weight-eft unavailable (${res.reason}).`));
|
|
4114
|
+
output.writeln(output.dim('Install: npm i @metaharness/weight-eft'));
|
|
4115
|
+
}
|
|
4116
|
+
return { success: true, exitCode: 0, data: payload };
|
|
4117
|
+
}
|
|
4118
|
+
const payload = { ok: true, base: res.base, sft: res.sft, dpo: res.dpo, dryRun: true };
|
|
4119
|
+
if (fmt === 'json') {
|
|
4120
|
+
output.writeln(JSON.stringify(payload, null, 2));
|
|
4121
|
+
return { success: true, data: payload };
|
|
4122
|
+
}
|
|
4123
|
+
output.writeln();
|
|
4124
|
+
output.writeln(output.bold(`weight-eft GPU training plan ($0 dry-run — no tune runs from ruflo)`));
|
|
4125
|
+
output.writeln(` base model: ${res.base.id} (${res.base.paramsB}B)`);
|
|
4126
|
+
output.writeln(output.dim(' SFT stage:'));
|
|
4127
|
+
output.writeln(` ${res.sft.summary}`);
|
|
4128
|
+
output.writeln(` $ ${res.sft.command}`);
|
|
4129
|
+
output.writeln(output.dim(' DPO stage (on-policy, init from SFT adapter):'));
|
|
4130
|
+
output.writeln(` ${res.dpo.summary}`);
|
|
4131
|
+
output.writeln(` $ ${res.dpo.command}`);
|
|
4132
|
+
output.writeln();
|
|
4133
|
+
output.writeln(output.dim('These commands run on a GPU host; ruflo does not execute them. See `neural distill train --remote` for a spend-gated remote path.'));
|
|
4134
|
+
return { success: true, data: payload };
|
|
4135
|
+
},
|
|
4136
|
+
};
|
|
4137
|
+
const distillEvalCommand = {
|
|
4138
|
+
name: 'eval',
|
|
4139
|
+
description: 'Fold two CascadeOutcome[] JSON files (base vs adapter) into the cost-Pareto delta — escalation-rate reduction + $/resolved. $0. Measures cost, does NOT claim a tune ran.',
|
|
4140
|
+
options: [
|
|
4141
|
+
{ name: 'base-outcomes', type: 'string', description: 'JSON file: CascadeOutcome[] for the BASE cascade run', required: true },
|
|
4142
|
+
{ name: 'adapter-outcomes', type: 'string', description: 'JSON file: CascadeOutcome[] for the ADAPTER cascade run', required: true },
|
|
4143
|
+
{ name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
|
|
4144
|
+
],
|
|
4145
|
+
examples: [
|
|
4146
|
+
{ command: 'claude-flow neural distill eval --base-outcomes base.json --adapter-outcomes adapter.json', description: 'Cost-Pareto delta between base and adapter cascade runs' },
|
|
4147
|
+
],
|
|
4148
|
+
action: async (ctx) => {
|
|
4149
|
+
const fs = await import('node:fs');
|
|
4150
|
+
const { runEval } = await import('../services/weight-eft.js');
|
|
4151
|
+
const fmt = ctx.flags.format || 'table';
|
|
4152
|
+
const basePath = (ctx.flags['base-outcomes'] ?? ctx.flags.baseOutcomes);
|
|
4153
|
+
const adapterPath = (ctx.flags['adapter-outcomes'] ?? ctx.flags.adapterOutcomes);
|
|
4154
|
+
if (!basePath || !adapterPath) {
|
|
4155
|
+
output.printError('Both --base-outcomes and --adapter-outcomes are required.');
|
|
4156
|
+
return { success: false, exitCode: 2 };
|
|
4157
|
+
}
|
|
4158
|
+
let baseOutcomes;
|
|
4159
|
+
let adapterOutcomes;
|
|
4160
|
+
try {
|
|
4161
|
+
baseOutcomes = JSON.parse(fs.readFileSync(basePath, 'utf8'));
|
|
4162
|
+
adapterOutcomes = JSON.parse(fs.readFileSync(adapterPath, 'utf8'));
|
|
4163
|
+
}
|
|
4164
|
+
catch (e) {
|
|
4165
|
+
output.printError(`Failed to read outcome files: ${e.message}`);
|
|
4166
|
+
return { success: false, exitCode: 1 };
|
|
4167
|
+
}
|
|
4168
|
+
if (!Array.isArray(baseOutcomes) || !Array.isArray(adapterOutcomes)) {
|
|
4169
|
+
output.printError('Both files must contain a JSON array of CascadeOutcome objects.');
|
|
4170
|
+
return { success: false, exitCode: 1 };
|
|
4171
|
+
}
|
|
4172
|
+
const res = await runEval({ baseOutcomes: baseOutcomes, adapterOutcomes: adapterOutcomes });
|
|
4173
|
+
if (res.degraded) {
|
|
4174
|
+
const payload = { degraded: true, reason: res.reason };
|
|
4175
|
+
if (fmt === 'json')
|
|
4176
|
+
output.writeln(JSON.stringify(payload, null, 2));
|
|
4177
|
+
else {
|
|
4178
|
+
output.writeln(output.warning(`weight-eft unavailable (${res.reason}).`));
|
|
4179
|
+
output.writeln(output.dim('Install: npm i @metaharness/weight-eft'));
|
|
4180
|
+
}
|
|
4181
|
+
return { success: true, exitCode: 0, data: payload };
|
|
4182
|
+
}
|
|
4183
|
+
if (fmt === 'json') {
|
|
4184
|
+
output.writeln(JSON.stringify({ ok: true, delta: res.delta }, null, 2));
|
|
4185
|
+
return { success: true, data: res.delta };
|
|
4186
|
+
}
|
|
4187
|
+
output.writeln();
|
|
4188
|
+
output.writeln(output.bold('weight-eft cost-Pareto delta (measurement only)'));
|
|
4189
|
+
output.writeln(` cheap-resolve lift: ${res.delta.cheapResolveLift.toFixed(4)}`);
|
|
4190
|
+
output.writeln(` escalation-rate reduction: ${res.delta.escalationRateReduction.toFixed(4)}`);
|
|
4191
|
+
output.writeln(` $/resolved reduction: ${res.delta.costPerResolvedReduction.toFixed(6)}`);
|
|
4192
|
+
output.writeln(` resolve-rate delta: ${res.delta.resolveRateDelta.toFixed(4)} (expected ≈ 0 — ceiling unmoved)`);
|
|
4193
|
+
output.writeln(` verdict: ${res.delta.verdict}`);
|
|
4194
|
+
return { success: true, data: res.delta };
|
|
4195
|
+
},
|
|
4196
|
+
};
|
|
4197
|
+
const distillTrainCommand = {
|
|
4198
|
+
name: 'train',
|
|
4199
|
+
description: 'Remote-GPU LoRA tune over SSH — DRY-RUN by default (prints ssh/rsync/ruvllm commands + read-only preflight). Real compute ONLY with --execute --yes (spends GPU time on YOUR host). Not a $0/local tune.',
|
|
4200
|
+
options: [
|
|
4201
|
+
{ name: 'remote', short: 'r', type: 'string', description: 'SSH host or tailscale name (default: $RUFLO_DISTILL_REMOTE). Never hard-coded.' },
|
|
4202
|
+
{ name: 'base', short: 'b', type: 'string', description: 'Base model id to tune. Default Qwen2.5-Coder-7B-Instruct' },
|
|
4203
|
+
{ name: 'sft', type: 'string', description: 'Local sft.jsonl (default: .claude-flow/neural/weft-export/sft.jsonl)' },
|
|
4204
|
+
{ name: 'dpo', type: 'string', description: 'Local dpo.jsonl (default: .claude-flow/neural/weft-export/dpo.jsonl)' },
|
|
4205
|
+
{ name: 'adapter-dir', type: 'string', description: 'Local dir to fetch the trained adapter into', default: '.claude-flow/neural' },
|
|
4206
|
+
{ name: 'ssh-user', type: 'string', description: 'SSH user (default: current user)' },
|
|
4207
|
+
{ name: 'ssh-port', type: 'number', description: 'SSH port', default: '22' },
|
|
4208
|
+
{ name: 'remote-workdir', type: 'string', description: 'Remote working dir (default: ~/.ruflo-weft/<runId>)' },
|
|
4209
|
+
{ name: 'execute', type: 'boolean', description: 'Opt in to REAL GPU compute on the remote host (still needs --yes)', default: 'false' },
|
|
4210
|
+
{ name: 'yes', type: 'boolean', description: 'Second confirmation gate; required with --execute to actually spend', default: 'false' },
|
|
4211
|
+
{ name: 'preflight', type: 'boolean', description: 'Opt in to read-only reachability/GPU probes against the host (bare dry-run is fully offline and contacts nothing)', default: 'false' },
|
|
4212
|
+
{ name: 'format', short: 'f', type: 'string', description: 'Output format: table, json', default: 'table' },
|
|
4213
|
+
],
|
|
4214
|
+
examples: [
|
|
4215
|
+
{ command: 'claude-flow neural distill train --remote gpu-box', description: 'OFFLINE DRY-RUN: print the ssh/rsync/ruvllm commands only (no host contact)' },
|
|
4216
|
+
{ command: 'claude-flow neural distill train --remote gpu-box --preflight', description: 'DRY-RUN + read-only reachability/GPU probes against the host' },
|
|
4217
|
+
{ command: 'RUFLO_DISTILL_REMOTE=gpu-box claude-flow neural distill train --execute --yes', description: 'Run the real remote tune (spends GPU time on your host)' },
|
|
4218
|
+
],
|
|
4219
|
+
action: async (ctx) => {
|
|
4220
|
+
const path = await import('node:path');
|
|
4221
|
+
const { runRemoteTrain } = await import('../services/weight-eft.js');
|
|
4222
|
+
const fmt = ctx.flags.format || 'table';
|
|
4223
|
+
const host = ctx.flags.remote || process.env.RUFLO_DISTILL_REMOTE;
|
|
4224
|
+
if (!host) {
|
|
4225
|
+
output.printError('No remote host. Pass --remote <host> or set RUFLO_DISTILL_REMOTE.');
|
|
4226
|
+
return { success: false, exitCode: 2 };
|
|
4227
|
+
}
|
|
4228
|
+
const res = await runRemoteTrain({
|
|
4229
|
+
host,
|
|
4230
|
+
base: ctx.flags.base ? String(ctx.flags.base) : undefined,
|
|
4231
|
+
sftPath: ctx.flags.sft || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/sft.jsonl'),
|
|
4232
|
+
dpoPath: ctx.flags.dpo || path.resolve(process.cwd(), '.claude-flow/neural/weft-export/dpo.jsonl'),
|
|
4233
|
+
adapterDir: (ctx.flags['adapter-dir'] ?? ctx.flags.adapterDir) || '.claude-flow/neural',
|
|
4234
|
+
sshUser: (ctx.flags['ssh-user'] ?? ctx.flags.sshUser) ? String((ctx.flags['ssh-user'] ?? ctx.flags.sshUser)) : undefined,
|
|
4235
|
+
sshPort: (ctx.flags['ssh-port'] ?? ctx.flags.sshPort) != null ? parseInt(String((ctx.flags['ssh-port'] ?? ctx.flags.sshPort)), 10) : undefined,
|
|
4236
|
+
remoteWorkdir: (ctx.flags['remote-workdir'] ?? ctx.flags.remoteWorkdir) ? String((ctx.flags['remote-workdir'] ?? ctx.flags.remoteWorkdir)) : undefined,
|
|
4237
|
+
execute: ctx.flags.execute === true,
|
|
4238
|
+
yes: ctx.flags.yes === true,
|
|
4239
|
+
preflight: ctx.flags.preflight === true,
|
|
4240
|
+
});
|
|
4241
|
+
if ('degraded' in res && res.degraded) {
|
|
4242
|
+
const payload = { degraded: true, reason: res.reason };
|
|
4243
|
+
if (fmt === 'json')
|
|
4244
|
+
output.writeln(JSON.stringify(payload, null, 2));
|
|
4245
|
+
else
|
|
4246
|
+
output.writeln(output.warning(`remote-train unavailable (${res.reason}).`));
|
|
4247
|
+
return { success: true, exitCode: 0, data: payload };
|
|
4248
|
+
}
|
|
4249
|
+
if (fmt === 'json') {
|
|
4250
|
+
output.writeln(JSON.stringify(res, null, 2));
|
|
4251
|
+
return { success: res.mode !== 'preflight-failed', data: res };
|
|
4252
|
+
}
|
|
4253
|
+
output.writeln();
|
|
4254
|
+
output.writeln(output.bold(`weight-eft remote-GPU tune [${res.mode}] on ${res.plan.host}`));
|
|
4255
|
+
if (res.mode === 'dry-run')
|
|
4256
|
+
output.writeln(output.dim('DRY-RUN — no data transferred, no training. Re-run with --execute --yes to spend GPU time on your host.'));
|
|
4257
|
+
if (res.reason)
|
|
4258
|
+
output.writeln(output.warning(res.reason));
|
|
4259
|
+
output.writeln(` base: ${res.plan.base} remote workdir: ${res.plan.remoteWorkdir} adapter → ${res.plan.adapterDir}/${res.plan.dpoAdapter}`);
|
|
4260
|
+
if (res.preflight) {
|
|
4261
|
+
output.writeln(output.dim(' preflight (read-only probes):'));
|
|
4262
|
+
for (const p of res.preflight)
|
|
4263
|
+
output.writeln(` [${p.ok ? 'ok ' : 'FAIL'}] ${p.label}: ${p.detail}`);
|
|
4264
|
+
}
|
|
4265
|
+
output.writeln(output.dim(' commands that ' + (res.mode === 'executed' ? 'ran' : 'WOULD run') + ':'));
|
|
4266
|
+
for (const c of res.plan.humanCommands)
|
|
4267
|
+
output.writeln(` $ ${c}`);
|
|
4268
|
+
if (res.steps) {
|
|
4269
|
+
output.writeln(output.dim(' execution:'));
|
|
4270
|
+
for (const s of res.steps)
|
|
4271
|
+
output.writeln(` [${s.ok ? 'ok ' : 'FAIL'}] ${s.label}: ${s.detail}`);
|
|
4272
|
+
}
|
|
4273
|
+
output.writeln();
|
|
4274
|
+
output.writeln(output.dim('Honesty: ruflo does not train locally or at $0. This is an explicit, user-triggered remote-GPU spend. resolved-gold in the SFT data is still a proxy.'));
|
|
4275
|
+
return { success: res.mode !== 'preflight-failed', data: res };
|
|
4276
|
+
},
|
|
4277
|
+
};
|
|
4278
|
+
const distillCommand = {
|
|
4279
|
+
name: 'distill',
|
|
4280
|
+
description: 'weight-eft training-data + cost-audit slice (ADR-150): export | plan | eval | train. Ships audited SFT/DPO data + a cost-Pareto measurement + a GPU plan. Does NOT train a model or reduce escalation.',
|
|
4281
|
+
subcommands: [distillExportCommand, distillPlanCommand, distillEvalCommand, distillTrainCommand],
|
|
4282
|
+
examples: [
|
|
4283
|
+
{ command: 'claude-flow neural distill export', description: 'Captured transcripts → audited SFT/DPO JSONL + guard report ($0)' },
|
|
4284
|
+
{ command: 'claude-flow neural distill plan', description: 'Print the GPU training plan + ruvllm commands ($0 dry-run)' },
|
|
4285
|
+
{ command: 'claude-flow neural distill eval --base-outcomes b.json --adapter-outcomes a.json', description: 'Cost-Pareto delta ($0)' },
|
|
4286
|
+
{ command: 'claude-flow neural distill train --remote gpu-box', description: 'Remote-GPU tune DRY-RUN (spend-gated behind --execute --yes)' },
|
|
4287
|
+
],
|
|
4288
|
+
action: async () => {
|
|
4289
|
+
output.writeln('Use a subcommand: export | plan | eval | train');
|
|
4290
|
+
output.writeln(output.dim('Ships audited training DATA + a cost audit + a GPU plan. It does NOT train a model or reduce escalation (weight-eft train never spawns; resolved is a proxy).'));
|
|
4291
|
+
return { success: true };
|
|
4292
|
+
},
|
|
4293
|
+
};
|
|
3986
4294
|
// Main neural command
|
|
3987
4295
|
export const neuralCommand = {
|
|
3988
4296
|
name: 'neural',
|
|
3989
4297
|
description: 'Neural pattern training, MoE, Flash Attention, pattern learning',
|
|
3990
|
-
subcommands: [trainCommand, statusCommand, patternsCommand, predictCommand, optimizeCommand, benchmarkCommand, listCommand, exportCommand, importCommand, routerCommand],
|
|
4298
|
+
subcommands: [trainCommand, statusCommand, patternsCommand, predictCommand, optimizeCommand, benchmarkCommand, listCommand, exportCommand, importCommand, routerCommand, distillCommand],
|
|
3991
4299
|
examples: [
|
|
3992
4300
|
{ command: 'claude-flow neural status', description: 'Check neural system status' },
|
|
3993
4301
|
{ command: 'claude-flow neural train -p coordination', description: 'Train coordination patterns' },
|