@claude-flow/cli 3.19.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 +366 -12
- 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 +186 -79
- 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/index.d.ts +1 -1
- package/dist/src/ruvector/index.d.ts.map +1 -1
- package/dist/src/ruvector/index.js +1 -1
- package/dist/src/ruvector/index.js.map +1 -1
- package/dist/src/ruvector/lora-adapter.d.ts +35 -0
- package/dist/src/ruvector/lora-adapter.d.ts.map +1 -1
- package/dist/src/ruvector/lora-adapter.js +108 -1
- package/dist/src/ruvector/lora-adapter.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/native-training.d.ts +28 -0
- package/dist/src/services/native-training.d.ts.map +1 -1
- package/dist/src/services/native-training.js +62 -5
- package/dist/src/services/native-training.js.map +1 -1
- 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
|
@@ -24,6 +24,8 @@ const trainCommand = {
|
|
|
24
24
|
{ name: 'contrastive', type: 'boolean', description: 'Use contrastive learning (InfoNCE)', default: 'true' },
|
|
25
25
|
{ name: 'curriculum', type: 'boolean', description: 'Enable curriculum learning', default: 'false' },
|
|
26
26
|
{ name: 'backend', type: 'string', description: 'Training backend: auto (native when available), native (@ruvector/ruvllm TrainingPipeline, disk checkpoints), wasm (RuVector MicroLoRA/InfoNCE)', default: 'auto' },
|
|
27
|
+
{ name: 'val-split', type: 'number', description: 'Validation holdout fraction 0..1 (native backend). >0 reports Best Val Loss + early stopping; 0 disables', default: '0.1' },
|
|
28
|
+
{ name: 'resume', type: 'string', description: 'Resume native training from a checkpoint path (weights on 2.5.7; epoch position on >=2.6.0). Native backend only', default: '' },
|
|
27
29
|
],
|
|
28
30
|
examples: [
|
|
29
31
|
{ command: 'claude-flow neural train -p coordination -e 100', description: 'Train coordination patterns' },
|
|
@@ -41,6 +43,17 @@ const trainCommand = {
|
|
|
41
43
|
// 'wasm' = RuVector MicroLoRA/InfoNCE (pre-3.19 behavior),
|
|
42
44
|
// 'auto' = native when the module resolves, else wasm.
|
|
43
45
|
const backendFlag = String(ctx.flags.backend || 'auto');
|
|
46
|
+
// Feature: validation split + resume (native TrainingPipeline leg).
|
|
47
|
+
const valSplitRaw = parseFloat(ctx.flags['val-split'] ?? '0.1');
|
|
48
|
+
const valSplit = Number.isFinite(valSplitRaw) ? Math.max(0, Math.min(1, valSplitRaw)) : 0.1;
|
|
49
|
+
const resumePath = ctx.flags.resume ? String(ctx.flags.resume) : undefined;
|
|
50
|
+
// --resume is a native-only capability; refuse the WASM combination up
|
|
51
|
+
// front so the user gets a clear error rather than a silently-ignored flag.
|
|
52
|
+
if (resumePath && backendFlag === 'wasm') {
|
|
53
|
+
output.writeln();
|
|
54
|
+
output.writeln(output.error('--resume is only supported by the native backend; drop --backend wasm.'));
|
|
55
|
+
return { success: false, exitCode: 1 };
|
|
56
|
+
}
|
|
44
57
|
const useWasm = ctx.flags.wasm !== false;
|
|
45
58
|
const useFlash = ctx.flags.flash !== false;
|
|
46
59
|
const useMoE = ctx.flags.moe === true;
|
|
@@ -195,18 +208,34 @@ const trainCommand = {
|
|
|
195
208
|
const nativeTraining = await import('../services/native-training.js');
|
|
196
209
|
const useNative = backendFlag === 'native'
|
|
197
210
|
|| (backendFlag === 'auto' && nativeTraining.nativeTrainingAvailable());
|
|
211
|
+
// --resume only works on the native pipeline; if native is unavailable
|
|
212
|
+
// (module absent), fail loudly rather than silently fresh-train.
|
|
213
|
+
if (resumePath && !useNative) {
|
|
214
|
+
spinner.fail('--resume requires the native @ruvector/ruvllm backend, which is not available');
|
|
215
|
+
return { success: false, exitCode: 1 };
|
|
216
|
+
}
|
|
198
217
|
let nativeResult = null;
|
|
199
218
|
if (useNative) {
|
|
200
219
|
spinner.setText(`Training ${patternType} on native @ruvector/ruvllm pipeline...`);
|
|
201
220
|
const path = await import('path');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
try {
|
|
222
|
+
nativeResult = await nativeTraining.runNativeTraining({
|
|
223
|
+
embeddings,
|
|
224
|
+
epochs,
|
|
225
|
+
batchSize,
|
|
226
|
+
learningRate,
|
|
227
|
+
dim,
|
|
228
|
+
validationSplit: valSplit,
|
|
229
|
+
resumeFrom: resumePath,
|
|
230
|
+
checkpointPath: path.join(process.cwd(), '.claude-flow', 'neural', `lora-checkpoint-${Date.now()}.json`),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
// ResumeFailedError — an explicit --resume that could not load is a
|
|
235
|
+
// loud, exit-1 failure, never a silent fall-through to fresh training.
|
|
236
|
+
spinner.fail(`Resume failed: ${err.message}`);
|
|
237
|
+
return { success: false, exitCode: 1 };
|
|
238
|
+
}
|
|
210
239
|
if (!nativeResult && backendFlag === 'native') {
|
|
211
240
|
spinner.fail('Native backend requested (--backend native) but @ruvector/ruvllm training failed');
|
|
212
241
|
return { success: false, exitCode: 1 };
|
|
@@ -325,7 +354,20 @@ const trainCommand = {
|
|
|
325
354
|
];
|
|
326
355
|
// Native pipeline metrics (#2549 — the LoRA leg trained on ruvllm)
|
|
327
356
|
if (nativeResult) {
|
|
328
|
-
tableData.push({ metric: 'Backend', value: 'native (@ruvector/ruvllm TrainingPipeline)' }, { metric: 'Native Steps', value: String(nativeResult.steps) }, { metric: 'Final Loss', value: nativeResult.finalLoss.toExponential(3) }
|
|
357
|
+
tableData.push({ metric: 'Backend', value: 'native (@ruvector/ruvllm TrainingPipeline)' }, { metric: 'Native Steps', value: String(nativeResult.steps) }, { metric: 'Final Loss', value: nativeResult.finalLoss.toExponential(3) });
|
|
358
|
+
// Validation metrics only surface when a holdout actually ran
|
|
359
|
+
// (bestValLoss is non-null); Early Stopped is only meaningful then.
|
|
360
|
+
if (nativeResult.bestValLoss !== null && nativeResult.bestValLoss !== undefined) {
|
|
361
|
+
tableData.push({ metric: 'Best Val Loss', value: nativeResult.bestValLoss.toExponential(3) }, { metric: 'Early Stopped', value: nativeResult.earlyStopped ? 'yes' : 'no' });
|
|
362
|
+
}
|
|
363
|
+
if (nativeResult.resumed) {
|
|
364
|
+
tableData.push({
|
|
365
|
+
metric: 'Resumed',
|
|
366
|
+
value: nativeResult.resumeMode === 'resumeFrom'
|
|
367
|
+
? `${resumePath} (epoch position restored)`
|
|
368
|
+
: `${resumePath} (weights only — epoch-position resume needs @ruvector/ruvllm >=2.6.0)`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
329
371
|
if (nativeResult.checkpointPath) {
|
|
330
372
|
tableData.push({
|
|
331
373
|
metric: 'Checkpoint',
|
|
@@ -523,10 +565,14 @@ const statusCommand = {
|
|
|
523
565
|
details: stats._trainingBackend === 'ruvllm'
|
|
524
566
|
? await (async () => {
|
|
525
567
|
try {
|
|
526
|
-
const { nativeCheckpointsSupported } = await import('../ruvector/lora-adapter.js');
|
|
527
|
-
|
|
568
|
+
const { nativeCheckpointsSupported, latestCheckpointInfo } = await import('../ruvector/lora-adapter.js');
|
|
569
|
+
// Most important info first (truncation-friendly): backend
|
|
570
|
+
// capability, then the newest checkpoint + age when one exists.
|
|
571
|
+
const base = nativeCheckpointsSupported()
|
|
528
572
|
? 'native @ruvector/ruvllm pipeline + disk checkpoints'
|
|
529
573
|
: 'native @ruvector/ruvllm pipeline (checkpoints need >=2.5.7)';
|
|
574
|
+
const cp = latestCheckpointInfo();
|
|
575
|
+
return cp ? `${base} · latest: ${cp.filename} (${cp.ageLabel})` : base;
|
|
530
576
|
}
|
|
531
577
|
catch {
|
|
532
578
|
return 'native @ruvector/ruvllm pipeline';
|
|
@@ -3937,11 +3983,319 @@ const routerCommand = {
|
|
|
3937
3983
|
return { success: true };
|
|
3938
3984
|
},
|
|
3939
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
|
+
};
|
|
3940
4294
|
// Main neural command
|
|
3941
4295
|
export const neuralCommand = {
|
|
3942
4296
|
name: 'neural',
|
|
3943
4297
|
description: 'Neural pattern training, MoE, Flash Attention, pattern learning',
|
|
3944
|
-
subcommands: [trainCommand, statusCommand, patternsCommand, predictCommand, optimizeCommand, benchmarkCommand, listCommand, exportCommand, importCommand, routerCommand],
|
|
4298
|
+
subcommands: [trainCommand, statusCommand, patternsCommand, predictCommand, optimizeCommand, benchmarkCommand, listCommand, exportCommand, importCommand, routerCommand, distillCommand],
|
|
3945
4299
|
examples: [
|
|
3946
4300
|
{ command: 'claude-flow neural status', description: 'Check neural system status' },
|
|
3947
4301
|
{ command: 'claude-flow neural train -p coordination', description: 'Train coordination patterns' },
|