specmem-hardwicksoftware 3.5.99 → 3.6.0
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/bin/specmem-statusbar.cjs +154 -298
- package/claude-hooks/agent-loading-hook.js +8 -4
- package/claude-hooks/team-comms-enforcer.cjs +109 -92
- package/dist/config/embeddingTimeouts.js +4 -4
- package/dist/database.js +52 -6
- package/dist/db/bigBrainMigrations.js +7 -6
- package/dist/index.js +238 -13
- package/dist/installer/firstRun.js +2 -2
- package/dist/mcp/embeddingServerManager.js +225 -7
- package/dist/mcp/healthMonitor.js +165 -32
- package/dist/mcp/tools/embeddingControl.js +31 -0
- package/dist/mcp/tools/teamComms.js +16 -0
- package/dist/mcp/watcherIntegration.js +50 -7
- package/dist/services/CameraZoomSearch.js +62 -5
- package/dist/services/DimensionService.js +73 -6
- package/dist/services/EmbeddingQueue.js +64 -0
- package/dist/tools/goofy/findCodePointers.js +11 -7
- package/dist/tools/goofy/findWhatISaid.js +145 -53
- package/dist/utils/qoms.js +187 -4
- package/dist/watcher/changeHandler.js +54 -4
- package/dist/watcher/fileWatcher.js +121 -1
- package/dist/watcher/index.js +75 -31
- package/dist/watcher/syncChecker.js +248 -63
- package/embedding-sandbox/__pycache__/frankenstein-embeddings.cpython-313.pyc +0 -0
- package/embedding-sandbox/frankenstein-embeddings.py +175 -64
- package/package.json +1 -1
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* SPECMEM STATUS BAR -
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* SPECMEM STATUS BAR - Clean two-line status display
|
|
4
|
+
* ===================================================
|
|
5
|
+
* Old-school elegant theme with new functionality.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
const
|
|
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',
|
|
162
|
-
resourceUsage: null,
|
|
163
|
-
syncScore: null
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
177
|
+
// Status Formatting - Old-school elegant theme
|
|
310
178
|
// ============================================================================
|
|
311
179
|
|
|
312
180
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
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
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
'
|
|
366
|
-
'
|
|
367
|
-
'
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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 ?
|
|
377
|
-
|
|
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 ?
|
|
391
|
-
parts.push(`${syncColor}${score}%${RESET}${
|
|
252
|
+
const syncColor = score >= 90 ? GREEN : score >= 50 ? YELLOW : RED;
|
|
253
|
+
parts.push(`${syncColor}${score}%${RESET}${DIM} sync${RESET}`);
|
|
392
254
|
}
|
|
393
255
|
|
|
394
|
-
//
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
|
288
|
+
return `${DIM}${'─'.repeat(leftDashes)}${RESET} ${text} ${DIM}${'─'.repeat(rightDashes)}${RESET}`;
|
|
408
289
|
}
|
|
409
290
|
|
|
410
291
|
/**
|
|
411
|
-
* Draw status bar
|
|
412
|
-
*
|
|
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
|
|
416
|
-
|
|
417
|
-
|
|
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' +
|
|
435
|
-
`\x1b[${rows -
|
|
436
|
-
'\x1b[2K' +
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
503
|
-
console.log('
|
|
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
|
-
|
|
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('
|
|
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
|
|
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({
|
|
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
|
|
770
|
-
5. EVERY
|
|
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
|
}
|