pi-mega-compact 0.11.7 → 0.11.9

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 (93) hide show
  1. package/dist/extensions/dashboard-server/gather-debug-keys.js +18 -0
  2. package/dist/extensions/dashboard-server/gather-debug.js +129 -0
  3. package/dist/extensions/dashboard-server/routes-maintenance.js +6 -0
  4. package/dist/extensions/dashboard-server/routes-memory-map.js +8 -15
  5. package/dist/extensions/mega-commands.js +65 -5
  6. package/dist/extensions/mega-config.js +7 -0
  7. package/dist/extensions/mega-events/agent-handlers.js +74 -5
  8. package/dist/extensions/mega-events/context-handler.js +132 -3
  9. package/dist/extensions/mega-runtime/snapshot.js +1 -1
  10. package/dist/extensions/mega-trim.js +10 -0
  11. package/dist/src/config/turns.js +2 -1
  12. package/dist/src/memoryGraph/embedding.js +50 -0
  13. package/dist/src/memoryGraph/gates.js +379 -0
  14. package/dist/src/memoryGraph/sources.js +390 -0
  15. package/dist/src/memoryGraph.js +133 -259
  16. package/dist/src/store/sqlite/embedding-cache.js +71 -0
  17. package/dist/src/store/sqlite.js +1 -0
  18. package/dist/src/topics/cluster.js +10 -2
  19. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-DFitrb4Y.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-DFitrb4Y.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-PJrmq8pa.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-PJrmq8pa.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js +2 -0
  24. package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js.map +1 -0
  25. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-Dx-RpO8C.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-Dx-RpO8C.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-C63BEWEs.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-C63BEWEs.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-DHpJE6km.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-DHpJE6km.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js +2 -0
  32. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js.map +1 -0
  33. package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js +2 -0
  34. package/extensions/dashboard-client/dist/assets/MemoryMapTab-Cg1fgEx_.js.map +1 -0
  35. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-DCahxNOQ.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-DCahxNOQ.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-RYvO1SZL.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-RYvO1SZL.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-BDGr_UXb.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-BDGr_UXb.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-ByrTnHaB.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-ByrTnHaB.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-CELafKis.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-CELafKis.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-BLWtmfr_.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-BLWtmfr_.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js +2 -0
  48. package/extensions/dashboard-client/dist/assets/TopicsTab-8Fsh_Igx.js.map +1 -0
  49. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-D7mgXDe2.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-D7mgXDe2.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-h3jgEs6G.js} +10 -10
  52. package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-h3jgEs6G.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-C5lmipBF.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-C5lmipBF.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/index.html +1 -1
  56. package/extensions/dashboard-client/src/api/client.ts +5 -0
  57. package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
  58. package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
  59. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +29 -2
  60. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +101 -0
  61. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
  62. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
  63. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
  64. package/extensions/dashboard-server/api-contracts/index.ts +3 -1
  65. package/extensions/dashboard-server/api-contracts/maintenance.ts +13 -0
  66. package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
  67. package/extensions/dashboard-server/gather-debug-keys.ts +18 -0
  68. package/extensions/dashboard-server/gather-debug.ts +136 -0
  69. package/extensions/dashboard-server/routes-maintenance.ts +7 -0
  70. package/extensions/dashboard-server/routes-memory-map.ts +21 -29
  71. package/extensions/mega-commands.ts +63 -6
  72. package/extensions/mega-config.ts +22 -0
  73. package/extensions/mega-events/agent-handlers.ts +88 -5
  74. package/extensions/mega-events/context-handler.ts +152 -3
  75. package/extensions/mega-runtime/snapshot.ts +1 -1
  76. package/extensions/mega-trim.ts +15 -0
  77. package/package.json +1 -1
  78. package/src/config/turns.ts +5 -2
  79. package/src/memoryGraph/embedding.ts +63 -0
  80. package/src/memoryGraph/gates.ts +451 -0
  81. package/src/memoryGraph/sources.ts +484 -0
  82. package/src/memoryGraph.ts +188 -350
  83. package/src/store/sqlite/embedding-cache.ts +81 -0
  84. package/src/store/sqlite.ts +1 -0
  85. package/src/topics/cluster.ts +11 -1
  86. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +0 -2
  87. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +0 -1
  88. package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js +0 -2
  89. package/extensions/dashboard-client/dist/assets/MaintenanceTab-P4SUsvMf.js.map +0 -1
  90. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
  91. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
  92. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
  93. package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
@@ -1,16 +1,17 @@
1
1
  /**
2
- * MemoryMapTab.tsx — S46 visual memory map (force-directed graph of checkpoints).
3
- *
4
- * Fetches graph data from /api/memory-map and renders an SVG-based
5
- * force-directed layout. Uses no external graph library (no @xyflow/react).
6
- * The simulation uses Coulomb repulsion + Hooke attraction + center gravity.
2
+ * MemoryMapTab.tsx — D3 memory map with force-directed graph.
3
+ * Node shape encodes nodeType: checkpoint=filled circle, turn=hollow circle,
4
+ * turn-content=hollow+ring, memory=diamond.
5
+ * Fetches from /api/memory-map, renders SVG force layout.
7
6
  */
8
7
  import type React from "react";
9
8
  import { useEffect, useRef, useState, useCallback, useMemo } from "react";
9
+ import { renderNodeShape, NODE_COLORS, NODE_TYPE_LABELS } from "../memory-map-shapes.js";
10
+ import type { NodeType } from "../memory-map-shapes.js";
11
+ import { buildLayout, applyForces } from "../memory-map-layout.js";
12
+ import type { LayoutNode, LayoutEdge } from "../memory-map-layout.js";
10
13
 
11
- // ---------------------------------------------------------------------------
12
14
  // Types (mirror of api-contracts/memory-map.ts — client-side only)
13
- // ---------------------------------------------------------------------------
14
15
 
15
16
  interface GraphNode {
16
17
  id: string;
@@ -24,6 +25,8 @@ interface GraphNode {
24
25
  topicSummary: string | undefined;
25
26
  decisionCount: number;
26
27
  textSnippet: string;
28
+ /** Source type discriminator for UI node-shape encoding. */
29
+ nodeType: "checkpoint" | "turn" | "turn-content" | "memory";
27
30
  }
28
31
 
29
32
  interface GraphEdge {
@@ -33,64 +36,45 @@ interface GraphEdge {
33
36
  type: "temporal" | "semantic" | "raptor_parent";
34
37
  }
35
38
 
39
+ interface GraphValidationReport {
40
+ readonly gatesRun: number;
41
+ readonly gatesPassed: number;
42
+ readonly dropped: { nodes: number; edges: number };
43
+ readonly warnings: Array<{ gate: string; code: string; count: number }>;
44
+ readonly sources: {
45
+ checkpoint: number;
46
+ turn: number;
47
+ turnContent: number;
48
+ memory: number;
49
+ };
50
+ readonly builtAt: number;
51
+ }
52
+
36
53
  interface GraphMetadata {
37
- sessionCount: number;
38
- totalCheckpoints: number;
54
+ totalNodes: number;
39
55
  totalEdges: number;
40
- semanticEdgeCount: number;
41
- temporalEdgeCount: number;
42
- raptorEdgeCount: number;
43
- similarityThresholdUsed: number;
44
- builtAt: number;
56
+ avgWeight: number;
57
+ nodeTypeBreakdown: Record<string, number>;
58
+ edgeTypeBreakdown: Record<string, number>;
45
59
  }
46
60
 
47
61
  interface MemoryMapResponse {
48
62
  nodes: GraphNode[];
49
63
  edges: GraphEdge[];
50
64
  metadata: GraphMetadata;
65
+ /** Validation report from the 9-gate pipeline. Optional for backward compat. */
66
+ validation?: GraphValidationReport;
51
67
  }
52
68
 
53
- // ---------------------------------------------------------------------------
54
- // Force-directed layout types
55
- // ---------------------------------------------------------------------------
56
-
57
- interface Position {
58
- x: number;
59
- y: number;
60
- }
61
-
62
- interface LayoutNode extends GraphNode {
63
- pos: Position;
64
- vx: number;
65
- vy: number;
66
- pinned: boolean;
67
- }
69
+ // Simulation rendering constants (layout math is in memory-map-layout.ts)
68
70
 
69
- interface LayoutEdge {
70
- source: number;
71
- target: number;
72
- weight: number;
73
- type: GraphEdge["type"];
74
- }
75
-
76
- // ---------------------------------------------------------------------------
77
- // Force simulation constants
78
- // ---------------------------------------------------------------------------
79
-
80
- const REPULSION = 5_000;
81
- const ATTRACTION = 0.005;
82
- const CENTER_GRAVITY = 0.01;
83
- const DAMPING = 0.9;
84
- const MIN_VELOCITY = 0.1;
85
71
  const MAX_ITERATIONS = 300;
86
72
  const EDGE_ALPHA = 0.3;
87
73
  const SVG_WIDTH = 900;
88
74
  const SVG_HEIGHT = 600;
89
75
  const PADDING = 40;
90
76
 
91
- // ---------------------------------------------------------------------------
92
77
  // Helpers
93
- // ---------------------------------------------------------------------------
94
78
 
95
79
  function edgeColor(type: GraphEdge["type"]): string {
96
80
  switch (type) {
@@ -103,106 +87,49 @@ function edgeColor(type: GraphEdge["type"]): string {
103
87
  }
104
88
  }
105
89
 
106
- function nodeOpacity(dedupStatus: string | undefined): number {
107
- if (!dedupStatus || dedupStatus === "active") return 1;
108
- return 0.5;
109
- }
110
-
111
- // ---------------------------------------------------------------------------
112
- // Force simulation engine
113
- // ---------------------------------------------------------------------------
114
-
115
- function buildLayout(data: MemoryMapResponse): { nodes: LayoutNode[]; edges: LayoutEdge[] } {
116
- const nodes: LayoutNode[] = data.nodes.map((n, i) => {
117
- const angle = (2 * Math.PI * i) / Math.max(data.nodes.length, 1);
118
- const radius = 200;
119
- return {
120
- ...n,
121
- pos: { x: Math.cos(angle) * radius + 300, y: Math.sin(angle) * radius + 200 },
122
- vx: 0,
123
- vy: 0,
124
- pinned: false,
125
- };
126
- });
127
-
128
- const nodeIndex = new Map<string, number>();
129
- nodes.forEach((n, i) => nodeIndex.set(n.id, i));
130
-
131
- const edges: LayoutEdge[] = data.edges
132
- .map((e) => {
133
- const si = nodeIndex.get(e.source);
134
- const ti = nodeIndex.get(e.target);
135
- if (si === undefined || ti === undefined || si === ti) return null;
136
- return { source: si, target: ti, weight: e.weight, type: e.type };
137
- })
138
- .filter((e): e is LayoutEdge => e !== null);
139
-
140
- return { nodes, edges };
90
+ // Minimal legend swatch component
91
+ interface SwatchProps {
92
+ type: "node" | "edge";
93
+ color: string;
94
+ shape: "filled-circle" | "hollow-circle" | "hollow-ring" | "diamond" | "line";
95
+ label: string;
141
96
  }
142
-
143
- function applyForces(layout: { nodes: LayoutNode[]; edges: LayoutEdge[] }): void {
144
- const { nodes, edges } = layout;
145
- const n = nodes.length;
146
- if (n === 0) return;
147
-
148
- for (let i = 0; i < n; i++) {
149
- if (nodes[i].pinned) continue;
150
- let fx = 0;
151
- let fy = 0;
152
-
153
- for (let j = 0; j < n; j++) {
154
- if (i === j) continue;
155
- const dx = nodes[i].pos.x - nodes[j].pos.x;
156
- const dy = nodes[i].pos.y - nodes[j].pos.y;
157
- const distSq = dx * dx + dy * dy || 1;
158
- const force = REPULSION / distSq;
159
- const dist = Math.sqrt(distSq);
160
- fx += (dx / dist) * force;
161
- fy += (dy / dist) * force;
162
- }
163
-
164
- for (const e of edges) {
165
- let other: number;
166
- if (e.source === i) other = e.target;
167
- else if (e.target === i) other = e.source;
168
- else continue;
169
- const dx = nodes[other].pos.x - nodes[i].pos.x;
170
- const dy = nodes[other].pos.y - nodes[i].pos.y;
171
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
172
- fx += dx * ATTRACTION * e.weight;
173
- fy += dy * ATTRACTION * e.weight;
174
- }
175
-
176
- fx -= nodes[i].pos.x * CENTER_GRAVITY;
177
- fy -= nodes[i].pos.y * CENTER_GRAVITY;
178
-
179
- nodes[i].vx = (nodes[i].vx + fx) * DAMPING;
180
- nodes[i].vy = (nodes[i].vy + fy) * DAMPING;
181
-
182
- const speed = Math.sqrt(nodes[i].vx * nodes[i].vx + nodes[i].vy * nodes[i].vy);
183
- if (speed > 50) {
184
- nodes[i].vx = (nodes[i].vx / speed) * 50;
185
- nodes[i].vy = (nodes[i].vy / speed) * 50;
186
- }
187
-
188
- if (speed > MIN_VELOCITY) {
189
- nodes[i].pos.x += nodes[i].vx;
190
- nodes[i].pos.y += nodes[i].vy;
191
- }
97
+ const Swatch: React.FC<SwatchProps> = ({ color, shape, label }) => {
98
+ const base: React.CSSProperties = {
99
+ display: "inline-block",
100
+ width: "10px",
101
+ height: "10px",
102
+ verticalAlign: "middle",
103
+ marginRight: "3px",
104
+ borderRadius: shape.startsWith("hollow") || shape === "filled-circle" ? "50%" : undefined,
105
+ };
106
+ let swatch: JSX.Element;
107
+ if (shape === "line") {
108
+ return <span style={{ color, marginRight: "6px" }}>{label}</span>;
109
+ } else if (shape === "filled-circle") {
110
+ swatch = <span style={{ ...base, backgroundColor: color }} />;
111
+ } else if (shape === "hollow-circle") {
112
+ swatch = <span style={{ ...base, border: `2px solid ${color}`, backgroundColor: "transparent" }} />;
113
+ } else if (shape === "hollow-ring") {
114
+ swatch = (
115
+ <span style={{ ...base, border: `2px solid ${color}`, backgroundColor: "transparent", position: "relative" }}>
116
+ <span style={{ display: "block", width: "4px", height: "4px", borderRadius: "50%", backgroundColor: color, margin: "2px auto" }} />
117
+ </span>
118
+ );
119
+ } else {
120
+ swatch = <span style={{ ...base, clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)", backgroundColor: color }} />;
192
121
  }
193
- }
194
-
195
- // ---------------------------------------------------------------------------
196
- // Component
197
- // ---------------------------------------------------------------------------
122
+ return <><span className="memory-map-legend-item">{swatch}{label}</span> </>;
123
+ };
198
124
 
125
+ // Force simulation engine (delegated to memory-map-layout.ts)
199
126
  const MemoryMapTab: React.FC = () => {
200
127
  const [data, setData] = useState<MemoryMapResponse | null>(null);
201
128
  const [loading, setLoading] = useState<boolean>(true);
202
129
  const [error, setError] = useState<string | null>(null);
203
130
  const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
204
131
  const [searchQuery, setSearchQuery] = useState<string>("");
205
- const [filterSession, setFilterSession] = useState<string>("");
132
+ const [filterSession] = useState<string>("");
206
133
  const [frame, setFrame] = useState<number>(0);
207
134
 
208
135
  const layoutRef = useRef<{ nodes: LayoutNode[]; edges: LayoutEdge[] } | null>(null);
@@ -291,13 +218,79 @@ const MemoryMapTab: React.FC = () => {
291
218
  );
292
219
  }, [searchQuery]);
293
220
 
294
- // ---------- Loading state ----------
221
+ // Health indicator helpers (D3)
222
+
223
+ /**
224
+ * Derive graph health from validation report:
225
+ * green = all gates passed + no warnings
226
+ * yellow = warnings present but no critical gate failed
227
+ * red = a critical gate (gatesRun > gatesPassed) failed
228
+ */
229
+ const graphHealth = useMemo<{ level: "green" | "yellow" | "red"; label: string }>(() => {
230
+ const v = data?.validation;
231
+ if (!v) return { level: "yellow", label: "No validation" };
232
+ if (v.gatesPassed < v.gatesRun) return { level: "red", label: "Gate failure" };
233
+ if (v.warnings.length > 0) return { level: "yellow", label: `${v.warnings.length} warning(s)` };
234
+ return { level: "green", label: "Healthy" };
235
+ }, [data?.validation]);
236
+
237
+ const healthColor: Record<string, string> = {
238
+ green: "#22c55e",
239
+ yellow: "#eab308",
240
+ red: "#ef4444",
241
+ };
242
+
243
+ // Source availability indicators (D3)
244
+
245
+ /** Count badge fragments from validation.sources. */
246
+ const countBadge = useMemo<string>(() => {
247
+ const v = data?.validation;
248
+ if (!v) {
249
+ const totalNodes = data?.metadata.totalNodes ?? 0;
250
+ return `${totalNodes} nodes`;
251
+ }
252
+ const parts: string[] = [];
253
+ if (v.sources.checkpoint > 0) parts.push(`${v.sources.checkpoint} checkpoints`);
254
+ if (v.sources.turn > 0) parts.push(`${v.sources.turn} turns`);
255
+ if (v.sources.turnContent > 0) parts.push(`${v.sources.turnContent} turn-content`);
256
+ if (v.sources.memory > 0) parts.push(`${v.sources.memory} memories`);
257
+ if (parts.length === 0) {
258
+ parts.push(`${data?.metadata.totalNodes ?? 0} nodes`);
259
+ }
260
+ return parts.join(" · ");
261
+ }, [data?.validation, data?.metadata.totalNodes]);
262
+
263
+ /** Per-source availability: ✓ for available sources, ✗ for empty ones. */
264
+ const sourceAvail = useMemo<string>(() => {
265
+ const v = data?.validation;
266
+ if (!v) return "";
267
+ const parts: string[] = [];
268
+ parts.push(v.sources.checkpoint > 0 ? "✓ checkpoints" : "✗ checkpoints");
269
+ parts.push(v.sources.turn > 0 ? "✓ turns" : "✗ turns");
270
+ parts.push(v.sources.turnContent > 0 ? "✓ turn-content" : "✗ turn-content");
271
+ parts.push(v.sources.memory > 0 ? "✓ memories" : "✗ memories");
272
+ return parts.join(" | ");
273
+ }, [data?.validation]);
274
+
275
+ /** Whether all four sources are empty (true empty state). */
276
+ const allSourcesEmpty = useMemo<boolean>(() => {
277
+ const v = data?.validation;
278
+ if (!v) return data?.metadata.totalNodes === 0;
279
+ return (
280
+ v.sources.checkpoint === 0 &&
281
+ v.sources.turn === 0 &&
282
+ v.sources.turnContent === 0 &&
283
+ v.sources.memory === 0
284
+ );
285
+ }, [data?.validation, data?.metadata.totalNodes]);
286
+
287
+ // Loading state
295
288
 
296
289
  if (loading) {
297
290
  return <div className="memory-map-loading">Loading memory graph...</div>;
298
291
  }
299
292
 
300
- // ---------- Error state ----------
293
+ // Error state
301
294
 
302
295
  if (error) {
303
296
  return (
@@ -313,15 +306,19 @@ const MemoryMapTab: React.FC = () => {
313
306
  );
314
307
  }
315
308
 
316
- // ---------- Empty state ----------
309
+ // Empty state (D2)
317
310
 
318
- if (!data || !layoutRef.current) {
319
- return <div className="memory-map-empty">No memory data available.</div>;
311
+ if (!data || !layoutRef.current || allSourcesEmpty) {
312
+ return (
313
+ <div className="memory-map-empty">
314
+ <p>Memories appear after your first compaction. The graph shows checkpoints (compaction summaries) linked by semantic similarity and time. Run a longer session or lower the compaction tier to see it sooner.</p>
315
+ </div>
316
+ );
320
317
  }
321
318
 
322
319
  const layout = layoutRef.current;
323
320
 
324
- // ---------- Render ----------
321
+ // Render
325
322
 
326
323
  return (
327
324
  <div className="memory-map-container">
@@ -337,18 +334,48 @@ const MemoryMapTab: React.FC = () => {
337
334
  className="memory-map-search"
338
335
  />
339
336
  <span className="memory-map-stats">
340
- {data.nodes.length} memories, {data.edges.length} edges
337
+ {/* Count badge */}
338
+ <span className="memory-map-count-badge">{countBadge}</span>
341
339
  <br />
342
- Sessions: {data.metadata.sessionCount} | Threshold:{" "}
343
- {data.metadata.similarityThresholdUsed.toFixed(2)}
340
+ <span className="memory-map-edges">{data.edges.length} edges</span>
341
+ </span>
342
+ {/* Graph-health indicator */}
343
+ <span
344
+ className="memory-map-health-badge"
345
+ style={{
346
+ backgroundColor: healthColor[graphHealth.level] ?? "#6b7280",
347
+ color: "#fff",
348
+ padding: "2px 8px",
349
+ borderRadius: "10px",
350
+ fontSize: "11px",
351
+ fontWeight: 600,
352
+ marginLeft: "8px",
353
+ }}
354
+ title={`Validation: ${graphHealth.label}`}
355
+ >
356
+ {graphHealth.level === "green" ? "Healthy" : graphHealth.level === "yellow" ? "Warnings" : "Critical"}
344
357
  </span>
345
358
  </div>
346
359
 
347
- {/* Legend */}
360
+ {/* Source availability indicators */}
361
+ {sourceAvail ? (
362
+ <div className="memory-map-source-avail" style={{ fontSize: "11px", padding: "2px 12px", color: "#6b7280" }}>
363
+ {sourceAvail}
364
+ </div>
365
+ ) : null}
366
+
367
+ {/* Legend — node types + edge types */}
348
368
  <div className="memory-map-legend">
349
- <span style={{ color: edgeColor("semantic") }}>Semantic</span>
350
- <span style={{ color: edgeColor("temporal") }}>Temporal</span>
351
- <span style={{ color: edgeColor("raptor_parent") }}>Raptor</span>
369
+ <span className="memory-map-legend-section">Nodes:</span>
370
+ <Swatch type="node" color={NODE_COLORS.checkpoint} shape="filled-circle" label="Checkpoint" />
371
+ <Swatch type="node" color={NODE_COLORS.turn} shape="hollow-circle" label="Turn" />
372
+ <Swatch type="node" color={NODE_COLORS["turn-content"]} shape="hollow-ring" label="Turn Content" />
373
+ <Swatch type="node" color={NODE_COLORS.memory} shape="diamond" label="Memory" />
374
+ <span className="memory-map-legend-sep">|</span>
375
+ <span className="memory-map-legend-section">Edges:</span>
376
+ <Swatch type="edge" color={edgeColor("semantic")} shape="line" label="Semantic" />
377
+ <Swatch type="edge" color={edgeColor("temporal")} shape="line" label="Temporal" />
378
+ <Swatch type="edge" color={edgeColor("raptor_parent")} shape="line" label="Raptor" />
352
379
  </div>
353
380
 
354
381
  {/* SVG graph */}
@@ -378,7 +405,7 @@ const MemoryMapTab: React.FC = () => {
378
405
  );
379
406
  })}
380
407
 
381
- {/* Nodes */}
408
+ {/* Nodes — shape encodes nodeType */}
382
409
  {layout.nodes.map((n, i) => {
383
410
  const cx =
384
411
  PADDING + (n.pos.x / (layout.nodes.length || 1)) * (SVG_WIDTH - 2 * PADDING);
@@ -390,18 +417,13 @@ const MemoryMapTab: React.FC = () => {
390
417
  const nodeLabel = n.label.length > 20 ? n.label.slice(0, 18) + "..." : n.label;
391
418
 
392
419
  return (
393
- <g key={`node-${i}`} className="memory-map-node-group">
394
- <circle
395
- cx={cx}
396
- cy={cy}
397
- r={isSelected ? radius + 3 : radius}
398
- fill={isFiltered ? (n.raptorLevel > 0 ? "#f59e0b" : "#6366f1") : "#d1d5db"}
399
- opacity={nodeOpacity(n.dedupStatus)}
400
- stroke={isSelected ? "#ef4444" : "none"}
401
- strokeWidth={isSelected ? 2 : 0}
402
- className="memory-map-node"
403
- onClick={() => handleNodeClick(n, i)}
404
- />
420
+ <g
421
+ key={`node-${i}`}
422
+ className="memory-map-node-group"
423
+ onClick={() => handleNodeClick(n, i)}
424
+ style={{ cursor: "pointer" }}
425
+ >
426
+ {renderNodeShape(cx, cy, radius, n.nodeType as NodeType, isSelected, isFiltered, n.dedupStatus)}
405
427
  <text
406
428
  x={cx}
407
429
  y={cy + radius + 12}
@@ -420,7 +442,21 @@ const MemoryMapTab: React.FC = () => {
420
442
  {/* Node detail panel */}
421
443
  {selectedNode ? (
422
444
  <div className="memory-map-detail">
423
- <h3>{selectedNode.label}</h3>
445
+ <h3>
446
+ <span
447
+ className="memory-map-detail-type"
448
+ style={{
449
+ display: "inline-block",
450
+ fontSize: "12px",
451
+ fontWeight: 400,
452
+ color: "#6b7280",
453
+ marginRight: "6px",
454
+ }}
455
+ >
456
+ {NODE_TYPE_LABELS[selectedNode.nodeType]}
457
+ </span>
458
+ {selectedNode.label}
459
+ </h3>
424
460
  <p>{selectedNode.summaryTruncated}</p>
425
461
  {selectedNode.topicSummary ? (
426
462
  <p className="memory-map-topic">Topic: {selectedNode.topicSummary}</p>
@@ -70,6 +70,27 @@ export default function ReposTab(): React.ReactElement {
70
70
  const repos = indexData.repos;
71
71
  const servers = serversData?.servers ?? [];
72
72
 
73
+ if (repos.length === 0) {
74
+ return (
75
+ <div className="repos-tab">
76
+ <SummaryTiles
77
+ totalRepos={0}
78
+ totalCheckpoints={0}
79
+ totalTokensSaved={0}
80
+ compressedOriginalBytes={0}
81
+ />
82
+ <div className="tab-stub">
83
+ <h3>Repositories / Memory</h3>
84
+ <p>
85
+ Repo memory appears after the first compaction. Compaction fires
86
+ when context crosses the tier threshold (~50-70% of the window).
87
+ Run a longer session or lower the compaction tier to see it sooner.
88
+ </p>
89
+ </div>
90
+ </div>
91
+ );
92
+ }
93
+
73
94
  return (
74
95
  <div className="repos-tab">
75
96
  <SummaryTiles
@@ -63,7 +63,7 @@ export default function TopicsTab(): React.ReactElement {
63
63
  <div className="tab-stub">
64
64
  <h3>Wiki</h3>
65
65
  <p>
66
- No topics yet. Topics are auto-generated after every 10th compaction
66
+ No topics yet. Topics are auto-generated after every 3rd compaction
67
67
  from real memory embeddings (k-means + TF-IDF).
68
68
  </p>
69
69
  <p>Check back after a few more compaction cycles.</p>
@@ -186,6 +186,7 @@ export type {
186
186
  SchemaHealthRow,
187
187
  SchemaHealthResponse,
188
188
  MaintenanceAction,
189
+ DebugBundleResponse,
189
190
  } from "./maintenance.js";
190
191
 
191
192
  // S52 — turn-by-turn memory tracking + recall + rewind
@@ -210,12 +211,13 @@ export type { MemoryStatusResponse, MemoryStatsTopMemory } from "./memory.js";
210
211
  // Setup wizard — embedder configuration + backend detection
211
212
  export type { SetupStatusResponse, SetupDetectResponse, SetupConfigureRequest, SetupConfigureResponse, DetectResult, OllamaDetectResult } from "./setup.js";
212
213
 
213
- // S46 — memory graph (visual memory map)
214
+ // S46/D3 — memory graph (visual memory map) + validation report
214
215
  export type {
215
216
  MemoryMapResponse,
216
217
  MemoryMapNode,
217
218
  MemoryMapEdgeEntry,
218
219
  MemoryMapQuery,
220
+ GraphValidationReport,
219
221
  } from "./memory-map.js";
220
222
 
221
223
  // A3 — cache stripe distribution + health
@@ -82,3 +82,16 @@ export type MaintenanceAction =
82
82
  | { action: "reconcile-dedup" }
83
83
  | { action: "prune"; daysOld: number }
84
84
  | { action: "integrity-check" };
85
+
86
+ /** Debug bundle for gathering diagnostic info. */
87
+ export interface DebugBundleResponse {
88
+ builtAt: number;
89
+ version: string;
90
+ stateDir: string;
91
+ config: Record<string, string>;
92
+ schemaHealth: SchemaHealthResponse | { error: string };
93
+ storeStats: DbStatsResponse | { error: string };
94
+ dashboardSnapshot: unknown | { error: string };
95
+ recentEvents: unknown[];
96
+ criticalEvents: unknown[];
97
+ }
@@ -1,10 +1,11 @@
1
1
  /**
2
- * api-contracts/memory-map.ts — Memory graph API contract (S46).
2
+ * api-contracts/memory-map.ts — Memory graph API contract (S46/D3).
3
3
  *
4
- * Response shape for GET /api/memory-map: graph nodes, edges, and metadata.
5
- * This is a read-only JSON payload consumed by the MemoryMapTab React component.
4
+ * Response shape for GET /api/memory-map: graph nodes, edges, metadata,
5
+ * and validation report. Consumed by the MemoryMapTab React component.
6
6
  */
7
- import type { GraphMetadata } from "../../../src/memoryGraph.js";
7
+ import type { GraphMetadata, GraphValidationReport } from "../../../src/memoryGraph.js";
8
+ export type { GraphValidationReport } from "../../../src/memoryGraph.js";
8
9
 
9
10
  /**
10
11
  * Query parameters for GET /api/memory-map.
@@ -19,14 +20,19 @@ export interface MemoryMapQuery {
19
20
  readonly maxEdgesPerNode?: number;
20
21
  }
21
22
 
22
- /** Response body for GET /api/memory-map. */
23
+ /** Response body for GET /api/memory-map (D3: +validation). */
23
24
  export interface MemoryMapResponse {
24
25
  readonly nodes: MemoryMapNode[];
25
26
  readonly edges: MemoryMapEdgeEntry[];
26
27
  readonly metadata: GraphMetadata;
28
+ /** D3: validation report from the 9-gate pipeline. Optional for backward compat. */
29
+ readonly validation?: GraphValidationReport;
27
30
  }
28
31
 
29
- /** A node in the response (client-safe subset — no full embedding vector). */
32
+ /**
33
+ * A node in the response (client-safe subset — no full embedding vector).
34
+ * D3: +nodeType discriminator for UI node-shape rendering.
35
+ */
30
36
  export interface MemoryMapNode {
31
37
  readonly id: string;
32
38
  readonly sessionId: string;
@@ -39,6 +45,8 @@ export interface MemoryMapNode {
39
45
  readonly topicSummary: string | undefined;
40
46
  readonly decisionCount: number;
41
47
  readonly textSnippet: string;
48
+ /** D3: source type discriminator for UI node-shape encoding. */
49
+ readonly nodeType: "checkpoint" | "turn" | "turn-content" | "memory";
42
50
  }
43
51
 
44
52
  /** An edge entry in the response. */
@@ -0,0 +1,18 @@
1
+ export const MEGACOMPACT_ENV_KEYS = [
2
+ "MEGACOMPACT_AUTO",
3
+ "MEGACOMPACT_THRESHOLD_TOKENS",
4
+ "MEGACOMPACT_TIER",
5
+ "MEGACOMPACT_DBMIRROR",
6
+ "MEGACOMPACT_MESSAGE_SEPARATION",
7
+ "MEGACOMPACT_CACHE_STRIPING",
8
+ "MEGACOMPACT_LEGACY_DURABLE_TRIM",
9
+ "MEGACOMPACT_RACE_GUARD_STRICT",
10
+ "MEGACOMPACT_RECALL_TAIL_INJECT",
11
+ "MEGACOMPACT_PGLITE_DISABLED",
12
+ "MEGACOMPACT_WIKI_REBUILD_EVERY",
13
+ "MEGACOMPACT_WIKI_SEED_FROM_TURNS",
14
+ "MEGACOMPACT_MEMORY_GRAPH_SEED_TURNS",
15
+ "MEGACOMPACT_MEMORY_GRAPH_SEED_TIER",
16
+ "MEGACOMPACT_MEMORY_GRAPH_SEED_BATCH",
17
+ "MEGACOMPACT_MEMORY_GRAPH_SEED_MAX",
18
+ ];