monomind 2.1.9 → 2.3.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.
Files changed (93) hide show
  1. package/package.json +2 -2
  2. package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +24 -16
  3. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +23 -7
  4. package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -1
  5. package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +6 -3
  6. package/packages/@monomind/cli/.claude/skills/mastermind-skills/code-quality-reviewer-prompt.md +3 -3
  7. package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +19 -24
  8. package/packages/@monomind/cli/.claude/skills/mastermind-skills/debug.md +49 -4
  9. package/packages/@monomind/cli/.claude/skills/mastermind-skills/design.md +11 -10
  10. package/packages/@monomind/cli/.claude/skills/mastermind-skills/final-reviewer-prompt.md +144 -0
  11. package/packages/@monomind/cli/.claude/skills/mastermind-skills/implementer-prompt.md +9 -4
  12. package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +20 -2
  13. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +40 -19
  14. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +90 -41
  15. package/packages/@monomind/cli/.claude/skills/mastermind-skills/plan.md +19 -0
  16. package/packages/@monomind/cli/.claude/skills/mastermind-skills/skill-builder.md +171 -0
  17. package/packages/@monomind/cli/.claude/skills/mastermind-skills/spec-reviewer-prompt.md +19 -2
  18. package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +7 -5
  19. package/packages/@monomind/cli/.claude/skills/mastermind-skills/taskdev.md +83 -15
  20. package/packages/@monomind/cli/.claude/skills/mastermind-skills/tdd.md +30 -0
  21. package/packages/@monomind/cli/dist/src/autopilot-state.js +6 -4
  22. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +4 -1
  23. package/packages/@monomind/cli/dist/src/capabilities/manager.js +3 -1
  24. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +26 -0
  25. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +28 -46
  26. package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +18 -0
  27. package/packages/@monomind/cli/dist/src/commands/cleanup.js +120 -0
  28. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +13 -4
  29. package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +34 -18
  30. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -2
  31. package/packages/@monomind/cli/dist/src/commands/hooks.js +1 -1
  32. package/packages/@monomind/cli/dist/src/commands/init-wizard.js +2 -2
  33. package/packages/@monomind/cli/dist/src/commands/init.js +54 -10
  34. package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
  35. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
  36. package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
  37. package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
  38. package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
  39. package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
  40. package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
  41. package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
  42. package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
  43. package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
  44. package/packages/@monomind/cli/dist/src/commands/swarm.js +5 -4
  45. package/packages/@monomind/cli/dist/src/index.js +13 -3
  46. package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
  47. package/packages/@monomind/cli/dist/src/init/settings-generator.js +8 -1
  48. package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
  49. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
  50. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
  51. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
  52. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
  53. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
  54. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
  55. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
  56. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
  57. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
  58. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
  59. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
  60. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
  61. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
  62. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
  63. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
  64. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
  65. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
  66. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
  67. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
  68. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
  69. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
  70. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +68 -24
  71. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
  72. package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
  73. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +16 -4
  74. package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
  75. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
  76. package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
  77. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
  78. package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
  79. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
  80. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
  81. package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
  82. package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
  83. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
  84. package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
  85. package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
  86. package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
  87. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
  88. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
  89. package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
  90. package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
  91. package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
  92. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
  93. package/packages/@monomind/cli/package.json +2 -2
@@ -97,8 +97,10 @@ export const hooksPostEdit = {
97
97
  confidence: success ? 0.85 : 0.3,
98
98
  });
99
99
  }
100
- catch {
100
+ catch (e) {
101
101
  // Bridge not available — continue with basic response
102
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
103
+ console.error('[hooks-post-edit] memory bridge feedback failed:', e);
102
104
  }
103
105
  return {
104
106
  recorded: true,
@@ -738,8 +740,10 @@ export const hooksPostTask = {
738
740
  metadata: { taskId, duration: params.duration || undefined, patterns: params.patterns || undefined },
739
741
  });
740
742
  }
741
- catch {
743
+ catch (e) {
742
744
  // Bridge not available — continue with basic response
745
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
746
+ console.error('[hooks-post-task] memory bridge feedback failed:', e);
743
747
  }
744
748
  // Phase 3: Record causal edge (task → outcome)
745
749
  try {
@@ -751,8 +755,10 @@ export const hooksPostTask = {
751
755
  strength: quality,
752
756
  });
753
757
  }
754
- catch {
758
+ catch (e) {
755
759
  // Non-fatal
760
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
761
+ console.error('[hooks-post-task] causal edge record failed:', e);
756
762
  }
757
763
  // Persist routing outcome for runtime learning (file-based, always reliable).
758
764
  // B1.3: also gate this sibling learning sink on a known outcome — an unverified
@@ -938,8 +944,10 @@ export const hooksExplain = {
938
944
  }
939
945
  }
940
946
  }
941
- catch {
942
- // File unreadable; leave as null
947
+ catch (e) {
948
+ // File unreadable or corrupt; leave as null
949
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
950
+ console.error('[hooks-explain] routing outcomes file read/parse failed:', e);
943
951
  }
944
952
  return {
945
953
  task,
@@ -1059,7 +1067,11 @@ export const hooksPretrain = {
1059
1067
  });
1060
1068
  patternsStored = patterns.length;
1061
1069
  }
1062
- catch { /* memory backend unavailable */ }
1070
+ catch (e) {
1071
+ /* memory backend unavailable */
1072
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1073
+ console.error('[hooks-pretrain] pattern store failed:', e);
1074
+ }
1063
1075
  // Feed extracted import patterns into the neural training system so
1064
1076
  // pretrain actually trains, not just scans.
1065
1077
  let neuralPatternsLearned = 0;
@@ -1077,7 +1089,11 @@ export const hooksPretrain = {
1077
1089
  intel.flushPatterns();
1078
1090
  neuralPatternsLearned = steps.length;
1079
1091
  }
1080
- catch { /* intelligence not available */ }
1092
+ catch (e) {
1093
+ /* intelligence not available */
1094
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1095
+ console.error('[hooks-pretrain] intelligence training failed:', e);
1096
+ }
1081
1097
  }
1082
1098
  return {
1083
1099
  success: true,
@@ -1215,8 +1231,10 @@ export const hooksTransfer = {
1215
1231
  sourceStore = JSON.parse(readFileSync(sourceMemoryPath, 'utf-8'));
1216
1232
  }
1217
1233
  }
1218
- catch {
1234
+ catch (e) {
1219
1235
  // Fall back to empty store
1236
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1237
+ console.error('[hooks-transfer] source memory store read/parse failed:', e);
1220
1238
  }
1221
1239
  const sourceEntries = Object.values(sourceStore.entries);
1222
1240
  // Count patterns by type from source
@@ -1282,8 +1300,10 @@ export const hooksSessionStart = {
1282
1300
  };
1283
1301
  }
1284
1302
  }
1285
- catch {
1303
+ catch (e) {
1286
1304
  // Bridge not available
1305
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1306
+ console.error('[hooks-session-start] memory bridge failed:', e);
1287
1307
  }
1288
1308
  return {
1289
1309
  sessionId,
@@ -1303,6 +1323,86 @@ export const hooksSessionStart = {
1303
1323
  };
1304
1324
  },
1305
1325
  };
1326
+ // Session restore hook — hooks.ts's `hooks session-restore` (and its
1327
+ // `session-start` alias) called this tool name, but it was never registered
1328
+ // anywhere in the tool registry, so both always failed. This repo has no
1329
+ // per-session snapshot of agents/tasks to restore from, so "restore" here
1330
+ // means: report the currently-live (non-terminated/non-terminal) agents and
1331
+ // tasks as "carried forward", and reinitialize the memory-bridge session
1332
+ // context the same way hooks_session-start does — an honest, working
1333
+ // implementation rather than a fabricated one.
1334
+ export const hooksSessionRestore = {
1335
+ name: 'hooks_session-restore',
1336
+ description: 'Restore a previous session — reports currently-live agents/tasks and reinitializes memory-bridge session context',
1337
+ inputSchema: {
1338
+ type: 'object',
1339
+ properties: {
1340
+ sessionId: { type: 'string', description: 'Session ID to restore, or "latest"' },
1341
+ restoreAgents: { type: 'boolean', description: 'Include a count of currently-live agents (default true)' },
1342
+ restoreTasks: { type: 'boolean', description: 'Include a count of currently-live tasks (default true)' },
1343
+ },
1344
+ },
1345
+ handler: async (params) => {
1346
+ const originalSessionId = params.sessionId || 'latest';
1347
+ const newSessionId = `session-${Date.now()}`;
1348
+ const warnings = [];
1349
+ let agentsRestored = 0;
1350
+ if (params.restoreAgents !== false) {
1351
+ try {
1352
+ const { loadAgentStore } = await import('./agent-tools.js');
1353
+ const store = loadAgentStore();
1354
+ agentsRestored = Object.values(store.agents).filter((a) => a.status !== 'terminated').length;
1355
+ }
1356
+ catch (e) {
1357
+ warnings.push('Agent store unavailable — agent count not restored');
1358
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1359
+ console.error('[hooks-session-restore] agent store read failed:', e);
1360
+ }
1361
+ }
1362
+ let tasksRestored = 0;
1363
+ if (params.restoreTasks !== false) {
1364
+ try {
1365
+ const { loadTaskStore } = await import('./task-tools.js');
1366
+ const store = loadTaskStore();
1367
+ tasksRestored = Object.values(store.tasks).filter((t) => t.status === 'pending' || t.status === 'in_progress').length;
1368
+ }
1369
+ catch (e) {
1370
+ warnings.push('Task store unavailable — task count not restored');
1371
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1372
+ console.error('[hooks-session-restore] task store read failed:', e);
1373
+ }
1374
+ }
1375
+ // bridgeSessionStart only stores a new "session active" marker entry —
1376
+ // it has no pattern-restoration data to count, so there is no real
1377
+ // "memoryRestored" number to report. Track whether the bridge itself
1378
+ // came up instead of faking a count.
1379
+ let memoryBridgeInitialized = false;
1380
+ try {
1381
+ const bridge = await import('../memory/memory-bridge.js');
1382
+ const result = await bridge.bridgeSessionStart({
1383
+ sessionId: newSessionId,
1384
+ metadata: { context: 'restore previous session patterns' },
1385
+ });
1386
+ if (result) {
1387
+ memoryBridgeInitialized = result.success;
1388
+ }
1389
+ else {
1390
+ warnings.push('Memory bridge unavailable — pattern restoration skipped');
1391
+ }
1392
+ }
1393
+ catch (e) {
1394
+ warnings.push('Memory bridge failed to initialize');
1395
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1396
+ console.error('[hooks-session-restore] memory bridge failed:', e);
1397
+ }
1398
+ return {
1399
+ sessionId: newSessionId,
1400
+ originalSessionId,
1401
+ restoredState: { tasksRestored, agentsRestored, memoryBridgeInitialized },
1402
+ warnings: warnings.length > 0 ? warnings : undefined,
1403
+ };
1404
+ },
1405
+ };
1306
1406
  // Session end hook - persists state
1307
1407
  export const hooksSessionEnd = {
1308
1408
  name: 'hooks_session-end',
@@ -1356,8 +1456,10 @@ export const hooksSessionEnd = {
1356
1456
  };
1357
1457
  }
1358
1458
  }
1359
- catch {
1459
+ catch (e) {
1360
1460
  // Bridge not available
1461
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
1462
+ console.error('[hooks-session-end] memory bridge failed:', e);
1361
1463
  }
1362
1464
  return {
1363
1465
  sessionId,
@@ -3,7 +3,7 @@
3
3
  * Thin registration module that aggregates all hooks MCP tools into a single array.
4
4
  * Business logic lives in hooks-embedding.ts, hooks-routing.ts, and hooks-intelligence.ts.
5
5
  */
6
- import { hooksPreEdit, hooksPostEdit, hooksPreCommand, hooksPostCommand, hooksRoute, hooksRouteSemantic, hooksMetrics, hooksList, hooksPreTask, hooksPostTask, hooksExplain, hooksPretrain, hooksBuildAgents, hooksTransfer, hooksSessionStart, hooksSessionEnd, hooksIntelligence, } from './hooks-routing.js';
6
+ import { hooksPreEdit, hooksPostEdit, hooksPreCommand, hooksPostCommand, hooksRoute, hooksRouteSemantic, hooksMetrics, hooksList, hooksPreTask, hooksPostTask, hooksExplain, hooksPretrain, hooksBuildAgents, hooksTransfer, hooksSessionStart, hooksSessionRestore, hooksSessionEnd, hooksIntelligence, } from './hooks-routing.js';
7
7
  import { hooksIntelligenceReset, hooksTrajectoryStart, hooksTrajectoryStep, hooksTrajectoryEnd, hooksPatternStore, hooksPatternSearch, hooksIntelligenceStats, hooksIntelligenceLearn, hooksIntelligenceAttention, hooksModelRoute, hooksModelOutcome, hooksModelStats, } from './hooks-intelligence.js';
8
8
  // Export all hooks tools
9
9
  export const hooksTools = [
@@ -23,6 +23,7 @@ export const hooksTools = [
23
23
  hooksBuildAgents,
24
24
  hooksTransfer,
25
25
  hooksSessionStart,
26
+ hooksSessionRestore,
26
27
  hooksSessionEnd,
27
28
  hooksIntelligence,
28
29
  hooksIntelligenceReset,
@@ -639,7 +639,10 @@ const monographWatchTool = {
639
639
  }
640
640
  const watcher = new MonographWatcher(repoPath);
641
641
  watcher.on('monograph:updated', (_paths) => {
642
- import('@monoes/monograph').then(({ buildAsync }) => buildAsync(repoPath)).catch(() => { });
642
+ import('@monoes/monograph').then(({ buildAsync }) => buildAsync(repoPath)).catch((e) => {
643
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
644
+ console.error('[monograph_watch] background rebuild failed:', e);
645
+ });
643
646
  });
644
647
  await watcher.start();
645
648
  _activeWatchers.set(repoPath, watcher);
@@ -776,7 +779,10 @@ function triggerBackgroundBuildIfNeeded(repoPath, commitsBehind, threshold = STA
776
779
  _buildInProgress = true;
777
780
  void import('@monoes/monograph')
778
781
  .then(({ buildAsync }) => buildAsync(repoPath, { codeOnly: true }))
779
- .catch(() => { })
782
+ .catch((e) => {
783
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
784
+ console.error('[triggerBackgroundBuildIfNeeded] staleness-triggered rebuild failed:', e);
785
+ })
780
786
  .finally(() => { _buildInProgress = false; });
781
787
  return true;
782
788
  }
@@ -40,8 +40,9 @@ function loadPerfStore() {
40
40
  return JSON.parse(readFileSync(path, 'utf-8'));
41
41
  }
42
42
  }
43
- catch {
44
- // Return empty store
43
+ catch (e) {
44
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
45
+ console.error('[performance-tools] failed to load perf store, using empty store:', e);
45
46
  }
46
47
  return { metrics: [], benchmarks: {}, version: '3.0.0' };
47
48
  }
@@ -34,8 +34,9 @@ function loadSession(sessionId) {
34
34
  return JSON.parse(data);
35
35
  }
36
36
  }
37
- catch {
38
- // Return null on error
37
+ catch (e) {
38
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
39
+ console.error('[session-tools] loadSession failed:', e);
39
40
  }
40
41
  return null;
41
42
  }
@@ -90,7 +91,10 @@ function loadRelatedStores(options) {
90
91
  data.memory = JSON.parse(readFileSync(memoryPath, 'utf-8'));
91
92
  }
92
93
  }
93
- catch { /* ignore */ }
94
+ catch (e) {
95
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
96
+ console.error('[session-tools] failed to load memory store:', e);
97
+ }
94
98
  }
95
99
  if (options.includeTasks) {
96
100
  try {
@@ -99,7 +103,10 @@ function loadRelatedStores(options) {
99
103
  data.tasks = JSON.parse(readFileSync(taskPath, 'utf-8'));
100
104
  }
101
105
  }
102
- catch { /* ignore */ }
106
+ catch (e) {
107
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
108
+ console.error('[session-tools] failed to load task store:', e);
109
+ }
103
110
  }
104
111
  if (options.includeAgents) {
105
112
  try {
@@ -108,7 +115,10 @@ function loadRelatedStores(options) {
108
115
  data.agents = JSON.parse(readFileSync(agentPath, 'utf-8'));
109
116
  }
110
117
  }
111
- catch { /* ignore */ }
118
+ catch (e) {
119
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
120
+ console.error('[session-tools] failed to load agent store:', e);
121
+ }
112
122
  }
113
123
  return data;
114
124
  }
@@ -36,7 +36,10 @@ function loadSwarmStore() {
36
36
  return JSON.parse(readFileSync(path, 'utf-8'));
37
37
  }
38
38
  }
39
- catch { /* return default */ }
39
+ catch (e) {
40
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
41
+ console.error('[swarm-tools] failed to parse swarm-state.json — resetting to empty store:', e);
42
+ }
40
43
  return { swarms: {}, version: '3.0.0' };
41
44
  }
42
45
  function saveSwarmStore(store) {
@@ -51,8 +51,9 @@ function loadMetrics() {
51
51
  return JSON.parse(readFileSync(path, 'utf-8'));
52
52
  }
53
53
  }
54
- catch {
55
- // Return default metrics
54
+ catch (e) {
55
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
56
+ console.error('[loadMetrics] failed to read/parse metrics.json, using defaults:', e);
56
57
  }
57
58
  return {
58
59
  startTime: new Date().toISOString(),
@@ -543,8 +544,9 @@ export const systemTools = [
543
544
  tasks = Object.values(store.tasks || {});
544
545
  }
545
546
  }
546
- catch {
547
- // empty store
547
+ catch (e) {
548
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
549
+ console.error('[task_summary] failed to read/parse task store:', e);
548
550
  }
549
551
  return {
550
552
  total: tasks.length,
@@ -4,5 +4,25 @@
4
4
  * Tool definitions for task management with file persistence.
5
5
  */
6
6
  import { type MCPTool } from './types.js';
7
+ interface TaskRecord {
8
+ taskId: string;
9
+ type: string;
10
+ description: string;
11
+ priority: 'low' | 'normal' | 'high' | 'critical';
12
+ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
13
+ progress: number;
14
+ assignedTo: string[];
15
+ tags: string[];
16
+ createdAt: string;
17
+ startedAt: string | null;
18
+ completedAt: string | null;
19
+ result?: Record<string, unknown>;
20
+ }
21
+ interface TaskStore {
22
+ tasks: Record<string, TaskRecord>;
23
+ version: string;
24
+ }
25
+ export declare function loadTaskStore(): TaskStore;
7
26
  export declare const taskTools: MCPTool[];
27
+ export {};
8
28
  //# sourceMappingURL=task-tools.d.ts.map
@@ -23,7 +23,7 @@ function ensureTaskDir() {
23
23
  }
24
24
  }
25
25
  const MAX_TASK_STORE_BYTES = 50 * 1024 * 1024;
26
- function loadTaskStore() {
26
+ export function loadTaskStore() {
27
27
  try {
28
28
  const path = getTaskPath();
29
29
  if (existsSync(path)) {
@@ -36,8 +36,9 @@ function loadTaskStore() {
36
36
  return parsed;
37
37
  }
38
38
  }
39
- catch {
40
- // Return empty store on error
39
+ catch (e) {
40
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
41
+ console.error('[loadTaskStore] failed to read/parse task store:', e);
41
42
  }
42
43
  return { tasks: {}, version: '3.0.0' };
43
44
  }
@@ -273,8 +274,9 @@ export const taskTools = [
273
274
  writeFileSync(tmpAgent1, JSON.stringify(agentStore, null, 2), 'utf-8');
274
275
  renameSync(tmpAgent1, agentStorePath);
275
276
  }
276
- catch {
277
- // Best-effort agent sync
277
+ catch (e) {
278
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
279
+ console.error('[task_complete] agent store sync failed:', e);
278
280
  }
279
281
  }
280
282
  return {
@@ -372,15 +374,30 @@ export const taskTools = [
372
374
  return { taskId, error: 'Task not found' };
373
375
  }
374
376
  const previouslyAssigned = [...task.assignedTo];
375
- // Load agent store to sync worker state
377
+ // Load agent store to sync worker state. Distinguish "file doesn't
378
+ // exist yet" (safe to proceed with an empty store) from "file exists
379
+ // but failed to read/parse" (a real store that's momentarily corrupt
380
+ // or oversized) — the latter must NOT fall through to the unconditional
381
+ // write-back below, or a transient read failure silently wipes out
382
+ // every agent's real on-disk state.
376
383
  const agentStorePath = join(getMonomindDataRoot(), 'agents', 'store.json');
377
384
  let agentStore = { agents: {} };
385
+ let agentStoreReadFailed = false;
378
386
  try {
379
- if (existsSync(agentStorePath) && statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
380
- agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
387
+ if (existsSync(agentStorePath)) {
388
+ if (statSync(agentStorePath).size <= MAX_TASK_STORE_BYTES) {
389
+ agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
390
+ }
391
+ else {
392
+ agentStoreReadFailed = true;
393
+ }
381
394
  }
382
395
  }
383
- catch { /* ignore */ }
396
+ catch (e) {
397
+ agentStoreReadFailed = true;
398
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
399
+ console.error('[task_assign] failed to read agent store — skipping agent-store sync to avoid overwriting it with an empty one:', e);
400
+ }
384
401
  // Reject IDs that would mutate Object.prototype when used as a key in
385
402
  // the JSON-loaded plain object `agentStore.agents`.
386
403
  const FORBIDDEN_AGENT_IDS = new Set(['__proto__', 'constructor', 'prototype']);
@@ -422,19 +439,24 @@ export const taskTools = [
422
439
  }
423
440
  }
424
441
  saveTaskStore(store);
425
- // Save agent store
426
- const agentDir = join(getMonomindDataRoot(), 'agents');
427
- if (!existsSync(agentDir)) {
428
- mkdirSync(agentDir, { recursive: true });
442
+ // Save agent store — skipped when the read above failed, so a
443
+ // transient corrupt/oversized store.json is left alone instead of
444
+ // being overwritten with an empty one.
445
+ if (!agentStoreReadFailed) {
446
+ const agentDir = join(getMonomindDataRoot(), 'agents');
447
+ if (!existsSync(agentDir)) {
448
+ mkdirSync(agentDir, { recursive: true });
449
+ }
450
+ const tmpAgent2 = `${agentStorePath}.${process.pid}.${Date.now()}.tmp`;
451
+ writeFileSync(tmpAgent2, JSON.stringify(agentStore, null, 2), 'utf-8');
452
+ renameSync(tmpAgent2, agentStorePath);
429
453
  }
430
- const tmpAgent2 = `${agentStorePath}.${process.pid}.${Date.now()}.tmp`;
431
- writeFileSync(tmpAgent2, JSON.stringify(agentStore, null, 2), 'utf-8');
432
- renameSync(tmpAgent2, agentStorePath);
433
454
  return {
434
455
  taskId: task.taskId,
435
456
  assignedTo: task.assignedTo,
436
457
  previouslyAssigned,
437
458
  status: task.status,
459
+ ...(agentStoreReadFailed ? { agentStoreSyncSkipped: true } : {}),
438
460
  };
439
461
  },
440
462
  },
@@ -4,5 +4,7 @@
4
4
  * Terminal session management with real command execution.
5
5
  */
6
6
  import type { MCPTool } from './types.js';
7
+ /** Returns a copy of `env` with secret-shaped variable names removed. */
8
+ export declare function filterSecretEnvVars(env: NodeJS.ProcessEnv): Record<string, string>;
7
9
  export declare const terminalTools: MCPTool[];
8
10
  //# sourceMappingURL=terminal-tools.d.ts.map
@@ -1,45 +1,80 @@
1
1
  import { getProjectCwd } from './types.js';
2
- import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import { execSync } from 'node:child_process';
6
6
  import { randomBytes } from 'node:crypto';
7
+ import { writeJsonFileAtomic } from '../utils/json-file.js';
7
8
  // Storage paths
8
9
  const STORAGE_DIR = '.monomind';
9
10
  const TERMINAL_DIR = 'terminals';
10
11
  const TERMINAL_FILE = 'store.json';
12
+ // ── Secret-shaped env var filtering for terminal_execute ────────────────────
13
+ // terminal_execute runs arbitrary (metacharacter-denylisted) shell commands
14
+ // via execSync with `env: { ...process.env, ...session.env }` — the host
15
+ // process's real environment, which commonly holds provider API keys
16
+ // (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...), CI/VCS tokens, and cloud
17
+ // credentials. A single unpiped command (the metacharacter denylist blocks
18
+ // piping/chaining, but not e.g. `curl` or `aws` invoked directly) can still
19
+ // read and exfiltrate whatever env vars it inherits. Stripping variables
20
+ // whose *names* match common secret conventions is defense-in-depth: it
21
+ // doesn't change legitimate terminal usage (PATH/HOME/etc. stay intact) but
22
+ // meaningfully shrinks what a malicious or hijacked command can read by
23
+ // default. Callers that genuinely need a specific secret can pass it via
24
+ // terminal_create's `env` option, which flows through untouched (session.env
25
+ // is layered on *after* the filtered process.env below).
26
+ const SECRET_ENV_NAME_PATTERN = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|_AUTH$|^AUTH_|PRIVATE_KEY|ACCESS_KEY)/i;
27
+ /** Returns a copy of `env` with secret-shaped variable names removed. */
28
+ export function filterSecretEnvVars(env) {
29
+ const filtered = {};
30
+ for (const [k, v] of Object.entries(env)) {
31
+ if (v === undefined)
32
+ continue;
33
+ if (SECRET_ENV_NAME_PATTERN.test(k))
34
+ continue;
35
+ filtered[k] = v;
36
+ }
37
+ return filtered;
38
+ }
11
39
  function getTerminalDir() {
12
40
  return join(getProjectCwd(), STORAGE_DIR, TERMINAL_DIR);
13
41
  }
14
42
  function getTerminalPath() {
15
43
  return join(getTerminalDir(), TERMINAL_FILE);
16
44
  }
17
- function ensureTerminalDir() {
18
- const dir = getTerminalDir();
19
- if (!existsSync(dir)) {
20
- mkdirSync(dir, { recursive: true });
21
- }
22
- }
23
45
  const MAX_TERMINAL_STORE_BYTES = 10 * 1024 * 1024; // 10 MB
24
- function loadTerminalStore() {
46
+ // Same hardened-loader convention as agent-tools.ts's loadAgentStoreOrNull:
47
+ // handlers that mutate and save must use the null-aware variant, since
48
+ // treating a corrupt/oversized store as empty and then saving that back
49
+ // would silently wipe every real session. Also guards against a __proto__
50
+ // key in the parsed JSON (agent-tools.ts's loader does the same). Can't
51
+ // delegate to utils/json-file.ts's readJsonFileSync here — it collapses
52
+ // "file absent" and "file corrupt" into the same fallback value, but this
53
+ // loader must tell them apart (absent → empty default is safe to build on
54
+ // and save; corrupt → null, refuse to save over it).
55
+ function loadTerminalStoreOrNull() {
25
56
  try {
26
57
  const path = getTerminalPath();
27
- if (existsSync(path) && statSync(path).size <= MAX_TERMINAL_STORE_BYTES) {
28
- return JSON.parse(readFileSync(path, 'utf-8'));
29
- }
58
+ if (!existsSync(path))
59
+ return { sessions: {}, version: '3.0.0' };
60
+ if (statSync(path).size > MAX_TERMINAL_STORE_BYTES)
61
+ return null;
62
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
63
+ if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, '__proto__'))
64
+ return null;
65
+ return parsed;
30
66
  }
31
- catch {
32
- // Return empty store
67
+ catch (e) {
68
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
69
+ console.error('[loadTerminalStore] terminal store unreadable/corrupt:', e);
70
+ return null;
33
71
  }
34
- return { sessions: {}, version: '3.0.0' };
72
+ }
73
+ function loadTerminalStore() {
74
+ return loadTerminalStoreOrNull() ?? { sessions: {}, version: '3.0.0' };
35
75
  }
36
76
  function saveTerminalStore(store) {
37
- ensureTerminalDir();
38
- // Unique tmp filename so concurrent handler invocations cannot clobber each
39
- // other's .tmp mid-write (which would produce a partial JSON on rename).
40
- const tmpPath = `${getTerminalPath()}.${process.pid}.${Date.now()}.tmp`;
41
- writeFileSync(tmpPath, JSON.stringify(store, null, 2), 'utf-8');
42
- renameSync(tmpPath, getTerminalPath());
77
+ writeJsonFileAtomic(getTerminalPath(), store);
43
78
  }
44
79
  export const terminalTools = [
45
80
  {
@@ -55,7 +90,10 @@ export const terminalTools = [
55
90
  },
56
91
  },
57
92
  handler: async (input) => {
58
- const store = loadTerminalStore();
93
+ const store = loadTerminalStoreOrNull();
94
+ if (!store) {
95
+ return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to create a session to avoid overwriting real session data.' };
96
+ }
59
97
  const MAX_SESSIONS = 1000;
60
98
  if (Object.keys(store.sessions).length >= MAX_SESSIONS) {
61
99
  return { success: false, error: 'Session limit reached' };
@@ -140,7 +178,10 @@ export const terminalTools = [
140
178
  required: ['command'],
141
179
  },
142
180
  handler: async (input) => {
143
- const store = loadTerminalStore();
181
+ const store = loadTerminalStoreOrNull();
182
+ if (!store) {
183
+ return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to execute to avoid overwriting real session data.' };
184
+ }
144
185
  const sessionId = input.sessionId;
145
186
  // Cap command: the metacharacter regex check at line 220 is O(n), and the
146
187
  // raw command is stored verbatim in session history (up to 200 entries).
@@ -200,7 +241,7 @@ export const terminalTools = [
200
241
  timeout,
201
242
  maxBuffer: 5 * 1024 * 1024,
202
243
  stdio: ['pipe', 'pipe', 'pipe'],
203
- env: { ...process.env, ...session.env },
244
+ env: { ...filterSecretEnvVars(process.env), ...session.env },
204
245
  });
205
246
  exitCode = 0;
206
247
  }
@@ -286,7 +327,10 @@ export const terminalTools = [
286
327
  required: ['sessionId'],
287
328
  },
288
329
  handler: async (input) => {
289
- const store = loadTerminalStore();
330
+ const store = loadTerminalStoreOrNull();
331
+ if (!store) {
332
+ return { success: false, error: 'Terminal store is unreadable/corrupt — refusing to close a session to avoid overwriting real session data.' };
333
+ }
290
334
  const sessionId = input.sessionId;
291
335
  const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
292
336
  if (!sessionId || FORBIDDEN_KEYS.has(sessionId)) {
@@ -70,8 +70,10 @@ export class EWCConsolidator {
70
70
  this.initialized = true;
71
71
  return true;
72
72
  }
73
- catch {
73
+ catch (e) {
74
74
  // Start fresh if no persisted state
75
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
76
+ console.error('[ewc-consolidation] failed to load persisted state, starting fresh:', e);
75
77
  this.initialized = true;
76
78
  return true;
77
79
  }
@@ -544,8 +546,10 @@ export class EWCConsolidator {
544
546
  fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
545
547
  fs.renameSync(tmp, this.config.storagePath);
546
548
  }
547
- catch {
549
+ catch (e) {
548
550
  // Silently fail - persistence is best-effort
551
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
552
+ console.error('[ewc-consolidation] failed to persist state:', e);
549
553
  }
550
554
  }
551
555
  /**