mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
715 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2b7cff5fd2
|
feat(embeddings): structural chunking with data-driven CHUNKING_RULES dispatch (#987)
* feat(embeddings): structural chunking with data-driven CHUNKING_RULES dispatch Replace hardcoded label comparisons with a CHUNKING_RULES lookup table that drives chunking strategy and text generation. Key changes: - Data-driven dispatch: CHUNKING_RULES table maps labels to chunking mode (ast-function / ast-declaration), prefix/suffix, field grouping, and structural text mode - Struct support: add Struct to AST declaration chunking with field grouping (same as Class) - Multi-chunk context: preceding chunk tail (prevTail) injected into embedding text for cross-chunk coherence - Version-gated hashes: EMBEDDING_TEXT_VERSION prefix in content hashes invalidates stale vectors when text template changes - Compact container context: first declaration line preserved in every structural chunk for identity * fix(embeddings): address PR review findings for CHUNKING_RULES refactor - Remove LABEL_ENUM from STRUCTURAL_LABELS to avoid wasted AST parses - Add maintenance note about extractStructuralNames and EMBEDDING_TEXT_VERSION - Clarify CHUNK_MODE_CHARACTER is a no-op in CHUNKING_RULES - Strengthen EMBEDDING_TEXT_VERSION test assertion to exact value --------- Co-authored-by: wangjichao <wangjichao@inke.cn> |
||
|
|
d976038dc8
|
fix: guard RC docker job against empty vtag and add early validation in docker.yml (#983)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
Release Candidate / Build & Push RC Docker images (push) Blocked by required conditions
* Initial plan * fix: guard docker job and add tag validation in docker.yml - Add `&& needs.publish.outputs.vtag != ''` to the `docker` job's `if:` in release-candidate.yml so it is skipped when publish produces no vtag, preventing an opaque buildx "tag is needed" error. - Add an early "Validate tag input" step in docker.yml that fails fast with a clear ::error:: message when inputs.tag is empty, covering direct workflow_call invocations that bypass the release-candidate guard. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b9afe2df-85ea-4a87-bf30-77f0e945a64d Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: scope docker.yml tag validation to workflow_call only Direct tag-push triggers (on: push, tags: v*) populate the tag from GITHUB_REF and have inputs.tag empty, so the unconditional validation step would fail every direct tag-push run. Restrict the new step to workflow_call invocations, which is the only path where an empty tag is actually a problem. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/4b7e3bfa-15c0-4186-affa-95cd71e50153 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> |
||
|
|
9926804d75
|
feat(cli): infer registry name from git remote.origin.url (#981)
* Initial plan * Plan: smarter index name inference via git remote URL Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/95064d2d-b1da-4c89-9069-5b3e9cc2636a Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * feat(cli): infer registry name from git remote.origin.url (#979) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/95064d2d-b1da-4c89-9069-5b3e9cc2636a Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * refactor: skip git subprocess when --name was supplied (review feedback) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/95064d2d-b1da-4c89-9069-5b3e9cc2636a Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * style: prettier --write on run-analyze.ts Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/a4bf631d-ea6b-4d84-b426-29b1e5c3539f Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> |
||
|
|
fa39a4b4a5
|
fix(docker): build and push Docker images for Release Candidates (#978) | ||
|
|
dae7bd3b3f
|
feat(cli): analyze --name <alias> + duplicate-name guard for the repo registry (#955) | ||
|
|
363245eb63
|
fix: detect React component paths before lowercasing (#260) | ||
|
|
6222b5be9b
|
feat(ingestion): emit-references drains ReferenceIndex to graph edges (#925, RFC #909 Ring 2 PKG) (#973)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
|
||
|
|
e2ba4a04c9
|
feat(ingestion): shadow-mode parity harness + static dashboard (#923, RFC #909 Ring 2 PKG) (#972)
* feat(ingestion): shadow-mode parity harness + static dashboard (#923, RFC #909 Ring 2 PKG) Side-car observability for the RFC #909 registry rollout. Callers that dual-run legacy-DAG + `Registry.lookup` feed their result pairs into the harness; the harness diffs each pair via shared `diffResolutions` (#918), aggregates via `aggregateDiffs`, and persists a per-language parity report that the static dashboard can render offline. ## Shipped ### `gitnexus/src/core/ingestion/shadow-harness.ts` (new) ```ts createShadowHarness(): ShadowHarness ``` API: - `enabled` — `true` iff `GITNEXUS_SHADOW_MODE` is truthy at construction. Captured once; later env-var mutations don't flip it. - `record({ language, callsite, legacy, newResult, primary })` — accumulator. No-op when `enabled === false` (near-zero overhead). - `size()` — diagnostic counter. - `snapshot(now?)` — deterministic `ShadowParityReport` from the accumulated diffs. - `persist(outputDir, now?)` — writes BOTH a timestamped `<runId>.json` and a `latest.json` pointer. Creates outputDir if absent. Returns the per-run file path. - `clear()` — resets the accumulator; preserves `enabled`. Activation: `GITNEXUS_SHADOW_MODE` accepts `'true'` / `'1'` / `'yes'` (case-insensitive, trimmed); same truthy convention as `REGISTRY_PRIMARY_<LANG>` from #924. Typos → disabled (fail-safe). Persisted payload (`PersistedShadowReport`) is schema-versioned (`v1`): ```jsonc { "schemaVersion": 1, "runId": "YYYYMMDD-HHMMSS-xxxxxxxx", "generatedAt": "ISO 8601", "primaryByLanguage": { "python": "legacy", ... }, "report": { /* ShadowParityReport from #918 aggregateDiffs */ } } ``` `runId` prefix is the timestamp so files sort chronologically; the entropy suffix prevents collisions within a clock-second. ### `gitnexus/shadow-parity-dashboard/index.html` (new) Minimal static dashboard — one HTML file, zero build step, zero runtime deps. Fetches `./latest.json` and renders: - Overall summary cards (total calls, both agree, disagree, overall parity %) - Per-language table: language tag ("primary: legacy" / "primary: registry" pill) + total / agree / only-legacy / only-new / disagree / both-empty / parity% - Parity cells colored by threshold: ≥95% green, ≥80% amber, <80% red - Light / dark via `prefers-color-scheme` - Empty-state message when no records yet File-serving is static: `cp .gitnexus/shadow-parity/latest.json gitnexus/shadow-parity-dashboard/` + open in a browser. ## Tests (14, all passing) - **Flag detection** (5): default off · truthy variants case-insensitive · falsy / typo → off · record() is no-op when disabled · env flip AFTER construction doesn't enable (constructed-once semantics) - **Record + snapshot** (4): multi-language accumulation · per-language rows with correct outcomes · snapshot determinism · `clear()` resets accumulator + `primaryByLanguage` - **Persistence** (5): mkdir-p on missing outputDir · per-run + latest.json match byte-for-byte · schema v1 payload shape · runId timestamp prefix sorts chronologically · empty report persists gracefully Tests use a per-test tmpdir (`fs.mkdtemp`), cleaned in `afterEach`, so parallel vitest runs don't collide. `GITNEXUS_SHADOW_MODE` is saved + restored per-test. ## What's deliberately NOT in this PR (call-out in harness docstring) - **Dual-run dispatch.** The harness is a side-car — it does NOT invoke either resolution path. Call-processor integration that actually runs both legacy + registry paths lands as a follow-up. Without that integration, `record()` is never called in production today. The harness is tested in isolation with synthetic inputs. - **CI artifact publishing.** Config work to upload `latest.json` + the dashboard HTML per CI run. Tracked separately; the harness + dashboard are ready when the CI job wires in. - **Fixture-level drill-down.** The issue mentions per-fixture AST snippet + evidence trace drill-down. MVP dashboard shows per-language rows only; drill-down extends the static JSON format + the dashboard JS in a focused follow-up. ## Verification - `tsc --noEmit` clean (both `gitnexus-shared` and `gitnexus`) - 14/14 new tests pass - Full scope-resolution / shadow / model / flag suite: **335/335 pass** ## Part of - Parent: #909 - Depends on (code): #917 (registries), #918 (diff + aggregate) - Unblocks Ring 3 language flips: the parity dashboard becomes the checkpoint before flipping `REGISTRY_PRIMARY_<LANG>=true` for a language — once per-language parity stabilizes, the flip ships. * chore: prettier format on shadow-parity-dashboard index.html |
||
|
|
0c37eda482
|
feat(ingestion): per-language resolveImportTarget adapter (#922, RFC #909 Ring 2 PKG) (#971)
Bridges the CLI's existing per-language `ImportResolverFn`s (16 languages already implemented) to the shared `FinalizeHooks.resolveImportTarget` contract consumed by `finalize()` (#915) and `finalizeScopeModel` (#921). No resolver logic is reimplemented — the adapter wraps `provider.importResolver` from each `LanguageProvider` verbatim. ## Shipped ### `import-target-adapter.ts` (new) ```ts buildImportTargetWorkspace(providers, resolveCtx): ImportTargetWorkspace resolveImportTargetAcrossLanguages(targetRaw, fromFile, workspaceIndex): string | null ``` - `ImportTargetWorkspace` is the opaque `workspaceIndex` shape the adapter recognizes: `{ perLanguage: Map<SupportedLanguages, { resolver, ctx }> }`. Callers build it once per ingestion run from the active language providers. - `resolveImportTargetAcrossLanguages` is the `FinalizeHook` implementation. It: 1. Reads `getLanguageFromFilename(fromFile)`. 2. Looks up the per-language entry. 3. Calls the existing `ImportResolverFn` — same signature, same code path the legacy DAG uses today. 4. Picks `result.files[0]` (covers both `'files'` and `'package'` result kinds; the legacy pipeline's richer multi-file + dirSuffix semantics stay accessible through `importResolver` directly). 5. Returns `null` on any null result, empty files[], unknown extension, missing workspace, or resolver exception. - Exceptions from resolvers are swallowed — the finalize algorithm treats `null` as `linkStatus: 'unresolved'`, which is the right fallback for malformed inputs. ### What's deliberately NOT here - **Re-implementation of any per-language resolver.** Wraps the existing `importResolver` field on each provider. - **Dynamic-import handling.** The shared finalize algorithm short- circuits `ParsedImport { kind: 'dynamic-unresolved' }` before calling `resolveImportTarget`, so the adapter never sees them. - **`importPathPreprocessor`.** Preprocessing belongs inside the provider's `interpretImport` hook that produces `ParsedImport.targetRaw`; the adapter forwards that verbatim. ## Tests (12, all passing) - **`buildImportTargetWorkspace`** (3): registers providers with importResolver · skips providers without · threads shared ctx into every entry - **`resolveImportTargetAcrossLanguages`** (9): forwards targetRaw + fromFile · dispatches by extension · null resolver result → null · `package`-kind takes first file · empty files[] → null · no registered resolver → null · unknown extension → null · undefined/malformed workspace → null · resolver throw → null Real per-language resolver correctness is covered by the existing per-language resolver test suites — the adapter is the bridge layer. ## Verification - `tsc --noEmit` clean (both `gitnexus-shared` and `gitnexus`) - `gitnexus-shared` build clean - 12/12 new tests pass - Full scope-resolution / shadow / model / flag suite: **333/333 pass** ## Integration flow ```ts const workspace = buildImportTargetWorkspace(providers, resolveCtx); const indexes = finalizeScopeModel(parsedFiles, { hooks: { resolveImportTarget: resolveImportTargetAcrossLanguages }, workspaceIndex: workspace, }); model.attachScopeIndexes(indexes); ``` ## Closes part of #909. Unblocks - Ring 3 language migrations (#926+): a language flipping to `REGISTRY_PRIMARY_<LANG>=true` now has correct import-target resolution out of the box via its existing `importResolver`. - #923 shadow harness — can run the dual-path comparison knowing both sides use the same per-language resolution semantics. |
||
|
|
3adb97e993
|
feat(docker): ship signed UI + CLI/server images via docker-compose (#967)
* Initial plan * docker: ship signed UI + CLI/server images via docker-compose Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/883bcee1-4a1d-4b3d-bbb9-accd8846da96 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docker: lock image version to npm package + harden cosign verify guidance Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/6afd4fcd-5656-4e02-b796-a22b59000bde Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docker: add Sigstore ClusterImagePolicy + k8s admission docs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/aea3dd70-e2a9-443a-b578-cb3eca4093e1 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docker(k8s): collapse redundant image globs in ClusterImagePolicy Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/aea3dd70-e2a9-443a-b578-cb3eca4093e1 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docker(ci): drop deprecated COSIGN_EXPERIMENTAL, dead build-args, and loose verify regex in comment Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/bdf0d2cf-607c-4558-982a-be9b216b2d36 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docker(ci): use ${{ github.repository }} in verify-comment regex for fork portability Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/bdf0d2cf-607c-4558-982a-be9b216b2d36 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * style(deploy): prettier-format cluster-image-policy.yaml (single quotes) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b09a016e-56a1-4b73-bacc-e69084a48782 * ci(docker): drop workflow_dispatch, harden signing loop, fix verify-comment placeholder Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/6755064c-7871-4b2e-9b46-b4779eb215ac --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> |
||
|
|
25520e90a5
|
feat(ingestion): finalize-orchestrator materializes ScopeResolutionIndexes (#921, RFC #909 Ring 2 PKG) (#970)
Ties the Ring 2 pipeline together. Takes the `ParsedFile[]` produced by #920's parse-worker integration, feeds them to shared `finalize()` (#915), and bundles every workspace-wide index for attachment onto `MutableSemanticModel`. Thin integration glue per issue #884's boundary — all algorithm lives in `gitnexus-shared`. ## Shipped ### `model/scope-resolution-indexes.ts` (new) ```ts interface ScopeResolutionIndexes { readonly scopeTree: ScopeTree; readonly defs: DefIndex; readonly qualifiedNames: QualifiedNameIndex; readonly moduleScopes: ModuleScopeIndex; readonly methodDispatch: MethodDispatchIndex; readonly imports: ReadonlyMap<ScopeId, readonly ImportEdge[]>; readonly bindings: ReadonlyMap<ScopeId, ReadonlyMap<string, readonly BindingRef[]>>; readonly referenceSites: readonly ReferenceSite[]; readonly sccs: readonly FinalizedScc[]; readonly stats: FinalizeStats; } ``` The bundle produced by the orchestrator, consumed by the resolution phase. `ReferenceIndex` is deliberately NOT here — it's populated in the next phase (#925). ### `model/semantic-model.ts` — extended - `SemanticModel.scopes?: ScopeResolutionIndexes` — undefined until attached; once attached, frozen. - `MutableSemanticModel.attachScopeIndexes(indexes)` — one-shot write. Throws on second call; `Object.freeze`s the bundle on write. `clear()` resets the slot back to `undefined` so re-ingestion can re-attach. ### `finalize-orchestrator.ts` (new) ```ts finalizeScopeModel(parsedFiles, options?): ScopeResolutionIndexes ``` Orchestration steps: 1. Map `ParsedFile[]` → `FinalizeInput` (`FinalizeFile` is a structural subset, so no shape-shifting). 2. Call shared `finalize()` with provider hooks (defaults provided for the zero-provider case today). 3. Build the four workspace indexes (`DefIndex`, `QualifiedNameIndex`, `ModuleScopeIndex`, `ScopeTree`) from per-file unions. 4. Build an empty `MethodDispatchIndex` as a placeholder (owners=[], both callbacks return []). Real MRO wiring lands with the per-language adapters in #922. 5. Bundle + return. **Empty-input safety.** Zero parsedFiles → valid but empty bundle with all zero-sized indexes and `stats.totalFiles === 0`. Downstream code can consult `model.scopes` without branching on presence — only on `stats`. **Hook defaults** (`withDefaultHooks`) for missing provider hooks: - `resolveImportTarget: () => null` — every import goes `unresolved` - `expandsWildcardTo: () => []` — wildcards don't materialize - `mergeBindings: (a, b) => [...a, ...b]` — append without precedence Providers override these in #922 (per-language import adapters). ## Tests (10, all passing) - **Empty input** (1): zero parsedFiles → valid empty bundle - **Single file** (2): all per-file indexes populated · referenceSites aggregated - **Cross-file imports** (3): resolveImportTarget threads through + links · default-null resolver → unresolved · stats reflect graph - **MutableSemanticModel integration** (4): undefined initially · attach once · Object.freeze applied · throws on re-attach · clear() resets ## Verification - `tsc --noEmit` clean in both packages - `gitnexus-shared` build clean - 10/10 new tests pass - Full scope-resolution / shadow / model / flag suite: **321/321 pass** ## What's deferred (not this PR, per RFC #909 scope) - **Per-language hook adapters** (#922): `resolveImportTarget` + `expandsWildcardTo` + `mergeBindings` wired per language. - **MethodDispatchIndex wiring via HeritageMap**: populate MRO + implements via the existing CLI-package HeritageMap strategies. Likely companion to #922 or a focused follow-up. - **Pipeline invocation**: actually calling `finalizeScopeModel` from the real ingestion pipeline. The orchestrator is callable today; the ingestion entry point wiring lands with the shadow harness (#923). - **`ReferenceIndex` population**: RFC §3.2 Phase 4 / #925. ## Closes part of #909. Unblocks - #923 shadow harness — now has a fully materialized `model.scopes` to query against the legacy DAG for parity measurement - #925 ReferenceIndex → LadybugDB emission — consumes `model.scopes` - Ring 3 language migrations (#926+) — a language flipping to `REGISTRY_PRIMARY_<LANG>=true` can now expect `model.scopes` to be populated when the pipeline wires the orchestrator in |
||
|
|
39b5d295c7
|
feat(ingestion): wire ScopeExtractor into parse-worker + processor (#920, RFC #909 Ring 2 PKG) (#969)
Plumbs the ScopeExtractor (#919) into the real parsing pipeline. `ParsedFile` artifacts now flow from workers to the parsing-processor without changing any legacy-DAG behavior. ## Shipped ### `gitnexus/src/core/ingestion/scope-extractor-bridge.ts` (new) - `extractParsedFile(provider, sourceText, filePath, onWarn?)` - Short-circuits (returns `undefined`) when the provider has not implemented `emitScopeCaptures`. True for every language today — this is the default no-op path. - Invokes the hook + `ScopeExtractor.extract`, returns a `ParsedFile`. - **Swallows exceptions on both sides.** Failures route through the optional `onWarn` callback (or `console.warn`) and return `undefined`. Scope-extraction errors NEVER break legacy parsing on the same file. - Standalone module (not nested in `parse-worker.ts`) so tests can import it directly without triggering the worker's top-level `parentPort!.on(...)`. ### `gitnexus/src/core/ingestion/workers/parse-worker.ts` - `ParseWorkerResult.parsedFiles: ParsedFile[]` added. - `processFileGroup` calls `extractParsedFile` AFTER tree parse, BEFORE legacy extraction. Worker provides an `onWarn` callback that routes bridge warnings through `parentPort.postMessage({ type: 'warning', message })`. - `mergeResult` includes `parsedFiles` in the sub-batch merge. - Initial + reset accumulator templates include `parsedFiles: []`. ### `gitnexus/src/core/ingestion/parsing-processor.ts` - `WorkerExtractedData.parsedFiles: ParsedFile[]` added. - Empty-result branch and the across-chunk aggregation both include `parsedFiles`. Aggregation is tolerant of workers that don't emit the field (older builds / partial rollouts). ### Ring 1 tweak: `emitScopeCaptures` sync return `readonly CaptureMatch[]` (was `Promise<readonly CaptureMatch[]>`). Tree-sitter and COBOL's regex tagger are both synchronous; no foreseeable need for async work inside this hook. Sync lets the already-sync worker pipeline invoke it inline without cascading `async` up through the batch driver + IPC handler. ## Tests (9 new; full suite 311/311) `gitnexus/test/unit/scope-resolution/parse-worker-scope-integration.test.ts`: - Not-migrated (2): undefined-returning hook · never-invokes-extractor - Migrated (3): happy path · argument threading · honors `shouldCreateScope` override - Error resilience (4): hook throws · extractor throws (no Module) · extractor throws (sibling overlap) · `onWarn` gets routed message with filePath + error body ## Verification - `tsc --noEmit` clean in both packages - `gitnexus-shared` build clean - 311/311 combined scope-resolution / shadow / model / flag suite - 9/9 new bridge tests ## What's NOT in this PR (still deferred to #921) - Actually using the `parsedFiles` — that's the finalize orchestrator. - `ModuleScopeIndex.byFilePath` materialization — belongs alongside the rest of the SemanticModel indexes in #921. ## Closes part of #909. Unblocks - #921 finalize-orchestrator — consumes `WorkerExtractedData.parsedFiles` |
||
|
|
eece6344fc
|
feat(ingestion): REGISTRY_PRIMARY_<LANG> per-language flag reader (#924, RFC #909 Ring 2 PKG) (#968)
Adds the per-language feature flag primitive that gates the Ring 3
registry-primary rollout. Single source of truth for whether a given
language uses `Registry.lookup` (new) or the legacy DAG (current).
## Shipped
### `gitnexus/src/core/ingestion/registry-primary-flag.ts`
- `isRegistryPrimary(lang): boolean` — reads
`REGISTRY_PRIMARY_<UPPER(enum-value)>` from `process.env`.
- `envVarNameFor(lang): string` — exposed for CI tooling that
cross-references flag flips (and for test assertions).
- `primaryLanguages(): ReadonlySet<SupportedLanguages>` — all
currently-on languages; useful for startup logging + the #923
shadow dashboard which distinguishes "primary: legacy" vs
"primary: registry" rows.
### Contract
- Default: `false` for every language. A language must explicitly
opt in by setting its env var.
- Truthy: `'true'`, `'1'`, `'yes'` (case-insensitive, whitespace-
trimmed). Anything else — typos, empty string, `'off'` — is
`false`. Fail-safe posture: a misspelled flag doesn't accidentally
flip a language.
- No per-process caching. `process.env` is read per call; overhead
is negligible (one lookup per file at resolution time), and
test isolation is lexical (no cache-reset coordination).
### Env-var mapping
Uses the enum VALUE, not the TS key, for the env-var suffix:
- `SupportedLanguages.Python` → `REGISTRY_PRIMARY_PYTHON`
- `SupportedLanguages.CPlusPlus` → `REGISTRY_PRIMARY_CPP` (value `'cpp'`)
- `SupportedLanguages.CSharp` → `REGISTRY_PRIMARY_CSHARP`
Users flip languages by their canonical name, not the TS symbol.
## Tests (16, all passing)
- `envVarNameFor` (3): upper-casing · enum-VALUE-not-KEY mapping ·
all-languages uniqueness smoke-test
- `isRegistryPrimary` (9): default false · `'true'` / `'1'` / `'yes'`
truthy · mixed-case + whitespace-padded · falsy-looking values ·
unrecognized tokens (typo-safe) · per-language isolation · no
stale cache on mid-process mutation · CPlusPlus mapping
- `primaryLanguages` (3): empty · exact membership · Set instanceof
Tests scrub every `REGISTRY_PRIMARY_*` env var in `beforeEach` +
`afterEach` so parallel vitest runs on the same process don't bleed state.
## What's NOT in this PR (deferred by design)
The actual integration in `call-processor.ts` belongs in #921
(finalize-orchestrator). Reason: the "new path" requires a populated
`SemanticModel` to call `Registry.lookup` against, and the model
becomes accessible only after #921 orchestrates finalize. Wiring a
dead branch now would just get rewritten then.
This PR ships the flag primitive in isolation so #921 has a clean,
tested utility to consult — and so `#923` (shadow harness) has a
stable boolean to read for its "which row is primary?" rendering.
## Closes part of #909. Unblocks
- #921 finalize-orchestrator — can now consult `isRegistryPrimary`
at resolution time
- #923 shadow harness — can distinguish primary-flipped rows
|
||
|
|
c6a291de67
|
feat(ingestion): ScopeExtractor driver — 5-pass CaptureMatch → ParsedFile (#919, RFC #909 Ring 2 PKG) (#965)
* 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** |
||
|
|
e944f90879
|
chore(shared): apply Ring 2 SHARED review follow-ups in one diff (#964)
* chore(shared): apply Ring 2 SHARED review follow-ups in one diff Aggregates all actionable follow-ups from the 9 Ring 2 SHARED PRs (#949–#963) before proceeding to Ring 2 PKG. No behavior changes; docstring edits, test refinements, and one structural cleanup. ## #913 (DefIndex / ModuleScopeIndex / QualifiedNameIndex) - Rename `freezeIndex` → `wrapIndex` across all three index builders. The old name implied `Object.freeze` on the wrapper, which we never applied; `wrapIndex` more accurately describes the lightweight readonly-interface wrap. Safety surface (frozen bucket arrays, frozen miss-empty array, readonly Maps) is unchanged. - Document in `buildModuleScopeIndex` JSDoc that callers must pre-normalize `filePath` keys (no path-separator canonicalization happens here). Prevents silent cross-platform misses. - Add an explicit hit-path freeze assertion in `qualified-name-index.test.ts` (the existing test covered only the miss-path `EMPTY` array). ## #914 (MethodDispatchIndex) - Differentiate the C3 and BFS test cases: both tests now use distinct MRO orderings so they prove the materializer stores whatever order the `computeMro` callback produces (not that C3 and BFS yield identical output). - Add `implementsOfCalls` counter in the first-write-wins test, and document the call-count contract in `MethodDispatchInput.implementsOf` JSDoc: `implementsOf` fires **per occurrence** in `input.owners` (not per unique owner); `computeMro` fires at most once per unique owner. Callers with expensive `implementsOf` implementations should pre-dedupe `owners`. ## #916 (resolveTypeRef) - Document the deliberate exclusion of `'Type'` from `TYPE_KINDS` (verified no extractor in `gitnexus/src/core/ingestion/` emits `type: 'Type'` for annotation-relevant symbols). - Rename the namespace-origin test from `'resolves ...'` to `'returns null for a namespace-origin binding whose def is not a type kind'`, matching the failure-case intent. ## #918 (shadow diff + aggregate) - Remove the partial re-export `export type { ShadowAgreement, ShadowDiff };` from `aggregate.ts` — it omitted `ShadowCallsite` and diverged from the top-level barrel. Consumers import all three from the `gitnexus-shared` entry point. - Fix the invalid `'wildcard'` evidence kind in `diff.test.ts` fixture (that kind is not a valid `ResolutionEvidence.kind`). Replaced with `'global-name'`, a real kind the test treats identically. ## #912 (ScopeTree / PositionIndex / makeScopeId) - Document the touching-boundary semantics on `PositionIndex.atPosition`: when siblings share a boundary point, the right (later-start) sibling wins per the existing innermost-wins sort contract. - Resolve the layer-inversion flagged by review: move `ScopeLookup` from `resolve-type-ref.ts` to `types.ts` (its natural home in the data-model layer). `scope-tree.ts` now imports `ScopeLookup` from `types.js` directly; the old re-export from `resolve-type-ref.ts` is removed per repo convention (`feedback_no_reexport`). Barrel export moved alongside. ## #917 (ClassRegistry / MethodRegistry / FieldRegistry) - Replace the dangling "try a name-match among class-like defs" comment in `lookupReceiverType` with explicit prose that callers must pre-resolve via `resolveTypeRef` if they want richer semantics. No behavior change — the function already returned `undefined` on ambiguous/missing qnames. - Fix `tieBreakKey.origin` default for pure Step-2 candidates. Type-binding-only hits no longer falsely inherit `'local'` from `ensureCandidate`'s neutral default; they now demote to `'import'` on their first type-binding hit, and only a later Step-1 lexical hit can upgrade them back to `'local'`. Keeps the Appendix B cascade faithful to the true origin. - Document `'global-name'` in `evidence.ts`: currently reserved for Ring 3's byName global index; `lookupCore` never emits it today. The weight stays live so `composeEvidence` remains exhaustive over the origin union. - Rename the mislabeled Step-7 test from `'confidence DESC is the primary key'` (which actually tested hard-shadow baseline) to `'inner scope shadows outer, yielding single result'`, and add a separate test that actually exercises multi-candidate confidence ordering (local vs wildcard at the same scope). ## Verification - `tsc --noEmit` clean (both `gitnexus-shared` and `gitnexus`) - `gitnexus-shared` build clean - Combined scope-resolution / model / shadow suite: **260/260 pass** (+1 from the new multi-candidate ordering test in #917) ## Not addressed (non-actionable) - #949 CI "failure with zero failing tests": pre-existing Swift Node 22 grammar flake unrelated to #910 scope. - #950: the two non-blocking findings were already addressed in follow-up commit `cbac32ba` (ParsedImport discriminated union + `ScopeId | null` on the two hooks). - #915: the five in-scope findings were already addressed in follow-up commit `54515a7e` (dead code, unused params, multi-hop docs, cap-hit test, stats granularity). - #915 LanguageProvider.resolveImportTarget signature divergence + `findDefById` O(F×D) perf: tracked separately as follow-up issues for the Ring 3 migration window. * chore(shared): address ce:review findings on the follow-up diff ce:review (interactive) on PR #964 surfaced two P2s and several P3s. This commit applies all `safe_auto` fixes + both manual tests in-line so the PR ships with a cleaner review trail. ## P2 fixes - **Complete `freezeIndex` → `wrapIndex` rename.** The prior commit renamed 3 of 5 sibling index files; `method-dispatch-index.ts` and `position-index.ts` still carried the old name. Now all 5 helpers use the consistent `wrapIndex` naming. (maintainability + project-standards reviewers both flagged this.) - **Add regression tests for the `recordTypeBindingHit` origin demotion.** The prior commit introduced the `tieBreakKey.origin = 'import'` demotion for Step-2-only candidates without a direct test. Added: - `registries.test.ts`: two Step-2-only siblings under the same interface, asserting deterministic DefId.localeCompare tie-break AND the stronger invariant that composeEvidence never emits a where-found signal for Step-2-only candidates (no `signals.origin`). - `position-index.test.ts`: touching-boundary test proving the right-sibling-wins rule documented in the new JSDoc. (testing + kieran-typescript + api-contract reviewers all flagged these gaps.) ## P3 fixes - Fix wrong comment in `recordTypeBindingHit` that claimed Step 1 could later upgrade a demoted origin. Step 1 runs BEFORE Step 2 — the actual upgrade path is Step 3 (`seedFromOwnerScopedContributor`). Comment now describes execution order correctly. - Fix inaccurate "re-exported there" comment in `index.ts`. `types.ts` *defines* ScopeLookup natively; it's not a re-export. Phrasing now says "defined in types.ts and exported from the type-export block above — not from this module." - Update stale `scope-tree.ts` file-header prose that still referenced `ScopeLookup` as living in #916/resolve-type-ref.ts. Now points to `./types.js` with a cross-ref to both #916 and #917 consumers. - Expand `atPosition` touching-boundary JSDoc to name the mechanism (backward scan through start-sorted array) so readers can trace the binary-search code to the claim. - Add breadcrumb to `aggregate.ts` module header pointing future readers to `./diff.ts` / the top-level barrel for `ShadowAgreement`, `ShadowCallsite`, and `ShadowDiff`. - Remove unnecessary non-null assertion in `recordTypeBindingHit`. Local `const existingMroDepth = ...` lets TS narrow to `number` in the else-branch, eliminating the `!` without behavior change. ## Verification - `tsc --noEmit` clean (both `gitnexus-shared` and `gitnexus`) - `gitnexus-shared` build clean - Combined scope-resolution / model / shadow suite: **262/262 pass** (+2 from the new origin-demotion + touching-boundary regression tests) |
||
|
|
1bf9fb4ef1
|
feat(shared): ClassRegistry / MethodRegistry / FieldRegistry + 7-step lookup (#917, RFC #909 Ring 2 SHARED) (#963)
Capstone of Ring 2 SHARED. Implements RFC §4 — the shared, scope-aware
resolution surface the rest of the semantic model feeds into.
## Modules (`gitnexus-shared/src/scope-resolution/registries/`)
- `context.ts` — `RegistryContext` bundling ScopeTree / DefIndex
/ QualifiedNameIndex / ModuleScopeIndex /
MethodDispatchIndex + provider hooks.
Narrows Ring 1's opaque `RegistryContributor`
to concrete `OwnerScopedContributor`.
- `tie-breaks.ts` — `compareByConfidenceWithTiebreaks`, the RFC
Appendix B cascade: confidence DESC → scope
depth ASC → MRO depth ASC → ORIGIN_PRIORITY
ASC → DefId.localeCompare.
- `evidence.ts` — `composeEvidence(signals)` / `confidenceFromEvidence`.
Translates raw walk signals into the typed
`ResolutionEvidence[]` using authoritative
`EvidenceWeights`. No magic numbers.
- `lookup-qualified.ts`— RFC §4.5. Qualified-name fast path consumed
by `resolveTypeRef` dotted fallback and by
Step 6 of lookup-core.
- `lookup-core.ts` — The 7-step canonical algorithm. Pure. Param-
eterized by `CoreLookupParams`.
- `{class,method,field}-registry.ts`
— Thin wrappers over `lookupCore` that fix
`acceptedKinds` + `useReceiverTypeBinding` per
kind. `buildClassRegistry` / `buildMethodRegistry`
/ `buildFieldRegistry` factory functions.
## RFC §4.2 algorithm contract (honored verbatim)
1. Lexical scope-chain walk. Hard shadow on any `scope.bindings.has(name)`
regardless of kind survivorship.
2. Type-binding resolution (methods/fields only, opt-in via
`useReceiverTypeBinding`). MRO walk via `MethodDispatchIndex.mroFor`.
MRO-depth-decayed weight via `typeBindingWeightAtDepth`.
3. Owner-scoped contributor — when the caller knows the receiver owner,
its direct members merge in as `origin: 'local'`.
4. Kind filter — `acceptedKinds` per registry; `kind-match` evidence
at weight 0 is always emitted for debuggability.
5. Arity filter — `provider.arityCompatibility` per candidate. When at
least one compatible candidate exists, incompatibles are dropped;
otherwise the −0.15 penalty alone disambiguates (they stay in the
result, just ranked lower).
6. Global fallback — fires only when Steps 1-3 produced NO candidates
AND the name is dotted. Delegates to `lookupQualified`.
7. Rank + tie-break — evidence list sorted by the Appendix B cascade.
## §4.7 invariants asserted in tests
- No tier vocabulary in the return type (`Resolution`, not `TierXResult`).
- Confidence is per-candidate (not per-tier).
- Shadowing is a HARD filter; globals are consulted ONLY when lexically
empty.
- Caller can read `[0]` for one-shot answers.
- `Resolution.confidence` is capped at 1.0.
- `kind-match` is always emitted (weight 0).
## Unresolved-import + dynamic-unresolved evidence shape
- `BindingRef.via.linkStatus === 'unresolved'` applies the
`unlinkedImportMultiplier` (0.5×) to the where-found signal only.
Corroborators (`arity-match`, `owner-match`, `type-binding`) remain
unaffected — the RFC §4v2 capped-signal rule applies per-signal, not
per-candidate.
- `BindingRef.via.kind === 'dynamic-unresolved'` adds a degraded
`dynamic-import-unresolved` evidence signal at weight 0.02.
## Tests (28 in registries.test.ts, 259/259 combined)
Organized per RFC §4.2 step so a regression localizes to the step it broke:
- Step 1: local + walk-to-parent + hard-shadow + origin=import
- Step 2: explicit receiver type-binding + MRO depth decay on ancestor
- Step 3: owner-scoped contributor + owner-match
- Step 5: drop-incompatible-when-compatible-exists + soft-penalty-when-all-
incompatible + unknown-when-no-provider
- Step 6: global-qualified fires only when lexically empty + never for
non-dotted names + not consulted when lexical hit exists
- Step 7: tie-break cascade (inner shadows outer; defId.localeCompare
final)
- Corroborators: unresolved-import 0.5× cap per-signal + dynamic-
unresolved 0.02 degraded signal
- §4.5: lookupQualified kind filter + empty on miss + deterministic defId
order for partial classes
- §4.7: invariants — confidence per-candidate, capped at 1.0, kind-match
always present, [0]-for-one-shot
## Known follow-up optimizations
`collectOwnedMembers` in `lookup-core.ts` iterates `defs.byId.values()`
for each MRO hop — O(D) per call. Acceptable for Ring 2 fixtures; a
by-owner index should land before Ring 3 migrates large-workspace
languages. Tracked alongside the existing `findDefById` follow-up from
#915 review.
## Module placement
All under `gitnexus-shared/src/scope-resolution/registries/` — consistent
with the Ring 2 SHARED folder layout (#912/#913/#914/#915/#916/#918).
Slight deviation from the issue's `gitnexus-shared/src/registries/`
suggestion for consistency with siblings.
## Part of
- Parent: #909
- Depends on (code): #910, #911, #912, #913, #914, #915, #916, #918.
- Closes the Ring 2 SHARED delivery band. Unblocks Ring 2 PKG (#919–#925
bridges to the gitnexus/ CLI package) and Ring 3 language migrations.
|
||
|
|
a9a5e1c388
|
feat(shared): SCC-aware finalize algorithm with bounded fixpoint (#915, RFC #909 Ring 2 SHARED) (#962)
* feat(shared): SCC-aware finalize algorithm with bounded fixpoint (#915, RFC #909 Ring 2 SHARED) Implements RFC §3.2 Phase 2 as pure logic in `gitnexus-shared`. Takes per-file parse output and returns linked `ImportEdge[]` + materialized module-scope bindings, fully language-agnostic (target resolution, wildcard expansion, and binding precedence all go through caller hooks). Three-phase algorithm: 1. Tarjan SCC over the file-level import graph (iterative, deterministic node order, O(V+E)). Returns SCCs in reverse-topological order so leaves finalize before dependents — and so disjoint SCCs are explicitly surfaced for parallel-processing callers. 2. Per-SCC bounded fixpoint. For each SCC in topo order, iterate up to `N = |intra-SCC edges|`; each pass tries to resolve every still- unlinked edge by looking up the imported name in the target file's local defs. Stops early when no progress. Edges still unlinked after the cap get `linkStatus: 'unresolved'` — keeps malformed inputs bounded and preserves the RFC §4v2 capped-signal contract for unresolved markers. 3. Wildcard expansion + module-scope binding materialization. For each `wildcard` ParsedImport that linked to a module, expand via `expandsWildcardTo` into one `wildcard-expanded` ImportEdge per exported name. Bindings per module scope are the merge of local defs (`origin: 'local'`), named / alias / reexport imports (`origin: 'import' | 'reexport'`), namespace imports (`origin: 'namespace'`), and wildcard expansions (`origin: 'wildcard'`), with precedence delegated to `provider.mergeBindings`. Dynamic imports rule: `kind: 'dynamic-unresolved'` passes through as an ImportEdge with `targetFile: null` and no BindingRef. Re-export flattening: reexport edges land with `transitiveVia: [targetFile]`. Multi-hop chains settle iteratively across the fixpoint. Types: - Adds `'wildcard'` variant to ParsedImport (parse-time signal for `import * from M`). The finalize-only `'wildcard-expanded'` ImportEdge kind is unchanged and remains finalize output only, as documented. - Exports `finalize` + `FinalizeFile` / `FinalizeInput` / `FinalizeHooks` / `FinalizeOutput` / `FinalizedScc` / `FinalizeStats`. Simple-name derivation: `deriveSimpleName` uses `def.qualifiedName` as the authoritative source (tail after the last `.`). Defs without a qualifiedName are not name-resolvable by this algorithm — an explicit design choice that trades strictness for predictability (no heuristic nodeId parsing). Tests (20, all passing): - Trivial: empty workspace · acyclic resolution · unresolvable target (file + name) · dynamic-unresolved passthrough. - Cycles: A↔B two-file cycle linked · cycles packed into SCC with isCycle=true · disjoint cycles produce disjoint SCCs · mixed linked/unresolved edges reported correctly in stats. - Wildcards: one ImportEdge per exported name · unresolved wildcards survive as single edges · expanded bindings carry origin='wildcard'. - Reexports: transitiveVia carries the intermediate file path. - Aliased + namespace: alias preserves targetExportedName under its local name · namespace links to module scope even without a module-def. - Bindings: locals land as origin='local' · imports layer on via mergeBindings · mergeBindings can drop existing (last-write-wins precedence honored). - SCC-DAG: reverse-topological ordering verified (leaf first). Combined scope-resolution / model / shadow suite: 229/229 pass. `tsc --noEmit` clean in both `gitnexus-shared` and `gitnexus`. Closes part of #909. Unblocks #917 (Registry.lookup's import-chain fast path consumes finalized ImportEdges); unblocks Ring 3 language migrations (per-language providers supply FinalizeHooks implementations). * chore(shared): address #915 review findings — dead code, docs, tests Review thread on PR #962. Code changes: - Remove dead `resolvedTargets` map + `keyFor` + `ParsedImportKey` type alias. The map was populated but never read; originally intended to cache / dedup resolutions for later phases but that path was never wired (finding 1.1). - Drop unused params (`_edgeIndex`, `_hooks`, `_workspace`) from `tryFinalize`. No planned fixpoint-state consultation; no reason to keep them reserved (finding 2.1). Documentation: - `FinalizeFile.localDefs` now documents the multi-hop re-export contract explicitly: `finalize` looks names up in the target's static `localDefs`; if B only re-exports from C and doesn't surface the name in its own localDefs, A's import of that name from B will hit the cap and be marked unresolved. Parsers that want multi-hop chains to settle end-to-end must include re-exported names in the intermediate file's localDefs (finding 1.2). - `FinalizeStats` now documents its counting granularity: all edge counters are per-`ParsedImport`, not per-materialized-`ImportEdge`. A wildcard expanding to N exports counts as one linked edge; dynamic-unresolved pass-throughs count as linked. The bindings map is the authoritative "has a BindingRef" source (finding 3.2). Tests (2 added, 22 total in finalize-algorithm.test.ts, 231/231 combined): - Explicit cap-hit → `linkStatus: 'unresolved'` assertion for a cycle where the name-level lookup never succeeds (distinct from `targetFile: null`; cap exhaustion path) (finding 3.1). - Multi-hop re-export contract test: demonstrates both variants — intermediate B WITHOUT X in localDefs → unresolved; B WITH X in localDefs → resolved to the original source DefId (finding 1.2). Not addressed (filed as follow-up issues): - LanguageProvider.resolveImportTarget vs FinalizeHooks signature divergence (finding 1.3) — pre-Ring-3 concern. - findDefById O(F×D) scan in Phase 5 (finding 4.1) — acceptable for Ring 2; optimize before large-workspace Ring 3 migrations. |
||
|
|
8cf9ae0e0d
|
feat(shared): ScopeTree + PositionIndex + makeScopeId (#912, RFC #909 Ring 2 SHARED) (#961)
Implements the scope-tree spine and position-indexed lookup as pure logic in `gitnexus-shared`. Generalizes the `enclosingFunctions` pattern from closed PR #902 to arbitrary `ScopeKind`s. Three modules under `gitnexus-shared/src/scope-resolution/`: 1. `scope-id.ts` — `makeScopeId({filePath, range, kind})` builds the canonical RFC §2.2 shape `scope:{filePath}#{startLine}:{startCol}-{endLine}:{endCol}:{kind}` and interns the result through a process-local pool so repeated calls with structurally identical inputs return the same string reference. `clearScopeIdInternPool()` exported for test isolation. 2. `scope-tree.ts` — `buildScopeTree(scopes)` validates invariants and returns an immutable `ScopeTree`: - `getScope(id)` / `getParent(id)` / `getChildren(id)` / `getAncestors(id)` - Implements the `ScopeLookup` contract from #916, so `resolveTypeRef` can consume a `ScopeTree` directly (test included). Invariants enforced (throw `ScopeTreeInvariantError` on violation): - Non-Module scopes must have a parent. - Parent must exist in the supplied set. - Parent range STRICTLY contains child range (equal ranges rejected). - Sibling ranges under the same parent do not overlap. Ranges that merely touch at the boundary (`a.end == b.start`) are accepted. - Parent and child live in the same filePath. - Duplicate scope ids are rejected. 3. `position-index.ts` — `buildPositionIndex(scopes)` produces a `PositionIndex` with `atPosition(filePath, line, col)`. Per-file sorted array; binary-search the upper bound of `start ≤ query`, scan backward through the prefix, return the first containing hit. Complexity: `O(log N_file + D)` typical (D = lexical depth ≤ ~10); degrades to `O(N_file)` only under pathological inputs (many scopes starting at the same position). "Innermost wins" falls out of the sort + backward-scan contract because `ScopeTree`'s invariants guarantee that scopes containing a point form an ancestor chain. Types: - `ScopeTree` now exported from `scope-tree.ts`. The Ring 1 opaque placeholder in `types.ts` has been removed; LanguageProvider hooks that previously took `ScopeTree = unknown` now receive the concrete interface (CLI `tsc --noEmit` passes — no existing callers rely on the opaque shape). Tests (39, all passing): - scope-id: canonical shape · all six ScopeKinds encoded · identity equality (same inputs → same reference) · distinguished by filePath / range / kind · purity under repeated calls · intern-pool clear preserves canonical shape. - scope-tree: empty tree · single module · nested Module→Class→Function · multiple siblings input-order preserved · ScopeLookup integration with resolveTypeRef · frozen children and ancestor arrays · all six invariant violations (non-Module orphan, parent-not-found, parent doesn't contain, parent == child, siblings overlap, cross-file parent, duplicate id) · boundary-touching siblings accepted. - position-index: empty · unindexed filePath · before/after-file queries · start/end inclusivity · innermost-wins for nested / co- starting / co-ending / same-line scopes · sibling dispatch · multi- file isolation · size · id-dedup. Combined scope-resolution / model / shadow suite: 190/190 pass. `tsc --noEmit` clean in both `gitnexus-shared` and `gitnexus`. Closes part of #909. Unblocks #917 (`Registry.lookup` needs the scope spine); makes `ScopeLookup` in #916 concrete without API churn. |
||
|
|
ac148612ab
|
feat(search): per-phase timing instrumentation for the query pipeline (#953)
* feat(search): per-phase timing instrumentation for the query pipeline The eval harness already measures search-pipeline latency per phase, but the *product* query() tool has no timing visibility. That leaves production latency opaque: - Is BM25 the tail, or vector search? - How much Promise.all overlap do concurrent searches actually save? - Does symbol_lookup dominate when per-symbol Cypher round-trips pile up? None of this is answerable from the outside, which blocks the latency-quality Pareto work tracked in #546 / #553. Changes: * New PhaseTimer class at src/core/search/phase-timer.ts. Supports three APIs: - start(phase) / stop() for sequential phases (per issue spec) - mark(phase, durationMs) for pre-measured durations - time(phase, promise) to wrap a promise inside Promise.all The issue's original spec was sequential-only, which doesn't work for BM25 + vector inside Promise.all — the second start() would auto-stop the first and only one phase would get timed. The mark() and time() variants resolve that without changing the sequential API for the other phases. * local-backend.ts query() instrumented across seven phase markers: bm25, vector (concurrent via timer.time inside Promise.all) merge (RRF reciprocal-rank-fusion) symbol_lookup (per-symbol process + cohesion + content Cypher) ranking (in-memory priority sort) formatting (response object construction + dedup) wall (end-to-end; separate mark so callers can compare sum(phases) vs wall and see Promise.all savings) * logQueryTiming() helper next to logQueryError(), same console-based pattern (repo has no structured logger). Emits GitNexus [query:timing] query="..." totalMs=N phases={...} to stdout — greppable prefix, JSON-parseable payload, no new deps. * timing: Record<string, number> added as a top-level field on the query() response. Strict superset of the previous shape — existing tests only assert field presence, so no regression. Other MCP tools use the same top-level-metadata convention (status, row_count, warning) rather than a nested _meta wrapper. Tests: - 6 new unit tests for PhaseTimer covering start/stop, implicit stop-on-start, additive mark(), Promise.all-safe time(), negative/NaN rejection, and totalMs auto-stop. - 3 new assertions on the existing query integration test verifying timing.wall is a non-negative number and at least one of bm25/vector fired. Verification: npx vitest run test/unit/phase-timer.test.ts -> 6 pass npx vitest run test/unit/calltool-dispatch.test.ts -> 65 pass npx vitest run test/integration/local-backend-calltool.test.ts -> 18 pass npm run test:unit -> 3777 pass (4 pre-existing env failures unchanged: skip-git-cli needs built dist/, git-utils tmpdir on Windows worktree) npx tsc --noEmit -> clean Scope declined for v1: - In-process histogram aggregation — the log line is enough for external tooling - Pareto curve generation — issue asks to enable it, not generate it - Sub-phases of symbol_lookup (process vs cohesion vs content) — issue lists them under one bucket; can split later if demand surfaces Closes #553 * fix(search): route query:timing log to stderr to preserve stdio MCP contract CI (#953) failed the `query: JSON appears on stdout, not stderr` e2e test in test/integration/cli-e2e.test.ts with: SyntaxError: Unexpected token 'G', "GitNexus [..." is not valid JSON Root cause: my initial logQueryTiming() in |
||
|
|
5d76dbcfa2
|
feat(shared): MethodDispatchIndex materialized view over HeritageMap (#914, RFC #909 Ring 2 SHARED) (#960)
Implements RFC §3.1 `MethodDispatchIndex`: a two-way materialized view
keyed by `DefId` for O(1) method-dispatch resolution:
- `mroByOwnerDefId` — owner class → full MRO ancestor chain
(excludes self, per-language strategy order)
- `implsByInterfaceDefId` — interface/trait → classes that implement it
**Not an MRO implementation.** `buildMethodDispatchIndex` is a pure
aggregator that calls back into caller-provided `computeMro` and
`implementsOf` functions. The five existing strategies (Python C3, Ruby
kind-aware, Java/Kotlin linear, Rust qualified-syntax, COBOL none) stay
where they are today (`model/resolve.ts`, `languages/ruby.ts`); this index
does not reimplement them.
Why callbacks rather than a shared registry: the strategies depend on the
CLI's `HeritageMap` + `SemanticModel`. Migrating both to `gitnexus-shared`
is out of scope for #914; callbacks let the shared build stay pure.
Module placement: `gitnexus-shared/src/scope-resolution/method-dispatch-index.ts`
for consistency with the other RFC §3.1 indexes (#913 DefIndex /
ModuleScopeIndex / QualifiedNameIndex; #916 resolveTypeRef).
Safety surface mirrors sibling indexes:
- First-write-wins on duplicate owners.
- Repeated (interface, owner) pairs deduplicated.
- Stored arrays are `Object.freeze`d; caller mutation of the source
array does not leak into the index.
- Miss returns a shared frozen empty array.
Tests (19, all passing): empty input, single-inheritance chain, Python
C3 diamond, Java BFS, Ruby kind-aware mixin, Rust qualified-syntax empty,
interface inversion (single, multiple, ordered), dedup within and across
callback calls, frozen miss + bucket arrays, callback-array isolation,
readonly Map iteration.
Closes part of #909.
|
||
|
|
56e32b310b
|
feat(shared): resolveTypeRef strict single-return type resolver (#916, RFC #909 Ring 2 SHARED) (#959)
Implements RFC §4.6: a strict, pure resolver for `TypeRef`s used by
`Registry.lookup` Step 2 (type-binding propagation) and by any caller that
wants the single best type-target for an annotation without paying for the
full evidence pipeline.
Algorithm (strict):
1. Walk the scope chain from `ref.declaredAtScope`:
- Return the first binding for `rawName` whose origin is in
`{'local','import','namespace','reexport'}` AND whose `def.type` is a
type-kind (class-like, interface-like, enum-like, alias-like).
- If bindings exist but none qualify (non-type shadow, wildcard-only
origin), return null immediately — do NOT fall through to the global
qualified-name index.
2. If `rawName` is dotted and the scope walk produced no match, consult
`QualifiedNameIndex.byQualifiedName`. Only accept a UNIQUE type-kind
hit; ambiguous or non-type results return null.
`'wildcard'` is deliberately excluded from strict origins — a
wildcard-expanded name is too loose to anchor type resolution.
Module placement: `gitnexus-shared/src/scope-resolution/resolve-type-ref.ts`
(alongside sibling indexes) rather than the issue's suggested
`gitnexus-shared/src/resolve-type-ref.ts`, for consistency with the rest of
the RFC §2/§3 surface.
A minimal `ScopeLookup` interface is declared inline so #916 ships
standalone; #912's `ScopeTree` will satisfy this contract without change.
Closes part of #909.
|
||
|
|
ac2012e5ed
|
feat(shared): DefIndex / ModuleScopeIndex / QualifiedNameIndex (#913, RFC #909 Ring 2 SHARED) (#958)
Three flat O(1) indexes + pure build functions over per-file artifacts. Contract-only; no runtime behavior change yet — consumers (#917 Registry lookups, #915 SCC finalize, #919 ScopeExtractor) wire in later. Each index follows the same shape: - build function: flat input list → frozen immutable index - public interface: readonly Map + get/has/size accessors - first-write-wins on id/filePath collisions (upstream bug signal) - pure, side-effect-free, safe to call repeatedly DefIndex — the global "what is this id?" lookup gitnexus-shared/src/scope-resolution/def-index.ts buildDefIndex(defs: readonly SymbolDefinition[]): DefIndex byId: ReadonlyMap<DefId, SymbolDefinition> Consumed by Registry.lookup (#917) to materialize DefId[] hits back to full SymbolDefinition records. ModuleScopeIndex — `filePath → moduleScopeId` for cross-file hops gitnexus-shared/src/scope-resolution/module-scope-index.ts buildModuleScopeIndex(entries): ModuleScopeIndex byFilePath: ReadonlyMap<string, ScopeId> Consumed by the SCC finalize link pass (#915) to resolve ImportEdge.targetFile to a concrete module scope in constant time. QualifiedNameIndex — cross-kind qualified-name fast path gitnexus-shared/src/scope-resolution/qualified-name-index.ts buildQualifiedNameIndex(defs: readonly SymbolDefinition[]): QualifiedNameIndex byQualifiedName: ReadonlyMap<string, readonly DefId[]> Returns DefId[] (not a single DefId) because partial classes, method overloads, and cross-kind collisions can legitimately share a qualifiedName. Callers filter by acceptedKinds at the lookup site. Consumed by Registry.lookup qualified fast path + resolveTypeRef dotted fallback (#916, #917). Barrel re-exports added to gitnexus-shared/src/index.ts so consumers import from 'gitnexus-shared' rather than deep paths. Tests (gitnexus/test/unit/scope-resolution/, 23 total): def-index.test.ts (6): empty, single def, multiple distinct, first-write-wins collision, missing id returns undefined, byId direct iteration module-scope-index.test.ts (6): empty, single entry, multiple files, first-write-wins on duplicate filePath, missing returns undefined, byFilePath direct iteration qualified-name-index.test.ts (11): empty, single qnamed def, partial classes accumulate, input-order preservation, qname separation, skip undefined/empty qname, pair dedup, cross-kind indexing, frozen-empty-array on miss, direct iteration Verification: - gitnexus-shared + gitnexus build clean (tsc + scripts/build.js) - test/unit/scope-resolution: 23/23 pass - model + shadow + scope-resolution combined: 129/129 pass - No runtime consumer wiring yet — indexes are standalone library functions that #915, #917, #919 will import when ready Depends on #910 (SymbolDefinition, DefId, ScopeId types — already on main). Unblocks #915 (finalize algorithm), #917 (Registry.lookup), #919 (ScopeExtractor materialization). |
||
|
|
f73389eac3
|
fix: ENOBUFS in detect_changes by setting maxBuffer on git/rg execFileSync (#957)
* Initial plan * Fix ENOBUFS in detect_changes by setting maxBuffer on git/rg execFileSync Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/bb241ed0-3b39-431f-a242-b0c7ced9707b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> |
||
|
|
22f0beb057
|
feat(shared): shadow-mode diff + aggregate — full implementation (#918, RFC #909 Ring 2 SHARED) (#951)
Replaces the scaffold stubs with working pure-logic implementations plus
unit-test coverage for both functions. Unblocks Ring 2 PKG #923 (shadow
harness) to consume a concrete library instead of throwing scaffolds.
gitnexus-shared/src/scope-resolution/shadow/diff.ts
`diffResolutions(callsite, legacy, newResult): ShadowDiff`
- [0] on each side is the top match
- both empty → 'both-empty', delta []
- legacy empty only → 'only-new', delta = new top evidence
- new empty only → 'only-legacy', delta = legacy top evidence
- same top nodeId → 'both-agree', delta []
- different nodeIds → 'both-disagree',
delta = symmetric difference of evidence kinds
(legacy-only first in input order, then new-only)
Evidence identity is `ResolutionEvidence.kind` — weight/note differences
for the same kind do NOT produce delta entries. Rationale: the aggregator
wants to know which *signals* explain a disagreement, not fluctuations
in calibration values.
gitnexus-shared/src/scope-resolution/shadow/aggregate.ts
`aggregateDiffs(diffs, now?): ShadowParityReport`
- buckets by `SupportedLanguages`
- tallies agreements, evidence-breakdown (divergences only — agree and
empty rows do not contribute)
- parity = bothAgree / (totalCalls - bothEmpty), yields 0 (not NaN)
when the denominator is 0
- perLanguage sorted alphabetically by enum value for stable output
- evidenceBreakdown internally sorted by kind for stable output
- overall = column-wise sum across languages
- `now` parameter makes generatedAt deterministic in tests
gitnexus-shared/src/index.ts
Re-exports the full shadow API: diffResolutions, aggregateDiffs, and all
their types (ShadowAgreement, ShadowCallsite, ShadowDiff,
LanguageParityRow, ShadowParityReport).
gitnexus/test/unit/shadow/diff.test.ts (13 tests)
- 5 agreement outcomes
- symmetric-by-kind evidence delta (disjoint, overlapping, fully-overlapping)
- weight-only differences produce no delta
- top-match only (ignores indices beyond [0])
- callsite passthrough
- delta ordering (legacy-only first, input order preserved)
gitnexus/test/unit/shadow/aggregate.test.ts (9 tests)
- empty input
- single language, all agree / mixed / all empty
- multi-language bucketing + overall sum
- alphabetical language sort
- evidence breakdown scope
- determinism via injected `now` + JSON round-trip identity
Verification:
- gitnexus-shared + gitnexus build clean (tsc + scripts/build.js)
- test/unit/shadow: 22/22 pass
- test/unit/model + test/unit/shadow combined: 106/106 pass
- No runtime behavior changes (shadow is invoked by #923, not yet wired)
Stacked on main (
|
||
|
|
af1d278a7e
|
feat(shared,ingestion): extend LanguageProvider with scope-resolution hooks (#911, RFC #909 Ring 1) (#950)
Adds the 14 optional scope-resolution hooks from RFC #909 §5.2 to
`LanguageProviderConfig` plus the supporting input/output types in
`gitnexus-shared`. Contract-only; no runtime behavior changes.
Review-driven refinements (addresses two non-blocking review comments on #950):
1. `ParsedImport` is now a 5-variant discriminated union, not a flat
record. Each variant carries only its legal fields so invalid shapes
are compile errors:
- 'named', 'alias', 'namespace', 'reexport', 'dynamic-unresolved'
'wildcard-expanded' is deliberately excluded — finalize materializes
that kind; a provider must never emit it at parse time.
'reexport' is a first-class parse-phase variant so syntactically-
detectable re-exports (TS `export { X } from './y'`, Rust
`pub use foo::bar`) keep their parse-time signal through to finalize
rather than being re-derived by the SCC pass.
`namespace` gains an `importedName` field so `import numpy as np`
can carry both `localName: 'np'` and `importedName: 'numpy'`.
`dynamic-unresolved.targetRaw` is `string | null` (was mandatory
null) so providers can emit the unresolvable expression text for
diagnostics when available.
2. `bindingScopeFor` and `importOwningScope` return type changed from
`ScopeId` to `ScopeId | null`, aligning with the X | null convention
used by the 12 sibling optional hooks (receiverBinding,
resolveScopeKind, interpretTypeBinding, …). `null` = delegate to the
central default. Enables partial overrides — a JS provider can
return a hoisted scope for `var` and `null` for `let`/`const`
without re-implementing the default lookup.
Both hooks also gain a purity JSDoc contract: same inputs yield the
same ScopeId (or null) across invocations; no closure over mutable
state. Required to keep scope-tree construction deterministic.
A richer callable-defaults pattern (typed BindingScopeDefaults /
ImportOwningDefaults helper interfaces on a `defaults` parameter)
was considered and deferred to Ring 2 PKG #919, where the concrete
ScopeExtractor will exist to inform the helper shape. Designing that
pattern before the first consumer would set cross-hook precedent
based on a single motivating example.
Supporting types added to gitnexus-shared/src/scope-resolution/types.ts:
- CaptureMatch, ParsedImport, ParsedTypeBinding
- WorkspaceIndex, ScopeTree (opaque placeholders until Ring 2)
- Callsite
14 hooks added to LanguageProviderConfig (all optional):
Parse phase: emitScopeCaptures, interpretImport, receiverBinding,
interpretTypeBinding, resolveScopeKind, shouldCreateScope,
bindingScopeFor
Finalize phase: resolveImportTarget, expandsWildcardTo,
importOwningScope, mergeBindings
Reference-extraction phase: classifyCallForm
Resolution phase: shouldShadow, arityCompatibility
Verification:
- gitnexus-shared builds clean (tsc)
- gitnexus builds clean (scripts/build.js)
- test/unit/model: 84/84 pass — no regressions
- No provider needs updating (all hooks optional)
- No BindingScopeDefaults/ImportOwningDefaults/defaults parameter
introduced (deferred to #919)
Stacked on #910 (merged as
|
||
|
|
afc0a8b6c5
|
feat(shared): add scope-resolution types + constants (#910, RFC #909 Ring 1) (#949)
Lands the authoritative data model and constants for the pure scope-based resolution RFC (#909) as Ring 1, part 1. No runtime behavior changes — types + constants only. New in gitnexus-shared/src/scope-resolution/: - types.ts — Scope, ScopeKind, ScopeId, DefId, Range, Capture, BindingRef, ImportEdge, TypeRef, Resolution, ResolutionEvidence, Reference, ReferenceIndex, LookupParams, RegistryContributor - evidence-weights.ts — EvidenceWeights constant map + typeBindingWeightAtDepth (RFC Appendix A) - origin-priority.ts — ORIGIN_PRIORITY constant map for deterministic tie-breaks (RFC Appendix B) - language-classification.ts — LanguageClassification type + LanguageClassifications map (production × 14, experimental × 2 for vue/cobol; governs Ring 4 DAG-retirement gate) - symbol-definition.ts — SymbolDefinition moved from gitnexus/src/core/ingestion/model/symbol-table.ts so scope-resolution types can reference it from the shared package Consumer updates: - symbol-table.ts: removes local SymbolDefinition declaration; imports from gitnexus-shared - model/index.ts: drops SymbolDefinition from barrel re-export per "direct imports from gitnexus-shared" convention (see gitnexus-shared feedback in project memory) - 9 source files + 5 test files: import SymbolDefinition directly from 'gitnexus-shared' Verification: - gitnexus-shared builds clean (tsc) - gitnexus builds clean (scripts/build.js) - 131/132 unit test files pass; 3767 tests green - Zero behavior changes; SymbolDefinition shape unchanged Blocks: #911 (LanguageProvider hook interface extensions) and all of Ring 2 (#912-#925). Closes part of #909. |
||
|
|
d9da7d6692
|
fix(test): isolate cli-e2e from shared mini-repo fixture (#954)
Deterministic fix for the Windows-flaky pipeline-graph-golden test.
Root cause
cli-e2e.test.ts wrote into the SHARED fixture directory
(test/fixtures/mini-repo/) — git init, analyze run that creates
AGENTS.md, CLAUDE.md, .claude/, .gitnexus/. When pipeline-graph-golden
ran in parallel, its `cpSync` of the source directory could capture
the mid-flight pollution before cli-e2e's afterAll cleanup fired.
macOS/Ubuntu won the race often enough that the flake presented as
Windows-only.
Fix
cli-e2e now copies mini-repo into a fresh `mkdtemp`'d parent whose
basename is `mini-repo` (preserving `--repo mini-repo` CLI lookup by
basename), runs git-init there, and rm's the whole tmpdir in afterAll.
The shared fixture source is never touched.
Fallout from the cwd change: bare `--import tsx` specifiers (2
spawnSync + 1 spawn) can't resolve `tsx` from an os.tmpdir cwd where
there is no node_modules. Switched them to the already-existing
`tsxImportUrl` (absolute file:// URL to the tsx loader), matching
the `runCliOutsideProject` pattern that was already set up for this
exact case.
Updated the "MINI_REPO is inside the project tree" comment in the
`status on non-indexed repo` test — MINI_REPO is now in os.tmpdir,
so the rationale for using a separate throwaway tmp git repo is
different (but still valid: previous tests in the suite create
MINI_REPO/.gitnexus, which findRepo() would pick up).
Also updated pipeline-graph-golden's comment explaining WHY it
copies to tmp — it's now defense-in-depth rather than a necessity,
so a future test that adds files to the source can't silently
regress the golden.
Verification
- 5x consecutive `cli-e2e + pipeline-graph-golden` runs: 20/20 pass
(deterministic)
- 3x full suite including pipeline.test: 27/27 pass
- test/fixtures/mini-repo/ post-run contents: only `src/` —
zero pollution from any test
- macOS/Ubuntu behavior unchanged (they were passing; tmpdir
isolation is purely additive)
|
||
|
|
131d411ae4
|
feat(mcp): rank context/impact disambiguation candidates and expose kind/file_path hints (#888)
* feat(mcp): rank context/impact disambiguation candidates and expose kind/file_path hints
The `context` MCP tool already returned `{ status: 'ambiguous', candidates }`
when a name hit multiple symbols, but the candidates were returned in
arbitrary DB order and the only hint it accepted was file_path. The
`impact` tool was worse: when its name resolver found multiple viable
matches it silently picked the first one from a priority UNION, with no
signal back to the caller that a different symbol might have been
intended.
Both failure modes were flagged in issue #470 and reconfirmed in the
comments by a second user who described impact as returning "incorrect
parsing results and meaningless tool calls" in the multi-match case.
Changes:
* Add `resolveSymbolCandidates(repo, query, hints)` private helper on
LocalBackend. Single place that:
- Short-circuits on direct uid (zero-ambiguity)
- Runs the same name-or-qualified-id match as before, with LIMIT 20
(was 10) so the ranker has headroom instead of arbitrary truncation
- Preserves the #480 Class/Constructor preference -- when the only
ambiguity is a Class and its own Constructor, the Class wins
silently
- Scores each candidate (pure TS, no extra DB round-trip): base 0.50,
+0.40 for file_path match, +0.20 for kind match, plus a small
kind-priority tiebreaker (Class > Interface > Function > Method >
Constructor) when no explicit kind hint is given
- Sorts desc by score with stable tiebreakers (shorter filePath,
then lex uid)
- Promotes to a single confident resolve when the top score is
>= 0.95 AND beats the runner-up by >= 0.10 -- lets a strong hint
cut through without forcing the caller through a disambiguation
round-trip
* Rewire `context()` to use the shared helper. Response shape is a
strict superset of today's: candidates gain a `score` field, the
existing `{ uid, name, kind, filePath, line }` keys are preserved so
every downstream consumer (rename, eval-server formatter, etc.) keeps
working. New `kind` input hint accepted.
* Rewire `impact()` to use the shared helper. Now emits the same
`{ status: 'ambiguous', candidates, impactedCount: 0, risk: 'UNKNOWN' }`
shape instead of silent first-pick. New inputs accepted:
`target_uid`, `file_path`, `kind`.
* Update tool schemas in mcp/tools.ts to advertise the new inputs and
describe ranked disambiguation.
Backward compatibility:
The #480 Class/Constructor collapse is preserved and covered by the
existing java-class-impact integration test (still green). The
ambiguous response shape is a strict superset -- `eval-formatters`
unit test that parses the old shape is unchanged and still passes.
`impact` going from silent-first-pick to structured ambiguous is a
semantic improvement that is the entire point of the issue; callers
relying on silent first-pick now get an actionable response.
Scope declined for v1:
module/community hint -- the issue lists it as one of several hints,
but kind + file_path cover the vast majority of disambiguation needs
in practice, and a community-label filter requires an extra graph
query per candidate. Natural v2 follow-up.
Tests: calltool-dispatch.test.ts gains 5 new cases covering file_path
boost, kind hint boost, impact ambiguous shape, impact target_uid
short-circuit, and score field presence on the existing ambiguous
test. Plus the extended assertions on the existing
`context tool returns disambiguation for multiple matches`.
Verification:
npx vitest run test/unit/calltool-dispatch.test.ts -> 64 pass
npx vitest run test/integration/java-class-impact.test.ts -> pass
npm run test:unit -> 3642 pass
(4 pre-existing env failures unchanged: skip-git-cli needs built
dist/, git-utils tmpdir on Windows worktree -- same on main)
npx tsc --noEmit -> clean
Closes #470
* fix(mcp): enrich labels from UNION when labels(n)[0] is empty; address review findings
CI on PR #888 caught 13 integration-test failures I did not cover locally:
my resolver refactor collected candidates via `labels(n)[0] AS type`, but
LadybugDB returns an empty string for that projection on certain node
types (most importantly Class). With an empty `type`, impact's downstream
`_runImpactBFS` no longer recognised `symType === 'Class' | 'Interface'`
and stopped seeding Constructor + File nodes into the frontier, so the
"impact(upstream) surfaces the file importer" assertion broke across 11
language fixtures plus 2 OVERRIDES filter tests.
The original impact resolver worked around this by running a prioritised
UNION across Class/Interface/Function/Method/Constructor and picking the
first hit. My refactor dropped that. Fix: keep the simple candidate MATCH
but enrich types afterward via a single scoped UNION query, so every
candidate carries an accurate label for both scoring and downstream
BFS seeding. The UID direct-lookup path is patched the same way.
Also addresses the findings from the senior reviewer on PR #888:
* MIGRATION.md: document the `impact` behavioural change (silent first-
pick → structured `{ status: 'ambiguous', candidates }`) so downstream
callers know to branch on `result.status` before reading byDepth/
summary. `context` is unchanged shape-wise (strict superset).
* New test: `context tool promotes top candidate via scoring when
multiple rows survive DB pre-filter`. The review flagged that the
existing file_path test works only because the mock ignores WHERE
parameters -- the scored-promotion path (top ≥ 0.95 AND gap > 0.09)
wasn't directly exercised. The new test uses two candidates both in
App.tsx-containing paths plus a kind hint so promotion is decided by
scoring, not DB pre-filtering. Also tightened the comment on the
earlier file_path test to describe the mock vs production divergence
honestly.
* NIT: added a paragraph explaining why `scored.length >= 2` is kept as
a defensive guard even though the `normalized.length === 1` early
return already covers the single-candidate path.
* Integration: two tests in `local-backend-calltool.test.ts` targeted
`'authenticate'`, which now correctly resolves as ambiguous (two
Method nodes: AuthService.authenticate and BaseService.authenticate).
Updated both to pass `file_path: 'src/auth.ts'` so they exercise the
new disambiguation API and still assert the METHOD_OVERRIDES filtering
they were originally about.
Edge case fix in the promotion gap check: IEEE754 makes 0.50 + 0.40 +
0.20 - 0.90 = 0.09999999999999998 instead of exactly 0.10, which would
otherwise break the "winner clearly dominates" intent for legitimate
1.00 vs 0.90 cases. Changed `>= 0.10` to `> 0.09`; same user-facing
intent, no floating-point sensitivity.
Verification (all from gitnexus/):
npx vitest run test/integration/class-impact-all-languages.test.ts
-> 52 pass (was 11 FAIL on CI before this fix)
npx vitest run test/integration/local-backend-calltool.test.ts
-> 18 pass (was 2 FAIL on CI before this fix)
npx vitest run test/integration/java-class-impact.test.ts
-> 10 pass (regression guard for #480 preserved)
npx vitest run test/unit/calltool-dispatch.test.ts
-> 65 pass (1 new test + 4 from original #470 PR)
npm run test:unit
-> 3626 pass, 4 pre-existing env failures unchanged
npx tsc --noEmit
-> clean
|
||
|
|
b8875b9c80
|
chore(release): v1.6.2 (#952)
Bumps gitnexus to v1.6.2 and adds the matching CHANGELOG entry. Highlights since v1.6.1 (61 commits): - Docker support (#848) - Language-agnostic heritage / call / variable extractors (config+factory pattern, #877 #878 #890) - AST-aware embedding chunking (#889) - jQuery / axios HTTP consumer detection (#887) - SemanticModel wired as first-class resolution input, SM-20 (#885) - ImportSemantics split into per-strategy hooks (#886) - Python dotted-import fix (#899); worker warnings non-terminal (#900 / #261); global-install ENOTEMPTY fixes (#843 #846); embeddings staleness fix (#831) See gitnexus/CHANGELOG.md for the full list. After merge, tag `v1.6.2` triggers publish.yml which runs CI, verifies tag↔package.json match, publishes to npm with provenance, and creates the GitHub Release using the extracted CHANGELOG body. |
||
|
|
969b4623ca
|
fix: keep worker warnings non-terminal (#900)
* fix: keep worker warnings non-terminal Treat parse-worker warning messages as informational so a warning can be surfaced without short-circuiting the worker result protocol. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * style: apply prettier formatting --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> |
||
|
|
018e0e6b14
|
test(web-e2e): raise status-ready timeout to 45s for parallel-worker stability (#908)
* Initial plan * plan: stabilize web e2e tests timing out under parallel workers Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/49d09e67-5a8e-4eee-adcd-3d5416675a6b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test(web-e2e): bump status-ready timeout to 45s for parallel-worker stability Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/49d09e67-5a8e-4eee-adcd-3d5416675a6b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> |
||
|
|
040bb7a489
|
feat: add docker support (#848)
* feat: add docker support * feat: move docker files to root * feat: add docker build and push workflow * fix: pin docker action SHAs to verified commits Made-with: Cursor * fix: remove redundant --platform=$TARGETPLATFORM from runtime stage Made-with: Cursor * fix: upgrade docker actions to Node.js 24-compatible versions Made-with: Cursor * docs: updated readme * fix: update docker references * fix(docker-server): reject null bytes in resolvePath Defensively harden the path traversal guard by returning null early when the URL contains a null byte, before normalization runs. Made-with: Cursor * fix(docker-server): handle createReadStream errors Attach an error listener before piping so mid-flight read errors (truncated file, permission change) cleanly destroy the response instead of being silently swallowed. Made-with: Cursor * fix(docker-server): replace existsSync with async stat Eliminates the TOCTOU race between the initial stat call and the subsequent existsSync check. Reuses the async stat pattern already in place and removes the now-unused existsSync import. Made-with: Cursor * test(docker-server): add integration tests; fix %00 null-byte bypass Decode the URL before the null-byte check so percent-encoded null bytes (%00) are also rejected with 400 instead of falling through to the SPA fallback. Adds 5 node:test integration tests covering valid assets, SPA fallback, path traversal, null bytes, and 404. Made-with: Cursor * style: fix prettier formatting in docker-server files Made-with: Cursor * fix(docker): wire tests into CI, fix resolvePath separator, correct image namespace - Add `node --test docker-server.test.mjs` step to ci-tests.yml so the path-traversal guard tests run in every CI pass instead of being silently skipped. - Fix resolvePath containment check: `startsWith(root)` would allow sibling directories like `/app/dist-evil/`; now guards with `root + sep` or exact match. - Update docker-compose.yaml default image from `abhigyanpatwari` namespace to `brainifii` to match what docker.yml publishes to GHCR. * fix(docker): update apt-get commands and set user permissions - Modify Dockerfile and Dockerfile.test to include options for apt-get to bypass validity checks during updates. - Set ownership of the /app directory to the 'node' user in the runtime stage for improved security and proper permission handling. * fix(docker): switch to Alpine base images for smaller footprint - Update Dockerfile to use Alpine-based Node.js images for both builder and runtime stages, reducing image size and improving performance. - Replace apt-get commands with apk for package installation in the runtime stage. * fix(docker): update Node.js version in Dockerfile - Change base image from node:20-alpine to node:22-alpine * fix(docker): update Node.js version in Dockerfile to 22-alpine for runtime --------- Co-authored-by: kritik.b <kritik.b@media.net> |
||
|
|
725ed3fe66
|
chore(deps)(deps): bump glob from 11.1.0 to 13.0.6 in /gitnexus (#867) | ||
|
|
509185b8f9
|
chore(deps)(deps): bump commander from 12.1.0 to 14.0.3 in /gitnexus (#868) | ||
|
|
4988feec94
|
chore(deps)(deps-dev): bump wait-on from 8.0.5 to 9.0.5 in /gitnexus-web (#859) | ||
|
|
ef953beca9
|
chore(deps)(deps): bump @huggingface/transformers in /gitnexus (#869) | ||
|
|
0b5381695f
|
chore(deps)(deps-dev): bump @vitest/coverage-v8 in /gitnexus (#864) | ||
|
|
30292d7179
|
chore(deps)(deps): bump @modelcontextprotocol/sdk in /gitnexus (#866) | ||
|
|
7a98a01ad5
|
chore(deps)(deps): bump lru-cache from 11.2.7 to 11.3.5 in /gitnexus (#870) | ||
|
|
94cba48b6d
|
chore(deps)(deps): bump mnemonist from 0.39.8 to 0.40.3 in /gitnexus (#871) | ||
|
|
925460ab5b
|
refactor(cli): trim duplicated ai-context CLAUDE.md block (#904) | ||
|
|
08b4505197
|
chore(deps)(deps): bump @ladybugdb/core in /gitnexus (#873)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
|
||
|
|
d5225e699f
|
chore(deps)(deps): bump mermaid from 11.12.2 to 11.14.0 in /gitnexus-web (#860) | ||
|
|
0a3b9120a0
|
chore(deps)(deps): bump tailwindcss in /gitnexus-web (#861) | ||
|
|
5544350e30
|
chore(deps)(deps-dev): bump jsdom from 29.0.0 to 29.0.2 in /gitnexus-web (#863) | ||
|
|
dfa449ef41
|
feat(ingestion): language-agnostic heritage extractor with config+factory pattern (#890) | ||
|
|
daca8360bf
|
fix(python): avoid local matches for external dotted imports (#899) | ||
|
|
77a13113ea
|
fix: keep worker warnings non-terminal (#261) | ||
|
|
02739085d2
|
feat(embeddings): AST-aware chunking with offset-based splitting (#889)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
|
||
|
|
43098784cf
|
refactor(ingestion): split ImportSemantics into per-strategy hooks (Strategies 1-4) (#886)
* Initial plan
* refactor(ingestion): split ImportSemantics into per-strategy hooks
- Add ImportResolverStrategy and ImportResolutionConfig types
- Create createImportResolver factory (resolver-factory.ts)
- Add createStandardStrategy to standard.ts
- Extract per-language strategies from existing resolvers:
goPackageStrategy, javaJvmStrategy, kotlinJvmStrategy,
rustModuleStrategy, pythonImportStrategy, csharpNamespaceStrategy,
phpPsr4Strategy, swiftPackageStrategy, dartPackageStrategy,
dartRelativeStrategy, rubyRequireStrategy
- Create per-language config files in import-resolvers/configs/
- Update all 15 language providers to use createImportResolver(config)
- Add 38 unit tests for factory and strategy composition
- All 3640+ existing tests pass, tsc --noEmit passes
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c3aec32d-2155-4808-88df-9cd6b2384174
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* chore: remove unused resolver imports from language providers
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c3aec32d-2155-4808-88df-9cd6b2384174
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* docs: add error propagation note to createImportResolver JSDoc
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c3aec32d-2155-4808-88df-9cd6b2384174
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* refactor: consolidate strategies into configs, remove legacy resolvers
- Move all strategies from per-language files into their config files
- Remove swift.ts and vue.ts (no shared helpers needed)
- Remove legacy monolithic resolver functions from all per-language files
- Remove unused legacy wrapper functions from standard.ts
- Per-language files now only contain shared internal helpers
- Fix lint warning in languages/php.ts (no-non-null-assertion)
- Update test imports to reference configs/ instead of per-language files
- All 3262+ tests pass, tsc --noEmit passes, zero lint errors
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/f8da6bc2-957c-4d20-87ba-402fa223c6c8
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* refactor: address review feedback — remove dart.ts shim, JSDoc language field, update ARCHITECTURE.md
- Add JSDoc to ImportResolutionConfig.language clarifying it's
documentation-only metadata not used by the factory
- Remove dart.ts legacy shim (was only kept for backward-compat tests)
- Rewrite dart-import-resolver.test.ts to test production strategies
(dartPackageStrategy/dartRelativeStrategy) directly, including full
factory composition via dartImportConfig
- Fix lint warning (no-explicit-any) by using buildSuffixIndex in makeCtx
- Update ARCHITECTURE.md to mention import-resolvers/configs/ as the
extension point for per-language import resolution
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/53f09a4f-1ff1-4a3e-a29c-fda9cdb4c4ef
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* fix: address review comments — revert php.ts, tighten dart test assertion
- Revert php.ts: restore stack.pop()! (the while guard guarantees non-empty)
- Tighten dart relative import test to assert exact result instead of
permissive null-or-files check
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/53f09a4f-1ff1-4a3e-a29c-fda9cdb4c4ef
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* refactor(ingestion): strengthen import-resolver tests and document Vue config intent
Address non-blocking follow-ups from PR #886 review:
- Add inline comment to vueImportConfig explaining intentional
language: Vue / TypeScript-strategy mismatch (Vue SFCs are
preprocessed into TS upstream of import resolution).
- Replace 11 tautological typeof === 'function' assertions with
behavioral tests for goPackageStrategy, kotlinJvmStrategy, and
csharpNamespaceStrategy, including full-chain strategy-order
guards via createImportResolver(config).
- Apply prettier formatting to sibling configs touched during
factory introduction.
Test: 37 passed (previously 26), tsc --noEmit clean.
* test(ingestion): tighten import-resolver assertions and close coverage gaps
Apply ce-review findings on commit
|