gitnexus 1.6.3-rc.9 → 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 +4 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +2 -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/evidence.js +5 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js +21 -5
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
- 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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: scopeResolution
|
|
3
|
+
*
|
|
4
|
+
* Generic registry-primary resolution phase (RFC #909 Ring 3).
|
|
5
|
+
*
|
|
6
|
+
* For every language in `MIGRATED_LANGUAGES` (per-language flag set)
|
|
7
|
+
* whose provider is registered in `SCOPE_RESOLVERS`:
|
|
8
|
+
* 1. Filter scanned files by language extension.
|
|
9
|
+
* 2. Read file contents.
|
|
10
|
+
* 3. Drive the scope-based pipeline end-to-end via the generic
|
|
11
|
+
* `runScopeResolution(input, provider)` orchestrator.
|
|
12
|
+
* 4. Emit IMPORTS / CALLS / ACCESSES / INHERITS / USES edges.
|
|
13
|
+
*
|
|
14
|
+
* Pairs with the per-language gates in `import-processor.ts` and
|
|
15
|
+
* `call-processor.ts` that skip files when their language is registry-
|
|
16
|
+
* primary, so we don't double-emit edges from both code paths.
|
|
17
|
+
*
|
|
18
|
+
* Adding a language is two changes:
|
|
19
|
+
* - Implement `ScopeResolver` in `languages/<lang>/scope-resolver.ts`
|
|
20
|
+
* and register it in `scope-resolution/pipeline/registry.ts`.
|
|
21
|
+
* - Add the language to `MIGRATED_LANGUAGES` in
|
|
22
|
+
* `registry-primary-flag.ts`.
|
|
23
|
+
*
|
|
24
|
+
* @deps parse (needs Symbol nodes already in the graph so emit-references
|
|
25
|
+
* can attach edges to existing Function/Method/Class nodes)
|
|
26
|
+
* @reads scannedFiles
|
|
27
|
+
* @writes graph (IMPORTS, CALLS, ACCESSES, INHERITS, USES)
|
|
28
|
+
*/
|
|
29
|
+
import { getPhaseOutput } from '../../pipeline-phases/types.js';
|
|
30
|
+
import { isRegistryPrimary } from '../../registry-primary-flag.js';
|
|
31
|
+
import { getLanguageFromFilename } from '../../../../_shared/index.js';
|
|
32
|
+
import { readFileContents } from '../../filesystem-walker.js';
|
|
33
|
+
import { runScopeResolution } from './run.js';
|
|
34
|
+
import { SCOPE_RESOLVERS } from './registry.js';
|
|
35
|
+
import { isDev } from '../../utils/env.js';
|
|
36
|
+
const NOOP_OUTPUT = Object.freeze({
|
|
37
|
+
ran: false,
|
|
38
|
+
filesProcessed: 0,
|
|
39
|
+
importsEmitted: 0,
|
|
40
|
+
referenceEdgesEmitted: 0,
|
|
41
|
+
perLanguage: new Map(),
|
|
42
|
+
});
|
|
43
|
+
export const scopeResolutionPhase = {
|
|
44
|
+
name: 'scopeResolution',
|
|
45
|
+
// Depends on `parse` because emit-references attaches edges to
|
|
46
|
+
// already-existing Symbol nodes (Function/Method/Class). The legacy
|
|
47
|
+
// `parse` phase still creates those nodes; we only replace the
|
|
48
|
+
// import + call resolution layer.
|
|
49
|
+
//
|
|
50
|
+
// Also depends on `crossFile` — we don't read crossFile's output
|
|
51
|
+
// directly (we have our own cross-file resolution), but crossFile
|
|
52
|
+
// writes EXTENDS edges that `buildMro` consumes via
|
|
53
|
+
// `iterRelationshipsByType('EXTENDS')`. Declaring the dep pins the
|
|
54
|
+
// ordering explicitly: without it, Kahn's runner could schedule
|
|
55
|
+
// scopeResolution before crossFile (both unblock after parse), and
|
|
56
|
+
// the MRO walk would miss heritage edges crossFile later adds.
|
|
57
|
+
deps: ['parse', 'crossFile', 'structure'],
|
|
58
|
+
async execute(ctx, deps) {
|
|
59
|
+
const { scannedFiles } = getPhaseOutput(deps, 'structure');
|
|
60
|
+
// Reach into the parse phase's AST cache so per-file extract can
|
|
61
|
+
// skip a second tree-sitter parse. Cache miss is safe (re-parses).
|
|
62
|
+
// Worker-mode parses leave the cache empty for those files; they
|
|
63
|
+
// also fall back to a fresh parse — no correctness impact.
|
|
64
|
+
const parseOutput = getPhaseOutput(deps, 'parse');
|
|
65
|
+
const { scopeTreeCache, resolutionContext } = parseOutput;
|
|
66
|
+
// SemanticModel populated during `parse`: scope-resolution consumes
|
|
67
|
+
// TypeRegistry / MethodRegistry / SymbolTable lookups instead of
|
|
68
|
+
// rebuilding parallel indexes. See ARCHITECTURE.md § "Semantic-model
|
|
69
|
+
// source of truth".
|
|
70
|
+
const model = resolutionContext.model;
|
|
71
|
+
let totalFiles = 0;
|
|
72
|
+
let totalImports = 0;
|
|
73
|
+
let totalRefs = 0;
|
|
74
|
+
let anyRan = false;
|
|
75
|
+
const perLanguage = new Map();
|
|
76
|
+
for (const [lang, provider] of SCOPE_RESOLVERS) {
|
|
77
|
+
if (!isRegistryPrimary(lang))
|
|
78
|
+
continue;
|
|
79
|
+
const langFiles = scannedFiles.filter((f) => getLanguageFromFilename(f.path) === lang);
|
|
80
|
+
if (langFiles.length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
const filePaths = langFiles.map((f) => f.path);
|
|
83
|
+
const contents = await readFileContents(ctx.repoPath, filePaths);
|
|
84
|
+
const files = [];
|
|
85
|
+
for (const fp of filePaths) {
|
|
86
|
+
const content = contents.get(fp);
|
|
87
|
+
if (content !== undefined)
|
|
88
|
+
files.push({ path: fp, content });
|
|
89
|
+
}
|
|
90
|
+
const stats = runScopeResolution({
|
|
91
|
+
graph: ctx.graph,
|
|
92
|
+
model,
|
|
93
|
+
files,
|
|
94
|
+
treeCache: scopeTreeCache,
|
|
95
|
+
onWarn: (msg) => {
|
|
96
|
+
if (isDev)
|
|
97
|
+
console.warn(`[scope-resolution:${lang}] ${msg}`);
|
|
98
|
+
},
|
|
99
|
+
}, provider);
|
|
100
|
+
anyRan = true;
|
|
101
|
+
totalFiles += stats.filesProcessed;
|
|
102
|
+
totalImports += stats.importsEmitted;
|
|
103
|
+
totalRefs += stats.referenceEdgesEmitted;
|
|
104
|
+
perLanguage.set(lang, {
|
|
105
|
+
filesProcessed: stats.filesProcessed,
|
|
106
|
+
importsEmitted: stats.importsEmitted,
|
|
107
|
+
referenceEdgesEmitted: stats.referenceEdgesEmitted,
|
|
108
|
+
});
|
|
109
|
+
if (isDev) {
|
|
110
|
+
console.log(`[scope-resolution:${lang}] ${stats.filesProcessed} files → ${stats.importsEmitted} IMPORTS + ${stats.referenceEdgesEmitted} reference edges (${stats.resolve.unresolved} unresolved sites, ${stats.referenceSkipped} skipped)`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Dispose the cross-phase Tree cache — scope-resolution is the
|
|
114
|
+
// only consumer. Holding Trees past this point is pure memory
|
|
115
|
+
// pressure: downstream phases (mro, community, csv-generator)
|
|
116
|
+
// never read them, and tree-sitter Trees hold native-heap memory
|
|
117
|
+
// under WASM runtimes. ASTCache.clear() fires the LRU dispose
|
|
118
|
+
// handler which calls tree.delete?.() on each retained Tree.
|
|
119
|
+
scopeTreeCache.clear();
|
|
120
|
+
if (!anyRan)
|
|
121
|
+
return NOOP_OUTPUT;
|
|
122
|
+
return {
|
|
123
|
+
ran: true,
|
|
124
|
+
filesProcessed: totalFiles,
|
|
125
|
+
importsEmitted: totalImports,
|
|
126
|
+
referenceEdgesEmitted: totalRefs,
|
|
127
|
+
perLanguage,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconcile scope-resolution's ownership view into the SemanticModel.
|
|
3
|
+
*
|
|
4
|
+
* For migrated languages (Python in particular) the legacy `parse` phase
|
|
5
|
+
* emits class-body callables without `ownerId` because
|
|
6
|
+
* `parsing-processor`'s `resolveEnclosingOwner` is language-dependent and
|
|
7
|
+
* not every extractor carries the enclosing-class info at parse time.
|
|
8
|
+
* Scope-resolution later calls `provider.populateOwners(parsed)`, which
|
|
9
|
+
* stamps the correct `ownerId` onto `parsed.localDefs[i]`. This pass
|
|
10
|
+
* mirrors those corrections into `model.methods` and `model.fields` so
|
|
11
|
+
* downstream passes can consult `SemanticModel` as the single
|
|
12
|
+
* authoritative owner-keyed index — no parallel scope-resolution
|
|
13
|
+
* registry is needed.
|
|
14
|
+
*
|
|
15
|
+
* ## Single-source-of-truth invariant (I9)
|
|
16
|
+
*
|
|
17
|
+
* After this pass runs, every `def in parsed.localDefs` with a non-
|
|
18
|
+
* undefined `ownerId` is reachable via either:
|
|
19
|
+
* - `model.methods.lookupAllByOwner(ownerId, simpleName)` — if the
|
|
20
|
+
* def is a Method / Function / Constructor, OR
|
|
21
|
+
* - `model.fields.lookupFieldByOwner(ownerId, simpleName)` — if the
|
|
22
|
+
* def is a Property / Variable.
|
|
23
|
+
*
|
|
24
|
+
* This invariant is the foundation of Contract Invariant I9
|
|
25
|
+
* (`contract/scope-resolver.ts`): scope-resolution passes MUST read
|
|
26
|
+
* symbol-keyed lookups exclusively from `SemanticModel`.
|
|
27
|
+
*
|
|
28
|
+
* ## Idempotency
|
|
29
|
+
*
|
|
30
|
+
* The pass skips registration when `(ownerId, simpleName)` already
|
|
31
|
+
* contains a def with matching `nodeId`. Safe to call multiple times
|
|
32
|
+
* or after a language whose legacy extractor does populate `ownerId`
|
|
33
|
+
* (C#) — no duplicates are introduced.
|
|
34
|
+
*
|
|
35
|
+
* ## Transitional shim
|
|
36
|
+
*
|
|
37
|
+
* This reconciliation pass is an explicit shim. The architectural end
|
|
38
|
+
* state is for the legacy extractor to emit the correct `ownerId` for
|
|
39
|
+
* every language at parse time, removing the need for a second pass.
|
|
40
|
+
* See ARCHITECTURE.md § "Semantic-model source of truth" for the
|
|
41
|
+
* follow-up plan.
|
|
42
|
+
*/
|
|
43
|
+
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
44
|
+
import type { MutableSemanticModel, SemanticModel } from '../../model/semantic-model.js';
|
|
45
|
+
export interface ReconcileStats {
|
|
46
|
+
/** Method/Function/Constructor defs registered into MethodRegistry. */
|
|
47
|
+
readonly methodsRegistered: number;
|
|
48
|
+
/** Property/Variable defs registered into FieldRegistry. */
|
|
49
|
+
readonly fieldsRegistered: number;
|
|
50
|
+
/** Defs already present (idempotent skip). */
|
|
51
|
+
readonly skippedAlreadyPresent: number;
|
|
52
|
+
}
|
|
53
|
+
export declare function reconcileOwnership(parsedFiles: readonly ParsedFile[], model: MutableSemanticModel): ReconcileStats;
|
|
54
|
+
/**
|
|
55
|
+
* Debug-mode parity validator. Runs only when
|
|
56
|
+
* `VALIDATE_SEMANTIC_MODEL !== '0'` AND `NODE_ENV !== 'production'`.
|
|
57
|
+
*
|
|
58
|
+
* Iterates every def in `parsedFiles[i].localDefs` with an `ownerId`
|
|
59
|
+
* and asserts it is reachable via `model.methods.lookupAllByOwner` or
|
|
60
|
+
* `model.fields.lookupFieldByOwner`. On mismatch: emits a warning via
|
|
61
|
+
* `onWarn` — never throws, mirroring the pipeline's soft-fail posture.
|
|
62
|
+
*
|
|
63
|
+
* This is the enforcement of Contract Invariant I9 at runtime. In
|
|
64
|
+
* production it is a no-op; in development it surfaces drift between
|
|
65
|
+
* `parsed.localDefs` and `SemanticModel` that would otherwise silently
|
|
66
|
+
* produce wrong edges.
|
|
67
|
+
*/
|
|
68
|
+
export declare function validateOwnershipParity(parsedFiles: readonly ParsedFile[], model: SemanticModel, onWarn: (message: string) => void): number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconcile scope-resolution's ownership view into the SemanticModel.
|
|
3
|
+
*
|
|
4
|
+
* For migrated languages (Python in particular) the legacy `parse` phase
|
|
5
|
+
* emits class-body callables without `ownerId` because
|
|
6
|
+
* `parsing-processor`'s `resolveEnclosingOwner` is language-dependent and
|
|
7
|
+
* not every extractor carries the enclosing-class info at parse time.
|
|
8
|
+
* Scope-resolution later calls `provider.populateOwners(parsed)`, which
|
|
9
|
+
* stamps the correct `ownerId` onto `parsed.localDefs[i]`. This pass
|
|
10
|
+
* mirrors those corrections into `model.methods` and `model.fields` so
|
|
11
|
+
* downstream passes can consult `SemanticModel` as the single
|
|
12
|
+
* authoritative owner-keyed index — no parallel scope-resolution
|
|
13
|
+
* registry is needed.
|
|
14
|
+
*
|
|
15
|
+
* ## Single-source-of-truth invariant (I9)
|
|
16
|
+
*
|
|
17
|
+
* After this pass runs, every `def in parsed.localDefs` with a non-
|
|
18
|
+
* undefined `ownerId` is reachable via either:
|
|
19
|
+
* - `model.methods.lookupAllByOwner(ownerId, simpleName)` — if the
|
|
20
|
+
* def is a Method / Function / Constructor, OR
|
|
21
|
+
* - `model.fields.lookupFieldByOwner(ownerId, simpleName)` — if the
|
|
22
|
+
* def is a Property / Variable.
|
|
23
|
+
*
|
|
24
|
+
* This invariant is the foundation of Contract Invariant I9
|
|
25
|
+
* (`contract/scope-resolver.ts`): scope-resolution passes MUST read
|
|
26
|
+
* symbol-keyed lookups exclusively from `SemanticModel`.
|
|
27
|
+
*
|
|
28
|
+
* ## Idempotency
|
|
29
|
+
*
|
|
30
|
+
* The pass skips registration when `(ownerId, simpleName)` already
|
|
31
|
+
* contains a def with matching `nodeId`. Safe to call multiple times
|
|
32
|
+
* or after a language whose legacy extractor does populate `ownerId`
|
|
33
|
+
* (C#) — no duplicates are introduced.
|
|
34
|
+
*
|
|
35
|
+
* ## Transitional shim
|
|
36
|
+
*
|
|
37
|
+
* This reconciliation pass is an explicit shim. The architectural end
|
|
38
|
+
* state is for the legacy extractor to emit the correct `ownerId` for
|
|
39
|
+
* every language at parse time, removing the need for a second pass.
|
|
40
|
+
* See ARCHITECTURE.md § "Semantic-model source of truth" for the
|
|
41
|
+
* follow-up plan.
|
|
42
|
+
*/
|
|
43
|
+
import { simpleQualifiedName } from '../graph-bridge/ids.js';
|
|
44
|
+
export function reconcileOwnership(parsedFiles, model) {
|
|
45
|
+
let methodsRegistered = 0;
|
|
46
|
+
let fieldsRegistered = 0;
|
|
47
|
+
let skippedAlreadyPresent = 0;
|
|
48
|
+
for (const parsed of parsedFiles) {
|
|
49
|
+
for (const def of parsed.localDefs) {
|
|
50
|
+
const ownerId = def.ownerId;
|
|
51
|
+
if (ownerId === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
const simple = simpleQualifiedName(def);
|
|
54
|
+
if (simple === undefined)
|
|
55
|
+
continue;
|
|
56
|
+
if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
|
|
57
|
+
const existing = model.methods.lookupAllByOwner(ownerId, simple);
|
|
58
|
+
if (existing.some((e) => e.nodeId === def.nodeId)) {
|
|
59
|
+
skippedAlreadyPresent++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
model.methods.register(ownerId, simple, def);
|
|
63
|
+
methodsRegistered++;
|
|
64
|
+
}
|
|
65
|
+
else if (def.type === 'Property' || def.type === 'Variable') {
|
|
66
|
+
const existing = model.fields.lookupFieldByOwner(ownerId, simple);
|
|
67
|
+
if (existing !== undefined && existing.nodeId === def.nodeId) {
|
|
68
|
+
skippedAlreadyPresent++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
model.fields.register(ownerId, simple, def);
|
|
72
|
+
fieldsRegistered++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { methodsRegistered, fieldsRegistered, skippedAlreadyPresent };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Debug-mode parity validator. Runs only when
|
|
80
|
+
* `VALIDATE_SEMANTIC_MODEL !== '0'` AND `NODE_ENV !== 'production'`.
|
|
81
|
+
*
|
|
82
|
+
* Iterates every def in `parsedFiles[i].localDefs` with an `ownerId`
|
|
83
|
+
* and asserts it is reachable via `model.methods.lookupAllByOwner` or
|
|
84
|
+
* `model.fields.lookupFieldByOwner`. On mismatch: emits a warning via
|
|
85
|
+
* `onWarn` — never throws, mirroring the pipeline's soft-fail posture.
|
|
86
|
+
*
|
|
87
|
+
* This is the enforcement of Contract Invariant I9 at runtime. In
|
|
88
|
+
* production it is a no-op; in development it surfaces drift between
|
|
89
|
+
* `parsed.localDefs` and `SemanticModel` that would otherwise silently
|
|
90
|
+
* produce wrong edges.
|
|
91
|
+
*/
|
|
92
|
+
export function validateOwnershipParity(parsedFiles, model, onWarn) {
|
|
93
|
+
if (process.env.NODE_ENV === 'production')
|
|
94
|
+
return 0;
|
|
95
|
+
if (process.env.VALIDATE_SEMANTIC_MODEL === '0')
|
|
96
|
+
return 0;
|
|
97
|
+
let mismatches = 0;
|
|
98
|
+
for (const parsed of parsedFiles) {
|
|
99
|
+
for (const def of parsed.localDefs) {
|
|
100
|
+
const ownerId = def.ownerId;
|
|
101
|
+
if (ownerId === undefined)
|
|
102
|
+
continue;
|
|
103
|
+
const simple = simpleQualifiedName(def);
|
|
104
|
+
if (simple === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
|
|
107
|
+
const found = model.methods.lookupAllByOwner(ownerId, simple);
|
|
108
|
+
if (!found.some((d) => d.nodeId === def.nodeId)) {
|
|
109
|
+
onWarn(`semantic-model parity: ${def.type} ${def.nodeId} (${parsed.filePath}) ` +
|
|
110
|
+
`owned by ${ownerId} as "${simple}" not in MethodRegistry`);
|
|
111
|
+
mismatches++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (def.type === 'Property' || def.type === 'Variable') {
|
|
115
|
+
const found = model.fields.lookupFieldByOwner(ownerId, simple);
|
|
116
|
+
if (found === undefined || found.nodeId !== def.nodeId) {
|
|
117
|
+
onWarn(`semantic-model parity: ${def.type} ${def.nodeId} (${parsed.filePath}) ` +
|
|
118
|
+
`owned by ${ownerId} as "${simple}" not in FieldRegistry`);
|
|
119
|
+
mismatches++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return mismatches;
|
|
125
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-language `ScopeResolver` registry — the lookup the generic
|
|
3
|
+
* `scopeResolutionPhase` uses to pick the right resolver for each
|
|
4
|
+
* migrated language.
|
|
5
|
+
*
|
|
6
|
+
* Adding a language is two lines: implement a `ScopeResolver` in
|
|
7
|
+
* `languages/<lang>/scope-resolver.ts` and register it here. The
|
|
8
|
+
* phase picks it up automatically — no workflow changes, no
|
|
9
|
+
* per-language pipeline phase file.
|
|
10
|
+
*/
|
|
11
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
12
|
+
import type { ScopeResolver } from '../contract/scope-resolver.js';
|
|
13
|
+
/** Map of `SupportedLanguages` → `ScopeResolver`. The phase iterates
|
|
14
|
+
* this map intersected with `MIGRATED_LANGUAGES` (the per-language
|
|
15
|
+
* flag set) so adding a resolver here without flipping the flag is
|
|
16
|
+
* safe — the resolver sits idle until the language is migrated. */
|
|
17
|
+
export declare const SCOPE_RESOLVERS: ReadonlyMap<SupportedLanguages, ScopeResolver>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-language `ScopeResolver` registry — the lookup the generic
|
|
3
|
+
* `scopeResolutionPhase` uses to pick the right resolver for each
|
|
4
|
+
* migrated language.
|
|
5
|
+
*
|
|
6
|
+
* Adding a language is two lines: implement a `ScopeResolver` in
|
|
7
|
+
* `languages/<lang>/scope-resolver.ts` and register it here. The
|
|
8
|
+
* phase picks it up automatically — no workflow changes, no
|
|
9
|
+
* per-language pipeline phase file.
|
|
10
|
+
*/
|
|
11
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
12
|
+
import { pythonScopeResolver } from '../../languages/python/scope-resolver.js';
|
|
13
|
+
import { csharpScopeResolver } from '../../languages/csharp/scope-resolver.js';
|
|
14
|
+
/** Map of `SupportedLanguages` → `ScopeResolver`. The phase iterates
|
|
15
|
+
* this map intersected with `MIGRATED_LANGUAGES` (the per-language
|
|
16
|
+
* flag set) so adding a resolver here without flipping the flag is
|
|
17
|
+
* safe — the resolver sits idle until the language is migrated. */
|
|
18
|
+
export const SCOPE_RESOLVERS = new Map([
|
|
19
|
+
[SupportedLanguages.Python, pythonScopeResolver],
|
|
20
|
+
[SupportedLanguages.CSharp, csharpScopeResolver],
|
|
21
|
+
]);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runScopeResolution` — generic registry-primary resolution
|
|
3
|
+
* orchestrator.
|
|
4
|
+
*
|
|
5
|
+
* ParsedFile[] (one per file via `extractParsedFile`)
|
|
6
|
+
* │ finalizeScopeModel( + provider hooks adapted to FinalizeHooks)
|
|
7
|
+
* ▼
|
|
8
|
+
* ScopeResolutionIndexes
|
|
9
|
+
* │ resolveReferenceSites
|
|
10
|
+
* ▼
|
|
11
|
+
* ReferenceIndex
|
|
12
|
+
* │ emitReceiverBoundCalls (FIRST — see Contract Invariant I1)
|
|
13
|
+
* │ emitFreeCallFallback (THEN)
|
|
14
|
+
* │ emitReferencesViaLookup (LAST — uses handledSites)
|
|
15
|
+
* │ emitImportEdges
|
|
16
|
+
* ▼
|
|
17
|
+
* KnowledgeGraph
|
|
18
|
+
*
|
|
19
|
+
* Per-language entry points (e.g. `runPythonScopeResolution` in
|
|
20
|
+
* `languages/python/scope-resolver.ts`) construct an `ScopeResolver` and
|
|
21
|
+
* delegate here.
|
|
22
|
+
*
|
|
23
|
+
* Plan: `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
24
|
+
*/
|
|
25
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
26
|
+
import type { MutableSemanticModel } from '../../model/semantic-model.js';
|
|
27
|
+
import { type ResolveStats } from '../../resolve-references.js';
|
|
28
|
+
import type { ScopeResolver } from '../contract/scope-resolver.js';
|
|
29
|
+
interface RunScopeResolutionInput {
|
|
30
|
+
readonly graph: KnowledgeGraph;
|
|
31
|
+
/**
|
|
32
|
+
* Semantic model populated by the legacy `parse` phase. Scope-
|
|
33
|
+
* resolution consumes its `TypeRegistry` / `MethodRegistry` /
|
|
34
|
+
* `SymbolTable` lookups instead of rebuilding parallel indexes from
|
|
35
|
+
* `ParsedFile[]`. See ARCHITECTURE.md § "Semantic-model source of
|
|
36
|
+
* truth". Tests that invoke `runScopeResolution` in isolation pass a
|
|
37
|
+
* freshly-created `MutableSemanticModel` populated from the same
|
|
38
|
+
* `ParsedFile[]` to mirror the pipeline shape.
|
|
39
|
+
*/
|
|
40
|
+
readonly model: MutableSemanticModel;
|
|
41
|
+
readonly files: readonly {
|
|
42
|
+
readonly path: string;
|
|
43
|
+
readonly content: string;
|
|
44
|
+
}[];
|
|
45
|
+
readonly onWarn?: (message: string) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Optional pre-parsed-Tree lookup keyed by file path. When the
|
|
48
|
+
* pipeline's parse phase ran sequentially, it populated an
|
|
49
|
+
* `ASTCache`; passing that here lets the per-file extract step
|
|
50
|
+
* skip a second `tree-sitter parser.parse(...)` call. Cache miss
|
|
51
|
+
* is safe — falls back to a fresh parse inside the provider.
|
|
52
|
+
*/
|
|
53
|
+
readonly treeCache?: {
|
|
54
|
+
get(filePath: string): unknown;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
interface RunScopeResolutionStats {
|
|
58
|
+
readonly filesProcessed: number;
|
|
59
|
+
readonly filesSkipped: number;
|
|
60
|
+
readonly importsEmitted: number;
|
|
61
|
+
readonly resolve: ResolveStats;
|
|
62
|
+
readonly referenceEdgesEmitted: number;
|
|
63
|
+
readonly referenceSkipped: number;
|
|
64
|
+
}
|
|
65
|
+
export declare function runScopeResolution(input: RunScopeResolutionInput, provider: ScopeResolver): RunScopeResolutionStats;
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runScopeResolution` — generic registry-primary resolution
|
|
3
|
+
* orchestrator.
|
|
4
|
+
*
|
|
5
|
+
* ParsedFile[] (one per file via `extractParsedFile`)
|
|
6
|
+
* │ finalizeScopeModel( + provider hooks adapted to FinalizeHooks)
|
|
7
|
+
* ▼
|
|
8
|
+
* ScopeResolutionIndexes
|
|
9
|
+
* │ resolveReferenceSites
|
|
10
|
+
* ▼
|
|
11
|
+
* ReferenceIndex
|
|
12
|
+
* │ emitReceiverBoundCalls (FIRST — see Contract Invariant I1)
|
|
13
|
+
* │ emitFreeCallFallback (THEN)
|
|
14
|
+
* │ emitReferencesViaLookup (LAST — uses handledSites)
|
|
15
|
+
* │ emitImportEdges
|
|
16
|
+
* ▼
|
|
17
|
+
* KnowledgeGraph
|
|
18
|
+
*
|
|
19
|
+
* Per-language entry points (e.g. `runPythonScopeResolution` in
|
|
20
|
+
* `languages/python/scope-resolver.ts`) construct an `ScopeResolver` and
|
|
21
|
+
* delegate here.
|
|
22
|
+
*
|
|
23
|
+
* Plan: `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
24
|
+
*/
|
|
25
|
+
import { reconcileOwnership, validateOwnershipParity } from './reconcile-ownership.js';
|
|
26
|
+
import { extractParsedFile } from '../../scope-extractor-bridge.js';
|
|
27
|
+
import { finalizeScopeModel } from '../../finalize-orchestrator.js';
|
|
28
|
+
import { resolveReferenceSites } from '../../resolve-references.js';
|
|
29
|
+
import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
30
|
+
import { buildPopulatedMethodDispatch } from '../graph-bridge/method-dispatch.js';
|
|
31
|
+
import { propagateImportedReturnTypes } from '../passes/imported-return-types.js';
|
|
32
|
+
import { emitReceiverBoundCalls } from '../passes/receiver-bound-calls.js';
|
|
33
|
+
import { emitFreeCallFallback } from '../passes/free-call-fallback.js';
|
|
34
|
+
import { emitReferencesViaLookup } from '../graph-bridge/references-to-edges.js';
|
|
35
|
+
import { emitImportEdges } from '../graph-bridge/imports-to-edges.js';
|
|
36
|
+
import { buildWorkspaceResolutionIndex } from '../workspace-index.js';
|
|
37
|
+
export function runScopeResolution(input, provider) {
|
|
38
|
+
const { graph, files } = input;
|
|
39
|
+
const onWarn = input.onWarn ?? (() => { });
|
|
40
|
+
const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
|
|
41
|
+
const tStart = PROF ? process.hrtime.bigint() : 0n;
|
|
42
|
+
// ── Phase 1: extract each file → ParsedFile ────────────────────────────
|
|
43
|
+
const parsedFiles = [];
|
|
44
|
+
let filesSkipped = 0;
|
|
45
|
+
const treeCache = input.treeCache;
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const cachedTree = treeCache?.get(file.path);
|
|
48
|
+
const parsed = extractParsedFile(provider.languageProvider, file.content, file.path, onWarn, cachedTree);
|
|
49
|
+
if (parsed === undefined) {
|
|
50
|
+
filesSkipped++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
provider.populateOwners(parsed);
|
|
54
|
+
parsedFiles.push(parsed);
|
|
55
|
+
}
|
|
56
|
+
// Reconcile scope-resolution's ownership view into the SemanticModel.
|
|
57
|
+
// See `reconcile-ownership.ts` for the full rationale (Contract
|
|
58
|
+
// Invariant I9). Debug-mode validator runs immediately after to
|
|
59
|
+
// catch drift between `parsed.localDefs` and the registries.
|
|
60
|
+
//
|
|
61
|
+
// PHASE BOUNDARY: `input.model` is `MutableSemanticModel` up to this
|
|
62
|
+
// point (write phase: reconciliation). After this line no further
|
|
63
|
+
// writes are expected — downstream passes consume `readonlyModel`
|
|
64
|
+
// (narrowed to `SemanticModel`) so accidental writes would surface
|
|
65
|
+
// as type errors.
|
|
66
|
+
reconcileOwnership(parsedFiles, input.model);
|
|
67
|
+
validateOwnershipParity(parsedFiles, input.model, onWarn);
|
|
68
|
+
const readonlyModel = input.model;
|
|
69
|
+
if (parsedFiles.length === 0) {
|
|
70
|
+
return {
|
|
71
|
+
filesProcessed: 0,
|
|
72
|
+
filesSkipped,
|
|
73
|
+
importsEmitted: 0,
|
|
74
|
+
resolve: { sitesProcessed: 0, referencesEmitted: 0, unresolved: 0 },
|
|
75
|
+
referenceEdgesEmitted: 0,
|
|
76
|
+
referenceSkipped: 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const tExtract = PROF ? process.hrtime.bigint() : 0n;
|
|
80
|
+
// ── Phase 2: finalize → ScopeResolutionIndexes ─────────────────────────
|
|
81
|
+
const allFilePaths = new Set(parsedFiles.map((f) => f.filePath));
|
|
82
|
+
const nodeLookup = buildGraphNodeLookup(graph);
|
|
83
|
+
const mroByClassDefId = provider.buildMro(graph, parsedFiles, nodeLookup);
|
|
84
|
+
const finalized = finalizeScopeModel(parsedFiles, {
|
|
85
|
+
hooks: {
|
|
86
|
+
resolveImportTarget: (targetRaw, fromFile) => provider.resolveImportTarget(targetRaw, fromFile, allFilePaths),
|
|
87
|
+
mergeBindings: (existing, incoming, scopeId) => provider.mergeBindings(existing, incoming, scopeId),
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
// Replace the empty MethodDispatchIndex that finalizeScopeModel
|
|
91
|
+
// builds by design with the populated one derived from the
|
|
92
|
+
// language's MRO. Spread produces a fresh `ScopeResolutionIndexes`
|
|
93
|
+
// instead of mutating the finalized result through an `as` cast —
|
|
94
|
+
// downstream passes get an object whose readonly guarantees match
|
|
95
|
+
// the type system.
|
|
96
|
+
const indexes = {
|
|
97
|
+
...finalized,
|
|
98
|
+
methodDispatch: buildPopulatedMethodDispatch(mroByClassDefId),
|
|
99
|
+
};
|
|
100
|
+
// Build the workspace resolution index ONCE — scope-valued lookups
|
|
101
|
+
// (`classScopeByDefId`, `moduleScopeByFile`) that `SemanticModel`
|
|
102
|
+
// cannot carry. Must run AFTER `populateOwners` (so owned defs are
|
|
103
|
+
// attributed correctly) and AFTER finalize (so module-scope
|
|
104
|
+
// bindings are available).
|
|
105
|
+
const workspaceIndex = buildWorkspaceResolutionIndex(parsedFiles);
|
|
106
|
+
// Cross-file implicit-namespace visibility (C#). Must run before
|
|
107
|
+
// propagateImportedReturnTypes so the latter pass sees siblings'
|
|
108
|
+
// class bindings when chasing return-type chains across files.
|
|
109
|
+
if (provider.populateNamespaceSiblings !== undefined) {
|
|
110
|
+
const fileContents = new Map();
|
|
111
|
+
for (const f of files)
|
|
112
|
+
fileContents.set(f.path, f.content);
|
|
113
|
+
provider.populateNamespaceSiblings(parsedFiles, indexes, {
|
|
114
|
+
fileContents,
|
|
115
|
+
treeCache,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Cross-file return-type propagation (Contract Invariant I3 timing:
|
|
119
|
+
// after finalize, before resolve).
|
|
120
|
+
if (provider.propagatesReturnTypesAcrossImports !== false) {
|
|
121
|
+
propagateImportedReturnTypes(parsedFiles, indexes, workspaceIndex);
|
|
122
|
+
}
|
|
123
|
+
const tFinalize = PROF ? process.hrtime.bigint() : 0n;
|
|
124
|
+
// ── Phase 3: resolve references via Registry.lookup ────────────────────
|
|
125
|
+
const registryProviders = {
|
|
126
|
+
arityCompatibility: provider.arityCompatibility,
|
|
127
|
+
};
|
|
128
|
+
const { referenceIndex, stats: resolveStats } = resolveReferenceSites({
|
|
129
|
+
scopes: indexes,
|
|
130
|
+
providers: registryProviders,
|
|
131
|
+
});
|
|
132
|
+
const tResolve = PROF ? process.hrtime.bigint() : 0n;
|
|
133
|
+
// ── Phase 4: emit graph edges (LOAD-BEARING ORDER — see I1) ────────────
|
|
134
|
+
const handledSites = new Set();
|
|
135
|
+
const receiverExtras = emitReceiverBoundCalls(graph, indexes, parsedFiles, nodeLookup, handledSites, provider, workspaceIndex, readonlyModel);
|
|
136
|
+
const freeCallExtras = emitFreeCallFallback(graph, indexes, parsedFiles, nodeLookup, referenceIndex, handledSites, readonlyModel, workspaceIndex);
|
|
137
|
+
const { emitted, skipped } = emitReferencesViaLookup(graph, indexes, referenceIndex, nodeLookup, handledSites);
|
|
138
|
+
const importsEmitted = emitImportEdges(graph, indexes.imports, indexes.scopeTree, provider.importEdgeReason);
|
|
139
|
+
if (PROF) {
|
|
140
|
+
const tEnd = process.hrtime.bigint();
|
|
141
|
+
const ns = (a, b) => Number(b - a) / 1_000_000;
|
|
142
|
+
console.warn(`[scope-resolution prof] extract=${ns(tStart, tExtract).toFixed(0)}ms` +
|
|
143
|
+
` finalize+propagate=${ns(tExtract, tFinalize).toFixed(0)}ms` +
|
|
144
|
+
` resolve=${ns(tFinalize, tResolve).toFixed(0)}ms` +
|
|
145
|
+
` emit=${ns(tResolve, tEnd).toFixed(0)}ms` +
|
|
146
|
+
` total=${ns(tStart, tEnd).toFixed(0)}ms` +
|
|
147
|
+
` (${parsedFiles.length} files)`);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
filesProcessed: parsedFiles.length,
|
|
151
|
+
filesSkipped,
|
|
152
|
+
importsEmitted,
|
|
153
|
+
resolve: resolveStats,
|
|
154
|
+
referenceEdgesEmitted: emitted + receiverExtras + freeCallExtras,
|
|
155
|
+
referenceSkipped: skipped,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a per-file `localName → targetFilePath` map over the file's
|
|
3
|
+
* module-scope namespace-kind import edges.
|
|
4
|
+
*
|
|
5
|
+
* Namespace imports (`import X`, `import X as Y`) bind a name that can
|
|
6
|
+
* appear as a receiver in member calls (`X.foo()`, `Y.foo()`). Named
|
|
7
|
+
* imports (`from X import foo`) bind `foo` directly and are a different
|
|
8
|
+
* resolution path.
|
|
9
|
+
*
|
|
10
|
+
* Why not consult `scope.bindings` directly? For namespace imports
|
|
11
|
+
* where the target module has no self-named def,
|
|
12
|
+
* `finalize-algorithm.ts:540` skips binding creation entirely, so
|
|
13
|
+
* `scope.bindings.get('X')` returns undefined. We iterate
|
|
14
|
+
* `indexes.imports` to recover those targets.
|
|
15
|
+
*
|
|
16
|
+
* Next-consumer contract: any language with namespace-style imports
|
|
17
|
+
* (TypeScript `import * as X`, Java static import, Ruby `require`)
|
|
18
|
+
* uses this directly. `ParsedImport.kind === 'namespace'` is the
|
|
19
|
+
* cross-language hook.
|
|
20
|
+
*
|
|
21
|
+
* Scope-chain concern (verified 2026-04-21): `pythonImportOwningScope`
|
|
22
|
+
* documents that function-local and class-body imports bind to the
|
|
23
|
+
* inner scope, which would make a module-only read incomplete. In
|
|
24
|
+
* practice `finalize-algorithm` places ALL of a file's ImportEdges
|
|
25
|
+
* onto `indexes.imports[moduleScope]` regardless of where the
|
|
26
|
+
* `import` statement appears — the integration fixtures
|
|
27
|
+
* `python-function-local-namespace-import` and
|
|
28
|
+
* `python-class-body-namespace-import` both emit correct CALLS edges
|
|
29
|
+
* with reason "namespace-receiver", demonstrating that the module-
|
|
30
|
+
* scope read is sufficient today. If finalize routing ever changes to
|
|
31
|
+
* honor the hook's per-scope contract, this function must walk the
|
|
32
|
+
* reference-site scope chain (mirror `findExportedDefByName`).
|
|
33
|
+
*/
|
|
34
|
+
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
35
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
36
|
+
export declare function collectNamespaceTargets(parsed: ParsedFile, scopes: ScopeResolutionIndexes): Map<string, string>;
|