devsmind-mcp 2.3.0 → 3.0.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 (130) hide show
  1. package/README.md +326 -234
  2. package/dist/cli/activity.d.ts +9 -0
  3. package/dist/cli/activity.js +69 -0
  4. package/dist/cli/activity.js.map +1 -0
  5. package/dist/cli/describe.d.ts +31 -0
  6. package/dist/cli/describe.js +201 -0
  7. package/dist/cli/describe.js.map +1 -0
  8. package/dist/cli/diff.d.ts +14 -0
  9. package/dist/cli/diff.js +161 -0
  10. package/dist/cli/diff.js.map +1 -0
  11. package/dist/cli/embed.d.ts +6 -0
  12. package/dist/cli/embed.js +109 -0
  13. package/dist/cli/embed.js.map +1 -0
  14. package/dist/cli/extract-agent.d.ts +66 -0
  15. package/dist/cli/extract-agent.js +192 -0
  16. package/dist/cli/extract-agent.js.map +1 -0
  17. package/dist/cli/feedback.d.ts +11 -0
  18. package/dist/cli/feedback.js +90 -0
  19. package/dist/cli/feedback.js.map +1 -0
  20. package/dist/cli/index.js +128 -5
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/init.d.ts +30 -0
  23. package/dist/cli/init.js +98 -18
  24. package/dist/cli/init.js.map +1 -1
  25. package/dist/cli/integrations/memory-topics.d.ts +44 -0
  26. package/dist/cli/integrations/memory-topics.js +295 -0
  27. package/dist/cli/integrations/memory-topics.js.map +1 -0
  28. package/dist/cli/integrations/memory.d.ts +11 -5
  29. package/dist/cli/integrations/memory.js +132 -36
  30. package/dist/cli/integrations/memory.js.map +1 -1
  31. package/dist/cli/integrations/prompt.d.ts +8 -0
  32. package/dist/cli/integrations/prompt.js +24 -3
  33. package/dist/cli/integrations/prompt.js.map +1 -1
  34. package/dist/cli/integrations/registry.d.ts +19 -5
  35. package/dist/cli/integrations/registry.js +23 -13
  36. package/dist/cli/integrations/registry.js.map +1 -1
  37. package/dist/cli/llm-client.d.ts +116 -0
  38. package/dist/cli/llm-client.js +322 -0
  39. package/dist/cli/llm-client.js.map +1 -0
  40. package/dist/cli/prune.js +4 -3
  41. package/dist/cli/prune.js.map +1 -1
  42. package/dist/cli/rule.d.ts +22 -2
  43. package/dist/cli/rule.js +116 -56
  44. package/dist/cli/rule.js.map +1 -1
  45. package/dist/cli/runner.d.ts +21 -0
  46. package/dist/cli/runner.js +94 -276
  47. package/dist/cli/runner.js.map +1 -1
  48. package/dist/cli/sync-progress.d.ts +9 -0
  49. package/dist/cli/sync-progress.js +41 -0
  50. package/dist/cli/sync-progress.js.map +1 -0
  51. package/dist/cli/sync.js +5 -2
  52. package/dist/cli/sync.js.map +1 -1
  53. package/dist/cli/view.js +3 -2
  54. package/dist/cli/view.js.map +1 -1
  55. package/dist/cli/workflow.js +26 -20
  56. package/dist/cli/workflow.js.map +1 -1
  57. package/dist/db/activity.d.ts +200 -0
  58. package/dist/db/activity.js +347 -0
  59. package/dist/db/activity.js.map +1 -0
  60. package/dist/db/analyze.js +25 -9
  61. package/dist/db/analyze.js.map +1 -1
  62. package/dist/db/database.d.ts +607 -79
  63. package/dist/db/database.js +1936 -488
  64. package/dist/db/database.js.map +1 -1
  65. package/dist/db/edges.d.ts +24 -0
  66. package/dist/db/edges.js +84 -0
  67. package/dist/db/edges.js.map +1 -1
  68. package/dist/db/embedder.d.ts +39 -0
  69. package/dist/db/embedder.js +0 -0
  70. package/dist/db/embedder.js.map +1 -0
  71. package/dist/db/feedback.d.ts +128 -0
  72. package/dist/db/feedback.js +182 -0
  73. package/dist/db/feedback.js.map +1 -0
  74. package/dist/db/file-diff.d.ts +32 -0
  75. package/dist/db/file-diff.js +110 -0
  76. package/dist/db/file-diff.js.map +1 -0
  77. package/dist/db/grep.d.ts +78 -0
  78. package/dist/db/grep.js +475 -0
  79. package/dist/db/grep.js.map +1 -0
  80. package/dist/db/message-revert.d.ts +63 -0
  81. package/dist/db/message-revert.js +258 -0
  82. package/dist/db/message-revert.js.map +1 -0
  83. package/dist/db/revert.d.ts +31 -0
  84. package/dist/db/revert.js +108 -0
  85. package/dist/db/revert.js.map +1 -0
  86. package/dist/db/schema.d.ts +49 -4
  87. package/dist/db/schema.js +125 -73
  88. package/dist/db/schema.js.map +1 -1
  89. package/dist/db/search-index.d.ts +65 -0
  90. package/dist/db/search-index.js +74 -0
  91. package/dist/db/search-index.js.map +1 -0
  92. package/dist/db/staging.d.ts +90 -5
  93. package/dist/db/staging.js +148 -22
  94. package/dist/db/staging.js.map +1 -1
  95. package/dist/mcp/server.d.ts +29 -7
  96. package/dist/mcp/server.js +2755 -745
  97. package/dist/mcp/server.js.map +1 -1
  98. package/dist/mcp/vendor/3d-force-graph.min.js +5 -0
  99. package/dist/mcp/vendor/force-graph.min.js +5 -0
  100. package/dist/mcp/vendor/model/model_int8.onnx +0 -0
  101. package/dist/mcp/vendor/model/vocab.txt +30522 -0
  102. package/dist/mcp/vendor/three.min.js +7 -0
  103. package/dist/mcp/view.css +419 -0
  104. package/dist/mcp/view.html +161 -0
  105. package/dist/mcp/view.js +245 -0
  106. package/dist/mcp/view_chat.js +382 -0
  107. package/dist/mcp/view_graph.js +576 -0
  108. package/dist/mcp/visualizer.d.ts +25 -2
  109. package/dist/mcp/visualizer.js +31 -4
  110. package/dist/mcp/visualizer.js.map +1 -1
  111. package/dist/utils/ast.d.ts +187 -0
  112. package/dist/utils/ast.js +640 -20
  113. package/dist/utils/ast.js.map +1 -1
  114. package/dist/utils/diff.d.ts +44 -0
  115. package/dist/utils/diff.js +78 -0
  116. package/dist/utils/diff.js.map +1 -0
  117. package/dist/utils/edit.d.ts +47 -0
  118. package/dist/utils/edit.js +196 -0
  119. package/dist/utils/edit.js.map +1 -0
  120. package/dist/utils/scanner.js +12 -4
  121. package/dist/utils/scanner.js.map +1 -1
  122. package/dist/utils/tokenize.d.ts +45 -0
  123. package/dist/utils/tokenize.js +129 -0
  124. package/dist/utils/tokenize.js.map +1 -0
  125. package/dist/utils/version.d.ts +14 -0
  126. package/dist/utils/version.js +61 -0
  127. package/dist/utils/version.js.map +1 -0
  128. package/package.json +18 -5
  129. package/dist/mcp/visualizer_2d.html +0 -635
  130. package/dist/mcp/visualizer_3d.html +0 -613
@@ -1,613 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>DevsMind — Code Graph Visualizer (3D)</title>
7
- <!-- Tailwind CSS -->
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <!-- Google Fonts -->
10
- <link rel="preconnect" href="https://fonts.googleapis.com">
11
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
13
-
14
- <!-- Three.js and 3D Force Graph (UMD builds — no import map needed) -->
15
- <script src="https://unpkg.com/three@0.158.0/build/three.min.js"></script>
16
- <script src="https://unpkg.com/3d-force-graph@1.73.5/dist/3d-force-graph.min.js"></script>
17
-
18
- <script>
19
- tailwind.config = {
20
- darkMode: 'class',
21
- theme: {
22
- extend: {
23
- fontFamily: {
24
- sans: ['Outfit', 'sans-serif'],
25
- mono: ['JetBrains Mono', 'monospace'],
26
- }
27
- }
28
- }
29
- }
30
- </script>
31
- <style>
32
- body {
33
- background-color: #0b0f19;
34
- color: #e2e8f0;
35
- overflow: hidden;
36
- }
37
- /* Custom Scrollbar */
38
- ::-webkit-scrollbar {
39
- width: 6px;
40
- height: 6px;
41
- }
42
- ::-webkit-scrollbar-track {
43
- background: #0f172a;
44
- }
45
- ::-webkit-scrollbar-thumb {
46
- background: #334155;
47
- border-radius: 3px;
48
- }
49
- ::-webkit-scrollbar-thumb:hover {
50
- background: #475569;
51
- }
52
- </style>
53
- </head>
54
- <body class="font-sans antialiased h-screen flex flex-col">
55
-
56
- <!-- Header -->
57
- <header class="bg-slate-900/80 backdrop-blur border-b border-slate-800 px-6 py-4 flex items-center justify-between shrink-0">
58
- <div class="flex items-center space-x-3">
59
- <div class="h-9 w-9 rounded-xl bg-gradient-to-tr from-sky-400 to-indigo-600 flex items-center justify-center font-bold text-white shadow-lg shadow-sky-500/20">
60
- 🧠
61
- </div>
62
- <div>
63
- <h1 class="text-xl font-bold bg-gradient-to-r from-sky-400 via-indigo-200 to-white bg-clip-text text-transparent">
64
- DevsMind
65
- </h1>
66
- <p class="text-xs text-slate-400 font-medium">Team AI Brain Visualizer</p>
67
- </div>
68
- </div>
69
- <div class="flex items-center space-x-3">
70
- <div class="text-xs bg-slate-800 px-3 py-1.5 rounded-lg border border-slate-700 flex items-center space-x-2 mr-2">
71
- <span class="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"></span>
72
- <span class="text-slate-300 font-medium font-mono" id="project-name-label">Loading Project...</span>
73
- </div>
74
- <!-- Mode Toggle -->
75
- <button id="view-mode-toggle" onclick="switchTo2D()" class="px-3.5 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-lg border border-slate-700 transition flex items-center space-x-1.5 text-xs font-semibold">
76
- <span>📐 Switch to 2D</span>
77
- </button>
78
- <button onclick="refreshData()" class="p-2 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-lg border border-slate-700 transition text-xs font-semibold">
79
- 🔄 Reload
80
- </button>
81
- </div>
82
- </header>
83
-
84
- <!-- Main Layout -->
85
- <div class="flex-1 flex overflow-hidden relative">
86
-
87
- <!-- Left Panel: Filters & Stats -->
88
- <aside class="w-80 bg-slate-950/80 border-r border-slate-900 flex flex-col shrink-0">
89
- <!-- Stats Summary -->
90
- <div class="p-5 border-b border-slate-900 bg-slate-900/30">
91
- <h3 class="text-sm font-semibold text-slate-400 mb-3 uppercase tracking-wider">Brain Index Stats</h3>
92
- <div class="grid grid-cols-2 gap-3">
93
- <div class="bg-slate-900/60 p-3 rounded-xl border border-slate-800/60">
94
- <p class="text-xs text-slate-400">Total Nodes</p>
95
- <p class="text-2xl font-bold text-white mt-1" id="stat-nodes">0</p>
96
- </div>
97
- <div class="bg-slate-900/60 p-3 rounded-xl border border-slate-800/60">
98
- <p class="text-xs text-slate-400">Connections</p>
99
- <p class="text-2xl font-bold text-white mt-1" id="stat-links">0</p>
100
- </div>
101
- </div>
102
- </div>
103
-
104
- <!-- Filters & Search -->
105
- <div class="flex-1 overflow-y-auto p-5 space-y-6">
106
- <!-- Node Search -->
107
- <div>
108
- <label class="text-sm font-semibold text-slate-400 block mb-2 uppercase tracking-wider">Search Nodes</label>
109
- <div class="relative">
110
- <input type="text" id="node-search" oninput="applyFilters()" placeholder="Search ID or name..."
111
- class="w-full bg-slate-900 border border-slate-800 rounded-xl px-4 py-2 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:border-sky-500 transition">
112
- </div>
113
- </div>
114
-
115
- <!-- Node Types Filter -->
116
- <div>
117
- <label class="text-sm font-semibold text-slate-400 block mb-2 uppercase tracking-wider">Node Types</label>
118
- <div class="space-y-2 bg-slate-900/40 p-3 rounded-xl border border-slate-800/40" id="type-filters">
119
- <!-- Dynamically populated -->
120
- </div>
121
- </div>
122
-
123
- <!-- Developers Filter -->
124
- <div>
125
- <label class="text-sm font-semibold text-slate-400 block mb-2 uppercase tracking-wider">Developers</label>
126
- <div class="space-y-2 bg-slate-900/40 p-3 rounded-xl border border-slate-800/40" id="dev-filters">
127
- <!-- Dynamically populated -->
128
- </div>
129
- </div>
130
- </div>
131
- </aside>
132
-
133
- <!-- Center: Interactive 3D Graph View -->
134
- <main class="flex-1 relative bg-slate-950 flex items-center justify-center">
135
- <div id="graph-container" class="absolute inset-0"></div>
136
-
137
- <!-- Instructions tooltip -->
138
- <div class="absolute top-6 left-6 text-xs text-slate-500 bg-slate-900/50 px-3 py-2 rounded-lg border border-slate-800/50 pointer-events-none z-10">
139
- 🖱️ Left Click + Drag to rotate • Right Click + Drag to pan • Scroll to zoom
140
- </div>
141
- </main>
142
-
143
- <!-- Right Panel: Node Details & Version History -->
144
- <aside class="w-96 bg-slate-950/80 border-l border-slate-900 flex flex-col shrink-0 overflow-y-auto z-10">
145
- <!-- Empty State -->
146
- <div id="details-empty" class="flex-1 flex flex-col items-center justify-center p-8 text-center">
147
- <span class="text-5xl mb-4">🔍</span>
148
- <h4 class="text-slate-300 font-semibold mb-1">No Node Selected</h4>
149
- <p class="text-xs text-slate-500 max-w-[200px]">Click any node in the graph to view its documentation and version history.</p>
150
- </div>
151
-
152
- <!-- Content State -->
153
- <div id="details-content" class="hidden flex-1 flex flex-col p-6 space-y-6">
154
- <!-- Node Header -->
155
- <div class="border-b border-slate-900 pb-5">
156
- <div class="flex items-center space-x-2.5 mb-2">
157
- <span id="details-type" class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full"></span>
158
- </div>
159
- <h2 id="details-name" class="text-lg font-bold text-white break-words"></h2>
160
- <p id="details-id" class="text-xs text-slate-500 font-mono break-all mt-1"></p>
161
- </div>
162
-
163
- <!-- File & Signature -->
164
- <div class="space-y-4">
165
- <div>
166
- <span class="text-xs font-semibold text-slate-500 block mb-1">File Location</span>
167
- <code id="details-file" class="text-xs bg-slate-900/80 px-2.5 py-1.5 rounded-lg border border-slate-800 text-slate-300 font-mono break-all block"></code>
168
- </div>
169
- <div id="details-sig-container">
170
- <span class="text-xs font-semibold text-slate-500 block mb-1">Signature</span>
171
- <code id="details-sig" class="text-xs bg-slate-900/80 px-2.5 py-1.5 rounded-lg border border-slate-800 text-slate-300 font-mono block overflow-x-auto whitespace-nowrap"></code>
172
- </div>
173
- </div>
174
-
175
- <!-- Connections (Uses & Used By) -->
176
- <div class="space-y-4">
177
- <div>
178
- <span class="text-xs font-semibold text-slate-500 block mb-1.5" id="details-uses-title">Uses (0)</span>
179
- <div id="details-uses-list" class="space-y-1 max-h-32 overflow-y-auto"></div>
180
- </div>
181
- <div>
182
- <span class="text-xs font-semibold text-slate-500 block mb-1.5" id="details-usedby-title">Used By (0)</span>
183
- <div id="details-usedby-list" class="space-y-1 max-h-32 overflow-y-auto"></div>
184
- </div>
185
- </div>
186
-
187
- <!-- Version History Log -->
188
- <div class="border-t border-slate-900 pt-5">
189
- <h3 class="text-sm font-bold text-slate-300 mb-4 flex items-center justify-between">
190
- <span>Version History</span>
191
- <span id="details-history-count" class="bg-slate-800 text-slate-400 text-xs px-2 py-0.5 rounded-full font-mono">0</span>
192
- </h3>
193
- <div id="details-history-list" class="space-y-4"></div>
194
- </div>
195
- </div>
196
- </aside>
197
-
198
- </div>
199
-
200
- <script>
201
- let graphData = null;
202
- let width = 0;
203
- let height = 0;
204
- let selectedNodeId = null;
205
- let graph3dInstance = null;
206
-
207
- // Node Type Colors
208
- const typeColors = {
209
- 'class': '#6366f1',
210
- 'service': '#8b5cf6',
211
- 'controller': '#a855f7',
212
- 'function': '#3b82f6',
213
- 'method': '#06b6d4',
214
- 'type': '#10b981',
215
- 'interface': '#10b981',
216
- 'schema': '#10b981',
217
- 'enum': '#10b981',
218
- 'api_endpoint': '#f43f5e',
219
- 'variable': '#f59e0b',
220
- 'import': '#64748b'
221
- };
222
-
223
- const defaultColor = '#64748b';
224
-
225
- function getNodeColor(type) {
226
- if (typeColors[type]) return typeColors[type];
227
- let hash = 0;
228
- for (let i = 0; i < type.length; i++) {
229
- hash = type.charCodeAt(i) + ((hash << 5) - hash);
230
- }
231
- const hue = Math.abs(hash % 360);
232
- return `hsl(${hue}, 75%, 60%)`;
233
- }
234
-
235
- const getLinkId = (endpoint) => typeof endpoint === 'object' ? endpoint.id : endpoint;
236
-
237
- const urlParams = new URLSearchParams(window.location.search);
238
- const pathParam = urlParams.get('path') || '';
239
-
240
- // Initialize Page — UMD scripts are loaded synchronously, so globals are ready on DOMContentLoaded
241
- document.addEventListener('DOMContentLoaded', () => {
242
- if (!window.ForceGraph3D) {
243
- console.error('3D Force Graph failed to load. Check CDN connectivity.');
244
- return;
245
- }
246
- init();
247
- });
248
-
249
- async function init() {
250
- await refreshData();
251
- setupResizeHandler();
252
- }
253
-
254
- // Fetch and Populate Data
255
- async function refreshData() {
256
- try {
257
- const res = await fetch(`/api/graph-data?path=${encodeURIComponent(pathParam)}`);
258
- if (!res.ok) throw new Error(await res.text());
259
- graphData = await res.json();
260
-
261
- document.getElementById('project-name-label').textContent = 'Hanooot';
262
- document.getElementById('stat-nodes').textContent = graphData.nodes.length;
263
- document.getElementById('stat-links').textContent = graphData.connections.length;
264
-
265
- populateFilters();
266
- render3DGraph();
267
- } catch (err) {
268
- alert('Failed to load brain graph data: ' + err.message);
269
- }
270
- }
271
-
272
- // Switch URL back to 2D page
273
- function switchTo2D() {
274
- window.location.href = `/?path=${encodeURIComponent(pathParam)}`;
275
- }
276
-
277
- // Populate Type & Developer Filters
278
- function populateFilters() {
279
- const typeContainer = document.getElementById('type-filters');
280
- const devContainer = document.getElementById('dev-filters');
281
-
282
- const types = [...new Set(graphData.nodes.map(n => n.type))].sort();
283
- typeContainer.innerHTML = types.map(type => {
284
- const color = getNodeColor(type);
285
- return `
286
- <label class="flex items-center space-x-3 text-sm text-slate-300 cursor-pointer hover:text-white transition">
287
- <input type="checkbox" checked value="${type}" name="node-type" onchange="applyFilters()"
288
- class="rounded bg-slate-950 border-slate-800 text-sky-500 focus:ring-0">
289
- <span class="w-2.5 h-2.5 rounded-full" style="background-color: ${color}"></span>
290
- <span class="capitalize">${type}</span>
291
- </label>
292
- `;
293
- }).join('');
294
-
295
- const developers = new Set();
296
- graphData.history.forEach(h => {
297
- const match = h.reasoning.match(/Developer:\s*(.+)/i);
298
- if (match && match[1].trim()) {
299
- developers.add(match[1].trim());
300
- }
301
- });
302
-
303
- const sortedDevs = [...developers].sort();
304
- if (sortedDevs.length > 0) {
305
- devContainer.innerHTML = sortedDevs.map(dev => `
306
- <label class="flex items-center space-x-3 text-sm text-slate-300 cursor-pointer hover:text-white transition">
307
- <input type="checkbox" checked value="${dev}" name="dev" onchange="applyFilters()"
308
- class="rounded bg-slate-950 border-slate-800 text-sky-500 focus:ring-0">
309
- <span>${dev}</span>
310
- </label>
311
- `).join('');
312
- } else {
313
- devContainer.innerHTML = '<p class="text-xs text-slate-500">No developers recorded</p>';
314
- }
315
- }
316
-
317
- // WebGL 3D Force Graph Rendering
318
- function render3DGraph() {
319
- const container = document.getElementById('graph-container');
320
- container.innerHTML = '';
321
-
322
- if (!window.ForceGraph3D) {
323
- console.error("ForceGraph3D not defined! Cannot render 3D view.");
324
- container.innerHTML = `<div class="text-sm text-slate-400 p-8">WebGL ForceGraph3D module failed to load. Try reloading the page.</div>`;
325
- return;
326
- }
327
-
328
- graph3dInstance = ForceGraph3D()(container)
329
- .backgroundColor('#0b0f19')
330
- .linkDirectionalParticles(2)
331
- .linkDirectionalParticleWidth(1.2)
332
- .linkDirectionalParticleSpeed(0.005)
333
- .linkColor(() => '#334155')
334
- .nodeLabel(node => {
335
- if (typeof THREE === 'undefined') {
336
- return `
337
- <div class="bg-slate-900/95 border border-slate-800 text-slate-200 text-xs px-2.5 py-1.5 rounded-lg shadow-lg font-sans">
338
- <b class="capitalize text-sky-400 font-mono">${node.type}</b>: ${node.name}
339
- </div>
340
- `;
341
- }
342
- return null;
343
- })
344
- .nodeThreeObject(node => {
345
- if (typeof THREE === 'undefined') {
346
- return null; // Fallback to spheres if THREE did not load
347
- }
348
- try {
349
- const canvas = document.createElement('canvas');
350
- const ctx = canvas.getContext('2d');
351
- const label = node.name;
352
- const type = node.type;
353
- const color = getNodeColor(type);
354
-
355
- ctx.font = '24px Outfit, sans-serif';
356
- const textWidth = ctx.measureText(label).width;
357
-
358
- canvas.width = textWidth + 60;
359
- canvas.height = 40;
360
-
361
- ctx.font = '24px Outfit, sans-serif';
362
- ctx.textBaseline = 'middle';
363
-
364
- // Draw circle
365
- ctx.beginPath();
366
- ctx.arc(20, 20, 10, 0, 2 * Math.PI);
367
- ctx.fillStyle = color;
368
- ctx.fill();
369
- ctx.strokeStyle = node.id === selectedNodeId ? '#f43f5e' : '#1e293b';
370
- ctx.lineWidth = node.id === selectedNodeId ? 3 : 2;
371
- ctx.stroke();
372
-
373
- // Draw text
374
- ctx.fillStyle = node.id === selectedNodeId ? '#f43f5e' : '#cbd5e1';
375
- ctx.fillText(label, 40, 20);
376
-
377
- const texture = new THREE.CanvasTexture(canvas);
378
- const material = new THREE.SpriteMaterial({ map: texture, depthWrite: false });
379
- const sprite = new THREE.Sprite(material);
380
-
381
- sprite.scale.set((textWidth + 60) / 7, 40 / 7, 1);
382
- return sprite;
383
- } catch (err) {
384
- console.warn("Failed rendering 3D node canvas sprite, falling back to sphere:", err);
385
- return null;
386
- }
387
- })
388
- .onNodeClick(node => {
389
- const distance = 80;
390
- const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
391
- graph3dInstance.cameraPosition(
392
- { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio },
393
- node,
394
- 1200
395
- );
396
- selectNode(node);
397
- });
398
-
399
- applyFilters();
400
- }
401
-
402
- function applyFilters() {
403
- if (!graphData) return;
404
-
405
- const searchQuery = document.getElementById('node-search').value.toLowerCase().trim();
406
- const checkedTypes = new Set(Array.from(document.querySelectorAll('input[name="node-type"]:checked')).map(el => el.value));
407
- const checkedDevs = new Set(Array.from(document.querySelectorAll('input[name="dev"]:checked')).map(el => el.value));
408
- const hasDevFilters = document.querySelectorAll('input[name="dev"]').length > 0;
409
-
410
- const filteredNodes = graphData.nodes.filter(n => {
411
- const matchesSearch = n.id.toLowerCase().includes(searchQuery) || n.name.toLowerCase().includes(searchQuery);
412
- if (!matchesSearch) return false;
413
- if (!checkedTypes.has(n.type)) return false;
414
-
415
- if (hasDevFilters) {
416
- const nodeHistory = graphData.history.filter(h => h.node_id === n.id);
417
- if (nodeHistory.length > 0) {
418
- const hasMatches = nodeHistory.some(h => {
419
- const match = h.reasoning.match(/Developer:\s*(.+)/i);
420
- return match && checkedDevs.has(match[1].trim());
421
- });
422
- if (!hasMatches) return false;
423
- }
424
- }
425
- return true;
426
- });
427
-
428
- const nodeMap = new Map(filteredNodes.map(n => [n.id, n]));
429
- const links = graphData.connections
430
- .filter(c => nodeMap.has(c.source_node_id) && nodeMap.has(c.target_node_id))
431
- .map(c => ({ source: c.source_node_id, target: c.target_node_id }));
432
-
433
- if (graph3dInstance) {
434
- graph3dInstance.graphData({ nodes: filteredNodes, links: links });
435
- }
436
- }
437
-
438
- function selectNode(d) {
439
- selectedNodeId = d.id;
440
-
441
- if (graph3dInstance) {
442
- // Re-trigger nodeThreeObject by refreshing the accessor — forces label/color repaint
443
- const threeObjFn = graph3dInstance.nodeThreeObject();
444
- graph3dInstance.nodeThreeObject(threeObjFn);
445
-
446
- graph3dInstance
447
- .linkWidth(l => {
448
- const src = getLinkId(l.source);
449
- const tgt = getLinkId(l.target);
450
- return src === selectedNodeId || tgt === selectedNodeId ? 2.5 : 1;
451
- })
452
- .linkColor(l => {
453
- const src = getLinkId(l.source);
454
- const tgt = getLinkId(l.target);
455
- return src === selectedNodeId || tgt === selectedNodeId ? '#38bdf8' : '#334155';
456
- })
457
- .linkDirectionalParticles(l => {
458
- const src = getLinkId(l.source);
459
- const tgt = getLinkId(l.target);
460
- return src === selectedNodeId || tgt === selectedNodeId ? 4 : 2;
461
- });
462
- }
463
-
464
- document.getElementById('details-empty').classList.add('hidden');
465
- const detailsContent = document.getElementById('details-content');
466
- detailsContent.classList.remove('hidden');
467
-
468
- const color = getNodeColor(d.type);
469
- const badge = document.getElementById('details-type');
470
- badge.textContent = d.type;
471
- badge.style.backgroundColor = color + '20';
472
- badge.style.color = color;
473
-
474
- document.getElementById('details-name').textContent = d.name;
475
- document.getElementById('details-id').textContent = d.id;
476
- document.getElementById('details-file').textContent = d.file_path;
477
-
478
- const sigContainer = document.getElementById('details-sig-container');
479
- if (d.signature) {
480
- sigContainer.classList.remove('hidden');
481
- document.getElementById('details-sig').textContent = d.signature;
482
- } else {
483
- sigContainer.classList.add('hidden');
484
- }
485
-
486
- const uses = [];
487
- const usedBy = [];
488
-
489
- graphData.connections.forEach(c => {
490
- if (c.source_node_id === d.id) {
491
- const target = graphData.nodes.find(n => n.id === c.target_node_id);
492
- if (target) uses.push(target);
493
- } else if (c.target_node_id === d.id) {
494
- const source = graphData.nodes.find(n => n.id === c.source_node_id);
495
- if (source) usedBy.push(source);
496
- }
497
- });
498
-
499
- document.getElementById('details-uses-title').textContent = `Uses (${uses.length})`;
500
- document.getElementById('details-uses-list').innerHTML = uses.map(n => `
501
- <button onclick="zoomAndSelect('${n.id}')" class="text-xs text-left w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 hover:border-slate-700 hover:text-white rounded-lg transition break-all font-mono">
502
- <span class="w-1.5 h-1.5 rounded-full inline-block mr-1.5" style="background-color: ${getNodeColor(n.type)}"></span>
503
- ${n.id}
504
- </button>
505
- `).join('') || '<p class="text-xs text-slate-600 italic px-1">None</p>';
506
-
507
- document.getElementById('details-usedby-title').textContent = `Used By (${usedBy.length})`;
508
- document.getElementById('details-usedby-list').innerHTML = usedBy.map(n => `
509
- <button onclick="zoomAndSelect('${n.id}')" class="text-xs text-left w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 hover:border-slate-700 hover:text-white rounded-lg transition break-all font-mono">
510
- <span class="w-1.5 h-1.5 rounded-full inline-block mr-1.5" style="background-color: ${getNodeColor(n.type)}"></span>
511
- ${n.id}
512
- </button>
513
- `).join('') || '<p class="text-xs text-slate-600 italic px-1">None</p>';
514
-
515
- const nodeHistory = graphData.history.filter(h => h.node_id === d.id);
516
- document.getElementById('details-history-count').textContent = nodeHistory.length;
517
-
518
- document.getElementById('details-history-list').innerHTML = nodeHistory.map(h => {
519
- const lines = h.reasoning.split('\n');
520
- const pairs = {};
521
- lines.forEach(l => {
522
- const splitIdx = l.indexOf(':');
523
- if (splitIdx !== -1) {
524
- const k = l.substring(0, splitIdx).trim().toLowerCase().replace(/\s+/g, '_');
525
- const v = l.substring(splitIdx + 1).trim();
526
- pairs[k] = v;
527
- }
528
- });
529
-
530
- const what = pairs['what_changed'] || 'Code modified';
531
- const why = pairs['why'] || '';
532
- const goal = pairs['goal'] || '';
533
- const devName = pairs['developer'] || 'System';
534
- const formattedDate = new Date(h.updated_at).toLocaleString();
535
-
536
- return `
537
- <div class="bg-slate-900/60 border border-slate-800 p-4 rounded-xl space-y-3 relative overflow-hidden">
538
- <div class="flex items-center justify-between">
539
- <span class="text-[10px] font-semibold text-slate-400 font-mono">${formattedDate}</span>
540
- <span class="text-[9px] bg-slate-800/80 border border-slate-700/50 text-slate-400 px-2 py-0.5 rounded-full font-mono">${devName}</span>
541
- </div>
542
-
543
- <div class="space-y-1">
544
- <p class="text-xs font-semibold text-slate-200">${what}</p>
545
- ${why ? `<p class="text-xs text-slate-400"><span class="text-slate-500 font-medium">Why:</span> ${why}</p>` : ''}
546
- ${goal ? `<p class="text-xs text-slate-400"><span class="text-slate-500 font-medium">Goal:</span> ${goal}</p>` : ''}
547
- </div>
548
-
549
- <div>
550
- <button onclick="toggleCode('code-${h.id}')" class="text-[10px] font-bold text-sky-400 hover:text-sky-300 flex items-center space-x-1">
551
- <span>View Code Snapshot</span>
552
- </button>
553
- <div id="code-${h.id}" class="hidden mt-2 bg-slate-950 p-2.5 rounded-lg border border-slate-800 text-[10px] font-mono text-slate-300 max-h-48 overflow-y-auto whitespace-pre-wrap select-text">${escapeHtml(h.code_snapshot)}</div>
554
- </div>
555
- </div>
556
- `;
557
- }).join('') || '<p class="text-xs text-slate-500 italic">No history log recorded for this node.</p>';
558
- }
559
-
560
- function toggleCode(id) {
561
- const codeBlock = document.getElementById(id);
562
- codeBlock.classList.toggle('hidden');
563
- }
564
-
565
- function escapeHtml(text) {
566
- return text
567
- .replace(/&/g, "&amp;")
568
- .replace(/</g, "&lt;")
569
- .replace(/>/g, "&gt;")
570
- .replace(/"/g, "&quot;")
571
- .replace(/'/g, "&#039;");
572
- }
573
-
574
- function zoomAndSelect(nodeId) {
575
- if (!graphData) return;
576
- const target = graph3dInstance.graphData().nodes.find(n => n.id === nodeId);
577
- if (!target) return;
578
-
579
- selectNode(target);
580
-
581
- // Guard against NaN when simulation hasn't settled yet (x/y/z may be undefined)
582
- const nx = target.x || 0;
583
- const ny = target.y || 0;
584
- const nz = target.z || 0;
585
- const distance = 80;
586
- const hypot = Math.hypot(nx, ny, nz);
587
- const distRatio = hypot > 0 ? 1 + distance / hypot : 1;
588
- graph3dInstance.cameraPosition(
589
- { x: nx * distRatio, y: ny * distRatio, z: nz * distRatio },
590
- target,
591
- 1200
592
- );
593
- }
594
-
595
- function resetZoom() {
596
- if (graph3dInstance) {
597
- graph3dInstance.zoomToFit(600);
598
- }
599
- }
600
-
601
- function setupResizeHandler() {
602
- window.addEventListener('resize', () => {
603
- const container = document.getElementById('graph-container');
604
- width = container.clientWidth;
605
- height = container.clientHeight;
606
- if (graph3dInstance) {
607
- graph3dInstance.width(width).height(height);
608
- }
609
- });
610
- }
611
- </script>
612
- </body>
613
- </html>