@mirnoorata/codexa 0.2.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 (364) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +634 -0
  3. package/dist/artifacts.d.ts +2 -0
  4. package/dist/artifacts.js +375 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/autonomy.d.ts +17 -0
  7. package/dist/autonomy.js +124 -0
  8. package/dist/autonomy.js.map +1 -0
  9. package/dist/autoverify/policy.d.ts +5 -0
  10. package/dist/autoverify/policy.js +18 -0
  11. package/dist/autoverify/policy.js.map +1 -0
  12. package/dist/autoverify.d.ts +45 -0
  13. package/dist/autoverify.js +1041 -0
  14. package/dist/autoverify.js.map +1 -0
  15. package/dist/cache-lock.d.ts +16 -0
  16. package/dist/cache-lock.js +181 -0
  17. package/dist/cache-lock.js.map +1 -0
  18. package/dist/cli/hooks.d.ts +5 -0
  19. package/dist/cli/hooks.js +264 -0
  20. package/dist/cli/hooks.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +1034 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/codex-contract.d.ts +2 -0
  25. package/dist/codex-contract.js +78 -0
  26. package/dist/codex-contract.js.map +1 -0
  27. package/dist/command.d.ts +34 -0
  28. package/dist/command.js +162 -0
  29. package/dist/command.js.map +1 -0
  30. package/dist/doctor.d.ts +112 -0
  31. package/dist/doctor.js +518 -0
  32. package/dist/doctor.js.map +1 -0
  33. package/dist/eval/baseline.d.ts +7 -0
  34. package/dist/eval/baseline.js +146 -0
  35. package/dist/eval/baseline.js.map +1 -0
  36. package/dist/eval/historical.d.ts +4 -0
  37. package/dist/eval/historical.js +663 -0
  38. package/dist/eval/historical.js.map +1 -0
  39. package/dist/eval/render.d.ts +2 -0
  40. package/dist/eval/render.js +53 -0
  41. package/dist/eval/render.js.map +1 -0
  42. package/dist/eval/scoring.d.ts +21 -0
  43. package/dist/eval/scoring.js +618 -0
  44. package/dist/eval/scoring.js.map +1 -0
  45. package/dist/eval/synthetic.d.ts +36 -0
  46. package/dist/eval/synthetic.js +107 -0
  47. package/dist/eval/synthetic.js.map +1 -0
  48. package/dist/eval/types.d.ts +36 -0
  49. package/dist/eval/types.js +2 -0
  50. package/dist/eval/types.js.map +1 -0
  51. package/dist/eval.d.ts +140 -0
  52. package/dist/eval.js +551 -0
  53. package/dist/eval.js.map +1 -0
  54. package/dist/git.d.ts +17 -0
  55. package/dist/git.js +189 -0
  56. package/dist/git.js.map +1 -0
  57. package/dist/github-release.d.ts +47 -0
  58. package/dist/github-release.js +610 -0
  59. package/dist/github-release.js.map +1 -0
  60. package/dist/github-sync.d.ts +68 -0
  61. package/dist/github-sync.js +345 -0
  62. package/dist/github-sync.js.map +1 -0
  63. package/dist/graph.d.ts +10 -0
  64. package/dist/graph.js +665 -0
  65. package/dist/graph.js.map +1 -0
  66. package/dist/indexer/aliases.d.ts +2 -0
  67. package/dist/indexer/aliases.js +190 -0
  68. package/dist/indexer/aliases.js.map +1 -0
  69. package/dist/indexer/artifact-writing.d.ts +3 -0
  70. package/dist/indexer/artifact-writing.js +79 -0
  71. package/dist/indexer/artifact-writing.js.map +1 -0
  72. package/dist/indexer/discovery.d.ts +2 -0
  73. package/dist/indexer/discovery.js +5 -0
  74. package/dist/indexer/discovery.js.map +1 -0
  75. package/dist/indexer/external-facts.d.ts +6 -0
  76. package/dist/indexer/external-facts.js +45 -0
  77. package/dist/indexer/external-facts.js.map +1 -0
  78. package/dist/indexer/freshness.d.ts +8 -0
  79. package/dist/indexer/freshness.js +56 -0
  80. package/dist/indexer/freshness.js.map +1 -0
  81. package/dist/indexer/graph-stage.d.ts +2 -0
  82. package/dist/indexer/graph-stage.js +21 -0
  83. package/dist/indexer/graph-stage.js.map +1 -0
  84. package/dist/indexer/parsing.d.ts +30 -0
  85. package/dist/indexer/parsing.js +177 -0
  86. package/dist/indexer/parsing.js.map +1 -0
  87. package/dist/indexer/pipeline.d.ts +5 -0
  88. package/dist/indexer/pipeline.js +8 -0
  89. package/dist/indexer/pipeline.js.map +1 -0
  90. package/dist/indexer/ranking.d.ts +4 -0
  91. package/dist/indexer/ranking.js +134 -0
  92. package/dist/indexer/ranking.js.map +1 -0
  93. package/dist/indexer.d.ts +13 -0
  94. package/dist/indexer.js +395 -0
  95. package/dist/indexer.js.map +1 -0
  96. package/dist/init.d.ts +24 -0
  97. package/dist/init.js +566 -0
  98. package/dist/init.js.map +1 -0
  99. package/dist/language.d.ts +8 -0
  100. package/dist/language.js +123 -0
  101. package/dist/language.js.map +1 -0
  102. package/dist/live-index.d.ts +68 -0
  103. package/dist/live-index.js +215 -0
  104. package/dist/live-index.js.map +1 -0
  105. package/dist/lsp/assist.d.ts +44 -0
  106. package/dist/lsp/assist.js +331 -0
  107. package/dist/lsp/assist.js.map +1 -0
  108. package/dist/lsp/client.d.ts +59 -0
  109. package/dist/lsp/client.js +208 -0
  110. package/dist/lsp/client.js.map +1 -0
  111. package/dist/mcp/compaction.d.ts +15 -0
  112. package/dist/mcp/compaction.js +1249 -0
  113. package/dist/mcp/compaction.js.map +1 -0
  114. package/dist/mcp/envelope.d.ts +44 -0
  115. package/dist/mcp/envelope.js +425 -0
  116. package/dist/mcp/envelope.js.map +1 -0
  117. package/dist/mcp/prompts.d.ts +2 -0
  118. package/dist/mcp/prompts.js +109 -0
  119. package/dist/mcp/prompts.js.map +1 -0
  120. package/dist/mcp/resources.d.ts +2 -0
  121. package/dist/mcp/resources.js +132 -0
  122. package/dist/mcp/resources.js.map +1 -0
  123. package/dist/mcp/runtime.d.ts +15 -0
  124. package/dist/mcp/runtime.js +122 -0
  125. package/dist/mcp/runtime.js.map +1 -0
  126. package/dist/mcp/session-memory.d.ts +3 -0
  127. package/dist/mcp/session-memory.js +61 -0
  128. package/dist/mcp/session-memory.js.map +1 -0
  129. package/dist/mcp/tool-registry.d.ts +269 -0
  130. package/dist/mcp/tool-registry.js +284 -0
  131. package/dist/mcp/tool-registry.js.map +1 -0
  132. package/dist/mcp/tools.d.ts +53 -0
  133. package/dist/mcp/tools.js +372 -0
  134. package/dist/mcp/tools.js.map +1 -0
  135. package/dist/mcp-repo-root.d.ts +16 -0
  136. package/dist/mcp-repo-root.js +322 -0
  137. package/dist/mcp-repo-root.js.map +1 -0
  138. package/dist/mcp-tool-catalog.d.ts +2 -0
  139. package/dist/mcp-tool-catalog.js +2 -0
  140. package/dist/mcp-tool-catalog.js.map +1 -0
  141. package/dist/mcp.d.ts +11 -0
  142. package/dist/mcp.js +332 -0
  143. package/dist/mcp.js.map +1 -0
  144. package/dist/outcome-ranking.d.ts +5 -0
  145. package/dist/outcome-ranking.js +115 -0
  146. package/dist/outcome-ranking.js.map +1 -0
  147. package/dist/parser/context.d.ts +28 -0
  148. package/dist/parser/context.js +2 -0
  149. package/dist/parser/context.js.map +1 -0
  150. package/dist/parser/ecma.d.ts +5 -0
  151. package/dist/parser/ecma.js +388 -0
  152. package/dist/parser/ecma.js.map +1 -0
  153. package/dist/parser/facts.d.ts +12 -0
  154. package/dist/parser/facts.js +137 -0
  155. package/dist/parser/facts.js.map +1 -0
  156. package/dist/parser/json.d.ts +3 -0
  157. package/dist/parser/json.js +318 -0
  158. package/dist/parser/json.js.map +1 -0
  159. package/dist/parser/markdown.d.ts +3 -0
  160. package/dist/parser/markdown.js +180 -0
  161. package/dist/parser/markdown.js.map +1 -0
  162. package/dist/parser/nodes.d.ts +5 -0
  163. package/dist/parser/nodes.js +75 -0
  164. package/dist/parser/nodes.js.map +1 -0
  165. package/dist/parser/python.d.ts +2 -0
  166. package/dist/parser/python.js +307 -0
  167. package/dist/parser/python.js.map +1 -0
  168. package/dist/parser/references.d.ts +3 -0
  169. package/dist/parser/references.js +204 -0
  170. package/dist/parser/references.js.map +1 -0
  171. package/dist/parser/risks.d.ts +4 -0
  172. package/dist/parser/risks.js +62 -0
  173. package/dist/parser/risks.js.map +1 -0
  174. package/dist/parser/routes.d.ts +5 -0
  175. package/dist/parser/routes.js +97 -0
  176. package/dist/parser/routes.js.map +1 -0
  177. package/dist/parser/shallow.d.ts +3 -0
  178. package/dist/parser/shallow.js +545 -0
  179. package/dist/parser/shallow.js.map +1 -0
  180. package/dist/parser/source.d.ts +4 -0
  181. package/dist/parser/source.js +127 -0
  182. package/dist/parser/source.js.map +1 -0
  183. package/dist/parser.d.ts +2 -0
  184. package/dist/parser.js +2 -0
  185. package/dist/parser.js.map +1 -0
  186. package/dist/placeholder-signals.d.ts +15 -0
  187. package/dist/placeholder-signals.js +511 -0
  188. package/dist/placeholder-signals.js.map +1 -0
  189. package/dist/post-edit-outcomes.d.ts +167 -0
  190. package/dist/post-edit-outcomes.js +484 -0
  191. package/dist/post-edit-outcomes.js.map +1 -0
  192. package/dist/queries.d.ts +12 -0
  193. package/dist/queries.js +13 -0
  194. package/dist/queries.js.map +1 -0
  195. package/dist/query/change-plan.d.ts +48 -0
  196. package/dist/query/change-plan.js +858 -0
  197. package/dist/query/change-plan.js.map +1 -0
  198. package/dist/query/compact-data.d.ts +25 -0
  199. package/dist/query/compact-data.js +74 -0
  200. package/dist/query/compact-data.js.map +1 -0
  201. package/dist/query/context.d.ts +5 -0
  202. package/dist/query/context.js +1162 -0
  203. package/dist/query/context.js.map +1 -0
  204. package/dist/query/diff.d.ts +5 -0
  205. package/dist/query/diff.js +111 -0
  206. package/dist/query/diff.js.map +1 -0
  207. package/dist/query/edge-evidence.d.ts +3 -0
  208. package/dist/query/edge-evidence.js +36 -0
  209. package/dist/query/edge-evidence.js.map +1 -0
  210. package/dist/query/formatting.d.ts +14 -0
  211. package/dist/query/formatting.js +67 -0
  212. package/dist/query/formatting.js.map +1 -0
  213. package/dist/query/graph-traversal.d.ts +22 -0
  214. package/dist/query/graph-traversal.js +218 -0
  215. package/dist/query/graph-traversal.js.map +1 -0
  216. package/dist/query/graph.d.ts +14 -0
  217. package/dist/query/graph.js +102 -0
  218. package/dist/query/graph.js.map +1 -0
  219. package/dist/query/impact.d.ts +28 -0
  220. package/dist/query/impact.js +568 -0
  221. package/dist/query/impact.js.map +1 -0
  222. package/dist/query/inspection.d.ts +9 -0
  223. package/dist/query/inspection.js +290 -0
  224. package/dist/query/inspection.js.map +1 -0
  225. package/dist/query/next-tools.d.ts +3 -0
  226. package/dist/query/next-tools.js +25 -0
  227. package/dist/query/next-tools.js.map +1 -0
  228. package/dist/query/placeholders.d.ts +24 -0
  229. package/dist/query/placeholders.js +121 -0
  230. package/dist/query/placeholders.js.map +1 -0
  231. package/dist/query/post-edit/decision.d.ts +49 -0
  232. package/dist/query/post-edit/decision.js +130 -0
  233. package/dist/query/post-edit/decision.js.map +1 -0
  234. package/dist/query/post-edit/dirty-scope.d.ts +16 -0
  235. package/dist/query/post-edit/dirty-scope.js +21 -0
  236. package/dist/query/post-edit/dirty-scope.js.map +1 -0
  237. package/dist/query/post-edit/next-actions.d.ts +22 -0
  238. package/dist/query/post-edit/next-actions.js +44 -0
  239. package/dist/query/post-edit/next-actions.js.map +1 -0
  240. package/dist/query/post-edit/snapshot-contract.d.ts +8 -0
  241. package/dist/query/post-edit/snapshot-contract.js +111 -0
  242. package/dist/query/post-edit/snapshot-contract.js.map +1 -0
  243. package/dist/query/post-edit.d.ts +5 -0
  244. package/dist/query/post-edit.js +1108 -0
  245. package/dist/query/post-edit.js.map +1 -0
  246. package/dist/query/quality.d.ts +43 -0
  247. package/dist/query/quality.js +134 -0
  248. package/dist/query/quality.js.map +1 -0
  249. package/dist/query/raw-search.d.ts +23 -0
  250. package/dist/query/raw-search.js +147 -0
  251. package/dist/query/raw-search.js.map +1 -0
  252. package/dist/query/runtime.d.ts +11 -0
  253. package/dist/query/runtime.js +79 -0
  254. package/dist/query/runtime.js.map +1 -0
  255. package/dist/query/search.d.ts +25 -0
  256. package/dist/query/search.js +429 -0
  257. package/dist/query/search.js.map +1 -0
  258. package/dist/query/session-memory.d.ts +3 -0
  259. package/dist/query/session-memory.js +108 -0
  260. package/dist/query/session-memory.js.map +1 -0
  261. package/dist/query/session.d.ts +41 -0
  262. package/dist/query/session.js +90 -0
  263. package/dist/query/session.js.map +1 -0
  264. package/dist/query/targets.d.ts +25 -0
  265. package/dist/query/targets.js +97 -0
  266. package/dist/query/targets.js.map +1 -0
  267. package/dist/query/test-commands.d.ts +10 -0
  268. package/dist/query/test-commands.js +110 -0
  269. package/dist/query/test-commands.js.map +1 -0
  270. package/dist/query/test-plan.d.ts +6 -0
  271. package/dist/query/test-plan.js +104 -0
  272. package/dist/query/test-plan.js.map +1 -0
  273. package/dist/query/tests.d.ts +48 -0
  274. package/dist/query/tests.js +444 -0
  275. package/dist/query/tests.js.map +1 -0
  276. package/dist/query/verification/shell.d.ts +20 -0
  277. package/dist/query/verification/shell.js +164 -0
  278. package/dist/query/verification/shell.js.map +1 -0
  279. package/dist/query/verification.d.ts +47 -0
  280. package/dist/query/verification.js +1123 -0
  281. package/dist/query/verification.js.map +1 -0
  282. package/dist/query/workflow.d.ts +17 -0
  283. package/dist/query/workflow.js +252 -0
  284. package/dist/query/workflow.js.map +1 -0
  285. package/dist/query/workspace-guidance.d.ts +26 -0
  286. package/dist/query/workspace-guidance.js +214 -0
  287. package/dist/query/workspace-guidance.js.map +1 -0
  288. package/dist/query/worktree-state.d.ts +22 -0
  289. package/dist/query/worktree-state.js +32 -0
  290. package/dist/query/worktree-state.js.map +1 -0
  291. package/dist/query/worktree.d.ts +16 -0
  292. package/dist/query/worktree.js +194 -0
  293. package/dist/query/worktree.js.map +1 -0
  294. package/dist/query-data.d.ts +4 -0
  295. package/dist/query-data.js +112 -0
  296. package/dist/query-data.js.map +1 -0
  297. package/dist/repo-files.d.ts +24 -0
  298. package/dist/repo-files.js +105 -0
  299. package/dist/repo-files.js.map +1 -0
  300. package/dist/resolver.d.ts +9 -0
  301. package/dist/resolver.js +555 -0
  302. package/dist/resolver.js.map +1 -0
  303. package/dist/retrieval.d.ts +46 -0
  304. package/dist/retrieval.js +783 -0
  305. package/dist/retrieval.js.map +1 -0
  306. package/dist/risk-ingest.d.ts +16 -0
  307. package/dist/risk-ingest.js +458 -0
  308. package/dist/risk-ingest.js.map +1 -0
  309. package/dist/rules.d.ts +10 -0
  310. package/dist/rules.js +107 -0
  311. package/dist/rules.js.map +1 -0
  312. package/dist/semantic/python.d.ts +9 -0
  313. package/dist/semantic/python.js +817 -0
  314. package/dist/semantic/python.js.map +1 -0
  315. package/dist/semantic/typescript.d.ts +10 -0
  316. package/dist/semantic/typescript.js +714 -0
  317. package/dist/semantic/typescript.js.map +1 -0
  318. package/dist/semantic-retrieval.d.ts +53 -0
  319. package/dist/semantic-retrieval.js +673 -0
  320. package/dist/semantic-retrieval.js.map +1 -0
  321. package/dist/session-memory/derivation.d.ts +6 -0
  322. package/dist/session-memory/derivation.js +400 -0
  323. package/dist/session-memory/derivation.js.map +1 -0
  324. package/dist/session-memory/event-log.d.ts +23 -0
  325. package/dist/session-memory/event-log.js +126 -0
  326. package/dist/session-memory/event-log.js.map +1 -0
  327. package/dist/session-memory/formatting.d.ts +7 -0
  328. package/dist/session-memory/formatting.js +86 -0
  329. package/dist/session-memory/formatting.js.map +1 -0
  330. package/dist/session-memory/model.d.ts +94 -0
  331. package/dist/session-memory/model.js +17 -0
  332. package/dist/session-memory/model.js.map +1 -0
  333. package/dist/session-memory/runtime.d.ts +24 -0
  334. package/dist/session-memory/runtime.js +289 -0
  335. package/dist/session-memory/runtime.js.map +1 -0
  336. package/dist/session-memory/store.d.ts +27 -0
  337. package/dist/session-memory/store.js +447 -0
  338. package/dist/session-memory/store.js.map +1 -0
  339. package/dist/session-memory.d.ts +1 -0
  340. package/dist/session-memory.js +2 -0
  341. package/dist/session-memory.js.map +1 -0
  342. package/dist/static-analysis.d.ts +36 -0
  343. package/dist/static-analysis.js +505 -0
  344. package/dist/static-analysis.js.map +1 -0
  345. package/dist/symbol-report-ingest.d.ts +8 -0
  346. package/dist/symbol-report-ingest.js +504 -0
  347. package/dist/symbol-report-ingest.js.map +1 -0
  348. package/dist/task-snapshots.d.ts +41 -0
  349. package/dist/task-snapshots.js +430 -0
  350. package/dist/task-snapshots.js.map +1 -0
  351. package/dist/types.d.ts +848 -0
  352. package/dist/types.js +12 -0
  353. package/dist/types.js.map +1 -0
  354. package/dist/util.d.ts +11 -0
  355. package/dist/util.js +63 -0
  356. package/dist/util.js.map +1 -0
  357. package/dist/version.d.ts +1 -0
  358. package/dist/version.js +5 -0
  359. package/dist/version.js.map +1 -0
  360. package/package.json +81 -0
  361. package/plugins/codexa/.codex-plugin/plugin.json +38 -0
  362. package/plugins/codexa/.mcp.json +20 -0
  363. package/plugins/codexa/scripts/codexa-mcp.js +100 -0
  364. package/plugins/codexa/skills/codexa/SKILL.md +48 -0
@@ -0,0 +1,817 @@
1
+ import path from "node:path";
2
+ import { stableId } from "../util.js";
3
+ export function applyPythonSemanticAssist(index, options) {
4
+ const sourceByPath = new Map(options.files.filter((file) => file.path.endsWith(".py")).map((file) => [file.path, file.sourceText]));
5
+ if (sourceByPath.size === 0) {
6
+ return index;
7
+ }
8
+ const context = {
9
+ snapshotId: index.snapshot.snapshotId,
10
+ indexedAt: index.snapshot.indexedAt,
11
+ sourceByPath,
12
+ symbols: index.symbols.map((symbol) => ({ ...symbol })),
13
+ usageSites: index.usageSites.map((usage) => ({ ...usage })),
14
+ imports: index.imports.map((imp) => ({ ...imp })),
15
+ testEdges: index.testEdges.map((edge) => ({ ...edge })),
16
+ risks: index.risks.map((risk) => ({ ...risk })),
17
+ symbolsById: new Map(),
18
+ symbolsByPath: new Map(),
19
+ symbolsByPathAndName: new Map(),
20
+ fixtureByScopeAndName: new Map()
21
+ };
22
+ rebuildSymbolMaps(context);
23
+ resolvePythonImports(context);
24
+ addAssignmentReExportBindings(context);
25
+ addStarImportBindingsFromAll(context);
26
+ addPackageExportEvidence(context);
27
+ addPytestFixtureEvidence(context);
28
+ addPythonFrameworkEvidence(context);
29
+ addPythonModelAttributeEvidence(context);
30
+ return {
31
+ ...index,
32
+ symbols: sortSymbols(dedupeSymbols(context.symbols)),
33
+ usageSites: sortUsages(dedupeUsages(context.usageSites)),
34
+ imports: sortImports(dedupeImports(context.imports)),
35
+ testEdges: sortTestEdges(dedupeTestEdges(context.testEdges)),
36
+ risks: sortRisks(dedupeRisks(context.risks))
37
+ };
38
+ }
39
+ function resolvePythonImports(context) {
40
+ const files = new Set(context.sourceByPath.keys());
41
+ context.imports = context.imports.map((imp) => {
42
+ if (!imp.path.endsWith(".py") || imp.resolvedPath) {
43
+ return imp;
44
+ }
45
+ const resolved = resolvePythonImportPathWithRoots(imp, files);
46
+ return resolved ? { ...imp, resolvedPath: resolved.path, confidence: resolved.confidence } : imp;
47
+ });
48
+ }
49
+ function addStarImportBindingsFromAll(context) {
50
+ const additions = [];
51
+ for (const imp of context.imports) {
52
+ if (!imp.path.endsWith(".py") || imp.importedName !== "*" || !imp.resolvedPath?.endsWith("__init__.py")) {
53
+ continue;
54
+ }
55
+ const sourceText = context.sourceByPath.get(imp.resolvedPath);
56
+ if (!sourceText) {
57
+ continue;
58
+ }
59
+ for (const name of pythonAllNames(sourceText)) {
60
+ additions.push({
61
+ ...imp,
62
+ id: stableId("python-star-import", imp.path, imp.specifier, name, imp.range?.startByte ?? 0),
63
+ importedName: name,
64
+ localName: name,
65
+ confidence: "derived"
66
+ });
67
+ }
68
+ }
69
+ context.imports.push(...additions);
70
+ }
71
+ function addAssignmentReExportBindings(context) {
72
+ const additions = [];
73
+ for (const [filePath, sourceText] of context.sourceByPath) {
74
+ if (!filePath.endsWith("__init__.py")) {
75
+ continue;
76
+ }
77
+ for (const match of sourceText.matchAll(/^([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\b/gm)) {
78
+ const exportedName = match[1];
79
+ const namespaceName = match[2];
80
+ const memberName = match[3];
81
+ if (!exportedName || !namespaceName || !memberName) {
82
+ continue;
83
+ }
84
+ const namespaceImport = context.imports.find((imp) => imp.path === filePath && imp.resolvedPath && (imp.localName === namespaceName || imp.importedName === namespaceName || imp.specifier.endsWith(`.${namespaceName}`)));
85
+ if (!namespaceImport?.resolvedPath) {
86
+ continue;
87
+ }
88
+ additions.push({
89
+ id: stableId("python-assignment-reexport", filePath, exportedName, namespaceName, memberName, match.index ?? 0),
90
+ type: "ImportEdge",
91
+ path: filePath,
92
+ source: "tree-sitter",
93
+ confidence: "derived",
94
+ snapshotId: context.snapshotId,
95
+ indexedAt: context.indexedAt,
96
+ range: rangeFromOffsets(sourceText, match.index ?? 0, (match.index ?? 0) + match[0].length),
97
+ specifier: namespaceImport.specifier,
98
+ importedName: memberName,
99
+ localName: exportedName,
100
+ reExport: true,
101
+ resolvedPath: namespaceImport.resolvedPath
102
+ });
103
+ }
104
+ }
105
+ context.imports.push(...additions);
106
+ }
107
+ function addPackageExportEvidence(context) {
108
+ for (const [filePath, sourceText] of context.sourceByPath) {
109
+ if (!filePath.endsWith("__init__.py")) {
110
+ continue;
111
+ }
112
+ const exportedNames = pythonAllNames(sourceText);
113
+ if (exportedNames.length === 0) {
114
+ continue;
115
+ }
116
+ for (const name of exportedNames) {
117
+ const localSymbols = context.symbolsByPathAndName.get(`${filePath}\0${name}`) ?? [];
118
+ const reExportTarget = reExportTargetForName(context, filePath, name);
119
+ const target = reExportTarget ?? singleSymbol(localSymbols);
120
+ if (!target) {
121
+ context.risks.push(riskFact(context, filePath, "python-package-export-unresolved", 0.8, `__all__ lists ${name}, but Codexa could not resolve a local or re-exported symbol`, sourceText.indexOf(name)));
122
+ continue;
123
+ }
124
+ context.usageSites.push(usageFact(context, {
125
+ path: filePath,
126
+ name,
127
+ kind: "reference",
128
+ text: `__all__ export ${name}`,
129
+ confidence: "derived",
130
+ sourceText,
131
+ startByte: sourceText.indexOf(name),
132
+ targetSymbolId: target.id
133
+ }));
134
+ markExported(context, target.id);
135
+ }
136
+ }
137
+ }
138
+ function addPytestFixtureEvidence(context) {
139
+ for (const symbol of context.symbols) {
140
+ if (symbol.language !== "python" || symbol.kind !== "fixture") {
141
+ continue;
142
+ }
143
+ addFixtureCandidate(context, symbol);
144
+ }
145
+ const nextUsages = [];
146
+ for (const usage of context.usageSites) {
147
+ if (usage.kind !== "test_reference" || usage.targetSymbolId || !usage.path.endsWith(".py")) {
148
+ nextUsages.push(usage);
149
+ continue;
150
+ }
151
+ const fixture = visibleFixtureForUsage(context, usage);
152
+ if (!fixture) {
153
+ nextUsages.push(usage);
154
+ continue;
155
+ }
156
+ nextUsages.push({
157
+ ...usage,
158
+ targetSymbolId: fixture.symbol.id,
159
+ confidence: betterConfidence(usage.confidence, fixture.confidence)
160
+ });
161
+ if (usage.path !== fixture.symbol.path) {
162
+ context.testEdges.push(testEdgeFact(context, usage.path, fixture.symbol.path, `pytest fixture ${usage.name}${fixture.reason ? ` (${fixture.reason})` : ""}`, fixture.confidence, usage.range));
163
+ }
164
+ }
165
+ context.usageSites = nextUsages;
166
+ for (const fixture of context.symbols.filter((symbol) => symbol.language === "python" && symbol.kind === "fixture" && isAutouseFixture(symbol))) {
167
+ for (const testPath of visibleTestFilesForAutouseFixture(context, fixture)) {
168
+ if (testPath === fixture.path) {
169
+ continue;
170
+ }
171
+ context.testEdges.push(testEdgeFact(context, testPath, fixture.path, `pytest autouse fixture ${fixture.name}`, "heuristic", fixture.range));
172
+ }
173
+ }
174
+ }
175
+ function addPythonFrameworkEvidence(context) {
176
+ const celeryTasks = celeryTasksInProject(context);
177
+ const celeryTasksByName = uniqueCeleryTasksByName(celeryTasks);
178
+ for (const [filePath, sourceText] of context.sourceByPath) {
179
+ const dependsNames = fastApiDependsNames(context, filePath, sourceText);
180
+ for (const dependsName of dependsNames) {
181
+ const pattern = new RegExp(`\\b${escapeRegExp(dependsName)}\\s*\\(\\s*([A-Za-z_][\\w.]*)`, "g");
182
+ for (const match of sourceText.matchAll(pattern)) {
183
+ const name = match[1];
184
+ if (!name || ["None", "True", "False"].includes(name)) {
185
+ continue;
186
+ }
187
+ const target = resolveNameInFileOrUnique(context, filePath, name.split(".").at(-1) ?? name);
188
+ context.usageSites.push(usageFact(context, {
189
+ path: filePath,
190
+ name,
191
+ kind: "reference",
192
+ text: `FastAPI Depends(${name})`,
193
+ confidence: "heuristic",
194
+ sourceText,
195
+ startByte: match.index ?? 0,
196
+ targetSymbolId: target?.id
197
+ }));
198
+ context.risks.push(riskFact(context, filePath, "fastapi-dependency", 1.4, `FastAPI dependency ${name} is runtime-injected`, match.index ?? 0));
199
+ }
200
+ }
201
+ for (const match of sourceText.matchAll(/@\s*([A-Za-z_][\w.]*(?:\.task|shared_task|task))\s*(?:\(|$)/g)) {
202
+ const decorator = match[1] ?? "task";
203
+ context.risks.push(riskFact(context, filePath, "celery-task", 1.8, `Celery task decorator ${decorator}`, match.index ?? 0));
204
+ }
205
+ for (const match of sourceText.matchAll(/\.send_task\s*\(\s*["']([^"']+)["']/g)) {
206
+ const taskName = match[1];
207
+ if (!taskName) {
208
+ continue;
209
+ }
210
+ const task = celeryTasksByName.get(taskName);
211
+ context.usageSites.push(usageFact(context, {
212
+ path: filePath,
213
+ name: taskName,
214
+ kind: "reference",
215
+ text: `Celery send_task(${taskName})`,
216
+ confidence: "heuristic",
217
+ sourceText,
218
+ startByte: match.index ?? 0,
219
+ targetSymbolId: task?.symbol?.id
220
+ }));
221
+ }
222
+ for (const task of celeryTasks) {
223
+ for (const callName of celeryTaskCallNamesForFile(context, filePath, task)) {
224
+ for (const match of sourceText.matchAll(new RegExp(`\\b${escapeRegExp(callName)}\\.(?:delay|apply_async)\\s*\\(`, "g"))) {
225
+ context.usageSites.push(usageFact(context, {
226
+ path: filePath,
227
+ name: callName,
228
+ kind: "call",
229
+ text: `Celery task call ${callName}`,
230
+ confidence: filePath === task.definingPath ? "derived" : "heuristic",
231
+ sourceText,
232
+ startByte: match.index ?? 0,
233
+ targetSymbolId: task.symbol?.id
234
+ }));
235
+ }
236
+ }
237
+ }
238
+ for (const classSymbol of pythonClassesInFile(context, filePath)) {
239
+ if (isPydanticModelClass(context, classSymbol)) {
240
+ context.risks.push(riskFact(context, filePath, "pydantic-model", 1.8, `${classSymbol.qualifiedName} inherits from Pydantic BaseModel`, classSymbol.range?.startByte ?? 0));
241
+ }
242
+ if (isSqlAlchemyModelClass(context, classSymbol)) {
243
+ context.risks.push(riskFact(context, filePath, "sqlalchemy-model", 1.8, `${classSymbol.qualifiedName} looks like a SQLAlchemy/SQLModel model`, classSymbol.range?.startByte ?? 0));
244
+ }
245
+ }
246
+ }
247
+ }
248
+ function fastApiDependsNames(context, filePath, _sourceText) {
249
+ const names = new Set();
250
+ for (const imp of context.imports) {
251
+ if (imp.path !== filePath) {
252
+ continue;
253
+ }
254
+ if (imp.specifier === "fastapi" && imp.importedName === "Depends") {
255
+ names.add(imp.localName ?? "Depends");
256
+ }
257
+ if (imp.specifier === "fastapi" && imp.importedName === "*") {
258
+ names.add(`${imp.localName ?? "fastapi"}.Depends`);
259
+ }
260
+ }
261
+ return [...names].sort((a, b) => b.length - a.length || a.localeCompare(b));
262
+ }
263
+ function celeryTasksInProject(context) {
264
+ const tasks = [];
265
+ for (const [filePath, sourceText] of context.sourceByPath) {
266
+ tasks.push(...celeryTasksInFile(context, filePath, sourceText));
267
+ }
268
+ return tasks;
269
+ }
270
+ function uniqueCeleryTasksByName(tasks) {
271
+ const byName = new Map();
272
+ for (const task of tasks) {
273
+ const existing = byName.get(task.taskName) ?? [];
274
+ existing.push(task);
275
+ byName.set(task.taskName, existing);
276
+ }
277
+ const result = new Map();
278
+ for (const [name, candidates] of byName) {
279
+ if (candidates.length === 1) {
280
+ result.set(name, candidates[0]);
281
+ }
282
+ }
283
+ return result;
284
+ }
285
+ function celeryTaskCallNamesForFile(context, filePath, task) {
286
+ const names = new Set();
287
+ if (filePath === task.definingPath) {
288
+ names.add(task.functionName);
289
+ }
290
+ for (const imp of context.imports) {
291
+ if (imp.path !== filePath || imp.resolvedPath !== task.definingPath) {
292
+ continue;
293
+ }
294
+ if (imp.importedName === task.functionName) {
295
+ names.add(imp.localName ?? task.functionName);
296
+ }
297
+ if (imp.importedName === "*") {
298
+ if (imp.localName === "*") {
299
+ names.add(task.functionName);
300
+ }
301
+ else if (imp.specifier.endsWith(`.${path.posix.basename(task.definingPath, ".py")}`) || imp.specifier.replace(/\./g, "/") === task.definingPath.replace(/\.py$/, "")) {
302
+ const firstSpecifierPart = imp.specifier.split(".")[0];
303
+ const namespace = imp.localName && imp.localName !== firstSpecifierPart ? imp.localName : imp.specifier;
304
+ names.add(`${namespace}.${task.functionName}`);
305
+ }
306
+ else {
307
+ names.add(task.functionName);
308
+ }
309
+ }
310
+ }
311
+ return [...names].filter(Boolean).sort((a, b) => b.length - a.length || a.localeCompare(b));
312
+ }
313
+ function celeryTasksInFile(context, filePath, sourceText) {
314
+ const result = [];
315
+ const pattern = /@\s*(?:[A-Za-z_][\w.]*\.task|shared_task|task)\s*(?:\(([^)]*)\))?\s*(?:\r?\n)+\s*(?:async\s+def|def)\s+([A-Za-z_]\w*)/g;
316
+ for (const match of sourceText.matchAll(pattern)) {
317
+ const args = match[1] ?? "";
318
+ const functionName = match[2];
319
+ if (!functionName) {
320
+ continue;
321
+ }
322
+ const explicitName = /\bname\s*=\s*["']([^"']+)["']/.exec(args)?.[1];
323
+ const symbol = resolveNameInFileOrUnique(context, filePath, functionName);
324
+ result.push({ taskName: explicitName ?? functionName, functionName, definingPath: filePath, symbol });
325
+ }
326
+ return result;
327
+ }
328
+ function classHeaderText(context, classSymbol) {
329
+ const text = classText(context, classSymbol);
330
+ return text.split(/\r?\n/, 1)[0] ?? "";
331
+ }
332
+ function isPydanticModelClass(context, classSymbol) {
333
+ const sourceText = context.sourceByPath.get(classSymbol.path);
334
+ if (!sourceText || !classSymbol.range) {
335
+ return false;
336
+ }
337
+ const header = classHeaderText(context, classSymbol);
338
+ const pydanticBaseNames = importedFrameworkNames(context, classSymbol.path, "pydantic", "BaseModel", "BaseModel");
339
+ return pydanticBaseNames.some((name) => hasWord(header, name)) || /\bpydantic\.BaseModel\b/.test(header);
340
+ }
341
+ function isSqlAlchemyModelClass(context, classSymbol) {
342
+ if (!classSymbol.range || !hasSqlAlchemyImport(context, classSymbol.path)) {
343
+ return false;
344
+ }
345
+ const text = classText(context, classSymbol);
346
+ const header = classHeaderText(context, classSymbol);
347
+ if (!/\b(mapped_column|Column|relationship|Field|__tablename__)\b/.test(text)) {
348
+ return false;
349
+ }
350
+ if (/\b(DeclarativeBase|SQLModel)\b/.test(header)) {
351
+ return true;
352
+ }
353
+ if (/\bBase\b/.test(header)) {
354
+ const sourceText = context.sourceByPath.get(classSymbol.path) ?? "";
355
+ return /\bclass\s+Base\s*\(\s*(DeclarativeBase|SQLModel)\s*\)/.test(sourceText) || /\bBase\s*=\s*declarative_base\s*\(/.test(sourceText);
356
+ }
357
+ return false;
358
+ }
359
+ function pythonModelFieldConstructorPattern(context, classSymbol) {
360
+ if (isSqlAlchemyModelClass(context, classSymbol)) {
361
+ return /\b(mapped_column|Column|relationship|Field)\b/;
362
+ }
363
+ if (isPydanticModelClass(context, classSymbol)) {
364
+ return /\bField\b/;
365
+ }
366
+ return undefined;
367
+ }
368
+ function importedFrameworkNames(context, filePath, specifier, importedName, defaultName) {
369
+ const names = new Set();
370
+ for (const imp of context.imports) {
371
+ if (imp.path === filePath && imp.specifier === specifier && imp.importedName === importedName) {
372
+ names.add(imp.localName ?? defaultName);
373
+ }
374
+ }
375
+ return [...names].sort();
376
+ }
377
+ function hasSqlAlchemyImport(context, filePath) {
378
+ return context.imports.some((imp) => imp.path === filePath && /^(sqlalchemy|sqlmodel)(\.|$)/.test(imp.specifier));
379
+ }
380
+ function hasModelFrameworkImport(context, filePath) {
381
+ return hasSqlAlchemyImport(context, filePath) || context.imports.some((imp) => imp.path === filePath && /^(pydantic)(\.|$)/.test(imp.specifier));
382
+ }
383
+ function hasWord(text, name) {
384
+ return new RegExp(`\\b${escapeRegExp(name)}\\b`).test(text);
385
+ }
386
+ function escapeRegExp(value) {
387
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
388
+ }
389
+ function addPythonModelAttributeEvidence(context) {
390
+ for (const classSymbol of context.symbols.filter((symbol) => symbol.language === "python" && symbol.kind === "class" && symbol.range)) {
391
+ const sourceText = context.sourceByPath.get(classSymbol.path);
392
+ if (!sourceText || !classSymbol.range) {
393
+ continue;
394
+ }
395
+ if (!hasModelFrameworkImport(context, classSymbol.path)) {
396
+ continue;
397
+ }
398
+ const constructorPattern = pythonModelFieldConstructorPattern(context, classSymbol);
399
+ if (!constructorPattern) {
400
+ continue;
401
+ }
402
+ const text = classText(context, classSymbol);
403
+ if (!constructorPattern.test(text)) {
404
+ continue;
405
+ }
406
+ const classStart = classSymbol.range.startByte;
407
+ const classIndent = leadingWhitespace(sourceText, classStart).length;
408
+ const pattern = /^([ \t]+)([A-Za-z_]\w*)\s*(?::[^=\n]+)?=\s*(mapped_column|Column|relationship|Field)\b/gm;
409
+ for (const match of text.matchAll(pattern)) {
410
+ const indent = match[1] ?? "";
411
+ const name = match[2];
412
+ const constructor = match[3];
413
+ if (!name || !constructor || indent.length <= classIndent || !constructorPattern.test(constructor)) {
414
+ continue;
415
+ }
416
+ const startByte = classStart + (match.index ?? 0) + indent.length;
417
+ const qualifiedName = `${classSymbol.qualifiedName}.${name}`;
418
+ if (!hasSymbol(context, classSymbol.path, qualifiedName)) {
419
+ const symbol = {
420
+ id: stableId("python-model-attribute", classSymbol.path, qualifiedName, startByte),
421
+ type: "Symbol",
422
+ path: classSymbol.path,
423
+ source: "heuristic",
424
+ confidence: "heuristic",
425
+ snapshotId: context.snapshotId,
426
+ indexedAt: context.indexedAt,
427
+ range: rangeFromOffsets(sourceText, startByte, startByte + name.length),
428
+ name,
429
+ qualifiedName,
430
+ kind: "variable",
431
+ language: "python",
432
+ exported: false,
433
+ decorators: [],
434
+ parentSymbolId: classSymbol.id
435
+ };
436
+ context.symbols.push(symbol);
437
+ addSymbolToMaps(context, symbol);
438
+ }
439
+ context.risks.push(riskFact(context, classSymbol.path, "python-model-field", 1.2, `${qualifiedName} uses ${constructor}`, startByte));
440
+ }
441
+ }
442
+ }
443
+ function pythonAllNames(sourceText) {
444
+ const match = /__all__\s*=\s*(?:\(([\s\S]*?)\)|\[([\s\S]*?)\])/m.exec(sourceText);
445
+ const body = match?.[1] ?? match?.[2];
446
+ if (!body) {
447
+ return [];
448
+ }
449
+ return [...body.matchAll(/["']([A-Za-z_][\w]*)["']/g)].map((entry) => entry[1]).filter(Boolean).sort();
450
+ }
451
+ function reExportTargetForName(context, filePath, name) {
452
+ const files = new Set(context.sourceByPath.keys());
453
+ for (const imp of context.imports.filter((entry) => entry.path === filePath && (entry.localName === name || entry.importedName === name))) {
454
+ const resolvedPath = imp.resolvedPath ?? resolvePythonImportPath(imp, files);
455
+ if (!resolvedPath) {
456
+ continue;
457
+ }
458
+ const candidates = context.symbolsByPath.get(resolvedPath) ?? [];
459
+ const importedName = imp.importedName && imp.importedName !== "*" ? imp.importedName : name;
460
+ const exact = singleSymbol(candidates.filter((symbol) => symbol.name === importedName || symbol.qualifiedName === importedName || symbol.qualifiedName.endsWith(`.${importedName}`)));
461
+ if (exact) {
462
+ return exact;
463
+ }
464
+ }
465
+ return undefined;
466
+ }
467
+ function resolvePythonImportPath(imp, files) {
468
+ return resolvePythonImportPathWithRoots(imp, files)?.path;
469
+ }
470
+ function resolvePythonImportPathWithRoots(imp, files) {
471
+ const specifierPath = imp.specifier.startsWith(".") ? pythonRelativeImportCandidate(imp.path, imp.specifier) : imp.specifier.replace(/\./g, "/");
472
+ if (imp.importedName && imp.importedName !== "*") {
473
+ const importedAsModule = resolvePythonCandidate(path.posix.join(specifierPath, imp.importedName), files);
474
+ if (importedAsModule) {
475
+ return { path: importedAsModule, confidence: pythonImportConfidence(importedAsModule, files) };
476
+ }
477
+ }
478
+ const direct = resolvePythonCandidate(specifierPath, files);
479
+ if (direct) {
480
+ return { path: direct, confidence: pythonImportConfidence(direct, files) };
481
+ }
482
+ if (!imp.specifier.startsWith(".")) {
483
+ for (const root of pythonSourceRoots(files)) {
484
+ const packageName = specifierPath.split("/")[0];
485
+ if (!packageName || !files.has(path.posix.join(root, packageName, "__init__.py"))) {
486
+ continue;
487
+ }
488
+ const rooted = path.posix.join(root, specifierPath);
489
+ if (imp.importedName && imp.importedName !== "*") {
490
+ const importedAsRootedModule = resolvePythonCandidate(path.posix.join(rooted, imp.importedName), files);
491
+ if (importedAsRootedModule) {
492
+ return { path: importedAsRootedModule, confidence: "derived" };
493
+ }
494
+ }
495
+ const rootedResolved = resolvePythonCandidate(rooted, files);
496
+ if (rootedResolved) {
497
+ return { path: rootedResolved, confidence: "derived" };
498
+ }
499
+ }
500
+ }
501
+ return undefined;
502
+ }
503
+ function pythonRelativeImportCandidate(importerPath, specifier) {
504
+ const dotMatch = /^(\.+)(.*)$/.exec(specifier);
505
+ if (!dotMatch) {
506
+ return path.posix.join(path.posix.dirname(importerPath), specifier);
507
+ }
508
+ const [, dots, rest] = dotMatch;
509
+ let anchor = path.posix.dirname(importerPath);
510
+ for (let index = 1; index < dots.length; index += 1) {
511
+ anchor = path.posix.dirname(anchor);
512
+ }
513
+ return path.posix.normalize(path.posix.join(anchor, rest.replace(/\./g, "/")));
514
+ }
515
+ function resolvePythonCandidate(candidate, files) {
516
+ const variants = [candidate, `${candidate}.py`, `${candidate}/__init__.py`];
517
+ return variants.find((variant) => files.has(variant));
518
+ }
519
+ function pythonImportConfidence(resolvedPath, files) {
520
+ return pythonPackageDirsHaveInit(resolvedPath, files) ? "authoritative" : "derived";
521
+ }
522
+ function pythonPackageDirsHaveInit(resolvedPath, files) {
523
+ let dir = path.posix.dirname(resolvedPath);
524
+ if (!dir || dir === ".") {
525
+ return true;
526
+ }
527
+ const parts = dir.split("/");
528
+ let current = "";
529
+ for (const part of parts) {
530
+ current = current ? path.posix.join(current, part) : part;
531
+ if (!files.has(path.posix.join(current, "__init__.py"))) {
532
+ return false;
533
+ }
534
+ }
535
+ return true;
536
+ }
537
+ function pythonSourceRoots(files) {
538
+ const roots = ["src"];
539
+ return roots.filter((root) => [...files].some((file) => file.startsWith(`${root}/`)));
540
+ }
541
+ function addFixtureCandidate(context, symbol) {
542
+ const names = [];
543
+ const parent = symbol.parentSymbolId ? context.symbolsById.get(symbol.parentSymbolId) : undefined;
544
+ if (parent && isPythonClassScope(parent)) {
545
+ names.push(fixtureScopeKey(`${symbol.path}:${parent.id}`, "class"));
546
+ }
547
+ else {
548
+ names.push(fixtureScopeKey(symbol.path, "file"));
549
+ }
550
+ const conftestDir = path.posix.basename(symbol.path) === "conftest.py" ? normalizeDir(path.posix.dirname(symbol.path)) : undefined;
551
+ if (conftestDir !== undefined && !(parent && isPythonClassScope(parent))) {
552
+ names.push(fixtureScopeKey(conftestDir, "conftest"));
553
+ }
554
+ for (const key of names) {
555
+ const scopedKey = `${key}\0${symbol.name}`;
556
+ const existing = context.fixtureByScopeAndName.get(scopedKey) ?? [];
557
+ existing.push(symbol);
558
+ context.fixtureByScopeAndName.set(scopedKey, existing);
559
+ }
560
+ }
561
+ function visibleFixtureForUsage(context, usage) {
562
+ const usedBy = usage.usedBySymbolId ? context.symbolsById.get(usage.usedBySymbolId) : undefined;
563
+ const usedByClassId = usedBy?.parentSymbolId;
564
+ const usedByClass = usedByClassId ? context.symbolsById.get(usedByClassId) : undefined;
565
+ if (usedByClass?.range && usage.range && rangeContains(usedByClass.range, usage.range)) {
566
+ const classFixture = singleSymbol(context.fixtureByScopeAndName.get(`${fixtureScopeKey(`${usage.path}:${usedByClass.id}`, "class")}\0${usage.name}`) ?? []);
567
+ if (classFixture) {
568
+ return { symbol: classFixture, confidence: "authoritative", reason: "same test class" };
569
+ }
570
+ }
571
+ const sameFile = singleSymbol(context.fixtureByScopeAndName.get(`${fixtureScopeKey(usage.path, "file")}\0${usage.name}`) ?? []);
572
+ if (sameFile) {
573
+ return { symbol: sameFile, confidence: "authoritative", reason: "same file" };
574
+ }
575
+ for (const dir of ancestorDirs(path.posix.dirname(usage.path))) {
576
+ const scoped = singleSymbol(context.fixtureByScopeAndName.get(`${fixtureScopeKey(dir, "conftest")}\0${usage.name}`) ?? []);
577
+ if (scoped) {
578
+ return { symbol: scoped, confidence: "authoritative", reason: `conftest ${scoped.path}` };
579
+ }
580
+ }
581
+ const importedFixture = uniqueImportedFixture(context, usage.path, usage.name);
582
+ if (importedFixture) {
583
+ return { symbol: importedFixture, confidence: "derived", reason: "imported fixture" };
584
+ }
585
+ const allFixtures = context.symbols.filter((symbol) => symbol.language === "python" && symbol.kind === "fixture" && !symbol.parentSymbolId && symbol.name === usage.name);
586
+ const unique = singleSymbol(allFixtures);
587
+ return unique ? { symbol: unique, confidence: "heuristic", reason: "unique fixture name" } : undefined;
588
+ }
589
+ function uniqueImportedFixture(context, filePath, name) {
590
+ const importedUsage = context.usageSites.find((usage) => usage.path === filePath && usage.kind === "import" && usage.name === name && usage.targetSymbolId);
591
+ const target = importedUsage?.targetSymbolId ? context.symbolsById.get(importedUsage.targetSymbolId) : undefined;
592
+ return target?.kind === "fixture" ? target : undefined;
593
+ }
594
+ function visibleTestFilesForAutouseFixture(context, fixture) {
595
+ const fixtureDir = normalizeDir(path.posix.dirname(fixture.path));
596
+ const conftest = path.posix.basename(fixture.path) === "conftest.py";
597
+ const result = new Set();
598
+ for (const symbol of context.symbols) {
599
+ if (symbol.language !== "python" || symbol.kind !== "test") {
600
+ continue;
601
+ }
602
+ if (conftest) {
603
+ if (symbol.path === fixture.path || (fixtureDir ? symbol.path.startsWith(`${fixtureDir}/`) : true)) {
604
+ result.add(symbol.path);
605
+ }
606
+ continue;
607
+ }
608
+ if (symbol.path === fixture.path) {
609
+ result.add(symbol.path);
610
+ }
611
+ }
612
+ return [...result].sort();
613
+ }
614
+ function isAutouseFixture(symbol) {
615
+ return symbol.decorators.some((decorator) => /\bfixture\s*\([^)]*autouse\s*=\s*True\b/.test(decorator));
616
+ }
617
+ function fixtureScopeKey(value, kind) {
618
+ return `${kind}:${value}`;
619
+ }
620
+ function ancestorDirs(startDir) {
621
+ const result = [];
622
+ let dir = normalizeDir(startDir);
623
+ while (true) {
624
+ result.push(dir);
625
+ if (!dir) {
626
+ break;
627
+ }
628
+ const next = path.posix.dirname(dir);
629
+ dir = next === "." ? "" : next;
630
+ }
631
+ return result;
632
+ }
633
+ function normalizeDir(dir) {
634
+ return dir === "." ? "" : dir;
635
+ }
636
+ function isPythonClassScope(symbol) {
637
+ return symbol.language === "python" && (symbol.kind === "class" || (symbol.kind === "test" && !symbol.parentSymbolId && /^[A-Z]/.test(symbol.name)));
638
+ }
639
+ function rangeContains(container, candidate) {
640
+ return candidate.startByte >= container.startByte && candidate.endByte <= container.endByte;
641
+ }
642
+ function pythonClassesInFile(context, filePath) {
643
+ return (context.symbolsByPath.get(filePath) ?? []).filter((symbol) => symbol.kind === "class" && symbol.language === "python");
644
+ }
645
+ function classText(context, classSymbol) {
646
+ const sourceText = context.sourceByPath.get(classSymbol.path);
647
+ if (!sourceText || !classSymbol.range) {
648
+ return "";
649
+ }
650
+ return sourceText.slice(classSymbol.range.startByte, classEndOffset(sourceText, classSymbol.range.startByte));
651
+ }
652
+ function classEndOffset(sourceText, classStart) {
653
+ const classIndent = leadingWhitespace(sourceText, classStart).length;
654
+ const nextClassOrFunction = new RegExp(`^ {0,${classIndent}}(?:class|def|async\\s+def)\\s+`, "gm");
655
+ nextClassOrFunction.lastIndex = classStart + 1;
656
+ const match = nextClassOrFunction.exec(sourceText);
657
+ return match?.index ?? sourceText.length;
658
+ }
659
+ function leadingWhitespace(sourceText, offset) {
660
+ const lineStart = sourceText.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
661
+ const line = sourceText.slice(lineStart, offset);
662
+ return /^[ \t]*/.exec(line)?.[0] ?? "";
663
+ }
664
+ function resolveNameInFileOrUnique(context, filePath, name) {
665
+ const sameFile = singleSymbol(context.symbolsByPathAndName.get(`${filePath}\0${name}`) ?? []);
666
+ if (sameFile) {
667
+ return sameFile;
668
+ }
669
+ return singleSymbol(context.symbols.filter((symbol) => symbol.language === "python" && (symbol.name === name || symbol.qualifiedName === name)));
670
+ }
671
+ function hasSymbol(context, filePath, qualifiedName) {
672
+ return (context.symbolsByPath.get(filePath) ?? []).some((symbol) => symbol.qualifiedName === qualifiedName);
673
+ }
674
+ function markExported(context, symbolId) {
675
+ context.symbols = context.symbols.map((symbol) => (symbol.id === symbolId ? { ...symbol, exported: true } : symbol));
676
+ rebuildSymbolMaps(context);
677
+ }
678
+ function addSymbolToMaps(context, symbol) {
679
+ context.symbolsById.set(symbol.id, symbol);
680
+ const byPath = context.symbolsByPath.get(symbol.path) ?? [];
681
+ byPath.push(symbol);
682
+ context.symbolsByPath.set(symbol.path, byPath);
683
+ for (const name of [symbol.name, symbol.qualifiedName]) {
684
+ const key = `${symbol.path}\0${name}`;
685
+ const existing = context.symbolsByPathAndName.get(key) ?? [];
686
+ existing.push(symbol);
687
+ context.symbolsByPathAndName.set(key, existing);
688
+ }
689
+ }
690
+ function rebuildSymbolMaps(context) {
691
+ context.symbolsById = new Map();
692
+ context.symbolsByPath = new Map();
693
+ context.symbolsByPathAndName = new Map();
694
+ for (const symbol of context.symbols) {
695
+ addSymbolToMaps(context, symbol);
696
+ }
697
+ }
698
+ function usageFact(context, input) {
699
+ const startByte = Math.max(0, input.startByte);
700
+ return {
701
+ id: stableId("python-semantic-usage", input.path, input.name, input.kind, startByte, input.targetSymbolId),
702
+ type: "UsageSite",
703
+ path: input.path,
704
+ source: input.confidence === "heuristic" ? "heuristic" : "tree-sitter",
705
+ confidence: input.confidence,
706
+ snapshotId: context.snapshotId,
707
+ indexedAt: context.indexedAt,
708
+ range: rangeFromOffsets(input.sourceText, startByte, startByte + input.name.length),
709
+ name: input.name,
710
+ kind: input.kind,
711
+ text: input.text.replace(/\s+/g, " ").slice(0, 240),
712
+ targetSymbolId: input.targetSymbolId
713
+ };
714
+ }
715
+ function testEdgeFact(context, testPath, targetPath, reason, confidence, range) {
716
+ return {
717
+ id: stableId("python-semantic-test-edge", testPath, targetPath, reason, range?.startByte ?? 0),
718
+ type: "TestEdge",
719
+ path: testPath,
720
+ targetPath,
721
+ reason,
722
+ source: confidence === "heuristic" ? "heuristic" : "tree-sitter",
723
+ confidence,
724
+ snapshotId: context.snapshotId,
725
+ indexedAt: context.indexedAt,
726
+ range
727
+ };
728
+ }
729
+ function riskFact(context, filePath, signal, score, reason, startByte = 0) {
730
+ const sourceText = context.sourceByPath.get(filePath) ?? "";
731
+ return {
732
+ id: stableId("python-semantic-risk", filePath, signal, reason, startByte),
733
+ type: "RiskSignal",
734
+ path: filePath,
735
+ source: "heuristic",
736
+ confidence: "heuristic",
737
+ snapshotId: context.snapshotId,
738
+ indexedAt: context.indexedAt,
739
+ range: sourceText ? rangeFromOffsets(sourceText, Math.max(0, startByte), Math.max(0, startByte) + Math.min(reason.length, 80)) : undefined,
740
+ signal,
741
+ score,
742
+ reason
743
+ };
744
+ }
745
+ function rangeFromOffsets(sourceText, startByte, endByte) {
746
+ const safeStart = Math.max(0, Math.min(startByte, sourceText.length));
747
+ const safeEnd = Math.max(safeStart, Math.min(endByte, sourceText.length));
748
+ return {
749
+ startLine: lineForOffset(sourceText, safeStart),
750
+ endLine: lineForOffset(sourceText, safeEnd),
751
+ startByte: safeStart,
752
+ endByte: safeEnd
753
+ };
754
+ }
755
+ function lineForOffset(sourceText, offset) {
756
+ let line = 1;
757
+ for (let index = 0; index < offset && index < sourceText.length; index += 1) {
758
+ if (sourceText.charCodeAt(index) === 10) {
759
+ line += 1;
760
+ }
761
+ }
762
+ return line;
763
+ }
764
+ function singleSymbol(symbols) {
765
+ return symbols.length === 1 ? symbols[0] : undefined;
766
+ }
767
+ function betterConfidence(current, candidate) {
768
+ const rank = { authoritative: 0, derived: 1, heuristic: 2 };
769
+ return rank[candidate] < rank[current] ? candidate : current;
770
+ }
771
+ function dedupeSymbols(symbols) {
772
+ return dedupeBy(symbols, (symbol) => `${symbol.path}\0${symbol.qualifiedName}\0${symbol.kind}\0${symbol.range?.startByte ?? symbol.id}`);
773
+ }
774
+ function dedupeUsages(usages) {
775
+ return dedupeBy(usages, (usage) => `${usage.path}\0${usage.name}\0${usage.kind}\0${usage.targetSymbolId ?? ""}\0${usage.range?.startByte ?? 0}\0${usage.text}`);
776
+ }
777
+ function dedupeImports(imports) {
778
+ return dedupeBy(imports, (imp) => `${imp.path}\0${imp.specifier}\0${imp.importedName ?? ""}\0${imp.localName ?? ""}\0${imp.resolvedPath ?? ""}\0${imp.range?.startByte ?? 0}`);
779
+ }
780
+ function dedupeTestEdges(edges) {
781
+ return dedupeBy(edges, (edge) => `${edge.path}\0${edge.targetPath ?? ""}\0${edge.reason}\0${edge.range?.startByte ?? 0}`);
782
+ }
783
+ function dedupeRisks(risks) {
784
+ return dedupeBy(risks, (risk) => `${risk.path}\0${risk.signal}\0${risk.reason}\0${risk.range?.startByte ?? 0}`);
785
+ }
786
+ function dedupeBy(items, keyFor) {
787
+ const seen = new Set();
788
+ const result = [];
789
+ for (const item of items) {
790
+ const key = keyFor(item);
791
+ if (seen.has(key)) {
792
+ continue;
793
+ }
794
+ seen.add(key);
795
+ result.push(item);
796
+ }
797
+ return result;
798
+ }
799
+ function sortSymbols(symbols) {
800
+ return symbols.sort((a, b) => a.path.localeCompare(b.path) || a.qualifiedName.localeCompare(b.qualifiedName) || a.kind.localeCompare(b.kind));
801
+ }
802
+ function sortUsages(usages) {
803
+ return usages.sort((a, b) => a.path.localeCompare(b.path) || a.name.localeCompare(b.name) || a.kind.localeCompare(b.kind) || (a.range?.startByte ?? 0) - (b.range?.startByte ?? 0));
804
+ }
805
+ function sortImports(imports) {
806
+ return imports.sort((a, b) => a.path.localeCompare(b.path) ||
807
+ a.specifier.localeCompare(b.specifier) ||
808
+ (a.importedName ?? "").localeCompare(b.importedName ?? "") ||
809
+ (a.localName ?? "").localeCompare(b.localName ?? ""));
810
+ }
811
+ function sortTestEdges(edges) {
812
+ return edges.sort((a, b) => a.path.localeCompare(b.path) || (a.targetPath ?? "").localeCompare(b.targetPath ?? "") || a.reason.localeCompare(b.reason));
813
+ }
814
+ function sortRisks(risks) {
815
+ return risks.sort((a, b) => a.path.localeCompare(b.path) || b.score - a.score || a.signal.localeCompare(b.signal) || a.reason.localeCompare(b.reason));
816
+ }
817
+ //# sourceMappingURL=python.js.map