monomind 2.0.3 → 2.1.3

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 (113) hide show
  1. package/README.md +74 -92
  2. package/package.json +11 -7
  3. package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
  4. package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
  5. package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
  6. package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
  7. package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +99 -28
  8. package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
  9. package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +18 -9
  10. package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
  11. package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +33 -4
  12. package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +75 -45
  13. package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
  14. package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
  15. package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +75 -4
  16. package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
  17. package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
  18. package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
  19. package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
  20. package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
  21. package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
  22. package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
  23. package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
  24. package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
  25. package/packages/@monomind/cli/README.md +74 -92
  26. package/packages/@monomind/cli/bin/cli.js +10 -1
  27. package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
  28. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
  29. package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
  30. package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
  31. package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
  32. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
  33. package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
  34. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
  35. package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
  36. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
  37. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
  38. package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
  39. package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
  40. package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
  41. package/packages/@monomind/cli/dist/src/commands/org.js +321 -127
  42. package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
  43. package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
  44. package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
  45. package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
  46. package/packages/@monomind/cli/dist/src/index.js +64 -28
  47. package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
  48. package/packages/@monomind/cli/dist/src/init/executor.js +93 -55
  49. package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
  50. package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
  51. package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
  52. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
  53. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
  54. package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
  55. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
  56. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
  57. package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
  58. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +56 -35
  59. package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
  60. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
  61. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +88 -41
  62. package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
  63. package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
  64. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
  65. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
  66. package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
  67. package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
  68. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
  69. package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
  70. package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
  71. package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
  72. package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
  73. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
  74. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
  75. package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +26 -0
  76. package/packages/@monomind/cli/dist/src/orgrt/broker.js +73 -0
  77. package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
  78. package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
  79. package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +46 -0
  80. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +201 -18
  81. package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
  82. package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +138 -8
  83. package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
  84. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
  85. package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
  86. package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
  87. package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
  88. package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
  89. package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
  90. package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +9 -0
  91. package/packages/@monomind/cli/dist/src/orgrt/server.js +39 -0
  92. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +10 -1
  93. package/packages/@monomind/cli/dist/src/orgrt/session.js +24 -1
  94. package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
  95. package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
  96. package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +52 -52
  97. package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
  98. package/packages/@monomind/cli/dist/src/output.js +22 -7
  99. package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
  100. package/packages/@monomind/cli/dist/src/parser.js +130 -5
  101. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
  102. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
  103. package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
  104. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
  105. package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
  106. package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
  107. package/packages/@monomind/cli/dist/src/ui/dashboard.html +243 -49
  108. package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
  109. package/packages/@monomind/cli/dist/src/ui/orgs.html +79 -11
  110. package/packages/@monomind/cli/dist/src/ui/server.mjs +346 -139
  111. package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
  112. package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
  113. package/packages/@monomind/cli/package.json +15 -15
@@ -69,8 +69,8 @@ export declare const CoherenceInputSchema: z.ZodObject<{
69
69
  vectors: z.ZodArray<z.ZodArray<z.ZodNumber, "many">, "many">;
70
70
  threshold: z.ZodDefault<z.ZodNumber>;
71
71
  }, "strip", z.ZodTypeAny, {
72
- threshold: number;
73
72
  vectors: number[][];
73
+ threshold: number;
74
74
  }, {
75
75
  vectors: number[][];
76
76
  threshold?: number | undefined;
@@ -98,10 +98,10 @@ export declare const SpectralInputSchema: z.ZodObject<{
98
98
  analyzeType: z.ZodDefault<z.ZodEnum<["stability", "clustering", "connectivity"]>>;
99
99
  }, "strip", z.ZodTypeAny, {
100
100
  matrix: number[][];
101
- analyzeType: "stability" | "clustering" | "connectivity";
101
+ analyzeType: "clustering" | "connectivity" | "stability";
102
102
  }, {
103
103
  matrix: number[][];
104
- analyzeType?: "stability" | "clustering" | "connectivity" | undefined;
104
+ analyzeType?: "clustering" | "connectivity" | "stability" | undefined;
105
105
  }>;
106
106
  export type SpectralInput = z.infer<typeof SpectralInputSchema>;
107
107
  export interface SpectralResult {
@@ -150,15 +150,15 @@ export declare const CausalInputSchema: z.ZodObject<{
150
150
  nodes: string[];
151
151
  edges: [string, string][];
152
152
  };
153
- outcome: string;
154
153
  intervention: string;
154
+ outcome: string;
155
155
  }, {
156
156
  graph: {
157
157
  nodes: string[];
158
158
  edges: [string, string][];
159
159
  };
160
- outcome: string;
161
160
  intervention: string;
161
+ outcome: string;
162
162
  }>;
163
163
  export type CausalInput = z.infer<typeof CausalInputSchema>;
164
164
  export interface CausalResult {
@@ -185,13 +185,13 @@ export declare const AgentStateSchema: z.ZodObject<{
185
185
  }, "strip", z.ZodTypeAny, {
186
186
  agentId: string;
187
187
  embedding: number[];
188
- metadata?: Record<string, unknown> | undefined;
189
188
  vote?: string | undefined;
189
+ metadata?: Record<string, unknown> | undefined;
190
190
  }, {
191
191
  agentId: string;
192
192
  embedding: number[];
193
- metadata?: Record<string, unknown> | undefined;
194
193
  vote?: string | undefined;
194
+ metadata?: Record<string, unknown> | undefined;
195
195
  }>;
196
196
  export type AgentState = z.infer<typeof AgentStateSchema>;
197
197
  export declare const ConsensusInputSchema: z.ZodObject<{
@@ -203,29 +203,29 @@ export declare const ConsensusInputSchema: z.ZodObject<{
203
203
  }, "strip", z.ZodTypeAny, {
204
204
  agentId: string;
205
205
  embedding: number[];
206
- metadata?: Record<string, unknown> | undefined;
207
206
  vote?: string | undefined;
207
+ metadata?: Record<string, unknown> | undefined;
208
208
  }, {
209
209
  agentId: string;
210
210
  embedding: number[];
211
- metadata?: Record<string, unknown> | undefined;
212
211
  vote?: string | undefined;
212
+ metadata?: Record<string, unknown> | undefined;
213
213
  }>, "many">;
214
214
  threshold: z.ZodDefault<z.ZodNumber>;
215
215
  }, "strip", z.ZodTypeAny, {
216
- threshold: number;
217
216
  agentStates: {
218
217
  agentId: string;
219
218
  embedding: number[];
220
- metadata?: Record<string, unknown> | undefined;
221
219
  vote?: string | undefined;
220
+ metadata?: Record<string, unknown> | undefined;
222
221
  }[];
222
+ threshold: number;
223
223
  }, {
224
224
  agentStates: {
225
225
  agentId: string;
226
226
  embedding: number[];
227
- metadata?: Record<string, unknown> | undefined;
228
227
  vote?: string | undefined;
228
+ metadata?: Record<string, unknown> | undefined;
229
229
  }[];
230
230
  threshold?: number | undefined;
231
231
  }>;
@@ -247,11 +247,11 @@ export declare const SimplexSchema: z.ZodObject<{
247
247
  vertices: z.ZodArray<z.ZodNumber, "many">;
248
248
  dimension: z.ZodNumber;
249
249
  }, "strip", z.ZodTypeAny, {
250
- dimension: number;
251
250
  vertices: number[];
252
- }, {
253
251
  dimension: number;
252
+ }, {
254
253
  vertices: number[];
254
+ dimension: number;
255
255
  }>;
256
256
  export type Simplex = z.infer<typeof SimplexSchema>;
257
257
  export declare const SimplicialComplexSchema: z.ZodObject<{
@@ -260,25 +260,25 @@ export declare const SimplicialComplexSchema: z.ZodObject<{
260
260
  vertices: z.ZodArray<z.ZodNumber, "many">;
261
261
  dimension: z.ZodNumber;
262
262
  }, "strip", z.ZodTypeAny, {
263
- dimension: number;
264
263
  vertices: number[];
265
- }, {
266
264
  dimension: number;
265
+ }, {
267
266
  vertices: number[];
267
+ dimension: number;
268
268
  }>, "many">>;
269
269
  maxDimension: z.ZodDefault<z.ZodNumber>;
270
270
  }, "strip", z.ZodTypeAny, {
271
271
  vertices: number[][];
272
- maxDimension: number;
273
272
  simplices?: {
274
- dimension: number;
275
273
  vertices: number[];
274
+ dimension: number;
276
275
  }[] | undefined;
276
+ maxDimension: number;
277
277
  }, {
278
278
  vertices: number[][];
279
279
  simplices?: {
280
- dimension: number;
281
280
  vertices: number[];
281
+ dimension: number;
282
282
  }[] | undefined;
283
283
  maxDimension?: number | undefined;
284
284
  }>;
@@ -290,43 +290,43 @@ export declare const TopologyInputSchema: z.ZodObject<{
290
290
  vertices: z.ZodArray<z.ZodNumber, "many">;
291
291
  dimension: z.ZodNumber;
292
292
  }, "strip", z.ZodTypeAny, {
293
- dimension: number;
294
293
  vertices: number[];
295
- }, {
296
294
  dimension: number;
295
+ }, {
297
296
  vertices: number[];
297
+ dimension: number;
298
298
  }>, "many">>;
299
299
  maxDimension: z.ZodDefault<z.ZodNumber>;
300
300
  }, "strip", z.ZodTypeAny, {
301
301
  vertices: number[][];
302
- maxDimension: number;
303
302
  simplices?: {
304
- dimension: number;
305
303
  vertices: number[];
304
+ dimension: number;
306
305
  }[] | undefined;
306
+ maxDimension: number;
307
307
  }, {
308
308
  vertices: number[][];
309
309
  simplices?: {
310
- dimension: number;
311
310
  vertices: number[];
311
+ dimension: number;
312
312
  }[] | undefined;
313
313
  maxDimension?: number | undefined;
314
314
  }>;
315
315
  }, "strip", z.ZodTypeAny, {
316
316
  complex: {
317
317
  vertices: number[][];
318
- maxDimension: number;
319
318
  simplices?: {
320
- dimension: number;
321
319
  vertices: number[];
320
+ dimension: number;
322
321
  }[] | undefined;
322
+ maxDimension: number;
323
323
  };
324
324
  }, {
325
325
  complex: {
326
326
  vertices: number[][];
327
327
  simplices?: {
328
- dimension: number;
329
328
  vertices: number[];
329
+ dimension: number;
330
330
  }[] | undefined;
331
331
  maxDimension?: number | undefined;
332
332
  };
@@ -362,27 +362,27 @@ export declare const MemoryGateInputSchema: z.ZodObject<{
362
362
  reject: z.ZodDefault<z.ZodNumber>;
363
363
  warn: z.ZodDefault<z.ZodNumber>;
364
364
  }, "strip", z.ZodTypeAny, {
365
- warn: number;
366
365
  reject: number;
366
+ warn: number;
367
367
  }, {
368
- warn?: number | undefined;
369
368
  reject?: number | undefined;
369
+ warn?: number | undefined;
370
370
  }>>;
371
371
  }, "strip", z.ZodTypeAny, {
372
372
  key: string;
373
373
  value?: unknown;
374
374
  existingVectors?: number[][] | undefined;
375
375
  thresholds?: {
376
- warn: number;
377
376
  reject: number;
377
+ warn: number;
378
378
  } | undefined;
379
379
  }, {
380
380
  key: string;
381
381
  value?: unknown;
382
382
  existingVectors?: number[][] | undefined;
383
383
  thresholds?: {
384
- warn?: number | undefined;
385
384
  reject?: number | undefined;
385
+ warn?: number | undefined;
386
386
  } | undefined;
387
387
  }>;
388
388
  export type MemoryGateInput = z.infer<typeof MemoryGateInputSchema>;
@@ -60,6 +60,18 @@ function runSafe(cmd, args, cwd) {
60
60
  return null;
61
61
  }
62
62
  }
63
+ /** Run a `gh` command and preserve the real failure reason instead of collapsing to null. */
64
+ function runSafeResult(cmd, args, cwd) {
65
+ try {
66
+ const stdout = execFileSync(cmd, args, { encoding: 'utf-8', timeout: 15000, cwd: cwd || getProjectCwd(), stdio: ['pipe', 'pipe', 'pipe'] }).trim();
67
+ return { ok: true, stdout };
68
+ }
69
+ catch (err) {
70
+ const e = err;
71
+ const stderr = e.stderr ? String(e.stderr).trim() : '';
72
+ return { ok: false, error: stderr || e.message || 'gh command failed' };
73
+ }
74
+ }
63
75
  /** Check if gh CLI is available */
64
76
  function hasGhCli() {
65
77
  return run('gh --version') !== null;
@@ -216,7 +228,11 @@ export const githubTools = [
216
228
  }
217
229
  // Fallback: local store
218
230
  const prId = `pr-${Date.now()}`;
219
- const pr = { id: prId, title, status: 'open', branch: headBranch, baseBranch, createdAt: new Date().toISOString() };
231
+ // Locally-assigned PR number, independent of the timestamp-based key
232
+ // monotonic within this store so lookups by number are unambiguous.
233
+ const existingNumbers = Object.values(store.prs).map(p => p.number || 0);
234
+ const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
235
+ const pr = { id: prId, number: nextNumber, title, status: 'open', branch: headBranch, baseBranch, createdAt: new Date().toISOString() };
220
236
  store.prs[prId] = pr;
221
237
  saveGitHubStore(store);
222
238
  return { success: true, source: 'local-store', action: 'created', pullRequest: pr };
@@ -241,13 +257,17 @@ export const githubTools = [
241
257
  if (!prNumber)
242
258
  return { success: false, error: 'prNumber is required and must be a positive integer for merge.' };
243
259
  if (gh) {
244
- const result = runSafe('gh', ['pr', 'merge', String(prNumber), '--merge']);
245
- if (result !== null) {
260
+ // gh CLI is actually installed trust its real exit status. A genuine
261
+ // failure (branch protection, conflicts, auth) must be reported as a
262
+ // failure, not silently swallowed into the local-store simulation.
263
+ const result = runSafeResult('gh', ['pr', 'merge', String(prNumber), '--merge']);
264
+ if (result.ok) {
246
265
  return { success: true, _real: true, action: 'merged', prNumber, mergedAt: new Date().toISOString() };
247
266
  }
267
+ return { success: false, error: result.error };
248
268
  }
249
- // Fallback: local store
250
- const prKey = Object.keys(store.prs).find(k => k.includes(String(prNumber)));
269
+ // Fallback: local store (only reached when gh CLI is not installed at all)
270
+ const prKey = Object.keys(store.prs).find(k => store.prs[k].number === prNumber);
251
271
  if (prKey && store.prs[prKey]) {
252
272
  store.prs[prKey].status = 'merged';
253
273
  saveGitHubStore(store);
@@ -259,12 +279,14 @@ export const githubTools = [
259
279
  if (!prNumber)
260
280
  return { success: false, error: 'prNumber is required and must be a positive integer for close.' };
261
281
  if (gh) {
262
- const result = runSafe('gh', ['pr', 'close', String(prNumber)]);
263
- if (result !== null) {
282
+ const result = runSafeResult('gh', ['pr', 'close', String(prNumber)]);
283
+ if (result.ok) {
264
284
  return { success: true, _real: true, action: 'closed', prNumber, closedAt: new Date().toISOString() };
265
285
  }
286
+ return { success: false, error: result.error };
266
287
  }
267
- const prKey = Object.keys(store.prs).find(k => k.includes(String(prNumber)));
288
+ // Fallback: local store (only reached when gh CLI is not installed at all)
289
+ const prKey = Object.keys(store.prs).find(k => store.prs[k].number === prNumber);
268
290
  if (prKey && store.prs[prKey]) {
269
291
  store.prs[prKey].status = 'closed';
270
292
  saveGitHubStore(store);
@@ -4,7 +4,14 @@
4
4
  * All graphify_* tools are deprecated. They proxy to monograph_* tools.
5
5
  * Will be removed in next major release.
6
6
  */
7
- import { allMonographTools } from './monograph-tools.js';
7
+ import { allMonographTools, monographTools } from './monograph-tools.js';
8
+ // Advanced monograph tools are gated behind MONOGRAPH_MCP_ADVANCED=1 (see
9
+ // monograph-tools.ts). Shims that target an advanced-tier tool must be gated
10
+ // the same way, otherwise they re-expose advanced tools under a different
11
+ // name regardless of the flag. `monographTools` is the already-gated export
12
+ // (core-only unless MONOGRAPH_MCP_ADVANCED=1); a target is "advanced" if it's
13
+ // present in allMonographTools but absent from monographTools.
14
+ const exposedMonographNames = new Set(monographTools.map(t => t.name));
8
15
  function findMonographTool(name) {
9
16
  const tool = allMonographTools.find(t => t.name === name);
10
17
  if (!tool)
@@ -12,6 +19,8 @@ function findMonographTool(name) {
12
19
  return tool;
13
20
  }
14
21
  function shimTool(graphifyName, monographName, paramMap) {
22
+ if (!exposedMonographNames.has(monographName))
23
+ return null;
15
24
  const target = findMonographTool(monographName);
16
25
  return {
17
26
  name: graphifyName,
@@ -44,6 +53,6 @@ export const graphifyTools = [
44
53
  shimTool('graphify_report', 'monograph_report'),
45
54
  // Bug fix: graphify_health previously referenced `files.length` (ReferenceError) — now delegates cleanly
46
55
  shimTool('graphify_health', 'monograph_health'),
47
- ];
56
+ ].filter((t) => t !== null);
48
57
  export default graphifyTools;
49
58
  //# sourceMappingURL=graphify-tools.js.map
@@ -13,8 +13,12 @@
13
13
  import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
14
14
  import { randomBytes } from 'node:crypto';
15
15
  import { join } from 'node:path';
16
- import { getProjectCwd } from './types.js';
16
+ import { getProjectCwd, getMonomindDataRoot, migrateLegacyStoreFile } from './types.js';
17
17
  import { weightedTally } from '../consensus/tally.js';
18
+ // Reuse agent-tools.ts's hardened store loader (50MB size cap + __proto__
19
+ // rejection) instead of maintaining a second, weaker copy that reads the
20
+ // same physical file — see loadAgentStore export note there.
21
+ import { loadAgentStore } from './agent-tools.js';
18
22
  // Storage paths
19
23
  const STORAGE_DIR = '.monomind';
20
24
  const HIVE_DIR = 'hive-mind';
@@ -92,7 +96,7 @@ function tryResolveProposal(proposal, totalNodes) {
92
96
  return null;
93
97
  }
94
98
  function getHiveDir() {
95
- return join(getProjectCwd(), STORAGE_DIR, HIVE_DIR);
99
+ return join(getMonomindDataRoot(), HIVE_DIR);
96
100
  }
97
101
  function getHivePath() {
98
102
  return join(getHiveDir(), HIVE_FILE);
@@ -107,6 +111,7 @@ const MAX_HIVE_STATE_BYTES = 10 * 1024 * 1024; // 10 MB
107
111
  function loadHiveState() {
108
112
  try {
109
113
  const path = getHivePath();
114
+ migrateLegacyStoreFile(path, join(HIVE_DIR, HIVE_FILE));
110
115
  if (existsSync(path) && statSync(path).size <= MAX_HIVE_STATE_BYTES) {
111
116
  const data = readFileSync(path, 'utf-8');
112
117
  return JSON.parse(data);
@@ -176,21 +181,10 @@ function getOrCreateSessionSecret() {
176
181
  }
177
182
  }
178
183
  // Import agent store helpers for spawn functionality
179
- import { existsSync as agentStoreExists, readFileSync as readAgentStore, writeFileSync as writeAgentStore, mkdirSync as mkdirAgentStore } from 'node:fs';
180
- // Canonical agent store path matches agent-tools.ts: .monomind/agents/store.json
181
- function loadAgentStore() {
182
- const storePath = join(getProjectCwd(), '.monomind', 'agents', 'store.json');
183
- try {
184
- if (agentStoreExists(storePath)) {
185
- return JSON.parse(readAgentStore(storePath, 'utf-8'));
186
- }
187
- }
188
- catch { /* ignore */ }
189
- return { agents: {} };
190
- }
184
+ import { existsSync as agentStoreExists, writeFileSync as writeAgentStore, mkdirSync as mkdirAgentStore } from 'node:fs';
191
185
  const HIVE_RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
192
186
  function saveAgentStore(store) {
193
- const storeDir = join(getProjectCwd(), '.monomind', 'agents');
187
+ const storeDir = join(getMonomindDataRoot(), 'agents');
194
188
  if (!agentStoreExists(storeDir)) {
195
189
  mkdirAgentStore(storeDir, { recursive: true });
196
190
  }
@@ -284,6 +278,10 @@ export const hiveMindTools = [
284
278
  properties: {
285
279
  topology: { type: 'string', enum: ['mesh', 'hierarchical', 'ring', 'star'], description: 'Network topology' },
286
280
  queenId: { type: 'string', description: 'Initial queen agent ID' },
281
+ consensus: { type: 'string', enum: ['byzantine', 'bft', 'raft', 'quorum'], description: 'Consensus strategy to govern hive-mind_consensus propose/vote (gossip/crdt are planned but not implemented and fall back to byzantine). Default: byzantine.' },
282
+ maxAgents: { type: 'number', description: 'Maximum number of agents in the hive. Default: 15.' },
283
+ persist: { type: 'boolean', description: 'Whether to persist hive state to disk. Default: true.' },
284
+ memoryBackend: { type: 'string', description: 'Shared memory backend for the hive. Default: hybrid.' },
287
285
  },
288
286
  },
289
287
  handler: async (input) => {
@@ -294,32 +292,34 @@ export const hiveMindTools = [
294
292
  const queenId = typeof rawQueenId === 'string' && rawQueenId.length > MAX_QUEEN_ID_LEN
295
293
  ? rawQueenId.slice(0, MAX_QUEEN_ID_LEN) : rawQueenId;
296
294
  const now = new Date().toISOString();
295
+ // Validate consensus strategy: gossip and crdt are planned but not implemented
296
+ const SUPPORTED_CONSENSUS = ['byzantine', 'bft', 'raft', 'quorum'];
297
+ const PLANNED_CONSENSUS = ['gossip', 'crdt'];
298
+ const requestedConsensus = input.consensus || 'byzantine';
299
+ const consensusWarning = PLANNED_CONSENSUS.includes(requestedConsensus)
300
+ ? `Strategy "${requestedConsensus}" is planned but not yet implemented; defaulting to "byzantine".`
301
+ : undefined;
302
+ const effectiveConsensus = PLANNED_CONSENSUS.includes(requestedConsensus)
303
+ ? 'byzantine'
304
+ : (SUPPORTED_CONSENSUS.includes(requestedConsensus) ? requestedConsensus : 'byzantine');
297
305
  const state = {
298
306
  initialized: true,
299
307
  hiveId,
300
308
  topology: input.topology || 'mesh',
301
309
  queen: { agentId: queenId, electedAt: now, term: 1 },
302
310
  workers: [],
311
+ // Persist the chosen strategy on the hive state so hive-mind_consensus
312
+ // propose/vote default to it instead of silently using 'raft'.
313
+ // 'byzantine' is the CLI-facing name; the internal ConsensusStrategy type uses 'bft'.
314
+ consensusStrategy: (effectiveConsensus === 'byzantine' ? 'bft' : effectiveConsensus),
303
315
  consensus: { pending: [], history: [] },
304
316
  sharedMemory: {},
305
317
  createdAt: now,
306
318
  updatedAt: now,
307
319
  };
308
- saveHiveState(state);
309
- // Validate consensus strategy: gossip and crdt are planned but not implemented
310
- const SUPPORTED_CONSENSUS = ['byzantine', 'bft', 'raft', 'quorum'];
311
- const PLANNED_CONSENSUS = ['gossip', 'crdt'];
312
- const requestedConsensus = input.consensus || 'byzantine';
313
- const consensusWarning = PLANNED_CONSENSUS.includes(requestedConsensus)
314
- ? `Strategy "${requestedConsensus}" is planned but not yet implemented; defaulting to "byzantine".`
315
- : undefined;
316
- const effectiveConsensus = PLANNED_CONSENSUS.includes(requestedConsensus)
317
- ? 'byzantine'
318
- : (SUPPORTED_CONSENSUS.includes(requestedConsensus) ? requestedConsensus : 'byzantine');
319
- // Persist the chosen strategy on the hive state so hive-mind_consensus
320
- // propose/vote default to it instead of silently using 'raft'.
321
- // 'byzantine' is the CLI-facing name; the internal ConsensusStrategy type uses 'bft'.
322
- state.consensusStrategy = (effectiveConsensus === 'byzantine' ? 'bft' : effectiveConsensus);
320
+ // Single write — a prior version of this handler saved state twice
321
+ // (once before setting consensusStrategy, once after), which was
322
+ // redundant.
323
323
  saveHiveState(state);
324
324
  return {
325
325
  success: true,
@@ -357,7 +357,8 @@ export const hiveMindTools = [
357
357
  // Load agent store once for all workers
358
358
  const agentStore = loadAgentStore();
359
359
  // Compute real task metrics from task store
360
- const taskStorePath = join(getProjectCwd(), '.monomind', 'tasks', 'store.json');
360
+ const taskStorePath = join(getMonomindDataRoot(), 'tasks', 'store.json');
361
+ migrateLegacyStoreFile(taskStorePath, join('tasks', 'store.json'));
361
362
  let pendingTaskCount = 0;
362
363
  let activeTaskCount = 0;
363
364
  let completedTaskCount = 0;
@@ -382,7 +383,14 @@ export const hiveMindTools = [
382
383
  hiveId: state.hiveId ?? `hive-${state.createdAt ? new Date(state.createdAt).getTime() : Date.now()}`,
383
384
  status: state.initialized ? 'active' : 'offline',
384
385
  topology: state.topology,
385
- consensus: 'byzantine', // Default consensus type
386
+ // Reflect the strategy actually chosen at hive-mind_init (persisted
387
+ // as state.consensusStrategy so it "actually governs voting" — see
388
+ // the field's doc comment) instead of hardcoding a default that
389
+ // ignored what was configured. 'bft' is the internal name for the
390
+ // CLI-facing 'byzantine' strategy.
391
+ consensus: state.consensusStrategy
392
+ ? (state.consensusStrategy === 'bft' ? 'byzantine' : state.consensusStrategy)
393
+ : 'byzantine',
386
394
  queen: state.queen ? {
387
395
  id: state.queen.agentId,
388
396
  agentId: state.queen.agentId,
@@ -398,7 +406,11 @@ export const hiveMindTools = [
398
406
  id: w,
399
407
  type: agent?.agentType || 'worker',
400
408
  status: agent?.status || 'unknown',
401
- currentTask: agent?.currentTask || null,
409
+ // AgentRecord has no currentTask field — always null. Kept in the
410
+ // response shape for API stability; was already dead before this
411
+ // (the previous `as unknown as Record<string, unknown>` cast hid
412
+ // that `agent?.currentTask` never matched a real field).
413
+ currentTask: null,
402
414
  tasksCompleted: agent?.taskCount || 0,
403
415
  };
404
416
  }),
@@ -729,9 +741,18 @@ export const hiveMindTools = [
729
741
  confidence: 1.0, // uniform until confidence-probe is wired
730
742
  }));
731
743
  const cpwbftTally = weightedTally(weightedVotes);
732
- // O-Information gate: defer resolution if we haven't seen enough divergent rounds
744
+ // O-Information gate: defer resolution if we haven't seen enough divergent rounds.
745
+ // BUT: if every expected voter has already cast a vote (electorate
746
+ // exhausted), no more votes can ever arrive to produce a divergent
747
+ // round — a unanimous first-round vote would otherwise deadlock the
748
+ // proposal permanently since divergenceRoundsSeen only increments on
749
+ // disagreement. Treat electorate exhaustion as a terminal condition
750
+ // that opens the gate regardless of divergence.
751
+ const totalVotesCast = Object.keys(proposal.votes).length;
752
+ const electorateExhausted = totalNodes > 0 && totalVotesCast >= totalNodes;
733
753
  const divergenceGateOpen = !proposal.minDivergenceRounds
734
- || (proposal.divergenceRoundsSeen ?? 0) >= proposal.minDivergenceRounds;
754
+ || (proposal.divergenceRoundsSeen ?? 0) >= proposal.minDivergenceRounds
755
+ || electorateExhausted;
735
756
  // Try to resolve
736
757
  const resolution = divergenceGateOpen ? tryResolveProposal(proposal, totalNodes) : null;
737
758
  let resolved = false;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * MCP Knowledge Tools — Second Brain document ingest and search
3
3
  */
4
+ import { validateInput } from '../utils/input-guards.js';
4
5
  const knowledgeIngest = {
5
6
  name: 'knowledge_ingest',
6
7
  description: 'Ingest documents into the Second Brain knowledge base. Accepts a file or directory path. Extracts text, chunks, embeds, and stores in LanceDB for semantic search.',
@@ -15,10 +16,17 @@ const knowledgeIngest = {
15
16
  required: ['path'],
16
17
  },
17
18
  handler: async (input) => {
19
+ const pathCheck = validateInput(input.path, { type: 'path' });
20
+ if (!pathCheck.valid) {
21
+ return {
22
+ content: [{ type: 'text', text: JSON.stringify({ success: false, error: pathCheck.error }) }],
23
+ isError: true,
24
+ };
25
+ }
18
26
  const { ingestDocument, ingestDirectory } = await import('../knowledge/document-pipeline.js');
19
27
  const fs = await import('node:fs');
20
28
  const pathMod = await import('node:path');
21
- const target = pathMod.resolve(String(input.path));
29
+ const target = pathMod.resolve(pathCheck.sanitized);
22
30
  const scope = String(input.scope || 'shared');
23
31
  try {
24
32
  const stat = fs.statSync(target);
@@ -6,26 +6,67 @@
6
6
  * moved to the real package.
7
7
  */
8
8
  import { join, resolve, relative } from 'path';
9
- import { existsSync, readFileSync, statSync } from 'fs';
10
- import { openDb, closeDb, countNodes } from '@monoes/monograph';
9
+ import { existsSync, statSync } from 'fs';
10
+ import { execSync } from 'child_process';
11
+ import { openDb, closeDb, countNodes, parseGroupConfig } from '@monoes/monograph';
12
+ import { getProjectCwd } from './types.js';
13
+ import { realOrResolved } from '../utils/input-guards.js';
14
+ // ── Group config reader ───────────────────────────────────────────────────────
11
15
  const MAX_GROUP_CONFIG_BYTES = 1 * 1024 * 1024; // 1 MB
16
+ /**
17
+ * Read repos from a group.yaml config (the real group config format used by
18
+ * monograph_group_query / monograph_group_sync / monograph_group_list — see
19
+ * @monoes/monograph's groups/group-config.ts).
20
+ *
21
+ * Throws on an out-of-bounds or oversized path instead of silently returning
22
+ * an empty list, so callers can distinguish "no groups configured" from
23
+ * "your repoPath was rejected".
24
+ */
12
25
  function readGroupConfig(configPath) {
13
26
  if (!existsSync(configPath))
14
27
  return [];
28
+ // Guard: only allow paths within the project cwd to prevent traversal to
29
+ // /etc/passwd etc. Uses getProjectCwd() (honors MONOMIND_CWD) rather than
30
+ // process.cwd(), which is trivially bypassed when the MCP server runs with
31
+ // cwd "/".
32
+ // realOrResolved resolves symlinks (walking up to the longest existing
33
+ // ancestor) so a link that's lexically inside projectCwd but physically
34
+ // points outside it can't bypass this containment check — same guard
35
+ // input-guards.ts's validatePath uses, applied here against getProjectCwd()
36
+ // instead of process.cwd() since that's the whole reason this check isn't
37
+ // just a direct call to validateInput({type:'path'}).
38
+ const projectCwd = realOrResolved(getProjectCwd());
39
+ const resolved = realOrResolved(resolve(configPath));
40
+ const rel = relative(projectCwd, resolved);
41
+ if (rel.startsWith('..') || resolve(rel) === resolve('/')) {
42
+ throw new Error(`Rejected group config path outside project directory: ${resolved} (project cwd: ${projectCwd})`);
43
+ }
44
+ // OOM guard: skip files larger than 1 MB
45
+ if (statSync(configPath).size > MAX_GROUP_CONFIG_BYTES) {
46
+ throw new Error(`Group config file too large (> ${MAX_GROUP_CONFIG_BYTES} bytes): ${configPath}`);
47
+ }
48
+ return parseGroupConfig(configPath).repos;
49
+ }
50
+ /**
51
+ * Compute whether a repo's monograph index is stale relative to HEAD, the
52
+ * same way monograph_staleness does: compare the last indexed commit hash
53
+ * against `git rev-list --count <lastCommit>..HEAD`. Mirrors the threshold
54
+ * used by monograph-tools.ts (STALENESS_THRESHOLD = 10).
55
+ */
56
+ function isRepoStale(db, repoPath) {
15
57
  try {
16
- // Guard: only allow paths within cwd to prevent traversal to /etc/passwd etc.
17
- const resolved = resolve(configPath);
18
- const rel = relative(process.cwd(), resolved);
19
- if (rel.startsWith('..') || resolve(rel) === resolve('/'))
20
- return [];
21
- // OOM guard: skip files larger than 1 MB
22
- if (statSync(configPath).size > MAX_GROUP_CONFIG_BYTES)
23
- return [];
24
- const raw = readFileSync(configPath, 'utf-8');
25
- return JSON.parse(raw);
58
+ const meta = (db.prepare("SELECT value FROM index_meta WHERE key = 'last_commit_hash'").get()
59
+ ?? db.prepare("SELECT value FROM index_meta WHERE key = 'lastCommit'").get());
60
+ const lastCommit = meta?.value;
61
+ if (!lastCommit || !/^[0-9a-f]{7,40}$/i.test(lastCommit))
62
+ return false;
63
+ const out = execSync(`git rev-list --count ${lastCommit}..HEAD`, {
64
+ cwd: repoPath, encoding: 'utf-8',
65
+ }).trim();
66
+ return parseInt(out, 10) > 10;
26
67
  }
27
68
  catch {
28
- return [];
69
+ return false;
29
70
  }
30
71
  }
31
72
  // ── getGroupContracts ─────────────────────────────────────────────────────────
@@ -74,11 +115,12 @@ export async function getGroupStatus(configPath) {
74
115
  const nc = countNodes(db);
75
116
  const contracts = db.prepare("SELECT COUNT(*) as n FROM nodes WHERE label = 'Route'").get();
76
117
  const meta = db.prepare("SELECT value FROM index_meta WHERE key IN ('ua_last_commit','lastCommit') LIMIT 1").get();
118
+ const stale = isRepoStale(db, repoPath);
77
119
  closeDb(db);
78
120
  groups.push({
79
121
  name,
80
122
  indexed: nc > 0,
81
- stale: false,
123
+ stale,
82
124
  contractCount: contracts?.n ?? 0,
83
125
  ...(meta?.value ? { lastSync: meta.value } : {}),
84
126
  });