code-auditor-mcp 3.9.11 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +200 -0
  3. package/dist/analyzers/analyzerUtils.js +2 -2
  4. package/dist/analyzers/analyzerUtils.js.map +1 -1
  5. package/dist/analyzers/applicability.d.ts.map +1 -1
  6. package/dist/analyzers/applicability.js +1 -0
  7. package/dist/analyzers/applicability.js.map +1 -1
  8. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +1 -1
  9. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  10. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +3 -3
  11. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  12. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
  13. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  14. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +39 -15
  15. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  16. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  17. package/dist/analyzers/documentationAnalyzer.js +14 -16
  18. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  19. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
  20. package/dist/analyzers/invariantsAnalyzer.js +30 -25
  21. package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
  22. package/dist/analyzers/reactAnalyzer.d.ts +1 -1
  23. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/reactAnalyzer.js +34 -116
  25. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  26. package/dist/analyzers/ruleRegistry.d.ts +5 -3
  27. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  28. package/dist/analyzers/ruleRegistry.js +198 -54
  29. package/dist/analyzers/ruleRegistry.js.map +1 -1
  30. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +2 -2
  31. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  32. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +10 -17
  33. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  34. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +6 -6
  35. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +13 -13
  36. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  37. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  38. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +22 -64
  39. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  40. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -2
  41. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  42. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +14 -13
  43. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  44. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +11 -11
  45. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  46. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +1 -1
  47. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  48. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +18 -7
  49. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  50. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -2
  51. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  52. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +38 -26
  53. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  54. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +37 -13
  55. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  56. package/dist/analyzers/universal/schema/codeAnalysis.js +42 -54
  57. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  58. package/dist/analyzers/universal/schema/jsonSchema.js +1 -1
  59. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
  60. package/dist/analyzers/universal/schema/violations.d.ts +1 -1
  61. package/dist/analyzers/universal/schema/violations.d.ts.map +1 -1
  62. package/dist/auditRouter.js +7 -7
  63. package/dist/auditRouter.js.map +1 -1
  64. package/dist/auditRunner.d.ts.map +1 -1
  65. package/dist/auditRunner.js +36 -6
  66. package/dist/auditRunner.js.map +1 -1
  67. package/dist/cli.js +23 -17
  68. package/dist/cli.js.map +1 -1
  69. package/dist/codeIndexDB.d.ts +1 -1
  70. package/dist/codeIndexDB.d.ts.map +1 -1
  71. package/dist/codeIndexDB.js +148 -18
  72. package/dist/codeIndexDB.js.map +1 -1
  73. package/dist/componentScanner.d.ts.map +1 -1
  74. package/dist/componentScanner.js +89 -28
  75. package/dist/componentScanner.js.map +1 -1
  76. package/dist/config/configLoader.js +1 -1
  77. package/dist/config/configLoader.js.map +1 -1
  78. package/dist/config/defaults.js +3 -3
  79. package/dist/config/defaults.js.map +1 -1
  80. package/dist/constants.d.ts +1 -1
  81. package/dist/constants.d.ts.map +1 -1
  82. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  83. package/dist/conventions/conventionMiner.js +3 -10
  84. package/dist/conventions/conventionMiner.js.map +1 -1
  85. package/dist/daemon/core.d.ts.map +1 -1
  86. package/dist/daemon/core.js +33 -38
  87. package/dist/daemon/core.js.map +1 -1
  88. package/dist/daemon/lspServer.js +1 -1
  89. package/dist/daemon/lspServer.js.map +1 -1
  90. package/dist/daemon/main.d.ts.map +1 -1
  91. package/dist/daemon/main.js +14 -4
  92. package/dist/daemon/main.js.map +1 -1
  93. package/dist/enforcement/gate.js +2 -2
  94. package/dist/functionScanner.d.ts.map +1 -1
  95. package/dist/functionScanner.js +47 -42
  96. package/dist/functionScanner.js.map +1 -1
  97. package/dist/graph/importClassification.d.ts +76 -0
  98. package/dist/graph/importClassification.d.ts.map +1 -0
  99. package/dist/graph/importClassification.js +221 -0
  100. package/dist/graph/importClassification.js.map +1 -0
  101. package/dist/graph/importGraph.d.ts +1 -0
  102. package/dist/graph/importGraph.d.ts.map +1 -1
  103. package/dist/graph/importGraph.js +1 -1
  104. package/dist/graph/importGraph.js.map +1 -1
  105. package/dist/hooks/codex.d.ts.map +1 -1
  106. package/dist/hooks/codex.js +1 -1
  107. package/dist/hooks/codex.js.map +1 -1
  108. package/dist/hooks/core.d.ts +1 -1
  109. package/dist/hooks/core.d.ts.map +1 -1
  110. package/dist/hooks/core.js +4 -4
  111. package/dist/hooks/core.js.map +1 -1
  112. package/dist/hooks/cursor.d.ts.map +1 -1
  113. package/dist/hooks/cursor.js +2 -2
  114. package/dist/hooks/cursor.js.map +1 -1
  115. package/dist/index.d.ts +1 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js.map +1 -1
  118. package/dist/installer.d.ts +20 -0
  119. package/dist/installer.d.ts.map +1 -1
  120. package/dist/installer.js +58 -0
  121. package/dist/installer.js.map +1 -1
  122. package/dist/invariants/invariant-rules.schema.json +1 -1
  123. package/dist/invariants/ruleValidator.js +1 -1
  124. package/dist/invariants/ruleValidator.js.map +1 -1
  125. package/dist/invariants/types.d.ts +1 -1
  126. package/dist/invariants/types.d.ts.map +1 -1
  127. package/dist/languages/LanguageOrchestrator.d.ts +3 -2
  128. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  129. package/dist/languages/LanguageOrchestrator.js +1 -1
  130. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  131. package/dist/languages/RuntimeManager.d.ts +20 -11
  132. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  133. package/dist/languages/RuntimeManager.js +151 -51
  134. package/dist/languages/RuntimeManager.js.map +1 -1
  135. package/dist/languages/adapterBridge.d.ts +10 -0
  136. package/dist/languages/adapterBridge.d.ts.map +1 -1
  137. package/dist/languages/adapterBridge.js +30 -7
  138. package/dist/languages/adapterBridge.js.map +1 -1
  139. package/dist/languages/go/GoAdapter.d.ts +1 -19
  140. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  141. package/dist/languages/go/GoAdapter.js +33 -116
  142. package/dist/languages/go/GoAdapter.js.map +1 -1
  143. package/dist/languages/go/analyzer +0 -0
  144. package/dist/languages/go/analyzer-src/analyzer.go +8 -8
  145. package/dist/languages/go/analyzer-src/solid.go +5 -5
  146. package/dist/languages/goBinary.d.ts +46 -0
  147. package/dist/languages/goBinary.d.ts.map +1 -0
  148. package/dist/languages/goBinary.js +169 -0
  149. package/dist/languages/goBinary.js.map +1 -0
  150. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +1 -28
  151. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
  152. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +3 -42
  153. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
  154. package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
  155. package/dist/languages/tree-sitter/converter.js +2 -1
  156. package/dist/languages/tree-sitter/converter.js.map +1 -1
  157. package/dist/languages/tree-sitter/rawNode.d.ts +31 -0
  158. package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -0
  159. package/dist/languages/tree-sitter/rawNode.js +38 -0
  160. package/dist/languages/tree-sitter/rawNode.js.map +1 -0
  161. package/dist/languages/types.d.ts +0 -21
  162. package/dist/languages/types.d.ts.map +1 -1
  163. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +1 -32
  164. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  165. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +69 -203
  166. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  167. package/dist/ledger.d.ts +3 -0
  168. package/dist/ledger.d.ts.map +1 -1
  169. package/dist/ledger.js +45 -9
  170. package/dist/ledger.js.map +1 -1
  171. package/dist/mcp-tools/projectTasks.js +2 -2
  172. package/dist/mcp-tools/projectTasks.js.map +1 -1
  173. package/dist/mcp-tools/workflowGuide.js +3 -3
  174. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  175. package/dist/mcp-tools-shared.js +13 -13
  176. package/dist/mcp-tools-shared.js.map +1 -1
  177. package/dist/mcp-ui-simple.js +7 -7
  178. package/dist/mcp-ui-simple.js.map +1 -1
  179. package/dist/mcp.js +14 -14
  180. package/dist/mcp.js.map +1 -1
  181. package/dist/mcpAuditJobs.js +7 -7
  182. package/dist/mcpAuditJobs.js.map +1 -1
  183. package/dist/mcpAutoIndex.js +1 -1
  184. package/dist/mcpAutoIndex.js.map +1 -1
  185. package/dist/nextFile.d.ts +2 -2
  186. package/dist/nextFile.d.ts.map +1 -1
  187. package/dist/nextFile.js +19 -5
  188. package/dist/nextFile.js.map +1 -1
  189. package/dist/nextFileIncremental.d.ts +3 -2
  190. package/dist/nextFileIncremental.d.ts.map +1 -1
  191. package/dist/nextFileIncremental.js +20 -7
  192. package/dist/nextFileIncremental.js.map +1 -1
  193. package/dist/pipelineAdapters.d.ts +17 -0
  194. package/dist/pipelineAdapters.d.ts.map +1 -1
  195. package/dist/pipelineAdapters.js +279 -178
  196. package/dist/pipelineAdapters.js.map +1 -1
  197. package/dist/reporting/csvReportGenerator.js +6 -6
  198. package/dist/reporting/csvReportGenerator.js.map +1 -1
  199. package/dist/reporting/htmlReportGenerator.js +11 -11
  200. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  201. package/dist/reporting/jsonReportGenerator.js +2 -2
  202. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  203. package/dist/reporting/sarifReportGenerator.js +1 -1
  204. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  205. package/dist/scripts/benchmarkWorkers.js +1 -1
  206. package/dist/scripts/benchmarkWorkers.js.map +1 -1
  207. package/dist/styles/cssAstExtractor.d.ts +2 -2
  208. package/dist/styles/cssAstExtractor.d.ts.map +1 -1
  209. package/dist/styles/cssAstExtractor.js +67 -111
  210. package/dist/styles/cssAstExtractor.js.map +1 -1
  211. package/dist/styles/styleIndexer.d.ts.map +1 -1
  212. package/dist/styles/styleIndexer.js +19 -6
  213. package/dist/styles/styleIndexer.js.map +1 -1
  214. package/dist/telemetry.d.ts +1 -1
  215. package/dist/telemetry.d.ts.map +1 -1
  216. package/dist/types/crossLanguage.d.ts +1 -1
  217. package/dist/types/crossLanguage.d.ts.map +1 -1
  218. package/dist/types.d.ts +59 -19
  219. package/dist/types.d.ts.map +1 -1
  220. package/dist/types.js +6 -5
  221. package/dist/types.js.map +1 -1
  222. package/dist/utils/astParser.d.ts +1 -17
  223. package/dist/utils/astParser.d.ts.map +1 -1
  224. package/dist/utils/astParser.js +18 -60
  225. package/dist/utils/astParser.js.map +1 -1
  226. package/dist/utils/astUtils.d.ts +12 -12
  227. package/dist/utils/astUtils.d.ts.map +1 -1
  228. package/dist/utils/astUtils.js +83 -82
  229. package/dist/utils/astUtils.js.map +1 -1
  230. package/dist/utils/componentResponsibility.d.ts +8 -8
  231. package/dist/utils/componentResponsibility.d.ts.map +1 -1
  232. package/dist/utils/componentResponsibility.js +33 -38
  233. package/dist/utils/componentResponsibility.js.map +1 -1
  234. package/dist/utils/dependencyExtractor.d.ts +3 -3
  235. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  236. package/dist/utils/dependencyExtractor.js +26 -30
  237. package/dist/utils/dependencyExtractor.js.map +1 -1
  238. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  239. package/dist/utils/fileDiscovery.js +65 -0
  240. package/dist/utils/fileDiscovery.js.map +1 -1
  241. package/dist/utils/jsDocParser.d.ts +6 -6
  242. package/dist/utils/jsDocParser.d.ts.map +1 -1
  243. package/dist/utils/jsDocParser.js +18 -21
  244. package/dist/utils/jsDocParser.js.map +1 -1
  245. package/dist/utils/reactDetection.d.ts +7 -7
  246. package/dist/utils/reactDetection.d.ts.map +1 -1
  247. package/dist/utils/reactDetection.js +42 -49
  248. package/dist/utils/reactDetection.js.map +1 -1
  249. package/dist/version.generated.d.ts +1 -1
  250. package/dist/version.generated.d.ts.map +1 -1
  251. package/dist/version.generated.js +1 -1
  252. package/dist/version.generated.js.map +1 -1
  253. package/package.json +1 -1
  254. package/plugin/.claude-plugin/plugin.json +1 -1
  255. package/plugin/README.md +5 -4
  256. package/plugin/hooks/hooks.json +11 -0
  257. package/plugin/scripts/hook-common.sh +129 -26
  258. package/plugin/scripts/hook-warm.sh +44 -0
  259. package/plugin/skills/code-auditor/SKILL-RULE-KINDS.md +28 -21
  260. package/plugin/skills/code-auditor/SKILL.md +10 -10
  261. package/server.json +2 -2
@@ -19,19 +19,28 @@ import { createHash } from 'crypto';
19
19
  import path from 'path';
20
20
  import { RULE_REGISTRY } from './analyzers/ruleRegistry.js';
21
21
  import { TYPESCRIPT_EXTENSIONS, JAVASCRIPT_EXTENSIONS, getLanguageFromPath } from './utils/fileDiscovery.js';
22
- import { walkAST, isExported, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
22
+ import { walkAST, isExported, getNodeText, getFieldNode, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
23
23
  import { buildImportMap, extractFunctionCalls, } from './utils/dependencyExtractor.js';
24
24
  import { resolveDependency, basenameNoExt } from './graph/importGraph.js';
25
+ import { classifyImportSpecifier, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
25
26
  import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
26
27
  import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, checkUnresolvedQueries, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
27
28
  import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
28
- import { applyMigrationOps } from './analyzers/universal/schema/migrations.js';
29
+ import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
29
30
  // ── Rule ID helpers ──────────────────────────────────────────────────────────
30
31
  function getRuleIdsFor(name) {
31
32
  return Object.entries(RULE_REGISTRY)
32
33
  .filter(([, entry]) => entry.analyzer === name)
33
34
  .map(([id]) => id);
34
35
  }
36
+ /** Join a list of names as prose: "a", "a and b", "a, b and c". */
37
+ function joinEnglish(names) {
38
+ if (names.length === 0)
39
+ return '';
40
+ if (names.length === 1)
41
+ return names[0];
42
+ return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
43
+ }
35
44
  // ── Lazy import singleton ────────────────────────────────────────────────────
36
45
  function lazySingleton(loader) {
37
46
  let promise = null;
@@ -153,10 +162,6 @@ function computeContentHash(body, signature) {
153
162
  const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
154
163
  return createHash('sha256').update(normalized).digest('hex');
155
164
  }
156
- /** Raw text from an ASTNode (tree-sitter node stored on `.raw`). */
157
- function rawText(node) {
158
- return node.raw?.text ?? '';
159
- }
160
165
  export function createFunctionIndexVisitor() {
161
166
  return {
162
167
  name: 'function-index',
@@ -177,8 +182,18 @@ export function createFunctionIndexVisitor() {
177
182
  table: 'functions',
178
183
  data: { _action: 'clear-by-file', file_path: filePath },
179
184
  });
185
+ // Spec 60 — import classification is TS/JS-only: the normalization probe
186
+ // list has no `.go` extension, so applying it to Go relative imports would
187
+ // manufacture false `internal-broken` rows. Clear + re-emit only for TS/JS.
188
+ const isTsJs = lang === 'typescript' || lang === 'javascript';
189
+ if (isTsJs) {
190
+ indexFacts.push({
191
+ table: 'import_specifiers',
192
+ data: { _action: 'clear-by-file', file_path: filePath },
193
+ });
194
+ }
180
195
  // Build import map once per file for resolving call targets
181
- const importMap = buildImportMap(root);
196
+ const importMap = buildImportMap(root, sourceCode);
182
197
  // Collect function-like nodes in two passes:
183
198
  // 1) function_declaration + method_definition nodes
184
199
  // 2) arrow functions assigned to variables (variable_declarator children)
@@ -189,12 +204,11 @@ export function createFunctionIndexVisitor() {
189
204
  const nameNode = node.children?.find((c) => c.type === 'identifier');
190
205
  if (!nameNode)
191
206
  return;
192
- const name = rawText(nameNode);
207
+ const name = getNodeText(nameNode, sourceCode);
193
208
  if (!name)
194
209
  return;
195
210
  const { line } = getLineAndColumn(node);
196
- const raw = node.raw;
197
- const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
211
+ const endLine = node.location.end.line;
198
212
  const body = getFunctionBody(node, sourceCode);
199
213
  const calls = extractFunctionCalls(node, sourceCode, importMap);
200
214
  const callNames = [...new Set(calls.map((c) => c.callee))];
@@ -215,7 +229,7 @@ export function createFunctionIndexVisitor() {
215
229
  const nameNode = node.children?.find((c) => c.type === 'identifier');
216
230
  if (!nameNode)
217
231
  return;
218
- const methodName = rawText(nameNode);
232
+ const methodName = getNodeText(nameNode, sourceCode);
219
233
  if (!methodName)
220
234
  return;
221
235
  // Walk up to find class name
@@ -225,14 +239,13 @@ export function createFunctionIndexVisitor() {
225
239
  if (parent.type === 'class_declaration') {
226
240
  const cn = parent.children?.find((c) => c.type === 'identifier');
227
241
  if (cn)
228
- className = rawText(cn);
242
+ className = getNodeText(cn, sourceCode);
229
243
  break;
230
244
  }
231
245
  parent = parent.parent;
232
246
  }
233
247
  const { line } = getLineAndColumn(node);
234
- const raw = node.raw;
235
- const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
248
+ const endLine = node.location.end.line;
236
249
  const body = getFunctionBody(node, sourceCode);
237
250
  const calls = extractFunctionCalls(node, sourceCode, importMap);
238
251
  const callNames = [...new Set(calls.map((c) => c.callee))];
@@ -257,14 +270,13 @@ export function createFunctionIndexVisitor() {
257
270
  const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
258
271
  if (!nameNode || !arrowFunc)
259
272
  return;
260
- const name = rawText(nameNode);
273
+ const name = getNodeText(nameNode, sourceCode);
261
274
  if (!name)
262
275
  return;
263
276
  // Don't duplicate if already covered as function_declaration
264
277
  // (shouldn't happen — function_declaration is a different node type)
265
278
  const { line } = getLineAndColumn(arrowFunc);
266
- const raw = arrowFunc.raw;
267
- const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
279
+ const endLine = arrowFunc.location.end.line;
268
280
  const body = getFunctionBody(arrowFunc, sourceCode);
269
281
  const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
270
282
  const callNames = [...new Set(calls.map((c) => c.callee))];
@@ -290,12 +302,12 @@ export function createFunctionIndexVisitor() {
290
302
  filePath.endsWith('.jsx') ||
291
303
  (filePath.endsWith('.js') && hasReactImport)) {
292
304
  walkAST(root, (node) => {
293
- if (!isReactComponent(node))
305
+ if (!isReactComponent(node, sourceCode))
294
306
  return;
295
- const ct = detectComponentType(node);
307
+ const ct = detectComponentType(node, sourceCode);
296
308
  if (!ct)
297
309
  return;
298
- const cName = getComponentName(node);
310
+ const cName = getComponentName(node, sourceCode);
299
311
  if (!cName || cName === 'AnonymousComponent')
300
312
  return;
301
313
  const existing = fnEntries.find((f) => f.name === cName);
@@ -308,11 +320,7 @@ export function createFunctionIndexVisitor() {
308
320
  // New entry — class component, function_expression, or memo/forwardRef
309
321
  // wrapper not already captured by passes 1 or 2.
310
322
  const { line } = getLineAndColumn(node);
311
- const raw = node.raw;
312
- const endLine = line +
313
- (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) -
314
- (raw?.startPosition?.row ?? line) +
315
- 1;
323
+ const endLine = node.location.end.line;
316
324
  const body = getFunctionBody(node, sourceCode);
317
325
  fnEntries.push({
318
326
  name: cName,
@@ -335,7 +343,6 @@ export function createFunctionIndexVisitor() {
335
343
  isExported: fn.isExported,
336
344
  complexity: fn.complexity,
337
345
  functionCalls: fn.functionCalls,
338
- body: fn.body,
339
346
  };
340
347
  const contentHash = computeContentHash(fn.body, '');
341
348
  const now = new Date().toISOString();
@@ -379,29 +386,63 @@ export function createFunctionIndexVisitor() {
379
386
  isRequire: false,
380
387
  line: imp.location.start.line,
381
388
  }));
389
+ // Spec 60.1 — classify each static import specifier and emit one row per
390
+ // occurrence to `import_specifiers`. The corpus file set is the stage-1
391
+ // discovery list, threaded here as `_infra.files` (merged into
392
+ // `context.config` by pipeline.ts). Existence is checked against that
393
+ // in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
394
+ // `paths` patterns are threaded via `_infra` and passed to the classifier.
395
+ if (isTsJs) {
396
+ // Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
397
+ // (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
398
+ // the audit's analysis list (already pruned by includePaths/excludePaths),
399
+ // which drops `.json`/other non-analyzed extensions and would mis-classify
400
+ // a real `./invariant-rules.schema.json` as `internal-broken`.
401
+ const corpusFiles = new Set((context.config.corpusFiles) ??
402
+ (context.config.files) ??
403
+ []);
404
+ const virtualModules = context.config.importVirtualModules
405
+ ?? DEFAULT_VIRTUAL_MODULES;
406
+ const tsconfigAliases = context.config.tsconfigAliases;
407
+ for (const imp of staticImportInfos) {
408
+ const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
409
+ virtualModules,
410
+ aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
411
+ });
412
+ indexFacts.push({
413
+ table: 'import_specifiers',
414
+ data: {
415
+ file_path: filePath,
416
+ specifier: imp.source,
417
+ classification,
418
+ resolved_path: resolvedPath ?? null,
419
+ line: imp.location.start.line,
420
+ },
421
+ conflictKey: 'file_path, specifier, line',
422
+ });
423
+ }
424
+ }
382
425
  // Dynamic import() and require() — first attempt with pure NodePattern
383
426
  // Plan note: import keyword is an anonymous tree-sitter node, so
384
427
  // hasChild cannot see it. We use `custom` with raw node access.
385
428
  const dynamicCallNodes = langAdapter.findNodes(langAst, {
386
429
  type: 'call_expression',
387
430
  custom: (node) => {
388
- const raw = node.raw;
389
- const fn = raw?.firstChild;
431
+ const fn = node.children?.[0];
390
432
  return (fn?.type === 'import') ||
391
- (fn?.type === 'identifier' && fn.text === 'require');
433
+ (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
392
434
  },
393
435
  });
394
436
  const dynamicImports = [];
395
437
  for (const node of dynamicCallNodes) {
396
- const raw = node.raw;
397
- const fn = raw?.firstChild;
438
+ const fn = node.children?.[0];
398
439
  const isImport = fn?.type === 'import';
399
- const isRequire = !isImport && (fn?.type === 'identifier' && fn.text === 'require');
400
- // Walk the raw tree to find the string argument
401
- const argsNode = raw?.children?.find((c) => c.type === 'arguments');
440
+ const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
441
+ // Walk the AST children to find the string argument
442
+ const argsNode = node.children?.find((c) => c.type === 'arguments');
402
443
  const stringNode = argsNode?.children?.find((c) => c.type === 'string');
403
444
  if (stringNode) {
404
- const text = stringNode.text;
445
+ const text = getNodeText(stringNode, sourceCode);
405
446
  if (text.length >= 2) {
406
447
  dynamicImports.push({
407
448
  moduleSpecifier: text.slice(1, -1), // strip quotes
@@ -531,8 +572,8 @@ export function createStylesCssVisitor() {
531
572
  facts: {
532
573
  [filePath]: {
533
574
  declarations: extractDeclarationsFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
534
- tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath),
535
- classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath),
575
+ tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
576
+ classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
536
577
  },
537
578
  },
538
579
  };
@@ -940,35 +981,31 @@ export function createCrossDomainReducer() {
940
981
  // short-circuits with `notRunReason` BEFORE any corpus-wide work — the gate is in
941
982
  // the design, not retrofitted after the budget blows. This is the shape that made
942
983
  // the styles reducer 587ms; it must never run over a partial corpus.
943
- // ── Raw-node helpers (tree-sitter raw nodes are `any` here — strict is off) ──
944
- function clRawField(raw, field) {
945
- return typeof raw?.childForFieldName === 'function' ? raw.childForFieldName(field) : null;
946
- }
947
- function clRawNamedChild(raw, type) {
948
- return raw?.namedChildren?.find((c) => c.type === type) ?? null;
949
- }
984
+ // ── Entity helpers (ASTNode-based; parser-specific nodes stay in languages/) ──
950
985
  /** A function body node — `statement_block` (TS/JS) or `block` (Go). */
951
- function clRawBody(raw) {
952
- return raw?.namedChildren?.find((c) => c.type === 'statement_block' || c.type === 'block') ?? null;
986
+ function clBody(node) {
987
+ return node.children?.find((c) => c.type === 'statement_block' || c.type === 'block');
953
988
  }
954
- function clSignatureText(raw, sourceCode) {
955
- if (!raw)
956
- return '';
957
- const body = clRawBody(raw);
958
- const end = body ? body.startIndex : raw.endIndex;
959
- return sourceCode.slice(raw.startIndex, end).trim();
989
+ function clSignatureText(node, sourceCode) {
990
+ const body = clBody(node);
991
+ const end = body ? body.range[0] : node.range[1];
992
+ return sourceCode.slice(node.range[0], end).trim();
960
993
  }
961
- function clBodyText(raw, sourceCode) {
962
- const body = clRawBody(raw);
963
- return body ? sourceCode.slice(body.startIndex, body.endIndex) : '';
994
+ function clBodyText(node, sourceCode) {
995
+ const body = clBody(node);
996
+ return body ? sourceCode.slice(body.range[0], body.range[1]) : '';
964
997
  }
965
998
  /** Prefer the `name` field (works across TS + Go), fall back to identifier children. */
966
- function clNodeName(raw) {
967
- const field = clRawField(raw, 'name');
968
- if (field?.text)
969
- return field.text;
970
- const id = clRawNamedChild(raw, 'identifier') ?? clRawNamedChild(raw, 'property_identifier');
971
- return id?.text;
999
+ function clNodeName(node, sourceCode) {
1000
+ const field = getFieldNode(node, 'name');
1001
+ if (field) {
1002
+ const text = getNodeText(field, sourceCode);
1003
+ if (text)
1004
+ return text;
1005
+ }
1006
+ const id = node.children?.find((c) => c.type === 'identifier')
1007
+ ?? node.children?.find((c) => c.type === 'property_identifier');
1008
+ return id ? getNodeText(id, sourceCode) : undefined;
972
1009
  }
973
1010
  function clEntityId(filePath, type, name, line) {
974
1011
  return `${filePath}::${type}::${name}::${line}`;
@@ -977,25 +1014,20 @@ function clIsExportedGo(name) {
977
1014
  return name.length > 0 && name[0] === name[0].toUpperCase();
978
1015
  }
979
1016
  /** Collect callee names from a function's body (TS `call_expression` / Go `call_expression`). */
980
- function clCollectCallees(raw) {
1017
+ function clCollectCallees(node, sourceCode) {
981
1018
  const names = new Set();
982
- const walk = (node) => {
983
- if (!node)
984
- return;
985
- if (node.type === 'call_expression') {
986
- const fn = typeof node.childForFieldName === 'function'
987
- ? node.childForFieldName('function')
988
- : null;
989
- const callee = fn ?? node.namedChildren?.[0];
990
- const text = callee?.text?.trim();
1019
+ const walk = (n) => {
1020
+ if (n.type === 'call_expression') {
1021
+ const fn = getFieldNode(n, 'function') ?? n.children?.[0];
1022
+ const text = fn ? getNodeText(fn, sourceCode).trim() : '';
991
1023
  if (text && /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(text)) {
992
1024
  names.add(text);
993
1025
  }
994
1026
  }
995
- for (const child of node.namedChildren ?? [])
1027
+ for (const child of n.children ?? [])
996
1028
  walk(child);
997
1029
  };
998
- walk(raw);
1030
+ walk(node);
999
1031
  return [...names];
1000
1032
  }
1001
1033
  /**
@@ -1007,26 +1039,20 @@ function clCollectCallees(raw) {
1007
1039
  * (`arr.map(singularize)`, `[missedClose, …]`) is invisible to it and leaves
1008
1040
  * the genuinely-referenced target looking orphaned.
1009
1041
  */
1010
- function clCollectFileReferences(raw) {
1042
+ function clCollectFileReferences(node, sourceCode) {
1011
1043
  const names = new Set();
1012
1044
  const isIdentPath = (t) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(t);
1013
- const walk = (node) => {
1014
- if (!node)
1015
- return;
1016
- const t = node.type;
1045
+ const walk = (n) => {
1046
+ const t = n.type;
1017
1047
  if (t === 'call_expression') {
1018
- const fn = typeof node.childForFieldName === 'function'
1019
- ? node.childForFieldName('function')
1020
- : null;
1021
- const text = (fn ?? node.namedChildren?.[0])?.text?.trim();
1048
+ const fn = getFieldNode(n, 'function') ?? n.children?.[0];
1049
+ const text = fn ? getNodeText(fn, sourceCode).trim() : '';
1022
1050
  if (text && isIdentPath(text))
1023
1051
  names.add(text);
1024
1052
  }
1025
1053
  else if (t === 'jsx_opening_element' || t === 'jsx_self_closing_element') {
1026
- const nameNode = typeof node.childForFieldName === 'function'
1027
- ? node.childForFieldName('name')
1028
- : null;
1029
- const text = (nameNode ?? node.namedChildren?.[0])?.text?.trim();
1054
+ const nameNode = getFieldNode(n, 'name') ?? n.children?.[0];
1055
+ const text = nameNode ? getNodeText(nameNode, sourceCode).trim() : '';
1030
1056
  if (text && isIdentPath(text))
1031
1057
  names.add(text);
1032
1058
  }
@@ -1036,17 +1062,17 @@ function clCollectFileReferences(raw) {
1036
1062
  // function reference. Declaration names (`name` fields) and member
1037
1063
  // accesses (`property_identifier`) are separate node types and are not
1038
1064
  // captured here.
1039
- const parent = node.parent;
1065
+ const parent = n.parent;
1040
1066
  if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
1041
- const text = node.text?.trim();
1067
+ const text = getNodeText(n, sourceCode).trim();
1042
1068
  if (text && /^[A-Za-z_$][\w$]*$/.test(text))
1043
1069
  names.add(text);
1044
1070
  }
1045
1071
  }
1046
- for (const child of node.namedChildren ?? [])
1072
+ for (const child of n.children ?? [])
1047
1073
  walk(child);
1048
1074
  };
1049
- walk(raw);
1075
+ walk(node);
1050
1076
  return [...names];
1051
1077
  }
1052
1078
  function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false) {
@@ -1077,18 +1103,21 @@ function clMakeFunction(filePath, lang, name, line, signature, purpose, params,
1077
1103
  * (`import(someVariable)` — unresolvable, reported rather than silenced), or
1078
1104
  * `null` when the call is not a dynamic import/require.
1079
1105
  */
1080
- function clDynamicImport(raw) {
1081
- if (raw?.type !== 'call_expression')
1106
+ function clDynamicImport(node, sourceCode) {
1107
+ if (node.type !== 'call_expression')
1082
1108
  return null;
1083
- const fn = raw.firstChild;
1109
+ const fn = node.children?.[0];
1084
1110
  const isImport = fn?.type === 'import';
1085
- const isRequire = fn?.type === 'identifier' && fn?.text === 'require';
1111
+ const isRequire = fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require';
1086
1112
  if (!isImport && !isRequire)
1087
1113
  return null;
1088
- const args = raw.children?.find((c) => c.type === 'arguments');
1114
+ const args = node.children?.find((c) => c.type === 'arguments');
1089
1115
  const stringNode = args?.children?.find((c) => c.type === 'string');
1090
- if (stringNode && stringNode.text.length >= 2) {
1091
- return { specifier: stringNode.text.slice(1, -1) };
1116
+ if (stringNode) {
1117
+ const text = getNodeText(stringNode, sourceCode);
1118
+ if (text.length >= 2) {
1119
+ return { specifier: text.slice(1, -1) };
1120
+ }
1092
1121
  }
1093
1122
  // A template literal with no `${…}` substitution is a compile-time constant,
1094
1123
  // not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
@@ -1098,15 +1127,17 @@ function clDynamicImport(raw) {
1098
1127
  if (templateNode) {
1099
1128
  const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
1100
1129
  if (!hasSubstitution) {
1101
- const t = templateNode.text;
1130
+ const t = getNodeText(templateNode, sourceCode);
1102
1131
  if (t.length >= 2 && t.startsWith('`') && t.endsWith('`')) {
1103
1132
  return { specifier: t.slice(1, -1) };
1104
1133
  }
1105
1134
  }
1106
1135
  }
1107
1136
  // Computed specifier — the target module cannot be resolved statically.
1108
- const named = args?.namedChildren?.[0];
1109
- const expression = named?.text ?? (args?.text ?? '').replace(/^\(|\)$/g, '').trim();
1137
+ const named = args?.children?.[0];
1138
+ const expression = named
1139
+ ? getNodeText(named, sourceCode)
1140
+ : getNodeText(args, sourceCode).replace(/^\(|\)$/g, '').trim();
1110
1141
  return { computed: true, expression };
1111
1142
  }
1112
1143
  /**
@@ -1119,23 +1150,26 @@ function clDynamicImport(raw) {
1119
1150
  * names), since Go has no `export` keyword. Dynamic `import()`/`require()`
1120
1151
  * with a static string argument also record an import edge (Spec 58 R2).
1121
1152
  */
1122
- function clCollectFileInfo(root, lang) {
1153
+ function clCollectFileInfo(root, lang, sourceCode) {
1123
1154
  const imports = new Set();
1124
1155
  const unresolvedDynamicImports = [];
1125
1156
  let hasExports = false;
1126
1157
  walkAST(root, (node) => {
1127
- const raw = node.raw;
1128
1158
  if (node.type === 'import_statement') {
1129
- const source = clRawField(raw, 'source');
1130
- if (source?.text)
1131
- imports.add(source.text.replace(/^['"]|['"]$/g, ''));
1159
+ const source = getFieldNode(node, 'source');
1160
+ if (source) {
1161
+ const text = getNodeText(source, sourceCode);
1162
+ imports.add(text.replace(/^['"]|['"]$/g, ''));
1163
+ }
1132
1164
  }
1133
1165
  else if (node.type === 'import_declaration') {
1134
- for (const spec of raw?.namedChildren ?? []) {
1166
+ for (const spec of node.children ?? []) {
1135
1167
  if (spec.type === 'import_spec') {
1136
- const p = clRawField(spec, 'path');
1137
- if (p?.text)
1138
- imports.add(p.text.replace(/^['"]|['"]$/g, ''));
1168
+ const p = getFieldNode(spec, 'path');
1169
+ if (p) {
1170
+ const text = getNodeText(p, sourceCode);
1171
+ imports.add(text.replace(/^['"]|['"]$/g, ''));
1172
+ }
1139
1173
  }
1140
1174
  }
1141
1175
  }
@@ -1144,109 +1178,113 @@ function clCollectFileInfo(root, lang) {
1144
1178
  // Re-exports (`export { x } from './y'`, `export * from './y'`) are import
1145
1179
  // edges for reachability: a barrel re-exporting a module marks it live,
1146
1180
  // not dead. The `source` field is present only on the `from` form.
1147
- const source = clRawField(raw, 'source');
1148
- if (source?.text)
1149
- imports.add(source.text.replace(/^['"]|['"]$/g, ''));
1181
+ const source = getFieldNode(node, 'source');
1182
+ if (source) {
1183
+ const text = getNodeText(source, sourceCode);
1184
+ imports.add(text.replace(/^['"]|['"]$/g, ''));
1185
+ }
1150
1186
  }
1151
1187
  else if (node.type === 'call_expression') {
1152
- const dyn = clDynamicImport(raw);
1188
+ const dyn = clDynamicImport(node, sourceCode);
1153
1189
  if (dyn && 'specifier' in dyn) {
1154
1190
  imports.add(dyn.specifier);
1155
1191
  }
1156
1192
  else if (dyn) {
1157
- unresolvedDynamicImports.push({ line: (raw?.startPosition?.row ?? 0) + 1, expression: dyn.expression });
1193
+ unresolvedDynamicImports.push({ line: node.location.start.line, expression: dyn.expression });
1158
1194
  }
1159
1195
  }
1160
1196
  });
1161
1197
  return { imports: [...imports], hasExports, unresolvedDynamicImports };
1162
1198
  }
1163
1199
  // ── TS/JS extraction ─────────────────────────────────────────────────────────
1164
- function clExtractTSParams(raw) {
1200
+ function clExtractTSParams(node, sourceCode) {
1165
1201
  const params = [];
1166
- const formal = clRawNamedChild(raw, 'formal_parameters');
1167
- for (const p of formal?.namedChildren ?? []) {
1202
+ const formal = node.children?.find((c) => c.type === 'formal_parameters');
1203
+ for (const p of formal?.children ?? []) {
1168
1204
  if (p.type !== 'required_parameter' && p.type !== 'optional_parameter')
1169
1205
  continue;
1170
- const name = clRawField(p, 'name')?.text ?? clRawNamedChild(p, 'identifier')?.text;
1206
+ const nameNode = getFieldNode(p, 'name') ?? p.children?.find((c) => c.type === 'identifier');
1207
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1171
1208
  if (!name)
1172
1209
  continue;
1173
- const type = clRawField(p, 'type')?.text?.trim();
1210
+ const typeNode = getFieldNode(p, 'type');
1211
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1174
1212
  params.push({ name, type, optional: p.type === 'optional_parameter', language: 'typescript' });
1175
1213
  }
1176
1214
  return params;
1177
1215
  }
1178
- function clExtractTSInterfaceParams(raw) {
1216
+ function clExtractTSInterfaceParams(node, sourceCode) {
1179
1217
  const params = [];
1180
- const walk = (node) => {
1181
- if (!node)
1182
- return;
1183
- if (node.type === 'property_signature') {
1184
- const name = clRawField(node, 'name')?.text ?? clRawNamedChild(node, 'property_identifier')?.text;
1218
+ const walk = (n) => {
1219
+ if (n.type === 'property_signature') {
1220
+ const nameNode = getFieldNode(n, 'name') ?? n.children?.find((c) => c.type === 'property_identifier');
1221
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1185
1222
  if (!name)
1186
1223
  return;
1187
- const type = clRawField(node, 'type')?.text?.trim();
1188
- const optional = node.children?.some((c) => c.type === '?') ?? false;
1224
+ const typeNode = getFieldNode(n, 'type');
1225
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1226
+ const optional = n.children?.some((c) => c.type === '?') ?? false;
1189
1227
  params.push({ name, type, optional, language: 'typescript' });
1190
1228
  }
1191
- for (const child of node.namedChildren ?? [])
1229
+ for (const child of n.children ?? [])
1192
1230
  walk(child);
1193
1231
  };
1194
- walk(raw);
1232
+ walk(node);
1195
1233
  return params;
1196
1234
  }
1197
1235
  function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
1198
- const fileReferences = clCollectFileReferences(root.raw);
1236
+ const fileReferences = clCollectFileReferences(root, sourceCode);
1199
1237
  walkAST(root, (node) => {
1200
- const raw = node.raw;
1201
1238
  if (node.type === 'function_declaration' || node.type === 'method_definition') {
1202
- const name = clNodeName(raw);
1239
+ const name = clNodeName(node, sourceCode);
1203
1240
  if (!name)
1204
1241
  return;
1205
1242
  let displayName = name;
1206
1243
  if (node.type === 'method_definition') {
1207
- let p = raw?.parent;
1244
+ let p = node.parent ?? null;
1208
1245
  while (p) {
1209
1246
  if (p.type === 'class_declaration') {
1210
- const cn = clNodeName(p);
1247
+ const cn = clNodeName(p, sourceCode);
1211
1248
  if (cn)
1212
1249
  displayName = `${cn}.${name}`;
1213
1250
  break;
1214
1251
  }
1215
- p = p.parent;
1252
+ p = p.parent ?? null;
1216
1253
  }
1217
1254
  }
1218
- const line = (raw?.startPosition?.row ?? 0) + 1;
1219
- const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(raw, sourceCode) || raw?.text || '', clBodyText(raw, sourceCode), clExtractTSParams(raw), clCollectCallees(raw), isExported(node), calculateComplexity(node), node.type === 'method_definition');
1255
+ const line = node.location.start.line;
1256
+ const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractTSParams(node, sourceCode), clCollectCallees(node, sourceCode), isExported(node), calculateComplexity(node), node.type === 'method_definition');
1220
1257
  entity.metadata.fileReferences = fileReferences;
1221
1258
  out.push(entity);
1222
1259
  return;
1223
1260
  }
1224
1261
  if (node.type === 'variable_declarator') {
1225
- const arrow = clRawNamedChild(raw, 'arrow_function');
1262
+ const arrow = node.children?.find((c) => c.type === 'arrow_function');
1226
1263
  if (!arrow)
1227
1264
  return;
1228
- const name = clRawNamedChild(raw, 'identifier')?.text;
1265
+ const nameNode = node.children?.find((c) => c.type === 'identifier');
1266
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1229
1267
  if (!name)
1230
1268
  return;
1231
- const line = (raw?.startPosition?.row ?? 0) + 1;
1232
- const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || arrow?.text || '', clBodyText(arrow, sourceCode), [], clCollectCallees(arrow), isExported(node), calculateComplexity(node));
1269
+ const line = node.location.start.line;
1270
+ const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode), clBodyText(arrow, sourceCode), [], clCollectCallees(arrow, sourceCode), isExported(node), calculateComplexity(node));
1233
1271
  entity.metadata.fileReferences = fileReferences;
1234
1272
  out.push(entity);
1235
1273
  return;
1236
1274
  }
1237
1275
  if (node.type === 'interface_declaration') {
1238
- const name = clNodeName(raw);
1276
+ const name = clNodeName(node, sourceCode);
1239
1277
  if (!name)
1240
1278
  return;
1241
- const line = (raw?.startPosition?.row ?? 0) + 1;
1279
+ const line = node.location.start.line;
1242
1280
  out.push({
1243
1281
  id: clEntityId(filePath, 'interface', name, line),
1244
1282
  name,
1245
1283
  language: lang,
1246
1284
  file: filePath,
1247
1285
  type: 'interface',
1248
- signature: raw?.text ?? '',
1249
- parameters: clExtractTSInterfaceParams(raw),
1286
+ signature: getNodeText(node, sourceCode),
1287
+ parameters: clExtractTSInterfaceParams(node, sourceCode),
1250
1288
  startLine: line,
1251
1289
  visibility: isExported(node) ? 'public' : 'private',
1252
1290
  calls: [],
@@ -1261,70 +1299,75 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
1261
1299
  });
1262
1300
  }
1263
1301
  // ── Go extraction ────────────────────────────────────────────────────────────
1264
- function clGoReceiverType(receiver) {
1265
- for (const child of receiver?.namedChildren ?? []) {
1302
+ function clGoReceiverType(receiver, sourceCode) {
1303
+ for (const child of receiver.children ?? []) {
1266
1304
  if (child.type === 'parameter_declaration') {
1267
- const typeNode = clRawField(child, 'type');
1305
+ const typeNode = getFieldNode(child, 'type');
1268
1306
  if (typeNode)
1269
- return typeNode.text.replace(/^\*/, '');
1307
+ return getNodeText(typeNode, sourceCode).replace(/^\*/, '');
1270
1308
  }
1271
1309
  }
1272
1310
  return undefined;
1273
1311
  }
1274
- function clExtractGoParams(raw) {
1312
+ function clExtractGoParams(node, sourceCode) {
1275
1313
  const params = [];
1276
- const paramList = clRawField(raw, 'parameters');
1277
- for (const p of paramList?.namedChildren ?? []) {
1314
+ const paramList = getFieldNode(node, 'parameters');
1315
+ for (const p of paramList?.children ?? []) {
1278
1316
  if (p.type !== 'parameter_declaration')
1279
1317
  continue;
1280
- const name = clRawField(p, 'name')?.text ?? clRawField(p, 'type')?.text;
1281
- const type = clRawField(p, 'type')?.text?.trim();
1318
+ const nameNode = getFieldNode(p, 'name') ?? getFieldNode(p, 'type');
1319
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1320
+ const typeNode = getFieldNode(p, 'type');
1321
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1282
1322
  if (!name && !type)
1283
1323
  continue;
1284
1324
  params.push({ name: name ?? type ?? '<unknown>', type, optional: false, language: 'go' });
1285
1325
  }
1286
1326
  return params;
1287
1327
  }
1288
- function clExtractGoStructFields(structNode) {
1328
+ function clExtractGoStructFields(structNode, sourceCode) {
1289
1329
  const fields = [];
1290
- for (const f of structNode?.namedChildren ?? []) {
1330
+ for (const f of structNode.children ?? []) {
1291
1331
  if (f.type !== 'field_declaration')
1292
1332
  continue;
1293
- const name = clRawField(f, 'name')?.text;
1333
+ const nameNode = getFieldNode(f, 'name');
1334
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1294
1335
  if (!name)
1295
1336
  continue;
1296
- const type = clRawField(f, 'type')?.text?.trim();
1297
- const tag = clRawField(f, 'tag')?.text;
1337
+ const typeNode = getFieldNode(f, 'type');
1338
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1339
+ const tagNode = getFieldNode(f, 'tag');
1340
+ const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
1298
1341
  fields.push({ name, type, isExported: clIsExportedGo(name), tag });
1299
1342
  }
1300
1343
  return fields;
1301
1344
  }
1302
1345
  function clExtractGoEntities(root, filePath, sourceCode, out) {
1303
- const fileReferences = clCollectFileReferences(root.raw);
1346
+ const fileReferences = clCollectFileReferences(root, sourceCode);
1304
1347
  walkAST(root, (node) => {
1305
- const raw = node.raw;
1306
1348
  if (node.type === 'function_declaration') {
1307
- const name = clNodeName(raw);
1349
+ const name = clNodeName(node, sourceCode);
1308
1350
  if (!name)
1309
1351
  return;
1310
- const receiver = clRawField(raw, 'receiver');
1311
- const receiverType = receiver ? clGoReceiverType(receiver) : undefined;
1352
+ const receiver = getFieldNode(node, 'receiver');
1353
+ const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
1312
1354
  const displayName = receiverType ? `${receiverType}.${name}` : name;
1313
- const line = (raw?.startPosition?.row ?? 0) + 1;
1314
- const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(raw, sourceCode) || raw?.text || '', clBodyText(raw, sourceCode), clExtractGoParams(raw), clCollectCallees(raw), clIsExportedGo(name), calculateComplexity(node));
1355
+ const line = node.location.start.line;
1356
+ const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractGoParams(node, sourceCode), clCollectCallees(node, sourceCode), clIsExportedGo(name), calculateComplexity(node));
1315
1357
  entity.metadata.fileReferences = fileReferences;
1316
1358
  out.push(entity);
1317
1359
  return;
1318
1360
  }
1319
1361
  if (node.type === 'type_declaration') {
1320
- for (const spec of raw?.namedChildren ?? []) {
1362
+ for (const spec of node.children ?? []) {
1321
1363
  if (spec.type !== 'type_spec')
1322
1364
  continue;
1323
- const name = clRawField(spec, 'name')?.text;
1365
+ const nameNode = getFieldNode(spec, 'name');
1366
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1324
1367
  if (!name)
1325
1368
  continue;
1326
- const typeNode = clRawField(spec, 'type');
1327
- const line = (spec?.startPosition?.row ?? 0) + 1;
1369
+ const typeNode = getFieldNode(spec, 'type');
1370
+ const line = spec.location.start.line;
1328
1371
  if (typeNode?.type === 'struct_type') {
1329
1372
  out.push({
1330
1373
  id: clEntityId(filePath, 'struct', name, line),
@@ -1332,7 +1375,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1332
1375
  language: 'go',
1333
1376
  file: filePath,
1334
1377
  type: 'struct',
1335
- signature: spec?.text ?? '',
1378
+ signature: getNodeText(spec, sourceCode),
1336
1379
  parameters: [],
1337
1380
  startLine: line,
1338
1381
  visibility: clIsExportedGo(name) ? 'public' : 'private',
@@ -1341,7 +1384,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1341
1384
  purpose: '',
1342
1385
  context: '',
1343
1386
  searchTokens: [name.toLowerCase()],
1344
- metadata: { fields: clExtractGoStructFields(typeNode) },
1387
+ metadata: { fields: clExtractGoStructFields(typeNode, sourceCode) },
1345
1388
  });
1346
1389
  }
1347
1390
  else if (typeNode?.type === 'interface_type') {
@@ -1351,7 +1394,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1351
1394
  language: 'go',
1352
1395
  file: filePath,
1353
1396
  type: 'interface',
1354
- signature: spec?.text ?? '',
1397
+ signature: getNodeText(spec, sourceCode),
1355
1398
  parameters: [],
1356
1399
  startLine: line,
1357
1400
  visibility: clIsExportedGo(name) ? 'public' : 'private',
@@ -1390,7 +1433,7 @@ export function createCrossLanguageEntityVisitor() {
1390
1433
  else {
1391
1434
  clExtractTSEntities(root, filePath, sourceCode, lang, entities);
1392
1435
  }
1393
- const fileInfo = clCollectFileInfo(root, lang);
1436
+ const fileInfo = clCollectFileInfo(root, lang, sourceCode);
1394
1437
  // Go has no `export` keyword — an exported symbol is a capitalized
1395
1438
  // top-level name, which the entity extractor already records as public.
1396
1439
  const hasExports = lang === 'go'
@@ -1785,15 +1828,15 @@ export function createDependencyGraphReducer() {
1785
1828
  }
1786
1829
  const HEALTH_SEVERITY = {
1787
1830
  'break-cycles': 'severe',
1788
- 'reduce-coupling': 'high',
1789
- 'split-responsibilities': 'high',
1831
+ 'reduce-coupling': 'advisory',
1832
+ 'split-responsibilities': 'advisory',
1790
1833
  'review-orphans': 'severe',
1791
1834
  };
1792
1835
  for (const s of health.suggestions) {
1793
1836
  violations.push({
1794
1837
  file: '(multiple)',
1795
1838
  line: 0,
1796
- severity: HEALTH_SEVERITY[s.type] ?? 'high',
1839
+ severity: HEALTH_SEVERITY[s.type] ?? 'advisory',
1797
1840
  message: s.description,
1798
1841
  rule: s.type,
1799
1842
  type: s.type,
@@ -2029,7 +2072,7 @@ export function createSchemaCodeVisitor() {
2029
2072
  // Find table references (per-file, uses allTables for short-id false-positive filtering)
2030
2073
  const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
2031
2074
  // Record schema usage → emit as indexFacts via the shared instance
2032
- a.recordTableUsage(ast, adapter, context.filePath, tableRefs);
2075
+ a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
2033
2076
  const pending = a.getPendingSchemaRecords();
2034
2077
  // Emit clear-by-file + per-usage index facts
2035
2078
  if (pending.clearFiles.length > 0) {
@@ -2044,6 +2087,8 @@ export function createSchemaCodeVisitor() {
2044
2087
  file_path: usage.filePath,
2045
2088
  table_name: usage.tableName,
2046
2089
  function_name: usage.functionName,
2090
+ function_start_line: usage.functionStartLine,
2091
+ function_start_column: usage.functionStartColumn,
2047
2092
  usage_type: usage.usageType,
2048
2093
  line: usage.line,
2049
2094
  column: usage.column,
@@ -2218,6 +2263,12 @@ export function createSchemaReducer() {
2218
2263
  return na - nb;
2219
2264
  return a.filePath.localeCompare(b.filePath);
2220
2265
  });
2266
+ // Drop provenance — the last migration that removed a table, and the tables
2267
+ // that same migration introduced. Lets unknown-table detection distinguish
2268
+ // "never existed" from "existed and was dropped": the latter is a stale code
2269
+ // reference, not a typo. `dropProvenance` ends up holding exactly the tables
2270
+ // dropped and not subsequently recreated (CREATE clears the entry).
2271
+ const dropProvenance = new Map();
2221
2272
  for (const sqlFile of sqlFiles) {
2222
2273
  const before = new Set(knownTables);
2223
2274
  applyMigrationOps(sqlFile.ops, knownTables);
@@ -2229,6 +2280,25 @@ export function createSchemaReducer() {
2229
2280
  tableProvenances.set(table, sources);
2230
2281
  }
2231
2282
  }
2283
+ // Genuinely-new tables introduced by this migration (excludes rename/rebuild
2284
+ // churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
2285
+ const createdHere = [];
2286
+ for (const op of sqlFile.ops) {
2287
+ if (op.op === 'CREATE') {
2288
+ const t = stripIdentifier(op.table);
2289
+ if (!before.has(t))
2290
+ createdHere.push(t);
2291
+ }
2292
+ }
2293
+ for (const op of sqlFile.ops) {
2294
+ const t = stripIdentifier(op.table);
2295
+ if (op.op === 'DROP') {
2296
+ dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
2297
+ }
2298
+ else if (op.op === 'CREATE') {
2299
+ dropProvenance.delete(t);
2300
+ }
2301
+ }
2232
2302
  }
2233
2303
  // 1b. ORM tables from code files — with provenance from the table-source registry
2234
2304
  for (const [filePath, fact] of perFile()) {
@@ -2312,6 +2382,37 @@ export function createSchemaReducer() {
2312
2382
  const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
2313
2383
  if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
2314
2384
  for (const ref of unknownRefs) {
2385
+ // The table existed and was dropped in a migration — a stale code
2386
+ // reference, not a typo. Name the dropping migration and the tables
2387
+ // that migration introduced (evidence, not proof of a successor).
2388
+ const drop = dropProvenance.get(ref.table);
2389
+ if (drop) {
2390
+ const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
2391
+ const created = drop.createdInSameMigration;
2392
+ const msg = created.length > 0
2393
+ ? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
2394
+ : `${ref.table} was dropped in ${migrationName} and was not recreated.`;
2395
+ violations.push({
2396
+ file: ref.file,
2397
+ line: ref.line,
2398
+ column: ref.column,
2399
+ severity: 'critical',
2400
+ message: msg,
2401
+ rule: 'stale-table-reference',
2402
+ analyzer: 'schema',
2403
+ symbol: ref.table,
2404
+ resolution: {
2405
+ action: 'update-stale-reference',
2406
+ summary: created.length > 0
2407
+ ? `The table '${ref.table}' was dropped in ${migrationName}. That migration creates ${joinEnglish(created)} — update this reference to a table that still exists.`
2408
+ : `The table '${ref.table}' was dropped in ${migrationName} and not recreated — update or remove this reference.`,
2409
+ symbols: created.length > 0 ? created : [ref.table],
2410
+ files: [ref.file],
2411
+ lines: [ref.line],
2412
+ },
2413
+ });
2414
+ continue;
2415
+ }
2315
2416
  const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
2316
2417
  const msg = suggestions.length > 0
2317
2418
  ? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`