@monoes/monomindcli 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/handlers/route-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +14 -8
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +129 -57
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +134 -15
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +296 -41
- package/.claude/helpers/session.cjs +89 -32
- package/.claude/helpers/statusline.cjs +138 -2
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +96 -4
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +329 -37
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +295 -5
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/transfer/serialization/cfp.js +1 -1
- package/dist/src/transfer/serialization/cfp.js.map +1 -1
- package/dist/src/ui/dashboard.html +673 -6
- package/dist/src/ui/server.mjs +144 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- 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
|
-
|
|
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 =>
|
|
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 =>
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
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
|
|
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 =>
|
|
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
|
|
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 =>
|
|
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,
|
|
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(
|
|
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,
|
|
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:
|
|
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) =>
|
|
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(
|
|
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
|
|
513
|
-
|
|
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 => ` +
|
|
611
|
-
section('Removed nodes', diff.removedNodes.map(n => ` -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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} ${
|
|
1721
|
+
`[${result.node.label}] ${result.node.name} ${nodeLoc}`,
|
|
1435
1722
|
`Neighbors: ${result.neighbors.length}`,
|
|
1436
1723
|
'',
|
|
1437
|
-
...result.neighbors.map(n =>
|
|
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
|
}
|