@mfittko/repo-wiki 0.2.1

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 (190) hide show
  1. package/.llmwiki/schema.md +107 -0
  2. package/AGENTS.md +42 -0
  3. package/CHANGELOG.md +91 -0
  4. package/LICENSE +21 -0
  5. package/README.md +254 -0
  6. package/dist/bin/repo-wiki.d.ts +2 -0
  7. package/dist/bin/repo-wiki.js +7 -0
  8. package/dist/bin/repo-wiki.js.map +1 -0
  9. package/dist/src/cli.d.ts +1 -0
  10. package/dist/src/cli.js +404 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/compiler.d.ts +55 -0
  13. package/dist/src/compiler.js +2046 -0
  14. package/dist/src/compiler.js.map +1 -0
  15. package/dist/src/config.d.ts +63 -0
  16. package/dist/src/config.js +86 -0
  17. package/dist/src/config.js.map +1 -0
  18. package/dist/src/context-assembler.d.ts +68 -0
  19. package/dist/src/context-assembler.js +378 -0
  20. package/dist/src/context-assembler.js.map +1 -0
  21. package/dist/src/data-model-signals.d.ts +1 -0
  22. package/dist/src/data-model-signals.js +13 -0
  23. package/dist/src/data-model-signals.js.map +1 -0
  24. package/dist/src/docs-ingestor.d.ts +138 -0
  25. package/dist/src/docs-ingestor.js +844 -0
  26. package/dist/src/docs-ingestor.js.map +1 -0
  27. package/dist/src/docs-linter.d.ts +14 -0
  28. package/dist/src/docs-linter.js +164 -0
  29. package/dist/src/docs-linter.js.map +1 -0
  30. package/dist/src/docs-validation.d.ts +36 -0
  31. package/dist/src/docs-validation.js +297 -0
  32. package/dist/src/docs-validation.js.map +1 -0
  33. package/dist/src/extractors.d.ts +50 -0
  34. package/dist/src/extractors.js +2275 -0
  35. package/dist/src/extractors.js.map +1 -0
  36. package/dist/src/frontmatter.d.ts +46 -0
  37. package/dist/src/frontmatter.js +377 -0
  38. package/dist/src/frontmatter.js.map +1 -0
  39. package/dist/src/index.d.ts +26 -0
  40. package/dist/src/index.js +18 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/init.d.ts +12 -0
  43. package/dist/src/init.js +121 -0
  44. package/dist/src/init.js.map +1 -0
  45. package/dist/src/language.d.ts +2 -0
  46. package/dist/src/language.js +62 -0
  47. package/dist/src/language.js.map +1 -0
  48. package/dist/src/linter.d.ts +33 -0
  49. package/dist/src/linter.js +398 -0
  50. package/dist/src/linter.js.map +1 -0
  51. package/dist/src/llm-provider.d.ts +267 -0
  52. package/dist/src/llm-provider.js +474 -0
  53. package/dist/src/llm-provider.js.map +1 -0
  54. package/dist/src/page-ownership.d.ts +38 -0
  55. package/dist/src/page-ownership.js +96 -0
  56. package/dist/src/page-ownership.js.map +1 -0
  57. package/dist/src/planner.d.ts +55 -0
  58. package/dist/src/planner.js +422 -0
  59. package/dist/src/planner.js.map +1 -0
  60. package/dist/src/prompts.d.ts +103 -0
  61. package/dist/src/prompts.js +344 -0
  62. package/dist/src/prompts.js.map +1 -0
  63. package/dist/src/publisher.d.ts +68 -0
  64. package/dist/src/publisher.js +662 -0
  65. package/dist/src/publisher.js.map +1 -0
  66. package/dist/src/repository-analysis.d.ts +88 -0
  67. package/dist/src/repository-analysis.js +485 -0
  68. package/dist/src/repository-analysis.js.map +1 -0
  69. package/dist/src/scanner.d.ts +122 -0
  70. package/dist/src/scanner.js +309 -0
  71. package/dist/src/scanner.js.map +1 -0
  72. package/dist/src/search.d.ts +71 -0
  73. package/dist/src/search.js +410 -0
  74. package/dist/src/search.js.map +1 -0
  75. package/dist/src/secret-patterns.d.ts +3 -0
  76. package/dist/src/secret-patterns.js +14 -0
  77. package/dist/src/secret-patterns.js.map +1 -0
  78. package/dist/src/utils/args.d.ts +2 -0
  79. package/dist/src/utils/args.js +19 -0
  80. package/dist/src/utils/args.js.map +1 -0
  81. package/dist/src/utils/dotenv.d.ts +7 -0
  82. package/dist/src/utils/dotenv.js +73 -0
  83. package/dist/src/utils/dotenv.js.map +1 -0
  84. package/dist/src/utils/fs.d.ts +22 -0
  85. package/dist/src/utils/fs.js +83 -0
  86. package/dist/src/utils/fs.js.map +1 -0
  87. package/dist/src/utils/git.d.ts +13 -0
  88. package/dist/src/utils/git.js +39 -0
  89. package/dist/src/utils/git.js.map +1 -0
  90. package/dist/src/wiki-graph.d.ts +74 -0
  91. package/dist/src/wiki-graph.js +335 -0
  92. package/dist/src/wiki-graph.js.map +1 -0
  93. package/dist/src/wiki-patch.d.ts +152 -0
  94. package/dist/src/wiki-patch.js +489 -0
  95. package/dist/src/wiki-patch.js.map +1 -0
  96. package/dist/src/wiki-query.d.ts +63 -0
  97. package/dist/src/wiki-query.js +255 -0
  98. package/dist/src/wiki-query.js.map +1 -0
  99. package/dist/test/cli.test.d.ts +1 -0
  100. package/dist/test/cli.test.js +514 -0
  101. package/dist/test/cli.test.js.map +1 -0
  102. package/dist/test/compiler-eval.test.d.ts +1 -0
  103. package/dist/test/compiler-eval.test.js +234 -0
  104. package/dist/test/compiler-eval.test.js.map +1 -0
  105. package/dist/test/compiler.test.d.ts +1 -0
  106. package/dist/test/compiler.test.js +2537 -0
  107. package/dist/test/compiler.test.js.map +1 -0
  108. package/dist/test/context-assembler.test.d.ts +1 -0
  109. package/dist/test/context-assembler.test.js +379 -0
  110. package/dist/test/context-assembler.test.js.map +1 -0
  111. package/dist/test/docs-linter.test.d.ts +1 -0
  112. package/dist/test/docs-linter.test.js +900 -0
  113. package/dist/test/docs-linter.test.js.map +1 -0
  114. package/dist/test/dotenv.test.d.ts +1 -0
  115. package/dist/test/dotenv.test.js +77 -0
  116. package/dist/test/dotenv.test.js.map +1 -0
  117. package/dist/test/extractors-go.test.d.ts +1 -0
  118. package/dist/test/extractors-go.test.js +393 -0
  119. package/dist/test/extractors-go.test.js.map +1 -0
  120. package/dist/test/extractors-rust.test.d.ts +1 -0
  121. package/dist/test/extractors-rust.test.js +219 -0
  122. package/dist/test/extractors-rust.test.js.map +1 -0
  123. package/dist/test/extractors-utils.test.d.ts +1 -0
  124. package/dist/test/extractors-utils.test.js +786 -0
  125. package/dist/test/extractors-utils.test.js.map +1 -0
  126. package/dist/test/fixtures/compiler-e2e/basic-node-service/repo/infra/deploy.d.ts +1 -0
  127. package/dist/test/fixtures/compiler-e2e/basic-node-service/repo/infra/deploy.js +4 -0
  128. package/dist/test/fixtures/compiler-e2e/basic-node-service/repo/infra/deploy.js.map +1 -0
  129. package/dist/test/frontmatter.test.d.ts +1 -0
  130. package/dist/test/frontmatter.test.js +287 -0
  131. package/dist/test/frontmatter.test.js.map +1 -0
  132. package/dist/test/init-planner.test.d.ts +1 -0
  133. package/dist/test/init-planner.test.js +688 -0
  134. package/dist/test/init-planner.test.js.map +1 -0
  135. package/dist/test/linter.test.d.ts +1 -0
  136. package/dist/test/linter.test.js +426 -0
  137. package/dist/test/linter.test.js.map +1 -0
  138. package/dist/test/llm-provider.test.d.ts +1 -0
  139. package/dist/test/llm-provider.test.js +783 -0
  140. package/dist/test/llm-provider.test.js.map +1 -0
  141. package/dist/test/page-ownership.test.d.ts +1 -0
  142. package/dist/test/page-ownership.test.js +247 -0
  143. package/dist/test/page-ownership.test.js.map +1 -0
  144. package/dist/test/publisher.test.d.ts +1 -0
  145. package/dist/test/publisher.test.js +1297 -0
  146. package/dist/test/publisher.test.js.map +1 -0
  147. package/dist/test/repository-analysis.test.d.ts +1 -0
  148. package/dist/test/repository-analysis.test.js +182 -0
  149. package/dist/test/repository-analysis.test.js.map +1 -0
  150. package/dist/test/run-compiled-tests.d.ts +1 -0
  151. package/dist/test/run-compiled-tests.js +48 -0
  152. package/dist/test/run-compiled-tests.js.map +1 -0
  153. package/dist/test/scanner.test.d.ts +1 -0
  154. package/dist/test/scanner.test.js +551 -0
  155. package/dist/test/scanner.test.js.map +1 -0
  156. package/dist/test/search.test.d.ts +1 -0
  157. package/dist/test/search.test.js +92 -0
  158. package/dist/test/search.test.js.map +1 -0
  159. package/dist/test/update-changelog.test.d.ts +1 -0
  160. package/dist/test/update-changelog.test.js +125 -0
  161. package/dist/test/update-changelog.test.js.map +1 -0
  162. package/dist/test/wiki-graph.test.d.ts +1 -0
  163. package/dist/test/wiki-graph.test.js +164 -0
  164. package/dist/test/wiki-graph.test.js.map +1 -0
  165. package/dist/test/wiki-patch.test.d.ts +1 -0
  166. package/dist/test/wiki-patch.test.js +610 -0
  167. package/dist/test/wiki-patch.test.js.map +1 -0
  168. package/dist/test/wiki-query.test.d.ts +1 -0
  169. package/dist/test/wiki-query.test.js +163 -0
  170. package/dist/test/wiki-query.test.js.map +1 -0
  171. package/docs/PLAN.md +993 -0
  172. package/docs/WHY.md +61 -0
  173. package/docs/plans/agent-integration.md +85 -0
  174. package/docs/plans/ci-publishing.md +111 -0
  175. package/docs/plans/doc-validation.md +92 -0
  176. package/docs/plans/github-action.md +113 -0
  177. package/docs/plans/incremental-mode.md +98 -0
  178. package/docs/plans/karpathy-llm-wiki-alignment.md +84 -0
  179. package/docs/plans/llm-compiler.md +160 -0
  180. package/docs/plans/production-scanner.md +104 -0
  181. package/docs/plans/query-and-file-back.md +103 -0
  182. package/docs/plans/search-index.md +118 -0
  183. package/docs/plans/trust-hardening.md +74 -0
  184. package/docs/plans/wiki-graph.md +183 -0
  185. package/docs/plans/wiki-health.md +76 -0
  186. package/package.json +83 -0
  187. package/prompts/compiler.md +16 -0
  188. package/prompts/lint.md +18 -0
  189. package/prompts/page-templates.md +25 -0
  190. package/skills/repo-wiki-cli/SKILL.md +139 -0
@@ -0,0 +1,2275 @@
1
+ import * as ts from 'typescript';
2
+ const JAVASCRIPT_LANGUAGES = new Set([
3
+ 'JavaScript',
4
+ 'JavaScript React',
5
+ 'TypeScript',
6
+ 'TypeScript React'
7
+ ]);
8
+ const PYTHON_LANGUAGE = 'Python';
9
+ const RUBY_LANGUAGE = 'Ruby';
10
+ const SYMBOL_LIMIT = 50;
11
+ const GO_LANGUAGE = 'Go';
12
+ const RUST_LANGUAGE = 'Rust';
13
+ const ROUTE_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'all', 'use'];
14
+ const NEST_ROUTE_DECORATORS = ['Get', 'Post', 'Put', 'Patch', 'Delete', 'Options', 'Head', 'All'];
15
+ const MAX_GRAPHQL_RESOLVER_BODY_LENGTH = 2000;
16
+ const MAX_OPENAPI_REGISTER_BODY_LENGTH = 1000;
17
+ let lastJavaScriptAstMetadata = null;
18
+ let lastGoDeclarations = null;
19
+ let lastRustDeclarations = null;
20
+ export function extractImports(content, language) {
21
+ if (language === GO_LANGUAGE) {
22
+ return extractGoImports(content);
23
+ }
24
+ if (language === RUST_LANGUAGE) {
25
+ return getRustDeclarations(content).imports;
26
+ }
27
+ if (isPython(language)) {
28
+ return extractPythonImports(content);
29
+ }
30
+ if (isRuby(language)) {
31
+ return extractRubyImports(content);
32
+ }
33
+ if (!isJavaScriptLike(language)) {
34
+ return [];
35
+ }
36
+ const imports = new Set();
37
+ const patterns = [
38
+ /import\s+(?:[^'";]+\s+from\s+)?['"]([^'"]+)['"]/g,
39
+ /export\s+[^'";]+\s+from\s+['"]([^'"]+)['"]/g,
40
+ /require\(['"]([^'"]+)['"]\)/g
41
+ ];
42
+ for (const pattern of patterns) {
43
+ for (const match of content.matchAll(pattern)) {
44
+ imports.add(match[1]);
45
+ }
46
+ }
47
+ return [...imports].sort();
48
+ }
49
+ export function extractSymbols(content, language) {
50
+ if (language === GO_LANGUAGE) {
51
+ return getGoDeclarations(content).allSymbols;
52
+ }
53
+ if (language === RUST_LANGUAGE) {
54
+ return getRustDeclarations(content).allSymbols;
55
+ }
56
+ if (isPython(language)) {
57
+ return extractPythonSymbols(content);
58
+ }
59
+ if (isRuby(language)) {
60
+ return extractRubySymbols(content);
61
+ }
62
+ if (!isJavaScriptLike(language)) {
63
+ return [];
64
+ }
65
+ const ast = extractJavaScriptAstMetadata(content, language);
66
+ if (ast) {
67
+ return [...ast.symbols].sort().slice(0, SYMBOL_LIMIT);
68
+ }
69
+ /* c8 ignore start: retained for unexpected TypeScript parser failures */
70
+ const fallbackSymbols = new Set();
71
+ const fallbackPatterns = [
72
+ /export\s+async\s+function\s+([A-Za-z_$][\w$]*)/g,
73
+ /export\s+function\s+([A-Za-z_$][\w$]*)/g,
74
+ /export\s+class\s+([A-Za-z_$][\w$]*)/g,
75
+ /export\s+const\s+([A-Za-z_$][\w$]*)/g,
76
+ /export\s+let\s+([A-Za-z_$][\w$]*)/g,
77
+ /export\s+var\s+([A-Za-z_$][\w$]*)/g,
78
+ /function\s+([A-Za-z_$][\w$]*)/g,
79
+ /class\s+([A-Za-z_$][\w$]*)/g
80
+ ];
81
+ for (const pattern of fallbackPatterns) {
82
+ for (const match of content.matchAll(pattern)) {
83
+ fallbackSymbols.add(match[1]);
84
+ }
85
+ }
86
+ return [...fallbackSymbols].sort().slice(0, SYMBOL_LIMIT);
87
+ /* c8 ignore stop */
88
+ }
89
+ export function extractExportedSymbols(content, language) {
90
+ if (language === GO_LANGUAGE) {
91
+ return getGoDeclarations(content).exported;
92
+ }
93
+ if (language === RUST_LANGUAGE) {
94
+ return getRustDeclarations(content).exported;
95
+ }
96
+ if (!isJavaScriptLike(language)) {
97
+ return [];
98
+ }
99
+ const ast = extractJavaScriptAstMetadata(content, language);
100
+ if (ast) {
101
+ return [...ast.exported]
102
+ .sort((left, right) => left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind))
103
+ .slice(0, SYMBOL_LIMIT);
104
+ }
105
+ /* c8 ignore start: retained for unexpected TypeScript parser failures */
106
+ const exported = [];
107
+ const seen = new Set();
108
+ const directPatterns = [
109
+ { pattern: /export\s+default\s+async\s+function\s+([A-Za-z_$][\w$]*)/g, kind: 'function' },
110
+ { pattern: /export\s+default\s+function(?:\s+([A-Za-z_$][\w$]*))?\s*\(/g, kind: 'function', allowDefaultName: true },
111
+ { pattern: /export\s+default\s+class(?:\s+([A-Za-z_$][\w$]*))?/g, kind: 'class', allowDefaultName: true },
112
+ { pattern: /export\s+async\s+function\s+([A-Za-z_$][\w$]*)/g, kind: 'function' },
113
+ { pattern: /export\s+function\s+([A-Za-z_$][\w$]*)/g, kind: 'function' },
114
+ { pattern: /export\s+class\s+([A-Za-z_$][\w$]*)/g, kind: 'class' },
115
+ { pattern: /export\s+const\s+([A-Za-z_$][\w$]*)/g, kind: 'const' },
116
+ { pattern: /export\s+let\s+([A-Za-z_$][\w$]*)/g, kind: 'let' },
117
+ { pattern: /export\s+var\s+([A-Za-z_$][\w$]*)/g, kind: 'var' },
118
+ { pattern: /export\s+type\s+([A-Za-z_$][\w$]*)/g, kind: 'type' },
119
+ { pattern: /export\s+interface\s+([A-Za-z_$][\w$]*)/g, kind: 'interface' },
120
+ { pattern: /export\s+enum\s+([A-Za-z_$][\w$]*)/g, kind: 'enum' }
121
+ ];
122
+ for (const { pattern, kind, allowDefaultName = false } of directPatterns) {
123
+ for (const match of content.matchAll(pattern)) {
124
+ const name = match[1] || (allowDefaultName ? 'default' : null);
125
+ if (!name) {
126
+ continue;
127
+ }
128
+ pushExportedSymbol(exported, seen, { name, kind });
129
+ }
130
+ }
131
+ for (const match of content.matchAll(/export\s*\{([^}]+)\}/g)) {
132
+ for (const name of parseNamedExports(match[1])) {
133
+ pushExportedSymbol(exported, seen, { name, kind: 'named-export' });
134
+ }
135
+ }
136
+ return exported
137
+ .sort((left, right) => left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind))
138
+ .slice(0, SYMBOL_LIMIT);
139
+ /* c8 ignore stop */
140
+ }
141
+ export function extractEnvironmentVariables(content, language) {
142
+ if (!isJavaScriptLike(language)) {
143
+ return [];
144
+ }
145
+ const names = new Set();
146
+ const directPatterns = [
147
+ /process\.env(?:\?\.|\.)\s*([A-Za-z_][A-Za-z0-9_]*)/g,
148
+ /process\.env\s*\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]/g,
149
+ /import\.meta\.env(?:\?\.|\.)\s*([A-Za-z_][A-Za-z0-9_]*)/g,
150
+ /import\.meta\.env\s*\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]/g,
151
+ /\b(?:optionalEnv|requiredEnv)\s*\(\s*[A-Za-z_$][\w$]*\s*,\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\)/g
152
+ ];
153
+ for (const pattern of directPatterns) {
154
+ for (const match of content.matchAll(pattern)) {
155
+ names.add(match[1]);
156
+ }
157
+ }
158
+ collectDestructuredEnvNames(content, /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*process\.env\b/g, names);
159
+ collectDestructuredEnvNames(content, /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*import\.meta\.env\b/g, names);
160
+ return [...names].sort();
161
+ }
162
+ export function extractRouteSurfaces(filePath, content, language) {
163
+ if (!isJavaScriptLike(language)) {
164
+ return [];
165
+ }
166
+ const surfaces = [];
167
+ const seen = new Set();
168
+ const targets = inferRouteTargets(content);
169
+ const callPattern = new RegExp(`([A-Za-z_$][\\w$]*)\\s*\\.\\s*(${ROUTE_METHODS.join('|')})\\s*\\(\\s*(['"\`])([^'"\`]+)\\3`, 'g');
170
+ for (const match of content.matchAll(callPattern)) {
171
+ const target = match[1];
172
+ const handler = inferHandlerName(content, (match.index || 0) + match[0].length);
173
+ pushRouteSurface(surfaces, seen, {
174
+ kind: 'http-route',
175
+ framework: inferRouteFramework(target, targets),
176
+ target,
177
+ methods: [match[2].toUpperCase()],
178
+ path: match[4],
179
+ handler
180
+ });
181
+ }
182
+ for (const match of content.matchAll(/([A-Za-z_$][\w$]*)\s*\.\s*route\s*\(\s*\{([\s\S]{0,500}?)\}\s*\)/g)) {
183
+ const methods = parseRouteMethods(match[2]);
184
+ const routePath = match[2].match(/\b(?:url|path)\s*:\s*(['"`])([^'"`]+)\1/);
185
+ if (!methods.length || !routePath) {
186
+ continue;
187
+ }
188
+ const handler = match[2].match(/\bhandler\s*:\s*([A-Za-z_$][\w$]*)\b/)?.[1] || null;
189
+ pushRouteSurface(surfaces, seen, {
190
+ kind: 'http-route',
191
+ framework: inferRouteFramework(match[1], targets),
192
+ target: match[1],
193
+ methods,
194
+ path: routePath[2],
195
+ handler
196
+ });
197
+ }
198
+ extractNestRouteSurfaces(content, surfaces, seen);
199
+ extractTrpcRouteSurfaces(content, surfaces, seen);
200
+ extractGraphqlRouteSurfaces(content, surfaces, seen);
201
+ extractOpenApiRouteSurfaces(content, surfaces, seen);
202
+ const routeHandlerPath = inferFileRoutePath(filePath);
203
+ if (routeHandlerPath) {
204
+ const routeHandlerPatterns = [
205
+ /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(/g,
206
+ /export\s+const\s+(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*=/g
207
+ ];
208
+ for (const pattern of routeHandlerPatterns) {
209
+ for (const match of content.matchAll(pattern)) {
210
+ pushRouteSurface(surfaces, seen, {
211
+ kind: 'http-route',
212
+ framework: 'route-handler',
213
+ target: 'module',
214
+ methods: [match[1]],
215
+ path: routeHandlerPath,
216
+ handler: match[1]
217
+ });
218
+ }
219
+ }
220
+ }
221
+ return surfaces.sort(compareRouteSurfaces);
222
+ }
223
+ export function extractMigrationSurfaces(filePath, language) {
224
+ const normalized = normalizePath(filePath);
225
+ const lower = normalized.toLowerCase();
226
+ const isSqlLike = language === 'SQL' || lower.endsWith('.sql');
227
+ const inMigrationDirectory = /(^|\/)(?:db\/)?migrations?\//.test(lower) || /(^|\/)prisma\/migrations\//.test(lower);
228
+ if (!isSqlLike) {
229
+ return [];
230
+ }
231
+ const baseName = normalized.split('/').pop() || normalized;
232
+ const extensionless = baseName.replace(/\.[^.]+$/, '');
233
+ const looksLikeMigrationFile = /^v\d+(?:[._]\d+)*__.+$/i.test(extensionless) ||
234
+ /^\d{3,}[._-].+$/i.test(extensionless) ||
235
+ /^.+\.(?:up|down)$/i.test(extensionless) ||
236
+ /^migration$/i.test(extensionless);
237
+ if (!inMigrationDirectory && !looksLikeMigrationFile) {
238
+ return [];
239
+ }
240
+ const prismaMigration = normalized.match(/(?:^|\/)prisma\/migrations\/([^/]+)\/migration\.sql$/i);
241
+ if (prismaMigration) {
242
+ const folder = prismaMigration[1];
243
+ return [{
244
+ kind: 'prisma-migration',
245
+ id: extractMigrationId(folder),
246
+ name: cleanMigrationName(folder)
247
+ }];
248
+ }
249
+ const flywayMatch = extensionless.match(/^v([0-9][0-9._]*)__(.+)$/i);
250
+ if (flywayMatch) {
251
+ return [{
252
+ kind: 'sql-migration',
253
+ id: flywayMatch[1].replace(/_/g, '.'),
254
+ name: cleanMigrationName(flywayMatch[2])
255
+ }];
256
+ }
257
+ return [{
258
+ kind: 'sql-migration',
259
+ id: extractMigrationId(extensionless),
260
+ name: cleanMigrationName(extensionless)
261
+ }];
262
+ }
263
+ export function extractModelSurfaces(filePath, content, language) {
264
+ const surfaces = [];
265
+ const seen = new Set();
266
+ const lower = filePath.toLowerCase();
267
+ if (lower.endsWith('schema.prisma')) {
268
+ for (const match of content.matchAll(/(?:^|\n)\s*model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g)) {
269
+ pushModelSurface(surfaces, seen, { name: match[1], kind: 'model', framework: 'prisma' });
270
+ }
271
+ }
272
+ if (isJavaScriptLike(language)) {
273
+ const hasSequelizeSignal = /\bfrom\s+['"]sequelize['"]|require\(\s*['"]sequelize['"]\s*\)|\bsequelize\s*\./.test(content);
274
+ for (const match of content.matchAll(/@Entity(?:\s*\([^)]*\))?(?:\s*@[\w$]+(?:\s*\([^)]*\))?)*\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/g)) {
275
+ pushModelSurface(surfaces, seen, { name: match[1], kind: 'entity', framework: 'typeorm' });
276
+ }
277
+ if (hasSequelizeSignal) {
278
+ for (const match of content.matchAll(/\bclass\s+([A-Za-z_$][\w$]*)\s+extends\s+Model\b/g)) {
279
+ pushModelSurface(surfaces, seen, { name: match[1], kind: 'model', framework: 'sequelize' });
280
+ }
281
+ }
282
+ for (const match of content.matchAll(/sequelize\s*\.\s*define\s*\(\s*['"`]([A-Za-z_$][\w$]*)['"`]/g)) {
283
+ pushModelSurface(surfaces, seen, { name: match[1], kind: 'model', framework: 'sequelize' });
284
+ }
285
+ for (const match of content.matchAll(/mongoose\s*\.\s*model\s*\(\s*['"`]([A-Za-z_$][\w$]*)['"`]/g)) {
286
+ pushModelSurface(surfaces, seen, { name: match[1], kind: 'model', framework: 'mongoose' });
287
+ }
288
+ }
289
+ return surfaces
290
+ .sort((left, right) => left.name.localeCompare(right.name) || left.framework.localeCompare(right.framework) || left.kind.localeCompare(right.kind))
291
+ .slice(0, 50);
292
+ }
293
+ export function detectRuntimeHints(filePath, content, metadata = {}) {
294
+ const hints = [];
295
+ const lower = filePath.toLowerCase();
296
+ const language = metadata.language || inferRuntimeHintLanguage(filePath);
297
+ const routeSurfaces = metadata.routeSurfaces || extractRouteSurfaces(filePath, content, language);
298
+ const environmentVariables = metadata.environmentVariables || extractEnvironmentVariables(content, language);
299
+ const migrationSurfaces = metadata.migrationSurfaces || extractMigrationSurfaces(filePath, language);
300
+ const modelSurfaces = metadata.modelSurfaces || extractModelSurfaces(filePath, content, language);
301
+ if (routeSurfaces.length > 0) {
302
+ hints.push('http-route');
303
+ }
304
+ if (environmentVariables.length > 0) {
305
+ hints.push('environment-variable');
306
+ }
307
+ if (migrationSurfaces.length > 0) {
308
+ hints.push('database-migration');
309
+ }
310
+ if (modelSurfaces.length > 0) {
311
+ hints.push('orm-model');
312
+ }
313
+ if (migrationSurfaces.length > 0 || modelSurfaces.length > 0) {
314
+ hints.push('data-model');
315
+ }
316
+ if (/cron|schedule|queue|worker|job/i.test(filePath + '\n' + content.slice(0, 2000))) {
317
+ hints.push('background-work');
318
+ }
319
+ if (lower.includes('dockerfile') || lower.includes('docker-compose') || lower.includes('/infra/')) {
320
+ hints.push('deployment');
321
+ }
322
+ return [...new Set(hints)].sort();
323
+ }
324
+ export function extractGoPackage(content, language) {
325
+ if (language !== GO_LANGUAGE) {
326
+ return null;
327
+ }
328
+ const code = stripGoCommentsAndLiterals(content);
329
+ const match = code.match(/^\s*package\s+([A-Za-z_]\w*)\b/m);
330
+ return match ? match[1] : null;
331
+ }
332
+ function inferRuntimeHintLanguage(filePath) {
333
+ return filePath.toLowerCase().endsWith('.sql') ? 'SQL' : 'JavaScript';
334
+ }
335
+ function isJavaScriptLike(language) {
336
+ return JAVASCRIPT_LANGUAGES.has(language);
337
+ }
338
+ function isPython(language) {
339
+ return language === PYTHON_LANGUAGE;
340
+ }
341
+ function isRuby(language) {
342
+ return language === RUBY_LANGUAGE;
343
+ }
344
+ function extractRubyImports(content) {
345
+ const imports = new Set();
346
+ const code = stripRubyComments(content);
347
+ for (const match of code.matchAll(/(?:^|\n)[ \t]*require[ \t]*(?:\([ \t]*)?['"]([^'"\n]+)['"]/g)) {
348
+ const specifier = match[1].trim();
349
+ if (specifier) {
350
+ imports.add(specifier);
351
+ }
352
+ }
353
+ for (const match of code.matchAll(/(?:^|\n)[ \t]*require_relative[ \t]*(?:\([ \t]*)?['"]([^'"\n]+)['"]/g)) {
354
+ const specifier = match[1].trim();
355
+ if (!specifier) {
356
+ continue;
357
+ }
358
+ imports.add(specifier.startsWith('.') ? specifier : `./${specifier}`);
359
+ }
360
+ return [...imports].sort();
361
+ }
362
+ function extractRubySymbols(content) {
363
+ const symbols = new Set();
364
+ const scopeStack = [];
365
+ const lines = stripRubyComments(content).split(/\r?\n/);
366
+ for (const rawLine of lines) {
367
+ const line = rawLine.trim();
368
+ if (!line) {
369
+ continue;
370
+ }
371
+ const moduleMatch = line.match(/^module\s+([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\b/);
372
+ if (moduleMatch) {
373
+ const name = resolveRubyScopedName(moduleMatch[1], scopeStack);
374
+ symbols.add(name);
375
+ scopeStack.push({ kind: 'module', name });
376
+ }
377
+ if (/^class\s+<<\s*self\b/.test(line)) {
378
+ scopeStack.push({ kind: 'singleton', name: currentRubyNamespace(scopeStack) });
379
+ }
380
+ else {
381
+ const classMatch = line.match(/^class\s+([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\b/);
382
+ if (classMatch) {
383
+ const name = resolveRubyScopedName(classMatch[1], scopeStack);
384
+ symbols.add(name);
385
+ scopeStack.push({ kind: 'class', name });
386
+ }
387
+ }
388
+ const defMatch = line.match(/^def\s+((?:self|[A-Z][A-Za-z0-9_:]*)\.)?([A-Za-z_][A-Za-z0-9_]*[!?=]?|[+\-*/%<>=~`|^&]+)(?=\s|$|;)/);
389
+ if (defMatch) {
390
+ const receiverPrefix = defMatch[1];
391
+ const methodName = defMatch[2];
392
+ let methodSymbol = methodName;
393
+ if (receiverPrefix) {
394
+ const receiverRaw = receiverPrefix.slice(0, -1);
395
+ const receiver = receiverRaw === 'self'
396
+ ? (currentRubyClassScope(scopeStack) || currentRubyNamespace(scopeStack) || 'self')
397
+ : resolveRubyScopedName(receiverRaw, scopeStack);
398
+ methodSymbol = `${receiver}.${methodName}`;
399
+ }
400
+ else {
401
+ const singletonScope = currentRubySingletonScope(scopeStack);
402
+ if (singletonScope) {
403
+ methodSymbol = `${singletonScope}.${methodName}`;
404
+ }
405
+ else {
406
+ const classScope = currentRubyClassScope(scopeStack);
407
+ if (classScope) {
408
+ methodSymbol = `${classScope}#${methodName}`;
409
+ }
410
+ }
411
+ }
412
+ symbols.add(methodSymbol);
413
+ scopeStack.push({ kind: 'def', name: null });
414
+ }
415
+ const constantMatch = line.match(/^([A-Z][A-Za-z0-9_]*)(?:\s*=(?!=)|\s*\|\|=)/);
416
+ if (constantMatch) {
417
+ symbols.add(qualifyRubyConstant(constantMatch[1], scopeStack));
418
+ }
419
+ const normalizedLine = stripRubyQuotedStrings(line);
420
+ const blockOpenerCount = countRubyBlockOpeners(normalizedLine);
421
+ for (let index = 0; index < blockOpenerCount; index += 1) {
422
+ scopeStack.push({ kind: 'block', name: null });
423
+ }
424
+ const endCount = countRubyEndKeywords(normalizedLine);
425
+ for (let index = 0; index < endCount; index += 1) {
426
+ if (scopeStack.length > 0) {
427
+ scopeStack.pop();
428
+ }
429
+ }
430
+ }
431
+ return [...symbols].sort().slice(0, SYMBOL_LIMIT);
432
+ }
433
+ function resolveRubyScopedName(name, scopeStack) {
434
+ const normalized = name.replace(/^::/, '');
435
+ if (normalized.includes('::')) {
436
+ return normalized;
437
+ }
438
+ const namespace = currentRubyNamespace(scopeStack);
439
+ return namespace ? `${namespace}::${normalized}` : normalized;
440
+ }
441
+ function currentRubyNamespace(scopeStack) {
442
+ for (let index = scopeStack.length - 1; index >= 0; index -= 1) {
443
+ const scope = scopeStack[index];
444
+ if ((scope.kind === 'module' || scope.kind === 'class') && scope.name) {
445
+ return scope.name;
446
+ }
447
+ }
448
+ return null;
449
+ }
450
+ function currentRubyClassScope(scopeStack) {
451
+ for (let index = scopeStack.length - 1; index >= 0; index -= 1) {
452
+ const scope = scopeStack[index];
453
+ if (scope.kind === 'class' && scope.name) {
454
+ return scope.name;
455
+ }
456
+ }
457
+ return null;
458
+ }
459
+ function currentRubySingletonScope(scopeStack) {
460
+ for (let index = scopeStack.length - 1; index >= 0; index -= 1) {
461
+ const scope = scopeStack[index];
462
+ if (scope.kind === 'singleton') {
463
+ return scope.name;
464
+ }
465
+ }
466
+ return null;
467
+ }
468
+ function qualifyRubyConstant(name, scopeStack) {
469
+ const namespace = currentRubyNamespace(scopeStack);
470
+ return namespace ? `${namespace}::${name}` : name;
471
+ }
472
+ function stripRubyComments(content) {
473
+ const lines = content.split(/\r?\n/);
474
+ const strippedLines = [];
475
+ let inBlockComment = false;
476
+ const heredocDelimiters = [];
477
+ for (const line of lines) {
478
+ const trimmed = line.trim();
479
+ if (heredocDelimiters.length > 0) {
480
+ if (trimmed === heredocDelimiters[0]) {
481
+ heredocDelimiters.shift();
482
+ }
483
+ strippedLines.push('');
484
+ continue;
485
+ }
486
+ if (inBlockComment) {
487
+ if (/^\s*=end\b/.test(line)) {
488
+ inBlockComment = false;
489
+ }
490
+ strippedLines.push('');
491
+ continue;
492
+ }
493
+ if (/^\s*=begin\b/.test(line)) {
494
+ inBlockComment = true;
495
+ strippedLines.push('');
496
+ continue;
497
+ }
498
+ for (const delimiter of extractRubyHeredocDelimiters(line)) {
499
+ heredocDelimiters.push(delimiter);
500
+ }
501
+ strippedLines.push(stripRubyInlineComment(line));
502
+ }
503
+ return strippedLines.join('\n');
504
+ }
505
+ function extractRubyHeredocDelimiters(line) {
506
+ const delimiters = [];
507
+ const code = stripRubyInlineComment(line);
508
+ let quote = null;
509
+ for (let index = 0; index < code.length; index += 1) {
510
+ const current = code[index];
511
+ if (quote) {
512
+ if (current === '\\') {
513
+ index += 1;
514
+ continue;
515
+ }
516
+ if (current === quote) {
517
+ quote = null;
518
+ }
519
+ continue;
520
+ }
521
+ if (current === '"' || current === "'" || current === '`') {
522
+ quote = current;
523
+ continue;
524
+ }
525
+ if (current !== '<' || code[index + 1] !== '<') {
526
+ continue;
527
+ }
528
+ const before = code.slice(0, index).trimEnd();
529
+ let cursor = index + 2;
530
+ if (code[cursor] === '-' || code[cursor] === '~') {
531
+ cursor += 1;
532
+ }
533
+ const hasWhitespace = /[ \t]/.test(code[cursor] || '');
534
+ while (/[ \t]/.test(code[cursor] || '')) {
535
+ cursor += 1;
536
+ }
537
+ const delimiterQuote = code[cursor] === '"' || code[cursor] === "'" || code[cursor] === '`' ? code[cursor] : null;
538
+ if (hasWhitespace && !delimiterQuote) {
539
+ continue;
540
+ }
541
+ if (delimiterQuote) {
542
+ cursor += 1;
543
+ }
544
+ const match = /^[A-Za-z_][A-Za-z0-9_]*/.exec(code.slice(cursor));
545
+ if (!match) {
546
+ continue;
547
+ }
548
+ const delimiter = match[0];
549
+ if (delimiterQuote && code[cursor + delimiter.length] !== delimiterQuote) {
550
+ continue;
551
+ }
552
+ if (delimiter === 'self' && /\bclass\s*$/.test(before)) {
553
+ continue;
554
+ }
555
+ delimiters.push(delimiter);
556
+ }
557
+ return delimiters;
558
+ }
559
+ function stripRubyInlineComment(line) {
560
+ let result = '';
561
+ let quote = null;
562
+ for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
563
+ const current = line[charIndex];
564
+ if (quote) {
565
+ result += current;
566
+ if (current === '\\') {
567
+ charIndex += 1;
568
+ result += line[charIndex] || '';
569
+ continue;
570
+ }
571
+ if (current === quote) {
572
+ quote = null;
573
+ }
574
+ continue;
575
+ }
576
+ if (current === '"' || current === "'" || current === '`') {
577
+ quote = current;
578
+ result += current;
579
+ continue;
580
+ }
581
+ if (current === '#') {
582
+ break;
583
+ }
584
+ result += current;
585
+ }
586
+ return result;
587
+ }
588
+ function stripRubyQuotedStrings(line) {
589
+ let result = '';
590
+ let quote = null;
591
+ for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
592
+ const current = line[charIndex];
593
+ if (quote) {
594
+ if (current === '\\') {
595
+ charIndex += 1;
596
+ continue;
597
+ }
598
+ if (current === quote) {
599
+ quote = null;
600
+ }
601
+ continue;
602
+ }
603
+ if (current === '"' || current === "'" || current === '`') {
604
+ quote = current;
605
+ continue;
606
+ }
607
+ result += current;
608
+ }
609
+ return result;
610
+ }
611
+ function countRubyBlockOpeners(line) {
612
+ const normalized = line.trim();
613
+ let count = 0;
614
+ const opensKeywordBlock = /^(?:if|unless|case|begin|for|while|until)\b/.test(normalized);
615
+ if (opensKeywordBlock) {
616
+ count += 1;
617
+ }
618
+ if (!opensKeywordBlock) {
619
+ count += normalized.match(/\bdo\b/g)?.length || 0;
620
+ }
621
+ return count;
622
+ }
623
+ function countRubyEndKeywords(line) {
624
+ let count = 0;
625
+ const sanitized = stripRubyQuotedStrings(stripRubyInlineComment(line));
626
+ for (const match of sanitized.matchAll(/\bend\b/g)) {
627
+ const prefix = sanitized.slice(0, match.index).trimEnd();
628
+ const previous = prefix[prefix.length - 1];
629
+ if (previous === ':' || previous === '.') {
630
+ continue;
631
+ }
632
+ count += 1;
633
+ }
634
+ return count;
635
+ }
636
+ function extractPythonImports(content) {
637
+ const imports = new Set();
638
+ const lines = stripPythonTripleQuotedStrings(content).split(/\r?\n/);
639
+ for (const rawLine of lines) {
640
+ if (!isTopLevelLine(rawLine)) {
641
+ continue;
642
+ }
643
+ const line = stripInlineComment(rawLine).trim();
644
+ if (!line) {
645
+ continue;
646
+ }
647
+ const importMatch = line.match(/^import\s+(.+)$/);
648
+ if (importMatch) {
649
+ for (const entry of importMatch[1].split(',')) {
650
+ const specifier = entry.trim().split(/\s+as\s+/i)[0]?.trim();
651
+ if (specifier && /^[A-Za-z_][\w.]*$/.test(specifier)) {
652
+ imports.add(specifier);
653
+ }
654
+ }
655
+ continue;
656
+ }
657
+ const fromMatch = line.match(/^from\s+([.\w]+)\s+import\s+/);
658
+ if (fromMatch) {
659
+ imports.add(fromMatch[1]);
660
+ }
661
+ }
662
+ return [...imports].sort();
663
+ }
664
+ function extractPythonSymbols(content) {
665
+ const symbols = new Set();
666
+ const lines = stripPythonTripleQuotedStrings(content).split(/\r?\n/);
667
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
668
+ const rawLine = lines[lineIndex];
669
+ if (!isTopLevelLine(rawLine)) {
670
+ continue;
671
+ }
672
+ const line = stripInlineComment(rawLine).trim();
673
+ if (!line) {
674
+ continue;
675
+ }
676
+ if (line.startsWith('async def ')) {
677
+ const asyncSignature = collectPythonSignature(lines, lineIndex);
678
+ const asyncFunction = findPythonFunctionName(asyncSignature.signature, true);
679
+ if (asyncFunction) {
680
+ symbols.add(asyncFunction);
681
+ lineIndex = asyncSignature.endLineIndex;
682
+ }
683
+ continue;
684
+ }
685
+ if (line.startsWith('def ')) {
686
+ const functionSignature = collectPythonSignature(lines, lineIndex);
687
+ const functionName = findPythonFunctionName(functionSignature.signature, false);
688
+ if (functionName) {
689
+ symbols.add(functionName);
690
+ lineIndex = functionSignature.endLineIndex;
691
+ }
692
+ continue;
693
+ }
694
+ const classMatch = line.match(/^class\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\([^)]*\))?\s*:/);
695
+ if (classMatch) {
696
+ symbols.add(classMatch[1]);
697
+ continue;
698
+ }
699
+ const constantName = findPythonModuleConstant(line);
700
+ if (constantName) {
701
+ symbols.add(constantName);
702
+ }
703
+ }
704
+ return [...symbols].sort().slice(0, SYMBOL_LIMIT);
705
+ }
706
+ function stripInlineComment(line) {
707
+ let quote = null;
708
+ for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
709
+ const current = line[charIndex];
710
+ if (quote) {
711
+ if (current === '\\') {
712
+ charIndex += 1;
713
+ continue;
714
+ }
715
+ if (current === quote) {
716
+ quote = null;
717
+ }
718
+ continue;
719
+ }
720
+ if (current === '"' || current === "'") {
721
+ quote = current;
722
+ continue;
723
+ }
724
+ if (current === '#') {
725
+ return line.slice(0, charIndex);
726
+ }
727
+ }
728
+ return line;
729
+ }
730
+ function isTopLevelLine(line) {
731
+ return /^\S/.test(line);
732
+ }
733
+ function stripPythonTripleQuotedStrings(content) {
734
+ let result = '';
735
+ let tripleQuote = null;
736
+ let inlineQuote = null;
737
+ for (let charIndex = 0; charIndex < content.length; charIndex += 1) {
738
+ const current = content[charIndex];
739
+ if (tripleQuote) {
740
+ if (content.startsWith(tripleQuote, charIndex) && !isEscaped(content, charIndex)) {
741
+ tripleQuote = null;
742
+ charIndex += 2;
743
+ }
744
+ else if (current === '\n') {
745
+ result += '\n';
746
+ }
747
+ continue;
748
+ }
749
+ if (inlineQuote) {
750
+ result += current;
751
+ if (current === '\\') {
752
+ charIndex += 1;
753
+ result += content[charIndex] || '';
754
+ continue;
755
+ }
756
+ if (current === inlineQuote) {
757
+ inlineQuote = null;
758
+ }
759
+ continue;
760
+ }
761
+ if (content.startsWith("'''", charIndex)) {
762
+ tripleQuote = "'''";
763
+ charIndex += 2;
764
+ continue;
765
+ }
766
+ if (content.startsWith('"""', charIndex)) {
767
+ tripleQuote = '"""';
768
+ charIndex += 2;
769
+ continue;
770
+ }
771
+ if (current === '"' || current === "'") {
772
+ inlineQuote = current;
773
+ }
774
+ result += current;
775
+ }
776
+ return result;
777
+ }
778
+ function isEscaped(content, charIndex) {
779
+ let slashCount = 0;
780
+ for (let index = charIndex - 1; index >= 0 && content[index] === '\\'; index -= 1) {
781
+ slashCount += 1;
782
+ }
783
+ return slashCount % 2 === 1;
784
+ }
785
+ function collectPythonSignature(lines, startLineIndex) {
786
+ const signatureParts = [];
787
+ let lineIndex = startLineIndex;
788
+ let consumedUntil = startLineIndex;
789
+ let parenthesesDepth = 0;
790
+ let sawColon = false;
791
+ for (; lineIndex < lines.length; lineIndex += 1) {
792
+ const sourceLine = lines[lineIndex];
793
+ const currentLine = stripInlineComment(sourceLine).trim();
794
+ if (!currentLine) {
795
+ if (lineIndex > startLineIndex) {
796
+ break;
797
+ }
798
+ continue;
799
+ }
800
+ if (lineIndex > startLineIndex && isTopLevelLine(sourceLine) && parenthesesDepth <= 0) {
801
+ break;
802
+ }
803
+ signatureParts.push(currentLine);
804
+ consumedUntil = lineIndex;
805
+ parenthesesDepth += countParenthesisDelta(currentLine);
806
+ sawColon = sawColon || hasTopLevelColon(currentLine);
807
+ if (sawColon && parenthesesDepth <= 0) {
808
+ break;
809
+ }
810
+ }
811
+ return {
812
+ signature: signatureParts.join(' '),
813
+ endLineIndex: consumedUntil
814
+ };
815
+ }
816
+ function findPythonFunctionName(line, isAsyncFunction) {
817
+ const prefix = isAsyncFunction ? 'async def ' : 'def ';
818
+ if (!line.startsWith(prefix)) {
819
+ return null;
820
+ }
821
+ const rest = line.slice(prefix.length);
822
+ const nameMatch = rest.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*/);
823
+ if (!nameMatch) {
824
+ return null;
825
+ }
826
+ const name = nameMatch[1];
827
+ const signatureStart = prefix.length + nameMatch[0].length;
828
+ if (line[signatureStart] !== '(') {
829
+ return null;
830
+ }
831
+ const signatureEnd = findMatchingParenthesis(line, signatureStart);
832
+ if (signatureEnd < 0) {
833
+ return null;
834
+ }
835
+ const suffix = line.slice(signatureEnd + 1).trim();
836
+ if (suffix.startsWith(':')) {
837
+ return name;
838
+ }
839
+ if (suffix.startsWith('->')) {
840
+ return suffix.includes(':') ? name : null;
841
+ }
842
+ return null;
843
+ }
844
+ function findPythonModuleConstant(line) {
845
+ const assignmentOffset = findTopLevelAssignmentOffset(line);
846
+ if (assignmentOffset < 0) {
847
+ return null;
848
+ }
849
+ const lhs = line.slice(0, assignmentOffset).trim();
850
+ const match = lhs.match(/^([A-Z][A-Z0-9_]*)(?:\s*:.+?)?$/);
851
+ return match ? match[1] : null;
852
+ }
853
+ function findTopLevelAssignmentOffset(line) {
854
+ let quote = null;
855
+ let parenthesesDepth = 0;
856
+ let bracketDepth = 0;
857
+ let braceDepth = 0;
858
+ for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
859
+ const current = line[charIndex];
860
+ if (quote) {
861
+ if (current === '\\') {
862
+ charIndex += 1;
863
+ continue;
864
+ }
865
+ if (current === quote) {
866
+ quote = null;
867
+ }
868
+ continue;
869
+ }
870
+ if (current === '"' || current === "'") {
871
+ quote = current;
872
+ continue;
873
+ }
874
+ if (current === '(') {
875
+ parenthesesDepth += 1;
876
+ continue;
877
+ }
878
+ if (current === ')' && parenthesesDepth > 0) {
879
+ parenthesesDepth -= 1;
880
+ continue;
881
+ }
882
+ if (current === '[') {
883
+ bracketDepth += 1;
884
+ continue;
885
+ }
886
+ if (current === ']' && bracketDepth > 0) {
887
+ bracketDepth -= 1;
888
+ continue;
889
+ }
890
+ if (current === '{') {
891
+ braceDepth += 1;
892
+ continue;
893
+ }
894
+ if (current === '}' && braceDepth > 0) {
895
+ braceDepth -= 1;
896
+ continue;
897
+ }
898
+ if (current !== '=' || parenthesesDepth > 0 || bracketDepth > 0 || braceDepth > 0) {
899
+ continue;
900
+ }
901
+ const previous = line[charIndex - 1];
902
+ const next = line[charIndex + 1];
903
+ if (previous === '=' || previous === '!' || previous === '<' || previous === '>') {
904
+ continue;
905
+ }
906
+ if (next === '=') {
907
+ continue;
908
+ }
909
+ return charIndex;
910
+ }
911
+ return -1;
912
+ }
913
+ function findMatchingParenthesis(line, startOffset) {
914
+ let quote = null;
915
+ let depth = 0;
916
+ for (let charIndex = startOffset; charIndex < line.length; charIndex += 1) {
917
+ const current = line[charIndex];
918
+ if (quote) {
919
+ if (current === '\\') {
920
+ charIndex += 1;
921
+ continue;
922
+ }
923
+ if (current === quote) {
924
+ quote = null;
925
+ }
926
+ continue;
927
+ }
928
+ if (current === '"' || current === "'") {
929
+ quote = current;
930
+ continue;
931
+ }
932
+ if (current === '(') {
933
+ depth += 1;
934
+ continue;
935
+ }
936
+ if (current === ')') {
937
+ depth -= 1;
938
+ if (depth === 0) {
939
+ return charIndex;
940
+ }
941
+ }
942
+ }
943
+ return -1;
944
+ }
945
+ function countParenthesisDelta(line) {
946
+ let quote = null;
947
+ let depth = 0;
948
+ for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
949
+ const current = line[charIndex];
950
+ if (quote) {
951
+ if (current === '\\') {
952
+ charIndex += 1;
953
+ continue;
954
+ }
955
+ if (current === quote) {
956
+ quote = null;
957
+ }
958
+ continue;
959
+ }
960
+ if (current === '"' || current === "'") {
961
+ quote = current;
962
+ continue;
963
+ }
964
+ if (current === '(') {
965
+ depth += 1;
966
+ }
967
+ else if (current === ')') {
968
+ depth -= 1;
969
+ }
970
+ }
971
+ return depth;
972
+ }
973
+ function hasTopLevelColon(line) {
974
+ let quote = null;
975
+ let parenthesesDepth = 0;
976
+ let bracketDepth = 0;
977
+ let braceDepth = 0;
978
+ for (let charIndex = 0; charIndex < line.length; charIndex += 1) {
979
+ const current = line[charIndex];
980
+ if (quote) {
981
+ if (current === '\\') {
982
+ charIndex += 1;
983
+ continue;
984
+ }
985
+ if (current === quote) {
986
+ quote = null;
987
+ }
988
+ continue;
989
+ }
990
+ if (current === '"' || current === "'") {
991
+ quote = current;
992
+ continue;
993
+ }
994
+ if (current === '(') {
995
+ parenthesesDepth += 1;
996
+ continue;
997
+ }
998
+ if (current === ')' && parenthesesDepth > 0) {
999
+ parenthesesDepth -= 1;
1000
+ continue;
1001
+ }
1002
+ if (current === '[') {
1003
+ bracketDepth += 1;
1004
+ continue;
1005
+ }
1006
+ if (current === ']' && bracketDepth > 0) {
1007
+ bracketDepth -= 1;
1008
+ continue;
1009
+ }
1010
+ if (current === '{') {
1011
+ braceDepth += 1;
1012
+ continue;
1013
+ }
1014
+ if (current === '}' && braceDepth > 0) {
1015
+ braceDepth -= 1;
1016
+ continue;
1017
+ }
1018
+ if (current === ':' && parenthesesDepth === 0 && bracketDepth === 0 && braceDepth === 0) {
1019
+ return true;
1020
+ }
1021
+ }
1022
+ return false;
1023
+ }
1024
+ function extractJavaScriptAstMetadata(content, language) {
1025
+ if (lastJavaScriptAstMetadata?.content === content && lastJavaScriptAstMetadata.language === language) {
1026
+ return lastJavaScriptAstMetadata.metadata;
1027
+ }
1028
+ try {
1029
+ const sourceFile = ts.createSourceFile(language.startsWith('TypeScript') ? 'module.ts' : 'module.js', content, ts.ScriptTarget.Latest, true, language === 'TypeScript React'
1030
+ ? ts.ScriptKind.TSX
1031
+ : language === 'TypeScript'
1032
+ ? ts.ScriptKind.TS
1033
+ : language === 'JavaScript React'
1034
+ ? ts.ScriptKind.JSX
1035
+ : ts.ScriptKind.JS);
1036
+ const symbols = new Set();
1037
+ const exported = [];
1038
+ const seenExported = new Set();
1039
+ const declarationKinds = collectTopLevelDeclarationKinds(sourceFile);
1040
+ for (const statement of sourceFile.statements) {
1041
+ const modifierFlags = getModifierFlags(statement);
1042
+ if (ts.isFunctionDeclaration(statement)) {
1043
+ if (statement.name) {
1044
+ const name = statement.name.text;
1045
+ symbols.add(name);
1046
+ if (modifierFlags.defaultExport) {
1047
+ pushExportedSymbol(exported, seenExported, { name: 'default', kind: 'function' });
1048
+ }
1049
+ else if (modifierFlags.exported) {
1050
+ pushExportedSymbol(exported, seenExported, { name, kind: 'function' });
1051
+ }
1052
+ }
1053
+ else if (modifierFlags.defaultExport) {
1054
+ symbols.add('default');
1055
+ pushExportedSymbol(exported, seenExported, { name: 'default', kind: 'function' });
1056
+ }
1057
+ continue;
1058
+ }
1059
+ if (ts.isClassDeclaration(statement)) {
1060
+ if (statement.name) {
1061
+ const name = statement.name.text;
1062
+ symbols.add(name);
1063
+ if (modifierFlags.defaultExport) {
1064
+ pushExportedSymbol(exported, seenExported, { name: 'default', kind: 'class' });
1065
+ }
1066
+ else if (modifierFlags.exported) {
1067
+ pushExportedSymbol(exported, seenExported, { name, kind: 'class' });
1068
+ }
1069
+ }
1070
+ else if (modifierFlags.defaultExport) {
1071
+ symbols.add('default');
1072
+ pushExportedSymbol(exported, seenExported, { name: 'default', kind: 'class' });
1073
+ }
1074
+ continue;
1075
+ }
1076
+ if (ts.isInterfaceDeclaration(statement)) {
1077
+ const name = statement.name.text;
1078
+ symbols.add(name);
1079
+ if (modifierFlags.exported) {
1080
+ pushExportedSymbol(exported, seenExported, { name: statement.name.text, kind: 'interface' });
1081
+ }
1082
+ continue;
1083
+ }
1084
+ if (ts.isTypeAliasDeclaration(statement)) {
1085
+ const name = statement.name.text;
1086
+ symbols.add(name);
1087
+ if (modifierFlags.exported) {
1088
+ pushExportedSymbol(exported, seenExported, { name: statement.name.text, kind: 'type' });
1089
+ }
1090
+ continue;
1091
+ }
1092
+ if (ts.isEnumDeclaration(statement)) {
1093
+ const name = statement.name.text;
1094
+ symbols.add(name);
1095
+ if (modifierFlags.exported) {
1096
+ pushExportedSymbol(exported, seenExported, { name: statement.name.text, kind: 'enum' });
1097
+ }
1098
+ continue;
1099
+ }
1100
+ if (ts.isVariableStatement(statement)) {
1101
+ const kind = statement.declarationList.flags & ts.NodeFlags.Const
1102
+ ? 'const'
1103
+ : statement.declarationList.flags & ts.NodeFlags.Let
1104
+ ? 'let'
1105
+ : 'var';
1106
+ for (const declaration of statement.declarationList.declarations) {
1107
+ if (ts.isIdentifier(declaration.name)) {
1108
+ const name = declaration.name.text;
1109
+ symbols.add(name);
1110
+ if (modifierFlags.exported) {
1111
+ pushExportedSymbol(exported, seenExported, { name, kind });
1112
+ }
1113
+ }
1114
+ }
1115
+ continue;
1116
+ }
1117
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
1118
+ symbols.add('default');
1119
+ pushExportedSymbol(exported, seenExported, {
1120
+ name: 'default',
1121
+ kind: inferDefaultExportKind(statement.expression, declarationKinds)
1122
+ });
1123
+ continue;
1124
+ }
1125
+ if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
1126
+ for (const element of statement.exportClause.elements) {
1127
+ pushExportedSymbol(exported, seenExported, { name: element.name.text, kind: 'named-export' });
1128
+ }
1129
+ }
1130
+ }
1131
+ const metadata = { symbols, exported };
1132
+ lastJavaScriptAstMetadata = { content, language, metadata };
1133
+ return metadata;
1134
+ }
1135
+ catch {
1136
+ lastJavaScriptAstMetadata = { content, language, metadata: null };
1137
+ return null;
1138
+ }
1139
+ }
1140
+ function collectTopLevelDeclarationKinds(sourceFile) {
1141
+ const declarationKinds = new Map();
1142
+ for (const statement of sourceFile.statements) {
1143
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
1144
+ declarationKinds.set(statement.name.text, 'function');
1145
+ continue;
1146
+ }
1147
+ if (ts.isClassDeclaration(statement) && statement.name) {
1148
+ declarationKinds.set(statement.name.text, 'class');
1149
+ continue;
1150
+ }
1151
+ if (ts.isInterfaceDeclaration(statement)) {
1152
+ declarationKinds.set(statement.name.text, 'interface');
1153
+ continue;
1154
+ }
1155
+ if (ts.isTypeAliasDeclaration(statement)) {
1156
+ declarationKinds.set(statement.name.text, 'type');
1157
+ continue;
1158
+ }
1159
+ if (ts.isEnumDeclaration(statement)) {
1160
+ declarationKinds.set(statement.name.text, 'enum');
1161
+ continue;
1162
+ }
1163
+ if (ts.isVariableStatement(statement)) {
1164
+ const kind = statement.declarationList.flags & ts.NodeFlags.Const
1165
+ ? 'const'
1166
+ : statement.declarationList.flags & ts.NodeFlags.Let
1167
+ ? 'let'
1168
+ : 'var';
1169
+ for (const declaration of statement.declarationList.declarations) {
1170
+ if (ts.isIdentifier(declaration.name)) {
1171
+ declarationKinds.set(declaration.name.text, kind);
1172
+ }
1173
+ }
1174
+ }
1175
+ }
1176
+ return declarationKinds;
1177
+ }
1178
+ function inferDefaultExportKind(expression, declarationKinds) {
1179
+ if (ts.isIdentifier(expression)) {
1180
+ return declarationKinds.get(expression.text) || 'default';
1181
+ }
1182
+ if (ts.isFunctionExpression(expression) || ts.isArrowFunction(expression)) {
1183
+ return 'function';
1184
+ }
1185
+ if (ts.isClassExpression(expression)) {
1186
+ return 'class';
1187
+ }
1188
+ return 'default';
1189
+ }
1190
+ function getModifierFlags(node) {
1191
+ if (!ts.canHaveModifiers(node)) {
1192
+ return { exported: false, defaultExport: false };
1193
+ }
1194
+ const modifiers = ts.getModifiers(node) || [];
1195
+ return {
1196
+ exported: modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword),
1197
+ defaultExport: modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)
1198
+ };
1199
+ }
1200
+ function pushExportedSymbol(exported, seen, symbol) {
1201
+ const key = `${symbol.name}\u0000${symbol.kind}`;
1202
+ if (seen.has(key)) {
1203
+ return;
1204
+ }
1205
+ seen.add(key);
1206
+ exported.push(symbol);
1207
+ }
1208
+ function parseNamedExports(specifierList) {
1209
+ return specifierList
1210
+ .split(',')
1211
+ .map((entry) => entry.trim())
1212
+ .filter(Boolean)
1213
+ .map((entry) => entry.replace(/^type\s+/, ''))
1214
+ .map((entry) => entry.split(/\s+as\s+/i).pop()?.trim() || '')
1215
+ .filter((name) => /^[A-Za-z_$][\w$]*$/.test(name));
1216
+ }
1217
+ function collectDestructuredEnvNames(content, pattern, names) {
1218
+ for (const match of content.matchAll(pattern)) {
1219
+ for (const entry of match[1].split(',')) {
1220
+ const trimmed = entry.trim();
1221
+ if (!trimmed || trimmed.startsWith('...')) {
1222
+ continue;
1223
+ }
1224
+ const withoutDefault = trimmed.split('=')[0].trim();
1225
+ const key = withoutDefault.split(':')[0].trim();
1226
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1227
+ names.add(key);
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ function inferRouteTargets(content) {
1233
+ const targets = new Map();
1234
+ const routerFactoryFramework = inferRouterFactoryFramework(content);
1235
+ const patterns = [
1236
+ { pattern: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*express\s*\(/g, framework: 'express' },
1237
+ { pattern: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*express\s*\.\s*Router\s*\(/g, framework: 'express' },
1238
+ { pattern: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:new\s+)?Router\s*\(/g, framework: routerFactoryFramework },
1239
+ { pattern: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:fastify|Fastify)\s*\(/g, framework: 'fastify' },
1240
+ { pattern: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*new\s+Hono\s*\(/g, framework: 'hono' },
1241
+ { pattern: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*new\s+Koa\s*\(/g, framework: 'koa' }
1242
+ ];
1243
+ for (const { pattern, framework } of patterns) {
1244
+ for (const match of content.matchAll(pattern)) {
1245
+ targets.set(match[1], framework);
1246
+ }
1247
+ }
1248
+ return targets;
1249
+ }
1250
+ function inferRouteFramework(target, targets) {
1251
+ if (targets.has(target)) {
1252
+ return targets.get(target);
1253
+ }
1254
+ if (/koa/i.test(target)) {
1255
+ return 'koa';
1256
+ }
1257
+ if (/fastify/i.test(target)) {
1258
+ return 'fastify';
1259
+ }
1260
+ if (/router/i.test(target)) {
1261
+ return 'router';
1262
+ }
1263
+ if (/app|server/i.test(target)) {
1264
+ return 'http-server';
1265
+ }
1266
+ return 'unknown';
1267
+ }
1268
+ function inferHandlerName(content, offset) {
1269
+ const tail = content.slice(offset, offset + 160);
1270
+ return tail.match(/^\s*,\s*([A-Za-z_$][\w$]*)\b/)?.[1] || null;
1271
+ }
1272
+ function extractNestRouteSurfaces(content, surfaces, seen) {
1273
+ const controllers = [...content.matchAll(/@Controller\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/g)];
1274
+ if (!controllers.length) {
1275
+ return;
1276
+ }
1277
+ for (let index = 0; index < controllers.length; index += 1) {
1278
+ const controller = controllers[index];
1279
+ const basePath = controller[1] || '';
1280
+ const className = controller[2];
1281
+ const segmentStart = (controller.index || 0) + controller[0].length;
1282
+ const segmentEnd = index + 1 < controllers.length ? (controllers[index + 1].index || content.length) : content.length;
1283
+ const segment = content.slice(segmentStart, segmentEnd);
1284
+ const methodPattern = new RegExp(`@(${NEST_ROUTE_DECORATORS.join('|')})\\s*\\(\\s*(?:['"\`]([^'"\`]*)['"\`])?\\s*\\)\\s*(?:public|private|protected)?\\s*(?:static\\s+)?(?:async\\s+)?([A-Za-z_$][\\w$]*)\\s*\\(`, 'g');
1285
+ for (const match of segment.matchAll(methodPattern)) {
1286
+ const routePath = combineRoutePath(basePath, match[2] || '');
1287
+ pushRouteSurface(surfaces, seen, {
1288
+ kind: 'http-route',
1289
+ framework: 'nestjs',
1290
+ target: className,
1291
+ methods: [match[1].toUpperCase()],
1292
+ path: routePath,
1293
+ handler: match[3]
1294
+ });
1295
+ }
1296
+ }
1297
+ }
1298
+ function extractTrpcRouteSurfaces(content, surfaces, seen) {
1299
+ if (!/\b(?:@trpc\/server|initTRPC|createTRPCRouter|t\s*\.\s*router)\b/.test(content)) {
1300
+ return;
1301
+ }
1302
+ const procedurePattern = /\b([A-Za-z_$][\w$]*)\s*:\s*(?:[A-Za-z_$][\w$]*Procedure|[A-Za-z_$][\w$]*\s*\.\s*procedure|procedure)\s*\.\s*(query|mutation|subscription)\s*\(/g;
1303
+ for (const match of content.matchAll(procedurePattern)) {
1304
+ pushRouteSurface(surfaces, seen, {
1305
+ kind: 'rpc-route',
1306
+ framework: 'trpc',
1307
+ target: 'router',
1308
+ methods: [match[2].toUpperCase()],
1309
+ path: `/${match[1]}`,
1310
+ handler: match[1]
1311
+ });
1312
+ }
1313
+ }
1314
+ function extractGraphqlRouteSurfaces(content, surfaces, seen) {
1315
+ if (!/\b(?:graphql|gql|apollo)\b/i.test(content)) {
1316
+ return;
1317
+ }
1318
+ for (const block of content.matchAll(/\b(Query|Mutation|Subscription)\s*:\s*\{/g)) {
1319
+ const openBraceIndex = (block.index || 0) + block[0].length - 1;
1320
+ const body = readBalancedObjectBody(content, openBraceIndex, MAX_GRAPHQL_RESOLVER_BODY_LENGTH);
1321
+ if (!body) {
1322
+ continue;
1323
+ }
1324
+ for (const field of body.matchAll(/\b([A-Za-z_$][\w$]*)\s*:/g)) {
1325
+ if (!isTopLevelObjectKey(body, field.index || 0)) {
1326
+ continue;
1327
+ }
1328
+ const valueStart = (field.index || 0) + field[0].length;
1329
+ const tail = body.slice(valueStart, valueStart + 220);
1330
+ if (!isLikelyGraphqlOperationValue(body, valueStart, tail)) {
1331
+ continue;
1332
+ }
1333
+ pushRouteSurface(surfaces, seen, {
1334
+ kind: 'graphql-operation',
1335
+ framework: 'graphql',
1336
+ target: block[1],
1337
+ methods: [block[1].toUpperCase()],
1338
+ path: '/graphql',
1339
+ handler: field[1]
1340
+ });
1341
+ }
1342
+ }
1343
+ }
1344
+ function extractOpenApiRouteSurfaces(content, surfaces, seen) {
1345
+ if (!/\b(?:openapi|swagger|registerPath)\b/i.test(content)) {
1346
+ return;
1347
+ }
1348
+ for (const match of content.matchAll(/([A-Za-z_$][\w$]*)\s*\.\s*registerPath\s*\(\s*\{/g)) {
1349
+ const openBraceIndex = (match.index || 0) + match[0].length - 1;
1350
+ const body = readBalancedObjectBody(content, openBraceIndex, MAX_OPENAPI_REGISTER_BODY_LENGTH);
1351
+ if (!body) {
1352
+ continue;
1353
+ }
1354
+ const method = body.match(/\bmethod\s*:\s*['"`]([A-Za-z]+)['"`]/)?.[1];
1355
+ const routePath = body.match(/\bpath\s*:\s*['"`]([^'"`]+)['"`]/)?.[1];
1356
+ if (!method || !routePath) {
1357
+ continue;
1358
+ }
1359
+ pushRouteSurface(surfaces, seen, {
1360
+ kind: 'openapi-operation',
1361
+ framework: 'openapi',
1362
+ target: match[1],
1363
+ methods: [method.toUpperCase()],
1364
+ path: routePath,
1365
+ handler: body.match(/\boperationId\s*:\s*['"`]([^'"`]+)['"`]/)?.[1] || null
1366
+ });
1367
+ }
1368
+ }
1369
+ /**
1370
+ * Normalize and merge controller-level and method-level route segments.
1371
+ */
1372
+ function combineRoutePath(basePath, routePath) {
1373
+ const parts = [basePath, routePath]
1374
+ .map((value) => (value || '').trim())
1375
+ .filter((value) => value.length > 0)
1376
+ .map((value) => value.replace(/^\/+|\/+$/g, ''));
1377
+ return `/${parts.join('/')}`.replace(/\/+/g, '/');
1378
+ }
1379
+ function inferRouterFactoryFramework(content) {
1380
+ return /['"](?:@koa\/router|koa-router)['"]/.test(content) ? 'koa' : 'express';
1381
+ }
1382
+ /**
1383
+ * Read the body of a `{ ... }` object literal from source text with bounded scanning.
1384
+ */
1385
+ function readBalancedObjectBody(content, openBraceIndex, maxBodyLength) {
1386
+ if (content[openBraceIndex] !== '{') {
1387
+ return null;
1388
+ }
1389
+ let depth = 0;
1390
+ let inSingleQuote = false;
1391
+ let inDoubleQuote = false;
1392
+ let inTemplate = false;
1393
+ let inLineComment = false;
1394
+ let inBlockComment = false;
1395
+ let escaped = false;
1396
+ const scanLimit = Math.min(content.length, openBraceIndex + maxBodyLength + 500);
1397
+ for (let index = openBraceIndex; index < scanLimit; index += 1) {
1398
+ const token = content[index];
1399
+ if (inLineComment) {
1400
+ if (token === '\n') {
1401
+ inLineComment = false;
1402
+ }
1403
+ continue;
1404
+ }
1405
+ if (inBlockComment) {
1406
+ if (token === '*' && content[index + 1] === '/') {
1407
+ inBlockComment = false;
1408
+ index += 1;
1409
+ }
1410
+ continue;
1411
+ }
1412
+ if (inSingleQuote) {
1413
+ if (!escaped && token === '\'') {
1414
+ inSingleQuote = false;
1415
+ }
1416
+ escaped = !escaped && token === '\\';
1417
+ continue;
1418
+ }
1419
+ if (inDoubleQuote) {
1420
+ if (!escaped && token === '"') {
1421
+ inDoubleQuote = false;
1422
+ }
1423
+ escaped = !escaped && token === '\\';
1424
+ continue;
1425
+ }
1426
+ if (inTemplate) {
1427
+ if (!escaped && token === '`') {
1428
+ inTemplate = false;
1429
+ }
1430
+ escaped = !escaped && token === '\\';
1431
+ continue;
1432
+ }
1433
+ escaped = false;
1434
+ if (token === '/' && content[index + 1] === '/') {
1435
+ inLineComment = true;
1436
+ index += 1;
1437
+ continue;
1438
+ }
1439
+ if (token === '/' && content[index + 1] === '*') {
1440
+ inBlockComment = true;
1441
+ index += 1;
1442
+ continue;
1443
+ }
1444
+ if (token === '\'') {
1445
+ inSingleQuote = true;
1446
+ continue;
1447
+ }
1448
+ if (token === '"') {
1449
+ inDoubleQuote = true;
1450
+ continue;
1451
+ }
1452
+ if (token === '`') {
1453
+ inTemplate = true;
1454
+ continue;
1455
+ }
1456
+ if (token === '{') {
1457
+ depth += 1;
1458
+ continue;
1459
+ }
1460
+ if (token !== '}') {
1461
+ continue;
1462
+ }
1463
+ depth -= 1;
1464
+ if (depth !== 0) {
1465
+ continue;
1466
+ }
1467
+ const body = content.slice(openBraceIndex + 1, index);
1468
+ if (body.length > maxBodyLength) {
1469
+ return null;
1470
+ }
1471
+ return body;
1472
+ }
1473
+ return null;
1474
+ }
1475
+ /**
1476
+ * Heuristic: GraphQL resolver map entries should map field names to callable values.
1477
+ */
1478
+ function isLikelyGraphqlResolverValue(value) {
1479
+ // Only treat resolver entries as API surfaces when the mapped value looks callable.
1480
+ return /^\s*(?:async\s*)?(?:function\b|\(|[A-Za-z_$][\w$]*\s*\()/m.test(value);
1481
+ }
1482
+ function isLikelyGraphqlOperationValue(body, valueStart, valueTail) {
1483
+ if (isLikelyGraphqlResolverValue(valueTail)) {
1484
+ return true;
1485
+ }
1486
+ const leadingOffset = valueTail.match(/^\s*/)?.[0]?.length || 0;
1487
+ const objectStart = valueStart + leadingOffset;
1488
+ if (body[objectStart] !== '{') {
1489
+ return false;
1490
+ }
1491
+ const configBody = readBalancedObjectBody(body, objectStart, 500);
1492
+ if (!configBody) {
1493
+ return false;
1494
+ }
1495
+ for (const field of configBody.matchAll(/\bresolve\s*:/g)) {
1496
+ if (!isTopLevelObjectKey(configBody, field.index || 0)) {
1497
+ continue;
1498
+ }
1499
+ const resolveTail = configBody.slice((field.index || 0) + field[0].length, (field.index || 0) + field[0].length + 220);
1500
+ if (isLikelyGraphqlResolverValue(resolveTail)) {
1501
+ return true;
1502
+ }
1503
+ }
1504
+ return false;
1505
+ }
1506
+ function isTopLevelObjectKey(content, index) {
1507
+ let braceDepth = 0;
1508
+ let bracketDepth = 0;
1509
+ let parenDepth = 0;
1510
+ let inSingleQuote = false;
1511
+ let inDoubleQuote = false;
1512
+ let inTemplate = false;
1513
+ let inLineComment = false;
1514
+ let inBlockComment = false;
1515
+ let escaped = false;
1516
+ for (let cursor = 0; cursor < index; cursor += 1) {
1517
+ const token = content[cursor];
1518
+ if (inLineComment) {
1519
+ if (token === '\n') {
1520
+ inLineComment = false;
1521
+ }
1522
+ continue;
1523
+ }
1524
+ if (inBlockComment) {
1525
+ if (token === '*' && content[cursor + 1] === '/') {
1526
+ inBlockComment = false;
1527
+ cursor += 1;
1528
+ }
1529
+ continue;
1530
+ }
1531
+ if (inSingleQuote) {
1532
+ if (!escaped && token === '\'') {
1533
+ inSingleQuote = false;
1534
+ }
1535
+ escaped = !escaped && token === '\\';
1536
+ continue;
1537
+ }
1538
+ if (inDoubleQuote) {
1539
+ if (!escaped && token === '"') {
1540
+ inDoubleQuote = false;
1541
+ }
1542
+ escaped = !escaped && token === '\\';
1543
+ continue;
1544
+ }
1545
+ if (inTemplate) {
1546
+ if (!escaped && token === '`') {
1547
+ inTemplate = false;
1548
+ }
1549
+ escaped = !escaped && token === '\\';
1550
+ continue;
1551
+ }
1552
+ escaped = false;
1553
+ if (token === '/' && content[cursor + 1] === '/') {
1554
+ inLineComment = true;
1555
+ cursor += 1;
1556
+ continue;
1557
+ }
1558
+ if (token === '/' && content[cursor + 1] === '*') {
1559
+ inBlockComment = true;
1560
+ cursor += 1;
1561
+ continue;
1562
+ }
1563
+ if (token === '\'') {
1564
+ inSingleQuote = true;
1565
+ continue;
1566
+ }
1567
+ if (token === '"') {
1568
+ inDoubleQuote = true;
1569
+ continue;
1570
+ }
1571
+ if (token === '`') {
1572
+ inTemplate = true;
1573
+ continue;
1574
+ }
1575
+ if (token === '{') {
1576
+ braceDepth += 1;
1577
+ continue;
1578
+ }
1579
+ if (token === '}') {
1580
+ braceDepth = Math.max(0, braceDepth - 1);
1581
+ continue;
1582
+ }
1583
+ if (token === '[') {
1584
+ bracketDepth += 1;
1585
+ continue;
1586
+ }
1587
+ if (token === ']') {
1588
+ bracketDepth = Math.max(0, bracketDepth - 1);
1589
+ continue;
1590
+ }
1591
+ if (token === '(') {
1592
+ parenDepth += 1;
1593
+ continue;
1594
+ }
1595
+ if (token === ')') {
1596
+ parenDepth = Math.max(0, parenDepth - 1);
1597
+ }
1598
+ }
1599
+ return braceDepth === 0 && bracketDepth === 0 && parenDepth === 0;
1600
+ }
1601
+ function parseRouteMethods(body) {
1602
+ const value = body.match(/\bmethod\s*:\s*(\[[^\]]+\]|['"`][^'"`]+['"`])/);
1603
+ if (!value) {
1604
+ return [];
1605
+ }
1606
+ if (value[1].startsWith('[')) {
1607
+ return [...value[1].matchAll(/['"`]([A-Za-z]+)['"`]/g)].map((match) => match[1].toUpperCase()).sort();
1608
+ }
1609
+ const method = value[1].match(/['"`]([A-Za-z]+)['"`]/)?.[1];
1610
+ return method ? [method.toUpperCase()] : [];
1611
+ }
1612
+ function inferFileRoutePath(filePath) {
1613
+ const normalized = filePath.replace(/\\/g, '/');
1614
+ const extensionless = normalized.replace(/\.[^.]+$/, '');
1615
+ if (/(^|\/)pages\/api\//.test(extensionless)) {
1616
+ return `/${extensionless.replace(/^.*?pages\/api\//, 'api/')}`.replace(/\/index$/, '');
1617
+ }
1618
+ if (/(^|\/)app\/api\//.test(extensionless) && /\/route$/i.test(extensionless)) {
1619
+ return `/${extensionless.replace(/^.*?app\//, '').replace(/\/route$/i, '')}`;
1620
+ }
1621
+ if (/\/(api|routes?)\//.test(extensionless) && /\/(index|route)$/i.test(extensionless)) {
1622
+ return `/${extensionless.replace(/^.*?\/(api|routes?)\//, '$1/').replace(/\/(index|route)$/i, '')}`;
1623
+ }
1624
+ return null;
1625
+ }
1626
+ function normalizePath(filePath) {
1627
+ return filePath.replace(/\\/g, '/');
1628
+ }
1629
+ function extractMigrationId(value) {
1630
+ const match = value.match(/^(\d{3,})/);
1631
+ return match ? match[1] : null;
1632
+ }
1633
+ function cleanMigrationName(value) {
1634
+ const readable = value
1635
+ .replace(/^v\d+(?:[._]\d+)*__/i, '')
1636
+ .replace(/^(\d{3,})(?:[._-]+)?/i, '')
1637
+ .replace(/\.(?:up|down)$/i, '')
1638
+ .replace(/^migration[._-]?/i, '')
1639
+ .replace(/[_-]+/g, ' ')
1640
+ .trim();
1641
+ return readable || null;
1642
+ }
1643
+ function pushModelSurface(surfaces, seen, surface) {
1644
+ const key = `${surface.framework}\u0000${surface.kind}\u0000${surface.name}`;
1645
+ if (seen.has(key)) {
1646
+ return;
1647
+ }
1648
+ seen.add(key);
1649
+ surfaces.push(surface);
1650
+ }
1651
+ function pushRouteSurface(surfaces, seen, surface) {
1652
+ const normalizedSurface = {
1653
+ ...surface,
1654
+ methods: [...new Set(surface.methods)].sort(),
1655
+ handler: surface.handler || null
1656
+ };
1657
+ const key = [
1658
+ normalizedSurface.framework,
1659
+ normalizedSurface.target,
1660
+ normalizedSurface.path,
1661
+ normalizedSurface.methods.join(','),
1662
+ normalizedSurface.handler || ''
1663
+ ].join('\u0000');
1664
+ if (seen.has(key)) {
1665
+ return;
1666
+ }
1667
+ seen.add(key);
1668
+ surfaces.push(normalizedSurface);
1669
+ }
1670
+ function compareRouteSurfaces(left, right) {
1671
+ if (left.path !== right.path) {
1672
+ return left.path.localeCompare(right.path);
1673
+ }
1674
+ const leftMethods = left.methods.join(',');
1675
+ const rightMethods = right.methods.join(',');
1676
+ if (leftMethods !== rightMethods) {
1677
+ return leftMethods.localeCompare(rightMethods);
1678
+ }
1679
+ if (left.framework !== right.framework) {
1680
+ return left.framework.localeCompare(right.framework);
1681
+ }
1682
+ if (left.target !== right.target) {
1683
+ return left.target.localeCompare(right.target);
1684
+ }
1685
+ return (left.handler || '').localeCompare(right.handler || '');
1686
+ }
1687
+ function stripGoComments(content) {
1688
+ let output = '';
1689
+ let index = 0;
1690
+ while (index < content.length) {
1691
+ const char = content[index];
1692
+ const next = content[index + 1];
1693
+ if (char === '/' && next === '/') {
1694
+ output += ' ';
1695
+ index += 2;
1696
+ while (index < content.length && content[index] !== '\n') {
1697
+ output += ' ';
1698
+ index += 1;
1699
+ }
1700
+ continue;
1701
+ }
1702
+ if (char === '/' && next === '*') {
1703
+ output += ' ';
1704
+ index += 2;
1705
+ while (index < content.length && !(content[index] === '*' && content[index + 1] === '/')) {
1706
+ output += content[index] === '\n' ? '\n' : ' ';
1707
+ index += 1;
1708
+ }
1709
+ if (index < content.length) {
1710
+ output += ' ';
1711
+ index += 2;
1712
+ }
1713
+ continue;
1714
+ }
1715
+ output += char;
1716
+ index += 1;
1717
+ }
1718
+ return output;
1719
+ }
1720
+ function stripGoCommentsAndLiterals(content) {
1721
+ const withoutComments = stripGoComments(content);
1722
+ let output = '';
1723
+ let index = 0;
1724
+ while (index < withoutComments.length) {
1725
+ const char = withoutComments[index];
1726
+ if (char === '`') {
1727
+ output += ' ';
1728
+ index += 1;
1729
+ while (index < withoutComments.length && withoutComments[index] !== '`') {
1730
+ output += withoutComments[index] === '\n' ? '\n' : ' ';
1731
+ index += 1;
1732
+ }
1733
+ if (index < withoutComments.length) {
1734
+ output += ' ';
1735
+ index += 1;
1736
+ }
1737
+ continue;
1738
+ }
1739
+ if (char === '"' || char === "'") {
1740
+ const quote = char;
1741
+ output += ' ';
1742
+ index += 1;
1743
+ while (index < withoutComments.length) {
1744
+ const current = withoutComments[index];
1745
+ if (current === '\\') {
1746
+ output += ' ';
1747
+ index += 1;
1748
+ if (index < withoutComments.length) {
1749
+ output += withoutComments[index] === '\n' ? '\n' : ' ';
1750
+ index += 1;
1751
+ }
1752
+ continue;
1753
+ }
1754
+ output += current === '\n' ? '\n' : ' ';
1755
+ index += 1;
1756
+ if (current === quote) {
1757
+ break;
1758
+ }
1759
+ }
1760
+ continue;
1761
+ }
1762
+ output += char;
1763
+ index += 1;
1764
+ }
1765
+ return output;
1766
+ }
1767
+ function extractGoImports(content) {
1768
+ const imports = new Set();
1769
+ const code = stripGoComments(content);
1770
+ // Single import: import "path"
1771
+ for (const match of code.matchAll(/^\s*import\s+"([^"]+)"/mg)) {
1772
+ imports.add(match[1]);
1773
+ }
1774
+ // Block import: import (\n "path1"\n alias "path2"\n)
1775
+ for (const match of code.matchAll(/^\s*import\s*\(([\s\S]*?)^\s*\)/mg)) {
1776
+ for (const pathMatch of match[1].matchAll(/"([^"]+)"/g)) {
1777
+ imports.add(pathMatch[1]);
1778
+ }
1779
+ }
1780
+ return [...imports].sort();
1781
+ }
1782
+ function getGoDeclarations(content) {
1783
+ if (lastGoDeclarations?.content === content) {
1784
+ return lastGoDeclarations.declarations;
1785
+ }
1786
+ const declarations = computeGoDeclarations(content);
1787
+ lastGoDeclarations = { content, declarations };
1788
+ return declarations;
1789
+ }
1790
+ function computeGoDeclarations(content) {
1791
+ const code = stripGoCommentsAndLiterals(content);
1792
+ const symbols = new Set();
1793
+ const exported = [];
1794
+ const seenExported = new Set();
1795
+ const addSymbol = (name, kind) => {
1796
+ symbols.add(name);
1797
+ if (/^[A-Z]/.test(name)) {
1798
+ pushExportedSymbol(exported, seenExported, { name, kind });
1799
+ }
1800
+ };
1801
+ // Functions and methods: func [(receiver)] Name( or Name[ or Name{
1802
+ for (const match of code.matchAll(/^func\s+(?:\([^)]*\)\s+)?([A-Za-z_]\w*)\s*[([{]/mg)) {
1803
+ addSymbol(match[1], 'func');
1804
+ }
1805
+ // Type declarations: type Name [TypeParams] struct / interface / other
1806
+ for (const match of code.matchAll(/^type\s+([A-Za-z_]\w*)(?:\[[^\]]*\])?\s+(struct|interface|[^\s{])/mg)) {
1807
+ const name = match[1];
1808
+ const typeWord = match[2];
1809
+ const kind = typeWord === 'struct' ? 'struct'
1810
+ : typeWord === 'interface' ? 'interface'
1811
+ : 'type';
1812
+ addSymbol(name, kind);
1813
+ }
1814
+ // Single const declaration: const Name1, Name2, ... [type] [= ...]
1815
+ // Skip block form (starts with `(`). Capture the full identifier list and parse it.
1816
+ for (const match of code.matchAll(/^const\s+(?!\()(.+)/mg)) {
1817
+ for (const name of parseGoNameList(match[1])) {
1818
+ addSymbol(name, 'const');
1819
+ }
1820
+ }
1821
+ // Single var declaration: var Name1, Name2, ... [type] [= ...]
1822
+ // Skip block form (starts with `(`). Capture the full identifier list and parse it.
1823
+ for (const match of code.matchAll(/^var\s+(?!\()(.+)/mg)) {
1824
+ for (const name of parseGoNameList(match[1])) {
1825
+ addSymbol(name, 'var');
1826
+ }
1827
+ }
1828
+ // Const block: const (\n Name1, Name2, ...\n)
1829
+ // The lazy [\s\S]*? stops at the first ) that is at the start of a line (^, m flag),
1830
+ // which is gofmt's convention for closing parentheses.
1831
+ for (const match of code.matchAll(/^const\s*\(([\s\S]*?)^\s*\)/mg)) {
1832
+ for (const lineMatch of match[1].matchAll(/^[ \t]+(.+)/mg)) {
1833
+ for (const name of parseGoNameList(lineMatch[1])) {
1834
+ addSymbol(name, 'const');
1835
+ }
1836
+ }
1837
+ }
1838
+ // Var block: var (\n Name1, Name2, ...\n)
1839
+ // Same lazy-stop-at-line-start-) strategy as the const block above.
1840
+ for (const match of code.matchAll(/^var\s*\(([\s\S]*?)^\s*\)/mg)) {
1841
+ for (const lineMatch of match[1].matchAll(/^[ \t]+(.+)/mg)) {
1842
+ for (const name of parseGoNameList(lineMatch[1])) {
1843
+ addSymbol(name, 'var');
1844
+ }
1845
+ }
1846
+ }
1847
+ return {
1848
+ allSymbols: [...symbols].sort().slice(0, 50),
1849
+ exported: exported
1850
+ .sort((a, b) => a.name.localeCompare(b.name) || a.kind.localeCompare(b.kind))
1851
+ .slice(0, 50)
1852
+ };
1853
+ }
1854
+ /**
1855
+ * Parse a comma-separated identifier list from the head of a Go const/var declaration.
1856
+ *
1857
+ * Examples:
1858
+ * "Alpha, Beta int" → ["Alpha", "Beta"]
1859
+ * "Gamma, Delta = 1, 2" → ["Gamma", "Delta"]
1860
+ * "statusInternal = 500" → ["statusInternal"]
1861
+ *
1862
+ * Only the declaration head before `=` is inspected, so commas in right-hand
1863
+ * side expressions such as `var A = foo(bar, baz)` are not interpreted as
1864
+ * additional declaration names.
1865
+ */
1866
+ function parseGoNameList(segment) {
1867
+ const names = [];
1868
+ const declarationHead = segment.split('=')[0];
1869
+ for (const part of declarationHead.split(',')) {
1870
+ const m = part.trimStart().match(/^([A-Za-z_]\w*)/);
1871
+ if (!m) {
1872
+ break;
1873
+ }
1874
+ names.push(m[1]);
1875
+ }
1876
+ return names;
1877
+ }
1878
+ function getRustDeclarations(content) {
1879
+ if (lastRustDeclarations?.content === content) {
1880
+ return lastRustDeclarations.declarations;
1881
+ }
1882
+ const declarations = computeRustDeclarations(content);
1883
+ lastRustDeclarations = { content, declarations };
1884
+ return declarations;
1885
+ }
1886
+ function computeRustDeclarations(content) {
1887
+ const code = stripRustCommentsAndLiterals(content);
1888
+ const imports = new Set();
1889
+ const symbols = new Set();
1890
+ const exported = [];
1891
+ const seenExported = new Set();
1892
+ const addSymbol = (name, kind, isExported = false) => {
1893
+ symbols.add(name);
1894
+ if (isExported) {
1895
+ pushExportedSymbol(exported, seenExported, { name, kind });
1896
+ }
1897
+ };
1898
+ for (const match of topLevelRustMatches(code, /^\s*(?:pub(?:\([^)]*\))?\s+)?use\s+([\s\S]*?);/mg)) {
1899
+ for (const specifier of expandRustUseSpec(match[1])) {
1900
+ imports.add(specifier);
1901
+ }
1902
+ }
1903
+ for (const match of topLevelRustMatches(code, /^\s*pub(?:\([^)]*\))?\s+use\s+([\s\S]*?);/mg)) {
1904
+ for (const name of extractRustPublicUseNames(match[1])) {
1905
+ addSymbol(name, 're-export', true);
1906
+ }
1907
+ }
1908
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(?:const\s+)?fn\s+([A-Za-z_]\w*)\b/mg)) {
1909
+ addSymbol(match[2], 'fn', Boolean(match[1]));
1910
+ }
1911
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)\b/mg)) {
1912
+ addSymbol(match[2], 'struct', Boolean(match[1]));
1913
+ }
1914
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)\b/mg)) {
1915
+ addSymbol(match[2], 'enum', Boolean(match[1]));
1916
+ }
1917
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)\b/mg)) {
1918
+ addSymbol(match[2], 'trait', Boolean(match[1]));
1919
+ }
1920
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)\b/mg)) {
1921
+ addSymbol(match[2], 'mod', Boolean(match[1]));
1922
+ }
1923
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?const\s+([A-Za-z_]\w*)\b/mg)) {
1924
+ addSymbol(match[2], 'const', Boolean(match[1]));
1925
+ }
1926
+ for (const match of topLevelRustMatches(code, /^\s*(pub(?:\([^)]*\))?\s+)?static(?:\s+mut)?\s+([A-Za-z_]\w*)\b/mg)) {
1927
+ addSymbol(match[2], 'static', Boolean(match[1]));
1928
+ }
1929
+ for (const match of topLevelRustMatches(code, /^\s*impl(?:\s*<[^>{;]*>)?\s+([\s\S]*?)\{/mg)) {
1930
+ const implName = normalizeRustImplName(match[1]);
1931
+ if (implName) {
1932
+ addSymbol(implName, 'impl');
1933
+ }
1934
+ }
1935
+ return {
1936
+ imports: [...imports].sort(),
1937
+ allSymbols: [...symbols].sort().slice(0, SYMBOL_LIMIT),
1938
+ exported: exported
1939
+ .sort((left, right) => compareStrings(left.name, right.name) || compareStrings(left.kind, right.kind))
1940
+ .slice(0, SYMBOL_LIMIT)
1941
+ };
1942
+ }
1943
+ function* topLevelRustMatches(code, pattern) {
1944
+ for (const match of code.matchAll(pattern)) {
1945
+ if (rustBraceDepthAt(code, match.index) === 0 || rustMatchHasNoLeadingWhitespace(code, match.index)) {
1946
+ yield match;
1947
+ }
1948
+ }
1949
+ }
1950
+ function rustMatchHasNoLeadingWhitespace(code, index) {
1951
+ return code[index] !== ' ' && code[index] !== '\t';
1952
+ }
1953
+ function rustBraceDepthAt(code, index) {
1954
+ let depth = 0;
1955
+ for (let cursor = 0; cursor < index; cursor += 1) {
1956
+ if (code[cursor] === '{') {
1957
+ depth += 1;
1958
+ }
1959
+ else if (code[cursor] === '}') {
1960
+ depth = Math.max(0, depth - 1);
1961
+ }
1962
+ }
1963
+ return depth;
1964
+ }
1965
+ function compareStrings(left, right) {
1966
+ return left.localeCompare(right, 'en-US');
1967
+ }
1968
+ function stripRustCommentsAndLiterals(content) {
1969
+ let output = '';
1970
+ let index = 0;
1971
+ let blockCommentDepth = 0;
1972
+ while (index < content.length) {
1973
+ const char = content[index];
1974
+ const next = content[index + 1];
1975
+ if (blockCommentDepth > 0) {
1976
+ if (char === '/' && next === '*') {
1977
+ blockCommentDepth += 1;
1978
+ output += ' ';
1979
+ index += 2;
1980
+ continue;
1981
+ }
1982
+ if (char === '*' && next === '/') {
1983
+ blockCommentDepth -= 1;
1984
+ output += ' ';
1985
+ index += 2;
1986
+ continue;
1987
+ }
1988
+ output += char === '\n' ? '\n' : ' ';
1989
+ index += 1;
1990
+ continue;
1991
+ }
1992
+ if (char === '/' && next === '/') {
1993
+ output += ' ';
1994
+ index += 2;
1995
+ while (index < content.length && content[index] !== '\n') {
1996
+ output += ' ';
1997
+ index += 1;
1998
+ }
1999
+ continue;
2000
+ }
2001
+ if (char === '/' && next === '*') {
2002
+ blockCommentDepth = 1;
2003
+ output += ' ';
2004
+ index += 2;
2005
+ continue;
2006
+ }
2007
+ if (char === '"' || (char === 'r' && isRustRawStringStart(content, index))) {
2008
+ const end = findRustStringEnd(content, index);
2009
+ output += ' ';
2010
+ for (let cursor = index + 1; cursor < end; cursor += 1) {
2011
+ output += content[cursor] === '\n' ? '\n' : ' ';
2012
+ }
2013
+ if (end < content.length) {
2014
+ output += ' ';
2015
+ }
2016
+ index = Math.min(end + 1, content.length);
2017
+ continue;
2018
+ }
2019
+ output += char;
2020
+ index += 1;
2021
+ }
2022
+ return output;
2023
+ }
2024
+ function isRustRawStringStart(content, index) {
2025
+ if (content[index] !== 'r') {
2026
+ return false;
2027
+ }
2028
+ let cursor = index + 1;
2029
+ while (cursor < content.length && content[cursor] === '#') {
2030
+ cursor += 1;
2031
+ }
2032
+ return content[cursor] === '"';
2033
+ }
2034
+ function findRustStringEnd(content, start) {
2035
+ if (content[start] === '"') {
2036
+ let cursor = start + 1;
2037
+ while (cursor < content.length) {
2038
+ if (content[cursor] === '\\') {
2039
+ cursor += 2;
2040
+ continue;
2041
+ }
2042
+ if (content[cursor] === '"') {
2043
+ return cursor;
2044
+ }
2045
+ cursor += 1;
2046
+ }
2047
+ return content.length;
2048
+ }
2049
+ if (!isRustRawStringStart(content, start)) {
2050
+ return start;
2051
+ }
2052
+ let cursor = start + 1;
2053
+ while (content[cursor] === '#') {
2054
+ cursor += 1;
2055
+ }
2056
+ const hashes = content.slice(start + 1, cursor);
2057
+ cursor += 1; // opening quote
2058
+ const endDelimiter = `"${hashes}`;
2059
+ const end = content.indexOf(endDelimiter, cursor);
2060
+ return end >= 0 ? end + endDelimiter.length - 1 : content.length;
2061
+ }
2062
+ function expandRustUseSpec(spec) {
2063
+ const imports = new Set();
2064
+ const expand = (segment, prefix = '') => {
2065
+ const trimmed = segment.trim();
2066
+ if (!trimmed) {
2067
+ return;
2068
+ }
2069
+ const groups = splitTopLevel(trimmed, ',');
2070
+ if (groups.length > 1) {
2071
+ for (const group of groups) {
2072
+ expand(group, prefix);
2073
+ }
2074
+ return;
2075
+ }
2076
+ const braceIndex = findTopLevelBrace(trimmed);
2077
+ if (braceIndex >= 0) {
2078
+ const beforeBrace = trimmed.slice(0, braceIndex).replace(/::\s*$/, '').trim();
2079
+ const inner = trimmed.slice(braceIndex + 1, findMatchingBrace(trimmed, braceIndex));
2080
+ const nextPrefix = beforeBrace ? joinRustPath(prefix, beforeBrace) : prefix;
2081
+ for (const child of splitTopLevel(inner, ',')) {
2082
+ expand(child, nextPrefix);
2083
+ }
2084
+ return;
2085
+ }
2086
+ const withoutAlias = trimmed.replace(/\s+as\s+(?:[A-Za-z_]\w*|_)\s*$/, '').trim();
2087
+ const value = withoutAlias === 'self'
2088
+ ? prefix
2089
+ : joinRustPath(prefix, withoutAlias);
2090
+ if (value) {
2091
+ imports.add(normalizeRustPath(value));
2092
+ }
2093
+ };
2094
+ expand(spec);
2095
+ return [...imports].sort();
2096
+ }
2097
+ function splitTopLevel(value, delimiter) {
2098
+ const parts = [];
2099
+ let current = '';
2100
+ let depthParen = 0;
2101
+ let depthBracket = 0;
2102
+ let depthBrace = 0;
2103
+ let depthAngle = 0;
2104
+ for (const char of value) {
2105
+ if (char === '(')
2106
+ depthParen += 1;
2107
+ else if (char === ')' && depthParen > 0)
2108
+ depthParen -= 1;
2109
+ else if (char === '[')
2110
+ depthBracket += 1;
2111
+ else if (char === ']' && depthBracket > 0)
2112
+ depthBracket -= 1;
2113
+ else if (char === '{')
2114
+ depthBrace += 1;
2115
+ else if (char === '}' && depthBrace > 0)
2116
+ depthBrace -= 1;
2117
+ else if (char === '<')
2118
+ depthAngle += 1;
2119
+ else if (char === '>' && depthAngle > 0)
2120
+ depthAngle -= 1;
2121
+ if (char === delimiter
2122
+ && depthParen === 0
2123
+ && depthBracket === 0
2124
+ && depthBrace === 0
2125
+ && depthAngle === 0) {
2126
+ parts.push(current);
2127
+ current = '';
2128
+ continue;
2129
+ }
2130
+ current += char;
2131
+ }
2132
+ if (current) {
2133
+ parts.push(current);
2134
+ }
2135
+ return parts;
2136
+ }
2137
+ function findTopLevelBrace(value) {
2138
+ let depthParen = 0;
2139
+ let depthBracket = 0;
2140
+ let depthAngle = 0;
2141
+ for (let index = 0; index < value.length; index += 1) {
2142
+ const char = value[index];
2143
+ if (char === '(')
2144
+ depthParen += 1;
2145
+ else if (char === ')' && depthParen > 0)
2146
+ depthParen -= 1;
2147
+ else if (char === '[')
2148
+ depthBracket += 1;
2149
+ else if (char === ']' && depthBracket > 0)
2150
+ depthBracket -= 1;
2151
+ else if (char === '<')
2152
+ depthAngle += 1;
2153
+ else if (char === '>' && depthAngle > 0)
2154
+ depthAngle -= 1;
2155
+ else if (char === '{' && depthParen === 0 && depthBracket === 0 && depthAngle === 0) {
2156
+ return index;
2157
+ }
2158
+ }
2159
+ return -1;
2160
+ }
2161
+ function findMatchingBrace(value, openIndex) {
2162
+ let depth = 0;
2163
+ for (let index = openIndex; index < value.length; index += 1) {
2164
+ const char = value[index];
2165
+ if (char === '{') {
2166
+ depth += 1;
2167
+ }
2168
+ else if (char === '}') {
2169
+ depth -= 1;
2170
+ if (depth === 0) {
2171
+ return index;
2172
+ }
2173
+ }
2174
+ }
2175
+ return value.length - 1;
2176
+ }
2177
+ function joinRustPath(prefix, suffix) {
2178
+ const normalizedSuffix = normalizeRustPath(suffix);
2179
+ if (!normalizedSuffix) {
2180
+ return normalizeRustPath(prefix);
2181
+ }
2182
+ if (!prefix) {
2183
+ return normalizedSuffix;
2184
+ }
2185
+ if (normalizedSuffix.startsWith('::')) {
2186
+ return normalizedSuffix;
2187
+ }
2188
+ return normalizeRustPath(`${prefix}::${normalizedSuffix}`);
2189
+ }
2190
+ function normalizeRustPath(pathValue) {
2191
+ return pathValue
2192
+ .replace(/\s+/g, '')
2193
+ .replace(/:+/g, '::')
2194
+ .replace(/;+$/, '')
2195
+ .trim();
2196
+ }
2197
+ function normalizeRustImplName(raw) {
2198
+ const compact = raw.replace(/\s+/g, ' ').trim();
2199
+ if (!compact) {
2200
+ return null;
2201
+ }
2202
+ const withoutWhere = compact.replace(/\s+where\s+[\s\S]*$/, '').trim();
2203
+ const forIndex = withoutWhere.indexOf(' for ');
2204
+ if (forIndex >= 0) {
2205
+ const traitName = withoutWhere.slice(0, forIndex).trim();
2206
+ const typeName = withoutWhere.slice(forIndex + 5).trim();
2207
+ if (!traitName || !typeName) {
2208
+ return null;
2209
+ }
2210
+ return `impl ${traitName} for ${typeName}`;
2211
+ }
2212
+ return `impl ${withoutWhere}`;
2213
+ }
2214
+ function extractRustPublicUseNames(spec) {
2215
+ const names = new Set();
2216
+ const collect = (segment, prefix = '') => {
2217
+ const trimmed = segment.trim();
2218
+ if (!trimmed) {
2219
+ return;
2220
+ }
2221
+ const groups = splitTopLevel(trimmed, ',');
2222
+ if (groups.length > 1) {
2223
+ for (const group of groups) {
2224
+ collect(group, prefix);
2225
+ }
2226
+ return;
2227
+ }
2228
+ const braceIndex = findTopLevelBrace(trimmed);
2229
+ if (braceIndex >= 0) {
2230
+ const beforeBrace = trimmed.slice(0, braceIndex).replace(/::\s*$/, '').trim();
2231
+ const inner = trimmed.slice(braceIndex + 1, findMatchingBrace(trimmed, braceIndex));
2232
+ const nextPrefix = beforeBrace ? joinRustPath(prefix, beforeBrace) : prefix;
2233
+ for (const child of splitTopLevel(inner, ',')) {
2234
+ collect(child, nextPrefix);
2235
+ }
2236
+ return;
2237
+ }
2238
+ const aliasMatch = trimmed.match(/\s+as\s+([A-Za-z_]\w*|_)\s*$/);
2239
+ if (aliasMatch) {
2240
+ if (aliasMatch[1] !== '_') {
2241
+ names.add(aliasMatch[1]);
2242
+ }
2243
+ return;
2244
+ }
2245
+ if (trimmed === '*') {
2246
+ return;
2247
+ }
2248
+ if (trimmed === 'self') {
2249
+ const selfName = rustPathTerminal(prefix);
2250
+ if (selfName) {
2251
+ names.add(selfName);
2252
+ }
2253
+ return;
2254
+ }
2255
+ const full = joinRustPath(prefix, trimmed);
2256
+ const terminal = rustPathTerminal(full);
2257
+ if (terminal) {
2258
+ names.add(terminal);
2259
+ }
2260
+ };
2261
+ collect(spec);
2262
+ return [...names].sort();
2263
+ }
2264
+ function rustPathTerminal(pathValue) {
2265
+ const normalized = normalizeRustPath(pathValue).replace(/^::/, '');
2266
+ if (!normalized || normalized.endsWith('::')) {
2267
+ return null;
2268
+ }
2269
+ const terminal = normalized.split('::').pop() || '';
2270
+ if (!terminal || terminal === '*' || terminal === 'self') {
2271
+ return null;
2272
+ }
2273
+ return terminal;
2274
+ }
2275
+ //# sourceMappingURL=extractors.js.map