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