@monoes/monomindcli 1.10.14 → 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Show current budget status — today, month, limits, autotuned flag
3
+ ---
4
+
5
+ ```bash
6
+ node "$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs" budget-status
7
+ ```
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Single-line graph stats — nodes, edges, freshness, usage %
3
+ ---
4
+
5
+ ```bash
6
+ node "$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs" graph-status
7
+ ```
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: List active loops — command, type, run count, HIL status
3
+ ---
4
+
5
+ ```bash
6
+ node "$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs" loops-status
7
+ ```
@@ -215,30 +215,70 @@ function _recordToolCall(signature) {
215
215
 
216
216
  // ── Cost budget ────────────────────────────────────────────────────────────────
217
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.
218
220
  function _getBudgetStatus() {
219
221
  try {
220
222
  var budgetFile = path.join(CWD, '.monomind', 'budget.json');
221
223
  var summaryFile = path.join(CWD, '.monomind', 'metrics', 'token-summary.json');
222
224
  if (!fs.existsSync(summaryFile)) return null;
223
225
  var summary = JSON.parse(fs.readFileSync(summaryFile, 'utf-8'));
224
- // Support both shapes: { todayCost, monthCost } and { today: { cost }, month: { cost } }
225
226
  var todayCost = summary.todayCost || (summary.today && summary.today.cost) || 0;
226
227
  var monthCost = summary.monthCost || (summary.month && summary.month.cost) || 0;
227
- var dailyLimit = 50, monthlyLimit = 1500; // defaults
228
- try {
229
- if (fs.existsSync(budgetFile)) {
228
+
229
+ var dailyLimit, monthlyLimit, autoTuned = false;
230
+ if (fs.existsSync(budgetFile)) {
231
+ try {
230
232
  var b = JSON.parse(fs.readFileSync(budgetFile, 'utf-8'));
231
- dailyLimit = b.dailyLimit || dailyLimit;
232
- monthlyLimit = b.monthlyLimit || 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;
233
264
  }
234
- } catch (_) {}
265
+ }
266
+
235
267
  var dailyPct = Math.round((todayCost / dailyLimit) * 100);
236
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
+
237
274
  return {
238
275
  todayCost: todayCost, monthCost: monthCost,
239
276
  dailyLimit: dailyLimit, monthlyLimit: monthlyLimit,
240
277
  dailyPct: dailyPct, monthlyPct: monthlyPct,
241
- alert: dailyPct >= 80 || monthlyPct >= 80,
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,
242
282
  breached: dailyPct >= 100 || monthlyPct >= 100,
243
283
  };
244
284
  } catch (e) { return null; }
@@ -829,7 +869,59 @@ const handlers = {
829
869
  }
830
870
  if (router && (router.routeTaskSemantic || router.routeTask)) {
831
871
  const routeFn = router.routeTaskSemantic || router.routeTask;
832
- const result = await Promise.resolve(routeFn(prompt));
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
+
833
925
  var output = [];
834
926
  output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));
835
927
  output.push('');
@@ -1005,10 +1097,13 @@ const handlers = {
1005
1097
  try {
1006
1098
  var budget = _getBudgetStatus();
1007
1099
  if (budget && budget.alert) {
1008
- if (budget.breached) {
1009
- console.log('[BUDGET_BREACHED] Daily $' + budget.todayCost.toFixed(2) + '/$' + budget.dailyLimit + ' (' + budget.dailyPct + '%) · Monthly $' + budget.monthCost.toFixed(2) + '/$' + budget.monthlyLimit + ' (' + budget.monthlyPct + '%). Switch to Haiku with /model haiku or raise limits in .monomind/budget.json.');
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.');
1010
1105
  } else {
1011
- console.log('[BUDGET_ALERT] Daily ' + budget.dailyPct + '% of $' + budget.dailyLimit + ' · Monthly ' + budget.monthlyPct + '% of $' + budget.monthlyLimit + '. Adjust .monomind/budget.json if needed.');
1106
+ console.log('[BUDGET_ALERT] Daily ' + budget.dailyPct + '% of $' + budget.dailyLimit + ' · Monthly ' + budget.monthlyPct + '% of $' + budget.monthlyLimit + tunedNote + '.');
1012
1107
  }
1013
1108
  }
1014
1109
  } catch (e) {}
@@ -2297,6 +2392,75 @@ const handlers = {
2297
2392
  console.log(' Edit the file to fill in Context and Consequences, then change Status to Accepted/Rejected.');
2298
2393
  },
2299
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
+
2300
2464
  'status': () => {
2301
2465
  console.log('[OK] Status check');
2302
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> &nbsp;
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
+ &nbsp; <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.14",
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",