@monoes/monomindcli 2.7.4 → 2.7.5

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.
Files changed (89) hide show
  1. package/bin/cli.js +17 -1
  2. package/dist/src/commands/doctor-project-checks.d.ts +3 -0
  3. package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
  4. package/dist/src/commands/doctor-project-checks.js +81 -8
  5. package/dist/src/commands/doctor-project-checks.js.map +1 -1
  6. package/dist/src/commands/doctor.d.ts.map +1 -1
  7. package/dist/src/commands/doctor.js +16 -2
  8. package/dist/src/commands/doctor.js.map +1 -1
  9. package/dist/src/commands/memory-crud.d.ts.map +1 -1
  10. package/dist/src/commands/memory-crud.js +15 -5
  11. package/dist/src/commands/memory-crud.js.map +1 -1
  12. package/dist/src/commands/security-misc.d.ts.map +1 -1
  13. package/dist/src/commands/security-misc.js +7 -2
  14. package/dist/src/commands/security-misc.js.map +1 -1
  15. package/dist/src/init/helpers-generator.d.ts.map +1 -1
  16. package/dist/src/init/helpers-generator.js +5 -0
  17. package/dist/src/init/helpers-generator.js.map +1 -1
  18. package/dist/src/mcp-client.d.ts.map +1 -1
  19. package/dist/src/mcp-client.js +0 -2
  20. package/dist/src/mcp-client.js.map +1 -1
  21. package/dist/src/mcp-tools/agent-tools.js +1 -1
  22. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  23. package/dist/src/mcp-tools/embeddings-tools.d.ts +2 -0
  24. package/dist/src/mcp-tools/embeddings-tools.d.ts.map +1 -1
  25. package/dist/src/mcp-tools/embeddings-tools.js +11 -1
  26. package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
  27. package/dist/src/mcp-tools/guidance-tools.js +17 -17
  28. package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
  29. package/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -0
  30. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
  31. package/dist/src/mcp-tools/hive-mind-tools.js +14 -2
  32. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
  33. package/dist/src/mcp-tools/index.d.ts +0 -1
  34. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  35. package/dist/src/mcp-tools/index.js +0 -1
  36. package/dist/src/mcp-tools/index.js.map +1 -1
  37. package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
  38. package/dist/src/mcp-tools/monograph-tools.js +1 -2
  39. package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
  40. package/dist/src/mcp-tools/neural-tools.js +1 -1
  41. package/dist/src/mcp-tools/neural-tools.js.map +1 -1
  42. package/dist/src/mcp-tools/swarm-tools.js +2 -2
  43. package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
  44. package/dist/src/mcp-tools/transfer-tools.d.ts +2 -0
  45. package/dist/src/mcp-tools/transfer-tools.d.ts.map +1 -1
  46. package/dist/src/mcp-tools/transfer-tools.js +11 -1
  47. package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
  48. package/dist/src/ui/dashboard.html +44 -618
  49. package/dist/src/ui/server.mjs +0 -275
  50. package/dist/src/utils/cosine-similarity.d.ts +2 -2
  51. package/dist/src/utils/cosine-similarity.js +2 -2
  52. package/dist/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +1 -1
  54. package/dist/src/mcp-tools/coherence/causal-infer.d.ts +0 -15
  55. package/dist/src/mcp-tools/coherence/causal-infer.d.ts.map +0 -1
  56. package/dist/src/mcp-tools/coherence/causal-infer.js +0 -333
  57. package/dist/src/mcp-tools/coherence/causal-infer.js.map +0 -1
  58. package/dist/src/mcp-tools/coherence/coherence-check.d.ts +0 -15
  59. package/dist/src/mcp-tools/coherence/coherence-check.d.ts.map +0 -1
  60. package/dist/src/mcp-tools/coherence/coherence-check.js +0 -188
  61. package/dist/src/mcp-tools/coherence/coherence-check.js.map +0 -1
  62. package/dist/src/mcp-tools/coherence/consensus-verify.d.ts +0 -14
  63. package/dist/src/mcp-tools/coherence/consensus-verify.d.ts.map +0 -1
  64. package/dist/src/mcp-tools/coherence/consensus-verify.js +0 -305
  65. package/dist/src/mcp-tools/coherence/consensus-verify.js.map +0 -1
  66. package/dist/src/mcp-tools/coherence/memory-gate.d.ts +0 -16
  67. package/dist/src/mcp-tools/coherence/memory-gate.d.ts.map +0 -1
  68. package/dist/src/mcp-tools/coherence/memory-gate.js +0 -279
  69. package/dist/src/mcp-tools/coherence/memory-gate.js.map +0 -1
  70. package/dist/src/mcp-tools/coherence/quantum-topology.d.ts +0 -15
  71. package/dist/src/mcp-tools/coherence/quantum-topology.d.ts.map +0 -1
  72. package/dist/src/mcp-tools/coherence/quantum-topology.js +0 -400
  73. package/dist/src/mcp-tools/coherence/quantum-topology.js.map +0 -1
  74. package/dist/src/mcp-tools/coherence/spectral-analyze.d.ts +0 -14
  75. package/dist/src/mcp-tools/coherence/spectral-analyze.d.ts.map +0 -1
  76. package/dist/src/mcp-tools/coherence/spectral-analyze.js +0 -312
  77. package/dist/src/mcp-tools/coherence/spectral-analyze.js.map +0 -1
  78. package/dist/src/mcp-tools/coherence/types.d.ts +0 -430
  79. package/dist/src/mcp-tools/coherence/types.d.ts.map +0 -1
  80. package/dist/src/mcp-tools/coherence/types.js +0 -144
  81. package/dist/src/mcp-tools/coherence/types.js.map +0 -1
  82. package/dist/src/mcp-tools/coherence-tools.d.ts +0 -9
  83. package/dist/src/mcp-tools/coherence-tools.d.ts.map +0 -1
  84. package/dist/src/mcp-tools/coherence-tools.js +0 -25
  85. package/dist/src/mcp-tools/coherence-tools.js.map +0 -1
  86. package/dist/src/mcp-tools/daa-tools.d.ts +0 -13
  87. package/dist/src/mcp-tools/daa-tools.d.ts.map +0 -1
  88. package/dist/src/mcp-tools/daa-tools.js +0 -553
  89. package/dist/src/mcp-tools/daa-tools.js.map +0 -1
@@ -234,58 +234,6 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
234
234
  .chunk-excerpt { font-size: 12px; color: var(--text-hi); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
235
235
  .chunk-footer { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
236
236
  .chunk-ns { font-size: 10px; padding: 1px 6px; border-radius: 8px; background: var(--surface-hi); color: var(--text-lo); }
237
- /* injection analytics */
238
- .sb-analytics { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
239
- .sb-metric { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; }
240
- .sb-metric-val { font-size: 20px; font-weight: 700; color: var(--text-hi); font-family: var(--mono); }
241
- .sb-metric-lbl { font-size: 10px; color: var(--text-lo); text-transform: uppercase; letter-spacing: 0.07em; }
242
- .sb-metric-sub { font-size: 10px; color: var(--text-xs); font-family: var(--mono); }
243
- .sb-sparkline { display: flex; align-items: flex-end; gap: 2px; height: 32px; }
244
- .sb-spark-bar { min-width: 4px; flex: 1; border-radius: 2px 2px 0 0; transition: height 0.2s; }
245
- .sb-spark-bar.hit { background: oklch(72% 0.18 145); }
246
- .sb-spark-bar.miss { background: oklch(55% 0.08 60 / 0.4); }
247
- .sb-method-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin-top: 4px; }
248
- .sb-method-seg { transition: width 0.3s; }
249
- .sb-score-dist { display: flex; align-items: flex-end; gap: 2px; height: 24px; }
250
- .sb-score-bar { flex: 1; border-radius: 2px 2px 0 0; background: oklch(72% 0.18 75); min-width: 12px; }
251
- /* document cards */
252
- .sb-doc-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-bottom: 6px; display: flex; align-items: center; gap: 10px; }
253
- .sb-doc-card:hover { border-color: oklch(72% 0.18 75 / 0.5); }
254
- .sb-config-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; margin-bottom: 12px; }
255
- .sb-config-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
256
- .sb-config-row:last-child { margin-bottom: 0; }
257
- .sb-config-label { font-size: 11px; color: var(--text-lo); min-width: 120px; }
258
- .sb-config-input { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 3px 8px; font-size: 12px; color: var(--text-hi); font-family: var(--mono); width: 80px; }
259
- .sb-config-hint { font-size: 10px; color: var(--text-xs); flex: 1; }
260
- .sb-tune-badge { display: inline-block; font-size: 10px; padding: 2px 8px; border-radius: 10px; background: oklch(72% 0.18 145 / 0.12); color: oklch(72% 0.18 145); cursor: pointer; }
261
- .sb-ingest-feed { max-height: 240px; overflow-y: auto; font-size: 11px; }
262
- .sb-ingest-item { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-bottom: 1px solid var(--border); animation: sb-fade-in 0.3s ease; }
263
- .sb-ingest-item.injection { background: oklch(72% 0.18 145 / 0.04); }
264
- @keyframes sb-fade-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
265
- .sb-ingest-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
266
- .sb-ingest-dot.ingest { background: oklch(72% 0.18 200); }
267
- .sb-ingest-dot.injection { background: oklch(72% 0.18 145); }
268
- .sb-ingest-dot.miss { background: oklch(72% 0.18 25); }
269
- .sb-ingest-time { font-size: 9px; color: var(--text-xs); font-family: var(--mono); min-width: 48px; }
270
- .sb-live-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 9px; color: oklch(72% 0.18 145); text-transform: uppercase; letter-spacing: 0.07em; }
271
- .sb-live-dot { width: 5px; height: 5px; border-radius: 50%; background: oklch(72% 0.18 145); animation: sb-pulse 1.5s infinite; }
272
- @keyframes sb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
273
- .sb-kg-triplet { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--border); font-size: 12px; }
274
- .sb-kg-triplet:hover { background: oklch(72% 0.02 200 / 0.05); }
275
- .sb-kg-entity { padding: 2px 8px; border-radius: 10px; font-family: var(--mono); font-size: 11px; cursor: pointer; white-space: nowrap; }
276
- .sb-kg-entity.subject { background: oklch(72% 0.18 200 / 0.12); color: oklch(72% 0.18 200); }
277
- .sb-kg-entity.object { background: oklch(72% 0.18 145 / 0.12); color: oklch(72% 0.18 145); }
278
- .sb-kg-rel { font-size: 10px; color: var(--text-lo); font-style: italic; min-width: 60px; text-align: center; }
279
- .sb-kg-tag { display: inline-block; padding: 1px 6px; border-radius: 8px; font-size: 10px; cursor: pointer; background: var(--surface); border: 1px solid var(--border); color: var(--text-lo); }
280
- .sb-kg-tag:hover { border-color: var(--accent); color: var(--accent); }
281
- .sb-doc-icon { font-size: 18px; flex-shrink: 0; width: 28px; text-align: center; }
282
- .sb-doc-info { flex: 1; min-width: 0; }
283
- .sb-doc-name { font-size: 12px; font-weight: 600; color: var(--text-hi); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
284
- .sb-doc-path { font-size: 10px; color: var(--text-lo); font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
285
- .sb-doc-meta { display: flex; gap: 8px; margin-top: 3px; }
286
- .sb-doc-badge { font-size: 10px; padding: 1px 6px; border-radius: 8px; background: var(--surface-hi); color: var(--text-lo); }
287
- .sb-doc-badge.global { background: oklch(72% 0.12 200 / 0.15); color: oklch(72% 0.12 200); }
288
- .sb-doc-badge.missing { background: oklch(60% 0.18 25 / 0.15); color: oklch(60% 0.18 25); }
289
237
  /* chunk edit modal */
290
238
  #chunk-modal { display: none; position: fixed; inset: 0; z-index: 3000; background: oklch(0% 0 0 / 0.65); align-items: center; justify-content: center; }
291
239
  #chunk-modal.open { display: flex; }
@@ -1955,7 +1903,6 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
1955
1903
  <canvas id="tok-chart" height="100" style="width:100%;display:block"></canvas>
1956
1904
  </div>
1957
1905
  <div id="tok-table" style="margin-top:18px"></div>
1958
- <div id="tok-usage-breakdown" style="margin-top:18px"></div>
1959
1906
  </div>
1960
1907
  </div>
1961
1908
 
@@ -1966,8 +1913,12 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
1966
1913
  <div class="pg-sub">Knowledge palace — stored facts, graph, identity</div>
1967
1914
  <div class="mem-tab-bar" style="display:flex;gap:4px;margin-bottom:14px;border-bottom:1px solid var(--border);padding-bottom:6px">
1968
1915
  <button class="odt-btn active" data-memtab="memories" onclick="switchMemTab('memories')">Memories</button>
1916
+ <button class="odt-btn" data-memtab="routing" onclick="switchMemTab('routing')">Routing</button>
1917
+ <button class="odt-btn" data-memtab="usage" onclick="switchMemTab('usage')">Usage</button>
1969
1918
  <button class="odt-btn" data-memtab="adrs" onclick="switchMemTab('adrs')">ADRs</button>
1919
+ <button class="odt-btn" data-memtab="swarm" onclick="switchMemTab('swarm')">Swarm</button>
1970
1920
  <button class="odt-btn" data-memtab="chunks" onclick="switchMemTab('chunks')">Chunks</button>
1921
+ <button class="odt-btn" data-memtab="agent-graph" onclick="switchMemTab('agent-graph')">Agent Graph</button>
1971
1922
  </div>
1972
1923
  <div id="mem-tab-memories">
1973
1924
  <div class="mem-split" id="mem-split">
@@ -1980,52 +1931,28 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
1980
1931
  </div>
1981
1932
  <div style="margin-top:10px"><button class="btn" onclick="openNewMemModal()">+ New Memory</button></div>
1982
1933
  </div>
1934
+ <div id="mem-tab-routing" style="display:none"><div class="loading-txt">Loading…</div></div>
1935
+ <div id="mem-tab-usage" style="display:none"><div class="loading-txt">Loading…</div></div>
1983
1936
  <div id="mem-tab-adrs" style="display:none">
1984
1937
  <div class="filter-bar" style="margin-bottom:10px">
1985
1938
  <input class="filter-input" id="adr-filter" type="text" placeholder="Filter ADRs…" oninput="filterADRs(this.value)">
1986
1939
  </div>
1987
- <div style="display:flex;gap:0;min-height:340px">
1988
- <div id="adr-content" style="width:280px;flex-shrink:0;overflow-y:auto;border-right:1px solid var(--border);padding-right:8px"><div class="loading-txt">Loading…</div></div>
1989
- <div id="adr-viewer" style="flex:1;padding:0 14px;overflow-y:auto">
1990
- <div style="color:var(--text-lo);font-size:13px;padding:20px 0">Select an ADR to view</div>
1940
+ <div id="adr-content"><div class="loading-txt">Loading…</div></div>
1941
+ </div>
1942
+ <div id="mem-tab-swarm" style="display:none">
1943
+ <div style="display:flex;height:380px;gap:0">
1944
+ <div id="swarm-run-list" style="width:200px;flex-shrink:0;border-right:1px solid var(--border);overflow-y:auto;padding:6px"></div>
1945
+ <div id="swarm-run-detail" style="flex:1;padding:14px;overflow-y:auto">
1946
+ <div style="color:var(--text-lo);font-size:13px">Select a swarm run</div>
1991
1947
  </div>
1992
1948
  </div>
1949
+ <button class="btn" style="margin-top:10px;color:var(--red);border-color:var(--red)" onclick="cleanSwarmData()">&#x232B; Clean Data</button>
1993
1950
  </div>
1994
1951
  <div id="mem-tab-chunks" style="display:none">
1995
- <div id="sb-analytics" class="sb-analytics" style="display:none">
1996
- <div class="sb-metric">
1997
- <div class="sb-metric-val" id="sb-hit-rate">—</div>
1998
- <div class="sb-metric-lbl">Hit Rate</div>
1999
- <div class="sb-metric-sub" id="sb-hit-detail"></div>
2000
- </div>
2001
- <div class="sb-metric">
2002
- <div class="sb-metric-val" id="sb-method-val">—</div>
2003
- <div class="sb-metric-lbl">Method</div>
2004
- <div class="sb-method-bar" id="sb-method-bar"></div>
2005
- <div class="sb-metric-sub" id="sb-method-detail"></div>
2006
- </div>
2007
- <div class="sb-metric">
2008
- <div class="sb-metric-val" id="sb-avg-score">—</div>
2009
- <div class="sb-metric-lbl">Avg Relevance</div>
2010
- <div class="sb-score-dist" id="sb-score-dist"></div>
2011
- </div>
2012
- <div class="sb-metric">
2013
- <div class="sb-metric-lbl" style="margin-bottom:2px">Injections (24h)</div>
2014
- <div class="sb-sparkline" id="sb-sparkline"></div>
2015
- <div class="sb-metric-sub" id="sb-spark-detail"></div>
2016
- </div>
2017
- <div class="sb-metric" id="sb-overrides-card" style="display:none">
2018
- <div class="sb-metric-val" id="sb-overrides-val">0</div>
2019
- <div class="sb-metric-lbl">Misroutes</div>
2020
- <div class="sb-metric-sub" id="sb-overrides-detail"></div>
2021
- </div>
2022
- </div>
2023
1952
  <div class="chunk-stats-bar" id="chunk-stats-bar">
2024
1953
  <div class="chunk-stat"><div class="chunk-stat-val" id="chunk-count-val">—</div><div class="chunk-stat-lbl">Chunks</div></div>
2025
1954
  <div class="chunk-stat"><div class="chunk-stat-val" id="chunk-skills-val">—</div><div class="chunk-stat-lbl">Skills</div></div>
2026
1955
  <div class="chunk-stat"><div class="chunk-stat-val" id="chunk-ns-val">—</div><div class="chunk-stat-lbl">Namespaces</div></div>
2027
- <div class="chunk-stat"><div class="chunk-stat-val" id="chunk-global-val">—</div><div class="chunk-stat-lbl">Global Brain</div></div>
2028
- <div class="chunk-stat"><div class="chunk-stat-val" id="chunk-docs-val">—</div><div class="chunk-stat-lbl">Documents</div></div>
2029
1956
  <button class="btn chunk-build-btn" id="btn-build-docs" onclick="buildKnowledgeDocs()" title="Index .md/.mdx/.txt/.rst files">⊕ Build Docs</button>
2030
1957
  <span class="chunk-build-status" id="chunk-build-status"></span>
2031
1958
  </div>
@@ -2040,73 +1967,19 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
2040
1967
  <button class="btn" onclick="secondBrainSearch()" title="Semantic search (warm local model)">⌕ Search</button>
2041
1968
  </div>
2042
1969
  <div id="sb-search-results" style="display:none;margin-bottom:12px"></div>
2043
- <div style="display:flex;gap:6px;margin-bottom:8px;align-items:center">
2044
- <button class="btn" id="sb-view-chunks" onclick="switchSBView('chunks')" style="font-size:11px;font-weight:600;border-color:var(--accent);color:var(--accent)">Chunks</button>
2045
- <button class="btn" id="sb-view-docs" onclick="switchSBView('docs')" style="font-size:11px;opacity:0.6">Documents</button>
2046
- <button class="btn" id="sb-view-monitor" onclick="switchSBView('monitor')" style="font-size:11px;opacity:0.6">Live Monitor</button>
2047
- <button class="btn" id="sb-view-config" onclick="switchSBView('config')" style="font-size:11px;opacity:0.6">Config</button>
2048
- <button class="btn" id="sb-view-kg" onclick="switchSBView('kg')" style="font-size:11px;opacity:0.6">Knowledge Graph</button>
2049
- </div>
2050
- <div id="sb-view-chunks-pane">
2051
- <div class="filter-bar">
2052
- <input class="filter-input" type="text" placeholder="Filter chunks…" oninput="filterChunks(this.value)">
2053
- <button class="btn" onclick="_chunksLoaded=false;loadChunksTab()" title="Refresh">↺</button>
2054
- </div>
2055
- <div id="chunks-grid"></div>
2056
- </div>
2057
- <div id="sb-view-docs-pane" style="display:none">
2058
- <div class="filter-bar" style="margin-bottom:8px">
2059
- <input class="filter-input" id="sb-docs-filter" type="text" placeholder="Filter documents…" oninput="filterSBDocs(this.value)">
2060
- <button class="btn" onclick="_sbDocsLoaded=false;loadSBDocs()" title="Refresh">↺</button>
2061
- </div>
2062
- <div id="sb-docs-grid"></div>
2063
- </div>
2064
- <div id="sb-view-monitor-pane" style="display:none">
2065
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
2066
- <span class="sb-live-badge" id="sb-live-badge"><span class="sb-live-dot"></span> connecting…</span>
2067
- <span style="font-size:10px;color:var(--text-xs)" id="sb-monitor-count">0 events</span>
2068
- <button class="btn" style="margin-left:auto;font-size:10px;padding:2px 8px" onclick="clearIngestFeed()">Clear</button>
2069
- </div>
2070
- <div class="sb-ingest-feed" id="sb-ingest-feed">
2071
- <div style="color:var(--text-xs);font-size:11px;padding:12px;text-align:center">Waiting for ingest or injection events…</div>
2072
- </div>
2073
- </div>
2074
- <div id="sb-view-config-pane" style="display:none">
2075
- <div class="sb-config-panel">
2076
- <div style="font-size:12px;font-weight:600;color:var(--text-hi);margin-bottom:10px">Second Brain Configuration</div>
2077
- <div class="sb-config-row">
2078
- <span class="sb-config-label">Relevance Floor</span>
2079
- <input class="sb-config-input" id="sb-cfg-floor" type="number" step="0.05" min="0" max="1" value="0.35">
2080
- <span class="sb-config-hint">Minimum score for injection (0–1). Lower = more results, higher = stricter.</span>
2081
- </div>
2082
- <div class="sb-config-row">
2083
- <span class="sb-config-label">Injection Limit</span>
2084
- <input class="sb-config-input" id="sb-cfg-limit" type="number" step="1" min="1" max="20" value="3">
2085
- <span class="sb-config-hint">Max chunks injected per prompt.</span>
2086
- </div>
2087
- <div class="sb-config-row">
2088
- <button class="btn" onclick="saveSBConfig()" style="font-size:11px">Save</button>
2089
- <span id="sb-cfg-status" style="font-size:10px;color:var(--text-xs)"></span>
2090
- <span id="sb-cfg-suggestion" style="margin-left:auto"></span>
2091
- </div>
2092
- </div>
2093
- <div class="sb-config-panel" style="margin-top:10px">
2094
- <div style="font-size:12px;font-weight:600;color:var(--text-hi);margin-bottom:6px">Auto-Tune</div>
2095
- <div style="font-size:11px;color:var(--text-lo);margin-bottom:8px">Based on telemetry analysis of your injection history.</div>
2096
- <div id="sb-tune-result" style="font-size:11px;color:var(--text-xs)">Loading…</div>
2097
- </div>
1970
+ <div class="filter-bar">
1971
+ <input class="filter-input" type="text" placeholder="Filter chunks" oninput="filterChunks(this.value)">
1972
+ <button class="btn" onclick="_chunksLoaded=false;loadChunksTab()" title="Refresh">↺</button>
2098
1973
  </div>
2099
- <div id="sb-view-kg-pane" style="display:none">
2100
- <div style="display:flex;gap:10px;margin-bottom:10px;align-items:center">
2101
- <div style="font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-xs)">Entities: <span id="sb-kg-nodes" style="color:var(--text-hi)">—</span> &nbsp; Triplets: <span id="sb-kg-edges" style="color:var(--text-hi)">—</span></div>
2102
- </div>
2103
- <div class="filter-bar" style="margin-bottom:8px">
2104
- <input class="filter-input" id="sb-kg-search" type="text" placeholder="Search entities or relationships…" onkeydown="if(event.key==='Enter')loadSBKnowledgeGraph(this.value)">
2105
- <button class="btn" onclick="loadSBKnowledgeGraph(document.getElementById('sb-kg-search').value)" title="Search">⌕</button>
2106
- <button class="btn" onclick="document.getElementById('sb-kg-search').value='';loadSBKnowledgeGraph()" title="Show all">↺</button>
1974
+ <div id="chunks-grid"></div>
1975
+ </div>
1976
+ <div id="mem-tab-agent-graph" style="display:none">
1977
+ <div id="ag-summary-bar" style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--border)"></div>
1978
+ <div style="display:flex;gap:0;min-height:280px">
1979
+ <div id="ag-sess-list" style="width:190px;flex-shrink:0;overflow-y:auto;border-right:1px solid var(--border);padding-right:8px"></div>
1980
+ <div id="ag-detail" style="flex:1;padding:0 14px;overflow-y:auto">
1981
+ <div style="color:var(--text-lo);font-size:13px;padding:20px 0">Select a session</div>
2107
1982
  </div>
2108
- <div id="sb-kg-glossary" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px"></div>
2109
- <div id="sb-kg-triplets" style="max-height:360px;overflow-y:auto"><div class="loading-txt">Loading…</div></div>
2110
1983
  </div>
2111
1984
  </div>
2112
1985
  </div>
@@ -3341,59 +3214,11 @@ async function loadTokensView() {
3341
3214
  '</tbody></table></div>';
3342
3215
  } else { table.innerHTML = ''; }
3343
3216
  markLiveGlow('view-tokens');
3344
- loadTokenUsageBreakdown('today');
3345
3217
  } catch (_) {
3346
3218
  cards.innerHTML = '<div class="loading-txt" style="color:var(--text-lo)">No token data available</div>';
3347
3219
  }
3348
3220
  }
3349
3221
 
3350
- async function loadTokenUsageBreakdown(period) {
3351
- var el = document.getElementById('tok-usage-breakdown');
3352
- if (!el) return;
3353
- try {
3354
- var data = await apiFetch('/api/token-usage?period=' + enc(period || 'today') + '&dir=' + enc(DIR));
3355
- var s = data?.summary || data?.tokens?.summary || {};
3356
- var models = Array.isArray(data?.models) ? data.models : (data?.byModel || []);
3357
- var tools = Array.isArray(data?.tools) ? data.tools : (data?.topTools || []);
3358
- var mcps = Array.isArray(data?.mcpServers) ? data.mcpServers : (data?.mcp || []);
3359
- var categories = Array.isArray(data?.categories) ? data.categories : (data?.activity || []);
3360
- var projects = Array.isArray(data?.projects) ? data.projects : (data?.topProjects || []);
3361
- var projRows = projects.map(function(p) {
3362
- var raw = (p.project && p.project !== '?') ? p.project : (p.name && p.name !== '?' ? p.name : '');
3363
- var base = String(p.dir || p.path || '').split('/').filter(Boolean).pop() || '';
3364
- return Object.assign({}, p, { project: raw || base || 'unknown' });
3365
- });
3366
- function usageBar(items, valKey, labelKey, color, maxItems) {
3367
- if (!items.length) return '';
3368
- var maxVal = Math.max.apply(null, items.slice(0, maxItems).map(function(x) { return Number(x[valKey] || 0); }).concat([0.0001]));
3369
- return items.slice(0, maxItems).map(function(item) {
3370
- var pct = Math.round((Number(item[valKey] || 0) / maxVal) * 100);
3371
- var label = esc(String(item[labelKey] || '—').slice(0, 24));
3372
- var val = typeof item[valKey] === 'number' && valKey === 'cost' ? '$' + Number(item[valKey]).toFixed(4) : String(item[valKey] || 0);
3373
- return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
3374
- '<div style="width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px">' + label + '</div>' +
3375
- '<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:' + pct + '%;height:100%;background:' + color + ';border-radius:2px"></div></div>' +
3376
- '<div style="width:60px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + esc(val) + '</div></div>';
3377
- }).join('');
3378
- }
3379
- var cacheEff = s.cacheTokens ? Math.min(100, Math.round(s.cacheTokens / (s.cacheTokens + (Number(s.totalTokensIn) || 0)) * 100)) : null;
3380
- var html = '';
3381
- if (cacheEff !== null || s.totalTokensIn != null) {
3382
- html += '<div style="display:flex;gap:16px;flex-wrap:wrap;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--border)">';
3383
- if (s.totalTokensIn != null) html += '<div class="chunk-stat"><div class="chunk-stat-val">' + Number(s.totalTokensIn).toLocaleString() + '</div><div class="chunk-stat-lbl">Tokens In</div></div>';
3384
- if (s.totalTokensOut != null) html += '<div class="chunk-stat"><div class="chunk-stat-val">' + Number(s.totalTokensOut).toLocaleString() + '</div><div class="chunk-stat-lbl">Tokens Out</div></div>';
3385
- if (cacheEff !== null) html += '<div class="chunk-stat"><div class="chunk-stat-val" style="color:var(--green)">' + cacheEff + '%</div><div class="chunk-stat-lbl">Cache Eff.</div></div>';
3386
- html += '</div>';
3387
- }
3388
- if (models.length) html += '<div class="m-group-title" style="margin-bottom:6px">Models</div><div style="margin-bottom:14px">' + usageBar(models, 'cost', 'model', 'oklch(65% 0.13 290)', 8) + '</div>';
3389
- if (categories.length) html += '<div class="m-group-title" style="margin-bottom:6px">Activity</div><div style="margin-bottom:14px">' + usageBar(categories, 'turns', 'category', 'var(--accent)', 8) + '</div>';
3390
- if (tools.length) html += '<div class="m-group-title" style="margin-bottom:6px">Top Tools</div><div style="margin-bottom:14px">' + usageBar(tools, 'count', 'tool', 'oklch(72% 0.18 75)', 10) + '</div>';
3391
- if (mcps.length) html += '<div class="m-group-title" style="margin-bottom:6px">MCP Servers</div><div style="margin-bottom:14px">' + usageBar(mcps, 'count', 'server', 'oklch(70% 0.18 60)', 8) + '</div>';
3392
- if (projRows.length) html += '<div class="m-group-title" style="margin-bottom:6px">Projects</div><div style="margin-bottom:14px">' + usageBar(projRows, 'cost', 'project', 'oklch(65% 0.15 150)', 5) + '</div>';
3393
- el.innerHTML = html || '';
3394
- } catch (_) { el.innerHTML = ''; }
3395
- }
3396
-
3397
3222
  function renderTokChart(daily, animated = true) {
3398
3223
  const canvas = document.getElementById('tok-chart');
3399
3224
  if (!canvas || !daily.length) return;
@@ -3519,7 +3344,6 @@ async function setTokPeriod(btn, period) {
3519
3344
  '</tbody></table></div>';
3520
3345
  } else if (table) { table.innerHTML = ''; }
3521
3346
  markLiveGlow('view-tokens');
3522
- loadTokenUsageBreakdown(period);
3523
3347
  // Update topbar badge when showing today's data
3524
3348
  if (period === 'today') {
3525
3349
  const badge = document.getElementById('topbar-cost');
@@ -3546,13 +3370,17 @@ function switchMemTab(tab) {
3546
3370
  _currentMemTab = tab;
3547
3371
  document.querySelectorAll('.mem-tab-bar .odt-btn').forEach(b =>
3548
3372
  b.classList.toggle('active', b.dataset.memtab === tab));
3549
- ['memories','adrs','chunks'].forEach(t => {
3373
+ ['memories','routing','usage','adrs','swarm','chunks','agent-graph'].forEach(t => {
3550
3374
  const el = document.getElementById('mem-tab-' + t);
3551
3375
  if (el) el.style.display = t === tab ? '' : 'none';
3552
3376
  });
3553
3377
  if (tab === 'memories') loadMemoriesTab();
3378
+ else if (tab === 'routing') loadMemRouting();
3379
+ else if (tab === 'usage') loadMemUsage();
3554
3380
  else if (tab === 'adrs') loadMemADRs();
3381
+ else if (tab === 'swarm') loadSwarmTab();
3555
3382
  else if (tab === 'chunks') loadChunksTab();
3383
+ else if (tab === 'agent-graph') loadAgentGraphTab();
3556
3384
  }
3557
3385
 
3558
3386
  async function loadMemRouting() {
@@ -3714,12 +3542,12 @@ function renderADRs(list) {
3714
3542
  const pane = document.getElementById('adr-content');
3715
3543
  if (!pane) return;
3716
3544
  if (!list.length) { pane.innerHTML = '<div class="empty">No ADRs found</div>'; return; }
3717
- pane.innerHTML = list.slice(0, 50).map((a, i) => `<div class="adr-item" style="padding:8px 4px;border-bottom:1px solid var(--border);cursor:pointer;border-radius:4px" onclick="viewADR(${i})" onmouseenter="this.style.background='oklch(72% 0.02 200 / 0.06)'" onmouseleave="this.style.background=''">
3545
+ pane.innerHTML = list.slice(0, 50).map(a => `<div style="padding:8px 0;border-bottom:1px solid var(--border)">
3718
3546
  <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:3px">
3719
- <span style="color:var(--text-hi);font-size:11px;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:170px">${esc(a.number || a.id || a.title || '—')}</span>
3720
- <span class="ss-pill ${a.status === 'accepted' || a.status === 'Accepted' ? 'on' : a.status === 'deprecated' || a.status === 'Deprecated' ? 'warn' : ''}" style="font-size:9px">${esc(a.status || '?')}</span>
3547
+ <span style="color:var(--text-hi);font-size:12px;font-family:monospace">${esc(a.id || a.title || '—')}</span>
3548
+ <span class="ss-pill ${a.status === 'accepted' ? 'on' : a.status === 'deprecated' ? 'warn' : ''}">${esc(a.status || '?')}</span>
3721
3549
  </div>
3722
- <div style="font-size:10px;color:var(--text-lo);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(a.title || '')}</div>
3550
+ <div style="font-size:11px;color:var(--text-lo)">${esc((a.context || a.summary || '').slice(0, 120))}</div>
3723
3551
  </div>`).join('');
3724
3552
  }
3725
3553
 
@@ -3733,65 +3561,6 @@ function filterADRs(q) {
3733
3561
  renderADRs(filtered);
3734
3562
  }
3735
3563
 
3736
- async function viewADR(idx) {
3737
- var adr = _allADRs[idx];
3738
- if (!adr || !adr.file) return;
3739
- var viewer = document.getElementById('adr-viewer');
3740
- if (!viewer) return;
3741
- viewer.innerHTML = '<div class="loading-txt">Loading…</div>';
3742
- // Highlight selected
3743
- document.querySelectorAll('#adr-content .adr-item').forEach(function(el, i) {
3744
- el.style.background = i === idx ? 'oklch(72% 0.02 200 / 0.08)' : '';
3745
- el.style.borderLeft = i === idx ? '2px solid var(--accent)' : '';
3746
- });
3747
- try {
3748
- var data = await apiFetch('/api/adr-file?file=' + enc(adr.file) + '&dir=' + enc(DIR));
3749
- var md = data.content || '';
3750
- viewer.innerHTML = '<div style="font-family:var(--mono);font-size:12px;line-height:1.7;white-space:pre-wrap;color:var(--text-hi);overflow-x:auto">' + _renderMd(md) + '</div>';
3751
- } catch (e) {
3752
- viewer.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>';
3753
- }
3754
- }
3755
-
3756
- function _renderMd(md) {
3757
- var lines = md.split('\n');
3758
- var html = [];
3759
- var inCode = false, codeLang = '';
3760
- for (var i = 0; i < lines.length; i++) {
3761
- var line = lines[i];
3762
- if (line.startsWith('```')) {
3763
- if (!inCode) { inCode = true; codeLang = line.slice(3).trim(); html.push('<div style="background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:8px 10px;margin:6px 0;overflow-x:auto;font-size:11px">'); continue; }
3764
- else { inCode = false; html.push('</div>'); continue; }
3765
- }
3766
- if (inCode) { html.push(esc(line) + '\n'); continue; }
3767
- if (/^#{1,6}\s/.test(line)) {
3768
- var lvl = line.match(/^(#{1,6})/)[1].length;
3769
- var text = line.replace(/^#{1,6}\s+/, '');
3770
- var sizes = { 1: '16px', 2: '14px', 3: '13px', 4: '12px', 5: '12px', 6: '11px' };
3771
- html.push('<div style="font-size:' + (sizes[lvl] || '12px') + ';font-weight:700;color:var(--accent);margin:' + (lvl <= 2 ? '16px' : '10px') + ' 0 4px 0">' + esc(text) + '</div>');
3772
- } else if (/^\|/.test(line)) {
3773
- if (/^\|[\s-:|]+\|$/.test(line)) continue;
3774
- var cells = line.split('|').slice(1, -1).map(function(c) { return c.trim(); });
3775
- html.push('<div style="display:flex;gap:0;border-bottom:1px solid var(--border)">' + cells.map(function(c) {
3776
- return '<div style="flex:1;padding:2px 6px;font-size:11px">' + esc(c) + '</div>';
3777
- }).join('') + '</div>');
3778
- } else if (/^[-*]\s/.test(line)) {
3779
- var indent = line.match(/^(\s*)/)[1].length;
3780
- html.push('<div style="padding-left:' + (indent + 12) + 'px;position:relative"><span style="position:absolute;left:' + indent + 'px;color:var(--text-xs)">•</span>' + esc(line.replace(/^\s*[-*]\s/, '')) + '</div>');
3781
- } else if (/^\d+\.\s/.test(line)) {
3782
- var num = line.match(/^(\d+)\./)[1];
3783
- html.push('<div style="padding-left:16px;position:relative"><span style="position:absolute;left:0;color:var(--text-xs)">' + num + '.</span>' + esc(line.replace(/^\d+\.\s/, '')) + '</div>');
3784
- } else if (line.trim() === '') {
3785
- html.push('<div style="height:6px"></div>');
3786
- } else {
3787
- var styled = esc(line).replace(/\*\*([^*]+)\*\*/g, '<b style="color:var(--text-hi)">$1</b>').replace(/`([^`]+)`/g, '<code style="background:var(--bg);padding:1px 4px;border-radius:3px;font-size:11px">$1</code>');
3788
- html.push('<div>' + styled + '</div>');
3789
- }
3790
- }
3791
- if (inCode) html.push('</div>');
3792
- return html.join('\n');
3793
- }
3794
-
3795
3564
  function renderMiniSessions(sessions) {
3796
3565
  if (!sessions.length) return;
3797
3566
  const items = sessions.map((s, i) => `
@@ -13622,55 +13391,27 @@ async function secondBrainSearch() {
13622
13391
  box.innerHTML = '<div class="loading-txt">Searching…</div>';
13623
13392
  try {
13624
13393
  const scopeSel = document.getElementById('sb-search-scope');
13625
- const [r, routeR] = await Promise.all([
13626
- fetch('/api/knowledge/search', {
13627
- method: 'POST',
13628
- headers: { 'Content-Type': 'application/json' },
13629
- body: JSON.stringify({ query: q, limit: 5, scope: scopeSel ? scopeSel.value : 'all' }),
13630
- }),
13631
- fetch('/api/second-brain-route', {
13632
- method: 'POST',
13633
- headers: { 'Content-Type': 'application/json' },
13634
- body: JSON.stringify({ query: q }),
13635
- }).catch(function() { return null; }),
13636
- ]);
13394
+ const r = await fetch('/api/knowledge/search', {
13395
+ method: 'POST',
13396
+ headers: { 'Content-Type': 'application/json' },
13397
+ body: JSON.stringify({ query: q, limit: 5, scope: scopeSel ? scopeSel.value : 'all' }),
13398
+ });
13637
13399
  if (!r.ok) throw new Error('HTTP ' + r.status);
13638
13400
  const data = await r.json();
13639
- var routeData = null;
13640
- if (routeR && routeR.ok) { try { routeData = await routeR.json(); } catch(_) {} }
13641
13401
  const results = data.results || [];
13642
- // Route inspector bar
13643
- var routeHtml = '';
13644
- if (routeData && routeData.route) {
13645
- var rd = routeData.route;
13646
- var surfaces = ['chunks', 'kg', 'rules', 'memory'];
13647
- var surfaceColors = { chunks: 'oklch(72% 0.18 145)', kg: 'oklch(72% 0.18 280)', rules: 'oklch(72% 0.18 75)', memory: 'oklch(72% 0.18 30)' };
13648
- routeHtml = '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 10px;margin-bottom:8px">' +
13649
- '<div style="font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-xs);margin-bottom:5px">Route Decision' +
13650
- (rd.confident ? ' <span style="color:oklch(72% 0.18 145)">confident</span>' : ' <span style="color:oklch(72% 0.18 75)">low-confidence (all surfaces queried)</span>') +
13651
- '</div>' +
13652
- '<div style="display:flex;gap:8px;flex-wrap:wrap">' +
13653
- surfaces.map(function(s) {
13654
- var score = rd.scores[s] || 0;
13655
- var active = rd.surfaces.indexOf(s) !== -1;
13656
- return '<div style="font-size:11px;padding:3px 8px;border-radius:6px;border:1px solid ' +
13657
- (active ? surfaceColors[s] : 'var(--border)') + ';color:' +
13658
- (active ? surfaceColors[s] : 'var(--text-lo)') + ';background:' +
13659
- (active ? surfaceColors[s].replace(')', ' / 0.1)') : 'transparent') + '">' +
13660
- s + ' <span style="font-family:var(--mono);font-weight:600">' + score.toFixed(1) + '</span></div>';
13661
- }).join('') +
13662
- '</div></div>';
13663
- }
13664
13402
  if (!results.length) {
13665
- box.innerHTML = routeHtml + '<div class="empty" style="padding:10px">No matches (' + esc(data.method || '?') + '). Ingest documents with: <code>monomind doc ingest ./path</code></div>';
13403
+ box.innerHTML = '<div class="empty" style="padding:10px">No matches (' + esc(data.method || '?') + '). Ingest documents with: <code>monomind doc ingest ./path</code></div>';
13666
13404
  return;
13667
13405
  }
13668
- box.innerHTML = routeHtml +
13406
+ box.innerHTML =
13669
13407
  '<div style="font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-xs);margin-bottom:6px">' +
13670
13408
  results.length + ' result(s) · ' + esc(data.method || '') + '</div>' +
13671
13409
  results.map(function (res) {
13672
13410
  var srcTag = (res.tags || []).find(function (t) { return t.indexOf('src:') === 0; });
13673
13411
  var label = srcTag ? srcTag.slice(4).split('/').slice(-2).join('/') : (res.key || '');
13412
+ // Surface badge: the endpoint fuses chunks + KG triplets + rules +
13413
+ // pattern memories (RRF); show non-chunk kinds so users see where a
13414
+ // hit came from.
13674
13415
  var kindBadge = res.triplet ? 'kg' : (res.rule ? 'rule' : (res.memory ? 'memory' : ''));
13675
13416
  return '<div style="border:1px solid var(--border);border-radius:8px;padding:8px 10px;margin-bottom:6px">' +
13676
13417
  '<div style="font-size:10px;color:var(--text-lo);margin-bottom:3px">' + esc(label) +
@@ -13690,7 +13431,6 @@ async function loadChunksTab() {
13690
13431
  const grid = document.getElementById('chunks-grid');
13691
13432
  if (!grid) return;
13692
13433
  grid.innerHTML = '<div class="loading-txt">Loading…</div>';
13693
- loadSBAnalytics();
13694
13434
  try {
13695
13435
  const data = await apiFetch('/api/section?name=knowledge&full=1&dir=' + enc(DIR));
13696
13436
  // /api/section returns {"knowledge":{"chunks":N,"allChunks":[...]}} — extract the array
@@ -13706,310 +13446,6 @@ async function loadChunksTab() {
13706
13446
  }
13707
13447
  }
13708
13448
 
13709
- async function loadSBAnalytics() {
13710
- // Populate global brain & doc counts in parallel
13711
- try {
13712
- var docsData = await apiFetch('/api/second-brain-docs');
13713
- var gEl = document.getElementById('chunk-global-val');
13714
- var dEl = document.getElementById('chunk-docs-val');
13715
- if (gEl) gEl.textContent = (docsData.globalCount || 0).toLocaleString();
13716
- if (dEl) dEl.textContent = (docsData.totalDocs || 0).toLocaleString();
13717
- } catch (_) {}
13718
- try {
13719
- const data = await apiFetch('/api/second-brain-metrics');
13720
- if (!data || data.total === 0) return;
13721
- const el = document.getElementById('sb-analytics');
13722
- if (el) el.style.display = '';
13723
- // Hit rate
13724
- const hrEl = document.getElementById('sb-hit-rate');
13725
- if (hrEl) hrEl.textContent = (data.hitRate * 100).toFixed(1) + '%';
13726
- const hdEl = document.getElementById('sb-hit-detail');
13727
- if (hdEl) hdEl.textContent = data.injected + '/' + data.total + ' prompts';
13728
- // Method distribution
13729
- const mvEl = document.getElementById('sb-method-val');
13730
- const mbEl = document.getElementById('sb-method-bar');
13731
- const mdEl = document.getElementById('sb-method-detail');
13732
- const methods = data.methods || {};
13733
- const semCount = methods.semantic || 0;
13734
- const kwCount = methods.keyword || 0;
13735
- const totalMethods = semCount + kwCount + (methods.unknown || 0) + (methods.mixed || 0);
13736
- if (mvEl) mvEl.textContent = totalMethods > 0 && semCount > kwCount ? 'Semantic' : (kwCount > 0 ? 'Keyword' : '—');
13737
- if (mbEl && totalMethods > 0) {
13738
- const semPct = (semCount / totalMethods * 100).toFixed(0);
13739
- const kwPct = (kwCount / totalMethods * 100).toFixed(0);
13740
- mbEl.innerHTML = '<div class="sb-method-seg" style="width:' + semPct + '%;background:oklch(72% 0.18 145)" title="Semantic ' + semPct + '%"></div>' +
13741
- '<div class="sb-method-seg" style="width:' + kwPct + '%;background:oklch(72% 0.18 75)" title="Keyword ' + kwPct + '%"></div>';
13742
- }
13743
- if (mdEl) mdEl.textContent = semCount + ' semantic · ' + kwCount + ' keyword';
13744
- // Average score
13745
- const asEl = document.getElementById('sb-avg-score');
13746
- if (asEl) asEl.textContent = data.avgScore > 0 ? data.avgScore.toFixed(2) : '—';
13747
- // Score distribution
13748
- const sdEl = document.getElementById('sb-score-dist');
13749
- if (sdEl && data.scoreBuckets) {
13750
- const maxB = Math.max(...data.scoreBuckets, 1);
13751
- const labels = ['<0.3', '0.3–0.5', '0.5–0.7', '0.7–0.9', '0.9+'];
13752
- sdEl.innerHTML = data.scoreBuckets.map(function(v, i) {
13753
- return '<div class="sb-score-bar" style="height:' + Math.max(2, v / maxB * 24) + 'px" title="' + labels[i] + ': ' + v + '"></div>';
13754
- }).join('');
13755
- }
13756
- // Sparkline
13757
- const slEl = document.getElementById('sb-sparkline');
13758
- if (slEl && data.sparkline && data.sparkline.length > 0) {
13759
- const maxS = Math.max(...data.sparkline.map(function(b) { return b.total; }), 1);
13760
- slEl.innerHTML = data.sparkline.map(function(b) {
13761
- if (b.total === 0) return '<div class="sb-spark-bar" style="height:1px;background:var(--border)"></div>';
13762
- var hitH = Math.max(2, b.injected / maxS * 32);
13763
- var missH = Math.max(0, (b.total - b.injected) / maxS * 32);
13764
- return '<div style="display:flex;flex-direction:column;gap:1px;flex:1;align-items:stretch">' +
13765
- (missH > 0 ? '<div class="sb-spark-bar miss" style="height:' + missH + 'px"></div>' : '') +
13766
- '<div class="sb-spark-bar hit" style="height:' + hitH + 'px"></div></div>';
13767
- }).join('');
13768
- }
13769
- const spEl = document.getElementById('sb-spark-detail');
13770
- if (spEl) {
13771
- var recent24 = data.sparkline ? data.sparkline.reduce(function(a, b) { return { total: a.total + b.total, injected: a.injected + b.injected }; }, { total: 0, injected: 0 }) : { total: 0, injected: 0 };
13772
- spEl.textContent = recent24.injected + ' hits / ' + recent24.total + ' queries';
13773
- }
13774
- // Route overrides (misroutes)
13775
- var overrides = data.routeOverrides || {};
13776
- var overrideKeys = Object.keys(overrides);
13777
- if (overrideKeys.length > 0) {
13778
- var ocEl = document.getElementById('sb-overrides-card');
13779
- var ovEl = document.getElementById('sb-overrides-val');
13780
- var odEl = document.getElementById('sb-overrides-detail');
13781
- if (ocEl) ocEl.style.display = '';
13782
- var totalOverrides = overrideKeys.reduce(function(s, k) { return s + overrides[k]; }, 0);
13783
- if (ovEl) ovEl.textContent = totalOverrides;
13784
- if (odEl) odEl.textContent = overrideKeys.slice(0, 3).map(function(k) { return k + ': ' + overrides[k]; }).join(', ');
13785
- }
13786
- } catch (_) {}
13787
- }
13788
-
13789
- var _sbDocsLoaded = false;
13790
- var _sbDocs = [];
13791
- function switchSBView(view) {
13792
- var panes = ['chunks', 'docs', 'monitor', 'config', 'kg'];
13793
- panes.forEach(function(p) {
13794
- var pane = document.getElementById('sb-view-' + p + '-pane');
13795
- var btn = document.getElementById('sb-view-' + p);
13796
- if (pane) pane.style.display = p === view ? '' : 'none';
13797
- if (btn) {
13798
- if (p === view) { btn.style.borderColor = 'var(--accent)'; btn.style.color = 'var(--accent)'; btn.style.opacity = '1'; btn.style.fontWeight = '600'; }
13799
- else { btn.style.borderColor = ''; btn.style.color = ''; btn.style.opacity = '0.6'; btn.style.fontWeight = ''; }
13800
- }
13801
- });
13802
- if (view === 'docs') loadSBDocs();
13803
- if (view === 'monitor') startIngestStream();
13804
- if (view === 'config') loadSBConfig();
13805
- if (view === 'kg') loadSBKnowledgeGraph();
13806
- }
13807
-
13808
- async function loadSBDocs() {
13809
- if (_sbDocsLoaded) return;
13810
- var grid = document.getElementById('sb-docs-grid');
13811
- if (!grid) return;
13812
- grid.innerHTML = '<div class="loading-txt">Loading…</div>';
13813
- try {
13814
- var data = await apiFetch('/api/second-brain-docs');
13815
- _sbDocs = data.docs || [];
13816
- _sbDocsLoaded = true;
13817
- _renderSBDocs(_sbDocs);
13818
- } catch (e) {
13819
- grid.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>';
13820
- }
13821
- }
13822
-
13823
- function _renderSBDocs(list) {
13824
- var grid = document.getElementById('sb-docs-grid');
13825
- if (!grid) return;
13826
- if (!list.length) {
13827
- grid.innerHTML = '<div class="empty">No documents indexed.<br><span style="font-size:11px;color:var(--text-xs)">Run <code>monomind doc ingest ./path</code> to add documents.</span></div>';
13828
- return;
13829
- }
13830
- var extIcons = { '.pdf': '\u{1F4C4}', '.md': '\u{1F4DD}', '.txt': '\u{1F4C3}', '.docx': '\u{1F4D1}', '.mdx': '\u{1F4DD}', '.rst': '\u{1F4C3}' };
13831
- grid.innerHTML = '<div style="font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-xs);margin-bottom:6px">' + list.length + ' document(s)</div>' +
13832
- list.map(function(d) {
13833
- var ext = (d.filePath || '').split('.').pop();
13834
- var icon = extIcons['.' + ext] || '\u{1F4C4}';
13835
- var size = d.size > 1048576 ? (d.size / 1048576).toFixed(1) + ' MB' : (d.size > 1024 ? (d.size / 1024).toFixed(0) + ' KB' : d.size + ' B');
13836
- var ago = d.indexedAt ? _timeAgo(d.indexedAt) : '';
13837
- return '<div class="sb-doc-card" data-search="' + esc((d.name + ' ' + d.filePath).toLowerCase()) + '">' +
13838
- '<div class="sb-doc-icon">' + icon + '</div>' +
13839
- '<div class="sb-doc-info">' +
13840
- '<div class="sb-doc-name">' + esc(d.name || '—') + '</div>' +
13841
- '<div class="sb-doc-path">' + esc(d.filePath || '') + '</div>' +
13842
- '<div class="sb-doc-meta">' +
13843
- '<span class="sb-doc-badge">' + d.chunkCount + ' chunks</span>' +
13844
- '<span class="sb-doc-badge">' + size + '</span>' +
13845
- (d.brainScope === 'global' ? '<span class="sb-doc-badge global">global</span>' : '') +
13846
- (!d.exists ? '<span class="sb-doc-badge missing">file missing</span>' : '') +
13847
- (ago ? '<span class="sb-doc-badge">' + ago + '</span>' : '') +
13848
- '</div>' +
13849
- '</div></div>';
13850
- }).join('');
13851
- }
13852
-
13853
- function _timeAgo(iso) {
13854
- var diff = Date.now() - new Date(iso).getTime();
13855
- if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
13856
- if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
13857
- return Math.floor(diff / 86400000) + 'd ago';
13858
- }
13859
-
13860
- function filterSBDocs(val) {
13861
- var cards = document.querySelectorAll('#sb-docs-grid .sb-doc-card');
13862
- var q = (val || '').toLowerCase();
13863
- cards.forEach(function(c) { c.style.display = !q || (c.dataset.search || '').includes(q) ? '' : 'none'; });
13864
- }
13865
-
13866
- // --- Live Ingest Monitor (SSE) ---
13867
- var _ingestStream = null;
13868
- var _ingestEventCount = 0;
13869
- function startIngestStream() {
13870
- if (_ingestStream) return;
13871
- var badge = document.getElementById('sb-live-badge');
13872
- try {
13873
- _ingestStream = window.mmEventSource ? window.mmEventSource('/api/second-brain-ingest-stream') : new EventSource('/api/second-brain-ingest-stream');
13874
- _ingestStream.onopen = function() { if (badge) badge.innerHTML = '<span class="sb-live-dot"></span> live'; };
13875
- _ingestStream.onmessage = function(ev) {
13876
- try {
13877
- var d = JSON.parse(ev.data);
13878
- if (d.type === 'connected') { if (badge) badge.innerHTML = '<span class="sb-live-dot"></span> live'; return; }
13879
- _ingestEventCount++;
13880
- var countEl = document.getElementById('sb-monitor-count');
13881
- if (countEl) countEl.textContent = _ingestEventCount + ' events';
13882
- var feed = document.getElementById('sb-ingest-feed');
13883
- if (!feed) return;
13884
- if (_ingestEventCount === 1) feed.innerHTML = '';
13885
- var item = document.createElement('div');
13886
- item.className = 'sb-ingest-item' + (d.type === 'injection' ? ' injection' : '');
13887
- var now = new Date();
13888
- var timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');
13889
- if (d.type === 'ingest') {
13890
- item.innerHTML = '<span class="sb-ingest-dot ingest"></span><span class="sb-ingest-time">' + timeStr + '</span>' +
13891
- '<span style="flex:1">' + esc(d.doc.name || '—') + ' <span style="color:var(--text-xs)">' + (d.doc.chunkCount || '?') + ' chunks</span></span>' +
13892
- (d.doc.brainScope === 'global' ? '<span class="sb-doc-badge global" style="font-size:9px">global</span>' : '');
13893
- } else if (d.type === 'injection') {
13894
- var iev = d.event || {};
13895
- item.innerHTML = '<span class="sb-ingest-dot ' + (iev.injected ? 'injection' : 'miss') + '"></span><span class="sb-ingest-time">' + timeStr + '</span>' +
13896
- '<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + (iev.injected ? 'Injected' : 'Miss') + ' — ' + esc((iev.terms || []).join(' ').slice(0, 60)) + '</span>' +
13897
- '<span style="font-family:var(--mono);font-size:10px;color:var(--text-xs)">' + (iev.topScore != null ? iev.topScore.toFixed(2) : '—') + '</span>' +
13898
- '<span style="font-size:9px;padding:1px 5px;border-radius:8px;background:oklch(72% 0.18 ' + (iev.method === 'semantic' ? '145' : '75') + ' / 0.1);color:oklch(72% 0.18 ' + (iev.method === 'semantic' ? '145' : '75') + ')">' + (iev.method || '?') + '</span>';
13899
- }
13900
- feed.prepend(item);
13901
- while (feed.children.length > 100) feed.removeChild(feed.lastChild);
13902
- } catch (_) {}
13903
- };
13904
- _ingestStream.onerror = function() { if (badge) badge.innerHTML = '<span class="sb-live-dot" style="background:oklch(72% 0.18 25)"></span> disconnected'; };
13905
- } catch (_) { if (badge) badge.innerHTML = 'unavailable'; }
13906
- }
13907
- function clearIngestFeed() {
13908
- _ingestEventCount = 0;
13909
- var feed = document.getElementById('sb-ingest-feed');
13910
- if (feed) feed.innerHTML = '<div style="color:var(--text-xs);font-size:11px;padding:12px;text-align:center">Waiting for ingest or injection events…</div>';
13911
- var countEl = document.getElementById('sb-monitor-count');
13912
- if (countEl) countEl.textContent = '0 events';
13913
- }
13914
-
13915
- // --- Config / Auto-Tune Panel ---
13916
- var _sbConfigLoaded = false;
13917
- async function loadSBConfig() {
13918
- if (_sbConfigLoaded) return;
13919
- try {
13920
- var data = await apiFetch('/api/second-brain-config');
13921
- _sbConfigLoaded = true;
13922
- var cfg = data.config || {};
13923
- var floorEl = document.getElementById('sb-cfg-floor');
13924
- var limitEl = document.getElementById('sb-cfg-limit');
13925
- if (floorEl) floorEl.value = cfg.relevanceFloor != null ? cfg.relevanceFloor : 0.35;
13926
- if (limitEl) limitEl.value = cfg.injectionLimit != null ? cfg.injectionLimit : 5;
13927
- var tuneEl = document.getElementById('sb-tune-result');
13928
- if (data.suggestion) {
13929
- var s = data.suggestion;
13930
- if (tuneEl) tuneEl.innerHTML = '<span class="sb-tune-badge" onclick="applySBSuggestion(' + s.floor + ')" title="Click to apply">Suggested floor: ' + s.floor + '</span> ' +
13931
- '<span style="color:var(--text-lo);margin-left:6px">' + esc(s.reason) + '</span>';
13932
- } else {
13933
- if (tuneEl) tuneEl.textContent = 'Not enough data for a suggestion (need 20+ entries) or current settings are optimal.';
13934
- }
13935
- } catch (e) {
13936
- var tuneErr = document.getElementById('sb-tune-result');
13937
- if (tuneErr) tuneErr.textContent = 'Failed: ' + e.message;
13938
- }
13939
- }
13940
- function applySBSuggestion(floor) {
13941
- var el = document.getElementById('sb-cfg-floor');
13942
- if (el) el.value = floor;
13943
- saveSBConfig();
13944
- }
13945
- async function saveSBConfig() {
13946
- var statusEl = document.getElementById('sb-cfg-status');
13947
- var floor = parseFloat(document.getElementById('sb-cfg-floor').value);
13948
- var limit = parseInt(document.getElementById('sb-cfg-limit').value, 10);
13949
- if (isNaN(floor) || floor < 0 || floor > 1) { if (statusEl) statusEl.textContent = 'Invalid floor (0–1)'; return; }
13950
- if (isNaN(limit) || limit < 1) { if (statusEl) statusEl.textContent = 'Invalid limit'; return; }
13951
- try {
13952
- if (statusEl) statusEl.textContent = 'Saving…';
13953
- var resp = await fetch('/api/second-brain-config', {
13954
- method: 'PUT',
13955
- headers: { 'Content-Type': 'application/json' },
13956
- body: JSON.stringify({ relevanceFloor: floor, injectionLimit: limit })
13957
- });
13958
- if (!resp.ok) throw new Error('HTTP ' + resp.status);
13959
- if (statusEl) statusEl.textContent = 'Saved ✓';
13960
- _sbConfigLoaded = false;
13961
- setTimeout(function() { if (statusEl) statusEl.textContent = ''; }, 2000);
13962
- } catch (e) {
13963
- if (statusEl) statusEl.textContent = 'Error: ' + e.message;
13964
- }
13965
- }
13966
-
13967
- // --- Knowledge Graph Explorer ---
13968
- async function loadSBKnowledgeGraph(query) {
13969
- var tripletsEl = document.getElementById('sb-kg-triplets');
13970
- if (!tripletsEl) return;
13971
- tripletsEl.innerHTML = '<div class="loading-txt">Loading…</div>';
13972
- try {
13973
- var url = '/api/second-brain-kg?limit=100';
13974
- if (query) url += '&q=' + encodeURIComponent(query);
13975
- var data = await apiFetch(url);
13976
- var nodesEl = document.getElementById('sb-kg-nodes');
13977
- var edgesEl = document.getElementById('sb-kg-edges');
13978
- if (nodesEl) nodesEl.textContent = (data.stats.nodes || 0).toLocaleString();
13979
- if (edgesEl) edgesEl.textContent = (data.stats.edges || 0).toLocaleString();
13980
- // Glossary tags
13981
- var glossEl = document.getElementById('sb-kg-glossary');
13982
- if (glossEl && data.glossary && data.glossary.length > 0) {
13983
- glossEl.innerHTML = data.glossary.map(function(g) {
13984
- var name = typeof g === 'string' ? g : (g.name || g.entity || '');
13985
- var safe = esc(JSON.stringify(name));
13986
- return '<span class="sb-kg-tag" onclick="var n=' + safe + ';document.getElementById(\'sb-kg-search\').value=n;loadSBKnowledgeGraph(n)">' + esc(name) + '</span>';
13987
- }).join('');
13988
- }
13989
- // Triplets
13990
- var triplets = data.triplets || [];
13991
- if (!triplets.length) {
13992
- tripletsEl.innerHTML = '<div class="empty">No triplets found.' + (query ? ' Try a different search.' : ' Ingest documents to build the knowledge graph.') + '</div>';
13993
- return;
13994
- }
13995
- tripletsEl.innerHTML = triplets.map(function(t) {
13996
- var s = t.subject || t.s || t.head || '?';
13997
- var r = t.predicate || t.relation || t.p || t.rel || '→';
13998
- var o = t.object || t.o || t.tail || '?';
13999
- var safeS = esc(JSON.stringify(s));
14000
- var safeO = esc(JSON.stringify(o));
14001
- return '<div class="sb-kg-triplet">' +
14002
- '<span class="sb-kg-entity subject" onclick="var n=' + safeS + ';document.getElementById(\'sb-kg-search\').value=n;loadSBKnowledgeGraph(n)">' + esc(s) + '</span>' +
14003
- '<span class="sb-kg-rel">' + esc(r) + '</span>' +
14004
- '<span class="sb-kg-entity object" onclick="var n=' + safeO + ';document.getElementById(\'sb-kg-search\').value=n;loadSBKnowledgeGraph(n)">' + esc(o) + '</span>' +
14005
- (t.confidence ? '<span style="font-size:9px;color:var(--text-xs);margin-left:auto;font-family:var(--mono)">' + (t.confidence * 100).toFixed(0) + '%</span>' : '') +
14006
- '</div>';
14007
- }).join('');
14008
- } catch (e) {
14009
- tripletsEl.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>';
14010
- }
14011
- }
14012
-
14013
13449
  function _renderChunkStats(list) {
14014
13450
  const countEl = document.getElementById('chunk-count-val');
14015
13451
  const skillsEl = document.getElementById('chunk-skills-val');
@@ -14037,25 +13473,15 @@ function _renderChunks(list) {
14037
13473
  // leaked markup) whenever the text contained quotes.
14038
13474
  const shown = list.slice(0, 200);
14039
13475
  window._chunksShown = shown;
14040
- // Compute chunk size distribution for quality signals
14041
- var sizes = shown.map(function(c) { return (c.content || c.text || c.body || '').length; });
14042
- var avgSize = sizes.length > 0 ? sizes.reduce(function(a, b) { return a + b; }, 0) / sizes.length : 0;
14043
13476
  grid.innerHTML = shown.map((c, i) => {
14044
13477
  const src = (c.source || c.file || '').split('/').slice(-2).join('/');
14045
13478
  const excerpt = (c.content || c.text || c.body || '').slice(0, 220);
14046
13479
  const ns = c.namespace || c.type || '';
14047
- const cLen = (c.content || c.text || c.body || '').length;
14048
- var sizeNote = '';
14049
- if (cLen < 50) sizeNote = '<span class="chunk-ns" style="color:oklch(72% 0.18 75);background:oklch(72% 0.18 75 / 0.1)" title="Very short chunk — may be noise">tiny</span>';
14050
- else if (cLen > 4000) sizeNote = '<span class="chunk-ns" style="color:oklch(72% 0.18 280);background:oklch(72% 0.18 280 / 0.1)" title="Large chunk — may be imprecise for retrieval">large</span>';
14051
- var idxNote = typeof c.chunkIndex === 'number' ? '<span class="chunk-ns" title="Chunk index within document">#' + c.chunkIndex + '</span>' : '';
14052
13480
  return '<div class="chunk-card" data-search="' + esc((src + ' ' + excerpt + ' ' + ns).toLowerCase()) + '">' +
14053
13481
  '<div class="chunk-src">' + esc(src || '—') + '</div>' +
14054
13482
  '<div class="chunk-excerpt">' + esc(excerpt) + '</div>' +
14055
13483
  '<div class="chunk-footer">' +
14056
13484
  (ns ? '<span class="chunk-ns">' + esc(ns) + '</span>' : '') +
14057
- idxNote + sizeNote +
14058
- '<span class="chunk-ns" style="opacity:0.5" title="Character count">' + cLen + ' chars</span>' +
14059
13485
  '<button class="btn" style="margin-left:auto;font-size:10px;padding:1px 7px" onclick="openChunkEditAt(' + i + ')">✎ Edit</button>' +
14060
13486
  '<button class="btn" style="font-size:10px;padding:1px 7px;color:var(--red);border-color:var(--red)" onclick="deleteChunkAt(' + i + ')">✕</button>' +
14061
13487
  '</div>' +