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,79 @@
|
|
|
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 { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
18
|
+
/**
|
|
19
|
+
* Map a `Reference.kind` to a graph edge type. `import-use` is dropped
|
|
20
|
+
* (no edge type today — provenance lives on the IMPORTS edge emitted
|
|
21
|
+
* by `emitImportEdges`).
|
|
22
|
+
*/
|
|
23
|
+
export function mapReferenceKindToEdgeType(kind) {
|
|
24
|
+
switch (kind) {
|
|
25
|
+
case 'call':
|
|
26
|
+
return 'CALLS';
|
|
27
|
+
case 'read':
|
|
28
|
+
case 'write':
|
|
29
|
+
return 'ACCESSES';
|
|
30
|
+
case 'inherits':
|
|
31
|
+
return 'EXTENDS';
|
|
32
|
+
case 'type-reference':
|
|
33
|
+
return 'USES';
|
|
34
|
+
case 'import-use':
|
|
35
|
+
return undefined;
|
|
36
|
+
default:
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve caller + target to graph ids and emit the edge. Returns true
|
|
42
|
+
* if the edge was emitted (not deduped, not skipped).
|
|
43
|
+
*
|
|
44
|
+
* `seen` is a language-shared dedup set keyed by
|
|
45
|
+
* `${edgeType}:${callerGraphId}->${targetGraphId}:${line}:${col}` so
|
|
46
|
+
* multiple language-specific post-passes can share it and never
|
|
47
|
+
* double-emit a resolution one of them already produced.
|
|
48
|
+
*/
|
|
49
|
+
export function tryEmitEdge(graph, scopes, nodeLookup, site, targetDef, reason, seen, confidence = 0.85, collapseByCallerTarget = false) {
|
|
50
|
+
const callerGraphId = resolveCallerGraphId(site.inScope, scopes, nodeLookup);
|
|
51
|
+
const targetGraphId = resolveDefGraphId(targetDef.filePath, targetDef, nodeLookup);
|
|
52
|
+
const edgeType = mapReferenceKindToEdgeType(site.kind);
|
|
53
|
+
if (callerGraphId === undefined)
|
|
54
|
+
return false;
|
|
55
|
+
if (targetGraphId === undefined)
|
|
56
|
+
return false;
|
|
57
|
+
if (edgeType === undefined)
|
|
58
|
+
return false;
|
|
59
|
+
// CALLS edges may collapse to `(caller, target)` granularity when
|
|
60
|
+
// the provider opts in (C# matches legacy DAG behavior this way).
|
|
61
|
+
// Write/read ACCESSES keep per-site dedup so multiple writes to the
|
|
62
|
+
// same field on different lines produce distinct edges.
|
|
63
|
+
const useCollapsed = collapseByCallerTarget && edgeType === 'CALLS';
|
|
64
|
+
const dedupKey = useCollapsed
|
|
65
|
+
? `${edgeType}:${callerGraphId}->${targetGraphId}`
|
|
66
|
+
: `${edgeType}:${callerGraphId}->${targetGraphId}:${site.atRange.startLine}:${site.atRange.startCol}`;
|
|
67
|
+
if (seen.has(dedupKey))
|
|
68
|
+
return false;
|
|
69
|
+
seen.add(dedupKey);
|
|
70
|
+
graph.addRelationship({
|
|
71
|
+
id: `rel:${dedupKey}`,
|
|
72
|
+
sourceId: callerGraphId,
|
|
73
|
+
targetId: targetGraphId,
|
|
74
|
+
type: edgeType,
|
|
75
|
+
confidence,
|
|
76
|
+
reason,
|
|
77
|
+
});
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope-resolution → legacy graph-node ID bridging.
|
|
3
|
+
*
|
|
4
|
+
* Two functions:
|
|
5
|
+
* - `resolveDefGraphId` — turn a scope-resolution `SymbolDefinition`
|
|
6
|
+
* into the graph's node id for the corresponding legacy node.
|
|
7
|
+
* - `resolveCallerGraphId` — walk a scope chain from a reference
|
|
8
|
+
* site upward to find the enclosing function/method/class and
|
|
9
|
+
* return its graph-node id. Falls back to the File node for
|
|
10
|
+
* module-level calls so those still get an edge source.
|
|
11
|
+
*
|
|
12
|
+
* Next-consumer contract: language-agnostic. Any OO language with
|
|
13
|
+
* file-level module semantics (TypeScript, Java, Go, Kotlin) can
|
|
14
|
+
* reuse `resolveCallerGraphId` as-is. Languages with different
|
|
15
|
+
* top-level semantics (COBOL programs, Rust crate modules) may want
|
|
16
|
+
* a different file-level fallback — cross that bridge when they
|
|
17
|
+
* migrate.
|
|
18
|
+
*/
|
|
19
|
+
import type { NodeLabel, ScopeId, SymbolDefinition } from '../../../../_shared/index.js';
|
|
20
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
21
|
+
import { type GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
22
|
+
/**
|
|
23
|
+
* Look up a `SymbolDefinition` in the graph node lookup.
|
|
24
|
+
*
|
|
25
|
+
* Tries the type-prefixed fully-qualified key FIRST. That's the only
|
|
26
|
+
* correct key when:
|
|
27
|
+
* - Two classes in the same file define a method with the same
|
|
28
|
+
* simple name (`class User: def save` + `class Document: def save`).
|
|
29
|
+
* - A top-level function and a class method share a simple name
|
|
30
|
+
* (`def save` + `class User: def save` — the Function's qualifier
|
|
31
|
+
* is just `save`, which would alias the Method's simple-key slot
|
|
32
|
+
* without the type prefix).
|
|
33
|
+
*
|
|
34
|
+
* Falls back to the simple name for definitions whose qualifier the
|
|
35
|
+
* lookup didn't capture (rare, but keeps cross-file simple-name
|
|
36
|
+
* resolution working for languages that don't yet synthesize
|
|
37
|
+
* qualifiers).
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveDefGraphId(filePath: string, def: {
|
|
40
|
+
qualifiedName?: string;
|
|
41
|
+
type?: NodeLabel;
|
|
42
|
+
parameterTypes?: readonly string[];
|
|
43
|
+
}, nodeLookup: GraphNodeLookup): string | undefined;
|
|
44
|
+
/** Derive the simple (unqualified) name of a def from its `qualifiedName`. */
|
|
45
|
+
export declare function simpleQualifiedName(def: SymbolDefinition): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Walk the scope chain from `startScope` upward looking for the first
|
|
48
|
+
* scope whose `ownedDefs` contains a Function/Method/Class — that's
|
|
49
|
+
* our caller anchor. Translate via `nodeLookup` to the graph-node ID.
|
|
50
|
+
*
|
|
51
|
+
* Module-level references (e.g. Python `u = models.User()` at top
|
|
52
|
+
* level) have no enclosing function/method/class. Fall back to the
|
|
53
|
+
* File node for the scope's filePath so those calls still get an
|
|
54
|
+
* edge source. Matches legacy DAG behavior where module-level CALLS
|
|
55
|
+
* edges originate from the file symbol.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveCallerGraphId(startScope: ScopeId, scopes: ScopeResolutionIndexes, nodeLookup: GraphNodeLookup): string | undefined;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope-resolution → legacy graph-node ID bridging.
|
|
3
|
+
*
|
|
4
|
+
* Two functions:
|
|
5
|
+
* - `resolveDefGraphId` — turn a scope-resolution `SymbolDefinition`
|
|
6
|
+
* into the graph's node id for the corresponding legacy node.
|
|
7
|
+
* - `resolveCallerGraphId` — walk a scope chain from a reference
|
|
8
|
+
* site upward to find the enclosing function/method/class and
|
|
9
|
+
* return its graph-node id. Falls back to the File node for
|
|
10
|
+
* module-level calls so those still get an edge source.
|
|
11
|
+
*
|
|
12
|
+
* Next-consumer contract: language-agnostic. Any OO language with
|
|
13
|
+
* file-level module semantics (TypeScript, Java, Go, Kotlin) can
|
|
14
|
+
* reuse `resolveCallerGraphId` as-is. Languages with different
|
|
15
|
+
* top-level semantics (COBOL programs, Rust crate modules) may want
|
|
16
|
+
* a different file-level fallback — cross that bridge when they
|
|
17
|
+
* migrate.
|
|
18
|
+
*/
|
|
19
|
+
import { generateId } from '../../../../lib/utils.js';
|
|
20
|
+
import { isLinkableLabel, qualifiedKey, simpleKey, } from '../graph-bridge/node-lookup.js';
|
|
21
|
+
/**
|
|
22
|
+
* Look up a `SymbolDefinition` in the graph node lookup.
|
|
23
|
+
*
|
|
24
|
+
* Tries the type-prefixed fully-qualified key FIRST. That's the only
|
|
25
|
+
* correct key when:
|
|
26
|
+
* - Two classes in the same file define a method with the same
|
|
27
|
+
* simple name (`class User: def save` + `class Document: def save`).
|
|
28
|
+
* - A top-level function and a class method share a simple name
|
|
29
|
+
* (`def save` + `class User: def save` — the Function's qualifier
|
|
30
|
+
* is just `save`, which would alias the Method's simple-key slot
|
|
31
|
+
* without the type prefix).
|
|
32
|
+
*
|
|
33
|
+
* Falls back to the simple name for definitions whose qualifier the
|
|
34
|
+
* lookup didn't capture (rare, but keeps cross-file simple-name
|
|
35
|
+
* resolution working for languages that don't yet synthesize
|
|
36
|
+
* qualifiers).
|
|
37
|
+
*/
|
|
38
|
+
export function resolveDefGraphId(filePath, def, nodeLookup) {
|
|
39
|
+
const qn = def.qualifiedName;
|
|
40
|
+
if (qn === undefined || qn.length === 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
if (def.type !== undefined) {
|
|
43
|
+
// Overload disambiguation: when the def carries parameter types,
|
|
44
|
+
// try the parameter-typed key first so same-name same-arity
|
|
45
|
+
// overloads route to their distinct graph nodes.
|
|
46
|
+
if (def.type === 'Method' &&
|
|
47
|
+
def.parameterTypes !== undefined &&
|
|
48
|
+
def.parameterTypes.length > 0) {
|
|
49
|
+
const pKey = qualifiedKey(filePath, def.type, `${qn}~${def.parameterTypes.join(',')}`);
|
|
50
|
+
const pHit = nodeLookup.get(pKey);
|
|
51
|
+
if (pHit !== undefined)
|
|
52
|
+
return pHit;
|
|
53
|
+
}
|
|
54
|
+
const qualifiedHit = nodeLookup.get(qualifiedKey(filePath, def.type, qn));
|
|
55
|
+
if (qualifiedHit !== undefined)
|
|
56
|
+
return qualifiedHit;
|
|
57
|
+
}
|
|
58
|
+
const simpleName = qn.lastIndexOf('.') === -1 ? qn : qn.slice(qn.lastIndexOf('.') + 1);
|
|
59
|
+
return nodeLookup.get(simpleKey(filePath, simpleName));
|
|
60
|
+
}
|
|
61
|
+
/** Derive the simple (unqualified) name of a def from its `qualifiedName`. */
|
|
62
|
+
export function simpleQualifiedName(def) {
|
|
63
|
+
const q = def.qualifiedName;
|
|
64
|
+
if (q === undefined || q.length === 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
const dot = q.lastIndexOf('.');
|
|
67
|
+
return dot === -1 ? q : q.slice(dot + 1);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Walk the scope chain from `startScope` upward looking for the first
|
|
71
|
+
* scope whose `ownedDefs` contains a Function/Method/Class — that's
|
|
72
|
+
* our caller anchor. Translate via `nodeLookup` to the graph-node ID.
|
|
73
|
+
*
|
|
74
|
+
* Module-level references (e.g. Python `u = models.User()` at top
|
|
75
|
+
* level) have no enclosing function/method/class. Fall back to the
|
|
76
|
+
* File node for the scope's filePath so those calls still get an
|
|
77
|
+
* edge source. Matches legacy DAG behavior where module-level CALLS
|
|
78
|
+
* edges originate from the file symbol.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveCallerGraphId(startScope, scopes, nodeLookup) {
|
|
81
|
+
let current = startScope;
|
|
82
|
+
const visited = new Set();
|
|
83
|
+
let lastFilePath;
|
|
84
|
+
while (current !== null) {
|
|
85
|
+
if (visited.has(current))
|
|
86
|
+
return undefined;
|
|
87
|
+
visited.add(current);
|
|
88
|
+
const scope = scopes.scopeTree.getScope(current);
|
|
89
|
+
if (scope === undefined)
|
|
90
|
+
break;
|
|
91
|
+
lastFilePath = scope.filePath;
|
|
92
|
+
// Prefer Function/Method anchors; fall back to Class.
|
|
93
|
+
const fnDef = scope.ownedDefs.find((d) => d.type === 'Function' || d.type === 'Method' || d.type === 'Constructor');
|
|
94
|
+
if (fnDef !== undefined) {
|
|
95
|
+
const id = resolveDefGraphId(scope.filePath, fnDef, nodeLookup);
|
|
96
|
+
if (id !== undefined)
|
|
97
|
+
return id;
|
|
98
|
+
}
|
|
99
|
+
const classDef = scope.ownedDefs.find((d) => isLinkableLabel(d.type));
|
|
100
|
+
if (classDef !== undefined) {
|
|
101
|
+
const id = resolveDefGraphId(scope.filePath, classDef, nodeLookup);
|
|
102
|
+
if (id !== undefined)
|
|
103
|
+
return id;
|
|
104
|
+
}
|
|
105
|
+
current = scope.parent;
|
|
106
|
+
}
|
|
107
|
+
// Module-level calls — fall back to the File node for the scope's filePath.
|
|
108
|
+
if (lastFilePath !== undefined) {
|
|
109
|
+
return generateId('File', lastFilePath);
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File→File IMPORTS edge emission from a finalized `ImportEdge` map.
|
|
3
|
+
*
|
|
4
|
+
* Deduplicates by `(sourceFile, targetFile)` so multi-symbol imports
|
|
5
|
+
* from the same module collapse to a single edge — matching the
|
|
6
|
+
* legacy schema.
|
|
7
|
+
*
|
|
8
|
+
* Next-consumer contract: language-agnostic. Any provider with a
|
|
9
|
+
* scope-resolution ImportEdge stream emits File→File edges via this
|
|
10
|
+
* single function. The `reason` defaults to
|
|
11
|
+
* `'scope-resolution: import'`; provider may override if downstream
|
|
12
|
+
* filters on reason.
|
|
13
|
+
*/
|
|
14
|
+
import type { ImportEdge, ScopeId } from '../../../../_shared/index.js';
|
|
15
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
16
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
17
|
+
export declare function emitImportEdges(graph: KnowledgeGraph, imports: ReadonlyMap<ScopeId, readonly ImportEdge[]>, scopeTree: ScopeResolutionIndexes['scopeTree'], reason?: string): number;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File→File IMPORTS edge emission from a finalized `ImportEdge` map.
|
|
3
|
+
*
|
|
4
|
+
* Deduplicates by `(sourceFile, targetFile)` so multi-symbol imports
|
|
5
|
+
* from the same module collapse to a single edge — matching the
|
|
6
|
+
* legacy schema.
|
|
7
|
+
*
|
|
8
|
+
* Next-consumer contract: language-agnostic. Any provider with a
|
|
9
|
+
* scope-resolution ImportEdge stream emits File→File edges via this
|
|
10
|
+
* single function. The `reason` defaults to
|
|
11
|
+
* `'scope-resolution: import'`; provider may override if downstream
|
|
12
|
+
* filters on reason.
|
|
13
|
+
*/
|
|
14
|
+
import { generateId } from '../../../../lib/utils.js';
|
|
15
|
+
export function emitImportEdges(graph, imports, scopeTree, reason = 'scope-resolution: import') {
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
let emitted = 0;
|
|
18
|
+
for (const [scopeId, edges] of imports) {
|
|
19
|
+
const scope = scopeTree.getScope(scopeId);
|
|
20
|
+
if (scope === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
const sourceFile = scope.filePath;
|
|
23
|
+
for (const edge of edges) {
|
|
24
|
+
if (edge.targetFile === null)
|
|
25
|
+
continue;
|
|
26
|
+
if (edge.targetFile === sourceFile)
|
|
27
|
+
continue;
|
|
28
|
+
const dedupKey = `${sourceFile}->${edge.targetFile}`;
|
|
29
|
+
if (seen.has(dedupKey))
|
|
30
|
+
continue;
|
|
31
|
+
seen.add(dedupKey);
|
|
32
|
+
const sourceId = generateId('File', sourceFile);
|
|
33
|
+
const targetId = generateId('File', edge.targetFile);
|
|
34
|
+
graph.addRelationship({
|
|
35
|
+
id: generateId('IMPORTS', dedupKey),
|
|
36
|
+
sourceId,
|
|
37
|
+
targetId,
|
|
38
|
+
type: 'IMPORTS',
|
|
39
|
+
confidence: 1.0,
|
|
40
|
+
reason,
|
|
41
|
+
});
|
|
42
|
+
emitted++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return emitted;
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a `DefId → ancestor DefId[]` MRO map in the shared
|
|
3
|
+
* `MethodDispatchIndex` shape so it slots into
|
|
4
|
+
* `ScopeResolutionIndexes.methodDispatch`.
|
|
5
|
+
*
|
|
6
|
+
* `finalizeScopeModel` builds an empty `MethodDispatchIndex` by design
|
|
7
|
+
* (per the comment in `finalize-orchestrator.ts`). Per-language
|
|
8
|
+
* providers compute MRO their own way (Python C3 walk, Java class
|
|
9
|
+
* hierarchy, Ruby mixin chains, etc.) and use this bridge to plug the
|
|
10
|
+
* result back into the shared index shape.
|
|
11
|
+
*
|
|
12
|
+
* Next-consumer contract: any language that computes its own MRO map
|
|
13
|
+
* calls `buildPopulatedMethodDispatch(mroByOwnerDefId)` and assigns the
|
|
14
|
+
* result to `indexes.methodDispatch`. Interface-implementer tracking
|
|
15
|
+
* (`implsByInterfaceDefId`) stays empty in V1 — providers that need it
|
|
16
|
+
* can extend the return shape without breaking existing consumers.
|
|
17
|
+
*/
|
|
18
|
+
import type { MethodDispatchIndex } from '../../../../_shared/index.js';
|
|
19
|
+
export declare function buildPopulatedMethodDispatch(mroByDefId: ReadonlyMap<string, readonly string[]>): MethodDispatchIndex;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a `DefId → ancestor DefId[]` MRO map in the shared
|
|
3
|
+
* `MethodDispatchIndex` shape so it slots into
|
|
4
|
+
* `ScopeResolutionIndexes.methodDispatch`.
|
|
5
|
+
*
|
|
6
|
+
* `finalizeScopeModel` builds an empty `MethodDispatchIndex` by design
|
|
7
|
+
* (per the comment in `finalize-orchestrator.ts`). Per-language
|
|
8
|
+
* providers compute MRO their own way (Python C3 walk, Java class
|
|
9
|
+
* hierarchy, Ruby mixin chains, etc.) and use this bridge to plug the
|
|
10
|
+
* result back into the shared index shape.
|
|
11
|
+
*
|
|
12
|
+
* Next-consumer contract: any language that computes its own MRO map
|
|
13
|
+
* calls `buildPopulatedMethodDispatch(mroByOwnerDefId)` and assigns the
|
|
14
|
+
* result to `indexes.methodDispatch`. Interface-implementer tracking
|
|
15
|
+
* (`implsByInterfaceDefId`) stays empty in V1 — providers that need it
|
|
16
|
+
* can extend the return shape without breaking existing consumers.
|
|
17
|
+
*/
|
|
18
|
+
const EMPTY_DEFS = Object.freeze([]);
|
|
19
|
+
export function buildPopulatedMethodDispatch(mroByDefId) {
|
|
20
|
+
return {
|
|
21
|
+
mroByOwnerDefId: mroByDefId,
|
|
22
|
+
implsByInterfaceDefId: new Map(),
|
|
23
|
+
mroFor(ownerDefId) {
|
|
24
|
+
return mroByDefId.get(ownerDefId) ?? EMPTY_DEFS;
|
|
25
|
+
},
|
|
26
|
+
implementorsOf() {
|
|
27
|
+
return EMPTY_DEFS;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a `(filePath, name) → graphNodeId` lookup over the graph's
|
|
3
|
+
* Function/Method/Class/Constructor nodes. Two keys per node:
|
|
4
|
+
*
|
|
5
|
+
* - simple name (`User` / `save`) — legacy fallback
|
|
6
|
+
* - qualified name when derivable from the node id (`User.save`)
|
|
7
|
+
*
|
|
8
|
+
* The qualified key is the authoritative one when two classes in the
|
|
9
|
+
* same file define a method with the same simple name
|
|
10
|
+
* (`class User: def save` + `class Document: def save`). Without it,
|
|
11
|
+
* the simple-name key collides and every `document.save()` CALLS edge
|
|
12
|
+
* would silently target `User.save`. Method node ids encode the
|
|
13
|
+
* qualifier (`Method:file.py:User.save#1`), so we parse it back out.
|
|
14
|
+
*
|
|
15
|
+
* Language-agnostic seam. Any language provider migrating to the
|
|
16
|
+
* registry-primary path can consume this to translate scope-resolution
|
|
17
|
+
* `SymbolDefinition.nodeId` values into the legacy graph-node ID
|
|
18
|
+
* format that downstream consumers (queries, edges, MCP) expect.
|
|
19
|
+
*/
|
|
20
|
+
import type { NodeLabel } from '../../../../_shared/index.js';
|
|
21
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
22
|
+
export type GraphNodeLookup = ReadonlyMap<string, string>;
|
|
23
|
+
/**
|
|
24
|
+
* Build a qualified-key string in a separate keyspace from simple-key
|
|
25
|
+
* strings. Prefix `<q>` can't appear in a valid filePath on any OS, so
|
|
26
|
+
* no collision between the two keyspaces is possible.
|
|
27
|
+
*
|
|
28
|
+
* Includes the node label so a top-level `def save` (Function,
|
|
29
|
+
* qualifier = `save`) doesn't alias a class method `User.save` (Method,
|
|
30
|
+
* simple name = `save`) whose Function-typed qualifier would collapse
|
|
31
|
+
* to the same simple-key slot in a single map.
|
|
32
|
+
*/
|
|
33
|
+
export declare function qualifiedKey(filePath: string, label: NodeLabel, qualifiedName: string): string;
|
|
34
|
+
/** Simple-name key (legacy fallback keyspace — no `<q>` prefix). */
|
|
35
|
+
export declare function simpleKey(filePath: string, name: string): string;
|
|
36
|
+
export declare function buildGraphNodeLookup(graph: KnowledgeGraph): GraphNodeLookup;
|
|
37
|
+
export declare function isLinkableLabel(label: NodeLabel): boolean;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a `(filePath, name) → graphNodeId` lookup over the graph's
|
|
3
|
+
* Function/Method/Class/Constructor nodes. Two keys per node:
|
|
4
|
+
*
|
|
5
|
+
* - simple name (`User` / `save`) — legacy fallback
|
|
6
|
+
* - qualified name when derivable from the node id (`User.save`)
|
|
7
|
+
*
|
|
8
|
+
* The qualified key is the authoritative one when two classes in the
|
|
9
|
+
* same file define a method with the same simple name
|
|
10
|
+
* (`class User: def save` + `class Document: def save`). Without it,
|
|
11
|
+
* the simple-name key collides and every `document.save()` CALLS edge
|
|
12
|
+
* would silently target `User.save`. Method node ids encode the
|
|
13
|
+
* qualifier (`Method:file.py:User.save#1`), so we parse it back out.
|
|
14
|
+
*
|
|
15
|
+
* Language-agnostic seam. Any language provider migrating to the
|
|
16
|
+
* registry-primary path can consume this to translate scope-resolution
|
|
17
|
+
* `SymbolDefinition.nodeId` values into the legacy graph-node ID
|
|
18
|
+
* format that downstream consumers (queries, edges, MCP) expect.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Parse a qualified name out of a Function/Method node id.
|
|
22
|
+
*
|
|
23
|
+
* Node id format: `${label}:${filePath}:${qualifiedName}${arityTag}`,
|
|
24
|
+
* where `arityTag` is `#<n>` (or empty). Strips the known-length
|
|
25
|
+
* label + filePath prefix so colons inside `filePath` (Windows
|
|
26
|
+
* `C:\...`) don't break the parse. Returns `undefined` when the id
|
|
27
|
+
* doesn't match the expected shape.
|
|
28
|
+
*/
|
|
29
|
+
function parseQualifiedFromId(id, label, filePath) {
|
|
30
|
+
const prefix = `${label}:${filePath}:`;
|
|
31
|
+
if (!id.startsWith(prefix))
|
|
32
|
+
return undefined;
|
|
33
|
+
const suffix = id.slice(prefix.length);
|
|
34
|
+
if (suffix.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
const hash = suffix.indexOf('#');
|
|
37
|
+
return hash === -1 ? suffix : suffix.slice(0, hash);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build a qualified-key string in a separate keyspace from simple-key
|
|
41
|
+
* strings. Prefix `<q>` can't appear in a valid filePath on any OS, so
|
|
42
|
+
* no collision between the two keyspaces is possible.
|
|
43
|
+
*
|
|
44
|
+
* Includes the node label so a top-level `def save` (Function,
|
|
45
|
+
* qualifier = `save`) doesn't alias a class method `User.save` (Method,
|
|
46
|
+
* simple name = `save`) whose Function-typed qualifier would collapse
|
|
47
|
+
* to the same simple-key slot in a single map.
|
|
48
|
+
*/
|
|
49
|
+
export function qualifiedKey(filePath, label, qualifiedName) {
|
|
50
|
+
return `<q>:${filePath}::${label}::${qualifiedName}`;
|
|
51
|
+
}
|
|
52
|
+
/** Simple-name key (legacy fallback keyspace — no `<q>` prefix). */
|
|
53
|
+
export function simpleKey(filePath, name) {
|
|
54
|
+
return `${filePath}::${name}`;
|
|
55
|
+
}
|
|
56
|
+
export function buildGraphNodeLookup(graph) {
|
|
57
|
+
const lookup = new Map();
|
|
58
|
+
for (const node of graph.iterNodes()) {
|
|
59
|
+
const props = node.properties;
|
|
60
|
+
if (props.filePath === undefined || props.name === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
if (!isLinkableLabel(node.label))
|
|
63
|
+
continue;
|
|
64
|
+
// Primary key: fully-qualified name + label, in a separate
|
|
65
|
+
// keyspace from simple names. Class nodes carry `qualifiedName`
|
|
66
|
+
// in their properties (set by the parsing processor).
|
|
67
|
+
// Method/Function nodes do not, so derive the qualifier from the
|
|
68
|
+
// node id — that's where the parse-phase encoded it. Including
|
|
69
|
+
// the label avoids a collision when a free Function's qualifier
|
|
70
|
+
// happens to equal a Method's simple name (e.g. top-level
|
|
71
|
+
// `def save` vs `class User: def save`).
|
|
72
|
+
const qualified = props.qualifiedName ?? parseQualifiedFromId(node.id, node.label, props.filePath);
|
|
73
|
+
if (qualified !== undefined && qualified.length > 0) {
|
|
74
|
+
const qKey = qualifiedKey(props.filePath, node.label, qualified);
|
|
75
|
+
if (!lookup.has(qKey))
|
|
76
|
+
lookup.set(qKey, node.id);
|
|
77
|
+
// Overload-disambiguating key: include parameter types so two
|
|
78
|
+
// same-arity overloads (e.g. `Lookup(int)` vs `Lookup(string)`)
|
|
79
|
+
// map to distinct graph nodes. Legacy parse-phase encodes the
|
|
80
|
+
// type tag into the node id; we register both that node id and
|
|
81
|
+
// a parameter-types-suffixed key so resolveDefGraphId can find
|
|
82
|
+
// the right overload by matching its def's parameterTypes.
|
|
83
|
+
const pTypes = props.parameterTypes;
|
|
84
|
+
if (pTypes !== undefined && pTypes.length > 0 && node.label === 'Method') {
|
|
85
|
+
const pKey = qualifiedKey(props.filePath, node.label, `${qualified}~${pTypes.join(',')}`);
|
|
86
|
+
// Each overload is unique — set unconditionally.
|
|
87
|
+
lookup.set(pKey, node.id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Fallback key: simple name. First-wins within a file — used when
|
|
91
|
+
// the caller doesn't know the qualifier (unqualified free-call
|
|
92
|
+
// fallback, cross-file resolution where MethodRegistry already
|
|
93
|
+
// disambiguated the owner).
|
|
94
|
+
const sKey = simpleKey(props.filePath, props.name);
|
|
95
|
+
if (!lookup.has(sKey))
|
|
96
|
+
lookup.set(sKey, node.id);
|
|
97
|
+
}
|
|
98
|
+
return lookup;
|
|
99
|
+
}
|
|
100
|
+
export function isLinkableLabel(label) {
|
|
101
|
+
return (label === 'Function' ||
|
|
102
|
+
label === 'Method' ||
|
|
103
|
+
label === 'Constructor' ||
|
|
104
|
+
label === 'Class' ||
|
|
105
|
+
label === 'Interface' ||
|
|
106
|
+
label === 'Struct' ||
|
|
107
|
+
label === 'Enum' ||
|
|
108
|
+
// Variable / Property are linkable too — receiver-bound write/read
|
|
109
|
+
// ACCESSES edges target field nodes (e.g. `user.name = "x"` →
|
|
110
|
+
// ACCESSES edge to User's `name` Variable/Property node).
|
|
111
|
+
label === 'Variable' ||
|
|
112
|
+
label === 'Property');
|
|
113
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate the resolved `ReferenceIndex` into legacy graph edges.
|
|
3
|
+
*
|
|
4
|
+
* Per reference:
|
|
5
|
+
* 1. Resolve `fromScope` → caller graph-node id by walking the scope
|
|
6
|
+
* chain looking for an enclosing Function/Method/Class.
|
|
7
|
+
* 2. Resolve `toDef` → target graph-node id via `nodeLookup`.
|
|
8
|
+
* 3. Emit the edge (`CALLS` / `READS` / `WRITES` / `EXTENDS` / `USES`)
|
|
9
|
+
* with the standard reason format.
|
|
10
|
+
*
|
|
11
|
+
* Skips (without throwing) when either side fails to map — either side
|
|
12
|
+
* may legitimately not exist as a graph node (e.g. a resolved target
|
|
13
|
+
* lives in an external file that wasn't ingested into the graph).
|
|
14
|
+
*
|
|
15
|
+
* Next-consumer contract: this function is the canonical bridge from
|
|
16
|
+
* a shared `ReferenceIndex` into per-language graph edges. Every
|
|
17
|
+
* registry-primary language provider calls this exactly once with its
|
|
18
|
+
* `referenceIndex` output and its own `nodeLookup`.
|
|
19
|
+
*/
|
|
20
|
+
import type { Reference, ScopeId } from '../../../../_shared/index.js';
|
|
21
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
22
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
23
|
+
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
24
|
+
/**
|
|
25
|
+
* Optional opaque skip key — providers may pre-emit edges (e.g. via
|
|
26
|
+
* receiver-bound post-passes) and want this loop to skip references at
|
|
27
|
+
* the same source position so the shared resolver's potentially-wrong
|
|
28
|
+
* fallback resolution doesn't fight the precise emission. The key is
|
|
29
|
+
* `${filePath}:${startLine}:${startCol}`.
|
|
30
|
+
*/
|
|
31
|
+
type ReferenceSiteSkipSet = ReadonlySet<string>;
|
|
32
|
+
export declare function emitReferencesViaLookup(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, referenceIndex: {
|
|
33
|
+
readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]>;
|
|
34
|
+
}, nodeLookup: GraphNodeLookup, skipSites?: ReferenceSiteSkipSet): {
|
|
35
|
+
emitted: number;
|
|
36
|
+
skipped: number;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate the resolved `ReferenceIndex` into legacy graph edges.
|
|
3
|
+
*
|
|
4
|
+
* Per reference:
|
|
5
|
+
* 1. Resolve `fromScope` → caller graph-node id by walking the scope
|
|
6
|
+
* chain looking for an enclosing Function/Method/Class.
|
|
7
|
+
* 2. Resolve `toDef` → target graph-node id via `nodeLookup`.
|
|
8
|
+
* 3. Emit the edge (`CALLS` / `READS` / `WRITES` / `EXTENDS` / `USES`)
|
|
9
|
+
* with the standard reason format.
|
|
10
|
+
*
|
|
11
|
+
* Skips (without throwing) when either side fails to map — either side
|
|
12
|
+
* may legitimately not exist as a graph node (e.g. a resolved target
|
|
13
|
+
* lives in an external file that wasn't ingested into the graph).
|
|
14
|
+
*
|
|
15
|
+
* Next-consumer contract: this function is the canonical bridge from
|
|
16
|
+
* a shared `ReferenceIndex` into per-language graph edges. Every
|
|
17
|
+
* registry-primary language provider calls this exactly once with its
|
|
18
|
+
* `referenceIndex` output and its own `nodeLookup`.
|
|
19
|
+
*/
|
|
20
|
+
import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
21
|
+
import { mapReferenceKindToEdgeType } from '../graph-bridge/edges.js';
|
|
22
|
+
export function emitReferencesViaLookup(graph, scopes, referenceIndex, nodeLookup, skipSites) {
|
|
23
|
+
let emitted = 0;
|
|
24
|
+
let skipped = 0;
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const [fromScope, refs] of referenceIndex.bySourceScope) {
|
|
27
|
+
const callerGraphId = resolveCallerGraphId(fromScope, scopes, nodeLookup);
|
|
28
|
+
if (callerGraphId === undefined) {
|
|
29
|
+
skipped += refs.length;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const fromScopeMeta = scopes.scopeTree.getScope(fromScope);
|
|
33
|
+
const fromFilePath = fromScopeMeta?.filePath;
|
|
34
|
+
for (const ref of refs) {
|
|
35
|
+
if (skipSites !== undefined && fromFilePath !== undefined) {
|
|
36
|
+
const siteKey = `${fromFilePath}:${ref.atRange.startLine}:${ref.atRange.startCol}`;
|
|
37
|
+
if (skipSites.has(siteKey)) {
|
|
38
|
+
skipped++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const targetDef = scopes.defs.get(ref.toDef);
|
|
43
|
+
if (targetDef === undefined) {
|
|
44
|
+
skipped++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const targetGraphId = resolveDefGraphId(targetDef.filePath, targetDef, nodeLookup);
|
|
48
|
+
if (targetGraphId === undefined) {
|
|
49
|
+
skipped++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const edgeType = mapReferenceKindToEdgeType(ref.kind);
|
|
53
|
+
if (edgeType === undefined) {
|
|
54
|
+
skipped++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const dedupKey = `${edgeType}:${callerGraphId}->${targetGraphId}:${ref.atRange.startLine}:${ref.atRange.startCol}`;
|
|
58
|
+
if (seen.has(dedupKey))
|
|
59
|
+
continue;
|
|
60
|
+
seen.add(dedupKey);
|
|
61
|
+
graph.addRelationship({
|
|
62
|
+
id: `rel:${dedupKey}`,
|
|
63
|
+
sourceId: callerGraphId,
|
|
64
|
+
targetId: targetGraphId,
|
|
65
|
+
type: edgeType,
|
|
66
|
+
confidence: ref.confidence,
|
|
67
|
+
reason: `scope-resolution: ${ref.kind}`,
|
|
68
|
+
});
|
|
69
|
+
emitted++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { emitted, skipped };
|
|
73
|
+
}
|