kynjal-cli 3.1.3 β†’ 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,48 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * KynjalFlow Statusline Generator
3
+ * KynjalFlow Statusline Generator (Optimized)
4
4
  * Displays real-time V3 implementation progress and system status
5
5
  *
6
- * Usage: node statusline.cjs [options]
6
+ * Usage: node statusline.cjs [--json] [--compact]
7
7
  *
8
- * Options:
9
- * (default) Safe multi-line output with collision zone avoidance
10
- * --single Single-line output (completely avoids collision)
11
- * --unsafe Legacy multi-line without collision avoidance
12
- * --legacy Alias for --unsafe
13
- * --json JSON output with pretty printing
14
- * --compact JSON output without formatting
15
- *
16
- * Collision Zone Fix (Issue #985):
17
- * Claude Code writes its internal status (e.g., "7s β€’ 1p") at absolute
18
- * terminal coordinates (columns 15-25 on second-to-last line). The safe
19
- * mode pads the collision line with spaces to push content past column 25.
20
- *
21
- * IMPORTANT: This file uses .cjs extension to work in ES module projects.
22
- * 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
23
14
  */
24
15
 
25
16
  /* eslint-disable @typescript-eslint/no-var-requires */
26
17
  const fs = require('fs');
27
18
  const path = require('path');
28
19
  const { execSync } = require('child_process');
20
+ const os = require('os');
29
21
 
30
22
  // Configuration
31
23
  const CONFIG = {
32
- enabled: true,
33
- showProgress: true,
34
- showSecurity: true,
35
- showSwarm: true,
36
- showHooks: true,
37
- showPerformance: true,
38
- refreshInterval: 5000,
39
24
  maxAgents: 15,
40
- topology: 'hierarchical-mesh',
41
25
  };
42
26
 
43
- // Cross-platform helpers
44
- const isWindows = process.platform === 'win32';
45
- const nullDevice = isWindows ? 'NUL' : '/dev/null';
27
+ const CWD = process.cwd();
46
28
 
47
29
  // ANSI colors
48
30
  const c = {
@@ -64,533 +46,725 @@ const c = {
64
46
  brightWhite: '\x1b[1;37m',
65
47
  };
66
48
 
67
- // Get user info
68
- function getUserInfo() {
69
- let name = 'user';
70
- let gitBranch = '';
71
- let modelName = 'Unknown';
72
-
49
+ // Safe execSync with strict timeout (returns empty string on failure)
50
+ function safeExec(cmd, timeoutMs = 2000) {
73
51
  try {
74
- const gitUserCmd = isWindows
75
- ? 'git config user.name 2>NUL || echo user'
76
- : 'git config user.name 2>/dev/null || echo "user"';
77
- const gitBranchCmd = isWindows
78
- ? 'git branch --show-current 2>NUL || echo.'
79
- : 'git branch --show-current 2>/dev/null || echo ""';
80
- name = execSync(gitUserCmd, { encoding: 'utf-8' }).trim();
81
- gitBranch = execSync(gitBranchCmd, { encoding: 'utf-8' }).trim();
82
- if (gitBranch === '.') gitBranch = ''; // Windows echo. outputs a dot
83
- } catch (e) {
84
- // Ignore errors
52
+ return execSync(cmd, {
53
+ encoding: 'utf-8',
54
+ timeout: timeoutMs,
55
+ stdio: ['pipe', 'pipe', 'pipe'],
56
+ }).trim();
57
+ } catch {
58
+ return '';
85
59
  }
60
+ }
86
61
 
87
- // Auto-detect model from Claude Code's config
62
+ // Safe JSON file reader (returns null on failure)
63
+ function readJSON(filePath) {
88
64
  try {
89
- const homedir = require('os').homedir();
90
- const claudeConfigPath = path.join(homedir, '.claude.json');
91
- if (fs.existsSync(claudeConfigPath)) {
92
- const claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf-8'));
93
- // Try to find lastModelUsage - check current dir and parent dirs
94
- let lastModelUsage = null;
95
- const cwd = process.cwd();
96
- if (claudeConfig.projects) {
97
- // Try exact match first, then check if cwd starts with any project path
98
- for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
99
- if (cwd === projectPath || cwd.startsWith(projectPath + '/')) {
100
- lastModelUsage = projectConfig.lastModelUsage;
101
- break;
102
- }
103
- }
104
- }
105
- if (lastModelUsage) {
106
- const modelIds = Object.keys(lastModelUsage);
107
- if (modelIds.length > 0) {
108
- // Take the last model (most recently added to the object)
109
- // Or find the one with most tokens (most actively used this session)
110
- let modelId = modelIds[modelIds.length - 1];
111
- if (modelIds.length > 1) {
112
- // If multiple models, pick the one with most total tokens
113
- let maxTokens = 0;
114
- for (const id of modelIds) {
115
- const usage = lastModelUsage[id];
116
- const total = (usage.inputTokens || 0) + (usage.outputTokens || 0);
117
- if (total > maxTokens) {
118
- maxTokens = total;
119
- modelId = id;
120
- }
121
- }
122
- }
123
- // Parse model ID to human-readable name
124
- if (modelId.includes('opus-4-6') || modelId.includes('opus-4.6')) modelName = 'Opus 4.6';
125
- else if (modelId.includes('opus')) modelName = 'Opus 4.5';
126
- else if (modelId.includes('sonnet-4-6') || modelId.includes('sonnet-4.6')) modelName = 'Sonnet 4.6';
127
- else if (modelId.includes('sonnet')) modelName = 'Sonnet 4';
128
- else if (modelId.includes('haiku')) modelName = 'Haiku 4.5';
129
- else modelName = modelId.split('-').slice(1, 3).join(' ');
130
- }
131
- }
65
+ if (fs.existsSync(filePath)) {
66
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
132
67
  }
133
- } catch (e) {
134
- // Fallback to Unknown if can't read config
135
- }
68
+ } catch { /* ignore */ }
69
+ return null;
70
+ }
136
71
 
137
- return { name, gitBranch, modelName };
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;
138
78
  }
139
79
 
140
- // Get learning stats from intelligence loop data (ADR-050)
141
- function getLearningStats() {
142
- let patterns = 0;
143
- let sessions = 0;
144
- let trajectories = 0;
145
- let edges = 0;
146
- let confidenceMean = 0;
147
- let accessedCount = 0;
148
- let trend = 'STABLE';
149
-
150
- // PRIMARY: Read from intelligence loop data files
151
- const dataDir = path.join(process.cwd(), '.claude-flow', 'data');
152
-
153
- // 1. graph-state.json β€” authoritative node/edge counts
154
- const graphPath = path.join(dataDir, 'graph-state.json');
155
- if (fs.existsSync(graphPath)) {
156
- try {
157
- const graph = JSON.parse(fs.readFileSync(graphPath, 'utf-8'));
158
- patterns = graph.nodes ? Object.keys(graph.nodes).length : 0;
159
- edges = Array.isArray(graph.edges) ? graph.edges.length : 0;
160
- } catch (e) { /* ignore */ }
161
- }
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
+ }
162
89
 
163
- // 2. ranked-context.json β€” confidence and access data
164
- const rankedPath = path.join(dataDir, 'ranked-context.json');
165
- if (fs.existsSync(rankedPath)) {
166
- try {
167
- const ranked = JSON.parse(fs.readFileSync(rankedPath, 'utf-8'));
168
- if (ranked.entries && ranked.entries.length > 0) {
169
- patterns = Math.max(patterns, ranked.entries.length);
170
- let confSum = 0;
171
- let accCount = 0;
172
- for (let i = 0; i < ranked.entries.length; i++) {
173
- confSum += (ranked.entries[i].confidence || 0);
174
- if ((ranked.entries[i].accessCount || 0) > 0) accCount++;
175
- }
176
- confidenceMean = confSum / ranked.entries.length;
177
- accessedCount = accCount;
178
- }
179
- } catch (e) { /* ignore */ }
180
- }
90
+ // ─── Data Collection (all pure-Node.js or single-exec) ──────────
181
91
 
182
- // 3. intelligence-snapshot.json β€” trend history
183
- const snapshotPath = path.join(dataDir, 'intelligence-snapshot.json');
184
- if (fs.existsSync(snapshotPath)) {
185
- try {
186
- const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'));
187
- if (snapshot.history && snapshot.history.length >= 2) {
188
- const first = snapshot.history[0];
189
- const last = snapshot.history[snapshot.history.length - 1];
190
- const confDrift = (last.confidenceMean || 0) - (first.confidenceMean || 0);
191
- trend = confDrift > 0.01 ? 'IMPROVING' : confDrift < -0.01 ? 'DECLINING' : 'STABLE';
192
- sessions = Math.max(sessions, snapshot.history.length);
193
- }
194
- } catch (e) { /* ignore */ }
195
- }
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
+ };
196
98
 
197
- // 4. auto-memory-store.json β€” fallback entry count
198
- if (patterns === 0) {
199
- const autoMemPath = path.join(dataDir, 'auto-memory-store.json');
200
- if (fs.existsSync(autoMemPath)) {
201
- try {
202
- const data = JSON.parse(fs.readFileSync(autoMemPath, 'utf-8'));
203
- patterns = Array.isArray(data) ? data.length : (data.entries ? data.entries.length : 0);
204
- } catch (e) { /* ignore */ }
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++;
126
+ }
205
127
  }
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;
206
133
  }
207
134
 
208
- // FALLBACK: Legacy memory.db file-size estimation
209
- if (patterns === 0) {
210
- const memoryPaths = [
211
- path.join(process.cwd(), '.swarm', 'memory.db'),
212
- path.join(process.cwd(), '.claude', 'memory.db'),
213
- path.join(process.cwd(), 'data', 'memory.db'),
214
- ];
215
- for (let j = 0; j < memoryPaths.length; j++) {
216
- if (fs.existsSync(memoryPaths[j])) {
217
- try {
218
- const dbStats = fs.statSync(memoryPaths[j]);
219
- patterns = Math.floor(dbStats.size / 1024 / 2);
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
+ }
220
161
  break;
221
- } catch (e) { /* ignore */ }
162
+ }
222
163
  }
223
164
  }
165
+ } catch { /* ignore */ }
166
+
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';
224
174
  }
175
+ return 'Claude Code';
176
+ }
225
177
 
226
- // Session count from session files
227
- const sessionsPath = path.join(process.cwd(), '.claude', 'sessions');
228
- if (fs.existsSync(sessionsPath)) {
229
- try {
230
- const sessionFiles = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.json'));
231
- sessions = Math.max(sessions, sessionFiles.length);
232
- } catch (e) { /* ignore */ }
178
+ // Get learning stats from memory database (pure stat calls)
179
+ function getLearningStats() {
180
+ const memoryPaths = [
181
+ path.join(CWD, '.swarm', 'memory.db'),
182
+ path.join(CWD, '.kynjalflow', 'memory.db'),
183
+ path.join(CWD, '.claude', 'memory.db'),
184
+ path.join(CWD, 'data', 'memory.db'),
185
+ path.join(CWD, '.agentdb', 'memory.db'),
186
+ ];
187
+
188
+ for (const dbPath of memoryPaths) {
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
+ };
197
+ }
233
198
  }
234
199
 
235
- trajectories = Math.floor(patterns / 5);
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;
206
+ }
207
+ } catch { /* ignore */ }
236
208
 
237
- return { patterns, sessions, trajectories, edges, confidenceMean, accessedCount, trend };
209
+ return { patterns: 0, sessions };
238
210
  }
239
211
 
240
- // Get V3 progress from learning state (grows as system learns)
212
+ // V3 progress from metrics files (pure file reads)
241
213
  function getV3Progress() {
242
214
  const learning = getLearningStats();
243
-
244
- // DDD progress based on actual learned patterns
245
- // New install: 0 patterns = 0/5 domains, 0% DDD
246
- // As patterns grow: 10+ patterns = 1 domain, 50+ = 2, 100+ = 3, 200+ = 4, 500+ = 5
247
- let domainsCompleted = 0;
248
- if (learning.patterns >= 500) domainsCompleted = 5;
249
- else if (learning.patterns >= 200) domainsCompleted = 4;
250
- else if (learning.patterns >= 100) domainsCompleted = 3;
251
- else if (learning.patterns >= 50) domainsCompleted = 2;
252
- else if (learning.patterns >= 10) domainsCompleted = 1;
253
-
254
215
  const totalDomains = 5;
255
- const dddProgress = Math.min(100, Math.floor((domainsCompleted / totalDomains) * 100));
216
+
217
+ const dddData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'ddd-progress.json'));
218
+ let dddProgress = dddData?.progress || 0;
219
+ let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));
220
+
221
+ if (dddProgress === 0 && learning.patterns > 0) {
222
+ if (learning.patterns >= 500) domainsCompleted = 5;
223
+ else if (learning.patterns >= 200) domainsCompleted = 4;
224
+ else if (learning.patterns >= 100) domainsCompleted = 3;
225
+ else if (learning.patterns >= 50) domainsCompleted = 2;
226
+ else if (learning.patterns >= 10) domainsCompleted = 1;
227
+ dddProgress = Math.floor((domainsCompleted / totalDomains) * 100);
228
+ }
256
229
 
257
230
  return {
258
- domainsCompleted,
259
- totalDomains,
260
- dddProgress,
231
+ domainsCompleted, totalDomains, dddProgress,
261
232
  patternsLearned: learning.patterns,
262
- sessionsCompleted: learning.sessions
233
+ sessionsCompleted: learning.sessions,
263
234
  };
264
235
  }
265
236
 
266
- // Get security status based on actual scans
237
+ // Security status (pure file reads)
267
238
  function getSecurityStatus() {
268
- // Check for security scan results in memory
269
- const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
270
- let cvesFixed = 0;
271
- const totalCves = 3;
272
-
273
- if (fs.existsSync(scanResultsPath)) {
274
- try {
275
- const scans = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
276
- // Each successful scan file = 1 CVE addressed
277
- cvesFixed = Math.min(totalCves, scans.length);
278
- } catch (e) {
279
- // Ignore
239
+ const auditData = readJSON(path.join(CWD, '.kynjalflow', 'security', 'audit-status.json'));
240
+ if (auditData) {
241
+ const auditDate = auditData.lastAudit || auditData.lastScan;
242
+ if (!auditDate) {
243
+ // No audit has ever run β€” show as pending, not stale
244
+ return { status: 'PENDING', cvesFixed: 0, totalCves: 0 };
280
245
  }
246
+ const auditAge = Date.now() - new Date(auditDate).getTime();
247
+ const isStale = auditAge > 7 * 24 * 60 * 60 * 1000;
248
+ return {
249
+ status: isStale ? 'STALE' : (auditData.status || 'PENDING'),
250
+ cvesFixed: auditData.cvesFixed || 0,
251
+ totalCves: auditData.totalCves || 0,
252
+ };
281
253
  }
282
254
 
283
- // Also check .swarm/security for audit results
284
- const auditPath = path.join(process.cwd(), '.swarm', 'security');
285
- if (fs.existsSync(auditPath)) {
286
- try {
287
- const audits = fs.readdirSync(auditPath).filter(f => f.includes('audit'));
288
- cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
289
- } catch (e) {
290
- // Ignore
255
+ let scanCount = 0;
256
+ try {
257
+ const scanDir = path.join(CWD, '.claude', 'security-scans');
258
+ if (fs.existsSync(scanDir)) {
259
+ scanCount = fs.readdirSync(scanDir).filter(f => f.endsWith('.json')).length;
291
260
  }
292
- }
293
-
294
- const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
261
+ } catch { /* ignore */ }
295
262
 
296
263
  return {
297
- status,
298
- cvesFixed,
299
- totalCves,
264
+ status: scanCount > 0 ? 'SCANNED' : 'NONE',
265
+ cvesFixed: 0,
266
+ totalCves: 0,
300
267
  };
301
268
  }
302
269
 
303
- // Get swarm status
270
+ // Swarm status (pure file reads, NO ps aux)
304
271
  function getSwarmStatus() {
305
- let activeAgents = 0;
306
- let coordinationActive = false;
307
-
308
- // 1. Check metrics file first (written by swarm/agents during operation)
309
- try {
310
- const metricsPath = path.join(process.cwd(), '.claude-flow', 'metrics', 'swarm-activity.json');
311
- if (fs.existsSync(metricsPath)) {
312
- const metrics = JSON.parse(fs.readFileSync(metricsPath, 'utf-8'));
313
- // Only trust metrics if updated within last 60 seconds
314
- const age = Date.now() - (metrics.lastUpdated || 0);
315
- if (age < 60000) {
316
- activeAgents = metrics.activeAgents || 0;
317
- coordinationActive = metrics.coordinationActive || activeAgents > 0;
318
- return { activeAgents, maxAgents: CONFIG.maxAgents, coordinationActive };
319
- }
272
+ // Check swarm state file β€” only trust if recently updated (within 5 min)
273
+ const staleThresholdMs = 5 * 60 * 1000;
274
+ const now = Date.now();
275
+
276
+ const swarmStatePath = path.join(CWD, '.kynjalflow', 'swarm', 'swarm-state.json');
277
+ const swarmState = readJSON(swarmStatePath);
278
+ if (swarmState) {
279
+ const updatedAt = swarmState.updatedAt || swarmState.startedAt;
280
+ const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
281
+ if (age < staleThresholdMs) {
282
+ return {
283
+ activeAgents: swarmState.agents?.length || swarmState.agentCount || 0,
284
+ maxAgents: swarmState.maxAgents || CONFIG.maxAgents,
285
+ coordinationActive: true,
286
+ };
320
287
  }
321
- } catch (e) {
322
- // Fall through to process detection
323
288
  }
324
289
 
325
- // 2. Detect running agents from processes (safe: no user input in commands)
326
- try {
327
- if (isWindows) {
328
- const ps = execSync('tasklist 2>NUL | findstr /I "agentic-flow kynjalflow claude-flow" 2>NUL | find /C /V "" 2>NUL || echo 0', { encoding: 'utf-8' });
329
- activeAgents = Math.max(0, parseInt(ps.trim()) || 0);
330
- } else {
331
- const ps = execSync('ps aux 2>/dev/null | grep -E "(agentic-flow|kynjalflow|claude-flow)" | grep -v grep | wc -l || echo "0"', { encoding: 'utf-8' });
332
- activeAgents = Math.max(0, parseInt(ps.trim()) || 0);
290
+ const activityData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'swarm-activity.json'));
291
+ if (activityData?.swarm) {
292
+ const updatedAt = activityData.timestamp || activityData.swarm.timestamp;
293
+ const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
294
+ if (age < staleThresholdMs) {
295
+ return {
296
+ activeAgents: activityData.swarm.agent_count || 0,
297
+ maxAgents: CONFIG.maxAgents,
298
+ coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
299
+ };
333
300
  }
334
- coordinationActive = activeAgents > 0;
335
- } catch (e) {
336
- // Ignore errors - default to 0 agents
337
301
  }
338
302
 
339
- return {
340
- activeAgents,
341
- maxAgents: CONFIG.maxAgents,
342
- coordinationActive,
343
- };
303
+ return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
344
304
  }
345
305
 
346
- // Get system metrics (dynamic based on actual state)
306
+ // System metrics (uses process.memoryUsage() β€” no shell spawn)
347
307
  function getSystemMetrics() {
348
- let memoryMB = 0;
349
- let subAgents = 0;
308
+ const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
309
+ const learning = getLearningStats();
310
+ const agentdb = getAgentDBStats();
350
311
 
351
- try {
352
- if (isWindows) {
353
- // Windows: use tasklist for memory info, fallback to process.memoryUsage
354
- // tasklist memory column is complex to parse, use Node.js API instead
355
- memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
356
- } else {
357
- const mem = execSync('ps aux | grep -E "(node|agentic|claude)" | grep -v grep | awk \'{sum += $6} END {print int(sum/1024)}\'', { encoding: 'utf-8' });
358
- memoryMB = parseInt(mem.trim()) || 0;
359
- }
360
- } catch (e) {
361
- // Fallback
362
- memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
312
+ // Intelligence from learning.json
313
+ const learningData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'learning.json'));
314
+ let intelligencePct = 0;
315
+ let contextPct = 0;
316
+
317
+ if (learningData?.intelligence?.score !== undefined) {
318
+ intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
319
+ } else {
320
+ // Use actual vector/entry counts β€” 2000 entries = 100%
321
+ const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 20)) : 0;
322
+ const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 20)) : 0;
323
+ intelligencePct = Math.max(fromPatterns, fromVectors);
363
324
  }
364
325
 
365
- // Get learning stats for intelligence %
366
- const learning = getLearningStats();
326
+ // Maturity fallback (pure fs checks, no git exec)
327
+ if (intelligencePct === 0) {
328
+ let score = 0;
329
+ if (fs.existsSync(path.join(CWD, '.claude'))) score += 15;
330
+ const srcDirs = ['src', 'lib', 'app', 'packages', 'v3'];
331
+ for (const d of srcDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 15; break; } }
332
+ const testDirs = ['tests', 'test', '__tests__', 'spec'];
333
+ for (const d of testDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 10; break; } }
334
+ const cfgFiles = ['package.json', 'tsconfig.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
335
+ for (const f of cfgFiles) { if (fs.existsSync(path.join(CWD, f))) { score += 5; break; } }
336
+ intelligencePct = Math.min(100, score);
337
+ }
367
338
 
368
- // Intelligence % from REAL intelligence loop data (ADR-050)
369
- // Composite: 40% confidence mean + 30% access ratio + 30% pattern density
370
- let intelligencePct = 0;
371
- if (learning.confidenceMean > 0 || (learning.patterns > 0 && learning.accessedCount > 0)) {
372
- const confScore = Math.min(100, Math.floor(learning.confidenceMean * 100));
373
- const accessRatio = learning.patterns > 0 ? (learning.accessedCount / learning.patterns) : 0;
374
- const accessScore = Math.min(100, Math.floor(accessRatio * 100));
375
- const densityScore = Math.min(100, Math.floor(learning.patterns / 5));
376
- intelligencePct = Math.floor(confScore * 0.4 + accessScore * 0.3 + densityScore * 0.3);
339
+ if (learningData?.sessions?.total !== undefined) {
340
+ contextPct = Math.min(100, learningData.sessions.total * 5);
341
+ } else {
342
+ contextPct = Math.min(100, Math.floor(learning.sessions * 5));
377
343
  }
378
- // Fallback: legacy pattern count
379
- if (intelligencePct === 0 && learning.patterns > 0) {
380
- intelligencePct = Math.min(100, Math.floor(learning.patterns / 10));
344
+
345
+ // Sub-agents from file metrics (no ps aux)
346
+ let subAgents = 0;
347
+ const activityData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'swarm-activity.json'));
348
+ if (activityData?.processes?.estimated_agents) {
349
+ subAgents = activityData.processes.estimated_agents;
381
350
  }
382
351
 
383
- // Context % based on session history
384
- const contextPct = Math.min(100, Math.floor(learning.sessions * 5));
352
+ return { memoryMB, contextPct, intelligencePct, subAgents };
353
+ }
354
+
355
+ // ADR status (count files only β€” don't read contents)
356
+ function getADRStatus() {
357
+ // Count actual ADR files first β€” compliance JSON may be stale
358
+ const adrPaths = [
359
+ path.join(CWD, 'v3', 'implementation', 'adrs'),
360
+ path.join(CWD, 'docs', 'adrs'),
361
+ path.join(CWD, '.kynjalflow', 'adrs'),
362
+ ];
385
363
 
386
- // Count active sub-agents from process list
387
- try {
388
- if (isWindows) {
389
- // Windows: use tasklist and findstr for agent counting
390
- const agents = execSync('tasklist 2>NUL | findstr /I "claude-flow" 2>NUL | find /C /V "" 2>NUL || echo 0', { encoding: 'utf-8' });
391
- subAgents = Math.max(0, parseInt(agents.trim()) || 0);
392
- } else {
393
- const agents = execSync('ps aux 2>/dev/null | grep -c "claude-flow.*agent" || echo "0"', { encoding: 'utf-8' });
394
- subAgents = Math.max(0, parseInt(agents.trim()) - 1);
364
+ for (const adrPath of adrPaths) {
365
+ try {
366
+ if (fs.existsSync(adrPath)) {
367
+ const files = fs.readdirSync(adrPath).filter(f =>
368
+ f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\d{4}-/.test(f))
369
+ );
370
+ // Report actual count β€” don't guess compliance without reading files
371
+ return { count: files.length, implemented: files.length, compliance: 0 };
372
+ }
373
+ } catch { /* ignore */ }
374
+ }
375
+
376
+ return { count: 0, implemented: 0, compliance: 0 };
377
+ }
378
+
379
+ // Hooks status (shared settings cache)
380
+ function getHooksStatus() {
381
+ let enabled = 0;
382
+ let total = 0;
383
+ const settings = getSettings();
384
+
385
+ if (settings?.hooks) {
386
+ for (const category of Object.keys(settings.hooks)) {
387
+ const matchers = settings.hooks[category];
388
+ if (!Array.isArray(matchers)) continue;
389
+ for (const matcher of matchers) {
390
+ const hooks = matcher?.hooks;
391
+ if (Array.isArray(hooks)) {
392
+ total += hooks.length;
393
+ enabled += hooks.length;
394
+ }
395
+ }
395
396
  }
396
- } catch (e) {
397
- // Ignore - default to 0
398
397
  }
399
398
 
400
- return {
401
- memoryMB,
402
- contextPct,
403
- intelligencePct,
404
- subAgents,
405
- };
399
+ try {
400
+ const hooksDir = path.join(CWD, '.claude', 'hooks');
401
+ if (fs.existsSync(hooksDir)) {
402
+ const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh')).length;
403
+ total = Math.max(total, hookFiles);
404
+ enabled = Math.max(enabled, hookFiles);
405
+ }
406
+ } catch { /* ignore */ }
407
+
408
+ return { enabled, total };
406
409
  }
407
410
 
408
- // Generate progress bar
409
- function progressBar(current, total) {
410
- const width = 5;
411
- const filled = Math.round((current / total) * width);
412
- const empty = width - filled;
413
- return '[' + '\u25CF'.repeat(filled) + '\u25CB'.repeat(empty) + ']';
411
+ // AgentDB stats β€” count real entries, not file-size heuristics
412
+ function getAgentDBStats() {
413
+ let vectorCount = 0;
414
+ let dbSizeKB = 0;
415
+ let namespaces = 0;
416
+ let hasHnsw = false;
417
+
418
+ // 1. Count real entries from auto-memory-store.json
419
+ const storePath = path.join(CWD, '.kynjalflow', 'data', 'auto-memory-store.json');
420
+ const storeStat = safeStat(storePath);
421
+ if (storeStat) {
422
+ dbSizeKB += storeStat.size / 1024;
423
+ try {
424
+ const store = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
425
+ if (Array.isArray(store)) vectorCount += store.length;
426
+ else if (store?.entries) vectorCount += store.entries.length;
427
+ } catch { /* fall back to size estimate */ }
428
+ }
429
+
430
+ // 2. Count entries from ranked-context.json
431
+ const rankedPath = path.join(CWD, '.kynjalflow', 'data', 'ranked-context.json');
432
+ try {
433
+ const ranked = readJSON(rankedPath);
434
+ if (ranked?.entries?.length > vectorCount) vectorCount = ranked.entries.length;
435
+ } catch { /* ignore */ }
436
+
437
+ // 3. Add DB file sizes
438
+ const dbFiles = [
439
+ path.join(CWD, 'data', 'memory.db'),
440
+ path.join(CWD, '.kynjalflow', 'memory.db'),
441
+ path.join(CWD, '.swarm', 'memory.db'),
442
+ ];
443
+ for (const f of dbFiles) {
444
+ const stat = safeStat(f);
445
+ if (stat) {
446
+ dbSizeKB += stat.size / 1024;
447
+ namespaces++;
448
+ }
449
+ }
450
+
451
+ // 4. Check for graph data
452
+ const graphPath = path.join(CWD, 'data', 'memory.graph');
453
+ const graphStat = safeStat(graphPath);
454
+ if (graphStat) dbSizeKB += graphStat.size / 1024;
455
+
456
+ // 5. HNSW index
457
+ const hnswPaths = [
458
+ path.join(CWD, '.swarm', 'hnsw.index'),
459
+ path.join(CWD, '.kynjalflow', 'hnsw.index'),
460
+ ];
461
+ for (const p of hnswPaths) {
462
+ const stat = safeStat(p);
463
+ if (stat) {
464
+ hasHnsw = true;
465
+ break;
466
+ }
467
+ }
468
+
469
+ // HNSW is available if memory package is present
470
+ if (!hasHnsw) {
471
+ const memPkgPaths = [
472
+ path.join(CWD, 'v3', '@claude-flow', 'memory', 'dist'),
473
+ path.join(CWD, 'node_modules', '@claude-flow', 'memory'),
474
+ ];
475
+ for (const p of memPkgPaths) {
476
+ if (fs.existsSync(p)) { hasHnsw = true; break; }
477
+ }
478
+ }
479
+
480
+ return { vectorCount, dbSizeKB: Math.floor(dbSizeKB), namespaces, hasHnsw };
414
481
  }
415
482
 
416
- // Generate full statusline
417
- function generateStatusline() {
418
- const user = getUserInfo();
419
- const progress = getV3Progress();
420
- const security = getSecurityStatus();
421
- const swarm = getSwarmStatus();
422
- const system = getSystemMetrics();
423
- const lines = [];
483
+ // Test stats (count files only β€” NO reading file contents)
484
+ function getTestStats() {
485
+ let testFiles = 0;
424
486
 
425
- // Header Line
426
- let header = `${c.bold}${c.brightPurple}β–Š KynjalFlow${c.reset}`;
427
- header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
428
- if (user.gitBranch) {
429
- header += ` ${c.dim}β”‚${c.reset} ${c.brightBlue}βŽ‡ ${user.gitBranch}${c.reset}`;
487
+ function countTestFiles(dir, depth = 0) {
488
+ if (depth > 6) return;
489
+ try {
490
+ if (!fs.existsSync(dir)) return;
491
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
492
+ for (const entry of entries) {
493
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
494
+ countTestFiles(path.join(dir, entry.name), depth + 1);
495
+ } else if (entry.isFile()) {
496
+ const n = entry.name;
497
+ if (n.includes('.test.') || n.includes('.spec.') || n.includes('_test.') || n.includes('_spec.')) {
498
+ testFiles++;
499
+ }
500
+ }
501
+ }
502
+ } catch { /* ignore */ }
430
503
  }
431
- header += ` ${c.dim}β”‚${c.reset} ${c.purple}${user.modelName}${c.reset}`;
432
- lines.push(header);
433
504
 
434
- // Separator
435
- lines.push(`${c.dim}─────────────────────────────────────────────────────${c.reset}`);
505
+ // Scan all source directories
506
+ for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) {
507
+ countTestFiles(path.join(CWD, d));
508
+ }
436
509
 
437
- // Line 1: DDD Domain Progress
438
- const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
439
- lines.push(
440
- `${c.brightCyan}πŸ—οΈ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
441
- `${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
442
- `${c.brightYellow}⚑ 1.0x${c.reset} ${c.dim}β†’${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
443
- );
510
+ // Estimate ~4 test cases per file (avoids reading every file)
511
+ return { testFiles, testCases: testFiles * 4 };
512
+ }
444
513
 
445
- // Line 2: Swarm + CVE + Memory + Context + Intelligence
446
- const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}β—‰${c.reset}` : `${c.dim}β—‹${c.reset}`;
447
- const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
448
- let securityIcon = security.status === 'CLEAN' ? '🟒' : security.status === 'IN_PROGRESS' ? '🟑' : 'πŸ”΄';
449
- let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
514
+ // Integration status (shared settings + file checks)
515
+ function getIntegrationStatus() {
516
+ const mcpServers = { total: 0, enabled: 0 };
517
+ const settings = getSettings();
518
+
519
+ if (settings?.mcpServers && typeof settings.mcpServers === 'object') {
520
+ const servers = Object.keys(settings.mcpServers);
521
+ mcpServers.total = servers.length;
522
+ mcpServers.enabled = settings.enabledMcpjsonServers
523
+ ? settings.enabledMcpjsonServers.filter(s => servers.includes(s)).length
524
+ : servers.length;
525
+ }
450
526
 
451
- lines.push(
452
- `${c.brightYellow}πŸ€– Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
453
- `${c.brightPurple}πŸ‘₯ ${system.subAgents}${c.reset} ` +
454
- `${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
455
- `${c.brightCyan}πŸ’Ύ ${system.memoryMB}MB${c.reset} ` +
456
- `${c.brightGreen}πŸ“‚ ${String(system.contextPct).padStart(3)}%${c.reset} ` +
457
- `${c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
458
- );
527
+ // Fallback: .mcp.json
528
+ if (mcpServers.total === 0) {
529
+ const mcpConfig = readJSON(path.join(CWD, '.mcp.json'))
530
+ || readJSON(path.join(os.homedir(), '.claude', 'mcp.json'));
531
+ if (mcpConfig?.mcpServers) {
532
+ const s = Object.keys(mcpConfig.mcpServers);
533
+ mcpServers.total = s.length;
534
+ mcpServers.enabled = s.length;
535
+ }
536
+ }
459
537
 
460
- // Line 3: Architecture status
461
- const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
462
- lines.push(
463
- `${c.brightPurple}πŸ”§ Architecture${c.reset} ` +
464
- `${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}β”‚${c.reset} ` +
465
- `${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}β”‚${c.reset} ` +
466
- `${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}β”‚${c.reset} ` +
467
- `${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}●${c.reset}`
468
- );
538
+ const hasDatabase = ['.swarm/memory.db', '.kynjalflow/memory.db', 'data/memory.db']
539
+ .some(p => fs.existsSync(path.join(CWD, p)));
540
+ const hasApi = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
469
541
 
470
- return lines.join('\n');
542
+ return { mcpServers, hasDatabase, hasApi };
471
543
  }
472
544
 
473
- // Generate JSON data
474
- function generateJSON() {
475
- return {
476
- user: getUserInfo(),
477
- v3Progress: getV3Progress(),
478
- security: getSecurityStatus(),
479
- swarm: getSwarmStatus(),
480
- system: getSystemMetrics(),
481
- performance: {
482
- flashAttentionTarget: '2.49x-7.47x',
483
- searchImprovement: '150x-12,500x',
484
- memoryReduction: '50-75%',
485
- },
486
- lastUpdated: new Date().toISOString(),
487
- };
545
+ // Session stats (pure file reads)
546
+ function getSessionStats() {
547
+ for (const p of ['.kynjalflow/session.json', '.claude/session.json']) {
548
+ const data = readJSON(path.join(CWD, p));
549
+ if (data?.startTime) {
550
+ const diffMs = Date.now() - new Date(data.startTime).getTime();
551
+ const mins = Math.floor(diffMs / 60000);
552
+ const duration = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h${mins % 60}m`;
553
+ return { duration };
554
+ }
555
+ }
556
+ return { duration: '' };
488
557
  }
489
558
 
490
- /**
491
- * Generate single-line output for Claude Code compatibility
492
- * This avoids the collision zone issue entirely by using one line
493
- * @see https://github.com/ruvnet/claude-flow/issues/985
494
- */
495
- function generateSingleLine() {
496
- if (!CONFIG.enabled) return '';
559
+ // ─── Rendering ──────────────────────────────────────────────────
497
560
 
498
- const user = getUserInfo();
499
- const progress = getV3Progress();
500
- const security = getSecurityStatus();
561
+ function progressBar(current, total) {
562
+ const width = 5;
563
+ const filled = Math.round((current / total) * width);
564
+ return '[' + '\u25CF'.repeat(filled) + '\u25CB'.repeat(width - filled) + ']';
565
+ }
566
+
567
+ // Single-line statusline for Claude Code's status bar
568
+ function generateStatusline() {
569
+ const git = getGitInfo();
570
+ const modelName = getModelName();
501
571
  const swarm = getSwarmStatus();
502
572
  const system = getSystemMetrics();
573
+ const session = getSessionStats();
574
+ const hooks = getHooksStatus();
575
+ const integration = getIntegrationStatus();
576
+
577
+ const parts = [];
578
+
579
+ // Branding
580
+ parts.push(`${c.bold}${c.brightPurple}\u258A KynjalFlow${c.reset}`);
581
+
582
+ // User + swarm indicator
583
+ const dot = swarm.coordinationActive ? `${c.brightGreen}\u25CF${c.reset}` : `${c.brightCyan}\u25CF${c.reset}`;
584
+ parts.push(`${dot} ${c.brightCyan}${git.name}${c.reset}`);
585
+
586
+ // Git branch + changes
587
+ if (git.gitBranch) {
588
+ let branchPart = `${c.brightBlue}\u23C7 ${git.gitBranch}${c.reset}`;
589
+ const changes = [];
590
+ if (git.staged > 0) changes.push(`${c.brightGreen}+${git.staged}${c.reset}`);
591
+ if (git.modified > 0) changes.push(`${c.brightYellow}~${git.modified}${c.reset}`);
592
+ if (git.untracked > 0) changes.push(`${c.dim}?${git.untracked}${c.reset}`);
593
+ if (changes.length > 0) branchPart += ` ${changes.join('')}`;
594
+ if (git.ahead > 0) branchPart += ` ${c.brightGreen}\u2191${git.ahead}${c.reset}`;
595
+ if (git.behind > 0) branchPart += ` ${c.brightRed}\u2193${git.behind}${c.reset}`;
596
+ parts.push(branchPart);
597
+ }
503
598
 
504
- const swarmIndicator = swarm.coordinationActive ? '●' : 'β—‹';
505
- const securityStatus = security.status === 'CLEAN' ? 'βœ“' :
506
- security.cvesFixed > 0 ? '~' : 'βœ—';
599
+ // Model
600
+ parts.push(`${c.purple}${modelName}${c.reset}`);
507
601
 
508
- return `${c.brightPurple}CF-V3${c.reset} ${c.dim}|${c.reset} ` +
509
- `${c.cyan}D:${progress.domainsCompleted}/${progress.totalDomains}${c.reset} ${c.dim}|${c.reset} ` +
510
- `${c.yellow}S:${swarmIndicator}${swarm.activeAgents}/${swarm.maxAgents}${c.reset} ${c.dim}|${c.reset} ` +
511
- `${security.status === 'CLEAN' ? c.green : c.red}CVE:${securityStatus}${security.cvesFixed}/${security.totalCves}${c.reset} ${c.dim}|${c.reset} ` +
512
- `${c.dim}🧠${system.intelligencePct}%${c.reset}`;
513
- }
602
+ // Session duration
603
+ if (session.duration) {
604
+ parts.push(`${c.cyan}\u23F1 ${session.duration}${c.reset}`);
605
+ }
514
606
 
515
- /**
516
- * Generate safe multi-line statusline that avoids Claude Code collision zone
517
- * The collision zone is columns 15-25 on the second-to-last line.
518
- * We pad that line with spaces to push content past column 25.
519
- * @see https://github.com/ruvnet/claude-flow/issues/985
520
- */
521
- function generateSafeStatusline() {
522
- if (!CONFIG.enabled) return '';
607
+ // Intelligence %
608
+ const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
609
+ parts.push(`${intellColor}\u25CF ${system.intelligencePct}%${c.reset}`);
610
+
611
+ // Swarm agents (only if active)
612
+ if (swarm.activeAgents > 0 || swarm.coordinationActive) {
613
+ parts.push(`${c.brightYellow}\u25C9 ${swarm.activeAgents}/${swarm.maxAgents}${c.reset}`);
614
+ }
615
+
616
+ // Hooks (compact)
617
+ if (hooks.enabled > 0) {
618
+ parts.push(`${c.brightGreen}\u2713${hooks.enabled}h${c.reset}`);
619
+ }
523
620
 
524
- const user = getUserInfo();
621
+ // MCP (compact)
622
+ if (integration.mcpServers.total > 0) {
623
+ const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen : c.brightYellow;
624
+ parts.push(`${mcpCol}MCP${integration.mcpServers.enabled}${c.reset}`);
625
+ }
626
+
627
+ return parts.join(` ${c.dim}\u2502${c.reset} `);
628
+ }
629
+
630
+ // Multi-line dashboard (for --dashboard flag)
631
+ function generateDashboard() {
632
+ const git = getGitInfo();
633
+ const modelName = getModelName();
525
634
  const progress = getV3Progress();
526
635
  const security = getSecurityStatus();
527
636
  const swarm = getSwarmStatus();
528
637
  const system = getSystemMetrics();
638
+ const adrs = getADRStatus();
639
+ const hooks = getHooksStatus();
640
+ const agentdb = getAgentDBStats();
641
+ const tests = getTestStats();
642
+ const session = getSessionStats();
643
+ const integration = getIntegrationStatus();
529
644
  const lines = [];
530
645
 
531
- // Header Line
532
- let header = `${c.bold}${c.brightPurple}β–Š KynjalFlow${c.reset}`;
533
- header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
534
- if (user.gitBranch) {
535
- header += ` ${c.dim}β”‚${c.reset} ${c.brightBlue}βŽ‡ ${user.gitBranch}${c.reset}`;
646
+ // Header
647
+ let header = `${c.bold}${c.brightPurple}\u258A KynjalFlow ${c.reset}`;
648
+ header += `${swarm.coordinationActive ? c.brightCyan : c.dim}\u25CF ${c.brightCyan}${git.name}${c.reset}`;
649
+ if (git.gitBranch) {
650
+ header += ` ${c.dim}\u2502${c.reset} ${c.brightBlue}\u23C7 ${git.gitBranch}${c.reset}`;
651
+ const changes = git.modified + git.staged + git.untracked;
652
+ if (changes > 0) {
653
+ let ind = '';
654
+ if (git.staged > 0) ind += `${c.brightGreen}+${git.staged}${c.reset}`;
655
+ if (git.modified > 0) ind += `${c.brightYellow}~${git.modified}${c.reset}`;
656
+ if (git.untracked > 0) ind += `${c.dim}?${git.untracked}${c.reset}`;
657
+ header += ` ${ind}`;
658
+ }
659
+ if (git.ahead > 0) header += ` ${c.brightGreen}\u2191${git.ahead}${c.reset}`;
660
+ if (git.behind > 0) header += ` ${c.brightRed}\u2193${git.behind}${c.reset}`;
536
661
  }
537
- header += ` ${c.dim}β”‚${c.reset} ${c.purple}${user.modelName}${c.reset}`;
662
+ header += ` ${c.dim}\u2502${c.reset} ${c.purple}${modelName}${c.reset}`;
663
+ if (session.duration) header += ` ${c.dim}\u2502${c.reset} ${c.cyan}\u23F1 ${session.duration}${c.reset}`;
538
664
  lines.push(header);
539
665
 
540
666
  // Separator
541
- lines.push(`${c.dim}─────────────────────────────────────────────────────${c.reset}`);
667
+ 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}`);
542
668
 
543
- // Line 1: DDD Domain Progress
669
+ // Line 1: DDD Domains + perf
544
670
  const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
671
+ let perfIndicator;
672
+ if (agentdb.hasHnsw && agentdb.vectorCount > 0) {
673
+ const speedup = agentdb.vectorCount > 10000 ? '12500x' : agentdb.vectorCount > 1000 ? '150x' : '10x';
674
+ perfIndicator = `${c.brightGreen}\u26A1 HNSW ${speedup}${c.reset}`;
675
+ } else if (progress.patternsLearned > 0) {
676
+ const pk = progress.patternsLearned >= 1000 ? `${(progress.patternsLearned / 1000).toFixed(1)}k` : String(progress.patternsLearned);
677
+ perfIndicator = `${c.brightYellow}\uD83D\uDCDA ${pk} patterns${c.reset}`;
678
+ } else {
679
+ perfIndicator = `${c.dim}\u26A1 target: 150x-12500x${c.reset}`;
680
+ }
545
681
  lines.push(
546
- `${c.brightCyan}πŸ—οΈ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
547
- `${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
548
- `${c.brightYellow}⚑ 1.0x${c.reset} ${c.dim}β†’${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
682
+ `${c.brightCyan}\uD83C\uDFD7\uFE0F DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
683
+ `${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ${perfIndicator}`
549
684
  );
550
685
 
551
- // Line 2 (COLLISION LINE): Swarm status with 24 spaces padding after emoji
552
- // The emoji (πŸ€–) is 2 columns. 24 spaces pushes content to column 26, past the collision zone (15-25)
553
- const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}β—‰${c.reset}` : `${c.dim}β—‹${c.reset}`;
686
+ // Line 2: Swarm + Hooks + CVE + Memory + Intelligence
687
+ const swarmInd = swarm.coordinationActive ? `${c.brightGreen}\u25C9${c.reset}` : `${c.dim}\u25CB${c.reset}`;
554
688
  const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
555
- let securityIcon = security.status === 'CLEAN' ? '🟒' : security.status === 'IN_PROGRESS' ? '🟑' : 'πŸ”΄';
556
- let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
689
+ const secIcon = security.status === 'CLEAN' ? '\uD83D\uDFE2' : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? '\uD83D\uDFE1' : '\uD83D\uDD34';
690
+ const secColor = security.status === 'CLEAN' ? c.brightGreen : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? c.brightYellow : c.brightRed;
691
+ const hooksColor = hooks.enabled > 0 ? c.brightGreen : c.dim;
692
+ const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
557
693
 
558
- // CRITICAL: 24 spaces after πŸ€– (emoji is 2 cols, so 2+24=26, past collision zone cols 15-25)
559
694
  lines.push(
560
- `${c.brightYellow}πŸ€–${c.reset} ` + // 24 spaces padding
561
- `${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
562
- `${c.brightPurple}πŸ‘₯ ${system.subAgents}${c.reset} ` +
563
- `${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
564
- `${c.brightCyan}πŸ’Ύ ${system.memoryMB}MB${c.reset} ` +
565
- `${c.dim}🧠 ${system.intelligencePct}%${c.reset}`
695
+ `${c.brightYellow}\uD83E\uDD16 Swarm${c.reset} ${swarmInd} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
696
+ `${c.brightPurple}\uD83D\uDC65 ${system.subAgents}${c.reset} ` +
697
+ `${c.brightBlue}\uD83E\uDE9D ${hooksColor}${hooks.enabled}${c.reset}/${c.brightWhite}${hooks.total}${c.reset} ` +
698
+ `${secIcon} ${secColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
699
+ `${c.brightCyan}\uD83D\uDCBE ${system.memoryMB}MB${c.reset} ` +
700
+ `${intellColor}\uD83E\uDDE0 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
566
701
  );
567
702
 
568
- // Line 3: Architecture status (this is the last line, not in collision zone)
703
+ // Line 3: Architecture
569
704
  const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
705
+ const adrColor = adrs.count > 0 ? (adrs.implemented === adrs.count ? c.brightGreen : c.yellow) : c.dim;
706
+ const adrDisplay = adrs.compliance > 0 ? `${adrColor}\u25CF${adrs.compliance}%${c.reset}` : `${adrColor}\u25CF${adrs.implemented}/${adrs.count}${c.reset}`;
707
+
708
+ lines.push(
709
+ `${c.brightPurple}\uD83D\uDD27 Architecture${c.reset} ` +
710
+ `${c.cyan}ADRs${c.reset} ${adrDisplay} ${c.dim}\u2502${c.reset} ` +
711
+ `${c.cyan}DDD${c.reset} ${dddColor}\u25CF${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}\u2502${c.reset} ` +
712
+ `${c.cyan}Security${c.reset} ${secColor}\u25CF${security.status}${c.reset}`
713
+ );
714
+
715
+ // Line 4: AgentDB, Tests, Integration
716
+ const hnswInd = agentdb.hasHnsw ? `${c.brightGreen}\u26A1${c.reset}` : '';
717
+ const sizeDisp = agentdb.dbSizeKB >= 1024 ? `${(agentdb.dbSizeKB / 1024).toFixed(1)}MB` : `${agentdb.dbSizeKB}KB`;
718
+ const vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
719
+ const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
720
+
721
+ let integStr = '';
722
+ if (integration.mcpServers.total > 0) {
723
+ const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
724
+ integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
725
+ integStr += `${c.cyan}MCP${c.reset} ${mcpCol}\u25CF${integration.mcpServers.enabled}/${integration.mcpServers.total}${c.reset}`;
726
+ }
727
+ if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + `${c.brightGreen}\u25C6${c.reset}DB`;
728
+ if (integration.hasApi) integStr += (integStr ? ' ' : '') + `${c.brightGreen}\u25C6${c.reset}API`;
729
+ if (!integStr) integStr = `${c.dim}\u25CF none${c.reset}`;
730
+
570
731
  lines.push(
571
- `${c.brightPurple}πŸ”§ Architecture${c.reset} ` +
572
- `${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}β”‚${c.reset} ` +
573
- `${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}β”‚${c.reset} ` +
574
- `${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}β”‚${c.reset} ` +
575
- `${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}●${c.reset}`
732
+ `${c.brightCyan}\uD83D\uDCCA AgentDB${c.reset} ` +
733
+ `${c.cyan}Vectors${c.reset} ${vectorColor}\u25CF${agentdb.vectorCount}${hnswInd}${c.reset} ${c.dim}\u2502${c.reset} ` +
734
+ `${c.cyan}Size${c.reset} ${c.brightWhite}${sizeDisp}${c.reset} ${c.dim}\u2502${c.reset} ` +
735
+ `${c.cyan}Tests${c.reset} ${testColor}\u25CF${tests.testFiles}${c.reset} ${c.dim}(~${tests.testCases} cases)${c.reset} ${c.dim}\u2502${c.reset} ` +
736
+ integStr
576
737
  );
577
738
 
578
739
  return lines.join('\n');
579
740
  }
580
741
 
581
- // Main
742
+ // JSON output
743
+ function generateJSON() {
744
+ const git = getGitInfo();
745
+ return {
746
+ user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
747
+ v3Progress: getV3Progress(),
748
+ security: getSecurityStatus(),
749
+ swarm: getSwarmStatus(),
750
+ system: getSystemMetrics(),
751
+ adrs: getADRStatus(),
752
+ hooks: getHooksStatus(),
753
+ agentdb: getAgentDBStats(),
754
+ tests: getTestStats(),
755
+ git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
756
+ lastUpdated: new Date().toISOString(),
757
+ };
758
+ }
759
+
760
+ // ─── Main ───────────────────────────────────────────────────────
582
761
  if (process.argv.includes('--json')) {
583
762
  console.log(JSON.stringify(generateJSON(), null, 2));
584
763
  } else if (process.argv.includes('--compact')) {
585
764
  console.log(JSON.stringify(generateJSON()));
586
- } else if (process.argv.includes('--single')) {
587
- // Single-line mode - completely avoids collision zone
588
- console.log(generateSingleLine());
589
- } else if (process.argv.includes('--unsafe') || process.argv.includes('--legacy')) {
590
- // Legacy mode - original multi-line without collision avoidance
765
+ } else if (process.argv.includes('--single-line')) {
591
766
  console.log(generateStatusline());
592
767
  } else {
593
- // Default: Safe multi-line mode with collision zone avoidance
594
- // Use --unsafe or --legacy to get the original behavior
595
- console.log(generateSafeStatusline());
768
+ // Default: multi-line dashboard
769
+ console.log(generateDashboard());
596
770
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kynjal-cli",
3
- "version": "3.1.3",
3
+ "version": "3.1.4",
4
4
  "type": "module",
5
5
  "description": "Claude Flow CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",