kynjal-cli 3.1.2 β†’ 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,516 +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;
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
+ };
287
+ }
288
+ }
307
289
 
308
- try {
309
- if (isWindows) {
310
- // Windows: use tasklist and findstr
311
- const ps = execSync('tasklist 2>NUL | findstr /I "agentic-flow" 2>NUL | find /C /V "" 2>NUL || echo 0', { encoding: 'utf-8' });
312
- activeAgents = Math.max(0, parseInt(ps.trim()) || 0);
313
- } else {
314
- const ps = execSync('ps aux 2>/dev/null | grep -c agentic-flow || echo "0"', { encoding: 'utf-8' });
315
- activeAgents = Math.max(0, parseInt(ps.trim()) - 1);
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
+ };
316
300
  }
317
- coordinationActive = activeAgents > 0;
318
- } catch (e) {
319
- // Ignore errors - default to 0 agents
320
301
  }
321
302
 
322
- return {
323
- activeAgents,
324
- maxAgents: CONFIG.maxAgents,
325
- coordinationActive,
326
- };
303
+ return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
327
304
  }
328
305
 
329
- // Get system metrics (dynamic based on actual state)
306
+ // System metrics (uses process.memoryUsage() β€” no shell spawn)
330
307
  function getSystemMetrics() {
331
- let memoryMB = 0;
332
- let subAgents = 0;
308
+ const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
309
+ const learning = getLearningStats();
310
+ const agentdb = getAgentDBStats();
333
311
 
334
- try {
335
- if (isWindows) {
336
- // Windows: use tasklist for memory info, fallback to process.memoryUsage
337
- // tasklist memory column is complex to parse, use Node.js API instead
338
- memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
339
- } else {
340
- const mem = execSync('ps aux | grep -E "(node|agentic|claude)" | grep -v grep | awk \'{sum += $6} END {print int(sum/1024)}\'', { encoding: 'utf-8' });
341
- memoryMB = parseInt(mem.trim()) || 0;
342
- }
343
- } catch (e) {
344
- // Fallback
345
- 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);
346
324
  }
347
325
 
348
- // Get learning stats for intelligence %
349
- 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
+ }
350
338
 
351
- // Intelligence % from REAL intelligence loop data (ADR-050)
352
- // Composite: 40% confidence mean + 30% access ratio + 30% pattern density
353
- let intelligencePct = 0;
354
- if (learning.confidenceMean > 0 || (learning.patterns > 0 && learning.accessedCount > 0)) {
355
- const confScore = Math.min(100, Math.floor(learning.confidenceMean * 100));
356
- const accessRatio = learning.patterns > 0 ? (learning.accessedCount / learning.patterns) : 0;
357
- const accessScore = Math.min(100, Math.floor(accessRatio * 100));
358
- const densityScore = Math.min(100, Math.floor(learning.patterns / 5));
359
- 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));
360
343
  }
361
- // Fallback: legacy pattern count
362
- if (intelligencePct === 0 && learning.patterns > 0) {
363
- 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;
364
350
  }
365
351
 
366
- // Context % based on session history
367
- const contextPct = Math.min(100, Math.floor(learning.sessions * 5));
352
+ return { memoryMB, contextPct, intelligencePct, subAgents };
353
+ }
368
354
 
369
- // Count active sub-agents from process list
370
- try {
371
- if (isWindows) {
372
- // Windows: use tasklist and findstr for agent counting
373
- const agents = execSync('tasklist 2>NUL | findstr /I "claude-flow" 2>NUL | find /C /V "" 2>NUL || echo 0', { encoding: 'utf-8' });
374
- subAgents = Math.max(0, parseInt(agents.trim()) || 0);
375
- } else {
376
- const agents = execSync('ps aux 2>/dev/null | grep -c "claude-flow.*agent" || echo "0"', { encoding: 'utf-8' });
377
- subAgents = Math.max(0, parseInt(agents.trim()) - 1);
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
+ ];
363
+
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
+ }
378
396
  }
379
- } catch (e) {
380
- // Ignore - default to 0
381
397
  }
382
398
 
383
- return {
384
- memoryMB,
385
- contextPct,
386
- intelligencePct,
387
- subAgents,
388
- };
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 };
389
409
  }
390
410
 
391
- // Generate progress bar
392
- function progressBar(current, total) {
393
- const width = 5;
394
- const filled = Math.round((current / total) * width);
395
- const empty = width - filled;
396
- 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 };
397
481
  }
398
482
 
399
- // Generate full statusline
400
- function generateStatusline() {
401
- const user = getUserInfo();
402
- const progress = getV3Progress();
403
- const security = getSecurityStatus();
404
- const swarm = getSwarmStatus();
405
- const system = getSystemMetrics();
406
- const lines = [];
483
+ // Test stats (count files only β€” NO reading file contents)
484
+ function getTestStats() {
485
+ let testFiles = 0;
407
486
 
408
- // Header Line
409
- let header = `${c.bold}${c.brightPurple}β–Š KynjalFlow${c.reset}`;
410
- header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
411
- if (user.gitBranch) {
412
- 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 */ }
413
503
  }
414
- header += ` ${c.dim}β”‚${c.reset} ${c.purple}${user.modelName}${c.reset}`;
415
- lines.push(header);
416
504
 
417
- // Separator
418
- 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
+ }
419
509
 
420
- // Line 1: DDD Domain Progress
421
- const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
422
- lines.push(
423
- `${c.brightCyan}πŸ—οΈ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
424
- `${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
425
- `${c.brightYellow}⚑ 1.0x${c.reset} ${c.dim}β†’${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
426
- );
510
+ // Estimate ~4 test cases per file (avoids reading every file)
511
+ return { testFiles, testCases: testFiles * 4 };
512
+ }
427
513
 
428
- // Line 2: Swarm + CVE + Memory + Context + Intelligence
429
- const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}β—‰${c.reset}` : `${c.dim}β—‹${c.reset}`;
430
- const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
431
- let securityIcon = security.status === 'CLEAN' ? '🟒' : security.status === 'IN_PROGRESS' ? '🟑' : 'πŸ”΄';
432
- 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
+ }
433
526
 
434
- lines.push(
435
- `${c.brightYellow}πŸ€– Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
436
- `${c.brightPurple}πŸ‘₯ ${system.subAgents}${c.reset} ` +
437
- `${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
438
- `${c.brightCyan}πŸ’Ύ ${system.memoryMB}MB${c.reset} ` +
439
- `${c.brightGreen}πŸ“‚ ${String(system.contextPct).padStart(3)}%${c.reset} ` +
440
- `${c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
441
- );
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
+ }
442
537
 
443
- // Line 3: Architecture status
444
- const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
445
- lines.push(
446
- `${c.brightPurple}πŸ”§ Architecture${c.reset} ` +
447
- `${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}β”‚${c.reset} ` +
448
- `${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}β”‚${c.reset} ` +
449
- `${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}β”‚${c.reset} ` +
450
- `${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}●${c.reset}`
451
- );
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);
452
541
 
453
- return lines.join('\n');
542
+ return { mcpServers, hasDatabase, hasApi };
454
543
  }
455
544
 
456
- // Generate JSON data
457
- function generateJSON() {
458
- return {
459
- user: getUserInfo(),
460
- v3Progress: getV3Progress(),
461
- security: getSecurityStatus(),
462
- swarm: getSwarmStatus(),
463
- system: getSystemMetrics(),
464
- performance: {
465
- flashAttentionTarget: '2.49x-7.47x',
466
- searchImprovement: '150x-12,500x',
467
- memoryReduction: '50-75%',
468
- },
469
- lastUpdated: new Date().toISOString(),
470
- };
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: '' };
471
557
  }
472
558
 
473
- /**
474
- * Generate single-line output for Claude Code compatibility
475
- * This avoids the collision zone issue entirely by using one line
476
- * @see https://github.com/ruvnet/claude-flow/issues/985
477
- */
478
- function generateSingleLine() {
479
- if (!CONFIG.enabled) return '';
559
+ // ─── Rendering ──────────────────────────────────────────────────
480
560
 
481
- const user = getUserInfo();
482
- const progress = getV3Progress();
483
- 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();
484
571
  const swarm = getSwarmStatus();
485
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
+ }
486
598
 
487
- const swarmIndicator = swarm.coordinationActive ? '●' : 'β—‹';
488
- const securityStatus = security.status === 'CLEAN' ? 'βœ“' :
489
- security.cvesFixed > 0 ? '~' : 'βœ—';
599
+ // Model
600
+ parts.push(`${c.purple}${modelName}${c.reset}`);
490
601
 
491
- return `${c.brightPurple}CF-V3${c.reset} ${c.dim}|${c.reset} ` +
492
- `${c.cyan}D:${progress.domainsCompleted}/${progress.totalDomains}${c.reset} ${c.dim}|${c.reset} ` +
493
- `${c.yellow}S:${swarmIndicator}${swarm.activeAgents}/${swarm.maxAgents}${c.reset} ${c.dim}|${c.reset} ` +
494
- `${security.status === 'CLEAN' ? c.green : c.red}CVE:${securityStatus}${security.cvesFixed}/${security.totalCves}${c.reset} ${c.dim}|${c.reset} ` +
495
- `${c.dim}🧠${system.intelligencePct}%${c.reset}`;
496
- }
602
+ // Session duration
603
+ if (session.duration) {
604
+ parts.push(`${c.cyan}\u23F1 ${session.duration}${c.reset}`);
605
+ }
497
606
 
498
- /**
499
- * Generate safe multi-line statusline that avoids Claude Code collision zone
500
- * The collision zone is columns 15-25 on the second-to-last line.
501
- * We pad that line with spaces to push content past column 25.
502
- * @see https://github.com/ruvnet/claude-flow/issues/985
503
- */
504
- function generateSafeStatusline() {
505
- 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
+ }
506
620
 
507
- 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();
508
634
  const progress = getV3Progress();
509
635
  const security = getSecurityStatus();
510
636
  const swarm = getSwarmStatus();
511
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();
512
644
  const lines = [];
513
645
 
514
- // Header Line
515
- let header = `${c.bold}${c.brightPurple}β–Š KynjalFlow${c.reset}`;
516
- header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
517
- if (user.gitBranch) {
518
- 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}`;
519
661
  }
520
- 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}`;
521
664
  lines.push(header);
522
665
 
523
666
  // Separator
524
- 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}`);
525
668
 
526
- // Line 1: DDD Domain Progress
669
+ // Line 1: DDD Domains + perf
527
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
+ }
528
681
  lines.push(
529
- `${c.brightCyan}πŸ—οΈ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
530
- `${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
531
- `${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}`
532
684
  );
533
685
 
534
- // Line 2 (COLLISION LINE): Swarm status with 24 spaces padding after emoji
535
- // The emoji (πŸ€–) is 2 columns. 24 spaces pushes content to column 26, past the collision zone (15-25)
536
- 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}`;
537
688
  const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
538
- let securityIcon = security.status === 'CLEAN' ? '🟒' : security.status === 'IN_PROGRESS' ? '🟑' : 'πŸ”΄';
539
- 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;
540
693
 
541
- // CRITICAL: 24 spaces after πŸ€– (emoji is 2 cols, so 2+24=26, past collision zone cols 15-25)
542
694
  lines.push(
543
- `${c.brightYellow}πŸ€–${c.reset} ` + // 24 spaces padding
544
- `${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
545
- `${c.brightPurple}πŸ‘₯ ${system.subAgents}${c.reset} ` +
546
- `${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
547
- `${c.brightCyan}πŸ’Ύ ${system.memoryMB}MB${c.reset} ` +
548
- `${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}`
549
701
  );
550
702
 
551
- // Line 3: Architecture status (this is the last line, not in collision zone)
703
+ // Line 3: Architecture
552
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
+
553
731
  lines.push(
554
- `${c.brightPurple}πŸ”§ Architecture${c.reset} ` +
555
- `${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}β”‚${c.reset} ` +
556
- `${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}β”‚${c.reset} ` +
557
- `${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}β”‚${c.reset} ` +
558
- `${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
559
737
  );
560
738
 
561
739
  return lines.join('\n');
562
740
  }
563
741
 
564
- // 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 ───────────────────────────────────────────────────────
565
761
  if (process.argv.includes('--json')) {
566
762
  console.log(JSON.stringify(generateJSON(), null, 2));
567
763
  } else if (process.argv.includes('--compact')) {
568
764
  console.log(JSON.stringify(generateJSON()));
569
- } else if (process.argv.includes('--single')) {
570
- // Single-line mode - completely avoids collision zone
571
- console.log(generateSingleLine());
572
- } else if (process.argv.includes('--unsafe') || process.argv.includes('--legacy')) {
573
- // Legacy mode - original multi-line without collision avoidance
765
+ } else if (process.argv.includes('--single-line')) {
574
766
  console.log(generateStatusline());
575
767
  } else {
576
- // Default: Safe multi-line mode with collision zone avoidance
577
- // Use --unsafe or --legacy to get the original behavior
578
- console.log(generateSafeStatusline());
768
+ // Default: multi-line dashboard
769
+ console.log(generateDashboard());
579
770
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kynjal-cli",
3
- "version": "3.1.2",
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",