claude-flow 3.20.0 → 3.21.1

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.d.ts +148 -0
  3. package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.js +218 -0
  4. package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +45 -0
  5. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +56 -0
  6. package/v3/@claude-flow/cli/dist/src/commands/neural.js +309 -1
  7. package/v3/@claude-flow/cli/dist/src/init/executor.js +17 -0
  8. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +26 -2
  9. package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.d.ts +53 -0
  10. package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.js +118 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +9 -82
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
  21. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +49 -8
  22. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
  23. package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
  24. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
  25. package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
  26. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
  27. package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
  28. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
  29. package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
  30. package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
  31. package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
  32. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
  33. package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
  34. package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
  35. package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
  36. package/v3/@claude-flow/cli/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' },
@@ -16,6 +16,7 @@ import { generateMCPJson } from './mcp-generator.js';
16
16
  import { generateStatuslineScript } from './statusline-generator.js';
17
17
  import { generatePreCommitHook, generatePostCommitHook, generateSessionManager, generateAgentRouter, generateMemoryHelper, generateHookHandler, generateIntelligenceStub, generateAutoMemoryHook, generateRufloHookCjs, } from './helpers-generator.js';
18
18
  import { generateClaudeMd } from './claudemd-generator.js';
19
+ import { recordMemoryPackagePath } from './memory-package-resolver.js';
19
20
  /**
20
21
  * Skills to copy based on configuration
21
22
  */
@@ -196,6 +197,16 @@ export async function executeInit(options) {
196
197
  // Generate helpers
197
198
  if (options.components.helpers) {
198
199
  await writeHelpers(targetDir, options, result);
200
+ // #2545: The auto-memory hook needs @claude-flow/memory, which is an
201
+ // optionalDependency of the CLI and lands in the npx cache — unreachable
202
+ // by a node_modules walk-up from the user's project. Resolve it from the
203
+ // CLI's own context now (where it IS present) and record the absolute
204
+ // path in a machine-local sidecar the hook reads first. Best-effort:
205
+ // when the optional dep is absent the hook fails loud and doctor flags it.
206
+ const memRecord = recordMemoryPackagePath(targetDir, 'init');
207
+ if (memRecord) {
208
+ result.created.files.push('.claude-flow/memory-package.json');
209
+ }
199
210
  }
200
211
  // Generate statusline
201
212
  if (options.components.statusline) {
@@ -472,6 +483,12 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
472
483
  catch { }
473
484
  }
474
485
  }
486
+ // #2545: (re)record the resolved @claude-flow/memory path so the auto-memory
487
+ // hook can find it on the npx install path. Best-effort — see executeInit.
488
+ const memRecord = recordMemoryPackagePath(targetDir, 'upgrade');
489
+ if (memRecord) {
490
+ result.updated.push('.claude-flow/memory-package.json');
491
+ }
475
492
  // 1. ALWAYS update statusline helper (force overwrite)
476
493
  const statuslinePath = path.join(targetDir, '.claude', 'helpers', 'statusline.cjs');
477
494
  // Use default options with statusline config
@@ -863,13 +863,37 @@ const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
863
863
  const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
864
864
 
865
865
  const DIM = '\\x1b[2m';
866
+ const YELLOW = '\\x1b[0;33m';
866
867
  const RESET = '\\x1b[0m';
867
868
  const dim = (msg) => console.log(\` \${DIM}\${msg}\${RESET}\`);
868
869
 
870
+ // #2545: fail LOUD instead of a silent dim skip when @claude-flow/memory is
871
+ // unresolvable — self-learning imports are a no-op and the user must be told.
872
+ function warnMemoryUnavailable() {
873
+ const l1 = \`[AutoMemory] @claude-flow/memory not resolvable from \${PROJECT_ROOT} — self-learning imports are DISABLED.\`;
874
+ const l2 = ' Fix: npm i -D @claude-flow/memory (or re-run: npx ruflo@latest init, then npx ruflo@latest doctor --fix)';
875
+ console.log(\`\${YELLOW}\${l1}\${RESET}\`);
876
+ console.log(\`\${YELLOW}\${l2}\${RESET}\`);
877
+ process.stderr.write(\`\${l1}\\n\${l2}\\n\`);
878
+ }
879
+
869
880
  // Ensure data dir
870
881
  if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
871
882
 
872
883
  async function loadMemoryPackage() {
884
+ // Strategy 0 (#2545): sidecar recorded by \`init\` / \`doctor --fix\`. On the npx
885
+ // path @claude-flow/memory lands in the npx cache (unreachable by walk-up), so
886
+ // init records its absolute path here — the only strategy that works there.
887
+ try {
888
+ const sidecar = join(PROJECT_ROOT, '.claude-flow', 'memory-package.json');
889
+ if (existsSync(sidecar)) {
890
+ const rec = JSON.parse(readFileSync(sidecar, 'utf-8'));
891
+ if (rec && rec.distPath && existsSync(rec.distPath)) {
892
+ return await import(\`file://\${rec.distPath}\`);
893
+ }
894
+ }
895
+ } catch { /* fall through */ }
896
+
873
897
  // Strategy 1: Use createRequire for CJS-style resolution (handles nested node_modules
874
898
  // when installed as a transitive dependency via npx ruflo / npx claude-flow)
875
899
  try {
@@ -899,7 +923,7 @@ async function doImport() {
899
923
  const memPkg = await loadMemoryPackage();
900
924
 
901
925
  if (!memPkg || !memPkg.AutoMemoryBridge) {
902
- dim('Memory package not available — auto memory import skipped (non-critical)');
926
+ warnMemoryUnavailable();
903
927
  return;
904
928
  }
905
929
 
@@ -916,7 +940,7 @@ async function doSync() {
916
940
  const memPkg = await loadMemoryPackage();
917
941
 
918
942
  if (!memPkg || !memPkg.AutoMemoryBridge) {
919
- dim('Memory package not available — sync skipped (non-critical)');
943
+ warnMemoryUnavailable();
920
944
  return;
921
945
  }
922
946
 
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Memory Package Resolver (#2545)
3
+ *
4
+ * `@claude-flow/memory` is an *optionalDependency* of `@claude-flow/cli`. On the
5
+ * documented `npx ruflo` install path it lands in the npx cache
6
+ * (`~/.npm/_npx/<hash>/node_modules`), which is NOT on the node_modules walk-up
7
+ * path from the user's project. The SessionStart auto-memory hook therefore
8
+ * could never resolve it and self-learning silently no-op'd.
9
+ *
10
+ * At init time, however, the CLI *can* resolve the package from its own module
11
+ * context (it is installed alongside the CLI in that same npx cache). We resolve
12
+ * it once and record the absolute path in a machine-local project sidecar
13
+ * (`.claude-flow/memory-package.json`). The hook reads this sidecar first, so it
14
+ * reuses the copy npx already downloaded — no second install, no vendoring.
15
+ *
16
+ * This module is deliberately dependency-free and best-effort: nothing here ever
17
+ * throws into the init/doctor flow.
18
+ */
19
+ export declare const MEMORY_PACKAGE = "@claude-flow/memory";
20
+ /** Project-relative path of the resolver sidecar written by init / doctor --fix. */
21
+ export declare const MEMORY_SIDECAR_REL: string;
22
+ export interface MemoryPackageRecord {
23
+ /** Absolute path to the package's main entry (dist/index.js). */
24
+ distPath: string;
25
+ /** Resolved package version, if readable. */
26
+ version: string | null;
27
+ /** What produced this record ("init" | "doctor" | "upgrade"). */
28
+ resolvedBy: string;
29
+ /** ISO timestamp of resolution. */
30
+ resolvedAt: string;
31
+ }
32
+ /**
33
+ * Resolve `@claude-flow/memory`'s main entry from the CLI's own module context.
34
+ * Returns the absolute dist path, or null when the optional dependency is absent
35
+ * (e.g. installed with `--omit=optional`).
36
+ */
37
+ export declare function resolveMemoryPackageFromCli(): string | null;
38
+ /**
39
+ * Resolve `@claude-flow/memory` the same way the runtime hook does, from a target
40
+ * project directory: sidecar → project package.json → node_modules walk-up.
41
+ * Used by `doctor` so its verdict matches what the hook will actually experience.
42
+ */
43
+ export declare function resolveMemoryPackageFromProject(targetDir: string): string | null;
44
+ /** Read the version of the resolved memory package (dist/index.js → ../package.json). */
45
+ export declare function readMemoryPackageVersion(distPath: string): string | null;
46
+ /**
47
+ * Best-effort: resolve `@claude-flow/memory` from the CLI and record its path in
48
+ * the project sidecar so the runtime hook can find it on the npx install path.
49
+ * Returns the written record, or null when the package is not resolvable from the
50
+ * CLI (in which case the hook fails loud and `doctor` flags it).
51
+ */
52
+ export declare function recordMemoryPackagePath(targetDir: string, resolvedBy?: string): MemoryPackageRecord | null;
53
+ //# sourceMappingURL=memory-package-resolver.d.ts.map
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Memory Package Resolver (#2545)
3
+ *
4
+ * `@claude-flow/memory` is an *optionalDependency* of `@claude-flow/cli`. On the
5
+ * documented `npx ruflo` install path it lands in the npx cache
6
+ * (`~/.npm/_npx/<hash>/node_modules`), which is NOT on the node_modules walk-up
7
+ * path from the user's project. The SessionStart auto-memory hook therefore
8
+ * could never resolve it and self-learning silently no-op'd.
9
+ *
10
+ * At init time, however, the CLI *can* resolve the package from its own module
11
+ * context (it is installed alongside the CLI in that same npx cache). We resolve
12
+ * it once and record the absolute path in a machine-local project sidecar
13
+ * (`.claude-flow/memory-package.json`). The hook reads this sidecar first, so it
14
+ * reuses the copy npx already downloaded — no second install, no vendoring.
15
+ *
16
+ * This module is deliberately dependency-free and best-effort: nothing here ever
17
+ * throws into the init/doctor flow.
18
+ */
19
+ import { createRequire } from 'module';
20
+ import * as fs from 'fs';
21
+ import * as path from 'path';
22
+ export const MEMORY_PACKAGE = '@claude-flow/memory';
23
+ /** Project-relative path of the resolver sidecar written by init / doctor --fix. */
24
+ export const MEMORY_SIDECAR_REL = path.join('.claude-flow', 'memory-package.json');
25
+ /**
26
+ * Resolve `@claude-flow/memory`'s main entry from the CLI's own module context.
27
+ * Returns the absolute dist path, or null when the optional dependency is absent
28
+ * (e.g. installed with `--omit=optional`).
29
+ */
30
+ export function resolveMemoryPackageFromCli() {
31
+ try {
32
+ const require = createRequire(import.meta.url);
33
+ return require.resolve(MEMORY_PACKAGE);
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Resolve `@claude-flow/memory` the same way the runtime hook does, from a target
41
+ * project directory: sidecar → project package.json → node_modules walk-up.
42
+ * Used by `doctor` so its verdict matches what the hook will actually experience.
43
+ */
44
+ export function resolveMemoryPackageFromProject(targetDir) {
45
+ // 1. Sidecar written by a previous init / doctor --fix
46
+ try {
47
+ const sidecar = path.join(targetDir, MEMORY_SIDECAR_REL);
48
+ if (fs.existsSync(sidecar)) {
49
+ const rec = JSON.parse(fs.readFileSync(sidecar, 'utf-8'));
50
+ if (rec?.distPath && fs.existsSync(rec.distPath))
51
+ return rec.distPath;
52
+ }
53
+ }
54
+ catch {
55
+ /* fall through */
56
+ }
57
+ // 2. createRequire from the project's package.json (direct/transitive dep)
58
+ try {
59
+ const require = createRequire(path.join(targetDir, 'package.json'));
60
+ return require.resolve(MEMORY_PACKAGE);
61
+ }
62
+ catch {
63
+ /* fall through */
64
+ }
65
+ // 3. node_modules walk-up from the project root
66
+ let dir = path.resolve(targetDir);
67
+ // eslint-disable-next-line no-constant-condition
68
+ while (true) {
69
+ const candidate = path.join(dir, 'node_modules', '@claude-flow', 'memory', 'dist', 'index.js');
70
+ if (fs.existsSync(candidate))
71
+ return candidate;
72
+ const parent = path.dirname(dir);
73
+ if (parent === dir)
74
+ break;
75
+ dir = parent;
76
+ }
77
+ return null;
78
+ }
79
+ /** Read the version of the resolved memory package (dist/index.js → ../package.json). */
80
+ export function readMemoryPackageVersion(distPath) {
81
+ try {
82
+ const pkgJson = path.resolve(path.dirname(distPath), '..', 'package.json');
83
+ if (fs.existsSync(pkgJson)) {
84
+ const parsed = JSON.parse(fs.readFileSync(pkgJson, 'utf-8'));
85
+ return parsed.version ?? null;
86
+ }
87
+ }
88
+ catch {
89
+ /* ignore */
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * Best-effort: resolve `@claude-flow/memory` from the CLI and record its path in
95
+ * the project sidecar so the runtime hook can find it on the npx install path.
96
+ * Returns the written record, or null when the package is not resolvable from the
97
+ * CLI (in which case the hook fails loud and `doctor` flags it).
98
+ */
99
+ export function recordMemoryPackagePath(targetDir, resolvedBy = 'init') {
100
+ const distPath = resolveMemoryPackageFromCli();
101
+ if (!distPath)
102
+ return null;
103
+ const record = {
104
+ distPath,
105
+ version: readMemoryPackageVersion(distPath),
106
+ resolvedBy,
107
+ resolvedAt: new Date().toISOString(),
108
+ };
109
+ try {
110
+ fs.mkdirSync(path.join(targetDir, '.claude-flow'), { recursive: true });
111
+ fs.writeFileSync(path.join(targetDir, MEMORY_SIDECAR_REL), JSON.stringify(record, null, 2), 'utf-8');
112
+ return record;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ //# sourceMappingURL=memory-package-resolver.js.map
@@ -50,6 +50,9 @@ import { metaharnessTools } from './mcp-tools/metaharness-tools.js';
50
50
  // agenticow@~0.2.3 — Copy-On-Write memory branching tools (162-byte branches);
51
51
  // optional runtime dep, every handler returns `{degraded: true}` when missing.
52
52
  import { agenticowTools } from './mcp-tools/agenticow-tools.js';
53
+ // agenticow step 4 — speculative branch-and-promote (A/B memory exploration).
54
+ // Optional runtime dep, degrades to `{degraded: true}` when agenticow is missing.
55
+ import { agenticowSpeculateTools } from './mcp-tools/agenticow-speculate-tools.js';
53
56
  // ADR-164 — AgentBBS federated business-domain BBS rooms (Phase 1).
54
57
  // Optional runtime dep, every handler returns `{degraded: true}` when missing.
55
58
  import { agentbbsTools } from './mcp-tools/agentbbs-tools.js';
@@ -139,8 +142,10 @@ registerTools([
139
142
  ...coverageRouterTools,
140
143
  // ADR-150 — MetaHarness static-analysis tools (5)
141
144
  ...metaharnessTools,
142
- // agenticow@~0.2.3 — COW memory branching (4 tools, graceful-degraded when missing)
145
+ // agenticow@~0.2.4 — COW memory branching (9 tools: branch/checkpoint/rollback/promote + ingest/query/diff/lineage/status, graceful-degraded when missing)
143
146
  ...agenticowTools,
147
+ // agenticow step 4 — speculative branch-and-promote (1 tool, graceful-degraded when missing)
148
+ ...agenticowSpeculateTools,
144
149
  // ADR-164 — AgentBBS federated business-domain BBS rooms (4 tools, Phase 1, graceful-degraded)
145
150
  ...agentbbsTools,
146
151
  // ADR-164 Phase 2 + Phase 3 — business_pod_validate + business_pod_route_backend
@@ -512,6 +512,39 @@ export async function executeAgentTask(input) {
512
512
  }
513
513
  catch { /* never break execution */ }
514
514
  }
515
+ // ADR-150 weight-eft capture seam — the ONLY place a run's full outcome is
516
+ // known here: prompt (issue), assistant transcript, model, tier, and a
517
+ // resolved PROXY. Gated behind CLAUDE_FLOW_RUN_TRANSCRIPTS=1 (off by
518
+ // default; PII/retention surface, mirrors the router trajectory recorder).
519
+ // HONESTY: `resolved` here is the WEAKEST proxy — 'api-success' means only
520
+ // that the model returned without an API error, NOT that the output is
521
+ // correct (ruflo has no SWE-bench gold oracle). model_patch is '' because
522
+ // this single-shot execute path produces no unified diff. Both facts are
523
+ // stamped so no downstream weight-eft export mistakes this for gold data.
524
+ if (process.env.CLAUDE_FLOW_RUN_TRANSCRIPTS === '1') {
525
+ try {
526
+ const { recordRunTranscript, tierForModel } = await import('../ruvector/run-transcript-recorder.js');
527
+ const modelId = agent.modelId ?? String(agent.model ?? 'unknown');
528
+ const messages = [];
529
+ if (systemPrompt)
530
+ messages.push({ role: 'system', content: systemPrompt });
531
+ messages.push({ role: 'user', content: input.prompt });
532
+ if (result.success && typeof result.output === 'string') {
533
+ messages.push({ role: 'assistant', content: result.output });
534
+ }
535
+ recordRunTranscript({
536
+ task: input.prompt,
537
+ model: modelId,
538
+ tier: tierForModel(modelId),
539
+ resolved: outcome === 'success',
540
+ resolvedSource: 'api-success',
541
+ messages,
542
+ source: 'agent-execute',
543
+ tokens: result.usage ? { input: result.usage.inputTokens, output: result.usage.outputTokens } : undefined,
544
+ });
545
+ }
546
+ catch { /* never break execution */ }
547
+ }
515
548
  }
516
549
  catch {
517
550
  // Silent — bandit feedback must never block routing.