@monoes/monomindcli 1.10.29 → 1.10.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/auto-memory-hook.mjs +39 -4
- package/.claude/helpers/handlers/edit-handler.cjs +145 -0
- package/.claude/helpers/handlers/route-handler.cjs +393 -0
- package/.claude/helpers/handlers/session-handler.cjs +167 -0
- package/.claude/helpers/handlers/session-restore-handler.cjs +343 -0
- package/.claude/helpers/handlers/task-handler.cjs +329 -0
- package/.claude/helpers/hook-handler.cjs +114 -2273
- package/.claude/helpers/intelligence.cjs +21 -2
- package/.claude/helpers/learning-service.mjs +166 -8
- package/.claude/helpers/memory-palace.cjs +72 -12
- package/.claude/helpers/router.cjs +79 -5
- package/.claude/helpers/statusline.cjs +193 -399
- package/.claude/helpers/utils/micro-agents.cjs +338 -0
- package/.claude/helpers/utils/monograph.cjs +349 -0
- package/.claude/helpers/utils/telemetry.cjs +144 -0
- package/.claude/skills/agent-browser-testing/SKILL.md +3 -2
- package/.claude/skills/monomind/browse-agentcore.md +116 -0
- package/.claude/skills/monomind/browse-electron.md +189 -0
- package/.claude/skills/monomind/browse-qa.md +229 -0
- package/.claude/skills/monomind/browse-references/authentication.md +162 -0
- package/.claude/skills/monomind/browse-references/trust-boundaries.md +41 -0
- package/.claude/skills/monomind/browse-references/video-recording.md +84 -0
- package/.claude/skills/monomind/browse-slack.md +189 -0
- package/.claude/skills/monomind/browse-vercel.md +240 -0
- package/.claude/skills/monomind/browse.md +724 -0
- package/dist/src/browser/actions.d.ts +13 -0
- package/dist/src/browser/actions.d.ts.map +1 -0
- package/dist/src/browser/actions.js +201 -0
- package/dist/src/browser/actions.js.map +1 -0
- package/dist/src/browser/browser.d.ts +14 -0
- package/dist/src/browser/browser.d.ts.map +1 -0
- package/dist/src/browser/browser.js +198 -0
- package/dist/src/browser/browser.js.map +1 -0
- package/dist/src/browser/cdp.d.ts +17 -0
- package/dist/src/browser/cdp.d.ts.map +1 -0
- package/dist/src/browser/cdp.js +106 -0
- package/dist/src/browser/cdp.js.map +1 -0
- package/dist/src/browser/index.d.ts +11 -0
- package/dist/src/browser/index.d.ts.map +1 -0
- package/dist/src/browser/index.js +11 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/browser/network.d.ts +11 -0
- package/dist/src/browser/network.d.ts.map +1 -0
- package/dist/src/browser/network.js +81 -0
- package/dist/src/browser/network.js.map +1 -0
- package/dist/src/browser/screenshot.d.ts +15 -0
- package/dist/src/browser/screenshot.d.ts.map +1 -0
- package/dist/src/browser/screenshot.js +36 -0
- package/dist/src/browser/screenshot.js.map +1 -0
- package/dist/src/browser/session.d.ts +8 -0
- package/dist/src/browser/session.d.ts.map +1 -0
- package/dist/src/browser/session.js +50 -0
- package/dist/src/browser/session.js.map +1 -0
- package/dist/src/browser/snapshot.d.ts +12 -0
- package/dist/src/browser/snapshot.d.ts.map +1 -0
- package/dist/src/browser/snapshot.js +147 -0
- package/dist/src/browser/snapshot.js.map +1 -0
- package/dist/src/browser/tabs.d.ts +8 -0
- package/dist/src/browser/tabs.d.ts.map +1 -0
- package/dist/src/browser/tabs.js +25 -0
- package/dist/src/browser/tabs.js.map +1 -0
- package/dist/src/browser/types.d.ts +109 -0
- package/dist/src/browser/types.d.ts.map +1 -0
- package/dist/src/browser/types.js +16 -0
- package/dist/src/browser/types.js.map +1 -0
- package/dist/src/browser/wait.d.ts +4 -0
- package/dist/src/browser/wait.d.ts.map +1 -0
- package/dist/src/browser/wait.js +122 -0
- package/dist/src/browser/wait.js.map +1 -0
- package/dist/src/commands/browse.d.ts +8 -0
- package/dist/src/commands/browse.d.ts.map +1 -0
- package/dist/src/commands/browse.js +573 -0
- package/dist/src/commands/browse.js.map +1 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/ui/dashboard-v2.html +1692 -0
- package/dist/src/ui/server.mjs +15 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Extracted from hook-handler.cjs — micro-agent trigger scanner and knowledge base helpers.
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const { _openMonographDb } = require('./monograph.cjs');
|
|
8
|
+
|
|
9
|
+
const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
10
|
+
|
|
11
|
+
function safeRequire(modulePath) {
|
|
12
|
+
try {
|
|
13
|
+
if (fs.existsSync(modulePath)) {
|
|
14
|
+
const origLog = console.log;
|
|
15
|
+
const origError = console.error;
|
|
16
|
+
console.log = () => {};
|
|
17
|
+
console.error = () => {};
|
|
18
|
+
try {
|
|
19
|
+
const mod = require(modulePath);
|
|
20
|
+
return mod;
|
|
21
|
+
} finally {
|
|
22
|
+
console.log = origLog;
|
|
23
|
+
console.error = origError;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch (e) { /* silently fail */ }
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── MicroAgent Trigger Scanner ────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function _triggerExtractYamlValue(raw) {
|
|
33
|
+
var v = raw.trim();
|
|
34
|
+
if (v.startsWith('"') && v.endsWith('"')) {
|
|
35
|
+
v = v.slice(1, -1).replace(/\\\\/g, '\\');
|
|
36
|
+
} else if (v.startsWith("'") && v.endsWith("'")) {
|
|
37
|
+
v = v.slice(1, -1);
|
|
38
|
+
}
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _triggerFinalize(partial, agentSlug) {
|
|
43
|
+
return { pattern: partial.pattern, mode: partial.mode || 'inject', priority: partial.priority || 0, agentSlug: agentSlug };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _triggerExtractFromFrontmatter(content, agentSlug) {
|
|
47
|
+
var fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
48
|
+
if (!fmMatch) return [];
|
|
49
|
+
var block = fmMatch[1];
|
|
50
|
+
var triggers = [];
|
|
51
|
+
var lines = block.split('\n');
|
|
52
|
+
var inTriggers = false;
|
|
53
|
+
var cur = null;
|
|
54
|
+
for (var i = 0; i < lines.length; i++) {
|
|
55
|
+
var line = lines[i];
|
|
56
|
+
var trimmed = line.trim();
|
|
57
|
+
var indent = line.length - line.trimStart().length;
|
|
58
|
+
if (trimmed === 'triggers:' || trimmed.startsWith('triggers:')) { inTriggers = true; continue; }
|
|
59
|
+
if (inTriggers && indent === 0 && /^[a-zA-Z]/.test(trimmed)) {
|
|
60
|
+
inTriggers = false;
|
|
61
|
+
if (cur && cur.pattern) triggers.push(_triggerFinalize(cur, agentSlug));
|
|
62
|
+
cur = null; continue;
|
|
63
|
+
}
|
|
64
|
+
if (!inTriggers) continue;
|
|
65
|
+
if (trimmed.startsWith('- pattern:')) {
|
|
66
|
+
if (cur && cur.pattern) triggers.push(_triggerFinalize(cur, agentSlug));
|
|
67
|
+
cur = { pattern: _triggerExtractYamlValue(trimmed.replace(/^- pattern:\s*/, '')), agentSlug: agentSlug };
|
|
68
|
+
} else if (cur && trimmed.startsWith('mode:')) {
|
|
69
|
+
var mv = _triggerExtractYamlValue(trimmed.replace(/^mode:\s*/, ''));
|
|
70
|
+
if (mv === 'inject' || mv === 'takeover') cur.mode = mv;
|
|
71
|
+
} else if (cur && trimmed.startsWith('priority:')) {
|
|
72
|
+
var pv = parseInt(trimmed.replace(/^priority:\s*/, ''), 10);
|
|
73
|
+
if (!isNaN(pv)) cur.priority = pv;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (cur && cur.pattern) triggers.push(_triggerFinalize(cur, agentSlug));
|
|
77
|
+
return triggers;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function _triggerCollectMdFiles(dir) {
|
|
81
|
+
var results = [];
|
|
82
|
+
try {
|
|
83
|
+
var entries = fs.readdirSync(dir);
|
|
84
|
+
for (var i = 0; i < entries.length; i++) {
|
|
85
|
+
var full = path.join(dir, entries[i]);
|
|
86
|
+
try {
|
|
87
|
+
var st = fs.lstatSync(full);
|
|
88
|
+
if (st.isDirectory()) results = results.concat(_triggerCollectMdFiles(full));
|
|
89
|
+
else if (entries[i].endsWith('.md')) results.push(full);
|
|
90
|
+
} catch (e) {}
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function _triggerBuildIndex(agentDir) {
|
|
97
|
+
var patterns = [];
|
|
98
|
+
var files = _triggerCollectMdFiles(agentDir);
|
|
99
|
+
for (var i = 0; i < files.length; i++) {
|
|
100
|
+
var content;
|
|
101
|
+
try { content = fs.readFileSync(files[i], 'utf-8'); } catch (e) { continue; }
|
|
102
|
+
var slug = files[i].split('/').pop().replace(/\.md$/i, '').toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
103
|
+
patterns = patterns.concat(_triggerExtractFromFrontmatter(content, slug));
|
|
104
|
+
}
|
|
105
|
+
return patterns;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function scanMicroAgentTriggers(prompt) {
|
|
109
|
+
if (!prompt || typeof prompt !== 'string') return { matches: [], injectAgents: [] };
|
|
110
|
+
var indexPath = path.join(CWD, '.monomind', 'trigger-index.json');
|
|
111
|
+
var agentDir = path.join(CWD, '.claude', 'agents');
|
|
112
|
+
var patterns = [];
|
|
113
|
+
var cacheLoaded = false;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
if (fs.existsSync(indexPath)) {
|
|
117
|
+
var idx = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
118
|
+
var age = Date.now() - new Date(idx.builtAt || 0).getTime();
|
|
119
|
+
if (age < 3600000 && Array.isArray(idx.patterns)) {
|
|
120
|
+
patterns = idx.patterns;
|
|
121
|
+
cacheLoaded = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {}
|
|
125
|
+
|
|
126
|
+
if (!cacheLoaded) {
|
|
127
|
+
patterns = _triggerBuildIndex(agentDir);
|
|
128
|
+
try {
|
|
129
|
+
fs.mkdirSync(path.join(CWD, '.monomind'), { recursive: true });
|
|
130
|
+
fs.writeFileSync(indexPath, JSON.stringify({ patterns: patterns, builtAt: new Date().toISOString(), totalAgentsScanned: patterns.length }));
|
|
131
|
+
} catch (e) {}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
patterns.sort(function(a, b) { return (b.priority || 0) - (a.priority || 0); });
|
|
135
|
+
|
|
136
|
+
var matches = [];
|
|
137
|
+
var seen = {};
|
|
138
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
139
|
+
var p = patterns[i];
|
|
140
|
+
if (p.mode !== 'inject' && p.mode !== 'takeover') continue;
|
|
141
|
+
if (seen[p.agentSlug]) continue;
|
|
142
|
+
try {
|
|
143
|
+
var re = new RegExp(p.pattern, 'i');
|
|
144
|
+
var m = re.exec(prompt);
|
|
145
|
+
if (m) {
|
|
146
|
+
seen[p.agentSlug] = true;
|
|
147
|
+
matches.push({ agentSlug: p.agentSlug, mode: p.mode, matchedText: m[0] });
|
|
148
|
+
if (p.mode === 'takeover') {
|
|
149
|
+
return { matches: matches, takeoverAgent: p.agentSlug, injectAgents: [] };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {}
|
|
153
|
+
}
|
|
154
|
+
return { matches: matches, injectAgents: matches.map(function(m) { return m.agentSlug; }) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Knowledge Base ────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
var _KNOWLEDGE_STOPWORDS = new Set(['the','and','or','but','if','in','on','to','is','it','be','do','of','for','not','at','by','as','we','us','an','a','i']);
|
|
160
|
+
|
|
161
|
+
function _buildKnowledgeSearchFn(knowledgeDir) {
|
|
162
|
+
return async function(query, opts) {
|
|
163
|
+
var chunksFile = path.join(knowledgeDir, 'chunks.jsonl');
|
|
164
|
+
if (!fs.existsSync(chunksFile)) return [];
|
|
165
|
+
var lines;
|
|
166
|
+
try {
|
|
167
|
+
lines = fs.readFileSync(chunksFile, 'utf-8').trim().split('\n').filter(Boolean);
|
|
168
|
+
} catch (e) { return []; }
|
|
169
|
+
|
|
170
|
+
var ns = (opts && opts.namespace) || null;
|
|
171
|
+
var limit = (opts && opts.limit) || 10;
|
|
172
|
+
var minScore = (opts && opts.minScore != null) ? opts.minScore : 0.3;
|
|
173
|
+
var queryTerms = query.toLowerCase().split(/\s+/).filter(function(t) { return t.length >= 2 && !_KNOWLEDGE_STOPWORDS.has(t); });
|
|
174
|
+
if (queryTerms.length === 0) return [];
|
|
175
|
+
|
|
176
|
+
var results = [];
|
|
177
|
+
for (var i = 0; i < lines.length; i++) {
|
|
178
|
+
try {
|
|
179
|
+
var chunk = JSON.parse(lines[i]);
|
|
180
|
+
if (ns && chunk.namespace !== ns) continue;
|
|
181
|
+
var textLower = (chunk.text || '').toLowerCase();
|
|
182
|
+
var matchCount = queryTerms.filter(function(t) { return textLower.includes(t); }).length;
|
|
183
|
+
var score = matchCount / queryTerms.length;
|
|
184
|
+
if (score >= minScore) {
|
|
185
|
+
results.push({ key: chunk.chunkId, value: chunk.text, score: score, metadata: chunk.metadata || {} });
|
|
186
|
+
}
|
|
187
|
+
} catch (e) {}
|
|
188
|
+
}
|
|
189
|
+
results.sort(function(a, b) { return b.score - a.score; });
|
|
190
|
+
return results.slice(0, limit);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function _autoIndexKnowledge(knowledgeDir) {
|
|
195
|
+
var crypto = require('crypto');
|
|
196
|
+
var sources = [
|
|
197
|
+
{ filePath: path.join(CWD, 'CLAUDE.md'), label: 'project-instructions' },
|
|
198
|
+
{ filePath: path.join(CWD, 'docs/todo.md'), label: 'project-todo' },
|
|
199
|
+
{ filePath: path.join(CWD, 'CLAUDE.local.md'), label: 'local-instructions' },
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
var hashInput = '';
|
|
203
|
+
for (var i = 0; i < sources.length; i++) {
|
|
204
|
+
try {
|
|
205
|
+
if (fs.existsSync(sources[i].filePath)) {
|
|
206
|
+
var st = fs.statSync(sources[i].filePath);
|
|
207
|
+
hashInput += sources[i].filePath + ':' + st.size + ':' + st.mtimeMs + ';';
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {}
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
var statsForHash = JSON.parse(fs.readFileSync(path.join(CWD, '.monomind', 'graph', 'stats.json'), 'utf-8'));
|
|
213
|
+
hashInput += 'monograph:' + (statsForHash.builtAt || 0) + ';';
|
|
214
|
+
} catch(e) {}
|
|
215
|
+
|
|
216
|
+
var contentHash = crypto.createHash('md5').update(hashInput).digest('hex');
|
|
217
|
+
var chunksFile = path.join(knowledgeDir, 'chunks.jsonl');
|
|
218
|
+
var hashFile = path.join(knowledgeDir, '.index-hash');
|
|
219
|
+
var existingHash = '';
|
|
220
|
+
try { existingHash = fs.readFileSync(hashFile, 'utf-8').trim(); } catch (e) {}
|
|
221
|
+
|
|
222
|
+
var existingChunkCount = 0;
|
|
223
|
+
try { if (fs.existsSync(chunksFile)) { existingChunkCount = fs.readFileSync(chunksFile, 'utf-8').trim().split('\n').filter(Boolean).length; } } catch (e) {}
|
|
224
|
+
if (existingHash === contentHash && existingChunkCount > 0) return 0;
|
|
225
|
+
|
|
226
|
+
var newLines = [];
|
|
227
|
+
for (var si = 0; si < sources.length; si++) {
|
|
228
|
+
var src = sources[si];
|
|
229
|
+
try {
|
|
230
|
+
if (!fs.existsSync(src.filePath)) continue;
|
|
231
|
+
var content = fs.readFileSync(src.filePath, 'utf-8');
|
|
232
|
+
var sections = content.split(/\n{2,}|\n(?=#{1,3} )/);
|
|
233
|
+
for (var ci = 0; ci < sections.length; ci++) {
|
|
234
|
+
var text = sections[ci].trim();
|
|
235
|
+
if (text.length < 40 || text.length > 3000) continue;
|
|
236
|
+
var chunkId = crypto.createHash('md5').update(src.filePath + ':' + ci).digest('hex').slice(0, 16);
|
|
237
|
+
newLines.push(JSON.stringify({
|
|
238
|
+
chunkId: chunkId,
|
|
239
|
+
namespace: 'knowledge:shared',
|
|
240
|
+
text: text,
|
|
241
|
+
metadata: { filePath: src.filePath, label: src.label, chunkIndex: ci }
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Inject monograph graph summary as a knowledge chunk (reads from monograph.db).
|
|
248
|
+
try {
|
|
249
|
+
var mgDbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
250
|
+
var legacyStats = path.join(CWD, '.monomind', 'graph', 'stats.json');
|
|
251
|
+
var legacyGraph = path.join(CWD, '.monomind', 'graph', 'graph.json');
|
|
252
|
+
|
|
253
|
+
var summaryText = null;
|
|
254
|
+
var summaryMeta = {};
|
|
255
|
+
|
|
256
|
+
if (fs.existsSync(mgDbPath)) {
|
|
257
|
+
try {
|
|
258
|
+
var sumDb = _openMonographDb();
|
|
259
|
+
if (sumDb) {
|
|
260
|
+
try {
|
|
261
|
+
var nodeC = sumDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
262
|
+
var edgeC = sumDb.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
263
|
+
var topNodes = sumDb.prepare(
|
|
264
|
+
'SELECT n.name, n.label, n.file_path, ' +
|
|
265
|
+
'(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg ' +
|
|
266
|
+
'FROM nodes n WHERE n.file_path IS NOT NULL AND n.file_path != "" ORDER BY deg DESC LIMIT 15'
|
|
267
|
+
).all();
|
|
268
|
+
var typeRows = sumDb.prepare(
|
|
269
|
+
'SELECT label, COUNT(*) AS c FROM nodes GROUP BY label ORDER BY c DESC LIMIT 8'
|
|
270
|
+
).all();
|
|
271
|
+
var typeStr = typeRows.map(function(r) { return r.label + ':' + r.c; }).join(', ');
|
|
272
|
+
summaryText = [
|
|
273
|
+
'MONOGRAPH KNOWLEDGE GRAPH SUMMARY',
|
|
274
|
+
'Source: monograph.db | Nodes: ' + nodeC + ' | Edges: ' + edgeC,
|
|
275
|
+
'',
|
|
276
|
+
'TOP GOD NODES (highest connectivity — start exploration here):',
|
|
277
|
+
topNodes.map(function(n) {
|
|
278
|
+
return ' ' + n.name + ' [' + n.label + '] — ' + (n.file_path || '') + ' (degree: ' + n.deg + ')';
|
|
279
|
+
}).join('\n'),
|
|
280
|
+
'',
|
|
281
|
+
'NODE TYPE DISTRIBUTION: ' + typeStr,
|
|
282
|
+
'',
|
|
283
|
+
'Before grepping or globbing, prefer:',
|
|
284
|
+
' mcp__monomind__monograph_suggest({ task: "<your task>" }) — ranked relevant files',
|
|
285
|
+
' mcp__monomind__monograph_query({ q: "<symbol|keyword>" }) — BM25 search with file:line',
|
|
286
|
+
' mcp__monomind__monograph_impact({ name: "<file>" }) — upstream + downstream blast radius',
|
|
287
|
+
].join('\n');
|
|
288
|
+
summaryMeta = { label: 'monograph-graph-summary', source: 'monograph.db', nodes: nodeC, edges: edgeC };
|
|
289
|
+
} catch (e) { /* keep summaryText if partial */ }
|
|
290
|
+
}
|
|
291
|
+
} catch (e) { /* fall through to legacy */ }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!summaryText && fs.existsSync(legacyStats) && fs.existsSync(legacyGraph)) {
|
|
295
|
+
try {
|
|
296
|
+
var lStats = JSON.parse(fs.readFileSync(legacyStats, 'utf-8'));
|
|
297
|
+
var lGraphStat = fs.statSync(legacyGraph);
|
|
298
|
+
if (lGraphStat.size < 10 * 1024 * 1024) {
|
|
299
|
+
var lGraph = JSON.parse(fs.readFileSync(legacyGraph, 'utf-8'));
|
|
300
|
+
var lNodes = Array.isArray(lGraph.nodes) ? lGraph.nodes : [];
|
|
301
|
+
summaryText = 'MONOGRAPH KNOWLEDGE GRAPH SUMMARY (legacy JSON)\n' +
|
|
302
|
+
'Nodes: ' + (lStats.nodes || lNodes.length) + ' | Edges: ' + (lStats.edges || 0) + '\n' +
|
|
303
|
+
'Use mcp__monomind__monograph_suggest to find files relevant to your task.';
|
|
304
|
+
summaryMeta = { label: 'monograph-graph-summary', source: 'legacy-json', builtAt: lStats.builtAt };
|
|
305
|
+
}
|
|
306
|
+
} catch (e) { /* ignore */ }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (summaryText) {
|
|
310
|
+
var chunkId2 = crypto.createHash('md5').update('monograph-graph-summary').digest('hex').slice(0, 16);
|
|
311
|
+
newLines.push(JSON.stringify({
|
|
312
|
+
chunkId: chunkId2,
|
|
313
|
+
namespace: 'knowledge:shared',
|
|
314
|
+
text: summaryText,
|
|
315
|
+
metadata: summaryMeta
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
} catch (e) { /* graph not available yet */ }
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
322
|
+
fs.writeFileSync(chunksFile, newLines.length > 0 ? newLines.join('\n') + '\n' : '', 'utf-8');
|
|
323
|
+
fs.writeFileSync(hashFile, contentHash, 'utf-8');
|
|
324
|
+
} catch (e) {}
|
|
325
|
+
return newLines.length;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = {
|
|
329
|
+
safeRequire,
|
|
330
|
+
_triggerExtractYamlValue,
|
|
331
|
+
_triggerFinalize,
|
|
332
|
+
_triggerExtractFromFrontmatter,
|
|
333
|
+
_triggerCollectMdFiles,
|
|
334
|
+
_triggerBuildIndex,
|
|
335
|
+
scanMicroAgentTriggers,
|
|
336
|
+
_buildKnowledgeSearchFn,
|
|
337
|
+
_autoIndexKnowledge,
|
|
338
|
+
};
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Extracted from hook-handler.cjs — monograph graph helpers.
|
|
3
|
+
// All functions are stateless except for the module-level DB cache.
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const { _getRecentEdits } = require('./telemetry.cjs');
|
|
9
|
+
|
|
10
|
+
const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
11
|
+
|
|
12
|
+
function _requireMonograph() {
|
|
13
|
+
var candidates = [
|
|
14
|
+
path.join(CWD, 'node_modules/.pnpm/node_modules/@monoes/monograph'),
|
|
15
|
+
path.join(CWD, 'packages/node_modules/.pnpm/node_modules/@monoes/monograph'),
|
|
16
|
+
path.join(CWD, 'node_modules/@monoes/monograph'),
|
|
17
|
+
];
|
|
18
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
19
|
+
try { if (fs.existsSync(candidates[i])) return require(candidates[i]); } catch(e) {}
|
|
20
|
+
}
|
|
21
|
+
try { return require('@monoes/monograph'); } catch(e) {}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Memoized at module scope — opening monograph.db can take 7-10s.
|
|
26
|
+
// Callers MUST NOT close the returned handle.
|
|
27
|
+
var _cachedMonographDb = undefined;
|
|
28
|
+
function _openMonographDb() {
|
|
29
|
+
if (_cachedMonographDb !== undefined) return _cachedMonographDb;
|
|
30
|
+
try {
|
|
31
|
+
var dbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
32
|
+
if (!fs.existsSync(dbPath)) { _cachedMonographDb = null; return null; }
|
|
33
|
+
var mod = _requireMonograph();
|
|
34
|
+
if (!mod || !mod.openDb) { _cachedMonographDb = null; return null; }
|
|
35
|
+
_cachedMonographDb = mod.openDb(dbPath);
|
|
36
|
+
return _cachedMonographDb;
|
|
37
|
+
} catch (e) { _cachedMonographDb = null; return null; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getMonographSuggestions(taskText, limit) {
|
|
41
|
+
if (!taskText || typeof taskText !== 'string') return [];
|
|
42
|
+
var db = _openMonographDb();
|
|
43
|
+
if (!db) return [];
|
|
44
|
+
try {
|
|
45
|
+
var words = String(taskText).toLowerCase().match(/[a-z][a-z0-9_-]{3,}/g) || [];
|
|
46
|
+
var stop = { 'this':1,'that':1,'with':1,'from':1,'have':1,'into':1,'their':1,'what':1,'when':1,'where':1,'which':1,'should':1,'would':1,'could':1,'make':1,'just':1,'also':1,'them':1,'they':1,'will':1,'been':1,'were':1,'because':1,'about':1,'does':1,'work':1,'else':1,'more':1,'some':1,'like':1,'need':1,'want':1,'used':1,'using':1,'please':1,'thanks':1,'good':1,'great':1,'nice':1,'thing':1,'things':1,'better':1,'again':1,'first':1,'then':1,'only':1,'even':1 };
|
|
47
|
+
var uniq = {};
|
|
48
|
+
for (var i = 0; i < words.length; i++) if (!stop[words[i]]) uniq[words[i]] = 1;
|
|
49
|
+
var keys = Object.keys(uniq).slice(0, 8);
|
|
50
|
+
var isSymbolLookup = taskText.length <= 30 && /^[a-zA-Z0-9_\-./:]+$/.test(taskText.trim());
|
|
51
|
+
if (keys.length === 0) return [];
|
|
52
|
+
if (keys.length < 2 && !isSymbolLookup) return [];
|
|
53
|
+
|
|
54
|
+
var ftsQuery = keys.map(function(k){ return '"' + k.replace(/"/g, '') + '"'; }).join(' OR ');
|
|
55
|
+
var lim = Math.max(1, limit || 5);
|
|
56
|
+
var rows = [];
|
|
57
|
+
try {
|
|
58
|
+
rows = db.prepare(
|
|
59
|
+
'SELECT n.id, n.name, n.label, n.file_path AS file, ' +
|
|
60
|
+
'bm25(nodes_fts) AS bm25_score, ' +
|
|
61
|
+
'(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg, ' +
|
|
62
|
+
'CASE n.label WHEN \'File\' THEN 3 WHEN \'Function\' THEN 3 WHEN \'Class\' THEN 3 ' +
|
|
63
|
+
' WHEN \'Method\' THEN 2 WHEN \'Interface\' THEN 2 ELSE 1 END AS label_rank ' +
|
|
64
|
+
'FROM nodes_fts f JOIN nodes n ON f.rowid = n.rowid ' +
|
|
65
|
+
'WHERE nodes_fts MATCH ? AND n.file_path IS NOT NULL AND n.file_path != \'\' ' +
|
|
66
|
+
'AND n.label NOT IN (\'Concept\') ' +
|
|
67
|
+
'AND n.name NOT LIKE \'(%\' AND n.name NOT LIKE \'%=>%\' AND n.name != \'function\' ' +
|
|
68
|
+
'AND length(n.name) >= 3 ' +
|
|
69
|
+
'ORDER BY label_rank DESC, bm25_score ASC, deg DESC LIMIT ?'
|
|
70
|
+
).all(ftsQuery, lim);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
var likeFrag = keys.map(function(){ return 'lower(n.name) LIKE ?'; }).join(' OR ');
|
|
73
|
+
var likeArgs = keys.map(function(k){ return '%' + k + '%'; });
|
|
74
|
+
var stmt = db.prepare(
|
|
75
|
+
'SELECT n.id, n.name, n.label, n.file_path AS file, ' +
|
|
76
|
+
'(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg ' +
|
|
77
|
+
'FROM nodes n WHERE (' + likeFrag + ') AND n.file_path IS NOT NULL AND n.file_path != \'\' ' +
|
|
78
|
+
'AND n.label NOT IN (\'Concept\') ' +
|
|
79
|
+
'ORDER BY deg DESC LIMIT ?'
|
|
80
|
+
);
|
|
81
|
+
rows = stmt.all.apply(stmt, likeArgs.concat([lim]));
|
|
82
|
+
}
|
|
83
|
+
return rows || [];
|
|
84
|
+
} catch (e) { return []; }
|
|
85
|
+
finally { /* db is shared/cached; do not close */ }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getMonographNeighbors(filePath) {
|
|
89
|
+
if (!filePath) return null;
|
|
90
|
+
var db = _openMonographDb();
|
|
91
|
+
if (!db) return null;
|
|
92
|
+
try {
|
|
93
|
+
var rel = filePath;
|
|
94
|
+
if (filePath.indexOf(CWD) === 0) rel = filePath.slice(CWD.length + 1);
|
|
95
|
+
var node = db.prepare(
|
|
96
|
+
'SELECT id, name FROM nodes WHERE label=\'File\' AND (file_path=? OR file_path=? OR name=? OR name=?) LIMIT 1'
|
|
97
|
+
).get(filePath, rel, filePath, rel);
|
|
98
|
+
if (!node) return null;
|
|
99
|
+
|
|
100
|
+
var imports = db.prepare(
|
|
101
|
+
'SELECT DISTINCT n.name FROM edges e JOIN nodes n ON e.target_id = n.id ' +
|
|
102
|
+
'WHERE e.source_id=? AND e.relation IN (\'IMPORTS\',\'CALLS\',\'DEPENDS_ON\',\'CONTAINS\',\'DEFINES\') ' +
|
|
103
|
+
'AND n.file_path IS NOT NULL AND n.file_path != \'\' LIMIT 6'
|
|
104
|
+
).all(node.id).map(function(r){ return r.name; });
|
|
105
|
+
var importedBy = db.prepare(
|
|
106
|
+
'SELECT DISTINCT n.name FROM edges e JOIN nodes n ON e.source_id = n.id ' +
|
|
107
|
+
'WHERE e.target_id=? AND e.relation IN (\'IMPORTS\',\'CALLS\',\'DEPENDS_ON\',\'CONTAINS\',\'DEFINES\') ' +
|
|
108
|
+
'AND n.file_path IS NOT NULL AND n.file_path != \'\' LIMIT 6'
|
|
109
|
+
).all(node.id).map(function(r){ return r.name; });
|
|
110
|
+
|
|
111
|
+
return { imports: imports, importedBy: importedBy };
|
|
112
|
+
} catch (e) { return null; }
|
|
113
|
+
finally { /* db is shared/cached; do not close */ }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var _TOKEN_PER_EVENT = {
|
|
117
|
+
monograph_call: 300,
|
|
118
|
+
grep_call: 2000,
|
|
119
|
+
glob_call: 800,
|
|
120
|
+
bash_grep_call: 2000,
|
|
121
|
+
bash_find_call: 800,
|
|
122
|
+
};
|
|
123
|
+
var _DOLLAR_PER_1M_TOKENS = 3.0;
|
|
124
|
+
|
|
125
|
+
function _recordGraphTelemetry(event) {
|
|
126
|
+
try {
|
|
127
|
+
var metricsDir = path.join(CWD, '.monomind', 'metrics');
|
|
128
|
+
var f = path.join(metricsDir, 'graph-usage.json');
|
|
129
|
+
fs.mkdirSync(metricsDir, { recursive: true });
|
|
130
|
+
var d = {};
|
|
131
|
+
try { d = JSON.parse(fs.readFileSync(f, 'utf-8')); } catch (e) {}
|
|
132
|
+
if (typeof d !== 'object' || d === null) d = {};
|
|
133
|
+
d[event] = (d[event] || 0) + 1;
|
|
134
|
+
if (event === 'monograph_call') {
|
|
135
|
+
var saved = (_TOKEN_PER_EVENT.grep_call - _TOKEN_PER_EVENT.monograph_call);
|
|
136
|
+
d.tokens_saved = (d.tokens_saved || 0) + saved;
|
|
137
|
+
d.dollars_saved = ((d.tokens_saved / 1000000) * _DOLLAR_PER_1M_TOKENS);
|
|
138
|
+
}
|
|
139
|
+
if (event === 'grep_call' || event === 'bash_grep_call') {
|
|
140
|
+
var wasted = (_TOKEN_PER_EVENT.grep_call - _TOKEN_PER_EVENT.monograph_call);
|
|
141
|
+
d.tokens_wasted = (d.tokens_wasted || 0) + wasted;
|
|
142
|
+
}
|
|
143
|
+
d.lastUpdated = Date.now();
|
|
144
|
+
fs.writeFileSync(f, JSON.stringify(d));
|
|
145
|
+
} catch (e) { /* non-fatal */ }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function _injectCompactGraphMap() {
|
|
149
|
+
try {
|
|
150
|
+
var db = _openMonographDb();
|
|
151
|
+
if (!db) return;
|
|
152
|
+
try {
|
|
153
|
+
var nodeC = db.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
154
|
+
var anchors = [];
|
|
155
|
+
var seenPaths = {};
|
|
156
|
+
|
|
157
|
+
var recentEdits = _getRecentEdits();
|
|
158
|
+
for (var ri = 0; ri < Math.min(recentEdits.length, 5); ri++) {
|
|
159
|
+
var rfile = recentEdits[ri].file;
|
|
160
|
+
var rrel = (rfile.indexOf(CWD) === 0) ? rfile.slice(CWD.length + 1) : rfile;
|
|
161
|
+
try {
|
|
162
|
+
var rnode = db.prepare(
|
|
163
|
+
'SELECT n.name, n.label, n.file_path, ' +
|
|
164
|
+
'(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg ' +
|
|
165
|
+
'FROM nodes n WHERE n.label=\'File\' AND (n.file_path=? OR n.file_path=?) LIMIT 1'
|
|
166
|
+
).get(rfile, rrel);
|
|
167
|
+
if (rnode && !seenPaths[rnode.file_path]) {
|
|
168
|
+
seenPaths[rnode.file_path] = 1;
|
|
169
|
+
anchors.push({ name: rnode.name, label: rnode.label, file_path: rnode.file_path, deg: rnode.deg, tag: '✎' });
|
|
170
|
+
}
|
|
171
|
+
} catch (e) { /* ignore */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (anchors.length < 8) {
|
|
175
|
+
var gods = db.prepare(
|
|
176
|
+
'SELECT n.name, n.label, n.file_path, ' +
|
|
177
|
+
'(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg ' +
|
|
178
|
+
'FROM nodes n ' +
|
|
179
|
+
'WHERE n.label NOT IN (\'Concept\') AND n.file_path IS NOT NULL AND n.file_path != \'\' ' +
|
|
180
|
+
'AND n.file_path NOT LIKE \'%/node_modules/%\' AND n.file_path NOT LIKE \'%node_modules%\' ' +
|
|
181
|
+
'AND n.name NOT LIKE \'(%\' AND n.name NOT LIKE \'%=>%\' AND length(n.name) >= 3 ' +
|
|
182
|
+
'ORDER BY deg DESC LIMIT 15'
|
|
183
|
+
).all();
|
|
184
|
+
for (var gi = 0; gi < gods.length && anchors.length < 8; gi++) {
|
|
185
|
+
if (!seenPaths[gods[gi].file_path]) {
|
|
186
|
+
seenPaths[gods[gi].file_path] = 1;
|
|
187
|
+
anchors.push({ name: gods[gi].name, label: gods[gi].label, file_path: gods[gi].file_path, deg: gods[gi].deg, tag: '' });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (anchors.length > 0) {
|
|
193
|
+
console.log('[COMPACT_GRAPH] ' + nodeC + ' nodes. Session context (✎ = recently edited):');
|
|
194
|
+
for (var ci = 0; ci < anchors.length; ci++) {
|
|
195
|
+
var g = anchors[ci];
|
|
196
|
+
console.log(' ' + (g.tag || ' ') + ' ' + g.name + ' [' + g.label + '] — ' + g.file_path + ' (deg ' + g.deg + ')');
|
|
197
|
+
}
|
|
198
|
+
console.log(' Use mcp__monomind__monograph_suggest first when navigating.');
|
|
199
|
+
}
|
|
200
|
+
} finally { /* db is shared/cached; do not close */ }
|
|
201
|
+
} catch (e) {}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function _findAffectedTests(filePath) {
|
|
205
|
+
if (!filePath) return [];
|
|
206
|
+
var db = _openMonographDb();
|
|
207
|
+
if (!db) return [];
|
|
208
|
+
try {
|
|
209
|
+
var rel = filePath;
|
|
210
|
+
if (filePath.indexOf(CWD) === 0) rel = filePath.slice(CWD.length + 1);
|
|
211
|
+
var rows = db.prepare(
|
|
212
|
+
'SELECT DISTINCT src.file_path FROM edges e ' +
|
|
213
|
+
'JOIN nodes src ON e.source_id = src.id ' +
|
|
214
|
+
'JOIN nodes tgt ON e.target_id = tgt.id ' +
|
|
215
|
+
'WHERE e.relation IN (\'IMPORTS\',\'CALLS\',\'DEPENDS_ON\') ' +
|
|
216
|
+
'AND (tgt.file_path = ? OR tgt.file_path = ?) ' +
|
|
217
|
+
'AND src.file_path IS NOT NULL AND src.file_path != \'\' ' +
|
|
218
|
+
'AND (src.file_path LIKE \'%test%\' OR src.file_path LIKE \'%.spec.%\' OR src.file_path LIKE \'%__tests__%\') ' +
|
|
219
|
+
'AND src.file_path NOT LIKE \'%.worktrees%\' ' +
|
|
220
|
+
'LIMIT 5'
|
|
221
|
+
).all(filePath, rel);
|
|
222
|
+
return rows.map(function(r) { return r.file_path; });
|
|
223
|
+
} catch (e) { return []; }
|
|
224
|
+
finally { /* db is shared/cached; do not close */ }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function _maybeRebuildMonograph() {
|
|
228
|
+
try {
|
|
229
|
+
var metricsDir = path.join(CWD, '.monomind', 'metrics');
|
|
230
|
+
fs.mkdirSync(metricsDir, { recursive: true });
|
|
231
|
+
var f = path.join(metricsDir, 'graph-rebuild.json');
|
|
232
|
+
var d = {};
|
|
233
|
+
try { d = JSON.parse(fs.readFileSync(f, 'utf-8')); } catch (_) {}
|
|
234
|
+
if (typeof d !== 'object' || d === null) d = {};
|
|
235
|
+
d.writesSinceRebuild = (d.writesSinceRebuild || 0) + 1;
|
|
236
|
+
d.lastWriteAt = Date.now();
|
|
237
|
+
var THRESHOLD = 20;
|
|
238
|
+
var MIN_INTERVAL_MS = 5 * 60 * 1000;
|
|
239
|
+
var dueByCount = d.writesSinceRebuild >= THRESHOLD;
|
|
240
|
+
var dueByTime = !d.lastRebuildAt || (Date.now() - d.lastRebuildAt) > MIN_INTERVAL_MS;
|
|
241
|
+
if (dueByCount && dueByTime) {
|
|
242
|
+
d.writesSinceRebuild = 0;
|
|
243
|
+
d.lastRebuildAt = Date.now();
|
|
244
|
+
fs.writeFileSync(f, JSON.stringify(d));
|
|
245
|
+
try {
|
|
246
|
+
var freshenScript = path.join(CWD, '.claude', 'helpers', 'graphify-freshen.cjs');
|
|
247
|
+
if (fs.existsSync(freshenScript)) {
|
|
248
|
+
var spawn = require('child_process').spawn;
|
|
249
|
+
var child = spawn(process.execPath, [freshenScript], {
|
|
250
|
+
detached: true,
|
|
251
|
+
stdio: 'ignore',
|
|
252
|
+
cwd: CWD,
|
|
253
|
+
});
|
|
254
|
+
child.unref();
|
|
255
|
+
}
|
|
256
|
+
} catch (_) {}
|
|
257
|
+
} else {
|
|
258
|
+
fs.writeFileSync(f, JSON.stringify(d));
|
|
259
|
+
}
|
|
260
|
+
} catch (e) { /* non-fatal */ }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Inject god-node context at session-restore time: logs MONOGRAPH_CONTEXT,
|
|
264
|
+
// writes the god-node chunk into knowledge/chunks.jsonl for semantic recall.
|
|
265
|
+
// Shared between session-restore-handler and any other caller that needs it.
|
|
266
|
+
function injectGodNodesContext(CWD) {
|
|
267
|
+
try {
|
|
268
|
+
var mgDbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
269
|
+
if (!fs.existsSync(mgDbPath)) return;
|
|
270
|
+
var db = _openMonographDb();
|
|
271
|
+
if (!db) return;
|
|
272
|
+
try {
|
|
273
|
+
var nodeCount = db.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
274
|
+
var edgeCount = db.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
275
|
+
var godNodes = db.prepare(
|
|
276
|
+
"SELECT n.name, n.label, n.file_path, " +
|
|
277
|
+
"(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg " +
|
|
278
|
+
"FROM nodes n " +
|
|
279
|
+
"WHERE n.label NOT IN ('Concept') " +
|
|
280
|
+
"AND n.file_path IS NOT NULL AND n.file_path != '' " +
|
|
281
|
+
"AND n.file_path NOT LIKE '%/node_modules/%' AND n.file_path NOT LIKE '%node_modules%' " +
|
|
282
|
+
"ORDER BY deg DESC LIMIT 12"
|
|
283
|
+
).all();
|
|
284
|
+
|
|
285
|
+
// Staleness indicator: compare stored commit hash with current HEAD.
|
|
286
|
+
var staleIndicator = '';
|
|
287
|
+
try {
|
|
288
|
+
var lastCommitRow = null;
|
|
289
|
+
try { lastCommitRow = db.prepare("SELECT value FROM index_meta WHERE key='ua_last_commit'").get(); } catch (_) {}
|
|
290
|
+
if (lastCommitRow && lastCommitRow.value) {
|
|
291
|
+
var { execFileSync: execSync } = require('child_process');
|
|
292
|
+
var currentHead = '';
|
|
293
|
+
try { currentHead = execSync('git', ['rev-parse', 'HEAD'], { cwd: CWD, encoding: 'utf-8' }).trim(); } catch (_) {}
|
|
294
|
+
if (currentHead && currentHead !== lastCommitRow.value) {
|
|
295
|
+
var commitsBehind = 0;
|
|
296
|
+
try {
|
|
297
|
+
var revList = execSync('git', ['rev-list', '--count', lastCommitRow.value + '..' + currentHead], { cwd: CWD, encoding: 'utf-8' }).trim();
|
|
298
|
+
commitsBehind = parseInt(revList, 10) || 0;
|
|
299
|
+
} catch (_) {}
|
|
300
|
+
if (commitsBehind > 0) {
|
|
301
|
+
staleIndicator = ' [⚡ graph ' + commitsBehind + ' commit' + (commitsBehind === 1 ? '' : 's') + ' behind — run: npx monomind monograph build]';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (_) {}
|
|
306
|
+
|
|
307
|
+
if (godNodes.length > 0) {
|
|
308
|
+
var godStr = godNodes.slice(0, 8).map(function(n) {
|
|
309
|
+
return n.name + ' (' + n.label + ', ' + n.deg + ' links)';
|
|
310
|
+
}).join(', ');
|
|
311
|
+
console.log('[MONOGRAPH_CONTEXT] ' + nodeCount + ' nodes · ' + edgeCount + ' edges. Key nodes: ' + godStr + staleIndicator);
|
|
312
|
+
|
|
313
|
+
// Write god nodes into knowledge/chunks.jsonl so semantic search finds them.
|
|
314
|
+
var knowledgeDir = path.join(CWD, '.monomind', 'knowledge');
|
|
315
|
+
var chunksFile = path.join(knowledgeDir, 'chunks.jsonl');
|
|
316
|
+
try {
|
|
317
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
318
|
+
var godChunk = JSON.stringify({
|
|
319
|
+
id: 'monograph-god-nodes',
|
|
320
|
+
text: 'Codebase architecture — high-centrality nodes (most depended-on): ' + godNodes.map(function(n) {
|
|
321
|
+
return n.name + ' [' + n.label + '] at ' + (n.file_path || '') + ' (' + n.deg + ' connections)';
|
|
322
|
+
}).join('; '),
|
|
323
|
+
namespace: 'knowledge:monograph',
|
|
324
|
+
metadata: { label: 'monograph-god-nodes', nodes: nodeCount, edges: edgeCount }
|
|
325
|
+
});
|
|
326
|
+
var existing = [];
|
|
327
|
+
try { existing = fs.readFileSync(chunksFile, 'utf-8').trim().split('\n').filter(Boolean); } catch(e) {}
|
|
328
|
+
existing = existing.filter(function(line) {
|
|
329
|
+
try { return JSON.parse(line).id !== 'monograph-god-nodes'; } catch(e) { return true; }
|
|
330
|
+
});
|
|
331
|
+
existing.push(godChunk);
|
|
332
|
+
fs.writeFileSync(chunksFile, existing.join('\n') + '\n');
|
|
333
|
+
} catch(e) {}
|
|
334
|
+
}
|
|
335
|
+
} catch(e) { /* non-fatal */ }
|
|
336
|
+
} catch(e) { /* non-fatal */ }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = {
|
|
340
|
+
_requireMonograph,
|
|
341
|
+
_openMonographDb,
|
|
342
|
+
getMonographSuggestions,
|
|
343
|
+
getMonographNeighbors,
|
|
344
|
+
_recordGraphTelemetry,
|
|
345
|
+
_injectCompactGraphMap,
|
|
346
|
+
_findAffectedTests,
|
|
347
|
+
_maybeRebuildMonograph,
|
|
348
|
+
injectGodNodesContext,
|
|
349
|
+
};
|