diff --git a/AGENTS.md b/AGENTS.md index f4fbcadc0..1a7ed264c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,8 @@ Commands and gotchas live under **Repo reference** below and in **[CONTRIBUTING. ## Reference docs - **[ARCHITECTURE.md](ARCHITECTURE.md)**, **[CONTRIBUTING.md](CONTRIBUTING.md)**, **[GUARDRAILS.md](GUARDRAILS.md)** -- **Call-resolution DAG:** See ARCHITECTURE.md § Call-Resolution DAG. Typed 6-stage DAG inside the `parse` phase; language-specific behavior behind `inferImplicitReceiver` / `selectDispatch` hooks on `LanguageProvider`. Shared code in `gitnexus/src/core/ingestion/` must not name languages. Types: `gitnexus/src/core/ingestion/call-types.ts`. +- **Call-resolution DAG (legacy path):** See ARCHITECTURE.md § Call-Resolution DAG. Typed 6-stage DAG inside the `parse` phase; language-specific behavior behind `inferImplicitReceiver` / `selectDispatch` hooks on `LanguageProvider`. Shared code in `gitnexus/src/core/ingestion/` must not name languages. Types: `gitnexus/src/core/ingestion/call-types.ts`. +- **Scope-resolution pipeline (RFC #909 Ring 3):** See ARCHITECTURE.md § Scope-Resolution Pipeline. Replaces the legacy DAG for languages in `MIGRATED_LANGUAGES` (currently Python). A language plugs in by implementing `ScopeResolver` (`scope-resolution/contract/scope-resolver.ts`) and registering it in `SCOPE_RESOLVERS`. CI parity gate runs BOTH paths per migrated language on every PR. - **Cursor:** `.cursor/index.mdc` (always-on); `.cursor/rules/*.mdc` (glob-scoped). Legacy `.cursorrules` deprecated. - **GitNexus:** skills in `.claude/skills/gitnexus/`; MCP rules in `gitnexus:start` block below. @@ -47,6 +48,7 @@ Commands and gotchas live under **Repo reference** below and in **[CONTRIBUTING. | Date | Version | Change | |------|---------|--------| +| 2026-04-20 | 1.5.0 | Added scope-resolution pipeline pointer (RFC #909 Ring 3); Python migrated to registry-primary. | | 2026-04-16 | 1.4.0 | Fixed: web UI description, pre-commit behavior, MCP tools (7->16), added gitnexus-shared, removed stale vite-plugin-wasm gotcha. | | 2026-04-13 | 1.3.0 | Updated GitNexus index stats after DAG refactor. | | 2026-03-24 | 1.2.0 | Fixed gitnexus:start block duplication. | diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d934cd8b6..ae828f8ae 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -205,6 +205,87 @@ Both hooks are optional on `LanguageProvider`. Ruby is the only current implemen | `core/ingestion/languages/ruby.ts` | Both hooks + `mroStrategy: 'ruby-mixin'` | | `core/ingestion/utils/ruby-self-call.ts` | Bare-call rewrite for `inferImplicitReceiver` | +### Coexistence with the scope-resolution pipeline + +The Call-Resolution DAG is the **legacy path**. RFC #909 Ring 3 introduces a parallel **scope-resolution pipeline** (next section) that replaces stages 1–6 with a scope-indexed registry lookup. Both paths ship side-by-side and are gated per-language via `MIGRATED_LANGUAGES` + the `REGISTRY_PRIMARY_` env var. + +- **Unmigrated language** → Call-Resolution DAG runs; scope-resolution phase is a no-op. +- **Migrated language** (currently: Python) → scope-resolution owns CALLS/ACCESSES/USES emission; the legacy DAG gates off for that language via `isRegistryPrimary(lang)` checks in `call-processor.ts` and `import-processor.ts`. +- `import-processor` still populates `importMap` for migrated languages — heritage's `ctx.resolve` reads it to disambiguate parent classes. Only edge emission is gated. +- CI runs BOTH paths for every migrated language on every PR (`.github/workflows/ci-scope-parity.yml`); both must pass. + +--- + +## Scope-Resolution Pipeline (RFC #909 Ring 3) + +Language-agnostic registry-primary resolver. Replaces the Call-Resolution DAG for migrated languages. Adding a language is one interface implementation (`ScopeResolver`) plus two registrations — no changes to shared code, no new pipeline phase. + +### Pipeline stages + +``` + ParsedFile[] (extractParsedFile per file) + │ finalizeScopeModel (+ provider hooks) + ▼ + ScopeResolutionIndexes + │ resolveReferenceSites (via MethodRegistry.lookup) + ▼ + ReferenceIndex + │ emitReceiverBoundCalls ── FIRST + │ emitFreeCallFallback ── THEN + │ emitReferencesViaLookup ── LAST (uses handledSites) + │ emitImportEdges + ▼ + KnowledgeGraph (IMPORTS / CALLS / ACCESSES / INHERITS / USES) +``` + +Orchestrator: `runScopeResolution(input, provider)` in `scope-resolution/pipeline/run.ts`. +Pipeline phase: `scopeResolutionPhase` in `scope-resolution/pipeline/phase.ts` — iterates `SCOPE_RESOLVERS ∩ MIGRATED_LANGUAGES`, reads per-file Trees from the parse phase's `scopeTreeCache`, disposes the cache at the end. + +### `ScopeResolver` contract + +Single interface a language implements to plug into the pipeline. Contract fully documented in `scope-resolution/contract/scope-resolver.ts`. + +| Hook | Purpose | +|------|---------| +| `languageProvider` | Base `LanguageProvider` (tree-sitter query, `emitScopeCaptures`, import/binding interpreters, hooks) | +| `populateOwners(parsed)` | Fill deferred `ownerId` fields on method defs (captures can't always know the owning class at parse time) | +| `buildMro(graph, parsed, nodeLookup)` | Produce `mroByClassDefId: Map` — C3, Ruby-mixin, or first-wins per language | +| `resolveImportTarget(target, fromFile, allFiles)` | `(rawImportPath, sourceFile) → targetFilePath` (PEP-328 for Python, etc.) | +| `mergeBindings(existing, incoming, scopeId)` | Shadowing / LEGB precedence | +| `arityCompatibility` | Provider consumed by registry during `MethodRegistry.lookup` Step 2 | +| `importEdgeReason` | Confidence-tier string for IMPORTS edge reason field | +| `propagatesReturnTypesAcrossImports?` | Opt out of cross-file return-type propagation (default on) | + +### Per-language registration + +1. Implement `ScopeResolver` in `languages//scope-resolver.ts`. +2. Add entry to `SCOPE_RESOLVERS` in `scope-resolution/pipeline/registry.ts`. +3. Add the language to `MIGRATED_LANGUAGES` in `registry-primary-flag.ts` when the shadow-harness corpus parity ≥ 99% fixtures / ≥ 98% corpus. + +CI auto-discovers the set via `tsx`. No workflow edit required. + +### Code references + +| Module | Purpose | +|--------|---------| +| `scope-resolution/contract/scope-resolver.ts` | `ScopeResolver` interface + shared types | +| `scope-resolution/pipeline/run.ts` | Generic orchestrator | +| `scope-resolution/pipeline/phase.ts` | Pipeline-phase wrapper (deps: `parse`, `structure`) | +| `scope-resolution/pipeline/registry.ts` | `SCOPE_RESOLVERS` map | +| `scope-resolution/passes/*.ts` | Reference-resolution passes (receiver-bound, free-call fallback, compound-receiver, MRO, cross-file return-type propagation) | +| `scope-resolution/graph-bridge/*.ts` | CLI-local translation from resolved references → `KnowledgeGraph` edges | +| `scope-resolution/scope/*.ts` | Generic scope-chain walkers + namespace targets | +| `scope-resolution/workspace-index.ts` | Build-once O(1) lookup index | +| `registry-primary-flag.ts` | `MIGRATED_LANGUAGES` set + `isRegistryPrimary(lang)` | +| `languages/python/index.ts` | Python `ScopeResolver` hooks + known-limitation docs | +| `languages/python/captures.ts` | `emitPythonScopeCaptures` (honors cross-phase Tree cache) | + +### Performance notes + +- **Cross-phase Tree cache**: parse phase writes Trees into `scopeTreeCache` (separate from the chunk-local `astCache`) ONLY for languages with `emitScopeCaptures`. Scope-resolution reads from it to skip the second parse. Cleared at end of the phase. Workers leave the cache empty — Trees can't cross MessageChannels; cache miss = fresh parse. `PROF_SCOPE_RESOLUTION=1` emits hit/miss counters and a worker-engaged warning. +- **Typed relationship iteration**: heritage + MRO walk only the EXTENDS / IMPLEMENTS / HAS_METHOD edges via `iterRelationshipsByType`, not the full relationship map. +- **Workspace-resolution-index**: O(1) `findOwnedMember` / `findExportedDef` / `classScopeByDefId` built once per run. + --- ## Language-agnostic graph feeding diff --git a/type-resolution-system.md b/type-resolution-system.md index bfd77ac58..29d3c0ee5 100644 --- a/type-resolution-system.md +++ b/type-resolution-system.md @@ -38,6 +38,8 @@ buildTypeEnv(tree, language, symbolTable?) The `TypeEnvironment` is built once per file. `call-processor.ts` then uses `lookup()` to determine receiver types and narrow candidate symbols from the `SymbolTable`. +> **Note (RFC #909 Ring 3):** `call-processor.ts` is the legacy call-resolution path. Languages in `MIGRATED_LANGUAGES` (currently Python) route through the scope-resolution pipeline instead — see `ARCHITECTURE.md § Scope-Resolution Pipeline`. TypeEnv is still built for migrated languages in the parse worker, but receiver typing flows through `ParsedTypeBinding` + `ScopeResolutionIndexes` rather than `call-processor.ts`. + --- ## Architecture