@monoes/monomindcli 1.12.0 → 1.14.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 (61) hide show
  1. package/.claude/agents/generated/churn-analyst.md +53 -0
  2. package/.claude/agents/generated/code-reviewer.md +55 -0
  3. package/.claude/agents/generated/code-validator.md +57 -0
  4. package/.claude/agents/generated/complexity-scanner.md +56 -0
  5. package/.claude/agents/generated/devbot-orchestrator.md +58 -0
  6. package/.claude/agents/generated/devbot-planner.md +63 -0
  7. package/.claude/agents/generated/impact-assessor.md +54 -0
  8. package/.claude/commands/mastermind/master.md +88 -24
  9. package/.claude/helpers/control-start.cjs +60 -1
  10. package/.claude/helpers/event-logger.cjs +43 -2
  11. package/.claude/helpers/handlers/capture-handler.cjs +336 -0
  12. package/.claude/helpers/handlers/route-handler.cjs +20 -13
  13. package/.claude/helpers/handlers/session-restore-handler.cjs +14 -8
  14. package/.claude/helpers/hook-handler.cjs +57 -1
  15. package/.claude/helpers/intelligence.cjs +129 -57
  16. package/.claude/helpers/memory-palace.cjs +461 -0
  17. package/.claude/helpers/memory.cjs +134 -15
  18. package/.claude/helpers/metrics-db.mjs +87 -0
  19. package/.claude/helpers/router.cjs +296 -41
  20. package/.claude/helpers/session.cjs +107 -32
  21. package/.claude/helpers/statusline.cjs +138 -2
  22. package/.claude/helpers/toggle-statusline.cjs +73 -0
  23. package/.claude/helpers/token-tracker.cjs +934 -0
  24. package/.claude/helpers/utils/monograph.cjs +39 -4
  25. package/.claude/helpers/utils/telemetry.cjs +3 -3
  26. package/.claude/skills/mastermind/createorg.md +227 -16
  27. package/.claude/skills/mastermind/idea.md +15 -3
  28. package/.claude/skills/mastermind/runorg.md +2 -1
  29. package/dist/src/commands/doctor.d.ts.map +1 -1
  30. package/dist/src/commands/doctor.js +96 -4
  31. package/dist/src/commands/doctor.js.map +1 -1
  32. package/dist/src/commands/index.js +2 -0
  33. package/dist/src/commands/org.d.ts +4 -0
  34. package/dist/src/commands/org.d.ts.map +1 -0
  35. package/dist/src/commands/org.js +93 -0
  36. package/dist/src/commands/org.js.map +1 -0
  37. package/dist/src/mcp-tools/memory-tools.js +6 -6
  38. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  39. package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
  40. package/dist/src/mcp-tools/monograph-tools.js +329 -37
  41. package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
  42. package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
  43. package/dist/src/mcp-tools/session-tools.js +9 -10
  44. package/dist/src/mcp-tools/session-tools.js.map +1 -1
  45. package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
  46. package/dist/src/mcp-tools/task-tools.js +7 -8
  47. package/dist/src/mcp-tools/task-tools.js.map +1 -1
  48. package/dist/src/mcp-tools/types.d.ts +1 -0
  49. package/dist/src/mcp-tools/types.d.ts.map +1 -1
  50. package/dist/src/mcp-tools/types.js +49 -0
  51. package/dist/src/mcp-tools/types.js.map +1 -1
  52. package/dist/src/services/worker-daemon.d.ts.map +1 -1
  53. package/dist/src/services/worker-daemon.js +295 -5
  54. package/dist/src/services/worker-daemon.js.map +1 -1
  55. package/dist/src/transfer/serialization/cfp.js +1 -1
  56. package/dist/src/transfer/serialization/cfp.js.map +1 -1
  57. package/dist/src/ui/dashboard.html +2235 -178
  58. package/dist/src/ui/orgs.html +1 -0
  59. package/dist/src/ui/server.mjs +532 -133
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +1 -1
@@ -22,6 +22,7 @@ const monographBuildTool = {
22
22
  path: { type: 'string', description: 'Absolute path to the repo (defaults to project cwd)' },
23
23
  codeOnly: { type: 'boolean', description: 'Only index code files (skip docs, config)' },
24
24
  force: { type: 'boolean', description: 'Force full rebuild even if index is fresh' },
25
+ incremental: { type: 'boolean', description: 'Skip rebuild when index already matches HEAD (default false). Use when you want a no-op if the graph is fresh.' },
25
26
  },
26
27
  },
27
28
  handler: async (input) => {
@@ -31,9 +32,12 @@ const monographBuildTool = {
31
32
  await buildAsync(repoPath, {
32
33
  codeOnly: input.codeOnly ?? false,
33
34
  force: input.force ?? false,
35
+ incremental: input.incremental ?? false,
34
36
  onProgress: (p) => { progressLog += `[${p.phase}] ${p.message ?? ''}\n`; },
35
37
  });
36
- return text(`Monograph build complete for ${repoPath}\n${progressLog}`);
38
+ const skipped = progressLog.includes('skipping rebuild');
39
+ const summary = skipped ? `Index was already fresh — no rebuild needed for ${repoPath}` : `Monograph build complete for ${repoPath}`;
40
+ return text(`${summary}\n${progressLog}`);
37
41
  },
38
42
  };
39
43
  // ── monograph_query ───────────────────────────────────────────────────────────
@@ -73,13 +77,19 @@ const monographQueryTool = {
73
77
  const results = await hybridQuery(db, query, { limit, label });
74
78
  if (results.length === 0)
75
79
  return text('No results found.');
76
- const lines = results.map(r => `[${r.label ?? '?'}] ${r.name ?? r.id} ${r.filePath ?? ''} (score: ${r.score.toFixed(4)})`);
80
+ const lines = results.map(r => {
81
+ const loc = r.filePath ? (r.startLine != null ? `${r.filePath}:${r.startLine}` : r.filePath) : '';
82
+ return `[${r.label ?? '?'}] ${r.name ?? r.id} ${loc} (score: ${r.score.toFixed(4)})`;
83
+ });
77
84
  return text(lines.join('\n'));
78
85
  }
79
86
  const results = ftsSearch(db, query, limit, label);
80
87
  if (results.length === 0)
81
88
  return text('No results found.');
82
- const lines = results.map(r => `[${r.label}] ${r.name} ${r.filePath ?? ''} (score: ${r.rank.toFixed(3)})`);
89
+ const lines = results.map(r => {
90
+ const loc = r.filePath ? (r.startLine != null ? `${r.filePath}:${r.startLine}` : r.filePath) : '';
91
+ return `[${r.label}] ${r.name} ${loc} (score: ${r.rank.toFixed(3)})`;
92
+ });
83
93
  return text(lines.join('\n'));
84
94
  }
85
95
  finally {
@@ -117,7 +127,9 @@ const monographHealthTool = {
117
127
  const { execSync } = await import('child_process');
118
128
  const db = openDb(getDbPath());
119
129
  try {
120
- const meta = db.prepare("SELECT value FROM index_meta WHERE key = 'lastCommit'").get();
130
+ // The orchestrator writes the key as 'last_commit_hash' (orchestrator.ts:68).
131
+ // Fall back to legacy 'lastCommit' for indexes built with older versions.
132
+ const meta = db.prepare("SELECT value FROM index_meta WHERE key = 'last_commit_hash'").get() ?? db.prepare("SELECT value FROM index_meta WHERE key = 'lastCommit'").get();
121
133
  const lastCommit = meta?.value ?? null;
122
134
  if (!lastCommit)
123
135
  return text('Index has never been built. Run monograph_build first.');
@@ -162,7 +174,7 @@ const monographGodNodesTool = {
162
174
  : 20;
163
175
  const excluded = ['File', 'Folder', 'Community', 'Concept'];
164
176
  const rows = db.prepare(`
165
- SELECT n.id, n.label, n.name, n.file_path,
177
+ SELECT n.id, n.label, n.name, n.file_path, n.start_line,
166
178
  COUNT(DISTINCT e1.id) + COUNT(DISTINCT e2.id) AS degree,
167
179
  COUNT(DISTINCT e2.id) AS in_degree,
168
180
  COUNT(DISTINCT e1.id) AS out_degree
@@ -175,7 +187,10 @@ const monographGodNodesTool = {
175
187
  `).all(...excluded, limit);
176
188
  if (rows.length === 0)
177
189
  return text('No god nodes found. Run monograph_build first.');
178
- const lines = rows.map(r => `[${r.label}] ${r.name} degree=${r.degree} (↑${r.out_degree} ↓${r.in_degree}) ${r.file_path ?? ''}`);
190
+ const lines = rows.map(r => {
191
+ const loc = r.file_path ? (r.start_line != null ? `${r.file_path}:${r.start_line}` : r.file_path) : '';
192
+ return `[${r.label}] ${r.name} degree=${r.degree} (↑${r.out_degree} ↓${r.in_degree}) ${loc}`;
193
+ });
179
194
  return text(lines.join('\n'));
180
195
  }
181
196
  finally {
@@ -233,7 +248,15 @@ const monographShortestPathTool = {
233
248
  const path = getShortestPath(db, input.source, input.target, input.maxDepth ?? 6);
234
249
  if (!path)
235
250
  return text(`No path found between ${input.source} and ${input.target}`);
236
- return text(`Path (${path.length - 1} hops):\n${path.join(' ')}`);
251
+ // Enrich each node ID with file:line for direct LLM navigation
252
+ const enriched = path.map(nodeId => {
253
+ const row = db.prepare('SELECT label, name, file_path, start_line FROM nodes WHERE id = ? OR name = ? LIMIT 1').get(nodeId, nodeId);
254
+ if (!row)
255
+ return nodeId;
256
+ const loc = row.file_path ? (row.start_line != null ? `${row.file_path}:${row.start_line}` : row.file_path) : '';
257
+ return loc ? `${row.name ?? nodeId} [${loc}]` : (row.name ?? nodeId);
258
+ });
259
+ return text(`Path (${path.length - 1} hops):\n${enriched.join(' → ')}`);
237
260
  }
238
261
  finally {
239
262
  closeDb(db);
@@ -263,10 +286,13 @@ const monographCommunityTool = {
263
286
  const { openDb, closeDb } = await import('@monoes/monograph');
264
287
  const db = openDb(getDbPath());
265
288
  try {
266
- const rows = db.prepare('SELECT * FROM nodes WHERE community_id = ?').all(communityId);
289
+ const rows = db.prepare('SELECT id, label, name, file_path, start_line FROM nodes WHERE community_id = ?').all(communityId);
267
290
  if (rows.length === 0)
268
291
  return text(`No nodes in community ${communityId}`);
269
- return text(rows.map(r => `[${r.label}] ${r.name} ${r.file_path ?? ''}`).join('\n'));
292
+ return text(rows.map(r => {
293
+ const loc = r.file_path ? (r.start_line != null ? `${r.file_path}:${r.start_line}` : r.file_path) : '';
294
+ return `[${r.label}] ${r.name} ${loc}`;
295
+ }).join('\n'));
270
296
  }
271
297
  finally {
272
298
  closeDb(db);
@@ -292,7 +318,9 @@ const monographSurprisesTool = {
292
318
  ? Math.min(Math.floor(rawSurprisesLimit), MAX_SURPRISES_LIMIT)
293
319
  : 20;
294
320
  const rows = db.prepare(`
295
- SELECT e.*, n1.name as src_name, n2.name as tgt_name
321
+ SELECT e.confidence, e.confidence_score, e.relation,
322
+ n1.name as src_name, n1.file_path as src_file, n1.start_line as src_line,
323
+ n2.name as tgt_name, n2.file_path as tgt_file, n2.start_line as tgt_line
296
324
  FROM edges e
297
325
  JOIN nodes n1 ON n1.id = e.source_id
298
326
  JOIN nodes n2 ON n2.id = e.target_id
@@ -301,7 +329,12 @@ const monographSurprisesTool = {
301
329
  `).all(limit);
302
330
  if (rows.length === 0)
303
331
  return text('No surprising connections found.');
304
- return text(rows.map(r => `[${r.confidence}] ${r.src_name} --${r.relation}--> ${r.tgt_name} (score: ${r.confidence_score})`).join('\n'));
332
+ return text(rows.map(r => {
333
+ const srcLoc = r.src_file ? (r.src_line != null ? `${r.src_file}:${r.src_line}` : r.src_file) : '';
334
+ const tgtLoc = r.tgt_file ? (r.tgt_line != null ? `${r.tgt_file}:${r.tgt_line}` : r.tgt_file) : '';
335
+ const locHint = srcLoc || tgtLoc ? ` [${srcLoc}${tgtLoc ? ` → ${tgtLoc}` : ''}]` : '';
336
+ return `[${r.confidence}] ${r.src_name} --${r.relation}--> ${r.tgt_name} (score: ${r.confidence_score})${locHint}`;
337
+ }).join('\n'));
305
338
  }
306
339
  finally {
307
340
  closeDb(db);
@@ -336,6 +369,14 @@ const monographSuggestTool = {
336
369
  const task = typeof rawTask === 'string' && rawTask.length > MAX_SUGGEST_TASK_LEN
337
370
  ? rawTask.slice(0, MAX_SUGGEST_TASK_LEN)
338
371
  : rawTask;
372
+ // Format a suggestion row as a navigable string for LLM consumption.
373
+ // Includes file:line references so the LLM can jump directly to the code.
374
+ const formatSuggestion = (r) => {
375
+ const srcLoc = r.src_file ? (r.src_line != null ? `${r.src_file}:${r.src_line}` : r.src_file) : '';
376
+ const tgtLoc = r.tgt_file ? (r.tgt_line != null ? `${r.tgt_file}:${r.tgt_line}` : r.tgt_file) : '';
377
+ const locHint = srcLoc ? ` [${srcLoc}${tgtLoc ? ` → ${tgtLoc}` : ''}]` : '';
378
+ return `Why does ${r.src} ${r.relation.toLowerCase()} ${r.tgt}? (${r.confidence})${locHint}`;
379
+ };
339
380
  // When a task is provided and embeddings are enabled, use semantic search
340
381
  // to find relevant nodes and surface edge-level questions about them.
341
382
  if (task && process.env['MONOGRAPH_EMBEDDINGS'] === 'true') {
@@ -345,7 +386,9 @@ const monographSuggestTool = {
345
386
  return text('No suggestions for this task. Run monograph_build first or try a different query.');
346
387
  }
347
388
  const rows = db.prepare(`
348
- SELECT e.relation, e.confidence, n1.name as src, n2.name as tgt, n1.file_path as src_file
389
+ SELECT e.relation, e.confidence, n1.name as src, n2.name as tgt,
390
+ n1.file_path as src_file, n1.start_line as src_line,
391
+ n2.file_path as tgt_file, n2.start_line as tgt_line
349
392
  FROM edges e
350
393
  JOIN nodes n1 ON n1.id = e.source_id
351
394
  JOIN nodes n2 ON n2.id = e.target_id
@@ -354,11 +397,13 @@ const monographSuggestTool = {
354
397
  AND e.confidence IN ('AMBIGUOUS', 'INFERRED')
355
398
  LIMIT 100
356
399
  `).all(...[...hitIds], ...[...hitIds]);
357
- const questions = rows.map(r => `Why does ${r.src} ${r.relation.toLowerCase()} ${r.tgt}? (${r.confidence})`);
400
+ const questions = rows.map(formatSuggestion);
358
401
  return text(questions.slice(0, limit).join('\n') || 'No suggestions for this task. Run monograph_build first.');
359
402
  }
360
403
  const rows = db.prepare(`
361
- SELECT e.relation, e.confidence, n1.name as src, n2.name as tgt, n1.file_path as src_file
404
+ SELECT e.relation, e.confidence, n1.name as src, n2.name as tgt,
405
+ n1.file_path as src_file, n1.start_line as src_line,
406
+ n2.file_path as tgt_file, n2.start_line as tgt_line
362
407
  FROM edges e
363
408
  JOIN nodes n1 ON n1.id = e.source_id
364
409
  JOIN nodes n2 ON n2.id = e.target_id
@@ -366,7 +411,7 @@ const monographSuggestTool = {
366
411
  LIMIT 100
367
412
  `).all();
368
413
  let scored = rows.map(r => ({
369
- q: `Why does ${r.src} ${r.relation.toLowerCase()} ${r.tgt}? (${r.confidence})`,
414
+ q: formatSuggestion(r),
370
415
  relevance: task ? taskRelevance(task, r.src + ' ' + r.tgt + ' ' + (r.src_file ?? '')) : 0,
371
416
  }));
372
417
  if (task)
@@ -467,7 +512,7 @@ const monographReportTool = {
467
512
  const nodeCount = countNodes(db);
468
513
  const edgeCount = countEdges(db);
469
514
  const topNodes = db.prepare(`
470
- SELECT n.name, n.label, n.file_path,
515
+ SELECT n.name, n.label, n.file_path, n.start_line,
471
516
  COUNT(DISTINCT e1.id) + COUNT(DISTINCT e2.id) AS degree
472
517
  FROM nodes n
473
518
  LEFT JOIN edges e1 ON e1.source_id = n.id
@@ -480,7 +525,10 @@ const monographReportTool = {
480
525
  `**Generated:** ${new Date().toISOString()}`,
481
526
  `**Nodes:** ${nodeCount} **Edges:** ${edgeCount}\n`,
482
527
  '## Top 10 Most Connected Entities\n',
483
- ...topNodes.map((n, i) => `${i + 1}. **${n.name}** (${n.label}) — degree ${n.degree} \`${n.file_path ?? ''}\``),
528
+ ...topNodes.map((n, i) => {
529
+ const loc = n.file_path ? (n.start_line != null ? `${n.file_path}:${n.start_line}` : n.file_path) : '';
530
+ return `${i + 1}. **${n.name}** (${n.label}) — degree ${n.degree}${loc ? ` \`${loc}\`` : ''}`;
531
+ }),
484
532
  ].join('\n');
485
533
  const outPath = resolve(input.path ?? join(getProjectCwd(), '.monomind', 'GRAPH_REPORT.md'));
486
534
  const allowedRoot = resolve(getProjectCwd());
@@ -489,7 +537,7 @@ const monographReportTool = {
489
537
  }
490
538
  mkdirSync(join(outPath, '..'), { recursive: true });
491
539
  writeFileSync(outPath, report);
492
- return text(`Report written to ${outPath}`);
540
+ return text(`${report}\n\nReport written to ${outPath}`);
493
541
  }
494
542
  finally {
495
543
  closeDb(db);
@@ -509,8 +557,24 @@ const monographStalenessTool = {
509
557
  handler: async (input) => {
510
558
  const { getMonographStaleness } = await import('@monoes/monograph');
511
559
  const repoPath = input.path ?? getProjectCwd();
512
- const report = await getMonographStaleness(repoPath);
513
- return text(JSON.stringify(report, null, 2));
560
+ const r = await getMonographStaleness(repoPath);
561
+ if (!r.indexedCommit && !r.currentCommit) {
562
+ return text('Index has never been built or repo has no git history. Run monograph_build first.');
563
+ }
564
+ const statusLine = r.isStale
565
+ ? `STALE — index at ${r.indexedCommit}, HEAD at ${r.currentCommit}`
566
+ : `FRESH — index matches HEAD (${r.currentCommit})`;
567
+ const lines = [`Staleness: ${statusLine}`];
568
+ if (r.staleSince)
569
+ lines.push(`Stale since: ${r.staleSince}`);
570
+ if (r.changedSince.length > 0) {
571
+ const shown = r.changedSince.slice(0, 10);
572
+ const more = r.changedSince.length - shown.length;
573
+ lines.push(`Changed files (${r.changedSince.length}):${shown.map(f => `\n ${f}`).join('')}${more > 0 ? `\n … ${more} more` : ''}`);
574
+ }
575
+ if (r.isStale)
576
+ lines.push('Action: run monograph_build to re-index');
577
+ return text(lines.join('\n'));
514
578
  },
515
579
  };
516
580
  // ── monograph_snapshot ────────────────────────────────────────────────────────
@@ -604,13 +668,33 @@ const monographDiffTool = {
604
668
  }
605
669
  }
606
670
  const diff = diffSnapshots(before, after);
671
+ const nodeById = new Map();
672
+ const indexNodes = (nodes) => {
673
+ for (const n of nodes) {
674
+ if (n.id)
675
+ nodeById.set(n.id, n);
676
+ }
677
+ };
678
+ indexNodes(before.nodes);
679
+ indexNodes(after.nodes);
680
+ const resolveEdgeEnd = (id) => {
681
+ const ref = nodeById.get(id);
682
+ if (!ref)
683
+ return id; // fallback to raw id if not found
684
+ const loc = ref.filePath ? (ref.startLine != null ? `${ref.filePath}:${ref.startLine}` : ref.filePath) : '';
685
+ return loc ? `${ref.name} [${loc}]` : ref.name;
686
+ };
607
687
  const section = (label, items) => items.length > 0 ? `\n${label} (${items.length}):\n${items.slice(0, 10).join('\n')}${items.length > 10 ? `\n … ${items.length - 10} more` : ''}` : '';
688
+ const formatNode = (n) => {
689
+ const loc = n.filePath ? (n.startLine != null ? `${n.filePath}:${n.startLine}` : n.filePath) : '';
690
+ return ` [${n.label ?? '?'}] ${n.name ?? '?'}${loc ? ` ${loc}` : ''}`;
691
+ };
608
692
  const lines = [
609
693
  `Diff: ${diff.summary}`,
610
- section('New nodes', diff.newNodes.map(n => ` + [${n.label}] ${n.name} ${n.filePath ?? ''}`)),
611
- section('Removed nodes', diff.removedNodes.map(n => ` - [${n.label}] ${n.name} ${n.filePath ?? ''}`)),
612
- section('New edges', diff.newEdges.map(e => ` + ${e.sourceId} --[${e.relation}]--> ${e.targetId}`)),
613
- section('Removed edges', diff.removedEdges.map(e => ` - ${e.sourceId} --[${e.relation}]--> ${e.targetId}`)),
694
+ section('New nodes', diff.newNodes.map(n => ` + ${formatNode(n)}`)),
695
+ section('Removed nodes', diff.removedNodes.map(n => ` - ${formatNode(n)}`)),
696
+ section('New edges', diff.newEdges.map(e => ` + ${resolveEdgeEnd(e.sourceId)} --[${e.relation}]--> ${resolveEdgeEnd(e.targetId)}`)),
697
+ section('Removed edges', diff.removedEdges.map(e => ` - ${resolveEdgeEnd(e.sourceId)} --[${e.relation}]--> ${resolveEdgeEnd(e.targetId)}`)),
614
698
  ].join('');
615
699
  return text(lines);
616
700
  },
@@ -700,7 +784,38 @@ const monographContextTool = {
700
784
  name: ctxName,
701
785
  filePath: ctxPath,
702
786
  });
703
- return text(JSON.stringify(result, null, 2));
787
+ if (!result || !result.node)
788
+ return text(`No symbol found: ${ctxName}`);
789
+ // Format context as structured text for direct LLM consumption
790
+ const n = result.node;
791
+ const loc = n.filePath ? (n.startLine != null ? `${n.filePath}:${n.startLine}` : n.filePath) : '';
792
+ const lines = [
793
+ `[${n.label ?? '?'}] ${n.name} ${loc}`,
794
+ '',
795
+ ];
796
+ const formatNodes = (nodes, label) => {
797
+ if (!Array.isArray(nodes) || nodes.length === 0)
798
+ return;
799
+ lines.push(`${label} (${nodes.length}):`);
800
+ for (const node of nodes.slice(0, 20)) {
801
+ const fp = node.filePath ?? node.file_path ?? '';
802
+ const ln = node.startLine ?? node.start_line;
803
+ const nodeLoc = fp ? (ln != null ? `${fp}:${ln}` : fp) : '';
804
+ lines.push(` [${node.label ?? '?'}] ${node.name ?? node.id} ${nodeLoc}`);
805
+ }
806
+ if (nodes.length > 20)
807
+ lines.push(` … ${nodes.length - 20} more`);
808
+ lines.push('');
809
+ };
810
+ formatNodes(result.callers, 'Callers');
811
+ formatNodes(result.callees, 'Callees');
812
+ formatNodes(result.imports, 'Imports');
813
+ formatNodes(result.importedBy, 'ImportedBy');
814
+ if (result.community != null)
815
+ lines.push(`Community: ${result.community}`);
816
+ if (result.communityName)
817
+ lines.push(`Community name: ${result.communityName}`);
818
+ return text(lines.join('\n').trim());
704
819
  }
705
820
  finally {
706
821
  closeDb(db);
@@ -742,7 +857,35 @@ const monographImpactTool = {
742
857
  filePath: impactPath,
743
858
  depth,
744
859
  });
745
- return text(JSON.stringify(result, null, 2));
860
+ if (!result || !result.root)
861
+ return text(`No symbol found: ${impactName}`);
862
+ // Format impact as structured text for direct LLM consumption
863
+ const root = result.root;
864
+ const rootLoc = root.filePath ? (root.startLine != null ? `${root.filePath}:${root.startLine}` : root.filePath) : '';
865
+ const lines = [
866
+ `[${root.label ?? '?'}] ${root.name} ${rootLoc}`,
867
+ '',
868
+ `Blast radius: ${result.totalAffected ?? 0} symbols affected`,
869
+ ];
870
+ if (result.riskScore != null) {
871
+ const riskLabel = result.riskScore >= 0.8 ? 'HIGH' : result.riskScore >= 0.5 ? 'MEDIUM' : 'LOW';
872
+ lines.push(`Risk score: ${result.riskScore.toFixed(2)} (${riskLabel})`);
873
+ }
874
+ lines.push('');
875
+ const affected = (result.affected ?? result.callers ?? []);
876
+ if (affected.length > 0) {
877
+ lines.push(`Affected callers (${affected.length}):`);
878
+ for (const sym of affected.slice(0, 20)) {
879
+ const fp = sym.filePath ?? sym.file_path ?? '';
880
+ const ln = sym.startLine ?? sym.start_line;
881
+ const symLoc = fp ? (ln != null ? `${fp}:${ln}` : fp) : '';
882
+ const depth_marker = sym.depth != null ? ` [depth ${sym.depth}]` : '';
883
+ lines.push(` [${sym.label ?? '?'}] ${sym.name ?? sym.id} ${symLoc}${depth_marker}`);
884
+ }
885
+ if (affected.length > 20)
886
+ lines.push(` … ${affected.length - 20} more`);
887
+ }
888
+ return text(lines.join('\n').trim());
746
889
  }
747
890
  finally {
748
891
  closeDb(db);
@@ -769,7 +912,35 @@ const monographDetectChangesTool = {
769
912
  baseBranch: input.baseBranch,
770
913
  includeTests: input.includeTests,
771
914
  }, getProjectCwd());
772
- return text(JSON.stringify(result, null, 2));
915
+ // Format as structured text for direct LLM navigation instead of raw JSON
916
+ const r = result;
917
+ if (!r || (!r.changedFiles?.length && !r.affectedSymbols?.length)) {
918
+ return text('No changed files found relative to the base branch.');
919
+ }
920
+ const lines = [];
921
+ const base = r.baseBranch ?? 'main';
922
+ const changedFiles = r.changedFiles ?? [];
923
+ lines.push(`Changed files vs ${base}: ${changedFiles.length}`);
924
+ if (changedFiles.length > 0) {
925
+ for (const f of changedFiles.slice(0, 20))
926
+ lines.push(` ${f}`);
927
+ if (changedFiles.length > 20)
928
+ lines.push(` … ${changedFiles.length - 20} more`);
929
+ }
930
+ lines.push('');
931
+ const affected = r.affectedSymbols ?? r.affected ?? [];
932
+ if (affected.length > 0) {
933
+ lines.push(`Affected symbols (${affected.length}):`);
934
+ for (const sym of affected.slice(0, 30)) {
935
+ const fp = sym.filePath ?? sym.file_path ?? '';
936
+ const ln = sym.startLine ?? sym.start_line;
937
+ const loc = fp ? (ln != null ? `${fp}:${ln}` : fp) : '';
938
+ lines.push(` [${sym.label ?? '?'}] ${sym.name ?? sym.id} ${loc}`);
939
+ }
940
+ if (affected.length > 30)
941
+ lines.push(` … ${affected.length - 30} more`);
942
+ }
943
+ return text(lines.join('\n').trim());
773
944
  }
774
945
  finally {
775
946
  closeDb(db);
@@ -801,7 +972,25 @@ const monographRenameTool = {
801
972
  filePath: input.filePath,
802
973
  dryRun: input.dryRun ?? true,
803
974
  });
804
- return text(JSON.stringify(result, null, 2));
975
+ // Format as structured text for direct LLM navigation instead of raw JSON
976
+ const rn = result;
977
+ if (!rn)
978
+ return text(`Symbol not found: ${input.oldName}`);
979
+ const occurrences = rn.occurrences ?? rn.references ?? [];
980
+ const lines = [
981
+ `Rename: ${input.oldName} → ${input.newName} (dry-run)`,
982
+ `Occurrences: ${occurrences.length}`,
983
+ '',
984
+ ];
985
+ for (const occ of occurrences.slice(0, 30)) {
986
+ const fp = occ.filePath ?? occ.file_path ?? '';
987
+ const ln = occ.line ?? occ.startLine ?? occ.start_line;
988
+ const loc = fp ? (ln != null ? `${fp}:${ln}` : fp) : '';
989
+ lines.push(` ${loc || occ}`);
990
+ }
991
+ if (occurrences.length > 30)
992
+ lines.push(` … ${occurrences.length - 30} more`);
993
+ return text(lines.join('\n').trim());
805
994
  }
806
995
  finally {
807
996
  closeDb(db);
@@ -830,7 +1019,17 @@ const monographRouteMapTool = {
830
1019
  method: input.method,
831
1020
  includeMiddleware: input.includeMiddleware,
832
1021
  });
833
- return text(JSON.stringify(result, null, 2));
1022
+ if (result.routes.length === 0)
1023
+ return text('No routes found. Run monograph_build first or adjust your filters.');
1024
+ const lines = [`Routes (${result.total} total):`];
1025
+ for (const r of result.routes) {
1026
+ const loc = r.handlerFile
1027
+ ? (r.handlerLine != null ? `${r.handlerFile}:${r.handlerLine}` : r.handlerFile)
1028
+ : '';
1029
+ const mw = r.middlewareChain.length > 0 ? ` middleware: ${r.middlewareChain.join(' → ')}` : '';
1030
+ lines.push(` ${r.method} ${r.path}${r.handlerName ? ` → ${r.handlerName}` : ''}${loc ? ` (${loc})` : ''}${mw}`);
1031
+ }
1032
+ return text(lines.join('\n'));
834
1033
  }
835
1034
  finally {
836
1035
  closeDb(db);
@@ -858,7 +1057,33 @@ const monographApiImpactTool = {
858
1057
  routePath: input.routePath,
859
1058
  method: input.method,
860
1059
  });
861
- return text(JSON.stringify(result, null, 2));
1060
+ if (!result.route)
1061
+ return text(`Route not found: ${input.routePath}. Run monograph_build or check the path.`);
1062
+ const riskLabel = result.riskScore >= 0.7 ? 'HIGH' : result.riskScore >= 0.4 ? 'MEDIUM' : 'LOW';
1063
+ const lines = [
1064
+ `Route: ${result.route.method} ${result.route.path} risk=${riskLabel} (${result.riskScore.toFixed(2)})`,
1065
+ ];
1066
+ if (result.handler) {
1067
+ const hLoc = result.handler.filePath
1068
+ ? (result.handler.startLine != null ? `${result.handler.filePath}:${result.handler.startLine}` : result.handler.filePath)
1069
+ : '';
1070
+ lines.push(`Handler: ${result.handler.name}${hLoc ? ` ${hLoc}` : ''}`);
1071
+ }
1072
+ if (result.callees.length > 0) {
1073
+ lines.push(`Callees (${result.callees.length}):`);
1074
+ for (const c of result.callees.slice(0, 15)) {
1075
+ const loc = c.node.filePath
1076
+ ? (c.node.startLine != null ? `${c.node.filePath}:${c.node.startLine}` : c.node.filePath)
1077
+ : '';
1078
+ lines.push(` ${' '.repeat(c.depth)}→ ${c.node.name} [${c.node.label}]${loc ? ` ${loc}` : ''}`);
1079
+ }
1080
+ if (result.callees.length > 15)
1081
+ lines.push(` … ${result.callees.length - 15} more`);
1082
+ }
1083
+ if (result.affectedProcesses.length > 0) {
1084
+ lines.push(`Affected processes: ${result.affectedProcesses.map(p => p.name).join(', ')}`);
1085
+ }
1086
+ return text(lines.join('\n'));
862
1087
  }
863
1088
  finally {
864
1089
  closeDb(db);
@@ -947,7 +1172,15 @@ const monographGroupListTool = {
947
1172
  const { getGroupList } = await import('@monoes/monograph');
948
1173
  const configPath = input.configPath ?? join(getProjectCwd(), 'group.yaml');
949
1174
  const result = await getGroupList(configPath);
950
- return text(JSON.stringify(result, null, 2));
1175
+ if (!result.repos || result.repos.length === 0) {
1176
+ return text(`Group: ${result.group?.name ?? 'unknown'}\nNo repos configured. Check ${configPath}`);
1177
+ }
1178
+ const lines = [`Group: ${result.group?.name ?? 'unknown'} (${result.repos.length} repos)`];
1179
+ for (const r of result.repos) {
1180
+ const indexed = r.indexedAt ? r.indexedAt.slice(0, 10) : 'never';
1181
+ lines.push(` ${r.name} nodes=${r.nodeCount} indexed=${indexed} ${r.path}`);
1182
+ }
1183
+ return text(lines.join('\n'));
951
1184
  },
952
1185
  };
953
1186
  // ── monograph_group_query ─────────────────────────────────────────────────────
@@ -1000,7 +1233,12 @@ const monographWikiTool = {
1000
1233
  const db = openDb(getDbPath());
1001
1234
  try {
1002
1235
  const result = getWikiToolResult(db, { communityId: input.communityId });
1003
- return text(JSON.stringify(result, null, 2));
1236
+ if (result.pages.length === 0) {
1237
+ return text('No wiki pages found. Run monograph_wiki_build to generate community wiki pages.');
1238
+ }
1239
+ // Return pages as readable prose — content is already LLM-generated markdown.
1240
+ const sections = result.pages.map(p => `--- Community ${p.communityId} ---\n${p.content}`);
1241
+ return text(sections.join('\n\n'));
1004
1242
  }
1005
1243
  finally {
1006
1244
  closeDb(db);
@@ -1029,7 +1267,16 @@ const monographWikiBuildTool = {
1029
1267
  force: input.force,
1030
1268
  model: input.model,
1031
1269
  });
1032
- return text(JSON.stringify(result, null, 2));
1270
+ if (result.error)
1271
+ return text(`Wiki build failed: ${result.error}`);
1272
+ const parts = [];
1273
+ if (result.generated != null)
1274
+ parts.push(`${result.generated} page(s) generated`);
1275
+ if (result.skipped != null && result.skipped > 0)
1276
+ parts.push(`${result.skipped} skipped (already exist)`);
1277
+ if (result.errors != null && result.errors > 0)
1278
+ parts.push(`${result.errors} error(s)`);
1279
+ return text(`Wiki build complete: ${parts.join(', ') || 'nothing to do'}. Use monograph_wiki to read the pages.`);
1033
1280
  }
1034
1281
  finally {
1035
1282
  closeDb(db);
@@ -1077,7 +1324,13 @@ const monographToolMapTool = {
1077
1324
  const results = getToolMap(db, { tool: input.tool });
1078
1325
  if (results.length === 0)
1079
1326
  return text('No tools found. Run monograph_build first.');
1080
- return text(JSON.stringify(results, null, 2));
1327
+ const lines = results.map(r => {
1328
+ const loc = r.handlerFile
1329
+ ? (r.handlerLine != null ? `${r.handlerFile}:${r.handlerLine}` : r.handlerFile)
1330
+ : (r.filePath ?? '');
1331
+ return `${r.name}${r.handlerName ? ` → ${r.handlerName}` : ''}${loc ? ` (${loc})` : ''}`;
1332
+ });
1333
+ return text(`Tools (${results.length}):\n${lines.join('\n')}`);
1081
1334
  }
1082
1335
  finally {
1083
1336
  closeDb(db);
@@ -1105,7 +1358,38 @@ const monographShapeCheckTool = {
1105
1358
  route: input.route,
1106
1359
  file: input.file,
1107
1360
  });
1108
- return text(JSON.stringify(result, null, 2));
1361
+ // Render as structured text so LLMs can act on it directly without parsing JSON.
1362
+ const lines = [];
1363
+ lines.push(`Shape check: ${result.message}`);
1364
+ if (result.route) {
1365
+ const handlerLoc = result.route.handlerFile
1366
+ ? ` Handler: ${result.route.handlerName} [${result.route.handlerFile}]`
1367
+ : ` Handler: ${result.route.handlerName}`;
1368
+ lines.push(`Route: ${result.route.method} ${result.route.path}`);
1369
+ lines.push(handlerLoc);
1370
+ }
1371
+ if (result.shape.returnedKeys.length > 0) {
1372
+ lines.push(` Returned keys: ${result.shape.returnedKeys.join(', ')}`);
1373
+ }
1374
+ if (result.shape.accessedKeys.length > 0) {
1375
+ lines.push(` Accessed keys: ${result.shape.accessedKeys.join(', ')}`);
1376
+ }
1377
+ if (result.shape.mismatches.length > 0) {
1378
+ lines.push(` Mismatches (accessed but not returned): ${result.shape.mismatches.join(', ')}`);
1379
+ }
1380
+ if (result.shape.extra.length > 0) {
1381
+ lines.push(` Unused returned keys: ${result.shape.extra.join(', ')}`);
1382
+ }
1383
+ if (result.consumers.length > 0) {
1384
+ lines.push(` Consumers (${result.consumers.length}):`);
1385
+ for (const c of result.consumers.slice(0, 10)) {
1386
+ lines.push(` - ${c.name} [${c.filePath}]`);
1387
+ }
1388
+ if (result.consumers.length > 10) {
1389
+ lines.push(` … ${result.consumers.length - 10} more`);
1390
+ }
1391
+ }
1392
+ return text(lines.join('\n'));
1109
1393
  }
1110
1394
  finally {
1111
1395
  closeDb(db);
@@ -1430,11 +1714,19 @@ const monographNeighborsTool = {
1430
1714
  });
1431
1715
  if (!result.node)
1432
1716
  return text(`No node found with name: ${input.name}`);
1717
+ const nodeFilePath = result.node.filePath ?? '';
1718
+ const nodeStartLine = result.node.startLine ?? result.node.start_line;
1719
+ const nodeLoc = nodeFilePath ? (nodeStartLine != null ? `${nodeFilePath}:${nodeStartLine}` : nodeFilePath) : '';
1433
1720
  const lines = [
1434
- `[${result.node.label}] ${result.node.name} ${result.node.filePath ?? ''}`,
1721
+ `[${result.node.label}] ${result.node.name} ${nodeLoc}`,
1435
1722
  `Neighbors: ${result.neighbors.length}`,
1436
1723
  '',
1437
- ...result.neighbors.map(n => ` ${n.direction === 'inbound' ? '←' : '→'} [${n.node.label}] ${n.node.name} (${n.relation}) ${n.node.filePath ?? ''}`),
1724
+ ...result.neighbors.map(n => {
1725
+ const fp = n.node.filePath ?? n.node.file_path ?? '';
1726
+ const ln = n.node.startLine ?? n.node.start_line;
1727
+ const loc = fp ? (ln != null ? `${fp}:${ln}` : fp) : '';
1728
+ return ` ${n.direction === 'inbound' ? '←' : '→'} [${n.node.label}] ${n.node.name} (${n.relation}) ${loc}`;
1729
+ }),
1438
1730
  ];
1439
1731
  return text(lines.join('\n'));
1440
1732
  }