@claude-flow/cli 3.10.3 → 3.10.5

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.
Files changed (121) hide show
  1. package/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
  2. package/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts.map +1 -0
  3. package/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
  4. package/dist/src/benchmarks/gaia-agent-planning.smoke.js.map +1 -0
  5. package/dist/src/benchmarks/gaia-agent.d.ts +198 -0
  6. package/dist/src/benchmarks/gaia-agent.d.ts.map +1 -0
  7. package/dist/src/benchmarks/gaia-agent.js +651 -0
  8. package/dist/src/benchmarks/gaia-agent.js.map +1 -0
  9. package/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
  10. package/dist/src/benchmarks/gaia-causal-memory.d.ts.map +1 -0
  11. package/dist/src/benchmarks/gaia-causal-memory.js +281 -0
  12. package/dist/src/benchmarks/gaia-causal-memory.js.map +1 -0
  13. package/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
  14. package/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts.map +1 -0
  15. package/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
  16. package/dist/src/benchmarks/gaia-causal-memory.smoke.js.map +1 -0
  17. package/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
  18. package/dist/src/benchmarks/gaia-convergence.d.ts.map +1 -0
  19. package/dist/src/benchmarks/gaia-convergence.js +260 -0
  20. package/dist/src/benchmarks/gaia-convergence.js.map +1 -0
  21. package/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
  22. package/dist/src/benchmarks/gaia-convergence.smoke.d.ts.map +1 -0
  23. package/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
  24. package/dist/src/benchmarks/gaia-convergence.smoke.js.map +1 -0
  25. package/dist/src/benchmarks/gaia-critic.d.ts +123 -0
  26. package/dist/src/benchmarks/gaia-critic.d.ts.map +1 -0
  27. package/dist/src/benchmarks/gaia-critic.js +312 -0
  28. package/dist/src/benchmarks/gaia-critic.js.map +1 -0
  29. package/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
  30. package/dist/src/benchmarks/gaia-critic.smoke.d.ts.map +1 -0
  31. package/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
  32. package/dist/src/benchmarks/gaia-critic.smoke.js.map +1 -0
  33. package/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
  34. package/dist/src/benchmarks/gaia-decomposer.d.ts.map +1 -0
  35. package/dist/src/benchmarks/gaia-decomposer.js +350 -0
  36. package/dist/src/benchmarks/gaia-decomposer.js.map +1 -0
  37. package/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
  38. package/dist/src/benchmarks/gaia-decomposer.smoke.d.ts.map +1 -0
  39. package/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
  40. package/dist/src/benchmarks/gaia-decomposer.smoke.js.map +1 -0
  41. package/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
  42. package/dist/src/benchmarks/gaia-e2e-smoke.d.ts.map +1 -0
  43. package/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
  44. package/dist/src/benchmarks/gaia-e2e-smoke.js.map +1 -0
  45. package/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
  46. package/dist/src/benchmarks/gaia-extract.smoke.d.ts.map +1 -0
  47. package/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
  48. package/dist/src/benchmarks/gaia-extract.smoke.js.map +1 -0
  49. package/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
  50. package/dist/src/benchmarks/gaia-hardness/features.d.ts.map +1 -0
  51. package/dist/src/benchmarks/gaia-hardness/features.js +170 -0
  52. package/dist/src/benchmarks/gaia-hardness/features.js.map +1 -0
  53. package/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
  54. package/dist/src/benchmarks/gaia-hardness/predictor.d.ts.map +1 -0
  55. package/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
  56. package/dist/src/benchmarks/gaia-hardness/predictor.js.map +1 -0
  57. package/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
  58. package/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts.map +1 -0
  59. package/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
  60. package/dist/src/benchmarks/gaia-hardness/predictor.smoke.js.map +1 -0
  61. package/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
  62. package/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts.map +1 -0
  63. package/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
  64. package/dist/src/benchmarks/gaia-hardness/train-data-loader.js.map +1 -0
  65. package/dist/src/benchmarks/gaia-judge.d.ts +88 -0
  66. package/dist/src/benchmarks/gaia-judge.d.ts.map +1 -0
  67. package/dist/src/benchmarks/gaia-judge.js +437 -0
  68. package/dist/src/benchmarks/gaia-judge.js.map +1 -0
  69. package/dist/src/benchmarks/gaia-loader.d.ts +87 -0
  70. package/dist/src/benchmarks/gaia-loader.d.ts.map +1 -0
  71. package/dist/src/benchmarks/gaia-loader.js +326 -0
  72. package/dist/src/benchmarks/gaia-loader.js.map +1 -0
  73. package/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
  74. package/dist/src/benchmarks/gaia-tools/file_read.d.ts.map +1 -0
  75. package/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
  76. package/dist/src/benchmarks/gaia-tools/file_read.js.map +1 -0
  77. package/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
  78. package/dist/src/benchmarks/gaia-tools/grounded_query.d.ts.map +1 -0
  79. package/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
  80. package/dist/src/benchmarks/gaia-tools/grounded_query.js.map +1 -0
  81. package/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
  82. package/dist/src/benchmarks/gaia-tools/index.d.ts.map +1 -0
  83. package/dist/src/benchmarks/gaia-tools/index.js +36 -0
  84. package/dist/src/benchmarks/gaia-tools/index.js.map +1 -0
  85. package/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
  86. package/dist/src/benchmarks/gaia-tools/types.d.ts.map +1 -0
  87. package/dist/src/benchmarks/gaia-tools/types.js +12 -0
  88. package/dist/src/benchmarks/gaia-tools/types.js.map +1 -0
  89. package/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
  90. package/dist/src/benchmarks/gaia-tools/web_search.d.ts.map +1 -0
  91. package/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
  92. package/dist/src/benchmarks/gaia-tools/web_search.js.map +1 -0
  93. package/dist/src/benchmarks/gaia-voting.d.ts +88 -0
  94. package/dist/src/benchmarks/gaia-voting.d.ts.map +1 -0
  95. package/dist/src/benchmarks/gaia-voting.js +297 -0
  96. package/dist/src/benchmarks/gaia-voting.js.map +1 -0
  97. package/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
  98. package/dist/src/benchmarks/gaia-voting.smoke.d.ts.map +1 -0
  99. package/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
  100. package/dist/src/benchmarks/gaia-voting.smoke.js.map +1 -0
  101. package/dist/src/commands/gaia-bench.d.ts +40 -0
  102. package/dist/src/commands/gaia-bench.d.ts.map +1 -0
  103. package/dist/src/commands/gaia-bench.js +597 -0
  104. package/dist/src/commands/gaia-bench.js.map +1 -0
  105. package/dist/src/commands/index.d.ts.map +1 -1
  106. package/dist/src/commands/index.js +2 -0
  107. package/dist/src/commands/index.js.map +1 -1
  108. package/dist/src/commands/init.d.ts.map +1 -1
  109. package/dist/src/commands/init.js +41 -4
  110. package/dist/src/commands/init.js.map +1 -1
  111. package/dist/src/init/executor.d.ts.map +1 -1
  112. package/dist/src/init/executor.js +25 -6
  113. package/dist/src/init/executor.js.map +1 -1
  114. package/dist/src/init/mcp-generator.d.ts.map +1 -1
  115. package/dist/src/init/mcp-generator.js +9 -4
  116. package/dist/src/init/mcp-generator.js.map +1 -1
  117. package/dist/src/init/statusline-generator.d.ts.map +1 -1
  118. package/dist/src/init/statusline-generator.js +287 -642
  119. package/dist/src/init/statusline-generator.js.map +1 -1
  120. package/dist/tsconfig.tsbuildinfo +1 -1
  121. package/package.json +1 -1
@@ -23,17 +23,23 @@ export function generateStatuslineScript(options) {
23
23
  const maxAgents = options.runtime.maxAgents;
24
24
  return `#!/usr/bin/env node
25
25
  /**
26
- * RuFlo V3 Statusline Generator (Optimized)
27
- * Displays real-time V3 implementation progress and system status
26
+ * RuFlo V3 Statusline delegation build (#2195)
28
27
  *
29
- * Usage: node statusline.cjs [--json] [--compact]
28
+ * Fix for ruvnet/ruflo#2195: the previous version re-implemented all data
29
+ * readers locally using fragile file probes that missed AgentDB patterns,
30
+ * the v3/docs/adr/ ADR directory, and the real vector count.
30
31
  *
31
- * Performance notes:
32
- * - Single git execSync call (combines branch + status + upstream)
33
- * - No recursive file reading (only stat/readdir, never read test contents)
34
- * - No ps aux calls (uses process.memoryUsage() + file-based metrics)
35
- * - Strict 2s timeout on all execSync calls
36
- * - Shared settings cache across functions
32
+ * This version delegates to 'npx @claude-flow/cli hooks statusline --json'
33
+ * as the single source of truth. That command queries AgentDB directly,
34
+ * counts ADRs in both directories, and reports the real intelligence pct.
35
+ *
36
+ * ADR counting falls back to local file reads so the display still works
37
+ * without network access (counts both v3/docs/adr/ and v3/implementation/adrs/).
38
+ *
39
+ * Cache: JSON result is cached in /tmp for 10s so rapid prompt triggers
40
+ * (every keystroke in some shells) don't hammer the CLI on every call.
41
+ *
42
+ * Usage: node statusline.cjs [--json] [--compact] [--dashboard]
37
43
  */
38
44
 
39
45
  /* eslint-disable @typescript-eslint/no-var-requires */
@@ -49,6 +55,120 @@ const CONFIG = {
49
55
 
50
56
  const CWD = process.cwd();
51
57
 
58
+ // ─── Delegation cache ───────────────────────────────────────────
59
+ // Cache the CLI JSON result for 10s so rapid prompt re-renders
60
+ // (e.g. every keypress in some shells) don't re-invoke npx each time.
61
+ const CACHE_FILE = path.join(os.tmpdir(), 'ruflo-statusline-cache-' + require('crypto').createHash('md5').update(CWD).digest('hex').slice(0, 8) + '.json');
62
+ const CACHE_TTL_MS = 10000;
63
+
64
+ function readCache() {
65
+ try {
66
+ if (fs.existsSync(CACHE_FILE)) {
67
+ const raw = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
68
+ if (raw && raw._ts && (Date.now() - raw._ts) < CACHE_TTL_MS) {
69
+ return raw.data;
70
+ }
71
+ }
72
+ } catch { /* ignore */ }
73
+ return null;
74
+ }
75
+
76
+ function writeCache(data) {
77
+ try { fs.writeFileSync(CACHE_FILE, JSON.stringify({ _ts: Date.now(), data }), 'utf-8'); } catch { /* ignore */ }
78
+ }
79
+
80
+ /**
81
+ * Single source of truth: delegate to the CLI hooks statusline --json command.
82
+ * Falls back to a minimal static object on failure so the statusline still renders.
83
+ *
84
+ * Fix for ruflo#2195: the previous local readers returned 0 for AgentDB patterns
85
+ * (missed the .swarm/memory.db → AgentDB path), computed dddProgress wrong,
86
+ * and only counted ADRs in v3/implementation/adrs/ (missed v3/docs/adr/).
87
+ */
88
+ function getStatuslineData() {
89
+ const cached = readCache();
90
+ if (cached) return cached;
91
+
92
+ try {
93
+ const raw = execSync(
94
+ 'npx --yes @claude-flow/cli@latest hooks statusline --json 2>/dev/null',
95
+ { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], cwd: CWD }
96
+ ).trim();
97
+ // The CLI may emit preamble lines before the JSON — find the first '{'.
98
+ const jsonStart = raw.indexOf('{');
99
+ if (jsonStart === -1) throw new Error('no JSON in CLI output');
100
+ const data = JSON.parse(raw.slice(jsonStart));
101
+ // Overlay real ADR count from both local directories (fast, no network).
102
+ data.adrs = getLocalADRCount();
103
+ writeCache(data);
104
+ return data;
105
+ } catch { /* CLI unavailable or timed out */ }
106
+
107
+ // Fallback: use local file probes only (will be less accurate, but non-zero
108
+ // when CLI is available and accurate when it's not).
109
+ return buildLocalFallback();
110
+ }
111
+
112
+ // Count ADRs from BOTH known directories (fix for ruflo#2195: old code missed
113
+ // v3/docs/adr/ which holds ADR-088..ADR-137, i.e. 41 of the 128 total ADRs).
114
+ function getLocalADRCount() {
115
+ const adrDirs = [
116
+ path.join(CWD, 'v3', 'implementation', 'adrs'),
117
+ path.join(CWD, 'v3', 'docs', 'adr'),
118
+ path.join(CWD, 'docs', 'adrs'),
119
+ path.join(CWD, '.claude-flow', 'adrs'),
120
+ ];
121
+ let total = 0;
122
+ for (const dir of adrDirs) {
123
+ try {
124
+ if (fs.existsSync(dir)) {
125
+ const files = fs.readdirSync(dir).filter(function(f) {
126
+ return f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\\d{4}-/.test(f));
127
+ });
128
+ total += files.length;
129
+ }
130
+ } catch { /* ignore */ }
131
+ }
132
+ return { count: total, implemented: total, compliance: 0 };
133
+ }
134
+
135
+ // Minimal local fallback when the CLI is not installed or times out.
136
+ // Returns a structure that matches the CLI JSON schema so the renderer works.
137
+ function buildLocalFallback() {
138
+ const memMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
139
+ const adrs = getLocalADRCount();
140
+
141
+ // Test file count (pure directory walk, no file reads)
142
+ let testFiles = 0;
143
+ function countTests(dir, depth) {
144
+ if ((depth || 0) > 4) return;
145
+ try {
146
+ if (!fs.existsSync(dir)) return;
147
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
148
+ if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') {
149
+ countTests(path.join(dir, e.name), (depth || 0) + 1);
150
+ } else if (e.isFile() && (e.name.includes('.test.') || e.name.includes('.spec.') || e.name.startsWith('test_') || e.name.startsWith('spec_'))) {
151
+ testFiles++;
152
+ }
153
+ }
154
+ } catch { /* ignore */ }
155
+ }
156
+ for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) countTests(path.join(CWD, d));
157
+
158
+ return {
159
+ user: { name: 'user', gitBranch: '', modelName: 'Claude Code' },
160
+ v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 },
161
+ security: { status: 'NONE', cvesFixed: 0, totalCves: 0 },
162
+ swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false },
163
+ system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 },
164
+ adrs,
165
+ hooks: { enabled: 0, total: 0 },
166
+ agentdb: { vectorCount: 0, dbSizeKB: 0, hasHnsw: false },
167
+ tests: { testFiles, testCases: testFiles * 4 },
168
+ lastUpdated: new Date().toISOString(),
169
+ };
170
+ }
171
+
52
172
  // ANSI colors
53
173
  const c = {
54
174
  reset: '\\x1b[0m',
@@ -70,11 +190,11 @@ const c = {
70
190
  };
71
191
 
72
192
  // Safe execSync with strict timeout (returns empty string on failure)
73
- function safeExec(cmd, timeoutMs = 2000) {
193
+ function safeExec(cmd, timeoutMs) {
74
194
  try {
75
195
  return execSync(cmd, {
76
196
  encoding: 'utf-8',
77
- timeout: timeoutMs,
197
+ timeout: timeoutMs || 2000,
78
198
  stdio: ['pipe', 'pipe', 'pipe'],
79
199
  }).trim();
80
200
  } catch {
@@ -92,34 +212,14 @@ function readJSON(filePath) {
92
212
  return null;
93
213
  }
94
214
 
95
- // Safe file stat (returns null on failure)
96
- function safeStat(filePath) {
97
- try {
98
- return fs.statSync(filePath);
99
- } catch { /* ignore */ }
100
- return null;
101
- }
215
+ // ─── Git info (pure-Node / single exec — needed for branch display) ──────────
102
216
 
103
- // Shared settings cache — read once, used by multiple functions
104
- let _settingsCache = undefined;
105
- function getSettings() {
106
- if (_settingsCache !== undefined) return _settingsCache;
107
- _settingsCache = readJSON(path.join(CWD, '.claude', 'settings.json'))
108
- || readJSON(path.join(CWD, '.claude', 'settings.local.json'))
109
- || null;
110
- return _settingsCache;
111
- }
112
-
113
- // ─── Data Collection (all pure-Node.js or single-exec) ──────────
114
-
115
- // Get all git info in ONE shell call
116
217
  function getGitInfo() {
117
218
  const result = {
118
219
  name: 'user', gitBranch: '', modified: 0, untracked: 0,
119
220
  staged: 0, ahead: 0, behind: 0,
120
221
  };
121
222
 
122
- // Single shell: get user.name, branch, porcelain status, and upstream diff
123
223
  const script = [
124
224
  'git config user.name 2>/dev/null || echo user',
125
225
  'echo "---SEP---"',
@@ -133,12 +233,11 @@ function getGitInfo() {
133
233
  const raw = safeExec("sh -c '" + script + "'", 3000);
134
234
  if (!raw) return result;
135
235
 
136
- const parts = raw.split('---SEP---').map(s => s.trim());
236
+ const parts = raw.split('---SEP---').map(function(s) { return s.trim(); });
137
237
  if (parts.length >= 4) {
138
238
  result.name = parts[0] || 'user';
139
239
  result.gitBranch = parts[1] || '';
140
240
 
141
- // Parse porcelain status
142
241
  if (parts[2]) {
143
242
  for (const line of parts[2].split('\\n')) {
144
243
  if (!line || line.length < 2) continue;
@@ -149,7 +248,6 @@ function getGitInfo() {
149
248
  }
150
249
  }
151
250
 
152
- // Parse ahead/behind
153
251
  const ab = (parts[3] || '0 0').split(/\\s+/);
154
252
  result.ahead = parseInt(ab[0]) || 0;
155
253
  result.behind = parseInt(ab[1]) || 0;
@@ -198,450 +296,78 @@ function getModelName() {
198
296
  return 'Claude Code';
199
297
  }
200
298
 
201
- // Get learning stats from real data sources (no heuristics)
202
- function getLearningStats() {
203
- let patterns = 0;
204
- let sessions = 0;
205
-
206
- // 1. Count real patterns from intelligence pattern store
207
- const patternStorePath = path.join(CWD, '.claude-flow', 'data', 'patterns.json');
208
- try {
209
- if (fs.existsSync(patternStorePath)) {
210
- const data = JSON.parse(fs.readFileSync(patternStorePath, 'utf-8'));
211
- if (Array.isArray(data)) patterns = data.length;
212
- else if (data && data.patterns) patterns = Array.isArray(data.patterns) ? data.patterns.length : Object.keys(data.patterns).length;
213
- }
214
- } catch { /* ignore */ }
215
-
216
- // 2. Count patterns from auto-memory-store (real entries, not file size)
217
- if (patterns === 0) {
218
- const autoStorePath = path.join(CWD, '.claude-flow', 'data', 'auto-memory-store.json');
219
- try {
220
- if (fs.existsSync(autoStorePath)) {
221
- const data = JSON.parse(fs.readFileSync(autoStorePath, 'utf-8'));
222
- if (Array.isArray(data)) patterns = data.length;
223
- else if (data && data.entries) patterns = data.entries.length;
224
- }
225
- } catch { /* ignore */ }
226
- }
227
-
228
- // 3. Count patterns from memory.db using row count (sqlite header bytes 28-31).
229
- //
230
- // ruflo#1989: when encryption at rest is enabled, memory.db is no
231
- // longer a SQLite database -- it is an RFE1-magicked ciphertext blob.
232
- // The original code blindly read bytes 28-31 as a page count and
233
- // rendered 3.3B patterns (uint32 of random ciphertext). That
234
- // cascaded into fake DDD 5/5 / 100% indicators downstream.
235
- //
236
- // Guard with the SQLite magic ("SQLite format 3\\0", 16 bytes at
237
- // offset 0). Also clamp implausible page counts (>1M pages ~= 4GB)
238
- // to avoid reporting nonsense even on plaintext SQLite.
239
- if (patterns === 0) {
240
- const SQLITE_MAGIC = Buffer.from('SQLite format 3\\0', 'binary');
241
- const memoryPaths = [
242
- path.join(CWD, '.claude-flow', 'memory.db'),
243
- path.join(CWD, 'data', 'memory.db'),
244
- path.join(CWD, '.swarm', 'memory.db'),
245
- ];
246
- for (const dbPath of memoryPaths) {
247
- try {
248
- if (!fs.existsSync(dbPath)) continue;
249
- const fd = fs.openSync(dbPath, 'r');
250
- const head = Buffer.alloc(16);
251
- fs.readSync(fd, head, 0, 16, 0);
252
- if (!head.equals(SQLITE_MAGIC)) {
253
- // Not plaintext SQLite (likely RFE1 encrypted, an empty
254
- // file, or some other format). Skip — let the daemon or
255
- // patterns.json fallback report the real number.
256
- fs.closeSync(fd);
257
- continue;
258
- }
259
- const buf = Buffer.alloc(4);
260
- fs.readSync(fd, buf, 0, 4, 28);
261
- fs.closeSync(fd);
262
- const pageCount = buf.readUInt32BE(0);
263
- // Sanity: reject implausible counts (> 1M pages ≈ 4 GB DB).
264
- if (pageCount > 1_000_000) continue;
265
- // Each page typically holds ~10-50 rows; use page count as
266
- // conservative estimate. Report 0 if only schema pages (< 3).
267
- patterns = pageCount > 2 ? pageCount - 2 : 0;
268
- break;
269
- } catch { /* ignore */ }
270
- }
271
- }
272
-
273
- // 4. Count real session files
299
+ // ─── Stdin reader (Claude Code pipes session JSON) ──────────────
300
+ // Claude Code sends session JSON via stdin. Read synchronously so the
301
+ // script works both when invoked by Claude Code (stdin has JSON) and
302
+ // when run manually from terminal (stdin is empty/tty).
303
+ let _stdinData = null;
304
+ function getStdinData() {
305
+ if (_stdinData !== undefined && _stdinData !== null) return _stdinData;
274
306
  try {
275
- const sessDir = path.join(CWD, '.claude', 'sessions');
276
- if (fs.existsSync(sessDir)) {
277
- sessions = fs.readdirSync(sessDir).filter(f => f.endsWith('.json')).length;
278
- }
279
- } catch { /* ignore */ }
280
-
281
- // 5. Count session files from claude-flow
282
- if (sessions === 0) {
307
+ if (process.stdin.isTTY) { _stdinData = null; return null; }
308
+ const chunks = [];
309
+ const buf = Buffer.alloc(4096);
310
+ let bytesRead;
283
311
  try {
284
- const cfSessDir = path.join(CWD, '.claude-flow', 'sessions');
285
- if (fs.existsSync(cfSessDir)) {
286
- sessions = fs.readdirSync(cfSessDir).filter(f => f.endsWith('.json')).length;
312
+ while ((bytesRead = fs.readSync(0, buf, 0, buf.length, null)) > 0) {
313
+ chunks.push(buf.slice(0, bytesRead));
287
314
  }
288
- } catch { /* ignore */ }
315
+ } catch { /* EOF or read error */ }
316
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
317
+ _stdinData = (raw && raw.startsWith('{')) ? JSON.parse(raw) : null;
318
+ } catch {
319
+ _stdinData = null;
289
320
  }
321
+ return _stdinData;
322
+ }
290
323
 
291
- return { patterns, sessions };
324
+ function getModelFromStdin() {
325
+ const data = getStdinData();
326
+ return (data && data.model && data.model.display_name) ? data.model.display_name : null;
292
327
  }
293
328
 
294
- // V3 progress from metrics files (pure file reads)
295
- function getV3Progress() {
296
- const learning = getLearningStats();
297
- const totalDomains = 5;
298
-
299
- const dddData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'ddd-progress.json'));
300
- let dddProgress = dddData ? (dddData.progress || 0) : 0;
301
- let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));
302
-
303
- // Only derive DDD progress from real ddd-progress.json or real pattern data
304
- // Don't inflate domains from pattern count — 0 means no DDD work tracked.
305
- // ruflo#1989: defensively clamp learning.patterns even though
306
- // getLearningStats already guards against the RFE1-encrypted case --
307
- // if any future regression in the upstream reader returns a wild
308
- // value, we do not want to silently inflate DDD to 5/5 / 100%.
309
- const realPatterns = Number.isFinite(learning.patterns) && learning.patterns >= 0 && learning.patterns < 1_000_000
310
- ? learning.patterns
311
- : 0;
312
- if (dddProgress === 0 && realPatterns > 0) {
313
- // Conservative: only count domains if we have substantial real pattern data
314
- // Each domain requires ~100 real stored patterns to claim completion
315
- domainsCompleted = Math.min(5, Math.floor(realPatterns / 100));
316
- dddProgress = Math.floor((domainsCompleted / totalDomains) * 100);
329
+ function getContextFromStdin() {
330
+ const data = getStdinData();
331
+ if (data && data.context_window) {
332
+ return { usedPct: Math.floor(data.context_window.used_percentage || 0) };
317
333
  }
318
-
319
- return {
320
- domainsCompleted, totalDomains, dddProgress,
321
- patternsLearned: realPatterns,
322
- sessionsCompleted: learning.sessions,
323
- };
334
+ return null;
324
335
  }
325
336
 
326
- // Security status (pure file reads)
327
- function getSecurityStatus() {
328
- const auditData = readJSON(path.join(CWD, '.claude-flow', 'security', 'audit-status.json'));
329
- if (auditData) {
330
- const auditDate = auditData.lastAudit || auditData.lastScan;
331
- if (!auditDate) {
332
- return { status: 'PENDING', cvesFixed: 0, totalCves: 0 };
333
- }
334
- const auditAge = Date.now() - new Date(auditDate).getTime();
335
- const isStale = auditAge > 7 * 24 * 60 * 60 * 1000;
337
+ function getCostFromStdin() {
338
+ const data = getStdinData();
339
+ if (data && data.cost) {
340
+ const durationMs = data.cost.total_duration_ms || 0;
341
+ const mins = Math.floor(durationMs / 60000);
342
+ const secs = Math.floor((durationMs % 60000) / 1000);
336
343
  return {
337
- status: isStale ? 'STALE' : (auditData.status || 'PENDING'),
338
- cvesFixed: auditData.cvesFixed || 0,
339
- totalCves: auditData.totalCves || 0,
344
+ costUsd: data.cost.total_cost_usd || 0,
345
+ duration: mins > 0 ? mins + 'm' + secs + 's' : secs + 's',
340
346
  };
341
347
  }
342
-
343
- let scanCount = 0;
344
- try {
345
- const scanDir = path.join(CWD, '.claude', 'security-scans');
346
- if (fs.existsSync(scanDir)) {
347
- scanCount = fs.readdirSync(scanDir).filter(f => f.endsWith('.json')).length;
348
- }
349
- } catch { /* ignore */ }
350
-
351
- return {
352
- status: scanCount > 0 ? 'SCANNED' : 'NONE',
353
- cvesFixed: 0,
354
- totalCves: 0,
355
- };
356
- }
357
-
358
- // Swarm status (pure file reads, NO ps aux)
359
- function getSwarmStatus() {
360
- const staleThresholdMs = 5 * 60 * 1000;
361
- const now = Date.now();
362
-
363
- const swarmStatePath = path.join(CWD, '.claude-flow', 'swarm', 'swarm-state.json');
364
- const swarmState = readJSON(swarmStatePath);
365
- if (swarmState) {
366
- const updatedAt = swarmState.updatedAt || swarmState.startedAt;
367
- const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
368
- if (age < staleThresholdMs) {
369
- return {
370
- activeAgents: (swarmState.agents && swarmState.agents.length) || swarmState.agentCount || 0,
371
- maxAgents: swarmState.maxAgents || CONFIG.maxAgents,
372
- coordinationActive: true,
373
- };
374
- }
375
- }
376
-
377
- const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
378
- if (activityData && activityData.swarm) {
379
- const updatedAt = activityData.timestamp || (activityData.swarm && activityData.swarm.timestamp);
380
- const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
381
- if (age < staleThresholdMs) {
382
- return {
383
- activeAgents: activityData.swarm.agent_count || 0,
384
- maxAgents: CONFIG.maxAgents,
385
- coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
386
- };
387
- }
388
- }
389
-
390
- return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
391
- }
392
-
393
- // System metrics (uses process.memoryUsage() — no shell spawn)
394
- function getSystemMetrics() {
395
- const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
396
- const learning = getLearningStats();
397
- const agentdb = getAgentDBStats();
398
-
399
- // Intelligence from learning.json
400
- const learningData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'learning.json'));
401
- let intelligencePct = 0;
402
- let contextPct = 0;
403
-
404
- if (learningData && learningData.intelligence && learningData.intelligence.score !== undefined) {
405
- intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
406
- } else {
407
- // Use real data only — patterns from actual store, vectors from actual DB.
408
- // ruflo#1989: clamp patterns to a sane upper bound. A multi-billion
409
- // pattern count from a buggy reader would saturate intelligencePct
410
- // to 100% and silently lie about progress.
411
- const realPatterns = Number.isFinite(learning.patterns) && learning.patterns >= 0 && learning.patterns < 1_000_000
412
- ? learning.patterns
413
- : 0;
414
- const fromPatterns = realPatterns > 0 ? Math.min(100, Math.floor(realPatterns / 20)) : 0;
415
- const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 20)) : 0;
416
- intelligencePct = Math.max(fromPatterns, fromVectors);
417
- }
418
- // No fake fallback — 0% means no real learning data exists
419
-
420
- if (learningData && learningData.sessions && learningData.sessions.total !== undefined) {
421
- contextPct = Math.min(100, learningData.sessions.total * 5);
422
- } else {
423
- // Real session count only — no heuristic derivation from patterns
424
- contextPct = learning.sessions > 0 ? Math.min(100, learning.sessions * 5) : 0;
425
- }
426
-
427
- // Sub-agents from file metrics (no ps aux)
428
- let subAgents = 0;
429
- const activityData = readJSON(path.join(CWD, '.claude-flow', 'metrics', 'swarm-activity.json'));
430
- if (activityData && activityData.processes && activityData.processes.estimated_agents) {
431
- subAgents = activityData.processes.estimated_agents;
432
- }
433
-
434
- return { memoryMB, contextPct, intelligencePct, subAgents };
435
- }
436
-
437
- // ADR status (count files only — don't read contents)
438
- function getADRStatus() {
439
- // Count actual ADR files first — compliance JSON may be stale
440
- const adrPaths = [
441
- path.join(CWD, 'v3', 'implementation', 'adrs'),
442
- path.join(CWD, 'docs', 'adrs'),
443
- path.join(CWD, '.claude-flow', 'adrs'),
444
- ];
445
-
446
- for (const adrPath of adrPaths) {
447
- try {
448
- if (fs.existsSync(adrPath)) {
449
- const files = fs.readdirSync(adrPath).filter(f =>
450
- f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\\d{4}-/.test(f))
451
- );
452
- return { count: files.length, implemented: files.length, compliance: 0 };
453
- }
454
- } catch { /* ignore */ }
455
- }
456
-
457
- return { count: 0, implemented: 0, compliance: 0 };
458
- }
459
-
460
- // Hooks status (shared settings cache)
461
- function getHooksStatus() {
462
- let enabled = 0;
463
- let total = 0;
464
- const settings = getSettings();
465
-
466
- if (settings && settings.hooks) {
467
- for (const category of Object.keys(settings.hooks)) {
468
- const matchers = settings.hooks[category];
469
- if (!Array.isArray(matchers)) continue;
470
- for (const matcher of matchers) {
471
- const hooks = matcher && matcher.hooks;
472
- if (Array.isArray(hooks)) {
473
- total += hooks.length;
474
- enabled += hooks.length;
475
- }
476
- }
477
- }
478
- }
479
-
480
- try {
481
- const hooksDir = path.join(CWD, '.claude', 'hooks');
482
- if (fs.existsSync(hooksDir)) {
483
- const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh')).length;
484
- total = Math.max(total, hookFiles);
485
- enabled = Math.max(enabled, hookFiles);
486
- }
487
- } catch { /* ignore */ }
488
-
489
- return { enabled, total };
348
+ return null;
490
349
  }
491
350
 
492
- // AgentDB stats count real entries from all data stores
493
- function getAgentDBStats() {
494
- let vectorCount = 0;
495
- let dbSizeKB = 0;
496
- let namespaces = 0;
497
- let hasHnsw = false;
498
-
499
- // 1. Count real entries from auto-memory-store.json
500
- const storePath = path.join(CWD, '.claude-flow', 'data', 'auto-memory-store.json');
501
- const storeStat = safeStat(storePath);
502
- if (storeStat) {
503
- dbSizeKB += storeStat.size / 1024;
504
- try {
505
- const store = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
506
- if (Array.isArray(store)) vectorCount += store.length;
507
- else if (store && store.entries) vectorCount += store.entries.length;
508
- } catch { /* fall back */ }
509
- }
510
-
511
- // 2. Count entries from hooks memory store (.claude-flow/memory/store.json)
512
- const hooksStorePath = path.join(CWD, '.claude-flow', 'memory', 'store.json');
513
- const hooksStoreStat = safeStat(hooksStorePath);
514
- if (hooksStoreStat) {
515
- dbSizeKB += hooksStoreStat.size / 1024;
516
- try {
517
- const store = JSON.parse(fs.readFileSync(hooksStorePath, 'utf-8'));
518
- if (store && store.entries) {
519
- const entryCount = Object.keys(store.entries).length;
520
- vectorCount = Math.max(vectorCount, entryCount);
521
- if (entryCount > 0) namespaces++;
522
- }
523
- } catch { /* fall back */ }
524
- }
525
-
526
- // 3. Count entries from ranked-context.json
351
+ // Read package version from the first package.json we find.
352
+ function getPkgVersion() {
353
+ let ver = '3.6';
527
354
  try {
528
- const ranked = readJSON(path.join(CWD, '.claude-flow', 'data', 'ranked-context.json'));
529
- if (ranked && ranked.entries && ranked.entries.length > vectorCount) vectorCount = ranked.entries.length;
530
- } catch { /* ignore */ }
531
-
532
- // 3. Add DB file sizes
533
- const dbFiles = [
534
- path.join(CWD, 'data', 'memory.db'),
535
- path.join(CWD, '.claude-flow', 'memory.db'),
536
- path.join(CWD, '.swarm', 'memory.db'),
537
- ];
538
- for (const f of dbFiles) {
539
- const stat = safeStat(f);
540
- if (stat) {
541
- dbSizeKB += stat.size / 1024;
542
- namespaces++;
543
- }
544
- }
545
-
546
- // 4. Graph data size
547
- const graphStat = safeStat(path.join(CWD, 'data', 'memory.graph'));
548
- if (graphStat) dbSizeKB += graphStat.size / 1024;
549
-
550
- // 5. HNSW index or memory package
551
- const hnswPaths = [
552
- path.join(CWD, '.swarm', 'hnsw.index'),
553
- path.join(CWD, '.claude-flow', 'hnsw.index'),
554
- ];
555
- for (const p of hnswPaths) {
556
- if (safeStat(p)) { hasHnsw = true; break; }
557
- }
558
- if (!hasHnsw) {
559
- const memPkgPaths = [
560
- path.join(CWD, 'v3', '@claude-flow', 'memory', 'dist'),
561
- path.join(CWD, 'node_modules', '@claude-flow', 'memory'),
355
+ const home = os.homedir();
356
+ const pkgPaths = [
357
+ path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'package.json'),
358
+ path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'package.json'),
359
+ path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
360
+ path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
562
361
  ];
563
- for (const p of memPkgPaths) {
564
- if (fs.existsSync(p)) { hasHnsw = true; break; }
565
- }
566
- }
567
-
568
- return { vectorCount, dbSizeKB: Math.floor(dbSizeKB), namespaces, hasHnsw };
569
- }
570
-
571
- // Test stats (count files only — NO reading file contents)
572
- function getTestStats() {
573
- let testFiles = 0;
574
-
575
- function countTestFiles(dir, depth) {
576
- if (depth === undefined) depth = 0;
577
- if (depth > 6) return;
578
- try {
579
- if (!fs.existsSync(dir)) return;
580
- const entries = fs.readdirSync(dir, { withFileTypes: true });
581
- for (const entry of entries) {
582
- if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
583
- countTestFiles(path.join(dir, entry.name), depth + 1);
584
- } else if (entry.isFile()) {
585
- const n = entry.name;
586
- if (n.includes('.test.') || n.includes('.spec.') || n.includes('_test.') || n.includes('_spec.')) {
587
- testFiles++;
588
- }
589
- }
590
- }
591
- } catch { /* ignore */ }
592
- }
593
-
594
- var testDirNames = ['tests', 'test', '__tests__', 'src', 'v3'];
595
- for (var i = 0; i < testDirNames.length; i++) {
596
- countTestFiles(path.join(CWD, testDirNames[i]));
597
- }
598
-
599
- return { testFiles, testCases: testFiles * 4 };
600
- }
601
-
602
- // Integration status (shared settings + file checks)
603
- function getIntegrationStatus() {
604
- const mcpServers = { total: 0, enabled: 0 };
605
- const settings = getSettings();
606
-
607
- if (settings && settings.mcpServers && typeof settings.mcpServers === 'object') {
608
- const servers = Object.keys(settings.mcpServers);
609
- mcpServers.total = servers.length;
610
- mcpServers.enabled = settings.enabledMcpjsonServers
611
- ? settings.enabledMcpjsonServers.filter(s => servers.includes(s)).length
612
- : servers.length;
613
- }
614
-
615
- if (mcpServers.total === 0) {
616
- const mcpConfig = readJSON(path.join(CWD, '.mcp.json'))
617
- || readJSON(path.join(os.homedir(), '.claude', 'mcp.json'));
618
- if (mcpConfig && mcpConfig.mcpServers) {
619
- const s = Object.keys(mcpConfig.mcpServers);
620
- mcpServers.total = s.length;
621
- mcpServers.enabled = s.length;
622
- }
623
- }
624
-
625
- const hasDatabase = ['.swarm/memory.db', '.claude-flow/memory.db', 'data/memory.db']
626
- .some(p => fs.existsSync(path.join(CWD, p)));
627
- const hasApi = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
628
-
629
- return { mcpServers, hasDatabase, hasApi };
630
- }
631
-
632
- // Session stats (pure file reads)
633
- function getSessionStats() {
634
- var sessionPaths = ['.claude-flow/session.json', '.claude/session.json'];
635
- for (var i = 0; i < sessionPaths.length; i++) {
636
- const data = readJSON(path.join(CWD, sessionPaths[i]));
637
- if (data && data.startTime) {
638
- const diffMs = Date.now() - new Date(data.startTime).getTime();
639
- const mins = Math.floor(diffMs / 60000);
640
- const duration = mins < 60 ? mins + 'm' : Math.floor(mins / 60) + 'h' + (mins % 60) + 'm';
641
- return { duration: duration };
362
+ for (const p of pkgPaths) {
363
+ if (!fs.existsSync(p)) continue;
364
+ try {
365
+ const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
366
+ if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) { ver = pkg.version; break; }
367
+ } catch { /* ignore */ }
642
368
  }
643
- }
644
- return { duration: '' };
369
+ } catch { /* ignore */ }
370
+ return ver;
645
371
  }
646
372
 
647
373
  // ─── Rendering ──────────────────────────────────────────────────
@@ -649,59 +375,56 @@ function getSessionStats() {
649
375
  function progressBar(current, total) {
650
376
  const width = 5;
651
377
  const filled = Math.round((current / total) * width);
652
- return '[' + '\\u25CF'.repeat(filled) + '\\u25CB'.repeat(width - filled) + ']';
378
+ return '[' + ''.repeat(filled) + ''.repeat(width - filled) + ']';
653
379
  }
654
380
 
655
381
  function generateStatusline() {
382
+ const d = getStatuslineData();
656
383
  const git = getGitInfo();
657
- // Prefer model name from Claude Code stdin data, fallback to file-based detection
658
- const modelName = getModelFromStdin() || getModelName();
384
+ const modelName = getModelFromStdin() || (d.user && d.user.modelName) || 'Claude Code';
659
385
  const ctxInfo = getContextFromStdin();
660
386
  const costInfo = getCostFromStdin();
661
- const progress = getV3Progress();
662
- const security = getSecurityStatus();
663
- const swarm = getSwarmStatus();
664
- const system = getSystemMetrics();
665
- const adrs = getADRStatus();
666
- const hooks = getHooksStatus();
667
- const agentdb = getAgentDBStats();
668
- const tests = getTestStats();
669
- const session = getSessionStats();
670
- const integration = getIntegrationStatus();
387
+ const pkgVersion = getPkgVersion();
388
+
389
+ const progress = d.v3Progress || {};
390
+ const security = d.security || {};
391
+ const swarm = d.swarm || {};
392
+ const system = d.system || {};
393
+ const adrs = d.adrs || {};
394
+ const hooks = d.hooks || {};
395
+ const agentdb = d.agentdb || {};
396
+ const tests = d.tests || {};
397
+
398
+ const domainsCompleted = progress.domainsCompleted || 0;
399
+ const totalDomains = progress.totalDomains || 5;
400
+ const dddProgress = progress.dddProgress || 0;
401
+ const patternsLearned = progress.patternsLearned || 0;
402
+ const activeAgents = swarm.activeAgents || 0;
403
+ const maxAgents = swarm.maxAgents || CONFIG.maxAgents;
404
+ const coordinationActive = swarm.coordinationActive || false;
405
+ const intelligencePct = system.intelligencePct || 0;
406
+ const memoryMB = system.memoryMB || 0;
407
+ const subAgents = system.subAgents || 0;
408
+ const cvesFixed = security.cvesFixed || 0;
409
+ const totalCves = security.totalCves || 0;
410
+ const secStatus = security.status || 'NONE';
411
+ const adrCount = adrs.count || 0;
412
+ const adrImpl = adrs.implemented || 0;
413
+ const hooksEnabled = hooks.enabled || 0;
414
+ const hooksTotal = hooks.total || 0;
415
+ const vectorCount = agentdb.vectorCount || 0;
416
+ const hasHnsw = agentdb.hasHnsw || false;
417
+ const dbSizeKB = agentdb.dbSizeKB || 0;
418
+ const testFiles = tests.testFiles || 0;
419
+ const testCases = tests.testCases || testFiles * 4;
420
+
671
421
  const lines = [];
672
422
 
673
- // Header — read version from the FIRST package.json we find, preferring
674
- // the plugin install at ~/.claude/plugins/marketplaces/ruflo/package.json.
675
- // The previous list only checked project-local node_modules, so plugin
676
- // users saw the hard-coded fallback (V3.5) even on newer alphas (#1951).
677
- let pkgVersion = '3.6';
678
- try {
679
- const home = require('os').homedir();
680
- const pkgPaths = [
681
- // 1. The plugin's own root (installed via /plugin install).
682
- path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'package.json'),
683
- // 2. Project-local @claude-flow/cli — npm-style install.
684
- path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'package.json'),
685
- // 3. Project-local ruflo umbrella.
686
- path.join(CWD, 'node_modules', 'ruflo', 'package.json'),
687
- // 4. Source-checkout location (when developing in this repo).
688
- path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'),
689
- ];
690
- for (const p of pkgPaths) {
691
- if (!fs.existsSync(p)) continue;
692
- try {
693
- const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
694
- if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) {
695
- pkgVersion = pkg.version;
696
- break;
697
- }
698
- } catch { /* malformed package.json — try next */ }
699
- }
700
- } catch { /* fall through to the hardcoded default */ }
701
- let header = c.bold + c.brightPurple + '\\u258A RuFlo V' + pkgVersion + ' ' + c.reset;
702
- header += (swarm.coordinationActive ? c.brightCyan : c.dim) + '\\u25CF ' + c.brightCyan + git.name + c.reset;
423
+ // Header
424
+ let header = c.bold + c.brightPurple + '▊ RuFlo V' + pkgVersion + ' ' + c.reset;
425
+ header += (coordinationActive ? c.brightCyan : c.dim) + '● ' + c.brightCyan + git.name + c.reset;
703
426
  if (git.gitBranch) {
704
- header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.brightBlue + '\\u23C7 ' + git.gitBranch + c.reset;
427
+ header += ' ' + c.dim + '' + c.reset + ' ' + c.brightBlue + ' ' + git.gitBranch + c.reset;
705
428
  const changes = git.modified + git.staged + git.untracked;
706
429
  if (changes > 0) {
707
430
  let ind = '';
@@ -710,185 +433,107 @@ function generateStatusline() {
710
433
  if (git.untracked > 0) ind += c.dim + '?' + git.untracked + c.reset;
711
434
  header += ' ' + ind;
712
435
  }
713
- if (git.ahead > 0) header += ' ' + c.brightGreen + '\\u2191' + git.ahead + c.reset;
714
- if (git.behind > 0) header += ' ' + c.brightRed + '\\u2193' + git.behind + c.reset;
436
+ if (git.ahead > 0) header += ' ' + c.brightGreen + '' + git.ahead + c.reset;
437
+ if (git.behind > 0) header += ' ' + c.brightRed + '' + git.behind + c.reset;
715
438
  }
716
- header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.purple + modelName + c.reset;
717
- // Show session duration from Claude Code stdin if available, else from local files
718
- const duration = costInfo ? costInfo.duration : session.duration;
719
- if (duration) header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.cyan + '\\u23F1 ' + duration + c.reset;
720
- // Show context usage from Claude Code stdin if available
439
+ header += ' ' + c.dim + '' + c.reset + ' ' + c.purple + modelName + c.reset;
440
+ const duration = costInfo ? costInfo.duration : '';
441
+ if (duration) header += ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + '⏱ ' + duration + c.reset;
721
442
  if (ctxInfo && ctxInfo.usedPct > 0) {
722
443
  const ctxColor = ctxInfo.usedPct >= 90 ? c.brightRed : ctxInfo.usedPct >= 70 ? c.brightYellow : c.brightGreen;
723
- header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + ctxColor + '\\u25CF ' + ctxInfo.usedPct + '% ctx' + c.reset;
444
+ header += ' ' + c.dim + '' + c.reset + ' ' + ctxColor + ' ' + ctxInfo.usedPct + '% ctx' + c.reset;
724
445
  }
725
- // Show cost from Claude Code stdin if available
726
446
  if (costInfo && costInfo.costUsd > 0) {
727
- header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.brightYellow + '$' + costInfo.costUsd.toFixed(2) + c.reset;
447
+ header += ' ' + c.dim + '' + c.reset + ' ' + c.brightYellow + '$' + costInfo.costUsd.toFixed(2) + c.reset;
728
448
  }
729
449
  lines.push(header);
730
450
 
731
451
  // Separator
732
- lines.push(c.dim + '\\u2500'.repeat(53) + c.reset);
452
+ lines.push(c.dim + ''.repeat(53) + c.reset);
733
453
 
734
454
  // Line 1: DDD Domains
735
- const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
455
+ const domainsColor = domainsCompleted >= 3 ? c.brightGreen : domainsCompleted > 0 ? c.yellow : c.red;
736
456
  let perfIndicator;
737
- if (agentdb.hasHnsw && agentdb.vectorCount > 0) {
738
- const speedup = agentdb.vectorCount > 10000 ? '12500x' : agentdb.vectorCount > 1000 ? '150x' : '10x';
739
- perfIndicator = c.brightGreen + '\\u26A1 HNSW ' + speedup + c.reset;
740
- } else if (progress.patternsLearned > 0) {
741
- const pk = progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : String(progress.patternsLearned);
742
- perfIndicator = c.brightYellow + '\\uD83D\\uDCDA ' + pk + ' patterns' + c.reset;
457
+ if (hasHnsw && vectorCount > 0) {
458
+ const speedup = vectorCount > 10000 ? '12500x' : vectorCount > 1000 ? '150x' : '10x';
459
+ perfIndicator = c.brightGreen + ' HNSW ' + speedup + c.reset;
460
+ } else if (patternsLearned > 0) {
461
+ const pk = patternsLearned >= 1000 ? (patternsLearned / 1000).toFixed(1) + 'k' : String(patternsLearned);
462
+ perfIndicator = c.brightYellow + '📚 ' + pk + ' patterns' + c.reset;
743
463
  } else {
744
- perfIndicator = c.dim + '\\u26A1 target: 150x-12500x' + c.reset;
464
+ perfIndicator = c.dim + ' target: 150x-12500x' + c.reset;
745
465
  }
746
466
  lines.push(
747
- c.brightCyan + '\\uD83C\\uDFD7\\uFE0F DDD Domains' + c.reset + ' ' + progressBar(progress.domainsCompleted, progress.totalDomains) + ' ' +
748
- domainsColor + progress.domainsCompleted + c.reset + '/' + c.brightWhite + progress.totalDomains + c.reset + ' ' + perfIndicator
467
+ c.brightCyan + '🏗️ DDD Domains' + c.reset + ' ' + progressBar(domainsCompleted, totalDomains) + ' ' +
468
+ domainsColor + domainsCompleted + c.reset + '/' + c.brightWhite + totalDomains + c.reset + ' ' + perfIndicator
749
469
  );
750
470
 
751
471
  // Line 2: Swarm + Hooks + CVE + Memory + Intelligence
752
- const swarmInd = swarm.coordinationActive ? c.brightGreen + '\\u25C9' + c.reset : c.dim + '\\u25CB' + c.reset;
753
- const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
754
- const secIcon = security.status === 'CLEAN' ? '\\uD83D\\uDFE2' : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? '\\uD83D\\uDFE1' : (security.status === 'NONE' ? '\\u26AA' : '\\uD83D\\uDD34');
755
- const secColor = security.status === 'CLEAN' ? c.brightGreen : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? c.brightYellow : (security.status === 'NONE' ? c.dim : c.brightRed);
756
- const hooksColor = hooks.enabled > 0 ? c.brightGreen : c.dim;
757
- const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
472
+ const swarmInd = coordinationActive ? c.brightGreen + '' + c.reset : c.dim + '' + c.reset;
473
+ const agentsColor = activeAgents > 0 ? c.brightGreen : c.red;
474
+ const secIcon = secStatus === 'CLEAN' ? '🟢' : (secStatus === 'IN_PROGRESS' || secStatus === 'STALE') ? '🟡' : (secStatus === 'NONE' ? '' : '🔴');
475
+ const secColor = secStatus === 'CLEAN' ? c.brightGreen : (secStatus === 'IN_PROGRESS' || secStatus === 'STALE') ? c.brightYellow : (secStatus === 'NONE' ? c.dim : c.brightRed);
476
+ const hooksColor = hooksEnabled > 0 ? c.brightGreen : c.dim;
477
+ const intellColor = intelligencePct >= 80 ? c.brightGreen : intelligencePct >= 40 ? c.brightYellow : c.dim;
758
478
 
759
479
  lines.push(
760
- c.brightYellow + '\\uD83E\\uDD16 Swarm' + c.reset + ' ' + swarmInd + ' [' + agentsColor + String(swarm.activeAgents).padStart(2) + c.reset + '/' + c.brightWhite + swarm.maxAgents + c.reset + '] ' +
761
- c.brightPurple + '\\uD83D\\uDC65 ' + system.subAgents + c.reset + ' ' +
762
- c.brightBlue + '\\uD83E\\uDE9D ' + hooksColor + hooks.enabled + c.reset + '/' + c.brightWhite + hooks.total + c.reset + ' ' +
763
- secIcon + ' ' + secColor + 'CVE ' + security.cvesFixed + c.reset + '/' + c.brightWhite + security.totalCves + c.reset + ' ' +
764
- c.brightCyan + '\\uD83D\\uDCBE ' + system.memoryMB + 'MB' + c.reset + ' ' +
765
- intellColor + '\\uD83E\\uDDE0 ' + String(system.intelligencePct).padStart(3) + '%' + c.reset
480
+ c.brightYellow + '🤖 Swarm' + c.reset + ' ' + swarmInd + ' [' + agentsColor + String(activeAgents).padStart(2) + c.reset + '/' + c.brightWhite + maxAgents + c.reset + '] ' +
481
+ c.brightPurple + '👥 ' + subAgents + c.reset + ' ' +
482
+ c.brightBlue + '🪝 ' + hooksColor + hooksEnabled + c.reset + '/' + c.brightWhite + hooksTotal + c.reset + ' ' +
483
+ secIcon + ' ' + secColor + 'CVE ' + cvesFixed + c.reset + '/' + c.brightWhite + totalCves + c.reset + ' ' +
484
+ c.brightCyan + '💾 ' + memoryMB + 'MB' + c.reset + ' ' +
485
+ intellColor + '🧠 ' + String(intelligencePct).padStart(3) + '%' + c.reset
766
486
  );
767
487
 
768
488
  // Line 3: Architecture
769
- const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
770
- const adrColor = adrs.count > 0 ? (adrs.implemented === adrs.count ? c.brightGreen : c.yellow) : c.dim;
771
- const adrDisplay = adrs.compliance > 0 ? adrColor + '\\u25CF' + adrs.compliance + '%' + c.reset : adrColor + '\\u25CF' + adrs.implemented + '/' + adrs.count + c.reset;
489
+ const dddColor = dddProgress >= 50 ? c.brightGreen : dddProgress > 0 ? c.yellow : c.red;
490
+ const adrColor = adrCount > 0 ? (adrImpl === adrCount ? c.brightGreen : c.yellow) : c.dim;
491
+ const adrDisplay = adrColor + '' + adrImpl + '/' + adrCount + c.reset;
772
492
 
773
493
  lines.push(
774
- c.brightPurple + '\\uD83D\\uDD27 Architecture' + c.reset + ' ' +
775
- c.cyan + 'ADRs' + c.reset + ' ' + adrDisplay + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
776
- c.cyan + 'DDD' + c.reset + ' ' + dddColor + '\\u25CF' + String(progress.dddProgress).padStart(3) + '%' + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
777
- c.cyan + 'Security' + c.reset + ' ' + secColor + '\\u25CF' + security.status + c.reset
494
+ c.brightPurple + '🔧 Architecture' + c.reset + ' ' +
495
+ c.cyan + 'ADRs' + c.reset + ' ' + adrDisplay + ' ' + c.dim + '' + c.reset + ' ' +
496
+ c.cyan + 'DDD' + c.reset + ' ' + dddColor + '' + String(dddProgress).padStart(3) + '%' + c.reset + ' ' + c.dim + '' + c.reset + ' ' +
497
+ c.cyan + 'Security' + c.reset + ' ' + secColor + '' + secStatus + c.reset
778
498
  );
779
499
 
780
500
  // Line 4: AgentDB, Tests, Integration
781
- const hnswInd = agentdb.hasHnsw ? c.brightGreen + '\\u26A1' + c.reset : '';
782
- const sizeDisp = agentdb.dbSizeKB >= 1024 ? (agentdb.dbSizeKB / 1024).toFixed(1) + 'MB' : agentdb.dbSizeKB + 'KB';
783
- const vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
784
- const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
785
-
501
+ const hnswInd = hasHnsw ? c.brightGreen + '' + c.reset : '';
502
+ const sizeDisp = dbSizeKB >= 1024 ? (dbSizeKB / 1024).toFixed(1) + 'MB' : dbSizeKB + 'KB';
503
+ const vectorColor = vectorCount > 0 ? c.brightGreen : c.dim;
504
+ const testColor = testFiles > 0 ? c.brightGreen : c.dim;
505
+
506
+ // MCP / DB integration from data
507
+ const integration = d.integration || {};
508
+ const mcpServers = (integration.mcpServers) || {};
786
509
  let integStr = '';
787
- if (integration.mcpServers.total > 0) {
788
- const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
789
- integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
790
- integStr += c.cyan + 'MCP' + c.reset + ' ' + mcpCol + '\\u25CF' + integration.mcpServers.enabled + '/' + integration.mcpServers.total + c.reset;
510
+ if (mcpServers.total > 0) {
511
+ const mcpCol = mcpServers.enabled === mcpServers.total ? c.brightGreen : mcpServers.enabled > 0 ? c.brightYellow : c.red;
512
+ integStr += c.cyan + 'MCP' + c.reset + ' ' + mcpCol + '●' + mcpServers.enabled + '/' + mcpServers.total + c.reset;
791
513
  }
792
- if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + c.brightGreen + '\\u25C6' + c.reset + 'DB';
793
- if (integration.hasApi) integStr += (integStr ? ' ' : '') + c.brightGreen + '\\u25C6' + c.reset + 'API';
794
- if (!integStr) integStr = c.dim + '\\u25CF none' + c.reset;
514
+ if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + c.brightGreen + '' + c.reset + 'DB';
515
+ if (!integStr) integStr = c.dim + '● none' + c.reset;
795
516
 
796
517
  lines.push(
797
- c.brightCyan + '\\uD83D\\uDCCA AgentDB' + c.reset + ' ' +
798
- c.cyan + 'Vectors' + c.reset + ' ' + vectorColor + '\\u25CF' + agentdb.vectorCount + hnswInd + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
799
- c.cyan + 'Size' + c.reset + ' ' + c.brightWhite + sizeDisp + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
800
- c.cyan + 'Tests' + c.reset + ' ' + testColor + '\\u25CF' + tests.testFiles + c.reset + ' ' + c.dim + '(~' + tests.testCases + ' cases)' + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
518
+ c.brightCyan + '📊 AgentDB' + c.reset + ' ' +
519
+ c.cyan + 'Vectors' + c.reset + ' ' + vectorColor + '' + vectorCount + hnswInd + c.reset + ' ' + c.dim + '' + c.reset + ' ' +
520
+ c.cyan + 'Size' + c.reset + ' ' + c.brightWhite + sizeDisp + c.reset + ' ' + c.dim + '' + c.reset + ' ' +
521
+ c.cyan + 'Tests' + c.reset + ' ' + testColor + '' + testFiles + c.reset + ' ' + c.dim + '(~' + testCases + ' cases)' + c.reset + ' ' + c.dim + '' + c.reset + ' ' +
801
522
  integStr
802
523
  );
803
524
 
804
525
  return lines.join('\\n');
805
526
  }
806
527
 
807
- // JSON output
528
+ // JSON output — delegates to CLI for accuracy; caller can use --json flag
808
529
  function generateJSON() {
530
+ const d = getStatuslineData();
809
531
  const git = getGitInfo();
810
- return {
811
- user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
812
- v3Progress: getV3Progress(),
813
- security: getSecurityStatus(),
814
- swarm: getSwarmStatus(),
815
- system: getSystemMetrics(),
816
- adrs: getADRStatus(),
817
- hooks: getHooksStatus(),
818
- agentdb: getAgentDBStats(),
819
- tests: getTestStats(),
532
+ return Object.assign({}, d, {
533
+ user: Object.assign({ name: git.name, gitBranch: git.gitBranch }, d.user || {}),
820
534
  git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
821
535
  lastUpdated: new Date().toISOString(),
822
- };
823
- }
824
-
825
- // ─── Stdin reader (Claude Code pipes session JSON) ──────────────
826
-
827
- // Claude Code sends session JSON via stdin (model, context, cost, etc.)
828
- // Read it synchronously so the script works both:
829
- // 1. When invoked by Claude Code (stdin has JSON)
830
- // 2. When invoked manually from terminal (stdin is empty/tty)
831
- let _stdinData = null;
832
- function getStdinData() {
833
- if (_stdinData !== undefined && _stdinData !== null) return _stdinData;
834
- try {
835
- // Check if stdin is a TTY (manual run) — skip reading
836
- if (process.stdin.isTTY) { _stdinData = null; return null; }
837
- // Read stdin synchronously via fd 0
838
- const chunks = [];
839
- const buf = Buffer.alloc(4096);
840
- let bytesRead;
841
- try {
842
- while ((bytesRead = fs.readSync(0, buf, 0, buf.length, null)) > 0) {
843
- chunks.push(buf.slice(0, bytesRead));
844
- }
845
- } catch { /* EOF or read error */ }
846
- const raw = Buffer.concat(chunks).toString('utf-8').trim();
847
- if (raw && raw.startsWith('{')) {
848
- _stdinData = JSON.parse(raw);
849
- } else {
850
- _stdinData = null;
851
- }
852
- } catch {
853
- _stdinData = null;
854
- }
855
- return _stdinData;
856
- }
857
-
858
- // Override model detection to prefer stdin data from Claude Code
859
- function getModelFromStdin() {
860
- const data = getStdinData();
861
- if (data && data.model && data.model.display_name) return data.model.display_name;
862
- return null;
863
- }
864
-
865
- // Get context window info from Claude Code session
866
- function getContextFromStdin() {
867
- const data = getStdinData();
868
- if (data && data.context_window) {
869
- return {
870
- usedPct: Math.floor(data.context_window.used_percentage || 0),
871
- remainingPct: Math.floor(data.context_window.remaining_percentage || 100),
872
- };
873
- }
874
- return null;
875
- }
876
-
877
- // Get cost info from Claude Code session
878
- function getCostFromStdin() {
879
- const data = getStdinData();
880
- if (data && data.cost) {
881
- const durationMs = data.cost.total_duration_ms || 0;
882
- const mins = Math.floor(durationMs / 60000);
883
- const secs = Math.floor((durationMs % 60000) / 1000);
884
- return {
885
- costUsd: data.cost.total_cost_usd || 0,
886
- duration: mins > 0 ? mins + 'm' + secs + 's' : secs + 's',
887
- linesAdded: data.cost.total_lines_added || 0,
888
- linesRemoved: data.cost.total_lines_removed || 0,
889
- };
890
- }
891
- return null;
536
+ });
892
537
  }
893
538
 
894
539
  // ─── Main ───────────────────────────────────────────────────────