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
- * IMPORTANT: This file uses .cjs extension to work in ES module projects.
9
- * The require() syntax is intentional for CommonJS compatibility.
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
- // Get user info
51
- function getUserInfo() {
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
- name = execSync('git config user.name 2>/dev/null || echo "user"', { encoding: 'utf-8' }).trim();
58
- gitBranch = execSync('git branch --show-current 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim();
59
- } catch (e) {
60
- // Ignore errors
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
- // Auto-detect model from Claude Code's config
62
+ // Safe JSON file reader (returns null on failure)
63
+ function readJSON(filePath) {
64
64
  try {
65
- const homedir = require('os').homedir();
66
- const claudeConfigPath = path.join(homedir, '.claude.json');
67
- if (fs.existsSync(claudeConfigPath)) {
68
- const claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf-8'));
69
- // Try to find lastModelUsage - check current dir and parent dirs
70
- let lastModelUsage = null;
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
- // Parse model ID to human-readable name
102
- if (modelId.includes('opus')) modelName = 'Opus 4.5';
103
- else if (modelId.includes('sonnet')) modelName = 'Sonnet 4';
104
- else if (modelId.includes('haiku')) modelName = 'Haiku 4.5';
105
- else modelName = modelId.split('-').slice(1, 3).join(' ');
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
- } catch (e) {
110
- // Fallback to Unknown if can't read config
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
- // Fallback: check project's .claude/settings.json for model
114
- if (modelName === 'Unknown') {
115
- try {
116
- const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
117
- if (fs.existsSync(settingsPath)) {
118
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
119
- if (settings.model) {
120
- if (settings.model.includes('opus')) modelName = 'Opus 4.5';
121
- else if (settings.model.includes('sonnet')) modelName = 'Sonnet 4';
122
- else if (settings.model.includes('haiku')) modelName = 'Haiku 4.5';
123
- else modelName = settings.model.split('-').slice(1, 3).join(' ');
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
- return { name, gitBranch, modelName };
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(process.cwd(), '.swarm', 'memory.db'),
138
- path.join(process.cwd(), '.claude-flow', 'memory.db'),
139
- path.join(process.cwd(), '.claude', 'memory.db'),
140
- path.join(process.cwd(), 'data', 'memory.db'),
141
- path.join(process.cwd(), 'memory.db'),
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
- if (fs.existsSync(dbPath)) {
152
- try {
153
- // Count entries in memory file (rough estimate from file size)
154
- const stats = fs.statSync(dbPath);
155
- const sizeKB = stats.size / 1024;
156
- // Estimate: ~2KB per pattern on average
157
- patterns = Math.floor(sizeKB / 2);
158
- sessions = Math.max(1, Math.floor(patterns / 10));
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
- // Also check for session files
168
- const sessionsPath = path.join(process.cwd(), '.claude', 'sessions');
169
- if (fs.existsSync(sessionsPath)) {
170
- try {
171
- const sessionFiles = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.json'));
172
- sessions = Math.max(sessions, sessionFiles.length);
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, trajectories };
209
+ return { patterns: 0, sessions };
179
210
  }
180
211
 
181
- // Get V3 progress from REAL metrics files
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
- let dddProgress = 0;
187
- let dddScore = 0;
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
- // Get security status based on actual scans
237
+ // Security status (pure file reads)
231
238
  function getSecurityStatus() {
232
239
  const totalCves = 3;
233
- let cvesFixed = 0;
234
-
235
- // Check audit-status.json first (created by init)
236
- const auditStatusPath = path.join(process.cwd(), '.claude-flow', 'security', 'audit-status.json');
237
- if (fs.existsSync(auditStatusPath)) {
238
- try {
239
- const data = JSON.parse(fs.readFileSync(auditStatusPath, 'utf-8'));
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
- // Check for security scan results in memory
251
- const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
252
- if (fs.existsSync(scanResultsPath)) {
253
- try {
254
- const scans = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
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
- // Get swarm status (cross-platform)
264
+ // Swarm status (pure file reads, NO ps aux)
283
265
  function getSwarmStatus() {
284
- let activeAgents = 0;
285
- let coordinationActive = false;
286
-
287
- // Check swarm-activity.json first (works on all platforms)
288
- const activityPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'swarm-activity.json');
289
- if (fs.existsSync(activityPath)) {
290
- try {
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
- // Also check v3-progress.json for swarm data (secondary source)
305
- const progressPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'v3-progress.json');
306
- if (fs.existsSync(progressPath)) {
307
- try {
308
- const data = JSON.parse(fs.readFileSync(progressPath, 'utf-8'));
309
- if (data.swarm) {
310
- return {
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
- // Platform-specific process detection (fallback)
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
- // Get system metrics (cross-platform)
287
+ // System metrics (uses process.memoryUsage() β€” no shell spawn)
355
288
  function getSystemMetrics() {
356
- let memoryMB = 0;
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
- // Also get AgentDB stats for fallback intelligence calculation
402
- const agentdbStats = getAgentDBStats();
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 (intelligenceFromFile !== null) {
409
- intelligencePct = intelligenceFromFile;
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 = agentdbStats.vectorCount > 0 ? Math.min(100, Math.floor(agentdbStats.vectorCount / 100)) : 0;
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
- // If still 0, use project maturity fallback
306
+ // Maturity fallback (pure fs checks, no git exec)
419
307
  if (intelligencePct === 0) {
420
- // Final fallback: estimate from project maturity indicators
421
- let maturityScore = 0;
422
-
423
- // Check git commit count (proxy for project development)
424
- try {
425
- const commitCount = parseInt(execSync('git rev-list --count HEAD 2>/dev/null || echo "0"', { encoding: 'utf-8' }).trim());
426
- maturityScore += Math.min(30, Math.floor(commitCount / 10)); // Max 30% from commits
427
- } catch (e) { /* ignore */ }
428
-
429
- // Check for Claude session history
430
- const sessionPaths = [
431
- path.join(process.cwd(), '.claude', 'sessions'),
432
- path.join(process.cwd(), '.claude-flow', 'sessions'),
433
- ];
434
- for (const sessPath of sessionPaths) {
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
- // Fallback to process detection on Unix only
502
- if (subAgents === 0 && !isWindows) {
503
- try {
504
- const agents = execSync('ps aux 2>/dev/null | grep -c "claude-flow.*agent" || echo "0"', { encoding: 'utf-8' });
505
- subAgents = Math.max(0, parseInt(agents.trim()) - 1);
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
- // Get ADR (Architecture Decision Records) status from REAL compliance data
335
+ // ADR status (count files only β€” don't read contents)
520
336
  function getADRStatus() {
521
- let compliance = 0;
522
- let totalChecks = 0;
523
- let compliantChecks = 0;
524
- let checks = {};
525
-
526
- // Check adr-compliance.json for REAL compliance data
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 directly
345
+ // Fallback: just count ADR files (don't read them)
542
346
  const adrPaths = [
543
- path.join(process.cwd(), 'docs', 'adrs'),
544
- path.join(process.cwd(), 'docs', 'adr'),
545
- path.join(process.cwd(), 'adr'),
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
- if (fs.existsSync(adrPath)) {
557
- try {
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
- count = files.length;
562
-
563
- for (const file of files) {
564
- try {
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
- compliance = count > 0 ? Math.floor((implemented / count) * 100) : 0;
582
- return { count, implemented, compliance };
366
+ return { count: 0, implemented: 0, compliance: 0 };
583
367
  }
584
368
 
585
- // Get hooks status (enabled/registered hooks)
369
+ // Hooks status (shared settings cache)
586
370
  function getHooksStatus() {
587
371
  let enabled = 0;
588
- let total = 17; // V3 has 17 hook types
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
- for (const settingsPath of settingsPaths) {
597
- if (fs.existsSync(settingsPath)) {
598
- try {
599
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
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
- // Also check for hook files in .claude/hooks
619
- const hooksDir = path.join(process.cwd(), '.claude', 'hooks');
620
- if (fs.existsSync(hooksDir)) {
621
- try {
622
- const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh'));
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
- // Get AgentDB memory stats
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
- // Check for database directories
640
- const dbDirPaths = [
641
- path.join(process.cwd(), '.claude-flow', 'agentdb'),
642
- path.join(process.cwd(), '.swarm', 'agentdb'),
643
- path.join(process.cwd(), 'data', 'agentdb'),
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
- // Check for HNSW index files
658
- const hnswPaths = [
659
- path.join(process.cwd(), '.swarm', 'hnsw.index'),
660
- path.join(process.cwd(), '.claude-flow', 'hnsw.index'),
661
- path.join(process.cwd(), 'data', 'hnsw.index'),
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
- for (const dbPath of dbDirPaths) {
683
- if (fs.existsSync(dbPath)) {
684
- try {
685
- const stats = fs.statSync(dbPath);
686
- if (stats.isDirectory()) {
687
- const files = fs.readdirSync(dbPath);
688
- namespaces = files.filter(f => f.endsWith('.db') || f.endsWith('.sqlite')).length;
689
-
690
- for (const file of files) {
691
- const filePath = path.join(dbPath, file);
692
- const fileStat = fs.statSync(filePath);
693
- if (fileStat.isFile()) {
694
- dbSizeKB += fileStat.size / 1024;
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
- // Check for HNSW index (indicates vector search capability)
709
- for (const hnswPath of hnswPaths) {
710
- if (fs.existsSync(hnswPath)) {
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
- try {
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
- // Get test statistics
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 > 3) return; // Limit recursion
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 name = entry.name;
767
- if (name.includes('.test.') || name.includes('.spec.') ||
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 (e) {
787
- // Ignore
788
- }
789
- }
790
-
791
- for (const dir of testDirs) {
792
- countTestFiles(dir);
474
+ } catch { /* ignore */ }
793
475
  }
794
476
 
795
- // Also check src directory for colocated tests
796
- const srcDir = path.join(process.cwd(), 'src');
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
- return { testFiles, testCases };
482
+ // Estimate ~4 test cases per file (avoids reading every file)
483
+ return { testFiles, testCases: testFiles * 4 };
802
484
  }
803
485
 
804
- // Get integration status (MCP servers, external connections)
486
+ // Integration status (shared settings + file checks)
805
487
  function getIntegrationStatus() {
806
- let mcpServers = { total: 0, enabled: 0, names: [] };
807
- let hasDatabase = false;
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
- for (const settingsPath of settingsPaths) {
818
- if (fs.existsSync(settingsPath)) {
819
- try {
820
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
821
-
822
- // Check mcpServers object
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
- // Also check .mcp.json or mcp.json
841
- const mcpConfigPaths = [
842
- path.join(process.cwd(), '.mcp.json'),
843
- path.join(process.cwd(), 'mcp.json'),
844
- path.join(require('os').homedir(), '.claude', 'mcp.json'),
845
- ];
846
-
847
- for (const mcpPath of mcpConfigPaths) {
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
- // Check for database (AgentDB, SQLite, etc.)
862
- const dbPaths = [
863
- path.join(process.cwd(), '.swarm', 'memory.db'),
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
- // Check for API configuration (env vars or config)
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
- // Get session statistics
517
+ // Session stats (pure file reads)
932
518
  function getSessionStats() {
933
- let sessionStart = null;
934
- let duration = '';
935
- let lastActivity = '';
936
- let operationsCount = 0;
937
-
938
- // Check for session file
939
- const sessionPaths = [
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
- // Get trend indicator based on change
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
- const empty = width - filled;
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
- const user = getUserInfo();
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
- // Calculate intelligence trend
1033
- const intellTrend = getTrend(system.intelligencePct, prevIntelligence);
1034
-
1035
- // Save current values for next trend calculation
1036
- try {
1037
- const trendPath = path.join(process.cwd(), '.claude-flow', '.trend-cache.json');
1038
- const trendDir = path.dirname(trendPath);
1039
- if (!fs.existsSync(trendDir)) fs.mkdirSync(trendDir, { recursive: true });
1040
- fs.writeFileSync(trendPath, JSON.stringify({ intelligence: system.intelligencePct, timestamp: Date.now() }));
1041
- } catch (e) { /* ignore */ }
1042
-
1043
- // Header Line with git changes indicator
1044
- let header = `${c.bold}${c.brightPurple}β–Š Claude Flow V3 ${c.reset}`;
1045
- header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
1046
- if (user.gitBranch) {
1047
- header += ` ${c.dim}β”‚${c.reset} ${c.brightBlue}βŽ‡ ${user.gitBranch}${c.reset}`;
1048
- // Add git changes indicator
1049
- const gitChanges = git.modified + git.staged + git.untracked;
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}─────────────────────────────────────────────────────${c.reset}`);
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 Domain Progress with dynamic performance indicator
578
+ // Line 1: DDD Domains + perf
1074
579
  const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
1075
- // Show HNSW speedup if enabled, otherwise show patterns learned
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}⚑ HNSW ${speedup}${c.reset}`;
583
+ perfIndicator = `${c.brightGreen}\u26A1 HNSW ${speedup}${c.reset}`;
1081
584
  } else if (progress.patternsLearned > 0) {
1082
- // Show patterns learned
1083
- const patternsK = progress.patternsLearned >= 1000
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
- // New project: show target
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}πŸ—οΈ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
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 + Context + Intelligence
1098
- const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}β—‰${c.reset}` : `${c.dim}β—‹${c.reset}`;
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
- let securityIcon = security.status === 'CLEAN' ? '🟒' : security.status === 'IN_PROGRESS' ? '🟑' : 'πŸ”΄';
1101
- let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
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}πŸ€– Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
1106
- `${c.brightPurple}πŸ‘₯ ${system.subAgents}${c.reset} ` +
1107
- `${c.brightBlue}πŸͺ ${hooksColor}${hooks.enabled}${c.reset}/${c.brightWhite}${hooks.total}${c.reset} ` +
1108
- `${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
1109
- `${c.brightCyan}πŸ’Ύ ${system.memoryMB}MB${c.reset} ` +
1110
- `${system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${intellTrend}${c.reset}`
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 status with ADRs, AgentDB, Tests
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 vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
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}πŸ”§ Architecture${c.reset} ` +
1126
- `${c.cyan}ADRs${c.reset} ${adrDisplay} ${c.dim}β”‚${c.reset} ` +
1127
- `${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}β”‚${c.reset} ` +
1128
- `${c.cyan}Security${c.reset} ${securityColor}●${security.status}${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: Memory, Vectors, Tests
1132
- const hnswIndicator = agentdb.hasHnsw ? `${c.brightGreen}⚑${c.reset}` : '';
1133
- const sizeDisplay = agentdb.dbSizeKB >= 1024
1134
- ? `${(agentdb.dbSizeKB / 1024).toFixed(1)}MB`
1135
- : `${agentdb.dbSizeKB}KB`;
1136
- // Build integration status string
1137
- let integrationStr = '';
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 mcpColor = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
1140
- integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
1141
- integrationStr += `${c.cyan}MCP${c.reset} ${mcpColor}●${integration.mcpServers.enabled}/${integration.mcpServers.total}${c.reset}`;
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}πŸ“Š AgentDB${c.reset} ` +
1155
- `${c.cyan}Vectors${c.reset} ${vectorColor}●${agentdb.vectorCount}${hnswIndicator}${c.reset} ${c.dim}β”‚${c.reset} ` +
1156
- `${c.cyan}Size${c.reset} ${c.brightWhite}${sizeDisplay}${c.reset} ${c.dim}β”‚${c.reset} ` +
1157
- `${c.cyan}Tests${c.reset} ${testColor}●${tests.testFiles}${c.reset} ${c.dim}(${tests.testCases} cases)${c.reset} ${c.dim}β”‚${c.reset} ` +
1158
- integrationStr
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
- // Generate JSON data
651
+ // JSON output
1165
652
  function generateJSON() {
653
+ const git = getGitInfo();
1166
654
  return {
1167
- user: getUserInfo(),
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
- performance: {
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')) {