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