From a58a478c064c96506ba4cdf297240dbf9bec03b2 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 21 Apr 2026 16:55:22 +0100 Subject: [PATCH] chore(scope-resolution): drop unused python/scopes.scm sibling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file was documentation-only — the authoritative scope query is the embedded `PYTHON_SCOPE_QUERY` constant in `python/query.ts`. Nothing loaded the `.scm` at runtime, so it drifted from the code. Remove it and update the four doc comments that pointed at it: - language-provider.ts: "scopes.scm query" → "scope query (embedded in each language's query.ts)". - languages/python.ts: capture-vocabulary pointer → query.ts. - python/query.ts header: drop the "edit both together" note. - python/receiver-binding.ts: "keeps the .scm declarative" → "keeps the embedded scope query declarative". - scope/walkers.ts: "Python's scopes.scm" → "Python's scope query". Historical plan docs under docs/plans/ still reference scopes.scm but are frozen artifacts, not living documentation. C# never had a .scm sibling, so no action needed there. --- .../src/core/ingestion/language-provider.ts | 5 +- .../src/core/ingestion/languages/python.ts | 2 +- .../core/ingestion/languages/python/query.ts | 7 +- .../languages/python/receiver-binding.ts | 6 +- .../ingestion/languages/python/scopes.scm | 266 ------------------ .../scope-resolution/scope/walkers.ts | 2 +- 6 files changed, 9 insertions(+), 279 deletions(-) delete mode 100644 gitnexus/src/core/ingestion/languages/python/scopes.scm diff --git a/gitnexus/src/core/ingestion/language-provider.ts b/gitnexus/src/core/ingestion/language-provider.ts index 7ab0d33fc..e8bb332a7 100644 --- a/gitnexus/src/core/ingestion/language-provider.ts +++ b/gitnexus/src/core/ingestion/language-provider.ts @@ -303,8 +303,9 @@ interface LanguageProviderConfig { /** * Emit scope captures from raw source, **pre-grouped per tree-sitter - * query match**. Tree-sitter-based providers run a `scopes.scm` query - * and emit one `CaptureMatch` per query match; standalone providers + * query match**. Tree-sitter-based providers run a scope query + * (embedded as a string constant in each language's `query.ts`) and + * emit one `CaptureMatch` per query match; standalone providers * (COBOL) emit matches from a regex tagger. The return shape is * parser-agnostic: the central `ScopeExtractor` consumes * `CaptureMatch[]` without knowing which parser produced them. diff --git a/gitnexus/src/core/ingestion/languages/python.ts b/gitnexus/src/core/ingestion/languages/python.ts index 6b8686e33..fa1b60b94 100644 --- a/gitnexus/src/core/ingestion/languages/python.ts +++ b/gitnexus/src/core/ingestion/languages/python.ts @@ -92,7 +92,7 @@ export const pythonProvider = defineLanguage({ // ── RFC #909 Ring 3: scope-based resolution hooks (RFC §5) ────────── // Python is the first migration. See ./python/index.ts for the // full per-hook rationale and the canonical capture vocabulary in - // ./python/scopes.scm. + // ./python/query.ts (PYTHON_SCOPE_QUERY constant). emitScopeCaptures: emitPythonScopeCaptures, interpretImport: interpretPythonImport, interpretTypeBinding: interpretPythonTypeBinding, diff --git a/gitnexus/src/core/ingestion/languages/python/query.ts b/gitnexus/src/core/ingestion/languages/python/query.ts index 03b27f730..b22573a91 100644 --- a/gitnexus/src/core/ingestion/languages/python/query.ts +++ b/gitnexus/src/core/ingestion/languages/python/query.ts @@ -1,12 +1,7 @@ /** * Tree-sitter query for Python scope captures (RFC §5.1). * - * The `.scm` sibling file is the human-readable spec; this module - * mirrors it at runtime. **Edit both together** — the unit/integration - * tests reference the embedded constant, and the file documents the - * contract. - * - * Also exposes lazy `Parser` and `Query` singletons so callers don't + * Exposes lazy `Parser` and `Query` singletons so callers don't * pay tree-sitter init cost per file. */ diff --git a/gitnexus/src/core/ingestion/languages/python/receiver-binding.ts b/gitnexus/src/core/ingestion/languages/python/receiver-binding.ts index 8294233f1..ec525d398 100644 --- a/gitnexus/src/core/ingestion/languages/python/receiver-binding.ts +++ b/gitnexus/src/core/ingestion/languages/python/receiver-binding.ts @@ -4,9 +4,9 @@ * * Tree-sitter can't easily express "the first parameter of a function * defined directly inside a class body" via a single static query. - * Doing this in code keeps the `.scm` file declarative and lets us - * encode the `@classmethod` / `@staticmethod` decorator awareness that - * Python's runtime depends on. + * Doing this in code keeps the embedded scope query declarative and + * lets us encode the `@classmethod` / `@staticmethod` decorator + * awareness that Python's runtime depends on. */ import type { CaptureMatch } from 'gitnexus-shared'; diff --git a/gitnexus/src/core/ingestion/languages/python/scopes.scm b/gitnexus/src/core/ingestion/languages/python/scopes.scm deleted file mode 100644 index b03220bea..000000000 --- a/gitnexus/src/core/ingestion/languages/python/scopes.scm +++ /dev/null @@ -1,266 +0,0 @@ -; Tree-sitter Python query — RFC §5.1 captures for scope-based resolution -; (RFC #909 Ring 3, language: Python — first migration). -; -; Capture vocabulary consumed by the central `ScopeExtractor`: -; -; @scope.module — file root -; @scope.class — class body -; @scope.function — def / async def body (functions, methods, lambdas) -; -; @declaration.class + @declaration.name -; @declaration.function + @declaration.name -; @declaration.method + @declaration.name (functions inside class bodies) -; @declaration.variable + @declaration.name (module/class/function-level assignments) -; -; @import.statement — anchor for `interpretImport`. The hook reads -; the captured source text and tokenizes it into -; a `ParsedImport`. We do NOT decompose -; `import X, Y` here at query time — `interpretImport` -; splits multi-target statements into N matches. -; -; @type-binding.parameter + @type-binding.name + @type-binding.type -; -; @reference.call.free + @reference.name (e.g. `print(x)`) -; @reference.call.member + @reference.name + @reference.receiver -; (e.g. `obj.save()`) -; -; Python has NO block scope: `if`, `for`, `while`, `try`, `with`, `match` -; bodies do NOT introduce a new lexical scope (PEP 8 / language reference). -; We therefore do NOT emit `@scope.block` captures for those constructs; -; their contained declarations land in the enclosing function/class/module -; scope automatically (RFC §5.1 "transparent block" behavior). -; -; `@reference.call.constructor` is intentionally absent: Python has no -; `new` keyword. A call to a class is syntactically identical to a call to -; a free function; the registry decides constructor-vs-call by inspecting -; the resolved `def.type`. Stays out of the parser to avoid duplicating -; that logic in tree-sitter. - -; ─── Scopes ──────────────────────────────────────────────────────────────── - -(module) @scope.module - -(class_definition) @scope.class - -(function_definition) @scope.function - -; ─── Declarations: class / function ──────────────────────────────────────── - -(class_definition - name: (identifier) @declaration.name) @declaration.class - -(function_definition - name: (identifier) @declaration.name) @declaration.function - -; ─── Declarations: assignments (module-, class-, function-level variables) -; -; Note: tree-sitter-python parses both annotated and plain assignments as -; `(assignment left: ...)` — typed and untyped both surface here. The -; central extractor de-dupes by `nodeId` (file#line:col:type:name). - -(assignment - left: (identifier) @declaration.name) @declaration.variable - -; for-loop target — Python `for` does NOT introduce a new scope; the -; loop variable binds in the enclosing function/module scope. -(for_statement - left: (identifier) @declaration.name) @declaration.variable - -; ─── Imports ─────────────────────────────────────────────────────────────── -; -; The whole statement is the anchor — `interpretImport` decomposes it. -; We tag both shapes so the hook sees a single capture name (`@import.statement`). - -(import_statement) @import.statement - -(import_from_statement) @import.statement - -; ─── Type bindings: parameter annotations ────────────────────────────────── -; -; `def f(x: User)` — `x` is bound to `User` in `f`'s scope. -; -; `interpretTypeBinding` reads `@type-binding.name` (the parameter name) -; and `@type-binding.type` (the annotation source text) to produce a -; `ParsedTypeBinding { boundName, rawTypeName, source: 'parameter-annotation' }`. - -(typed_parameter - (identifier) @type-binding.name - type: (type) @type-binding.type) @type-binding.parameter - -(typed_default_parameter - name: (identifier) @type-binding.name - type: (type) @type-binding.type) @type-binding.parameter - -; ─── Type bindings: constructor-inferred assignments ─────────────────────── -; -; `u = User("alice")` — `u`'s type is inferred from the RHS call's target. -; Python has no `new` keyword, so the pattern matches any `assignment` -; whose RHS is a `call` with a bare-identifier function (constructor- -; shaped). The registry resolves the raw name through the scope chain at -; lookup time, so imported classes, local classes, and aliased imports -; all work without query-time knowledge. -; -; Emits `source: 'constructor-inferred'`. -; -; Listed BEFORE the annotation pattern so `u: User = find()` — which -; matches both patterns — has the annotation (later-processed match) -; overwrite the constructor-inferred guess. Explicit user intent wins. - -(assignment - left: (identifier) @type-binding.name - right: (call - function: (identifier) @type-binding.type)) @type-binding.constructor - -; ─── Type bindings: variable annotations ─────────────────────────────────── -; -; `u: User` or `u: User = some_value` — `u` is explicitly annotated. Both -; forms parse under tree-sitter-python as `(assignment left: type:)` with -; an optional `right:`. Module-, class-, and function-scope annotations -; all land here; scope attachment is handled by the central extractor -; via the anchor's innermost-containing scope. -; -; Emits `source: 'annotation'`. - -(assignment - left: (identifier) @type-binding.name - type: (type) @type-binding.type) @type-binding.annotation - -; For-loop iterable of a free-call result: `for u in get_users()` — -; binds `u → get_users` so the chain post-pass follows it through -; `get_users`'s return-type annotation (cross-file via -; `propagateImportedReturnTypes`). -(for_statement - left: (identifier) @type-binding.name - right: (call - function: (identifier) @type-binding.type)) @type-binding.alias - -; for (i, u) in enumerate(X) and for i, u in enumerate(X) — bind the -; second tuple element to X. enumerate yields (int, X-element); the -; chain-follow unwraps X via generic-strip when X is an annotated -; collection. -(for_statement - left: (tuple_pattern - (identifier) - (identifier) @type-binding.name) - right: (call - function: (identifier) @_enum - arguments: (argument_list - (identifier) @type-binding.type)) - (#eq? @_enum "enumerate")) @type-binding.alias - -(for_statement - left: (pattern_list - (identifier) - (identifier) @type-binding.name) - right: (call - function: (identifier) @_enum - arguments: (argument_list - (identifier) @type-binding.type)) - (#eq? @_enum "enumerate")) @type-binding.alias - -; for k, v in d.items() — bind v to d. The chain-follow unwraps d's -; dict[K, V] annotation to V via the dict-aware stripGeneric. -(for_statement - left: (pattern_list - (identifier) - (identifier) @type-binding.name) - right: (call - function: (attribute - object: (identifier) @type-binding.type - attribute: (identifier) @_items)) - (#eq? @_items "items")) @type-binding.alias - -(for_statement - left: (tuple_pattern - (identifier) - (identifier) @type-binding.name) - right: (call - function: (attribute - object: (identifier) @type-binding.type - attribute: (identifier) @_items)) - (#eq? @_items "items")) @type-binding.alias - -; for i, (k, v) in enumerate(d.items()) — nested tuple destructuring. -(for_statement - left: (pattern_list - (identifier) - (tuple_pattern - (identifier) - (identifier) @type-binding.name)) - right: (call - function: (identifier) @_enum - arguments: (argument_list - (call - function: (attribute - object: (identifier) @type-binding.type - attribute: (identifier) @_items)))) - (#eq? @_enum "enumerate") - (#eq? @_items "items")) @type-binding.alias - -; for i, k, v in enumerate(d.items()) — 3-var flat destructuring. -(for_statement - left: (pattern_list - (identifier) - (identifier) - (identifier) @type-binding.name) - right: (call - function: (identifier) @_enum - arguments: (argument_list - (call - function: (attribute - object: (identifier) @type-binding.type - attribute: (identifier) @_items)))) - (#eq? @_enum "enumerate") - (#eq? @_items "items")) @type-binding.alias - -; for u in self.X — heuristic: bind u to X (the attribute name). -; The chain-follow resolves X via the enclosing method's parameter -; typeBinding. Supports fixtures that reference `self.X` as a stand-in -; for a parameter X (matches legacy DAG fallback behavior). -(for_statement - left: (identifier) @type-binding.name - right: (attribute - object: (identifier) @_self - attribute: (identifier) @type-binding.type) - (#eq? @_self "self")) @type-binding.alias - -; for v in d.values() — bind v to d (dict-strip yields value type). -(for_statement - left: (identifier) @type-binding.name - right: (call - function: (attribute - object: (identifier) @type-binding.type - attribute: (identifier) @_values)) - (#eq? @_values "values")) @type-binding.alias - -; ─── Type bindings: function return-type annotations ───────────────────── -; -; `def get_user() -> User:` — binds the function's NAME to its return -; type in the enclosing scope. Combined with the constructor-inferred + -; chain-follow path, `u = get_user()` then resolves `u: User` cross- -; call. Python provider hoists the binding via `pythonBindingScopeFor` -; to the function's parent scope so callers in module/class scope see it. - -(function_definition - name: (identifier) @type-binding.name - return_type: (type) @type-binding.type) @type-binding.return - -; ─── References: calls ───────────────────────────────────────────────────── -; -; Free call: `print(x)` — function is a bare identifier -; Member call: `obj.save()` — function is an attribute access - -(call - function: (identifier) @reference.name) @reference.call.free - -(call - function: (attribute - object: (_) @reference.receiver - attribute: (identifier) @reference.name)) @reference.call.member - -; Attribute write: `obj.name = "x"` — emits ACCESSES (write) edge from -; the enclosing function to the field on obj's class. -(assignment - left: (attribute - object: (_) @reference.receiver - attribute: (identifier) @reference.name)) @reference.write.member diff --git a/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts b/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts index 14511b523..3f4dc8c39 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts @@ -159,7 +159,7 @@ export function populateClassOwnedMembers(parsed: ParsedFile): void { // when the def sits inside a class. Without this, two classes in the // same file that share a method name collide at the graph-bridge lookup // (`node-lookup.ts` keys by (filePath, qualifiedName) and falls back to - // simple name only). Python's `scopes.scm` doesn't emit + // simple name only). Python's scope query doesn't emit // `@declaration.qualified_name` for nested methods, so the finalized // defs arrive here with simple names — we stamp the qualifier while // we're already walking class scopes for ownerId.