@monoes/monomindcli 1.10.5 → 1.10.7

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.
@@ -659,13 +659,31 @@ function getAgentDBStats() {
659
659
  }
660
660
 
661
661
  // Graphify knowledge graph stats
662
+ // Sources, in priority order:
663
+ // 1. .monomind/graph/stats.json — explicit cached stats
664
+ // 2. .monomind/monograph.db — live SQLite (read counts via sqlite3)
665
+ // 3. .monomind/graph/graph.json — legacy JSON dump
662
666
  function getGraphifyStats() {
663
667
  const statsPath = path.join(CWD, '.monomind', 'graph', 'stats.json');
668
+ const dbPath = path.join(CWD, '.monomind', 'monograph.db');
664
669
  const graphPath = path.join(CWD, '.monomind', 'graph', 'graph.json');
670
+
665
671
  try {
666
672
  const s = readJSON(statsPath);
667
673
  if (s && s.nodes !== undefined) return { nodes: s.nodes, edges: s.edges || 0, exists: true };
668
674
  } catch { /* ignore */ }
675
+
676
+ // Live monograph.db — single SQLite call, strict 1s timeout
677
+ try {
678
+ if (fs.existsSync(dbPath)) {
679
+ const out = safeExec(`sqlite3 "${dbPath}" "SELECT (SELECT COUNT(*) FROM nodes), (SELECT COUNT(*) FROM edges);"`, 1000);
680
+ if (out) {
681
+ const [n, e] = out.split('|').map(v => parseInt(v, 10) || 0);
682
+ if (n > 0) return { nodes: n, edges: e, exists: true };
683
+ }
684
+ }
685
+ } catch { /* ignore */ }
686
+
669
687
  try {
670
688
  const stat = safeStat(graphPath);
671
689
  if (stat && stat.size < 10 * 1024 * 1024) {
@@ -678,6 +696,78 @@ function getGraphifyStats() {
678
696
  return { nodes: 0, edges: 0, exists: false };
679
697
  }
680
698
 
699
+ // Graph freshness — compare graph build time against most recent commit
700
+ // Returns: { commitsBehind, stale } where stale = >5 commits or never built
701
+ function getGraphFreshness() {
702
+ const lockPath = path.join(CWD, '.monomind', 'graph', '.rebuild-lock');
703
+ const dbPath = path.join(CWD, '.monomind', 'monograph.db');
704
+
705
+ let buildMs = 0;
706
+ try {
707
+ const lockStat = safeStat(lockPath);
708
+ const dbStat = safeStat(dbPath);
709
+ buildMs = Math.max(lockStat?.mtimeMs || 0, dbStat?.mtimeMs || 0);
710
+ } catch { /* ignore */ }
711
+ if (!buildMs) return { commitsBehind: -1, stale: true, fresh: false };
712
+
713
+ // Count commits since the graph was last built
714
+ const buildIso = new Date(buildMs).toISOString();
715
+ const out = safeExec(`git rev-list --count --since='${buildIso}' HEAD 2>/dev/null`, 1500);
716
+ const commitsBehind = parseInt(out, 10) || 0;
717
+ return {
718
+ commitsBehind,
719
+ stale: commitsBehind > 5,
720
+ fresh: commitsBehind === 0,
721
+ };
722
+ }
723
+
724
+ // Active loops — scan .monomind/loops/*.json
725
+ // Filters: skip *-hil*.json, skip stale (>6h since lastRunAt)
726
+ function getLoopStatus() {
727
+ const loopsDir = path.join(CWD, '.monomind', 'loops');
728
+ if (!fs.existsSync(loopsDir)) return { count: 0, loops: [] };
729
+ const STALE_MS = 6 * 60 * 60 * 1000;
730
+ const now = Date.now();
731
+ let loops = [];
732
+ try {
733
+ const files = fs.readdirSync(loopsDir).filter(f =>
734
+ f.endsWith('.json') && !f.includes('-hil') && !f.endsWith('.stop'));
735
+ for (const f of files) {
736
+ const d = readJSON(path.join(loopsDir, f));
737
+ if (!d || !d.command) continue;
738
+ const last = d.lastRunAt || d.nextRunAt || d.startedAt || 0;
739
+ if (last && (now - last) > STALE_MS) continue;
740
+ loops.push({
741
+ cmd: d.command.replace(/^\//,''),
742
+ type: d.type || 'repeat',
743
+ rep: d.currentRep || 0,
744
+ max: d.maxReps || 0,
745
+ status: d.status || 'running',
746
+ });
747
+ }
748
+ } catch { /* ignore */ }
749
+ return { count: loops.length, loops };
750
+ }
751
+
752
+ // HIL pending — count <id>-hil.md files with no human response yet
753
+ function getHILPending() {
754
+ const loopsDir = path.join(CWD, '.monomind', 'loops');
755
+ if (!fs.existsSync(loopsDir)) return { pending: 0 };
756
+ let pending = 0;
757
+ try {
758
+ const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('-hil.md'));
759
+ for (const f of files) {
760
+ try {
761
+ const txt = fs.readFileSync(path.join(loopsDir, f), 'utf-8');
762
+ // A response is a line starting with "> " followed by non-whitespace
763
+ const answered = /^[ \t]*>[ \t]+\S/m.test(txt);
764
+ if (!answered) pending++;
765
+ } catch { /* ignore */ }
766
+ }
767
+ } catch { /* ignore */ }
768
+ return { pending };
769
+ }
770
+
681
771
  // Memory Palace stats — drawers.jsonl + kg.json (the real persistent memory)
682
772
  function getMemoryPalaceStats() {
683
773
  const palaceDir = path.join(CWD, '.monomind', 'palace');
@@ -1112,158 +1202,68 @@ function generateDashboard() {
1112
1202
  lines.push(hdr);
1113
1203
  lines.push(SEP);
1114
1204
 
1115
- // ── Row 1: Knowledge & Graphify ──────────────────────────────
1116
- const knowStr = knowledge.chunks > 0
1117
- ? `${x.teal}📚 ${x.bold}${knowledge.chunks}${x.reset}${x.slate} chunks${x.reset}`
1118
- : `${x.slate}📚 no chunks${x.reset}`;
1119
-
1120
- const skillStr = knowledge.skills > 0
1121
- ? ` ${x.mint}✦ ${knowledge.skills} skills${x.reset}`
1122
- : '';
1123
-
1124
- const patStr = progress.patternsLearned > 0
1125
- ? `${x.gold}${progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : progress.patternsLearned} patterns${x.reset}`
1126
- : `${x.slate}0 patterns${x.reset}`;
1127
-
1128
- const gf = getGraphifyStats();
1129
- const gfStr = gf.exists
1130
- ? `${x.sky}🔗 ${x.bold}${gf.nodes}${x.reset}${x.slate} nodes · ${x.reset}${x.sky}${x.bold}${gf.edges}${x.reset}${x.slate} edges${x.reset}`
1131
- : `${x.slate}🔗 no graph${x.reset}`;
1132
-
1133
- lines.push(
1134
- `${x.purple}💡 INTEL${x.reset} ` +
1135
- `${knowStr}${skillStr} ${DIV} ` +
1136
- `${patStr} ${DIV} ` +
1137
- gfStr
1138
- );
1139
- lines.push(SEP);
1140
-
1141
- // ── Row 2: Agents & Triggers ──────────────────────────────────
1142
- const agentCol = swarm.activeAgents > 0 ? x.green : x.slate;
1143
- const hookCol = hooks.enabled > 0 ? x.mint : x.slate;
1144
-
1145
- // Triggers (Task 32)
1146
- const trigStr = triggers.triggers > 0
1147
- ? `${x.mint}🎯 ${x.bold}${triggers.triggers}${x.reset}${x.slate} triggers · ${triggers.agents} agents${x.reset}`
1148
- : `${x.slate}🎯 no triggers${x.reset}`;
1149
-
1150
- // Active agent badge
1151
- let agentBadge;
1205
+ // ── Row 1: Active agent + Loop status ────────────────────────
1206
+ let agentStr;
1152
1207
  if (activeAgent) {
1153
1208
  const isExtras = activeAgent.slug === 'extras' || activeAgent.name === 'Extras';
1154
1209
  if (isExtras && activeAgent.extrasMatches && activeAgent.extrasMatches.length > 0) {
1155
- // Show specific specialist names instead of generic "Extras"
1156
- const specialists = activeAgent.extrasMatches.slice(0, 3);
1157
- const badgeParts = specialists.map(s => `${x.sky}👤 ${x.bold}${s.name}${x.reset}`);
1158
- agentBadge = badgeParts.join(`${x.slate} ${x.reset}`);
1210
+ const specialists = activeAgent.extrasMatches.slice(0, 3).map(s => s.name).join(', ');
1211
+ agentStr = `${x.sky}👤 ${x.bold}${specialists}${x.reset}`;
1159
1212
  } else if (isExtras) {
1160
- // "extras" with no specific matches — suppress
1161
- agentBadge = `${x.slate}👤 no agent routed${x.reset}`;
1213
+ agentStr = `${x.slate}👤 no agent routed${x.reset}`;
1162
1214
  } else {
1163
1215
  const col = activeAgent.activated ? x.green : x.sky;
1164
- const mark = activeAgent.activated ? '● ACTIVE' : '';
1216
+ const mark = activeAgent.activated ? `${col}${x.bold}● ACTIVE${x.reset} ` : '';
1165
1217
  const conf = activeAgent.activated ? '' : ` ${x.slate}${(activeAgent.confidence * 100).toFixed(0)}%${x.reset}`;
1166
- const cat = activeAgent.category ? ` ${x.slate}[${activeAgent.category}]${x.reset}` : '';
1167
- agentBadge = mark
1168
- ? `${col}${x.bold}${mark}${x.reset} ${col}👤 ${x.bold}${activeAgent.name}${x.reset}${cat}${conf}`
1169
- : `${col}👤 ${x.bold}${activeAgent.name}${x.reset}${cat}${conf}`;
1218
+ agentStr = `${mark}${col}👤 ${x.bold}${activeAgent.name}${x.reset}${conf}`;
1170
1219
  }
1171
1220
  } else {
1172
- agentBadge = `${x.slate}👤 no agent routed${x.reset}`;
1221
+ agentStr = `${x.slate}👤 no agent routed${x.reset}`;
1173
1222
  }
1174
1223
 
1175
- // Swarm line: show active count, or "N ✓ idle" when recently used, or "idle" when never used
1176
- const swarmCountStr = swarm.activeAgents > 0
1177
- ? `${agentCol}${x.bold}${swarm.activeAgents}${x.reset}${x.slate}/${x.reset}${x.white}${swarm.maxAgents}${x.reset} agents`
1178
- : (swarm.lastActive || 0) > 0
1179
- ? `${x.slate}${swarm.lastActive}${x.reset}${x.slate}/${swarm.maxAgents} (${x.reset}${x.green}✓ done${x.slate})${x.reset}`
1180
- : `${x.slate}idle${x.reset} `;
1181
- lines.push(
1182
- `${x.gold}🐝 SWARM${x.reset} ` +
1183
- `${swarmCountStr} ` +
1184
- `${hookCol} ${hooks.enabled}/${hooks.total} hooks${x.reset} ${DIV} ` +
1185
- `${trigStr} ${DIV} ` +
1186
- agentBadge
1187
- );
1188
- lines.push(SEP);
1189
-
1190
- // ── Row 3: Security ──────────────────────────────────────────
1191
- const cveStatus = security.totalCves === 0
1192
- ? (security.status === 'NONE' ? `${x.slate}not scanned${x.reset}` : `${x.green}✔ clean${x.reset}`)
1193
- : `${x.coral}${security.cvesFixed}/${security.totalCves} fixed${x.reset}`;
1194
-
1195
- lines.push(
1196
- `${x.purple}🛡️ SECURITY${x.reset} ` +
1197
- `${sec.col}${sec.label}${x.reset} ${DIV} ` +
1198
- `CVE ${cveStatus}`
1199
- );
1200
- lines.push(SEP);
1201
-
1202
- // ── Row 4: Memory & Tests ─────────────────────────────────────
1203
- const testCol = tests.testFiles > 0 ? x.green : x.slate;
1204
- const memCol = system.memoryMB > 200 ? x.orange : x.sky;
1205
-
1206
- // Auto-memory files display
1207
- const memFileCol = autoMem.count > 0 ? x.purple : x.slate;
1208
- const memFileStr = autoMem.count > 0
1209
- ? `${memFileCol}${x.bold}${autoMem.count}${x.reset}${x.slate} memories${x.reset}`
1210
- : `${x.slate}no memories${x.reset}`;
1211
- const memTypeOrder = ['handoff', 'user', 'feedback', 'project', 'reference'];
1212
- const typeColors = { user: x.sky, feedback: x.gold, project: x.teal, reference: x.violet, handoff: x.coral };
1213
- const typeParts = memTypeOrder
1214
- .filter(t => autoMem.byType[t] > 0)
1215
- .map(t => `${typeColors[t] || x.slate}${autoMem.byType[t]}${t.slice(0,1)}${x.reset}`);
1216
- const kgStr = typeParts.length > 0 ? ` ${typeParts.join(' ')}` : '';
1217
-
1218
- // Total memory size (palace + agentdb)
1219
- const totalSizeKB = agentdb.dbSizeKB + palace.palaceSizeKB;
1220
- const sizeDisp = totalSizeKB >= 1024
1221
- ? `${(totalSizeKB / 1024).toFixed(1)} MB` : `${totalSizeKB} KB`;
1222
-
1223
- // HNSW tag only meaningful if vectors exist
1224
- const vecTotal = agentdb.vectorCount;
1225
- const hnswTag = agentdb.hasHnsw ? ` ${x.green}⚡ HNSW${x.reset}` : '';
1226
-
1227
- const chips = [];
1228
- if (integration.mcpServers.total > 0) {
1229
- const mc = integration.mcpServers.enabled === integration.mcpServers.total ? x.green
1230
- : integration.mcpServers.enabled > 0 ? x.gold : x.coral;
1231
- chips.push(`${mc}MCP ${integration.mcpServers.enabled}/${integration.mcpServers.total}${x.reset}`);
1224
+ const loopState = getLoopStatus();
1225
+ let loopStr;
1226
+ if (loopState.count > 0) {
1227
+ const parts = loopState.loops.slice(0, 2).map(l => {
1228
+ const status = l.status === 'hil:pending'
1229
+ ? `${x.coral}⏳ HIL${x.reset}`
1230
+ : `${x.green}⟳${x.reset}`;
1231
+ const tag = l.type === 'tillend'
1232
+ ? `${x.bold}${l.cmd}${x.reset}${x.slate} run ${l.rep}${x.reset}`
1233
+ : `${x.bold}${l.cmd}${x.reset}${x.slate} ${l.rep}/${l.max}${x.reset}`;
1234
+ return `${status} ${tag}`;
1235
+ });
1236
+ loopStr = `${x.gold}🔄${x.reset} ${parts.join(`${x.slate} · ${x.reset}`)}`;
1237
+ if (loopState.count > 2) loopStr += `${x.slate} +${loopState.count - 2} more${x.reset}`;
1238
+ } else {
1239
+ loopStr = `${x.slate}🔄 no active loops${x.reset}`;
1232
1240
  }
1233
- if (integration.hasDatabase) chips.push(`${x.green}DB ✔${x.reset}`);
1234
- if (integration.hasApi) chips.push(`${x.green}API ✔${x.reset}`);
1235
- const integStr = chips.length ? chips.join(' ') : `${x.slate}none${x.reset}`;
1236
-
1237
- lines.push(
1238
- `${x.teal}🗄️ MEMORY${x.reset} ` +
1239
- `${memFileStr}${kgStr}${hnswTag} ${DIV} ` +
1240
- `${x.white}${sizeDisp}${x.reset} ${DIV} ` +
1241
- `${testCol}🧪 ${tests.testFiles} test files${x.reset} ${DIV} ` +
1242
- integStr
1243
- );
1241
+
1242
+ lines.push(`${x.purple}🤖 AGENT${x.reset} ${agentStr} ${DIV} ${loopStr}`);
1244
1243
  lines.push(SEP);
1245
1244
 
1246
- // ── Row 5: Context budget ─────────────────────────────────────
1247
- // SI budget (Task 23 monitor)
1248
- let siStr;
1249
- if (si) {
1250
- const siCol = si.pct > 100 ? x.coral : si.pct > 80 ? x.gold : x.green;
1251
- siStr = `${siCol}📄 SI ${si.pct}% budget${x.reset} ${x.dim}(${si.len}/${si.limit} chars)${x.reset}`;
1245
+ // ── Row 2: Graph freshness + Pending HIL ─────────────────────
1246
+ const gf = getGraphifyStats();
1247
+ const freshness = getGraphFreshness();
1248
+ let graphStr;
1249
+ if (gf.exists) {
1250
+ const nodesFmt = gf.nodes >= 1000 ? `${(gf.nodes / 1000).toFixed(0)}k` : `${gf.nodes}`;
1251
+ const freshTag = freshness.fresh
1252
+ ? `${x.green}● fresh${x.reset}`
1253
+ : freshness.stale
1254
+ ? `${x.coral}● ${freshness.commitsBehind} commits stale${x.reset}`
1255
+ : `${x.gold}● ${freshness.commitsBehind} behind${x.reset}`;
1256
+ graphStr = `${x.sky}🔗 ${x.bold}${nodesFmt}${x.reset}${x.slate} nodes${x.reset} ${freshTag}`;
1252
1257
  } else {
1253
- siStr = `${x.slate}📄 no shared instructions${x.reset}`;
1258
+ graphStr = `${x.slate}🔗 no graph${x.reset}`;
1254
1259
  }
1255
1260
 
1256
- let monthStr = '';
1257
- if (tokens) {
1258
- const mFmt = tokens.monthCost >= 100 ? `$${tokens.monthCost.toFixed(2)}` : tokens.monthCost >= 1 ? `$${tokens.monthCost.toFixed(3)}` : `$${tokens.monthCost.toFixed(4)}`;
1259
- monthStr = ` ${DIV} ${x.gold}📈 ${x.bold}${mFmt}${x.reset}${x.slate} month · ${tokens.monthCalls} calls${x.reset}`;
1260
- }
1261
- lines.push(
1262
- `${x.slate}📋 CONTEXT${x.reset} ` +
1263
- `${siStr} ${DIV} ` +
1264
- `${x.dim}💾 ${system.memoryMB} MB RAM${x.reset}` +
1265
- monthStr
1266
- );
1261
+ const hil = getHILPending();
1262
+ const hilStr = hil.pending > 0
1263
+ ? `${x.coral} ${x.bold}${hil.pending}${x.reset}${x.coral} HIL pending${x.reset}`
1264
+ : `${x.slate} no pending HIL${x.reset}`;
1265
+
1266
+ lines.push(`${x.teal}🧠 CONTEXT${x.reset} ${graphStr} ${DIV} ${hilStr}`);
1267
1267
 
1268
1268
  return lines.join('\n');
1269
1269
  }
@@ -1 +1 @@
1
- {"version":3,"file":"statusline-generator.d.ts","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA8lCrE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA8BnE"}
1
+ {"version":3,"file":"statusline-generator.d.ts","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAonCrE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA8BnE"}
@@ -828,6 +828,105 @@ function getTriggerStats() {
828
828
  } catch { return { triggers: 0, agents: 0 }; }
829
829
  }
830
830
 
831
+ // Graph freshness — compare last build time vs commits since
832
+ function getGraphFreshness() {
833
+ const lockPath = path.join(CWD, '.monomind', 'graph', '.rebuild-lock');
834
+ const dbPath = path.join(CWD, '.monomind', 'monograph.db');
835
+ let buildMs = 0;
836
+ try {
837
+ const lockStat = safeStat(lockPath);
838
+ const dbStat = safeStat(dbPath);
839
+ buildMs = Math.max(lockStat?.mtimeMs || 0, dbStat?.mtimeMs || 0);
840
+ } catch { /* ignore */ }
841
+ if (!buildMs) return { commitsBehind: -1, stale: true, fresh: false };
842
+ const buildIso = new Date(buildMs).toISOString();
843
+ const out = safeExec(\`git rev-list --count --since='\${buildIso}' HEAD 2>/dev/null\`, 1500);
844
+ const commitsBehind = parseInt(out, 10) || 0;
845
+ return { commitsBehind, stale: commitsBehind > 5, fresh: commitsBehind === 0 };
846
+ }
847
+
848
+ // Active loops — scan .monomind/loops/*.json, skip stale (>6h)
849
+ function getLoopStatus() {
850
+ const loopsDir = path.join(CWD, '.monomind', 'loops');
851
+ if (!fs.existsSync(loopsDir)) return { count: 0, loops: [] };
852
+ const STALE_MS = 6 * 60 * 60 * 1000;
853
+ const now = Date.now();
854
+ const loops = [];
855
+ try {
856
+ const files = fs.readdirSync(loopsDir).filter(f =>
857
+ f.endsWith('.json') && !f.includes('-hil') && !f.endsWith('.stop'));
858
+ for (const f of files) {
859
+ const d = readJSON(path.join(loopsDir, f));
860
+ if (!d || !d.command) continue;
861
+ const last = d.lastRunAt || d.nextRunAt || d.startedAt || 0;
862
+ if (last && (now - last) > STALE_MS) continue;
863
+ loops.push({
864
+ cmd: String(d.command).replace(/^\\//,''),
865
+ type: d.type || 'repeat',
866
+ rep: d.currentRep || 0,
867
+ max: d.maxReps || 0,
868
+ status: d.status || 'running',
869
+ });
870
+ }
871
+ } catch { /* ignore */ }
872
+ return { count: loops.length, loops };
873
+ }
874
+
875
+ // HIL pending — count <id>-hil.md files with no human response yet
876
+ function getHILPending() {
877
+ const loopsDir = path.join(CWD, '.monomind', 'loops');
878
+ if (!fs.existsSync(loopsDir)) return { pending: 0 };
879
+ let pending = 0;
880
+ try {
881
+ const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('-hil.md'));
882
+ for (const f of files) {
883
+ try {
884
+ const txt = fs.readFileSync(path.join(loopsDir, f), 'utf-8');
885
+ const answered = /^[ \\t]*>[ \\t]+\\S/m.test(txt);
886
+ if (!answered) pending++;
887
+ } catch { /* ignore */ }
888
+ }
889
+ } catch { /* ignore */ }
890
+ return { pending };
891
+ }
892
+
893
+ // Monograph knowledge graph stats
894
+ // Sources, in priority order:
895
+ // 1. .monomind/graph/stats.json — explicit cached stats
896
+ // 2. .monomind/monograph.db — live SQLite (read counts via sqlite3)
897
+ // 3. .monomind/graph/graph.json — legacy JSON dump
898
+ function getGraphifyStats() {
899
+ const statsPath = path.join(CWD, '.monomind', 'graph', 'stats.json');
900
+ const dbPath = path.join(CWD, '.monomind', 'monograph.db');
901
+ const graphPath = path.join(CWD, '.monomind', 'graph', 'graph.json');
902
+
903
+ try {
904
+ const s = readJSON(statsPath);
905
+ if (s && s.nodes !== undefined) return { nodes: s.nodes, edges: s.edges || 0, exists: true };
906
+ } catch { /* ignore */ }
907
+
908
+ try {
909
+ if (fs.existsSync(dbPath)) {
910
+ const out = safeExec(\`sqlite3 "\${dbPath}" "SELECT (SELECT COUNT(*) FROM nodes), (SELECT COUNT(*) FROM edges);"\`, 1000);
911
+ if (out) {
912
+ const [n, e] = out.split('|').map(v => parseInt(v, 10) || 0);
913
+ if (n > 0) return { nodes: n, edges: e, exists: true };
914
+ }
915
+ }
916
+ } catch { /* ignore */ }
917
+
918
+ try {
919
+ const stat = safeStat(graphPath);
920
+ if (stat && stat.size < 10 * 1024 * 1024) {
921
+ const g = JSON.parse(fs.readFileSync(graphPath, 'utf-8'));
922
+ const nodes = Array.isArray(g.nodes) ? g.nodes.length : 0;
923
+ const edges = (Array.isArray(g.edges) ? g.edges : (Array.isArray(g.links) ? g.links : [])).length;
924
+ return { nodes, edges, exists: true };
925
+ }
926
+ } catch { /* ignore */ }
927
+ return { nodes: 0, edges: 0, exists: false };
928
+ }
929
+
831
930
  function getSIBudget() {
832
931
  const SI_LIMIT = 1500;
833
932
  const siPath = path.join(CWD, '.agents', 'shared_instructions.md');
@@ -942,137 +1041,60 @@ function generateDashboard() {
942
1041
  lines.push(hdr);
943
1042
  lines.push(SEP);
944
1043
 
945
- // ── Row 1: Intelligence & Learning ───────────────────────────
946
- const intellCol = pctColor(system.intelligencePct);
947
- const intellBar = blockBar(system.intelligencePct, 100, 6);
948
-
949
- // Knowledge (Task 28)
950
- const knowStr = knowledge.chunks > 0
951
- ? \`\${x.teal}📚 \${x.bold}\${knowledge.chunks}\${x.reset}\${x.slate} chunks\${x.reset}\`
952
- : \`\${x.slate}📚 no chunks\${x.reset}\`;
953
-
954
- // Skills (Task 45)
955
- const skillStr = knowledge.skills > 0
956
- ? \` \${x.mint}✦ \${knowledge.skills} skills\${x.reset}\`
957
- : '';
958
-
959
- // Patterns
960
- const patStr = progress.patternsLearned > 0
961
- ? \`\${x.gold}\${progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : progress.patternsLearned} patterns\${x.reset}\`
962
- : \`\${x.slate}0 patterns\${x.reset}\`;
963
-
964
- lines.push(
965
- \`\${x.purple}💡 INTEL\${x.reset} \` +
966
- \`\${intellCol}\${intellBar} \${x.bold}\${system.intelligencePct}%\${x.reset} \${DIV} \` +
967
- \`\${knowStr}\${skillStr} \${DIV} \` +
968
- patStr
969
- );
970
- lines.push(SEP);
971
-
972
- // ── Row 2: Agents & Triggers ──────────────────────────────────
973
- const agentCol = swarm.activeAgents > 0 ? x.green : x.slate;
974
- const hookCol = hooks.enabled > 0 ? x.mint : x.slate;
975
-
976
- // Triggers (Task 32)
977
- const trigStr = triggers.triggers > 0
978
- ? \`\${x.mint}🎯 \${x.bold}\${triggers.triggers}\${x.reset}\${x.slate} triggers · \${triggers.agents} agents\${x.reset}\`
979
- : \`\${x.slate}🎯 no triggers\${x.reset}\`;
980
-
981
- // Active agent badge
982
- let agentBadge;
1044
+ // ── Row 1: Active agent + Loop status ────────────────────────
1045
+ let agentStr;
983
1046
  if (activeAgent) {
984
1047
  const col = activeAgent.activated ? x.green : x.sky;
985
- const mark = activeAgent.activated ? '● ACTIVE' : '→ ROUTED';
1048
+ const mark = activeAgent.activated ? \`\${col}\${x.bold}● ACTIVE\${x.reset} \` : '';
986
1049
  const conf = activeAgent.activated ? '' : \` \${x.slate}\${(activeAgent.confidence * 100).toFixed(0)}%\${x.reset}\`;
987
- const cat = activeAgent.category ? \` \${x.slate}[\${activeAgent.category}]\${x.reset}\` : '';
988
- agentBadge = \`\${col}\${x.bold}\${mark}\${x.reset} \${col}👤 \${x.bold}\${activeAgent.name}\${x.reset}\${cat}\${conf}\`;
1050
+ agentStr = \`\${mark}\${col}👤 \${x.bold}\${activeAgent.name}\${x.reset}\${conf}\`;
989
1051
  } else {
990
- agentBadge = \`\${x.slate}👤 no agent routed\${x.reset}\`;
1052
+ agentStr = \`\${x.slate}👤 no agent routed\${x.reset}\`;
991
1053
  }
992
1054
 
993
- lines.push(
994
- \`\${x.gold}🐝 SWARM\${x.reset} \` +
995
- \`\${agentCol}\${x.bold}\${swarm.activeAgents}\${x.reset}\${x.slate}/\${x.reset}\${x.white}\${swarm.maxAgents}\${x.reset} agents \` +
996
- \`\${hookCol}⚡ \${hooks.enabled}/\${hooks.total} hooks\${x.reset} \${DIV} \` +
997
- \`\${trigStr} \${DIV} \` +
998
- agentBadge
999
- );
1000
- lines.push(SEP);
1001
-
1002
- // ── Row 3: Architecture & Security ───────────────────────────
1003
- const adrCol = adrs.count > 0
1004
- ? (adrs.implemented >= adrs.count ? x.green : x.gold)
1005
- : x.slate;
1006
- const adrStr = adrs.count > 0
1007
- ? \`\${adrCol}\${x.bold}\${adrs.implemented}\${x.reset}\${x.slate}/\${x.reset}\${x.white}\${adrs.count}\${x.reset} ADRs\`
1008
- : \`\${x.slate}no ADRs\${x.reset}\`;
1009
-
1010
- const dddCol = pctColor(progress.dddProgress);
1011
- const dddBar = blockBar(progress.dddProgress, 100, 5);
1012
-
1013
- const cveStatus = security.totalCves === 0
1014
- ? (security.status === 'NONE' ? \`\${x.slate}not scanned\${x.reset}\` : \`\${x.green}✔ clean\${x.reset}\`)
1015
- : \`\${x.coral}\${security.cvesFixed}/\${security.totalCves} fixed\${x.reset}\`;
1016
-
1017
- lines.push(
1018
- \`\${x.purple}🧩 ARCH\${x.reset} \` +
1019
- \`\${adrStr} \${DIV} \` +
1020
- \`DDD \${dddBar} \${dddCol}\${x.bold}\${progress.dddProgress}%\${x.reset} \${DIV} \` +
1021
- \`🛡️ \${sec.col}\${sec.label}\${x.reset} \${DIV} \` +
1022
- \`CVE \${cveStatus}\`
1023
- );
1024
- lines.push(SEP);
1025
-
1026
- // ── Row 4: Memory & Tests ─────────────────────────────────────
1027
- const vecCol = agentdb.vectorCount > 0 ? x.green : x.slate;
1028
- const hnswTag = agentdb.hasHnsw && agentdb.vectorCount > 0 ? \` \${x.green}⚡ HNSW\${x.reset}\` : '';
1029
- const sizeDisp = agentdb.dbSizeKB >= 1024
1030
- ? \`\${(agentdb.dbSizeKB / 1024).toFixed(1)} MB\` : \`\${agentdb.dbSizeKB} KB\`;
1031
- const testCol = tests.testFiles > 0 ? x.green : x.slate;
1032
- const memCol = system.memoryMB > 200 ? x.orange : x.sky;
1033
-
1034
- const chips = [];
1035
- if (integration.mcpServers.total > 0) {
1036
- const mc = integration.mcpServers.enabled === integration.mcpServers.total ? x.green
1037
- : integration.mcpServers.enabled > 0 ? x.gold : x.coral;
1038
- chips.push(\`\${mc}MCP \${integration.mcpServers.enabled}/\${integration.mcpServers.total}\${x.reset}\`);
1055
+ const loopState = getLoopStatus();
1056
+ let loopStr;
1057
+ if (loopState.count > 0) {
1058
+ const parts = loopState.loops.slice(0, 2).map(l => {
1059
+ const status = l.status === 'hil:pending'
1060
+ ? \`\${x.coral}⏳ HIL\${x.reset}\`
1061
+ : \`\${x.green}⟳\${x.reset}\`;
1062
+ const tag = l.type === 'tillend'
1063
+ ? \`\${x.bold}\${l.cmd}\${x.reset}\${x.slate} run \${l.rep}\${x.reset}\`
1064
+ : \`\${x.bold}\${l.cmd}\${x.reset}\${x.slate} \${l.rep}/\${l.max}\${x.reset}\`;
1065
+ return \`\${status} \${tag}\`;
1066
+ });
1067
+ loopStr = \`\${x.gold}🔄\${x.reset} \${parts.join(\`\${x.slate} · \${x.reset}\`)}\`;
1068
+ if (loopState.count > 2) loopStr += \`\${x.slate} +\${loopState.count - 2} more\${x.reset}\`;
1069
+ } else {
1070
+ loopStr = \`\${x.slate}🔄 no active loops\${x.reset}\`;
1039
1071
  }
1040
- if (integration.hasDatabase) chips.push(\`\${x.green}DB ✔\${x.reset}\`);
1041
- if (integration.hasApi) chips.push(\`\${x.green}API ✔\${x.reset}\`);
1042
- const integStr = chips.length ? chips.join(' ') : \`\${x.slate}none\${x.reset}\`;
1043
-
1044
- lines.push(
1045
- \`\${x.teal}🗄️ MEMORY\${x.reset} \` +
1046
- \`\${vecCol}\${x.bold}\${agentdb.vectorCount}\${x.reset}\${x.slate} vectors\${x.reset}\${hnswTag} \${DIV} \` +
1047
- \`\${x.white}\${sizeDisp}\${x.reset} \${DIV} \` +
1048
- \`\${testCol}🧪 \${tests.testFiles} test files\${x.reset} \${DIV} \` +
1049
- integStr
1050
- );
1072
+
1073
+ lines.push(\`\${x.purple}🤖 AGENT\${x.reset} \${agentStr} \${DIV} \${loopStr}\`);
1051
1074
  lines.push(SEP);
1052
1075
 
1053
- // ── Row 5: Context budget ─────────────────────────────────────
1054
- // SI budget (Task 23 monitor)
1055
- let siStr;
1056
- if (si) {
1057
- const siCol = si.pct > 100 ? x.coral : si.pct > 80 ? x.gold : x.green;
1058
- siStr = \`\${siCol}📄 SI \${si.pct}% budget\${x.reset} \${x.dim}(\${si.len}/\${si.limit} chars)\${x.reset}\`;
1076
+ // ── Row 2: Graph freshness + Pending HIL ─────────────────────
1077
+ const gf = getGraphifyStats();
1078
+ const freshness = getGraphFreshness();
1079
+ let graphStr;
1080
+ if (gf.exists) {
1081
+ const nodesFmt = gf.nodes >= 1000 ? \`\${(gf.nodes / 1000).toFixed(0)}k\` : \`\${gf.nodes}\`;
1082
+ const freshTag = freshness.fresh
1083
+ ? \`\${x.green}● fresh\${x.reset}\`
1084
+ : freshness.stale
1085
+ ? \`\${x.coral}● \${freshness.commitsBehind} commits stale\${x.reset}\`
1086
+ : \`\${x.gold}● \${freshness.commitsBehind} behind\${x.reset}\`;
1087
+ graphStr = \`\${x.sky}🔗 \${x.bold}\${nodesFmt}\${x.reset}\${x.slate} nodes\${x.reset} \${freshTag}\`;
1059
1088
  } else {
1060
- siStr = \`\${x.slate}📄 no shared instructions\${x.reset}\`;
1089
+ graphStr = \`\${x.slate}🔗 no graph\${x.reset}\`;
1061
1090
  }
1062
1091
 
1063
- // Domains
1064
- const domCol = progress.domainsCompleted >= 4 ? x.green
1065
- : progress.domainsCompleted >= 2 ? x.gold
1066
- : progress.domainsCompleted >= 1 ? x.orange
1067
- : x.slate;
1068
- const domBar = blockBar(progress.domainsCompleted, progress.totalDomains);
1069
-
1070
- lines.push(
1071
- \`\${x.slate}📋 CONTEXT\${x.reset} \` +
1072
- \`\${siStr} \${DIV} \` +
1073
- \`\${x.teal}🏗 \${domBar} \${domCol}\${x.bold}\${progress.domainsCompleted}\${x.reset}\${x.slate}/\${x.reset}\${x.white}\${progress.totalDomains}\${x.reset} domains \${DIV} \` +
1074
- \`\${x.dim}💾 \${system.memoryMB} MB RAM\${x.reset}\`
1075
- );
1092
+ const hil = getHILPending();
1093
+ const hilStr = hil.pending > 0
1094
+ ? \`\${x.coral}✨ \${x.bold}\${hil.pending}\${x.reset}\${x.coral} HIL pending\${x.reset}\`
1095
+ : \`\${x.slate}✨ no pending HIL\${x.reset}\`;
1096
+
1097
+ lines.push(\`\${x.teal}🧠 CONTEXT\${x.reset} \${graphStr} \${DIV} \${hilStr}\`);
1076
1098
 
1077
1099
  return lines.join('\\n');
1078
1100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"statusline-generator.js","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAoB;IAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;eAuBM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAokCvB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAoB;IACzD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QAChC,OAAO,sCAAsC,CAAC;IAChD,CAAC;IAED,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CAwBR,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"statusline-generator.js","sourceRoot":"","sources":["../../../src/init/statusline-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAoB;IAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;eAuBM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0lCvB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAoB;IACzD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QAChC,OAAO,sCAAsC,CAAC;IAChD,CAAC;IAED,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CAwBR,CAAC;AACF,CAAC"}