@mgamil/mapx 0.2.4

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 (203) hide show
  1. package/LICENSE +194 -0
  2. package/README.md +488 -0
  3. package/VERSION +1 -0
  4. package/dist/agents/generator.d.ts +74 -0
  5. package/dist/agents/generator.js +375 -0
  6. package/dist/agents/templates.d.ts +29 -0
  7. package/dist/agents/templates.js +459 -0
  8. package/dist/cli.d.ts +16 -0
  9. package/dist/cli.js +1835 -0
  10. package/dist/core/cluster-engine.d.ts +32 -0
  11. package/dist/core/cluster-engine.js +314 -0
  12. package/dist/core/config.d.ts +29 -0
  13. package/dist/core/config.js +178 -0
  14. package/dist/core/context-builder.d.ts +61 -0
  15. package/dist/core/context-builder.js +252 -0
  16. package/dist/core/flow-tracer.d.ts +63 -0
  17. package/dist/core/flow-tracer.js +366 -0
  18. package/dist/core/git-tracker.d.ts +20 -0
  19. package/dist/core/git-tracker.js +159 -0
  20. package/dist/core/graph.d.ts +42 -0
  21. package/dist/core/graph.js +186 -0
  22. package/dist/core/metrics.d.ts +24 -0
  23. package/dist/core/metrics.js +87 -0
  24. package/dist/core/scanner.d.ts +53 -0
  25. package/dist/core/scanner.js +949 -0
  26. package/dist/core/store-bun.d.ts +13 -0
  27. package/dist/core/store-bun.js +34 -0
  28. package/dist/core/store-interface.d.ts +15 -0
  29. package/dist/core/store-interface.js +7 -0
  30. package/dist/core/store-node.d.ts +13 -0
  31. package/dist/core/store-node.js +35 -0
  32. package/dist/core/store.d.ts +132 -0
  33. package/dist/core/store.js +614 -0
  34. package/dist/core/workspace-manager.d.ts +9 -0
  35. package/dist/core/workspace-manager.js +64 -0
  36. package/dist/exporters/dot-exporter.d.ts +16 -0
  37. package/dist/exporters/dot-exporter.js +179 -0
  38. package/dist/exporters/graph-exporter.d.ts +14 -0
  39. package/dist/exporters/graph-exporter.js +85 -0
  40. package/dist/exporters/index.d.ts +9 -0
  41. package/dist/exporters/index.js +12 -0
  42. package/dist/exporters/llm-exporter.d.ts +18 -0
  43. package/dist/exporters/llm-exporter.js +224 -0
  44. package/dist/exporters/svg-exporter.d.ts +19 -0
  45. package/dist/exporters/svg-exporter.js +319 -0
  46. package/dist/exporters/toon-exporter.d.ts +16 -0
  47. package/dist/exporters/toon-exporter.js +246 -0
  48. package/dist/frameworks/detectors/aspnet.d.ts +11 -0
  49. package/dist/frameworks/detectors/aspnet.js +52 -0
  50. package/dist/frameworks/detectors/django.d.ts +14 -0
  51. package/dist/frameworks/detectors/django.js +135 -0
  52. package/dist/frameworks/detectors/drupal.d.ts +13 -0
  53. package/dist/frameworks/detectors/drupal.js +94 -0
  54. package/dist/frameworks/detectors/express.d.ts +12 -0
  55. package/dist/frameworks/detectors/express.js +234 -0
  56. package/dist/frameworks/detectors/fastapi.d.ts +12 -0
  57. package/dist/frameworks/detectors/fastapi.js +203 -0
  58. package/dist/frameworks/detectors/flask.d.ts +12 -0
  59. package/dist/frameworks/detectors/flask.js +244 -0
  60. package/dist/frameworks/detectors/go.d.ts +11 -0
  61. package/dist/frameworks/detectors/go.js +75 -0
  62. package/dist/frameworks/detectors/laravel.d.ts +11 -0
  63. package/dist/frameworks/detectors/laravel.js +462 -0
  64. package/dist/frameworks/detectors/nestjs.d.ts +12 -0
  65. package/dist/frameworks/detectors/nestjs.js +155 -0
  66. package/dist/frameworks/detectors/nextjs.d.ts +11 -0
  67. package/dist/frameworks/detectors/nextjs.js +118 -0
  68. package/dist/frameworks/detectors/rails.d.ts +12 -0
  69. package/dist/frameworks/detectors/rails.js +76 -0
  70. package/dist/frameworks/detectors/react-router.d.ts +11 -0
  71. package/dist/frameworks/detectors/react-router.js +115 -0
  72. package/dist/frameworks/detectors/rust.d.ts +11 -0
  73. package/dist/frameworks/detectors/rust.js +59 -0
  74. package/dist/frameworks/detectors/spring.d.ts +11 -0
  75. package/dist/frameworks/detectors/spring.js +56 -0
  76. package/dist/frameworks/detectors/sveltekit.d.ts +11 -0
  77. package/dist/frameworks/detectors/sveltekit.js +154 -0
  78. package/dist/frameworks/detectors/symfony.d.ts +13 -0
  79. package/dist/frameworks/detectors/symfony.js +175 -0
  80. package/dist/frameworks/detectors/tanstack-router.d.ts +12 -0
  81. package/dist/frameworks/detectors/tanstack-router.js +80 -0
  82. package/dist/frameworks/detectors/vapor.d.ts +11 -0
  83. package/dist/frameworks/detectors/vapor.js +52 -0
  84. package/dist/frameworks/detectors/vue-router.d.ts +12 -0
  85. package/dist/frameworks/detectors/vue-router.js +237 -0
  86. package/dist/frameworks/detectors/wordpress.d.ts +13 -0
  87. package/dist/frameworks/detectors/wordpress.js +141 -0
  88. package/dist/frameworks/detectors/yii.d.ts +11 -0
  89. package/dist/frameworks/detectors/yii.js +131 -0
  90. package/dist/frameworks/framework-registry.d.ts +13 -0
  91. package/dist/frameworks/framework-registry.js +77 -0
  92. package/dist/frameworks/route-registry.d.ts +26 -0
  93. package/dist/frameworks/route-registry.js +102 -0
  94. package/dist/index.d.ts +19 -0
  95. package/dist/index.js +30 -0
  96. package/dist/languages/index.d.ts +2 -0
  97. package/dist/languages/index.js +7 -0
  98. package/dist/languages/installer.d.ts +13 -0
  99. package/dist/languages/installer.js +103 -0
  100. package/dist/languages/registry.d.ts +19 -0
  101. package/dist/languages/registry.js +427 -0
  102. package/dist/main.d.ts +2 -0
  103. package/dist/main.js +20 -0
  104. package/dist/mcp.d.ts +11 -0
  105. package/dist/mcp.js +1699 -0
  106. package/dist/parsers/common-methods.d.ts +3 -0
  107. package/dist/parsers/common-methods.js +33 -0
  108. package/dist/parsers/fallback-parser.d.ts +10 -0
  109. package/dist/parsers/fallback-parser.js +18 -0
  110. package/dist/parsers/generic-wasm-parser.d.ts +23 -0
  111. package/dist/parsers/generic-wasm-parser.js +168 -0
  112. package/dist/parsers/ignored-symbols.d.ts +26 -0
  113. package/dist/parsers/ignored-symbols.js +77 -0
  114. package/dist/parsers/index.d.ts +9 -0
  115. package/dist/parsers/index.js +13 -0
  116. package/dist/parsers/languages/javascript.d.ts +11 -0
  117. package/dist/parsers/languages/javascript.js +28 -0
  118. package/dist/parsers/languages/php.d.ts +15 -0
  119. package/dist/parsers/languages/php.js +648 -0
  120. package/dist/parsers/languages/typescript.d.ts +10 -0
  121. package/dist/parsers/languages/typescript.js +9 -0
  122. package/dist/parsers/languages/vue.d.ts +13 -0
  123. package/dist/parsers/languages/vue.js +63 -0
  124. package/dist/parsers/parse-worker.d.ts +2 -0
  125. package/dist/parsers/parse-worker.js +185 -0
  126. package/dist/parsers/parser-interface.d.ts +9 -0
  127. package/dist/parsers/parser-interface.js +0 -0
  128. package/dist/parsers/parser-registry.d.ts +8 -0
  129. package/dist/parsers/parser-registry.js +52 -0
  130. package/dist/parsers/wasm-parser.d.ts +16 -0
  131. package/dist/parsers/wasm-parser.js +110 -0
  132. package/dist/types.d.ts +172 -0
  133. package/dist/types.js +0 -0
  134. package/dist/ui/index.html +270 -0
  135. package/dist/ui/main.js +581 -0
  136. package/dist/ui/main.js.map +7 -0
  137. package/dist/ui/styles.css +573 -0
  138. package/dist/ui-events.d.ts +36 -0
  139. package/dist/ui-events.js +61 -0
  140. package/dist/ui-server.d.ts +12 -0
  141. package/dist/ui-server.js +504 -0
  142. package/package.json +179 -0
  143. package/queries/bash/references.scm +22 -0
  144. package/queries/bash/symbols.scm +15 -0
  145. package/queries/c/references.scm +14 -0
  146. package/queries/c/symbols.scm +30 -0
  147. package/queries/c-sharp/references.scm +26 -0
  148. package/queries/c-sharp/symbols.scm +57 -0
  149. package/queries/cpp/references.scm +21 -0
  150. package/queries/cpp/symbols.scm +44 -0
  151. package/queries/dart/references.scm +33 -0
  152. package/queries/dart/symbols.scm +38 -0
  153. package/queries/elixir/references.scm +45 -0
  154. package/queries/elixir/symbols.scm +41 -0
  155. package/queries/go/references.scm +22 -0
  156. package/queries/go/symbols.scm +53 -0
  157. package/queries/java/references.scm +32 -0
  158. package/queries/java/symbols.scm +41 -0
  159. package/queries/javascript/references.scm +14 -0
  160. package/queries/javascript/symbols.scm +23 -0
  161. package/queries/kotlin/references.scm +31 -0
  162. package/queries/kotlin/symbols.scm +24 -0
  163. package/queries/lua/references.scm +19 -0
  164. package/queries/lua/symbols.scm +29 -0
  165. package/queries/pascal/references.scm +29 -0
  166. package/queries/pascal/symbols.scm +45 -0
  167. package/queries/php/references.scm +109 -0
  168. package/queries/php/symbols.scm +33 -0
  169. package/queries/python/references.scm +50 -0
  170. package/queries/python/symbols.scm +21 -0
  171. package/queries/ruby/references.scm +48 -0
  172. package/queries/ruby/symbols.scm +24 -0
  173. package/queries/rust/references.scm +31 -0
  174. package/queries/rust/symbols.scm +35 -0
  175. package/queries/scala/references.scm +30 -0
  176. package/queries/scala/symbols.scm +35 -0
  177. package/queries/svelte/references.scm +20 -0
  178. package/queries/svelte/symbols.scm +30 -0
  179. package/queries/swift/references.scm +22 -0
  180. package/queries/swift/symbols.scm +37 -0
  181. package/queries/typescript/references.scm +25 -0
  182. package/queries/typescript/symbols.scm +35 -0
  183. package/queries/vue/references.scm +20 -0
  184. package/queries/vue/symbols.scm +28 -0
  185. package/queries/zig/references.scm +20 -0
  186. package/queries/zig/symbols.scm +22 -0
  187. package/wasm/tree-sitter-c.wasm +0 -0
  188. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  189. package/wasm/tree-sitter-cpp.wasm +0 -0
  190. package/wasm/tree-sitter-dart.wasm +0 -0
  191. package/wasm/tree-sitter-go.wasm +0 -0
  192. package/wasm/tree-sitter-java.wasm +0 -0
  193. package/wasm/tree-sitter-javascript.wasm +0 -0
  194. package/wasm/tree-sitter-kotlin.wasm +0 -0
  195. package/wasm/tree-sitter-php.wasm +0 -0
  196. package/wasm/tree-sitter-python.wasm +0 -0
  197. package/wasm/tree-sitter-ruby.wasm +0 -0
  198. package/wasm/tree-sitter-rust.wasm +0 -0
  199. package/wasm/tree-sitter-scala.wasm +0 -0
  200. package/wasm/tree-sitter-swift.wasm +0 -0
  201. package/wasm/tree-sitter-tsx.wasm +0 -0
  202. package/wasm/tree-sitter-typescript.wasm +0 -0
  203. package/wasm/tree-sitter-vue.wasm +0 -0
@@ -0,0 +1,319 @@
1
+ import { execSync } from "node:child_process";
2
+ import { DotExporter } from "./dot-exporter.js";
3
+ class SvgExporter {
4
+ store;
5
+ graph;
6
+ constructor(store, graph) {
7
+ this.store = store;
8
+ this.graph = graph;
9
+ }
10
+ export(repo, filesFilter, opts) {
11
+ if (opts?.forceFallback) {
12
+ return this.renderFallback(repo, filesFilter, opts);
13
+ }
14
+ const dotExporter = new DotExporter(this.store, this.graph);
15
+ const dot = dotExporter.export(repo, filesFilter, opts);
16
+ try {
17
+ return execSync("dot -Tsvg", {
18
+ input: dot,
19
+ encoding: "utf-8",
20
+ maxBuffer: 50 * 1024 * 1024,
21
+ stdio: ["pipe", "pipe", "pipe"]
22
+ });
23
+ } catch {
24
+ return this.renderFallback(repo, filesFilter, opts);
25
+ }
26
+ }
27
+ renderFallback(repo, filesFilter, opts) {
28
+ const clusterMode = opts?.cluster ?? "none";
29
+ let files = this.store.getAllFiles(repo);
30
+ let edges = this.store.getAllEdges(repo);
31
+ let rankedFiles = this.graph.getRankedFiles();
32
+ if (filesFilter) {
33
+ const allowed = new Set(filesFilter);
34
+ files = files.filter((f) => allowed.has(f.path));
35
+ edges = edges.filter((e) => allowed.has(e.source_file) && allowed.has(e.target_file));
36
+ rankedFiles = rankedFiles.filter((f) => allowed.has(f.path));
37
+ }
38
+ const langColors = {
39
+ php: "#4f5b93",
40
+ javascript: "#eab308",
41
+ typescript: "#2563eb",
42
+ python: "#3b82f6",
43
+ go: "#06b6d4",
44
+ rust: "#f97316",
45
+ java: "#ea580c",
46
+ c: "#0284c7",
47
+ cpp: "#0284c7",
48
+ csharp: "#0891b2",
49
+ ruby: "#dc2626",
50
+ swift: "#f97316",
51
+ kotlin: "#7c3aed",
52
+ scala: "#dc2626",
53
+ shell: "#10b981",
54
+ html: "#f97316",
55
+ css: "#2563eb",
56
+ sql: "#005b96",
57
+ yaml: "#78716c",
58
+ json: "#78716c",
59
+ markdown: "#0f172a"
60
+ };
61
+ const maxPr = Math.max(...rankedFiles.map((f) => f.pagerank), 1e-3);
62
+ const rankMap = new Map(rankedFiles.map((f) => [f.path, f.pagerank]));
63
+ const TARGET_WIDTH = 1200;
64
+ const MARGIN = 40;
65
+ const CARD_PADDING = 16;
66
+ const TITLE_HEIGHT = 28;
67
+ const CARD_GAP = 24;
68
+ const ITEM_W = 160;
69
+ const ITEM_H = 32;
70
+ const ITEM_GAP_X = 12;
71
+ const ITEM_GAP_Y = 8;
72
+ const nodePositions = /* @__PURE__ */ new Map();
73
+ let svgW = TARGET_WIDTH;
74
+ let svgH = 0;
75
+ const cardLines = [];
76
+ if (clusterMode !== "none") {
77
+ const clusters = this.store.getClusters(repo);
78
+ const memberships = this.store.getClusterMemberships(repo);
79
+ const primaryMemberships = /* @__PURE__ */ new Map();
80
+ for (const m of memberships) {
81
+ if (m.is_primary === 1) {
82
+ primaryMemberships.set(m.file_path, m.cluster_name);
83
+ }
84
+ }
85
+ const clusterMap = /* @__PURE__ */ new Map();
86
+ const unclustered = [];
87
+ for (const f of files) {
88
+ const cName = primaryMemberships.get(f.path);
89
+ if (cName) {
90
+ if (!clusterMap.has(cName)) {
91
+ clusterMap.set(cName, []);
92
+ }
93
+ clusterMap.get(cName).push(f);
94
+ } else {
95
+ unclustered.push(f);
96
+ }
97
+ }
98
+ const getCols = (N) => {
99
+ if (N <= 3) return 1;
100
+ if (N <= 8) return 2;
101
+ if (N <= 15) return 3;
102
+ if (N <= 30) return 4;
103
+ return 5;
104
+ };
105
+ const groups = [];
106
+ for (const [cName, fList] of clusterMap.entries()) {
107
+ const clusterInfo = clusters.find((c) => c.name === cName);
108
+ const label = String(clusterInfo?.label || cName);
109
+ fList.sort((a, b) => {
110
+ const prA = rankMap.get(a.path) || 0;
111
+ const prB = rankMap.get(b.path) || 0;
112
+ return prB - prA;
113
+ });
114
+ const N = fList.length;
115
+ const cols = getCols(N);
116
+ const width = CARD_PADDING * 2 + cols * ITEM_W + (cols - 1) * ITEM_GAP_X;
117
+ const rows = Math.ceil(N / cols);
118
+ const height = CARD_PADDING * 2 + TITLE_HEIGHT + rows * ITEM_H + (rows - 1) * ITEM_GAP_Y;
119
+ groups.push({
120
+ name: cName,
121
+ label,
122
+ files: fList,
123
+ cols,
124
+ width,
125
+ height
126
+ });
127
+ }
128
+ if (unclustered.length > 0) {
129
+ unclustered.sort((a, b) => {
130
+ const prA = rankMap.get(a.path) || 0;
131
+ const prB = rankMap.get(b.path) || 0;
132
+ return prB - prA;
133
+ });
134
+ const N = unclustered.length;
135
+ const cols = getCols(N);
136
+ const width = CARD_PADDING * 2 + cols * ITEM_W + (cols - 1) * ITEM_GAP_X;
137
+ const rows = Math.ceil(N / cols);
138
+ const height = CARD_PADDING * 2 + TITLE_HEIGHT + rows * ITEM_H + (rows - 1) * ITEM_GAP_Y;
139
+ groups.push({
140
+ name: "__unclustered__",
141
+ label: "Other Files",
142
+ files: unclustered,
143
+ cols,
144
+ width,
145
+ height
146
+ });
147
+ }
148
+ groups.sort((a, b) => b.files.length - a.files.length);
149
+ let currentX = MARGIN;
150
+ let currentY = MARGIN;
151
+ let rowMaxHeight = 0;
152
+ for (const group of groups) {
153
+ if (currentX + group.width > TARGET_WIDTH - MARGIN && currentX > MARGIN) {
154
+ currentX = MARGIN;
155
+ currentY += rowMaxHeight + CARD_GAP;
156
+ rowMaxHeight = 0;
157
+ }
158
+ group.x = currentX;
159
+ group.y = currentY;
160
+ cardLines.push(
161
+ ` <!-- Cluster Group: ${this.escXml(group.name)} -->`,
162
+ ` <rect x="${group.x}" y="${group.y}" width="${group.width}" height="${group.height}" rx="12" fill="#131b2e" stroke="#1e293b" stroke-width="1.5" class="cluster-card"/>`,
163
+ ` <text x="${group.x + CARD_PADDING}" y="${group.y + CARD_PADDING + 14}" fill="#94a3b8" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="600" letter-spacing="0.5">${this.escXml(group.label.toUpperCase())}</text>`
164
+ );
165
+ const gX = group.x;
166
+ const gY = group.y;
167
+ const cols = group.cols;
168
+ for (let idx = 0; idx < group.files.length; idx++) {
169
+ const file = group.files[idx];
170
+ const col = idx % cols;
171
+ const row = Math.floor(idx / cols);
172
+ const nodeX = gX + CARD_PADDING + col * (ITEM_W + ITEM_GAP_X);
173
+ const nodeY = gY + CARD_PADDING + TITLE_HEIGHT + row * (ITEM_H + ITEM_GAP_Y);
174
+ nodePositions.set(file.path, {
175
+ x: nodeX,
176
+ y: nodeY,
177
+ w: ITEM_W,
178
+ h: ITEM_H,
179
+ file
180
+ });
181
+ }
182
+ currentX += group.width + CARD_GAP;
183
+ if (group.height > rowMaxHeight) {
184
+ rowMaxHeight = group.height;
185
+ }
186
+ }
187
+ svgH = currentY + rowMaxHeight + MARGIN;
188
+ } else {
189
+ const GAP_X = 16;
190
+ const GAP_Y = 12;
191
+ const cols = Math.max(1, Math.floor((TARGET_WIDTH - MARGIN * 2 + GAP_X) / (ITEM_W + GAP_X)));
192
+ files.sort((a, b) => {
193
+ const prA = rankMap.get(a.path) || 0;
194
+ const prB = rankMap.get(b.path) || 0;
195
+ return prB - prA;
196
+ });
197
+ for (let i = 0; i < files.length; i++) {
198
+ const col = i % cols;
199
+ const row = Math.floor(i / cols);
200
+ const nodeX = MARGIN + col * (ITEM_W + GAP_X);
201
+ const nodeY = MARGIN + row * (ITEM_H + GAP_Y);
202
+ nodePositions.set(files[i].path, {
203
+ x: nodeX,
204
+ y: nodeY,
205
+ w: ITEM_W,
206
+ h: ITEM_H,
207
+ file: files[i]
208
+ });
209
+ }
210
+ const rows = Math.ceil(files.length / cols);
211
+ svgH = MARGIN * 2 + rows * ITEM_H + (rows - 1) * GAP_Y;
212
+ }
213
+ const edgeStyles = {
214
+ import: { stroke: "#64748b", dash: "" },
215
+ require: { stroke: "#64748b", dash: "" },
216
+ extends: { stroke: "#3b82f6", dash: "" },
217
+ implements: { stroke: "#10b981", dash: "4,4" },
218
+ call: { stroke: "#f59e0b", dash: "3,3" },
219
+ instantiation: { stroke: "#8b5cf6", dash: "3,3" },
220
+ relation: { stroke: "#3b82f6", dash: "" },
221
+ route: { stroke: "#10b981", dash: "" },
222
+ binding: { stroke: "#8b5cf6", dash: "4,4" },
223
+ middleware: { stroke: "#f97316", dash: "2,2" }
224
+ };
225
+ const seen = /* @__PURE__ */ new Set();
226
+ const edgeLines = [];
227
+ for (const edge of edges) {
228
+ const src = edge.source_file;
229
+ const tgt = edge.target_file;
230
+ const type = edge.edge_type;
231
+ const key = `${src}->${tgt}:${type}`;
232
+ if (seen.has(key)) continue;
233
+ seen.add(key);
234
+ const srcNode = nodePositions.get(src);
235
+ const tgtNode = nodePositions.get(tgt);
236
+ if (!srcNode || !tgtNode) continue;
237
+ const sx = srcNode.x + srcNode.w / 2;
238
+ const sy = srcNode.y + ITEM_H;
239
+ const tx = tgtNode.x + tgtNode.w / 2;
240
+ const ty = tgtNode.y;
241
+ const style = edgeStyles[type] || { stroke: "#64748b", dash: "" };
242
+ const dash = edge.verifiability === "inferred" ? "5,5" : style.dash;
243
+ const dashAttr = dash ? ` stroke-dasharray="${dash}"` : "";
244
+ const midY = (sy + ty) / 2;
245
+ edgeLines.push(
246
+ ` <path d="M${sx},${sy} C${sx},${midY} ${tx},${midY} ${tx},${ty}" fill="none" stroke="${style.stroke}" stroke-width="1.2"${dashAttr} opacity="0.35" class="edge-path" marker-end="url(#arrow)"/>`
247
+ );
248
+ }
249
+ const nodeLines = [];
250
+ for (const [path, n] of nodePositions.entries()) {
251
+ const pr = rankMap.get(path) || 0;
252
+ const opacity = 0.7 + 0.3 * (pr / maxPr);
253
+ const textY = n.y + ITEM_H / 2 + 4.5;
254
+ const color = langColors[n.file.language] || "#CCCCCC";
255
+ const textColor = "#ffffff";
256
+ const isHighRank = pr > maxPr * 0.5;
257
+ const strokeAttr = isHighRank ? ` stroke="#60a5fa" stroke-width="1.5"` : ` stroke="#1e293b" stroke-width="1"`;
258
+ const filePath = String(n.file.path || "");
259
+ nodeLines.push(
260
+ ` <g class="node-group">`,
261
+ ` <rect x="${n.x}" y="${n.y}" width="${n.w}" height="${ITEM_H}" rx="6" fill="${color}" opacity="${opacity.toFixed(2)}"${strokeAttr} class="node-rect"/>`,
262
+ ` <text x="${n.x + n.w / 2}" y="${textY}" text-anchor="middle" fill="${textColor}" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="500">${this.escXml(filePath.split("/").pop() || filePath)}</text>`,
263
+ ` <title>${this.escXml(filePath)} (PageRank: ${pr.toFixed(4)})</title>`,
264
+ ` </g>`
265
+ );
266
+ }
267
+ const styleBlock = [
268
+ " <style>",
269
+ " .cluster-card {",
270
+ " transition: stroke 0.2s ease, fill 0.2s ease;",
271
+ " }",
272
+ " .cluster-card:hover {",
273
+ " stroke: #475569;",
274
+ " fill: #1e293b;",
275
+ " }",
276
+ " .node-rect {",
277
+ " transition: filter 0.2s ease, stroke 0.2s ease;",
278
+ " }",
279
+ " .node-rect:hover {",
280
+ " filter: brightness(1.2);",
281
+ " stroke: #60a5fa;",
282
+ " stroke-width: 1.5px;",
283
+ " cursor: pointer;",
284
+ " }",
285
+ " .edge-path {",
286
+ " transition: opacity 0.2s ease, stroke-width 0.2s ease;",
287
+ " }",
288
+ " .edge-path:hover {",
289
+ " opacity: 0.85;",
290
+ " stroke-width: 2px;",
291
+ " }",
292
+ " </style>"
293
+ ].join("\n");
294
+ const defs = [
295
+ " <defs>",
296
+ ' <marker id="arrow" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">',
297
+ ' <path d="M 0 2 L 8 5 L 0 8 z" fill="#64748b"/>',
298
+ " </marker>",
299
+ " </defs>"
300
+ ].join("\n");
301
+ return [
302
+ '<?xml version="1.0" encoding="UTF-8"?>',
303
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svgW} ${svgH}" width="${svgW}" height="${svgH}">`,
304
+ styleBlock,
305
+ defs,
306
+ ' <rect width="100%" height="100%" fill="#0b0f19"/>',
307
+ ...cardLines,
308
+ ...edgeLines,
309
+ ...nodeLines,
310
+ "</svg>"
311
+ ].join("\n");
312
+ }
313
+ escXml(s) {
314
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
315
+ }
316
+ }
317
+ export {
318
+ SvgExporter
319
+ };
@@ -0,0 +1,16 @@
1
+ import { Store } from '../core/store.js';
2
+ import { MapxGraph } from '../core/graph.js';
3
+ import { ExportOptions } from '../types.js';
4
+ import '../core/store-interface.js';
5
+
6
+ declare function toonQuote(value: string, activeDelimiter?: ',' | '\t' | '|'): string;
7
+ declare function formatNumber(n: number): string;
8
+ declare class ToonExporter {
9
+ private store;
10
+ private graph;
11
+ constructor(store: Store, graph: MapxGraph);
12
+ export(options?: Partial<ExportOptions>): string;
13
+ private generateDocument;
14
+ }
15
+
16
+ export { ToonExporter, formatNumber, toonQuote };
@@ -0,0 +1,246 @@
1
+ function toonQuote(value, activeDelimiter = ",") {
2
+ const needsQuoting = value === "" || /^\s|\s$/.test(value) || value === "true" || value === "false" || value === "null" || /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(value) || /[:"\\\[\]{}\u0000-\u001F\u007F-\u009F]/.test(value) || value === "-" || /^-\S/.test(value) || value.includes(activeDelimiter);
3
+ if (!needsQuoting) return value;
4
+ return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/[\u0000-\u001F\u007F-\u009F]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`) + '"';
5
+ }
6
+ function formatNumber(n) {
7
+ if (Object.is(n, -0)) return "0";
8
+ if (isNaN(n) || !isFinite(n)) return "null";
9
+ if (n === 0) return "0";
10
+ const abs = Math.abs(n);
11
+ if (abs >= 1e-6 && abs < 1e21) {
12
+ let s = n.toString();
13
+ if (s.includes("e") || s.includes("E")) {
14
+ s = n.toFixed(20).replace(/\.?0+$/, "");
15
+ }
16
+ return s;
17
+ } else {
18
+ return n.toString().toLowerCase();
19
+ }
20
+ }
21
+ class ToonExporter {
22
+ store;
23
+ graph;
24
+ constructor(store, graph) {
25
+ this.store = store;
26
+ this.graph = graph;
27
+ }
28
+ export(options) {
29
+ const opt = options || {};
30
+ const budget = opt.tokenBudget || 8192;
31
+ const delimiterName = opt.delimiter || "comma";
32
+ const delimiterMap = { comma: ",", tab: " ", pipe: "|" };
33
+ const delim = delimiterMap[delimiterName];
34
+ let files = this.store.getAllFiles(opt.repo);
35
+ let symbols = this.store.getAllSymbols(opt.repo);
36
+ let edges = this.store.getAllEdges(opt.repo);
37
+ const clusters = this.store.getClusters(opt.repo);
38
+ let rankedFiles = this.graph.getRankedFiles();
39
+ let rankedSymbols = this.graph.getRankedSymbols();
40
+ if (opt.files) {
41
+ const allowed = new Set(opt.files);
42
+ files = files.filter((f) => allowed.has(f.path));
43
+ symbols = symbols.filter((s) => allowed.has(s.file_path));
44
+ edges = edges.filter((e) => allowed.has(e.source_file) && allowed.has(e.target_file));
45
+ rankedFiles = rankedFiles.filter((f) => allowed.has(f.path));
46
+ rankedSymbols = rankedSymbols.filter((s) => allowed.has(s.filePath));
47
+ }
48
+ let low = 0;
49
+ let high = symbols.length;
50
+ let bestOutput = "";
51
+ const sortedSymbols = [...symbols].sort((a, b) => {
52
+ const rankA = rankedSymbols.find((rs) => rs.name === a.name && rs.filePath === a.file_path)?.pagerank || 0;
53
+ const rankB = rankedSymbols.find((rs) => rs.name === b.name && rs.filePath === b.file_path)?.pagerank || 0;
54
+ return rankB - rankA;
55
+ });
56
+ while (low <= high) {
57
+ const mid = Math.floor((low + high) / 2);
58
+ const output = this.generateDocument({
59
+ opt,
60
+ delim,
61
+ files,
62
+ rankedFiles,
63
+ symbols: sortedSymbols.slice(0, mid),
64
+ rankedSymbols,
65
+ edges,
66
+ clusters,
67
+ isTruncated: mid < symbols.length,
68
+ truncatedCount: symbols.length - mid,
69
+ originalSymbolsCount: symbols.length
70
+ });
71
+ const estimatedTokens = Math.ceil(Buffer.byteLength(output, "utf8") / 4);
72
+ if (estimatedTokens <= budget) {
73
+ bestOutput = output;
74
+ low = mid + 1;
75
+ } else {
76
+ high = mid - 1;
77
+ }
78
+ }
79
+ if (!bestOutput || Math.ceil(Buffer.byteLength(bestOutput, "utf8") / 4) > budget) {
80
+ let lowEdge = 0;
81
+ let highEdge = edges.length;
82
+ let bestEdgeOutput = "";
83
+ while (lowEdge <= highEdge) {
84
+ const midEdge = Math.floor((lowEdge + highEdge) / 2);
85
+ const output = this.generateDocument({
86
+ opt,
87
+ delim,
88
+ files,
89
+ rankedFiles,
90
+ symbols: [],
91
+ rankedSymbols,
92
+ edges: edges.slice(0, midEdge),
93
+ clusters,
94
+ isTruncated: true,
95
+ truncatedCount: symbols.length,
96
+ originalSymbolsCount: symbols.length,
97
+ isEdgesTruncated: midEdge < edges.length,
98
+ edgesTruncatedCount: edges.length - midEdge
99
+ });
100
+ const estimatedTokens = Math.ceil(Buffer.byteLength(output, "utf8") / 4);
101
+ if (estimatedTokens <= budget) {
102
+ bestEdgeOutput = output;
103
+ lowEdge = midEdge + 1;
104
+ } else {
105
+ highEdge = midEdge - 1;
106
+ }
107
+ }
108
+ if (bestEdgeOutput) {
109
+ bestOutput = bestEdgeOutput;
110
+ } else {
111
+ bestOutput = this.generateDocument({
112
+ opt,
113
+ delim,
114
+ files: [],
115
+ rankedFiles: [],
116
+ symbols: [],
117
+ rankedSymbols: [],
118
+ edges: [],
119
+ clusters: [],
120
+ isTruncated: true,
121
+ truncatedCount: symbols.length,
122
+ originalSymbolsCount: symbols.length
123
+ });
124
+ }
125
+ }
126
+ return bestOutput.endsWith("\n") ? bestOutput.slice(0, -1) : bestOutput;
127
+ }
128
+ generateDocument(params) {
129
+ const {
130
+ opt,
131
+ delim,
132
+ files,
133
+ rankedFiles,
134
+ symbols,
135
+ rankedSymbols,
136
+ edges,
137
+ clusters,
138
+ isTruncated,
139
+ truncatedCount,
140
+ originalSymbolsCount,
141
+ isEdgesTruncated,
142
+ edgesTruncatedCount
143
+ } = params;
144
+ const parts = [];
145
+ parts.push("version: 1");
146
+ parts.push(`generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
147
+ parts.push(`repo: ${opt.repo || "all"}`);
148
+ parts.push(`tokenBudget: ${opt.tokenBudget || 8192}`);
149
+ parts.push("");
150
+ parts.push("summary:");
151
+ parts.push(` files: ${files.length}`);
152
+ parts.push(` symbols: ${originalSymbolsCount}`);
153
+ parts.push(` edges: ${edges.length}`);
154
+ const languages = Array.from(new Set(files.map((f) => f.language))).filter(Boolean).sort();
155
+ if (languages.length > 0) {
156
+ const escapedLangs = languages.map((l) => toonQuote(l, delim)).join(delim);
157
+ parts.push(` languages[${languages.length}]: ${escapedLangs}`);
158
+ }
159
+ parts.push("");
160
+ const fileRankMap = new Map(rankedFiles.map((f) => [f.path, f.pagerank]));
161
+ const symbolRankMap = new Map(rankedSymbols.map((s) => [`${s.filePath}::${s.name}`, s.pagerank]));
162
+ if (files.length > 0) {
163
+ const sortedFiles = [...files].sort((a, b) => {
164
+ const rA = fileRankMap.get(a.path) || 0;
165
+ const rB = fileRankMap.get(b.path) || 0;
166
+ return rB - rA;
167
+ });
168
+ parts.push(`files[${files.length}]{path,language,symbols,pagerank}:`);
169
+ for (const f of sortedFiles) {
170
+ const path = toonQuote(f.path, delim);
171
+ const language = toonQuote(f.language, delim);
172
+ const symbolsCount = f.lines ? this.store.getSymbolsForFile(f.path).length : 0;
173
+ const rank = formatNumber(parseFloat((fileRankMap.get(f.path) || 0).toFixed(6)));
174
+ parts.push(` ${path}${delim}${language}${delim}${symbolsCount}${delim}${rank}`);
175
+ }
176
+ parts.push("");
177
+ }
178
+ if (symbols.length > 0) {
179
+ parts.push(`symbols[${symbols.length}]{name,kind,file,scope,pagerank}:`);
180
+ for (const s of symbols) {
181
+ const name = toonQuote(s.name, delim);
182
+ const kind = toonQuote(s.kind, delim);
183
+ const file = toonQuote(s.file_path, delim);
184
+ const scope = toonQuote(s.scope || "", delim);
185
+ const rankKey = `${s.file_path}::${s.name}`;
186
+ const rank = formatNumber(parseFloat((symbolRankMap.get(rankKey) || 0).toFixed(6)));
187
+ parts.push(` ${name}${delim}${kind}${delim}${file}${delim}${scope}${delim}${rank}`);
188
+ }
189
+ parts.push("");
190
+ }
191
+ const keptSymbolsSet = new Set(symbols.map((s) => `${s.file_path}::${s.name}`));
192
+ const filteredEdges = edges.filter((e) => {
193
+ if (e.source_symbol) {
194
+ const srcKey = `${e.source_file}::${e.source_symbol}`;
195
+ if (!keptSymbolsSet.has(srcKey)) return false;
196
+ }
197
+ if (e.target_symbol) {
198
+ const tgtKey = `${e.target_file}::${e.target_symbol}`;
199
+ if (!keptSymbolsSet.has(tgtKey)) return false;
200
+ }
201
+ return true;
202
+ });
203
+ if (filteredEdges.length > 0) {
204
+ parts.push(`edges[${filteredEdges.length}]{sourceFile,targetFile,edgeType,sourceSymbol,targetSymbol,weight}:`);
205
+ for (const e of filteredEdges) {
206
+ const sourceFile = toonQuote(e.source_file, delim);
207
+ const targetFile = toonQuote(e.target_file || "", delim);
208
+ const edgeType = toonQuote(e.edge_type, delim);
209
+ const sourceSymbol = toonQuote(e.source_symbol || "", delim);
210
+ const targetSymbol = toonQuote(e.target_symbol || "", delim);
211
+ const weight = formatNumber(e.weight || 1);
212
+ parts.push(` ${sourceFile}${delim}${targetFile}${delim}${edgeType}${delim}${sourceSymbol}${delim}${targetSymbol}${delim}${weight}`);
213
+ }
214
+ parts.push("");
215
+ }
216
+ if (clusters.length > 0) {
217
+ parts.push(`clusters[${clusters.length}]{name,source,parentName,depth,fileCount}:`);
218
+ for (const c of clusters) {
219
+ const name = toonQuote(c.name, delim);
220
+ const source = toonQuote(c.source, delim);
221
+ const parentName = toonQuote(c.parent_name || "", delim);
222
+ const depth = formatNumber(c.depth || 0);
223
+ const fileCount = formatNumber(c.file_count || 0);
224
+ parts.push(` ${name}${delim}${source}${delim}${parentName}${delim}${depth}${delim}${fileCount}`);
225
+ }
226
+ parts.push("");
227
+ }
228
+ if (isTruncated) {
229
+ parts.push("truncated: true");
230
+ parts.push("truncatedAt: symbols");
231
+ parts.push(`includedSymbols: ${symbols.length}`);
232
+ parts.push(`totalSymbols: ${originalSymbolsCount}`);
233
+ if (isEdgesTruncated) {
234
+ parts.push("edgesTruncated: true");
235
+ parts.push(`includedEdges: ${filteredEdges.length}`);
236
+ parts.push(`totalEdges: ${edges.length}`);
237
+ }
238
+ }
239
+ return parts.join("\n");
240
+ }
241
+ }
242
+ export {
243
+ ToonExporter,
244
+ formatNumber,
245
+ toonQuote
246
+ };
@@ -0,0 +1,11 @@
1
+ import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
2
+
3
+ declare class AspNetDetector implements FrameworkDetector {
4
+ readonly name = "aspnet";
5
+ readonly language = "csharp";
6
+ readonly filePattern: RegExp;
7
+ detect(projectRoot: string, files: string[]): Promise<boolean>;
8
+ extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
9
+ }
10
+
11
+ export { AspNetDetector };
@@ -0,0 +1,52 @@
1
+ class AspNetDetector {
2
+ name = "aspnet";
3
+ language = "csharp";
4
+ filePattern = /\.cs$/;
5
+ async detect(projectRoot, files) {
6
+ const hasCsproj = files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"));
7
+ if (hasCsproj) return true;
8
+ return files.some((f) => f.endsWith(".cs"));
9
+ }
10
+ async extractRoutes(filePath, content, ctx) {
11
+ const routes = [];
12
+ if (!content.includes("Controller") && !content.includes("Route") && !content.includes("Http")) {
13
+ return [];
14
+ }
15
+ const classMatch = content.match(/\bclass\s+([a-zA-Z0-9_]+)\b/);
16
+ if (!classMatch) return [];
17
+ const className = classMatch[1];
18
+ const controllerName = className.endsWith("Controller") ? className.substring(0, className.length - 10).toLowerCase() : className.toLowerCase();
19
+ let classRoutePrefix = "";
20
+ const classRouteMatch = content.match(/\[Route\s*\(\s*['"]([^'"]+)['"]\s*\)\]/);
21
+ if (classRouteMatch) {
22
+ classRoutePrefix = classRouteMatch[1];
23
+ classRoutePrefix = classRoutePrefix.replace(/\[controller\]/gi, controllerName);
24
+ }
25
+ const methodRouteRegex = /\[Http(Get|Post|Put|Delete|Patch)(?:\s*\(\s*['"]([^'"]+)['"]\s*\))?\][^({]*?\b([a-zA-Z0-9_]+)\s*\(/g;
26
+ let match;
27
+ while ((match = methodRouteRegex.exec(content)) !== null) {
28
+ const verb = match[1].toUpperCase();
29
+ const methodTemplate = match[2] || "";
30
+ const methodName = match[3];
31
+ const cleanClassPrefix = classRoutePrefix.replace(/^\/|\/$/g, "");
32
+ const cleanMethodTemplate = methodTemplate.replace(/^\/|\/$/g, "");
33
+ let combinedPath = "/" + [cleanClassPrefix, cleanMethodTemplate].filter(Boolean).join("/");
34
+ combinedPath = combinedPath.replace(/\{([a-zA-Z0-9_?*]+)(?::[a-zA-Z0-9_]+)?\}/g, "{$1}");
35
+ routes.push({
36
+ framework: this.name,
37
+ method: verb,
38
+ path: combinedPath,
39
+ handlerFile: filePath,
40
+ handlerSymbol: `${className}.${methodName}`,
41
+ metadata: {
42
+ confidence: "inferred",
43
+ routeType: "server"
44
+ }
45
+ });
46
+ }
47
+ return routes;
48
+ }
49
+ }
50
+ export {
51
+ AspNetDetector
52
+ };
@@ -0,0 +1,14 @@
1
+ import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
2
+
3
+ declare class DjangoDetector implements FrameworkDetector {
4
+ readonly name = "django";
5
+ readonly language = "python";
6
+ readonly filePattern: RegExp;
7
+ private projectFiles;
8
+ detect(projectRoot: string, files: string[]): Promise<boolean>;
9
+ extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
10
+ private parseUrlsFile;
11
+ private resolveModulePath;
12
+ }
13
+
14
+ export { DjangoDetector };