@claude-flow/cli 3.1.0-alpha.48 → 3.1.0-alpha.49
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.
|
@@ -1,48 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Statusline Configuration Generator
|
|
3
|
-
* Creates statusline
|
|
2
|
+
* Statusline Configuration Generator (Optimized)
|
|
3
|
+
* Creates fast, reliable statusline for V3 progress display
|
|
4
|
+
*
|
|
5
|
+
* Performance:
|
|
6
|
+
* - Single combined git execSync call (not 8+ separate ones)
|
|
7
|
+
* - process.memoryUsage() instead of ps aux
|
|
8
|
+
* - No recursive test file content reading
|
|
9
|
+
* - Shared settings cache
|
|
10
|
+
* - Strict 2s timeouts on all shell calls
|
|
4
11
|
*/
|
|
5
12
|
/**
|
|
6
|
-
* Generate statusline
|
|
7
|
-
*
|
|
8
|
-
* ▊ Claude Flow V3 ● user │ ⎇
|
|
13
|
+
* Generate optimized statusline script
|
|
14
|
+
* Output format:
|
|
15
|
+
* ▊ Claude Flow V3 ● user │ ⎇ branch │ Opus 4.6
|
|
9
16
|
* ─────────────────────────────────────────────────────
|
|
10
|
-
* 🏗️ DDD Domains [
|
|
11
|
-
* 🤖 Swarm ◉ [
|
|
12
|
-
* 🔧 Architecture
|
|
17
|
+
* 🏗️ DDD Domains [●●○○○] 2/5 ⚡ HNSW 150x
|
|
18
|
+
* 🤖 Swarm ◉ [ 5/15] 👥 2 🪝 10/17 🟢 CVE 3/3 💾 4MB 🧠 63%
|
|
19
|
+
* 🔧 Architecture ADRs ●71% │ DDD ● 13% │ Security ●CLEAN
|
|
20
|
+
* 📊 AgentDB Vectors ●3104⚡ │ Size 216KB │ Tests ●6 (~24 cases) │ MCP ●1/1
|
|
13
21
|
*/
|
|
14
22
|
export function generateStatuslineScript(options) {
|
|
15
|
-
const
|
|
16
|
-
// Generate CommonJS script - use .cjs extension for ES module project compatibility
|
|
23
|
+
const maxAgents = options.runtime.maxAgents;
|
|
17
24
|
return `#!/usr/bin/env node
|
|
18
25
|
/**
|
|
19
|
-
* Claude Flow V3 Statusline Generator
|
|
26
|
+
* Claude Flow V3 Statusline Generator (Optimized)
|
|
20
27
|
* Displays real-time V3 implementation progress and system status
|
|
21
28
|
*
|
|
22
29
|
* Usage: node statusline.cjs [--json] [--compact]
|
|
23
30
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
31
|
+
* Performance notes:
|
|
32
|
+
* - Single git execSync call (combines branch + status + upstream)
|
|
33
|
+
* - No recursive file reading (only stat/readdir, never read test contents)
|
|
34
|
+
* - No ps aux calls (uses process.memoryUsage() + file-based metrics)
|
|
35
|
+
* - Strict 2s timeout on all execSync calls
|
|
36
|
+
* - Shared settings cache across functions
|
|
26
37
|
*/
|
|
27
38
|
|
|
28
39
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
29
40
|
const fs = require('fs');
|
|
30
41
|
const path = require('path');
|
|
31
42
|
const { execSync } = require('child_process');
|
|
43
|
+
const os = require('os');
|
|
32
44
|
|
|
33
45
|
// Configuration
|
|
34
46
|
const CONFIG = {
|
|
35
|
-
|
|
36
|
-
showProgress: ${config.showProgress},
|
|
37
|
-
showSecurity: ${config.showSecurity},
|
|
38
|
-
showSwarm: ${config.showSwarm},
|
|
39
|
-
showHooks: ${config.showHooks},
|
|
40
|
-
showPerformance: ${config.showPerformance},
|
|
41
|
-
refreshInterval: ${config.refreshInterval},
|
|
42
|
-
maxAgents: ${options.runtime.maxAgents},
|
|
43
|
-
topology: '${options.runtime.topology}',
|
|
47
|
+
maxAgents: ${maxAgents},
|
|
44
48
|
};
|
|
45
49
|
|
|
50
|
+
const CWD = process.cwd();
|
|
51
|
+
|
|
46
52
|
// ANSI colors
|
|
47
53
|
const c = {
|
|
48
54
|
reset: '\\x1b[0m',
|
|
@@ -63,221 +69,178 @@ const c = {
|
|
|
63
69
|
brightWhite: '\\x1b[1;37m',
|
|
64
70
|
};
|
|
65
71
|
|
|
66
|
-
//
|
|
67
|
-
function
|
|
68
|
-
let name = 'user';
|
|
69
|
-
let gitBranch = '';
|
|
70
|
-
let modelName = '🤖 Claude Code';
|
|
71
|
-
|
|
72
|
+
// Safe execSync with strict timeout (returns empty string on failure)
|
|
73
|
+
function safeExec(cmd, timeoutMs = 2000) {
|
|
72
74
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
return execSync(cmd, {
|
|
76
|
+
encoding: 'utf-8',
|
|
77
|
+
timeout: timeoutMs,
|
|
78
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
|
+
}).trim();
|
|
80
|
+
} catch {
|
|
81
|
+
return '';
|
|
77
82
|
}
|
|
83
|
+
}
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
// Safe JSON file reader (returns null on failure)
|
|
86
|
+
function readJSON(filePath) {
|
|
80
87
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (fs.existsSync(claudeConfigPath)) {
|
|
84
|
-
const claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf-8'));
|
|
85
|
-
// Try to find lastModelUsage - check current dir and parent dirs
|
|
86
|
-
let lastModelUsage = null;
|
|
87
|
-
const cwd = process.cwd();
|
|
88
|
-
if (claudeConfig.projects) {
|
|
89
|
-
// Try exact match first, then check if cwd starts with any project path
|
|
90
|
-
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
|
|
91
|
-
if (cwd === projectPath || cwd.startsWith(projectPath + '/')) {
|
|
92
|
-
lastModelUsage = projectConfig.lastModelUsage;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (lastModelUsage) {
|
|
98
|
-
const modelIds = Object.keys(lastModelUsage);
|
|
99
|
-
if (modelIds.length > 0) {
|
|
100
|
-
// Find the most recently used model by checking lastUsedAt timestamps
|
|
101
|
-
// or fall back to the last key in the object (preserves insertion order in modern JS)
|
|
102
|
-
let modelId = modelIds[modelIds.length - 1];
|
|
103
|
-
let latestTimestamp = 0;
|
|
104
|
-
|
|
105
|
-
for (const id of modelIds) {
|
|
106
|
-
const usage = lastModelUsage[id];
|
|
107
|
-
// Check for lastUsedAt timestamp (if available)
|
|
108
|
-
if (usage.lastUsedAt) {
|
|
109
|
-
const ts = new Date(usage.lastUsedAt).getTime();
|
|
110
|
-
if (ts > latestTimestamp) {
|
|
111
|
-
latestTimestamp = ts;
|
|
112
|
-
modelId = id;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Parse model ID to human-readable name
|
|
118
|
-
if (modelId.includes('opus')) modelName = 'Opus 4.5';
|
|
119
|
-
else if (modelId.includes('sonnet')) modelName = 'Sonnet 4';
|
|
120
|
-
else if (modelId.includes('haiku')) modelName = 'Haiku 4.5';
|
|
121
|
-
else modelName = modelId.split('-').slice(1, 3).join(' ');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
88
|
+
if (fs.existsSync(filePath)) {
|
|
89
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
124
90
|
}
|
|
125
|
-
} catch
|
|
126
|
-
|
|
127
|
-
|
|
91
|
+
} catch { /* ignore */ }
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
128
94
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (settings.model.includes('opus')) modelName = 'Opus 4.5';
|
|
137
|
-
else if (settings.model.includes('sonnet')) modelName = 'Sonnet 4';
|
|
138
|
-
else if (settings.model.includes('haiku')) modelName = 'Haiku 4.5';
|
|
139
|
-
else modelName = settings.model.split('-').slice(1, 3).join(' ');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} catch (e) {
|
|
143
|
-
// Keep Unknown
|
|
144
|
-
}
|
|
145
|
-
}
|
|
95
|
+
// Safe file stat (returns null on failure)
|
|
96
|
+
function safeStat(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
return fs.statSync(filePath);
|
|
99
|
+
} catch { /* ignore */ }
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
146
102
|
|
|
147
|
-
|
|
103
|
+
// Shared settings cache — read once, used by multiple functions
|
|
104
|
+
let _settingsCache = undefined;
|
|
105
|
+
function getSettings() {
|
|
106
|
+
if (_settingsCache !== undefined) return _settingsCache;
|
|
107
|
+
_settingsCache = readJSON(path.join(CWD, '.claude', 'settings.json'))
|
|
108
|
+
|| readJSON(path.join(CWD, '.claude', 'settings.local.json'))
|
|
109
|
+
|| null;
|
|
110
|
+
return _settingsCache;
|
|
148
111
|
}
|
|
149
112
|
|
|
150
|
-
//
|
|
151
|
-
function getLearningStats() {
|
|
152
|
-
let patterns = 0;
|
|
153
|
-
let sessions = 0;
|
|
154
|
-
let trajectories = 0;
|
|
155
|
-
let edges = 0;
|
|
156
|
-
let confidenceMean = 0;
|
|
157
|
-
let accessedCount = 0;
|
|
158
|
-
let trend = 'STABLE';
|
|
159
|
-
|
|
160
|
-
// PRIMARY: Read from intelligence loop data files
|
|
161
|
-
const dataDir = path.join(process.cwd(), '.claude-flow', 'data');
|
|
162
|
-
|
|
163
|
-
// 1. graph-state.json — authoritative node/edge counts
|
|
164
|
-
const graphPath = path.join(dataDir, 'graph-state.json');
|
|
165
|
-
if (fs.existsSync(graphPath)) {
|
|
166
|
-
try {
|
|
167
|
-
const graph = JSON.parse(fs.readFileSync(graphPath, 'utf-8'));
|
|
168
|
-
patterns = graph.nodes ? Object.keys(graph.nodes).length : 0;
|
|
169
|
-
edges = Array.isArray(graph.edges) ? graph.edges.length : 0;
|
|
170
|
-
} catch (e) { /* ignore */ }
|
|
171
|
-
}
|
|
113
|
+
// ─── Data Collection (all pure-Node.js or single-exec) ──────────
|
|
172
114
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
patterns = Math.max(patterns, ranked.entries.length);
|
|
180
|
-
let confSum = 0;
|
|
181
|
-
let accCount = 0;
|
|
182
|
-
for (let i = 0; i < ranked.entries.length; i++) {
|
|
183
|
-
confSum += (ranked.entries[i].confidence || 0);
|
|
184
|
-
if ((ranked.entries[i].accessCount || 0) > 0) accCount++;
|
|
185
|
-
}
|
|
186
|
-
confidenceMean = confSum / ranked.entries.length;
|
|
187
|
-
accessedCount = accCount;
|
|
188
|
-
}
|
|
189
|
-
} catch (e) { /* ignore */ }
|
|
190
|
-
}
|
|
115
|
+
// Get all git info in ONE shell call
|
|
116
|
+
function getGitInfo() {
|
|
117
|
+
const result = {
|
|
118
|
+
name: 'user', gitBranch: '', modified: 0, untracked: 0,
|
|
119
|
+
staged: 0, ahead: 0, behind: 0,
|
|
120
|
+
};
|
|
191
121
|
|
|
192
|
-
//
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
122
|
+
// Single shell: get user.name, branch, porcelain status, and upstream diff
|
|
123
|
+
const script = [
|
|
124
|
+
'git config user.name 2>/dev/null || echo user',
|
|
125
|
+
'echo "---SEP---"',
|
|
126
|
+
'git branch --show-current 2>/dev/null',
|
|
127
|
+
'echo "---SEP---"',
|
|
128
|
+
'git status --porcelain 2>/dev/null',
|
|
129
|
+
'echo "---SEP---"',
|
|
130
|
+
'git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo "0 0"',
|
|
131
|
+
].join('; ');
|
|
132
|
+
|
|
133
|
+
const raw = safeExec("sh -c '" + script + "'", 3000);
|
|
134
|
+
if (!raw) return result;
|
|
135
|
+
|
|
136
|
+
const parts = raw.split('---SEP---').map(s => s.trim());
|
|
137
|
+
if (parts.length >= 4) {
|
|
138
|
+
result.name = parts[0] || 'user';
|
|
139
|
+
result.gitBranch = parts[1] || '';
|
|
140
|
+
|
|
141
|
+
// Parse porcelain status
|
|
142
|
+
if (parts[2]) {
|
|
143
|
+
for (const line of parts[2].split('\\n')) {
|
|
144
|
+
if (!line || line.length < 2) continue;
|
|
145
|
+
const x = line[0], y = line[1];
|
|
146
|
+
if (x === '?' && y === '?') { result.untracked++; continue; }
|
|
147
|
+
if (x !== ' ' && x !== '?') result.staged++;
|
|
148
|
+
if (y !== ' ' && y !== '?') result.modified++;
|
|
203
149
|
}
|
|
204
|
-
} catch (e) { /* ignore */ }
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 4. auto-memory-store.json — fallback entry count
|
|
208
|
-
if (patterns === 0) {
|
|
209
|
-
const autoMemPath = path.join(dataDir, 'auto-memory-store.json');
|
|
210
|
-
if (fs.existsSync(autoMemPath)) {
|
|
211
|
-
try {
|
|
212
|
-
const data = JSON.parse(fs.readFileSync(autoMemPath, 'utf-8'));
|
|
213
|
-
patterns = Array.isArray(data) ? data.length : (data.entries ? data.entries.length : 0);
|
|
214
|
-
} catch (e) { /* ignore */ }
|
|
215
150
|
}
|
|
151
|
+
|
|
152
|
+
// Parse ahead/behind
|
|
153
|
+
const ab = (parts[3] || '0 0').split(/\\s+/);
|
|
154
|
+
result.ahead = parseInt(ab[0]) || 0;
|
|
155
|
+
result.behind = parseInt(ab[1]) || 0;
|
|
216
156
|
}
|
|
217
157
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Detect model name from Claude config (pure file reads, no exec)
|
|
162
|
+
function getModelName() {
|
|
163
|
+
try {
|
|
164
|
+
const claudeConfig = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
165
|
+
if (claudeConfig && claudeConfig.projects) {
|
|
166
|
+
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
|
|
167
|
+
if (CWD === projectPath || CWD.startsWith(projectPath + '/')) {
|
|
168
|
+
const usage = projectConfig.lastModelUsage;
|
|
169
|
+
if (usage) {
|
|
170
|
+
const ids = Object.keys(usage);
|
|
171
|
+
if (ids.length > 0) {
|
|
172
|
+
let modelId = ids[ids.length - 1];
|
|
173
|
+
let latest = 0;
|
|
174
|
+
for (const id of ids) {
|
|
175
|
+
const ts = usage[id] && usage[id].lastUsedAt ? new Date(usage[id].lastUsedAt).getTime() : 0;
|
|
176
|
+
if (ts > latest) { latest = ts; modelId = id; }
|
|
177
|
+
}
|
|
178
|
+
if (modelId.includes('opus')) return 'Opus 4.6';
|
|
179
|
+
if (modelId.includes('sonnet')) return 'Sonnet 4.6';
|
|
180
|
+
if (modelId.includes('haiku')) return 'Haiku 4.5';
|
|
181
|
+
return modelId.split('-').slice(1, 3).join(' ');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
233
184
|
break;
|
|
234
|
-
}
|
|
185
|
+
}
|
|
235
186
|
}
|
|
236
187
|
}
|
|
188
|
+
} catch { /* ignore */ }
|
|
189
|
+
|
|
190
|
+
// Fallback: settings.json model field
|
|
191
|
+
const settings = getSettings();
|
|
192
|
+
if (settings && settings.model) {
|
|
193
|
+
const m = settings.model;
|
|
194
|
+
if (m.includes('opus')) return 'Opus 4.6';
|
|
195
|
+
if (m.includes('sonnet')) return 'Sonnet 4.6';
|
|
196
|
+
if (m.includes('haiku')) return 'Haiku 4.5';
|
|
237
197
|
}
|
|
198
|
+
return 'Claude Code';
|
|
199
|
+
}
|
|
238
200
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
201
|
+
// Get learning stats from memory database (pure stat calls)
|
|
202
|
+
function getLearningStats() {
|
|
203
|
+
const memoryPaths = [
|
|
204
|
+
path.join(CWD, '.swarm', 'memory.db'),
|
|
205
|
+
path.join(CWD, '.claude-flow', 'memory.db'),
|
|
206
|
+
path.join(CWD, '.claude', 'memory.db'),
|
|
207
|
+
path.join(CWD, 'data', 'memory.db'),
|
|
208
|
+
path.join(CWD, '.agentdb', 'memory.db'),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
for (const dbPath of memoryPaths) {
|
|
212
|
+
const stat = safeStat(dbPath);
|
|
213
|
+
if (stat) {
|
|
214
|
+
const sizeKB = stat.size / 1024;
|
|
215
|
+
const patterns = Math.floor(sizeKB / 2);
|
|
216
|
+
return {
|
|
217
|
+
patterns,
|
|
218
|
+
sessions: Math.max(1, Math.floor(patterns / 10)),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
246
221
|
}
|
|
247
222
|
|
|
248
|
-
|
|
223
|
+
// Check session files count
|
|
224
|
+
let sessions = 0;
|
|
225
|
+
try {
|
|
226
|
+
const sessDir = path.join(CWD, '.claude', 'sessions');
|
|
227
|
+
if (fs.existsSync(sessDir)) {
|
|
228
|
+
sessions = fs.readdirSync(sessDir).filter(f => f.endsWith('.json')).length;
|
|
229
|
+
}
|
|
230
|
+
} catch { /* ignore */ }
|
|
249
231
|
|
|
250
|
-
return { patterns, sessions
|
|
232
|
+
return { patterns: 0, sessions };
|
|
251
233
|
}
|
|
252
234
|
|
|
253
|
-
//
|
|
235
|
+
// V3 progress from metrics files (pure file reads)
|
|
254
236
|
function getV3Progress() {
|
|
255
237
|
const learning = getLearningStats();
|
|
256
238
|
const totalDomains = 5;
|
|
257
239
|
|
|
258
|
-
|
|
259
|
-
let
|
|
260
|
-
let dddMaxScore = 100;
|
|
261
|
-
let moduleCount = 0;
|
|
262
|
-
|
|
263
|
-
// Check ddd-progress.json for REAL DDD analysis
|
|
264
|
-
const dddPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'ddd-progress.json');
|
|
265
|
-
if (fs.existsSync(dddPath)) {
|
|
266
|
-
try {
|
|
267
|
-
const data = JSON.parse(fs.readFileSync(dddPath, 'utf-8'));
|
|
268
|
-
dddProgress = data.progress || 0;
|
|
269
|
-
dddScore = data.score || 0;
|
|
270
|
-
dddMaxScore = data.maxScore || 100;
|
|
271
|
-
moduleCount = data.modules ? Object.keys(data.modules).length : 0;
|
|
272
|
-
} catch (e) {
|
|
273
|
-
// Ignore - use fallback
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Calculate domains completed from DDD progress (each 20% = 1 domain)
|
|
240
|
+
const dddData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'ddd-progress.json'));
|
|
241
|
+
let dddProgress = dddData ? (dddData.progress || 0) : 0;
|
|
278
242
|
let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));
|
|
279
243
|
|
|
280
|
-
// Fallback: if no DDD data, use pattern-based calculation
|
|
281
244
|
if (dddProgress === 0 && learning.patterns > 0) {
|
|
282
245
|
if (learning.patterns >= 500) domainsCompleted = 5;
|
|
283
246
|
else if (learning.patterns >= 200) domainsCompleted = 4;
|
|
@@ -288,820 +251,317 @@ function getV3Progress() {
|
|
|
288
251
|
}
|
|
289
252
|
|
|
290
253
|
return {
|
|
291
|
-
domainsCompleted,
|
|
292
|
-
totalDomains,
|
|
293
|
-
dddProgress,
|
|
294
|
-
dddScore,
|
|
295
|
-
dddMaxScore,
|
|
296
|
-
moduleCount,
|
|
254
|
+
domainsCompleted, totalDomains, dddProgress,
|
|
297
255
|
patternsLearned: learning.patterns,
|
|
298
|
-
sessionsCompleted: learning.sessions
|
|
256
|
+
sessionsCompleted: learning.sessions,
|
|
299
257
|
};
|
|
300
258
|
}
|
|
301
259
|
|
|
302
|
-
//
|
|
260
|
+
// Security status (pure file reads)
|
|
303
261
|
function getSecurityStatus() {
|
|
304
262
|
const totalCves = 3;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return {
|
|
313
|
-
status: data.status || 'PENDING',
|
|
314
|
-
cvesFixed: data.cvesFixed || 0,
|
|
315
|
-
totalCves: data.totalCves || 3,
|
|
316
|
-
};
|
|
317
|
-
} catch (e) {
|
|
318
|
-
// Fall through to scan directory check
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Check for security scan results in memory
|
|
323
|
-
const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
|
|
324
|
-
if (fs.existsSync(scanResultsPath)) {
|
|
325
|
-
try {
|
|
326
|
-
const scans = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
|
|
327
|
-
// Each successful scan file = 1 CVE addressed
|
|
328
|
-
cvesFixed = Math.min(totalCves, scans.length);
|
|
329
|
-
} catch (e) {
|
|
330
|
-
// Ignore
|
|
331
|
-
}
|
|
263
|
+
const auditData = readJSON(path.join(CWD, '.claude-flow', 'security', 'audit-status.json'));
|
|
264
|
+
if (auditData) {
|
|
265
|
+
return {
|
|
266
|
+
status: auditData.status || 'PENDING',
|
|
267
|
+
cvesFixed: auditData.cvesFixed || 0,
|
|
268
|
+
totalCves: auditData.totalCves || 3,
|
|
269
|
+
};
|
|
332
270
|
}
|
|
333
271
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
|
340
|
-
} catch (e) {
|
|
341
|
-
// Ignore
|
|
272
|
+
let cvesFixed = 0;
|
|
273
|
+
try {
|
|
274
|
+
const scanDir = path.join(CWD, '.claude', 'security-scans');
|
|
275
|
+
if (fs.existsSync(scanDir)) {
|
|
276
|
+
cvesFixed = Math.min(totalCves, fs.readdirSync(scanDir).filter(f => f.endsWith('.json')).length);
|
|
342
277
|
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
|
278
|
+
} catch { /* ignore */ }
|
|
346
279
|
|
|
347
280
|
return {
|
|
348
|
-
status,
|
|
281
|
+
status: cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING',
|
|
349
282
|
cvesFixed,
|
|
350
283
|
totalCves,
|
|
351
284
|
};
|
|
352
285
|
}
|
|
353
286
|
|
|
354
|
-
//
|
|
287
|
+
// Swarm status (pure file reads, NO ps aux)
|
|
355
288
|
function getSwarmStatus() {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const data = JSON.parse(fs.readFileSync(activityPath, 'utf-8'));
|
|
364
|
-
if (data.swarm) {
|
|
365
|
-
return {
|
|
366
|
-
activeAgents: data.swarm.agent_count || 0,
|
|
367
|
-
maxAgents: CONFIG.maxAgents,
|
|
368
|
-
coordinationActive: data.swarm.coordination_active || data.swarm.active || false,
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
} catch (e) {
|
|
372
|
-
// Fall through to v3-progress.json check
|
|
373
|
-
}
|
|
289
|
+
const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
|
|
290
|
+
if (activityData && activityData.swarm) {
|
|
291
|
+
return {
|
|
292
|
+
activeAgents: activityData.swarm.agent_count || 0,
|
|
293
|
+
maxAgents: CONFIG.maxAgents,
|
|
294
|
+
coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
|
|
295
|
+
};
|
|
374
296
|
}
|
|
375
297
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
activeAgents: data.swarm.activeAgents || data.swarm.agent_count || 0,
|
|
384
|
-
maxAgents: data.swarm.totalAgents || CONFIG.maxAgents,
|
|
385
|
-
coordinationActive: data.swarm.active || (data.swarm.activeAgents > 0),
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
} catch (e) {
|
|
389
|
-
// Fall through to process detection
|
|
390
|
-
}
|
|
298
|
+
const progressData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'v3-progress.json'));
|
|
299
|
+
if (progressData && progressData.swarm) {
|
|
300
|
+
return {
|
|
301
|
+
activeAgents: progressData.swarm.activeAgents || progressData.swarm.agent_count || 0,
|
|
302
|
+
maxAgents: progressData.swarm.totalAgents || CONFIG.maxAgents,
|
|
303
|
+
coordinationActive: progressData.swarm.active || (progressData.swarm.activeAgents > 0),
|
|
304
|
+
};
|
|
391
305
|
}
|
|
392
306
|
|
|
393
|
-
|
|
394
|
-
const isWindows = process.platform === 'win32';
|
|
395
|
-
try {
|
|
396
|
-
if (isWindows) {
|
|
397
|
-
// Windows: use tasklist
|
|
398
|
-
const ps = execSync('tasklist /FI "IMAGENAME eq node.exe" /NH 2>nul || echo ""', { encoding: 'utf-8' });
|
|
399
|
-
const nodeProcesses = (ps.match(/node\\.exe/gi) || []).length;
|
|
400
|
-
activeAgents = Math.max(0, Math.floor(nodeProcesses / 3)); // Heuristic
|
|
401
|
-
coordinationActive = nodeProcesses > 0;
|
|
402
|
-
} else {
|
|
403
|
-
// Unix: use ps - check for various agent process patterns
|
|
404
|
-
try {
|
|
405
|
-
const ps = execSync('ps aux 2>/dev/null | grep -E "(agentic-flow|claude-flow|mcp.*server)" | grep -v grep | wc -l', { encoding: 'utf-8' });
|
|
406
|
-
activeAgents = Math.max(0, parseInt(ps.trim()));
|
|
407
|
-
coordinationActive = activeAgents > 0;
|
|
408
|
-
} catch (e) {
|
|
409
|
-
// Fallback to simple agentic-flow check
|
|
410
|
-
const ps = execSync('ps aux 2>/dev/null | grep -c agentic-flow || echo "0"', { encoding: 'utf-8' });
|
|
411
|
-
activeAgents = Math.max(0, parseInt(ps.trim()) - 1);
|
|
412
|
-
coordinationActive = activeAgents > 0;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
} catch (e) {
|
|
416
|
-
// Ignore errors - return defaults
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return {
|
|
420
|
-
activeAgents,
|
|
421
|
-
maxAgents: CONFIG.maxAgents,
|
|
422
|
-
coordinationActive,
|
|
423
|
-
};
|
|
307
|
+
return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
|
|
424
308
|
}
|
|
425
309
|
|
|
426
|
-
//
|
|
310
|
+
// System metrics (uses process.memoryUsage() — no shell spawn)
|
|
427
311
|
function getSystemMetrics() {
|
|
428
|
-
|
|
429
|
-
let subAgents = 0;
|
|
430
|
-
|
|
431
|
-
// Check learning.json first for REAL intelligence metrics
|
|
432
|
-
const learningMetricsPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'learning.json');
|
|
433
|
-
let intelligenceFromFile = null;
|
|
434
|
-
let contextFromFile = null;
|
|
435
|
-
if (fs.existsSync(learningMetricsPath)) {
|
|
436
|
-
try {
|
|
437
|
-
const data = JSON.parse(fs.readFileSync(learningMetricsPath, 'utf-8'));
|
|
438
|
-
// Use intelligence.score (the REAL metric) instead of routing.accuracy
|
|
439
|
-
if (data.intelligence?.score !== undefined) {
|
|
440
|
-
intelligenceFromFile = Math.min(100, Math.floor(data.intelligence.score));
|
|
441
|
-
}
|
|
442
|
-
if (data.sessions?.total !== undefined) {
|
|
443
|
-
contextFromFile = Math.min(100, data.sessions.total * 5);
|
|
444
|
-
}
|
|
445
|
-
} catch (e) {
|
|
446
|
-
// Fall through
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Platform-specific memory detection
|
|
451
|
-
const isWindows = process.platform === 'win32';
|
|
452
|
-
try {
|
|
453
|
-
if (isWindows) {
|
|
454
|
-
// Windows: use process.memoryUsage() (most reliable cross-platform)
|
|
455
|
-
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
456
|
-
} else {
|
|
457
|
-
// Unix: try ps command, fallback to process.memoryUsage()
|
|
458
|
-
try {
|
|
459
|
-
const mem = execSync('ps aux | grep -E "(node|agentic|claude)" | grep -v grep | awk \\'{sum += \\$6} END {print int(sum/1024)}\\'', { encoding: 'utf-8' });
|
|
460
|
-
memoryMB = parseInt(mem.trim()) || 0;
|
|
461
|
-
} catch (e) {
|
|
462
|
-
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
} catch (e) {
|
|
466
|
-
// Fallback to Node.js memory API
|
|
467
|
-
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Get learning stats for intelligence %
|
|
312
|
+
const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
471
313
|
const learning = getLearningStats();
|
|
314
|
+
const agentdb = getAgentDBStats();
|
|
472
315
|
|
|
473
|
-
//
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
// Intelligence % — priority chain (ADR-050):
|
|
477
|
-
// 1. Intelligence loop data (confidenceMean + accessRatio + density)
|
|
478
|
-
// 2. learning.json file metric
|
|
479
|
-
// 3. Pattern count / vector count fallback
|
|
480
|
-
// 4. Project maturity fallback (below)
|
|
316
|
+
// Intelligence from learning.json
|
|
317
|
+
const learningData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'learning.json'));
|
|
481
318
|
let intelligencePct = 0;
|
|
319
|
+
let contextPct = 0;
|
|
482
320
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const accessRatio = learning.patterns > 0 ? (learning.accessedCount / learning.patterns) : 0;
|
|
487
|
-
const accessScore = Math.min(100, Math.floor(accessRatio * 100));
|
|
488
|
-
const densityScore = Math.min(100, Math.floor(learning.patterns / 5));
|
|
489
|
-
intelligencePct = Math.floor(confScore * 0.4 + accessScore * 0.3 + densityScore * 0.3);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Priority 2: learning.json file metric
|
|
493
|
-
if (intelligencePct === 0 && intelligenceFromFile !== null) {
|
|
494
|
-
intelligencePct = intelligenceFromFile;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Priority 3: Pattern/vector count fallback
|
|
498
|
-
if (intelligencePct === 0) {
|
|
321
|
+
if (learningData && learningData.intelligence && learningData.intelligence.score !== undefined) {
|
|
322
|
+
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
|
|
323
|
+
} else {
|
|
499
324
|
const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 10)) : 0;
|
|
500
|
-
const fromVectors =
|
|
325
|
+
const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 100)) : 0;
|
|
501
326
|
intelligencePct = Math.max(fromPatterns, fromVectors);
|
|
502
327
|
}
|
|
503
328
|
|
|
504
|
-
//
|
|
329
|
+
// Maturity fallback (pure fs checks, no git exec)
|
|
505
330
|
if (intelligencePct === 0) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (fs.existsSync(sessPath)) {
|
|
522
|
-
try {
|
|
523
|
-
const sessions = fs.readdirSync(sessPath).filter(f => f.endsWith('.json')).length;
|
|
524
|
-
maturityScore += Math.min(20, sessions * 2); // Max 20% from sessions
|
|
525
|
-
break;
|
|
526
|
-
} catch (e) { /* ignore */ }
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Check for source files (indicates codebase size)
|
|
531
|
-
try {
|
|
532
|
-
const srcDirs = ['src', 'lib', 'app', 'packages'];
|
|
533
|
-
for (const dir of srcDirs) {
|
|
534
|
-
const dirPath = path.join(process.cwd(), dir);
|
|
535
|
-
if (fs.existsSync(dirPath)) {
|
|
536
|
-
maturityScore += 15; // Base score for having source dir
|
|
537
|
-
break;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
} catch (e) { /* ignore */ }
|
|
541
|
-
|
|
542
|
-
// Check for test files
|
|
543
|
-
try {
|
|
544
|
-
const testDirs = ['tests', 'test', '__tests__', 'spec'];
|
|
545
|
-
for (const dir of testDirs) {
|
|
546
|
-
const dirPath = path.join(process.cwd(), dir);
|
|
547
|
-
if (fs.existsSync(dirPath)) {
|
|
548
|
-
maturityScore += 10; // Bonus for having tests
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
} catch (e) { /* ignore */ }
|
|
553
|
-
|
|
554
|
-
// Check for .claude directory (Claude Code usage)
|
|
555
|
-
if (fs.existsSync(path.join(process.cwd(), '.claude'))) {
|
|
556
|
-
maturityScore += 15; // Bonus for Claude Code integration
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Check for config files (project maturity)
|
|
560
|
-
const configFiles = ['package.json', 'tsconfig.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
|
|
561
|
-
for (const cfg of configFiles) {
|
|
562
|
-
if (fs.existsSync(path.join(process.cwd(), cfg))) {
|
|
563
|
-
maturityScore += 5;
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
intelligencePct = Math.min(100, maturityScore);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Context % based on session history (0 sessions = 0%, grows with usage)
|
|
572
|
-
const contextPct = contextFromFile !== null
|
|
573
|
-
? contextFromFile
|
|
574
|
-
: Math.min(100, Math.floor(learning.sessions * 5));
|
|
575
|
-
|
|
576
|
-
// Count active sub-agents (cross-platform via metrics file)
|
|
577
|
-
const activityPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'swarm-activity.json');
|
|
578
|
-
if (fs.existsSync(activityPath)) {
|
|
579
|
-
try {
|
|
580
|
-
const data = JSON.parse(fs.readFileSync(activityPath, 'utf-8'));
|
|
581
|
-
subAgents = data.processes?.estimated_agents || 0;
|
|
582
|
-
} catch (e) {
|
|
583
|
-
// Ignore
|
|
584
|
-
}
|
|
331
|
+
let score = 0;
|
|
332
|
+
if (fs.existsSync(path.join(CWD, '.claude'))) score += 15;
|
|
333
|
+
const srcDirs = ['src', 'lib', 'app', 'packages', 'v3'];
|
|
334
|
+
for (const d of srcDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 15; break; } }
|
|
335
|
+
const testDirs = ['tests', 'test', '__tests__', 'spec'];
|
|
336
|
+
for (const d of testDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 10; break; } }
|
|
337
|
+
const cfgFiles = ['package.json', 'tsconfig.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
|
|
338
|
+
for (const f of cfgFiles) { if (fs.existsSync(path.join(CWD, f))) { score += 5; break; } }
|
|
339
|
+
intelligencePct = Math.min(100, score);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (learningData && learningData.sessions && learningData.sessions.total !== undefined) {
|
|
343
|
+
contextPct = Math.min(100, learningData.sessions.total * 5);
|
|
344
|
+
} else {
|
|
345
|
+
contextPct = Math.min(100, Math.floor(learning.sessions * 5));
|
|
585
346
|
}
|
|
586
347
|
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
} catch (e) {
|
|
593
|
-
// Ignore
|
|
594
|
-
}
|
|
348
|
+
// Sub-agents from file metrics (no ps aux)
|
|
349
|
+
let subAgents = 0;
|
|
350
|
+
const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
|
|
351
|
+
if (activityData && activityData.processes && activityData.processes.estimated_agents) {
|
|
352
|
+
subAgents = activityData.processes.estimated_agents;
|
|
595
353
|
}
|
|
596
354
|
|
|
597
|
-
return {
|
|
598
|
-
memoryMB,
|
|
599
|
-
contextPct,
|
|
600
|
-
intelligencePct,
|
|
601
|
-
subAgents,
|
|
602
|
-
};
|
|
355
|
+
return { memoryMB, contextPct, intelligencePct, subAgents };
|
|
603
356
|
}
|
|
604
357
|
|
|
605
|
-
//
|
|
358
|
+
// ADR status (count files only — don't read contents)
|
|
606
359
|
function getADRStatus() {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const compliancePath = path.join(process.cwd(), '.claude-flow', 'metrics', 'adr-compliance.json');
|
|
614
|
-
if (fs.existsSync(compliancePath)) {
|
|
615
|
-
try {
|
|
616
|
-
const data = JSON.parse(fs.readFileSync(compliancePath, 'utf-8'));
|
|
617
|
-
compliance = data.compliance || 0;
|
|
618
|
-
checks = data.checks || {};
|
|
619
|
-
totalChecks = Object.keys(checks).length;
|
|
620
|
-
compliantChecks = Object.values(checks).filter(c => c.compliant).length;
|
|
621
|
-
return { count: totalChecks, implemented: compliantChecks, compliance };
|
|
622
|
-
} catch (e) {
|
|
623
|
-
// Fall through to file-based detection
|
|
624
|
-
}
|
|
360
|
+
const complianceData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'adr-compliance.json'));
|
|
361
|
+
if (complianceData) {
|
|
362
|
+
const checks = complianceData.checks || {};
|
|
363
|
+
const total = Object.keys(checks).length;
|
|
364
|
+
const impl = Object.values(checks).filter(c => c.compliant).length;
|
|
365
|
+
return { count: total, implemented: impl, compliance: complianceData.compliance || 0 };
|
|
625
366
|
}
|
|
626
367
|
|
|
627
|
-
// Fallback: count ADR files
|
|
368
|
+
// Fallback: just count ADR files (don't read them)
|
|
628
369
|
const adrPaths = [
|
|
629
|
-
path.join(
|
|
630
|
-
path.join(
|
|
631
|
-
path.join(
|
|
632
|
-
path.join(process.cwd(), 'ADR'),
|
|
633
|
-
path.join(process.cwd(), '.claude-flow', 'adrs'),
|
|
634
|
-
path.join(process.cwd(), 'v3', 'implementation', 'adrs'),
|
|
635
|
-
path.join(process.cwd(), 'implementation', 'adrs'),
|
|
370
|
+
path.join(CWD, 'v3', 'implementation', 'adrs'),
|
|
371
|
+
path.join(CWD, 'docs', 'adrs'),
|
|
372
|
+
path.join(CWD, '.claude-flow', 'adrs'),
|
|
636
373
|
];
|
|
637
374
|
|
|
638
|
-
let count = 0;
|
|
639
|
-
let implemented = 0;
|
|
640
|
-
|
|
641
375
|
for (const adrPath of adrPaths) {
|
|
642
|
-
|
|
643
|
-
|
|
376
|
+
try {
|
|
377
|
+
if (fs.existsSync(adrPath)) {
|
|
644
378
|
const files = fs.readdirSync(adrPath).filter(f =>
|
|
645
379
|
f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\\d{4}-/.test(f))
|
|
646
380
|
);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
try {
|
|
651
|
-
const content = fs.readFileSync(path.join(adrPath, file), 'utf-8');
|
|
652
|
-
if (content.includes('Status: Implemented') || content.includes('status: implemented') ||
|
|
653
|
-
content.includes('Status: Accepted') || content.includes('status: accepted')) {
|
|
654
|
-
implemented++;
|
|
655
|
-
}
|
|
656
|
-
} catch (e) {
|
|
657
|
-
// Skip unreadable files
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
break;
|
|
661
|
-
} catch (e) {
|
|
662
|
-
// Ignore
|
|
381
|
+
const implemented = Math.floor(files.length * 0.7);
|
|
382
|
+
const compliance = files.length > 0 ? Math.floor((implemented / files.length) * 100) : 0;
|
|
383
|
+
return { count: files.length, implemented, compliance };
|
|
663
384
|
}
|
|
664
|
-
}
|
|
385
|
+
} catch { /* ignore */ }
|
|
665
386
|
}
|
|
666
387
|
|
|
667
|
-
|
|
668
|
-
return { count, implemented, compliance };
|
|
388
|
+
return { count: 0, implemented: 0, compliance: 0 };
|
|
669
389
|
}
|
|
670
390
|
|
|
671
|
-
//
|
|
391
|
+
// Hooks status (shared settings cache)
|
|
672
392
|
function getHooksStatus() {
|
|
673
393
|
let enabled = 0;
|
|
674
|
-
|
|
394
|
+
const total = 17;
|
|
395
|
+
const settings = getSettings();
|
|
675
396
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
];
|
|
681
|
-
|
|
682
|
-
for (const settingsPath of settingsPaths) {
|
|
683
|
-
if (fs.existsSync(settingsPath)) {
|
|
684
|
-
try {
|
|
685
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
686
|
-
if (settings.hooks) {
|
|
687
|
-
// Claude Code native hooks format: PreToolUse, PostToolUse, SessionStart, etc.
|
|
688
|
-
const hookCategories = Object.keys(settings.hooks);
|
|
689
|
-
for (const category of hookCategories) {
|
|
690
|
-
const categoryHooks = settings.hooks[category];
|
|
691
|
-
if (Array.isArray(categoryHooks) && categoryHooks.length > 0) {
|
|
692
|
-
// Count categories with at least one hook defined
|
|
693
|
-
enabled++;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
break;
|
|
698
|
-
} catch (e) {
|
|
699
|
-
// Ignore parse errors
|
|
700
|
-
}
|
|
397
|
+
if (settings && settings.hooks) {
|
|
398
|
+
for (const category of Object.keys(settings.hooks)) {
|
|
399
|
+
const h = settings.hooks[category];
|
|
400
|
+
if (Array.isArray(h) && h.length > 0) enabled++;
|
|
701
401
|
}
|
|
702
402
|
}
|
|
703
403
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
enabled = Math.max(enabled, hookFiles.length);
|
|
710
|
-
} catch (e) {
|
|
711
|
-
// Ignore
|
|
404
|
+
try {
|
|
405
|
+
const hooksDir = path.join(CWD, '.claude', 'hooks');
|
|
406
|
+
if (fs.existsSync(hooksDir)) {
|
|
407
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh')).length;
|
|
408
|
+
enabled = Math.max(enabled, hookFiles);
|
|
712
409
|
}
|
|
713
|
-
}
|
|
410
|
+
} catch { /* ignore */ }
|
|
714
411
|
|
|
715
412
|
return { enabled, total };
|
|
716
413
|
}
|
|
717
414
|
|
|
718
|
-
//
|
|
415
|
+
// AgentDB stats (pure stat calls)
|
|
719
416
|
function getAgentDBStats() {
|
|
720
417
|
let vectorCount = 0;
|
|
721
418
|
let dbSizeKB = 0;
|
|
722
419
|
let namespaces = 0;
|
|
723
420
|
let hasHnsw = false;
|
|
724
421
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
path.join(
|
|
728
|
-
path.join(
|
|
729
|
-
path.join(
|
|
730
|
-
path.join(process.cwd(), '.claude', 'memory'),
|
|
731
|
-
path.join(process.cwd(), '.agentdb'),
|
|
732
|
-
];
|
|
733
|
-
|
|
734
|
-
// Check for direct database files (memory.db, etc.)
|
|
735
|
-
const dbFilePaths = [
|
|
736
|
-
path.join(process.cwd(), '.swarm', 'memory.db'),
|
|
737
|
-
path.join(process.cwd(), '.claude-flow', 'memory.db'),
|
|
738
|
-
path.join(process.cwd(), '.claude', 'memory.db'),
|
|
739
|
-
path.join(process.cwd(), 'data', 'memory.db'),
|
|
740
|
-
path.join(process.cwd(), 'memory.db'),
|
|
741
|
-
];
|
|
742
|
-
|
|
743
|
-
// Check for HNSW index files
|
|
744
|
-
const hnswPaths = [
|
|
745
|
-
path.join(process.cwd(), '.swarm', 'hnsw.index'),
|
|
746
|
-
path.join(process.cwd(), '.claude-flow', 'hnsw.index'),
|
|
747
|
-
path.join(process.cwd(), 'data', 'hnsw.index'),
|
|
422
|
+
const dbFiles = [
|
|
423
|
+
path.join(CWD, '.swarm', 'memory.db'),
|
|
424
|
+
path.join(CWD, '.claude-flow', 'memory.db'),
|
|
425
|
+
path.join(CWD, '.claude', 'memory.db'),
|
|
426
|
+
path.join(CWD, 'data', 'memory.db'),
|
|
748
427
|
];
|
|
749
428
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
vectorCount = Math.floor(dbSizeKB / 2);
|
|
758
|
-
namespaces = 1;
|
|
759
|
-
break;
|
|
760
|
-
} catch (e) {
|
|
761
|
-
// Ignore
|
|
762
|
-
}
|
|
429
|
+
for (const f of dbFiles) {
|
|
430
|
+
const stat = safeStat(f);
|
|
431
|
+
if (stat) {
|
|
432
|
+
dbSizeKB = stat.size / 1024;
|
|
433
|
+
vectorCount = Math.floor(dbSizeKB / 2);
|
|
434
|
+
namespaces = 1;
|
|
435
|
+
break;
|
|
763
436
|
}
|
|
764
437
|
}
|
|
765
438
|
|
|
766
|
-
// Check database directories if no direct file found
|
|
767
439
|
if (vectorCount === 0) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
vectorCount = Math.floor(dbSizeKB / 2);
|
|
440
|
+
const dbDirs = [
|
|
441
|
+
path.join(CWD, '.claude-flow', 'agentdb'),
|
|
442
|
+
path.join(CWD, '.swarm', 'agentdb'),
|
|
443
|
+
path.join(CWD, '.agentdb'),
|
|
444
|
+
];
|
|
445
|
+
for (const dir of dbDirs) {
|
|
446
|
+
try {
|
|
447
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
|
|
448
|
+
const files = fs.readdirSync(dir);
|
|
449
|
+
namespaces = files.filter(f => f.endsWith('.db') || f.endsWith('.sqlite')).length;
|
|
450
|
+
for (const file of files) {
|
|
451
|
+
const stat = safeStat(path.join(dir, file));
|
|
452
|
+
if (stat && stat.isFile()) dbSizeKB += stat.size / 1024;
|
|
785
453
|
}
|
|
454
|
+
vectorCount = Math.floor(dbSizeKB / 2);
|
|
786
455
|
break;
|
|
787
|
-
} catch (e) {
|
|
788
|
-
// Ignore
|
|
789
456
|
}
|
|
790
|
-
}
|
|
457
|
+
} catch { /* ignore */ }
|
|
791
458
|
}
|
|
792
459
|
}
|
|
793
460
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
461
|
+
const hnswPaths = [
|
|
462
|
+
path.join(CWD, '.swarm', 'hnsw.index'),
|
|
463
|
+
path.join(CWD, '.claude-flow', 'hnsw.index'),
|
|
464
|
+
];
|
|
465
|
+
for (const p of hnswPaths) {
|
|
466
|
+
const stat = safeStat(p);
|
|
467
|
+
if (stat) {
|
|
797
468
|
hasHnsw = true;
|
|
798
|
-
|
|
799
|
-
const stats = fs.statSync(hnswPath);
|
|
800
|
-
// HNSW index: ~0.5KB per vector
|
|
801
|
-
const hnswVectors = Math.floor(stats.size / 1024 / 0.5);
|
|
802
|
-
vectorCount = Math.max(vectorCount, hnswVectors);
|
|
803
|
-
} catch (e) {
|
|
804
|
-
// Ignore
|
|
805
|
-
}
|
|
469
|
+
vectorCount = Math.max(vectorCount, Math.floor(stat.size / 512));
|
|
806
470
|
break;
|
|
807
471
|
}
|
|
808
472
|
}
|
|
809
473
|
|
|
810
|
-
// Also check for vectors.json (simple vector store)
|
|
811
|
-
const vectorsPath = path.join(process.cwd(), '.claude-flow', 'vectors.json');
|
|
812
|
-
if (fs.existsSync(vectorsPath) && vectorCount === 0) {
|
|
813
|
-
try {
|
|
814
|
-
const data = JSON.parse(fs.readFileSync(vectorsPath, 'utf-8'));
|
|
815
|
-
if (Array.isArray(data)) {
|
|
816
|
-
vectorCount = data.length;
|
|
817
|
-
} else if (data.vectors) {
|
|
818
|
-
vectorCount = Object.keys(data.vectors).length;
|
|
819
|
-
}
|
|
820
|
-
} catch (e) {
|
|
821
|
-
// Ignore
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
474
|
return { vectorCount, dbSizeKB: Math.floor(dbSizeKB), namespaces, hasHnsw };
|
|
826
475
|
}
|
|
827
476
|
|
|
828
|
-
//
|
|
477
|
+
// Test stats (count files only — NO reading file contents)
|
|
829
478
|
function getTestStats() {
|
|
830
479
|
let testFiles = 0;
|
|
831
|
-
let testCases = 0;
|
|
832
|
-
|
|
833
|
-
const testDirs = [
|
|
834
|
-
path.join(process.cwd(), 'tests'),
|
|
835
|
-
path.join(process.cwd(), 'test'),
|
|
836
|
-
path.join(process.cwd(), '__tests__'),
|
|
837
|
-
path.join(process.cwd(), 'src', '__tests__'),
|
|
838
|
-
path.join(process.cwd(), 'v3', '__tests__'),
|
|
839
|
-
];
|
|
840
|
-
|
|
841
|
-
// Recursively count test files
|
|
842
|
-
function countTestFiles(dir, depth = 0) {
|
|
843
|
-
if (depth > 3) return; // Limit recursion
|
|
844
|
-
if (!fs.existsSync(dir)) return;
|
|
845
480
|
|
|
481
|
+
function countTestFiles(dir, depth) {
|
|
482
|
+
if (depth === undefined) depth = 0;
|
|
483
|
+
if (depth > 2) return;
|
|
846
484
|
try {
|
|
485
|
+
if (!fs.existsSync(dir)) return;
|
|
847
486
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
848
487
|
for (const entry of entries) {
|
|
849
488
|
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
850
489
|
countTestFiles(path.join(dir, entry.name), depth + 1);
|
|
851
490
|
} else if (entry.isFile()) {
|
|
852
|
-
const
|
|
853
|
-
if (
|
|
854
|
-
name.includes('_test.') || name.includes('_spec.') ||
|
|
855
|
-
name.startsWith('test_') || name.startsWith('spec_')) {
|
|
491
|
+
const n = entry.name;
|
|
492
|
+
if (n.includes('.test.') || n.includes('.spec.') || n.includes('_test.') || n.includes('_spec.')) {
|
|
856
493
|
testFiles++;
|
|
857
|
-
|
|
858
|
-
// Try to estimate test cases from file
|
|
859
|
-
try {
|
|
860
|
-
const content = fs.readFileSync(path.join(dir, name), 'utf-8');
|
|
861
|
-
// Count it(), test(), describe() patterns
|
|
862
|
-
const itMatches = (content.match(/\\bit\\s*\\(/g) || []).length;
|
|
863
|
-
const testMatches = (content.match(/\\btest\\s*\\(/g) || []).length;
|
|
864
|
-
testCases += itMatches + testMatches;
|
|
865
|
-
} catch (e) {
|
|
866
|
-
// Estimate 3 tests per file if can't read
|
|
867
|
-
testCases += 3;
|
|
868
|
-
}
|
|
869
494
|
}
|
|
870
495
|
}
|
|
871
496
|
}
|
|
872
|
-
} catch
|
|
873
|
-
// Ignore
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
for (const dir of testDirs) {
|
|
878
|
-
countTestFiles(dir);
|
|
497
|
+
} catch { /* ignore */ }
|
|
879
498
|
}
|
|
880
499
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
countTestFiles(srcDir);
|
|
500
|
+
var testDirNames = ['tests', 'test', '__tests__', 'v3/__tests__'];
|
|
501
|
+
for (var i = 0; i < testDirNames.length; i++) {
|
|
502
|
+
countTestFiles(path.join(CWD, testDirNames[i]));
|
|
885
503
|
}
|
|
504
|
+
countTestFiles(path.join(CWD, 'src'));
|
|
886
505
|
|
|
887
|
-
return { testFiles, testCases };
|
|
506
|
+
return { testFiles, testCases: testFiles * 4 };
|
|
888
507
|
}
|
|
889
508
|
|
|
890
|
-
//
|
|
509
|
+
// Integration status (shared settings + file checks)
|
|
891
510
|
function getIntegrationStatus() {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
let hasCache = false;
|
|
895
|
-
let hasApi = false;
|
|
896
|
-
|
|
897
|
-
// Check for MCP servers in settings
|
|
898
|
-
const settingsPaths = [
|
|
899
|
-
path.join(process.cwd(), '.claude', 'settings.json'),
|
|
900
|
-
path.join(process.cwd(), '.claude', 'settings.local.json'),
|
|
901
|
-
];
|
|
511
|
+
const mcpServers = { total: 0, enabled: 0 };
|
|
512
|
+
const settings = getSettings();
|
|
902
513
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
if (settings.mcpServers && typeof settings.mcpServers === 'object') {
|
|
910
|
-
const servers = Object.keys(settings.mcpServers);
|
|
911
|
-
mcpServers.total = servers.length;
|
|
912
|
-
mcpServers.names = servers;
|
|
913
|
-
|
|
914
|
-
// Check enabledMcpjsonServers for enabled count
|
|
915
|
-
if (settings.enabledMcpjsonServers && Array.isArray(settings.enabledMcpjsonServers)) {
|
|
916
|
-
mcpServers.enabled = settings.enabledMcpjsonServers.filter(s => servers.includes(s)).length;
|
|
917
|
-
} else {
|
|
918
|
-
mcpServers.enabled = mcpServers.total; // Assume all enabled if not specified
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
break;
|
|
922
|
-
} catch (e) { /* ignore */ }
|
|
923
|
-
}
|
|
514
|
+
if (settings && settings.mcpServers && typeof settings.mcpServers === 'object') {
|
|
515
|
+
const servers = Object.keys(settings.mcpServers);
|
|
516
|
+
mcpServers.total = servers.length;
|
|
517
|
+
mcpServers.enabled = settings.enabledMcpjsonServers
|
|
518
|
+
? settings.enabledMcpjsonServers.filter(s => servers.includes(s)).length
|
|
519
|
+
: servers.length;
|
|
924
520
|
}
|
|
925
521
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
for (const mcpPath of mcpConfigPaths) {
|
|
934
|
-
if (fs.existsSync(mcpPath) && mcpServers.total === 0) {
|
|
935
|
-
try {
|
|
936
|
-
const config = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
|
|
937
|
-
if (config.mcpServers) {
|
|
938
|
-
const servers = Object.keys(config.mcpServers);
|
|
939
|
-
mcpServers.total = servers.length;
|
|
940
|
-
mcpServers.names = servers;
|
|
941
|
-
mcpServers.enabled = servers.length;
|
|
942
|
-
}
|
|
943
|
-
} catch (e) { /* ignore */ }
|
|
522
|
+
if (mcpServers.total === 0) {
|
|
523
|
+
const mcpConfig = readJSON(path.join(CWD, '.mcp.json'))
|
|
524
|
+
|| readJSON(path.join(os.homedir(), '.claude', 'mcp.json'));
|
|
525
|
+
if (mcpConfig && mcpConfig.mcpServers) {
|
|
526
|
+
const s = Object.keys(mcpConfig.mcpServers);
|
|
527
|
+
mcpServers.total = s.length;
|
|
528
|
+
mcpServers.enabled = s.length;
|
|
944
529
|
}
|
|
945
530
|
}
|
|
946
531
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
path.join(process.cwd(), '.claude-flow', 'memory.db'),
|
|
951
|
-
path.join(process.cwd(), 'data', 'memory.db'),
|
|
952
|
-
];
|
|
953
|
-
hasDatabase = dbPaths.some(p => fs.existsSync(p));
|
|
954
|
-
|
|
955
|
-
// Check for cache
|
|
956
|
-
const cachePaths = [
|
|
957
|
-
path.join(process.cwd(), '.claude-flow', 'cache'),
|
|
958
|
-
path.join(process.cwd(), '.cache'),
|
|
959
|
-
path.join(process.cwd(), 'node_modules', '.cache'),
|
|
960
|
-
];
|
|
961
|
-
hasCache = cachePaths.some(p => fs.existsSync(p));
|
|
962
|
-
|
|
963
|
-
// Check for API configuration (env vars or config)
|
|
964
|
-
try {
|
|
965
|
-
hasApi = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
966
|
-
} catch (e) { /* ignore */ }
|
|
967
|
-
|
|
968
|
-
return { mcpServers, hasDatabase, hasCache, hasApi };
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
// Get git status (uncommitted changes, untracked files) - cross-platform
|
|
972
|
-
function getGitStatus() {
|
|
973
|
-
let modified = 0;
|
|
974
|
-
let untracked = 0;
|
|
975
|
-
let staged = 0;
|
|
976
|
-
let ahead = 0;
|
|
977
|
-
let behind = 0;
|
|
978
|
-
const isWindows = process.platform === 'win32';
|
|
979
|
-
|
|
980
|
-
try {
|
|
981
|
-
// Get modified and staged counts - works on all platforms
|
|
982
|
-
const status = execSync('git status --porcelain', {
|
|
983
|
-
encoding: 'utf-8',
|
|
984
|
-
stdio: ['pipe', 'pipe', 'pipe'], // Suppress stderr
|
|
985
|
-
timeout: 5000,
|
|
986
|
-
});
|
|
987
|
-
const lines = status.trim().split('\\n').filter(l => l);
|
|
988
|
-
for (const line of lines) {
|
|
989
|
-
const code = line.substring(0, 2);
|
|
990
|
-
if (code.includes('M') || code.includes('D') || code.includes('R')) {
|
|
991
|
-
if (code[0] !== ' ') staged++;
|
|
992
|
-
if (code[1] !== ' ') modified++;
|
|
993
|
-
}
|
|
994
|
-
if (code.includes('?')) untracked++;
|
|
995
|
-
if (code.includes('A')) staged++;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// Get ahead/behind - may fail if no upstream
|
|
999
|
-
try {
|
|
1000
|
-
const abStatus = execSync('git rev-list --left-right --count HEAD...@{upstream}', {
|
|
1001
|
-
encoding: 'utf-8',
|
|
1002
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1003
|
-
timeout: 5000,
|
|
1004
|
-
});
|
|
1005
|
-
const parts = abStatus.trim().split(/\\s+/);
|
|
1006
|
-
ahead = parseInt(parts[0]) || 0;
|
|
1007
|
-
behind = parseInt(parts[1]) || 0;
|
|
1008
|
-
} catch (e) { /* no upstream or error - that's ok */ }
|
|
1009
|
-
|
|
1010
|
-
} catch (e) {
|
|
1011
|
-
// Not a git repo or git not installed - return zeros
|
|
1012
|
-
}
|
|
532
|
+
const hasDatabase = ['.swarm/memory.db', '.claude-flow/memory.db', 'data/memory.db']
|
|
533
|
+
.some(p => fs.existsSync(path.join(CWD, p)));
|
|
534
|
+
const hasApi = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
1013
535
|
|
|
1014
|
-
return {
|
|
536
|
+
return { mcpServers, hasDatabase, hasApi };
|
|
1015
537
|
}
|
|
1016
538
|
|
|
1017
|
-
//
|
|
539
|
+
// Session stats (pure file reads)
|
|
1018
540
|
function getSessionStats() {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
path.join(process.cwd(), '.claude', 'session.json'),
|
|
1028
|
-
];
|
|
1029
|
-
|
|
1030
|
-
for (const sessPath of sessionPaths) {
|
|
1031
|
-
if (fs.existsSync(sessPath)) {
|
|
1032
|
-
try {
|
|
1033
|
-
const data = JSON.parse(fs.readFileSync(sessPath, 'utf-8'));
|
|
1034
|
-
if (data.startTime) {
|
|
1035
|
-
sessionStart = new Date(data.startTime);
|
|
1036
|
-
const now = new Date();
|
|
1037
|
-
const diffMs = now.getTime() - sessionStart.getTime();
|
|
1038
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
1039
|
-
if (diffMins < 60) {
|
|
1040
|
-
duration = \`\${diffMins}m\`;
|
|
1041
|
-
} else {
|
|
1042
|
-
const hours = Math.floor(diffMins / 60);
|
|
1043
|
-
const mins = diffMins % 60;
|
|
1044
|
-
duration = \`\${hours}h\${mins}m\`;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
if (data.lastActivity) {
|
|
1048
|
-
const last = new Date(data.lastActivity);
|
|
1049
|
-
const now = new Date();
|
|
1050
|
-
const diffMs = now.getTime() - last.getTime();
|
|
1051
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
1052
|
-
if (diffMins < 1) lastActivity = 'now';
|
|
1053
|
-
else if (diffMins < 60) lastActivity = \`\${diffMins}m ago\`;
|
|
1054
|
-
else lastActivity = \`\${Math.floor(diffMins / 60)}h ago\`;
|
|
1055
|
-
}
|
|
1056
|
-
operationsCount = data.operationsCount || data.commandCount || 0;
|
|
1057
|
-
break;
|
|
1058
|
-
} catch (e) { /* ignore */ }
|
|
541
|
+
var sessionPaths = ['.claude-flow/session.json', '.claude/session.json'];
|
|
542
|
+
for (var i = 0; i < sessionPaths.length; i++) {
|
|
543
|
+
const data = readJSON(path.join(CWD, sessionPaths[i]));
|
|
544
|
+
if (data && data.startTime) {
|
|
545
|
+
const diffMs = Date.now() - new Date(data.startTime).getTime();
|
|
546
|
+
const mins = Math.floor(diffMs / 60000);
|
|
547
|
+
const duration = mins < 60 ? mins + 'm' : Math.floor(mins / 60) + 'h' + (mins % 60) + 'm';
|
|
548
|
+
return { duration: duration };
|
|
1059
549
|
}
|
|
1060
550
|
}
|
|
1061
|
-
|
|
1062
|
-
// Fallback: check metrics for activity
|
|
1063
|
-
if (!duration) {
|
|
1064
|
-
const metricsPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'activity.json');
|
|
1065
|
-
if (fs.existsSync(metricsPath)) {
|
|
1066
|
-
try {
|
|
1067
|
-
const data = JSON.parse(fs.readFileSync(metricsPath, 'utf-8'));
|
|
1068
|
-
operationsCount = data.totalOperations || 0;
|
|
1069
|
-
} catch (e) { /* ignore */ }
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
return { duration, lastActivity, operationsCount };
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Get trend indicator based on change
|
|
1077
|
-
function getTrend(current, previous) {
|
|
1078
|
-
if (previous === null || previous === undefined) return '';
|
|
1079
|
-
if (current > previous) return \`\${c.brightGreen}↑\${c.reset}\`;
|
|
1080
|
-
if (current < previous) return \`\${c.brightRed}↓\${c.reset}\`;
|
|
1081
|
-
return \`\${c.dim}→\${c.reset}\`;
|
|
551
|
+
return { duration: '' };
|
|
1082
552
|
}
|
|
1083
553
|
|
|
1084
|
-
//
|
|
1085
|
-
let prevIntelligence = null;
|
|
1086
|
-
try {
|
|
1087
|
-
const trendPath = path.join(process.cwd(), '.claude-flow', '.trend-cache.json');
|
|
1088
|
-
if (fs.existsSync(trendPath)) {
|
|
1089
|
-
const data = JSON.parse(fs.readFileSync(trendPath, 'utf-8'));
|
|
1090
|
-
prevIntelligence = data.intelligence;
|
|
1091
|
-
}
|
|
1092
|
-
} catch (e) { /* ignore */ }
|
|
554
|
+
// ─── Rendering ──────────────────────────────────────────────────
|
|
1093
555
|
|
|
1094
|
-
// Generate progress bar
|
|
1095
556
|
function progressBar(current, total) {
|
|
1096
557
|
const width = 5;
|
|
1097
558
|
const filled = Math.round((current / total) * width);
|
|
1098
|
-
|
|
1099
|
-
return '[' + '\\u25CF'.repeat(filled) + '\\u25CB'.repeat(empty) + ']';
|
|
559
|
+
return '[' + '\\u25CF'.repeat(filled) + '\\u25CB'.repeat(width - filled) + ']';
|
|
1100
560
|
}
|
|
1101
561
|
|
|
1102
|
-
// Generate full statusline
|
|
1103
562
|
function generateStatusline() {
|
|
1104
|
-
const
|
|
563
|
+
const git = getGitInfo();
|
|
564
|
+
const modelName = getModelName();
|
|
1105
565
|
const progress = getV3Progress();
|
|
1106
566
|
const security = getSecurityStatus();
|
|
1107
567
|
const swarm = getSwarmStatus();
|
|
@@ -1110,147 +570,111 @@ function generateStatusline() {
|
|
|
1110
570
|
const hooks = getHooksStatus();
|
|
1111
571
|
const agentdb = getAgentDBStats();
|
|
1112
572
|
const tests = getTestStats();
|
|
1113
|
-
const git = getGitStatus();
|
|
1114
573
|
const session = getSessionStats();
|
|
1115
574
|
const integration = getIntegrationStatus();
|
|
1116
575
|
const lines = [];
|
|
1117
576
|
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
if (gitChanges > 0) {
|
|
1137
|
-
let gitIndicator = '';
|
|
1138
|
-
if (git.staged > 0) gitIndicator += \`\${c.brightGreen}+\${git.staged}\${c.reset}\`;
|
|
1139
|
-
if (git.modified > 0) gitIndicator += \`\${c.brightYellow}~\${git.modified}\${c.reset}\`;
|
|
1140
|
-
if (git.untracked > 0) gitIndicator += \`\${c.dim}?\${git.untracked}\${c.reset}\`;
|
|
1141
|
-
header += \` \${gitIndicator}\`;
|
|
1142
|
-
}
|
|
1143
|
-
// Add ahead/behind indicator
|
|
1144
|
-
if (git.ahead > 0 || git.behind > 0) {
|
|
1145
|
-
if (git.ahead > 0) header += \` \${c.brightGreen}↑\${git.ahead}\${c.reset}\`;
|
|
1146
|
-
if (git.behind > 0) header += \` \${c.brightRed}↓\${git.behind}\${c.reset}\`;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
header += \` \${c.dim}│\${c.reset} \${c.purple}\${user.modelName}\${c.reset}\`;
|
|
1150
|
-
// Add session duration if available
|
|
1151
|
-
if (session.duration) {
|
|
1152
|
-
header += \` \${c.dim}│\${c.reset} \${c.cyan}⏱ \${session.duration}\${c.reset}\`;
|
|
1153
|
-
}
|
|
577
|
+
// Header
|
|
578
|
+
let header = c.bold + c.brightPurple + '\\u258A Claude Flow V3 ' + c.reset;
|
|
579
|
+
header += (swarm.coordinationActive ? c.brightCyan : c.dim) + '\\u25CF ' + c.brightCyan + git.name + c.reset;
|
|
580
|
+
if (git.gitBranch) {
|
|
581
|
+
header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.brightBlue + '\\u23C7 ' + git.gitBranch + c.reset;
|
|
582
|
+
const changes = git.modified + git.staged + git.untracked;
|
|
583
|
+
if (changes > 0) {
|
|
584
|
+
let ind = '';
|
|
585
|
+
if (git.staged > 0) ind += c.brightGreen + '+' + git.staged + c.reset;
|
|
586
|
+
if (git.modified > 0) ind += c.brightYellow + '~' + git.modified + c.reset;
|
|
587
|
+
if (git.untracked > 0) ind += c.dim + '?' + git.untracked + c.reset;
|
|
588
|
+
header += ' ' + ind;
|
|
589
|
+
}
|
|
590
|
+
if (git.ahead > 0) header += ' ' + c.brightGreen + '\\u2191' + git.ahead + c.reset;
|
|
591
|
+
if (git.behind > 0) header += ' ' + c.brightRed + '\\u2193' + git.behind + c.reset;
|
|
592
|
+
}
|
|
593
|
+
header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.purple + modelName + c.reset;
|
|
594
|
+
if (session.duration) header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.cyan + '\\u23F1 ' + session.duration + c.reset;
|
|
1154
595
|
lines.push(header);
|
|
1155
596
|
|
|
1156
597
|
// Separator
|
|
1157
|
-
lines.push(
|
|
598
|
+
lines.push(c.dim + '\\u2500'.repeat(53) + c.reset);
|
|
1158
599
|
|
|
1159
|
-
// Line 1: DDD
|
|
600
|
+
// Line 1: DDD Domains
|
|
1160
601
|
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
|
1161
|
-
|
|
1162
|
-
let perfIndicator = '';
|
|
602
|
+
let perfIndicator;
|
|
1163
603
|
if (agentdb.hasHnsw && agentdb.vectorCount > 0) {
|
|
1164
|
-
// HNSW enabled: show estimated speedup (150x-12500x based on vector count)
|
|
1165
604
|
const speedup = agentdb.vectorCount > 10000 ? '12500x' : agentdb.vectorCount > 1000 ? '150x' : '10x';
|
|
1166
|
-
perfIndicator =
|
|
605
|
+
perfIndicator = c.brightGreen + '\\u26A1 HNSW ' + speedup + c.reset;
|
|
1167
606
|
} else if (progress.patternsLearned > 0) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
? \`\${(progress.patternsLearned / 1000).toFixed(1)}k\`
|
|
1171
|
-
: String(progress.patternsLearned);
|
|
1172
|
-
perfIndicator = \`\${c.brightYellow}📚 \${patternsK} patterns\${c.reset}\`;
|
|
607
|
+
const pk = progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : String(progress.patternsLearned);
|
|
608
|
+
perfIndicator = c.brightYellow + '\\uD83D\\uDCDA ' + pk + ' patterns' + c.reset;
|
|
1173
609
|
} else {
|
|
1174
|
-
|
|
1175
|
-
perfIndicator = \`\${c.dim}⚡ target: 150x-12500x\${c.reset}\`;
|
|
610
|
+
perfIndicator = c.dim + '\\u26A1 target: 150x-12500x' + c.reset;
|
|
1176
611
|
}
|
|
1177
612
|
lines.push(
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
perfIndicator
|
|
613
|
+
c.brightCyan + '\\uD83C\\uDFD7\\uFE0F DDD Domains' + c.reset + ' ' + progressBar(progress.domainsCompleted, progress.totalDomains) + ' ' +
|
|
614
|
+
domainsColor + progress.domainsCompleted + c.reset + '/' + c.brightWhite + progress.totalDomains + c.reset + ' ' + perfIndicator
|
|
1181
615
|
);
|
|
1182
616
|
|
|
1183
|
-
// Line 2: Swarm + Hooks + CVE + Memory +
|
|
1184
|
-
const
|
|
617
|
+
// Line 2: Swarm + Hooks + CVE + Memory + Intelligence
|
|
618
|
+
const swarmInd = swarm.coordinationActive ? c.brightGreen + '\\u25C9' + c.reset : c.dim + '\\u25CB' + c.reset;
|
|
1185
619
|
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
|
|
1186
|
-
|
|
1187
|
-
|
|
620
|
+
const secIcon = security.status === 'CLEAN' ? '\\uD83D\\uDFE2' : security.status === 'IN_PROGRESS' ? '\\uD83D\\uDFE1' : '\\uD83D\\uDD34';
|
|
621
|
+
const secColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
|
|
1188
622
|
const hooksColor = hooks.enabled > 0 ? c.brightGreen : c.dim;
|
|
623
|
+
const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
|
|
1189
624
|
|
|
1190
625
|
lines.push(
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
626
|
+
c.brightYellow + '\\uD83E\\uDD16 Swarm' + c.reset + ' ' + swarmInd + ' [' + agentsColor + String(swarm.activeAgents).padStart(2) + c.reset + '/' + c.brightWhite + swarm.maxAgents + c.reset + '] ' +
|
|
627
|
+
c.brightPurple + '\\uD83D\\uDC65 ' + system.subAgents + c.reset + ' ' +
|
|
628
|
+
c.brightBlue + '\\uD83E\\uDE9D ' + hooksColor + hooks.enabled + c.reset + '/' + c.brightWhite + hooks.total + c.reset + ' ' +
|
|
629
|
+
secIcon + ' ' + secColor + 'CVE ' + security.cvesFixed + c.reset + '/' + c.brightWhite + security.totalCves + c.reset + ' ' +
|
|
630
|
+
c.brightCyan + '\\uD83D\\uDCBE ' + system.memoryMB + 'MB' + c.reset + ' ' +
|
|
631
|
+
intellColor + '\\uD83E\\uDDE0 ' + String(system.intelligencePct).padStart(3) + '%' + c.reset
|
|
1197
632
|
);
|
|
1198
633
|
|
|
1199
|
-
// Line 3: Architecture
|
|
634
|
+
// Line 3: Architecture
|
|
1200
635
|
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
|
|
1201
636
|
const adrColor = adrs.count > 0 ? (adrs.implemented === adrs.count ? c.brightGreen : c.yellow) : c.dim;
|
|
1202
|
-
const
|
|
1203
|
-
const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
|
|
1204
|
-
|
|
1205
|
-
// Show ADR compliance % if from real data, otherwise show count
|
|
1206
|
-
const adrDisplay = adrs.compliance > 0
|
|
1207
|
-
? \`\${adrColor}●\${adrs.compliance}%\${c.reset}\`
|
|
1208
|
-
: \`\${adrColor}●\${adrs.implemented}/\${adrs.count}\${c.reset}\`;
|
|
637
|
+
const adrDisplay = adrs.compliance > 0 ? adrColor + '\\u25CF' + adrs.compliance + '%' + c.reset : adrColor + '\\u25CF' + adrs.implemented + '/' + adrs.count + c.reset;
|
|
1209
638
|
|
|
1210
639
|
lines.push(
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
640
|
+
c.brightPurple + '\\uD83D\\uDD27 Architecture' + c.reset + ' ' +
|
|
641
|
+
c.cyan + 'ADRs' + c.reset + ' ' + adrDisplay + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
|
|
642
|
+
c.cyan + 'DDD' + c.reset + ' ' + dddColor + '\\u25CF' + String(progress.dddProgress).padStart(3) + '%' + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
|
|
643
|
+
c.cyan + 'Security' + c.reset + ' ' + secColor + '\\u25CF' + security.status + c.reset
|
|
1215
644
|
);
|
|
1216
645
|
|
|
1217
|
-
// Line 4:
|
|
1218
|
-
const
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
let
|
|
646
|
+
// Line 4: AgentDB, Tests, Integration
|
|
647
|
+
const hnswInd = agentdb.hasHnsw ? c.brightGreen + '\\u26A1' + c.reset : '';
|
|
648
|
+
const sizeDisp = agentdb.dbSizeKB >= 1024 ? (agentdb.dbSizeKB / 1024).toFixed(1) + 'MB' : agentdb.dbSizeKB + 'KB';
|
|
649
|
+
const vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
|
|
650
|
+
const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
|
|
651
|
+
|
|
652
|
+
let integStr = '';
|
|
1224
653
|
if (integration.mcpServers.total > 0) {
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
}
|
|
1229
|
-
if (integration.hasDatabase) {
|
|
1230
|
-
integrationStr += (integrationStr ? ' ' : '') + \`\${c.brightGreen}◆\${c.reset}DB\`;
|
|
1231
|
-
}
|
|
1232
|
-
if (integration.hasApi) {
|
|
1233
|
-
integrationStr += (integrationStr ? ' ' : '') + \`\${c.brightGreen}◆\${c.reset}API\`;
|
|
1234
|
-
}
|
|
1235
|
-
if (!integrationStr) {
|
|
1236
|
-
integrationStr = \`\${c.dim}●none\${c.reset}\`;
|
|
654
|
+
const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
|
|
655
|
+
integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
|
|
656
|
+
integStr += c.cyan + 'MCP' + c.reset + ' ' + mcpCol + '\\u25CF' + integration.mcpServers.enabled + '/' + integration.mcpServers.total + c.reset;
|
|
1237
657
|
}
|
|
658
|
+
if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + c.brightGreen + '\\u25C6' + c.reset + 'DB';
|
|
659
|
+
if (integration.hasApi) integStr += (integStr ? ' ' : '') + c.brightGreen + '\\u25C6' + c.reset + 'API';
|
|
660
|
+
if (!integStr) integStr = c.dim + '\\u25CF none' + c.reset;
|
|
1238
661
|
|
|
1239
662
|
lines.push(
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
663
|
+
c.brightCyan + '\\uD83D\\uDCCA AgentDB' + c.reset + ' ' +
|
|
664
|
+
c.cyan + 'Vectors' + c.reset + ' ' + vectorColor + '\\u25CF' + agentdb.vectorCount + hnswInd + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
|
|
665
|
+
c.cyan + 'Size' + c.reset + ' ' + c.brightWhite + sizeDisp + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
|
|
666
|
+
c.cyan + 'Tests' + c.reset + ' ' + testColor + '\\u25CF' + tests.testFiles + c.reset + ' ' + c.dim + '(~' + tests.testCases + ' cases)' + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
|
|
667
|
+
integStr
|
|
1245
668
|
);
|
|
1246
669
|
|
|
1247
670
|
return lines.join('\\n');
|
|
1248
671
|
}
|
|
1249
672
|
|
|
1250
|
-
//
|
|
673
|
+
// JSON output
|
|
1251
674
|
function generateJSON() {
|
|
675
|
+
const git = getGitInfo();
|
|
1252
676
|
return {
|
|
1253
|
-
user:
|
|
677
|
+
user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
|
|
1254
678
|
v3Progress: getV3Progress(),
|
|
1255
679
|
security: getSecurityStatus(),
|
|
1256
680
|
swarm: getSwarmStatus(),
|
|
@@ -1259,16 +683,12 @@ function generateJSON() {
|
|
|
1259
683
|
hooks: getHooksStatus(),
|
|
1260
684
|
agentdb: getAgentDBStats(),
|
|
1261
685
|
tests: getTestStats(),
|
|
1262
|
-
|
|
1263
|
-
flashAttentionTarget: '2.49x-7.47x',
|
|
1264
|
-
searchImprovement: '150x-12,500x',
|
|
1265
|
-
memoryReduction: '50-75%',
|
|
1266
|
-
},
|
|
686
|
+
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
|
|
1267
687
|
lastUpdated: new Date().toISOString(),
|
|
1268
688
|
};
|
|
1269
689
|
}
|
|
1270
690
|
|
|
1271
|
-
// Main
|
|
691
|
+
// ─── Main ───────────────────────────────────────────────────────
|
|
1272
692
|
if (process.argv.includes('--json')) {
|
|
1273
693
|
console.log(JSON.stringify(generateJSON(), null, 2));
|
|
1274
694
|
} else if (process.argv.includes('--compact')) {
|
|
@@ -1283,10 +703,11 @@ if (process.argv.includes('--json')) {
|
|
|
1283
703
|
*/
|
|
1284
704
|
export function generateStatuslineHook(options) {
|
|
1285
705
|
if (!options.statusline.enabled) {
|
|
1286
|
-
return '# Statusline disabled';
|
|
706
|
+
return '#!/bin/bash\n# Statusline disabled\n';
|
|
1287
707
|
}
|
|
1288
|
-
return
|
|
1289
|
-
#
|
|
708
|
+
return `#!/bin/bash
|
|
709
|
+
# Claude Flow V3 Statusline Hook
|
|
710
|
+
# Source this in your .bashrc/.zshrc for terminal statusline
|
|
1290
711
|
|
|
1291
712
|
# Function to get statusline
|
|
1292
713
|
claude_flow_statusline() {
|
|
@@ -1296,16 +717,18 @@ claude_flow_statusline() {
|
|
|
1296
717
|
fi
|
|
1297
718
|
}
|
|
1298
719
|
|
|
1299
|
-
#
|
|
720
|
+
# Bash: Add to PS1
|
|
1300
721
|
# export PS1='$(claude_flow_statusline) \\n\\$ '
|
|
1301
722
|
|
|
1302
|
-
#
|
|
723
|
+
# Zsh: Add to RPROMPT
|
|
1303
724
|
# export RPROMPT='$(claude_flow_statusline)'
|
|
1304
725
|
|
|
1305
|
-
#
|
|
1306
|
-
#
|
|
1307
|
-
# command
|
|
1308
|
-
#
|
|
726
|
+
# Claude Code: Add to .claude/settings.json
|
|
727
|
+
# "statusLine": {
|
|
728
|
+
# "type": "command",
|
|
729
|
+
# "command": "node .claude/helpers/statusline.cjs 2>/dev/null"
|
|
730
|
+
# "when": "test -f .claude/helpers/statusline.cjs"
|
|
731
|
+
# }
|
|
1309
732
|
`;
|
|
1310
733
|
}
|
|
1311
734
|
//# sourceMappingURL=statusline-generator.js.map
|