@monoes/monomindcli 1.7.0 → 1.8.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 (34) hide show
  1. package/.claude/commands/monomind-createtask.md +2 -2
  2. package/.claude/commands/monomind-do.md +1 -1
  3. package/.claude/commands/monomind-idea.md +1 -1
  4. package/.claude/commands/monomind-improve.md +3 -3
  5. package/.claude/helpers/learning-service.mjs +0 -0
  6. package/.claude/helpers/metrics-db.mjs +0 -0
  7. package/.claude/helpers/swarm-hooks.sh +0 -0
  8. package/LICENSE +21 -0
  9. package/dist/src/commands/doctor.d.ts.map +1 -1
  10. package/dist/src/commands/doctor.js +23 -58
  11. package/dist/src/commands/doctor.js.map +1 -1
  12. package/dist/src/init/executor.d.ts.map +1 -1
  13. package/dist/src/init/executor.js +10 -67
  14. package/dist/src/init/executor.js.map +1 -1
  15. package/dist/src/init/helpers-generator.js +14 -14
  16. package/dist/src/init/helpers-generator.js.map +1 -1
  17. package/dist/src/mcp-client.d.ts.map +1 -1
  18. package/dist/src/mcp-client.js +5 -2
  19. package/dist/src/mcp-client.js.map +1 -1
  20. package/dist/src/mcp-tools/graphify-tools.d.ts +4 -67
  21. package/dist/src/mcp-tools/graphify-tools.d.ts.map +1 -1
  22. package/dist/src/mcp-tools/graphify-tools.js +40 -1250
  23. package/dist/src/mcp-tools/graphify-tools.js.map +1 -1
  24. package/dist/src/mcp-tools/index.d.ts +1 -0
  25. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  26. package/dist/src/mcp-tools/index.js +1 -0
  27. package/dist/src/mcp-tools/index.js.map +1 -1
  28. package/dist/src/mcp-tools/monograph-tools.d.ts +9 -0
  29. package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -0
  30. package/dist/src/mcp-tools/monograph-tools.js +495 -0
  31. package/dist/src/mcp-tools/monograph-tools.js.map +1 -0
  32. package/dist/src/ui/server.mjs +5 -4
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +11 -11
@@ -1,1259 +1,49 @@
1
1
  /**
2
- * Graphify MCP Tools
2
+ * Graphify MCP Tools — DEPRECATED SHIMS
3
3
  *
4
- * Bridges graphify's (Python) knowledge graph into monomind's MCP tool surface.
5
- * Agents can query the codebase knowledge graph without reading files —
6
- * god_nodes(), query_graph(), shortest_path() give structural understanding
7
- * in milliseconds vs. reading dozens of source files.
8
- *
9
- * Graph is built by `graphify update` (Python CLI) on `monomind init` and
10
- * stored at .monomind/graph/graph.json (legacy: graphify-out/graph.json).
11
- * Install: uv tool install graphifyy
12
- */
13
- import { existsSync, readFileSync } from 'fs';
14
- import { join, resolve } from 'path';
15
- import { getProjectCwd } from './types.js';
16
- // ── Path helpers ──────────────────────────────────────────────────────────────
17
- /** Resolve graph path: prefer native monomind path, fall back to legacy graphify path. */
18
- function getGraphPath(cwd) {
19
- const nativePath = resolve(join(cwd, '.monomind', 'graph', 'graph.json'));
20
- const legacyPath = resolve(join(cwd, 'graphify-out', 'graph.json'));
21
- if (existsSync(nativePath))
22
- return nativePath;
23
- if (existsSync(legacyPath))
24
- return legacyPath;
25
- return nativePath; // return expected path even if not yet built
26
- }
27
- function graphExists(cwd) {
28
- return existsSync(getGraphPath(cwd));
29
- }
30
- /**
31
- * Load the knowledge graph.
32
- * Parses the graph.json produced by graphify (Python).
33
- */
34
- async function loadKnowledgeGraph(cwd) {
35
- const graphPath = getGraphPath(cwd);
36
- const data = JSON.parse(readFileSync(graphPath, 'utf-8'));
37
- const rawNodes = data.nodes || [];
38
- const rawEdges = data.links || data.edges || [];
39
- // Build in-memory graph structures
40
- const nodes = new Map();
41
- for (const n of rawNodes) {
42
- nodes.set(n.id, n);
43
- }
44
- const adj = new Map();
45
- const radj = new Map();
46
- const degree = new Map();
47
- for (const n of rawNodes) {
48
- adj.set(n.id, []);
49
- radj.set(n.id, []);
50
- degree.set(n.id, 0);
51
- }
52
- for (const e of rawEdges) {
53
- const src = e.source;
54
- const tgt = e.target;
55
- adj.get(src)?.push(tgt);
56
- radj.get(tgt)?.push(src);
57
- degree.set(src, (degree.get(src) ?? 0) + 1);
58
- degree.set(tgt, (degree.get(tgt) ?? 0) + 1);
59
- }
60
- return { nodes, adj, radj, edges: rawEdges, degree, graphPath };
61
- }
62
- // ── Shared output helpers ─────────────────────────────────────────────────────
63
- function nodeOut(g, id) {
64
- const d = g.nodes.get(id) ?? { id };
4
+ * All graphify_* tools are deprecated. They proxy to monograph_* tools.
5
+ * Will be removed in next major release.
6
+ */
7
+ import { monographTools } from './monograph-tools.js';
8
+ function findMonographTool(name) {
9
+ const tool = monographTools.find(t => t.name === name);
10
+ if (!tool)
11
+ throw new Error(`[monograph] Tool ${name} not found`);
12
+ return tool;
13
+ }
14
+ function shimTool(graphifyName, monographName, paramMap) {
15
+ const target = findMonographTool(monographName);
65
16
  return {
66
- id,
67
- label: d.label ?? id,
68
- file: d.source_file ?? '',
69
- location: d.source_location ?? '',
70
- community: d.community ?? null,
71
- degree: g.degree.get(id) ?? 0,
72
- file_type: d.file_type ?? '',
73
- };
74
- }
75
- /** Score a node against search terms. */
76
- function scoreNode(g, id, terms) {
77
- const d = g.nodes.get(id);
78
- if (!d)
79
- return 0;
80
- const label = (d.label ?? id).toLowerCase();
81
- const file = (d.source_file ?? '').toLowerCase();
82
- return terms.reduce((s, t) => {
83
- if (label.includes(t))
84
- s += 1;
85
- if (file.includes(t))
86
- s += 0.5;
87
- return s;
88
- }, 0);
89
- }
90
- // ── Tool Definitions ──────────────────────────────────────────────────────────
91
- /**
92
- * Build or rebuild the knowledge graph for a directory.
93
- */
94
- export const graphifyBuildTool = {
95
- name: 'graphify_build',
96
- description: 'Build (or rebuild) the knowledge graph for a project directory. ' +
97
- 'Extracts AST structure from code files, semantic relationships from docs, ' +
98
- 'and clusters them into communities. Run this first before using other graphify tools. ' +
99
- 'Code-only changes are fast (tree-sitter, no LLM). Doc changes require an LLM call.',
100
- category: 'graphify',
101
- tags: ['knowledge-graph', 'codebase', 'architecture', 'analysis'],
102
- inputSchema: {
103
- type: 'object',
104
- properties: {
105
- path: {
106
- type: 'string',
107
- description: 'Path to analyse (defaults to current project root)',
108
- },
109
- codeOnly: {
110
- type: 'boolean',
111
- description: 'Only re-extract changed code files — no LLM, fast rebuild',
112
- default: false,
113
- },
114
- },
115
- },
116
- handler: async (params) => {
117
- const cwd = getProjectCwd();
118
- const targetPath = params.path || cwd;
119
- try {
120
- const { execSync } = await import('child_process');
121
- execSync(`graphify update ${targetPath}`, {
122
- encoding: 'utf8',
123
- cwd: targetPath,
124
- timeout: 300000,
125
- stdio: 'pipe',
126
- });
127
- // graphify outputs to graphify-out/ by default
128
- const graphPath = getGraphPath(targetPath);
129
- const outputDir = join(targetPath, '.monomind', 'graph');
130
- const statsPath = join(outputDir, 'stats.json');
131
- let stats = { nodes: 0, edges: 0, communities: 0, files: 0 };
132
- try {
133
- stats = JSON.parse(readFileSync(statsPath, 'utf-8'));
134
- }
135
- catch {
136
- // Stats may not exist yet — read from graph directly
137
- try {
138
- const g = JSON.parse(readFileSync(graphPath, 'utf-8'));
139
- stats.nodes = g.nodes?.length || 0;
140
- stats.edges = (g.links || g.edges)?.length || 0;
141
- }
142
- catch { }
143
- }
144
- return {
145
- success: true,
146
- graphPath,
147
- outputDir,
148
- nodes: stats.nodes,
149
- edges: stats.edges,
150
- communities: stats.communities || 0,
151
- filesProcessed: stats.files || 0,
152
- message: `Knowledge graph built (${stats.nodes}n/${stats.edges}e)`,
153
- };
154
- }
155
- catch (err) {
156
- return {
157
- error: true,
158
- message: String(err),
159
- hint: 'graphify not installed — run: uv tool install graphifyy',
160
- };
161
- }
162
- },
163
- };
164
- /**
165
- * Query the knowledge graph with natural language.
166
- */
167
- export const graphifyQueryTool = {
168
- name: 'graphify_query',
169
- description: 'Search the knowledge graph with a natural language question or keywords. ' +
170
- 'Returns relevant nodes and edges as structured context — use this instead of reading ' +
171
- 'many files when you want to understand how components relate. ' +
172
- 'BFS mode gives broad context; DFS traces a specific call path.',
173
- category: 'graphify',
174
- tags: ['knowledge-graph', 'search', 'architecture', 'codebase'],
175
- inputSchema: {
176
- type: 'object',
177
- properties: {
178
- question: {
179
- type: 'string',
180
- description: 'Natural language question or keyword (e.g. "authentication flow", "how does caching work")',
181
- },
182
- mode: {
183
- type: 'string',
184
- enum: ['bfs', 'dfs'],
185
- default: 'bfs',
186
- description: 'bfs = broad context, dfs = trace specific path',
187
- },
188
- depth: {
189
- type: 'integer',
190
- default: 3,
191
- description: 'Traversal depth (1–6)',
192
- },
193
- tokenBudget: {
194
- type: 'integer',
195
- default: 2000,
196
- description: 'Approximate max output tokens',
197
- },
198
- path: {
199
- type: 'string',
200
- description: 'Project path whose graph to query (defaults to current project root)',
201
- },
202
- },
203
- required: ['question'],
204
- },
205
- handler: async (params) => {
206
- const cwd = getProjectCwd();
207
- const targetPath = params.path || cwd;
208
- if (!graphExists(targetPath)) {
209
- return {
210
- error: true,
211
- message: 'No graph found. Run graphify_build first.',
212
- hint: `Expected: ${getGraphPath(targetPath)}`,
213
- };
214
- }
215
- const question = params.question;
216
- const mode = params.mode || 'bfs';
217
- const depth = params.depth || 3;
218
- try {
219
- const g = await loadKnowledgeGraph(targetPath);
220
- const terms = question.toLowerCase().split(/\s+/).filter(t => t.length > 2);
221
- // Score nodes; fall back to highest-degree nodes if no match
222
- let startNodes = [];
223
- if (terms.length > 0) {
224
- const scored = [];
225
- for (const id of g.nodes.keys()) {
226
- const s = scoreNode(g, id, terms);
227
- if (s > 0)
228
- scored.push([s, id]);
229
- }
230
- scored.sort((a, b) => b[0] - a[0]);
231
- startNodes = scored.slice(0, 5).map(([, id]) => id);
232
- }
233
- if (startNodes.length === 0) {
234
- startNodes = [...g.nodes.keys()]
235
- .sort((a, b) => (g.degree.get(b) ?? 0) - (g.degree.get(a) ?? 0))
236
- .slice(0, 3);
237
- }
238
- const visited = new Set(startNodes);
239
- const edgesSeen = [];
240
- if (mode === 'bfs') {
241
- let frontier = new Set(startNodes);
242
- for (let d = 0; d < depth; d++) {
243
- const next = new Set();
244
- for (const n of frontier) {
245
- for (const nbr of g.adj.get(n) ?? []) {
246
- if (!visited.has(nbr)) {
247
- next.add(nbr);
248
- edgesSeen.push([n, nbr]);
249
- }
250
- }
251
- }
252
- for (const n of next)
253
- visited.add(n);
254
- frontier = next;
255
- }
256
- }
257
- else {
258
- // DFS
259
- const stack = startNodes.map(n => [n, 0]);
260
- while (stack.length > 0) {
261
- const [node, d] = stack.pop();
262
- if (visited.has(node) && d > 0)
263
- continue;
264
- if (d > depth)
265
- continue;
266
- visited.add(node);
267
- for (const nbr of g.adj.get(node) ?? []) {
268
- if (!visited.has(nbr)) {
269
- stack.push([nbr, d + 1]);
270
- edgesSeen.push([node, nbr]);
271
- }
272
- }
273
- }
274
- }
275
- const nodesOut = [...visited]
276
- .sort((a, b) => (g.degree.get(b) ?? 0) - (g.degree.get(a) ?? 0))
277
- .slice(0, 60)
278
- .map(id => nodeOut(g, id));
279
- // Build edge lookup for attribute access
280
- const edgeLookup = new Map();
281
- for (const e of g.edges) {
282
- edgeLookup.set(`${e.source}__${e.target}`, e);
283
- }
284
- const edgesOut = edgesSeen
285
- .filter(([u, v]) => visited.has(u) && visited.has(v))
286
- .slice(0, 80)
287
- .map(([u, v]) => {
288
- const e = edgeLookup.get(`${u}__${v}`) ?? {};
289
- return {
290
- from: (g.nodes.get(u)?.label ?? u),
291
- to: (g.nodes.get(v)?.label ?? v),
292
- relation: e.relation ?? '',
293
- confidence: e.confidence ?? '',
294
- };
295
- });
296
- return {
297
- question,
298
- mode,
299
- depth,
300
- nodes: nodesOut,
301
- edges: edgesOut,
302
- total_nodes: visited.size,
303
- total_edges: edgesSeen.length,
304
- };
305
- }
306
- catch (err) {
307
- return { error: true, message: String(err) };
308
- }
309
- },
310
- };
311
- /**
312
- * Get the most connected (god) nodes — the core abstractions.
313
- */
314
- export const graphifyGodNodesTool = {
315
- name: 'graphify_god_nodes',
316
- description: 'Return the most connected nodes in the knowledge graph — the core abstractions ' +
317
- 'and central concepts of the codebase. Use this at the start of any architectural analysis ' +
318
- 'to understand what the most important components are before diving into details.',
319
- category: 'graphify',
320
- tags: ['knowledge-graph', 'architecture', 'abstractions', 'codebase'],
321
- inputSchema: {
322
- type: 'object',
323
- properties: {
324
- topN: {
325
- type: 'integer',
326
- default: 15,
327
- description: 'Number of god nodes to return',
328
- },
329
- path: {
330
- type: 'string',
331
- description: 'Project path (defaults to current project root)',
332
- },
333
- },
334
- },
335
- handler: async (params) => {
336
- const cwd = getProjectCwd();
337
- const targetPath = params.path || cwd;
338
- if (!graphExists(targetPath)) {
339
- return {
340
- error: true,
341
- message: 'No graph found. Run graphify_build first.',
342
- };
343
- }
344
- const topN = params.topN || 15;
345
- try {
346
- const g = await loadKnowledgeGraph(targetPath);
347
- const sortedIds = [...g.nodes.keys()]
348
- .filter(id => {
349
- const source_file = g.nodes.get(id)?.source_file ?? '';
350
- return source_file !== '';
351
- })
352
- .sort((a, b) => (g.degree.get(b) ?? 0) - (g.degree.get(a) ?? 0))
353
- .slice(0, topN);
354
- const godNodes = sortedIds.map(id => {
355
- const d = g.nodes.get(id) ?? { id };
356
- const neighbors = (g.adj.get(id) ?? [])
357
- .slice(0, 8)
358
- .map(nid => g.nodes.get(nid)?.label ?? nid);
359
- return {
360
- label: d.label ?? id,
361
- degree: g.degree.get(id) ?? 0,
362
- file: d.source_file ?? '',
363
- location: d.source_location ?? '',
364
- community: d.community ?? null,
365
- file_type: d.file_type ?? '',
366
- neighbors,
367
- };
368
- });
369
- const internalCount = [...g.nodes.keys()].filter(id => (g.nodes.get(id)?.source_file ?? '') !== '').length;
370
- return { god_nodes: godNodes, total_nodes: g.nodes.size, internal_nodes: internalCount };
371
- }
372
- catch (err) {
373
- return { error: true, message: String(err) };
374
- }
375
- },
376
- };
377
- /**
378
- * Get full details for a specific node.
379
- */
380
- export const graphifyGetNodeTool = {
381
- name: 'graphify_get_node',
382
- description: 'Get all details for a specific concept/node in the knowledge graph: ' +
383
- 'its source location, community, all relationships, and confidence levels. ' +
384
- 'Use this when you need to deeply understand one specific component.',
385
- category: 'graphify',
386
- tags: ['knowledge-graph', 'node', 'details'],
387
- inputSchema: {
388
- type: 'object',
389
- properties: {
390
- label: {
391
- type: 'string',
392
- description: 'Node label or ID to look up (case-insensitive)',
393
- },
394
- path: {
395
- type: 'string',
396
- description: 'Project path (defaults to current project root)',
397
- },
398
- },
399
- required: ['label'],
400
- },
401
- handler: async (params) => {
402
- const cwd = getProjectCwd();
403
- const targetPath = params.path || cwd;
404
- if (!graphExists(targetPath)) {
405
- return { error: true, message: 'No graph found. Run graphify_build first.' };
406
- }
407
- try {
408
- const g = await loadKnowledgeGraph(targetPath);
409
- const term = params.label.toLowerCase();
410
- const matches = [...g.nodes.entries()]
411
- .filter(([id, d]) => (d.label ?? id).toLowerCase().includes(term) || id.toLowerCase() === term)
412
- .sort(([aId], [bId]) => (g.degree.get(bId) ?? 0) - (g.degree.get(aId) ?? 0))
413
- .map(([id]) => id);
414
- if (matches.length === 0) {
415
- return { error: 'Node not found', searched: term };
416
- }
417
- const id = matches[0];
418
- const d = g.nodes.get(id) ?? { id };
419
- // Build edge lookup
420
- const edgeLookup = new Map();
421
- for (const e of g.edges) {
422
- edgeLookup.set(`${e.source}__${e.target}`, e);
423
- }
424
- const outEdges = (g.adj.get(id) ?? []).slice(0, 40).map(tgt => {
425
- const e = edgeLookup.get(`${id}__${tgt}`) ?? {};
426
- return {
427
- direction: 'outgoing',
428
- to: g.nodes.get(tgt)?.label ?? tgt,
429
- relation: e.relation ?? '',
430
- confidence: e.confidence ?? '',
431
- confidence_score: e.confidence_score ?? null,
432
- };
433
- });
434
- const inEdges = (g.radj.get(id) ?? []).slice(0, 40).map(src => {
435
- const e = edgeLookup.get(`${src}__${id}`) ?? {};
436
- return {
437
- direction: 'incoming',
438
- from: g.nodes.get(src)?.label ?? src,
439
- relation: e.relation ?? '',
440
- confidence: e.confidence ?? '',
441
- };
442
- });
443
- // Strip well-known fields from attributes output to avoid duplication
444
- const knownKeys = new Set(['label', 'source_file', 'source_location', 'community', 'file_type', 'id']);
445
- const attributes = {};
446
- for (const [k, v] of Object.entries(d)) {
447
- if (!knownKeys.has(k))
448
- attributes[k] = v;
449
- }
450
- return {
451
- id,
452
- label: d.label ?? id,
453
- file: d.source_file ?? '',
454
- location: d.source_location ?? '',
455
- community: d.community ?? null,
456
- file_type: d.file_type ?? '',
457
- degree: g.degree.get(id) ?? 0,
458
- attributes,
459
- edges: [...outEdges, ...inEdges],
460
- all_matches: matches.slice(0, 10).map(m => g.nodes.get(m)?.label ?? m),
461
- };
462
- }
463
- catch (err) {
464
- return { error: true, message: String(err) };
465
- }
466
- },
467
- };
468
- /**
469
- * Find shortest path between two concepts.
470
- */
471
- export const graphifyShortestPathTool = {
472
- name: 'graphify_shortest_path',
473
- description: 'Find the shortest relationship path between two concepts in the knowledge graph. ' +
474
- 'Use this to trace how component A depends on or relates to component B, ' +
475
- 'revealing hidden coupling chains (e.g. "how does the router connect to the database?").',
476
- category: 'graphify',
477
- tags: ['knowledge-graph', 'path', 'dependencies', 'coupling'],
478
- inputSchema: {
479
- type: 'object',
480
- properties: {
481
- source: { type: 'string', description: 'Source concept label' },
482
- target: { type: 'string', description: 'Target concept label' },
483
- maxHops: { type: 'integer', default: 8, description: 'Maximum hops to search' },
484
- path: { type: 'string', description: 'Project path (defaults to current project root)' },
485
- },
486
- required: ['source', 'target'],
487
- },
488
- handler: async (params) => {
489
- const cwd = getProjectCwd();
490
- const targetPath = params.path || cwd;
491
- if (!graphExists(targetPath)) {
492
- return { error: true, message: 'No graph found. Run graphify_build first.' };
493
- }
494
- try {
495
- const g = await loadKnowledgeGraph(targetPath);
496
- const maxHops = params.maxHops || 8;
497
- /** Find node ids matching a search term, sorted by degree descending. */
498
- function findNodes(term) {
499
- const t = term.toLowerCase();
500
- return [...g.nodes.entries()]
501
- .filter(([id, d]) => (d.label ?? id).toLowerCase().includes(t) || id.toLowerCase() === t)
502
- .sort(([aId], [bId]) => (g.degree.get(bId) ?? 0) - (g.degree.get(aId) ?? 0))
503
- .map(([id]) => id);
504
- }
505
- const srcNodes = findNodes(params.source);
506
- const tgtNodes = findNodes(params.target);
507
- if (srcNodes.length === 0) {
508
- return { error: true, message: `Source not found: ${params.source}` };
509
- }
510
- if (tgtNodes.length === 0) {
511
- return { error: true, message: `Target not found: ${params.target}` };
512
- }
513
- // BFS on undirected graph (use both adj and radj as neighbours)
514
- function bfsPath(start, end) {
515
- const prev = new Map();
516
- const queue = [start];
517
- const visited = new Set([start]);
518
- while (queue.length > 0) {
519
- const cur = queue.shift();
520
- if (cur === end) {
521
- // Reconstruct path
522
- const path = [];
523
- let node = end;
524
- while (node !== undefined) {
525
- path.unshift(node);
526
- node = prev.get(node);
527
- }
528
- return path.length - 1 <= maxHops ? path : null;
529
- }
530
- // Treat edges as undirected
531
- const nbrs = [...(g.adj.get(cur) ?? []), ...(g.radj.get(cur) ?? [])];
532
- for (const nbr of nbrs) {
533
- if (!visited.has(nbr)) {
534
- visited.add(nbr);
535
- prev.set(nbr, cur);
536
- if (queue.length < 100000)
537
- queue.push(nbr);
538
- }
539
- }
540
- }
541
- return null;
542
- }
543
- let bestPath = null;
544
- for (const src of srcNodes.slice(0, 3)) {
545
- for (const tgt of tgtNodes.slice(0, 3)) {
546
- const p = bfsPath(src, tgt);
547
- if (p && (!bestPath || p.length < bestPath.length)) {
548
- bestPath = p;
549
- }
550
- }
551
- }
552
- if (!bestPath) {
553
- return {
554
- found: false,
555
- message: `No path within ${maxHops} hops between "${params.source}" and "${params.target}"`,
556
- };
557
- }
558
- // Build edge lookup
559
- const edgeLookup = new Map();
560
- for (const e of g.edges) {
561
- edgeLookup.set(`${e.source}__${e.target}`, e);
562
- edgeLookup.set(`${e.target}__${e.source}`, e); // bidirectional lookup
563
- }
564
- const steps = bestPath.map((id, i) => {
565
- const d = g.nodes.get(id) ?? { id };
566
- const step = {
567
- label: d.label ?? id,
568
- file: d.source_file ?? '',
569
- location: d.source_location ?? '',
570
- };
571
- if (i < bestPath.length - 1) {
572
- const nextId = bestPath[i + 1];
573
- const e = edgeLookup.get(`${id}__${nextId}`) ?? edgeLookup.get(`${nextId}__${id}`) ?? {};
574
- step.next_relation = e.relation ?? '';
575
- step.confidence = e.confidence ?? '';
576
- }
577
- return step;
578
- });
579
- return { found: true, hops: bestPath.length - 1, path: steps };
580
- }
581
- catch (err) {
582
- return { error: true, message: String(err) };
583
- }
584
- },
585
- };
586
- /**
587
- * Get all nodes in a community (cluster of related components).
588
- */
589
- export const graphifyGetCommunityTool = {
590
- name: 'graphify_community',
591
- description: 'Get all nodes in a specific community cluster. Communities are groups of ' +
592
- 'tightly related components detected by graph clustering. Use graph_stats first to ' +
593
- 'see community count, then explore communities to understand subsystem boundaries.',
594
- category: 'graphify',
595
- tags: ['knowledge-graph', 'community', 'clusters', 'subsystems'],
596
- inputSchema: {
597
- type: 'object',
598
- properties: {
599
- communityId: {
600
- type: 'integer',
601
- description: 'Community ID (0 = largest community)',
602
- },
603
- path: {
604
- type: 'string',
605
- description: 'Project path (defaults to current project root)',
606
- },
607
- },
608
- required: ['communityId'],
609
- },
610
- handler: async (params) => {
611
- const cwd = getProjectCwd();
612
- const targetPath = params.path || cwd;
613
- if (!graphExists(targetPath)) {
614
- return { error: true, message: 'No graph found. Run graphify_build first.' };
615
- }
616
- try {
617
- const g = await loadKnowledgeGraph(targetPath);
618
- const cid = params.communityId;
619
- const members = [...g.nodes.entries()]
620
- .filter(([, d]) => d.community === cid)
621
- .sort(([aId], [bId]) => (g.degree.get(bId) ?? 0) - (g.degree.get(aId) ?? 0))
622
- .slice(0, 50)
623
- .map(([id, d]) => ({
624
- label: d.label ?? id,
625
- file: d.source_file ?? '',
626
- location: d.source_location ?? '',
627
- degree: g.degree.get(id) ?? 0,
628
- file_type: d.file_type ?? '',
629
- }));
630
- // Build edge lookup
631
- const edgeLookup = new Map();
632
- for (const e of g.edges) {
633
- edgeLookup.set(`${e.source}__${e.target}`, e);
634
- }
635
- const externalEdges = [];
636
- for (const [id, d] of g.nodes.entries()) {
637
- if (d.community !== cid)
638
- continue;
639
- for (const nbr of g.adj.get(id) ?? []) {
640
- const nbrD = g.nodes.get(nbr);
641
- if (nbrD?.community !== cid) {
642
- const e = edgeLookup.get(`${id}__${nbr}`) ?? {};
643
- externalEdges.push({
644
- from: d.label ?? id,
645
- to: nbrD?.label ?? nbr,
646
- to_community: nbrD?.community ?? null,
647
- relation: e.relation ?? '',
648
- });
649
- }
650
- }
651
- }
652
- return {
653
- community_id: cid,
654
- member_count: members.length,
655
- members,
656
- external_connections: externalEdges.slice(0, 30),
657
- };
658
- }
659
- catch (err) {
660
- return { error: true, message: String(err) };
661
- }
662
- },
663
- };
664
- /**
665
- * Get graph statistics: node/edge counts, communities, confidence breakdown.
666
- */
667
- export const graphifyStatsTool = {
668
- name: 'graphify_stats',
669
- description: 'Get summary statistics for the knowledge graph: node count, edge count, ' +
670
- 'community count, confidence breakdown (EXTRACTED/INFERRED/AMBIGUOUS), ' +
671
- 'and top god nodes. Use this first to understand graph size and structure.',
672
- category: 'graphify',
673
- tags: ['knowledge-graph', 'stats', 'overview'],
674
- inputSchema: {
675
- type: 'object',
676
- properties: {
677
- path: {
678
- type: 'string',
679
- description: 'Project path (defaults to current project root)',
680
- },
681
- },
682
- },
683
- handler: async (params) => {
684
- const cwd = getProjectCwd();
685
- const targetPath = params.path || cwd;
686
- if (!graphExists(targetPath)) {
687
- return {
688
- error: true,
689
- message: 'No graph found. Run graphify_build first.',
690
- hint: `Expected: ${getGraphPath(targetPath)}`,
691
- };
692
- }
693
- try {
694
- const g = await loadKnowledgeGraph(targetPath);
695
- // Community sizes
696
- const communities = new Map();
697
- for (const d of g.nodes.values()) {
698
- if (d.community != null) {
699
- communities.set(d.community, (communities.get(d.community) ?? 0) + 1);
700
- }
701
- }
702
- const communitySizes = {};
703
- [...communities.entries()]
704
- .sort((a, b) => b[1] - a[1])
705
- .slice(0, 10)
706
- .forEach(([cid, count]) => { communitySizes[String(cid)] = count; });
707
- // Confidence and relation counts
708
- const confidenceCounts = {};
709
- const relationCounts = {};
710
- const fileTypeCounts = {};
711
- for (const e of g.edges) {
712
- const conf = e.confidence ?? 'UNKNOWN';
713
- confidenceCounts[conf] = (confidenceCounts[conf] ?? 0) + 1;
714
- const rel = e.relation ?? 'unknown';
715
- relationCounts[rel] = (relationCounts[rel] ?? 0) + 1;
716
- }
717
- for (const d of g.nodes.values()) {
718
- const ft = d.file_type ?? 'unknown';
719
- fileTypeCounts[ft] = (fileTypeCounts[ft] ?? 0) + 1;
720
- }
721
- const topRelations = Object.entries(relationCounts)
722
- .sort((a, b) => b[1] - a[1])
723
- .slice(0, 10)
724
- .reduce((acc, [k, v]) => { acc[k] = v; return acc; }, {});
725
- const topGodNodes = [...g.nodes.keys()]
726
- .filter(id => {
727
- const source_file = g.nodes.get(id)?.source_file ?? '';
728
- return source_file !== '';
729
- })
730
- .sort((a, b) => (g.degree.get(b) ?? 0) - (g.degree.get(a) ?? 0))
731
- .slice(0, 5)
732
- .map(id => g.nodes.get(id)?.label ?? id);
733
- return {
734
- nodes: g.nodes.size,
735
- edges: g.edges.length,
736
- communities: communities.size,
737
- community_sizes: communitySizes,
738
- confidence: confidenceCounts,
739
- top_relations: topRelations,
740
- file_types: fileTypeCounts,
741
- graph_path: g.graphPath,
742
- top_god_nodes: topGodNodes,
743
- is_directed: true,
744
- };
745
- }
746
- catch (err) {
747
- return { error: true, message: String(err) };
748
- }
749
- },
750
- };
751
- /**
752
- * Find surprising cross-community connections (architectural insights).
753
- */
754
- export const graphifySurprisesTool = {
755
- name: 'graphify_surprises',
756
- description: 'Find surprising connections between components that are in different communities ' +
757
- 'but have strong relationships. These unexpected couplings often reveal hidden dependencies, ' +
758
- 'design smells, or important architectural patterns worth understanding.',
759
- category: 'graphify',
760
- tags: ['knowledge-graph', 'architecture', 'coupling', 'surprises'],
761
- inputSchema: {
762
- type: 'object',
763
- properties: {
764
- topN: {
765
- type: 'integer',
766
- default: 10,
767
- description: 'Number of surprising connections to return',
768
- },
769
- path: {
770
- type: 'string',
771
- description: 'Project path (defaults to current project root)',
772
- },
773
- },
774
- },
775
- handler: async (params) => {
776
- const cwd = getProjectCwd();
777
- const targetPath = params.path || cwd;
778
- if (!graphExists(targetPath)) {
779
- return { error: true, message: 'No graph found. Run graphify_build first.' };
780
- }
781
- try {
782
- const g = await loadKnowledgeGraph(targetPath);
783
- const topN = params.topN || 10;
784
- const surprises = [];
785
- for (const e of g.edges) {
786
- const uD = g.nodes.get(e.source);
787
- const vD = g.nodes.get(e.target);
788
- const cu = uD?.community ?? null;
789
- const cv = vD?.community ?? null;
790
- if (cu != null && cv != null && cu !== cv) {
791
- const score = (g.degree.get(e.source) ?? 0) * (g.degree.get(e.target) ?? 0);
792
- surprises.push({
793
- score,
794
- from: uD?.label ?? e.source,
795
- from_community: cu,
796
- from_file: uD?.source_file ?? '',
797
- to: vD?.label ?? e.target,
798
- to_community: cv,
799
- to_file: vD?.source_file ?? '',
800
- relation: e.relation ?? '',
801
- confidence: e.confidence ?? '',
802
- });
803
- }
804
- }
805
- surprises.sort((a, b) => b.score - a.score);
806
- return {
807
- surprises: surprises.slice(0, topN),
808
- total_cross_community_edges: surprises.length,
809
- };
810
- }
811
- catch (err) {
812
- return { error: true, message: String(err) };
813
- }
814
- },
815
- };
816
- /**
817
- * Generate or open the interactive HTML visualization of the knowledge graph.
818
- */
819
- export const graphifyVisualizeTool = {
820
- name: 'graphify_visualize',
821
- description: 'Generate (and optionally open) the interactive HTML knowledge graph explorer. ' +
822
- 'Produces graph.html alongside graph.json — a self-contained visualization with force-directed ' +
823
- 'layout, community coloring, god-node panel, sidebar details, minimap, and search. ' +
824
- 'No internet connection or server required — just open the HTML file in a browser.',
825
- category: 'graphify',
826
- tags: ['knowledge-graph', 'visualization', 'html', 'browser'],
827
- inputSchema: {
828
- type: 'object',
829
- properties: {
830
- open: {
831
- type: 'boolean',
832
- description: 'Open the HTML file in the default browser after generating (macOS/Linux)',
833
- default: false,
834
- },
835
- path: {
836
- type: 'string',
837
- description: 'Project path (defaults to current project root)',
838
- },
839
- },
840
- },
841
- handler: async (params) => {
842
- const cwd = getProjectCwd();
843
- const targetPath = params.path || cwd;
844
- if (!graphExists(targetPath)) {
845
- return {
846
- error: true,
847
- message: 'No graph found. Run graphify_build first.',
848
- hint: `Expected: ${getGraphPath(targetPath)}`,
849
- };
850
- }
851
- try {
852
- const graphPath = getGraphPath(targetPath);
853
- const { dirname } = await import('path');
854
- const outputDir = dirname(graphPath);
855
- const { execSync, spawn } = await import('child_process');
856
- // graphify doesn't have a visualize CLI command — generate a simple HTML viewer
857
- const graphData = readFileSync(graphPath, 'utf-8');
858
- const htmlContent = `<!DOCTYPE html><html><head><title>Knowledge Graph</title></head><body>
859
- <h1>Knowledge Graph Visualization</h1>
860
- <p>Nodes: ${JSON.parse(graphData).nodes?.length || 0}, Edges: ${(JSON.parse(graphData).links || JSON.parse(graphData).edges)?.length || 0}</p>
861
- <pre style="max-height:80vh;overflow:auto">${graphData.slice(0, 50000)}</pre>
862
- </body></html>`;
863
- const { writeFileSync } = await import('fs');
864
- const htmlPath = join(outputDir, 'graph.html');
865
- writeFileSync(htmlPath, htmlContent);
866
- if (params.open) {
867
- const opener = process.platform === 'darwin' ? 'open'
868
- : process.platform === 'win32' ? 'start' : 'xdg-open';
869
- spawn(opener, [htmlPath], { detached: true, stdio: 'ignore' }).unref();
870
- }
871
- return {
872
- success: true,
873
- htmlPath,
874
- message: `Interactive visualization generated at ${htmlPath}`,
875
- hint: params.open ? 'Opening in browser…' : `Open in browser: open "${htmlPath}"`,
876
- };
877
- }
878
- catch (err) {
879
- return { error: true, message: String(err), hint: 'graphify not installed — run: uv tool install graphifyy' };
880
- }
881
- },
882
- };
883
- // ── Watch PID helpers ─────────────────────────────────────────────────────────
884
- function getPidPath(cwd) {
885
- return resolve(join(cwd, '.monomind', 'graph', 'watch.pid'));
886
- }
887
- function readWatchPid(cwd) {
888
- const pidPath = getPidPath(cwd);
889
- if (!existsSync(pidPath))
890
- return null;
891
- try {
892
- const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
893
- return isNaN(pid) ? null : pid;
894
- }
895
- catch {
896
- return null;
897
- }
898
- }
899
- function isProcessRunning(pid) {
900
- try {
901
- process.kill(pid, 0);
902
- return true;
903
- }
904
- catch {
905
- return false;
906
- }
907
- }
908
- // ── Watch tools ───────────────────────────────────────────────────────────────
909
- /**
910
- * Start a background file watcher that rebuilds the knowledge graph + HTML
911
- * whenever source files change (debounced 2 s).
912
- */
913
- export const graphifyWatchTool = {
914
- name: 'graphify_watch',
915
- description: 'Start a background file watcher that automatically rebuilds the knowledge graph ' +
916
- '(graph.json + graph.html) whenever source files change. Uses a 2-second debounce ' +
917
- 'so rapid saves do not trigger repeated rebuilds. The watcher runs as a detached ' +
918
- 'background process — call graphify_watch_stop to stop it.',
919
- category: 'graphify',
920
- tags: ['knowledge-graph', 'watch', 'auto-rebuild'],
921
- inputSchema: {
922
- type: 'object',
923
- properties: {
924
- path: {
925
- type: 'string',
926
- description: 'Project root to watch (defaults to current project root)',
927
- },
928
- debounce: {
929
- type: 'number',
930
- description: 'Debounce delay in milliseconds (default 2000)',
931
- default: 2000,
932
- },
933
- extensions: {
934
- type: 'string',
935
- description: 'Comma-separated list of file extensions to watch (default: ts,js,tsx,jsx,py,go,rs,java,cs,rb,cpp,c)',
936
- default: 'ts,js,tsx,jsx,py,go,rs,java,cs,rb,cpp,c',
937
- },
17
+ name: graphifyName,
18
+ description: `[DEPRECATED: use ${monographName}] ${target.description}`,
19
+ inputSchema: target.inputSchema,
20
+ handler: async (input, ctx) => {
21
+ console.warn(`[monograph] ${graphifyName} is deprecated, use ${monographName}`);
22
+ const mapped = paramMap ? paramMap(input) : input;
23
+ return target.handler(mapped, ctx);
938
24
  },
939
- },
940
- handler: async (params) => {
941
- const cwd = getProjectCwd();
942
- const targetPath = params.path || cwd;
943
- const debounceMs = params.debounce || 2000;
944
- const extensions = (params.extensions || 'ts,js,tsx,jsx,py,go,rs,java,cs,rb,cpp,c')
945
- .split(',')
946
- .map((e) => e.trim())
947
- .filter(Boolean);
948
- // Check if already running
949
- const existingPid = readWatchPid(targetPath);
950
- if (existingPid !== null && isProcessRunning(existingPid)) {
951
- return {
952
- success: false,
953
- message: `Watcher already running (PID ${existingPid})`,
954
- hint: 'Call graphify_watch_stop first to restart.',
955
- };
956
- }
957
- const pidPath = getPidPath(targetPath);
958
- const outputDir = resolve(join(targetPath, '.monomind', 'graph'));
959
- // Inline watcher script — runs as a detached node process
960
- const watcherScript = `
961
- import { watch } from 'chokidar';
962
- import { writeFileSync, mkdirSync } from 'fs';
963
- import { join } from 'path';
964
-
965
- const TARGET = ${JSON.stringify(targetPath)};
966
- const OUTPUT_DIR = ${JSON.stringify(outputDir)};
967
- const PID_PATH = ${JSON.stringify(pidPath)};
968
- const DEBOUNCE_MS = ${debounceMs};
969
- const EXTS = new Set(${JSON.stringify(extensions)});
970
- const IGNORE = [
971
- /node_modules/,
972
- /\\.git/,
973
- /\\.monomind/,
974
- /dist[\\\\/]/,
975
- /\\.next/,
976
- /\\.turbo/,
977
- /coverage/,
978
- ];
979
-
980
- // Write own PID
981
- mkdirSync(OUTPUT_DIR, { recursive: true });
982
- writeFileSync(PID_PATH, String(process.pid));
983
-
984
- console.log('[graphify-watch] PID', process.pid, '— watching', TARGET);
985
-
986
- let timer = null;
987
-
988
- async function rebuild() {
989
- const start = Date.now();
990
- console.log('[graphify-watch] Change detected — rebuilding graph…');
991
- try {
992
- const { execSync } = await import('child_process');
993
- execSync('graphify update ' + TARGET, {
994
- encoding: 'utf8', cwd: TARGET, timeout: 300000, stdio: 'pipe',
995
- });
996
- console.log('[graphify-watch] Done in', Date.now() - start, 'ms');
997
- } catch (err) {
998
- console.error('[graphify-watch] Build error:', err.message ?? err);
999
- }
25
+ };
1000
26
  }
1001
-
1002
- const watcher = watch(TARGET, {
1003
- ignored: (p) => IGNORE.some((r) => r.test(p)),
1004
- persistent: true,
1005
- ignoreInitial: true,
1006
- awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
1007
- });
1008
-
1009
- watcher.on('all', (event, filePath) => {
1010
- const ext = filePath.split('.').pop() ?? '';
1011
- if (!EXTS.has(ext)) return;
1012
- if (timer) clearTimeout(timer);
1013
- timer = setTimeout(rebuild, DEBOUNCE_MS);
1014
- });
1015
-
1016
- watcher.on('error', (err) => console.error('[graphify-watch] Watcher error:', err));
1017
-
1018
- // Clean up PID on exit
1019
- process.on('exit', () => { try { require('fs').unlinkSync(PID_PATH); } catch {} });
1020
- process.on('SIGINT', () => process.exit(0));
1021
- process.on('SIGTERM', () => process.exit(0));
1022
- `;
1023
- try {
1024
- const { spawn } = await import('child_process');
1025
- const { writeFileSync } = await import('fs');
1026
- const { mkdirSync } = await import('fs');
1027
- mkdirSync(outputDir, { recursive: true });
1028
- // Write the script to a temp file so it can use ESM imports
1029
- const scriptPath = resolve(join(outputDir, '_watcher.mjs'));
1030
- writeFileSync(scriptPath, watcherScript);
1031
- const child = spawn(process.execPath, [scriptPath], {
1032
- detached: true,
1033
- stdio: 'ignore',
1034
- env: { ...process.env },
1035
- });
1036
- child.unref();
1037
- // Give the process a moment to write its own PID
1038
- await new Promise((r) => setTimeout(r, 500));
1039
- const pid = readWatchPid(targetPath) ?? child.pid;
1040
- return {
1041
- success: true,
1042
- pid,
1043
- watching: targetPath,
1044
- debounceMs,
1045
- extensions,
1046
- message: `Graph watcher started (PID ${pid}). graph.json + graph.html will rebuild on file changes.`,
1047
- hint: 'Call graphify_watch_stop to stop.',
1048
- };
1049
- }
1050
- catch (err) {
1051
- return { error: true, message: String(err) };
1052
- }
1053
- },
1054
- };
1055
- /**
1056
- * Stop the background graph watcher started by graphify_watch.
1057
- */
1058
- export const graphifyWatchStopTool = {
1059
- name: 'graphify_watch_stop',
1060
- description: 'Stop the background file watcher started by graphify_watch.',
1061
- category: 'graphify',
1062
- tags: ['knowledge-graph', 'watch'],
1063
- inputSchema: {
1064
- type: 'object',
1065
- properties: {
1066
- path: {
1067
- type: 'string',
1068
- description: 'Project root (defaults to current project root)',
1069
- },
1070
- },
1071
- },
1072
- handler: async (params) => {
1073
- const cwd = getProjectCwd();
1074
- const targetPath = params.path || cwd;
1075
- const pid = readWatchPid(targetPath);
1076
- if (pid === null) {
1077
- return { success: false, message: 'No watcher PID found — watcher may not be running.' };
1078
- }
1079
- if (!isProcessRunning(pid)) {
1080
- // Clean up stale PID file
1081
- try {
1082
- const { unlinkSync } = await import('fs');
1083
- unlinkSync(getPidPath(targetPath));
1084
- }
1085
- catch { /* ignore */ }
1086
- return { success: false, message: `Process ${pid} is not running (stale PID cleaned up).` };
1087
- }
1088
- try {
1089
- process.kill(pid, 'SIGTERM');
1090
- // Remove PID file
1091
- try {
1092
- const { unlinkSync } = await import('fs');
1093
- unlinkSync(getPidPath(targetPath));
1094
- }
1095
- catch { /* ignore */ }
1096
- return { success: true, message: `Watcher (PID ${pid}) stopped.` };
1097
- }
1098
- catch (err) {
1099
- return { error: true, message: `Failed to stop PID ${pid}: ${String(err)}` };
1100
- }
1101
- },
1102
- };
1103
- /**
1104
- * Read the GRAPH_REPORT.md generated during the last build.
1105
- */
1106
- export const graphifyReportTool = {
1107
- name: 'graphify_report',
1108
- description: 'Read the GRAPH_REPORT.md generated by the last graphify_build. ' +
1109
- 'Returns the full markdown audit trail: corpus check, god nodes, surprising connections, ' +
1110
- 'communities, ambiguous edges, knowledge gaps, and suggested questions.',
1111
- category: 'graphify',
1112
- tags: ['knowledge-graph', 'report', 'audit'],
1113
- inputSchema: {
1114
- type: 'object',
1115
- properties: {
1116
- path: {
1117
- type: 'string',
1118
- description: 'Project path (defaults to current project root)',
1119
- },
1120
- },
1121
- },
1122
- handler: async (params) => {
1123
- const cwd = getProjectCwd();
1124
- const targetPath = params.path || cwd;
1125
- const reportPath = resolve(join(targetPath, '.monomind', 'graph', 'GRAPH_REPORT.md'));
1126
- if (!existsSync(reportPath)) {
1127
- return {
1128
- error: true,
1129
- message: 'No report found. Run graphify_build first.',
1130
- hint: `Expected: ${reportPath}`,
1131
- };
1132
- }
1133
- try {
1134
- const content = readFileSync(reportPath, 'utf-8');
1135
- return { success: true, reportPath, content };
1136
- }
1137
- catch (err) {
1138
- return { error: true, message: String(err) };
1139
- }
1140
- },
1141
- };
1142
- /**
1143
- * Suggest questions the knowledge graph can answer about the codebase.
1144
- */
1145
- export const graphifySuggestTool = {
1146
- name: 'graphify_suggest',
1147
- description: 'Return a prioritised list of questions this knowledge graph is uniquely ' +
1148
- 'positioned to answer — e.g. bridge nodes between subsystems, god nodes worth investigating, ' +
1149
- 'isolated components with no connections, and low-cohesion communities. ' +
1150
- 'Use after graphify_build to guide architectural analysis.',
1151
- category: 'graphify',
1152
- tags: ['knowledge-graph', 'questions', 'analysis'],
1153
- inputSchema: {
1154
- type: 'object',
1155
- properties: {
1156
- path: {
1157
- type: 'string',
1158
- description: 'Project path (defaults to current project root)',
1159
- },
1160
- },
1161
- },
1162
- handler: async (params) => {
1163
- const cwd = getProjectCwd();
1164
- const targetPath = params.path || cwd;
1165
- if (!graphExists(targetPath)) {
1166
- return { error: true, message: 'No graph found. Run graphify_build first.' };
1167
- }
1168
- try {
1169
- const loaded = await loadKnowledgeGraph(targetPath);
1170
- const godNodes = [...loaded.degree.entries()]
1171
- .filter(([id]) => loaded.nodes.get(id)?.source_file)
1172
- .sort((a, b) => b[1] - a[1])
1173
- .slice(0, 10)
1174
- .map(([id, deg]) => ({ node: id, degree: deg, file: loaded.nodes.get(id)?.source_file, question: `What role does ${id} play and why does it have ${deg} connections?` }));
1175
- // Find isolated nodes (no connections)
1176
- const isolated = [...loaded.degree.entries()]
1177
- .filter(([id, d]) => d === 0 && loaded.nodes.get(id)?.source_file)
1178
- .slice(0, 3)
1179
- .map(([id]) => ({ node: id, degree: 0, file: loaded.nodes.get(id)?.source_file, question: `Why is ${id} isolated with no connections?` }));
1180
- const questions = [...godNodes, ...isolated];
1181
- return { success: true, questions, total: questions.length };
1182
- }
1183
- catch (err) {
1184
- return { error: true, message: String(err) };
1185
- }
1186
- },
1187
- };
1188
- /**
1189
- * Run a corpus health check on the project files.
1190
- */
1191
- export const graphifyHealthTool = {
1192
- name: 'graphify_health',
1193
- description: 'Run a corpus health check on the project directory — warns when the codebase ' +
1194
- 'is too small for graph analysis, too large to be useful, security-sensitive files were ' +
1195
- 'found, or when the doc-to-code ratio is skewed. Run before graphify_build to catch issues early.',
1196
- category: 'graphify',
1197
- tags: ['knowledge-graph', 'health', 'corpus'],
1198
- inputSchema: {
1199
- type: 'object',
1200
- properties: {
1201
- path: {
1202
- type: 'string',
1203
- description: 'Project path (defaults to current project root)',
1204
- },
1205
- },
1206
- },
1207
- handler: async (params) => {
1208
- const cwd = getProjectCwd();
1209
- const targetPath = params.path || cwd;
1210
- try {
1211
- const warnings = [];
1212
- let totalFiles = 0;
1213
- if (!graphExists(targetPath)) {
1214
- warnings.push('No graph found — run graphify_build first');
1215
- }
1216
- else {
1217
- const loaded = await loadKnowledgeGraph(targetPath);
1218
- totalFiles = new Set([...loaded.nodes.values()].map(n => n.source_file).filter(Boolean)).size;
1219
- if (loaded.nodes.size === 0)
1220
- warnings.push('Graph is empty — rebuild with graphify_build');
1221
- if (totalFiles < 3)
1222
- warnings.push('Very few source files — graph analysis may not be useful');
1223
- if (loaded.edges.length === 0)
1224
- warnings.push('No edges found — code may lack imports/calls');
1225
- }
1226
- return {
1227
- success: true,
1228
- totalFiles,
1229
- warnings,
1230
- healthy: warnings.length === 0,
1231
- message: warnings.length === 0
1232
- ? `Corpus looks healthy (${files.length} files).`
1233
- : `${warnings.length} warning(s) found.`,
1234
- };
1235
- }
1236
- catch (err) {
1237
- return { error: true, message: String(err) };
1238
- }
1239
- },
1240
- };
1241
- // ── Exports ───────────────────────────────────────────────────────────────────
1242
27
  export const graphifyTools = [
1243
- graphifyBuildTool,
1244
- graphifyQueryTool,
1245
- graphifyGodNodesTool,
1246
- graphifyGetNodeTool,
1247
- graphifyShortestPathTool,
1248
- graphifyGetCommunityTool,
1249
- graphifyStatsTool,
1250
- graphifySurprisesTool,
1251
- graphifyVisualizeTool,
1252
- graphifyWatchTool,
1253
- graphifyWatchStopTool,
1254
- graphifyReportTool,
1255
- graphifySuggestTool,
1256
- graphifyHealthTool,
28
+ shimTool('graphify_build', 'monograph_build'),
29
+ shimTool('graphify_query', 'monograph_query'),
30
+ shimTool('graphify_god_nodes', 'monograph_god_nodes'),
31
+ shimTool('graphify_get_node', 'monograph_get_node'),
32
+ shimTool('graphify_shortest_path', 'monograph_shortest_path'),
33
+ shimTool('graphify_community', 'monograph_community'),
34
+ shimTool('graphify_stats', 'monograph_stats'),
35
+ shimTool('graphify_surprises', 'monograph_surprises'),
36
+ // Bug fix: graphify_suggest used to ignore prompt — now maps to monograph_suggest with task param
37
+ shimTool('graphify_suggest', 'monograph_suggest', (input) => ({
38
+ task: input.prompt ?? input.task ?? '',
39
+ limit: input.limit,
40
+ })),
41
+ shimTool('graphify_visualize', 'monograph_visualize'),
42
+ shimTool('graphify_watch', 'monograph_watch'),
43
+ shimTool('graphify_watch_stop', 'monograph_watch_stop'),
44
+ shimTool('graphify_report', 'monograph_report'),
45
+ // Bug fix: graphify_health previously referenced `files.length` (ReferenceError) — now delegates cleanly
46
+ shimTool('graphify_health', 'monograph_health'),
1257
47
  ];
1258
48
  export default graphifyTools;
1259
49
  //# sourceMappingURL=graphify-tools.js.map