mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-30 01:51:20 +00:00
* feat(ingestion): ScopeExtractor driver — 5-pass CaptureMatch → ParsedFile (#919, RFC #909 Ring 2 PKG) Kicks off Ring 2 PKG. Implements RFC §5.3 + §3.2 Phase 1: the central, source-agnostic driver that turns a language provider's `CaptureMatch[]` into a `ParsedFile` — the per-file artifact the finalize orchestrator (#921) feeds into the shared `finalize()` algorithm (#915). ## Files ### New shared contracts - `gitnexus-shared/src/scope-resolution/parsed-file.ts` Per-file extraction artifact: scopes, parsedImports, localDefs, referenceSites. Structural superset of `FinalizeFile` so the finalize orchestrator threads `ParsedFile` through unchanged. - `gitnexus-shared/src/scope-resolution/reference-site.ts` Pre-resolution usage fact: name, atRange, inScope, kind, optional callForm/explicitReceiver/arity. Converted to `Reference` records by the resolution phase (populates `ReferenceIndex`). ### Ring 1 collateral tweak - `language-provider.ts: emitScopeCaptures` now returns `Promise<readonly CaptureMatch[]>` (was `readonly Capture[]`). Pre-grouping per tree-sitter match is the provider's job — the extractor expects coherent matches, not flat captures. No consumers yet (all languages still on legacy DAG), so no breakage. Docstring updated. ### New CLI module - `gitnexus/src/core/ingestion/scope-extractor.ts` Single entry point: `extract(matches, filePath, provider): ParsedFile`. Five-pass pipeline: Pass 1 — Build scope tree. `@scope.*` → `ScopeDraft[]` via range-containment parent derivation. Honors `provider.shouldCreateScope` (skip-but-reparent-children) and `provider.resolveScopeKind`. Throws `ScopeTreeInvariantError` via `buildScopeTree` on malformed input. Pass 2 — Attach declarations + local bindings. `@declaration.*` → `SymbolDefinition` + `BindingRef { origin: 'local' }`. Default attachment: innermost containing scope. Hoisting via `provider.bindingScopeFor`. Pass 3 — Collect raw imports. `@import.*` → `ParsedImport` via `provider.interpretImport`. Attached to ParsedFile (finalize resolves owning scope in Phase 2). Pass 4 — Collect type bindings. `@type-binding.*` → `TypeRef` via `provider.interpretTypeBinding` → `scope.typeBindings`. Hoistable via `bindingScopeFor`. Pass 5 — Collect reference sites. `@reference.*` → `ReferenceSite[]`. Call form from declarative sub-tag (`@reference.call.member`) or `provider.classifyCallForm`. ### Tests - `gitnexus/test/unit/scope-resolution/scope-extractor.test.ts` 23 tests organized by pass + one end-to-end fixture exercising all 5 passes together. MockProvider emits synthetic `CaptureMatch[]` with no AST — extractor is pure given those. ## Design notes - **Source-agnostic.** No `Tree` / `SyntaxNode` types leak into the driver. Works for tree-sitter providers and COBOL's regex tagger. - **One AST walk per language.** Providers do the walk inside `emitScopeCaptures`; this driver does zero traversal. - **Invariants delegated.** `ScopeTree.buildScopeTree` enforces structural rules (non-Module has parent, parent contains child, siblings don't overlap). The extractor doesn't try to repair malformed captures. - **Sub-tag whitelist.** `@reference.receiver`, `@declaration.name`, `@import.source`, etc. are known sub-tags — excluded from anchor selection so the broadest-range heuristic doesn't mis-identify them as anchors for their topic. Bug surfaced in the end-to-end fixture test (member call with a large-range receiver) and was fixed before commit. ## Verification - `tsc --noEmit` clean (both `gitnexus-shared` and `gitnexus`) - `gitnexus-shared` build clean - 23/23 new tests pass - Full scope-resolution / model / shadow suite: **285/285 pass** ## Closes part of #909. Unblocks - #920 parse-worker integration (emit ParsedFile from the worker) - #921 finalize orchestrator (consume ParsedFile[] workspace-wide) - #922 per-language import adapters * chore(ingestion): address #919 review findings on the extractor Addresses all 5 items from the PR #965 review in-PR. ## Structural changes - **Extract `ScopeExtractorHooks` as the narrow dependency surface.** The extractor now declares its dependency on a `Pick`-narrowed subset of `LanguageProvider` (just the 6 scope-resolution hooks it actually reads). Test mocks implement exactly that interface — no more `as unknown as LanguageProvider` cast hiding missing-field bugs. Adding a new hook read becomes a compile error, not a silent test pass. (Finding 3.2) - **Remove dead `ownerDefIdFor` stub + `isOwnerKind` helper.** The function always returned `undefined` with `void innermost; void drafts;` suppressors — an incomplete-implementation signal. The code path was also misleading: creating a clone of the def with `ownerId: undefined` is structurally identical to keeping the original. Pass 2 now keeps the def as-is. Contract is documented in a code comment: providers that need `ownerId` set it from their declaration hook; `finalize` (via #914 `MethodDispatchIndex`) fills in method/field `ownerId` in a post-extraction pass that has full def visibility. (Finding 2.1) - **Standardize `filePath` threading across passes 4 and 5.** Pass 4 was reading `drafts[0]!.filePath`; pass 5 was reading `anyFilePathFromScopeTree(scopeTree)`. Both equivalent but inconsistent. Both now take `filePath` as a parameter from the top-level `extract()` call. The `anyFilePathFromScopeTree` helper is removed. (Finding 2.2) ## Documentation - **Snapshot-semantics comment on `scopeTree` + `positionIndex`.** The hooks called during Passes 2-5 receive a `scopeTree` built BEFORE any bindings/ownedDefs/typeBindings were written. Hooks MUST NOT rely on `scope.bindings` etc. being populated — they're for parent/range/kind queries only. Added a doc block at the `scopeTree`/`positionIndex` construction site so future Ring 3 implementers don't write a `classifyCallForm` that reads bindings. (Finding 2.3) ## Tests - **Regression for the anchor-vs-receiver bug** (Finding 3.1): a member-call match where `@reference.receiver` spans columns 0-10 (wider) and the call name spans 11-15 (narrower). Without the `KNOWN_SUB_TAGS` exclusion, the broadest-range heuristic would have picked the receiver; the test pins that the call name is the one that ends up in `referenceSites[0].name`. - **Mock provider now types exactly `ScopeExtractorHooks`**, no more double-cast. Any future hook added to `extract()` that isn't in `ScopeExtractorHooks` is a compile error. ## Verification - `tsc --noEmit` clean in both `gitnexus-shared` and `gitnexus` - `gitnexus-shared` build clean - 24/24 scope-extractor tests pass (+1 regression) - Full scope-resolution / model / shadow suite: **286/286 pass**
150 lines
6 KiB
TypeScript
150 lines
6 KiB
TypeScript
// Graph types
|
|
export type {
|
|
NodeLabel,
|
|
NodeProperties,
|
|
RelationshipType,
|
|
GraphNode,
|
|
GraphRelationship,
|
|
} from './graph/types.js';
|
|
|
|
// Schema constants
|
|
export {
|
|
NODE_TABLES,
|
|
REL_TABLE_NAME,
|
|
REL_TYPES,
|
|
EMBEDDING_TABLE_NAME,
|
|
} from './lbug/schema-constants.js';
|
|
export type { NodeTableName, RelType } from './lbug/schema-constants.js';
|
|
|
|
// Language support
|
|
export { SupportedLanguages } from './languages.js';
|
|
export { getLanguageFromFilename, getSyntaxLanguageFromFilename } from './language-detection.js';
|
|
export type { MroStrategy } from './mro-strategy.js';
|
|
|
|
// Pipeline progress
|
|
export type { PipelinePhase, PipelineProgress } from './pipeline.js';
|
|
|
|
// ─── Scope-based resolution — RFC #909 (Ring 1 #910) ────────────────────────
|
|
// Data model (RFC §2)
|
|
export type { SymbolDefinition } from './scope-resolution/symbol-definition.js';
|
|
export type {
|
|
ScopeId,
|
|
DefId,
|
|
ScopeKind,
|
|
Range,
|
|
Capture,
|
|
CaptureMatch,
|
|
BindingRef,
|
|
ImportEdge,
|
|
TypeRef,
|
|
Scope,
|
|
ResolutionEvidence,
|
|
Resolution,
|
|
Reference,
|
|
ReferenceIndex,
|
|
LookupParams,
|
|
RegistryContributor,
|
|
ParsedImport,
|
|
ParsedTypeBinding,
|
|
WorkspaceIndex,
|
|
Callsite,
|
|
ScopeLookup,
|
|
} from './scope-resolution/types.js';
|
|
|
|
// Evidence + tie-break constants (RFC Appendix A, Appendix B)
|
|
export { EvidenceWeights, typeBindingWeightAtDepth } from './scope-resolution/evidence-weights.js';
|
|
export { ORIGIN_PRIORITY } from './scope-resolution/origin-priority.js';
|
|
export type { OriginForTieBreak } from './scope-resolution/origin-priority.js';
|
|
|
|
// Language classification (RFC §6.1 Ring 3/4 governance)
|
|
export {
|
|
LanguageClassifications,
|
|
isProductionLanguage,
|
|
} from './scope-resolution/language-classification.js';
|
|
export type { LanguageClassification } from './scope-resolution/language-classification.js';
|
|
|
|
// Core indexes over per-file artifacts (RFC §3.1; Ring 2 SHARED #913)
|
|
export { buildDefIndex } from './scope-resolution/def-index.js';
|
|
export type { DefIndex } from './scope-resolution/def-index.js';
|
|
export { buildModuleScopeIndex } from './scope-resolution/module-scope-index.js';
|
|
export type { ModuleScopeIndex, ModuleScopeEntry } from './scope-resolution/module-scope-index.js';
|
|
export { buildQualifiedNameIndex } from './scope-resolution/qualified-name-index.js';
|
|
export type { QualifiedNameIndex } from './scope-resolution/qualified-name-index.js';
|
|
|
|
// Strict type-reference resolver (RFC §4.6; Ring 2 SHARED #916)
|
|
// `ScopeLookup` is defined in `./scope-resolution/types.js` and exported
|
|
// from the type-export block above — not from this module.
|
|
export { resolveTypeRef } from './scope-resolution/resolve-type-ref.js';
|
|
export type { ResolveTypeRefContext } from './scope-resolution/resolve-type-ref.js';
|
|
|
|
// ScopeExtractor output contracts (RFC §3.2 Phase 1; Ring 2 PKG #919)
|
|
export type { ParsedFile } from './scope-resolution/parsed-file.js';
|
|
export type { ReferenceSite, ReferenceKind, CallForm } from './scope-resolution/reference-site.js';
|
|
|
|
// Method-dispatch materialized view over HeritageMap (RFC §3.1; Ring 2 SHARED #914)
|
|
export { buildMethodDispatchIndex } from './scope-resolution/method-dispatch-index.js';
|
|
export type {
|
|
MethodDispatchIndex,
|
|
MethodDispatchInput,
|
|
} from './scope-resolution/method-dispatch-index.js';
|
|
|
|
// SCC-aware cross-file finalize (RFC §3.2 Phase 2; Ring 2 SHARED #915)
|
|
export { finalize } from './scope-resolution/finalize-algorithm.js';
|
|
export type {
|
|
FinalizeInput,
|
|
FinalizeFile,
|
|
FinalizeHooks,
|
|
FinalizeOutput,
|
|
FinalizedScc,
|
|
FinalizeStats,
|
|
} from './scope-resolution/finalize-algorithm.js';
|
|
|
|
// Scope-aware registries + 7-step lookup (RFC §4; Ring 2 SHARED #917)
|
|
export { buildClassRegistry } from './scope-resolution/registries/class-registry.js';
|
|
export type { ClassRegistry } from './scope-resolution/registries/class-registry.js';
|
|
export { buildMethodRegistry } from './scope-resolution/registries/method-registry.js';
|
|
export type {
|
|
MethodRegistry,
|
|
MethodLookupOptions,
|
|
} from './scope-resolution/registries/method-registry.js';
|
|
export { buildFieldRegistry } from './scope-resolution/registries/field-registry.js';
|
|
export type {
|
|
FieldRegistry,
|
|
FieldLookupOptions,
|
|
} from './scope-resolution/registries/field-registry.js';
|
|
export { lookupCore } from './scope-resolution/registries/lookup-core.js';
|
|
export type { CoreLookupParams } from './scope-resolution/registries/lookup-core.js';
|
|
export { lookupQualified } from './scope-resolution/registries/lookup-qualified.js';
|
|
export type { LookupQualifiedParams } from './scope-resolution/registries/lookup-qualified.js';
|
|
export { composeEvidence, confidenceFromEvidence } from './scope-resolution/registries/evidence.js';
|
|
export type { RawSignals } from './scope-resolution/registries/evidence.js';
|
|
export {
|
|
compareByConfidenceWithTiebreaks,
|
|
CONFIDENCE_EPSILON,
|
|
} from './scope-resolution/registries/tie-breaks.js';
|
|
export type { TieBreakKey } from './scope-resolution/registries/tie-breaks.js';
|
|
export { CLASS_KINDS, METHOD_KINDS, FIELD_KINDS } from './scope-resolution/registries/context.js';
|
|
export type {
|
|
RegistryContext,
|
|
RegistryProviders,
|
|
OwnerScopedContributor,
|
|
ArityVerdict,
|
|
} from './scope-resolution/registries/context.js';
|
|
|
|
// Scope tree spine + position lookup (RFC §2.2 + §3.1; Ring 2 SHARED #912)
|
|
export { makeScopeId, clearScopeIdInternPool } from './scope-resolution/scope-id.js';
|
|
export type { ScopeIdInput } from './scope-resolution/scope-id.js';
|
|
export { buildScopeTree, ScopeTreeInvariantError } from './scope-resolution/scope-tree.js';
|
|
export type { ScopeTree } from './scope-resolution/scope-tree.js';
|
|
export { buildPositionIndex } from './scope-resolution/position-index.js';
|
|
export type { PositionIndex } from './scope-resolution/position-index.js';
|
|
|
|
// Shadow-mode diff + aggregation (RFC §6.3; Ring 2 SHARED #918)
|
|
export { diffResolutions } from './scope-resolution/shadow/diff.js';
|
|
export type {
|
|
ShadowAgreement,
|
|
ShadowCallsite,
|
|
ShadowDiff,
|
|
} from './scope-resolution/shadow/diff.js';
|
|
export { aggregateDiffs } from './scope-resolution/shadow/aggregate.js';
|
|
export type { LanguageParityRow, ShadowParityReport } from './scope-resolution/shadow/aggregate.js';
|