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.
- package/.claude/helpers/statusline.cjs +613 -422
- package/package.json +1 -1
|
@@ -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 [
|
|
6
|
+
* Usage: node statusline.cjs [--json] [--compact]
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
68
|
-
function
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
62
|
+
// Safe JSON file reader (returns null on failure)
|
|
63
|
+
function readJSON(filePath) {
|
|
88
64
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
68
|
+
} catch { /* ignore */ }
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
136
71
|
|
|
137
|
-
|
|
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
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
|
209
|
+
return { patterns: 0, sessions };
|
|
238
210
|
}
|
|
239
211
|
|
|
240
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
237
|
+
// Security status (pure file reads)
|
|
267
238
|
function getSecurityStatus() {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
//
|
|
270
|
+
// Swarm status (pure file reads, NO ps aux)
|
|
304
271
|
function getSwarmStatus() {
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
//
|
|
306
|
+
// System metrics (uses process.memoryUsage() β no shell spawn)
|
|
330
307
|
function getSystemMetrics() {
|
|
331
|
-
|
|
332
|
-
|
|
308
|
+
const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
309
|
+
const learning = getLearningStats();
|
|
310
|
+
const agentdb = getAgentDBStats();
|
|
333
311
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
//
|
|
349
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
367
|
-
|
|
352
|
+
return { memoryMB, contextPct, intelligencePct, subAgents };
|
|
353
|
+
}
|
|
368
354
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
//
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
//
|
|
400
|
-
function
|
|
401
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
//
|
|
418
|
-
|
|
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
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
542
|
+
return { mcpServers, hasDatabase, hasApi };
|
|
454
543
|
}
|
|
455
544
|
|
|
456
|
-
//
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
482
|
-
const
|
|
483
|
-
const
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
security.cvesFixed > 0 ? '~' : 'β';
|
|
599
|
+
// Model
|
|
600
|
+
parts.push(`${c.purple}${modelName}${c.reset}`);
|
|
490
601
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
`${c.
|
|
494
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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
|
|
515
|
-
let header = `${c.bold}${c.brightPurple}
|
|
516
|
-
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}
|
|
517
|
-
if (
|
|
518
|
-
header += ` ${c.dim}
|
|
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}
|
|
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}
|
|
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
|
|
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}
|
|
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
|
|
535
|
-
|
|
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
|
-
|
|
539
|
-
|
|
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}
|
|
544
|
-
`${
|
|
545
|
-
`${c.
|
|
546
|
-
`${
|
|
547
|
-
`${c.brightCyan}
|
|
548
|
-
`${
|
|
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
|
|
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.
|
|
555
|
-
`${c.cyan}
|
|
556
|
-
`${c.cyan}
|
|
557
|
-
`${c.cyan}
|
|
558
|
-
|
|
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
|
-
//
|
|
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:
|
|
577
|
-
|
|
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.
|
|
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",
|