kynjal-cli 3.1.3 β 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/statusline.cjs +611 -437
- 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,533 +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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
activeAgents
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
272
|
+
// Check swarm state file β only trust if recently updated (within 5 min)
|
|
273
|
+
const staleThresholdMs = 5 * 60 * 1000;
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
|
|
276
|
+
const swarmStatePath = path.join(CWD, '.kynjalflow', 'swarm', 'swarm-state.json');
|
|
277
|
+
const swarmState = readJSON(swarmStatePath);
|
|
278
|
+
if (swarmState) {
|
|
279
|
+
const updatedAt = swarmState.updatedAt || swarmState.startedAt;
|
|
280
|
+
const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
|
|
281
|
+
if (age < staleThresholdMs) {
|
|
282
|
+
return {
|
|
283
|
+
activeAgents: swarmState.agents?.length || swarmState.agentCount || 0,
|
|
284
|
+
maxAgents: swarmState.maxAgents || CONFIG.maxAgents,
|
|
285
|
+
coordinationActive: true,
|
|
286
|
+
};
|
|
320
287
|
}
|
|
321
|
-
} catch (e) {
|
|
322
|
-
// Fall through to process detection
|
|
323
288
|
}
|
|
324
289
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
290
|
+
const activityData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'swarm-activity.json'));
|
|
291
|
+
if (activityData?.swarm) {
|
|
292
|
+
const updatedAt = activityData.timestamp || activityData.swarm.timestamp;
|
|
293
|
+
const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
|
|
294
|
+
if (age < staleThresholdMs) {
|
|
295
|
+
return {
|
|
296
|
+
activeAgents: activityData.swarm.agent_count || 0,
|
|
297
|
+
maxAgents: CONFIG.maxAgents,
|
|
298
|
+
coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
|
|
299
|
+
};
|
|
333
300
|
}
|
|
334
|
-
coordinationActive = activeAgents > 0;
|
|
335
|
-
} catch (e) {
|
|
336
|
-
// Ignore errors - default to 0 agents
|
|
337
301
|
}
|
|
338
302
|
|
|
339
|
-
return {
|
|
340
|
-
activeAgents,
|
|
341
|
-
maxAgents: CONFIG.maxAgents,
|
|
342
|
-
coordinationActive,
|
|
343
|
-
};
|
|
303
|
+
return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
|
|
344
304
|
}
|
|
345
305
|
|
|
346
|
-
//
|
|
306
|
+
// System metrics (uses process.memoryUsage() β no shell spawn)
|
|
347
307
|
function getSystemMetrics() {
|
|
348
|
-
|
|
349
|
-
|
|
308
|
+
const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
309
|
+
const learning = getLearningStats();
|
|
310
|
+
const agentdb = getAgentDBStats();
|
|
350
311
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
312
|
+
// Intelligence from learning.json
|
|
313
|
+
const learningData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'learning.json'));
|
|
314
|
+
let intelligencePct = 0;
|
|
315
|
+
let contextPct = 0;
|
|
316
|
+
|
|
317
|
+
if (learningData?.intelligence?.score !== undefined) {
|
|
318
|
+
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
|
|
319
|
+
} else {
|
|
320
|
+
// Use actual vector/entry counts β 2000 entries = 100%
|
|
321
|
+
const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 20)) : 0;
|
|
322
|
+
const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 20)) : 0;
|
|
323
|
+
intelligencePct = Math.max(fromPatterns, fromVectors);
|
|
363
324
|
}
|
|
364
325
|
|
|
365
|
-
//
|
|
366
|
-
|
|
326
|
+
// Maturity fallback (pure fs checks, no git exec)
|
|
327
|
+
if (intelligencePct === 0) {
|
|
328
|
+
let score = 0;
|
|
329
|
+
if (fs.existsSync(path.join(CWD, '.claude'))) score += 15;
|
|
330
|
+
const srcDirs = ['src', 'lib', 'app', 'packages', 'v3'];
|
|
331
|
+
for (const d of srcDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 15; break; } }
|
|
332
|
+
const testDirs = ['tests', 'test', '__tests__', 'spec'];
|
|
333
|
+
for (const d of testDirs) { if (fs.existsSync(path.join(CWD, d))) { score += 10; break; } }
|
|
334
|
+
const cfgFiles = ['package.json', 'tsconfig.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
|
|
335
|
+
for (const f of cfgFiles) { if (fs.existsSync(path.join(CWD, f))) { score += 5; break; } }
|
|
336
|
+
intelligencePct = Math.min(100, score);
|
|
337
|
+
}
|
|
367
338
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const confScore = Math.min(100, Math.floor(learning.confidenceMean * 100));
|
|
373
|
-
const accessRatio = learning.patterns > 0 ? (learning.accessedCount / learning.patterns) : 0;
|
|
374
|
-
const accessScore = Math.min(100, Math.floor(accessRatio * 100));
|
|
375
|
-
const densityScore = Math.min(100, Math.floor(learning.patterns / 5));
|
|
376
|
-
intelligencePct = Math.floor(confScore * 0.4 + accessScore * 0.3 + densityScore * 0.3);
|
|
339
|
+
if (learningData?.sessions?.total !== undefined) {
|
|
340
|
+
contextPct = Math.min(100, learningData.sessions.total * 5);
|
|
341
|
+
} else {
|
|
342
|
+
contextPct = Math.min(100, Math.floor(learning.sessions * 5));
|
|
377
343
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
344
|
+
|
|
345
|
+
// Sub-agents from file metrics (no ps aux)
|
|
346
|
+
let subAgents = 0;
|
|
347
|
+
const activityData = readJSON(path.join(CWD, '.kynjalflow', 'metrics', 'swarm-activity.json'));
|
|
348
|
+
if (activityData?.processes?.estimated_agents) {
|
|
349
|
+
subAgents = activityData.processes.estimated_agents;
|
|
381
350
|
}
|
|
382
351
|
|
|
383
|
-
|
|
384
|
-
|
|
352
|
+
return { memoryMB, contextPct, intelligencePct, subAgents };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ADR status (count files only β don't read contents)
|
|
356
|
+
function getADRStatus() {
|
|
357
|
+
// Count actual ADR files first β compliance JSON may be stale
|
|
358
|
+
const adrPaths = [
|
|
359
|
+
path.join(CWD, 'v3', 'implementation', 'adrs'),
|
|
360
|
+
path.join(CWD, 'docs', 'adrs'),
|
|
361
|
+
path.join(CWD, '.kynjalflow', 'adrs'),
|
|
362
|
+
];
|
|
385
363
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
364
|
+
for (const adrPath of adrPaths) {
|
|
365
|
+
try {
|
|
366
|
+
if (fs.existsSync(adrPath)) {
|
|
367
|
+
const files = fs.readdirSync(adrPath).filter(f =>
|
|
368
|
+
f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\d{4}-/.test(f))
|
|
369
|
+
);
|
|
370
|
+
// Report actual count β don't guess compliance without reading files
|
|
371
|
+
return { count: files.length, implemented: files.length, compliance: 0 };
|
|
372
|
+
}
|
|
373
|
+
} catch { /* ignore */ }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return { count: 0, implemented: 0, compliance: 0 };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Hooks status (shared settings cache)
|
|
380
|
+
function getHooksStatus() {
|
|
381
|
+
let enabled = 0;
|
|
382
|
+
let total = 0;
|
|
383
|
+
const settings = getSettings();
|
|
384
|
+
|
|
385
|
+
if (settings?.hooks) {
|
|
386
|
+
for (const category of Object.keys(settings.hooks)) {
|
|
387
|
+
const matchers = settings.hooks[category];
|
|
388
|
+
if (!Array.isArray(matchers)) continue;
|
|
389
|
+
for (const matcher of matchers) {
|
|
390
|
+
const hooks = matcher?.hooks;
|
|
391
|
+
if (Array.isArray(hooks)) {
|
|
392
|
+
total += hooks.length;
|
|
393
|
+
enabled += hooks.length;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
395
396
|
}
|
|
396
|
-
} catch (e) {
|
|
397
|
-
// Ignore - default to 0
|
|
398
397
|
}
|
|
399
398
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
399
|
+
try {
|
|
400
|
+
const hooksDir = path.join(CWD, '.claude', 'hooks');
|
|
401
|
+
if (fs.existsSync(hooksDir)) {
|
|
402
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh')).length;
|
|
403
|
+
total = Math.max(total, hookFiles);
|
|
404
|
+
enabled = Math.max(enabled, hookFiles);
|
|
405
|
+
}
|
|
406
|
+
} catch { /* ignore */ }
|
|
407
|
+
|
|
408
|
+
return { enabled, total };
|
|
406
409
|
}
|
|
407
410
|
|
|
408
|
-
//
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
411
|
+
// AgentDB stats β count real entries, not file-size heuristics
|
|
412
|
+
function getAgentDBStats() {
|
|
413
|
+
let vectorCount = 0;
|
|
414
|
+
let dbSizeKB = 0;
|
|
415
|
+
let namespaces = 0;
|
|
416
|
+
let hasHnsw = false;
|
|
417
|
+
|
|
418
|
+
// 1. Count real entries from auto-memory-store.json
|
|
419
|
+
const storePath = path.join(CWD, '.kynjalflow', 'data', 'auto-memory-store.json');
|
|
420
|
+
const storeStat = safeStat(storePath);
|
|
421
|
+
if (storeStat) {
|
|
422
|
+
dbSizeKB += storeStat.size / 1024;
|
|
423
|
+
try {
|
|
424
|
+
const store = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
|
|
425
|
+
if (Array.isArray(store)) vectorCount += store.length;
|
|
426
|
+
else if (store?.entries) vectorCount += store.entries.length;
|
|
427
|
+
} catch { /* fall back to size estimate */ }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 2. Count entries from ranked-context.json
|
|
431
|
+
const rankedPath = path.join(CWD, '.kynjalflow', 'data', 'ranked-context.json');
|
|
432
|
+
try {
|
|
433
|
+
const ranked = readJSON(rankedPath);
|
|
434
|
+
if (ranked?.entries?.length > vectorCount) vectorCount = ranked.entries.length;
|
|
435
|
+
} catch { /* ignore */ }
|
|
436
|
+
|
|
437
|
+
// 3. Add DB file sizes
|
|
438
|
+
const dbFiles = [
|
|
439
|
+
path.join(CWD, 'data', 'memory.db'),
|
|
440
|
+
path.join(CWD, '.kynjalflow', 'memory.db'),
|
|
441
|
+
path.join(CWD, '.swarm', 'memory.db'),
|
|
442
|
+
];
|
|
443
|
+
for (const f of dbFiles) {
|
|
444
|
+
const stat = safeStat(f);
|
|
445
|
+
if (stat) {
|
|
446
|
+
dbSizeKB += stat.size / 1024;
|
|
447
|
+
namespaces++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 4. Check for graph data
|
|
452
|
+
const graphPath = path.join(CWD, 'data', 'memory.graph');
|
|
453
|
+
const graphStat = safeStat(graphPath);
|
|
454
|
+
if (graphStat) dbSizeKB += graphStat.size / 1024;
|
|
455
|
+
|
|
456
|
+
// 5. HNSW index
|
|
457
|
+
const hnswPaths = [
|
|
458
|
+
path.join(CWD, '.swarm', 'hnsw.index'),
|
|
459
|
+
path.join(CWD, '.kynjalflow', 'hnsw.index'),
|
|
460
|
+
];
|
|
461
|
+
for (const p of hnswPaths) {
|
|
462
|
+
const stat = safeStat(p);
|
|
463
|
+
if (stat) {
|
|
464
|
+
hasHnsw = true;
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// HNSW is available if memory package is present
|
|
470
|
+
if (!hasHnsw) {
|
|
471
|
+
const memPkgPaths = [
|
|
472
|
+
path.join(CWD, 'v3', '@claude-flow', 'memory', 'dist'),
|
|
473
|
+
path.join(CWD, 'node_modules', '@claude-flow', 'memory'),
|
|
474
|
+
];
|
|
475
|
+
for (const p of memPkgPaths) {
|
|
476
|
+
if (fs.existsSync(p)) { hasHnsw = true; break; }
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return { vectorCount, dbSizeKB: Math.floor(dbSizeKB), namespaces, hasHnsw };
|
|
414
481
|
}
|
|
415
482
|
|
|
416
|
-
//
|
|
417
|
-
function
|
|
418
|
-
|
|
419
|
-
const progress = getV3Progress();
|
|
420
|
-
const security = getSecurityStatus();
|
|
421
|
-
const swarm = getSwarmStatus();
|
|
422
|
-
const system = getSystemMetrics();
|
|
423
|
-
const lines = [];
|
|
483
|
+
// Test stats (count files only β NO reading file contents)
|
|
484
|
+
function getTestStats() {
|
|
485
|
+
let testFiles = 0;
|
|
424
486
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
487
|
+
function countTestFiles(dir, depth = 0) {
|
|
488
|
+
if (depth > 6) return;
|
|
489
|
+
try {
|
|
490
|
+
if (!fs.existsSync(dir)) return;
|
|
491
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
492
|
+
for (const entry of entries) {
|
|
493
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
494
|
+
countTestFiles(path.join(dir, entry.name), depth + 1);
|
|
495
|
+
} else if (entry.isFile()) {
|
|
496
|
+
const n = entry.name;
|
|
497
|
+
if (n.includes('.test.') || n.includes('.spec.') || n.includes('_test.') || n.includes('_spec.')) {
|
|
498
|
+
testFiles++;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} catch { /* ignore */ }
|
|
430
503
|
}
|
|
431
|
-
header += ` ${c.dim}β${c.reset} ${c.purple}${user.modelName}${c.reset}`;
|
|
432
|
-
lines.push(header);
|
|
433
504
|
|
|
434
|
-
//
|
|
435
|
-
|
|
505
|
+
// Scan all source directories
|
|
506
|
+
for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) {
|
|
507
|
+
countTestFiles(path.join(CWD, d));
|
|
508
|
+
}
|
|
436
509
|
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
`${c.brightCyan}ποΈ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
|
|
441
|
-
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
|
|
442
|
-
`${c.brightYellow}β‘ 1.0x${c.reset} ${c.dim}β${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
|
|
443
|
-
);
|
|
510
|
+
// Estimate ~4 test cases per file (avoids reading every file)
|
|
511
|
+
return { testFiles, testCases: testFiles * 4 };
|
|
512
|
+
}
|
|
444
513
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
514
|
+
// Integration status (shared settings + file checks)
|
|
515
|
+
function getIntegrationStatus() {
|
|
516
|
+
const mcpServers = { total: 0, enabled: 0 };
|
|
517
|
+
const settings = getSettings();
|
|
518
|
+
|
|
519
|
+
if (settings?.mcpServers && typeof settings.mcpServers === 'object') {
|
|
520
|
+
const servers = Object.keys(settings.mcpServers);
|
|
521
|
+
mcpServers.total = servers.length;
|
|
522
|
+
mcpServers.enabled = settings.enabledMcpjsonServers
|
|
523
|
+
? settings.enabledMcpjsonServers.filter(s => servers.includes(s)).length
|
|
524
|
+
: servers.length;
|
|
525
|
+
}
|
|
450
526
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
527
|
+
// Fallback: .mcp.json
|
|
528
|
+
if (mcpServers.total === 0) {
|
|
529
|
+
const mcpConfig = readJSON(path.join(CWD, '.mcp.json'))
|
|
530
|
+
|| readJSON(path.join(os.homedir(), '.claude', 'mcp.json'));
|
|
531
|
+
if (mcpConfig?.mcpServers) {
|
|
532
|
+
const s = Object.keys(mcpConfig.mcpServers);
|
|
533
|
+
mcpServers.total = s.length;
|
|
534
|
+
mcpServers.enabled = s.length;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
459
537
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
`${c.brightPurple}π§ Architecture${c.reset} ` +
|
|
464
|
-
`${c.cyan}DDD${c.reset} ${dddColor}β${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}β${c.reset} ` +
|
|
465
|
-
`${c.cyan}Security${c.reset} ${securityColor}β${security.status}${c.reset} ${c.dim}β${c.reset} ` +
|
|
466
|
-
`${c.cyan}Memory${c.reset} ${c.brightGreen}βAgentDB${c.reset} ${c.dim}β${c.reset} ` +
|
|
467
|
-
`${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}β${c.reset}`
|
|
468
|
-
);
|
|
538
|
+
const hasDatabase = ['.swarm/memory.db', '.kynjalflow/memory.db', 'data/memory.db']
|
|
539
|
+
.some(p => fs.existsSync(path.join(CWD, p)));
|
|
540
|
+
const hasApi = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
469
541
|
|
|
470
|
-
return
|
|
542
|
+
return { mcpServers, hasDatabase, hasApi };
|
|
471
543
|
}
|
|
472
544
|
|
|
473
|
-
//
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
},
|
|
486
|
-
lastUpdated: new Date().toISOString(),
|
|
487
|
-
};
|
|
545
|
+
// Session stats (pure file reads)
|
|
546
|
+
function getSessionStats() {
|
|
547
|
+
for (const p of ['.kynjalflow/session.json', '.claude/session.json']) {
|
|
548
|
+
const data = readJSON(path.join(CWD, p));
|
|
549
|
+
if (data?.startTime) {
|
|
550
|
+
const diffMs = Date.now() - new Date(data.startTime).getTime();
|
|
551
|
+
const mins = Math.floor(diffMs / 60000);
|
|
552
|
+
const duration = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h${mins % 60}m`;
|
|
553
|
+
return { duration };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return { duration: '' };
|
|
488
557
|
}
|
|
489
558
|
|
|
490
|
-
|
|
491
|
-
* Generate single-line output for Claude Code compatibility
|
|
492
|
-
* This avoids the collision zone issue entirely by using one line
|
|
493
|
-
* @see https://github.com/ruvnet/claude-flow/issues/985
|
|
494
|
-
*/
|
|
495
|
-
function generateSingleLine() {
|
|
496
|
-
if (!CONFIG.enabled) return '';
|
|
559
|
+
// βββ Rendering ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
497
560
|
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
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();
|
|
501
571
|
const swarm = getSwarmStatus();
|
|
502
572
|
const system = getSystemMetrics();
|
|
573
|
+
const session = getSessionStats();
|
|
574
|
+
const hooks = getHooksStatus();
|
|
575
|
+
const integration = getIntegrationStatus();
|
|
576
|
+
|
|
577
|
+
const parts = [];
|
|
578
|
+
|
|
579
|
+
// Branding
|
|
580
|
+
parts.push(`${c.bold}${c.brightPurple}\u258A KynjalFlow${c.reset}`);
|
|
581
|
+
|
|
582
|
+
// User + swarm indicator
|
|
583
|
+
const dot = swarm.coordinationActive ? `${c.brightGreen}\u25CF${c.reset}` : `${c.brightCyan}\u25CF${c.reset}`;
|
|
584
|
+
parts.push(`${dot} ${c.brightCyan}${git.name}${c.reset}`);
|
|
585
|
+
|
|
586
|
+
// Git branch + changes
|
|
587
|
+
if (git.gitBranch) {
|
|
588
|
+
let branchPart = `${c.brightBlue}\u23C7 ${git.gitBranch}${c.reset}`;
|
|
589
|
+
const changes = [];
|
|
590
|
+
if (git.staged > 0) changes.push(`${c.brightGreen}+${git.staged}${c.reset}`);
|
|
591
|
+
if (git.modified > 0) changes.push(`${c.brightYellow}~${git.modified}${c.reset}`);
|
|
592
|
+
if (git.untracked > 0) changes.push(`${c.dim}?${git.untracked}${c.reset}`);
|
|
593
|
+
if (changes.length > 0) branchPart += ` ${changes.join('')}`;
|
|
594
|
+
if (git.ahead > 0) branchPart += ` ${c.brightGreen}\u2191${git.ahead}${c.reset}`;
|
|
595
|
+
if (git.behind > 0) branchPart += ` ${c.brightRed}\u2193${git.behind}${c.reset}`;
|
|
596
|
+
parts.push(branchPart);
|
|
597
|
+
}
|
|
503
598
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
security.cvesFixed > 0 ? '~' : 'β';
|
|
599
|
+
// Model
|
|
600
|
+
parts.push(`${c.purple}${modelName}${c.reset}`);
|
|
507
601
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
`${c.
|
|
511
|
-
|
|
512
|
-
`${c.dim}π§ ${system.intelligencePct}%${c.reset}`;
|
|
513
|
-
}
|
|
602
|
+
// Session duration
|
|
603
|
+
if (session.duration) {
|
|
604
|
+
parts.push(`${c.cyan}\u23F1 ${session.duration}${c.reset}`);
|
|
605
|
+
}
|
|
514
606
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
607
|
+
// Intelligence %
|
|
608
|
+
const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
|
|
609
|
+
parts.push(`${intellColor}\u25CF ${system.intelligencePct}%${c.reset}`);
|
|
610
|
+
|
|
611
|
+
// Swarm agents (only if active)
|
|
612
|
+
if (swarm.activeAgents > 0 || swarm.coordinationActive) {
|
|
613
|
+
parts.push(`${c.brightYellow}\u25C9 ${swarm.activeAgents}/${swarm.maxAgents}${c.reset}`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Hooks (compact)
|
|
617
|
+
if (hooks.enabled > 0) {
|
|
618
|
+
parts.push(`${c.brightGreen}\u2713${hooks.enabled}h${c.reset}`);
|
|
619
|
+
}
|
|
523
620
|
|
|
524
|
-
|
|
621
|
+
// MCP (compact)
|
|
622
|
+
if (integration.mcpServers.total > 0) {
|
|
623
|
+
const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen : c.brightYellow;
|
|
624
|
+
parts.push(`${mcpCol}MCP${integration.mcpServers.enabled}${c.reset}`);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return parts.join(` ${c.dim}\u2502${c.reset} `);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Multi-line dashboard (for --dashboard flag)
|
|
631
|
+
function generateDashboard() {
|
|
632
|
+
const git = getGitInfo();
|
|
633
|
+
const modelName = getModelName();
|
|
525
634
|
const progress = getV3Progress();
|
|
526
635
|
const security = getSecurityStatus();
|
|
527
636
|
const swarm = getSwarmStatus();
|
|
528
637
|
const system = getSystemMetrics();
|
|
638
|
+
const adrs = getADRStatus();
|
|
639
|
+
const hooks = getHooksStatus();
|
|
640
|
+
const agentdb = getAgentDBStats();
|
|
641
|
+
const tests = getTestStats();
|
|
642
|
+
const session = getSessionStats();
|
|
643
|
+
const integration = getIntegrationStatus();
|
|
529
644
|
const lines = [];
|
|
530
645
|
|
|
531
|
-
// Header
|
|
532
|
-
let header = `${c.bold}${c.brightPurple}
|
|
533
|
-
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}
|
|
534
|
-
if (
|
|
535
|
-
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}`;
|
|
536
661
|
}
|
|
537
|
-
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}`;
|
|
538
664
|
lines.push(header);
|
|
539
665
|
|
|
540
666
|
// Separator
|
|
541
|
-
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}`);
|
|
542
668
|
|
|
543
|
-
// Line 1: DDD
|
|
669
|
+
// Line 1: DDD Domains + perf
|
|
544
670
|
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
|
671
|
+
let perfIndicator;
|
|
672
|
+
if (agentdb.hasHnsw && agentdb.vectorCount > 0) {
|
|
673
|
+
const speedup = agentdb.vectorCount > 10000 ? '12500x' : agentdb.vectorCount > 1000 ? '150x' : '10x';
|
|
674
|
+
perfIndicator = `${c.brightGreen}\u26A1 HNSW ${speedup}${c.reset}`;
|
|
675
|
+
} else if (progress.patternsLearned > 0) {
|
|
676
|
+
const pk = progress.patternsLearned >= 1000 ? `${(progress.patternsLearned / 1000).toFixed(1)}k` : String(progress.patternsLearned);
|
|
677
|
+
perfIndicator = `${c.brightYellow}\uD83D\uDCDA ${pk} patterns${c.reset}`;
|
|
678
|
+
} else {
|
|
679
|
+
perfIndicator = `${c.dim}\u26A1 target: 150x-12500x${c.reset}`;
|
|
680
|
+
}
|
|
545
681
|
lines.push(
|
|
546
|
-
`${c.brightCyan}
|
|
547
|
-
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} `
|
|
548
|
-
`${c.brightYellow}β‘ 1.0x${c.reset} ${c.dim}β${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
|
|
682
|
+
`${c.brightCyan}\uD83C\uDFD7\uFE0F DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
|
|
683
|
+
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ${perfIndicator}`
|
|
549
684
|
);
|
|
550
685
|
|
|
551
|
-
// Line 2
|
|
552
|
-
|
|
553
|
-
const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}β${c.reset}` : `${c.dim}β${c.reset}`;
|
|
686
|
+
// Line 2: Swarm + Hooks + CVE + Memory + Intelligence
|
|
687
|
+
const swarmInd = swarm.coordinationActive ? `${c.brightGreen}\u25C9${c.reset}` : `${c.dim}\u25CB${c.reset}`;
|
|
554
688
|
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
|
|
555
|
-
|
|
556
|
-
|
|
689
|
+
const secIcon = security.status === 'CLEAN' ? '\uD83D\uDFE2' : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? '\uD83D\uDFE1' : '\uD83D\uDD34';
|
|
690
|
+
const secColor = security.status === 'CLEAN' ? c.brightGreen : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? c.brightYellow : c.brightRed;
|
|
691
|
+
const hooksColor = hooks.enabled > 0 ? c.brightGreen : c.dim;
|
|
692
|
+
const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
|
|
557
693
|
|
|
558
|
-
// CRITICAL: 24 spaces after π€ (emoji is 2 cols, so 2+24=26, past collision zone cols 15-25)
|
|
559
694
|
lines.push(
|
|
560
|
-
`${c.brightYellow}
|
|
561
|
-
`${
|
|
562
|
-
`${c.
|
|
563
|
-
`${
|
|
564
|
-
`${c.brightCyan}
|
|
565
|
-
`${
|
|
695
|
+
`${c.brightYellow}\uD83E\uDD16 Swarm${c.reset} ${swarmInd} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
|
|
696
|
+
`${c.brightPurple}\uD83D\uDC65 ${system.subAgents}${c.reset} ` +
|
|
697
|
+
`${c.brightBlue}\uD83E\uDE9D ${hooksColor}${hooks.enabled}${c.reset}/${c.brightWhite}${hooks.total}${c.reset} ` +
|
|
698
|
+
`${secIcon} ${secColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
|
|
699
|
+
`${c.brightCyan}\uD83D\uDCBE ${system.memoryMB}MB${c.reset} ` +
|
|
700
|
+
`${intellColor}\uD83E\uDDE0 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
|
|
566
701
|
);
|
|
567
702
|
|
|
568
|
-
// Line 3: Architecture
|
|
703
|
+
// Line 3: Architecture
|
|
569
704
|
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
|
|
705
|
+
const adrColor = adrs.count > 0 ? (adrs.implemented === adrs.count ? c.brightGreen : c.yellow) : c.dim;
|
|
706
|
+
const adrDisplay = adrs.compliance > 0 ? `${adrColor}\u25CF${adrs.compliance}%${c.reset}` : `${adrColor}\u25CF${adrs.implemented}/${adrs.count}${c.reset}`;
|
|
707
|
+
|
|
708
|
+
lines.push(
|
|
709
|
+
`${c.brightPurple}\uD83D\uDD27 Architecture${c.reset} ` +
|
|
710
|
+
`${c.cyan}ADRs${c.reset} ${adrDisplay} ${c.dim}\u2502${c.reset} ` +
|
|
711
|
+
`${c.cyan}DDD${c.reset} ${dddColor}\u25CF${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}\u2502${c.reset} ` +
|
|
712
|
+
`${c.cyan}Security${c.reset} ${secColor}\u25CF${security.status}${c.reset}`
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
// Line 4: AgentDB, Tests, Integration
|
|
716
|
+
const hnswInd = agentdb.hasHnsw ? `${c.brightGreen}\u26A1${c.reset}` : '';
|
|
717
|
+
const sizeDisp = agentdb.dbSizeKB >= 1024 ? `${(agentdb.dbSizeKB / 1024).toFixed(1)}MB` : `${agentdb.dbSizeKB}KB`;
|
|
718
|
+
const vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
|
|
719
|
+
const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
|
|
720
|
+
|
|
721
|
+
let integStr = '';
|
|
722
|
+
if (integration.mcpServers.total > 0) {
|
|
723
|
+
const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
|
|
724
|
+
integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
|
|
725
|
+
integStr += `${c.cyan}MCP${c.reset} ${mcpCol}\u25CF${integration.mcpServers.enabled}/${integration.mcpServers.total}${c.reset}`;
|
|
726
|
+
}
|
|
727
|
+
if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + `${c.brightGreen}\u25C6${c.reset}DB`;
|
|
728
|
+
if (integration.hasApi) integStr += (integStr ? ' ' : '') + `${c.brightGreen}\u25C6${c.reset}API`;
|
|
729
|
+
if (!integStr) integStr = `${c.dim}\u25CF none${c.reset}`;
|
|
730
|
+
|
|
570
731
|
lines.push(
|
|
571
|
-
`${c.
|
|
572
|
-
`${c.cyan}
|
|
573
|
-
`${c.cyan}
|
|
574
|
-
`${c.cyan}
|
|
575
|
-
|
|
732
|
+
`${c.brightCyan}\uD83D\uDCCA AgentDB${c.reset} ` +
|
|
733
|
+
`${c.cyan}Vectors${c.reset} ${vectorColor}\u25CF${agentdb.vectorCount}${hnswInd}${c.reset} ${c.dim}\u2502${c.reset} ` +
|
|
734
|
+
`${c.cyan}Size${c.reset} ${c.brightWhite}${sizeDisp}${c.reset} ${c.dim}\u2502${c.reset} ` +
|
|
735
|
+
`${c.cyan}Tests${c.reset} ${testColor}\u25CF${tests.testFiles}${c.reset} ${c.dim}(~${tests.testCases} cases)${c.reset} ${c.dim}\u2502${c.reset} ` +
|
|
736
|
+
integStr
|
|
576
737
|
);
|
|
577
738
|
|
|
578
739
|
return lines.join('\n');
|
|
579
740
|
}
|
|
580
741
|
|
|
581
|
-
//
|
|
742
|
+
// JSON output
|
|
743
|
+
function generateJSON() {
|
|
744
|
+
const git = getGitInfo();
|
|
745
|
+
return {
|
|
746
|
+
user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
|
|
747
|
+
v3Progress: getV3Progress(),
|
|
748
|
+
security: getSecurityStatus(),
|
|
749
|
+
swarm: getSwarmStatus(),
|
|
750
|
+
system: getSystemMetrics(),
|
|
751
|
+
adrs: getADRStatus(),
|
|
752
|
+
hooks: getHooksStatus(),
|
|
753
|
+
agentdb: getAgentDBStats(),
|
|
754
|
+
tests: getTestStats(),
|
|
755
|
+
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
|
|
756
|
+
lastUpdated: new Date().toISOString(),
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// βββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
582
761
|
if (process.argv.includes('--json')) {
|
|
583
762
|
console.log(JSON.stringify(generateJSON(), null, 2));
|
|
584
763
|
} else if (process.argv.includes('--compact')) {
|
|
585
764
|
console.log(JSON.stringify(generateJSON()));
|
|
586
|
-
} else if (process.argv.includes('--single')) {
|
|
587
|
-
// Single-line mode - completely avoids collision zone
|
|
588
|
-
console.log(generateSingleLine());
|
|
589
|
-
} else if (process.argv.includes('--unsafe') || process.argv.includes('--legacy')) {
|
|
590
|
-
// Legacy mode - original multi-line without collision avoidance
|
|
765
|
+
} else if (process.argv.includes('--single-line')) {
|
|
591
766
|
console.log(generateStatusline());
|
|
592
767
|
} else {
|
|
593
|
-
// Default:
|
|
594
|
-
|
|
595
|
-
console.log(generateSafeStatusline());
|
|
768
|
+
// Default: multi-line dashboard
|
|
769
|
+
console.log(generateDashboard());
|
|
596
770
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kynjal-cli",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"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",
|