@monoes/monomindcli 1.10.13 → 1.10.15
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/budget.md +7 -0
- package/.claude/commands/monomind/graph-status.md +7 -0
- package/.claude/commands/monomind/loops.md +7 -0
- package/.claude/helpers/graphify-freshen.cjs +20 -3
- package/.claude/helpers/hook-handler.cjs +219 -47
- package/dist/src/ui/dashboard.html +57 -0
- package/package.json +1 -1
|
@@ -81,11 +81,28 @@ try {
|
|
|
81
81
|
// Write lock file; the build process removes it on completion
|
|
82
82
|
try { fs.writeFileSync(lockPath, String(process.pid)); } catch { /* non-fatal */ }
|
|
83
83
|
|
|
84
|
-
// Spawn a detached node process to run buildAsync from @monoes/monograph (ESM)
|
|
84
|
+
// Spawn a detached node process to run buildAsync from @monoes/monograph (ESM).
|
|
85
|
+
// After the build, VACUUM the DB if it has >50% bloat (reclaim space from
|
|
86
|
+
// delete/insert churn; opens are ~5x faster on a tight DB).
|
|
87
|
+
const dbPathStr = JSON.stringify(path.join(projectDir, '.monomind', 'monograph.db'));
|
|
85
88
|
const script = `
|
|
86
89
|
import { buildAsync } from ${JSON.stringify(pathToFileURL(entryPoint).href)};
|
|
87
|
-
import { unlinkSync } from 'fs';
|
|
88
|
-
|
|
90
|
+
import { unlinkSync, statSync } from 'fs';
|
|
91
|
+
import { execSync } from 'child_process';
|
|
92
|
+
try {
|
|
93
|
+
await buildAsync(${JSON.stringify(projectDir)});
|
|
94
|
+
// Vacuum if bloat ratio is high — keeps openDb fast over time.
|
|
95
|
+
try {
|
|
96
|
+
const dbPath = ${dbPathStr};
|
|
97
|
+
const fileMB = statSync(dbPath).size / 1024 / 1024;
|
|
98
|
+
const liveMB = parseInt(
|
|
99
|
+
execSync('sqlite3 "' + dbPath + '" "SELECT SUM(pgsize)/1024/1024 FROM dbstat;"',
|
|
100
|
+
{ encoding: 'utf-8' }).trim(), 10);
|
|
101
|
+
if (fileMB > 100 && liveMB / fileMB < 0.5) {
|
|
102
|
+
execSync('sqlite3 "' + dbPath + '" "VACUUM;"', { timeout: 120000 });
|
|
103
|
+
}
|
|
104
|
+
} catch (_) {}
|
|
105
|
+
} finally {
|
|
89
106
|
try { unlinkSync(${JSON.stringify(lockPath)}); } catch {}
|
|
90
107
|
}`;
|
|
91
108
|
const child = spawn(process.execPath, ['--input-type=module', '--eval', script], {
|
|
@@ -29,14 +29,20 @@ function _requireMonograph() {
|
|
|
29
29
|
// Used by route (pre-resolve), pre-search (Grep/Glob redirect), and post-read
|
|
30
30
|
// (neighbor footer). All calls are best-effort; failures are silent.
|
|
31
31
|
|
|
32
|
+
// Memoized at module scope — opening a multi-GB monograph.db can take 7-10s,
|
|
33
|
+
// and we call this 3+ times per route hook. Cache for the lifetime of this
|
|
34
|
+
// hook process. Callers should NOT close the returned handle.
|
|
35
|
+
var _cachedMonographDb = undefined;
|
|
32
36
|
function _openMonographDb() {
|
|
37
|
+
if (_cachedMonographDb !== undefined) return _cachedMonographDb;
|
|
33
38
|
try {
|
|
34
39
|
var dbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
35
|
-
if (!fs.existsSync(dbPath)) return null;
|
|
40
|
+
if (!fs.existsSync(dbPath)) { _cachedMonographDb = null; return null; }
|
|
36
41
|
var mod = _requireMonograph();
|
|
37
|
-
if (!mod || !mod.openDb) return null;
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
if (!mod || !mod.openDb) { _cachedMonographDb = null; return null; }
|
|
43
|
+
_cachedMonographDb = mod.openDb(dbPath);
|
|
44
|
+
return _cachedMonographDb;
|
|
45
|
+
} catch (e) { _cachedMonographDb = null; return null; }
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
function getMonographSuggestions(taskText, limit) {
|
|
@@ -91,7 +97,7 @@ function getMonographSuggestions(taskText, limit) {
|
|
|
91
97
|
}
|
|
92
98
|
return rows || [];
|
|
93
99
|
} catch (e) { return []; }
|
|
94
|
-
finally {
|
|
100
|
+
finally { /* db is shared/cached; do not close */ }
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
function getMonographNeighbors(filePath) {
|
|
@@ -119,7 +125,7 @@ function getMonographNeighbors(filePath) {
|
|
|
119
125
|
|
|
120
126
|
return { imports: imports, importedBy: importedBy };
|
|
121
127
|
} catch (e) { return null; }
|
|
122
|
-
finally {
|
|
128
|
+
finally { /* db is shared/cached; do not close */ }
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
// Rough per-event token + USD cost estimates. Tuned to Sonnet input pricing
|
|
@@ -184,7 +190,7 @@ function _injectCompactGraphMap() {
|
|
|
184
190
|
}
|
|
185
191
|
console.log(' Use mcp__monomind__monograph_suggest first when navigating.');
|
|
186
192
|
}
|
|
187
|
-
} finally {
|
|
193
|
+
} finally { /* db is shared/cached; do not close */ }
|
|
188
194
|
} catch (e) {}
|
|
189
195
|
}
|
|
190
196
|
|
|
@@ -209,30 +215,70 @@ function _recordToolCall(signature) {
|
|
|
209
215
|
|
|
210
216
|
// ── Cost budget ────────────────────────────────────────────────────────────────
|
|
211
217
|
// Read today's cost from token-summary and compare against budget ceiling.
|
|
218
|
+
// If no budget.json exists, auto-tune from 30-day rolling mean (1.5x) so we
|
|
219
|
+
// don't shout BUDGET_BREACHED at users whose normal spend is above the default.
|
|
212
220
|
function _getBudgetStatus() {
|
|
213
221
|
try {
|
|
214
222
|
var budgetFile = path.join(CWD, '.monomind', 'budget.json');
|
|
215
223
|
var summaryFile = path.join(CWD, '.monomind', 'metrics', 'token-summary.json');
|
|
216
224
|
if (!fs.existsSync(summaryFile)) return null;
|
|
217
225
|
var summary = JSON.parse(fs.readFileSync(summaryFile, 'utf-8'));
|
|
218
|
-
// Support both shapes: { todayCost, monthCost } and { today: { cost }, month: { cost } }
|
|
219
226
|
var todayCost = summary.todayCost || (summary.today && summary.today.cost) || 0;
|
|
220
227
|
var monthCost = summary.monthCost || (summary.month && summary.month.cost) || 0;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
|
|
229
|
+
var dailyLimit, monthlyLimit, autoTuned = false;
|
|
230
|
+
if (fs.existsSync(budgetFile)) {
|
|
231
|
+
try {
|
|
224
232
|
var b = JSON.parse(fs.readFileSync(budgetFile, 'utf-8'));
|
|
225
|
-
dailyLimit = b.dailyLimit
|
|
226
|
-
monthlyLimit = b.monthlyLimit
|
|
233
|
+
dailyLimit = b.dailyLimit;
|
|
234
|
+
monthlyLimit = b.monthlyLimit;
|
|
235
|
+
} catch (_) {}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Auto-tune: monthCost / daysSoFar = avg daily; 1.5x that = limit.
|
|
239
|
+
// Only auto-tune when we actually have 7+ days of data and no manual budget.
|
|
240
|
+
if (!dailyLimit || !monthlyLimit) {
|
|
241
|
+
var now = new Date();
|
|
242
|
+
var daysIntoMonth = now.getUTCDate();
|
|
243
|
+
var dailyAvg = daysIntoMonth >= 1 ? monthCost / daysIntoMonth : 0;
|
|
244
|
+
if (dailyAvg > 5 && daysIntoMonth >= 7) {
|
|
245
|
+
dailyLimit = Math.max(dailyLimit || 0, Math.ceil(dailyAvg * 1.5));
|
|
246
|
+
monthlyLimit = Math.max(monthlyLimit || 0, Math.ceil(dailyAvg * 1.5 * 30));
|
|
247
|
+
autoTuned = true;
|
|
248
|
+
// Persist so future runs are stable and the user can edit.
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(path.dirname(budgetFile), { recursive: true });
|
|
251
|
+
fs.writeFileSync(budgetFile, JSON.stringify({
|
|
252
|
+
dailyLimit: dailyLimit,
|
|
253
|
+
monthlyLimit: monthlyLimit,
|
|
254
|
+
autoTuned: true,
|
|
255
|
+
tunedAt: now.toISOString(),
|
|
256
|
+
basis: 'rolling avg $' + dailyAvg.toFixed(2) + '/day × 1.5',
|
|
257
|
+
note: 'Edit these values to set a hard ceiling. Delete the file to re-tune.',
|
|
258
|
+
}, null, 2));
|
|
259
|
+
} catch (_) {}
|
|
260
|
+
} else {
|
|
261
|
+
// Fall back to sensible defaults when there's not enough history.
|
|
262
|
+
dailyLimit = dailyLimit || 50;
|
|
263
|
+
monthlyLimit = monthlyLimit || 1500;
|
|
227
264
|
}
|
|
228
|
-
}
|
|
265
|
+
}
|
|
266
|
+
|
|
229
267
|
var dailyPct = Math.round((todayCost / dailyLimit) * 100);
|
|
230
268
|
var monthlyPct = Math.round((monthCost / monthlyLimit) * 100);
|
|
269
|
+
|
|
270
|
+
// Spike detection: today is >2x the rolling daily avg (suspicious activity)
|
|
271
|
+
var rollingDaily = (new Date()).getUTCDate() >= 1 ? monthCost / (new Date()).getUTCDate() : 0;
|
|
272
|
+
var spike = rollingDaily > 0 && todayCost > rollingDaily * 2.0 && todayCost > 5;
|
|
273
|
+
|
|
231
274
|
return {
|
|
232
275
|
todayCost: todayCost, monthCost: monthCost,
|
|
233
276
|
dailyLimit: dailyLimit, monthlyLimit: monthlyLimit,
|
|
234
277
|
dailyPct: dailyPct, monthlyPct: monthlyPct,
|
|
235
|
-
|
|
278
|
+
autoTuned: autoTuned,
|
|
279
|
+
spike: spike,
|
|
280
|
+
// Alert only when either the limit is breached OR there's a real spike
|
|
281
|
+
alert: dailyPct >= 80 || monthlyPct >= 80 || spike,
|
|
236
282
|
breached: dailyPct >= 100 || monthlyPct >= 100,
|
|
237
283
|
};
|
|
238
284
|
} catch (e) { return null; }
|
|
@@ -264,7 +310,7 @@ function _findAffectedTests(filePath) {
|
|
|
264
310
|
).all(filePath, rel);
|
|
265
311
|
return rows.map(function(r) { return r.file_path; });
|
|
266
312
|
} catch (e) { return []; }
|
|
267
|
-
finally {
|
|
313
|
+
finally { /* db is shared/cached; do not close */ }
|
|
268
314
|
}
|
|
269
315
|
|
|
270
316
|
// ── Hook latency tracking ─────────────────────────────────────────────────────
|
|
@@ -629,9 +675,8 @@ function _autoIndexKnowledge(knowledgeDir) {
|
|
|
629
675
|
|
|
630
676
|
if (fs.existsSync(mgDbPath2)) {
|
|
631
677
|
try {
|
|
632
|
-
var
|
|
633
|
-
if (
|
|
634
|
-
var sumDb = mgMod2.openDb(mgDbPath2);
|
|
678
|
+
var sumDb = _openMonographDb();
|
|
679
|
+
if (sumDb) {
|
|
635
680
|
try {
|
|
636
681
|
var nodeC = sumDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
637
682
|
var edgeC = sumDb.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
@@ -661,9 +706,7 @@ function _autoIndexKnowledge(knowledgeDir) {
|
|
|
661
706
|
' mcp__monomind__monograph_impact({ name: "<file>" }) — upstream + downstream blast radius',
|
|
662
707
|
].join('\n');
|
|
663
708
|
summaryMeta = { label: 'monograph-graph-summary', source: 'monograph.db', nodes: nodeC, edges: edgeC };
|
|
664
|
-
}
|
|
665
|
-
try { sumDb.close(); } catch (_) {}
|
|
666
|
-
}
|
|
709
|
+
} catch (e) { /* keep summaryText if partial */ }
|
|
667
710
|
}
|
|
668
711
|
} catch (e) { /* fall through to legacy */ }
|
|
669
712
|
}
|
|
@@ -826,15 +869,78 @@ const handlers = {
|
|
|
826
869
|
}
|
|
827
870
|
if (router && (router.routeTaskSemantic || router.routeTask)) {
|
|
828
871
|
const routeFn = router.routeTaskSemantic || router.routeTask;
|
|
829
|
-
|
|
872
|
+
var result = await Promise.resolve(routeFn(prompt));
|
|
873
|
+
|
|
874
|
+
// Graph-fallback override: when the router picked a low-confidence
|
|
875
|
+
// non-dev specialist (marketing slugs etc) but monograph has a strong
|
|
876
|
+
// graph match for the prompt, derive the agent from the top file's
|
|
877
|
+
// label instead. Stops "improve the system" → China E-Commerce.
|
|
878
|
+
try {
|
|
879
|
+
// Don't override when the prompt has obvious non-dev keywords —
|
|
880
|
+
// marketing/sales/finance asks SHOULD route to those specialists.
|
|
881
|
+
var nonDevPrompt = /\b(marketing|advertis|seo|tiktok|instagram|linkedin|sales|customer|brand|blog post|content strategy|copy(?:writ|writing)|pitch|investor|hr|recruit|legal|compliance|tax|invoice|accounting|onboarding|design syst|figma|user research|persona)\b/i.test(prompt);
|
|
882
|
+
|
|
883
|
+
var devAgents = /^(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)$/i;
|
|
884
|
+
var pickedDev = devAgents.test(String(result.agent || '').trim()) ||
|
|
885
|
+
devAgents.test(String(result.agentSlug || '').trim());
|
|
886
|
+
|
|
887
|
+
var resConf = (result.confidence != null ? result.confidence : 0);
|
|
888
|
+
var resReason = String(result.reason || '');
|
|
889
|
+
var fromKeywordStage = resReason.indexOf('Keyword 2-stage') !== -1;
|
|
890
|
+
var promptIsDevish = /\b(improve|refactor|fix|bug|optimi[sz]e|implement|build|debug|deploy|test|feature|system|performance|architecture|memory|hook|graph|statusline|monograph|api|cli|skill|hooks|agent|workflow|init|module|package|registry|server|client|route|handler)\b/i.test(prompt);
|
|
891
|
+
|
|
892
|
+
var shouldOverride = !nonDevPrompt && (
|
|
893
|
+
(!pickedDev && resConf < 0.85) ||
|
|
894
|
+
(fromKeywordStage && promptIsDevish)
|
|
895
|
+
);
|
|
896
|
+
if (shouldOverride) {
|
|
897
|
+
var topGraph = getMonographSuggestions(prompt, 1)[0];
|
|
898
|
+
if (topGraph) {
|
|
899
|
+
var agent = 'coder';
|
|
900
|
+
var file = (topGraph.file || '').toLowerCase();
|
|
901
|
+
// Test files
|
|
902
|
+
if (/\.(test|spec)\./.test(file) || file.includes('__tests__')) agent = 'tester';
|
|
903
|
+
// Architecture/system docs → architect
|
|
904
|
+
else if (/(architect|adr-|design-doc|rfc-)/.test(file)) agent = 'system-architect';
|
|
905
|
+
// Pure docs → tech writer
|
|
906
|
+
else if (file.endsWith('readme.md') || file.startsWith('docs/') || /\/docs\//.test(file)) agent = 'Technical Writer';
|
|
907
|
+
// Other .md (skills, agents, configs) → coder (they're code-adjacent)
|
|
908
|
+
else if (file.endsWith('.md')) agent = 'coder';
|
|
909
|
+
// Class/Interface → architect
|
|
910
|
+
else if (topGraph.label === 'Class' || topGraph.label === 'Interface') agent = 'system-architect';
|
|
911
|
+
// Functions, files, methods → coder
|
|
912
|
+
else agent = 'coder';
|
|
913
|
+
result = Object.assign({}, result, {
|
|
914
|
+
agent: agent,
|
|
915
|
+
agentSlug: agent,
|
|
916
|
+
confidence: 0.70,
|
|
917
|
+
reason: 'Graph fallback: top file ' + (topGraph.name || '').substring(0, 30) + ' [' + topGraph.label + ']',
|
|
918
|
+
specificAgents: [],
|
|
919
|
+
extrasMatches: [],
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
} catch (e) {}
|
|
924
|
+
|
|
830
925
|
var output = [];
|
|
831
926
|
output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));
|
|
832
927
|
output.push('');
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
928
|
+
// Suppress the agent recommendation panel for low-confidence routes on
|
|
929
|
+
// short prompts — the recommendation is almost always wrong (e.g.
|
|
930
|
+
// "what else can we do" → marketing → China E-Commerce). Saves ~150
|
|
931
|
+
// tokens per prompt. Skill matches and specific agents still render
|
|
932
|
+
// below when confidence is decent.
|
|
933
|
+
var conf = result.confidence != null ? result.confidence : 0;
|
|
934
|
+
var promptShort = (prompt || '').trim().length < 60;
|
|
935
|
+
var lowConf = conf < 0.70;
|
|
936
|
+
var suppressPanel = lowConf && promptShort;
|
|
937
|
+
if (!suppressPanel) {
|
|
938
|
+
output.push('+------------- monomind | Primary Recommendation --------------+');
|
|
939
|
+
output.push('| Agent: ' + (result.agent || 'unknown').substring(0, 54).padEnd(54) + '|');
|
|
940
|
+
output.push('| Confidence: ' + ((result.confidence != null ? (result.confidence * 100).toFixed(1) : '?') + '%').padEnd(49) + '|');
|
|
941
|
+
output.push('| Reason: ' + (result.reason || '').substring(0, 53).padEnd(53) + '|');
|
|
942
|
+
output.push('+--------------------------------------------------------------+');
|
|
943
|
+
}
|
|
838
944
|
|
|
839
945
|
// ── Persist routing result for statusline display ─────────────
|
|
840
946
|
try {
|
|
@@ -913,8 +1019,9 @@ const handlers = {
|
|
|
913
1019
|
}
|
|
914
1020
|
|
|
915
1021
|
// ── Specific agent panel ──────────────────────────────────────────────────
|
|
1022
|
+
// Skip entirely on suppressed (low-confidence + short) prompts.
|
|
916
1023
|
var specificAgents = result.specificAgents || [];
|
|
917
|
-
if (specificAgents.length > 0) {
|
|
1024
|
+
if (specificAgents.length > 0 && !suppressPanel) {
|
|
918
1025
|
output.push('');
|
|
919
1026
|
var saHdr = '------- Specific Agents (' + specificAgents.length + ' available) ';
|
|
920
1027
|
output.push('+' + saHdr + '-'.repeat(Math.max(1, 62 - saHdr.length)) + '+');
|
|
@@ -934,7 +1041,7 @@ const handlers = {
|
|
|
934
1041
|
// ── Specialist agents (non-dev domain) — only shown when specificAgents panel wasn't shown ──
|
|
935
1042
|
var extras = result.extrasMatches || [];
|
|
936
1043
|
var specificAgentsShown = (result.specificAgents || []).length > 0;
|
|
937
|
-
if (extras.length > 0 && !specificAgentsShown) {
|
|
1044
|
+
if (extras.length > 0 && !specificAgentsShown && !suppressPanel) {
|
|
938
1045
|
output.push('');
|
|
939
1046
|
var spHdr = '------- Specialist Agents (' + extras.length + ' matched) ';
|
|
940
1047
|
output.push('+' + spHdr + '-'.repeat(Math.max(1, 62 - spHdr.length)) + '+');
|
|
@@ -990,10 +1097,13 @@ const handlers = {
|
|
|
990
1097
|
try {
|
|
991
1098
|
var budget = _getBudgetStatus();
|
|
992
1099
|
if (budget && budget.alert) {
|
|
993
|
-
|
|
994
|
-
|
|
1100
|
+
var tunedNote = budget.autoTuned ? ' (auto-tuned)' : '';
|
|
1101
|
+
if (budget.spike && !budget.breached) {
|
|
1102
|
+
console.log('[BUDGET_SPIKE] Today $' + budget.todayCost.toFixed(2) + ' is >2x your rolling daily avg. Unusual spend — review .monomind/metrics/token-summary.json.');
|
|
1103
|
+
} else if (budget.breached) {
|
|
1104
|
+
console.log('[BUDGET_BREACHED] Daily $' + budget.todayCost.toFixed(2) + '/$' + budget.dailyLimit + ' (' + budget.dailyPct + '%) · Monthly $' + budget.monthCost.toFixed(2) + '/$' + budget.monthlyLimit + ' (' + budget.monthlyPct + '%)' + tunedNote + '. Switch to Haiku with /model haiku or edit .monomind/budget.json.');
|
|
995
1105
|
} else {
|
|
996
|
-
console.log('[BUDGET_ALERT] Daily ' + budget.dailyPct + '% of $' + budget.dailyLimit + ' · Monthly ' + budget.monthlyPct + '% of $' + budget.monthlyLimit +
|
|
1106
|
+
console.log('[BUDGET_ALERT] Daily ' + budget.dailyPct + '% of $' + budget.dailyLimit + ' · Monthly ' + budget.monthlyPct + '% of $' + budget.monthlyLimit + tunedNote + '.');
|
|
997
1107
|
}
|
|
998
1108
|
}
|
|
999
1109
|
} catch (e) {}
|
|
@@ -1007,14 +1117,9 @@ const handlers = {
|
|
|
1007
1117
|
var nodeCount = 0;
|
|
1008
1118
|
if (fs.existsSync(monographDb)) {
|
|
1009
1119
|
try {
|
|
1010
|
-
var
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
try {
|
|
1014
|
-
nodeCount = hintDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
1015
|
-
} finally {
|
|
1016
|
-
try { hintDb.close(); } catch (_) {}
|
|
1017
|
-
}
|
|
1120
|
+
var hintDb = _openMonographDb();
|
|
1121
|
+
if (hintDb) {
|
|
1122
|
+
nodeCount = hintDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
1018
1123
|
}
|
|
1019
1124
|
} catch (e) { /* ignore — fall back to legacy */ }
|
|
1020
1125
|
}
|
|
@@ -1451,10 +1556,8 @@ const handlers = {
|
|
|
1451
1556
|
try {
|
|
1452
1557
|
var mgDbPath = path.join(CWD, '.monomind', 'monograph.db');
|
|
1453
1558
|
if (fs.existsSync(mgDbPath)) {
|
|
1454
|
-
var
|
|
1455
|
-
|
|
1456
|
-
if (mgMod && mgMod.openDb) {
|
|
1457
|
-
var mgDb = mgMod.openDb(mgDbPath);
|
|
1559
|
+
var mgDb = _openMonographDb();
|
|
1560
|
+
if (mgDb) {
|
|
1458
1561
|
try {
|
|
1459
1562
|
var mgNodeCount = mgDb.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
1460
1563
|
var mgEdgeCount = mgDb.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
@@ -1493,7 +1596,7 @@ const handlers = {
|
|
|
1493
1596
|
fs.writeFileSync(mgChunksFile, mgExisting.join('\n') + '\n');
|
|
1494
1597
|
} catch(e) {}
|
|
1495
1598
|
}
|
|
1496
|
-
}
|
|
1599
|
+
} catch(e) { /* non-fatal */ }
|
|
1497
1600
|
}
|
|
1498
1601
|
}
|
|
1499
1602
|
} catch(e) { /* non-fatal */ }
|
|
@@ -2226,7 +2329,7 @@ const handlers = {
|
|
|
2226
2329
|
} catch (_) {}
|
|
2227
2330
|
console.log(' Use mcp__monomind__monograph_suggest / monograph_query in this subagent before grepping.');
|
|
2228
2331
|
}
|
|
2229
|
-
}
|
|
2332
|
+
} catch (e) { /* non-fatal */ }
|
|
2230
2333
|
}
|
|
2231
2334
|
} catch (e) { /* non-fatal */ }
|
|
2232
2335
|
|
|
@@ -2289,6 +2392,75 @@ const handlers = {
|
|
|
2289
2392
|
console.log(' Edit the file to fill in Context and Consequences, then change Status to Accepted/Rejected.');
|
|
2290
2393
|
},
|
|
2291
2394
|
|
|
2395
|
+
'graph-status': () => {
|
|
2396
|
+
var db = _openMonographDb();
|
|
2397
|
+
if (!db) { console.log('No monograph.db found. Run /monomind:understand to build.'); return; }
|
|
2398
|
+
try {
|
|
2399
|
+
var n = db.prepare("SELECT COUNT(*) AS c FROM nodes").get().c;
|
|
2400
|
+
var e = db.prepare("SELECT COUNT(*) AS c FROM edges").get().c;
|
|
2401
|
+
var usage = (function() {
|
|
2402
|
+
try { return JSON.parse(fs.readFileSync(path.join(CWD, '.monomind', 'metrics', 'graph-usage.json'), 'utf-8')); }
|
|
2403
|
+
catch (_) { return {}; }
|
|
2404
|
+
})();
|
|
2405
|
+
var wins = (usage.monograph_call || 0) + (usage.preresolve_hit || 0)
|
|
2406
|
+
+ (usage.graph_assist_search || 0) + (usage.graph_assist_neighbors || 0);
|
|
2407
|
+
var search = (usage.grep_call || 0) + (usage.glob_call || 0)
|
|
2408
|
+
+ (usage.bash_grep_call || 0) + (usage.bash_find_call || 0);
|
|
2409
|
+
var pct = (wins + search) > 0 ? Math.round((wins / (wins + search)) * 100) : 0;
|
|
2410
|
+
var saved = usage.dollars_saved || 0;
|
|
2411
|
+
console.log('Monograph: ' + n.toLocaleString() + ' nodes · ' + e.toLocaleString() + ' edges');
|
|
2412
|
+
console.log('Usage: ' + pct + '% graph · ' + (100 - pct) + '% grep · ' +
|
|
2413
|
+
'wins=' + wins + ' search=' + search +
|
|
2414
|
+
(saved > 0 ? ' · saved $' + saved.toFixed(2) : ''));
|
|
2415
|
+
} catch (err) { console.log('Error: ' + err.message); }
|
|
2416
|
+
},
|
|
2417
|
+
|
|
2418
|
+
'budget-status': () => {
|
|
2419
|
+
var b = _getBudgetStatus();
|
|
2420
|
+
if (!b) { console.log('No budget data yet — token tracking not initialized.'); return; }
|
|
2421
|
+
console.log('Today: $' + b.todayCost.toFixed(2) + ' / $' + b.dailyLimit + ' (' + b.dailyPct + '%)' + (b.autoTuned ? ' [auto-tuned]' : ''));
|
|
2422
|
+
console.log('Month: $' + b.monthCost.toFixed(2) + ' / $' + b.monthlyLimit + ' (' + b.monthlyPct + '%)');
|
|
2423
|
+
console.log('Status: ' + (b.breached ? 'BREACHED' : b.spike ? 'SPIKE' : b.alert ? 'ALERT' : 'OK'));
|
|
2424
|
+
console.log('Edit .monomind/budget.json to adjust. Delete to re-tune.');
|
|
2425
|
+
},
|
|
2426
|
+
|
|
2427
|
+
'loops-status': () => {
|
|
2428
|
+
var loopsDir = path.join(CWD, '.monomind', 'loops');
|
|
2429
|
+
if (!fs.existsSync(loopsDir)) { console.log('No loops directory.'); return; }
|
|
2430
|
+
var files = fs.readdirSync(loopsDir).filter(function(f) {
|
|
2431
|
+
return f.endsWith('.json') && !f.includes('-hil') && !f.endsWith('.stop');
|
|
2432
|
+
});
|
|
2433
|
+
var STALE_MS = 6 * 60 * 60 * 1000;
|
|
2434
|
+
var now = Date.now();
|
|
2435
|
+
var active = [], stale = [];
|
|
2436
|
+
files.forEach(function(f) {
|
|
2437
|
+
try {
|
|
2438
|
+
var d = JSON.parse(fs.readFileSync(path.join(loopsDir, f), 'utf-8'));
|
|
2439
|
+
var last = d.lastRunAt || d.startedAt || 0;
|
|
2440
|
+
var ageMs = last ? (now - last) : Infinity;
|
|
2441
|
+
if (ageMs > STALE_MS) stale.push({ d: d, ageH: Math.round(ageMs / 3600000) });
|
|
2442
|
+
else active.push(d);
|
|
2443
|
+
} catch (_) {}
|
|
2444
|
+
});
|
|
2445
|
+
if (active.length === 0 && stale.length === 0) {
|
|
2446
|
+
console.log('No loops.'); return;
|
|
2447
|
+
}
|
|
2448
|
+
if (active.length > 0) {
|
|
2449
|
+
console.log('Active (' + active.length + '):');
|
|
2450
|
+
active.forEach(function(d) {
|
|
2451
|
+
console.log(' · ' + (d.command || '?') + ' [' + (d.type || '?') + '] run ' + (d.currentRep || 0) +
|
|
2452
|
+
(d.maxReps ? '/' + d.maxReps : '') + ' · ' + (d.status || '?'));
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
if (stale.length > 0) {
|
|
2456
|
+
console.log('Stale (' + stale.length + ' >6h):');
|
|
2457
|
+
stale.forEach(function(s) {
|
|
2458
|
+
console.log(' · ' + (s.d.command || '?') + ' run ' + (s.d.currentRep || 0) +
|
|
2459
|
+
' · ' + s.ageH + 'h ago · ' + (s.d.status || '?'));
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
},
|
|
2463
|
+
|
|
2292
2464
|
'status': () => {
|
|
2293
2465
|
console.log('[OK] Status check');
|
|
2294
2466
|
},
|
|
@@ -2584,6 +2584,7 @@
|
|
|
2584
2584
|
<button class="mm-tab" onclick="switchMmTab('loops')">LOOPS</button>
|
|
2585
2585
|
<button class="mm-tab" onclick="switchMmTab('create')">CREATE ORG</button>
|
|
2586
2586
|
<button class="mm-tab" onclick="switchMmTab('metrics')">METRICS</button>
|
|
2587
|
+
<button class="mm-tab" onclick="switchMmTab('graph')">GRAPH</button>
|
|
2587
2588
|
</div>
|
|
2588
2589
|
<div id="mm-body">
|
|
2589
2590
|
<div class="mm-pane active" id="mm-pane-orgs">
|
|
@@ -2633,6 +2634,18 @@
|
|
|
2633
2634
|
<div class="mm-section-title" style="margin-top:16px">Recent Events</div>
|
|
2634
2635
|
<div id="mm-metrics-events" style="font-size:9px;color:rgba(150,100,200,0.6);font-family:'Azeret Mono',monospace"></div>
|
|
2635
2636
|
</div>
|
|
2637
|
+
<div class="mm-pane" id="mm-pane-graph">
|
|
2638
|
+
<div class="mm-section-title">Monograph Knowledge Graph</div>
|
|
2639
|
+
<div id="mm-graph-summary" style="display:flex;gap:24px;margin-bottom:16px;font-size:11px">
|
|
2640
|
+
<span style="color:rgba(150,100,200,0.4)">Loading graph stats…</span>
|
|
2641
|
+
</div>
|
|
2642
|
+
<div class="mm-section-title" style="margin-top:8px">Top God Nodes (highest centrality)</div>
|
|
2643
|
+
<div id="mm-graph-gods" style="font-size:9px;color:rgba(180,140,220,0.85);font-family:'Azeret Mono',monospace;line-height:1.6"></div>
|
|
2644
|
+
<div class="mm-section-title" style="margin-top:16px">Node Types</div>
|
|
2645
|
+
<div id="mm-graph-types" style="font-size:9px;color:rgba(180,140,220,0.85);font-family:'Azeret Mono',monospace"></div>
|
|
2646
|
+
<div class="mm-section-title" style="margin-top:16px">Edge Relations</div>
|
|
2647
|
+
<div id="mm-graph-relations" style="font-size:9px;color:rgba(180,140,220,0.85);font-family:'Azeret Mono',monospace"></div>
|
|
2648
|
+
</div>
|
|
2636
2649
|
</div>
|
|
2637
2650
|
</div>
|
|
2638
2651
|
</div>
|
|
@@ -10264,6 +10277,50 @@ window.switchMmTab = function(tab) {
|
|
|
10264
10277
|
if (tab === 'loops') loadMmLoops();
|
|
10265
10278
|
if (tab === 'metrics') loadMmMetrics();
|
|
10266
10279
|
if (tab === 'skills') renderMmSkills('');
|
|
10280
|
+
if (tab === 'graph') loadMmGraph();
|
|
10281
|
+
};
|
|
10282
|
+
|
|
10283
|
+
window.loadMmGraph = async function() {
|
|
10284
|
+
const summary = document.getElementById('mm-graph-summary');
|
|
10285
|
+
const gods = document.getElementById('mm-graph-gods');
|
|
10286
|
+
const types = document.getElementById('mm-graph-types');
|
|
10287
|
+
const relations = document.getElementById('mm-graph-relations');
|
|
10288
|
+
if (!summary) return;
|
|
10289
|
+
try {
|
|
10290
|
+
const r = await fetch('/api/monograph');
|
|
10291
|
+
const d = await r.json();
|
|
10292
|
+
if (!d.exists) {
|
|
10293
|
+
summary.innerHTML = '<span style="color:rgba(220,120,120,0.7)">No monograph.db found. Run /monomind:understand or wait for SessionStart to build the graph.</span>';
|
|
10294
|
+
gods.textContent = ''; types.textContent = ''; relations.textContent = '';
|
|
10295
|
+
return;
|
|
10296
|
+
}
|
|
10297
|
+
summary.innerHTML = `
|
|
10298
|
+
<div><div style="color:rgba(150,100,200,0.5);font-size:8px;letter-spacing:0.1em">NODES</div>
|
|
10299
|
+
<div style="font-size:18px;color:rgba(120,200,160,1);font-weight:bold">${d.nodes.toLocaleString()}</div></div>
|
|
10300
|
+
<div><div style="color:rgba(150,100,200,0.5);font-size:8px;letter-spacing:0.1em">EDGES</div>
|
|
10301
|
+
<div style="font-size:18px;color:rgba(120,200,160,1);font-weight:bold">${d.edges.toLocaleString()}</div></div>
|
|
10302
|
+
<div><div style="color:rgba(150,100,200,0.5);font-size:8px;letter-spacing:0.1em">UPDATED</div>
|
|
10303
|
+
<div style="font-size:11px;color:rgba(210,140,255,0.8)">${new Date(d.updatedAt).toLocaleString()}</div></div>
|
|
10304
|
+
`;
|
|
10305
|
+
gods.innerHTML = (d.godNodes || []).map(n =>
|
|
10306
|
+
`<div>· <span style="color:rgba(255,200,100,0.9)">${(n.deg||0).toString().padStart(4,' ')}</span>
|
|
10307
|
+
<span style="color:rgba(210,140,255,1)">${n.name||''}</span>
|
|
10308
|
+
<span style="color:rgba(150,100,200,0.5)">[${n.label||''}]</span>
|
|
10309
|
+
<span style="color:rgba(120,200,160,0.7)">${n.file_path||''}</span></div>`
|
|
10310
|
+
).join('') || '<span style="color:rgba(150,100,200,0.4)">none</span>';
|
|
10311
|
+
types.innerHTML = (d.typeDistribution || []).map(t =>
|
|
10312
|
+
`<span style="display:inline-block;padding:2px 8px;margin:2px;background:rgba(200,120,255,0.08);border:1px solid rgba(200,120,255,0.2);border-radius:3px">
|
|
10313
|
+
${t.label}: <strong style="color:rgba(120,200,160,1)">${t.count.toLocaleString()}</strong>
|
|
10314
|
+
</span>`
|
|
10315
|
+
).join('');
|
|
10316
|
+
relations.innerHTML = (d.relationDistribution || []).map(r =>
|
|
10317
|
+
`<span style="display:inline-block;padding:2px 8px;margin:2px;background:rgba(200,120,255,0.08);border:1px solid rgba(200,120,255,0.2);border-radius:3px">
|
|
10318
|
+
${r.relation}: <strong style="color:rgba(255,200,100,1)">${r.count.toLocaleString()}</strong>
|
|
10319
|
+
</span>`
|
|
10320
|
+
).join('');
|
|
10321
|
+
} catch (err) {
|
|
10322
|
+
summary.innerHTML = `<span style="color:rgba(220,120,120,0.7)">Failed to load graph: ${err.message}</span>`;
|
|
10323
|
+
}
|
|
10267
10324
|
};
|
|
10268
10325
|
|
|
10269
10326
|
window.filterMmSkills = function(q) { renderMmSkills(q); };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.15",
|
|
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",
|