claude-flow 3.19.0 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +57 -11
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +182 -2
- package/v3/@claude-flow/cli/dist/src/ruvector/index.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/index.js +1 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.d.ts +35 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.js +108 -1
- package/v3/@claude-flow/cli/dist/src/services/native-training.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/services/native-training.js +62 -5
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"@grpc/grpc-js": ">=1.14.4",
|
|
118
118
|
"form-data": ">=4.0.6",
|
|
119
119
|
"http-proxy-middleware": ">=3.0.7",
|
|
120
|
-
"@ruvector/ruvllm": ">=2.
|
|
120
|
+
"@ruvector/ruvllm": ">=2.6.0"
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
123
|
"@openai/codex": "^0.98.0",
|
|
@@ -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';
|
|
@@ -121,6 +121,7 @@ export const agenticowTools = [
|
|
|
121
121
|
branchPath: { type: 'string', description: 'Path to write the branch file' },
|
|
122
122
|
label: { type: 'string', description: 'Human-readable label for the branch (alnum + _.-:/@ only)' },
|
|
123
123
|
dimension: { type: 'integer', description: 'Vector dimension (required only when basePath does not exist yet)' },
|
|
124
|
+
nativeAnn: { type: 'boolean', description: 'Use the native Rust COW dual-graph ANN path (recall@10=1.0, query spans the COW boundary in one Rust call). Default false (exact JS chain-walk). Set true when the branch will be queried.', default: false },
|
|
124
125
|
},
|
|
125
126
|
required: ['basePath', 'branchPath', 'label'],
|
|
126
127
|
},
|
|
@@ -132,9 +133,10 @@ export const agenticowTools = [
|
|
|
132
133
|
const basePath = resolveMemoryPath(String(input.basePath));
|
|
133
134
|
const branchPath = resolveMemoryPath(String(input.branchPath));
|
|
134
135
|
const dim = input.dimension;
|
|
136
|
+
const nativeAnn = input.nativeAnn === true;
|
|
135
137
|
const base = await openWithLineage(api, basePath, dim);
|
|
136
138
|
try {
|
|
137
|
-
const branch = await base.fork(label, branchPath);
|
|
139
|
+
const branch = await base.fork(label, branchPath, { nativeAnn });
|
|
138
140
|
// Persist lineage manifests so the branch (and base) reopen with
|
|
139
141
|
// their COW chain intact. Without this, fork is in-memory only.
|
|
140
142
|
await branch.save?.(manifestFor(branchPath));
|
|
@@ -145,6 +147,7 @@ export const agenticowTools = [
|
|
|
145
147
|
basePath,
|
|
146
148
|
branchPath,
|
|
147
149
|
label,
|
|
150
|
+
nativeAnn,
|
|
148
151
|
};
|
|
149
152
|
}
|
|
150
153
|
finally {
|
|
@@ -152,6 +155,181 @@ export const agenticowTools = [
|
|
|
152
155
|
}
|
|
153
156
|
},
|
|
154
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: 'agenticow_ingest',
|
|
160
|
+
description: 'agenticow — write vectors (with optional text payloads) into an .rvf memory branch or base. Records: [{id?, vector, text?}] — id auto-assigns when omitted. This is the write half that makes a branch usable: agenticow_branch creates an empty COW child, but without ingest it has nothing to read back. Use after branching to populate an agent/session-local memory. Editing the base directly is wrong when the writes are speculative — ingest into a branch, then promote only if validated. Persists via .agenticow.json lineage manifest.',
|
|
161
|
+
category: 'memory',
|
|
162
|
+
tags: ['agenticow', 'memory', 'cow', 'ingest', 'write'],
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
path: { type: 'string', description: 'Path to .rvf memory file (branch or base)' },
|
|
167
|
+
records: {
|
|
168
|
+
type: 'array',
|
|
169
|
+
description: 'Vectors to ingest: [{id?: number, vector: number[], text?: string}]',
|
|
170
|
+
items: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
id: { type: 'integer', description: 'Explicit id (auto-assigned when omitted)' },
|
|
174
|
+
vector: { type: 'array', items: { type: 'number' }, description: 'Embedding vector (length must equal the memory dimension)' },
|
|
175
|
+
text: { type: 'string', description: 'Optional payload surfaced on query hits' },
|
|
176
|
+
},
|
|
177
|
+
required: ['vector'],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
dimension: { type: 'integer', description: 'Vector dimension (required only when path does not exist yet)' },
|
|
181
|
+
},
|
|
182
|
+
required: ['path', 'records'],
|
|
183
|
+
},
|
|
184
|
+
handler: async (input) => {
|
|
185
|
+
const api = await loadAgenticow();
|
|
186
|
+
if (!api)
|
|
187
|
+
return degradedResult('agenticow-not-found');
|
|
188
|
+
const path = resolveMemoryPath(String(input.path));
|
|
189
|
+
const records = input.records;
|
|
190
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
191
|
+
throw new Error('records must be a non-empty array of {id?, vector, text?}');
|
|
192
|
+
}
|
|
193
|
+
for (const r of records) {
|
|
194
|
+
if (!Array.isArray(r.vector) || r.vector.length === 0) {
|
|
195
|
+
throw new Error('each record requires a non-empty numeric vector');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const dim = input.dimension ?? records[0].vector.length;
|
|
199
|
+
const mem = await openWithLineage(api, path, dim);
|
|
200
|
+
try {
|
|
201
|
+
const result = await mem.ingest(records.map((r) => ({
|
|
202
|
+
...(typeof r.id === 'number' ? { id: r.id } : {}),
|
|
203
|
+
vector: r.vector,
|
|
204
|
+
...(r.text !== undefined ? { text: r.text } : {}),
|
|
205
|
+
})));
|
|
206
|
+
await mem.save?.(manifestFor(path));
|
|
207
|
+
return { success: true, path, ingested: result };
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await mem.close?.();
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'agenticow_query',
|
|
216
|
+
description: 'agenticow — k-NN read across an .rvf memory branch\'s full COW lineage (parent ∪ edits, child wins), returning {id, distance, branch, text}. Read-only (no manifest write). The `branch` field on each hit tells you which lineage node the result came from — the read-through semantics that make branching useful. Use to retrieve from an agent/session branch without materializing a full copy. Re-opening the base and manually merging edits is wrong: query already spans the chain (and uses the single-call Rust path when the branch was forked with nativeAnn).',
|
|
217
|
+
category: 'memory',
|
|
218
|
+
tags: ['agenticow', 'memory', 'cow', 'query', 'read', 'search'],
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
223
|
+
vector: { type: 'array', items: { type: 'number' }, description: 'Query embedding vector' },
|
|
224
|
+
k: { type: 'integer', description: 'Number of nearest neighbors to return', default: 10 },
|
|
225
|
+
efSearch: { type: 'integer', description: 'HNSW efSearch per lineage store (higher = better recall, slower)' },
|
|
226
|
+
},
|
|
227
|
+
required: ['path', 'vector'],
|
|
228
|
+
},
|
|
229
|
+
handler: async (input) => {
|
|
230
|
+
const api = await loadAgenticow();
|
|
231
|
+
if (!api)
|
|
232
|
+
return degradedResult('agenticow-not-found');
|
|
233
|
+
const path = resolveMemoryPath(String(input.path));
|
|
234
|
+
const vector = input.vector;
|
|
235
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
236
|
+
throw new Error('vector must be a non-empty numeric array');
|
|
237
|
+
}
|
|
238
|
+
const k = typeof input.k === 'number' ? input.k : 10;
|
|
239
|
+
const opts = {};
|
|
240
|
+
if (typeof input.efSearch === 'number')
|
|
241
|
+
opts.efSearch = input.efSearch;
|
|
242
|
+
const mem = await openWithLineage(api, path);
|
|
243
|
+
try {
|
|
244
|
+
const hits = await mem.query(vector, k, opts);
|
|
245
|
+
return { success: true, path, k, hits };
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
await mem.close?.();
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'agenticow_diff',
|
|
254
|
+
description: 'agenticow — show what a branch changed relative to its lineage: {added, overridden, deleted} vector-id lists. Use before promote to preview the exact merge, or to audit what an agent/session branch actually wrote. Diffing by re-querying is wrong because deletions (tombstones) are invisible to a read — diff() surfaces them explicitly. Requires the branch was opened with edit tracking (default on).',
|
|
255
|
+
category: 'memory',
|
|
256
|
+
tags: ['agenticow', 'memory', 'cow', 'diff'],
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
path: { type: 'string', description: 'Path to branch .rvf file' },
|
|
261
|
+
},
|
|
262
|
+
required: ['path'],
|
|
263
|
+
},
|
|
264
|
+
handler: async (input) => {
|
|
265
|
+
const api = await loadAgenticow();
|
|
266
|
+
if (!api)
|
|
267
|
+
return degradedResult('agenticow-not-found');
|
|
268
|
+
const path = resolveMemoryPath(String(input.path));
|
|
269
|
+
const mem = await openWithLineage(api, path);
|
|
270
|
+
try {
|
|
271
|
+
const diff = await mem.diff();
|
|
272
|
+
return { success: true, path, diff };
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
await mem.close?.();
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'agenticow_lineage',
|
|
281
|
+
description: 'agenticow — walk the COW chain of an .rvf memory file: an ordered list of nodes (role working|checkpoint|base, id, label, parent, createdAt, mutations, tombstones). Use to understand branch history, find checkpoint ids for a targeted rollback, or debug a promote. Guessing the chain from filenames is wrong — lineage is the authoritative structure the store maintains.',
|
|
282
|
+
category: 'memory',
|
|
283
|
+
tags: ['agenticow', 'memory', 'cow', 'lineage', 'history'],
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
288
|
+
},
|
|
289
|
+
required: ['path'],
|
|
290
|
+
},
|
|
291
|
+
handler: async (input) => {
|
|
292
|
+
const api = await loadAgenticow();
|
|
293
|
+
if (!api)
|
|
294
|
+
return degradedResult('agenticow-not-found');
|
|
295
|
+
const path = resolveMemoryPath(String(input.path));
|
|
296
|
+
const mem = await openWithLineage(api, path);
|
|
297
|
+
try {
|
|
298
|
+
const lineage = await mem.lineage();
|
|
299
|
+
return { success: true, path, lineage };
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
await mem.close?.();
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'agenticow_status',
|
|
308
|
+
description: 'agenticow — health/geometry of an .rvf memory file: {totalVectors, totalSegments, fileSize, currentEpoch, deadSpaceRatio, readOnly, chainDepth, dimension, metric}. Use to check vector count before/after ingest, spot compaction pressure (deadSpaceRatio), or confirm the dimension before ingesting into a shared base. Pure read.',
|
|
309
|
+
category: 'memory',
|
|
310
|
+
tags: ['agenticow', 'memory', 'cow', 'status'],
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
properties: {
|
|
314
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
315
|
+
},
|
|
316
|
+
required: ['path'],
|
|
317
|
+
},
|
|
318
|
+
handler: async (input) => {
|
|
319
|
+
const api = await loadAgenticow();
|
|
320
|
+
if (!api)
|
|
321
|
+
return degradedResult('agenticow-not-found');
|
|
322
|
+
const path = resolveMemoryPath(String(input.path));
|
|
323
|
+
const mem = await openWithLineage(api, path);
|
|
324
|
+
try {
|
|
325
|
+
const status = await mem.status();
|
|
326
|
+
return { success: true, path, status };
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
await mem.close?.();
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
},
|
|
155
333
|
{
|
|
156
334
|
name: 'agenticow_checkpoint',
|
|
157
335
|
description: 'agenticow — freeze a labelled restore point on an .rvf memory file. Subsequent edits stay in a fresh COW child; rollback returns here. Use when you are about to run an experimental Darwin tick or speculative agent edit that may need to be discarded. Relying on the working node alone is wrong because there is no "undo last N writes" semantics — without a checkpoint, a bad ingest contaminates the base. Persists via .agenticow.json lineage manifest so it survives close+reopen.',
|
|
@@ -191,6 +369,7 @@ export const agenticowTools = [
|
|
|
191
369
|
type: 'object',
|
|
192
370
|
properties: {
|
|
193
371
|
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
372
|
+
checkpointId: { type: 'string', description: 'Target checkpoint id from agenticow_lineage (omit to roll back to the most recent checkpoint)' },
|
|
194
373
|
},
|
|
195
374
|
required: ['path'],
|
|
196
375
|
},
|
|
@@ -199,9 +378,10 @@ export const agenticowTools = [
|
|
|
199
378
|
if (!api)
|
|
200
379
|
return degradedResult('agenticow-not-found');
|
|
201
380
|
const path = resolveMemoryPath(String(input.path));
|
|
381
|
+
const checkpointId = input.checkpointId ? String(input.checkpointId) : undefined;
|
|
202
382
|
const mem = await openWithLineage(api, path);
|
|
203
383
|
try {
|
|
204
|
-
const r = await mem.rollback();
|
|
384
|
+
const r = checkpointId ? await mem.rollback(checkpointId) : await mem.rollback();
|
|
205
385
|
await mem.save?.(manifestFor(path));
|
|
206
386
|
return { success: true, path, rolledBack: true, result: r };
|
|
207
387
|
}
|
|
@@ -18,7 +18,7 @@ export { DiffClassifier, createDiffClassifier, analyzeDiff, analyzeDiffSync, ass
|
|
|
18
18
|
export { CoverageRouter, createCoverageRouter, coverageRoute, coverageSuggest, coverageGaps, clearCoverageCache, getCoverageCacheStats, type CoverageRouterConfig, type FileCoverage, type CoverageReport, type CoverageRouteResult, type CoverageSuggestResult, type CoverageGapsResult, type CoverageRouteOptions, type CoverageSuggestOptions, type CoverageGapsOptions, } from './coverage-router.js';
|
|
19
19
|
export { coverageRouterTools, hooksCoverageRoute, hooksCoverageSuggest, hooksCoverageGaps } from './coverage-tools.js';
|
|
20
20
|
export { buildDependencyGraph, analyzeGraph, analyzeMinCutBoundaries, analyzeModuleCommunities, detectCircularDependencies, exportToDot, loadRuVector, fallbackMinCut, fallbackLouvain, clearGraphCaches, getGraphCacheStats, type GraphNode, type GraphEdge, type DependencyGraph, type MinCutBoundary, type ModuleCommunity, type CircularDependency, type GraphAnalysisResult, } from './graph-analyzer.js';
|
|
21
|
-
export { LoRAAdapter, getLoRAAdapter, resetLoRAAdapter, createLoRAAdapter, adaptEmbedding, trainLoRA, getLoRAStats, DEFAULT_RANK, DEFAULT_ALPHA, INPUT_DIM as LORA_INPUT_DIM, OUTPUT_DIM as LORA_OUTPUT_DIM, type LoRAConfig, type LoRAWeights, type AdaptationResult, type LoRAStats, } from './lora-adapter.js';
|
|
21
|
+
export { LoRAAdapter, getLoRAAdapter, resetLoRAAdapter, createLoRAAdapter, adaptEmbedding, trainLoRA, getLoRAStats, loadLatestCheckpoint, latestCheckpointInfo, formatCheckpointAge, DEFAULT_RANK, DEFAULT_ALPHA, INPUT_DIM as LORA_INPUT_DIM, OUTPUT_DIM as LORA_OUTPUT_DIM, type LoRAConfig, type LoRAWeights, type AdaptationResult, type LoRAStats, type CheckpointInfo, } from './lora-adapter.js';
|
|
22
22
|
export { ModelRouter, getModelRouter, resetModelRouter, createModelRouter, routeToModel, routeToModelFull, analyzeTaskComplexity, getModelRouterStats, recordModelOutcome, MODEL_CAPABILITIES, COMPLEXITY_INDICATORS, type ClaudeModel, type ModelRouterConfig, type ModelRoutingResult, type ComplexityAnalysis, } from './model-router.js';
|
|
23
23
|
export { SemanticRouter, createSemanticRouter, type Intent, type RouteResult, type RouterConfig, } from './semantic-router.js';
|
|
24
24
|
export { isRuvllmWasmAvailable, initRuvllmWasm, getRuvllmStatus, createHnswRouter, createSonaInstant, createMicroLora, formatChat, createKvCache, createGenerateConfig, createBufferPool, createInferenceArena, HNSW_MAX_SAFE_PATTERNS, type HnswRouterConfig, type HnswPattern, type HnswRouteResult, type SonaConfig, type MicroLoraConfig, type ChatMessage, type GenerateOptions, type RuvllmStatus, } from './ruvllm-wasm.js';
|
|
@@ -36,7 +36,7 @@ clearGraphCaches, getGraphCacheStats, } from './graph-analyzer.js';
|
|
|
36
36
|
// consumers (hooks-tools.ts, neural-tools.ts) import from '@claude-flow/neural'
|
|
37
37
|
// explicitly; re-exporting through this barrel pulls the package's
|
|
38
38
|
// transitive @ruvector/sona dep into vitest's eager resolution.
|
|
39
|
-
export { LoRAAdapter, getLoRAAdapter, resetLoRAAdapter, createLoRAAdapter, adaptEmbedding, trainLoRA, getLoRAStats, DEFAULT_RANK, DEFAULT_ALPHA, INPUT_DIM as LORA_INPUT_DIM, OUTPUT_DIM as LORA_OUTPUT_DIM, } from './lora-adapter.js';
|
|
39
|
+
export { LoRAAdapter, getLoRAAdapter, resetLoRAAdapter, createLoRAAdapter, adaptEmbedding, trainLoRA, getLoRAStats, loadLatestCheckpoint, latestCheckpointInfo, formatCheckpointAge, DEFAULT_RANK, DEFAULT_ALPHA, INPUT_DIM as LORA_INPUT_DIM, OUTPUT_DIM as LORA_OUTPUT_DIM, } from './lora-adapter.js';
|
|
40
40
|
export { ModelRouter, getModelRouter, resetModelRouter, createModelRouter, routeToModel, routeToModelFull, analyzeTaskComplexity, getModelRouterStats, recordModelOutcome, MODEL_CAPABILITIES, COMPLEXITY_INDICATORS, } from './model-router.js';
|
|
41
41
|
export { SemanticRouter, createSemanticRouter, } from './semantic-router.js';
|
|
42
42
|
// ── RuVector LLM WASM (inference utilities) ─────────────────
|
|
@@ -213,6 +213,41 @@ export declare class LoRAAdapter {
|
|
|
213
213
|
scaling: number;
|
|
214
214
|
}): boolean;
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Info about the newest on-disk training checkpoint.
|
|
218
|
+
*/
|
|
219
|
+
export interface CheckpointInfo {
|
|
220
|
+
/** Absolute path to the checkpoint file */
|
|
221
|
+
path: string;
|
|
222
|
+
/** Basename, e.g. lora-checkpoint-1712345678901.json */
|
|
223
|
+
filename: string;
|
|
224
|
+
/** Age in ms derived from the filename timestamp (falls back to mtime) */
|
|
225
|
+
ageMs: number;
|
|
226
|
+
/** Human-friendly age, e.g. "2h ago" */
|
|
227
|
+
ageLabel: string;
|
|
228
|
+
}
|
|
229
|
+
/** Compact relative-age label. Most-significant unit only. */
|
|
230
|
+
export declare function formatCheckpointAge(ms: number): string;
|
|
231
|
+
/**
|
|
232
|
+
* Find the newest `lora-checkpoint-<ts>.json` without loading it. Pure disk
|
|
233
|
+
* scan — safe to call from read-only status surfaces. Returns null when no
|
|
234
|
+
* checkpoint exists or the directory is unreadable.
|
|
235
|
+
*/
|
|
236
|
+
export declare function latestCheckpointInfo(): CheckpointInfo | null;
|
|
237
|
+
/**
|
|
238
|
+
* Flywheel entry point: locate the newest trained checkpoint and load it into
|
|
239
|
+
* `adapter` via the adapter's existing loadCheckpoint path, so routing /
|
|
240
|
+
* adaptation benefits from prior `neural train` runs.
|
|
241
|
+
*
|
|
242
|
+
* Contract: lazy (call on first adaptation use, never at CLI startup),
|
|
243
|
+
* non-fatal on any failure, and kill-switchable via
|
|
244
|
+
* CLAUDE_FLOW_NO_CHECKPOINT_AUTOLOAD=1.
|
|
245
|
+
*/
|
|
246
|
+
export declare function loadLatestCheckpoint(adapter: LoRAAdapter): Promise<{
|
|
247
|
+
loaded: boolean;
|
|
248
|
+
path?: string;
|
|
249
|
+
ageMs?: number;
|
|
250
|
+
}>;
|
|
216
251
|
/**
|
|
217
252
|
* Get or create singleton LoRA adapter instance
|
|
218
253
|
*/
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* @module lora-adapter
|
|
19
19
|
*/
|
|
20
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
20
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
21
21
|
import { dirname, join } from 'path';
|
|
22
22
|
import { createRequire } from 'module';
|
|
23
23
|
// ============================================================================
|
|
@@ -527,6 +527,105 @@ export class LoRAAdapter {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Directories `neural train` writes checkpoints to. It only ever writes to
|
|
532
|
+
* `<cwd>/.claude-flow/neural` (see commands/neural.ts + services/native-training.ts),
|
|
533
|
+
* so that is the single source of truth — kept in a helper so the discovery
|
|
534
|
+
* and status surfaces stay consistent.
|
|
535
|
+
*/
|
|
536
|
+
function checkpointDirs() {
|
|
537
|
+
return [join(process.cwd(), '.claude-flow', 'neural')];
|
|
538
|
+
}
|
|
539
|
+
/** Compact relative-age label. Most-significant unit only. */
|
|
540
|
+
export function formatCheckpointAge(ms) {
|
|
541
|
+
const s = Math.floor(ms / 1000);
|
|
542
|
+
if (s < 60)
|
|
543
|
+
return `${s}s ago`;
|
|
544
|
+
const m = Math.floor(s / 60);
|
|
545
|
+
if (m < 60)
|
|
546
|
+
return `${m}m ago`;
|
|
547
|
+
const h = Math.floor(m / 60);
|
|
548
|
+
if (h < 24)
|
|
549
|
+
return `${h}h ago`;
|
|
550
|
+
const d = Math.floor(h / 24);
|
|
551
|
+
return `${d}d ago`;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Find the newest `lora-checkpoint-<ts>.json` without loading it. Pure disk
|
|
555
|
+
* scan — safe to call from read-only status surfaces. Returns null when no
|
|
556
|
+
* checkpoint exists or the directory is unreadable.
|
|
557
|
+
*/
|
|
558
|
+
export function latestCheckpointInfo() {
|
|
559
|
+
try {
|
|
560
|
+
let best = null;
|
|
561
|
+
for (const dir of checkpointDirs()) {
|
|
562
|
+
if (!existsSync(dir))
|
|
563
|
+
continue;
|
|
564
|
+
for (const f of readdirSync(dir)) {
|
|
565
|
+
const match = /^lora-checkpoint-(\d+)\.json$/.exec(f);
|
|
566
|
+
if (!match)
|
|
567
|
+
continue;
|
|
568
|
+
const ts = Number(match[1]);
|
|
569
|
+
if (!Number.isFinite(ts))
|
|
570
|
+
continue;
|
|
571
|
+
if (!best || ts > best.ts)
|
|
572
|
+
best = { path: join(dir, f), filename: f, ts };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (!best)
|
|
576
|
+
return null;
|
|
577
|
+
const ageMs = Math.max(0, Date.now() - best.ts);
|
|
578
|
+
return { path: best.path, filename: best.filename, ageMs, ageLabel: formatCheckpointAge(ageMs) };
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Validate a file looks like a ruvllm training checkpoint before loading it,
|
|
586
|
+
* so a stray/corrupt JSON in the neural dir can't poison routing weights.
|
|
587
|
+
* Accepts both the JS-fallback envelope ({A,B,scaling}) and native ruvllm
|
|
588
|
+
* checkpoint shapes (weights/adapter/epoch/step/loss/version/index markers).
|
|
589
|
+
*/
|
|
590
|
+
function isCheckpointEnvelope(path) {
|
|
591
|
+
try {
|
|
592
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
593
|
+
if (!data || typeof data !== 'object')
|
|
594
|
+
return false;
|
|
595
|
+
const obj = data;
|
|
596
|
+
if (Array.isArray(obj.A) && Array.isArray(obj.B))
|
|
597
|
+
return true;
|
|
598
|
+
return ['weights', 'adapter', 'epoch', 'step', 'loss', 'version', 'index'].some((k) => k in obj);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Flywheel entry point: locate the newest trained checkpoint and load it into
|
|
606
|
+
* `adapter` via the adapter's existing loadCheckpoint path, so routing /
|
|
607
|
+
* adaptation benefits from prior `neural train` runs.
|
|
608
|
+
*
|
|
609
|
+
* Contract: lazy (call on first adaptation use, never at CLI startup),
|
|
610
|
+
* non-fatal on any failure, and kill-switchable via
|
|
611
|
+
* CLAUDE_FLOW_NO_CHECKPOINT_AUTOLOAD=1.
|
|
612
|
+
*/
|
|
613
|
+
export async function loadLatestCheckpoint(adapter) {
|
|
614
|
+
if (process.env.CLAUDE_FLOW_NO_CHECKPOINT_AUTOLOAD === '1')
|
|
615
|
+
return { loaded: false };
|
|
616
|
+
try {
|
|
617
|
+
const info = latestCheckpointInfo();
|
|
618
|
+
if (!info)
|
|
619
|
+
return { loaded: false };
|
|
620
|
+
if (!isCheckpointEnvelope(info.path))
|
|
621
|
+
return { loaded: false, path: info.path, ageMs: info.ageMs };
|
|
622
|
+
const loaded = await adapter.loadCheckpoint(info.path);
|
|
623
|
+
return { loaded, path: info.path, ageMs: info.ageMs };
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return { loaded: false };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
530
629
|
// ============================================================================
|
|
531
630
|
// Singleton & Factory Functions
|
|
532
631
|
// ============================================================================
|
|
@@ -545,6 +644,14 @@ export async function getLoRAAdapter() {
|
|
|
545
644
|
initPromise = (async () => {
|
|
546
645
|
const adapter = new LoRAAdapter();
|
|
547
646
|
await adapter.initialize();
|
|
647
|
+
// Flywheel: fold in the newest trained checkpoint so routing/adaptation
|
|
648
|
+
// benefits from prior `neural train` runs. This singleton is constructed
|
|
649
|
+
// lazily on first adaptation use (NOT at CLI startup), so the checkpoint
|
|
650
|
+
// scan + ruvllm load stay off the hot `--help` path. Non-fatal.
|
|
651
|
+
try {
|
|
652
|
+
await loadLatestCheckpoint(adapter);
|
|
653
|
+
}
|
|
654
|
+
catch { /* auto-load is best-effort — never block adaptation */ }
|
|
548
655
|
loraInstance = adapter;
|
|
549
656
|
return adapter;
|
|
550
657
|
})();
|
|
@@ -25,6 +25,14 @@ export interface NativeTrainingResult {
|
|
|
25
25
|
earlyStopped: boolean;
|
|
26
26
|
checkpointPath?: string;
|
|
27
27
|
checkpointBytes?: number;
|
|
28
|
+
/** True when training resumed from a --resume checkpoint. */
|
|
29
|
+
resumed?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* How the resume happened: 'resumeFrom' (ruvllm >=2.6.0 — restores epoch
|
|
32
|
+
* position + optimizer state) or 'loadCheckpoint' (2.5.7 fallback —
|
|
33
|
+
* weights only, training restarts from epoch 0).
|
|
34
|
+
*/
|
|
35
|
+
resumeMode?: 'resumeFrom' | 'loadCheckpoint';
|
|
28
36
|
}
|
|
29
37
|
export interface NativeTrainingOptions {
|
|
30
38
|
embeddings: Float32Array[];
|
|
@@ -32,9 +40,29 @@ export interface NativeTrainingOptions {
|
|
|
32
40
|
batchSize: number;
|
|
33
41
|
learningRate: number;
|
|
34
42
|
dim: number;
|
|
43
|
+
/**
|
|
44
|
+
* Validation holdout fraction (0..1). >0 makes the pipeline report
|
|
45
|
+
* bestValLoss + early-stopping; 0 (or omitted) disables validation.
|
|
46
|
+
* validationSplit exists in ruvllm 2.5.7 — no version gate needed.
|
|
47
|
+
*/
|
|
48
|
+
validationSplit?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Resume weights (and, on >=2.6.0, epoch position) from this checkpoint
|
|
51
|
+
* BEFORE training. A missing/invalid path throws ResumeFailedError — an
|
|
52
|
+
* explicit --resume must fail loudly, never silently fresh-train.
|
|
53
|
+
*/
|
|
54
|
+
resumeFrom?: string;
|
|
35
55
|
/** When set, the TRAINED pipeline checkpoints here (ruvllm >=2.5.7). */
|
|
36
56
|
checkpointPath?: string;
|
|
37
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Thrown when an explicit --resume checkpoint cannot be found or loaded.
|
|
60
|
+
* Distinct from the generic "native unavailable → null" degradation so the
|
|
61
|
+
* caller can surface a loud, exit-1 failure rather than fresh training.
|
|
62
|
+
*/
|
|
63
|
+
export declare class ResumeFailedError extends Error {
|
|
64
|
+
constructor(message: string);
|
|
65
|
+
}
|
|
38
66
|
export declare function nativeTrainingAvailable(): boolean;
|
|
39
67
|
export declare function runNativeTraining(opts: NativeTrainingOptions): Promise<NativeTrainingResult | null>;
|
|
40
68
|
//# sourceMappingURL=native-training.d.ts.map
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
import { createRequire } from 'module';
|
|
20
20
|
import { mkdirSync, existsSync } from 'fs';
|
|
21
21
|
import { dirname } from 'path';
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when an explicit --resume checkpoint cannot be found or loaded.
|
|
24
|
+
* Distinct from the generic "native unavailable → null" degradation so the
|
|
25
|
+
* caller can surface a loud, exit-1 failure rather than fresh training.
|
|
26
|
+
*/
|
|
27
|
+
export class ResumeFailedError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'ResumeFailedError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
22
33
|
export function nativeTrainingAvailable() {
|
|
23
34
|
try {
|
|
24
35
|
createRequire(import.meta.url).resolve('@ruvector/ruvllm');
|
|
@@ -29,19 +40,53 @@ export function nativeTrainingAvailable() {
|
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
42
|
export async function runNativeTraining(opts) {
|
|
32
|
-
const { embeddings, epochs, batchSize, learningRate, dim, checkpointPath } = opts;
|
|
43
|
+
const { embeddings, epochs, batchSize, learningRate, dim, validationSplit, resumeFrom, checkpointPath } = opts;
|
|
33
44
|
if (embeddings.length < 2)
|
|
34
45
|
return null;
|
|
35
46
|
try {
|
|
36
47
|
const req = createRequire(import.meta.url);
|
|
37
48
|
const ruvllm = req('@ruvector/ruvllm');
|
|
38
|
-
const
|
|
49
|
+
const pipelineConfig = {
|
|
39
50
|
learningRate,
|
|
40
51
|
batchSize,
|
|
41
52
|
epochs,
|
|
42
53
|
inputDim: dim,
|
|
43
54
|
outputDim: dim,
|
|
44
|
-
}
|
|
55
|
+
};
|
|
56
|
+
// 0 (or omitted) disables validation; only pass a real holdout through.
|
|
57
|
+
if (typeof validationSplit === 'number' && validationSplit > 0) {
|
|
58
|
+
pipelineConfig.validationSplit = validationSplit;
|
|
59
|
+
}
|
|
60
|
+
const pipeline = new ruvllm.TrainingPipeline(pipelineConfig);
|
|
61
|
+
// Resume BEFORE training. Prefer resumeFrom() (2.6.0 — epoch position +
|
|
62
|
+
// optimizer state); fall back to loadCheckpoint() (2.5.7 — weights only).
|
|
63
|
+
// Any failure with an explicit --resume is loud (ResumeFailedError),
|
|
64
|
+
// never silent fresh training.
|
|
65
|
+
let resumed = false;
|
|
66
|
+
let resumeMode;
|
|
67
|
+
if (resumeFrom) {
|
|
68
|
+
if (!existsSync(resumeFrom)) {
|
|
69
|
+
throw new ResumeFailedError(`--resume checkpoint not found: ${resumeFrom}`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
if (typeof pipeline.resumeFrom === 'function') {
|
|
73
|
+
pipeline.resumeFrom(resumeFrom);
|
|
74
|
+
resumeMode = 'resumeFrom';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const ok = pipeline.loadCheckpoint(resumeFrom);
|
|
78
|
+
if (ok === false)
|
|
79
|
+
throw new Error('loadCheckpoint returned false');
|
|
80
|
+
resumeMode = 'loadCheckpoint';
|
|
81
|
+
}
|
|
82
|
+
resumed = true;
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
if (e instanceof ResumeFailedError)
|
|
86
|
+
throw e;
|
|
87
|
+
throw new ResumeFailedError(`--resume failed to load checkpoint ${resumeFrom}: ${e.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
45
90
|
// Pattern-alignment pairs, chunked into pipeline batches.
|
|
46
91
|
const inputs = [];
|
|
47
92
|
const targets = [];
|
|
@@ -55,13 +100,21 @@ export async function runNativeTraining(opts) {
|
|
|
55
100
|
pipeline.addBatch(inputs.slice(i, i + batchSize), targets.slice(i, i + batchSize), qualities.slice(i, i + batchSize));
|
|
56
101
|
}
|
|
57
102
|
const r = pipeline.train();
|
|
103
|
+
// When validation is disabled the pipeline reports Infinity for
|
|
104
|
+
// bestValLoss — normalize any non-finite value to null so downstream
|
|
105
|
+
// "bestValLoss !== null ⇒ validation ran" holds for the results table.
|
|
106
|
+
const bestValLoss = typeof r.bestValLoss === 'number' && Number.isFinite(r.bestValLoss)
|
|
107
|
+
? r.bestValLoss
|
|
108
|
+
: null;
|
|
58
109
|
const result = {
|
|
59
110
|
epochs: r.epochs,
|
|
60
111
|
steps: r.steps,
|
|
61
112
|
finalLoss: r.finalLoss,
|
|
62
|
-
bestValLoss
|
|
113
|
+
bestValLoss,
|
|
63
114
|
durationMs: r.durationMs,
|
|
64
115
|
earlyStopped: !!r.earlyStopped,
|
|
116
|
+
resumed,
|
|
117
|
+
resumeMode,
|
|
65
118
|
};
|
|
66
119
|
if (checkpointPath) {
|
|
67
120
|
try {
|
|
@@ -77,7 +130,11 @@ export async function runNativeTraining(opts) {
|
|
|
77
130
|
}
|
|
78
131
|
return result;
|
|
79
132
|
}
|
|
80
|
-
catch {
|
|
133
|
+
catch (err) {
|
|
134
|
+
// An explicit --resume failure must propagate as a loud error; every
|
|
135
|
+
// other failure degrades to null so callers fall back to the WASM path.
|
|
136
|
+
if (err instanceof ResumeFailedError)
|
|
137
|
+
throw err;
|
|
81
138
|
return null;
|
|
82
139
|
}
|
|
83
140
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|