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.
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.d.ts +148 -0
- package/v3/@claude-flow/cli/dist/src/agenticow/speculative-exploration.js +218 -0
- package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +45 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +56 -0
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +309 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +17 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +26 -2
- package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.d.ts +53 -0
- package/v3/@claude-flow/cli/dist/src/init/memory-package-resolver.js +118 -0
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-loader.js +105 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +202 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +9 -82
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +49 -8
- package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/router-trajectory.js +31 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/run-transcript-recorder.js +209 -0
- package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.d.ts +140 -0
- package/v3/@claude-flow/cli/dist/src/services/checkpoint-gate.js +223 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.d.ts +190 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-oracle.js +349 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.d.ts +168 -0
- package/v3/@claude-flow/cli/dist/src/services/fable-harness.js +347 -0
- package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.d.ts +135 -0
- package/v3/@claude-flow/cli/dist/src/services/swarm-memory-branches.js +213 -0
- package/v3/@claude-flow/cli/dist/src/services/weight-eft.d.ts +305 -0
- package/v3/@claude-flow/cli/dist/src/services/weight-eft.js +296 -0
- package/v3/@claude-flow/cli/package.json +5 -4
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|