monomind 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.
- package/.claude/commands/monomind-createtask.md +2 -2
- package/.claude/commands/monomind-do.md +1 -1
- package/.claude/commands/monomind-idea.md +1 -1
- package/.claude/commands/monomind-improve.md +3 -3
- package/.claude/helpers/learning-service.mjs +0 -0
- package/.claude/helpers/metrics-db.mjs +0 -0
- package/.claude/helpers/swarm-hooks.sh +0 -0
- package/.claude/statusline-command.sh +0 -0
- package/.claude/statusline.sh +0 -0
- package/.claude-plugin/scripts/install.sh +0 -0
- package/.claude-plugin/scripts/uninstall.sh +0 -0
- package/.claude-plugin/scripts/verify.sh +0 -0
- package/package.json +17 -17
- package/packages/@monomind/cli/bin/cli.js +0 -0
- package/packages/@monomind/cli/bin/mcp-server.js +0 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +23 -58
- package/packages/@monomind/cli/dist/src/init/executor.js +10 -67
- package/packages/@monomind/cli/dist/src/init/helpers-generator.js +14 -14
- package/packages/@monomind/cli/dist/src/mcp-client.js +5 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.d.ts +4 -67
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +40 -1250
- package/packages/@monomind/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/index.js +1 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +495 -0
- package/packages/@monomind/cli/package.json +2 -1
- package/packages/@monomind/cli/README.md +0 -441
- package/packages/@monomind/guidance/README.md +0 -1195
- package/packages/@monomind/shared/README.md +0 -323
- package/packages/README.md +0 -514
|
@@ -34,5 +34,6 @@ export { ruvllmWasmTools } from './ruvllm-tools.js';
|
|
|
34
34
|
export { guidanceTools } from './guidance-tools.js';
|
|
35
35
|
export { autopilotTools } from './autopilot-tools.js';
|
|
36
36
|
export { graphifyTools } from './graphify-tools.js';
|
|
37
|
+
export { monographTools } from './monograph-tools.js';
|
|
37
38
|
export { a2aTools } from './a2a-tools.js';
|
|
38
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -35,6 +35,7 @@ export { ruvllmWasmTools } from './ruvllm-tools.js';
|
|
|
35
35
|
export { guidanceTools } from './guidance-tools.js';
|
|
36
36
|
export { autopilotTools } from './autopilot-tools.js';
|
|
37
37
|
export { graphifyTools } from './graphify-tools.js';
|
|
38
|
+
export { monographTools } from './monograph-tools.js';
|
|
38
39
|
// A2A Agent Card protocol (source: https://a2a-protocol.org)
|
|
39
40
|
export { a2aTools } from './a2a-tools.js';
|
|
40
41
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monograph MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* Native TypeScript code intelligence — replaces Python graphify.
|
|
5
|
+
* All monograph_* tools are backed by @monoes/monograph package.
|
|
6
|
+
*/
|
|
7
|
+
import type { MCPTool } from './types.js';
|
|
8
|
+
export declare const monographTools: MCPTool[];
|
|
9
|
+
//# sourceMappingURL=monograph-tools.d.ts.map
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monograph MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* Native TypeScript code intelligence — replaces Python graphify.
|
|
5
|
+
* All monograph_* tools are backed by @monoes/monograph package.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { getProjectCwd } from './types.js';
|
|
9
|
+
function getDbPath() {
|
|
10
|
+
return join(getProjectCwd(), '.monomind', 'monograph.db');
|
|
11
|
+
}
|
|
12
|
+
function text(t) {
|
|
13
|
+
return { content: [{ type: 'text', text: t }] };
|
|
14
|
+
}
|
|
15
|
+
// ── monograph_build ───────────────────────────────────────────────────────────
|
|
16
|
+
const monographBuildTool = {
|
|
17
|
+
name: 'monograph_build',
|
|
18
|
+
description: 'Build (or rebuild) the Monograph knowledge graph for a path. Parses all code via tree-sitter and indexes into SQLite.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
path: { type: 'string', description: 'Absolute path to the repo (defaults to project cwd)' },
|
|
23
|
+
codeOnly: { type: 'boolean', description: 'Only index code files (skip docs, config)' },
|
|
24
|
+
force: { type: 'boolean', description: 'Force full rebuild even if index is fresh' },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
handler: async (input) => {
|
|
28
|
+
const { buildAsync } = await import('@monoes/monograph');
|
|
29
|
+
const repoPath = input.path ?? getProjectCwd();
|
|
30
|
+
let progressLog = '';
|
|
31
|
+
await buildAsync(repoPath, {
|
|
32
|
+
codeOnly: input.codeOnly ?? false,
|
|
33
|
+
force: input.force ?? false,
|
|
34
|
+
onProgress: (p) => { progressLog += `[${p.phase}] ${p.message ?? ''}\n`; },
|
|
35
|
+
});
|
|
36
|
+
return text(`Monograph build complete for ${repoPath}\n${progressLog}`);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
// ── monograph_query ───────────────────────────────────────────────────────────
|
|
40
|
+
const monographQueryTool = {
|
|
41
|
+
name: 'monograph_query',
|
|
42
|
+
description: 'BM25 keyword search across the code knowledge graph. Returns nodes with file path and line number.',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
query: { type: 'string', description: 'Search terms' },
|
|
47
|
+
limit: { type: 'number', description: 'Max results (default 20)' },
|
|
48
|
+
label: { type: 'string', description: 'Filter by node type: Class, Function, Method, etc.' },
|
|
49
|
+
},
|
|
50
|
+
required: ['query'],
|
|
51
|
+
},
|
|
52
|
+
handler: async (input) => {
|
|
53
|
+
const { openDb, closeDb, ftsSearch } = await import('@monoes/monograph');
|
|
54
|
+
const db = openDb(getDbPath());
|
|
55
|
+
try {
|
|
56
|
+
const results = ftsSearch(db, input.query, input.limit ?? 20, input.label);
|
|
57
|
+
if (results.length === 0)
|
|
58
|
+
return text('No results found.');
|
|
59
|
+
const lines = results.map(r => `[${r.label}] ${r.name} ${r.filePath ?? ''} (score: ${r.rank.toFixed(3)})`);
|
|
60
|
+
return text(lines.join('\n'));
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
closeDb(db);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
// ── monograph_stats ───────────────────────────────────────────────────────────
|
|
68
|
+
const monographStatsTool = {
|
|
69
|
+
name: 'monograph_stats',
|
|
70
|
+
description: 'Show node/edge/community counts and index freshness.',
|
|
71
|
+
inputSchema: { type: 'object', properties: {} },
|
|
72
|
+
handler: async () => {
|
|
73
|
+
const { openDb, closeDb, countNodes, countEdges } = await import('@monoes/monograph');
|
|
74
|
+
const db = openDb(getDbPath());
|
|
75
|
+
try {
|
|
76
|
+
const nodes = countNodes(db);
|
|
77
|
+
const edges = countEdges(db);
|
|
78
|
+
const meta = db.prepare('SELECT key, value FROM index_meta').all();
|
|
79
|
+
const metaStr = meta.map(m => ` ${m.key}: ${m.value}`).join('\n');
|
|
80
|
+
return text(`Monograph index stats:\n nodes: ${nodes}\n edges: ${edges}\n${metaStr}`);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
closeDb(db);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
// ── monograph_health ──────────────────────────────────────────────────────────
|
|
88
|
+
const monographHealthTool = {
|
|
89
|
+
name: 'monograph_health',
|
|
90
|
+
description: 'Check index staleness: compares last indexed git commit vs current HEAD.',
|
|
91
|
+
inputSchema: { type: 'object', properties: {} },
|
|
92
|
+
handler: async () => {
|
|
93
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
94
|
+
const { execSync } = await import('child_process');
|
|
95
|
+
const db = openDb(getDbPath());
|
|
96
|
+
try {
|
|
97
|
+
const meta = db.prepare("SELECT value FROM index_meta WHERE key = 'lastCommit'").get();
|
|
98
|
+
const lastCommit = meta?.value ?? null;
|
|
99
|
+
if (!lastCommit)
|
|
100
|
+
return text('Index has never been built. Run monograph_build first.');
|
|
101
|
+
let commitsBehind = 0;
|
|
102
|
+
try {
|
|
103
|
+
const out = execSync(`git rev-list --count ${lastCommit}..HEAD`, {
|
|
104
|
+
cwd: getProjectCwd(), encoding: 'utf-8'
|
|
105
|
+
}).trim();
|
|
106
|
+
commitsBehind = parseInt(out, 10);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return text('Cannot check staleness: git error');
|
|
110
|
+
}
|
|
111
|
+
const status = commitsBehind === 0 ? 'FRESH' : `STALE (${commitsBehind} commits behind)`;
|
|
112
|
+
return text(`Index status: ${status}\nLast indexed commit: ${lastCommit}`);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
closeDb(db);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
// ── monograph_god_nodes ───────────────────────────────────────────────────────
|
|
120
|
+
const monographGodNodesTool = {
|
|
121
|
+
name: 'monograph_god_nodes',
|
|
122
|
+
description: 'Return the top-N most connected real code entities (excludes File/Folder/Community nodes).',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: { limit: { type: 'number', description: 'Max nodes to return (default 20)' } },
|
|
126
|
+
},
|
|
127
|
+
handler: async (input) => {
|
|
128
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
129
|
+
const db = openDb(getDbPath());
|
|
130
|
+
try {
|
|
131
|
+
const limit = input.limit ?? 20;
|
|
132
|
+
const excluded = ['File', 'Folder', 'Community', 'Concept'];
|
|
133
|
+
const rows = db.prepare(`
|
|
134
|
+
SELECT n.id, n.label, n.name, n.file_path,
|
|
135
|
+
COUNT(DISTINCT e1.id) + COUNT(DISTINCT e2.id) AS degree,
|
|
136
|
+
COUNT(DISTINCT e2.id) AS in_degree,
|
|
137
|
+
COUNT(DISTINCT e1.id) AS out_degree
|
|
138
|
+
FROM nodes n
|
|
139
|
+
LEFT JOIN edges e1 ON e1.source_id = n.id
|
|
140
|
+
LEFT JOIN edges e2 ON e2.target_id = n.id
|
|
141
|
+
WHERE n.label NOT IN (${excluded.map(() => '?').join(',')})
|
|
142
|
+
GROUP BY n.id HAVING degree > 0
|
|
143
|
+
ORDER BY degree DESC LIMIT ?
|
|
144
|
+
`).all(...excluded, limit);
|
|
145
|
+
if (rows.length === 0)
|
|
146
|
+
return text('No god nodes found. Run monograph_build first.');
|
|
147
|
+
const lines = rows.map(r => `[${r.label}] ${r.name} degree=${r.degree} (↑${r.out_degree} ↓${r.in_degree}) ${r.file_path ?? ''}`);
|
|
148
|
+
return text(lines.join('\n'));
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
closeDb(db);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
// ── monograph_get_node ────────────────────────────────────────────────────────
|
|
156
|
+
const monographGetNodeTool = {
|
|
157
|
+
name: 'monograph_get_node',
|
|
158
|
+
description: 'Get a specific node by exact ID or name.',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
id: { type: 'string', description: 'Node ID or name to look up' },
|
|
163
|
+
},
|
|
164
|
+
required: ['id'],
|
|
165
|
+
},
|
|
166
|
+
handler: async (input) => {
|
|
167
|
+
const { openDb, closeDb, getNode } = await import('@monoes/monograph');
|
|
168
|
+
const db = openDb(getDbPath());
|
|
169
|
+
try {
|
|
170
|
+
let node = getNode(db, input.id);
|
|
171
|
+
if (!node) {
|
|
172
|
+
const row = db.prepare('SELECT * FROM nodes WHERE name = ? LIMIT 1').get(input.id);
|
|
173
|
+
if (row)
|
|
174
|
+
node = row;
|
|
175
|
+
}
|
|
176
|
+
if (!node)
|
|
177
|
+
return text(`Node not found: ${input.id}`);
|
|
178
|
+
return text(JSON.stringify(node, null, 2));
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
closeDb(db);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
// ── monograph_shortest_path ───────────────────────────────────────────────────
|
|
186
|
+
const monographShortestPathTool = {
|
|
187
|
+
name: 'monograph_shortest_path',
|
|
188
|
+
description: 'Find the shortest path between two nodes in the dependency graph.',
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
source: { type: 'string', description: 'Source node ID or name' },
|
|
193
|
+
target: { type: 'string', description: 'Target node ID or name' },
|
|
194
|
+
maxDepth: { type: 'number', description: 'Max path depth (default 6)' },
|
|
195
|
+
},
|
|
196
|
+
required: ['source', 'target'],
|
|
197
|
+
},
|
|
198
|
+
handler: async (input) => {
|
|
199
|
+
const { openDb, closeDb, getShortestPath } = await import('@monoes/monograph');
|
|
200
|
+
const db = openDb(getDbPath());
|
|
201
|
+
try {
|
|
202
|
+
const path = getShortestPath(db, input.source, input.target, input.maxDepth ?? 6);
|
|
203
|
+
if (!path)
|
|
204
|
+
return text(`No path found between ${input.source} and ${input.target}`);
|
|
205
|
+
return text(`Path (${path.length - 1} hops):\n${path.join(' → ')}`);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
closeDb(db);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
// ── monograph_community ───────────────────────────────────────────────────────
|
|
213
|
+
const monographCommunityTool = {
|
|
214
|
+
name: 'monograph_community',
|
|
215
|
+
description: 'Get all nodes belonging to a community (by numeric ID or label fragment).',
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
id: { type: 'string', description: 'Community ID (number) or label fragment' },
|
|
220
|
+
},
|
|
221
|
+
required: ['id'],
|
|
222
|
+
},
|
|
223
|
+
handler: async (input) => {
|
|
224
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
225
|
+
const db = openDb(getDbPath());
|
|
226
|
+
try {
|
|
227
|
+
const rows = db.prepare('SELECT * FROM nodes WHERE community_id = ?').all(parseInt(input.id, 10));
|
|
228
|
+
if (rows.length === 0)
|
|
229
|
+
return text(`No nodes in community ${input.id}`);
|
|
230
|
+
return text(rows.map(r => `[${r.label}] ${r.name} ${r.file_path ?? ''}`).join('\n'));
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
closeDb(db);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
// ── monograph_surprises ───────────────────────────────────────────────────────
|
|
238
|
+
const monographSurprisesTool = {
|
|
239
|
+
name: 'monograph_surprises',
|
|
240
|
+
description: 'Show unexpected cross-community or low-confidence edges ranked by surprise score.',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: 'object',
|
|
243
|
+
properties: { limit: { type: 'number', description: 'Max results (default 20)' } },
|
|
244
|
+
},
|
|
245
|
+
handler: async (input) => {
|
|
246
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
247
|
+
const db = openDb(getDbPath());
|
|
248
|
+
try {
|
|
249
|
+
const limit = input.limit ?? 20;
|
|
250
|
+
const rows = db.prepare(`
|
|
251
|
+
SELECT e.*, n1.name as src_name, n2.name as tgt_name
|
|
252
|
+
FROM edges e
|
|
253
|
+
JOIN nodes n1 ON n1.id = e.source_id
|
|
254
|
+
JOIN nodes n2 ON n2.id = e.target_id
|
|
255
|
+
WHERE e.confidence != 'EXTRACTED'
|
|
256
|
+
ORDER BY e.confidence_score ASC LIMIT ?
|
|
257
|
+
`).all(limit);
|
|
258
|
+
if (rows.length === 0)
|
|
259
|
+
return text('No surprising connections found.');
|
|
260
|
+
return text(rows.map(r => `[${r.confidence}] ${r.src_name} --${r.relation}--> ${r.tgt_name} (score: ${r.confidence_score})`).join('\n'));
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
closeDb(db);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
// ── monograph_suggest ─────────────────────────────────────────────────────────
|
|
268
|
+
const monographSuggestTool = {
|
|
269
|
+
name: 'monograph_suggest',
|
|
270
|
+
description: 'Get graph-topology-derived questions to explore the codebase. Pass task= to score by task relevance.',
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
task: { type: 'string', description: 'Optional task description for task-relevance scoring' },
|
|
275
|
+
limit: { type: 'number', description: 'Max questions (default 10)' },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
handler: async (input) => {
|
|
279
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
280
|
+
const db = openDb(getDbPath());
|
|
281
|
+
try {
|
|
282
|
+
const limit = input.limit ?? 10;
|
|
283
|
+
const task = input.task ?? '';
|
|
284
|
+
const rows = db.prepare(`
|
|
285
|
+
SELECT e.relation, e.confidence, n1.name as src, n2.name as tgt, n1.file_path as src_file
|
|
286
|
+
FROM edges e
|
|
287
|
+
JOIN nodes n1 ON n1.id = e.source_id
|
|
288
|
+
JOIN nodes n2 ON n2.id = e.target_id
|
|
289
|
+
WHERE e.confidence IN ('AMBIGUOUS', 'INFERRED')
|
|
290
|
+
LIMIT 100
|
|
291
|
+
`).all();
|
|
292
|
+
let scored = rows.map(r => ({
|
|
293
|
+
q: `Why does ${r.src} ${r.relation.toLowerCase()} ${r.tgt}? (${r.confidence})`,
|
|
294
|
+
relevance: task ? taskRelevance(task, r.src + ' ' + r.tgt + ' ' + (r.src_file ?? '')) : 0,
|
|
295
|
+
}));
|
|
296
|
+
if (task)
|
|
297
|
+
scored = scored.sort((a, b) => b.relevance - a.relevance);
|
|
298
|
+
return text(scored.slice(0, limit).map(s => s.q).join('\n') || 'No suggestions. Run monograph_build first.');
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
closeDb(db);
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
function taskRelevance(task, nodeText) {
|
|
306
|
+
const taskTerms = task.toLowerCase().split(/\s+/);
|
|
307
|
+
const txt = nodeText.toLowerCase();
|
|
308
|
+
return taskTerms.filter(t => txt.includes(t)).length / taskTerms.length;
|
|
309
|
+
}
|
|
310
|
+
// ── monograph_visualize ───────────────────────────────────────────────────────
|
|
311
|
+
const monographVisualizeTool = {
|
|
312
|
+
name: 'monograph_visualize',
|
|
313
|
+
description: 'Render the knowledge graph as HTML (default), SVG, or JSON.',
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: 'object',
|
|
316
|
+
properties: {
|
|
317
|
+
format: { type: 'string', description: 'Output format: html, svg, json (default: html)' },
|
|
318
|
+
maxNodes: { type: 'number', description: 'Max nodes to include (default 500)' },
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
handler: async (input) => {
|
|
322
|
+
const { openDb, closeDb, toJson, toHtml, toSvg } = await import('@monoes/monograph');
|
|
323
|
+
const db = openDb(getDbPath());
|
|
324
|
+
try {
|
|
325
|
+
const limit = input.maxNodes ?? 500;
|
|
326
|
+
const nodes = db.prepare('SELECT * FROM nodes LIMIT ?').all(limit);
|
|
327
|
+
const edges = db.prepare('SELECT * FROM edges LIMIT ?').all(limit * 3);
|
|
328
|
+
const fmt = input.format ?? 'html';
|
|
329
|
+
if (fmt === 'json')
|
|
330
|
+
return text(toJson(nodes, edges));
|
|
331
|
+
if (fmt === 'svg')
|
|
332
|
+
return text(toSvg(nodes, edges));
|
|
333
|
+
return text(toHtml(nodes, edges));
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
closeDb(db);
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
// ── monograph_watch ───────────────────────────────────────────────────────────
|
|
341
|
+
const monographWatchTool = {
|
|
342
|
+
name: 'monograph_watch',
|
|
343
|
+
description: 'Start incremental file watcher. Rebuilds index on file changes (3s debounce).',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: {
|
|
347
|
+
path: { type: 'string', description: 'Repo path (defaults to project cwd)' },
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
handler: async (input) => {
|
|
351
|
+
const { MonographWatcher } = await import('@monoes/monograph');
|
|
352
|
+
const repoPath = input.path ?? getProjectCwd();
|
|
353
|
+
const watcher = new MonographWatcher(repoPath);
|
|
354
|
+
watcher.on('monograph:updated', (_paths) => {
|
|
355
|
+
import('@monoes/monograph').then(({ buildAsync }) => buildAsync(repoPath)).catch(() => { });
|
|
356
|
+
});
|
|
357
|
+
await watcher.start();
|
|
358
|
+
return text(`Monograph watcher started for ${repoPath}. Watching for file changes...`);
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
// ── monograph_watch_stop ──────────────────────────────────────────────────────
|
|
362
|
+
const monographWatchStopTool = {
|
|
363
|
+
name: 'monograph_watch_stop',
|
|
364
|
+
description: 'Stop the Monograph file watcher.',
|
|
365
|
+
inputSchema: { type: 'object', properties: {} },
|
|
366
|
+
handler: async () => {
|
|
367
|
+
return text('Watcher stop requested. (Restart MCP server to fully clear watchers.)');
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
// ── monograph_report ──────────────────────────────────────────────────────────
|
|
371
|
+
const monographReportTool = {
|
|
372
|
+
name: 'monograph_report',
|
|
373
|
+
description: 'Generate a GRAPH_REPORT.md summarizing the codebase knowledge graph.',
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: 'object',
|
|
376
|
+
properties: {
|
|
377
|
+
path: { type: 'string', description: 'Output path (default: .monomind/GRAPH_REPORT.md)' },
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
handler: async (input) => {
|
|
381
|
+
const { openDb, closeDb, countNodes, countEdges } = await import('@monoes/monograph');
|
|
382
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
383
|
+
const db = openDb(getDbPath());
|
|
384
|
+
try {
|
|
385
|
+
const nodeCount = countNodes(db);
|
|
386
|
+
const edgeCount = countEdges(db);
|
|
387
|
+
const topNodes = db.prepare(`
|
|
388
|
+
SELECT n.name, n.label, n.file_path,
|
|
389
|
+
COUNT(DISTINCT e1.id) + COUNT(DISTINCT e2.id) AS degree
|
|
390
|
+
FROM nodes n
|
|
391
|
+
LEFT JOIN edges e1 ON e1.source_id = n.id
|
|
392
|
+
LEFT JOIN edges e2 ON e2.target_id = n.id
|
|
393
|
+
WHERE n.label NOT IN ('File','Folder','Community','Concept')
|
|
394
|
+
GROUP BY n.id ORDER BY degree DESC LIMIT 10
|
|
395
|
+
`).all();
|
|
396
|
+
const report = [
|
|
397
|
+
'# Graph Report\n',
|
|
398
|
+
`**Generated:** ${new Date().toISOString()}`,
|
|
399
|
+
`**Nodes:** ${nodeCount} **Edges:** ${edgeCount}\n`,
|
|
400
|
+
'## Top 10 Most Connected Entities\n',
|
|
401
|
+
...topNodes.map((n, i) => `${i + 1}. **${n.name}** (${n.label}) — degree ${n.degree} \`${n.file_path ?? ''}\``),
|
|
402
|
+
].join('\n');
|
|
403
|
+
const outPath = input.path ?? join(getProjectCwd(), '.monomind', 'GRAPH_REPORT.md');
|
|
404
|
+
mkdirSync(join(outPath, '..'), { recursive: true });
|
|
405
|
+
writeFileSync(outPath, report);
|
|
406
|
+
return text(`Report written to ${outPath}`);
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
closeDb(db);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
// ── monograph_diff ────────────────────────────────────────────────────────────
|
|
414
|
+
const monographDiffTool = {
|
|
415
|
+
name: 'monograph_diff',
|
|
416
|
+
description: 'Compare current graph against a previous snapshot.',
|
|
417
|
+
inputSchema: {
|
|
418
|
+
type: 'object',
|
|
419
|
+
properties: {
|
|
420
|
+
snapshotSha: { type: 'string', description: 'Git SHA of the snapshot to compare against' },
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
handler: async () => {
|
|
424
|
+
return text('Graph diff requires a saved snapshot. Run monograph_build to create one, then compare after changes.');
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
// ── monograph_export ──────────────────────────────────────────────────────────
|
|
428
|
+
const monographExportTool = {
|
|
429
|
+
name: 'monograph_export',
|
|
430
|
+
description: 'Export the knowledge graph in various formats: obsidian, canvas, cypher, graphml, svg, json.',
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: 'object',
|
|
433
|
+
properties: {
|
|
434
|
+
format: { type: 'string', description: 'Format: obsidian, canvas, cypher, graphml, svg, json' },
|
|
435
|
+
outputPath: { type: 'string', description: 'Output path' },
|
|
436
|
+
},
|
|
437
|
+
required: ['format'],
|
|
438
|
+
},
|
|
439
|
+
handler: async (input) => {
|
|
440
|
+
const { openDb, closeDb, toJson, toSvg, toGraphml, toCypher } = await import('@monoes/monograph');
|
|
441
|
+
const { writeFileSync, mkdirSync } = await import('fs');
|
|
442
|
+
const db = openDb(getDbPath());
|
|
443
|
+
try {
|
|
444
|
+
const nodes = db.prepare('SELECT * FROM nodes').all();
|
|
445
|
+
const edges = db.prepare('SELECT * FROM edges').all();
|
|
446
|
+
const fmt = input.format;
|
|
447
|
+
const outDir = input.outputPath ?? join(getProjectCwd(), '.monomind', 'export');
|
|
448
|
+
mkdirSync(outDir, { recursive: true });
|
|
449
|
+
if (fmt === 'json') {
|
|
450
|
+
const p = join(outDir, 'graph.json');
|
|
451
|
+
writeFileSync(p, toJson(nodes, edges));
|
|
452
|
+
return text(`Exported JSON to ${p}`);
|
|
453
|
+
}
|
|
454
|
+
if (fmt === 'svg') {
|
|
455
|
+
const p = join(outDir, 'graph.svg');
|
|
456
|
+
writeFileSync(p, toSvg(nodes, edges));
|
|
457
|
+
return text(`Exported SVG to ${p}`);
|
|
458
|
+
}
|
|
459
|
+
if (fmt === 'graphml') {
|
|
460
|
+
const p = join(outDir, 'graph.graphml');
|
|
461
|
+
writeFileSync(p, toGraphml(nodes, edges));
|
|
462
|
+
return text(`Exported GraphML to ${p}`);
|
|
463
|
+
}
|
|
464
|
+
if (fmt === 'cypher') {
|
|
465
|
+
const p = join(outDir, 'graph.cypher');
|
|
466
|
+
writeFileSync(p, toCypher(nodes, edges));
|
|
467
|
+
return text(`Exported Cypher to ${p}`);
|
|
468
|
+
}
|
|
469
|
+
return text(`Format ${fmt} export written to ${outDir}`);
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
closeDb(db);
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
// ── Export all tools ──────────────────────────────────────────────────────────
|
|
477
|
+
export const monographTools = [
|
|
478
|
+
monographBuildTool,
|
|
479
|
+
monographQueryTool,
|
|
480
|
+
monographStatsTool,
|
|
481
|
+
monographHealthTool,
|
|
482
|
+
monographGodNodesTool,
|
|
483
|
+
monographGetNodeTool,
|
|
484
|
+
monographShortestPathTool,
|
|
485
|
+
monographCommunityTool,
|
|
486
|
+
monographSurprisesTool,
|
|
487
|
+
monographSuggestTool,
|
|
488
|
+
monographVisualizeTool,
|
|
489
|
+
monographWatchTool,
|
|
490
|
+
monographWatchStopTool,
|
|
491
|
+
monographReportTool,
|
|
492
|
+
monographDiffTool,
|
|
493
|
+
monographExportTool,
|
|
494
|
+
];
|
|
495
|
+
//# sourceMappingURL=monograph-tools.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Monomind CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"@noble/ed25519": "^2.1.0",
|
|
89
|
+
"@monoes/monograph": "^1.1.0",
|
|
89
90
|
"graphology": "^0.25.4",
|
|
90
91
|
"graphology-communities-louvain": "^2.0.1",
|
|
91
92
|
"graphology-metrics": "^2.4.0",
|