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.
- package/README.md +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +20 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +11 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.js +44 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScopeResolver` — the per-language contract consumed by the generic
|
|
3
|
+
* scope-resolution orchestrator (`runScopeResolution`).
|
|
4
|
+
*
|
|
5
|
+
* ## Migration cookbook (next language)
|
|
6
|
+
*
|
|
7
|
+
* To add a language to the registry-primary path:
|
|
8
|
+
*
|
|
9
|
+
* 1. Implement `ScopeResolver` in
|
|
10
|
+
* `gitnexus/src/core/ingestion/languages/<lang>/scope-resolver.ts`.
|
|
11
|
+
* Nine required fields (language, languageProvider,
|
|
12
|
+
* importEdgeReason, resolveImportTarget, mergeBindings,
|
|
13
|
+
* arityCompatibility, buildMro, populateOwners, isSuperReceiver)
|
|
14
|
+
* plus optional toggles / hooks:
|
|
15
|
+
* - propagatesReturnTypesAcrossImports (default true)
|
|
16
|
+
* - fieldFallbackOnMethodLookup (default true — turn OFF for
|
|
17
|
+
* statically-typed languages; the heuristic over-connects)
|
|
18
|
+
* - unwrapCollectionAccessor — property-style collection views
|
|
19
|
+
* - collapseMemberCallsByCallerTarget — one edge per caller/target
|
|
20
|
+
* - populateNamespaceSiblings — cross-file implicit visibility
|
|
21
|
+
* - hoistTypeBindingsToModule — enable ONLY when method return
|
|
22
|
+
* types are stored on the enclosing Module scope; most
|
|
23
|
+
* languages attach them to the class scope and leave this off
|
|
24
|
+
* 2. Export a thin entry point:
|
|
25
|
+
* `runYourLangScopeResolution(input) = runScopeResolution(input, yourScopeResolver)`.
|
|
26
|
+
* 3. Register the provider in
|
|
27
|
+
* `gitnexus/src/core/ingestion/scope-resolution/pipeline/registry.ts`
|
|
28
|
+
* (the `SCOPE_RESOLVERS` map).
|
|
29
|
+
* 4. Add `SupportedLanguages.YourLang` to `MIGRATED_LANGUAGES` in
|
|
30
|
+
* `registry-primary-flag.ts`.
|
|
31
|
+
* 5. Verify the resolver integration test at
|
|
32
|
+
* `gitnexus/test/integration/resolvers/<lang>.test.ts` passes
|
|
33
|
+
* under both `REGISTRY_PRIMARY_<LANG>=0` (legacy) and `=1`
|
|
34
|
+
* (registry-primary). The CI parity gate enforces this.
|
|
35
|
+
*
|
|
36
|
+
* No new pipeline phase, no orchestrator copy-paste, no workflow
|
|
37
|
+
* change. The generic `scopeResolutionPhase` and the CI parity
|
|
38
|
+
* workflow auto-discover everything via `MIGRATED_LANGUAGES`.
|
|
39
|
+
*
|
|
40
|
+
* ## ScopeResolver vs LanguageProvider
|
|
41
|
+
*
|
|
42
|
+
* The codebase has two provider contracts. Their lifecycles differ:
|
|
43
|
+
*
|
|
44
|
+
* - `LanguageProvider` (`language-provider.ts`) is the
|
|
45
|
+
* **parsing-side** contract — how to emit captures, classify
|
|
46
|
+
* scopes, interpret imports / typeBindings. ~40 fields covering
|
|
47
|
+
* both legacy and new pipelines. Consumed by `ScopeExtractor`,
|
|
48
|
+
* once per file at extract time.
|
|
49
|
+
* - `ScopeResolver` (this file) is the **emit-side** contract — how
|
|
50
|
+
* the resolution pipeline dispatches references to graph edges.
|
|
51
|
+
* 8 fields total. Consumed by `runScopeResolution`, once per
|
|
52
|
+
* workspace at resolve time.
|
|
53
|
+
*
|
|
54
|
+
* They share three concept names (`arityCompatibility`, `mergeBindings`,
|
|
55
|
+
* `resolveImportTarget`) because the emit pipeline reuses a few
|
|
56
|
+
* finalize hooks. Per-language wiring passes the SAME function
|
|
57
|
+
* reference through both interfaces — no second copy of the logic.
|
|
58
|
+
* Rationale for not collapsing: lifecycle separation, and merging
|
|
59
|
+
* would create a god-interface complicating future migrations.
|
|
60
|
+
*
|
|
61
|
+
* ## Reference implementation
|
|
62
|
+
*
|
|
63
|
+
* `gitnexus/src/core/ingestion/languages/python/scope-resolver.ts` —
|
|
64
|
+
* `pythonScopeResolver` is the canonical example. Read that file when
|
|
65
|
+
* migrating a new language; this interface lists the fields that
|
|
66
|
+
* implementation populates.
|
|
67
|
+
*
|
|
68
|
+
* ## Contract Invariants the orchestrator depends on
|
|
69
|
+
*
|
|
70
|
+
* These are non-obvious behaviors that the orchestrator and the
|
|
71
|
+
* existing Python + C# resolvers depend on. Future implementers will
|
|
72
|
+
* break them silently if not documented.
|
|
73
|
+
*
|
|
74
|
+
* - **I1 — Phase 4 emission order is load-bearing.** `emitReceiverBoundCalls`
|
|
75
|
+
* runs FIRST (populates `handledSites`), then `emitFreeCallFallback`,
|
|
76
|
+
* then `emitReferencesViaLookup` (consumes `handledSites` as a skip
|
|
77
|
+
* set), then `emitImportEdges`. Reordering breaks same-name collision
|
|
78
|
+
* resolution: the shared lookup can mis-resolve `app_metrics.get_metrics()`
|
|
79
|
+
* to a same-named local function, and only the precise per-receiver
|
|
80
|
+
* pass running first prevents the wrong edge.
|
|
81
|
+
*
|
|
82
|
+
* - **I2 — `handledSites` semantics.** A site is added to
|
|
83
|
+
* `handledSites` IFF a `tryEmitEdge` call returned `true` for it.
|
|
84
|
+
* Sites a pass touched but couldn't resolve do NOT get marked —
|
|
85
|
+
* they still get a chance from the shared resolver. Exception:
|
|
86
|
+
* the free-call fallback marks the site unconditionally after
|
|
87
|
+
* attempting emission (even on dedup-collapse), because the
|
|
88
|
+
* per-(caller, target) collapse semantics require multiple call
|
|
89
|
+
* sites in the same caller body not produce multiple edges.
|
|
90
|
+
*
|
|
91
|
+
* - **I3 — `propagateImportedReturnTypes` mutation timing.** The
|
|
92
|
+
* pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
|
|
93
|
+
* `draftToScope`, NOT frozen). It MUST run AFTER `finalizeScopeModel`
|
|
94
|
+
* (so `indexes.bindings` is populated) and BEFORE
|
|
95
|
+
* `resolveReferenceSites` (so resolution sees the propagated types).
|
|
96
|
+
* The pass also re-runs `followChainPostFinalize` on every scope's
|
|
97
|
+
* typeBindings because scope-extractor's pass-4 already ran and
|
|
98
|
+
* missed any chain whose terminal lives in a foreign file.
|
|
99
|
+
*
|
|
100
|
+
* - **I4 — `emitReceiverBoundCalls` case order.** Cases are evaluated
|
|
101
|
+
* in this order; the FIRST that emits an edge wins:
|
|
102
|
+
* 1. super branch (`provider.isSuperReceiver(receiverName)`)
|
|
103
|
+
* 2. Case 0 compound (`receiverName` has `.` or `(`)
|
|
104
|
+
* 3. Case 1 namespace-receiver
|
|
105
|
+
* 4. Case 2 class-name receiver
|
|
106
|
+
* 5. Case 3 dotted typeBinding for namespace prefix
|
|
107
|
+
* 6. Case 3b chain-typebinding (compound resolver)
|
|
108
|
+
* 7. Case 4 simple typeBinding (MRO walk + findOwnedMember)
|
|
109
|
+
* Reordering or merging cases changes resolution semantics. The
|
|
110
|
+
* numbering is part of the contract — keep the comments.
|
|
111
|
+
*
|
|
112
|
+
* - **I5 — Pre-seeding `seen` from `referenceIndex` is forbidden.**
|
|
113
|
+
* Earlier versions of the receiver-bound pass pre-populated `seen`
|
|
114
|
+
* to avoid double-emit. After Phase 4 was reordered, pre-seeding
|
|
115
|
+
* became actively harmful: it suppresses correct emissions for
|
|
116
|
+
* sites the shared resolver happened to resolve to a wrong target.
|
|
117
|
+
* The orchestrator MUST NOT pre-seed.
|
|
118
|
+
*
|
|
119
|
+
* - **I6 — `Scope.typeBindings` is mutable post-finalize.** `draftToScope`
|
|
120
|
+
* (in `scope-extractor.ts`) builds `typeBindings` as a plain
|
|
121
|
+
* `new Map(...)` — not frozen, intentionally. Passes below rely on
|
|
122
|
+
* this. Do NOT freeze `typeBindings` in any downstream refactor.
|
|
123
|
+
*
|
|
124
|
+
* - **I7 — `ScopeResolver` and `LanguageProvider` are distinct contracts.**
|
|
125
|
+
* Python and C# pass the SAME function reference through both
|
|
126
|
+
* interfaces where they share a hook name — no second copy of the
|
|
127
|
+
* logic. Rationale for not collapsing them: lifecycles differ
|
|
128
|
+
* (parsing-side runs once per file at extract time, emit-side runs
|
|
129
|
+
* once per workspace at resolve time), and merging would create a
|
|
130
|
+
* god-interface that complicates future migrations.
|
|
131
|
+
*
|
|
132
|
+
* - **I8 — Post-finalize hooks may mutate `Scope.typeBindings` and
|
|
133
|
+
* `indexes.bindings`.** `propagateImportedReturnTypes` and
|
|
134
|
+
* `populateNamespaceSiblings` both write to these structures via
|
|
135
|
+
* `as Map<...>` casts through `ReadonlyMap` facades. Downstream
|
|
136
|
+
* consumers MUST NOT freeze or snapshot these maps before all
|
|
137
|
+
* post-finalize hooks have run. The `ReadonlyMap<...>` type on
|
|
138
|
+
* `ScopeResolutionIndexes` is a read-guidance surface for
|
|
139
|
+
* consumers, NOT an immutability promise during the resolve phase.
|
|
140
|
+
*
|
|
141
|
+
* - **I9 — `SemanticModel` is the single authoritative symbol store.**
|
|
142
|
+
* Every symbol-indexed lookup (key = `nodeId | simpleName |
|
|
143
|
+
* qualifiedName | filePath`) resolves through
|
|
144
|
+
* `SemanticModel.{symbols,types,methods,fields}`. Scope-resolution
|
|
145
|
+
* passes MUST NOT maintain parallel owner-keyed or name-keyed
|
|
146
|
+
* symbol indexes — `WorkspaceResolutionIndex` is reserved for
|
|
147
|
+
* `Scope`-valued lookups that `SemanticModel` structurally cannot
|
|
148
|
+
* carry.
|
|
149
|
+
*
|
|
150
|
+
* The `runScopeResolution` orchestrator guarantees this invariant
|
|
151
|
+
* in two steps:
|
|
152
|
+
* 1. The legacy `parse` phase populates `SemanticModel` via
|
|
153
|
+
* `symbolTable.add(...)`. For languages whose extractor
|
|
154
|
+
* resolves `enclosingClassId` at parse time, class-body defs
|
|
155
|
+
* are correctly owner-keyed there.
|
|
156
|
+
* 2. The `reconcileOwnership` pass runs after
|
|
157
|
+
* `provider.populateOwners(parsed)` and registers any def in
|
|
158
|
+
* `parsed.localDefs[i]` with a corrected `ownerId` that the
|
|
159
|
+
* legacy pass missed (primarily Python class-body methods).
|
|
160
|
+
* Idempotent — duplicates are skipped by `nodeId`.
|
|
161
|
+
*
|
|
162
|
+
* Contract for consumers: `model` is `MutableSemanticModel` only
|
|
163
|
+
* during those two write phases. Downstream passes receive a
|
|
164
|
+
* narrowed `SemanticModel` (read-only) handle. This is enforced by
|
|
165
|
+
* `runScopeResolution`'s type-level narrowing at the phase
|
|
166
|
+
* boundary.
|
|
167
|
+
*
|
|
168
|
+
* The dev-mode runtime validator (`validateOwnershipParity`)
|
|
169
|
+
* surfaces any drift between `parsed.localDefs` ownership and the
|
|
170
|
+
* registries via `onWarn` when
|
|
171
|
+
* `NODE_ENV !== 'production' && VALIDATE_SEMANTIC_MODEL !== '0'`.
|
|
172
|
+
*
|
|
173
|
+
* This invariant is a **transitional shim**: the architectural
|
|
174
|
+
* end state is for every language's parse-time extractor to emit
|
|
175
|
+
* the correct `ownerId` directly, removing the need for
|
|
176
|
+
* reconciliation. Tracked as a follow-up; see ARCHITECTURE.md §
|
|
177
|
+
* "Semantic-model source of truth".
|
|
178
|
+
*
|
|
179
|
+
* ## Semantic-model source of truth
|
|
180
|
+
*
|
|
181
|
+
* `ParsedFile` (from `gitnexus-shared/src/scope-resolution/parsed-file.ts`)
|
|
182
|
+
* is the single semantic model consumed by both the legacy DAG and the
|
|
183
|
+
* scope-resolution pipeline. Scope-resolution passes MUST NOT build a
|
|
184
|
+
* parallel parse representation; if a pass needs AST-level facts that
|
|
185
|
+
* `ParsedFile` doesn't expose, it should reuse the orchestrator's
|
|
186
|
+
* `treeCache` (see `RunScopeResolutionInput.treeCache`) rather than
|
|
187
|
+
* re-invoke `parser.parse(...)` on its own.
|
|
188
|
+
*
|
|
189
|
+
* ## Same-graph guarantee
|
|
190
|
+
*
|
|
191
|
+
* Edges emitted by `runScopeResolution` and edges emitted by the legacy
|
|
192
|
+
* DAG are indistinguishable to downstream consumers:
|
|
193
|
+
* - Node identity: same `generateId(...)` helper, same qualified-name
|
|
194
|
+
* keyspace, same File/Folder/Method/Class node labels.
|
|
195
|
+
* - Edge vocabulary: `'import-resolved' | 'global' | 'local-call' |
|
|
196
|
+
* 'same-file' | 'interface-dispatch' | 'read' | 'write'` — both
|
|
197
|
+
* paths emit the same reasons (see
|
|
198
|
+
* `gitnexus/src/core/ingestion/call-processor.ts` for the legacy
|
|
199
|
+
* emitter and `passes/receiver-bound-calls.ts` /
|
|
200
|
+
* `passes/free-call-fallback.ts` for the scope-resolution emitters).
|
|
201
|
+
* - Overload disambiguation: both paths use
|
|
202
|
+
* `generateId('Method', ...)` suffixed with `parameterTypes` when a
|
|
203
|
+
* method has overloads — see `graph-bridge/ids.ts`.
|
|
204
|
+
*
|
|
205
|
+
* The CI parity workflow (`.github/workflows/ci-scope-parity.yml`)
|
|
206
|
+
* runs both paths on every migrated language's fixture corpus and
|
|
207
|
+
* fails if the graph outputs diverge.
|
|
208
|
+
*
|
|
209
|
+
* Plan that introduced most of these invariants:
|
|
210
|
+
* `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
211
|
+
*/
|
|
212
|
+
import type { BindingRef, Callsite, ParsedFile, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
|
|
213
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
214
|
+
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
215
|
+
import { LanguageProvider } from '../../language-provider.js';
|
|
216
|
+
import { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
217
|
+
/** A LinearizeStrategy receives the full ancestor map so C3-style
|
|
218
|
+
* algorithms (which need to merge each parent's MRO) can implement
|
|
219
|
+
* themselves. Python's depth-first first-seen only consumes
|
|
220
|
+
* `directParents` and `parentsByDefId`. */
|
|
221
|
+
export type LinearizeStrategy = (classDefId: string, directParents: readonly string[], parentsByDefId: ReadonlyMap<string, readonly string[]>) => string[];
|
|
222
|
+
/** Result of `ScopeResolver.arityCompatibility` — mirrors `RegistryProviders.arityCompatibility`. */
|
|
223
|
+
export type ArityVerdict = 'compatible' | 'unknown' | 'incompatible';
|
|
224
|
+
export interface ScopeResolver {
|
|
225
|
+
/** Identity for telemetry + per-language flag check. */
|
|
226
|
+
readonly language: SupportedLanguages;
|
|
227
|
+
/** Parsing-side hook bag consumed by `extractParsedFile`. The
|
|
228
|
+
* same `LanguageProvider` reference flows through both interfaces
|
|
229
|
+
* to keep parsing and emit semantics in sync. */
|
|
230
|
+
readonly languageProvider: LanguageProvider;
|
|
231
|
+
/** Reason text on emitted IMPORTS edges. Mirrors the legacy DAG's
|
|
232
|
+
* per-language convention so consumers asserting on reason keep
|
|
233
|
+
* working. */
|
|
234
|
+
readonly importEdgeReason: string;
|
|
235
|
+
/**
|
|
236
|
+
* Resolve an import statement's `targetRaw` (e.g. `models.user`,
|
|
237
|
+
* `./helpers`) into an absolute repo-relative file path, or `null`
|
|
238
|
+
* for unresolvable / external modules.
|
|
239
|
+
*
|
|
240
|
+
* Called once per `ParsedImport` during `finalizeScopeModel`. The
|
|
241
|
+
* Python implementation wraps `resolvePythonImportTarget`.
|
|
242
|
+
*
|
|
243
|
+
* `allFilePaths` is the workspace's file set — needed by per-language
|
|
244
|
+
* resolvers that must distinguish "this module exists in the repo"
|
|
245
|
+
* from "this module is external" (Python's fallback resolver, for
|
|
246
|
+
* example).
|
|
247
|
+
*/
|
|
248
|
+
resolveImportTarget(targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string>): string | null;
|
|
249
|
+
/**
|
|
250
|
+
* Per-scope binding-merge precedence. The shared finalize pass
|
|
251
|
+
* collects bindings from multiple sources (local declarations,
|
|
252
|
+
* imports, namespace, wildcard, reexport) and asks the language
|
|
253
|
+
* how to order them.
|
|
254
|
+
*
|
|
255
|
+
* Python uses LEGB: local > import / namespace / reexport > wildcard.
|
|
256
|
+
*/
|
|
257
|
+
mergeBindings(existing: readonly BindingRef[], incoming: readonly BindingRef[], scopeId: ScopeId): BindingRef[];
|
|
258
|
+
/**
|
|
259
|
+
* Per-language arity compatibility between a callsite and a
|
|
260
|
+
* candidate def. The shared `MethodRegistry.lookup` consults this
|
|
261
|
+
* to penalize incompatible candidates without disqualifying them
|
|
262
|
+
* outright. Note arg order — `(callsite, def)` matches the
|
|
263
|
+
* `RegistryProviders` contract; some legacy provider impls use
|
|
264
|
+
* `(def, callsite)` and need an adapter at the wiring site.
|
|
265
|
+
*/
|
|
266
|
+
arityCompatibility(callsite: Callsite, def: SymbolDefinition): ArityVerdict;
|
|
267
|
+
/**
|
|
268
|
+
* Compute the method-dispatch order for every Class def in the
|
|
269
|
+
* workspace. Python uses depth-first first-seen via
|
|
270
|
+
* `pythonLinearize`; future languages may use C3 (Ruby, Python's
|
|
271
|
+
* real MRO when we go beyond the simplified walk), single-
|
|
272
|
+
* inheritance only (Java), or empty-map (languages without
|
|
273
|
+
* inheritance).
|
|
274
|
+
*/
|
|
275
|
+
buildMro(graph: KnowledgeGraph, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup): Map<string, string[]>;
|
|
276
|
+
/**
|
|
277
|
+
* Mutate `parsed.localDefs[i].ownerId` to point at the structural
|
|
278
|
+
* owner. Python's rule: methods (Function defs whose parent scope
|
|
279
|
+
* is Class) AND class-body fields (defs in Class scopes) are owned
|
|
280
|
+
* by the enclosing class. Other languages may have richer rules
|
|
281
|
+
* (e.g., Java inner-class qualification).
|
|
282
|
+
*/
|
|
283
|
+
populateOwners(parsed: ParsedFile): void;
|
|
284
|
+
/**
|
|
285
|
+
* Recognize a `super(...)`-style receiver text. Python returns
|
|
286
|
+
* `/^super\s*\(/.test(t)`. Java returns `t === 'super'`. C++ may
|
|
287
|
+
* also need `this` capture. Languages without inheritance return
|
|
288
|
+
* constant `false`.
|
|
289
|
+
*/
|
|
290
|
+
isSuperReceiver(receiverText: string): boolean;
|
|
291
|
+
/**
|
|
292
|
+
* Whether the orchestrator should run `propagateImportedReturnTypes`
|
|
293
|
+
* after finalize. Default `true`. TypeScript with explicit type
|
|
294
|
+
* exports may want a different propagation strategy and opt out.
|
|
295
|
+
*/
|
|
296
|
+
readonly propagatesReturnTypesAcrossImports?: boolean;
|
|
297
|
+
/**
|
|
298
|
+
* Whether the compound-receiver resolver should fall back to
|
|
299
|
+
* walking field types when method lookup on the receiver's class
|
|
300
|
+
* fails (the "Phase-9C unified fixpoint" heuristic). Default
|
|
301
|
+
* `true`. Strictly-typed languages should set `false` because the
|
|
302
|
+
* heuristic can produce edges that wouldn't survive a real type
|
|
303
|
+
* check.
|
|
304
|
+
*/
|
|
305
|
+
readonly fieldFallbackOnMethodLookup?: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Unwrap a property-style collection accessor on a typed receiver
|
|
308
|
+
* to its element type. Called by `resolveCompoundReceiverClass`
|
|
309
|
+
* when walking dotted member-access chains of the form
|
|
310
|
+
* `receiver.Accessor`. The provider returns the element type's
|
|
311
|
+
* simple name, or `undefined` when the accessor doesn't unwrap —
|
|
312
|
+
* in which case the regular field-walk resumes.
|
|
313
|
+
*
|
|
314
|
+
* Use this only for languages that expose collection views as
|
|
315
|
+
* properties rather than method calls; languages whose collection
|
|
316
|
+
* views are `.values()` / `.keys()` method calls leave this
|
|
317
|
+
* undefined and let the normal call-expression branch handle them.
|
|
318
|
+
*/
|
|
319
|
+
readonly unwrapCollectionAccessor?: (receiverType: string, accessor: string) => string | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* Collapse member-call CALLS edges by `(caller, target)` rather
|
|
322
|
+
* than per-site. Default `false` — scope-resolution's contract
|
|
323
|
+
* invariant is per-site dedup.
|
|
324
|
+
*
|
|
325
|
+
* Enable this when the language's graph convention is one edge per
|
|
326
|
+
* caller/target pair regardless of how many syntactic sites exist,
|
|
327
|
+
* e.g. to match a legacy graph's edge count so downstream
|
|
328
|
+
* consumers don't see a migration-induced inflation.
|
|
329
|
+
*/
|
|
330
|
+
readonly collapseMemberCallsByCallerTarget?: boolean;
|
|
331
|
+
/**
|
|
332
|
+
* Optional post-finalize hook to inject cross-file bindings that
|
|
333
|
+
* aren't modeled via explicit imports. Runs after
|
|
334
|
+
* `buildWorkspaceResolutionIndex` and before
|
|
335
|
+
* `propagateImportedReturnTypes`.
|
|
336
|
+
*
|
|
337
|
+
* Use this for languages where a compiler-implicit visibility rule
|
|
338
|
+
* makes names visible across files without a syntactic import —
|
|
339
|
+
* for example a shared-namespace convention where types declared
|
|
340
|
+
* in the same namespace see each other without a `using` / `import`
|
|
341
|
+
* statement. Languages that require explicit imports for cross-file
|
|
342
|
+
* visibility leave this undefined.
|
|
343
|
+
*/
|
|
344
|
+
readonly populateNamespaceSiblings?: (parsedFiles: readonly ParsedFile[], indexes: ScopeResolutionIndexes, ctx: {
|
|
345
|
+
readonly fileContents: ReadonlyMap<string, string>;
|
|
346
|
+
/** Pre-parsed tree-sitter trees keyed by file path. Same cache
|
|
347
|
+
* the orchestrator hands to `extractParsedFile`; passing it
|
|
348
|
+
* through here lets per-language hooks read the AST without
|
|
349
|
+
* triggering a second parse. Cache miss = the hook re-parses
|
|
350
|
+
* itself; the cache is opt-in for hooks that need AST-level
|
|
351
|
+
* facts beyond what `ParsedFile` exposes. */
|
|
352
|
+
readonly treeCache?: {
|
|
353
|
+
get(filePath: string): unknown;
|
|
354
|
+
};
|
|
355
|
+
}) => void;
|
|
356
|
+
/**
|
|
357
|
+
* Whether the compound-receiver resolver should walk up from a
|
|
358
|
+
* class scope to ancestor (Module) scopes when looking up a
|
|
359
|
+
* method's return-type typeBinding. Default `false`.
|
|
360
|
+
*
|
|
361
|
+
* Set `true` only when the provider stores method return-type
|
|
362
|
+
* bindings on the enclosing Module scope rather than on the class
|
|
363
|
+
* scope. Without this walk-up, chain resolution fails for methods
|
|
364
|
+
* whose return types were hoisted to module scope.
|
|
365
|
+
*
|
|
366
|
+
* Providers that attach return-type bindings directly to the class
|
|
367
|
+
* scope leave this undefined — enabling the walk-up for them would
|
|
368
|
+
* add an unnecessary branch and risk picking up unrelated module-
|
|
369
|
+
* level bindings.
|
|
370
|
+
*/
|
|
371
|
+
readonly hoistTypeBindingsToModule?: boolean;
|
|
372
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScopeResolver` — the per-language contract consumed by the generic
|
|
3
|
+
* scope-resolution orchestrator (`runScopeResolution`).
|
|
4
|
+
*
|
|
5
|
+
* ## Migration cookbook (next language)
|
|
6
|
+
*
|
|
7
|
+
* To add a language to the registry-primary path:
|
|
8
|
+
*
|
|
9
|
+
* 1. Implement `ScopeResolver` in
|
|
10
|
+
* `gitnexus/src/core/ingestion/languages/<lang>/scope-resolver.ts`.
|
|
11
|
+
* Nine required fields (language, languageProvider,
|
|
12
|
+
* importEdgeReason, resolveImportTarget, mergeBindings,
|
|
13
|
+
* arityCompatibility, buildMro, populateOwners, isSuperReceiver)
|
|
14
|
+
* plus optional toggles / hooks:
|
|
15
|
+
* - propagatesReturnTypesAcrossImports (default true)
|
|
16
|
+
* - fieldFallbackOnMethodLookup (default true — turn OFF for
|
|
17
|
+
* statically-typed languages; the heuristic over-connects)
|
|
18
|
+
* - unwrapCollectionAccessor — property-style collection views
|
|
19
|
+
* - collapseMemberCallsByCallerTarget — one edge per caller/target
|
|
20
|
+
* - populateNamespaceSiblings — cross-file implicit visibility
|
|
21
|
+
* - hoistTypeBindingsToModule — enable ONLY when method return
|
|
22
|
+
* types are stored on the enclosing Module scope; most
|
|
23
|
+
* languages attach them to the class scope and leave this off
|
|
24
|
+
* 2. Export a thin entry point:
|
|
25
|
+
* `runYourLangScopeResolution(input) = runScopeResolution(input, yourScopeResolver)`.
|
|
26
|
+
* 3. Register the provider in
|
|
27
|
+
* `gitnexus/src/core/ingestion/scope-resolution/pipeline/registry.ts`
|
|
28
|
+
* (the `SCOPE_RESOLVERS` map).
|
|
29
|
+
* 4. Add `SupportedLanguages.YourLang` to `MIGRATED_LANGUAGES` in
|
|
30
|
+
* `registry-primary-flag.ts`.
|
|
31
|
+
* 5. Verify the resolver integration test at
|
|
32
|
+
* `gitnexus/test/integration/resolvers/<lang>.test.ts` passes
|
|
33
|
+
* under both `REGISTRY_PRIMARY_<LANG>=0` (legacy) and `=1`
|
|
34
|
+
* (registry-primary). The CI parity gate enforces this.
|
|
35
|
+
*
|
|
36
|
+
* No new pipeline phase, no orchestrator copy-paste, no workflow
|
|
37
|
+
* change. The generic `scopeResolutionPhase` and the CI parity
|
|
38
|
+
* workflow auto-discover everything via `MIGRATED_LANGUAGES`.
|
|
39
|
+
*
|
|
40
|
+
* ## ScopeResolver vs LanguageProvider
|
|
41
|
+
*
|
|
42
|
+
* The codebase has two provider contracts. Their lifecycles differ:
|
|
43
|
+
*
|
|
44
|
+
* - `LanguageProvider` (`language-provider.ts`) is the
|
|
45
|
+
* **parsing-side** contract — how to emit captures, classify
|
|
46
|
+
* scopes, interpret imports / typeBindings. ~40 fields covering
|
|
47
|
+
* both legacy and new pipelines. Consumed by `ScopeExtractor`,
|
|
48
|
+
* once per file at extract time.
|
|
49
|
+
* - `ScopeResolver` (this file) is the **emit-side** contract — how
|
|
50
|
+
* the resolution pipeline dispatches references to graph edges.
|
|
51
|
+
* 8 fields total. Consumed by `runScopeResolution`, once per
|
|
52
|
+
* workspace at resolve time.
|
|
53
|
+
*
|
|
54
|
+
* They share three concept names (`arityCompatibility`, `mergeBindings`,
|
|
55
|
+
* `resolveImportTarget`) because the emit pipeline reuses a few
|
|
56
|
+
* finalize hooks. Per-language wiring passes the SAME function
|
|
57
|
+
* reference through both interfaces — no second copy of the logic.
|
|
58
|
+
* Rationale for not collapsing: lifecycle separation, and merging
|
|
59
|
+
* would create a god-interface complicating future migrations.
|
|
60
|
+
*
|
|
61
|
+
* ## Reference implementation
|
|
62
|
+
*
|
|
63
|
+
* `gitnexus/src/core/ingestion/languages/python/scope-resolver.ts` —
|
|
64
|
+
* `pythonScopeResolver` is the canonical example. Read that file when
|
|
65
|
+
* migrating a new language; this interface lists the fields that
|
|
66
|
+
* implementation populates.
|
|
67
|
+
*
|
|
68
|
+
* ## Contract Invariants the orchestrator depends on
|
|
69
|
+
*
|
|
70
|
+
* These are non-obvious behaviors that the orchestrator and the
|
|
71
|
+
* existing Python + C# resolvers depend on. Future implementers will
|
|
72
|
+
* break them silently if not documented.
|
|
73
|
+
*
|
|
74
|
+
* - **I1 — Phase 4 emission order is load-bearing.** `emitReceiverBoundCalls`
|
|
75
|
+
* runs FIRST (populates `handledSites`), then `emitFreeCallFallback`,
|
|
76
|
+
* then `emitReferencesViaLookup` (consumes `handledSites` as a skip
|
|
77
|
+
* set), then `emitImportEdges`. Reordering breaks same-name collision
|
|
78
|
+
* resolution: the shared lookup can mis-resolve `app_metrics.get_metrics()`
|
|
79
|
+
* to a same-named local function, and only the precise per-receiver
|
|
80
|
+
* pass running first prevents the wrong edge.
|
|
81
|
+
*
|
|
82
|
+
* - **I2 — `handledSites` semantics.** A site is added to
|
|
83
|
+
* `handledSites` IFF a `tryEmitEdge` call returned `true` for it.
|
|
84
|
+
* Sites a pass touched but couldn't resolve do NOT get marked —
|
|
85
|
+
* they still get a chance from the shared resolver. Exception:
|
|
86
|
+
* the free-call fallback marks the site unconditionally after
|
|
87
|
+
* attempting emission (even on dedup-collapse), because the
|
|
88
|
+
* per-(caller, target) collapse semantics require multiple call
|
|
89
|
+
* sites in the same caller body not produce multiple edges.
|
|
90
|
+
*
|
|
91
|
+
* - **I3 — `propagateImportedReturnTypes` mutation timing.** The
|
|
92
|
+
* pass mutates `Scope.typeBindings` (a plain `new Map(...)` from
|
|
93
|
+
* `draftToScope`, NOT frozen). It MUST run AFTER `finalizeScopeModel`
|
|
94
|
+
* (so `indexes.bindings` is populated) and BEFORE
|
|
95
|
+
* `resolveReferenceSites` (so resolution sees the propagated types).
|
|
96
|
+
* The pass also re-runs `followChainPostFinalize` on every scope's
|
|
97
|
+
* typeBindings because scope-extractor's pass-4 already ran and
|
|
98
|
+
* missed any chain whose terminal lives in a foreign file.
|
|
99
|
+
*
|
|
100
|
+
* - **I4 — `emitReceiverBoundCalls` case order.** Cases are evaluated
|
|
101
|
+
* in this order; the FIRST that emits an edge wins:
|
|
102
|
+
* 1. super branch (`provider.isSuperReceiver(receiverName)`)
|
|
103
|
+
* 2. Case 0 compound (`receiverName` has `.` or `(`)
|
|
104
|
+
* 3. Case 1 namespace-receiver
|
|
105
|
+
* 4. Case 2 class-name receiver
|
|
106
|
+
* 5. Case 3 dotted typeBinding for namespace prefix
|
|
107
|
+
* 6. Case 3b chain-typebinding (compound resolver)
|
|
108
|
+
* 7. Case 4 simple typeBinding (MRO walk + findOwnedMember)
|
|
109
|
+
* Reordering or merging cases changes resolution semantics. The
|
|
110
|
+
* numbering is part of the contract — keep the comments.
|
|
111
|
+
*
|
|
112
|
+
* - **I5 — Pre-seeding `seen` from `referenceIndex` is forbidden.**
|
|
113
|
+
* Earlier versions of the receiver-bound pass pre-populated `seen`
|
|
114
|
+
* to avoid double-emit. After Phase 4 was reordered, pre-seeding
|
|
115
|
+
* became actively harmful: it suppresses correct emissions for
|
|
116
|
+
* sites the shared resolver happened to resolve to a wrong target.
|
|
117
|
+
* The orchestrator MUST NOT pre-seed.
|
|
118
|
+
*
|
|
119
|
+
* - **I6 — `Scope.typeBindings` is mutable post-finalize.** `draftToScope`
|
|
120
|
+
* (in `scope-extractor.ts`) builds `typeBindings` as a plain
|
|
121
|
+
* `new Map(...)` — not frozen, intentionally. Passes below rely on
|
|
122
|
+
* this. Do NOT freeze `typeBindings` in any downstream refactor.
|
|
123
|
+
*
|
|
124
|
+
* - **I7 — `ScopeResolver` and `LanguageProvider` are distinct contracts.**
|
|
125
|
+
* Python and C# pass the SAME function reference through both
|
|
126
|
+
* interfaces where they share a hook name — no second copy of the
|
|
127
|
+
* logic. Rationale for not collapsing them: lifecycles differ
|
|
128
|
+
* (parsing-side runs once per file at extract time, emit-side runs
|
|
129
|
+
* once per workspace at resolve time), and merging would create a
|
|
130
|
+
* god-interface that complicates future migrations.
|
|
131
|
+
*
|
|
132
|
+
* - **I8 — Post-finalize hooks may mutate `Scope.typeBindings` and
|
|
133
|
+
* `indexes.bindings`.** `propagateImportedReturnTypes` and
|
|
134
|
+
* `populateNamespaceSiblings` both write to these structures via
|
|
135
|
+
* `as Map<...>` casts through `ReadonlyMap` facades. Downstream
|
|
136
|
+
* consumers MUST NOT freeze or snapshot these maps before all
|
|
137
|
+
* post-finalize hooks have run. The `ReadonlyMap<...>` type on
|
|
138
|
+
* `ScopeResolutionIndexes` is a read-guidance surface for
|
|
139
|
+
* consumers, NOT an immutability promise during the resolve phase.
|
|
140
|
+
*
|
|
141
|
+
* - **I9 — `SemanticModel` is the single authoritative symbol store.**
|
|
142
|
+
* Every symbol-indexed lookup (key = `nodeId | simpleName |
|
|
143
|
+
* qualifiedName | filePath`) resolves through
|
|
144
|
+
* `SemanticModel.{symbols,types,methods,fields}`. Scope-resolution
|
|
145
|
+
* passes MUST NOT maintain parallel owner-keyed or name-keyed
|
|
146
|
+
* symbol indexes — `WorkspaceResolutionIndex` is reserved for
|
|
147
|
+
* `Scope`-valued lookups that `SemanticModel` structurally cannot
|
|
148
|
+
* carry.
|
|
149
|
+
*
|
|
150
|
+
* The `runScopeResolution` orchestrator guarantees this invariant
|
|
151
|
+
* in two steps:
|
|
152
|
+
* 1. The legacy `parse` phase populates `SemanticModel` via
|
|
153
|
+
* `symbolTable.add(...)`. For languages whose extractor
|
|
154
|
+
* resolves `enclosingClassId` at parse time, class-body defs
|
|
155
|
+
* are correctly owner-keyed there.
|
|
156
|
+
* 2. The `reconcileOwnership` pass runs after
|
|
157
|
+
* `provider.populateOwners(parsed)` and registers any def in
|
|
158
|
+
* `parsed.localDefs[i]` with a corrected `ownerId` that the
|
|
159
|
+
* legacy pass missed (primarily Python class-body methods).
|
|
160
|
+
* Idempotent — duplicates are skipped by `nodeId`.
|
|
161
|
+
*
|
|
162
|
+
* Contract for consumers: `model` is `MutableSemanticModel` only
|
|
163
|
+
* during those two write phases. Downstream passes receive a
|
|
164
|
+
* narrowed `SemanticModel` (read-only) handle. This is enforced by
|
|
165
|
+
* `runScopeResolution`'s type-level narrowing at the phase
|
|
166
|
+
* boundary.
|
|
167
|
+
*
|
|
168
|
+
* The dev-mode runtime validator (`validateOwnershipParity`)
|
|
169
|
+
* surfaces any drift between `parsed.localDefs` ownership and the
|
|
170
|
+
* registries via `onWarn` when
|
|
171
|
+
* `NODE_ENV !== 'production' && VALIDATE_SEMANTIC_MODEL !== '0'`.
|
|
172
|
+
*
|
|
173
|
+
* This invariant is a **transitional shim**: the architectural
|
|
174
|
+
* end state is for every language's parse-time extractor to emit
|
|
175
|
+
* the correct `ownerId` directly, removing the need for
|
|
176
|
+
* reconciliation. Tracked as a follow-up; see ARCHITECTURE.md §
|
|
177
|
+
* "Semantic-model source of truth".
|
|
178
|
+
*
|
|
179
|
+
* ## Semantic-model source of truth
|
|
180
|
+
*
|
|
181
|
+
* `ParsedFile` (from `gitnexus-shared/src/scope-resolution/parsed-file.ts`)
|
|
182
|
+
* is the single semantic model consumed by both the legacy DAG and the
|
|
183
|
+
* scope-resolution pipeline. Scope-resolution passes MUST NOT build a
|
|
184
|
+
* parallel parse representation; if a pass needs AST-level facts that
|
|
185
|
+
* `ParsedFile` doesn't expose, it should reuse the orchestrator's
|
|
186
|
+
* `treeCache` (see `RunScopeResolutionInput.treeCache`) rather than
|
|
187
|
+
* re-invoke `parser.parse(...)` on its own.
|
|
188
|
+
*
|
|
189
|
+
* ## Same-graph guarantee
|
|
190
|
+
*
|
|
191
|
+
* Edges emitted by `runScopeResolution` and edges emitted by the legacy
|
|
192
|
+
* DAG are indistinguishable to downstream consumers:
|
|
193
|
+
* - Node identity: same `generateId(...)` helper, same qualified-name
|
|
194
|
+
* keyspace, same File/Folder/Method/Class node labels.
|
|
195
|
+
* - Edge vocabulary: `'import-resolved' | 'global' | 'local-call' |
|
|
196
|
+
* 'same-file' | 'interface-dispatch' | 'read' | 'write'` — both
|
|
197
|
+
* paths emit the same reasons (see
|
|
198
|
+
* `gitnexus/src/core/ingestion/call-processor.ts` for the legacy
|
|
199
|
+
* emitter and `passes/receiver-bound-calls.ts` /
|
|
200
|
+
* `passes/free-call-fallback.ts` for the scope-resolution emitters).
|
|
201
|
+
* - Overload disambiguation: both paths use
|
|
202
|
+
* `generateId('Method', ...)` suffixed with `parameterTypes` when a
|
|
203
|
+
* method has overloads — see `graph-bridge/ids.ts`.
|
|
204
|
+
*
|
|
205
|
+
* The CI parity workflow (`.github/workflows/ci-scope-parity.yml`)
|
|
206
|
+
* runs both paths on every migrated language's fixture corpus and
|
|
207
|
+
* fails if the graph outputs diverge.
|
|
208
|
+
*
|
|
209
|
+
* Plan that introduced most of these invariants:
|
|
210
|
+
* `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
211
|
+
*/
|
|
212
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph edge emission primitives.
|
|
3
|
+
*
|
|
4
|
+
* Two functions:
|
|
5
|
+
* - `mapReferenceKindToEdgeType` — translate a scope-resolution
|
|
6
|
+
* `Reference.kind` into the corresponding graph edge type.
|
|
7
|
+
* - `tryEmitEdge` — given a reference site + target def, resolve
|
|
8
|
+
* caller + target to graph ids and emit the edge with
|
|
9
|
+
* language-provided reason text, dedup-keyed by
|
|
10
|
+
* `(edgeType, callerId, targetId, line, col)`.
|
|
11
|
+
*
|
|
12
|
+
* Next-consumer contract: any language provider can call `tryEmitEdge`
|
|
13
|
+
* from its own post-pass to emit edges it resolves Python-specific
|
|
14
|
+
* (or TypeScript-specific, etc.) logic. The dedup key is
|
|
15
|
+
* language-agnostic — no language needs to change it.
|
|
16
|
+
*/
|
|
17
|
+
import type { Reference, ScopeId, SymbolDefinition } from '../../../../_shared/index.js';
|
|
18
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
19
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
20
|
+
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
21
|
+
/**
|
|
22
|
+
* Map a `Reference.kind` to a graph edge type. `import-use` is dropped
|
|
23
|
+
* (no edge type today — provenance lives on the IMPORTS edge emitted
|
|
24
|
+
* by `emitImportEdges`).
|
|
25
|
+
*/
|
|
26
|
+
export declare function mapReferenceKindToEdgeType(kind: Reference['kind']): 'CALLS' | 'ACCESSES' | 'EXTENDS' | 'USES' | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve caller + target to graph ids and emit the edge. Returns true
|
|
29
|
+
* if the edge was emitted (not deduped, not skipped).
|
|
30
|
+
*
|
|
31
|
+
* `seen` is a language-shared dedup set keyed by
|
|
32
|
+
* `${edgeType}:${callerGraphId}->${targetGraphId}:${line}:${col}` so
|
|
33
|
+
* multiple language-specific post-passes can share it and never
|
|
34
|
+
* double-emit a resolution one of them already produced.
|
|
35
|
+
*/
|
|
36
|
+
export declare function tryEmitEdge(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, nodeLookup: GraphNodeLookup, site: {
|
|
37
|
+
readonly inScope: ScopeId;
|
|
38
|
+
readonly atRange: {
|
|
39
|
+
startLine: number;
|
|
40
|
+
startCol: number;
|
|
41
|
+
};
|
|
42
|
+
readonly kind: string;
|
|
43
|
+
}, targetDef: SymbolDefinition, reason: string, seen: Set<string>, confidence?: number, collapseByCallerTarget?: boolean): boolean;
|