specmem-hardwicksoftware 3.7.35 → 3.7.38

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 (71) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +11 -15
  3. package/bin/specmem-autoclaude.cjs +12 -1
  4. package/bin/specmem-cli.cjs +1077 -11
  5. package/bin/specmem-console.cjs +890 -63
  6. package/bootstrap.cjs +10 -2
  7. package/claude-hooks/agent-loading-hook.cjs +16 -16
  8. package/claude-hooks/agent-loading-hook.js +28 -21
  9. package/claude-hooks/agent-type-matcher.js +1 -1
  10. package/claude-hooks/background-completion-silencer.js +1 -1
  11. package/claude-hooks/file-claim-enforcer.cjs +37 -36
  12. package/claude-hooks/output-cleaner.cjs +1 -1
  13. package/claude-hooks/refusal-detector-hook.cjs +53 -0
  14. package/claude-hooks/settings.json +64 -4
  15. package/claude-hooks/smart-search-interceptor.js +1 -1
  16. package/claude-hooks/specmem-search-enforcer.cjs +2 -11
  17. package/claude-hooks/specmem-team-member-inject.js +1 -1
  18. package/claude-hooks/specmem-unified-hook.py +1 -1
  19. package/claude-hooks/subagent-loading-hook.cjs +1 -1
  20. package/claude-hooks/task-progress-hook.cjs +7 -7
  21. package/claude-hooks/task-progress-hook.js +3 -3
  22. package/claude-hooks/team-comms-enforcer.cjs +113 -47
  23. package/claude-hooks/use-code-pointers.cjs +1 -1
  24. package/dist/claude-sessions/sessionParser.js +5 -0
  25. package/dist/cli/deploy-to-claude.js +9 -2
  26. package/dist/codebase/codebaseIndexer.js +48 -17
  27. package/dist/codebase/exclusions.js +3 -4
  28. package/dist/codebase/index.js +4 -0
  29. package/dist/codebase/pdfExtractor.js +298 -0
  30. package/dist/dashboard/api/taskTeamMembers.js +2 -2
  31. package/dist/db/bigBrainMigrations.js +29 -0
  32. package/dist/hooks/hookManager.js +4 -4
  33. package/dist/hooks/teamFramingCli.js +1 -1
  34. package/dist/hooks/teamMemberPrepromptHook.js +5 -5
  35. package/dist/index.js +49 -12
  36. package/dist/init/claudeConfigInjector.js +27 -8
  37. package/dist/installer/autoInstall.js +7 -1
  38. package/dist/mcp/compactionProxy.js +1052 -192
  39. package/dist/mcp/compactionProxyDaemon.js +112 -37
  40. package/dist/mcp/contextVault.js +439 -0
  41. package/dist/mcp/embeddingServerManager.js +151 -17
  42. package/dist/mcp/mcpProtocolHandler.js +6 -1
  43. package/dist/mcp/miniCOTServerManager.js +82 -8
  44. package/dist/mcp/specMemServer.js +45 -10
  45. package/dist/mcp/toolRegistry.js +6 -0
  46. package/dist/startup/startupIndexing.js +14 -0
  47. package/dist/team-members/taskOrchestrator.js +3 -3
  48. package/dist/team-members/taskTeamMemberLogger.js +2 -2
  49. package/dist/tools/goofy/deployTeamMember.js +3 -3
  50. package/dist/tools/goofy/digInTheVault.js +81 -0
  51. package/dist/tools/goofy/findCodePointers.js +17 -0
  52. package/dist/tools/goofy/findWhatISaid.js +19 -0
  53. package/dist/tools/goofy/stashTheGoods.js +56 -0
  54. package/dist/tools/teamMemberDeployer.js +2 -2
  55. package/dist/watcher/changeHandler.js +65 -8
  56. package/dist/watcher/changeQueue.js +20 -1
  57. package/embedding-sandbox/frankenstein-embeddings.py +4 -3
  58. package/embedding-sandbox/mini-cot-service.py +11 -13
  59. package/embedding-sandbox/pdf-text-extract.py +208 -0
  60. package/package.json +1 -1
  61. package/scripts/deploy-hooks.cjs +12 -4
  62. package/scripts/fast-batch-embedder.cjs +2 -2
  63. package/scripts/force-retry.cjs +34 -0
  64. package/scripts/global-postinstall.cjs +97 -4
  65. package/scripts/poetic-abliteration.cjs +379 -0
  66. package/scripts/refusal-enforcer.cjs +88 -0
  67. package/scripts/specmem-init.cjs +222 -41
  68. package/specmem/model-config.json +6 -6
  69. package/specmem/supervisord.conf +1 -1
  70. package/svg-sections/readme-token-compaction.svg +246 -0
  71. package/claude-hooks/agent-chooser-hook.js +0 -179
@@ -44,6 +44,20 @@ const SPECMEM_PKG = path.dirname(__dirname);
44
44
  const MODELS_DIR = path.join(SPECMEM_PKG, 'models');
45
45
  const OPTIMIZED_DIR = path.join(MODELS_DIR, 'optimized');
46
46
 
47
+ // Helper: Get compaction proxy port if running
48
+ function getProxyEnv() {
49
+ try {
50
+ const portFile = path.join(os.homedir(), '.claude', '.compaction-proxy-port');
51
+ if (fs.existsSync(portFile)) {
52
+ const port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10);
53
+ if (port) {
54
+ return { ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` };
55
+ }
56
+ }
57
+ } catch (e) {}
58
+ return {};
59
+ }
60
+
47
61
  const args = process.argv.slice(2);
48
62
  const command = args[0] || 'help';
49
63
  const subArgs = args.slice(1);
@@ -237,6 +251,20 @@ switch (command) {
237
251
  case 'proxy':
238
252
  manageProxy(subArgs);
239
253
  break;
254
+ case 'embed':
255
+ manageEmbed(subArgs);
256
+ break;
257
+ case 'config':
258
+ manageConfig(subArgs);
259
+ break;
260
+ case 'minicot':
261
+ case 'minicott':
262
+ manageMinicot(subArgs);
263
+ break;
264
+ case 'quickstart':
265
+ // Minimal init without database setup
266
+ runQuickstart();
267
+ break;
240
268
  case '--version':
241
269
  case '-v':
242
270
  case 'version':
@@ -570,11 +598,11 @@ ${CYAN}What's configured:${RESET}
570
598
  } catch {
571
599
  console.log(`${YELLOW}⚠${RESET} Could not install screen, launching directly...`);
572
600
  console.log(`${DIM}Run: claude${RESET}\n`);
573
- // Launch directly without screen
601
+ // Launch directly without screen (add proxy env)
574
602
  const child = spawn('claude', [], {
575
603
  cwd: projectDir,
576
604
  stdio: 'inherit',
577
- env: { ...process.env, SPECMEM_PROJECT_PATH: projectDir }
605
+ env: { ...process.env, SPECMEM_PROJECT_PATH: projectDir, ...getProxyEnv() }
578
606
  });
579
607
  return;
580
608
  }
@@ -601,10 +629,12 @@ ${CYAN}What's configured:${RESET}
601
629
  console.log(`${DIM}───────────────────────────────────────────────${RESET}`);
602
630
 
603
631
  // Use -S to create named session, no -d so it attaches immediately
632
+ // Include proxy env to route through compaction proxy
633
+ const proxyEnv = getProxyEnv();
604
634
  const result = spawnSync('screen', ['-S', screenName, 'claude'], {
605
635
  cwd: projectDir,
606
636
  stdio: 'inherit',
607
- env: { ...process.env, SPECMEM_PROJECT_PATH: projectDir }
637
+ env: { ...process.env, SPECMEM_PROJECT_PATH: projectDir, ...proxyEnv }
608
638
  });
609
639
 
610
640
  // User has detached or exited
@@ -909,24 +939,1060 @@ ${BOLD}Aliases:${RESET}
909
939
  }
910
940
  }
911
941
 
942
+ // ============================================================================
943
+ // EMBED COMMAND - Embedding Server Management
944
+ // ============================================================================
945
+ function manageEmbed(args) {
946
+ const sub = (args[0] || 'status').toLowerCase();
947
+ const projectPath = process.cwd();
948
+
949
+ // Get project-specific paths
950
+ const specmemDir = path.join(projectPath, 'specmem');
951
+ const socketDir = path.join(specmemDir, 'run');
952
+ const socketPath = path.join(socketDir, 'embed.sock');
953
+ const pidFilePath = path.join(socketDir, 'embedding.pid');
954
+
955
+ switch (sub) {
956
+ case 'start': {
957
+ // Ensure directories exist
958
+ if (!fs.existsSync(socketDir)) {
959
+ fs.mkdirSync(socketDir, { recursive: true });
960
+ }
961
+
962
+ // Check if already running
963
+ if (fs.existsSync(pidFilePath)) {
964
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
965
+ const [pid] = pidData.split(':');
966
+ try {
967
+ process.kill(pid, 0); // Check if process exists
968
+ console.log(`${YELLOW}Embedding server already running (PID: ${pid})${RESET}`);
969
+ process.exit(0);
970
+ } catch (e) {
971
+ // PID file stale, remove it
972
+ fs.unlinkSync(pidFilePath);
973
+ }
974
+ }
975
+
976
+ // Start the embedding server
977
+ console.log(`${CYAN}Starting embedding server...${RESET}`);
978
+ const embedScript = path.join(SPECMEM_PKG, 'embedding-sandbox', 'frankenstein-embeddings.py');
979
+
980
+ if (!fs.existsSync(embedScript)) {
981
+ console.log(`${RED}Embedding script not found: ${embedScript}${RESET}`);
982
+ process.exit(1);
983
+ }
984
+
985
+ const env = {
986
+ ...process.env,
987
+ SPECMEM_PROJECT_PATH: projectPath,
988
+ SPECMEM_EMBEDDING_SOCKET: socketPath,
989
+ };
990
+
991
+ const child = spawn('python3', [embedScript], {
992
+ cwd: projectPath,
993
+ env,
994
+ detached: true,
995
+ stdio: 'ignore',
996
+ });
997
+
998
+ child.unref();
999
+
1000
+ // Write PID file
1001
+ fs.writeFileSync(pidFilePath, `${child.pid}:${Date.now()}`, { mode: 0o644 });
1002
+
1003
+ console.log(`${GREEN}Embedding server started (PID: ${child.pid})${RESET}`);
1004
+ console.log(`${DIM}Socket: ${socketPath}${RESET}`);
1005
+ break;
1006
+ }
1007
+
1008
+ case 'stop': {
1009
+ if (!fs.existsSync(pidFilePath)) {
1010
+ console.log(`${YELLOW}No PID file found - server may not be running${RESET}`);
1011
+ process.exit(1);
1012
+ }
1013
+
1014
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
1015
+ const [pid, timestamp] = pidData.split(':');
1016
+
1017
+ try {
1018
+ process.kill(parseInt(pid), 'SIGTERM');
1019
+ console.log(`${GREEN}Sent SIGTERM to embedding server (PID: ${pid})${RESET}`);
1020
+
1021
+ // Wait a bit then force kill if still alive
1022
+ setTimeout(() => {
1023
+ try {
1024
+ process.kill(parseInt(pid), 0);
1025
+ process.kill(parseInt(pid), 'SIGKILL');
1026
+ console.log(`${YELLOW}Force killed server${RESET}`);
1027
+ } catch (e) {
1028
+ // Process already dead
1029
+ }
1030
+ }, 3000);
1031
+
1032
+ // Remove PID file
1033
+ fs.unlinkSync(pidFilePath);
1034
+ console.log(`${GREEN}Embedding server stopped${RESET}`);
1035
+ } catch (e) {
1036
+ console.log(`${RED}Failed to stop server: ${e.message}${RESET}`);
1037
+ fs.unlinkSync(pidFilePath);
1038
+ process.exit(1);
1039
+ }
1040
+ break;
1041
+ }
1042
+
1043
+ case 'restart': {
1044
+ // First try to stop
1045
+ if (fs.existsSync(pidFilePath)) {
1046
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
1047
+ const [pid] = pidData.split(':');
1048
+ try {
1049
+ process.kill(parseInt(pid), 'SIGTERM');
1050
+ } catch (e) {
1051
+ // Already dead
1052
+ }
1053
+ fs.unlinkSync(pidFilePath);
1054
+ }
1055
+
1056
+ // Small delay then start
1057
+ setTimeout(() => {
1058
+ // Recursive call to start
1059
+ manageEmbed(['start']);
1060
+ }, 1000);
1061
+ break;
1062
+ }
1063
+
1064
+ case 'force-restart': {
1065
+ // Force kill regardless of state
1066
+ if (fs.existsSync(pidFilePath)) {
1067
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
1068
+ const [pid] = pidData.split(':');
1069
+ try {
1070
+ process.kill(parseInt(pid), 'SIGKILL');
1071
+ } catch (e) {
1072
+ // Already dead
1073
+ }
1074
+ fs.unlinkSync(pidFilePath);
1075
+ }
1076
+
1077
+ // Also try to kill any process using the socket
1078
+ try {
1079
+ execSync(`lsof -t ${socketPath} 2>/dev/null | xargs -r kill -9 2>/dev/null || true`, { stdio: 'pipe' });
1080
+ } catch (e) {}
1081
+
1082
+ // Start fresh
1083
+ setTimeout(() => {
1084
+ manageEmbed(['start']);
1085
+ }, 500);
1086
+ break;
1087
+ }
1088
+
1089
+ case 'status': {
1090
+ console.log(`${CYAN}═══ Embedding Server Status ═══${RESET}\n`);
1091
+
1092
+ if (!fs.existsSync(socketDir)) {
1093
+ console.log(`${YELLOW}Project not initialized - no specmem directory${RESET}`);
1094
+ console.log(`${DIM}Run 'specmem init' or 'specmem quickstart' first${RESET}`);
1095
+ process.exit(1);
1096
+ }
1097
+
1098
+ // Check PID file
1099
+ if (fs.existsSync(pidFilePath)) {
1100
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
1101
+ const [pid, timestamp] = pidData.split(':');
1102
+ console.log(` PID File: ${GREEN}exists${RESET} (PID: ${pid})`);
1103
+ console.log(` Started: ${new Date(parseInt(timestamp)).toLocaleString()}`);
1104
+
1105
+ // Check if process is alive
1106
+ try {
1107
+ process.kill(parseInt(pid), 0);
1108
+ console.log(` Process: ${GREEN}running${RESET}`);
1109
+ } catch (e) {
1110
+ console.log(` Process: ${RED}dead (stale PID file)${RESET}`);
1111
+ }
1112
+ } else {
1113
+ console.log(` PID File: ${YELLOW}not found${RESET}`);
1114
+ }
1115
+
1116
+ // Check socket
1117
+ console.log(`\n Socket: ${socketPath}`);
1118
+ if (fs.existsSync(socketPath)) {
1119
+ console.log(` ${GREEN}exists${RESET}`);
1120
+ } else {
1121
+ console.log(` ${YELLOW}not found${RESET}`);
1122
+ }
1123
+
1124
+ // Check for stopped flag
1125
+ const stoppedFlag = path.join(socketDir, 'embedding.stopped');
1126
+ if (fs.existsSync(stoppedFlag)) {
1127
+ console.log(`\n ${YELLOW}Server was manually stopped by user${RESET}`);
1128
+ console.log(` ${DIM}Use 'specmem embed start' to restart${RESET}`);
1129
+ }
1130
+ break;
1131
+ }
1132
+
1133
+ case 'list': {
1134
+ console.log(`${CYAN}═══ All Embedding Servers ═══${RESET}\n`);
1135
+
1136
+ // Find all PID files in common locations
1137
+ const searchPaths = [
1138
+ path.join(os.homedir(), '.specmem'),
1139
+ path.join(projectPath, 'specmem'),
1140
+ path.join(projectPath, '.specmem'),
1141
+ ];
1142
+
1143
+ let found = false;
1144
+ for (const sp of searchPaths) {
1145
+ if (!fs.existsSync(sp)) continue;
1146
+
1147
+ try {
1148
+ const pidFiles = execSync(`find ${sp} -name "embedding.pid" 2>/dev/null`, { encoding: 'utf8' })
1149
+ .trim()
1150
+ .split('\n')
1151
+ .filter(Boolean);
1152
+
1153
+ for (const pf of pidFiles) {
1154
+ const dir = path.dirname(pf);
1155
+ const socketPath = path.join(dir, 'embed.sock');
1156
+
1157
+ if (fs.existsSync(pf)) {
1158
+ const pidData = fs.readFileSync(pf, 'utf8').trim();
1159
+ const [pid, timestamp] = pidData.split(':');
1160
+ const projName = path.basename(path.dirname(path.dirname(dir)));
1161
+
1162
+ let status = `${RED}dead${RESET}`;
1163
+ try {
1164
+ process.kill(parseInt(pid), 0);
1165
+ status = `${GREEN}running${RESET}`;
1166
+ } catch (e) {}
1167
+
1168
+ console.log(` Project: ${projName}`);
1169
+ console.log(` PID: ${pid} (${status})`);
1170
+ console.log(` Socket: ${socketPath}`);
1171
+ console.log(` Started: ${new Date(parseInt(timestamp)).toLocaleString()}`);
1172
+ console.log('');
1173
+ found = true;
1174
+ }
1175
+ }
1176
+ } catch (e) {}
1177
+ }
1178
+
1179
+ if (!found) {
1180
+ console.log(` ${DIM}No embedding servers found${RESET}`);
1181
+ }
1182
+ break;
1183
+ }
1184
+
1185
+ case 'diagnose': {
1186
+ console.log(`${CYAN}═══ Embedding Server Diagnostics ═══${RESET}\n`);
1187
+
1188
+ console.log(`Project: ${projectPath}`);
1189
+ console.log(`SpecMem Dir: ${specmemDir}`);
1190
+ console.log(`Socket Dir: ${socketDir}`);
1191
+ console.log(`Socket Path: ${socketPath}`);
1192
+ console.log(`PID File: ${pidFilePath}`);
1193
+
1194
+ // Check if directories exist
1195
+ console.log(`\n${CYAN}Directory Status:${RESET}`);
1196
+ console.log(` specmem/ exists: ${fs.existsSync(specmemDir) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1197
+ console.log(` run/ exists: ${fs.existsSync(socketDir) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1198
+ console.log(` socket exists: ${fs.existsSync(socketPath) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1199
+ console.log(` PID file exists: ${fs.existsSync(pidFilePath) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1200
+
1201
+ // Check process
1202
+ if (fs.existsSync(pidFilePath)) {
1203
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
1204
+ const [pid] = pidData.split(':');
1205
+
1206
+ console.log(`\n${CYAN}Process Info:${RESET}`);
1207
+ console.log(` PID: ${pid}`);
1208
+
1209
+ try {
1210
+ const procPath = `/proc/${pid}`;
1211
+ if (fs.existsSync(procPath)) {
1212
+ const cmdline = fs.readFileSync(path.join(procPath, 'cmdline'), 'utf8').replace(/\0/g, ' ');
1213
+ console.log(` Command: ${cmdline.slice(0, 100)}`);
1214
+
1215
+ // Check env
1216
+ try {
1217
+ const envPath = path.join(procPath, 'environ');
1218
+ const envData = fs.readFileSync(envPath);
1219
+ const envStr = envData.toString('utf8');
1220
+ const specmemEnv = envStr.split('\0').find(e => e.startsWith('SPECMEM'));
1221
+ if (specmemEnv) {
1222
+ console.log(` Env: ${specmemEnv.slice(0, 80)}`);
1223
+ }
1224
+ } catch (e) {}
1225
+ } else {
1226
+ console.log(` ${RED}Process does not exist (stale PID)${RESET}`);
1227
+ }
1228
+ } catch (e) {
1229
+ console.log(` ${RED}Error reading process: ${e.message}${RESET}`);
1230
+ }
1231
+ }
1232
+
1233
+ // Check socket ownership
1234
+ if (fs.existsSync(socketPath)) {
1235
+ console.log(`\n${CYAN}Socket Status:${RESET}`);
1236
+ try {
1237
+ const stats = fs.statSync(socketPath);
1238
+ console.log(` Created: ${stats.birthtime}`);
1239
+ console.log(` Size: ${stats.size} bytes`);
1240
+ } catch (e) {
1241
+ console.log(` Error: ${e.message}`);
1242
+ }
1243
+ }
1244
+ break;
1245
+ }
1246
+
1247
+ case 'cleanup': {
1248
+ console.log(`${CYAN}Cleaning up stale embedding server files...${RESET}`);
1249
+
1250
+ let cleaned = 0;
1251
+
1252
+ // Clean stale PID files
1253
+ const searchPaths = [
1254
+ path.join(os.homedir(), '.specmem'),
1255
+ path.join(projectPath, 'specmem'),
1256
+ path.join(projectPath, '.specmem'),
1257
+ ];
1258
+
1259
+ for (const sp of searchPaths) {
1260
+ if (!fs.existsSync(sp)) continue;
1261
+
1262
+ try {
1263
+ const pidFiles = execSync(`find ${sp} -name "embedding.pid" 2>/dev/null`, { encoding: 'utf8' })
1264
+ .trim()
1265
+ .split('\n')
1266
+ .filter(Boolean);
1267
+
1268
+ for (const pf of pidFiles) {
1269
+ try {
1270
+ const pidData = fs.readFileSync(pf, 'utf8').trim();
1271
+ const [pid] = pidData.split(':');
1272
+ try {
1273
+ process.kill(parseInt(pid), 0);
1274
+ // Process still alive, skip
1275
+ } catch (e) {
1276
+ // Process dead, remove PID file
1277
+ fs.unlinkSync(pf);
1278
+ console.log(` Removed stale PID file: ${pf}`);
1279
+ cleaned++;
1280
+ }
1281
+ } catch (e) {}
1282
+ }
1283
+ } catch (e) {}
1284
+ }
1285
+
1286
+ console.log(`${GREEN}Cleaned ${cleaned} stale PID files${RESET}`);
1287
+ break;
1288
+ }
1289
+
1290
+ default:
1291
+ console.log(`${BOLD}Usage:${RESET} specmem embed <command>
1292
+
1293
+ ${BOLD}Commands:${RESET}
1294
+ ${CYAN}start${RESET} Start embedding server
1295
+ ${CYAN}stop${RESET} Stop embedding server
1296
+ ${CYAN}restart${RESET} Restart embedding server
1297
+ ${CYAN}force-restart${RESET} Force restart (kill and start fresh)
1298
+ ${CYAN}status${RESET} Show embedding server status
1299
+ ${CYAN}list${RESET} List all embedding servers
1300
+ ${CYAN}diagnose${RESET} Full diagnostic info
1301
+ ${CYAN}cleanup${RESET} Clean up stale PID files
1302
+ `);
1303
+ }
1304
+ }
1305
+
1306
+ // ============================================================================
1307
+ // CONFIG COMMAND - Configuration Management
1308
+ // ============================================================================
1309
+ function manageConfig(args) {
1310
+ const sub = (args[0] || 'list').toLowerCase();
1311
+ const key = args[1];
1312
+ const value = args[2];
1313
+
1314
+ const configDir = path.join(os.homedir(), '.specmem');
1315
+ const configFile = path.join(configDir, 'config.json');
1316
+
1317
+ // Default config values
1318
+ const defaults = {
1319
+ SPECMEM_SEARCH_LIMIT: '5',
1320
+ SPECMEM_THRESHOLD: '0.30',
1321
+ SPECMEM_MAX_CONTENT: '200',
1322
+ SPECMEM_DB_HOST: 'localhost',
1323
+ SPECMEM_DB_PORT: '5432',
1324
+ SPECMEM_EMBEDDING_MODEL: 'BAAI/bge-small-en-v1.5',
1325
+ SPECMEM_EMBEDDING_BATCH_SIZE: '100',
1326
+ SPECMEM_AUTO_CONSOLIDATE: 'true',
1327
+ };
1328
+
1329
+ // Load existing config
1330
+ let config = {};
1331
+ if (fs.existsSync(configFile)) {
1332
+ try {
1333
+ config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
1334
+ } catch (e) {}
1335
+ }
1336
+
1337
+ // Helper to save config
1338
+ const saveConfig = () => {
1339
+ if (!fs.existsSync(configDir)) {
1340
+ fs.mkdirSync(configDir, { recursive: true });
1341
+ }
1342
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2), { mode: 0o644 });
1343
+ };
1344
+
1345
+ // Helper to get value (config > env > default)
1346
+ const getValue = (k) => {
1347
+ if (config[k] !== undefined) return config[k];
1348
+ if (process.env[k] !== undefined) return process.env[k];
1349
+ return defaults[k];
1350
+ };
1351
+
1352
+ switch (sub) {
1353
+ case 'list': {
1354
+ console.log(`${CYAN}═══ SpecMem Configuration ═══${RESET}\n`);
1355
+
1356
+ // Show source
1357
+ if (fs.existsSync(configFile)) {
1358
+ console.log(`${GREEN}Config file:${RESET} ${configFile}`);
1359
+ } else {
1360
+ console.log(`${YELLOW}Config file:${RESET} not found (using defaults)`);
1361
+ }
1362
+ console.log('');
1363
+
1364
+ // List all settings
1365
+ const allKeys = [...new Set([...Object.keys(defaults), ...Object.keys(config)])];
1366
+ for (const k of allKeys.sort()) {
1367
+ const val = getValue(k);
1368
+ const def = defaults[k];
1369
+ const isDefault = val === def;
1370
+ const isEnvOverride = process.env[k] !== undefined && config[k] === undefined;
1371
+
1372
+ let source = '';
1373
+ if (isEnvOverride) {
1374
+ source = ` ${DIM}(env override)${RESET}`;
1375
+ } else if (!isDefault) {
1376
+ source = ` ${CYAN}(custom)${RESET}`;
1377
+ } else {
1378
+ source = ` ${DIM}(default)${RESET}`;
1379
+ }
1380
+
1381
+ console.log(` ${k}: ${val}${source}`);
1382
+ }
1383
+ break;
1384
+ }
1385
+
1386
+ case 'get': {
1387
+ if (!key) {
1388
+ console.log(`${RED}Error: Please specify a key to get${RESET}`);
1389
+ console.log(`Usage: specmem config get <key>`);
1390
+ process.exit(1);
1391
+ }
1392
+
1393
+ const val = getValue(key);
1394
+ console.log(`${key}=${val}`);
1395
+ break;
1396
+ }
1397
+
1398
+ case 'set': {
1399
+ if (!key || value === undefined) {
1400
+ console.log(`${RED}Error: Please specify key and value${RESET}`);
1401
+ console.log(`Usage: specmem config set <key> <value>`);
1402
+ process.exit(1);
1403
+ }
1404
+
1405
+ config[key] = value;
1406
+ saveConfig();
1407
+ console.log(`${GREEN}Set ${key}=${value}${RESET}`);
1408
+ console.log(`${DIM}This will override environment variable ${key}${RESET}`);
1409
+ console.log(`${DIM}To apply, restart your Claude session or set the env var manually${RESET}`);
1410
+ break;
1411
+ }
1412
+
1413
+ case 'unset': {
1414
+ if (!key) {
1415
+ console.log(`${RED}Error: Please specify a key to unset${RESET}`);
1416
+ console.log(`Usage: specmem config unset <key>`);
1417
+ process.exit(1);
1418
+ }
1419
+
1420
+ if (config[key] !== undefined) {
1421
+ delete config[key];
1422
+ saveConfig();
1423
+ console.log(`${GREEN}Unset ${key}${RESET}`);
1424
+ console.log(`${DIM}Now using default: ${defaults[key] || '(none)'}${RESET}`);
1425
+ } else {
1426
+ console.log(`${YELLOW}${key} was not set in config (using default)${RESET}`);
1427
+ }
1428
+ break;
1429
+ }
1430
+
1431
+ case 'reset': {
1432
+ if (fs.existsSync(configFile)) {
1433
+ fs.unlinkSync(configFile);
1434
+ console.log(`${GREEN}Reset config to defaults${RESET}`);
1435
+ } else {
1436
+ console.log(`${YELLOW}No config file to reset${RESET}`);
1437
+ }
1438
+ break;
1439
+ }
1440
+
1441
+ case 'edit': {
1442
+ // Open config file in editor
1443
+ if (!fs.existsSync(configDir)) {
1444
+ fs.mkdirSync(configDir, { recursive: true });
1445
+ }
1446
+ if (!fs.existsSync(configFile)) {
1447
+ saveConfig();
1448
+ }
1449
+
1450
+ const editor = process.env.EDITOR || 'nano';
1451
+ try {
1452
+ execSync(`${editor} "${configFile}"`, { stdio: 'inherit' });
1453
+ console.log(`${GREEN}Config file saved${RESET}`);
1454
+ } catch (e) {
1455
+ console.log(`${RED}Failed to open editor: ${e.message}${RESET}`);
1456
+ console.log(`Edit manually: ${configFile}`);
1457
+ }
1458
+ break;
1459
+ }
1460
+
1461
+ default:
1462
+ console.log(`${BOLD}Usage:${RESET} specmem config <command>
1463
+
1464
+ ${BOLD}Commands:${RESET}
1465
+ ${CYAN}list${RESET} List all settings with current values
1466
+ ${CYAN}get <key>${RESET} Get value of specific setting
1467
+ ${CYAN}set <key> <val>${RESET} Set value (persists to config file)
1468
+ ${CYAN}unset <key>${RESET} Remove custom value (use default)
1469
+ ${CYAN}reset${RESET} Reset all config to defaults
1470
+ ${CYAN}edit${RESET} Open config file in editor
1471
+
1472
+ ${BOLD}Configurable Settings:${RESET}
1473
+ SPECMEM_SEARCH_LIMIT Memory search limit (default: 5)
1474
+ SPECMEM_THRESHOLD Similarity threshold (default: 0.30)
1475
+ SPECMEM_MAX_CONTENT Max content length (default: 200)
1476
+ SPECMEM_DB_HOST Database host
1477
+ SPECMEM_DB_PORT Database port
1478
+ SPECMEM_EMBEDDING_MODEL Embedding model name
1479
+ SPECMEM_EMBEDDING_BATCH_SIZE Batch size (default: 100)
1480
+ SPECMEM_AUTO_CONSOLIDATE Auto consolidation toggle
1481
+ `);
1482
+ }
1483
+ }
1484
+
1485
+ // ============================================================================
1486
+ // MINICOT COMMAND - Mini COT (Pythia) Server Management
1487
+ // ============================================================================
1488
+ function manageMinicot(args) {
1489
+ const sub = (args[0] || 'status').toLowerCase();
1490
+ const projectPath = process.cwd();
1491
+
1492
+ // Get project-specific paths - prefer sockets/, fallback to run/
1493
+ const specmemDir = path.join(projectPath, 'specmem');
1494
+ const socketDir = path.join(specmemDir, 'sockets');
1495
+ const runDir = path.join(specmemDir, 'run');
1496
+ const socketPath = path.join(socketDir, 'minicot.sock');
1497
+ const runSocketPath = path.join(runDir, 'minicot.sock');
1498
+ const pidFilePath = path.join(socketDir, 'minicot.pid');
1499
+ const runPidFilePath = path.join(runDir, 'minicot.pid');
1500
+ const stoppedFlag = path.join(socketDir, 'minicot.stopped');
1501
+ const runStoppedFlag = path.join(runDir, 'minicot.stopped');
1502
+
1503
+ // Helper to find the actual paths in use
1504
+ const getActivePaths = () => {
1505
+ if (fs.existsSync(pidFilePath) || fs.existsSync(socketPath)) {
1506
+ return { socketDir, socketPath, pidFilePath, stoppedFlag };
1507
+ }
1508
+ if (fs.existsSync(runPidFilePath) || fs.existsSync(runSocketPath)) {
1509
+ return { socketDir: runDir, socketPath: runSocketPath, pidFilePath: runPidFilePath, stoppedFlag: runStoppedFlag };
1510
+ }
1511
+ return { socketDir, socketPath, pidFilePath, stoppedFlag };
1512
+ };
1513
+
1514
+ // Helper to get minicot script path
1515
+ const getMinicotScript = () => {
1516
+ const candidates = [
1517
+ path.join(projectPath, 'mini-cot-service.py'),
1518
+ path.join(SPECMEM_PKG, 'mini-cot-service.py'),
1519
+ path.join(projectPath, 'node_modules', 'specmem-hardwicksoftware', 'mini-cot-service.py'),
1520
+ path.join(dirname(process.execPath), 'lib', 'node_modules', 'specmem-hardwicksoftware', 'mini-cot-service.py'),
1521
+ ];
1522
+ for (const c of candidates) {
1523
+ if (fs.existsSync(c)) return c;
1524
+ }
1525
+ return candidates[0]; // Return first candidate anyway for error message
1526
+ };
1527
+
1528
+ switch (sub) {
1529
+ case 'start': {
1530
+ // Ensure directories exist - prefer sockets/, create run/ as fallback
1531
+ if (!fs.existsSync(socketDir)) {
1532
+ fs.mkdirSync(socketDir, { recursive: true });
1533
+ }
1534
+
1535
+ // Check if already running
1536
+ const activePaths = getActivePaths();
1537
+ if (fs.existsSync(activePaths.pidFilePath)) {
1538
+ const pidData = fs.readFileSync(activePaths.pidFilePath, 'utf8').trim();
1539
+ const [pid] = pidData.split(':');
1540
+ try {
1541
+ process.kill(pid, 0);
1542
+ console.log(`${YELLOW}Mini COT server already running (PID: ${pid})${RESET}`);
1543
+ process.exit(0);
1544
+ } catch (e) {
1545
+ fs.unlinkSync(activePaths.pidFilePath);
1546
+ }
1547
+ }
1548
+
1549
+ // Start the minicot server
1550
+ console.log(`${CYAN}Starting Mini COT server...${RESET}`);
1551
+ const minicotScript = getMinicotScript();
1552
+
1553
+ if (!fs.existsSync(minicotScript)) {
1554
+ console.log(`${RED}Mini COT script not found: ${minicotScript}${RESET}`);
1555
+ process.exit(1);
1556
+ }
1557
+
1558
+ const env = {
1559
+ ...process.env,
1560
+ SPECMEM_PROJECT_PATH: projectPath,
1561
+ SPECMEM_MINICOT_SOCKET: socketPath,
1562
+ SPECMEM_MINICOT_MODEL: process.env.SPECMEM_MINICOT_MODEL || 'EleutherAI/pythia-410m',
1563
+ };
1564
+
1565
+ const child = spawn('python3', [minicotScript, '--socket', socketPath], {
1566
+ cwd: projectPath,
1567
+ env,
1568
+ detached: true,
1569
+ stdio: 'ignore',
1570
+ });
1571
+
1572
+ child.unref();
1573
+
1574
+ // Write PID file
1575
+ fs.writeFileSync(pidFilePath, `${child.pid}:${Date.now()}`, { mode: 0o644 });
1576
+
1577
+ console.log(`${GREEN}Mini COT server started (PID: ${child.pid})${RESET}`);
1578
+ console.log(`${DIM}Socket: ${socketPath}${RESET}`);
1579
+ console.log(`${DIM}Model: ${env.SPECMEM_MINICOT_MODEL}${RESET}`);
1580
+ break;
1581
+ }
1582
+
1583
+ case 'stop': {
1584
+ const activePaths = getActivePaths();
1585
+ if (!fs.existsSync(activePaths.pidFilePath)) {
1586
+ console.log(`${YELLOW}No PID file found - server may not be running${RESET}`);
1587
+ process.exit(1);
1588
+ }
1589
+
1590
+ const pidData = fs.readFileSync(activePaths.pidFilePath, 'utf8').trim();
1591
+ const [pid, timestamp] = pidData.split(':');
1592
+
1593
+ try {
1594
+ process.kill(parseInt(pid), 'SIGTERM');
1595
+ console.log(`${GREEN}Sent SIGTERM to Mini COT server (PID: ${pid})${RESET}`);
1596
+
1597
+ setTimeout(() => {
1598
+ try {
1599
+ process.kill(parseInt(pid), 0);
1600
+ process.kill(parseInt(pid), 'SIGKILL');
1601
+ console.log(`${YELLOW}Force killed server${RESET}`);
1602
+ } catch (e) {}
1603
+ }, 3000);
1604
+
1605
+ fs.unlinkSync(activePaths.pidFilePath);
1606
+ console.log(`${GREEN}Mini COT server stopped${RESET}`);
1607
+ } catch (e) {
1608
+ console.log(`${RED}Failed to stop server: ${e.message}${RESET}`);
1609
+ fs.unlinkSync(activePaths.pidFilePath);
1610
+ process.exit(1);
1611
+ }
1612
+ break;
1613
+ }
1614
+
1615
+ case 'restart': {
1616
+ const activePaths = getActivePaths();
1617
+ if (fs.existsSync(activePaths.pidFilePath)) {
1618
+ const pidData = fs.readFileSync(activePaths.pidFilePath, 'utf8').trim();
1619
+ const [pid] = pidData.split(':');
1620
+ try {
1621
+ process.kill(parseInt(pid), 'SIGTERM');
1622
+ } catch (e) {}
1623
+ fs.unlinkSync(activePaths.pidFilePath);
1624
+ }
1625
+
1626
+ setTimeout(() => {
1627
+ manageMinicot(['start']);
1628
+ }, 1000);
1629
+ break;
1630
+ }
1631
+
1632
+ case 'force-restart': {
1633
+ let activePaths;
1634
+ try {
1635
+ activePaths = getActivePaths();
1636
+ } catch (e) {
1637
+ activePaths = { socketDir, socketPath, pidFilePath, stoppedFlag };
1638
+ }
1639
+
1640
+ if (fs.existsSync(pidFilePath)) {
1641
+ const pidData = fs.readFileSync(pidFilePath, 'utf8').trim();
1642
+ const [pid] = pidData.split(':');
1643
+ try {
1644
+ process.kill(parseInt(pid), 'SIGKILL');
1645
+ } catch (e) {}
1646
+ fs.unlinkSync(pidFilePath);
1647
+ }
1648
+ if (fs.existsSync(runPidFilePath)) {
1649
+ const pidData = fs.readFileSync(runPidFilePath, 'utf8').trim();
1650
+ const [pid] = pidData.split(':');
1651
+ try {
1652
+ process.kill(parseInt(pid), 'SIGKILL');
1653
+ } catch (e) {}
1654
+ fs.unlinkSync(runPidFilePath);
1655
+ }
1656
+
1657
+ // Also try to kill any process using the socket
1658
+ try {
1659
+ execSync(`lsof -t ${socketPath} 2>/dev/null | xargs -r kill -9 2>/dev/null || true`, { stdio: 'pipe' });
1660
+ execSync(`lsof -t ${runSocketPath} 2>/dev/null | xargs -r kill -9 2>/dev/null || true`, { stdio: 'pipe' });
1661
+ } catch (e) {}
1662
+
1663
+ setTimeout(() => {
1664
+ manageMinicot(['start']);
1665
+ }, 500);
1666
+ break;
1667
+ }
1668
+
1669
+ case 'status': {
1670
+ console.log(`${CYAN}═══ Mini COT Server Status ═══${RESET}\n`);
1671
+
1672
+ const activePaths = getActivePaths();
1673
+
1674
+ if (!fs.existsSync(socketDir) && !fs.existsSync(runDir)) {
1675
+ console.log(`${YELLOW}Project not initialized - no specmem directory${RESET}`);
1676
+ console.log(`${DIM}Run 'specmem init' or 'specmem quickstart' first${RESET}`);
1677
+ process.exit(1);
1678
+ }
1679
+
1680
+ // Check PID file
1681
+ if (fs.existsSync(activePaths.pidFilePath)) {
1682
+ const pidData = fs.readFileSync(activePaths.pidFilePath, 'utf8').trim();
1683
+ const [pid, timestamp] = pidData.split(':');
1684
+ console.log(` PID File: ${GREEN}exists${RESET} (PID: ${pid})`);
1685
+ console.log(` Started: ${new Date(parseInt(timestamp)).toLocaleString()}`);
1686
+
1687
+ try {
1688
+ process.kill(parseInt(pid), 0);
1689
+ console.log(` Process: ${GREEN}running${RESET}`);
1690
+ } catch (e) {
1691
+ console.log(` Process: ${RED}dead (stale PID file)${RESET}`);
1692
+ }
1693
+ } else {
1694
+ console.log(` PID File: ${YELLOW}not found${RESET}`);
1695
+ }
1696
+
1697
+ // Check socket
1698
+ console.log(`\n Socket: ${activePaths.socketPath}`);
1699
+ if (fs.existsSync(activePaths.socketPath)) {
1700
+ console.log(` ${GREEN}exists${RESET}`);
1701
+ } else {
1702
+ console.log(` ${YELLOW}not found${RESET}`);
1703
+ }
1704
+
1705
+ // Check stopped flag
1706
+ if (fs.existsSync(stoppedFlag) || fs.existsSync(runStoppedFlag)) {
1707
+ console.log(`\n ${YELLOW}Server was manually stopped by user${RESET}`);
1708
+ console.log(` ${DIM}Use 'specmem minicot start' to restart${RESET}`);
1709
+ }
1710
+
1711
+ // Show model info
1712
+ console.log(`\n Model: ${process.env.SPECMEM_MINICOT_MODEL || 'EleutherAI/pythia-410m'}${RESET}`);
1713
+ break;
1714
+ }
1715
+
1716
+ case 'list': {
1717
+ console.log(`${CYAN}═══ All Mini COT Servers ═══${RESET}\n`);
1718
+
1719
+ const searchPaths = [
1720
+ path.join(os.homedir(), '.specmem'),
1721
+ ];
1722
+
1723
+ // Add all specmem dirs we can find
1724
+ try {
1725
+ const projDirs = execSync(`find ${os.homedir()} -maxdepth 3 -type d -name "specmem" 2>/dev/null | head -20`, { encoding: 'utf8' })
1726
+ .trim()
1727
+ .split('\n')
1728
+ .filter(Boolean);
1729
+ for (const d of projDirs) {
1730
+ if (!searchPaths.includes(d)) searchPaths.push(d);
1731
+ }
1732
+ } catch (e) {}
1733
+
1734
+ let found = false;
1735
+ for (const sp of searchPaths) {
1736
+ if (!fs.existsSync(sp)) continue;
1737
+
1738
+ for (const subdir of ['sockets', 'run']) {
1739
+ const dir = path.join(sp, subdir);
1740
+ if (!fs.existsSync(dir)) continue;
1741
+
1742
+ const pf = path.join(dir, 'minicot.pid');
1743
+ if (!fs.existsSync(pf)) continue;
1744
+
1745
+ const pidData = fs.readFileSync(pf, 'utf8').trim();
1746
+ const [pid, timestamp] = pidData.split(':');
1747
+ const projName = path.basename(path.dirname(path.dirname(dir)));
1748
+
1749
+ let status = `${RED}dead${RESET}`;
1750
+ try {
1751
+ process.kill(parseInt(pid), 0);
1752
+ status = `${GREEN}running${RESET}`;
1753
+ } catch (e) {}
1754
+
1755
+ console.log(` Project: ${projName}`);
1756
+ console.log(` PID: ${pid} (${status})`);
1757
+ console.log(` Socket: ${path.join(dir, 'minicot.sock')}`);
1758
+ console.log(` Started: ${new Date(parseInt(timestamp)).toLocaleString()}`);
1759
+ console.log('');
1760
+ found = true;
1761
+ }
1762
+ }
1763
+
1764
+ if (!found) {
1765
+ console.log(` ${DIM}No Mini COT servers found${RESET}`);
1766
+ }
1767
+ break;
1768
+ }
1769
+
1770
+ case 'diagnose': {
1771
+ console.log(`${CYAN}═══ Mini COT Server Diagnostics ═══${RESET}\n`);
1772
+
1773
+ const activePaths = getActivePaths();
1774
+
1775
+ console.log(`Project: ${projectPath}`);
1776
+ console.log(`SpecMem Dir: ${specmemDir}`);
1777
+ console.log(`Socket Dir: ${activePaths.socketDir}`);
1778
+ console.log(`Socket Path: ${activePaths.socketPath}`);
1779
+ console.log(`PID File: ${activePaths.pidFilePath}`);
1780
+
1781
+ // Check if directories exist
1782
+ console.log(`\n${CYAN}Directory Status:${RESET}`);
1783
+ console.log(` specmem/ exists: ${fs.existsSync(specmemDir) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1784
+ console.log(` sockets/ exists: ${fs.existsSync(socketDir) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1785
+ console.log(` run/ exists: ${fs.existsSync(runDir) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1786
+ console.log(` socket exists: ${fs.existsSync(activePaths.socketPath) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1787
+ console.log(` PID file exists: ${fs.existsSync(activePaths.pidFilePath) ? GREEN + 'yes' + RESET : YELLOW + 'no' + RESET}`);
1788
+
1789
+ // Check process
1790
+ if (fs.existsSync(activePaths.pidFilePath)) {
1791
+ const pidData = fs.readFileSync(activePaths.pidFilePath, 'utf8').trim();
1792
+ const [pid] = pidData.split(':');
1793
+
1794
+ console.log(`\n${CYAN}Process Info:${RESET}`);
1795
+ console.log(` PID: ${pid}`);
1796
+
1797
+ try {
1798
+ const procPath = `/proc/${pid}`;
1799
+ if (fs.existsSync(procPath)) {
1800
+ const cmdline = fs.readFileSync(path.join(procPath, 'cmdline'), 'utf8').replace(/\0/g, ' ');
1801
+ console.log(` Command: ${cmdline.slice(0, 100)}`);
1802
+
1803
+ try {
1804
+ const envPath = path.join(procPath, 'environ');
1805
+ const envData = fs.readFileSync(envPath);
1806
+ const envStr = envData.toString('utf8');
1807
+ const minicotEnv = envStr.split('\0').find(e => e.startsWith('SPECMEM_MINICOT'));
1808
+ if (minicotEnv) {
1809
+ console.log(` Env: ${minicotEnv.slice(0, 80)}`);
1810
+ }
1811
+ } catch (e) {}
1812
+ } else {
1813
+ console.log(` ${RED}Process does not exist (stale PID)${RESET}`);
1814
+ }
1815
+ } catch (e) {
1816
+ console.log(` ${RED}Error reading process: ${e.message}${RESET}`);
1817
+ }
1818
+ }
1819
+
1820
+ // Check for running minicot processes
1821
+ console.log(`\n${CYAN}Running Python Processes:${RESET}`);
1822
+ try {
1823
+ const procs = execSync(`pgrep -af "mini-cot-service.py" 2>/dev/null || echo ""`, { encoding: 'utf8' }).trim();
1824
+ if (procs) {
1825
+ console.log(procs.split('\n').map(l => ` ${l}`).join('\n'));
1826
+ } else {
1827
+ console.log(` ${DIM}None found${RESET}`);
1828
+ }
1829
+ } catch (e) {
1830
+ console.log(` ${DIM}Could not search processes${RESET}`);
1831
+ }
1832
+ break;
1833
+ }
1834
+
1835
+ case 'cleanup': {
1836
+ console.log(`${CYAN}Cleaning up stale Mini COT server files...${RESET}`);
1837
+
1838
+ let cleaned = 0;
1839
+
1840
+ const searchPaths = [
1841
+ path.join(os.homedir(), '.specmem'),
1842
+ ];
1843
+
1844
+ // Find all specmem dirs
1845
+ try {
1846
+ const projDirs = execSync(`find ${os.homedir()} -maxdepth 3 -type d -name "specmem" 2>/dev/null | head -20`, { encoding: 'utf8' })
1847
+ .trim()
1848
+ .split('\n')
1849
+ .filter(Boolean);
1850
+ for (const d of projDirs) {
1851
+ if (!searchPaths.includes(d)) searchPaths.push(d);
1852
+ }
1853
+ } catch (e) {}
1854
+
1855
+ for (const sp of searchPaths) {
1856
+ if (!fs.existsSync(sp)) continue;
1857
+
1858
+ for (const subdir of ['sockets', 'run']) {
1859
+ const dir = path.join(sp, subdir);
1860
+ if (!fs.existsSync(dir)) continue;
1861
+
1862
+ const pf = path.join(dir, 'minicot.pid');
1863
+ if (!fs.existsSync(pf)) continue;
1864
+
1865
+ try {
1866
+ const pidData = fs.readFileSync(pf, 'utf8').trim();
1867
+ const [pid] = pidData.split(':');
1868
+ try {
1869
+ process.kill(parseInt(pid), 0);
1870
+ } catch (e) {
1871
+ fs.unlinkSync(pf);
1872
+ console.log(` Removed stale PID file: ${pf}`);
1873
+ cleaned++;
1874
+ }
1875
+ } catch (e) {}
1876
+ }
1877
+ }
1878
+
1879
+ console.log(`${GREEN}Cleaned ${cleaned} stale PID files${RESET}`);
1880
+ break;
1881
+ }
1882
+
1883
+ default:
1884
+ console.log(`${BOLD}Usage:${RESET} specmem minicot <command>
1885
+
1886
+ ${BOLD}Commands:${RESET}
1887
+ ${CYAN}start${RESET} Start Mini COT server (Pythia-410M)
1888
+ ${CYAN}stop${RESET} Stop Mini COT server
1889
+ ${CYAN}restart${RESET} Restart Mini COT server
1890
+ ${CYAN}force-restart${RESET} Force restart (kill and start fresh)
1891
+ ${CYAN}status${RESET} Show Mini COT server status
1892
+ ${CYAN}list${RESET} List all Mini COT servers
1893
+ ${CYAN}diagnose${RESET} Full diagnostic info
1894
+ ${CYAN}cleanup${RESET} Clean up stale PID files
1895
+ `);
1896
+ }
1897
+ }
1898
+
1899
+ // ============================================================================
1900
+ // QUICKSTART - Minimal init without database
1901
+ // ============================================================================
1902
+ function runQuickstart() {
1903
+ showBanner();
1904
+ console.log(`${CYAN}${BOLD}═══ SpecMem Quickstart ═══${RESET}\n`);
1905
+ console.log(`${DIM}Setting up minimal SpecMem configuration...${RESET}\n`);
1906
+
1907
+ const projectPath = process.cwd();
1908
+ const specmemDir = path.join(projectPath, 'specmem');
1909
+ const configDir = path.join(os.homedir(), '.specmem');
1910
+
1911
+ // Create project directories
1912
+ console.log(`${CYAN}→${RESET} Creating project directories...`);
1913
+ const dirs = ['run', 'sockets', 'logs', 'data'];
1914
+ for (const dir of dirs) {
1915
+ const dirPath = path.join(specmemDir, dir);
1916
+ if (!fs.existsSync(dirPath)) {
1917
+ fs.mkdirSync(dirPath, { recursive: true });
1918
+ }
1919
+ }
1920
+ console.log(`${GREEN}✓${RESET} Created ${specmemDir}/*`);
1921
+
1922
+ // Create global config if needed
1923
+ console.log(`${CYAN}→${RESET} Setting up global config...`);
1924
+ const configFile = path.join(configDir, 'config.json');
1925
+ if (!fs.existsSync(configFile)) {
1926
+ if (!fs.existsSync(configDir)) {
1927
+ fs.mkdirSync(configDir, { recursive: true });
1928
+ }
1929
+ const defaultConfig = {
1930
+ SPECMEM_SEARCH_LIMIT: '5',
1931
+ SPECMEM_THRESHOLD: '0.30',
1932
+ SPECMEM_MAX_CONTENT: '200',
1933
+ SPECMEM_AUTO_CONSOLIDATE: 'true',
1934
+ };
1935
+ fs.writeFileSync(configFile, JSON.stringify(defaultConfig, null, 2), { mode: 0o644 });
1936
+ console.log(`${GREEN}✓${RESET} Created default config`);
1937
+ } else {
1938
+ console.log(`${DIM} Config already exists${RESET}`);
1939
+ }
1940
+
1941
+ // Create .env file if needed
1942
+ const envFile = path.join(projectPath, '.env');
1943
+ if (!fs.existsSync(envFile)) {
1944
+ const envContent = `# SpecMem Configuration
1945
+ # Database (optional - will use defaults if not set)
1946
+ # SPECMEM_DB_HOST=localhost
1947
+ # SPECMEM_DB_PORT=5432
1948
+ # SPECMEM_DB_NAME=specmem
1949
+
1950
+ # Embedding settings
1951
+ # SPECMEM_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5
1952
+
1953
+ # Memory settings
1954
+ # SPECMEM_SEARCH_LIMIT=5
1955
+ # SPECMEM_THRESHOLD=0.30
1956
+ `;
1957
+ fs.writeFileSync(envFile, envContent, { mode: 0o644 });
1958
+ console.log(`${GREEN}✓${RESET} Created .env template`);
1959
+ } else {
1960
+ console.log(`${DIM} .env already exists${RESET}`);
1961
+ }
1962
+
1963
+ console.log(`
1964
+ ${GREEN}${BOLD}✓ Quickstart complete!${RESET}
1965
+
1966
+ ${CYAN}Next steps:${RESET}
1967
+ • Run ${CYAN}specmem embed start${RESET} to start embedding server
1968
+ • Or run ${CYAN}claude${RESET} - SpecMem will auto-start services
1969
+
1970
+ ${YELLOW}Note:${RESET} Database not configured. Run ${CYAN}specmem init${RESET} for full setup.
1971
+ `);
1972
+ }
1973
+
912
1974
  // Show help
913
1975
  function showHelp() {
914
1976
  showBanner();
915
1977
  console.log(`${BOLD}Usage:${RESET} specmem <command>
916
1978
 
917
1979
  ${BOLD}Commands:${RESET}
918
- ${CYAN}setup${RESET} First-run setup (download + optimize embedding models)
919
- ${CYAN}init${RESET} Initialize SpecMem for current project
920
- ${CYAN}status${RESET} Check SpecMem status and model info
921
- ${CYAN}proxy${RESET} Manage compaction proxy (enable/disable/status)
1980
+ ${CYAN}setup${RESET} First-run setup (download + optimize embedding models)
1981
+ ${CYAN}init${RESET} Initialize SpecMem for current project
1982
+ ${CYAN}quickstart${RESET} Quick setup without database
1983
+ ${CYAN}status${RESET} Check SpecMem status and model info
1984
+ ${CYAN}proxy${RESET} Manage compaction proxy (enable/disable/status)
1985
+ ${CYAN}embed${RESET} Manage embedding server (start/stop/status)
1986
+ ${CYAN}minicot${RESET} Manage Mini COT server (Pythia-410M)
1987
+ ${CYAN}config${RESET} Manage configuration settings
922
1988
  ${CYAN}dashboard${RESET} Launch the AEGIS dashboard (alias: dash)
923
- ${CYAN}doctor${RESET} Check system requirements
1989
+ ${CYAN}doctor${RESET} Check system requirements
924
1990
 
925
1991
  ${BOLD}Quick Start:${RESET}
926
- ${DIM}$${RESET} specmem init ${DIM}# First time only - optimizes models${RESET}
1992
+ ${DIM}$${RESET} specmem init ${DIM}# First time only - optimizes models${RESET}
927
1993
  ${DIM}$${RESET} cd your-project
928
- ${DIM}$${RESET} specmem init ${DIM}# Initialize project${RESET}
929
- ${DIM}$${RESET} claude ${DIM}# Services auto-start with !${RESET}
1994
+ ${DIM}$${RESET} specmem init ${DIM}# Initialize project${RESET}
1995
+ ${DIM}$${RESET} claude ${DIM}# Services auto-start with !${RESET}
930
1996
 
931
1997
  ${BOLD}What It Does:${RESET}
932
1998
  SpecMem provides semantic memory for Code sessions.