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