moflo 4.7.6 → 4.7.8
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/.claude/helpers/gate.cjs +148 -0
- package/.claude/helpers/statusline.cjs +34 -26
- package/.claude/settings.json +2 -2
- package/README.md +1 -1
- package/bin/hooks.mjs +30 -3
- package/package.json +3 -5
- package/src/@claude-flow/cli/README.md +1 -1
- package/src/@claude-flow/cli/dist/src/commands/init.js +0 -145
- package/src/@claude-flow/cli/dist/src/config-adapter.d.ts +1 -1
- package/src/@claude-flow/cli/dist/src/init/executor.js +3 -12
- package/src/@claude-flow/cli/dist/src/init/mcp-generator.d.ts +3 -4
- package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +65 -22
- package/src/@claude-flow/cli/dist/src/init/types.d.ts +0 -4
- package/src/@claude-flow/cli/dist/src/init/types.js +0 -5
- package/src/@claude-flow/cli/dist/src/mcp-server.js +36 -0
- package/src/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +6 -0
- package/src/@claude-flow/cli/dist/src/memory/memory-bridge.js +66 -0
- package/src/@claude-flow/cli/dist/src/memory/memory-initializer.js +52 -1
- package/src/@claude-flow/cli/package.json +2 -6
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
|
|
6
|
+
var PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
7
|
+
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
8
|
+
|
|
9
|
+
function readState() {
|
|
10
|
+
try {
|
|
11
|
+
if (fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
12
|
+
} catch (e) { /* reset on corruption */ }
|
|
13
|
+
return { tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeState(s) {
|
|
17
|
+
try {
|
|
18
|
+
var dir = path.dirname(STATE_FILE);
|
|
19
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(s, null, 2));
|
|
21
|
+
} catch (e) { /* non-fatal */ }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Load moflo.yaml gate config (defaults: all enabled)
|
|
25
|
+
function loadGateConfig() {
|
|
26
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true };
|
|
27
|
+
try {
|
|
28
|
+
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
29
|
+
if (fs.existsSync(yamlPath)) {
|
|
30
|
+
var content = fs.readFileSync(yamlPath, 'utf-8');
|
|
31
|
+
if (/memory_first:\s*false/i.test(content)) defaults.memory_first = false;
|
|
32
|
+
if (/task_create_first:\s*false/i.test(content)) defaults.task_create_first = false;
|
|
33
|
+
if (/context_tracking:\s*false/i.test(content)) defaults.context_tracking = false;
|
|
34
|
+
}
|
|
35
|
+
} catch (e) { /* use defaults */ }
|
|
36
|
+
return defaults;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var config = loadGateConfig();
|
|
40
|
+
var command = process.argv[2];
|
|
41
|
+
|
|
42
|
+
var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules'];
|
|
43
|
+
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
44
|
+
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
|
|
45
|
+
var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
|
|
46
|
+
|
|
47
|
+
switch (command) {
|
|
48
|
+
case 'check-before-agent': {
|
|
49
|
+
var s = readState();
|
|
50
|
+
if (config.task_create_first && !s.tasksCreated) {
|
|
51
|
+
console.log('BLOCKED: Call TaskCreate before spawning agents.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
if (config.memory_first && !s.memorySearched) {
|
|
55
|
+
console.log('BLOCKED: Search memory before spawning agents.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'check-before-scan': {
|
|
61
|
+
if (!config.memory_first) break;
|
|
62
|
+
var s = readState();
|
|
63
|
+
if (s.memorySearched || !s.memoryRequired) break;
|
|
64
|
+
var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
|
|
65
|
+
if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
|
|
66
|
+
var now = Date.now();
|
|
67
|
+
var last = s.lastBlockedAt ? new Date(s.lastBlockedAt).getTime() : 0;
|
|
68
|
+
if (now - last > 2000) {
|
|
69
|
+
s.lastBlockedAt = new Date(now).toISOString();
|
|
70
|
+
writeState(s);
|
|
71
|
+
console.log('BLOCKED: Search memory before exploring files.');
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
case 'check-before-read': {
|
|
76
|
+
if (!config.memory_first) break;
|
|
77
|
+
var s = readState();
|
|
78
|
+
if (s.memorySearched || !s.memoryRequired) break;
|
|
79
|
+
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
80
|
+
if (fp.indexOf('.claude/guidance/') < 0 && fp.indexOf('.claude\\guidance\\') < 0) break;
|
|
81
|
+
var now = Date.now();
|
|
82
|
+
var last = s.lastBlockedAt ? new Date(s.lastBlockedAt).getTime() : 0;
|
|
83
|
+
if (now - last > 2000) {
|
|
84
|
+
s.lastBlockedAt = new Date(now).toISOString();
|
|
85
|
+
writeState(s);
|
|
86
|
+
console.log('BLOCKED: Search memory before reading guidance files.');
|
|
87
|
+
}
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
case 'record-task-created': {
|
|
91
|
+
var s = readState();
|
|
92
|
+
s.tasksCreated = true;
|
|
93
|
+
s.taskCount = (s.taskCount || 0) + 1;
|
|
94
|
+
writeState(s);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case 'record-memory-searched': {
|
|
98
|
+
var s = readState();
|
|
99
|
+
s.memorySearched = true;
|
|
100
|
+
writeState(s);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'check-bash-memory': {
|
|
104
|
+
var cmd = process.env.TOOL_INPUT_command || '';
|
|
105
|
+
if (/semantic-search|memory search|memory retrieve|memory-search/.test(cmd)) {
|
|
106
|
+
var s = readState();
|
|
107
|
+
s.memorySearched = true;
|
|
108
|
+
writeState(s);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 'check-dangerous-command': {
|
|
113
|
+
var cmd = (process.env.TOOL_INPUT_command || '').toLowerCase();
|
|
114
|
+
for (var i = 0; i < DANGEROUS.length; i++) {
|
|
115
|
+
if (cmd.indexOf(DANGEROUS[i]) >= 0) {
|
|
116
|
+
console.log('[BLOCKED] Dangerous command: ' + DANGEROUS[i]);
|
|
117
|
+
process.exit(2);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case 'prompt-reminder': {
|
|
123
|
+
var s = readState();
|
|
124
|
+
s.memorySearched = false;
|
|
125
|
+
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
126
|
+
s.memoryRequired = prompt.length >= 4 && !DIRECTIVE_RE.test(prompt) && (TASK_RE.test(prompt) || prompt.length > 80);
|
|
127
|
+
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
128
|
+
writeState(s);
|
|
129
|
+
if (!s.tasksCreated) console.log('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.');
|
|
130
|
+
if (config.context_tracking) {
|
|
131
|
+
var ic = s.interactionCount;
|
|
132
|
+
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
133
|
+
else if (ic > 20) console.log('Context: DEPLETED. Checkpoint progress. Recommend /compact or fresh session.');
|
|
134
|
+
else if (ic > 10) console.log('Context: MODERATE. Re-state goal before architectural decisions.');
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'compact-guidance': {
|
|
139
|
+
console.log('Pre-Compact: Check CLAUDE.md for rules. Use memory search to recover context after compact.');
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case 'session-reset': {
|
|
143
|
+
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
@@ -330,25 +330,33 @@ function getSecurityStatus() {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// Swarm status (pure file reads, NO ps aux)
|
|
333
|
+
// Metrics files older than 5 minutes are considered stale (swarm no longer running)
|
|
333
334
|
function getSwarmStatus() {
|
|
335
|
+
const STALE_MS = 5 * 60_000;
|
|
336
|
+
const now = Date.now();
|
|
337
|
+
|
|
334
338
|
const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
|
|
335
339
|
if (activityData?.swarm) {
|
|
336
|
-
const
|
|
340
|
+
const ts = activityData.timestamp ? new Date(activityData.timestamp).getTime() : 0;
|
|
341
|
+
const stale = (now - ts) > STALE_MS;
|
|
342
|
+
const count = stale ? 0 : (activityData.swarm.agent_count || 0);
|
|
337
343
|
return {
|
|
338
344
|
activeAgents: Math.min(count, CONFIG.maxAgents),
|
|
339
345
|
maxAgents: CONFIG.maxAgents,
|
|
340
|
-
coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
|
|
346
|
+
coordinationActive: stale ? false : (activityData.swarm.coordination_active || activityData.swarm.active || false),
|
|
341
347
|
};
|
|
342
348
|
}
|
|
343
349
|
|
|
344
350
|
const progressData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'v3-progress.json'));
|
|
345
351
|
if (progressData?.swarm) {
|
|
346
|
-
const
|
|
352
|
+
const ts = progressData.timestamp ? new Date(progressData.timestamp).getTime() : 0;
|
|
353
|
+
const stale = (now - ts) > STALE_MS;
|
|
354
|
+
const count = stale ? 0 : (progressData.swarm.activeAgents || progressData.swarm.agent_count || 0);
|
|
347
355
|
const max = progressData.swarm.totalAgents || CONFIG.maxAgents;
|
|
348
356
|
return {
|
|
349
357
|
activeAgents: Math.min(count, max),
|
|
350
358
|
maxAgents: max,
|
|
351
|
-
coordinationActive: progressData.swarm.active || (count > 0),
|
|
359
|
+
coordinationActive: stale ? false : (progressData.swarm.active || (count > 0)),
|
|
352
360
|
};
|
|
353
361
|
}
|
|
354
362
|
|
|
@@ -461,44 +469,44 @@ function getHooksStatus() {
|
|
|
461
469
|
return { enabled, total };
|
|
462
470
|
}
|
|
463
471
|
|
|
464
|
-
// AgentDB stats —
|
|
472
|
+
// AgentDB stats — reads from cache file written by embedding/memory operations.
|
|
473
|
+
// No subprocess spawning. Falls back to DB file size estimate if cache is missing.
|
|
465
474
|
function getAgentDBStats() {
|
|
466
475
|
let vectorCount = 0;
|
|
467
476
|
let dbSizeKB = 0;
|
|
468
477
|
let namespaces = 0;
|
|
469
478
|
let hasHnsw = false;
|
|
470
|
-
let dbPath = null;
|
|
471
479
|
|
|
480
|
+
// Read cached stats (written by memory store/embed/rebuild commands)
|
|
481
|
+
const cachePaths = [
|
|
482
|
+
path.join(CWD, '.claude-flow', 'vector-stats.json'),
|
|
483
|
+
path.join(CWD, '.swarm', 'vector-stats.json'),
|
|
484
|
+
];
|
|
485
|
+
for (const cp of cachePaths) {
|
|
486
|
+
const cached = readJSON(cp);
|
|
487
|
+
if (cached && typeof cached.vectorCount === 'number') {
|
|
488
|
+
vectorCount = cached.vectorCount;
|
|
489
|
+
dbSizeKB = cached.dbSizeKB || 0;
|
|
490
|
+
namespaces = cached.namespaces || 0;
|
|
491
|
+
hasHnsw = cached.hasHnsw || false;
|
|
492
|
+
return { vectorCount, dbSizeKB, namespaces, hasHnsw };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Fallback: estimate from DB file size (no subprocess)
|
|
472
497
|
const dbFiles = [
|
|
473
498
|
path.join(CWD, '.swarm', 'memory.db'),
|
|
474
499
|
path.join(CWD, '.claude-flow', 'memory.db'),
|
|
475
500
|
path.join(CWD, '.claude', 'memory.db'),
|
|
476
501
|
path.join(CWD, 'data', 'memory.db'),
|
|
477
502
|
];
|
|
478
|
-
|
|
479
503
|
for (const f of dbFiles) {
|
|
480
504
|
const stat = safeStat(f);
|
|
481
505
|
if (stat) {
|
|
482
|
-
dbSizeKB = stat.size / 1024;
|
|
483
|
-
dbPath = f;
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Try to get real count from sqlite (fast — single COUNT query)
|
|
489
|
-
if (dbPath) {
|
|
490
|
-
const countOutput = safeExec(`node -e "const S=require('sql.js');const f=require('fs');S().then(Q=>{const d=new Q.Database(f.readFileSync('${dbPath.replace(/\\/g, '/')}'));const s=d.prepare('SELECT COUNT(*) as c FROM memory_entries WHERE status=\\"active\\" AND embedding IS NOT NULL');s.step();console.log(JSON.stringify(s.getAsObject()));s.free();const n=d.prepare('SELECT COUNT(DISTINCT namespace) as n FROM memory_entries WHERE status=\\"active\\"');n.step();console.log(JSON.stringify(n.getAsObject()));n.free();d.close();})"`, 3000);
|
|
491
|
-
if (countOutput) {
|
|
492
|
-
try {
|
|
493
|
-
const lines = countOutput.trim().split('\n');
|
|
494
|
-
vectorCount = JSON.parse(lines[0]).c || 0;
|
|
495
|
-
namespaces = lines[1] ? JSON.parse(lines[1]).n || 0 : 0;
|
|
496
|
-
} catch { /* fall back to estimate */ }
|
|
497
|
-
}
|
|
498
|
-
// Fallback to file size estimate if query failed
|
|
499
|
-
if (vectorCount === 0) {
|
|
506
|
+
dbSizeKB = Math.floor(stat.size / 1024);
|
|
500
507
|
vectorCount = Math.floor(dbSizeKB / 2);
|
|
501
508
|
namespaces = 1;
|
|
509
|
+
break;
|
|
502
510
|
}
|
|
503
511
|
}
|
|
504
512
|
|
|
@@ -513,7 +521,7 @@ function getAgentDBStats() {
|
|
|
513
521
|
}
|
|
514
522
|
}
|
|
515
523
|
|
|
516
|
-
return { vectorCount, dbSizeKB
|
|
524
|
+
return { vectorCount, dbSizeKB, namespaces, hasHnsw };
|
|
517
525
|
}
|
|
518
526
|
|
|
519
527
|
// Test stats (count files only — NO reading file contents)
|
package/.claude/settings.json
CHANGED
package/README.md
CHANGED
package/bin/hooks.mjs
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { spawn } from 'child_process';
|
|
23
|
-
import { existsSync, appendFileSync } from 'fs';
|
|
23
|
+
import { existsSync, appendFileSync, readFileSync } from 'fs';
|
|
24
24
|
import { resolve, dirname } from 'path';
|
|
25
25
|
import { fileURLToPath } from 'url';
|
|
26
26
|
|
|
@@ -306,7 +306,11 @@ async function main() {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
case 'daemon-start': {
|
|
309
|
-
|
|
309
|
+
if (!isDaemonRunning()) {
|
|
310
|
+
await runClaudeFlow('daemon', ['start', '--quiet']);
|
|
311
|
+
} else {
|
|
312
|
+
log('info', 'Daemon already running, skipping start');
|
|
313
|
+
}
|
|
310
314
|
break;
|
|
311
315
|
}
|
|
312
316
|
|
|
@@ -475,7 +479,25 @@ function runBackgroundTraining() {
|
|
|
475
479
|
spawnWindowless('node', [localCli, 'neural', 'optimize'], 'neural optimize');
|
|
476
480
|
}
|
|
477
481
|
|
|
478
|
-
//
|
|
482
|
+
// Check if daemon is already running via PID file.
|
|
483
|
+
// Returns true if a live daemon process exists, false otherwise.
|
|
484
|
+
function isDaemonRunning() {
|
|
485
|
+
const pidFile = resolve(projectRoot, '.claude-flow', 'daemon.pid');
|
|
486
|
+
if (existsSync(pidFile)) {
|
|
487
|
+
try {
|
|
488
|
+
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
489
|
+
if (pid && !isNaN(pid)) {
|
|
490
|
+
process.kill(pid, 0); // signal 0 = check if process exists, doesn't kill
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
} catch {
|
|
494
|
+
// Process doesn't exist — stale PID file
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Run daemon start in background (non-blocking) — skip if already running
|
|
479
501
|
function runDaemonStartBackground() {
|
|
480
502
|
const localCli = getLocalCliPath();
|
|
481
503
|
if (!localCli) {
|
|
@@ -483,6 +505,11 @@ function runDaemonStartBackground() {
|
|
|
483
505
|
return;
|
|
484
506
|
}
|
|
485
507
|
|
|
508
|
+
if (isDaemonRunning()) {
|
|
509
|
+
log('info', 'Daemon already running, skipping start');
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
486
513
|
spawnWindowless('node', [localCli, 'daemon', 'start', '--quiet'], 'daemon');
|
|
487
514
|
}
|
|
488
515
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.8",
|
|
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",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"dev": "tsx watch src/index.ts",
|
|
50
50
|
"build": "tsc",
|
|
51
51
|
"build:ts": "cd src/@claude-flow/cli && npm run build || true",
|
|
52
|
-
"test": "vitest",
|
|
52
|
+
"test": "vitest run",
|
|
53
53
|
"test:ui": "vitest --ui",
|
|
54
54
|
"test:security": "vitest run src/__tests__/security/",
|
|
55
55
|
"lint": "cd src/@claude-flow/cli && npm run lint || true",
|
|
@@ -67,7 +67,6 @@
|
|
|
67
67
|
"zod": "^3.22.4"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
|
-
"@claude-flow/codex": "^3.0.0-alpha.8",
|
|
71
70
|
"@claude-flow/plugin-gastown-bridge": "^0.1.3",
|
|
72
71
|
"@ruvector/attention": "^0.1.3",
|
|
73
72
|
"@ruvector/core": "^0.1.30",
|
|
@@ -81,14 +80,13 @@
|
|
|
81
80
|
"hono": ">=4.11.4"
|
|
82
81
|
},
|
|
83
82
|
"devDependencies": {
|
|
84
|
-
"@openai/codex": "^0.98.0",
|
|
85
83
|
"@types/bcrypt": "^5.0.2",
|
|
86
84
|
"@types/node": "^20.0.0",
|
|
87
85
|
"eslint": "^8.0.0",
|
|
88
86
|
"moflo": "^4.7.4",
|
|
89
87
|
"tsx": "^4.21.0",
|
|
90
88
|
"typescript": "^5.0.0",
|
|
91
|
-
"vitest": "^
|
|
89
|
+
"vitest": "^4.0.0"
|
|
92
90
|
},
|
|
93
91
|
"engines": {
|
|
94
92
|
"node": ">=20.0.0"
|
|
@@ -7,129 +7,6 @@ import { confirm, select, multiSelect, input } from '../prompt.js';
|
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { executeInit, executeUpgrade, executeUpgradeWithMissing, DEFAULT_INIT_OPTIONS, MINIMAL_INIT_OPTIONS, FULL_INIT_OPTIONS, } from '../init/index.js';
|
|
10
|
-
// Codex initialization action
|
|
11
|
-
async function initCodexAction(ctx, options) {
|
|
12
|
-
const { force, minimal, full, dualMode } = options;
|
|
13
|
-
output.writeln();
|
|
14
|
-
output.writeln(output.bold('Initializing MoFlo V4 for OpenAI Codex'));
|
|
15
|
-
output.writeln();
|
|
16
|
-
// Determine template
|
|
17
|
-
const template = minimal ? 'minimal' : full ? 'full' : 'default';
|
|
18
|
-
const spinner = output.createSpinner({ text: 'Initializing Codex project...' });
|
|
19
|
-
spinner.start();
|
|
20
|
-
try {
|
|
21
|
-
// Dynamic import of the Codex initializer with lazy loading fallback
|
|
22
|
-
let CodexInitializer;
|
|
23
|
-
// Try multiple resolution strategies for the @claude-flow/codex package
|
|
24
|
-
// Use a variable to prevent TypeScript from statically resolving the optional module
|
|
25
|
-
const codexModuleId = '@claude-flow/codex';
|
|
26
|
-
const resolutionStrategies = [
|
|
27
|
-
// Strategy 1: Direct import (works if installed as CLI dependency)
|
|
28
|
-
async () => (await import(codexModuleId)).CodexInitializer,
|
|
29
|
-
// Strategy 2: Project node_modules (works if installed in user's project)
|
|
30
|
-
async () => {
|
|
31
|
-
const projectPath = path.join(ctx.cwd, 'node_modules', '@claude-flow', 'codex', 'dist', 'index.js');
|
|
32
|
-
if (fs.existsSync(projectPath)) {
|
|
33
|
-
const mod = await import(`file://${projectPath}`);
|
|
34
|
-
return mod.CodexInitializer;
|
|
35
|
-
}
|
|
36
|
-
throw new Error('Not found in project');
|
|
37
|
-
},
|
|
38
|
-
// Strategy 3: Global node_modules
|
|
39
|
-
async () => {
|
|
40
|
-
const { execSync } = await import('child_process');
|
|
41
|
-
const globalPath = execSync('npm root -g', { encoding: 'utf-8', windowsHide: true }).trim();
|
|
42
|
-
const codexPath = path.join(globalPath, '@claude-flow', 'codex', 'dist', 'index.js');
|
|
43
|
-
if (fs.existsSync(codexPath)) {
|
|
44
|
-
const mod = await import(`file://${codexPath}`);
|
|
45
|
-
return mod.CodexInitializer;
|
|
46
|
-
}
|
|
47
|
-
throw new Error('Not found globally');
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
for (const strategy of resolutionStrategies) {
|
|
51
|
-
try {
|
|
52
|
-
CodexInitializer = await strategy();
|
|
53
|
-
if (CodexInitializer)
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
// Try next strategy
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (!CodexInitializer) {
|
|
61
|
-
throw new Error('Cannot find module @claude-flow/codex');
|
|
62
|
-
}
|
|
63
|
-
const initializer = new CodexInitializer();
|
|
64
|
-
const result = await initializer.initialize({
|
|
65
|
-
projectPath: ctx.cwd,
|
|
66
|
-
template: template,
|
|
67
|
-
force,
|
|
68
|
-
dual: dualMode,
|
|
69
|
-
});
|
|
70
|
-
if (!result.success) {
|
|
71
|
-
spinner.fail('Codex initialization failed');
|
|
72
|
-
if (result.errors) {
|
|
73
|
-
for (const error of result.errors) {
|
|
74
|
-
output.printError(error);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { success: false, exitCode: 1 };
|
|
78
|
-
}
|
|
79
|
-
spinner.succeed('Codex project initialized successfully!');
|
|
80
|
-
output.writeln();
|
|
81
|
-
// Display summary
|
|
82
|
-
const summary = [];
|
|
83
|
-
summary.push(`Files: ${result.filesCreated.length} created`);
|
|
84
|
-
summary.push(`Skills: ${result.skillsGenerated.length} installed`);
|
|
85
|
-
output.printBox(summary.join('\n'), 'Summary');
|
|
86
|
-
output.writeln();
|
|
87
|
-
// Show what was created
|
|
88
|
-
output.printBox([
|
|
89
|
-
`AGENTS.md: Main project instructions`,
|
|
90
|
-
`.agents/config.toml: Project configuration`,
|
|
91
|
-
`.agents/skills/: ${result.skillsGenerated.length} skills`,
|
|
92
|
-
`.codex/: Local overrides (gitignored)`,
|
|
93
|
-
dualMode ? `CLAUDE.md: Claude Code compatibility` : '',
|
|
94
|
-
].filter(Boolean).join('\n'), 'OpenAI Codex Integration');
|
|
95
|
-
output.writeln();
|
|
96
|
-
// Warnings
|
|
97
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
98
|
-
output.printWarning('Warnings:');
|
|
99
|
-
for (const warning of result.warnings.slice(0, 5)) {
|
|
100
|
-
output.printInfo(` • ${warning}`);
|
|
101
|
-
}
|
|
102
|
-
if (result.warnings.length > 5) {
|
|
103
|
-
output.printInfo(` ... and ${result.warnings.length - 5} more`);
|
|
104
|
-
}
|
|
105
|
-
output.writeln();
|
|
106
|
-
}
|
|
107
|
-
// Next steps
|
|
108
|
-
output.writeln(output.bold('Next steps:'));
|
|
109
|
-
output.printList([
|
|
110
|
-
`Review ${output.highlight('AGENTS.md')} for project instructions`,
|
|
111
|
-
`Add skills with ${output.highlight('$skill-name')} syntax`,
|
|
112
|
-
`Configure ${output.highlight('.agents/config.toml')} for your project`,
|
|
113
|
-
dualMode ? `Claude Code users can use ${output.highlight('CLAUDE.md')}` : '',
|
|
114
|
-
].filter(Boolean));
|
|
115
|
-
return { success: true, data: result };
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
spinner.fail('Codex initialization failed');
|
|
119
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
120
|
-
// Handle module not found error gracefully
|
|
121
|
-
if (errorMessage.includes('Cannot find module') || errorMessage.includes('@claude-flow/codex')) {
|
|
122
|
-
output.printError('The @claude-flow/codex package is not installed.');
|
|
123
|
-
output.printInfo('Install it with: npm install @claude-flow/codex');
|
|
124
|
-
output.writeln();
|
|
125
|
-
output.printInfo('Alternatively, copy skills manually from .claude/skills/ to .agents/skills/');
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
output.printError(`Failed to initialize: ${errorMessage}`);
|
|
129
|
-
}
|
|
130
|
-
return { success: false, exitCode: 1 };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
10
|
// Check if project is already initialized
|
|
134
11
|
function isInitialized(cwd) {
|
|
135
12
|
const claudePath = path.join(cwd, '.claude', 'settings.json');
|
|
@@ -146,13 +23,7 @@ const initAction = async (ctx) => {
|
|
|
146
23
|
const full = ctx.flags.full;
|
|
147
24
|
const skipClaude = ctx.flags['skip-claude'];
|
|
148
25
|
const onlyClaude = ctx.flags['only-claude'];
|
|
149
|
-
const codexMode = ctx.flags.codex;
|
|
150
|
-
const dualMode = ctx.flags.dual;
|
|
151
26
|
const cwd = ctx.cwd;
|
|
152
|
-
// If codex mode, use the Codex initializer
|
|
153
|
-
if (codexMode || dualMode) {
|
|
154
|
-
return initCodexAction(ctx, { codexMode, dualMode, force, minimal, full });
|
|
155
|
-
}
|
|
156
27
|
// ── MoFlo Project Setup ────────────────────────────────────────────
|
|
157
28
|
// Always run MoFlo init to ensure moflo.yaml, hooks, skill, and
|
|
158
29
|
// CLAUDE.md are set up, regardless of other init options.
|
|
@@ -708,7 +579,6 @@ const skillsCommand = {
|
|
|
708
579
|
flowNexus: false,
|
|
709
580
|
browser: false,
|
|
710
581
|
v3: ctx.flags.v3,
|
|
711
|
-
dualMode: false,
|
|
712
582
|
},
|
|
713
583
|
};
|
|
714
584
|
const spinner = output.createSpinner({ text: 'Installing skills...' });
|
|
@@ -971,18 +841,6 @@ export const initCommand = {
|
|
|
971
841
|
default: 'all-MiniLM-L6-v2',
|
|
972
842
|
choices: ['all-MiniLM-L6-v2', 'all-mpnet-base-v2'],
|
|
973
843
|
},
|
|
974
|
-
{
|
|
975
|
-
name: 'codex',
|
|
976
|
-
description: 'Initialize for OpenAI Codex CLI (creates AGENTS.md, .agents/)',
|
|
977
|
-
type: 'boolean',
|
|
978
|
-
default: false,
|
|
979
|
-
},
|
|
980
|
-
{
|
|
981
|
-
name: 'dual',
|
|
982
|
-
description: 'Initialize for both Claude Code and OpenAI Codex',
|
|
983
|
-
type: 'boolean',
|
|
984
|
-
default: false,
|
|
985
|
-
},
|
|
986
844
|
],
|
|
987
845
|
examples: [
|
|
988
846
|
{ command: 'claude-flow init', description: 'Initialize with default configuration' },
|
|
@@ -1001,9 +859,6 @@ export const initCommand = {
|
|
|
1001
859
|
{ command: 'claude-flow init upgrade', description: 'Update helpers while preserving data' },
|
|
1002
860
|
{ command: 'claude-flow init upgrade --settings', description: 'Update helpers and merge new settings (Agent Teams)' },
|
|
1003
861
|
{ command: 'claude-flow init upgrade --verbose', description: 'Show detailed upgrade info' },
|
|
1004
|
-
{ command: 'claude-flow init --codex', description: 'Initialize for OpenAI Codex (AGENTS.md)' },
|
|
1005
|
-
{ command: 'claude-flow init --codex --full', description: 'Codex init with all 137+ skills' },
|
|
1006
|
-
{ command: 'claude-flow init --dual', description: 'Initialize for both Claude Code and Codex' },
|
|
1007
862
|
],
|
|
1008
863
|
action: initAction,
|
|
1009
864
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Configuration Adapter
|
|
3
3
|
* Converts between SystemConfig and V3Config types
|
|
4
4
|
*/
|
|
5
|
-
import type { SystemConfig } from '
|
|
5
|
+
import type { SystemConfig } from '../../shared/src/index.js';
|
|
6
6
|
import type { V3Config } from './types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Convert SystemConfig to V3Config (CLI-specific format)
|
|
@@ -31,7 +31,6 @@ const SKILLS_MAP = {
|
|
|
31
31
|
'skill-builder',
|
|
32
32
|
],
|
|
33
33
|
browser: ['browser'], // agent-browser integration
|
|
34
|
-
dualMode: ['dual-mode'], // Claude Code + Codex hybrid execution
|
|
35
34
|
agentdb: [
|
|
36
35
|
'agentdb-advanced',
|
|
37
36
|
'agentdb-learning',
|
|
@@ -89,7 +88,6 @@ const AGENTS_MAP = {
|
|
|
89
88
|
sparc: ['sparc'],
|
|
90
89
|
swarm: ['swarm'],
|
|
91
90
|
browser: ['browser'], // agent-browser integration
|
|
92
|
-
dualMode: ['dual-mode'], // Claude Code + Codex hybrid execution
|
|
93
91
|
// V3-specific agents
|
|
94
92
|
v3: ['v3'],
|
|
95
93
|
optimization: ['optimization'],
|
|
@@ -711,8 +709,6 @@ async function copySkills(targetDir, options, result) {
|
|
|
711
709
|
skillsToCopy.push(...SKILLS_MAP.browser);
|
|
712
710
|
if (skillsConfig.v3)
|
|
713
711
|
skillsToCopy.push(...SKILLS_MAP.v3);
|
|
714
|
-
if (skillsConfig.dualMode)
|
|
715
|
-
skillsToCopy.push(...SKILLS_MAP.dualMode);
|
|
716
712
|
}
|
|
717
713
|
// Find source skills directory
|
|
718
714
|
const sourceSkillsDir = findSourceDir('skills', options.sourceBaseDir);
|
|
@@ -825,9 +821,6 @@ async function copyAgents(targetDir, options, result) {
|
|
|
825
821
|
agentsToCopy.push(...(AGENTS_MAP.optimization || []));
|
|
826
822
|
if (agentsConfig.testing)
|
|
827
823
|
agentsToCopy.push(...(AGENTS_MAP.testing || []));
|
|
828
|
-
// Dual-mode agents (Claude Code + Codex hybrid)
|
|
829
|
-
if (agentsConfig.dualMode)
|
|
830
|
-
agentsToCopy.push(...(AGENTS_MAP.dualMode || []));
|
|
831
824
|
}
|
|
832
825
|
// Find source agents directory
|
|
833
826
|
const sourceAgentsDir = findSourceDir('agents', options.sourceBaseDir);
|
|
@@ -935,12 +928,10 @@ async function writeHelpers(targetDir, options, result) {
|
|
|
935
928
|
result.skipped.push(`.claude/helpers/${file}`);
|
|
936
929
|
}
|
|
937
930
|
}
|
|
938
|
-
|
|
939
|
-
return; // Skip generating if we copied from source
|
|
940
|
-
}
|
|
931
|
+
// Don't return early — still need to generate any missing required helpers below
|
|
941
932
|
}
|
|
942
|
-
//
|
|
943
|
-
// gate.cjs and hook-handler.cjs are
|
|
933
|
+
// Generate required helpers that weren't copied from source.
|
|
934
|
+
// gate.cjs and hook-handler.cjs are critical — hooks call them directly
|
|
944
935
|
// via `node` instead of `npx flo` to avoid CLI bootstrap overhead.
|
|
945
936
|
const helpers = {
|
|
946
937
|
'pre-commit': generatePreCommitHook(),
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* MCP Configuration Generator
|
|
3
3
|
* Creates .mcp.json for Claude Code MCP server integration
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* on Windows caused MCP servers to fail to start.
|
|
5
|
+
* Uses direct `node` invocation when moflo is locally installed to avoid
|
|
6
|
+
* npx overhead (package resolution, registry checks). Falls back to `npx`
|
|
7
|
+
* for external packages (ruv-swarm, flow-nexus) that may not be installed.
|
|
9
8
|
*/
|
|
10
9
|
import type { InitOptions } from './types.js';
|
|
11
10
|
/**
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
* MCP Configuration Generator
|
|
3
3
|
* Creates .mcp.json for Claude Code MCP server integration
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* on Windows caused MCP servers to fail to start.
|
|
5
|
+
* Uses direct `node` invocation when moflo is locally installed to avoid
|
|
6
|
+
* npx overhead (package resolution, registry checks). Falls back to `npx`
|
|
7
|
+
* for external packages (ruv-swarm, flow-nexus) that may not be installed.
|
|
9
8
|
*/
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
10
11
|
/**
|
|
11
|
-
* Generate MCP server entry
|
|
12
|
-
* Uses `npx` directly on all platforms — Claude Code handles process
|
|
13
|
-
* spawning correctly without needing a cmd.exe wrapper.
|
|
12
|
+
* Generate MCP server entry using npx (for external packages)
|
|
14
13
|
*/
|
|
15
|
-
function
|
|
14
|
+
function createNpxServerEntry(npxArgs, env, additionalProps = {}) {
|
|
16
15
|
return {
|
|
17
16
|
command: 'npx',
|
|
18
17
|
args: ['-y', ...npxArgs],
|
|
@@ -20,6 +19,35 @@ function createMCPServerEntry(npxArgs, env, additionalProps = {}) {
|
|
|
20
19
|
...additionalProps,
|
|
21
20
|
};
|
|
22
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate MCP server entry using direct node invocation (for local moflo).
|
|
24
|
+
* Avoids npx overhead — faster startup, fewer intermediate processes.
|
|
25
|
+
*/
|
|
26
|
+
function createDirectServerEntry(cliPath, cliArgs, env, additionalProps = {}) {
|
|
27
|
+
return {
|
|
28
|
+
command: 'node',
|
|
29
|
+
args: [cliPath, ...cliArgs],
|
|
30
|
+
env,
|
|
31
|
+
...additionalProps,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Find the moflo CLI entry point relative to the project root.
|
|
36
|
+
* Returns the path if found, null otherwise.
|
|
37
|
+
*/
|
|
38
|
+
function findMofloCli(projectRoot) {
|
|
39
|
+
const candidates = [
|
|
40
|
+
// Installed as dependency
|
|
41
|
+
join(projectRoot, 'node_modules', 'moflo', 'bin', 'cli.js'),
|
|
42
|
+
// Running from moflo repo itself
|
|
43
|
+
join(projectRoot, 'bin', 'cli.js'),
|
|
44
|
+
];
|
|
45
|
+
for (const candidate of candidates) {
|
|
46
|
+
if (existsSync(candidate))
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
23
51
|
/**
|
|
24
52
|
* Generate MCP configuration
|
|
25
53
|
*/
|
|
@@ -32,24 +60,32 @@ export function generateMCPConfig(options) {
|
|
|
32
60
|
// When toolDefer is true, emit "deferred" so Claude Code loads schemas on
|
|
33
61
|
// demand via ToolSearch instead of putting 150+ schemas into context at startup.
|
|
34
62
|
const deferProps = config.toolDefer ? { toolDefer: 'deferred' } : {};
|
|
35
|
-
|
|
63
|
+
const mcpEnv = {
|
|
64
|
+
...npmEnv,
|
|
65
|
+
CLAUDE_FLOW_MODE: 'v3',
|
|
66
|
+
CLAUDE_FLOW_HOOKS_ENABLED: 'true',
|
|
67
|
+
CLAUDE_FLOW_TOPOLOGY: options.runtime.topology,
|
|
68
|
+
CLAUDE_FLOW_MAX_AGENTS: String(options.runtime.maxAgents),
|
|
69
|
+
CLAUDE_FLOW_MEMORY_BACKEND: options.runtime.memoryBackend,
|
|
70
|
+
};
|
|
71
|
+
// Claude Flow MCP server (core) — use direct node when locally installed
|
|
36
72
|
if (config.claudeFlow) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
73
|
+
const projectRoot = options.targetDir ?? process.cwd();
|
|
74
|
+
const localCli = findMofloCli(projectRoot);
|
|
75
|
+
if (localCli) {
|
|
76
|
+
mcpServers['claude-flow'] = createDirectServerEntry(localCli, ['mcp', 'start'], mcpEnv, { autoStart: config.autoStart, ...deferProps });
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
mcpServers['claude-flow'] = createNpxServerEntry(['moflo', 'mcp', 'start'], mcpEnv, { autoStart: config.autoStart, ...deferProps });
|
|
80
|
+
}
|
|
45
81
|
}
|
|
46
|
-
// Ruv-Swarm MCP server (enhanced coordination)
|
|
82
|
+
// Ruv-Swarm MCP server (enhanced coordination) — always npx (external package)
|
|
47
83
|
if (config.ruvSwarm) {
|
|
48
|
-
mcpServers['ruv-swarm'] =
|
|
84
|
+
mcpServers['ruv-swarm'] = createNpxServerEntry(['ruv-swarm', 'mcp', 'start'], { ...npmEnv }, { optional: true, ...deferProps });
|
|
49
85
|
}
|
|
50
|
-
// Flow Nexus MCP server (cloud features)
|
|
86
|
+
// Flow Nexus MCP server (cloud features) — always npx (external package)
|
|
51
87
|
if (config.flowNexus) {
|
|
52
|
-
mcpServers['flow-nexus'] =
|
|
88
|
+
mcpServers['flow-nexus'] = createNpxServerEntry(['flow-nexus@latest', 'mcp', 'start'], { ...npmEnv }, { optional: true, requiresAuth: true, ...deferProps });
|
|
53
89
|
}
|
|
54
90
|
return { mcpServers };
|
|
55
91
|
}
|
|
@@ -67,7 +103,14 @@ export function generateMCPCommands(options) {
|
|
|
67
103
|
const commands = [];
|
|
68
104
|
const config = options.mcp;
|
|
69
105
|
if (config.claudeFlow) {
|
|
70
|
-
|
|
106
|
+
const projectRoot = options.targetDir ?? process.cwd();
|
|
107
|
+
const localCli = findMofloCli(projectRoot);
|
|
108
|
+
if (localCli) {
|
|
109
|
+
commands.push(`claude mcp add claude-flow -- node ${localCli} mcp start`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
commands.push('claude mcp add claude-flow -- npx -y moflo mcp start');
|
|
113
|
+
}
|
|
71
114
|
}
|
|
72
115
|
if (config.ruvSwarm) {
|
|
73
116
|
commands.push('claude mcp add ruv-swarm -- npx -y ruv-swarm mcp start');
|
|
@@ -66,8 +66,6 @@ export interface SkillsConfig {
|
|
|
66
66
|
browser: boolean;
|
|
67
67
|
/** Include V3 implementation skills */
|
|
68
68
|
v3: boolean;
|
|
69
|
-
/** Include dual-mode skills (Claude Code + Codex hybrid) */
|
|
70
|
-
dualMode: boolean;
|
|
71
69
|
/** Include all available skills */
|
|
72
70
|
all: boolean;
|
|
73
71
|
}
|
|
@@ -118,8 +116,6 @@ export interface AgentsConfig {
|
|
|
118
116
|
optimization: boolean;
|
|
119
117
|
/** Include testing agents */
|
|
120
118
|
testing: boolean;
|
|
121
|
-
/** Include dual-mode agents (Claude Code + Codex hybrid) */
|
|
122
|
-
dualMode: boolean;
|
|
123
119
|
/** Include all agents */
|
|
124
120
|
all: boolean;
|
|
125
121
|
}
|
|
@@ -75,7 +75,6 @@ export const DEFAULT_INIT_OPTIONS = {
|
|
|
75
75
|
flowNexus: false,
|
|
76
76
|
browser: true,
|
|
77
77
|
v3: true,
|
|
78
|
-
dualMode: false, // Optional: enable with --dual flag
|
|
79
78
|
all: false,
|
|
80
79
|
},
|
|
81
80
|
commands: {
|
|
@@ -100,7 +99,6 @@ export const DEFAULT_INIT_OPTIONS = {
|
|
|
100
99
|
v3: true,
|
|
101
100
|
optimization: true,
|
|
102
101
|
testing: true,
|
|
103
|
-
dualMode: false, // Optional: enable with --dual flag
|
|
104
102
|
all: true,
|
|
105
103
|
},
|
|
106
104
|
statusline: {
|
|
@@ -169,7 +167,6 @@ export const MINIMAL_INIT_OPTIONS = {
|
|
|
169
167
|
flowNexus: false,
|
|
170
168
|
browser: false,
|
|
171
169
|
v3: false,
|
|
172
|
-
dualMode: false,
|
|
173
170
|
all: false,
|
|
174
171
|
},
|
|
175
172
|
agents: {
|
|
@@ -183,7 +180,6 @@ export const MINIMAL_INIT_OPTIONS = {
|
|
|
183
180
|
v3: false,
|
|
184
181
|
optimization: false,
|
|
185
182
|
testing: false,
|
|
186
|
-
dualMode: false,
|
|
187
183
|
all: false,
|
|
188
184
|
},
|
|
189
185
|
runtime: {
|
|
@@ -229,7 +225,6 @@ export const FULL_INIT_OPTIONS = {
|
|
|
229
225
|
flowNexus: true,
|
|
230
226
|
browser: true,
|
|
231
227
|
v3: true,
|
|
232
|
-
dualMode: true, // Include in full init
|
|
233
228
|
all: true,
|
|
234
229
|
},
|
|
235
230
|
commands: {
|
|
@@ -319,13 +319,49 @@ export class MCPServerManager extends EventEmitter {
|
|
|
319
319
|
console.error(`[${new Date().toISOString()}] INFO [claude-flow-mcp] (${sessionId}) stdin closed, shutting down...`);
|
|
320
320
|
process.exit(0);
|
|
321
321
|
});
|
|
322
|
+
process.stdin.on('error', () => {
|
|
323
|
+
// stdin pipe broken — parent disconnected
|
|
324
|
+
process.exit(0);
|
|
325
|
+
});
|
|
326
|
+
// Orphan watchdog: on Windows, stdin 'end' doesn't always fire when the
|
|
327
|
+
// parent process disconnects. Poll the parent PID to detect orphaning and
|
|
328
|
+
// self-terminate. Also tracks stdin inactivity as a secondary signal.
|
|
329
|
+
const parentPid = process.ppid;
|
|
330
|
+
let lastStdinActivity = Date.now();
|
|
331
|
+
const WATCHDOG_INTERVAL_MS = 10_000; // Check every 10s
|
|
332
|
+
const STDIN_IDLE_TIMEOUT_MS = 5 * 60_000; // 5 min with no stdin = likely orphaned
|
|
333
|
+
// Track stdin activity
|
|
334
|
+
process.stdin.on('data', () => { lastStdinActivity = Date.now(); });
|
|
335
|
+
const watchdog = setInterval(() => {
|
|
336
|
+
// Check 1: Is parent process still alive?
|
|
337
|
+
if (parentPid) {
|
|
338
|
+
try {
|
|
339
|
+
process.kill(parentPid, 0); // signal 0 = existence check
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// Parent is gone — we're orphaned
|
|
343
|
+
console.error(`[${new Date().toISOString()}] INFO [claude-flow-mcp] (${sessionId}) Parent (PID ${parentPid}) gone, shutting down...`);
|
|
344
|
+
clearInterval(watchdog);
|
|
345
|
+
process.exit(0);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Check 2: Has stdin been idle too long?
|
|
349
|
+
if (Date.now() - lastStdinActivity > STDIN_IDLE_TIMEOUT_MS) {
|
|
350
|
+
console.error(`[${new Date().toISOString()}] INFO [claude-flow-mcp] (${sessionId}) No stdin activity for ${STDIN_IDLE_TIMEOUT_MS / 1000}s, shutting down...`);
|
|
351
|
+
clearInterval(watchdog);
|
|
352
|
+
process.exit(0);
|
|
353
|
+
}
|
|
354
|
+
}, WATCHDOG_INTERVAL_MS);
|
|
355
|
+
watchdog.unref(); // Don't keep process alive just for watchdog
|
|
322
356
|
// Handle process termination
|
|
323
357
|
process.on('SIGINT', () => {
|
|
324
358
|
console.error(`[${new Date().toISOString()}] INFO [claude-flow-mcp] (${sessionId}) Received SIGINT, shutting down...`);
|
|
359
|
+
clearInterval(watchdog);
|
|
325
360
|
process.exit(0);
|
|
326
361
|
});
|
|
327
362
|
process.on('SIGTERM', () => {
|
|
328
363
|
console.error(`[${new Date().toISOString()}] INFO [claude-flow-mcp] (${sessionId}) Received SIGTERM, shutting down...`);
|
|
364
|
+
clearInterval(watchdog);
|
|
329
365
|
process.exit(0);
|
|
330
366
|
});
|
|
331
367
|
// Mark as ready immediately for stdio
|
|
@@ -404,4 +404,10 @@ export declare function bridgeContextSynthesize(params: {
|
|
|
404
404
|
export declare function bridgeSemanticRoute(params: {
|
|
405
405
|
input: string;
|
|
406
406
|
}): Promise<any>;
|
|
407
|
+
/**
|
|
408
|
+
* Write vector-stats.json cache file used by the statusline.
|
|
409
|
+
* Synchronous — safe to call from short-lived CLI commands.
|
|
410
|
+
* Uses the already-initialized registry; no-ops if registry isn't loaded.
|
|
411
|
+
*/
|
|
412
|
+
export declare function refreshVectorStatsCache(dbPathOverride?: string): void;
|
|
407
413
|
//# sourceMappingURL=memory-bridge.d.ts.map
|
|
@@ -388,6 +388,9 @@ export async function bridgeStoreEntry(options) {
|
|
|
388
388
|
await cacheSet(registry, cacheKey, { id, key, namespace, content: value, embedding: embeddingJson });
|
|
389
389
|
// Phase 4: AttestationLog write audit
|
|
390
390
|
await logAttestation(registry, 'store', id, { key, namespace, hasEmbedding: !!embeddingJson });
|
|
391
|
+
// Update statusline vector stats cache (debounced)
|
|
392
|
+
if (embeddingJson)
|
|
393
|
+
refreshVectorStatsCache();
|
|
391
394
|
return {
|
|
392
395
|
success: true,
|
|
393
396
|
id,
|
|
@@ -690,6 +693,9 @@ export async function bridgeDeleteEntry(options) {
|
|
|
690
693
|
catch {
|
|
691
694
|
// Non-fatal
|
|
692
695
|
}
|
|
696
|
+
// Update statusline vector stats cache (debounced)
|
|
697
|
+
if (changes > 0)
|
|
698
|
+
refreshVectorStatsCache();
|
|
693
699
|
return {
|
|
694
700
|
success: true,
|
|
695
701
|
deleted: changes > 0,
|
|
@@ -1547,4 +1553,64 @@ function cosineSim(a, b) {
|
|
|
1547
1553
|
const mag = Math.sqrt(normA * normB);
|
|
1548
1554
|
return mag === 0 ? 0 : dot / mag;
|
|
1549
1555
|
}
|
|
1556
|
+
// ===== Vector stats cache for statusline =====
|
|
1557
|
+
// Written after memory mutations so the statusline can read stats without
|
|
1558
|
+
// spawning a subprocess. Debounced to avoid thrashing during batch operations.
|
|
1559
|
+
/**
|
|
1560
|
+
* Write vector-stats.json cache file used by the statusline.
|
|
1561
|
+
* Synchronous — safe to call from short-lived CLI commands.
|
|
1562
|
+
* Uses the already-initialized registry; no-ops if registry isn't loaded.
|
|
1563
|
+
*/
|
|
1564
|
+
export function refreshVectorStatsCache(dbPathOverride) {
|
|
1565
|
+
try {
|
|
1566
|
+
const registry = registryInstance; // Use existing instance only, don't init
|
|
1567
|
+
if (!registry)
|
|
1568
|
+
return;
|
|
1569
|
+
const ctx = getDb(registry);
|
|
1570
|
+
if (!ctx?.db)
|
|
1571
|
+
return;
|
|
1572
|
+
let vectorCount = 0;
|
|
1573
|
+
let namespaces = 0;
|
|
1574
|
+
let dbSizeKB = 0;
|
|
1575
|
+
let hasHnsw = false;
|
|
1576
|
+
try {
|
|
1577
|
+
const countRow = ctx.db.prepare('SELECT COUNT(*) as c FROM memory_entries WHERE status = ? AND embedding IS NOT NULL').get('active');
|
|
1578
|
+
vectorCount = countRow?.c ?? 0;
|
|
1579
|
+
const nsRow = ctx.db.prepare('SELECT COUNT(DISTINCT namespace) as n FROM memory_entries WHERE status = ?').get('active');
|
|
1580
|
+
namespaces = nsRow?.n ?? 0;
|
|
1581
|
+
}
|
|
1582
|
+
catch {
|
|
1583
|
+
// Table may not exist yet
|
|
1584
|
+
}
|
|
1585
|
+
// DB file size
|
|
1586
|
+
const dbFile = dbPathOverride || getDbPath();
|
|
1587
|
+
try {
|
|
1588
|
+
const stat = fs.statSync(dbFile);
|
|
1589
|
+
dbSizeKB = Math.floor(stat.size / 1024);
|
|
1590
|
+
}
|
|
1591
|
+
catch { /* file may not exist */ }
|
|
1592
|
+
// HNSW index presence
|
|
1593
|
+
const root = getProjectRoot();
|
|
1594
|
+
const hnswPaths = [
|
|
1595
|
+
path.join(root, '.swarm', 'hnsw.index'),
|
|
1596
|
+
path.join(root, '.claude-flow', 'hnsw.index'),
|
|
1597
|
+
];
|
|
1598
|
+
for (const p of hnswPaths) {
|
|
1599
|
+
try {
|
|
1600
|
+
fs.statSync(p);
|
|
1601
|
+
hasHnsw = true;
|
|
1602
|
+
break;
|
|
1603
|
+
}
|
|
1604
|
+
catch { /* nope */ }
|
|
1605
|
+
}
|
|
1606
|
+
// Write cache file
|
|
1607
|
+
const cacheDir = path.join(root, '.claude-flow');
|
|
1608
|
+
if (!fs.existsSync(cacheDir))
|
|
1609
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
1610
|
+
fs.writeFileSync(path.join(cacheDir, 'vector-stats.json'), JSON.stringify({ vectorCount, dbSizeKB, namespaces, hasHnsw, updatedAt: Date.now() }));
|
|
1611
|
+
}
|
|
1612
|
+
catch {
|
|
1613
|
+
// Non-fatal — statusline falls back to file size estimate
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1550
1616
|
//# sourceMappingURL=memory-bridge.js.map
|
|
@@ -10,6 +10,41 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
13
|
+
/**
|
|
14
|
+
* Write vector-stats.json cache for the statusline (no subprocess needed).
|
|
15
|
+
* Called after memory store/delete to keep the cache fresh.
|
|
16
|
+
* @param dbPath - path to the SQLite database file
|
|
17
|
+
* @param stats - optional exact counts from a db query already in progress
|
|
18
|
+
*/
|
|
19
|
+
function writeVectorStatsCache(dbPath, stats) {
|
|
20
|
+
try {
|
|
21
|
+
const fileStat = fs.statSync(dbPath);
|
|
22
|
+
const dbSizeKB = Math.floor(fileStat.size / 1024);
|
|
23
|
+
const vectorCount = stats?.vectorCount ?? 0;
|
|
24
|
+
const namespaces = stats?.namespaces ?? 0;
|
|
25
|
+
// Check HNSW index presence
|
|
26
|
+
const dbDir = path.dirname(dbPath);
|
|
27
|
+
const projectDir = path.dirname(dbDir); // .swarm -> project root
|
|
28
|
+
let hasHnsw = false;
|
|
29
|
+
for (const p of [
|
|
30
|
+
path.join(dbDir, 'hnsw.index'),
|
|
31
|
+
path.join(projectDir, '.claude-flow', 'hnsw.index'),
|
|
32
|
+
]) {
|
|
33
|
+
try {
|
|
34
|
+
fs.statSync(p);
|
|
35
|
+
hasHnsw = true;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
catch { /* nope */ }
|
|
39
|
+
}
|
|
40
|
+
// Write to .claude-flow dir next to the .swarm dir
|
|
41
|
+
const cacheDir = path.join(projectDir, '.claude-flow');
|
|
42
|
+
if (!fs.existsSync(cacheDir))
|
|
43
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
44
|
+
fs.writeFileSync(path.join(cacheDir, 'vector-stats.json'), JSON.stringify({ vectorCount, dbSizeKB, namespaces, hasHnsw, updatedAt: Date.now() }));
|
|
45
|
+
}
|
|
46
|
+
catch { /* Non-fatal */ }
|
|
47
|
+
}
|
|
13
48
|
// ADR-053: Lazy import of AgentDB v3 bridge
|
|
14
49
|
let _bridge;
|
|
15
50
|
async function getBridge() {
|
|
@@ -1747,8 +1782,13 @@ export async function storeEntry(options) {
|
|
|
1747
1782
|
const bridge = await getBridge();
|
|
1748
1783
|
if (bridge) {
|
|
1749
1784
|
const bridgeResult = await bridge.bridgeStoreEntry(options);
|
|
1750
|
-
if (bridgeResult)
|
|
1785
|
+
if (bridgeResult) {
|
|
1786
|
+
// Update statusline cache after successful bridge store
|
|
1787
|
+
const swarmDir = path.join(process.cwd(), '.swarm');
|
|
1788
|
+
const dbFile = options.dbPath || path.join(swarmDir, 'memory.db');
|
|
1789
|
+
writeVectorStatsCache(dbFile);
|
|
1751
1790
|
return bridgeResult;
|
|
1791
|
+
}
|
|
1752
1792
|
}
|
|
1753
1793
|
// Fallback: raw sql.js
|
|
1754
1794
|
const { key, value, namespace = 'default', generateEmbeddingFlag = true, tags = [], ttl, dbPath: customPath, upsert = false } = options;
|
|
@@ -1805,6 +1845,15 @@ export async function storeEntry(options) {
|
|
|
1805
1845
|
// Save
|
|
1806
1846
|
const data = db.export();
|
|
1807
1847
|
fs.writeFileSync(dbPath, Buffer.from(data));
|
|
1848
|
+
// Query exact stats while DB is still open
|
|
1849
|
+
let vecCount = 0, nsCount = 0;
|
|
1850
|
+
try {
|
|
1851
|
+
const vc = db.exec("SELECT COUNT(*) FROM memory_entries WHERE status='active' AND embedding IS NOT NULL");
|
|
1852
|
+
vecCount = vc[0]?.values?.[0]?.[0] ?? 0;
|
|
1853
|
+
const nc = db.exec("SELECT COUNT(DISTINCT namespace) FROM memory_entries WHERE status='active'");
|
|
1854
|
+
nsCount = nc[0]?.values?.[0]?.[0] ?? 0;
|
|
1855
|
+
}
|
|
1856
|
+
catch { /* table may not have status column in older DBs */ }
|
|
1808
1857
|
db.close();
|
|
1809
1858
|
// Add to HNSW index for faster future searches
|
|
1810
1859
|
if (embeddingJson) {
|
|
@@ -1816,6 +1865,8 @@ export async function storeEntry(options) {
|
|
|
1816
1865
|
content: value
|
|
1817
1866
|
});
|
|
1818
1867
|
}
|
|
1868
|
+
// Update statusline cache with exact counts
|
|
1869
|
+
writeVectorStatsCache(dbPath, { vectorCount: vecCount, namespaces: nsCount });
|
|
1819
1870
|
return {
|
|
1820
1871
|
success: true,
|
|
1821
1872
|
id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moflo/cli",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.8",
|
|
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",
|
|
@@ -81,18 +81,14 @@
|
|
|
81
81
|
"publish:all": "./scripts/publish.sh"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"typescript": "^5.3.0"
|
|
85
|
-
"vitest": "^4.0.16"
|
|
84
|
+
"typescript": "^5.3.0"
|
|
86
85
|
},
|
|
87
86
|
"dependencies": {
|
|
88
|
-
"@claude-flow/mcp": "^3.0.0-alpha.8",
|
|
89
|
-
"@claude-flow/shared": "^3.0.0-alpha.1",
|
|
90
87
|
"@noble/ed25519": "^2.1.0",
|
|
91
88
|
"semver": "^7.6.0"
|
|
92
89
|
},
|
|
93
90
|
"optionalDependencies": {
|
|
94
91
|
"@claude-flow/aidefence": "^3.0.2",
|
|
95
|
-
"@claude-flow/codex": "^3.0.0-alpha.8",
|
|
96
92
|
"@claude-flow/embeddings": "^3.0.0-alpha.12",
|
|
97
93
|
"@claude-flow/guidance": "^3.0.0-alpha.1",
|
|
98
94
|
"@claude-flow/memory": "^3.0.0-alpha.11",
|