@monoes/monomindcli 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/handlers/route-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +14 -8
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +129 -57
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +134 -15
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +296 -41
- package/.claude/helpers/session.cjs +89 -32
- package/.claude/helpers/statusline.cjs +138 -2
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +96 -4
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +329 -37
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +295 -5
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/transfer/serialization/cfp.js +1 -1
- package/dist/src/transfer/serialization/cfp.js.map +1 -1
- package/dist/src/ui/dashboard.html +673 -6
- package/dist/src/ui/server.mjs +144 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -118,8 +118,8 @@ module.exports = {
|
|
|
118
118
|
} catch (e) {}
|
|
119
119
|
|
|
120
120
|
var output = [];
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// Skip the noisy "[INFO] Routing task: ..." prefix — it repeats the user's
|
|
122
|
+
// own words back and adds token overhead without helping Claude.
|
|
123
123
|
// Routing panel strategy:
|
|
124
124
|
// conf >= 0.90 → show primary recommendation (router is confident, trust it)
|
|
125
125
|
// conf < 0.90 → show category picker so Claude uses its own context to
|
|
@@ -185,13 +185,17 @@ module.exports = {
|
|
|
185
185
|
var devAgentsForPersist = /^(coder|tester|reviewer|planner|researcher|system-architect|backend-dev|backend-architect|mobile-dev|ml-developer|cicd-engineer|api-docs|code-analyzer|production-validator|Technical Writer|Software Architect|Frontend Developer|AI Engineer|Data Engineer|Security Engineer|DevOps Automator|SRE)$/i;
|
|
186
186
|
var persistedIsNonDev = !devAgentsForPersist.test(String(result.agent || '').trim());
|
|
187
187
|
var resolvedAgent = result.agent;
|
|
188
|
+
var resolvedFromExtras = false;
|
|
188
189
|
if (!resolvedAgent || resolvedAgent === 'extras') {
|
|
189
190
|
var topExtra = result.extrasMatches && result.extrasMatches[0];
|
|
190
191
|
resolvedAgent = topExtra ? topExtra.name : 'Specialist Agent';
|
|
192
|
+
resolvedFromExtras = true;
|
|
191
193
|
}
|
|
192
194
|
// If router was uncertain (< 90%) and picked a non-dev specialist,
|
|
193
195
|
// show "AI selecting" in statusline rather than the wrong agent.
|
|
194
|
-
|
|
196
|
+
// Exception: when agent was explicitly 'extras' and we resolved from extrasMatches,
|
|
197
|
+
// trust that resolution — the domain specialist was explicitly matched.
|
|
198
|
+
if (!resolvedFromExtras && confForPersist < 0.90 && persistedIsNonDev && !String(result.reason || '').startsWith('Graph fallback')) {
|
|
195
199
|
resolvedAgent = 'AI selecting';
|
|
196
200
|
}
|
|
197
201
|
var routePayload = {
|
|
@@ -420,7 +424,10 @@ module.exports = {
|
|
|
420
424
|
for (var si = 0; si < suggestions.length; si++) {
|
|
421
425
|
var s = suggestions[si];
|
|
422
426
|
var editTag = s._editBoost ? ' ✎' : '';
|
|
423
|
-
|
|
427
|
+
// Include :line suffix when available so LLM can navigate directly
|
|
428
|
+
var fileLoc = (s.file || '');
|
|
429
|
+
if (fileLoc && s.startLine != null) fileLoc = fileLoc + ':' + s.startLine;
|
|
430
|
+
console.log(' · ' + s.name + ' [' + s.label + '] — ' + fileLoc + (s.deg ? ' (deg ' + s.deg + ')' : '') + editTag);
|
|
424
431
|
}
|
|
425
432
|
console.log(' Use mcp__monomind__monograph_query / monograph_impact for deeper drill-down.');
|
|
426
433
|
hCtx._recordGraphTelemetry('preresolve_hit');
|
|
@@ -291,8 +291,11 @@ module.exports = {
|
|
|
291
291
|
daemonChild.on('error', function() {});
|
|
292
292
|
daemonChild.unref();
|
|
293
293
|
console.log('[DAEMON_AUTOSTART] Background daemon started (pid ' + daemonChild.pid + ')');
|
|
294
|
-
}
|
|
295
|
-
|
|
294
|
+
}
|
|
295
|
+
// Daemon not running + no autoStart: emit only if this project has a config
|
|
296
|
+
// (i.e. daemon was intentionally set up). Avoids noisy output in daemon-less projects.
|
|
297
|
+
else if (fs.existsSync(path.join(CWD, 'monomind.config.json'))) {
|
|
298
|
+
console.log('[DAEMON_STOPPED] Run `npx monomind daemon start` to enable background workers');
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
301
|
} catch (e) { /* non-fatal */ }
|
|
@@ -328,11 +331,13 @@ module.exports = {
|
|
|
328
331
|
}
|
|
329
332
|
} catch (e) { /* non-fatal */ }
|
|
330
333
|
|
|
331
|
-
// Monomind Control UI Status — only probe when a daemon
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
// Monomind Control UI Status — only probe when a daemon.pid file exists,
|
|
335
|
+
// meaning the daemon was intentionally started in this project. This avoids
|
|
336
|
+
// printing "[CONTROL_UI] offline" noise on every session in projects that
|
|
337
|
+
// never run the daemon. The broader "monomind.config.json" check is dropped
|
|
338
|
+
// because that file is present in the dev repo itself and in any initialized
|
|
339
|
+
// project, making the old condition nearly always true.
|
|
340
|
+
var _controlUiShouldProbe = fs.existsSync(path.join(CWD, '.monomind', 'daemon.pid'));
|
|
336
341
|
if (_controlUiShouldProbe) {
|
|
337
342
|
try {
|
|
338
343
|
var http = require('http');
|
|
@@ -344,7 +349,8 @@ module.exports = {
|
|
|
344
349
|
res.resume();
|
|
345
350
|
});
|
|
346
351
|
req.on('error', function() {
|
|
347
|
-
|
|
352
|
+
// Only warn when daemon was previously running (pid file exists but server is gone)
|
|
353
|
+
console.log('[CONTROL_UI] offline — restart with: npx monomind mcp start');
|
|
348
354
|
});
|
|
349
355
|
req.setTimeout(800, function() { req.destroy(); });
|
|
350
356
|
} catch (e) { /* non-fatal */ }
|
|
@@ -288,6 +288,46 @@ const handlers = {
|
|
|
288
288
|
var tool = hCtx.toolName || '';
|
|
289
289
|
if (tool === 'Grep') _recordGraphTelemetry('grep_call');
|
|
290
290
|
else if (tool === 'Glob') _recordGraphTelemetry('glob_call');
|
|
291
|
+
|
|
292
|
+
// Monograph symbol hint: when the Grep pattern looks like a plain symbol name
|
|
293
|
+
// (no regex metacharacters), look it up directly in the graph index and print
|
|
294
|
+
// the file:line so the LLM may skip or narrow the Grep.
|
|
295
|
+
// Session-level cache prevents repeated identical DB lookups across Grep bursts.
|
|
296
|
+
try {
|
|
297
|
+
var grepPattern = (typeof toolInput === 'object' && toolInput !== null)
|
|
298
|
+
? (toolInput.pattern || toolInput.query || '')
|
|
299
|
+
: '';
|
|
300
|
+
// Only attempt lookup for clean identifiers — skip regexes and paths
|
|
301
|
+
var isCleanSymbol = grepPattern.length >= 3 && grepPattern.length <= 80
|
|
302
|
+
&& /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(grepPattern);
|
|
303
|
+
if (isCleanSymbol) {
|
|
304
|
+
if (!hCtx._preSearchCache) hCtx._preSearchCache = {};
|
|
305
|
+
var cacheKey = 'presearch:' + grepPattern;
|
|
306
|
+
if (!(cacheKey in hCtx._preSearchCache)) {
|
|
307
|
+
var db = _openMonographDb();
|
|
308
|
+
var hint = null;
|
|
309
|
+
if (db) {
|
|
310
|
+
try {
|
|
311
|
+
var row = db.prepare(
|
|
312
|
+
'SELECT n.name, n.label, n.file_path, n.start_line FROM nodes n ' +
|
|
313
|
+
'WHERE n.name = ? AND n.label NOT IN (\'Concept\',\'Community\',\'Folder\') ' +
|
|
314
|
+
'AND n.file_path IS NOT NULL LIMIT 1'
|
|
315
|
+
).get(grepPattern);
|
|
316
|
+
if (row) {
|
|
317
|
+
hint = row.file_path + (row.start_line != null ? ':' + row.start_line : '');
|
|
318
|
+
}
|
|
319
|
+
} catch (e) { /* non-fatal */ }
|
|
320
|
+
}
|
|
321
|
+
hCtx._preSearchCache[cacheKey] = hint;
|
|
322
|
+
}
|
|
323
|
+
var cachedHint = hCtx._preSearchCache[cacheKey];
|
|
324
|
+
if (cachedHint) {
|
|
325
|
+
// Use the correct MCP tool name — monograph_context does not exist;
|
|
326
|
+
// the callable tool is mcp__monomind__monograph_query
|
|
327
|
+
console.log('[MONOGRAPH_HINT] ' + grepPattern + ' found at ' + cachedHint + ' — use mcp__monomind__monograph_query instead of Grep for better results');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} catch (e) { /* non-fatal — telemetry always proceeds */ }
|
|
291
331
|
},
|
|
292
332
|
|
|
293
333
|
'post-graph-tool': () => {
|
|
@@ -1,92 +1,164 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
3
|
* Intelligence context module for hook-handler.cjs
|
|
4
|
-
* Provides context injection, trajectory logging,
|
|
4
|
+
* Provides context injection, trajectory logging, feedback recording,
|
|
5
|
+
* edit tracking, consolidation, and stats.
|
|
6
|
+
*
|
|
7
|
+
* Data directory: $CLAUDE_PROJECT_DIR/.monomind/data/
|
|
8
|
+
* auto-memory-store.json — persisted memory entries
|
|
9
|
+
* ranked-context.json — ranked view written by init()
|
|
10
|
+
* pending-insights.jsonl — pending entries to consolidate
|
|
11
|
+
* intelligence-outcomes.jsonl — feedback records
|
|
5
12
|
*/
|
|
6
13
|
|
|
7
14
|
const path = require('path');
|
|
8
15
|
const fs = require('fs');
|
|
9
16
|
|
|
17
|
+
// Resolve base dir at require-time so tests can inject CLAUDE_PROJECT_DIR
|
|
18
|
+
// per fresh require() call.
|
|
10
19
|
const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
20
|
+
const DATA_DIR = path.join(CWD, '.monomind', 'data');
|
|
21
|
+
const STORE_FILE = path.join(DATA_DIR, 'auto-memory-store.json');
|
|
22
|
+
const RANKED_FILE = path.join(DATA_DIR, 'ranked-context.json');
|
|
23
|
+
const PENDING_FILE = path.join(DATA_DIR, 'pending-insights.jsonl');
|
|
24
|
+
const OUTCOMES_FILE = path.join(DATA_DIR, 'intelligence-outcomes.jsonl');
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
var _trajectory = [];
|
|
26
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB guard
|
|
27
|
+
const RING_BUFFER_MAX = 50;
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
var _entries = []; // deduplicated memory entries loaded from store
|
|
30
|
+
var _recentEdits = []; // ring buffer of recently edited paths
|
|
31
|
+
|
|
32
|
+
function ensureDataDir() {
|
|
33
|
+
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch (_) {}
|
|
21
34
|
}
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
function safeReadJson(filePath) {
|
|
37
|
+
try {
|
|
38
|
+
if (!fs.existsSync(filePath)) return null;
|
|
39
|
+
var st = fs.statSync(filePath);
|
|
40
|
+
if (st.size > MAX_FILE_SIZE) return null;
|
|
41
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
42
|
+
} catch (_) { return null; }
|
|
43
|
+
}
|
|
24
44
|
|
|
25
|
-
function
|
|
45
|
+
function safeReadLines(filePath) {
|
|
26
46
|
try {
|
|
27
|
-
if (fs.existsSync(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
if (!fs.existsSync(filePath)) return [];
|
|
48
|
+
var st = fs.statSync(filePath);
|
|
49
|
+
if (st.size > MAX_FILE_SIZE) return [];
|
|
50
|
+
return fs.readFileSync(filePath, 'utf-8')
|
|
51
|
+
.split('\n')
|
|
52
|
+
.filter(function(l) { return l.trim().length > 0; });
|
|
53
|
+
} catch (_) { return []; }
|
|
33
54
|
}
|
|
34
55
|
|
|
56
|
+
// ── init ───────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
35
58
|
function init() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
ensureDataDir();
|
|
60
|
+
|
|
61
|
+
// Load entries from store, deduplicate by id
|
|
62
|
+
var raw = safeReadJson(STORE_FILE);
|
|
63
|
+
var arr = Array.isArray(raw) ? raw : [];
|
|
64
|
+
var seen = new Set();
|
|
65
|
+
_entries = arr.filter(function(e) {
|
|
66
|
+
var key = e && e.id ? String(e.id) : null;
|
|
67
|
+
if (!key) return true;
|
|
68
|
+
if (seen.has(key)) return false;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Write ranked-context.json (sorted by confidence desc) with version envelope
|
|
74
|
+
var ranked = _entries.slice().sort(function(a, b) {
|
|
75
|
+
return (b.confidence || 0) - (a.confidence || 0);
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
fs.writeFileSync(RANKED_FILE, JSON.stringify({ version: 1, entries: ranked }, null, 2), 'utf-8');
|
|
79
|
+
} catch (_) {}
|
|
80
|
+
|
|
81
|
+
return { nodes: _entries.length, edges: 0 };
|
|
41
82
|
}
|
|
42
83
|
|
|
84
|
+
// ── getContext ─────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
43
86
|
function getContext(prompt) {
|
|
44
|
-
if (!prompt || typeof prompt !== 'string') return null;
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
var
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') return null;
|
|
88
|
+
if (_entries.length === 0) return null;
|
|
89
|
+
|
|
90
|
+
var promptWords = prompt.toLowerCase().split(/\W+/).filter(Boolean);
|
|
91
|
+
var promptSet = new Set(promptWords);
|
|
92
|
+
|
|
93
|
+
var matches = _entries.filter(function(e) {
|
|
94
|
+
var content = ((e.content || '') + ' ' + (e.summary || '')).toLowerCase();
|
|
95
|
+
var words = content.split(/\W+/).filter(Boolean);
|
|
96
|
+
return words.some(function(w) { return promptSet.has(w); });
|
|
51
97
|
});
|
|
98
|
+
|
|
52
99
|
if (matches.length === 0) return null;
|
|
100
|
+
|
|
53
101
|
var top = matches[0];
|
|
54
|
-
return '[INTELLIGENCE]
|
|
55
|
-
(top.suggestion ? ' — ' + top.suggestion : '');
|
|
102
|
+
return '[INTELLIGENCE] ' + (top.summary || top.content || top.id || 'context match');
|
|
56
103
|
}
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
fs.writeFileSync(TRAJECTORY_FILE, JSON.stringify(_trajectory.slice(-200), null, 2), 'utf-8');
|
|
65
|
-
} catch (_) {}
|
|
105
|
+
// ── recordEdit ────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function recordEdit(filePath) {
|
|
108
|
+
_recentEdits.push({ path: String(filePath || ''), ts: Date.now() });
|
|
109
|
+
if (_recentEdits.length > RING_BUFFER_MAX) {
|
|
110
|
+
_recentEdits = _recentEdits.slice(-RING_BUFFER_MAX);
|
|
66
111
|
}
|
|
67
112
|
}
|
|
68
113
|
|
|
114
|
+
// ── consolidate ───────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
function consolidate() {
|
|
117
|
+
ensureDataDir();
|
|
118
|
+
var lines = safeReadLines(PENDING_FILE);
|
|
119
|
+
var count = lines.length;
|
|
120
|
+
|
|
121
|
+
// Clear the pending file
|
|
122
|
+
try { fs.writeFileSync(PENDING_FILE, '', 'utf-8'); } catch (_) {}
|
|
123
|
+
|
|
124
|
+
return { entries: count, edges: 0, newEntries: count };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── feedback ──────────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
69
129
|
function feedback(success) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
130
|
+
ensureDataDir();
|
|
131
|
+
var record = JSON.stringify({
|
|
132
|
+
ts: Date.now(),
|
|
133
|
+
success: !!success,
|
|
134
|
+
context: null,
|
|
135
|
+
recentEdits: _recentEdits.slice(),
|
|
136
|
+
}) + '\n';
|
|
137
|
+
try { fs.appendFileSync(OUTCOMES_FILE, record, 'utf-8'); } catch (_) {}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── stats ─────────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
function stats(asJson) {
|
|
143
|
+
var result = {
|
|
144
|
+
entries: _entries.length,
|
|
145
|
+
recentEdits: _recentEdits.length,
|
|
146
|
+
pending: safeReadLines(PENDING_FILE).length,
|
|
147
|
+
};
|
|
148
|
+
if (asJson) return JSON.stringify(result);
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── legacy ────────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
function logTrajectory(step) {
|
|
155
|
+
// no-op stub for backward compatibility
|
|
156
|
+
void step;
|
|
79
157
|
}
|
|
80
158
|
|
|
81
159
|
function storePattern(pattern) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
var safeId = String(pattern.id || '').slice(0, 256);
|
|
85
|
-
_patterns = _patterns.filter(function(p) { return p.id !== safeId; });
|
|
86
|
-
_patterns.push(Object.assign({ storedAt: new Date().toISOString() }, pattern, { id: safeId }));
|
|
87
|
-
try {
|
|
88
|
-
fs.writeFileSync(PATTERNS_FILE, JSON.stringify(_patterns.slice(-500), null, 2), 'utf-8');
|
|
89
|
-
} catch (_) {}
|
|
160
|
+
// no-op stub for backward compatibility
|
|
161
|
+
void pattern;
|
|
90
162
|
}
|
|
91
163
|
|
|
92
|
-
module.exports = { init, getContext,
|
|
164
|
+
module.exports = { init, getContext, recordEdit, consolidate, feedback, stats, logTrajectory, storePattern };
|