recallnest 1.4.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.dockerignore +11 -0
  2. package/CLAUDE.md +7 -3
  3. package/Dockerfile +20 -0
  4. package/README.md +202 -130
  5. package/README_CN.md +263 -190
  6. package/ROADMAP.md +128 -81
  7. package/SKILL.md +250 -0
  8. package/assets/dashboard.png +0 -0
  9. package/assets/knowledge-graph.png +0 -0
  10. package/assets/screenshots/ui-full.png +0 -0
  11. package/assets/screenshots/ui-overview.png +0 -0
  12. package/assets/screenshots/ui-search.png +0 -0
  13. package/assets/ui/app.js +227 -3
  14. package/assets/ui/index.html +81 -11
  15. package/assets/ui/styles.css +178 -2
  16. package/bin/recallnest.mjs +62 -0
  17. package/bun.lock +337 -0
  18. package/daily-recall.sh +23 -0
  19. package/daily-reflections/2026-04-02.md +35 -0
  20. package/daily-reflections/2026-04-03.md +37 -0
  21. package/daily-reflections/2026-04-04.md +37 -0
  22. package/daily-reflections/2026-04-05.md +35 -0
  23. package/daily-reflections/2026-04-06.md +36 -0
  24. package/daily-reflections/2026-04-07.md +35 -0
  25. package/daily-reflections/2026-04-08.md +35 -0
  26. package/daily-reflections/2026-04-12.md +35 -0
  27. package/data/alias-map.example.json +22 -0
  28. package/docs/plans/2026-04-01-recall-boost.md +100 -0
  29. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +1195 -0
  30. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +398 -0
  31. package/glama.json +6 -0
  32. package/integrations/setup-all.sh +31 -0
  33. package/package.json +10 -3
  34. package/plan-philosophy-upgrades.md +363 -0
  35. package/plan-product-upgrades.md +155 -0
  36. package/plan.md +140 -0
  37. package/research-memory-evolution.md +440 -0
  38. package/research-rl-agent-memory.md +330 -0
  39. package/scripts/backfill-anchors.ts +130 -0
  40. package/scripts/diagnose-vector-search.ts +132 -0
  41. package/scripts/migrate-language.ts +75 -0
  42. package/scripts/verify-emotion-decay.ts +148 -0
  43. package/src/__tests__/activity-counter.test.ts +96 -0
  44. package/src/__tests__/admission-control.test.ts +206 -0
  45. package/src/__tests__/anchor-generator.test.ts +67 -0
  46. package/src/__tests__/babel-integration.test.ts +139 -0
  47. package/src/__tests__/capture-engine.test.ts +5 -3
  48. package/src/__tests__/cascade-forget.test.ts +1 -1
  49. package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
  50. package/src/__tests__/circuit-breaker.test.ts +99 -0
  51. package/src/__tests__/cluster-consolidation.test.ts +712 -0
  52. package/src/__tests__/confidence-metadata.test.ts +125 -0
  53. package/src/__tests__/confidence-tracker.test.ts +53 -0
  54. package/src/__tests__/consolidation-engine.test.ts +1 -1
  55. package/src/__tests__/constructive-retrieval.test.ts +509 -0
  56. package/src/__tests__/context-collapse-renderer.test.ts +214 -0
  57. package/src/__tests__/context-reconstructor.test.ts +65 -0
  58. package/src/__tests__/conversation-importer.test.ts +692 -0
  59. package/src/__tests__/data-checkup.test.ts +225 -0
  60. package/src/__tests__/decay-exemption.test.ts +84 -0
  61. package/src/__tests__/dedup-guard.test.ts +39 -0
  62. package/src/__tests__/distill-lock.test.ts +176 -0
  63. package/src/__tests__/dream-pipeline.test.ts +232 -0
  64. package/src/__tests__/emotion-decay.test.ts +46 -0
  65. package/src/__tests__/emotion-detector.test.ts +56 -0
  66. package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
  67. package/src/__tests__/emotion-retrieval.test.ts +56 -0
  68. package/src/__tests__/ethics-layer.test.ts +476 -0
  69. package/src/__tests__/feature-flags-integration.test.ts +54 -0
  70. package/src/__tests__/frequency-tracker.test.ts +113 -0
  71. package/src/__tests__/graph-export.test.ts +489 -0
  72. package/src/__tests__/interference-detector.test.ts +203 -0
  73. package/src/__tests__/mcp-efficiency.test.ts +288 -0
  74. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +243 -0
  75. package/src/__tests__/memory-evolution.test.ts +271 -0
  76. package/src/__tests__/memory-lint.test.ts +271 -0
  77. package/src/__tests__/narrative-architecture.test.ts +456 -0
  78. package/src/__tests__/pending-review.test.ts +77 -0
  79. package/src/__tests__/post-retrieval-filter.test.ts +178 -0
  80. package/src/__tests__/prediction-engine.test.ts +349 -0
  81. package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
  82. package/src/__tests__/preference-slots.test.ts +99 -0
  83. package/src/__tests__/query-expander-alias.test.ts +89 -0
  84. package/src/__tests__/recall-governor.test.ts +227 -0
  85. package/src/__tests__/resume-context.test.ts +156 -1
  86. package/src/__tests__/salience-scoring.test.ts +106 -0
  87. package/src/__tests__/session-distiller.test.ts +611 -0
  88. package/src/__tests__/skill-engine.test.ts +546 -0
  89. package/src/__tests__/skill-promotion.test.ts +349 -0
  90. package/src/__tests__/supersede-chain.test.ts +154 -0
  91. package/src/__tests__/temporal-validity.test.ts +160 -0
  92. package/src/__tests__/topic-tag.test.ts +113 -0
  93. package/src/__tests__/trusted-memory.test.ts +828 -0
  94. package/src/__tests__/write-verifier.test.ts +122 -0
  95. package/src/access-tracker.ts +3 -2
  96. package/src/activity-counter.ts +109 -0
  97. package/src/adaptive-retrieval.ts +6 -5
  98. package/src/admission-control.ts +163 -0
  99. package/src/anchor-generator.ts +80 -0
  100. package/src/api-server.ts +10 -0
  101. package/src/audit-log.ts +122 -0
  102. package/src/auto-gc.ts +98 -29
  103. package/src/auto-recall.ts +33 -4
  104. package/src/capture-engine.ts +372 -23
  105. package/src/cascade-forget.ts +3 -2
  106. package/src/cli.ts +107 -5
  107. package/src/compat.ts +22 -0
  108. package/src/confidence-tracker.ts +210 -47
  109. package/src/conflict-store.ts +12 -3
  110. package/src/consolidation-engine.ts +296 -8
  111. package/src/context-collapse-renderer.ts +222 -0
  112. package/src/context-composer-stable-selection.ts +5 -1
  113. package/src/context-composer.ts +239 -4
  114. package/src/context-reconstructor.ts +404 -0
  115. package/src/conversation-importer.ts +441 -0
  116. package/src/data-checkup.ts +232 -0
  117. package/src/decay-engine.ts +84 -0
  118. package/src/distill-lock.ts +122 -0
  119. package/src/doctor.ts +8 -10
  120. package/src/dream-pipeline.ts +227 -0
  121. package/src/emotion-detector.ts +90 -0
  122. package/src/eval.ts +3 -2
  123. package/src/forget-engine.ts +269 -0
  124. package/src/frequency-tracker.ts +175 -0
  125. package/src/graph-export.ts +644 -0
  126. package/src/ingest.ts +36 -6
  127. package/src/interference-detector.ts +187 -0
  128. package/src/kg-extractor.ts +15 -24
  129. package/src/kg-store.ts +2 -1
  130. package/src/language-hook.ts +67 -0
  131. package/src/llm-client.ts +289 -6
  132. package/src/mcp-server.ts +707 -80
  133. package/src/memory-assets.ts +22 -5
  134. package/src/memory-evolution.ts +365 -0
  135. package/src/memory-lint.ts +429 -0
  136. package/src/memory-output.ts +69 -0
  137. package/src/memory-schema.ts +74 -2
  138. package/src/narrative-schema.ts +79 -0
  139. package/src/narrative-tagger.ts +164 -0
  140. package/src/pii-detector.ts +98 -0
  141. package/src/post-retrieval-filter.ts +76 -0
  142. package/src/prediction-engine.ts +253 -0
  143. package/src/preference-slots.ts +65 -2
  144. package/src/prospective-memory.ts +215 -0
  145. package/src/query-expander.ts +67 -0
  146. package/src/recall-governor.ts +183 -0
  147. package/src/retention-policy.ts +90 -0
  148. package/src/retriever.ts +828 -55
  149. package/src/rif.ts +34 -8
  150. package/src/runtime-config.ts +23 -8
  151. package/src/scope-inventory.ts +4 -3
  152. package/src/scope-policy.ts +4 -0
  153. package/src/session-distiller.ts +307 -0
  154. package/src/session-output.ts +40 -0
  155. package/src/session-schema.ts +37 -0
  156. package/src/session-store.ts +7 -2
  157. package/src/skill-engine.ts +208 -0
  158. package/src/skill-promotion.ts +293 -0
  159. package/src/skill-schema.ts +39 -0
  160. package/src/store.ts +96 -10
  161. package/src/topic-tag.ts +66 -0
  162. package/src/tracker.ts +3 -1
  163. package/src/ui-server.ts +80 -8
  164. package/src/workflow-observation-scope-review.ts +2 -1
  165. package/src/workflow-observation-store.ts +2 -1
  166. package/src/write-verifier.ts +126 -0
package/assets/ui/app.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const queryInput = document.getElementById('queryInput');
2
2
  const profileInput = document.getElementById('profileInput');
3
3
  const scopeInput = document.getElementById('scopeInput');
4
+ const topicTagInput = document.getElementById('topicTagInput');
4
5
  const limitInput = document.getElementById('limitInput');
5
6
  const formatInput = document.getElementById('formatInput');
6
7
  const resultOutput = document.getElementById('resultOutput');
@@ -24,7 +25,7 @@ const toggleTraceButton = document.getElementById('toggleTraceButton');
24
25
  const viewTabs = Array.from(document.querySelectorAll('.view-tab'));
25
26
  const quickCards = Array.from(document.querySelectorAll('[data-quick-action]'));
26
27
 
27
- let currentView = 'search';
28
+ let currentView = 'dashboard';
28
29
  let lastItems = [];
29
30
  let lastMode = 'search';
30
31
  let lastArtifact = null;
@@ -38,6 +39,7 @@ let lastExports = [];
38
39
  let currentViewFilter = '';
39
40
  let activeAssetTag = '';
40
41
  let lastDirtyBriefCount = 0;
42
+ let lastSkills = [];
41
43
 
42
44
  async function api(path, payload) {
43
45
  const response = await fetch(path, {
@@ -57,6 +59,7 @@ function currentPayload() {
57
59
  query: queryInput.value.trim(),
58
60
  profile: profileInput.value,
59
61
  scope: scopeInput.value.trim() || undefined,
62
+ topicTag: topicTagInput.value || undefined,
60
63
  limit: Number(limitInput.value) || 5,
61
64
  };
62
65
  }
@@ -526,6 +529,13 @@ function renderMainSurface() {
526
529
  return;
527
530
  }
528
531
 
532
+ if (currentView === 'skills') {
533
+ resultTitle.textContent = 'Executable Skills';
534
+ resultMeta.textContent = `Skills: ${lastSkills.length}`;
535
+ renderSkillsView(lastSkills);
536
+ return;
537
+ }
538
+
529
539
  if (currentView === 'exports') {
530
540
  const filtered = filterExports(lastExports);
531
541
  resultTitle.textContent = 'Export Artifacts';
@@ -606,6 +616,97 @@ async function loadExports() {
606
616
  }
607
617
  }
608
618
 
619
+ async function loadSkills() {
620
+ try {
621
+ const data = await api('/api/skills');
622
+ lastSkills = data.items || [];
623
+ if (currentView === 'skills') renderMainSurface();
624
+ } catch (error) {
625
+ statusLine.textContent = String(error.message || error);
626
+ }
627
+ }
628
+
629
+ function renderSkillsView(items) {
630
+ if (!items || items.length === 0) {
631
+ resultCards.textContent = '';
632
+ const emptyDiv = document.createElement('div');
633
+ emptyDiv.className = 'empty-state';
634
+ emptyDiv.textContent = 'No skills stored yet. Use store_skill via MCP to add executable skills.';
635
+ resultCards.appendChild(emptyDiv);
636
+ return;
637
+ }
638
+
639
+ resultCards.textContent = '';
640
+ for (const item of items) {
641
+ const article = document.createElement('article');
642
+ article.className = 'list-card result-card';
643
+ article.dataset.cardId = item.shortId;
644
+
645
+ const head = document.createElement('div');
646
+ head.className = 'list-card-head';
647
+ const nameEl = document.createElement('strong');
648
+ nameEl.textContent = item.name;
649
+ const typeChip = document.createElement('span');
650
+ typeChip.className = 'card-chip';
651
+ typeChip.textContent = item.type;
652
+ head.appendChild(nameEl);
653
+ head.appendChild(typeChip);
654
+
655
+ const desc = document.createElement('p');
656
+ desc.className = 'result-snippet';
657
+ desc.textContent = item.description;
658
+
659
+ const meta = document.createElement('div');
660
+ meta.className = 'result-card-meta';
661
+ const triggerSpan = document.createElement('span');
662
+ triggerSpan.textContent = 'Trigger: ' + item.trigger;
663
+ const scopeSpan = document.createElement('span');
664
+ scopeSpan.textContent = item.scope;
665
+ meta.appendChild(triggerSpan);
666
+ meta.appendChild(scopeSpan);
667
+
668
+ const actions = document.createElement('div');
669
+ actions.className = 'result-card-actions';
670
+ const detailBtn = document.createElement('button');
671
+ detailBtn.className = 'card-chip';
672
+ detailBtn.dataset.toggleId = item.shortId;
673
+ detailBtn.textContent = 'Details';
674
+ const copyBtn = document.createElement('button');
675
+ copyBtn.className = 'card-chip';
676
+ copyBtn.dataset.copyText = encodeURIComponent(item.implementation);
677
+ copyBtn.textContent = 'Copy Code';
678
+ actions.appendChild(detailBtn);
679
+ actions.appendChild(copyBtn);
680
+
681
+ const detail = document.createElement('div');
682
+ detail.className = 'result-card-detail';
683
+ const implBlock = document.createElement('div');
684
+ implBlock.className = 'detail-block';
685
+ const implLabel = document.createElement('strong');
686
+ implLabel.textContent = 'Implementation';
687
+ const implPre = document.createElement('pre');
688
+ implPre.textContent = item.implementation;
689
+ implBlock.appendChild(implLabel);
690
+ implBlock.appendChild(implPre);
691
+ const tagsBlock = document.createElement('div');
692
+ tagsBlock.className = 'detail-block';
693
+ const tagsLabel = document.createElement('strong');
694
+ tagsLabel.textContent = 'Tags: ' + ((item.tags || []).join(', ') || '-');
695
+ tagsBlock.appendChild(tagsLabel);
696
+ detail.appendChild(implBlock);
697
+ detail.appendChild(tagsBlock);
698
+
699
+ article.appendChild(head);
700
+ article.appendChild(desc);
701
+ article.appendChild(meta);
702
+ article.appendChild(actions);
703
+ article.appendChild(detail);
704
+ resultCards.appendChild(article);
705
+ }
706
+
707
+ bindResultCardActions();
708
+ }
709
+
609
710
  async function loadStats() {
610
711
  try {
611
712
  const data = await api('/api/stats');
@@ -723,6 +824,12 @@ async function handleQuickAction(action) {
723
824
  await runMode('distill');
724
825
  return;
725
826
  }
827
+ if (action === 'skills') {
828
+ setActiveView('skills');
829
+ await loadSkills();
830
+ renderMainSurface();
831
+ return;
832
+ }
726
833
  if (action === 'pin') {
727
834
  pinMemoryId.focus();
728
835
  pinMemoryId.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -737,6 +844,9 @@ viewTabs.forEach((tab) => {
737
844
  if (view === 'pins') {
738
845
  await loadPins();
739
846
  }
847
+ if (view === 'skills') {
848
+ await loadSkills();
849
+ }
740
850
  if (view === 'exports') {
741
851
  await loadExports();
742
852
  }
@@ -794,7 +904,7 @@ assetTagBar.addEventListener('click', (event) => {
794
904
 
795
905
  queryInput.value = 'telegram bridge';
796
906
  profileInput.value = 'debug';
797
- setActiveView('search');
907
+ setActiveView('dashboard');
798
908
  renderArtifactBar();
799
909
  renderStats();
800
910
  renderPinsPanel();
@@ -804,4 +914,118 @@ renderMainSurface();
804
914
  loadPins();
805
915
  loadExports();
806
916
  loadStats();
807
- runMode('search');
917
+
918
+ // ---------------------------------------------------------------------------
919
+ // Dashboard
920
+ // ---------------------------------------------------------------------------
921
+
922
+ const dashTotal = document.getElementById('dashTotal');
923
+ const dashHealth = document.getElementById('dashHealth');
924
+ const dashWeek = document.getElementById('dashWeek');
925
+ const dashMonth = document.getElementById('dashMonth');
926
+ const dashCategoryBars = document.getElementById('dashCategoryBars');
927
+ const dashLintSummary = document.getElementById('dashLintSummary');
928
+ const dashStaleList = document.getElementById('dashStaleList');
929
+ const dashStaleCount = document.getElementById('dashStaleCount');
930
+ const dashboardView = document.getElementById('dashboardView');
931
+ const searchView = document.getElementById('searchView');
932
+ const resultView = document.getElementById('resultView');
933
+ const sideView = document.getElementById('sideView');
934
+ const refreshDashboard = document.getElementById('refreshDashboard');
935
+
936
+ const CATEGORY_COLORS = {
937
+ profile: '#f59e0b',
938
+ preferences: '#3b82f6',
939
+ entities: '#10b981',
940
+ events: '#6b7280',
941
+ cases: '#ef4444',
942
+ patterns: '#8b5cf6',
943
+ };
944
+
945
+ async function loadDashboard() {
946
+ try {
947
+ const [statsRes, lintRes, staleRes] = await Promise.all([
948
+ fetch('/api/dashboard-stats'),
949
+ fetch('/api/lint-summary'),
950
+ fetch('/api/stale-memories'),
951
+ ]);
952
+ const stats = await statsRes.json();
953
+ const lint = await lintRes.json();
954
+ const stale = await staleRes.json();
955
+
956
+ dashTotal.textContent = stats.totalCount.toLocaleString();
957
+ dashWeek.textContent = '+' + stats.growth.thisWeek;
958
+ dashMonth.textContent = '+' + stats.growth.thisMonth;
959
+ dashHealth.textContent = lint.healthScore + '/100';
960
+ dashHealth.className = 'stat-value ' + (lint.healthScore >= 80 ? 'good' : lint.healthScore >= 50 ? 'warn' : 'bad');
961
+
962
+ const cats = stats.categoryCounts || {};
963
+ const maxCount = Math.max(1, ...Object.values(cats));
964
+ const barHtml = Object.entries(cats)
965
+ .sort((a, b) => b[1] - a[1])
966
+ .map(([cat, count]) => {
967
+ const pct = Math.round((count / maxCount) * 100);
968
+ const color = CATEGORY_COLORS[cat] || '#6b7280';
969
+ return '<div class="bar-row">'
970
+ + '<span class="bar-label">' + escapeHtml(cat) + '</span>'
971
+ + '<div class="bar-track"><div class="bar-fill" style="width:' + pct + '%;background:' + color + '"></div></div>'
972
+ + '<span class="bar-count">' + count + '</span>'
973
+ + '</div>';
974
+ }).join('');
975
+ dashCategoryBars.innerHTML = barHtml;
976
+
977
+ const s = lint.summary;
978
+ const issues = [];
979
+ if (s.contradictions > 0) issues.push(s.contradictions + ' contradiction' + (s.contradictions > 1 ? 's' : ''));
980
+ if (s.duplicates > 0) issues.push(s.duplicates + ' duplicate' + (s.duplicates > 1 ? 's' : ''));
981
+ if (s.staleMemories > 0) issues.push(s.staleMemories + ' stale');
982
+ if (s.orphans > 0) issues.push(s.orphans + ' orphan' + (s.orphans > 1 ? 's' : ''));
983
+ if (issues.length === 0) {
984
+ dashLintSummary.innerHTML = '<div class="lint-ok">All Clear — no issues found</div>';
985
+ } else {
986
+ dashLintSummary.innerHTML = issues.map(function(i) {
987
+ return '<div class="lint-issue">' + escapeHtml(i) + '</div>';
988
+ }).join('');
989
+ }
990
+
991
+ dashStaleCount.textContent = stale.count;
992
+ if (stale.items.length === 0) {
993
+ dashStaleList.innerHTML = '<div class="lint-ok">No stale memories</div>';
994
+ } else {
995
+ dashStaleList.innerHTML = stale.items
996
+ .map(function(item) { return '<div class="stale-item">' + escapeHtml(item.detail) + '</div>'; })
997
+ .join('');
998
+ }
999
+ } catch (err) {
1000
+ console.error('Dashboard load error:', err);
1001
+ dashLintSummary.textContent = 'Failed to load dashboard data';
1002
+ }
1003
+ }
1004
+
1005
+ const originalViewTabs = Array.from(document.querySelectorAll('.view-tab'));
1006
+ for (const tab of originalViewTabs) {
1007
+ tab.addEventListener('click', () => {
1008
+ const view = tab.dataset.view;
1009
+ originalViewTabs.forEach(t => t.classList.toggle('is-active', t === tab));
1010
+
1011
+ if (view === 'dashboard') {
1012
+ if (dashboardView) dashboardView.classList.remove('is-hidden');
1013
+ if (searchView) searchView.classList.add('is-hidden');
1014
+ if (resultView) resultView.classList.add('is-hidden');
1015
+ if (sideView) sideView.classList.add('is-hidden');
1016
+ } else {
1017
+ if (dashboardView) dashboardView.classList.add('is-hidden');
1018
+ if (searchView) searchView.classList.remove('is-hidden');
1019
+ if (resultView) resultView.classList.remove('is-hidden');
1020
+ if (sideView) sideView.classList.remove('is-hidden');
1021
+ }
1022
+
1023
+ currentView = view;
1024
+ });
1025
+ }
1026
+
1027
+ if (refreshDashboard) {
1028
+ refreshDashboard.addEventListener('click', loadDashboard);
1029
+ }
1030
+
1031
+ loadDashboard();
@@ -11,25 +11,30 @@
11
11
  <header class="hero">
12
12
  <div>
13
13
  <p class="eyebrow">RecallNest</p>
14
- <h1>AI transcript memory workbench.</h1>
15
- <p class="subtitle">Search, explain, distill, pin.</p>
14
+ <h1>Shared memory layer workbench.</h1>
15
+ <p class="subtitle">Search, distill, pin, import, skills.</p>
16
16
  </div>
17
17
  <div class="hero-badge">
18
- <span>MCP-native</span>
19
- <span>Hybrid Retrieval</span>
20
- <span>Asset Recall</span>
18
+ <span>38 MCP Tools</span>
19
+ <span>6-Channel Hybrid</span>
20
+ <span>KG + PPR</span>
21
+ <span>1168 Tests</span>
21
22
  </div>
22
23
  </header>
23
24
 
24
25
  <section class="brand-strip">
25
26
  <button class="brand-card quick-card" data-quick-action="search">
26
27
  <strong>Search</strong>
27
- <span>run ranked recall</span>
28
+ <span>hybrid recall</span>
28
29
  </button>
29
30
  <button class="brand-card quick-card" data-quick-action="distill">
30
31
  <strong>Distill</strong>
31
32
  <span>build a briefing</span>
32
33
  </button>
34
+ <button class="brand-card quick-card" data-quick-action="skills">
35
+ <strong>Skills</strong>
36
+ <span>executable patterns</span>
37
+ </button>
33
38
  <button class="brand-card quick-card" data-quick-action="pin">
34
39
  <strong>Pin</strong>
35
40
  <span>jump to pin flow</span>
@@ -37,14 +42,58 @@
37
42
  </section>
38
43
 
39
44
  <main class="workspace">
40
- <section class="control-panel card">
45
+ <section id="dashboardView" class="dashboard-view card">
46
+ <div class="section-head">
47
+ <h2>Memory Dashboard</h2>
48
+ <button id="refreshDashboard" class="ghost-button">Refresh</button>
49
+ </div>
50
+
51
+ <div class="dash-stats-row" id="dashStatsRow">
52
+ <div class="stat-card">
53
+ <span class="stat-value" id="dashTotal">-</span>
54
+ <span class="stat-label">Total Memories</span>
55
+ </div>
56
+ <div class="stat-card">
57
+ <span class="stat-value" id="dashHealth">-</span>
58
+ <span class="stat-label">Health Score</span>
59
+ </div>
60
+ <div class="stat-card">
61
+ <span class="stat-value" id="dashWeek">-</span>
62
+ <span class="stat-label">This Week</span>
63
+ </div>
64
+ <div class="stat-card">
65
+ <span class="stat-value" id="dashMonth">-</span>
66
+ <span class="stat-label">This Month</span>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="dash-grid">
71
+ <div class="dash-panel">
72
+ <h3>Category Distribution</h3>
73
+ <div id="dashCategoryBars" class="category-bars"></div>
74
+ </div>
75
+ <div class="dash-panel">
76
+ <h3>Lint Summary</h3>
77
+ <div id="dashLintSummary" class="lint-summary">Loading...</div>
78
+ </div>
79
+ </div>
80
+
81
+ <div class="dash-panel full-width">
82
+ <h3>Stale Memories <span id="dashStaleCount" class="badge">0</span></h3>
83
+ <div id="dashStaleList" class="stale-list">Loading...</div>
84
+ </div>
85
+ </section>
86
+
87
+ <section class="control-panel card is-hidden" id="searchView">
41
88
  <div class="section-head">
42
89
  <h2>Query Console</h2>
43
90
  <button id="statsButton" class="ghost-button">Refresh Stats</button>
44
91
  </div>
45
92
 
46
93
  <div class="view-switch" id="viewSwitch">
47
- <button class="view-tab is-active" data-view="search">Search</button>
94
+ <button class="view-tab is-active" data-view="dashboard">Dashboard</button>
95
+ <button class="view-tab" data-view="search">Search</button>
96
+ <button class="view-tab" data-view="skills">Skills</button>
48
97
  <button class="view-tab" data-view="pins">Assets</button>
49
98
  <button class="view-tab" data-view="exports">Exports</button>
50
99
  </div>
@@ -70,7 +119,28 @@
70
119
  </label>
71
120
  </div>
72
121
 
73
- <div class="grid two-up">
122
+ <div class="grid three-up">
123
+ <label class="field">
124
+ <span>Topic Tag</span>
125
+ <select id="topicTagInput">
126
+ <option value="">all topics</option>
127
+ <option value="auth">auth</option>
128
+ <option value="deploy">deploy</option>
129
+ <option value="infra">infra</option>
130
+ <option value="testing">testing</option>
131
+ <option value="database">database</option>
132
+ <option value="api">api</option>
133
+ <option value="ui">ui</option>
134
+ <option value="perf">perf</option>
135
+ <option value="security">security</option>
136
+ <option value="memory">memory</option>
137
+ <option value="mcp">mcp</option>
138
+ <option value="llm">llm</option>
139
+ <option value="config">config</option>
140
+ <option value="data">data</option>
141
+ <option value="docs">docs</option>
142
+ </select>
143
+ </label>
74
144
  <label class="field">
75
145
  <span>Limit</span>
76
146
  <input id="limitInput" type="number" value="5" min="1" max="20" />
@@ -95,7 +165,7 @@
95
165
  <p id="statusLine" class="status-line">Ready.</p>
96
166
  </section>
97
167
 
98
- <section class="result-panel card">
168
+ <section class="result-panel card is-hidden" id="resultView">
99
169
  <div class="section-head">
100
170
  <h2 id="resultTitle">Result Surface</h2>
101
171
  <button id="reloadPinsButton" class="ghost-button">Load Assets</button>
@@ -113,7 +183,7 @@
113
183
  <div id="resultCards" class="result-cards"></div>
114
184
  </section>
115
185
 
116
- <section class="side-panel card">
186
+ <section class="side-panel card is-hidden" id="sideView">
117
187
  <div class="section-head">
118
188
  <h2>Recall Actions</h2>
119
189
  </div>
@@ -170,7 +170,7 @@ h2, h3 {
170
170
 
171
171
  .view-switch {
172
172
  display: grid;
173
- grid-template-columns: repeat(3, 1fr);
173
+ grid-template-columns: repeat(4, 1fr);
174
174
  gap: 8px;
175
175
  margin-bottom: 14px;
176
176
  }
@@ -206,6 +206,11 @@ textarea { resize: vertical; min-height: 120px; }
206
206
  grid-template-columns: 1fr 1fr;
207
207
  gap: 12px;
208
208
  }
209
+ .grid.three-up {
210
+ display: grid;
211
+ grid-template-columns: 1fr 1fr 1fr;
212
+ gap: 12px;
213
+ }
209
214
 
210
215
  .action-row {
211
216
  display: flex;
@@ -595,7 +600,7 @@ textarea { resize: vertical; min-height: 120px; }
595
600
  width: min(100vw - 18px, 100%);
596
601
  padding-top: 16px;
597
602
  }
598
- .grid.two-up {
603
+ .grid.two-up, .grid.three-up {
599
604
  grid-template-columns: 1fr;
600
605
  }
601
606
  .card {
@@ -603,3 +608,174 @@ textarea { resize: vertical; min-height: 120px; }
603
608
  padding: 16px;
604
609
  }
605
610
  }
611
+
612
+ .is-hidden {
613
+ display: none !important;
614
+ }
615
+
616
+ /* ---------------------------------------------------------------------------
617
+ Dashboard
618
+ --------------------------------------------------------------------------- */
619
+
620
+ .dashboard-view {
621
+ grid-column: 1 / -1;
622
+ }
623
+
624
+ .dash-stats-row {
625
+ display: grid;
626
+ grid-template-columns: repeat(4, 1fr);
627
+ gap: 1rem;
628
+ margin-bottom: 1.5rem;
629
+ }
630
+
631
+ .stat-card {
632
+ display: flex;
633
+ flex-direction: column;
634
+ align-items: center;
635
+ padding: 1.25rem;
636
+ border-radius: 0.75rem;
637
+ background: rgba(29, 42, 43, 0.05);
638
+ border: 1px solid var(--line);
639
+ }
640
+
641
+ .stat-value {
642
+ font-size: 2rem;
643
+ font-weight: 700;
644
+ font-variant-numeric: tabular-nums;
645
+ line-height: 1.2;
646
+ }
647
+
648
+ .stat-value.good { color: #10b981; }
649
+ .stat-value.warn { color: #f59e0b; }
650
+ .stat-value.bad { color: #ef4444; }
651
+
652
+ .stat-label {
653
+ font-size: 0.8rem;
654
+ color: var(--muted);
655
+ margin-top: 0.25rem;
656
+ text-transform: uppercase;
657
+ letter-spacing: 0.05em;
658
+ }
659
+
660
+ .dash-grid {
661
+ display: grid;
662
+ grid-template-columns: 1fr 1fr;
663
+ gap: 1.5rem;
664
+ margin-bottom: 1.5rem;
665
+ }
666
+
667
+ .dash-panel {
668
+ padding: 1rem;
669
+ border-radius: 0.75rem;
670
+ background: rgba(29, 42, 43, 0.05);
671
+ border: 1px solid var(--line);
672
+ }
673
+
674
+ .dash-panel.full-width {
675
+ grid-column: 1 / -1;
676
+ }
677
+
678
+ .dash-panel h3 {
679
+ font-size: 0.85rem;
680
+ font-weight: 600;
681
+ margin-bottom: 0.75rem;
682
+ color: var(--ink);
683
+ }
684
+
685
+ .badge {
686
+ display: inline-block;
687
+ background: rgba(29, 42, 43, 0.08);
688
+ color: var(--muted);
689
+ padding: 0.1em 0.5em;
690
+ border-radius: 999px;
691
+ font-size: 0.75rem;
692
+ font-weight: 500;
693
+ margin-left: 0.5rem;
694
+ }
695
+
696
+ /* Category bar chart */
697
+ .category-bars {
698
+ display: flex;
699
+ flex-direction: column;
700
+ gap: 0.5rem;
701
+ }
702
+
703
+ .bar-row {
704
+ display: grid;
705
+ grid-template-columns: 5.5rem 1fr 2.5rem;
706
+ align-items: center;
707
+ gap: 0.5rem;
708
+ }
709
+
710
+ .bar-label {
711
+ font-size: 0.8rem;
712
+ color: var(--muted);
713
+ text-align: right;
714
+ }
715
+
716
+ .bar-track {
717
+ height: 1.25rem;
718
+ background: rgba(29, 42, 43, 0.06);
719
+ border-radius: 0.25rem;
720
+ overflow: hidden;
721
+ }
722
+
723
+ .bar-fill {
724
+ height: 100%;
725
+ border-radius: 0.25rem;
726
+ transition: width 0.4s ease;
727
+ }
728
+
729
+ .bar-count {
730
+ font-size: 0.8rem;
731
+ color: var(--muted);
732
+ font-variant-numeric: tabular-nums;
733
+ }
734
+
735
+ /* Lint summary */
736
+ .lint-summary {
737
+ display: flex;
738
+ flex-direction: column;
739
+ gap: 0.5rem;
740
+ }
741
+
742
+ .lint-ok {
743
+ color: var(--accent);
744
+ font-size: 0.85rem;
745
+ }
746
+
747
+ .lint-issue {
748
+ font-size: 0.85rem;
749
+ padding: 0.35rem 0;
750
+ border-bottom: 1px solid var(--line);
751
+ }
752
+
753
+ .lint-issue:last-child {
754
+ border-bottom: none;
755
+ }
756
+
757
+ /* Stale list */
758
+ .stale-list {
759
+ max-height: 300px;
760
+ overflow-y: auto;
761
+ }
762
+
763
+ .stale-item {
764
+ font-size: 0.8rem;
765
+ color: var(--muted);
766
+ padding: 0.35rem 0;
767
+ border-bottom: 1px solid var(--line);
768
+ }
769
+
770
+ .stale-item:last-child {
771
+ border-bottom: none;
772
+ }
773
+
774
+ @media (max-width: 768px) {
775
+ .dash-stats-row {
776
+ grid-template-columns: repeat(2, 1fr);
777
+ }
778
+ .dash-grid {
779
+ grid-template-columns: 1fr;
780
+ }
781
+ }