@rangerchaz/aimem 0.1.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/dist/cli/commands/git.d.ts +6 -0
  4. package/dist/cli/commands/git.d.ts.map +1 -0
  5. package/dist/cli/commands/git.js +298 -0
  6. package/dist/cli/commands/git.js.map +1 -0
  7. package/dist/cli/commands/hook-session-end.d.ts +7 -0
  8. package/dist/cli/commands/hook-session-end.d.ts.map +1 -0
  9. package/dist/cli/commands/hook-session-end.js +109 -0
  10. package/dist/cli/commands/hook-session-end.js.map +1 -0
  11. package/dist/cli/commands/hook-session-start.d.ts +7 -0
  12. package/dist/cli/commands/hook-session-start.d.ts.map +1 -0
  13. package/dist/cli/commands/hook-session-start.js +116 -0
  14. package/dist/cli/commands/hook-session-start.js.map +1 -0
  15. package/dist/cli/commands/import.d.ts +14 -0
  16. package/dist/cli/commands/import.d.ts.map +1 -0
  17. package/dist/cli/commands/import.js +527 -0
  18. package/dist/cli/commands/import.js.map +1 -0
  19. package/dist/cli/commands/init.d.ts +2 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -0
  21. package/dist/cli/commands/init.js +32 -0
  22. package/dist/cli/commands/init.js.map +1 -0
  23. package/dist/cli/commands/mcp-serve.d.ts +2 -0
  24. package/dist/cli/commands/mcp-serve.d.ts.map +1 -0
  25. package/dist/cli/commands/mcp-serve.js +5 -0
  26. package/dist/cli/commands/mcp-serve.js.map +1 -0
  27. package/dist/cli/commands/query.d.ts +8 -0
  28. package/dist/cli/commands/query.d.ts.map +1 -0
  29. package/dist/cli/commands/query.js +83 -0
  30. package/dist/cli/commands/query.js.map +1 -0
  31. package/dist/cli/commands/setup.d.ts +10 -0
  32. package/dist/cli/commands/setup.d.ts.map +1 -0
  33. package/dist/cli/commands/setup.js +504 -0
  34. package/dist/cli/commands/setup.js.map +1 -0
  35. package/dist/cli/commands/start.d.ts +8 -0
  36. package/dist/cli/commands/start.d.ts.map +1 -0
  37. package/dist/cli/commands/start.js +90 -0
  38. package/dist/cli/commands/start.js.map +1 -0
  39. package/dist/cli/commands/status.d.ts +2 -0
  40. package/dist/cli/commands/status.d.ts.map +1 -0
  41. package/dist/cli/commands/status.js +85 -0
  42. package/dist/cli/commands/status.js.map +1 -0
  43. package/dist/cli/commands/stop.d.ts +7 -0
  44. package/dist/cli/commands/stop.d.ts.map +1 -0
  45. package/dist/cli/commands/stop.js +46 -0
  46. package/dist/cli/commands/stop.js.map +1 -0
  47. package/dist/cli/commands/visualize.d.ts +8 -0
  48. package/dist/cli/commands/visualize.d.ts.map +1 -0
  49. package/dist/cli/commands/visualize.js +96 -0
  50. package/dist/cli/commands/visualize.js.map +1 -0
  51. package/dist/cli/index.d.ts +3 -0
  52. package/dist/cli/index.d.ts.map +1 -0
  53. package/dist/cli/index.js +114 -0
  54. package/dist/cli/index.js.map +1 -0
  55. package/dist/db/index.d.ts +55 -0
  56. package/dist/db/index.d.ts.map +1 -0
  57. package/dist/db/index.js +464 -0
  58. package/dist/db/index.js.map +1 -0
  59. package/dist/db/schema.d.ts +4 -0
  60. package/dist/db/schema.d.ts.map +1 -0
  61. package/dist/db/schema.js +200 -0
  62. package/dist/db/schema.js.map +1 -0
  63. package/dist/extractor/index.d.ts +27 -0
  64. package/dist/extractor/index.d.ts.map +1 -0
  65. package/dist/extractor/index.js +227 -0
  66. package/dist/extractor/index.js.map +1 -0
  67. package/dist/git/extractor.d.ts +30 -0
  68. package/dist/git/extractor.d.ts.map +1 -0
  69. package/dist/git/extractor.js +126 -0
  70. package/dist/git/extractor.js.map +1 -0
  71. package/dist/git/hooks.d.ts +36 -0
  72. package/dist/git/hooks.d.ts.map +1 -0
  73. package/dist/git/hooks.js +142 -0
  74. package/dist/git/hooks.js.map +1 -0
  75. package/dist/git/index.d.ts +69 -0
  76. package/dist/git/index.d.ts.map +1 -0
  77. package/dist/git/index.js +250 -0
  78. package/dist/git/index.js.map +1 -0
  79. package/dist/indexer/index.d.ts +20 -0
  80. package/dist/indexer/index.d.ts.map +1 -0
  81. package/dist/indexer/index.js +173 -0
  82. package/dist/indexer/index.js.map +1 -0
  83. package/dist/indexer/parsers/base.d.ts +19 -0
  84. package/dist/indexer/parsers/base.d.ts.map +1 -0
  85. package/dist/indexer/parsers/base.js +46 -0
  86. package/dist/indexer/parsers/base.js.map +1 -0
  87. package/dist/indexer/parsers/cpp.d.ts +3 -0
  88. package/dist/indexer/parsers/cpp.d.ts.map +1 -0
  89. package/dist/indexer/parsers/cpp.js +180 -0
  90. package/dist/indexer/parsers/cpp.js.map +1 -0
  91. package/dist/indexer/parsers/go.d.ts +3 -0
  92. package/dist/indexer/parsers/go.d.ts.map +1 -0
  93. package/dist/indexer/parsers/go.js +98 -0
  94. package/dist/indexer/parsers/go.js.map +1 -0
  95. package/dist/indexer/parsers/java.d.ts +3 -0
  96. package/dist/indexer/parsers/java.d.ts.map +1 -0
  97. package/dist/indexer/parsers/java.js +204 -0
  98. package/dist/indexer/parsers/java.js.map +1 -0
  99. package/dist/indexer/parsers/javascript.d.ts +3 -0
  100. package/dist/indexer/parsers/javascript.d.ts.map +1 -0
  101. package/dist/indexer/parsers/javascript.js +157 -0
  102. package/dist/indexer/parsers/javascript.js.map +1 -0
  103. package/dist/indexer/parsers/kotlin.d.ts +3 -0
  104. package/dist/indexer/parsers/kotlin.d.ts.map +1 -0
  105. package/dist/indexer/parsers/kotlin.js +182 -0
  106. package/dist/indexer/parsers/kotlin.js.map +1 -0
  107. package/dist/indexer/parsers/php.d.ts +3 -0
  108. package/dist/indexer/parsers/php.d.ts.map +1 -0
  109. package/dist/indexer/parsers/php.js +190 -0
  110. package/dist/indexer/parsers/php.js.map +1 -0
  111. package/dist/indexer/parsers/python.d.ts +3 -0
  112. package/dist/indexer/parsers/python.d.ts.map +1 -0
  113. package/dist/indexer/parsers/python.js +101 -0
  114. package/dist/indexer/parsers/python.js.map +1 -0
  115. package/dist/indexer/parsers/ruby.d.ts +3 -0
  116. package/dist/indexer/parsers/ruby.d.ts.map +1 -0
  117. package/dist/indexer/parsers/ruby.js +92 -0
  118. package/dist/indexer/parsers/ruby.js.map +1 -0
  119. package/dist/indexer/parsers/rust.d.ts +3 -0
  120. package/dist/indexer/parsers/rust.d.ts.map +1 -0
  121. package/dist/indexer/parsers/rust.js +190 -0
  122. package/dist/indexer/parsers/rust.js.map +1 -0
  123. package/dist/indexer/watcher-daemon.d.ts +2 -0
  124. package/dist/indexer/watcher-daemon.d.ts.map +1 -0
  125. package/dist/indexer/watcher-daemon.js +27 -0
  126. package/dist/indexer/watcher-daemon.js.map +1 -0
  127. package/dist/indexer/watcher.d.ts +7 -0
  128. package/dist/indexer/watcher.d.ts.map +1 -0
  129. package/dist/indexer/watcher.js +77 -0
  130. package/dist/indexer/watcher.js.map +1 -0
  131. package/dist/mcp/server.d.ts +2 -0
  132. package/dist/mcp/server.d.ts.map +1 -0
  133. package/dist/mcp/server.js +241 -0
  134. package/dist/mcp/server.js.map +1 -0
  135. package/dist/proxy/interceptor-mockttp.d.ts +27 -0
  136. package/dist/proxy/interceptor-mockttp.d.ts.map +1 -0
  137. package/dist/proxy/interceptor-mockttp.js +274 -0
  138. package/dist/proxy/interceptor-mockttp.js.map +1 -0
  139. package/dist/proxy/proxy-daemon.d.ts +5 -0
  140. package/dist/proxy/proxy-daemon.d.ts.map +1 -0
  141. package/dist/proxy/proxy-daemon.js +26 -0
  142. package/dist/proxy/proxy-daemon.js.map +1 -0
  143. package/dist/query/index.d.ts +32 -0
  144. package/dist/query/index.d.ts.map +1 -0
  145. package/dist/query/index.js +135 -0
  146. package/dist/query/index.js.map +1 -0
  147. package/dist/types/index.d.ts +89 -0
  148. package/dist/types/index.d.ts.map +1 -0
  149. package/dist/types/index.js +3 -0
  150. package/dist/types/index.js.map +1 -0
  151. package/dist/visualize/index.d.ts +144 -0
  152. package/dist/visualize/index.d.ts.map +1 -0
  153. package/dist/visualize/index.js +707 -0
  154. package/dist/visualize/index.js.map +1 -0
  155. package/dist/visualize/server.d.ts +7 -0
  156. package/dist/visualize/server.d.ts.map +1 -0
  157. package/dist/visualize/server.js +77 -0
  158. package/dist/visualize/server.js.map +1 -0
  159. package/dist/visualize/template.d.ts +3 -0
  160. package/dist/visualize/template.d.ts.map +1 -0
  161. package/dist/visualize/template.js +3465 -0
  162. package/dist/visualize/template.js.map +1 -0
  163. package/package.json +56 -0
@@ -0,0 +1,707 @@
1
+ import { generateDashboardHTML } from './template.js';
2
+ // Build graph data from database records
3
+ export function buildVisualizationData(project, structures, files, links, extractions, conversations = []) {
4
+ // Count structures by type
5
+ const byType = {};
6
+ for (const s of structures) {
7
+ byType[s.type] = (byType[s.type] || 0) + 1;
8
+ }
9
+ return {
10
+ project,
11
+ graphs: {
12
+ overview: buildOverviewGraph(structures, files),
13
+ callGraph: buildCallGraph(structures, links),
14
+ dependencies: buildDependencyGraph(structures, files),
15
+ classes: buildClassGraph(structures, links),
16
+ decisions: buildDecisionGraph(structures, extractions, links),
17
+ },
18
+ // New visualization data
19
+ smells: buildCodeSmellsData(structures, files, links),
20
+ hotspots: buildHotspotsData(structures, links),
21
+ gallery: buildGalleryData(extractions, links, structures, conversations),
22
+ timeline: buildTimelineData(conversations, links, structures, extractions),
23
+ treemap: buildTreemapData(structures),
24
+ stats: {
25
+ totalStructures: structures.length,
26
+ totalFiles: files.length,
27
+ totalLinks: links.length,
28
+ totalDecisions: extractions.filter(e => e.type === 'decision').length,
29
+ totalConversations: conversations.length,
30
+ byType,
31
+ },
32
+ };
33
+ }
34
+ // Overview: Files only (cleaner view) - structures shown on click via details panel
35
+ function buildOverviewGraph(structures, files) {
36
+ const nodes = [];
37
+ const edges = [];
38
+ const fileMap = new Map();
39
+ // Count structures per file
40
+ for (const s of structures) {
41
+ if (!fileMap.has(s.file_path)) {
42
+ fileMap.set(s.file_path, { count: 0, types: new Set() });
43
+ }
44
+ const info = fileMap.get(s.file_path);
45
+ info.count++;
46
+ info.types.add(s.type);
47
+ }
48
+ // Group files by directory for edges
49
+ const dirMap = new Map();
50
+ for (const filePath of fileMap.keys()) {
51
+ const dir = getDirectory(filePath);
52
+ if (!dirMap.has(dir)) {
53
+ dirMap.set(dir, []);
54
+ }
55
+ dirMap.get(dir).push(filePath);
56
+ }
57
+ // Add file nodes with structure counts
58
+ for (const [filePath, info] of fileMap) {
59
+ nodes.push({
60
+ data: {
61
+ id: `file:${filePath}`,
62
+ label: `${getFileName(filePath)} (${info.count})`,
63
+ type: 'file',
64
+ file: filePath,
65
+ content: `Contains ${info.count} structures: ${Array.from(info.types).join(', ')}`,
66
+ },
67
+ });
68
+ }
69
+ return { nodes, edges };
70
+ }
71
+ // Call graph: Function/method call relationships
72
+ function buildCallGraph(structures, links) {
73
+ const nodes = [];
74
+ const edges = [];
75
+ const structureMap = new Map();
76
+ // Build lookup map for functions/methods only
77
+ for (const s of structures) {
78
+ if (s.type === 'function' || s.type === 'method') {
79
+ structureMap.set(s.id, s);
80
+ }
81
+ }
82
+ // Collect which structures are involved in calls
83
+ const includedIds = new Set();
84
+ // Find call relationships - only include if both source and target exist
85
+ for (const link of links) {
86
+ if ((link.link_type === 'calls' || link.link_type === 'called_by') &&
87
+ link.source_type === 'structure' && link.target_type === 'structure') {
88
+ const sourceId = link.link_type === 'calls' ? link.source_id : link.target_id;
89
+ const targetId = link.link_type === 'calls' ? link.target_id : link.source_id;
90
+ // Only add edge if both nodes exist in our map
91
+ if (structureMap.has(sourceId) && structureMap.has(targetId) && sourceId !== targetId) {
92
+ includedIds.add(sourceId);
93
+ includedIds.add(targetId);
94
+ edges.push({
95
+ data: {
96
+ id: `call:${sourceId}:${targetId}`,
97
+ source: `structure:${sourceId}`,
98
+ target: `structure:${targetId}`,
99
+ type: 'calls',
100
+ label: 'calls',
101
+ },
102
+ });
103
+ }
104
+ }
105
+ }
106
+ // Calculate connection weights for each node
107
+ const connectionCount = new Map();
108
+ for (const edge of edges) {
109
+ const sourceId = parseInt(edge.data.source.replace('structure:', ''));
110
+ const targetId = parseInt(edge.data.target.replace('structure:', ''));
111
+ connectionCount.set(sourceId, (connectionCount.get(sourceId) || 0) + 1);
112
+ connectionCount.set(targetId, (connectionCount.get(targetId) || 0) + 1);
113
+ }
114
+ // Add nodes for structures involved in calls
115
+ for (const id of includedIds) {
116
+ const s = structureMap.get(id);
117
+ const connections = connectionCount.get(id) || 0;
118
+ nodes.push({
119
+ data: {
120
+ id: `structure:${s.id}`,
121
+ label: s.name,
122
+ type: s.type,
123
+ file: s.file_path,
124
+ line: s.line_start,
125
+ signature: s.signature || undefined,
126
+ content: s.raw_content,
127
+ weight: connections, // Used for node sizing
128
+ },
129
+ });
130
+ }
131
+ // If no call links, show all functions/methods
132
+ if (nodes.length === 0) {
133
+ for (const s of structures) {
134
+ if (s.type === 'function' || s.type === 'method') {
135
+ nodes.push({
136
+ data: {
137
+ id: `structure:${s.id}`,
138
+ label: s.name,
139
+ type: s.type,
140
+ file: s.file_path,
141
+ line: s.line_start,
142
+ signature: s.signature || undefined,
143
+ content: s.raw_content,
144
+ },
145
+ });
146
+ }
147
+ }
148
+ }
149
+ return { nodes, edges };
150
+ }
151
+ // Dependency graph: File-level relationships
152
+ function buildDependencyGraph(structures, files) {
153
+ const nodes = [];
154
+ const edges = [];
155
+ const fileSet = new Set();
156
+ // Get unique files from structures
157
+ for (const s of structures) {
158
+ fileSet.add(s.file_path);
159
+ }
160
+ // Add file nodes
161
+ for (const filePath of fileSet) {
162
+ nodes.push({
163
+ data: {
164
+ id: `file:${filePath}`,
165
+ label: getFileName(filePath),
166
+ type: 'file',
167
+ file: filePath,
168
+ },
169
+ });
170
+ }
171
+ // For now, just show files - import analysis would require parsing
172
+ return { nodes, edges };
173
+ }
174
+ // Class hierarchy: Classes and their methods
175
+ function buildClassGraph(structures, links) {
176
+ const nodes = [];
177
+ const edges = [];
178
+ const classIds = new Set();
179
+ const structureMap = new Map();
180
+ for (const s of structures) {
181
+ structureMap.set(s.id, s);
182
+ }
183
+ // Find classes and interfaces
184
+ for (const s of structures) {
185
+ if (s.type === 'class' || s.type === 'interface') {
186
+ classIds.add(s.id);
187
+ nodes.push({
188
+ data: {
189
+ id: `structure:${s.id}`,
190
+ label: s.name,
191
+ type: s.type,
192
+ file: s.file_path,
193
+ line: s.line_start,
194
+ signature: s.signature || undefined,
195
+ content: s.raw_content,
196
+ },
197
+ });
198
+ }
199
+ }
200
+ // Find methods that belong to classes (by file proximity or metadata)
201
+ // This is a simplified approach - methods appear near their class
202
+ for (const s of structures) {
203
+ if (s.type === 'method') {
204
+ nodes.push({
205
+ data: {
206
+ id: `structure:${s.id}`,
207
+ label: s.name,
208
+ type: s.type,
209
+ file: s.file_path,
210
+ line: s.line_start,
211
+ signature: s.signature || undefined,
212
+ content: s.raw_content,
213
+ },
214
+ });
215
+ }
216
+ }
217
+ return { nodes, edges };
218
+ }
219
+ // Decision graph: Decisions and affected code
220
+ function buildDecisionGraph(structures, extractions, links) {
221
+ const nodes = [];
222
+ const edges = [];
223
+ const structureMap = new Map();
224
+ const affectedStructures = new Set();
225
+ for (const s of structures) {
226
+ structureMap.set(s.id, s);
227
+ }
228
+ // Add extraction nodes (decisions, patterns, rejections)
229
+ for (const e of extractions) {
230
+ if (e.type === 'decision' || e.type === 'pattern' || e.type === 'rejection') {
231
+ nodes.push({
232
+ data: {
233
+ id: `extraction:${e.id}`,
234
+ label: truncate(e.content, 50),
235
+ type: e.type,
236
+ content: e.content,
237
+ },
238
+ });
239
+ }
240
+ }
241
+ // Find links between extractions and structures
242
+ for (const link of links) {
243
+ if (link.source_type === 'extraction' && link.target_type === 'structure') {
244
+ affectedStructures.add(link.target_id);
245
+ edges.push({
246
+ data: {
247
+ id: `decision-link:${link.source_id}:${link.target_id}`,
248
+ source: `extraction:${link.source_id}`,
249
+ target: `structure:${link.target_id}`,
250
+ type: link.link_type,
251
+ label: link.link_type,
252
+ },
253
+ });
254
+ }
255
+ }
256
+ // Add structure nodes that are affected by decisions
257
+ for (const id of affectedStructures) {
258
+ const s = structureMap.get(id);
259
+ if (s) {
260
+ nodes.push({
261
+ data: {
262
+ id: `structure:${s.id}`,
263
+ label: s.name,
264
+ type: s.type,
265
+ file: s.file_path,
266
+ line: s.line_start,
267
+ content: s.raw_content,
268
+ },
269
+ });
270
+ }
271
+ }
272
+ return { nodes, edges };
273
+ }
274
+ // Helper: Get filename from path
275
+ function getFileName(filePath) {
276
+ const parts = filePath.split(/[/\\]/);
277
+ return parts[parts.length - 1] || filePath;
278
+ }
279
+ // Helper: Get directory from path
280
+ function getDirectory(filePath) {
281
+ const parts = filePath.split(/[/\\]/);
282
+ parts.pop();
283
+ return parts.join('/') || '.';
284
+ }
285
+ // Helper: Truncate string
286
+ function truncate(str, maxLen) {
287
+ if (str.length <= maxLen)
288
+ return str;
289
+ return str.slice(0, maxLen - 3) + '...';
290
+ }
291
+ // ============================================
292
+ // Code Smells Builder
293
+ // ============================================
294
+ function buildCodeSmellsData(structures, files, links) {
295
+ const thresholds = {
296
+ largeFile: 10, // >10 structures in a file
297
+ longFunction: 100, // >100 lines
298
+ tooManyCallers: 10, // >10 inbound calls
299
+ tooManyCallees: 10, // >10 outbound calls
300
+ };
301
+ const smells = [];
302
+ // Count structures per file
303
+ const structuresPerFile = new Map();
304
+ for (const s of structures) {
305
+ structuresPerFile.set(s.file_path, (structuresPerFile.get(s.file_path) || 0) + 1);
306
+ }
307
+ // Check for large files
308
+ for (const [filePath, count] of structuresPerFile) {
309
+ if (count > thresholds.largeFile) {
310
+ const severity = count > thresholds.largeFile * 2 ? 'high' : count > thresholds.largeFile * 1.5 ? 'medium' : 'low';
311
+ smells.push({
312
+ type: 'large-file',
313
+ severity,
314
+ filePath,
315
+ name: getFileName(filePath),
316
+ metric: count,
317
+ threshold: thresholds.largeFile,
318
+ description: `File has ${count} structures (threshold: ${thresholds.largeFile})`,
319
+ });
320
+ }
321
+ }
322
+ // Count inbound/outbound links per structure
323
+ const inboundCount = new Map();
324
+ const outboundCount = new Map();
325
+ for (const link of links) {
326
+ if (link.link_type === 'calls' && link.source_type === 'structure' && link.target_type === 'structure') {
327
+ outboundCount.set(link.source_id, (outboundCount.get(link.source_id) || 0) + 1);
328
+ inboundCount.set(link.target_id, (inboundCount.get(link.target_id) || 0) + 1);
329
+ }
330
+ }
331
+ // Build structure map for lookups
332
+ const structureMap = new Map();
333
+ for (const s of structures) {
334
+ structureMap.set(s.id, s);
335
+ }
336
+ // Check each structure for smells
337
+ for (const s of structures) {
338
+ if (s.type !== 'function' && s.type !== 'method')
339
+ continue;
340
+ const lines = (s.line_end || s.line_start) - s.line_start + 1;
341
+ const inbound = inboundCount.get(s.id) || 0;
342
+ const outbound = outboundCount.get(s.id) || 0;
343
+ // Long function
344
+ if (lines > thresholds.longFunction) {
345
+ const severity = lines > thresholds.longFunction * 2 ? 'high' : lines > thresholds.longFunction * 1.5 ? 'medium' : 'low';
346
+ smells.push({
347
+ type: 'long-function',
348
+ severity,
349
+ structureId: s.id,
350
+ filePath: s.file_path,
351
+ name: s.name,
352
+ metric: lines,
353
+ threshold: thresholds.longFunction,
354
+ description: `Function has ${lines} lines (threshold: ${thresholds.longFunction})`,
355
+ });
356
+ }
357
+ // Too many callers
358
+ if (inbound > thresholds.tooManyCallers) {
359
+ const severity = inbound > thresholds.tooManyCallers * 2 ? 'high' : inbound > thresholds.tooManyCallers * 1.5 ? 'medium' : 'low';
360
+ smells.push({
361
+ type: 'too-many-callers',
362
+ severity,
363
+ structureId: s.id,
364
+ filePath: s.file_path,
365
+ name: s.name,
366
+ metric: inbound,
367
+ threshold: thresholds.tooManyCallers,
368
+ description: `Function is called by ${inbound} others (threshold: ${thresholds.tooManyCallers})`,
369
+ });
370
+ }
371
+ // Too many callees
372
+ if (outbound > thresholds.tooManyCallees) {
373
+ const severity = outbound > thresholds.tooManyCallees * 2 ? 'high' : outbound > thresholds.tooManyCallees * 1.5 ? 'medium' : 'low';
374
+ smells.push({
375
+ type: 'too-many-callees',
376
+ severity,
377
+ structureId: s.id,
378
+ filePath: s.file_path,
379
+ name: s.name,
380
+ metric: outbound,
381
+ threshold: thresholds.tooManyCallees,
382
+ description: `Function calls ${outbound} others (threshold: ${thresholds.tooManyCallees})`,
383
+ });
384
+ }
385
+ // Orphan code (not called by anything, and not an entry point)
386
+ // Note: Orphan detection has limited accuracy - cross-file and intra-file calls may not be tracked
387
+ const isEntryPoint = outbound > 0 && inbound === 0;
388
+ if (inbound === 0 && outbound === 0) {
389
+ // Skip likely false positives
390
+ const filePath = s.file_path.toLowerCase();
391
+ const funcName = s.name;
392
+ // Skip test files and fixtures
393
+ if (filePath.includes('/test/') || filePath.includes('.test.') || filePath.includes('.spec.') ||
394
+ filePath.includes('/fixtures/') || filePath.includes('/__tests__/')) {
395
+ continue;
396
+ }
397
+ // Skip build output directories (check both /dist/ and paths starting with dist/)
398
+ if (filePath.includes('/dist/') || filePath.includes('/dist-test/') || filePath.includes('/build/') ||
399
+ filePath.includes('/node_modules/') || filePath.startsWith('dist/') || filePath.startsWith('dist-test/')) {
400
+ continue;
401
+ }
402
+ // Skip common framework hooks (mitmproxy, express, etc.)
403
+ const frameworkHooks = ['request', 'response', 'load', 'configure', 'handler', 'middleware', 'setup', 'teardown'];
404
+ if (frameworkHooks.includes(funcName.toLowerCase())) {
405
+ continue;
406
+ }
407
+ // Skip parser artifacts and built-in property names
408
+ const parserArtifacts = ['name', 'return', 'constructor', 'toString', 'valueOf', 'length'];
409
+ if (parserArtifacts.includes(funcName)) {
410
+ continue;
411
+ }
412
+ // Skip Python private helper functions (likely called internally)
413
+ if (funcName.startsWith('_') && !funcName.startsWith('__')) {
414
+ continue;
415
+ }
416
+ // Skip functions with common utility prefixes (likely exported)
417
+ const utilityPrefixes = ['get', 'set', 'is', 'has', 'create', 'build', 'parse', 'format', 'validate', 'extract', 'find', 'ensure', 'init', 'setup', 'open', 'close', 'read', 'write', 'insert', 'update', 'delete', 'start', 'stop', 'store', 'escape', 'clean', 'deduplicate', 'encode', 'decode', 'render', 'load', 'save', 'fetch', 'send', 'handle', 'process', 'transform', 'convert', 'generate', 'calculate', 'compute'];
418
+ const lowerName = funcName.toLowerCase();
419
+ if (utilityPrefixes.some(p => lowerName.startsWith(p))) {
420
+ continue;
421
+ }
422
+ smells.push({
423
+ type: 'orphan',
424
+ severity: 'low',
425
+ structureId: s.id,
426
+ filePath: s.file_path,
427
+ name: s.name,
428
+ metric: 0,
429
+ threshold: 1,
430
+ description: 'No detected callers or callees (may be called dynamically or cross-file)',
431
+ });
432
+ }
433
+ }
434
+ // Sort by severity (high first)
435
+ const severityOrder = { high: 0, medium: 1, low: 2 };
436
+ smells.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
437
+ return {
438
+ smells,
439
+ summary: {
440
+ high: smells.filter(s => s.severity === 'high').length,
441
+ medium: smells.filter(s => s.severity === 'medium').length,
442
+ low: smells.filter(s => s.severity === 'low').length,
443
+ },
444
+ thresholds,
445
+ };
446
+ }
447
+ // ============================================
448
+ // Hotspots Builder
449
+ // ============================================
450
+ function buildHotspotsData(structures, links) {
451
+ // Count inbound/outbound links per structure
452
+ const inboundCount = new Map();
453
+ const outboundCount = new Map();
454
+ for (const link of links) {
455
+ if (link.link_type === 'calls' && link.source_type === 'structure' && link.target_type === 'structure') {
456
+ outboundCount.set(link.source_id, (outboundCount.get(link.source_id) || 0) + 1);
457
+ inboundCount.set(link.target_id, (inboundCount.get(link.target_id) || 0) + 1);
458
+ }
459
+ }
460
+ // Count structures per file
461
+ const structuresPerFile = new Map();
462
+ for (const s of structures) {
463
+ structuresPerFile.set(s.file_path, (structuresPerFile.get(s.file_path) || 0) + 1);
464
+ }
465
+ // Largest functions by line count
466
+ const functionsWithLines = structures
467
+ .filter(s => s.type === 'function' || s.type === 'method')
468
+ .map(s => ({
469
+ id: s.id,
470
+ name: s.name,
471
+ file: s.file_path,
472
+ type: s.type,
473
+ lines: (s.line_end || s.line_start) - s.line_start + 1,
474
+ }))
475
+ .sort((a, b) => b.lines - a.lines)
476
+ .slice(0, 10);
477
+ // Most connected functions
478
+ const functionsWithConnections = structures
479
+ .filter(s => s.type === 'function' || s.type === 'method')
480
+ .map(s => ({
481
+ id: s.id,
482
+ name: s.name,
483
+ file: s.file_path,
484
+ type: s.type,
485
+ inbound: inboundCount.get(s.id) || 0,
486
+ outbound: outboundCount.get(s.id) || 0,
487
+ total: (inboundCount.get(s.id) || 0) + (outboundCount.get(s.id) || 0),
488
+ }))
489
+ .filter(s => s.total > 0)
490
+ .sort((a, b) => b.total - a.total)
491
+ .slice(0, 10);
492
+ // Densest files
493
+ const densestFiles = Array.from(structuresPerFile.entries())
494
+ .map(([file, count]) => ({
495
+ id: 0,
496
+ name: getFileName(file),
497
+ file: file,
498
+ type: 'file',
499
+ structureCount: count,
500
+ }))
501
+ .sort((a, b) => b.structureCount - a.structureCount)
502
+ .slice(0, 10);
503
+ // Hub functions (called by many)
504
+ const hubFunctions = structures
505
+ .filter(s => s.type === 'function' || s.type === 'method')
506
+ .map(s => ({
507
+ id: s.id,
508
+ name: s.name,
509
+ file: s.file_path,
510
+ type: s.type,
511
+ inbound: inboundCount.get(s.id) || 0,
512
+ }))
513
+ .filter(s => s.inbound > 0)
514
+ .sort((a, b) => b.inbound - a.inbound)
515
+ .slice(0, 10);
516
+ return {
517
+ largestFunctions: functionsWithLines,
518
+ mostConnected: functionsWithConnections,
519
+ densestFiles,
520
+ hubFunctions,
521
+ };
522
+ }
523
+ // ============================================
524
+ // Gallery Builder
525
+ // ============================================
526
+ function buildGalleryData(extractions, links, structures, conversations) {
527
+ const structureMap = new Map();
528
+ for (const s of structures) {
529
+ structureMap.set(s.id, s);
530
+ }
531
+ const conversationMap = new Map();
532
+ for (const c of conversations) {
533
+ conversationMap.set(c.id, c);
534
+ }
535
+ // Build extraction to structure links
536
+ const extractionLinks = new Map();
537
+ for (const link of links) {
538
+ if (link.source_type === 'extraction' && link.target_type === 'structure') {
539
+ const s = structureMap.get(link.target_id);
540
+ if (s) {
541
+ if (!extractionLinks.has(link.source_id)) {
542
+ extractionLinks.set(link.source_id, []);
543
+ }
544
+ extractionLinks.get(link.source_id).push({
545
+ id: s.id,
546
+ name: s.name,
547
+ file: s.file_path,
548
+ type: s.type,
549
+ });
550
+ }
551
+ }
552
+ }
553
+ const items = [];
554
+ let decisions = 0, patterns = 0, rejections = 0;
555
+ for (const e of extractions) {
556
+ if (e.type !== 'decision' && e.type !== 'pattern' && e.type !== 'rejection')
557
+ continue;
558
+ const conv = conversationMap.get(e.conversation_id);
559
+ items.push({
560
+ id: e.id,
561
+ type: e.type,
562
+ content: e.content,
563
+ timestamp: conv?.timestamp || '',
564
+ affectedCode: extractionLinks.get(e.id) || [],
565
+ });
566
+ if (e.type === 'decision')
567
+ decisions++;
568
+ else if (e.type === 'pattern')
569
+ patterns++;
570
+ else if (e.type === 'rejection')
571
+ rejections++;
572
+ }
573
+ // Sort by timestamp descending
574
+ items.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
575
+ return {
576
+ items,
577
+ byType: { decision: decisions, pattern: patterns, rejection: rejections },
578
+ };
579
+ }
580
+ // ============================================
581
+ // Timeline Builder
582
+ // ============================================
583
+ function buildTimelineData(conversations, links, structures, extractions) {
584
+ if (conversations.length === 0) {
585
+ return { entries: [], dateRange: null };
586
+ }
587
+ const structureMap = new Map();
588
+ for (const s of structures) {
589
+ structureMap.set(s.id, s);
590
+ }
591
+ // Build conversation to structure links
592
+ const conversationStructures = new Map();
593
+ for (const link of links) {
594
+ if (link.source_type === 'conversation' && link.target_type === 'structure') {
595
+ const s = structureMap.get(link.target_id);
596
+ if (s) {
597
+ if (!conversationStructures.has(link.source_id)) {
598
+ conversationStructures.set(link.source_id, []);
599
+ }
600
+ conversationStructures.get(link.source_id).push({
601
+ id: s.id,
602
+ name: s.name,
603
+ file: s.file_path,
604
+ });
605
+ }
606
+ }
607
+ }
608
+ // Count extractions per conversation
609
+ const extractionCounts = new Map();
610
+ for (const e of extractions) {
611
+ extractionCounts.set(e.conversation_id, (extractionCounts.get(e.conversation_id) || 0) + 1);
612
+ }
613
+ const entries = conversations.map(c => ({
614
+ id: c.id,
615
+ timestamp: c.timestamp,
616
+ summary: c.summary,
617
+ model: c.model,
618
+ tool: c.tool,
619
+ touchedStructures: conversationStructures.get(c.id) || [],
620
+ extractionCount: extractionCounts.get(c.id) || 0,
621
+ }));
622
+ // Sort by timestamp
623
+ entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
624
+ const timestamps = entries.map(e => e.timestamp);
625
+ const dateRange = {
626
+ start: timestamps[0],
627
+ end: timestamps[timestamps.length - 1],
628
+ };
629
+ return { entries, dateRange };
630
+ }
631
+ // ============================================
632
+ // Treemap Builder
633
+ // ============================================
634
+ function buildTreemapData(structures) {
635
+ // Build directory tree
636
+ const root = {
637
+ name: 'root',
638
+ path: '',
639
+ type: 'directory',
640
+ value: 0,
641
+ children: [],
642
+ };
643
+ // Group structures by file, then by directory
644
+ const fileStructures = new Map();
645
+ for (const s of structures) {
646
+ if (!fileStructures.has(s.file_path)) {
647
+ fileStructures.set(s.file_path, []);
648
+ }
649
+ fileStructures.get(s.file_path).push(s);
650
+ }
651
+ // Build tree from file paths
652
+ for (const [filePath, fileStructs] of fileStructures) {
653
+ const parts = filePath.split(/[/\\]/).filter(p => p);
654
+ let current = root;
655
+ // Navigate/create directory nodes
656
+ for (let i = 0; i < parts.length - 1; i++) {
657
+ const dirName = parts[i];
658
+ const dirPath = parts.slice(0, i + 1).join('/');
659
+ let child = current.children?.find(c => c.name === dirName && c.type === 'directory');
660
+ if (!child) {
661
+ child = {
662
+ name: dirName,
663
+ path: dirPath,
664
+ type: 'directory',
665
+ value: 0,
666
+ children: [],
667
+ };
668
+ current.children = current.children || [];
669
+ current.children.push(child);
670
+ }
671
+ current = child;
672
+ }
673
+ // Add file node
674
+ const fileName = parts[parts.length - 1];
675
+ const fileValue = fileStructs.reduce((sum, s) => sum + ((s.line_end || s.line_start) - s.line_start + 1), 0);
676
+ const fileNode = {
677
+ name: fileName,
678
+ path: filePath,
679
+ type: 'file',
680
+ value: fileValue,
681
+ children: fileStructs.map(s => ({
682
+ name: s.name,
683
+ path: `${filePath}:${s.line_start}`,
684
+ type: 'structure',
685
+ structureType: s.type,
686
+ value: (s.line_end || s.line_start) - s.line_start + 1,
687
+ })),
688
+ };
689
+ current.children = current.children || [];
690
+ current.children.push(fileNode);
691
+ }
692
+ // Calculate directory values (sum of children)
693
+ function calculateValue(node) {
694
+ if (!node.children || node.children.length === 0) {
695
+ return node.value;
696
+ }
697
+ node.value = node.children.reduce((sum, child) => sum + calculateValue(child), 0);
698
+ return node.value;
699
+ }
700
+ calculateValue(root);
701
+ return root;
702
+ }
703
+ // Generate HTML dashboard
704
+ export function generateDashboard(data) {
705
+ return generateDashboardHTML(data);
706
+ }
707
+ //# sourceMappingURL=index.js.map