specmem-hardwicksoftware 3.5.99 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * SPECMEM STATUS BAR - Draws above claudefix footer
4
- * =================================================
5
- * Updated with AEGIS Theme truecolor support
3
+ * SPECMEM STATUS BAR - Clean two-line status display
4
+ * ===================================================
5
+ * Old-school elegant theme with new functionality.
6
6
  *
7
- * Shows real-time specmem status:
8
- * - Embedding server health (healthy/unhealthy/starting)
9
- * - Last tool call + duration
10
- * - Memory count
11
- * - MCP connection status
12
- * - Resource monitoring (RAM/CPU)
7
+ * Two lines:
8
+ * Line 1 (top): Team comms - latest message centered on dash line
9
+ * Line 2 (bottom): Status info centered on dash line
13
10
  *
14
11
  * Coordinates with claudefix:
15
12
  * - claudefix draws on row `rows` (bottom)
16
- * - specmem draws on row `rows - 1` (above claudefix)
17
- * - scroll region set to `rows - 2` when both active
18
- *
19
- * AEGIS Footer Style:
20
- * COMMAND | SpecMem v3.5.89 | claude-specmem | ● Online | [h]Help [q]Quit
13
+ * - specmem draws on rows `rows - 2` and `rows - 1` (above claudefix)
21
14
  *
22
15
  * @author hardwicksoftwareservices
23
16
  * @website https://justcalljon.pro
@@ -34,109 +27,17 @@ const SPECMEM_VERSION = (() => {
34
27
  })();
35
28
 
36
29
  // ============================================================================
37
- // AEGIS Theme Import (graceful fallback)
38
- // ============================================================================
39
-
40
- let AegisTheme = null;
41
- let AEGIS_COLORS = null;
42
- try {
43
- const aegis = require('./AegisTheme.cjs');
44
- AegisTheme = aegis.AegisTheme;
45
- AEGIS_COLORS = aegis.AEGIS_COLORS;
46
- } catch (_err) {
47
- // AegisTheme not available - will fall back to basic ANSI colors
48
- }
49
-
50
- // ============================================================================
51
- // AEGIS Truecolor Helpers
52
- // ============================================================================
53
-
54
- /**
55
- * Convert a hex color (#RRGGBB) to truecolor foreground escape sequence.
56
- * @param {string} hex - Hex color string like '#00d4ff'
57
- * @returns {string} ANSI truecolor foreground escape
58
- */
59
- function hexFg(hex) {
60
- if (AegisTheme && typeof AegisTheme.fg === 'function') {
61
- try {
62
- const resolved = AegisTheme.fg(hex);
63
- if (resolved && resolved !== hex) return resolved;
64
- } catch (_e) { /* fall through */ }
65
- }
66
- const r = parseInt(hex.slice(1, 3), 16);
67
- const g = parseInt(hex.slice(3, 5), 16);
68
- const b = parseInt(hex.slice(5, 7), 16);
69
- return `\x1b[38;2;${r};${g};${b}m`;
70
- }
71
-
72
- /**
73
- * Convert a hex color (#RRGGBB) to truecolor background escape sequence.
74
- * @param {string} hex - Hex color string like '#131924'
75
- * @returns {string} ANSI truecolor background escape
76
- */
77
- function hexBg(hex) {
78
- if (AegisTheme && typeof AegisTheme.bg === 'function') {
79
- try {
80
- const resolved = AegisTheme.bg(hex);
81
- if (resolved && resolved !== hex) return resolved;
82
- } catch (_e) { /* fall through */ }
83
- }
84
- const r = parseInt(hex.slice(1, 3), 16);
85
- const g = parseInt(hex.slice(3, 5), 16);
86
- const b = parseInt(hex.slice(5, 7), 16);
87
- return `\x1b[48;2;${r};${g};${b}m`;
88
- }
89
-
90
- // ============================================================================
91
- // AEGIS Color Palette
92
- // ============================================================================
93
-
94
- // Surface / background
95
- const AEGIS_SURFACE = '#131924';
96
- const AEGIS_SURFACE_ALT = '#1a2332';
97
-
98
- // Text
99
- const AEGIS_TEXT_PRIMARY = '#d4dae4';
100
- const AEGIS_TEXT_DIM = '#6a7a8a';
101
- const AEGIS_TEXT_DARK = '#0a0f18';
102
-
103
- // Accents
104
- const AEGIS_CYAN = '#00d4ff';
105
- const AEGIS_GREEN = '#00ff88';
106
- const AEGIS_MAGENTA = '#cc66ff';
107
- const AEGIS_YELLOW = '#ffaa00';
108
- const AEGIS_RED = '#ff4466';
109
-
110
- // Borders
111
- const AEGIS_BORDER = '#2a3a4a';
112
-
113
- // ============================================================================
114
- // Colors - AEGIS Truecolor (with basic ANSI fallbacks)
30
+ // Colors - Clean basic ANSI (old-school elegant)
115
31
  // ============================================================================
116
32
 
117
33
  const RESET = '\x1b[0m';
118
34
  const BOLD = '\x1b[1m';
119
35
  const DIM = '\x1b[2m';
120
-
121
- // Foreground colors
122
- const FG_PRIMARY = hexFg(AEGIS_TEXT_PRIMARY);
123
- const FG_DIM = hexFg(AEGIS_TEXT_DIM);
124
- const FG_DARK = hexFg(AEGIS_TEXT_DARK);
125
- const FG_CYAN = hexFg(AEGIS_CYAN);
126
- const FG_GREEN = hexFg(AEGIS_GREEN);
127
- const FG_MAGENTA = hexFg(AEGIS_MAGENTA);
128
- const FG_YELLOW = hexFg(AEGIS_YELLOW);
129
- const FG_RED = hexFg(AEGIS_RED);
130
- const FG_BORDER = hexFg(AEGIS_BORDER);
131
-
132
- // Background colors
133
- const BG_SURFACE = hexBg(AEGIS_SURFACE);
134
- const BG_CYAN = hexBg(AEGIS_CYAN);
135
- const BG_GREEN = hexBg(AEGIS_GREEN);
136
- const BG_MAGENTA = hexBg(AEGIS_MAGENTA);
137
-
138
- // Separator character using border color
139
- const SEP = ` ${FG_BORDER}\u2502${RESET}${BG_SURFACE}${FG_PRIMARY} `;
36
+ const GREEN = '\x1b[32m';
37
+ const YELLOW = '\x1b[33m';
38
+ const RED = '\x1b[31m';
39
+ const CYAN = '\x1b[36m';
40
+ const MAGENTA = '\x1b[35m';
140
41
 
141
42
  // ============================================================================
142
43
  // Configuration
@@ -146,6 +47,10 @@ const PROJECT_PATH = process.env.SPECMEM_PROJECT_PATH || process.cwd();
146
47
  const SOCKET_PATH = path.join(PROJECT_PATH, 'specmem', 'sockets', 'embeddings.sock');
147
48
  const DEBUG_LOG = path.join(PROJECT_PATH, 'specmem', 'sockets', 'mcp-debug.log');
148
49
  const STATUS_FILE = path.join(PROJECT_PATH, 'specmem', 'sockets', 'statusbar-state.json');
50
+ const TEAM_COMMS_FILE = path.join(PROJECT_PATH, 'specmem', 'sockets', 'team-comms-latest.json');
51
+
52
+ // Max age for team comms to be considered "active" (5 minutes)
53
+ const TEAM_COMMS_MAX_AGE_MS = parseInt(process.env.SPECMEM_TEAM_COMMS_TTL || '300000', 10);
149
54
 
150
55
  // ============================================================================
151
56
  // Status State
@@ -158,18 +63,16 @@ let statusState = {
158
63
  memoryCount: 0,
159
64
  mcpConnected: false,
160
65
  lastUpdate: 0,
161
- mode: 'COMMAND', // Current mode: COMMAND, CLAUDE, SPECMEM
162
- resourceUsage: null, // { ramPercent, ramUsedMB, cpuPercent }
163
- syncScore: null // Sync score percentage (0-100)
66
+ mode: 'COMMAND',
67
+ resourceUsage: null,
68
+ syncScore: null,
69
+ teamComms: null
164
70
  };
165
71
 
166
72
  // ============================================================================
167
73
  // Health Checks
168
74
  // ============================================================================
169
75
 
170
- /**
171
- * Check embedding server health via socket
172
- */
173
76
  function checkEmbeddingHealth() {
174
77
  return new Promise((resolve) => {
175
78
  if (!fs.existsSync(SOCKET_PATH)) {
@@ -192,8 +95,6 @@ function checkEmbeddingHealth() {
192
95
  socket.destroy();
193
96
  try {
194
97
  const response = JSON.parse(data.toString().split('\n')[0]);
195
- // Server sends a {"status":"processing"} heartbeat before the real response.
196
- // Any valid JSON response without an error means the server is alive and healthy.
197
98
  if (response.error) {
198
99
  resolve('degraded');
199
100
  } else {
@@ -206,45 +107,26 @@ function checkEmbeddingHealth() {
206
107
  });
207
108
 
208
109
  socket.on('error', () => {
209
- if (!responded) {
210
- responded = true;
211
- socket.destroy();
212
- resolve('error');
213
- }
110
+ if (!responded) { responded = true; socket.destroy(); resolve('error'); }
214
111
  });
215
112
 
216
113
  socket.on('timeout', () => {
217
- if (!responded) {
218
- responded = true;
219
- socket.destroy();
220
- resolve('timeout');
221
- }
114
+ if (!responded) { responded = true; socket.destroy(); resolve('timeout'); }
222
115
  });
223
116
  });
224
117
  }
225
118
 
226
- /**
227
- * Get last tool call from debug log
228
- */
229
119
  function getLastToolCall() {
230
120
  try {
231
121
  if (!fs.existsSync(DEBUG_LOG)) return null;
232
-
233
122
  const content = fs.readFileSync(DEBUG_LOG, 'utf8');
234
123
  const lines = content.split('\n').filter(l => l.trim());
235
-
236
- // Get last non-error line
237
124
  for (let i = lines.length - 1; i >= 0 && i > lines.length - 20; i--) {
238
125
  const line = lines[i];
239
126
  if (!line.includes('ERROR')) {
240
- // Parse: [HH:MM:SS] tool_name (XXms) ...
241
127
  const match = line.match(/\[(\d+:\d+:\d+)\]\s+(\w+)\s+\((\d+)ms\)/);
242
128
  if (match) {
243
- return {
244
- time: match[1],
245
- tool: match[2],
246
- duration: parseInt(match[3], 10)
247
- };
129
+ return { time: match[1], tool: match[2], duration: parseInt(match[3], 10) };
248
130
  }
249
131
  }
250
132
  }
@@ -258,197 +140,188 @@ function getLastToolCall() {
258
140
  // Resource Monitoring
259
141
  // ============================================================================
260
142
 
261
- /**
262
- * Get current system resource usage (RAM and CPU load average).
263
- * @returns {{ ramPercent: number, ramUsedMB: number, ramTotalMB: number, cpuLoad: number }}
264
- */
265
143
  function getResourceUsage() {
266
144
  try {
267
145
  const totalMem = os.totalmem();
268
146
  const freeMem = os.freemem();
269
147
  const usedMem = totalMem - freeMem;
270
148
  const ramPercent = Math.round((usedMem / totalMem) * 100);
271
- const ramUsedMB = Math.round(usedMem / (1024 * 1024));
272
- const ramTotalMB = Math.round(totalMem / (1024 * 1024));
273
-
274
- // 1-minute load average (Unix only, returns 0 on unsupported)
275
149
  const loadAvg = os.loadavg();
276
150
  const cpuCount = os.cpus().length || 1;
277
151
  const cpuLoad = Math.round((loadAvg[0] / cpuCount) * 100);
278
-
279
- return { ramPercent, ramUsedMB, ramTotalMB, cpuLoad };
152
+ return { ramPercent, cpuLoad };
280
153
  } catch (_e) {
281
154
  return null;
282
155
  }
283
156
  }
284
157
 
285
- /**
286
- * Format resource usage for status bar display with AEGIS colors.
287
- * RAM color: green < 60%, yellow 60-85%, red > 85%
288
- * CPU color: green < 70%, yellow 70-90%, red > 90%
289
- * @param {{ ramPercent: number, ramUsedMB: number, cpuLoad: number }} res
290
- * @returns {string} Formatted resource string
291
- */
292
- function formatResourceUsage(res) {
293
- if (!res) return '';
294
-
295
- // RAM indicator
296
- const ramColor = res.ramPercent > 85 ? FG_RED :
297
- res.ramPercent > 60 ? FG_YELLOW : FG_GREEN;
298
- const ramStr = `${ramColor}${res.ramPercent}%${RESET}${BG_SURFACE}${FG_DIM} RAM${RESET}${BG_SURFACE}`;
299
-
300
- // CPU load indicator
301
- const cpuColor = res.cpuLoad > 90 ? FG_RED :
302
- res.cpuLoad > 70 ? FG_YELLOW : FG_GREEN;
303
- const cpuStr = `${cpuColor}${res.cpuLoad}%${RESET}${BG_SURFACE}${FG_DIM} CPU${RESET}${BG_SURFACE}`;
158
+ // ============================================================================
159
+ // Team Comms
160
+ // ============================================================================
304
161
 
305
- return `${ramStr} ${cpuStr}`;
162
+ function getLatestTeamComms() {
163
+ try {
164
+ if (!fs.existsSync(TEAM_COMMS_FILE)) return null;
165
+ const raw = fs.readFileSync(TEAM_COMMS_FILE, 'utf-8');
166
+ const data = JSON.parse(raw);
167
+ // Check if message is still fresh
168
+ const age = Date.now() - new Date(data.timestamp).getTime();
169
+ if (age > TEAM_COMMS_MAX_AGE_MS) return null;
170
+ return data;
171
+ } catch (_e) {
172
+ return null;
173
+ }
306
174
  }
307
175
 
308
176
  // ============================================================================
309
- // Mode Badge Rendering
177
+ // Status Formatting - Old-school elegant theme
310
178
  // ============================================================================
311
179
 
312
180
  /**
313
- * Render mode badge with AEGIS theme colors.
314
- * COMMAND: cyan bg + dark text
315
- * CLAUDE: green bg + dark text
316
- * SPECMEM: magenta bg + dark text
317
- * @param {string} mode - Current mode name
318
- * @returns {string} Styled mode badge
181
+ * Format team comms line (top line)
182
+ * Shows latest agent message truncated to 24 chars, or "no active team working"
319
183
  */
320
- function renderModeBadge(mode) {
321
- const m = (mode || 'COMMAND').toUpperCase();
322
- let bg, label;
323
- switch (m) {
324
- case 'CLAUDE':
325
- bg = BG_GREEN;
326
- label = ' CLAUDE ';
327
- break;
328
- case 'SPECMEM':
329
- bg = BG_MAGENTA;
330
- label = ' SPECMEM ';
331
- break;
332
- case 'COMMAND':
333
- default:
334
- bg = BG_CYAN;
335
- label = ' COMMAND ';
336
- break;
184
+ function formatTeamComms() {
185
+ const comms = statusState.teamComms;
186
+ if (!comms || !comms.message) {
187
+ return `${DIM}no active team working${RESET}`;
188
+ }
189
+ // Clean the message - strip tags, metadata, swarm IDs
190
+ let msg = comms.message
191
+ .replace(/\[.*?\]/g, '') // Remove [tags]
192
+ .replace(/@\S+/g, '') // Remove @mentions
193
+ .replace(/swarm-\d+/gi, '') // Remove swarm IDs
194
+ .replace(/\s+/g, ' ') // Normalize whitespace
195
+ .trim();
196
+
197
+ // Truncate to 24 chars
198
+ if (msg.length > 24) {
199
+ msg = msg.slice(0, 23) + '\u2026';
337
200
  }
338
- return `${bg}${BOLD}${FG_DARK}${label}${RESET}${BG_SURFACE}`;
339
- }
340
201
 
341
- // ============================================================================
342
- // Status Bar Drawing
343
- // ============================================================================
202
+ // Show sender name (truncated) + message
203
+ let sender = (comms.sender || 'agent').replace(/^mcp-/, '').replace(/-\d+$/, '');
204
+ if (sender.length > 10) sender = sender.slice(0, 9) + '\u2026';
205
+
206
+ return `${CYAN}${sender}${RESET}${DIM}:${RESET} ${msg}`;
207
+ }
344
208
 
345
209
  /**
346
- * Format status for display - AEGIS truecolor theme
210
+ * Format status line (bottom line) - old-school style with new data
347
211
  *
348
- * Layout:
349
- * [MODE] | SpecMem v3.5.89 | ● health | last_tool Xms | Nmem | 45% RAM 12% CPU | [h]Help [q]Quit
212
+ * Layout: ● health │ SpecMem v3.6.0 │ tool Xms │ 98% sync │ 14% RAM 9% CPU
350
213
  */
351
214
  function formatStatus() {
352
215
  const parts = [];
353
216
 
354
- // Mode badge
355
- parts.push(renderModeBadge(statusState.mode));
356
-
357
- // SpecMem branding with version
358
- parts.push(`${FG_CYAN}${BOLD}SpecMem${RESET}${BG_SURFACE} ${FG_DIM}v${SPECMEM_VERSION}${RESET}${BG_SURFACE}`);
359
-
360
- // Embedding status with AEGIS colored indicators
361
- const embIndicator = {
362
- 'healthy': `${FG_GREEN}\u25cf${RESET}${BG_SURFACE} ${FG_PRIMARY}Online${RESET}${BG_SURFACE}`,
363
- 'degraded': `${FG_YELLOW}\u25d0${RESET}${BG_SURFACE} ${FG_YELLOW}Degraded${RESET}${BG_SURFACE}`,
364
- 'error': `${FG_RED}\u25cf${RESET}${BG_SURFACE} ${FG_RED}Error${RESET}${BG_SURFACE}`,
365
- 'offline': `${FG_DIM}\u25cb${RESET}${BG_SURFACE} ${FG_DIM}Offline${RESET}${BG_SURFACE}`,
366
- 'timeout': `${FG_YELLOW}\u25cc${RESET}${BG_SURFACE} ${FG_YELLOW}Timeout${RESET}${BG_SURFACE}`,
367
- 'unknown': `${FG_DIM}?${RESET}${BG_SURFACE} ${FG_DIM}Unknown${RESET}${BG_SURFACE}`
368
- }[statusState.embeddingHealth] || `${FG_DIM}?${RESET}${BG_SURFACE} ${FG_DIM}Unknown${RESET}${BG_SURFACE}`;
369
-
370
- parts.push(embIndicator);
371
-
372
- // Last tool - AEGIS styled
217
+ // Embedding health indicator
218
+ const embIcon = {
219
+ 'healthy': `${GREEN}\u25cf${RESET}`,
220
+ 'degraded': `${YELLOW}\u25d0${RESET}`,
221
+ 'error': `${RED}\u25cf${RESET}`,
222
+ 'offline': `${DIM}\u25cb${RESET}`,
223
+ 'timeout': `${YELLOW}\u25cc${RESET}`,
224
+ 'unknown': `${DIM}?${RESET}`
225
+ }[statusState.embeddingHealth] || `${DIM}?${RESET}`;
226
+
227
+ const healthLabel = {
228
+ 'healthy': `${GREEN}Online${RESET}`,
229
+ 'degraded': `${YELLOW}Degraded${RESET}`,
230
+ 'error': `${RED}Error${RESET}`,
231
+ 'offline': `${DIM}Offline${RESET}`,
232
+ 'timeout': `${YELLOW}Timeout${RESET}`,
233
+ 'unknown': `${DIM}Unknown${RESET}`
234
+ }[statusState.embeddingHealth] || `${DIM}Unknown${RESET}`;
235
+
236
+ parts.push(`${embIcon} ${healthLabel}`);
237
+
238
+ // Version
239
+ parts.push(`${CYAN}${BOLD}SpecMem${RESET} ${DIM}v${SPECMEM_VERSION}${RESET}`);
240
+
241
+ // Last tool call
373
242
  if (statusState.lastToolCall) {
374
- const tool = statusState.lastToolCall.tool;
243
+ const tool = statusState.lastToolCall.tool.replace('mcp__specmem__', '').slice(0, 18);
375
244
  const dur = statusState.lastToolCall.duration;
376
- const durColor = dur > 5000 ? FG_RED : dur > 1000 ? FG_YELLOW : FG_GREEN;
377
- // Truncate and style tool name
378
- const toolDisplay = tool.replace('mcp__specmem__', '').slice(0, 18);
379
- parts.push(`${FG_CYAN}${toolDisplay}${RESET}${BG_SURFACE} ${durColor}${dur}ms${RESET}${BG_SURFACE}`);
380
- }
381
-
382
- // Memory count
383
- if (statusState.memoryCount > 0) {
384
- parts.push(`${FG_MAGENTA}${statusState.memoryCount}${RESET}${BG_SURFACE}${FG_DIM} mem${RESET}${BG_SURFACE}`);
245
+ const durColor = dur > 5000 ? RED : dur > 1000 ? YELLOW : GREEN;
246
+ parts.push(`${CYAN}${tool}${RESET} ${durColor}${dur}ms${RESET}`);
385
247
  }
386
248
 
387
249
  // Sync score
388
250
  if (statusState.syncScore !== null && statusState.syncScore !== undefined) {
389
251
  const score = Math.round(statusState.syncScore);
390
- const syncColor = score >= 90 ? FG_GREEN : score >= 50 ? FG_YELLOW : FG_RED;
391
- parts.push(`${syncColor}${score}%${RESET}${BG_SURFACE}${FG_DIM} sync${RESET}${BG_SURFACE}`);
252
+ const syncColor = score >= 90 ? GREEN : score >= 50 ? YELLOW : RED;
253
+ parts.push(`${syncColor}${score}%${RESET}${DIM} sync${RESET}`);
392
254
  }
393
255
 
394
- // Resource monitoring
395
- const resUsage = statusState.resourceUsage || getResourceUsage();
396
- const resStr = formatResourceUsage(resUsage);
397
- if (resStr) {
398
- parts.push(resStr);
256
+ // Resources
257
+ const res = statusState.resourceUsage || getResourceUsage();
258
+ if (res) {
259
+ const ramColor = res.ramPercent > 85 ? RED : res.ramPercent > 60 ? YELLOW : GREEN;
260
+ const cpuColor = res.cpuLoad > 90 ? RED : res.cpuLoad > 70 ? YELLOW : GREEN;
261
+ parts.push(`${ramColor}${res.ramPercent}%${RESET}${DIM} RAM${RESET} ${cpuColor}${res.cpuLoad}%${RESET}${DIM} CPU${RESET}`);
399
262
  }
400
263
 
401
- // Key hints: cyan key, dim description
402
- parts.push(
403
- `${FG_CYAN}[h]${RESET}${BG_SURFACE}${FG_DIM}Help${RESET}${BG_SURFACE} ` +
404
- `${FG_CYAN}[q]${RESET}${BG_SURFACE}${FG_DIM}Quit${RESET}${BG_SURFACE}`
405
- );
264
+ return parts.join(` ${DIM}\u2502${RESET} `);
265
+ }
266
+
267
+ // ============================================================================
268
+ // Drawing - Old-school centered-on-dashes style
269
+ // ============================================================================
270
+
271
+ /**
272
+ * Center text on a line of dashes
273
+ * @param {string} text - ANSI-colored text to center
274
+ * @param {number} cols - Terminal width
275
+ * @returns {string} Full-width dash line with centered text
276
+ */
277
+ function centerOnDashes(text, cols) {
278
+ const plainLen = text.replace(/\x1b\[[0-9;]*m/g, '').length;
279
+ const totalDashes = Math.max(0, cols - plainLen - 4); // 4 = spaces around text
280
+ const leftDashes = Math.floor(totalDashes / 2);
281
+ const rightDashes = totalDashes - leftDashes;
282
+
283
+ if (plainLen >= cols - 4) {
284
+ // Text too long, just show it
285
+ return text;
286
+ }
406
287
 
407
- return parts.join(SEP);
288
+ return `${DIM}${'─'.repeat(leftDashes)}${RESET} ${text} ${DIM}${'─'.repeat(rightDashes)}${RESET}`;
408
289
  }
409
290
 
410
291
  /**
411
- * Draw status bar on row `rows - 1` (above claudefix)
412
- * AEGIS truecolor theme with surface background
292
+ * Draw two-line status bar
293
+ * Line 1 (rows - 2): Team comms centered on dashes
294
+ * Line 2 (rows - 1): Status info centered on dashes
413
295
  */
414
296
  function drawStatusBar(rows, cols) {
415
- const status = formatStatus();
416
- // Calculate visible (plain text) length to center / pad correctly
417
- const plainLen = status.replace(/\x1b\[[0-9;]*m/g, '').length;
418
-
419
- // Build full-width background line
420
- const totalPad = Math.max(0, cols - plainLen);
421
- const leftPad = 1; // 1-char left margin
422
- const rightPad = Math.max(0, totalPad - leftPad);
423
-
424
- // Full-width AEGIS surface background bar
425
- const line =
426
- `${BG_SURFACE}` +
427
- ' '.repeat(leftPad) +
428
- status +
429
- ' '.repeat(rightPad) +
430
- `${RESET}`;
431
-
432
- // Draw on second-to-last row (rows - 1), claudefix is on rows
297
+ const commsLine = centerOnDashes(formatTeamComms(), cols);
298
+ const statusLine = centerOnDashes(formatStatus(), cols);
299
+
300
+ // Draw on rows - 2 and rows - 1 (above claudefix on rows)
433
301
  process.stdout.write(
434
- '\x1b7' + // Save cursor
435
- `\x1b[${rows - 1};1H` + // Move to row above claudefix
436
- '\x1b[2K' + // Clear line
437
- line + // AEGIS themed status bar
438
- '\x1b8' // Restore cursor
302
+ '\x1b7' + // Save cursor
303
+ `\x1b[${rows - 2};1H` + // Move to team comms row
304
+ '\x1b[2K' + // Clear line
305
+ commsLine + // Team comms
306
+ `\x1b[${rows - 1};1H` + // Move to status row
307
+ '\x1b[2K' + // Clear line
308
+ statusLine + // Status info
309
+ '\x1b8' // Restore cursor
439
310
  );
440
311
  }
441
312
 
442
- /**
443
- * Update status state
444
- */
313
+ // ============================================================================
314
+ // Update
315
+ // ============================================================================
316
+
445
317
  async function updateStatus() {
446
318
  statusState.embeddingHealth = await checkEmbeddingHealth();
447
319
  statusState.lastToolCall = getLastToolCall();
448
320
  statusState.resourceUsage = getResourceUsage();
321
+ statusState.teamComms = getLatestTeamComms();
449
322
  statusState.lastUpdate = Date.now();
450
323
 
451
- // Read sync score from state file (written by watcher)
324
+ // Read sync score from state file
452
325
  try {
453
326
  if (fs.existsSync(STATUS_FILE)) {
454
327
  const existing = JSON.parse(fs.readFileSync(STATUS_FILE, 'utf-8'));
@@ -464,10 +337,6 @@ async function updateStatus() {
464
337
  } catch (e) { /* ignore */ }
465
338
  }
466
339
 
467
- /**
468
- * Set current mode for the mode badge.
469
- * @param {string} mode - One of 'COMMAND', 'CLAUDE', 'SPECMEM'
470
- */
471
340
  function setMode(mode) {
472
341
  const valid = ['COMMAND', 'CLAUDE', 'SPECMEM'];
473
342
  statusState.mode = valid.includes((mode || '').toUpperCase())
@@ -484,11 +353,10 @@ module.exports = {
484
353
  updateStatus,
485
354
  getStatus: () => statusState,
486
355
  formatStatus,
356
+ formatTeamComms,
487
357
  checkEmbeddingHealth,
488
358
  setMode,
489
- getResourceUsage,
490
- formatResourceUsage,
491
- renderModeBadge
359
+ getResourceUsage
492
360
  };
493
361
 
494
362
  // ============================================================================
@@ -499,27 +367,15 @@ if (require.main === module) {
499
367
  const rows = process.stdout.rows || 24;
500
368
  const cols = process.stdout.columns || 80;
501
369
 
502
- console.log('SpecMem Status Bar - Standalone Test (AEGIS Theme)');
503
- console.log('===================================================\n');
370
+ console.log('SpecMem Status Bar - Standalone Test');
371
+ console.log('=====================================\n');
504
372
 
505
373
  async function run() {
506
374
  await updateStatus();
507
375
 
508
- // Show plain text status
376
+ console.log('Team Comms:', formatTeamComms().replace(/\x1b\[[0-9;]*m/g, ''));
509
377
  console.log('Status:', formatStatus().replace(/\x1b\[[0-9;]*m/g, ''));
510
- console.log('State:', JSON.stringify(statusState, null, 2));
511
-
512
- // Show resource usage
513
- const res = getResourceUsage();
514
- if (res) {
515
- console.log(`\nResources: RAM ${res.ramPercent}% (${res.ramUsedMB}MB/${res.ramTotalMB}MB), CPU load ${res.cpuLoad}%`);
516
- }
517
-
518
- // Test mode badges (visual)
519
- console.log('\nMode badges (with AEGIS colors):');
520
- console.log(' ' + renderModeBadge('COMMAND') + RESET);
521
- console.log(' ' + renderModeBadge('CLAUDE') + RESET);
522
- console.log(' ' + renderModeBadge('SPECMEM') + RESET);
378
+ console.log('\nState:', JSON.stringify(statusState, null, 2));
523
379
 
524
380
  // Draw at bottom
525
381
  console.log('\nDrawing status bar...');
@@ -741,13 +741,16 @@ There is NO alternative path. The system ENFORCES MCP tool usage:
741
741
  - You CANNOT proceed without announcing via send_team_message FIRST
742
742
  - You CANNOT write code without claiming files via claim_task
743
743
  - You CANNOT search more than 3 times without using find_memory/find_code_pointers
744
- - Every 5 tool calls, you WILL BE BLOCKED until you check read_team_messages
744
+ - Every 4 tool calls, you WILL BE BLOCKED until you check read_team_messages (team comms)
745
+ - Every 5 tool calls, you WILL BE BLOCKED until you check read_team_messages with include_broadcasts:true
745
746
  - Every 8 tool calls, you WILL BE BLOCKED until you check get_team_status for help requests
747
+ - These counters apply to ALL tool calls, including memory tools and searches - no exceptions
746
748
  If you want to complete your goal, you MUST use MCP tools. There is no workaround.
747
749
 
748
750
  TEAM COMMUNICATION (mcp__specmem__ prefix):
749
751
  - send_team_message({message, channel:"main"|"swarm-1".."swarm-5", type:"status"|"question"|"update", priority})
750
- - read_team_messages({channel, limit:10, include_broadcasts:true}) - CHECK BROADCASTS REGULARLY!
752
+ - read_team_messages({limit:5, include_swarms:true}) - MANDATORY every 4 tool calls!
753
+ - read_team_messages({include_broadcasts:true, include_swarms:true, limit:10}) - MANDATORY every 5 calls!
751
754
  - broadcast_to_team({message, broadcast_type:"status"|"progress"|"announcement", priority})
752
755
  - claim_task({description, files:["path1","path2"]}) - REQUIRED before editing
753
756
  - release_task({claimId:"all"|"<id>"}) - release when done
@@ -766,8 +769,9 @@ WORKFLOW (enforced - you cannot skip steps):
766
769
  1. START: send_team_message({type:"status", message:"Starting: [task]"})
767
770
  2. CLAIM: claim_task({description, files}) - REQUIRED before any writes
768
771
  3. SEARCH: find_memory/find_code_pointers FIRST, then Grep/Glob if needed
769
- 4. EVERY 5 CALLS: read_team_messages({include_broadcasts:true}) - MANDATORY
770
- 5. EVERY 8 CALLS: get_team_status() - check if anyone needs help!
772
+ 4. EVERY 4 CALLS: read_team_messages({include_swarms:true, limit:5}) - MANDATORY, you WILL be blocked
773
+ 5. EVERY 5 CALLS: read_team_messages({include_broadcasts:true, include_swarms:true, limit:10}) - MANDATORY
774
+ 6. EVERY 8 CALLS: get_team_status() - check if anyone needs help!
771
775
  6. DONE: release_task({claimId:"all"}), send completion status
772
776
  [/TEAM CONTEXT]`;
773
777
  }