moflo 4.8.1 → 4.8.2
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/README.md +15 -2
- package/package.json +2 -2
- package/src/@claude-flow/cli/dist/src/commands/diagnose.d.ts +16 -0
- package/src/@claude-flow/cli/dist/src/commands/diagnose.js +503 -0
- package/src/@claude-flow/cli/dist/src/commands/index.d.ts +1 -0
- package/src/@claude-flow/cli/dist/src/commands/index.js +7 -0
- package/src/@claude-flow/cli/dist/src/commands/memory.js +165 -2
- package/src/@claude-flow/cli/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,11 @@ npx flo memory code-map # Index your code structure
|
|
|
124
124
|
npx flo doctor # Verify everything works
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Both indexes run automatically at session start after this, so you only need to run them manually on first setup or after major structural changes.
|
|
127
|
+
Both indexes run automatically at session start after this, so you only need to run them manually on first setup or after major structural changes. To reindex everything at once:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx flo memory refresh # Reindex all content, rebuild embeddings, cleanup, vacuum
|
|
131
|
+
```
|
|
128
132
|
|
|
129
133
|
## The `/flo` Skill
|
|
130
134
|
|
|
@@ -217,6 +221,7 @@ flo memory search -q "auth patterns" # Semantic search
|
|
|
217
221
|
flo memory index-guidance # Index guidance docs
|
|
218
222
|
flo memory code-map # Index code structure
|
|
219
223
|
flo memory rebuild-index # Regenerate all embeddings
|
|
224
|
+
flo memory refresh # Reindex all + rebuild + cleanup + vacuum
|
|
220
225
|
flo memory stats # Show statistics
|
|
221
226
|
```
|
|
222
227
|
|
|
@@ -238,11 +243,19 @@ flo gate prompt-reminder # Context bracket tracking
|
|
|
238
243
|
flo gate session-reset # Reset workflow state
|
|
239
244
|
```
|
|
240
245
|
|
|
246
|
+
### Diagnostics
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
flo doctor # Quick health check (environment, deps, config)
|
|
250
|
+
flo diagnose # Full integration test (memory, swarm, hive, hooks, neural)
|
|
251
|
+
flo diagnose --suite memory # Run only memory tests
|
|
252
|
+
flo diagnose --json # JSON output for CI/automation
|
|
253
|
+
```
|
|
254
|
+
|
|
241
255
|
### System
|
|
242
256
|
|
|
243
257
|
```bash
|
|
244
258
|
flo init # Initialize project (one-time setup)
|
|
245
|
-
flo doctor # Health check
|
|
246
259
|
flo --version # Show version
|
|
247
260
|
```
|
|
248
261
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.2",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@types/bcrypt": "^5.0.2",
|
|
84
84
|
"@types/node": "^20.19.37",
|
|
85
85
|
"eslint": "^8.0.0",
|
|
86
|
-
"moflo": "^4.8.
|
|
86
|
+
"moflo": "^4.8.1",
|
|
87
87
|
"tsx": "^4.21.0",
|
|
88
88
|
"typescript": "^5.9.3",
|
|
89
89
|
"vitest": "^4.0.0"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 CLI Diagnose Command
|
|
3
|
+
* Full integration test suite — runs non-destructively in the destination project.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `doctor` (which checks environment health), `diagnose` exercises
|
|
6
|
+
* every subsystem end-to-end: memory CRUD, swarm lifecycle, hive-mind,
|
|
7
|
+
* task management, hooks, config, neural, and init idempotency.
|
|
8
|
+
*
|
|
9
|
+
* All test data is cleaned up after each test — no code or state is left behind.
|
|
10
|
+
*
|
|
11
|
+
* Created with motailz.com
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from '../types.js';
|
|
14
|
+
export declare const diagnoseCommand: Command;
|
|
15
|
+
export default diagnoseCommand;
|
|
16
|
+
//# sourceMappingURL=diagnose.d.ts.map
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 CLI Diagnose Command
|
|
3
|
+
* Full integration test suite — runs non-destructively in the destination project.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `doctor` (which checks environment health), `diagnose` exercises
|
|
6
|
+
* every subsystem end-to-end: memory CRUD, swarm lifecycle, hive-mind,
|
|
7
|
+
* task management, hooks, config, neural, and init idempotency.
|
|
8
|
+
*
|
|
9
|
+
* All test data is cleaned up after each test — no code or state is left behind.
|
|
10
|
+
*
|
|
11
|
+
* Created with motailz.com
|
|
12
|
+
*/
|
|
13
|
+
import { output } from '../output.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Helpers
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const DIAG_NAMESPACE = '__moflo_diagnose__';
|
|
18
|
+
const DIAG_KEY = '__diag_test_entry__';
|
|
19
|
+
async function timed(name, fn) {
|
|
20
|
+
const t0 = performance.now();
|
|
21
|
+
try {
|
|
22
|
+
const r = await fn();
|
|
23
|
+
return { name, ...r, duration: performance.now() - t0 };
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return { name, status: 'fail', message: err instanceof Error ? err.message : String(err), duration: performance.now() - t0 };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Lazy-load memory functions to avoid pulling in WASM at import time.
|
|
31
|
+
*/
|
|
32
|
+
async function getMemFns() {
|
|
33
|
+
const { storeEntry, searchEntries, listEntries, getEntry, deleteEntry, initializeMemoryDatabase, checkMemoryInitialization, } = await import('../memory/memory-initializer.js');
|
|
34
|
+
return { storeEntry, searchEntries, listEntries, getEntry, deleteEntry, initializeMemoryDatabase, checkMemoryInitialization };
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Individual diagnostic tests
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
function testMemoryInit() {
|
|
40
|
+
return () => timed('Memory Init', async () => {
|
|
41
|
+
const { initializeMemoryDatabase, checkMemoryInitialization } = await getMemFns();
|
|
42
|
+
const status = await checkMemoryInitialization();
|
|
43
|
+
if (!status.initialized) {
|
|
44
|
+
await initializeMemoryDatabase({ force: false, verbose: false });
|
|
45
|
+
const recheck = await checkMemoryInitialization();
|
|
46
|
+
if (!recheck.initialized)
|
|
47
|
+
return { status: 'fail', message: 'Could not initialize memory database' };
|
|
48
|
+
}
|
|
49
|
+
return { status: 'pass', message: `Initialized (v${status.version || '3.0.0'})` };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function testMemoryStore() {
|
|
53
|
+
return () => timed('Memory Store', async () => {
|
|
54
|
+
const { storeEntry, deleteEntry } = await getMemFns();
|
|
55
|
+
// Clean up any leftover from a previous run
|
|
56
|
+
try {
|
|
57
|
+
await deleteEntry({ key: DIAG_KEY, namespace: DIAG_NAMESPACE });
|
|
58
|
+
}
|
|
59
|
+
catch { /* ignore */ }
|
|
60
|
+
const result = await storeEntry({
|
|
61
|
+
key: DIAG_KEY,
|
|
62
|
+
value: 'diagnose test value — safe to delete',
|
|
63
|
+
namespace: DIAG_NAMESPACE,
|
|
64
|
+
generateEmbeddingFlag: true,
|
|
65
|
+
tags: ['diagnose'],
|
|
66
|
+
upsert: true,
|
|
67
|
+
});
|
|
68
|
+
if (!result.success)
|
|
69
|
+
return { status: 'fail', message: result.error || 'store failed' };
|
|
70
|
+
const dims = result.embedding?.dimensions;
|
|
71
|
+
return { status: 'pass', message: `Stored with ${dims ?? 0}-dim vector` };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function testMemoryRetrieve() {
|
|
75
|
+
return () => timed('Memory Retrieve', async () => {
|
|
76
|
+
const { getEntry } = await getMemFns();
|
|
77
|
+
const result = await getEntry({ key: DIAG_KEY, namespace: DIAG_NAMESPACE });
|
|
78
|
+
if (!result.found)
|
|
79
|
+
return { status: 'fail', message: 'Entry not found after store' };
|
|
80
|
+
if (!result.entry?.content?.includes('diagnose test value'))
|
|
81
|
+
return { status: 'fail', message: 'Content mismatch' };
|
|
82
|
+
return { status: 'pass', message: 'Retrieved and verified' };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function testMemorySearch() {
|
|
86
|
+
return () => timed('Memory Search', async () => {
|
|
87
|
+
const { searchEntries } = await getMemFns();
|
|
88
|
+
const result = await searchEntries({
|
|
89
|
+
query: 'diagnose test value safe delete',
|
|
90
|
+
namespace: DIAG_NAMESPACE,
|
|
91
|
+
limit: 5,
|
|
92
|
+
threshold: 0.1,
|
|
93
|
+
});
|
|
94
|
+
if (!result.results || result.results.length === 0)
|
|
95
|
+
return { status: 'fail', message: 'No search results returned' };
|
|
96
|
+
const top = result.results[0];
|
|
97
|
+
return { status: 'pass', message: `Top hit: ${top.key} (score ${top.score.toFixed(2)})` };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function testMemoryList() {
|
|
101
|
+
return () => timed('Memory List', async () => {
|
|
102
|
+
const { listEntries } = await getMemFns();
|
|
103
|
+
const result = await listEntries({ namespace: DIAG_NAMESPACE, limit: 50 });
|
|
104
|
+
if (result.total === 0)
|
|
105
|
+
return { status: 'fail', message: 'No entries in diagnose namespace' };
|
|
106
|
+
return { status: 'pass', message: `${result.total} entries found` };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function testMemoryDelete() {
|
|
110
|
+
return () => timed('Memory Delete', async () => {
|
|
111
|
+
const { deleteEntry, listEntries } = await getMemFns();
|
|
112
|
+
const result = await deleteEntry({ key: DIAG_KEY, namespace: DIAG_NAMESPACE });
|
|
113
|
+
if (!result.deleted)
|
|
114
|
+
return { status: 'fail', message: 'Delete returned false' };
|
|
115
|
+
// Verify it's gone
|
|
116
|
+
const list = await listEntries({ namespace: DIAG_NAMESPACE, limit: 50 });
|
|
117
|
+
if (list.total !== 0)
|
|
118
|
+
return { status: 'fail', message: `${list.total} entries still remain after delete` };
|
|
119
|
+
return { status: 'pass', message: 'Deleted and verified' };
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function testSwarmLifecycle() {
|
|
123
|
+
return () => timed('Swarm Lifecycle', async () => {
|
|
124
|
+
// Use dynamic import to the MCP tools which contain the swarm logic
|
|
125
|
+
let swarmId;
|
|
126
|
+
try {
|
|
127
|
+
const { swarmTools } = await import('../mcp-tools/swarm-tools.js');
|
|
128
|
+
const tools = swarmTools;
|
|
129
|
+
const initTool = tools.find(t => t.name === 'swarm_init');
|
|
130
|
+
if (!initTool)
|
|
131
|
+
return { status: 'skip', message: 'swarm_init tool not found' };
|
|
132
|
+
const initResult = await initTool.handler({
|
|
133
|
+
topology: 'hierarchical',
|
|
134
|
+
maxAgents: 4,
|
|
135
|
+
strategy: 'specialized',
|
|
136
|
+
});
|
|
137
|
+
swarmId = initResult?.swarmId;
|
|
138
|
+
if (!swarmId)
|
|
139
|
+
return { status: 'fail', message: 'No swarm ID returned' };
|
|
140
|
+
// Spawn an agent
|
|
141
|
+
const spawnTool = tools.find(t => t.name === 'agent_spawn');
|
|
142
|
+
if (spawnTool) {
|
|
143
|
+
await spawnTool.handler({ type: 'coder', name: '__diag_agent__' });
|
|
144
|
+
}
|
|
145
|
+
// Status
|
|
146
|
+
const statusTool = tools.find(t => t.name === 'swarm_status');
|
|
147
|
+
if (statusTool) {
|
|
148
|
+
await statusTool.handler({});
|
|
149
|
+
}
|
|
150
|
+
// Stop the agent and swarm
|
|
151
|
+
const agentListTool = tools.find(t => t.name === 'agent_list');
|
|
152
|
+
if (agentListTool) {
|
|
153
|
+
const agents = await agentListTool.handler({});
|
|
154
|
+
const agentList = (agents?.agents ?? []);
|
|
155
|
+
const stopAgentTool = tools.find(t => t.name === 'agent_stop');
|
|
156
|
+
if (stopAgentTool) {
|
|
157
|
+
for (const a of agentList) {
|
|
158
|
+
if (a.id)
|
|
159
|
+
await stopAgentTool.handler({ agentId: a.id });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { status: 'pass', message: `Swarm ${swarmId} — init/spawn/status/stop OK` };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function testHiveMindLifecycle() {
|
|
171
|
+
return () => timed('Hive-Mind Lifecycle', async () => {
|
|
172
|
+
try {
|
|
173
|
+
const { hiveMindTools } = await import('../mcp-tools/hive-mind-tools.js');
|
|
174
|
+
const tools = hiveMindTools;
|
|
175
|
+
const initTool = tools.find(t => t.name === 'hive-mind_init');
|
|
176
|
+
if (!initTool)
|
|
177
|
+
return { status: 'skip', message: 'hive-mind_init tool not found' };
|
|
178
|
+
const initResult = await initTool.handler({
|
|
179
|
+
topology: 'hierarchical-mesh',
|
|
180
|
+
consensus: 'raft',
|
|
181
|
+
maxAgents: 4,
|
|
182
|
+
});
|
|
183
|
+
const hiveId = initResult?.hiveId;
|
|
184
|
+
if (!hiveId)
|
|
185
|
+
return { status: 'fail', message: 'No hive ID returned' };
|
|
186
|
+
// Spawn a worker
|
|
187
|
+
const spawnTool = tools.find(t => t.name === 'hive-mind_spawn');
|
|
188
|
+
if (spawnTool) {
|
|
189
|
+
await spawnTool.handler({ role: 'worker', name: '__diag_worker__' });
|
|
190
|
+
}
|
|
191
|
+
// Status
|
|
192
|
+
const statusTool = tools.find(t => t.name === 'hive-mind_status');
|
|
193
|
+
if (statusTool) {
|
|
194
|
+
await statusTool.handler({});
|
|
195
|
+
}
|
|
196
|
+
// Shutdown
|
|
197
|
+
const shutdownTool = tools.find(t => t.name === 'hive-mind_shutdown');
|
|
198
|
+
if (shutdownTool) {
|
|
199
|
+
await shutdownTool.handler({});
|
|
200
|
+
}
|
|
201
|
+
return { status: 'pass', message: `Hive ${hiveId} — init/spawn/status/shutdown OK` };
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function testTaskLifecycle() {
|
|
209
|
+
return () => timed('Task Lifecycle', async () => {
|
|
210
|
+
try {
|
|
211
|
+
const { taskTools } = await import('../mcp-tools/task-tools.js');
|
|
212
|
+
const tools = taskTools;
|
|
213
|
+
const createTool = tools.find(t => t.name === 'task_create');
|
|
214
|
+
if (!createTool)
|
|
215
|
+
return { status: 'skip', message: 'task_create tool not found' };
|
|
216
|
+
const createResult = await createTool.handler({
|
|
217
|
+
type: 'implementation',
|
|
218
|
+
description: '__moflo_diagnose__ test task — safe to delete',
|
|
219
|
+
});
|
|
220
|
+
const taskId = createResult?.taskId;
|
|
221
|
+
if (!taskId)
|
|
222
|
+
return { status: 'fail', message: 'No task ID returned' };
|
|
223
|
+
// List tasks
|
|
224
|
+
const listTool = tools.find(t => t.name === 'task_list');
|
|
225
|
+
if (listTool) {
|
|
226
|
+
const list = await listTool.handler({});
|
|
227
|
+
const tasks = (list?.tasks ?? []);
|
|
228
|
+
if (tasks.length === 0)
|
|
229
|
+
return { status: 'fail', message: 'Task list empty after create' };
|
|
230
|
+
}
|
|
231
|
+
return { status: 'pass', message: `Task ${taskId} — create/list OK` };
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function testHooksRouting() {
|
|
239
|
+
return () => timed('Hooks Routing', async () => {
|
|
240
|
+
try {
|
|
241
|
+
const { hooksTools } = await import('../mcp-tools/hooks-tools.js');
|
|
242
|
+
const tools = hooksTools;
|
|
243
|
+
const routeTool = tools.find(t => t.name === 'hooks_route');
|
|
244
|
+
if (!routeTool)
|
|
245
|
+
return { status: 'skip', message: 'hooks_route tool not found' };
|
|
246
|
+
const result = await routeTool.handler({
|
|
247
|
+
task: 'add user authentication with OAuth',
|
|
248
|
+
});
|
|
249
|
+
const primary = result?.primaryAgent;
|
|
250
|
+
const agent = primary?.type;
|
|
251
|
+
const confidence = primary?.confidence;
|
|
252
|
+
if (!agent)
|
|
253
|
+
return { status: 'fail', message: 'No agent recommendation returned' };
|
|
254
|
+
return { status: 'pass', message: `Routed to ${agent} (${confidence}% confidence)` };
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function testConfigShow() {
|
|
262
|
+
return () => timed('Config Show', async () => {
|
|
263
|
+
try {
|
|
264
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
265
|
+
const yamlPaths = [
|
|
266
|
+
'moflo.yaml',
|
|
267
|
+
'.claude-flow/config.yaml',
|
|
268
|
+
'.claude-flow/config.yml',
|
|
269
|
+
];
|
|
270
|
+
for (const p of yamlPaths) {
|
|
271
|
+
if (existsSync(p)) {
|
|
272
|
+
const content = readFileSync(p, 'utf-8');
|
|
273
|
+
if (content.length > 10) {
|
|
274
|
+
return { status: 'pass', message: `${p} (${content.length} bytes)` };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const jsonPaths = [
|
|
279
|
+
'.claude-flow/config.json',
|
|
280
|
+
'claude-flow.config.json',
|
|
281
|
+
];
|
|
282
|
+
for (const p of jsonPaths) {
|
|
283
|
+
if (existsSync(p)) {
|
|
284
|
+
JSON.parse(readFileSync(p, 'utf-8'));
|
|
285
|
+
return { status: 'pass', message: `${p} (valid JSON)` };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return { status: 'fail', message: 'No config file found' };
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function testInitIdempotency() {
|
|
296
|
+
return () => timed('Init Idempotency', async () => {
|
|
297
|
+
try {
|
|
298
|
+
const { existsSync } = await import('fs');
|
|
299
|
+
// Verify that key init artifacts exist (but don't re-run init to avoid side effects)
|
|
300
|
+
const expected = [
|
|
301
|
+
'.claude/settings.json',
|
|
302
|
+
'.claude/agents',
|
|
303
|
+
'.claude/skills/flo/SKILL.md',
|
|
304
|
+
];
|
|
305
|
+
const missing = expected.filter(p => !existsSync(p));
|
|
306
|
+
if (missing.length > 0) {
|
|
307
|
+
return { status: 'fail', message: `Missing: ${missing.join(', ')}` };
|
|
308
|
+
}
|
|
309
|
+
return { status: 'pass', message: `${expected.length} artifacts verified` };
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function testNeuralStatus() {
|
|
317
|
+
return () => timed('Neural Status', async () => {
|
|
318
|
+
try {
|
|
319
|
+
const { neuralTools } = await import('../mcp-tools/neural-tools.js');
|
|
320
|
+
const tools = neuralTools;
|
|
321
|
+
const statusTool = tools.find(t => t.name === 'neural_status');
|
|
322
|
+
if (!statusTool)
|
|
323
|
+
return { status: 'skip', message: 'neural_status tool not found' };
|
|
324
|
+
const result = await statusTool.handler({});
|
|
325
|
+
const components = result?.components;
|
|
326
|
+
if (!components || components.length === 0) {
|
|
327
|
+
// Still pass if we got a response — neural may not be fully loaded
|
|
328
|
+
return { status: 'pass', message: 'Neural subsystem responded' };
|
|
329
|
+
}
|
|
330
|
+
const active = components.filter(c => c.status === 'Active' || c.status === 'Available' || c.status === 'Loaded');
|
|
331
|
+
return { status: 'pass', message: `${active.length}/${components.length} components active` };
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
// Neural is optional — don't fail the whole suite
|
|
335
|
+
return { status: 'skip', message: `Neural not available: ${err instanceof Error ? err.message : String(err)}` };
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function testMcpParity() {
|
|
340
|
+
return () => timed('MCP Tools Available', async () => {
|
|
341
|
+
try {
|
|
342
|
+
const { memoryTools: memTools } = await import('../mcp-tools/memory-tools.js');
|
|
343
|
+
const expectedMemTools = ['memory_store', 'memory_retrieve', 'memory_search', 'memory_delete', 'memory_list', 'memory_stats'];
|
|
344
|
+
const found = expectedMemTools.filter(name => memTools.find(t => t.name === name));
|
|
345
|
+
const missing = expectedMemTools.filter(name => !memTools.find(t => t.name === name));
|
|
346
|
+
if (missing.length > 0) {
|
|
347
|
+
return { status: 'fail', message: `Missing MCP tools: ${missing.join(', ')}` };
|
|
348
|
+
}
|
|
349
|
+
return { status: 'pass', message: `${found.length} memory tools registered` };
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
return { status: 'fail', message: err instanceof Error ? err.message : String(err) };
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// Main command
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
export const diagnoseCommand = {
|
|
360
|
+
name: 'diagnose',
|
|
361
|
+
description: 'Full integration test suite — exercises all subsystems non-destructively',
|
|
362
|
+
aliases: ['diag'],
|
|
363
|
+
options: [
|
|
364
|
+
{
|
|
365
|
+
name: 'suite',
|
|
366
|
+
short: 's',
|
|
367
|
+
description: 'Run specific suite: memory, swarm, hive, task, hooks, config, neural, mcp, init, all',
|
|
368
|
+
type: 'string',
|
|
369
|
+
default: 'all',
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'verbose',
|
|
373
|
+
short: 'v',
|
|
374
|
+
description: 'Show detailed output for each test',
|
|
375
|
+
type: 'boolean',
|
|
376
|
+
default: false,
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: 'json',
|
|
380
|
+
description: 'Output results as JSON',
|
|
381
|
+
type: 'boolean',
|
|
382
|
+
default: false,
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
examples: [
|
|
386
|
+
{ command: 'moflo diagnose', description: 'Run full integration diagnostics' },
|
|
387
|
+
{ command: 'moflo diagnose --suite memory', description: 'Run only memory tests' },
|
|
388
|
+
{ command: 'moflo diagnose --json', description: 'Output results as JSON' },
|
|
389
|
+
{ command: 'moflo diag', description: 'Alias for diagnose' },
|
|
390
|
+
],
|
|
391
|
+
action: async (ctx) => {
|
|
392
|
+
const suite = ctx.flags.suite || 'all';
|
|
393
|
+
const verbose = ctx.flags.verbose;
|
|
394
|
+
const jsonOutput = ctx.flags.json;
|
|
395
|
+
if (!jsonOutput) {
|
|
396
|
+
output.writeln();
|
|
397
|
+
output.writeln(output.bold('MoFlo Diagnose'));
|
|
398
|
+
output.writeln(output.dim('Full integration test suite — all test data is cleaned up'));
|
|
399
|
+
output.writeln(output.dim('─'.repeat(60)));
|
|
400
|
+
output.writeln();
|
|
401
|
+
}
|
|
402
|
+
// Build test list based on suite filter
|
|
403
|
+
const suites = {
|
|
404
|
+
memory: [
|
|
405
|
+
testMemoryInit(),
|
|
406
|
+
testMemoryStore(),
|
|
407
|
+
testMemoryRetrieve(),
|
|
408
|
+
testMemorySearch(),
|
|
409
|
+
testMemoryList(),
|
|
410
|
+
testMemoryDelete(),
|
|
411
|
+
],
|
|
412
|
+
swarm: [testSwarmLifecycle()],
|
|
413
|
+
hive: [testHiveMindLifecycle()],
|
|
414
|
+
task: [testTaskLifecycle()],
|
|
415
|
+
hooks: [testHooksRouting()],
|
|
416
|
+
config: [testConfigShow()],
|
|
417
|
+
neural: [testNeuralStatus()],
|
|
418
|
+
mcp: [testMcpParity()],
|
|
419
|
+
init: [testInitIdempotency()],
|
|
420
|
+
};
|
|
421
|
+
let tests;
|
|
422
|
+
if (suite === 'all') {
|
|
423
|
+
tests = Object.values(suites).flat();
|
|
424
|
+
}
|
|
425
|
+
else if (suites[suite]) {
|
|
426
|
+
tests = suites[suite];
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
const valid = Object.keys(suites).join(', ');
|
|
430
|
+
output.writeln(output.error(`Unknown suite "${suite}". Valid: ${valid}, all`));
|
|
431
|
+
return { success: false, exitCode: 1 };
|
|
432
|
+
}
|
|
433
|
+
// Run tests sequentially (some depend on prior state, e.g. memory store → retrieve)
|
|
434
|
+
const results = [];
|
|
435
|
+
const spinner = output.createSpinner({ text: 'Running diagnostics...', spinner: 'dots' });
|
|
436
|
+
if (!jsonOutput)
|
|
437
|
+
spinner.start();
|
|
438
|
+
for (const test of tests) {
|
|
439
|
+
const result = await test();
|
|
440
|
+
results.push(result);
|
|
441
|
+
if (!jsonOutput) {
|
|
442
|
+
spinner.stop();
|
|
443
|
+
const icon = result.status === 'pass' ? output.success('✓')
|
|
444
|
+
: result.status === 'skip' ? output.dim('○')
|
|
445
|
+
: output.error('✗');
|
|
446
|
+
const dur = result.duration < 1000
|
|
447
|
+
? `${result.duration.toFixed(0)}ms`
|
|
448
|
+
: `${(result.duration / 1000).toFixed(1)}s`;
|
|
449
|
+
output.writeln(`${icon} ${result.name}: ${result.message} ${output.dim(`(${dur})`)}`);
|
|
450
|
+
spinner.start();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!jsonOutput)
|
|
454
|
+
spinner.stop();
|
|
455
|
+
// Summary
|
|
456
|
+
const passed = results.filter(r => r.status === 'pass').length;
|
|
457
|
+
const failed = results.filter(r => r.status === 'fail').length;
|
|
458
|
+
const skipped = results.filter(r => r.status === 'skip').length;
|
|
459
|
+
const totalTime = results.reduce((s, r) => s + r.duration, 0);
|
|
460
|
+
if (jsonOutput) {
|
|
461
|
+
const out = {
|
|
462
|
+
passed,
|
|
463
|
+
failed,
|
|
464
|
+
skipped,
|
|
465
|
+
total: results.length,
|
|
466
|
+
totalTime: `${totalTime.toFixed(0)}ms`,
|
|
467
|
+
results: results.map(r => ({
|
|
468
|
+
name: r.name,
|
|
469
|
+
status: r.status,
|
|
470
|
+
message: r.message,
|
|
471
|
+
duration: `${r.duration.toFixed(0)}ms`,
|
|
472
|
+
})),
|
|
473
|
+
};
|
|
474
|
+
output.writeln(JSON.stringify(out, null, 2));
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
output.writeln();
|
|
478
|
+
output.writeln(output.dim('─'.repeat(60)));
|
|
479
|
+
output.writeln();
|
|
480
|
+
const parts = [
|
|
481
|
+
output.success(`${passed} passed`),
|
|
482
|
+
failed > 0 ? output.error(`${failed} failed`) : null,
|
|
483
|
+
skipped > 0 ? output.dim(`${skipped} skipped`) : null,
|
|
484
|
+
].filter(Boolean);
|
|
485
|
+
output.writeln(`${output.bold('Results:')} ${parts.join(', ')} ${output.dim(`(${(totalTime / 1000).toFixed(1)}s)`)}`);
|
|
486
|
+
if (failed > 0) {
|
|
487
|
+
output.writeln();
|
|
488
|
+
output.writeln(output.error('Some tests failed. Run with --verbose or fix the issues above.'));
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
output.writeln();
|
|
492
|
+
output.writeln(output.success('All systems operational.'));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
success: failed === 0,
|
|
497
|
+
exitCode: failed > 0 ? 1 : 0,
|
|
498
|
+
data: { passed, failed, skipped, total: results.length, results },
|
|
499
|
+
};
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
export default diagnoseCommand;
|
|
503
|
+
//# sourceMappingURL=diagnose.js.map
|
|
@@ -29,6 +29,7 @@ export { ruvectorCommand } from './ruvector/index.js';
|
|
|
29
29
|
export { hiveMindCommand } from './hive-mind.js';
|
|
30
30
|
export { guidanceCommand } from './guidance.js';
|
|
31
31
|
export { applianceCommand } from './appliance.js';
|
|
32
|
+
export { diagnoseCommand } from './diagnose.js';
|
|
32
33
|
export declare function getConfigCommand(): Promise<Command | undefined>;
|
|
33
34
|
export declare function getMigrateCommand(): Promise<Command | undefined>;
|
|
34
35
|
export declare function getWorkflowCommand(): Promise<Command | undefined>;
|
|
@@ -55,6 +55,8 @@ const commandLoaders = {
|
|
|
55
55
|
update: () => import('./update.js'),
|
|
56
56
|
// RuVector PostgreSQL Bridge
|
|
57
57
|
ruvector: () => import('./ruvector/index.js'),
|
|
58
|
+
// Full integration diagnostics
|
|
59
|
+
diagnose: () => import('./diagnose.js'),
|
|
58
60
|
// Benchmark Suite (Pre-training, Neural, Memory)
|
|
59
61
|
benchmark: () => import('./benchmark.js'),
|
|
60
62
|
// Guidance Control Plane
|
|
@@ -134,6 +136,7 @@ import updateCommand from './update.js';
|
|
|
134
136
|
import { processCommand } from './process.js';
|
|
135
137
|
import { guidanceCommand } from './guidance.js';
|
|
136
138
|
import { applianceCommand } from './appliance.js';
|
|
139
|
+
import { diagnoseCommand } from './diagnose.js';
|
|
137
140
|
// Pre-populate cache with core commands
|
|
138
141
|
loadedCommands.set('init', initCommand);
|
|
139
142
|
loadedCommands.set('start', startCommand);
|
|
@@ -154,6 +157,7 @@ loadedCommands.set('security', securityCommand);
|
|
|
154
157
|
loadedCommands.set('ruvector', ruvectorCommand);
|
|
155
158
|
loadedCommands.set('hive-mind', hiveMindCommand);
|
|
156
159
|
loadedCommands.set('guidance', guidanceCommand);
|
|
160
|
+
loadedCommands.set('diagnose', diagnoseCommand);
|
|
157
161
|
// =============================================================================
|
|
158
162
|
// Exports (maintain backwards compatibility)
|
|
159
163
|
// =============================================================================
|
|
@@ -178,6 +182,7 @@ export { ruvectorCommand } from './ruvector/index.js';
|
|
|
178
182
|
export { hiveMindCommand } from './hive-mind.js';
|
|
179
183
|
export { guidanceCommand } from './guidance.js';
|
|
180
184
|
export { applianceCommand } from './appliance.js';
|
|
185
|
+
export { diagnoseCommand } from './diagnose.js';
|
|
181
186
|
// Lazy-loaded command re-exports (for backwards compatibility, but async-only)
|
|
182
187
|
export async function getConfigCommand() { return loadCommand('config'); }
|
|
183
188
|
export async function getMigrateCommand() { return loadCommand('migrate'); }
|
|
@@ -227,6 +232,7 @@ export const commands = [
|
|
|
227
232
|
ruvectorCommand,
|
|
228
233
|
hiveMindCommand,
|
|
229
234
|
guidanceCommand,
|
|
235
|
+
diagnoseCommand,
|
|
230
236
|
];
|
|
231
237
|
/**
|
|
232
238
|
* Commands organized by category for help display
|
|
@@ -256,6 +262,7 @@ export const commandsByCategory = {
|
|
|
256
262
|
utility: [
|
|
257
263
|
configCommand,
|
|
258
264
|
doctorCommand,
|
|
265
|
+
diagnoseCommand,
|
|
259
266
|
daemonCommand,
|
|
260
267
|
completionsCommand,
|
|
261
268
|
migrateCommand,
|
|
@@ -2384,11 +2384,173 @@ const codeMapCommand = {
|
|
|
2384
2384
|
return { success: true };
|
|
2385
2385
|
}
|
|
2386
2386
|
};
|
|
2387
|
+
// refresh subcommand — reindex everything + vacuum
|
|
2388
|
+
const refreshCommand = {
|
|
2389
|
+
name: 'refresh',
|
|
2390
|
+
description: 'Reindex all guidance and code, rebuild embeddings, clean up expired entries, and vacuum the database',
|
|
2391
|
+
options: [
|
|
2392
|
+
{
|
|
2393
|
+
name: 'skip-guidance',
|
|
2394
|
+
description: 'Skip guidance reindexing',
|
|
2395
|
+
type: 'boolean',
|
|
2396
|
+
default: false,
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
name: 'skip-code-map',
|
|
2400
|
+
description: 'Skip code map regeneration',
|
|
2401
|
+
type: 'boolean',
|
|
2402
|
+
default: false,
|
|
2403
|
+
},
|
|
2404
|
+
{
|
|
2405
|
+
name: 'skip-cleanup',
|
|
2406
|
+
description: 'Skip expired entry cleanup',
|
|
2407
|
+
type: 'boolean',
|
|
2408
|
+
default: false,
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
name: 'verbose',
|
|
2412
|
+
short: 'v',
|
|
2413
|
+
description: 'Verbose output',
|
|
2414
|
+
type: 'boolean',
|
|
2415
|
+
default: false,
|
|
2416
|
+
},
|
|
2417
|
+
],
|
|
2418
|
+
examples: [
|
|
2419
|
+
{ command: 'flo memory refresh', description: 'Full reindex + vacuum' },
|
|
2420
|
+
{ command: 'flo memory refresh --skip-code-map', description: 'Reindex guidance only + vacuum' },
|
|
2421
|
+
],
|
|
2422
|
+
action: async (ctx) => {
|
|
2423
|
+
const skipGuidance = ctx.flags['skip-guidance'];
|
|
2424
|
+
const skipCodeMap = ctx.flags['skip-code-map'];
|
|
2425
|
+
const skipCleanup = ctx.flags['skip-cleanup'];
|
|
2426
|
+
output.writeln();
|
|
2427
|
+
output.writeln(output.bold('MoFlo Memory Refresh'));
|
|
2428
|
+
output.writeln(output.dim('Reindex all content, rebuild embeddings, clean up, and vacuum'));
|
|
2429
|
+
output.writeln(output.dim('─'.repeat(60)));
|
|
2430
|
+
output.writeln();
|
|
2431
|
+
const t0 = performance.now();
|
|
2432
|
+
const steps = [];
|
|
2433
|
+
// Helper to run a subcommand action
|
|
2434
|
+
const runStep = async (name, skip, action) => {
|
|
2435
|
+
if (skip) {
|
|
2436
|
+
steps.push({ name, status: 'skip', message: 'Skipped', duration: 0 });
|
|
2437
|
+
output.writeln(`${output.dim('○')} ${name}: ${output.dim('Skipped')}`);
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const stepStart = performance.now();
|
|
2441
|
+
try {
|
|
2442
|
+
const result = await action();
|
|
2443
|
+
const dur = performance.now() - stepStart;
|
|
2444
|
+
const success = result === undefined || result.success;
|
|
2445
|
+
steps.push({ name, status: success ? 'pass' : 'fail', message: success ? 'Done' : (result?.message || 'Failed'), duration: dur });
|
|
2446
|
+
const icon = success ? output.success('✓') : output.error('✗');
|
|
2447
|
+
const durStr = dur < 1000 ? `${dur.toFixed(0)}ms` : `${(dur / 1000).toFixed(1)}s`;
|
|
2448
|
+
output.writeln(`${icon} ${name} ${output.dim(`(${durStr})`)}`);
|
|
2449
|
+
}
|
|
2450
|
+
catch (err) {
|
|
2451
|
+
const dur = performance.now() - stepStart;
|
|
2452
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2453
|
+
steps.push({ name, status: 'fail', message: msg, duration: dur });
|
|
2454
|
+
output.writeln(`${output.error('✗')} ${name}: ${msg}`);
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
// Build a fake context with force flag for subcommand calls
|
|
2458
|
+
const forceCtx = {
|
|
2459
|
+
args: [],
|
|
2460
|
+
flags: { force: true, _: [], 'no-embeddings': false, overlap: 20 },
|
|
2461
|
+
cwd: ctx.cwd,
|
|
2462
|
+
interactive: false,
|
|
2463
|
+
};
|
|
2464
|
+
// Step 1: Index guidance
|
|
2465
|
+
await runStep('Index Guidance', skipGuidance, async () => {
|
|
2466
|
+
return indexGuidanceCommand.action(forceCtx);
|
|
2467
|
+
});
|
|
2468
|
+
// Step 2: Code map
|
|
2469
|
+
await runStep('Code Map', skipCodeMap, async () => {
|
|
2470
|
+
const codeMapCtx = {
|
|
2471
|
+
args: [],
|
|
2472
|
+
flags: { force: true, _: [], stats: false },
|
|
2473
|
+
cwd: ctx.cwd,
|
|
2474
|
+
interactive: false,
|
|
2475
|
+
};
|
|
2476
|
+
return codeMapCommand.action(codeMapCtx);
|
|
2477
|
+
});
|
|
2478
|
+
// Step 3: Rebuild embeddings
|
|
2479
|
+
await runStep('Rebuild Embeddings', false, async () => {
|
|
2480
|
+
const rebuildCtx = {
|
|
2481
|
+
args: [],
|
|
2482
|
+
flags: { force: true, _: [] },
|
|
2483
|
+
cwd: ctx.cwd,
|
|
2484
|
+
interactive: false,
|
|
2485
|
+
};
|
|
2486
|
+
return rebuildIndexCommand.action(rebuildCtx);
|
|
2487
|
+
});
|
|
2488
|
+
// Step 4: Cleanup expired entries (direct SQL — avoids MCP dependency)
|
|
2489
|
+
await runStep('Cleanup Expired', skipCleanup, async () => {
|
|
2490
|
+
const { db, dbPath } = await openDb(ctx.cwd);
|
|
2491
|
+
try {
|
|
2492
|
+
const now = Date.now();
|
|
2493
|
+
const result = db.run(`DELETE FROM memory_entries WHERE expires_at IS NOT NULL AND expires_at > 0 AND expires_at < ?`, [now]);
|
|
2494
|
+
const deleted = db.getRowsModified();
|
|
2495
|
+
if (deleted > 0) {
|
|
2496
|
+
saveAndCloseDb(db, dbPath);
|
|
2497
|
+
output.writeln(output.dim(` Removed ${deleted} expired entries`));
|
|
2498
|
+
}
|
|
2499
|
+
else {
|
|
2500
|
+
db.close();
|
|
2501
|
+
output.writeln(output.dim(' No expired entries found'));
|
|
2502
|
+
}
|
|
2503
|
+
return { success: true };
|
|
2504
|
+
}
|
|
2505
|
+
catch (err) {
|
|
2506
|
+
try {
|
|
2507
|
+
db.close();
|
|
2508
|
+
}
|
|
2509
|
+
catch { /* ignore */ }
|
|
2510
|
+
throw err;
|
|
2511
|
+
}
|
|
2512
|
+
});
|
|
2513
|
+
// Step 5: VACUUM the database
|
|
2514
|
+
await runStep('Vacuum Database', false, async () => {
|
|
2515
|
+
const { db, dbPath } = await openDb(ctx.cwd);
|
|
2516
|
+
try {
|
|
2517
|
+
db.run('VACUUM');
|
|
2518
|
+
saveAndCloseDb(db, dbPath);
|
|
2519
|
+
return { success: true };
|
|
2520
|
+
}
|
|
2521
|
+
catch (err) {
|
|
2522
|
+
try {
|
|
2523
|
+
db.close();
|
|
2524
|
+
}
|
|
2525
|
+
catch { /* ignore */ }
|
|
2526
|
+
throw err;
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
// Summary
|
|
2530
|
+
const totalTime = performance.now() - t0;
|
|
2531
|
+
const passed = steps.filter(s => s.status === 'pass').length;
|
|
2532
|
+
const failed = steps.filter(s => s.status === 'fail').length;
|
|
2533
|
+
const skipped = steps.filter(s => s.status === 'skip').length;
|
|
2534
|
+
output.writeln();
|
|
2535
|
+
output.writeln(output.dim('─'.repeat(60)));
|
|
2536
|
+
const parts = [
|
|
2537
|
+
output.success(`${passed} done`),
|
|
2538
|
+
failed > 0 ? output.error(`${failed} failed`) : null,
|
|
2539
|
+
skipped > 0 ? output.dim(`${skipped} skipped`) : null,
|
|
2540
|
+
].filter(Boolean);
|
|
2541
|
+
const durStr = totalTime < 1000 ? `${totalTime.toFixed(0)}ms` : `${(totalTime / 1000).toFixed(1)}s`;
|
|
2542
|
+
output.writeln(`${output.bold('Refresh complete:')} ${parts.join(', ')} ${output.dim(`(${durStr})`)}`);
|
|
2543
|
+
if (failed > 0) {
|
|
2544
|
+
return { success: false, exitCode: 1 };
|
|
2545
|
+
}
|
|
2546
|
+
return { success: true };
|
|
2547
|
+
},
|
|
2548
|
+
};
|
|
2387
2549
|
// Main memory command
|
|
2388
2550
|
export const memoryCommand = {
|
|
2389
2551
|
name: 'memory',
|
|
2390
2552
|
description: 'Memory management commands',
|
|
2391
|
-
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, statsCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, indexGuidanceCommand, rebuildIndexCommand, codeMapCommand],
|
|
2553
|
+
subcommands: [initMemoryCommand, storeCommand, retrieveCommand, searchCommand, listCommand, deleteCommand, statsCommand, configureCommand, cleanupCommand, compressCommand, exportCommand, importCommand, indexGuidanceCommand, rebuildIndexCommand, codeMapCommand, refreshCommand],
|
|
2392
2554
|
options: [],
|
|
2393
2555
|
examples: [
|
|
2394
2556
|
{ command: 'claude-flow memory store -k "key" -v "value"', description: 'Store data' },
|
|
@@ -2417,7 +2579,8 @@ export const memoryCommand = {
|
|
|
2417
2579
|
`${output.highlight('import')} - Import from file`,
|
|
2418
2580
|
`${output.highlight('index-guidance')} - Index .claude/guidance/ files with RAG segments`,
|
|
2419
2581
|
`${output.highlight('rebuild-index')} - Regenerate embeddings for memory entries`,
|
|
2420
|
-
`${output.highlight('code-map')} - Generate structural code map
|
|
2582
|
+
`${output.highlight('code-map')} - Generate structural code map`,
|
|
2583
|
+
`${output.highlight('refresh')} - Reindex all content, rebuild embeddings, cleanup, and vacuum`
|
|
2421
2584
|
]);
|
|
2422
2585
|
return { success: true };
|
|
2423
2586
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moflo/cli",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MoFlo CLI — AI agent orchestration with specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|