gitnexus 1.6.3-rc.8 → 1.6.3

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 (285) hide show
  1. package/README.md +21 -5
  2. package/dist/_shared/graph/types.d.ts +16 -0
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +20 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +11 -0
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/scope-resolution/def-index.js +2 -2
  9. package/dist/_shared/scope-resolution/def-index.js.map +1 -1
  10. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
  11. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
  13. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  14. package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
  15. package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
  17. package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
  18. package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
  19. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
  20. package/dist/_shared/scope-resolution/parsed-file.js +54 -0
  21. package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
  22. package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
  23. package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/position-index.js +2 -2
  25. package/dist/_shared/scope-resolution/position-index.js.map +1 -1
  26. package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
  27. package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
  28. package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
  29. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
  30. package/dist/_shared/scope-resolution/reference-site.js +24 -0
  31. package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
  32. package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
  33. package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
  34. package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
  35. package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
  36. package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
  37. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
  38. package/dist/_shared/scope-resolution/registries/context.js +44 -0
  39. package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
  40. package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
  41. package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
  42. package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
  43. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
  44. package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
  45. package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
  46. package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
  47. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
  48. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
  49. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
  50. package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
  51. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
  52. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
  53. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
  54. package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
  55. package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
  56. package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
  57. package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
  58. package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
  59. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
  60. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
  61. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
  62. package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
  63. package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
  64. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  65. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  66. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  67. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  68. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  69. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  70. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  71. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  72. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  73. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  74. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  75. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  76. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  77. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  78. package/dist/cli/ai-context.js +35 -4
  79. package/dist/cli/analyze.d.ts +27 -0
  80. package/dist/cli/analyze.js +31 -1
  81. package/dist/cli/clean.js +19 -1
  82. package/dist/cli/group.js +73 -0
  83. package/dist/cli/index-repo.js +8 -1
  84. package/dist/cli/index.js +26 -1
  85. package/dist/cli/list.js +11 -1
  86. package/dist/cli/remove.d.ts +30 -0
  87. package/dist/cli/remove.js +99 -0
  88. package/dist/cli/setup.js +185 -57
  89. package/dist/cli/tool.d.ts +5 -0
  90. package/dist/cli/tool.js +42 -0
  91. package/dist/config/ignore-service.d.ts +9 -0
  92. package/dist/config/ignore-service.js +80 -13
  93. package/dist/core/embedding-mode.d.ts +30 -0
  94. package/dist/core/embedding-mode.js +30 -0
  95. package/dist/core/embeddings/ast-utils.js +22 -22
  96. package/dist/core/embeddings/chunker.js +30 -25
  97. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  98. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  99. package/dist/core/embeddings/text-generator.d.ts +1 -1
  100. package/dist/core/embeddings/text-generator.js +33 -24
  101. package/dist/core/embeddings/types.d.ts +43 -1
  102. package/dist/core/embeddings/types.js +101 -29
  103. package/dist/core/git-staleness.d.ts +18 -0
  104. package/dist/core/git-staleness.js +108 -0
  105. package/dist/core/graph/graph.js +115 -20
  106. package/dist/core/graph/types.d.ts +12 -1
  107. package/dist/core/group/config-parser.d.ts +4 -0
  108. package/dist/core/group/config-parser.js +18 -1
  109. package/dist/core/group/cross-impact.d.ts +41 -0
  110. package/dist/core/group/cross-impact.js +441 -0
  111. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  112. package/dist/core/group/group-path-utils.d.ts +17 -0
  113. package/dist/core/group/group-path-utils.js +40 -0
  114. package/dist/core/group/resolve-at-member.d.ts +10 -0
  115. package/dist/core/group/resolve-at-member.js +31 -0
  116. package/dist/core/group/service.d.ts +9 -0
  117. package/dist/core/group/service.js +259 -25
  118. package/dist/core/group/types.d.ts +30 -0
  119. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  120. package/dist/core/ingestion/ast-cache.js +14 -2
  121. package/dist/core/ingestion/call-processor.js +9 -0
  122. package/dist/core/ingestion/emit-references.d.ts +88 -0
  123. package/dist/core/ingestion/emit-references.js +229 -0
  124. package/dist/core/ingestion/filesystem-walker.js +6 -4
  125. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  126. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  127. package/dist/core/ingestion/framework-detection.js +6 -2
  128. package/dist/core/ingestion/import-processor.js +4 -0
  129. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  130. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  131. package/dist/core/ingestion/import-target-adapter.js +95 -0
  132. package/dist/core/ingestion/language-provider.d.ts +36 -33
  133. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  134. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  135. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  136. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  137. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  138. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  139. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  140. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  141. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  142. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  143. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  144. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  145. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  146. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  147. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  148. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  149. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  150. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  151. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  152. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  153. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  154. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  155. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  156. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  157. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  158. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  159. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  160. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  161. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  162. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  163. package/dist/core/ingestion/languages/csharp.js +14 -0
  164. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  165. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  166. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  167. package/dist/core/ingestion/languages/python/arity.js +38 -0
  168. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  169. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  170. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  171. package/dist/core/ingestion/languages/python/captures.js +106 -0
  172. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  173. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  174. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  175. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  176. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  177. package/dist/core/ingestion/languages/python/index.js +80 -0
  178. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  179. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  180. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  181. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  182. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  183. package/dist/core/ingestion/languages/python/query.js +267 -0
  184. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  185. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  186. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  187. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  188. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  189. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  190. package/dist/core/ingestion/languages/python.js +14 -0
  191. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  192. package/dist/core/ingestion/model/method-registry.js +4 -0
  193. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  194. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  195. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  196. package/dist/core/ingestion/model/semantic-model.js +55 -0
  197. package/dist/core/ingestion/mro-processor.js +38 -22
  198. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  199. package/dist/core/ingestion/parsing-processor.js +45 -11
  200. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  201. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  202. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  203. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  204. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  205. package/dist/core/ingestion/pipeline.js +2 -1
  206. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  207. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  208. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  209. package/dist/core/ingestion/resolve-references.js +175 -0
  210. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  211. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  212. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  213. package/dist/core/ingestion/scope-extractor.js +758 -0
  214. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  215. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  216. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  217. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  218. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  219. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  220. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  221. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  222. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  223. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  224. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  225. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  226. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  227. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  228. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  229. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  230. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  231. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  232. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  233. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  234. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  235. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  236. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  237. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  238. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  239. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  240. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  241. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  242. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  243. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  244. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  245. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  246. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  247. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  248. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  249. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  250. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  251. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  252. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  253. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  254. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  255. package/dist/core/ingestion/shadow-harness.js +148 -0
  256. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  257. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  258. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  259. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  260. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  261. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  262. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  263. package/dist/core/lbug/lbug-adapter.js +58 -14
  264. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  265. package/dist/core/lbug/pool-adapter.js +24 -14
  266. package/dist/core/run-analyze.d.ts +32 -0
  267. package/dist/core/run-analyze.js +74 -19
  268. package/dist/core/search/bm25-index.d.ts +18 -0
  269. package/dist/core/search/bm25-index.js +125 -12
  270. package/dist/core/tree-sitter/parser-loader.js +6 -1
  271. package/dist/mcp/local/local-backend.d.ts +67 -3
  272. package/dist/mcp/local/local-backend.js +296 -34
  273. package/dist/mcp/resources.d.ts +31 -0
  274. package/dist/mcp/resources.js +100 -17
  275. package/dist/mcp/tools.d.ts +4 -1
  276. package/dist/mcp/tools.js +75 -54
  277. package/dist/server/api.js +6 -2
  278. package/dist/storage/git.d.ts +49 -0
  279. package/dist/storage/git.js +111 -0
  280. package/dist/storage/repo-manager.d.ts +246 -1
  281. package/dist/storage/repo-manager.js +391 -9
  282. package/package.json +7 -6
  283. package/scripts/bench-scope-resolution.ts +134 -0
  284. package/scripts/ci-list-migrated-languages.ts +24 -0
  285. package/skills/gitnexus-cli.md +1 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Adapter from `(ParsedImport, WorkspaceIndex)` → concrete file path.
3
+ *
4
+ * Delegates to the existing `resolvePythonImportInternal` (PEP-328
5
+ * relative resolution + standard suffix matching). The `WorkspaceIndex`
6
+ * is opaque at this layer; consumers wire a `PythonResolveContext`
7
+ * shape carrying `fromFile` + `allFilePaths`.
8
+ *
9
+ * Returning `null` lets the finalize algorithm mark the edge as
10
+ * `linkStatus: 'unresolved'`.
11
+ */
12
+ import type { ParsedImport, WorkspaceIndex } from '../../../../_shared/index.js';
13
+ export interface PythonResolveContext {
14
+ readonly fromFile: string;
15
+ /** Mutable `Set` because the legacy `resolvePythonImportInternal`
16
+ * chain downstream is typed to accept `Set<string>`. Callers that
17
+ * only hold a `ReadonlySet` should copy via `new Set(...)` at the
18
+ * adapter boundary. */
19
+ readonly allFilePaths: Set<string>;
20
+ }
21
+ export declare function resolvePythonImportTarget(parsedImport: ParsedImport, workspaceIndex: WorkspaceIndex): string | null;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Adapter from `(ParsedImport, WorkspaceIndex)` → concrete file path.
3
+ *
4
+ * Delegates to the existing `resolvePythonImportInternal` (PEP-328
5
+ * relative resolution + standard suffix matching). The `WorkspaceIndex`
6
+ * is opaque at this layer; consumers wire a `PythonResolveContext`
7
+ * shape carrying `fromFile` + `allFilePaths`.
8
+ *
9
+ * Returning `null` lets the finalize algorithm mark the edge as
10
+ * `linkStatus: 'unresolved'`.
11
+ */
12
+ import { resolvePythonImportInternal } from '../../import-resolvers/python.js';
13
+ export function resolvePythonImportTarget(parsedImport, workspaceIndex) {
14
+ // WorkspaceIndex is `unknown` in the shared contract (Ring 1
15
+ // placeholder). The scope-resolution orchestrator hands us a
16
+ // PythonResolveContext-shaped object; narrow structurally rather
17
+ // than via a cast chain so unexpected shapes return null cleanly.
18
+ const ctx = workspaceIndex;
19
+ if (ctx === undefined ||
20
+ typeof ctx.fromFile !== 'string' ||
21
+ !(ctx.allFilePaths instanceof Set)) {
22
+ return null;
23
+ }
24
+ if (parsedImport.kind === 'dynamic-unresolved')
25
+ return null;
26
+ if (parsedImport.targetRaw === null || parsedImport.targetRaw === '')
27
+ return null;
28
+ // PEP-328 relative + single-segment proximity bare imports.
29
+ const internal = resolvePythonImportInternal(ctx.fromFile, parsedImport.targetRaw, ctx.allFilePaths);
30
+ if (internal !== null)
31
+ return internal;
32
+ // PEP-328: unresolved relative imports must NOT fall through to suffix
33
+ // matching. Mirrors `pythonImportStrategy` in `configs/python.ts`.
34
+ if (parsedImport.targetRaw.startsWith('.'))
35
+ return null;
36
+ // External dotted imports like `django.apps` must not fall through to
37
+ // generic suffix matching when the repo has unrelated local files such
38
+ // as `accounts/apps.py`. Mirrors `pythonImportStrategy`'s
39
+ // `hasRepoCandidate` check: only suffix-match if the leading segment
40
+ // looks like a local package/module somewhere in-repo.
41
+ const pathLike = parsedImport.targetRaw.replace(/\./g, '/');
42
+ if (pathLike.includes('/')) {
43
+ const [leadingSegment] = pathLike.split('/').filter(Boolean);
44
+ if (!leadingSegment || !hasRepoCandidate(leadingSegment, ctx.allFilePaths)) {
45
+ return null;
46
+ }
47
+ }
48
+ // Multi-segment absolute resolve: try exact paths first, then suffix
49
+ // match in nested repos. Using direct `Set.has` + `endsWith` instead of
50
+ // `suffixResolve`'s shared helper because that helper requires a
51
+ // pre-built `SuffixIndex` to disambiguate ties — without one it falls
52
+ // back to an O(files) scan that silently picks the wrong file when
53
+ // the last segment collides across directories (e.g. `accounts.models`
54
+ // matching `billing/models.py` when both files exist).
55
+ return resolveAbsoluteFromFiles(pathLike, ctx.allFilePaths);
56
+ }
57
+ /**
58
+ * Resolve `package/sub/module` style paths (already dot-flattened) to a
59
+ * concrete file in `allFilePaths`. Tries the exact path first, then the
60
+ * `__init__.py` variant, then a suffix match for nested layouts.
61
+ * Returns the original (un-normalized) path from the set.
62
+ */
63
+ function resolveAbsoluteFromFiles(pathLike, allFilePaths) {
64
+ const directFile = `${pathLike}.py`;
65
+ const directPkg = `${pathLike}/__init__.py`;
66
+ const suffixFile = `/${directFile}`;
67
+ const suffixPkg = `/${directPkg}`;
68
+ let suffixMatch = null;
69
+ for (const raw of allFilePaths) {
70
+ const f = raw.replace(/\\/g, '/');
71
+ if (f === directFile || f === directPkg)
72
+ return raw;
73
+ if (suffixMatch === null && (f.endsWith(suffixFile) || f.endsWith(suffixPkg))) {
74
+ suffixMatch = raw;
75
+ }
76
+ }
77
+ return suffixMatch;
78
+ }
79
+ /**
80
+ * Does the repo contain a module/package named `leadingSegment` at the top
81
+ * level? Used to guard against false-positive suffix matches on external
82
+ * dotted imports (e.g. `django.apps` matching a local `accounts/apps.py`).
83
+ *
84
+ * Checks, in order: `<segment>.py` root file, `<segment>/__init__.py`
85
+ * regular package, or any `<segment>/**.py` file (namespace package).
86
+ */
87
+ function hasRepoCandidate(leadingSegment, allFilePaths) {
88
+ const prefix = `${leadingSegment}/`;
89
+ const rootFile = `${leadingSegment}.py`;
90
+ const initFile = `${leadingSegment}/__init__.py`;
91
+ for (const raw of allFilePaths) {
92
+ const f = raw.replace(/\\/g, '/');
93
+ if (f === rootFile || f === initFile)
94
+ return true;
95
+ if (f.startsWith(prefix) && f.endsWith('.py'))
96
+ return true;
97
+ }
98
+ return false;
99
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Python scope-resolution hooks (RFC #909 Ring 3, RFC §5).
3
+ *
4
+ * Public API barrel. Consumers should import from this file rather than
5
+ * the individual modules — that keeps the per-hook organization an
6
+ * implementation detail we can refactor without touching the provider
7
+ * wiring.
8
+ *
9
+ * Module layout (each file is a single concern):
10
+ *
11
+ * - `query.ts` — tree-sitter query string + lazy parser/query singletons
12
+ * - `ast-utils.ts` — generic `SyntaxNode` helpers
13
+ * - `import-decomposer.ts` — `import a, b` / `from m import x, y` → one match per name
14
+ * - `receiver-binding.ts` — synthesize `self`/`cls` type bindings on methods
15
+ * - `captures.ts` — `emitPythonScopeCaptures` (top-level orchestrator)
16
+ * - `cache-stats.ts` — PROF_SCOPE_RESOLUTION cache hit/miss counters
17
+ * - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
18
+ * - `merge-bindings.ts` — Python LEGB precedence
19
+ * - `arity.ts` — Python arity check (`*args`, `**kwargs`, defaults)
20
+ * - `import-target.ts` — `(ParsedImport, WorkspaceIndex) → file path` adapter
21
+ * - `simple-hooks.ts` — small/no-op hooks made explicit
22
+ *
23
+ * ## Known limitations
24
+ *
25
+ * The Python registry-primary path intentionally does NOT resolve the
26
+ * following. Each is a conscious trade-off at migration time; lifting any
27
+ * of them is tracked as a separate follow-up rather than silently
28
+ * "maybe-resolving" and emitting low-confidence edges.
29
+ *
30
+ * 1. **Dynamic attribute access** — `getattr(obj, 'name')` and
31
+ * `setattr` bind at runtime. We emit no edge; the call site
32
+ * surfaces as an unresolved reference.
33
+ * 2. **Dynamic imports** — `importlib.import_module(...)` and
34
+ * `__import__(...)` are not followed. Static `import x` and
35
+ * `from m import x` are fully resolved.
36
+ * 3. **Metaclass-driven dispatch** — C3 linearization drives MRO
37
+ * (see `mro-processor.ts`), but method resolution that depends
38
+ * on `__getattribute__` overrides or metaclass `__call__`
39
+ * remains unresolved.
40
+ * 4. **Union / Optional type hints** — `def f(x: Union[A, B])` or
41
+ * `x: Optional[A]`: `arity.ts` validates parameter count only;
42
+ * receiver-binding and field-type resolution pick the first arm
43
+ * and emit a single edge rather than branching. `List[T]` /
44
+ * `Dict[K, V]` strip the outer generic for receiver typing (see
45
+ * `interpret.ts`).
46
+ * 5. **Decorators that rewrite signatures** — `@dataclass`,
47
+ * `@property`, `@classmethod`, `@staticmethod` are recognized
48
+ * by `receiver-binding.ts`. Arbitrary decorators (e.g.
49
+ * `functools.wraps`, custom retry wrappers) preserve the wrapped
50
+ * function's declared signature; a decorator that returns a
51
+ * different callable is followed only through the declared
52
+ * return type.
53
+ * 6. **`typing.TYPE_CHECKING`-guarded imports** — treated like any
54
+ * other `import` for reference resolution. We do not distinguish
55
+ * runtime-visible from type-checker-only imports; this is
56
+ * intentional (type-only imports are still structurally valid
57
+ * type references).
58
+ * 7. **`*args` / `**kwargs` type flow-through** — `arity.ts`
59
+ * accepts any call count when a variadic is present, but no
60
+ * type information flows through the variadic into the callee
61
+ * body. Receiver-binding still works for explicit parameters.
62
+ * 8. **`super()` outside a method with a literal class binding** —
63
+ * resolved for the standard `class Child(Parent): def m(self):
64
+ * super().m()` pattern. Zero-arg `super()` inside a nested
65
+ * function, a `functools.wraps`-rewrapped method, or a call
66
+ * site where the enclosing class can't be statically determined
67
+ * is left unresolved.
68
+ *
69
+ * Shadow-harness corpus parity is the authoritative signal for which
70
+ * of these matter in practice. The CI parity gate blocks any PR that
71
+ * regresses either the legacy or registry-primary run of
72
+ * `test/integration/resolvers/python.test.ts`.
73
+ */
74
+ export { emitPythonScopeCaptures } from './captures.js';
75
+ export { getPythonCaptureCacheStats, resetPythonCaptureCacheStats } from './cache-stats.js';
76
+ export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.js';
77
+ export { pythonMergeBindings } from './merge-bindings.js';
78
+ export { pythonArityCompatibility } from './arity.js';
79
+ export { resolvePythonImportTarget, type PythonResolveContext } from './import-target.js';
80
+ export { pythonBindingScopeFor, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Python scope-resolution hooks (RFC #909 Ring 3, RFC §5).
3
+ *
4
+ * Public API barrel. Consumers should import from this file rather than
5
+ * the individual modules — that keeps the per-hook organization an
6
+ * implementation detail we can refactor without touching the provider
7
+ * wiring.
8
+ *
9
+ * Module layout (each file is a single concern):
10
+ *
11
+ * - `query.ts` — tree-sitter query string + lazy parser/query singletons
12
+ * - `ast-utils.ts` — generic `SyntaxNode` helpers
13
+ * - `import-decomposer.ts` — `import a, b` / `from m import x, y` → one match per name
14
+ * - `receiver-binding.ts` — synthesize `self`/`cls` type bindings on methods
15
+ * - `captures.ts` — `emitPythonScopeCaptures` (top-level orchestrator)
16
+ * - `cache-stats.ts` — PROF_SCOPE_RESOLUTION cache hit/miss counters
17
+ * - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
18
+ * - `merge-bindings.ts` — Python LEGB precedence
19
+ * - `arity.ts` — Python arity check (`*args`, `**kwargs`, defaults)
20
+ * - `import-target.ts` — `(ParsedImport, WorkspaceIndex) → file path` adapter
21
+ * - `simple-hooks.ts` — small/no-op hooks made explicit
22
+ *
23
+ * ## Known limitations
24
+ *
25
+ * The Python registry-primary path intentionally does NOT resolve the
26
+ * following. Each is a conscious trade-off at migration time; lifting any
27
+ * of them is tracked as a separate follow-up rather than silently
28
+ * "maybe-resolving" and emitting low-confidence edges.
29
+ *
30
+ * 1. **Dynamic attribute access** — `getattr(obj, 'name')` and
31
+ * `setattr` bind at runtime. We emit no edge; the call site
32
+ * surfaces as an unresolved reference.
33
+ * 2. **Dynamic imports** — `importlib.import_module(...)` and
34
+ * `__import__(...)` are not followed. Static `import x` and
35
+ * `from m import x` are fully resolved.
36
+ * 3. **Metaclass-driven dispatch** — C3 linearization drives MRO
37
+ * (see `mro-processor.ts`), but method resolution that depends
38
+ * on `__getattribute__` overrides or metaclass `__call__`
39
+ * remains unresolved.
40
+ * 4. **Union / Optional type hints** — `def f(x: Union[A, B])` or
41
+ * `x: Optional[A]`: `arity.ts` validates parameter count only;
42
+ * receiver-binding and field-type resolution pick the first arm
43
+ * and emit a single edge rather than branching. `List[T]` /
44
+ * `Dict[K, V]` strip the outer generic for receiver typing (see
45
+ * `interpret.ts`).
46
+ * 5. **Decorators that rewrite signatures** — `@dataclass`,
47
+ * `@property`, `@classmethod`, `@staticmethod` are recognized
48
+ * by `receiver-binding.ts`. Arbitrary decorators (e.g.
49
+ * `functools.wraps`, custom retry wrappers) preserve the wrapped
50
+ * function's declared signature; a decorator that returns a
51
+ * different callable is followed only through the declared
52
+ * return type.
53
+ * 6. **`typing.TYPE_CHECKING`-guarded imports** — treated like any
54
+ * other `import` for reference resolution. We do not distinguish
55
+ * runtime-visible from type-checker-only imports; this is
56
+ * intentional (type-only imports are still structurally valid
57
+ * type references).
58
+ * 7. **`*args` / `**kwargs` type flow-through** — `arity.ts`
59
+ * accepts any call count when a variadic is present, but no
60
+ * type information flows through the variadic into the callee
61
+ * body. Receiver-binding still works for explicit parameters.
62
+ * 8. **`super()` outside a method with a literal class binding** —
63
+ * resolved for the standard `class Child(Parent): def m(self):
64
+ * super().m()` pattern. Zero-arg `super()` inside a nested
65
+ * function, a `functools.wraps`-rewrapped method, or a call
66
+ * site where the enclosing class can't be statically determined
67
+ * is left unresolved.
68
+ *
69
+ * Shadow-harness corpus parity is the authoritative signal for which
70
+ * of these matter in practice. The CI parity gate blocks any PR that
71
+ * regresses either the legacy or registry-primary run of
72
+ * `test/integration/resolvers/python.test.ts`.
73
+ */
74
+ export { emitPythonScopeCaptures } from './captures.js';
75
+ export { getPythonCaptureCacheStats, resetPythonCaptureCacheStats } from './cache-stats.js';
76
+ export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.js';
77
+ export { pythonMergeBindings } from './merge-bindings.js';
78
+ export { pythonArityCompatibility } from './arity.js';
79
+ export { resolvePythonImportTarget } from './import-target.js';
80
+ export { pythonBindingScopeFor, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Capture-match → semantic-shape interpreters.
3
+ *
4
+ * Two pure functions, both consumed by the central scope extractor:
5
+ *
6
+ * - `interpretPythonImport` → `ParsedImport`
7
+ * - `interpretPythonTypeBinding` → `ParsedTypeBinding`
8
+ *
9
+ * The matches arrive pre-decomposed by `emitPythonScopeCaptures`
10
+ * (one imported name per match; synthesized `self`/`cls` markers
11
+ * already attached) so these functions are straight-line tag readers.
12
+ */
13
+ import type { CaptureMatch, ParsedImport, ParsedTypeBinding } from '../../../../_shared/index.js';
14
+ export declare function interpretPythonImport(captures: CaptureMatch): ParsedImport | null;
15
+ export declare function interpretPythonTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Capture-match → semantic-shape interpreters.
3
+ *
4
+ * Two pure functions, both consumed by the central scope extractor:
5
+ *
6
+ * - `interpretPythonImport` → `ParsedImport`
7
+ * - `interpretPythonTypeBinding` → `ParsedTypeBinding`
8
+ *
9
+ * The matches arrive pre-decomposed by `emitPythonScopeCaptures`
10
+ * (one imported name per match; synthesized `self`/`cls` markers
11
+ * already attached) so these functions are straight-line tag readers.
12
+ */
13
+ // ─── interpretImport ──────────────────────────────────────────────────────
14
+ export function interpretPythonImport(captures) {
15
+ // Markers attached by `splitImportStatement` (import-decomposer.ts):
16
+ // `@import.kind` : 'plain' | 'aliased' | 'from' | 'from-alias' | 'wildcard' | 'dynamic'
17
+ // `@import.name` : the imported symbol name (or module name for plain imports)
18
+ // `@import.alias` : the local alias name (for `as` forms)
19
+ // `@import.source`: the module path (always present except for `dynamic`)
20
+ const kindCap = captures['@import.kind'];
21
+ const nameCap = captures['@import.name'];
22
+ const aliasCap = captures['@import.alias'];
23
+ const sourceCap = captures['@import.source'];
24
+ const kind = kindCap?.text;
25
+ if (kind === undefined)
26
+ return null;
27
+ switch (kind) {
28
+ case 'plain': {
29
+ // `import numpy`
30
+ if (sourceCap === undefined)
31
+ return null;
32
+ return {
33
+ kind: 'namespace',
34
+ localName: sourceCap.text.split('.')[0], // `import a.b.c` exposes `a`
35
+ importedName: sourceCap.text,
36
+ targetRaw: sourceCap.text,
37
+ };
38
+ }
39
+ case 'aliased': {
40
+ // `import numpy as np`
41
+ if (sourceCap === undefined || aliasCap === undefined)
42
+ return null;
43
+ return {
44
+ kind: 'namespace',
45
+ localName: aliasCap.text,
46
+ importedName: sourceCap.text,
47
+ targetRaw: sourceCap.text,
48
+ };
49
+ }
50
+ case 'from': {
51
+ // `from m import x`
52
+ if (sourceCap === undefined || nameCap === undefined)
53
+ return null;
54
+ return {
55
+ kind: 'named',
56
+ localName: nameCap.text,
57
+ importedName: nameCap.text,
58
+ targetRaw: sourceCap.text,
59
+ };
60
+ }
61
+ case 'from-alias': {
62
+ // `from m import x as y`
63
+ if (sourceCap === undefined || nameCap === undefined || aliasCap === undefined)
64
+ return null;
65
+ return {
66
+ kind: 'alias',
67
+ localName: aliasCap.text,
68
+ importedName: nameCap.text,
69
+ alias: aliasCap.text,
70
+ targetRaw: sourceCap.text,
71
+ };
72
+ }
73
+ case 'wildcard': {
74
+ // `from m import *`
75
+ if (sourceCap === undefined)
76
+ return null;
77
+ return { kind: 'wildcard', targetRaw: sourceCap.text };
78
+ }
79
+ case 'dynamic': {
80
+ // `importlib.import_module(...)` — preserved for diagnostics.
81
+ return {
82
+ kind: 'dynamic-unresolved',
83
+ localName: '',
84
+ targetRaw: sourceCap?.text ?? null,
85
+ };
86
+ }
87
+ default:
88
+ return null;
89
+ }
90
+ }
91
+ // ─── interpretTypeBinding ─────────────────────────────────────────────────
92
+ export function interpretPythonTypeBinding(captures) {
93
+ // Synthesized `self` / `cls` captures carry `@type-binding.name` and
94
+ // `@type-binding.type` directly — same shape as parameter annotations,
95
+ // source differs.
96
+ const nameCap = captures['@type-binding.name'];
97
+ const typeCap = captures['@type-binding.type'];
98
+ if (nameCap === undefined || typeCap === undefined)
99
+ return null;
100
+ // Strip surrounding quotes for PEP 484 forward references:
101
+ // `def f(x: "User")`. Then unwrap nullable unions — `User | None`,
102
+ // `None | User`, `Optional[User]` — to the concrete class name so
103
+ // receiver-typed resolution treats nullable receivers identically to
104
+ // non-nullable ones. Finally strip single-arg generic wrappers so
105
+ // `list[User]` / `Iterable[User]` behave like `User` for iterable
106
+ // for-loop chain propagation.
107
+ const rawType = stripGeneric(stripNullable(stripForwardRefQuotes(typeCap.text.trim())));
108
+ // Order matters: more specific anchor captures take precedence. `self`
109
+ // and `cls` are synthesized with their own marker captures; the SCM
110
+ // anchor topic captures (`@type-binding.parameter`,
111
+ // `@type-binding.annotation`, `@type-binding.constructor`) distinguish
112
+ // the variable-annotation and constructor-inferred forms from the
113
+ // classic parameter annotation.
114
+ let source = 'parameter-annotation';
115
+ if (captures['@type-binding.self'] !== undefined)
116
+ source = 'self';
117
+ // `cls` is a self-like receiver; share the source label so downstream
118
+ // `Registry.lookup` Step 2 treats them identically.
119
+ else if (captures['@type-binding.cls'] !== undefined)
120
+ source = 'self';
121
+ else if (captures['@type-binding.constructor'] !== undefined)
122
+ source = 'constructor-inferred';
123
+ else if (captures['@type-binding.annotation'] !== undefined)
124
+ source = 'annotation';
125
+ else if (captures['@type-binding.alias'] !== undefined)
126
+ source = 'assignment-inferred';
127
+ else if (captures['@type-binding.return'] !== undefined)
128
+ source = 'return-annotation';
129
+ return { boundName: nameCap.text, rawTypeName: rawType, source };
130
+ }
131
+ function stripForwardRefQuotes(text) {
132
+ if ((text.startsWith('"') && text.endsWith('"')) ||
133
+ (text.startsWith("'") && text.endsWith("'"))) {
134
+ return text.slice(1, -1);
135
+ }
136
+ return text;
137
+ }
138
+ /**
139
+ * Unwrap a single-arg generic collection wrapper — `list[User]`,
140
+ * `set[User]`, `Iterable[User]`, `Sequence[User]`, `Iterator[User]`,
141
+ * `Generator[User, ...]` — to its element type.
142
+ *
143
+ * Point: for-loop and cross-file chain propagation need the element
144
+ * type, not the container. Multi-arg generics (`dict[str, User]`,
145
+ * `Callable[[int], User]`) are left alone — the element semantics
146
+ * aren't unambiguous and the scope-chain fallback handles them at
147
+ * resolution time.
148
+ */
149
+ function stripGeneric(text) {
150
+ const single = text.match(/^(?:[A-Za-z_][A-Za-z0-9_]*\.)?(?:list|List|set|Set|tuple|Tuple|Iterable|Iterator|Sequence|Generator|AsyncIterable|AsyncIterator)\[([^,\]]+)\]$/);
151
+ if (single !== null)
152
+ return single[1].trim();
153
+ // dict[K, V] / Dict[K, V] / Mapping[K, V] — strip to value type V.
154
+ // For-loop destructuring of `for k, v in d.items()` binds `v` to
155
+ // `d`; the chain-follow then unwraps the dict annotation to V.
156
+ // Single-key dict `dict[K]` is not legal Python, so two args is the
157
+ // only shape worth handling. Match a top-level K up to the first
158
+ // comma and a V to the closing bracket; nested generics in V (e.g.
159
+ // `dict[str, list[User]]`) are left for a downstream strip pass.
160
+ const dict = text.match(/^(?:[A-Za-z_][A-Za-z0-9_]*\.)?(?:dict|Dict|Mapping|MutableMapping|OrderedDict|DefaultDict)\[[^,\]]+,\s*([^\]]+)\]$/);
161
+ if (dict !== null)
162
+ return dict[1].trim();
163
+ return text;
164
+ }
165
+ /**
166
+ * Unwrap nullable type annotations so downstream resolution treats
167
+ * `User | None`, `None | User`, and `Optional[User]` identically to
168
+ * `User`. A missing/unknown variant returns the input unchanged.
169
+ *
170
+ * This is a syntactic strip, not a semantic parse — it handles the
171
+ * canonical PEP-604 and `typing.Optional` shapes that cover the
172
+ * overwhelming majority of real-world Python annotations and punts on
173
+ * exotic unions (e.g. `User | Error`, which is ambiguous and should not
174
+ * auto-bind to one arm).
175
+ */
176
+ function stripNullable(text) {
177
+ // `Optional[X]` / `typing.Optional[X]` / `t.Optional[X]`
178
+ const optMatch = text.match(/^(?:[A-Za-z_][A-Za-z0-9_]*\.)?Optional\[(.+)\]$/);
179
+ if (optMatch !== null)
180
+ return optMatch[1].trim();
181
+ // Binary union forms. A three-arm or larger union (`User | None | Error`)
182
+ // is ambiguous for single-receiver inference, so we leave it alone.
183
+ const parts = text.split('|').map((p) => p.trim());
184
+ if (parts.length !== 2)
185
+ return text;
186
+ if (parts[0] === 'None')
187
+ return parts[1];
188
+ if (parts[1] === 'None')
189
+ return parts[0];
190
+ return text;
191
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Python LEGB precedence merge for the `mergeBindings` hook.
3
+ *
4
+ * Tier ranking (lower wins in shadowing):
5
+ *
6
+ * - 0: `local` — an `x = …` or `def x` or `class x` in this scope
7
+ * - 1: `import` / `namespace` / `reexport` — `from m import x`,
8
+ * `import m`, public re-exports
9
+ * - 2: `wildcard` — `from m import *`
10
+ *
11
+ * Within a surviving tier we de-dup by `DefId`, last-write-wins (Python
12
+ * semantics: a later assignment replaces an earlier one for lookup
13
+ * purposes).
14
+ */
15
+ import type { BindingRef } from '../../../../_shared/index.js';
16
+ export declare function pythonMergeBindings(bindings: readonly BindingRef[]): readonly BindingRef[];
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Python LEGB precedence merge for the `mergeBindings` hook.
3
+ *
4
+ * Tier ranking (lower wins in shadowing):
5
+ *
6
+ * - 0: `local` — an `x = …` or `def x` or `class x` in this scope
7
+ * - 1: `import` / `namespace` / `reexport` — `from m import x`,
8
+ * `import m`, public re-exports
9
+ * - 2: `wildcard` — `from m import *`
10
+ *
11
+ * Within a surviving tier we de-dup by `DefId`, last-write-wins (Python
12
+ * semantics: a later assignment replaces an earlier one for lookup
13
+ * purposes).
14
+ */
15
+ const TIER_LOCAL = 0;
16
+ const TIER_IMPORT = 1;
17
+ const TIER_WILDCARD = 2;
18
+ const TIER_UNKNOWN = 3;
19
+ function tierOf(b) {
20
+ switch (b.origin) {
21
+ case 'local':
22
+ return TIER_LOCAL;
23
+ case 'reexport':
24
+ case 'import':
25
+ case 'namespace':
26
+ return TIER_IMPORT;
27
+ case 'wildcard':
28
+ return TIER_WILDCARD;
29
+ default:
30
+ return TIER_UNKNOWN;
31
+ }
32
+ }
33
+ export function pythonMergeBindings(bindings) {
34
+ if (bindings.length === 0)
35
+ return bindings;
36
+ let bestTier = Number.POSITIVE_INFINITY;
37
+ for (const b of bindings)
38
+ bestTier = Math.min(bestTier, tierOf(b));
39
+ const survivors = bindings.filter((b) => tierOf(b) === bestTier);
40
+ const seen = new Map();
41
+ for (const b of survivors)
42
+ seen.set(b.def.nodeId, b);
43
+ return [...seen.values()];
44
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tree-sitter query for Python scope captures (RFC §5.1).
3
+ *
4
+ * Exposes lazy `Parser` and `Query` singletons so callers don't
5
+ * pay tree-sitter init cost per file.
6
+ */
7
+ import Parser from 'tree-sitter';
8
+ export declare function getPythonParser(): Parser;
9
+ export declare function getPythonScopeQuery(): Parser.Query;