@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.
@@ -118,8 +118,8 @@ module.exports = {
118
118
  } catch (e) {}
119
119
 
120
120
  var output = [];
121
- output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));
122
- output.push('');
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
- if (confForPersist < 0.90 && persistedIsNonDev && !String(result.reason || '').startsWith('Graph fallback')) {
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
- console.log(' · ' + s.name + ' [' + s.label + '] — ' + (s.file || '') + (s.deg ? ' (deg ' + s.deg + ')' : '') + editTag);
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
- } else {
295
- console.log('[DAEMON_STOPPED] Background daemon is not running. To auto-start, set daemon.autoStart=true in monomind.config.json or run: npx monomind daemon start');
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 has previously run
332
- // (indicated by daemon.pid or monomind.config.json). Skips silently in fresh
333
- // environments and test fixtures that have no daemon history.
334
- var _controlUiShouldProbe = fs.existsSync(path.join(CWD, '.monomind', 'daemon.pid'))
335
- || fs.existsSync(path.join(CWD, 'monomind.config.json'));
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
- console.log('[CONTROL_UI] offline run: npx monomind mcp start');
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, and feedback recording.
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 INTEL_DIR = path.join(CWD, '.monomind', 'intelligence');
12
- const PATTERNS_FILE = path.join(INTEL_DIR, 'patterns.json');
13
- const TRAJECTORY_FILE = path.join(INTEL_DIR, 'trajectory.json');
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
- var _initialized = false;
16
- var _patterns = [];
17
- var _trajectory = [];
26
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB guard
27
+ const RING_BUFFER_MAX = 50;
18
28
 
19
- function ensureDir() {
20
- try { fs.mkdirSync(INTEL_DIR, { recursive: true }); } catch (_) {}
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
- var MAX_PATTERNS_SIZE = 10 * 1024 * 1024; // 10 MiB guard
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 loadPatterns() {
45
+ function safeReadLines(filePath) {
26
46
  try {
27
- if (fs.existsSync(PATTERNS_FILE)) {
28
- var st = fs.statSync(PATTERNS_FILE);
29
- if (st.size > MAX_PATTERNS_SIZE) { _patterns = []; return; }
30
- _patterns = JSON.parse(fs.readFileSync(PATTERNS_FILE, 'utf-8'));
31
- }
32
- } catch (_) { _patterns = []; }
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
- if (_initialized) return { ok: true, patternCount: _patterns.length };
37
- ensureDir();
38
- loadPatterns();
39
- _initialized = true;
40
- return { ok: true, patternCount: _patterns.length };
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 (!_initialized) init();
46
- // Match patterns against prompt
47
- var matches = _patterns.filter(function(p) {
48
- return p.keywords && p.keywords.some(function(kw) {
49
- return prompt.toLowerCase().includes(kw.toLowerCase());
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] Pattern match: ' + (top.name || top.id || 'pattern') +
55
- (top.suggestion ? ' — ' + top.suggestion : '');
102
+ return '[INTELLIGENCE] ' + (top.summary || top.content || top.id || 'context match');
56
103
  }
57
104
 
58
- function logTrajectory(step) {
59
- ensureDir();
60
- _trajectory.push(Object.assign({ ts: new Date().toISOString() }, step || {}));
61
- // Flush every 10 steps to avoid excessive writes
62
- if (_trajectory.length % 10 === 0) {
63
- try {
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
- // Record outcome for last trajectory step
71
- if (_trajectory.length > 0) {
72
- _trajectory[_trajectory.length - 1].outcome = success ? 'success' : 'failure';
73
- }
74
- // Persist
75
- ensureDir();
76
- try {
77
- fs.writeFileSync(TRAJECTORY_FILE, JSON.stringify(_trajectory.slice(-200), null, 2), 'utf-8');
78
- } catch (_) {}
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
- ensureDir();
83
- loadPatterns();
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, logTrajectory, feedback, storePattern };
164
+ module.exports = { init, getContext, recordEdit, consolidate, feedback, stats, logTrajectory, storePattern };