mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-10 22:43:40 +00:00
33 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c34c36036f
|
fix(workers): resilient + zero-copy ingestion worker pool — prevent analyze hangs on TS-root-scale loads (#1693)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* Initial plan * fix: skip worker-timeout files in sequential fallback and optimize TS capture node lookup Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0e53743e-0600-4690-bd0d-198894daef58 * refactor: clarify TS capture helpers after validation feedback Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0e53743e-0600-4690-bd0d-198894daef58 * fix(workers): exclude in-flight file on worker error/exit, not just singleton timeout WorkerPoolDispatchError previously surfaced the stalled path only for the singleton-timeout final-fail branch. Worker `error` and `exit` events (and the msg-channel `error` reply) fell back to plain `Error`, so the sequential fallback re-attempted every file in the active job — re-hanging on the same pathological file when the worker crashed mid-parse. Lift the in-flight-file inference into `inFlightExcludePath(job, lastProgress)` and wire it into the three remaining in-pool failure sites. `lastProgress` is already in `runWorker` scope, so `items[lastProgress]` (the next file the worker was about to acknowledge) is the best single guess at the culprit; earlier files are still re-tried sequentially. Returns `[]` when no path is determinable (`lastProgress >= items.length`, or path missing/non-string) so sequential retries the whole job. Replacement-worker startup failures stay plain `Error` (no job context); the result-before-flush protocol bug stays plain `Error` (code fault, not file). Tests cover the three new exclusion paths plus a negative test confirming non-WorkerPoolDispatchError throws fall through to full sequential retry. * fix(review): apply autofix feedback - Use cause-neutral "worker-excluded" label in skip messages and tests now that worker error/exit paths share the same exclusion contract as singleton-timeout (correctness + maintainability reviewers). - Add JSDoc to findSelfOrAncestorOfType{s} explaining the parent-walk short-circuit vs root-DFS fallback (maintainability reviewer). * feat(workers): resilient + scalable worker pool Restructures `createWorkerPool` so a single bad file no longer kills the pool for the rest of an analyze run. Five interlocking layers: 1. **Auto-respawn on error/exit** — worker death triggers `replaceWorker` on the same slot, bounded by `maxRespawnsPerSlot` (default 3). The slot is dropped from rotation when the budget is exhausted; other slots keep running. 2. **Circuit breaker** — replaces the permanent `poolBroken=true` with a consecutive-failure counter. The pool only trips after `consecutiveFailureThreshold` deaths (default `max(3, poolSize)`) with no successful job in between. A successful job resets the counter so transient bursts of bad files don't escalate. 3. **Session-scoped file quarantine** — paths identified as the in-flight file at the moment of a worker death are added to a `Set<string>` on the pool. `dispatch()` filters quarantined items up front (they never reach a worker again this pool lifetime). Exposed via the new `WorkerPool.getQuarantinedPaths()` so callers can log/route them. `processParsing` surfaces the per-chunk quarantine summary alongside the existing fallback-exclusion log. 4. **Authoritative in-flight tracking** — `parse-worker.ts` emits `{type:'starting-file', path}` before each file. The pool tracks this per slot and uses it for crash attribution, falling back to the `items[lastProgress]` heuristic only when no starting-file has been observed (very-early crash, older worker build). Closes the reorder/race concerns raised by reviewers C1 and R3 in the earlier review run. 5. **Per-job cumulative timeout budget** — each `WorkerJob` tracks the total wall time spent across attempts/splits/retries. When the budget is exhausted (default 5x `subBatchIdleTimeoutMs`), the pool surfaces the in-flight path instead of letting exponential backoff balloon into multi-hour stalls. Cross-layer wiring: a new `wakeIdleSlots` helper kicks any non-busy live slot when items are requeued (after a death or split-retry), so a dropped slot doesn't strand work in the queue. `recoverAndResume` consolidates the per-job teardown shared by the three in-pool death sites (`error`, `exit`, msg-channel `error`). New env knobs: `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT`, `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS`, `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD`. New `WorkerPoolOptions.workerFactory` injection point for unit tests. Tests: 12 new unit tests using a FakeWorker mock cover quarantine seeding, slot-respawn, slot-drop after budget, breaker trip + reset, and quarantine filtering. Plus option-resolution tests for the three new env vars. All 19 worker-pool/-fallback/-options tests pass; full unit suite 6040 passed / 30 skipped / 0 failed. * fix(workers): apply code-review fixes (12 findings) Walks through every finding from ce-code-review run 20260519-094648-3549cf5e. All 12 picked Apply. Critical: - F1 — Layer 5 cumulative-timeout exhaustion no longer silently drops the rest of the job. `requeueRemainder` is now invoked before `handleWorkerDeath` in both Layer 5 and singleton-final-fail give-up paths so non-quarantined items get re-tried by another worker. - F2 — idle-timer recovery overhaul. `!shouldContinue` branch no longer calls `replaceWorker` (double-spawn race with the `handleWorkerDeath` inside `requeueAfterTimeout`). `shouldContinue` branch now enforces `maxRespawnsPerSlot` before respawning, closing the budget-bypass for the timeout-retry path. Also fixes premature `maybeDone` by simplifying the bookkeeping. - F3 — `requeueRemainder` no longer pre-charges `cumulativeTimeoutMs` by `job.timeoutMs`. The death itself consumed no budget, so the next `requeueAfterTimeout` was double-billing the first attempt. - F4 — `WorkerPool.getQuarantinedPaths` is now optional on the interface, matching the defensive `?.()` call site and the existing mocks. Removes the contract-vs-callsite contradiction. - F5 — per-job unattributed-death tracking. When a worker dies with no exclusion attribution, `requeueRemainder` tracks death count per `startIndex`. First time: re-queue intact. Second time: quarantine items[0] as best guess, or drop the job entirely when items lack paths. Bounds the death loop the original design admitted to. - F6 — per-slot consecutive-failure counter. Replaces the pool-wide scalar so a chronically-failing slot trips the breaker on its own streak instead of being masked by another slot's successes. Smaller: - F7 — exhaustiveness `never` check on `WorkerOutgoingMessage` union. - F8 — recursive `runWorker` on fully-quarantined jobs converted to a while-loop. - F9 — `tripBreaker` calls `reject(err)` BEFORE awaiting `worker.terminate()`. A stuck terminate no longer blocks the caller. - F10 — `parsing-processor.ts` quarantine log de-duplicates per pool instance via a `WeakMap`. Only newly-quarantined paths are logged in each chunk; the per-chunk count still surfaces via progress. - F11 — extract `firstPath` local in `requeueAfterTimeout`; eliminates double `itemPath` call and the `unknown as string` cast. Tests (F12, 6 new): - crash-error event path (errorHandler). - F5 drop-branch coverage via items without `.path`. - Common-case unattributable crash falling back to items[0] heuristic. - `replaceWorker` startup failure (workerFactory emits 'exit' before 'online'). - All-slots-dropped breaker trip. - `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` env override. Residual gap (deferred): no unit test exercises the Layer 5 cumulative-budget runtime path — requires fake-timer interleaving with FakeWorker that's too brittle for this iteration. Tracked. Unit suite: 257 files / 6056 passed / 30 skipped / 0 failed. * test(workers): integration tests for resilience layers + fix requeue-after-timeout flow Adds 6 new real-worker integration tests covering the PR #1693 resilience layers + fixes 3 follow-on bugs surfaced while writing them. New integration coverage (real worker threads + temp fixture scripts): - `respawns the slot after worker process.exit and finishes the work on the replacement` — exercises Layer 1 auto-respawn + Layer 3 quarantine through real IPC. - `attributes exactly via authoritative starting-file message on worker crash` — Layer 4 end-to-end: starting-file message → exact quarantine attribution (not the items[0] heuristic). - `quarantine filters subsequent dispatches without sending to a worker` — second dispatch's sub-batch payload audited via filesystem; the quarantined path is never sent across the message channel. - `drops a slot after maxRespawnsPerSlot and continues on the survivor` — 2-slot pool, slot dies twice past budget, survivor finishes re-queued remainder. - `trips the circuit breaker on cascading per-slot consecutive failures` — single-slot pool, dies on every job, breaker trips after consecutiveFailureThreshold with WorkerPoolDispatchError carrying the cumulative quarantine. - `survives a worker error event (uncaught throw) the same as a process.exit` — validates recoverAndResume on the errorHandler path via a real worker `throw` (not just process.exit). Bug fixes uncovered while writing these tests: 1. **Stack-overflow recursion in runWorker's no-worker branch** — `if (!worker) { ...; wakeIdleSlots(); maybeDone(); }` recursed indefinitely when multiple slots were mid-respawn simultaneously (wakeIdleSlots → runWorker → no worker → wakeIdleSlots → …). Removed the wakeIdleSlots call: the slot's own respawn IIFE owns runWorker post-respawn, and other slots will pick up work via finishJob's runWorker. 2. **requeueAfterTimeout dispatched work before respawn completed** — the F2 fix had `requeueAfterTimeout` `void`-discarding `handleWorkerDeath`, so the `!shouldContinue` IIFE had no way to know when the respawn finished. New design: `requeueAfterTimeout` returns a `TimeoutDecision` discriminated union; the IIFE owns the death-and-respawn-and-dispatch orchestration in an async closure so it can `await handleWorkerDeath` and then call `runWorker` deterministically. 3. **Stalled-singleton + protocol-error + replacement-startup-crash tests** had stale contracts predating the resilience refactor. The stalled-singleton no longer rejects (it quarantines + resolves `[]`); the protocol-error rejection message now mentions "circuit breaker tripped"; the replacement-startup-crash test documents the known `waitForWorkerOnline` race (online fires before the worker's main script runs, so a top-level throw looks like a successful spawn) — the test asserts the file is quarantined via the second-idle-timeout give-up path. Full suite: 334 files / 8982 passed / 43 skipped / 0 failed (second run; first run had a Vitest-reported flake from an uncaught worker exception bleeding into the test report — repeated runs are clean). * perf(workers): raise pool cap to cores-1 + defer per-chunk extraction to keep workers busy User reported 4-5% CPU utilization on a multi-core machine during ingestion. Two structural reasons: 1. **Pool cap.** `createWorkerPool` resolved size as `Math.min(8, max(1, os.cpus().length - 1))` — a 16-core box got 8 workers (50% theoretical max). U1 lifts the default to `min(16, max(1, cores - 1))`, exposes `GITNEXUS_WORKER_POOL_SIZE` env override, and adds `--workers <N>` CLI flag (`0` disables the pool for sequential fallback). 2. **Per-chunk extraction serialized the loop.** Per chunk: dispatch → await workers → main-thread `processImportsFromExtracted` + `processHeritageFromExtracted` + `processRoutesFromExtracted` + `synthesizeWildcardImportBindings` + `seedCrossFileReceiverTypes` → next chunk dispatch. Workers sat idle through every extraction block. U2 (revised from the plan's pipelined-chunks design) defers these passes to a single end-of-loop batch. Chunk loop becomes parse + merge + accumulate. Resolution sees strictly-more-info (full repo graph) so cross-chunk import/heritage targets resolve at least as well as before. Memory cost: `deferredWorkerImports` accumulates across chunks; bounded by total file count, acceptable. Plan deviation note: the plan called for an in-flight chunk pipeline (N concurrent dispatches with bounded memory). That design needed either a `processParsing` API refactor or duplicating its catch-block fallback in `parse-impl`. The deferred-extraction approach delivers the same "workers stay busy" outcome with much smaller surface area and zero changes to `processParsing`. The `GITNEXUS_PARSE_CHUNK_CONCURRENCY` env var documented in U2 of the plan is therefore not implemented in this commit; if memory growth from `deferredWorkerImports` becomes a problem at very-large-repo scale, a bounded sliding-window variant can land as a follow-up. Tests: - New `test/unit/analyze-worker-pool-size.test.ts` covers --workers validation (5 invalid inputs rejected with exit code 1 + clear error; valid integers set the env var; `--workers 0` routes to sequential). - Extended `worker-pool-resilience.test.ts` with `resolveAutoPoolSize` scenarios: env override, env=0, env above cap, invalid env fallback, auto-formula match, integer return type. - Full unit suite: 6097 / 6127 passed / 30 skipped / 0 failed. - Full integration suite (second run): 77 / 78 passed / 1 skipped / 0 failed. First run had a known cosmetic flake from an uncaught worker exception bleeding into the test reporter. Resilience contract from PR #1693 preserved: per-slot respawn budget, circuit breaker, quarantine, authoritative in-flight tracking, cumulative timeout budget — all unchanged. New env vars surfaced in --help: GITNEXUS_WORKER_POOL_SIZE, GITNEXUS_PARSE_CHUNK_CONCURRENCY (reserved for future bounded pipelining). * docs(readme): document --workers CLI flag * feat(workers): add getStats() and per-chunk throughput logging * test(workers): cleanup leaked temp-dirs and drop duplicate option-resolution block - Add afterEach to worker-pool-resilience.test.ts cleaning up the per-test temp directory created by beforeEach (~25 stale dirs per CI run previously). - Delete the duplicated describe('worker pool option resolution', ...) block. Verified the first block (lines 490-532) is a strict superset (includes the GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS env test the second block omitted), so deletion loses no test coverage. Addresses PR #1693 review findings L2 (temp-dir leak) and L3 (duplicate block). * feat(cli): thread --workers via PipelineOptions + snapshot/restore CLI env Resolves PR #1693 review B2 (env-var leak in long-running hosts): - --workers is now threaded through AnalyzeOptions -> runFullAnalysis -> PipelineOptions.workerPoolSize -> createWorkerPool's explicit poolSize arg, bypassing the GITNEXUS_WORKER_POOL_SIZE env channel. The env var remains as a back-compat fallback inside resolveAutoPoolSize for operators who set it directly. - analyzeCommand and wikiCommand snapshot the GITNEXUS_* env vars they mutate at function entry and restore them in finally. Inner *Impl extraction keeps the diff surgical (no body re-indent). process.exit(0) on the CLI success path still terminates the process; restoration matters for programmatic callers (tests, long-running hosts) reaching early-return paths or the alreadyUpToDate fast path. - Tests updated to assert the new behavior: analyze-worker-pool-size.test.ts: workerPoolSize flows through runFullAnalysis options; env is not mutated; back-to-back calls see their own values, not the previous call's leak. analyze-worker-timeout.test.ts: env IS set during the runFullAnalysis call (captured via mockImplementation) and restored after, proving the timeout reaches downstream while the leak fix holds. - Also addresses L4: afterEach NODE_OPTIONS restore so back-to-back test runs don't accumulate --max-old-space-size=8192 tokens. Addresses PR #1693 review B2 (blocker) and L4 (test polish). * feat(workers): harden worker lifecycle (messageerror + availableParallelism + ready handshake) Resolves PR #1693 review H1, H2, M4: H1 - messageerror handler at every dispatch site V8 deserialization failure on postMessage previously left the message silently lost; the pool would wait out the idle timeout (default 30s) instead of treating it as worker death. The dispatch loop now wires worker.once('messageerror', ...) alongside error/exit and routes through recoverAndResume so the existing per-slot respawn budget, in-flight file attribution, and circuit-breaker layers fire as designed. H2 - resolveAutoPoolSize uses os.availableParallelism() Mirrors the pattern at capabilities.ts:85 (defaultEmbeddingThreads). os.cpus().length returns the host CPU count, which over-sizes the pool on cgroup-limited containers, taskset-restricted runtimes, and CI runners with explicit CPU quotas. Falls back to os.cpus().length on Node < 18.14. M4 - worker-side ready handshake replaces online-trust parse-worker.ts now emits {type: 'ready'} after all top-of-script initialization completes, BEFORE the message handler is attached. The pool's renamed waitForWorkerReady listens for this message under a bounded WORKER_READY_TIMEOUT_MS (5s) budget instead of trusting Node's online event - which fires when the worker thread starts, BEFORE the script body runs, letting init crashes slip past pool startup. ready is added to WorkerOutgoingMessage with an exhaustiveness-checked no-op branch in the dispatch handler (defensive: the message is consumed by waitForWorkerReady before dispatch handlers attach). messageerror is wired into waitForWorkerReady the same way. Test scaffolding: - FakeWorker emits {type: 'ready'} in addition to 'online' so replacement workers in unit tests don't hit the 5s budget. - Integration test ad-hoc worker scripts go through a writeReadyWorker helper that prepends the ready handshake. Tests intending to script "crash BEFORE ready" can bypass the helper. 61/61 worker-pool unit tests pass; 28/28 integration tests pass. * feat(parse-impl): monotonic progress + verbose-gated throughput log + seed-before-build Resolves PR #1693 review M2, M3, L1, L5 in a single parse-impl.ts pass: M2 - Monotonic progress through deferred phase (no more "stuck at 82%") Previously the deferred resolution stages (imports, heritage, routes, calls) all emitted percent: 82 — the UI looked frozen for the duration of the deferred work, which on large repos is several seconds to minutes and visually identical to the hang PR #1693 set out to fix. Redistributed: parse phase: 20-70 (was 20-82) imports: 70-75 heritage: 75-80 routes: 80-85 calls: 85-95 Each deferred stage now advances through its own band via the existing per-batch progress callback. Skipped stages (zero deferred input) leave their band as a no-op jump - the next stage still starts at its own band, preserving strict monotonicity. The "no parseable files" early return now jumps to 95 (was 82), and the duplicate "Parsing N files..." announcement is suppressed when totalParseable === 0 to avoid a non-monotonic 95 -> 20 regression that pre-existed (uncovered by the new monotonic test). M3 - Throughput log gated on `--verbose`, not just NODE_ENV=development The per-chunk files/s log was gated on `isDev`, so operators running `gitnexus analyze --verbose` in a production install never saw it. Now fires when (isDev || isVerboseIngestionEnabled()) — matches the documented promise that `--verbose` shows tuning observability. L1 - Typo rename: `chunkChunkStartMs` -> `chunkStartMs` L5 - `buildExportedTypeMapFromGraph` runs BEFORE `seedCrossFileReceiverTypes` Previously the seeding branch was reached with `exportedTypeMap.size === 0` in the worker path (the map was only built far below, AFTER the seeding branch), so the seed dead-coded itself silently and call resolution never got the cross-file receiver-type enrichment. Now the map is populated from the in-progress graph before the seed call; the post-parse builder remains as a defensive sequential-path fallback, guarded by `size === 0` so we don't pay the cost twice on the worker path. Net win: cross-file CALLS edges that previously had no receiver type now get enriched. New test: parse-impl-progress-monotonic.test.ts Asserts the emitted percent stream is strictly non-decreasing across the parse + deferred phases, and that the deferred band (>=70) is actually reached. Also pins the "no parseable files" path to exactly [95] so the 95 -> 20 regression we just fixed can't re-emerge. * feat(parse-impl): bounded chunk concurrency via file-pre-fetch pipeline Resolves PR #1693 review B1 (GITNEXUS_PARSE_CHUNK_CONCURRENCY documented in --help but unimplemented). The chunk loop now pre-fetches chunk file contents up to `parseChunkConcurrency` chunks ahead of the worker-dispatch cursor so disk I/O overlaps with worker compute. Worker dispatch itself stays serial because WorkerPool.dispatch is not reentrant — concurrent calls would race on the shared per-slot busy/in-flight state, regressing the hang/resilience work this PR is built on. The pre-fetch path is the honest interpretation of "concurrent in-flight parse chunks" that the help text advertises: I/O overlap, not parallel worker dispatch. Concurrency value resolution: 1. PipelineOptions.parseChunkConcurrency (threaded from CLI) 2. GITNEXUS_PARSE_CHUNK_CONCURRENCY env var 3. Default 2 (matches the help text) F4 (wildcard-synthesis ordering) is preserved: deferred-state aggregation runs in chunkIdx order because the for-loop iterates sequentially after awaiting each chunk's pre-fetched contents. Cross-chunk processors (processImportsFromExtracted, synthesizeWildcardImportBindings, etc.) still run only after all chunks complete — they see deterministic input regardless of file-read completion order. Concurrency=1 produces behavior identical to the pure-serial loop; that's the regression baseline. New test: parse-impl-chunk-concurrency.test.ts - Asserts graph output is identical (nodeCount + relationshipCount) between parseChunkConcurrency=1 and =2 — the critical correctness invariant. Exact .toBe(N) comparisons per DoD §2.7 (the second run's counts must equal the first run's exactly). - Pins specific fixture symbols (foo/bar/Baz) under both parseChunkConcurrency=1 and the env-fallback (3) path. - Env-fallback test confirms GITNEXUS_PARSE_CHUNK_CONCURRENCY is honored when the option is undefined. * test(workers): pin cumulative-timeout exhaustion behavior Resolves PR #1693 review M6: the existing resilience suite asserts only the *default value* of maxCumulativeTimeoutMs (5x subBatchIdleTimeoutMs), not that dispatch actually aborts the offending job when the cumulative wall-clock budget is exhausted. Without this test, a future refactor could remove the exhaustion branch in requeueAfterTimeout and the suite would stay green while the pool sat in retry loops for an hour on a real production stall. Scenario: subBatchIdleTimeoutMs = 100ms timeoutBackoffFactor = 10 maxCumulativeTimeoutMs = 300ms Single file, HangingWorker that never responds. First attempt times out at 100ms (cumulative=100). The next backoff (1000ms, cumulative 1100ms) exceeds the 300ms cap, so requeueAfterTimeout returns give-up on the first timeout retry and the file goes to the session quarantine. Asserts: - pool.getQuarantinedPaths() includes 'src/stuck.ts' after dispatch - if dispatch rejected, the error is a WorkerPoolDispatchError (the typed surface that routes to sequential fallback) Uses a local minimal HangingWorker double rather than the full action-scripted FakeWorker from worker-pool-resilience.test.ts — the inverse pattern (always hang) doesn't need the scripted-action machinery and keeps the test file focused on the one behavior. * docs(readme): add environment-variables reference table Resolves PR #1693 review L6: operator-facing env vars were either mentioned inline (GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS) or only documented via `gitnexus --help`, with no single place to look up the full set. The new "Environment variables" subsection under the Quick Start CLI block lists every operator-facing knob with default, effect, and tuning guidance, matching the names in cli/index.ts addHelpText post-U2 / U1. Covers: GITNEXUS_WORKER_POOL_SIZE (--workers) GITNEXUS_PARSE_CHUNK_CONCURRENCY (newly real per U1) GITNEXUS_VERBOSE (--verbose) GITNEXUS_MAX_FILE_SIZE (--max-file-size) GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS (--worker-timeout × 1000) GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES GITNEXUS_CHUNK_BYTE_BUDGET GITNEXUS_NO_GITIGNORE GITNEXUS_SKIP_OPTIONAL_GRAMMARS CLI flag vs env-var precedence is stated explicitly (CLI > env > default) so operators running long-lived hosts (MCP server, eval-server) know which channel wins. * test(workers): pin quarantine path round-trip and non-normalization contract Resolves PR #1693 review M5 (Windows quarantine path-normalization coverage). worker-pool.ts quarantines paths via a Set<string> keyed by exact string equality. The existing suite never asserted this contract, which lets a future "helpfully normalizing" refactor on one side of the pipeline (caller, worker, or pool) silently break quarantine filtering on Windows. This file pins the contract from both directions: 1. Round-trip: a path the caller dispatches with backslashes (src\bad.ts) flows through starting-file -> death -> quarantine -> next-dispatch filter verbatim. The replacement worker never sees the re-dispatched bad path because the pool's pre-dispatch filter short-circuits it. 2. Non-normalization: quarantining src\poison.ts does NOT filter src/poison.ts. Whoever changes that contract has to update this test alongside (the load-bearing assertion catches accidental path.normalize() calls in the quarantine path). Runs on every platform — the path strings are test-injected, so the test exercises the same code path regardless of the host's path.sep. Used a self-contained FakeWorker that emits {type:'ready'} for U3's waitForWorkerReady handshake, so the test doesn't depend on the larger worker-pool-resilience.test.ts harness. * test(typescript): pin capture-anchor rewrite invariants (B5 regression) Resolves PR #1693 review B5: the captures.ts ancestor-walk rewrite (findSelfOrAncestorOfType[s] + pickFirstNode replacing the prior findNodeAtRange-from-root path) was semantically equivalent to its predecessor per Lane 4 of the production-readiness review, but the existing typescript-captures.test.ts didn't pin the specific sharp edges where an over-aggressive walk would silently break captures. This file does. Each test exercises a capture class whose anchor type is one the rewrite explicitly handles: - member call obj.foo() -> @reference.call.member (call_expression anchor walks to self) - dynamic import import("./helper") -> raw @import.dynamic gets decomposed by splitImportStatement into @import.statement with @import.kind=dynamic + @import.source stripped of quotes - JSX <Foo /> in .tsx -> @reference.call.free emitted (TSX query pattern, query.ts:899-905) but @declaration.parameter-count is NOT synthesized because findSelfOrAncestorOfType('call_expression') returns null on a jsx_self_closing_element anchor. Pre-rewrite the range lookup also returned null. Pinning this contract catches accidental "walk JSX -> outer call" refactors. - constructor `new Foo(1,2)` -> @reference.call.constructor (new_expression anchor walks to self) - named/namespace import + re-export -> @import.statement (one each) - class method override -> @declaration.method per class, no collapse - member read obj.foo (no call) -> @reference.read.member All assertions use exact .toBe(N) per DoD §2.7. * test(parse-impl): pin multi-chunk graph equivalence under deferred extraction Resolves PR #1693 review B4: the deferred-extraction reorder (moving processImportsFromExtracted / Heritage / Routes / Wildcard / ReceiverTypes from per-chunk to end-of-loop) was proven observably equivalent by Lane 4 of the production-readiness review. Until now, the existing suite never asserted cross-chunk graph equivalence, which lets a future refactor that accidentally tightens the per-chunk vs end-of-loop coupling silently break cross-chunk resolution. This test forces multi-chunk parsing on a small fixture by setting GITNEXUS_CHUNK_BYTE_BUDGET=64 BEFORE the parse-impl module loads (the budget is captured at module load via vi.resetModules — a future move to function-scope env reads is U14 in Phase 2). Then runs the same fixture under a 10MB budget (single chunk) and asserts the two graphs are byte-identical: same nodeCount, same relationshipCount, exact .toBe(N) per DoD §2.7. Fixture: 3-file class hierarchy with cross-file inheritance — Animal (a.ts) -> Dog extends Animal (b.ts) -> makeDog returns Dog (c.ts). Forces the resolver to chain imports + heritage across chunks. A second test pins specific symbol names (Animal, Dog, makeDog, speak, bark) in the multi-chunk graph so a regression in chunk-boundary resolution surfaces as a missing-symbol failure with a specific diagnostic instead of a bare count mismatch. * test(parse-impl): wall-clock integration pinning multi-chunk pipeline (B3) Resolves PR #1693 review B3 — the final P0/P1 merge blocker. With this test, all five doc-review blockers (B1-B5) are pinned by regression coverage. The PR's headline claim is "analyze no longer hangs on TS-root-shaped loads". The existing suite pins each resilience layer (worker-pool- resilience.test.ts), the deferred-extraction equivalence (U7), and the chunk-concurrency contract (U1). What was missing: a single end-to-end run that exercises the full chunked parse-and-resolve path on a multi-chunk fixture, BOUNDED by a wall-clock budget so a regression that re-introduces the hang fails this test loudly via timeout rather than slipping past as a count drift. Implementation: - 17-file synthetic fixture: 15 small modules (one function each), one "realistic dense" complex.ts (30 functions + class + interface), and an index.ts re-exporting them. Forces cross-chunk import chains. - GITNEXUS_CHUNK_BYTE_BUDGET=64 via vi.resetModules forces multi-chunk parsing on the small fixture. - Promise.race with 30s timeout: a hang fails as "exceeded WALL_CLOCK_BUDGET_MS — likely the hang B3 was meant to prevent", not as a bounds-only inequality (DoD §2.7 distinction — hang-detector via exception, not regression-mask via inequality). - Exact .toBe(true) assertions on specific expected symbols (fn0..fn14, Service, Config, configure, describe, complex0/15/29) so a silent mid-chunk crash that exits 0 without producing graph data also fails this test, not just the hang case. Scope: runs the sequential-fallback path (skipWorkers: true) because the full real-worker scenario requires a built dist/parse-worker.js and ~60s wall-clock per run — appropriate for a CI-integration job, not vitest. The load-bearing invariants pinned here catch the bulk of B3's concern; the dist-worker swap is a Phase 2 follow-up documented in the file header. * refactor(parse-impl): move chunk-byte-budget env read to function scope Resolves PR #1693 review F7 / U14: pre-U14, `CHUNK_BYTE_BUDGET` was a module-load IIFE constant that captured `GITNEXUS_CHUNK_BYTE_BUDGET` once and froze the value for the module's lifetime. That defeated per-call option threading (a future `PipelineOptions.chunkByteBudget` was silently no-op'd because the function body read the frozen module-level constant) AND forced tests to use `vi.resetModules` to vary chunk layout. The U7 deferred-extraction test and the U6 multi-chunk integration test both used the workaround. After this change: - `DEFAULT_CHUNK_BYTE_BUDGET = 2 * 1024 * 1024` stays as a module-level constant — purely a default, no env access. - `resolveChunkByteBudget(options)` runs per call: option wins, then env, then default. Same options-first/env-fallback/default pattern as resolveAutoPoolSize and the U1 parseChunkConcurrency resolver — keeps the ingestion code's configuration model uniform. - `PipelineOptions.chunkByteBudget?` added with documentation that threading through options lets long-running hosts (eval-server, MCP daemon) size per-call without leaking process.env state across analyze invocations. New test (parse-impl-env-reads.test.ts) pins all four behaviors: 1. option-first: option present + env present -> option wins 2. env-fallback: option absent + env present -> env wins 3. default-fallback: both absent -> 2 MB default 4. per-call: two back-to-back runs in the same vitest worker with different chunkByteBudget option values observe their OWN values, proving the module-load freeze is gone (no vi.resetModules in this test — that's the invariant being verified). All four assertions use exact `.toBe(N)` per DoD §2.7. The chunk count is observed by parsing the `Parsing chunk X/Y` progress message stream — a stable proxy that doesn't require exposing internal parse-impl counter state. Note: U7 and U6 tests still use `vi.resetModules` because they were written before this change. A follow-up cleanup could simplify those tests (drop the resetModules dance, pass chunkByteBudget via options), but they pass as-is so this commit doesn't touch them. * feat(workers): per-slot generation counter for late-event protection (U12) Adds a monotonic per-slot generation counter to createWorkerPool's state. Each successful worker replacement (replaceWorker) bumps the slot's counter exactly once — atomically with the workers[slotIndex] swap, so observers (getStats) see the new (worker, generation) pair consistently. Handler closures in the dispatch loop capture the slot's generation at attach time and short-circuit when they fire on a stale generation. In the current implementation, cleanup() synchronously removes listeners on a Worker instance the moment a death is observed, so no listener naturally fires on a stale generation — the guard is a defensive layer protecting against any future refactor that loosens cleanup() ordering or re-attaches handlers across the swap. The load-bearing observable is the slotGenerations[] array exposed via WorkerPoolStats so operators (and tests) can confirm a slot was actually replaced and not just the same worker recycled. Implementation: - const slotGenerations: number[] = new Array(size).fill(0) in createWorkerPool's per-pool state, alongside respawnCount and consecutiveFailuresPerSlot. - replaceWorker: slotGenerations[workerIndex]++ AFTER the workers[workerIndex] = replacement swap (only on the success branch — drop-slot paths leave the counter unchanged). - runWorker dispatch loop: const slotGen = slotGenerations[workerIndex] captured before handler attachment; every handler (handler / errorHandler / exitHandler / messageErrorHandler) starts with `if (slotGenerations[workerIndex] !== slotGen) return`. - WorkerPoolStats gains `readonly slotGenerations: readonly number[]`. - getStats() returns slotGenerations.slice() so callers can't mutate pool state by writing to the returned array. Two existing toEqual snapshots in worker-pool-resilience.test.ts extended with the new slotGenerations field (both expect all-zeros — neither test scenario triggers a respawn). New test file (worker-pool-slot-generation.test.ts, 4 tests): 1. Fresh pool: every slot at generation 0. 2. Successful crash + respawn: generation bumps to 1 exactly once. 3. Crash that drops the slot (maxRespawnsPerSlot:0): generation stays at 0 because no successful respawn happened. The dispatch rejection on breaker trip is the expected outcome here; the load-bearing assertion is the post-rejection stats. 4. Multi-slot independence: one slot crashing bumps only that slot's generation, not the other. Order-independent via sort() because the round-robin assignment isn't pinned by contract. All assertions exact .toEqual / .toBe per DoD §2.7. * docs(bench): add parse-throughput benchmark scaffold (R13) Resolves PR #1693 review R13 (benchmark artifact requirement). Creates `gitnexus/bench/parse-throughput.md` documenting: - Synthetic fixture spec (same shape as the U6 integration test, so CI smoke baseline and ad-hoc benchmark exercise the same paths). - What to measure (wall-clock, peak heap, chunk count, getStats snapshot) and the hardware-shape metadata to record alongside. - Harness recipe — vitest + env-var overrides to exercise sequential fallback vs worker-pool paths. - Latest-measurement table with placeholder rows for the three paths (sequential, workers+concurrency, workers single-threaded) and an explicit "Status: scaffold — fill in before merging" callout. The U6 test's observed ~6 s wall-clock is captured as a smoke-baseline. - Operator-tuning quick reference cross-linked to the README env-var section (U11) so the doc is actionable without re-reading the PR. - "What this benchmark does NOT measure" section explicitly scoping the artifact's limits (synthetic ≠ real-repo, throughput-only ≠ resilience-tested, Phase 3 IPC repack row reserved for U16-U17). Mitigates the doc-review SG5 "static doc drift" concern via: 1. Explicit "regenerate this file before merging" callout at the top. 2. Self-contained methodology so anyone can re-run the numbers. 3. Cross-links to the U6 integration test that already bounds the wall-clock as part of the CI suite — so "is it still completing?" is regression-tested even if the numbers in this doc drift. The standalone harness script (`bench/scripts/parse-throughput.ts`) remains a stretch goal per the original plan. The U6 vitest with verbose ingestion logs covers the primary observability gap until the standalone harness lands. * perf(parse-impl): free deferred-extraction arrays after consumption (U15 lightweight M1) PR #1693 review M1 noted that the deferred-extraction accumulator arrays (`deferredWorkerImports`, `deferredWorkerCalls`, `deferredWorkerHeritage`, `deferredConstructorBindings`, `deferredAssignments`) were retained until function return, making peak accumulator memory O(repo) instead of O(in-flight stage). This commit implements the LIGHTWEIGHT version: free each array immediately after its last consumer drains/reads it, dropping peak accumulator memory progressively through the deferred-extraction stages. The structural per-chunk streaming variant (the original U15 framing) is deliberately deferred — the doc-review's adversarial reviewer (A4) flagged it as defending unmeasured memory pressure, and the simpler array-clearing captures the bulk of the benefit without committing to a scheduling-strategy decision (microtask vs parallel extractor task vs worker-side) that profile data should inform. Clears added: 1. After `processImportsFromExtracted` (the sole consumer of `deferredWorkerImports`): clear the imports array before the heavier heritage/calls stages run. 2. After `buildHeritageMap` (the LAST consumer of the raw `deferredWorkerHeritage` records — processCallsFromExtracted reads from the derived `fullWorkerHeritageMap` instead): clear the heritage array before the call-resolution stage. 3. After `processAssignmentsFromExtracted` (the joint last consumer with processCallsFromExtracted for the calls/ bindings/assignments triple): clear all three before downstream graph-build / scope-resolution uses its own working memory. Arrays returned in the function result object (allFetchCalls, allExtractedRoutes, allDecoratorRoutes, allToolDefs, allORMQueries, allParsedFiles) intentionally stay live — downstream consumers need them. Graph-output equivalence is preserved (U7 multi-chunk equivalence test passes — the clears happen AFTER each array's last consumer has copied data into the graph or derived structures). * feat(workers): introduce protocol.ts wire-format module (U16, IPC scaffold) Defines the binary frame for worker-thread IPC as an isolated, fully-tested module. Production wiring is deferred to U17 — shipping the wire-format contract first de-risks the migration by establishing a single source of truth for the byte layout. Resolves the scaffold half of PR #1693 review R12. Wire layout (per message, single buffer): +---------+-----------+---------------------+ | tag | length | payload bytes … | | 1 byte | 4 bytes | | +---------+-----------+---------------------+ tag : MessageTag enum value (0x01 DispatchJob ... 0x08 Ready) length : little-endian uint32 byte count for the payload region payload: UTF-8 JSON-encoded value, possibly "null" Why JSON for the body (rather than per-shape binary encoders): the doc-review adversarial reviewer (A2) flagged that a true per-shape binary encoder for the result message — which carries nested heterogeneous extracted-call / import / heritage / route arrays — would be 500-1500 LOC and a substantial maintenance burden. The honest perf win the IPC repack targets is moving file CONTENTS via ArrayBuffer transferList (zero-copy ownership transfer for the largest single piece of state in any message). That win is captured by U17 layering transferList over the bulk file-content payload while keeping this module's framing for the surrounding metadata. If U18 benchmark data shows the JSON body is itself a bottleneck after U17 lands, a follow-up unit can swap to per-shape binary encoding behind the same encodeMessage / decodeMessage surface without changing the frame. API: - MessageTag (const object): stable byte tags 0x01..0x08 - PROTOCOL_HEADER_BYTES = 5 - ProtocolDecodeError extends Error: distinct class so U17's pool-side handler can route protocol violations through the existing messageerror recovery layer (U3 H1) distinctly from other failure classes - encodeMessage(tag, payload): Buffer - decodeMessage(buf): { tag, payload } - Uses Buffer#subarray instead of the deprecated Buffer#slice Tests (18, all exact-equality per DoD §2.7): - byte layout (tag at offset 0, length LE uint32 at offset 1) - empty/null payload encodes to 5-byte header + 4-byte "null" body - round-trip for every MessageTag with representative payloads - non-ASCII path string (UTF-8 byte-length boundary) - 9 MB payload (well past the existing 8 MB sub-batch budget) - decode errors surface as ProtocolDecodeError, not generic Error: * buffer < header size * tag outside valid range * declared length exceeds buffer * payload bytes are not valid JSON - error class name is preserved through prototype chain so callers can `err instanceof ProtocolDecodeError` reliably * refactor(workers): extract quarantine into its own module (U13 partial) Honest partial U13: extract the quarantine resilience layer (Layer 3 of the 5-layer model) into a dedicated module with a small explicit interface. The full 5-module split that the original plan named was flagged by doc-review A10 as abstraction-without-multi-consumer-demand ("Each has exactly one consumer: worker-pool.ts. None of these layers is imported elsewhere in the codebase pre-extraction, and the plan doesn't identify any future consumer.") This commit ships the smallest self-contained layer as a named module to validate the factory + interface pattern with minimal risk. The remaining four layers (respawn-budget, cumulative-timeout, circuit-breaker, slot-attribution) stay inline until a real second consumer emerges (e.g., a non-parse worker pool that reuses the same resilience layers). Module shape (`workers/quarantine.ts`, ~30 LOC): interface Quarantine { add(path: string): void; has(path: string): boolean; snapshot(): string[]; // defensive copy readonly size: number; // getter, reflects state at access time } function createQuarantine(): Quarantine Replaces in `worker-pool.ts`: - `const quarantined: Set<string> = new Set()` -> `createQuarantine()` - `quarantined.has(p)` -> `quarantine.has(p)` (2 sites) - `quarantined.add(p)` -> `quarantine.add(p)` (2 sites) - `quarantined.size` -> `quarantine.size` (2 sites) - `Array.from(quarantined)` -> `quarantine.snapshot()` (6 sites) Public worker-pool.ts API is unchanged — `getQuarantinedPaths()` still returns the same defensive `string[]` copy. The behavioral contract is preserved: paths are quarantined as opaque strings (the U9 / M5 non-normalization contract still holds — see the new dedicated test). Tests: - 8 isolated unit tests for the quarantine module — pins the interface contract (empty start, add/has/size, dedup on repeated add, no separator normalization, snapshot defensive copy + freshness, size-getter live behavior). - All 86 existing worker-pool tests pass unchanged — they exercise the quarantine through the pool and act as the regression net for behavior preservation. Why not the full 5-module extraction in this commit: doc-review A10's concern is real — a single-consumer abstraction adds module-boundary overhead (5 sets of imports, 5 dedicated test files, 5 interfaces to keep in sync with worker-pool) without any structural benefit until a second consumer materializes. Extracting one validates the pattern; the remaining four can be moved on demand. * feat(workers): wire protocol.ts encoded IPC into parse-worker + pool (U17) Production worker IPC now uses the U16 binary wire format (1-byte tag + 4-byte LE length + UTF-8 JSON body) end-to-end. The pool encodes every outgoing `sub-batch` / `flush` dispatch via `encodeMessage`; the worker decodes incoming frames via `decodeMessage` and encodes its `ready`, `starting-file`, `progress`, `sub-batch-done`, `result`, `warning`, and `error` outputs the same way. The load-bearing correctness fix is making `decodeMessage` accept `Uint8Array` rather than only `Buffer`: Node's `worker_threads` `postMessage` structured-clones the payload, which strips the `Buffer` prototype on the receive side. A frame sent as `Buffer` arrives as a plain `Uint8Array`, and `Buffer.isBuffer(raw)` returns false — so the first attempt at U17 (gating decode on `Buffer.isBuffer`) silently treated every incoming frame as POJO and the worker never responded. The fix adopts the underlying memory zero-copy via `Buffer.from(view.buffer, view.byteOffset, view.byteLength)` and uses `raw instanceof Uint8Array` at every call site (parse-worker decode, pool dispatch handler, pool ready-handshake handler, FakeWorker test mocks, and the integration-test worker preamble). The pool stays tolerant of POJO incoming so unit-test FakeWorkers don't need rewriting — only the new outgoing encoded dispatches require the test scaffolding to decode on receive, which the test FakeWorkers and the integration test's inline `parentPort.on` wrapper now do. The slot-drop integration test was rewritten from a shared-counter-file race (which pre-U17 timing happened to land on the assertion-friendly counter==2 endpoint, but post-U17 protocol decoding latency shifted to counter==1 and produced 3 quarantines instead of 2) to a deterministic path-based crash trigger: slot 0 crashes on a.ts, respawns, crashes on the requeued b.ts, slot is dropped after budget exhausted; slot 1 handles [c.ts, d.ts] normally. Outcome no longer depends on inter-worker file-write ordering. Protocol coverage adds two regression tests pinning the Uint8Array decode path: structured-clone-stripped frames decode identically to their Buffer originals, and Uint8Array views with non-zero byteOffset into a wider ArrayBuffer also decode correctly (catches `Buffer.from(uint8)` copying semantics if a future refactor loses the zero-copy adoption). All 94 worker-pool tests (9 files, unit + integration) pass; the full unit suite (6128 tests across 268 files) passes unchanged. * perf(workers): zero-copy file content transfer via transferList (U19) Pool dispatch now hoists `{path, content: string}[]` file contents OUT of the U17 JSON envelope into separately-allocated `Uint8Array`s whose ArrayBuffers are passed to `worker.postMessage`'s `transferList` for zero-copy ownership transfer. The envelope itself carries only lightweight metadata (`{path, byteLength}` per file) and is structure- cloned the same as before. What this saves vs U17 baseline: - **JSON.stringify of file contents on main thread** drops to zero — the envelope is now O(paths + sizes), not O(total bytes). For a 200- file sub-batch of 10 KB TS files, that's ~2 MB of escape processing per dispatch that disappears. JSON.stringify's per-character branch on quotes/backslashes/control chars is roughly 2x slower than UTF-8 transcode in TextEncoder, so the replacement is a CPU win even though it adds a single TextEncoder.encode per file. - **Structured-clone memcpy of file contents** drops to zero — the contents' backing ArrayBuffers are ownership-transferred, not copied into the worker's heap. The envelope's struct-clone cost is now proportional to metadata size only. - **JSON.parse on worker thread** likewise no longer scales with content size. Worker decodes each `Uint8Array` to string via `TextDecoder` lazily at the parse boundary — runs on the worker thread, parallel with continued main-thread work, vs U17's sequential JSON.parse blocking the worker before processBatch can start. Pipelining: TextEncoder.encode (main) and TextDecoder.decode (worker) can both run while the OTHER side is doing useful work. Under U17, struct-clone was a synchronous main-thread blocker. The ArrayBuffer ownership contract is load-bearing: - File-content `Uint8Array`s are allocated via `TextEncoder.encode`, NOT `Buffer.from(str, 'utf8')`. TextEncoder produces a dedicated ArrayBuffer per call; `Buffer.from(str)` carves from Node's shared `Buffer.poolSize` slab for small strings, so transferring one pool-backed Buffer's ArrayBuffer would detach every other Buffer that shares that slab — silent data corruption. - The envelope itself is NOT transferred. It MAY be pool-backed by `encodeMessage`, and at ~30-80 bytes/file the struct-clone cost is negligible. Not transferring avoids the same detach-collateral risk the contents path is careful to dodge. Detection is strict: every input element must have both `path: string` and `content: string`. A single non-conforming element disqualifies the whole batch from the transfer path and falls back to the legacy single-Uint8Array `encodeMessage` envelope. Safer than partial transfer (which would split a sub-batch into mixed-shape messages the worker can't reassemble). `parse-worker.ts` `decodeIncomingMessage` recognizes the hybrid `{envelope, contents}` shape, decodes the envelope, zips metadata positionally with the contents array, decodes UTF-8 → string per file, and hands the reassembled `ParseWorkerInput[]` to the existing `processBatch`. Identical downstream behavior to U17 — the IPC optimization is invisible above this line. Test scaffolding (3 FakeWorkers + 1 integration-test preamble) gain a `decodeDispatchedMessage` helper that tolerates BOTH shapes (legacy single-frame Uint8Array AND the new hybrid envelope+contents) so the in-process unit mocks keep their existing action-scripting API and the 9 ad-hoc integration test workers keep their `msg.type === 'sub-batch'` handlers unchanged. `buildDispatchMessage` is now exported from worker-pool.ts so its contract can be tested in isolation. A new `test/unit/worker-pool-transferlist.test.ts` pins: - hybrid shape produced for parse-worker inputs - transferList carries one ArrayBuffer per file in input order - envelope decodes to metadata only (no `content` field) - content bytes round-trip byte-for-byte through UTF-8 (ASCII, multi-byte, surrogate-pair emoji) - each content's ArrayBuffer is independently allocated (no pool sharing) — the load-bearing transfer-safety invariant - non-parse shapes, empty arrays, and mixed-conformance arrays all fall back to the legacy single-frame path All 271 test files (6166 unit + integration tests) pass. * fix(workers,tests,docs): apply ce-code-review findings (16 items) Walks the full set of findings from a multi-agent code review (11 reviewers, 1 maintainability dispatch lost to tool-permission denial) of the PR #1693 branch. All 16 actionable findings — 4 P1, 4 P2, 8 P3 — applied in a single pass against a consistent tree. Tests pass (269/269 unit files, 29/29 integration). P1 — bounds-only / disguised-bounds assertions across 4 test files (per user-memory DoD §2.7): - worker-pool.test.ts: 5 sites — `nodes.length > 0` dropped (redundant after `.toContain('validateInput')`); `files.length >= 4` pinned to `.toBe(7)` (mini-repo/src has exactly 7 .ts files); `results.length > 0` pinned to `.toHaveLength(1)` (default sub-batch absorbs all 7); `result.fileCount >= 0` pinned to `.toBe(1)` (empty file is still "processed"); `warnRecords.length > 0` replaced with content- predicate `/respawn|dropping|replacement|did not report ready/` (catches silenced warnings); `fallbackExcludePaths.length > 0` pinned to exact `['one.ts', 'two.ts']` (deterministic given the single-slot pool + 2 items + per-item starting-file). - parse-impl-fallback.test.ts: 3 sites — `astCacheClearCalls >= 1` pinned to exact 4 (per-chunk × 2 + finally × 2); the two error-path delta checks pinned to exact +2 and +3 (verified empirically). - parse-impl-progress-monotonic.test.ts: `percents.length > 0` → `.not.toEqual([])`; per-element `Math.max(prev, cur)` tautology replaced with direct `if (cur < prev) throw`; final-percent `Math.min(last, 95)` tautology pinned to exact `.toBe(70)` (3-file skipWorkers fixture's deferred band lands at the band start). - parse-impl-large-fixture.test.ts: `Math.min(elapsedMs, BUDGET)` tautology removed; Promise.race rejection is the load-bearing wall-clock check. P1 — terminate() lacks `.catch` mask: - worker-pool.ts terminate() now matches the `.catch(() => undefined)` pattern used at every other internal terminate site. Prevents a hung/OOM worker's terminate rejection from masking the original pipeline error when called from parse-impl.ts's finally block, and guarantees `workers.length = 0` / `activeSlots.clear()` always run. P1 — hybrid envelope length-mismatch + null-payload silent data loss: - parse-worker.ts decodeIncomingMessage: explicit non-null-and-typed check before `.type` access (decodeMessage permits null payloads per encodeMessage contract); explicit length-equality assertion between `decoded.files` and `contents` before zipping. Without these, `TextDecoder.decode(undefined)` silently returns "" and produces empty-content graph nodes — a contract violation that used to be undetectable. Both throws route through the outer try/catch → worker `error` reply → pool's recoverAndResume. P1 — unsafe casts at the IPC boundary: - buildDispatchMessage now uses a properly-typed `isParseWorkerItemArray` type guard. The narrowed branch accesses `item.path` and `item.content` as statically-typed strings — a future rename of `ParseWorkerInput.content` would fail to compile inside the branch instead of silently mismatching at runtime. The remaining decodeMessage payload casts are bounded by the F3/F6 runtime guards. P2 — idle-timeout retry bypasses circuit breaker: - worker-pool.ts timeout-retry IIFE now increments `consecutiveFailuresPerSlot[workerIndex]` alongside `respawnCount`. A slot that consistently times out (vs crashes) now trips the per-slot breaker, instead of consuming its full respawn budget over potentially tens of minutes without the breaker firing. P2 — null/non-object worker message crashes pool handler: - Dispatch handler in worker-pool.ts now guards `null / non-object / no string type discriminant` before `msg.type` access and routes through recoverAndResume on violation. Previously a legitimate `null` payload would throw TypeError out of the EventEmitter listener → uncaughtException on main, crashing the analyze. P2 — workerPoolSize === 0 creates unusable pool: - parse-impl.ts now treats `workerPoolSize === 0` as `skipWorkers` at the gate. Matches the PipelineOptions docstring contract ("0 disables the pool entirely — equivalent to skipWorkers"); avoids constructing a pool that rejects every dispatch and logs "Worker pool parsing stopped" per chunk. P2 — encodeMessage 2-buffer allocation per frame: - protocol.ts encodeMessage coalesced to a single `Buffer.allocUnsafe + writeUInt8 + writeUInt32LE + buf.write (string, offset, 'utf8')`. Drops the intermediate `Buffer.from(JSON.stringify(...), 'utf8')` allocation + memcpy. Length pre-check via `Buffer.byteLength(string, 'utf8')` surfaces the uint32 cap before any allocation. P3 — slotGenerations made optional on WorkerPoolStats so external implementations of getStats() that predate U12 don't compile-break; in-repo callers already use optional chaining. P3 — buildDispatchMessage marked `@internal` so it isn't surfaced as public API by typedoc / api-extractor (it's a test-only export). P3 — verboseThroughputLog hoisted above the chunk loop (env vars can't change mid-run; one O(env-read) per analyze, not per chunk). P3 — corrected the messageerror routing comment in worker-pool.ts dispatch handler. `ProtocolDecodeError` is caught by the surrounding try/catch — distinct from `messageerror`, which fires for V8 structured-clone failures before the message body would reach the handler. P3 — initial pool spawn now uses a `Promise.allSettled` ready-handshake gate symmetric with `replaceWorker`. Dispatch awaits this gate before selecting slots, so an init-crashing initial worker is dropped from `activeSlots` and a downstream OOM/missing-native-binding failure surfaces in seconds (bounded by WORKER_READY_TIMEOUT_MS) rather than waiting for the first idle timeout (30s default). P3 — `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT`, `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS`, `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` added to: - CLI `--help` text in src/cli/index.ts - Root README env-var table - gitnexus/README troubleshooting section (new "Worker pool resilience tuning" subsection) P3 — CLI `catch (e: any)` / `catch (err: any)` in analyze.ts replaced with `catch (err: unknown)` + narrowed access; matches modern TS best practice and the codebase pattern at other catch sites. P3 — `WorkerPoolStats.terminated: boolean` field added (optional, for backward compatibility). `terminate()` sets it true; `getStats()` surfaces it. Distinguishes graceful shutdown from a circuit-breaker trip in observability surfaces. Coverage / advisory items not addressed in this commit (kept in the report only): - maintainability reviewer failed (Read/Bash denied) — god-module audit on worker-pool.ts (~1400 LOC) carried as residual risk - quarantine case-sensitivity contract unpinned (adversarial #8) - WORKER_READY_TIMEOUT_MS env-configurability (adversarial #2) - chunk-byte-budget × parseChunkConcurrency memory multiplier doc (adversarial #5) - MCP discoverability gaps for env vars / verbose (agent-native W1/W2) - bench/parse-throughput.md scaffold-with-TBD-rows (PS RR-003) * fix(parsing): sequential gap-fill for worker-quarantined chunk files (U20.U1) When the worker pool's Layer 3 quarantine filters one or more files out of a chunk's dispatch, the worker results returned to processParsing are silently narrower than the input chunk. Without this reparse, the graph for this run would be missing every quarantined file's symbols/imports/calls/heritage with no failure signal. After the existing per-chunk quarantine log emits in processParsing's worker-path try-block, run processParsingSequential on JUST the quarantined-in-chunk files. The sequential path writes directly to the graph, so symbols for those files land alongside worker output for the surviving files. Mirrors the WorkerPoolDispatchError catch-block's processParsingSequential call shape — same signature, same args, same scopeTreeCache wiring. Emits a structured warn naming `reparsedPaths` so operators can observe the sequential fall-through. This fixes the in-run side of the corruption Codex's adversarial review of PR #1693 flagged. The cross-run side (chunk-cache poisoning) is closed by U20.U2 in a follow-up commit. References plan: docs/plans/2026-05-20-002-fix-chunk-cache-corruption-on-worker-quarantine-plan.md * fix(parse-impl): suppress chunk-cache write when any chunk file was quarantined (U20.U2) The chunk hash at parse-impl.ts:424-428 is computed from every file in the chunk. The worker pool's Layer 3 quarantine (worker-pool.ts createQuarantine) filters quarantined files out of dispatch, so `rawResults` reflects only the surviving files. Before this commit, the write at line 500-507 stored that partial result under the full-coverage chunk hash — and on the next analyze with unchanged content, the cache HIT branch (line 439-464) silently replayed the incomplete result. Symbols from the quarantined file were missing from the graph for as long as the cache survived. Codex's adversarial review of PR #1693 flagged this as a silent- corruption class because there's no failure signal: no warn log during the replay, no graph-equivalence check, no exit code change. The corruption only surfaces if an operator notices a missing symbol in `gitnexus_query` output. Guard the write with `chunkFiles.some(f => quarantineSet.has(f.path))`. When any chunk file is in the worker pool's cumulative quarantine snapshot, skip the `parseCache.entries.set` call. Emits a verbose- only info log so operators investigating "why aren't my chunks caching" have a diagnostic trail. Skipping the write means the next analyze gets a cache miss for this chunk and re-dispatches it. Quarantine is session-scoped (a fresh createWorkerPool starts with an empty quarantine), so the new pool gives the quarantined file another chance. If quarantine fires again, U20.U1's sequential gap-fill still produces a complete graph for that run; the cache stays empty for the chunk until a fully-clean dispatch lands. The cache-hit replay branch at parse-impl.ts:439-464 is unchanged. Its contract strengthens: "cache entries are complete" becomes true post-fix, but the replay code doesn't need to know that. Closes the cross-run side of the Codex finding. U20.U3 adds the regression test. References plan: docs/plans/2026-05-20-002-fix-chunk-cache-corruption-on-worker-quarantine-plan.md * test(parse-impl): integration regression for quarantine + chunk-cache (U20.U3) Pins the U20 fix end-to-end via REAL `worker_threads` + `createWorkerPool`. Mirrors the writeReadyWorker pattern from `test/integration/worker-pool.test.ts` — inline READY_PREAMBLE + custom test worker script that: 1. Decodes the U17/U19 IPC protocol (Buffer frame OR hybrid envelope/ contents shape) the same way the production parse-worker does. 2. Emits a `{type:'ready'}` handshake so the pool's `waitForWorkerReady` resolves promptly. 3. On a sub-batch containing `poison.ts`, emits starting-file + `process.exit(134)`. The pool attributes the death to `poison.ts` via the in-flight signal and adds it to the session-scoped quarantine. 4. On a sub-batch without poison, synthesizes a minimal valid `ParseWorkerResult` with one `Function` node per file (no tree-sitter dep in the test worker — the synthesized nodes give `mergeChunkResults` deterministic content for the graph). Assertions exercise both fix layers: - U1 (sequential gap-fill in processParsing): the graph contains a `Function` node named `poison` AFTER the run. The custom worker never emits anything for `poison.ts`, so the only path for that symbol to reach the graph is `processParsing`'s sequential reparse of the quarantined-in-chunk file using the real tree-sitter parser against the actual source. - U2 (cache-write suppression in runChunkedParseAndResolve): `parseCache.entries` does NOT contain the chunk hash after the run; `parseCache.usedKeys` DOES contain it (chunk processed, cache write specifically skipped). - Cross-run: a second pass over the same fixture with the same parseCache and a fresh worker pool re-dispatches the chunk (cache empty), the worker crashes again, sequential gap-fill runs again, and the cache stays empty. Pins the round-trip contract. Adds `workerUrlForTest?: URL` to PipelineOptions — same `@internal` test-only injection precedent as `workerThresholdsForTest` (already in PipelineOptions for thresholds). When set, parse-impl uses the provided URL instead of the src/ → dist/ resolution dance. Production call sites never set this field; the only consumer today is this integration test. Why integration over unit: - The fix lives at the boundary between parsing-processor.ts and parse-impl.ts under a real WorkerPool. Unit-mocking the worker-pool module bypasses the structured-clone boundary, the dispatch lifecycle, and the actual quarantine flow — it verifies the test setup rather than the contract. The real worker thread executing through the U17/U19 IPC protocol IS the load-bearing surface. - User-explicit preference (saved as feedback_integration_over_vimock.md memory). For worker-pool / parse-impl / IPC-touching code: write integration tests under test/integration/ using writeReadyWorker patterns; avoid vi.mock on worker-pool.js. Test wall-clock: under 2s; both `it` blocks together complete in ~1.8s under the existing CI conditions. References plan: docs/plans/2026-05-20-002-fix-chunk-cache-corruption-on-worker-quarantine-plan.md * refactor(parsing): remove sequential-parser fallback (U20 design pivot) The worker pool's resilience layers — respawn budget, circuit breaker, quarantine, slot-attribution, cumulative timeout — are now the SOLE contract for handling worker failures. Two sequential-reparse paths are removed from processParsing: 1. **U20.U1 sequential gap-fill for quarantined chunk files** (just added in commit |
||
|
|
f350ae278a
|
feat: Add analyze --repair-fts, enforce FTS verification, and harden repair safeguards (#1720)
* Initial plan * feat(analyze): add --repair-fts and verify FTS index rebuilds Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/dccb3673-af86-43aa-aede-2e1449399775 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * refactor(fts): tighten repair/verify messaging and option naming Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/dccb3673-af86-43aa-aede-2e1449399775 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docs: highlight analyze --repair-fts vs --force in READMEs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/61edc967-debc-419f-9f51-aebf2ef08d22 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(analyze): guard repair mode against missing graph store * fix(cli): reject --repair-fts with --force * test(analyze): document repair-store fixture intent * test(analyze): tidy repair failure fixtures and constants * test(analyze): clarify mock constants in repair tests * test(analyze): rename simulated missing-index constant * test(analyze): clarify mocked graph shape in full-verify test * refactor(analyze): finalize flag validation and test clarity * test(skip-git): avoid hard failing when FTS extension is unavailable * test(skip-git): log visible FTS-unavailable test skips * test(skip-git): tighten FTS-unavailable error detection * test(skip-git): simplify FTS-unavailable message checks * test(skip-git): avoid HOME pointing at parent repo in fixture env * fix(analyze): address Claude follow-up findings for repair guardrails Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2f7243d3-ba16-4d83-86e5-17e6c58a3b0d * fix(repair-fts): clarify invalid graph-store preflight errors Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2f7243d3-ba16-4d83-86e5-17e6c58a3b0d * test(analyze): strengthen assertions for conflict and missing-store errors Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2f7243d3-ba16-4d83-86e5-17e6c58a3b0d * fix(repair-fts): make invalid graph-store type errors explicit Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2f7243d3-ba16-4d83-86e5-17e6c58a3b0d * fix(repair-fts): improve graph-store type diagnostics Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2f7243d3-ba16-4d83-86e5-17e6c58a3b0d --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
2620b704e0
|
feat(cursor): upgrade hooks to Cursor 2.4 postToolUse for Read/Grep/Shell coverage (#1467)
* feat(cursor): upgrade hooks to Cursor 2.4 postToolUse for Read/Grep/Shell coverage
Cursor 2.4 (released 2026-01-22) shipped generic preToolUse/postToolUse hooks
matching `Shell|Read|Write|Grep|Delete|Task|MCP:<tool>`, replacing the
2.3-era beforeShellExecution hook that only fired on shell commands. The
existing integration only intercepted the shell path, so Cursor users got
graph augmentation roughly 10% as often as Claude Code users — only when
the agent dropped to rg/grep instead of using its native Read/Grep tools.
This swaps the integration over to postToolUse and ports the bash+jq
hook script to cross-platform Node:
- gitnexus-cursor-integration/hooks/hooks.json: registers a single
postToolUse hook matching Shell|Read|Grep that invokes the new
gitnexus-hook.cjs.
- gitnexus-cursor-integration/hooks/gitnexus-hook.cjs: new Node hook
mirroring the safety patterns from the Claude hook (absolute-cwd
validation, .gitnexus discovery with linked-worktree fallback,
npx.cmd on Windows, end-of-options `--` marker, debug truncation,
graceful failure). Extracts the search pattern per tool kind:
Grep -> toolInput.query; Read -> file basename stripped to identifier
chars; Shell -> existing rg/grep arg parser. Emits Cursor-shape
`{ "additional_context": "..." }` on stdout — no shell, no jq.
- gitnexus-cursor-integration/hooks/augment-shell.sh: removed (Windows
incompatible, narrower coverage).
- gitnexus/test/unit/cursor-hook.test.ts: 33 regression tests covering
manifest wiring, source-level invariants (no shell:true, npx.cmd,
isAbsolute, additional_context output shape, end-of-options marker),
extractPattern coverage per tool, and behavioral early-exit paths
(empty/invalid stdin, relative cwd, no .gitnexus, unknown tool name,
short patterns, non-search shell commands, case-insensitive matching).
- README.md / gitnexus/README.md: editor-support table now lists Cursor
as Full / hooks=Yes (postToolUse), matching reality.
- gitnexus/src/cli/augment.ts and gitnexus/src/core/augmentation/engine.ts:
doc-strings updated from `Cursor beforeShellExecution` to
`Cursor postToolUse`.
Closes #1466.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cursor): hook timeout is in seconds, not milliseconds
Cursor's `timeout` field in hooks.json is in seconds (per
https://cursor.com/docs/agent/hooks and the original integration's
`"timeout": 5`). I'd written `10000` after blindly copying the issue
body's example — that resolves to ~2.8 hours, not 10 seconds. If the
script ever hangs before reaching its inner spawnSync timeouts (e.g.
during stdin read), Cursor would have waited that long before killing
it.
Drop to `10` (seconds), matching the Claude plugin's hooks.json and
giving plenty of headroom over the inner 7s augment-CLI timeout.
Add a regression-guard assertion in cursor-hook.test.ts so a future
ms/s mixup fails fast.
Reported by Cursor Bugbot on PR #1467.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cursor): address Claude review findings — payload aliases, debug, install docs
Resolves three findings from Claude reviewer on PR #1467:
1. Cursor payload field-name uncertainty (SIGNIFICANT)
Claude flagged that the Grep `query` field is an unverified assumption
per Cursor 2.4 docs (https://cursor.com/docs/agent/hooks). Mitigated:
- Expanded Grep aliases: query | pattern | regex | q | search | searchQuery
- Added pickLongestStringValue() last-resort fallback so the hook
extracts *something* even if Cursor renames every documented field
- Added GITNEXUS_DEBUG=1 stderr logging of the raw stdin payload so
users can capture Cursor's actual contract when diagnosing silent
no-ops, and report it back if aliases drift
- Added Read alias `filePath` (camelCase variant alongside `file_path`)
- Inline comment block citing the docs URL and the uncertainty
2. Hook command path resolution + install docs (SIGNIFICANT)
Claude flagged `node ./hooks/gitnexus-hook.cjs` as relative without
documented install path. Added gitnexus-cursor-integration/README.md
with explicit install steps:
- .cursor/hooks.json + hooks/gitnexus-hook.cjs at project root
- Confirms Cursor's project-root CWD convention with doc link
- Verify steps including GITNEXUS_DEBUG capture
- Pattern-extraction contract table per tool
- Troubleshooting: not-firing, npx fallback, wrong-pattern diagnosis
3. README "Full" overclaim for Cursor (MODERATE)
Both README rows now read `Yes (postToolUse, manual install)` linking
to the new install README, accurately signaling that hooks aren't
automated by `gitnexus setup` like they are for Claude Code.
4. Shell quoted-pattern parser limitation (MINOR, documented)
Added inline comment in gitnexus-hook.cjs documenting the known
`rg "User Service"` -> `User` truncation, plus regression tests in
cursor-hook.test.ts pinning the behavior so a future change is
visible.
Test additions (33 -> 41):
- Wide-alias source coverage for Grep (query / pattern / regex / q /
search / searchQuery) plus pickLongestStringValue fallback
- Read alias coverage including camelCase filePath
- GITNEXUS_DEBUG behavioral test: stderr quiet by default, payload
echoed when env var set, stdout output contract preserved either way
- Shell quoted-pattern documented behavior tests
- Install README presence + content (.cursor/hooks.json, hooks/, debug
diagnostics)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
|
||
|
|
ffa0510f9a
|
fix(lbug): prevent DuckDB extension install hangs (#1129)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / scope-parity (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
* fix(lbug): bound DuckDB extension install via ExtensionManager (closes #1128) `gitnexus analyze` could hang indefinitely (60% / 85% on Windows) when DuckDB's `INSTALL fts` or `INSTALL VECTOR` was unable to reach `extensions.duckdb.org`. The DuckDB driver's INSTALL is a synchronous network call, so any blocked egress would block the Node event loop forever. Replace the ad-hoc, in-process INSTALL/LOAD scattered across `lbug-adapter.ts` and `pool-adapter.ts` with a single `ExtensionManager` that owns the lifecycle of optional DuckDB extensions: * `LOAD` is always tried first — per-connection, idempotent, no network. * If `LOAD` fails and policy permits, INSTALL runs in a short-lived child Node process bounded by `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` (default 15s). The parent loop keeps spinning; on timeout the child is killed with SIGKILL and the capability is flagged unavailable. * Capabilities and install attempts are cached per process, so a single bounded install per extension covers every subsequent call. Install policy is now an explicit, per-context decision: * `auto` (default for analyze) — try LOAD, fall back to bounded INSTALL. * `load-only` — used by `pool-adapter` (serve / MCP read paths) so user queries never block on a network install. * `never` — operator escape hatch for offline / airgapped environments. `createFTSIndex` and `createVectorIndex` now check the boolean return value before issuing the index DDL, so missing extensions degrade BM25 and semantic search gracefully without ever throwing during analyze. Tests: - New unit suite for `ExtensionManager` covering LOAD-first behavior, all three policies, install caching, observability, and warn dedup. - Existing vector-extension integration tests pass against the new boolean return type. - Existing embedding-pipeline mocks updated to return `true`. Docs: `gitnexus/README.md` documents `GITNEXUS_LBUG_EXTENSION_INSTALL` and `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` with examples for offline and slow-network environments. Made-with: Cursor * fix(lbug): move DuckDB extension install child into script Keep the bounded out-of-process INSTALL behavior, but replace the inline child code with a stable packaged ESM script. This makes the child process directly runnable and gives debuggable stack traces without source-vs-dist branching or a runtime transpiler. Made-with: Cursor |
||
|
|
38ccf7ceb1
|
fix: recover worker parse stalls (#1121)
* fix(ingestion): recover worker parse stalls Made-with: Cursor * test(ingestion): cover worker timeout controls Made-with: Cursor * docs: document analyze worker timeout controls Made-with: Cursor * fix(ingestion): fail fast after worker pool hard failure Made-with: Cursor * test(ingestion): stabilize worker stall recovery tests Made-with: Cursor --------- Co-authored-by: GitNexus Maintainer <maintainer@gitnexus.local> |
||
|
|
9bf9c49a53
|
docs(ingestion): document configurable large-file skip threshold (#991) (#1045)
Follow-up to #1044. Adds user-facing documentation for the configurable skip threshold introduced in that PR: - README CLI Commands: new --max-file-size example line - README Troubleshooting: new 'Large files are being skipped' subsection covering the CLI flag, env var, default (512 KB), ceiling (32768 KB), fallback behaviour, and the effective-threshold banner - CHANGELOG [Unreleased] Added: feature entry with issue/PR cross-refs |
||
|
|
ea418c0126
|
docs: fix group add and group remove usage in READMEs (#1020)
The top-level and CLI READMEs advertised `gitnexus group add <name> <repo>` (two args) and `gitnexus group remove <name> <repo>`, but the CLI (`gitnexus/src/cli/group.ts`) actually requires three args for `add` (`<group> <groupPath> <registryName>`) and uses `<groupPath>` — not a repo path — for `remove`. Reusing the same second argument across two `group add` invocations silently overwrote the previous mapping because the hierarchy path is the key in `group.yaml`'s `repos` map. Update both READMEs to match the real CLI contract. Node_modules not installed locally for this docs-only change, so pre-commit (prettier + typecheck) was skipped. Made-with: Cursor Co-authored-by: TuanPM1 <tuanpm1@kaopiz.com> |
||
|
|
baf3f9e37d
|
feat(ci): add release-candidate publish pipeline (#825)
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
* feat(ci): add release-candidate publish pipeline
Auto-publishes gitnexus@rc on every merge to main. Version scheme is
canonical semver X.Y.Z-rc.N where the base is the current npm 'latest'
bumped by the 'bump' input (default patch) and N auto-increments by
querying existing rc versions on the registry. First rc for a new base
is rc.1; the counter resets naturally when the base advances after a
stable release.
- Reuses ci.yml via workflow_call so tests must pass before publish
- SHA-pinned actions, per-job permission scoping, provenance enabled
- Guard job dedupes duplicate dispatches against HEAD via v*-rc.* tags
- Docs-only pushes skipped via paths-ignore
- workflow_dispatch inputs: bump (patch/minor/major), force (override guard)
- Publishes under the 'rc' dist-tag so 'latest' is never moved
- Tags commits as v<rc-version> and creates GitHub prereleases
* fix(ci): address release-candidate review feedback
- Sort rc tags by creatordate (handles out-of-order pushes correctly)
- Fail fast on npm registry errors; only fall back to package.json on E404
- Drop unused pull-requests: write permission on the reused CI job
- Add secrets: inherit so any future CI secrets are available to sub-jobs
- Remove unused reltag step output
* fix(ci): address Copilot review comments
- Correct concurrency comment (runs serialize on same ref, not overlap)
- Apply E404-only fallback to 'npm view versions' query, matching the
pattern used for the 'npm view version' query
- README: clarify that docs-only merges don't trigger rc publish
- CONTRIBUTING: drop 'from main' claim for publish.yml; the tag-push
trigger does not enforce branch reachability
* fix(ci): address adversarial review — idempotency, cycle continuity, tag integrity
Codex adversarial review flagged three release-safety issues in the rc
pipeline. Fixes:
1. Cycle continuity (H). Non-patch rc trains no longer collapse back to
patch on the next push. 'bump' input accepts a new 'auto' value
(default) that infers the active rc base from the registry: if any
X.Y.Z-rc.* exists with X.Y.Z > latest, continue that base; otherwise
patch-bump. Explicit patch/minor/major still forces a cycle reset and
now also bypasses the dedup guard so an explicit dispatch on a
tagged HEAD is honored.
2. Idempotency across post-publish failures (H). The guard marker
('rc/<HEAD_SHA>' lightweight tag) and the release tag ('v<RC>'
annotated) are now pushed atomically *before* 'npm publish'. A
publish failure leaves the marker in place and the guard refuses to
re-publish. Added a defensive 'npm view <pkg>@<rc> version' check
before publish to catch registry-level races. Recovery path
documented in CONTRIBUTING.md.
3. Tag ↔ package integrity (M). 'v<RC>' now points at a detached
release commit whose tree contains the rewritten package.json, so
the tag's source archive matches the npm tarball exactly. 'main'
stays pristine; the release commit is reachable only via the tag.
* fix(ci): surface registry errors on defensive version check; drop actions: read
- npm view <pkg>@<rc> version now distinguishes E404 (safe) from network
failures (abort) via the same mktemp+grep pattern used for the other
two npm view calls
- Dropped actions: read on the ci workflow_call — no sub-workflow uses
the Actions API
|
||
|
|
1a597f3cc6
|
Fix npm arborist crash caused by tree-sitter-dart tarball URL format (#820)
* Initial plan * fix: change tree-sitter-dart from tarball URL to git URL to fix npm arborist crash, add error handling and troubleshooting docs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/382c76c6-89c3-463a-8631-2a5d6510be4c Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * refine error handler patterns and troubleshooting docs for arborist crash Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/909b319b-c367-40aa-8033-32dfb6231d4e Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * style: run prettier on changed files Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/50eb6b94-9300-4bf2-9b61-c2d78f637fc6 * fix: use github: shorthand for tree-sitter-dart to avoid SSH in CI Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2ce3f4b3-4c1e-4c39-b824-c25cfe145529 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * revert: use git+https:// for tree-sitter-dart instead of github: shorthand (fixes arborist crash from PR #811) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b786b68d-6c76-4054-88eb-ad46ea9f5b81 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> |
||
|
|
4fed097abb |
feat(group): add sync pipeline, CLI, MCP tools, and monorepo fixture
Wire extractors into the sync pipeline with service boundary detection. GroupService provides high-level API for all group operations. - Sync pipeline: orchestrates extraction (HTTP, gRPC, topics) with service boundary assignment and exact matching - GroupService: groupList, groupSync, groupContracts, groupQuery, groupStatus (groupImpact deferred to cross-repo follow-up PR) - CLI: group create/add/remove/list/sync/contracts/query/status - MCP tools: group_list, group_sync, group_contracts, group_query, group_status - Monorepo fixture: 3 services (auth/orders/gateway) connected via gRPC + Kafka + HTTP — all intra-repo cross-links discovered - Documentation: CLI commands and MCP tools added to both READMEs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
b03413dcf9
|
feat: added skip-agents-md cli flag (#517)
* feat: added skip-agents-md cli flag * fix: apply prettier formatting * feat: added skip-agents-md cli flag * fix: apply prettier formatting * feat: add skipAgentsMd option to skip AGENTS.md and CLAUDE.md updates * fixed bad merge |
||
|
|
cde3858e03
|
refactor: Phase 8 & 9 — Field Types and Return-Type Binding (#494)
* feat(phase8): add field type data structures and extractor interface
* feat(phase8): implement TypeScript field extractor
* feat-phase9-add-call-result-binding
* test-phase8-add-field-extraction-unit-tests
* docs: update documentation for Phase 8 and Phase 9
* feat(swift): Phase 8/9 integration tests for field-type and call-result binding
Add Swift field-type resolution and call-result binding integration tests
with fixtures, plus merge-conflict fixes for the FieldExtractor code.
**Swift integration tests:**
- `swift-field-types/` fixture (Models.swift + App.swift) — tests
HAS_PROPERTY edges, field-chain CALLS resolution (user.address.save()
→ Address#save), and ACCESSES edges for field reads.
- `swift-call-result-binding/` fixture — tests call-result binding
(let user = getUser(); user.save() → User#save).
- 2 new describe blocks in swift.test.ts with skipIf(!swiftAvailable).
**Swift arity fix:**
- extractMethodSignature fallback counts direct `parameter` children
when no wrapper list node exists (Swift's tree-sitter grammar places
parameters as direct children of function_declaration). Without this,
all Swift functions had parameterCount: 0 and the arity filter rejected
valid call targets.
**FieldExtractor merge-conflict fixes:**
- field-extractor.ts: update import from removed ./utils.js to
./utils/ast-helpers.js; use typeEnv.fileScope() instead of .get('').
- field-extractors/typescript.ts: same import fix.
- field-types.ts: alias TypeEnvironment as TypeEnv (renamed on main).
- field-extraction.test.ts: mock TypeEnvironment interface properly.
* feat(field-extractors): generic table-driven field extractors for all 14 languages, wired into pipeline
Implements field extractors for all supported languages and integrates
them into the ingestion pipeline as the single source of truth for
Property node metadata.
**Generic field extractor factory** — `field-extractors/generic.ts`
defines a `createFieldExtractor(config)` factory that generates
FieldExtractor instances from a per-language `FieldExtractionConfig`.
Each config specifies AST node types, name/type/visibility extraction
functions, and static/readonly detection — typically 20-40 lines per
language vs 300+ for a hand-written extractor.
**Per-language configs** — `field-extractors/configs/` has 11 config
files covering 13 languages (TS/JS share, Java/Kotlin share).
TypeScript keeps its hand-written extractor for richer handling.
**LanguageProvider integration** — New optional `fieldExtractor` property
on LanguageProviderConfig, set via defineLanguage() in each language
file. Follows the same strategy pattern as typeConfig, exportChecker,
and labelOverride. Removed the separate FieldExtractorRegistry class
and field-extractors/index.ts — extractors are accessed via
getProvider(lang).fieldExtractor.
**Pipeline wiring** — Both parse-worker.ts (worker pool) and
parsing-processor.ts (sequential fallback) now call the FieldExtractor
during Property node creation. Results are cached per class node.
Property nodes are enriched with: declaredType, visibility, isStatic,
isReadonly.
**extractPropertyDeclaredType removed** — The 100-line multi-strategy
function in type-extractors/shared.ts is replaced by the FieldExtractor.
All 14 languages register an extractor, eliminating the need for a
generic fallback. The Python config's extractType was fixed to handle
annotation-without-value patterns (address: Address).
**Integration tests** — Each language's resolver test file gains
pipeline-based assertions verifying visibility/isStatic/isReadonly on
Property nodes via getNodesByLabelFull. Tests run through
runPipelineFromRepo with real fixtures — no direct extractor calls.
* fix(type-env): thread enclosingFunctionFinder through scope resolution, unskip Dart ACCESSES test
The type-env's findEnclosingScopeKey had the same Dart sibling problem
as findEnclosingFunction — it walked parents but never found
function_signature because the call lives inside function_body (a
sibling). Instead of hardcoding a function_body check, thread the
provider's enclosingFunctionFinder hook through BuildTypeEnvOptions →
lookupInEnv → findEnclosingScopeKey. All three buildTypeEnv call sites
(call-processor, parsing-processor, parse-worker) now pass the hook.
This enables the type-env to resolve scoped parameter bindings for Dart
(e.g., `user: User` in processUser), which lets the chain-resolution
tier (Step 1c) walk `user.address` and emit ACCESSES edges.
Dart integration test unskipped — 10/10 passing including ACCESSES.
Reverted CHANGELOG.md to origin/main.
* fix: resolve all PR #494 review findings (10 items)
CRITICAL:
- parse-worker.ts: classNode: any → SyntaxNode on getFieldInfo
and findEnclosingClassNode; removed redundant as number casts
- parsing-processor.ts: classNode: any → SyntaxNode on seqGetFieldInfo
HIGH:
- ruby.ts: attr_accessor now extracts ALL symbol arguments via
extractNames hook in generic factory (was firstNamedChild only)
- typescript.ts: added JSDoc explaining why hand-written extractor
coexists with config-based typescript-javascript.ts
MEDIUM:
- field-types.ts: FieldVisibility union type replaces string
('public'|'private'|'protected'|'internal'|'package'|'fileprivate'|'open')
Propagated through field-extractor.ts, generic.ts, all 7 config files
- typescript.ts: extractFullType collapsed from 12 branches to 3 lines
- generic.ts: added extractNames? optional hook + buildField refactor
LOW:
- ruby.ts: extractVisibility(node) → extractVisibility(_node)
- python.ts: fixed misleading isStatic comment
TypeScript compiles cleanly.
* test: add 24 field extraction tests for generic factory + 5 languages
Generic factory (4 tests):
- createFieldExtractor with TypeScript config validates factory itself
- Body discovery for interfaces, static/readonly modifiers
- Non-type node rejection
Python (4 tests):
- Annotated class field extraction
- Underscore-based visibility: _name=protected, __name=private
Go (5 tests):
- isTypeDeclaration on type_declaration nodes
- Config functions: uppercase=public, lowercase=package visibility
- extractType, isStatic, isReadonly
C++ (5 tests):
- public/private/protected access specifier backward-sibling walk
- Default visibility: class=private, struct=public
- static/const modifier detection
Ruby (6 tests):
- attr_accessor multi-symbol: :name, :email, :age → 3 fields
- attr_reader=readonly, attr_writer=non-readonly
- Multiple attr_* calls in one class
Total: 46 tests passing
* chore: remove plan doc from PR
---------
Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
|
||
|
|
6fabd7a2df
|
Merge pull request #402 from adonisdoda/feat/index-cli | ||
|
|
3961ad01cf | updating windows support | ||
|
|
a3fac2f672
|
Merge pull request #395 from zm2231/feat/http-embedding-backend | ||
|
|
c1996f45d8 | feat(cli): add 'index' command to register existing .gitnexus/ folder | ||
|
|
7dcafb647b |
fix: address review feedback — timeout, retry, guards, tests
- Add AbortSignal.timeout(30s) on all fetch calls - Add retry with backoff for 429/5xx (core: 2 retries, MCP: 1 retry) - Guard initEmbedder() and getEmbedder() to throw in HTTP mode - Discard cached embeddings on dimension mismatch during incremental re-index - Add MCP embedQuery retry for transient failures - Add 16 unit tests covering both core and MCP HTTP paths - Fix README: concise, accurate env var docs |
||
|
|
a7b8c302d4
|
Merge branch 'main' into feat/codex-support | ||
|
|
5a5850832c
|
refactor: migrate from KuzuDB to LadybugDB v0.15 (#275)
* refactor: migrate from KuzuDB to LadybugDB v0.15 KuzuDB was archived (Apple acquisition, Oct 2025). LadybugDB is the community fork with full API compatibility. - Package swap: kuzu → @ladybugdb/core, kuzu-wasm → @ladybugdb/wasm-core - Rename all internal paths: kuzu → lbug (adapters, schema, storage) - Storage path: .gitnexus/kuzu → .gitnexus/lbug (with auto-cleanup) - Add explicit VECTOR extension loading (required in v0.15) - Update CI workflow, documentation, and all tests - 1151 unit + 27 integration tests passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address code review findings (P1-P3) P1: Fix WASM adapter to use getAll() API, wire cleanupOldKuzuFiles into analyze command, add symlink path traversal protection. P2: Cache VECTOR extension load state, batch augmentation engine queries (20→4), fix web getCopyQuery for multi-language tables, fix stale KuzuDB references, correct brainstorm package names. P3: Complete lbug-wasm.d.ts type declarations, batch semantic search per-label, update stale BM25 comment. * chore: remove outdated KuzuDB migration brainstorming document * fix: load FTS extension in MCP pool adapter on init The read-only pool adapter never loaded the FTS extension, so all QUERY_FTS_INDEX calls failed silently. This broke search-pool and augmentation integration tests, and caused empty results in the web UI server mode. * feat: implement shared Database caching and connection reference counting * feat: enhance KuzuDB migration handling and status reporting * fix: mock cleanupOldKuzuFiles in local backend callTool tests * fix: update mock for cleanupOldKuzuFiles and adjust imports in callTool tests --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
62242d5f44
|
feat: TypeEnvironment API with constructor inference, self/this/super resolution (#274)
* feat(type-env): constructor-call type inference for TypeEnv (Phase 1)
Add extractInitializer as a Tier 1 fallback in buildTypeEnv: when a
declaration node has no explicit type annotation, infer the type from
constructor-call patterns (new X(), X::new(), X::default(), $x = new X()).
Languages covered: TypeScript/JS, Java (var), Rust, PHP, C++ (auto).
Python/Kotlin/Swift deferred — need symbol-table access to distinguish
class constructors from function calls.
Adds 20 new unit tests covering constructor inference, annotation
precedence, and known limitations across all supported languages.
* fix(type-env): class-aware constructor resolution, multi-declarator fix
- Add collectClassNames pre-scan: walks AST to build Set<string> of
class/struct names defined in the file
- C++ extractInitializer uses classNames.has() to verify identifier is
a known class before inferring (auto x = User() resolves, auto x =
getUser() does not — no false positives)
- Add InitializerExtractor type that receives classNames parameter
- Fix env.size gating: always call extractInitializer when available,
so mixed declarators like const a: A = x, b = new B() resolve both
- Add env.has() guard in Java extractInitializer to skip already-bound vars
- Document Rust new/default whitelist rationale
- Pin all test assertions, add mixed multi-declarator test case
* fix(type-env): resolve Self/self/static/parent to actual type names
- Rust: Self::new()/Self::default() resolves to enclosing impl type
- PHP: new self()/static() resolves to enclosing class, parent() to superclass
- Rust: Tier 0 annotation guard prevents overwrite by constructor inference
- Rust: mut_pattern handling in extractVarName for let mut bindings
- TS: fix misleading comment in extractInitializer
- 58 tests passing (3 new Self/self resolution tests)
* perf(type-env): single-pass AST walk with closure-scoped state
Refactors buildTypeEnv to use closures instead of passing mutable state
as parameters. classNames, env, and config are captured by the inner
walk and extractTypeBinding functions — no parameter mutation.
- Eliminates separate collectClassNames pre-scan (O(2n) → O(n))
- config looked up once per file instead of per-node
- 29 fewer lines
* feat(type-env): constructor-inferred type resolution for all languages
Add cross-file constructor type inference to the ingestion pipeline,
enabling receiver-type disambiguation for member calls like
`user.save()` when the variable is assigned from a constructor without
explicit type annotations.
Pipeline changes:
- Add extractInitializer to Python and Swift type extractors
- Add CONSTRUCTOR_BINDING_SCANNERS for Python, Swift, C/C++ in type-env
- Wire constructorBindings through parse-worker → parsing-processor →
pipeline → processCallsFromExtracted
- Rewrite resolveCallTarget receiver-type filtering (step D) to use
tiered import resolution (same-file → import-scoped → global) before
falling back to fuzzy ownerId matching
- Use collectTieredCandidates for constructor binding verification
instead of raw lookupFuzzy
Bug fixes:
- Fix C++ inline method query: @definition.method was captured on
field_declaration_list instead of function_definition, causing wrong
parameterCount for all inline class methods
- Fix parse-worker accumulated/flush results missing constructorBindings
CI changes:
- Add swift.test.ts to ci-integration pipeline group and coverage job
- Update ci-report to fetch base branch (main) coverage for delta
reporting instead of showing config thresholds
- Add per-suite timing breakdown table (unit/integration/total)
- Add expandable skipped test details section
Tests: 288 passed, 4 skipped (swift — macOS only) across 10 languages
- 36 new constructor-inferred integration tests (4 per language)
- 10 fixture directories with cross-file constructor patterns
- TypeScript, JavaScript, Java, Kotlin, Python, PHP, Rust, Go, C++, Swift
* fix(type-extractors): add type assertion for LanguageTypeConfig
* feat(ruby): constructor-inferred type resolution and self-receiver mapping
Add Ruby User.new constructor binding scanner to type-env, enabling
receiver-type disambiguation for member calls like user.save vs repo.save.
Add self/this → enclosing class resolution in lookupTypeEnv so self.method()
calls resolve to the correct class even when the method name is ambiguous.
* docs: update README with constructor inference and self/this resolution details
* refactor(ingestion): unified ResolutionContext replaces fragmented map passing
Introduce createResolutionContext() as the single resolution API for all
processors. Eliminates duplicated tier-selection logic, fixes heritage
namedImportMap bug, and adds per-file resolution caching.
- NEW resolution-context.ts: closure-factory with resolve(), per-file cache,
TIER_CONFIDENCE constant, and shared ResolutionTier type
- DELETE symbol-resolver.ts: zero production importers, logic now in
resolution-context.ts
- call-processor: all functions take ctx instead of 6 separate maps,
collectTieredCandidates removed (ctx.resolve replaces it),
D4 redundant re-resolve eliminated
- heritage-processor: takes ctx, resolveHeritageId helper extracts
repeated 14-line fallback pattern, namedImportMap now included
- import-processor: takes ctx, dead createImportMap/createPackageMap/
createNamedImportMap factories removed
- pipeline: creates single ctx, wires onProgress to all processors,
logs cache hit rate in dev mode
- Tier renamed: unique-global → global (honest about returning all candidates)
- Tests migrated: 1178 unit + 84 integration passing
* feat(type-env): self/this/super resolution, TypeEnvironment API, and review fixes
Add cross-language receiver keyword resolution:
- self/this/$this → enclosing class name via AST walk
- super/base/parent → parent class name via heritage AST extraction
(8 grammar variants: TS/JS, Java, Python, Ruby, C#, PHP, Kotlin, C++, Swift)
- D-phase widening in resolveCallTarget for super→parent method dispatch
Introduce TypeEnvironment API replacing loose TypeEnvResult + lookupTypeEnv:
- buildTypeEnv() returns TypeEnvironment with .lookup() method
- Single-pass AST walk merges constructor binding scan (was separate traversal)
- ClassNameLookup type replaces over-broad ReadonlySet<string> facade
- Memoized class name lookups to avoid redundant SymbolTable scans
Code review fixes (6 agents, 11 findings):
- Replace ctx.resolve(name, '') hack with direct symbols.lookupFuzzy()
- Extract scope key helpers (extractFuncNameFromScope, receiverKey)
- Simplify D-phase from 5 steps to 4 with deduped typeNodeIds
- Remove C from CONSTRUCTOR_BINDING_SCANNERS (YAGNI — C has no constructors)
- Cache Map reuse in ResolutionContext to reduce GC pressure
- Remove unused TieredCandidates import
Integration tests for self/this, parent, and super resolution across all
12 supported languages with per-language fixture directories.
* fix(type-env): generic parent resolution, TS cast inference, C++ brace-init
Fix generic parent class breaking super resolution:
- extractParentClassFromNode now uses extractSimpleTypeName to strip
generic params (Base<T> → Base) and qualified names (models.Model → Model)
- Affects TS, Java, Python, C# heritage extraction
Fix TypeScript new X() as T / new X()! missed inference:
- Unwrap as_expression and non_null_expression before checking for
new_expression in extractInitializer
Fix C++ brace-init User{} missed inference:
- Handle compound_literal_expression with type_identifier child
in extractInitializer
Clean up deprecated lookupTypeEnv:
- Remove standalone lookupTypeEnv export, migrate all callers to
TypeEnvironment.lookup() method
- Update all 80+ test assertions to use the new API
Integration test fixtures added:
- typescript-cast-constructor-inference (new X() as T, new X()!)
- typescript/java/csharp/kotlin-generic-parent-resolution
- cpp-brace-init-inference (auto x = User{})
* fix(type-extractors): Go &User{}, TS double-cast, Swift .init inference
Fix Go pointer-to-struct literal not inferred:
- Unwrap unary_expression (address-of &) before composite_literal check
- user := &User{} now correctly infers type User
Fix TypeScript double-cast only unwrapping one level:
- Change if to while loop for nested as_expression/non_null_expression
- new User() as unknown as Admin now correctly infers type User
Fix Swift User.init(name:) explicit init call missed:
- Handle navigation_expression callee with .init suffix in extractInitializer
Integration test fixtures:
- go-pointer-constructor-inference (&User{}, &Repo{})
- typescript-double-cast-inference (as unknown as T)
* feat: Rust struct literal, Python qualified ctor, Go new(), Swift .init scanner
- Rust: handle struct_expression in extractInitializer (User { name: "alice" })
- Python: support attribute nodes in extractInitializer (models.User("alice"))
and the cross-file scanner — extractSimpleTypeName handles qualified names
- Go: handle new(User) built-in in extractGoShortVarDeclaration
- Swift: extend CONSTRUCTOR_BINDING_SCANNERS to handle navigation_expression
callee for User.init(name:) cross-file resolution
Unit tests: 87 → 96 (Rust struct literal, Go new(), Python qualified ctor,
Python scanner qualified, plus edge cases)
Integration tests: 4 new describe blocks with fixtures
* fix: Rust Self{} resolution, C++ scoped brace-init, PHP promotion params, Ruby constants
- Rust: resolve Self {} struct literal to enclosing impl type (was stored as "Self")
- C++: replace type_identifier guard with extractSimpleTypeName for compound_literal_expression,
enabling ns::User{} scoped brace-init (closes previously deferred gap)
- PHP: add property_promotion_parameter to TYPED_PARAMETER_TYPES for PHP 8.0+
constructor property promotion (__construct(private Foo $x))
- Ruby: extend extractRubyConstructorBinding to accept constant left-hand side
(REPO = Repo.new)
Unit tests: 96 → 101 (+5: Rust Self{} ×2, C++ ns::User{} ×1, PHP promotion ×1,
Ruby constant ×1)
Integration tests: 4 new describe blocks with fixtures
* feat: Phase 1 type resolution gaps — walrus, PHP properties, nullable, Go make/assert
Phase 1 quick wins from the type resolution gap analysis:
1. Python walrus operator := (named_expression) — extractInitializer + scanner
2. PHP 7.4+ typed class properties — property_declaration in extractDeclaration
3. Nullable union unwrapping — User | null → User in extractSimpleTypeName
4. Go make() builtin — slice/map element type extraction
5. Go type assertions — iface.(User) type extraction
Also: PHP primitive_type handling in extractSimpleTypeName (string, int, etc.)
Unit tests: 101 → 114 (+13)
Integration tests: 8 new describe blocks with fixtures
* feat: Phase 2 type resolution gaps — C++ range-for, Rust if-let, C# pattern matching, Python class annotations
Phase 2 medium-effort improvements:
1. C++ range-for with explicit type — for (User& u : vec) binds u: User
2. Rust if-let/while-let captured_pattern — user @ User { .. } binds user: User
3. C# is-pattern matching — if (obj is User user) binds user: User
4. Python class-level annotations — confirmed already working, added tests
Unit tests: 114 → 127 (+13)
Integration tests: 11 new test cases with fixtures
|
||
|
|
0999595444
|
feat(ruby): Add Ruby language support for CLI and web (#111) | ||
|
|
1afe9166aa
|
feat: language-aware code intelligence — symbol resolution, MRO, constructor discrimination (#238)
* feat: add Method Resolution Order (MRO) with language-specific rules
Implement full MRO computation for multi-language inheritance hierarchies:
- HAS_METHOD edges: Class→Method ownership edges emitted during parsing
(both worker pool and sequential fallback paths)
- Method signatures: extract parameterCount and returnType from AST nodes
- C# heritage fix: distinguish EXTENDS vs IMPLEMENTS for base_list captures
using symbol table lookup + I[A-Z] naming heuristic fallback
- MRO processor (Phase 4.5): walks inheritance DAG, detects method-name
collisions across parents, applies language-specific resolution:
- C++: leftmost base class in declaration order wins
- C#/Java: class method wins over interface default
- Python: C3 linearization with cycle detection
- Rust: no auto-resolution (requires qualified syntax)
- Default: first definition in BFS order wins
- OVERRIDES edges emitted for resolved method collisions
- KuzuDB schema: Method table extended with parameterCount/returnType;
dedicated CSV writer and COPY query for 10-column Method rows
- MCP tools: updated Cypher examples for HAS_METHOD, OVERRIDES, diamond
72 tests across 5 test files covering MRO resolution, HAS_METHOD edges,
method signature extraction, C# heritage resolution, and integration
tests across C#/Rust/Python/TS/Java/C++.
* feat: add scope-based symbol resolution replacing raw lookupFuzzy
Introduces a shared 3-tier resolveSymbol function used by both
heritage-processor and call-processor:
1. Same-file (lookupExactFull — authoritative)
2. Import-scoped (filtered by ImportMap — high confidence)
3. Global fuzzy (first match — low confidence fallback)
Adds lookupExactFull to SymbolTable returning full SymbolDefinition
with type info needed for heritage Class/Interface disambiguation.
* refactor: tighten symbol resolution — Tier 3 refuses ambiguous matches
- lookupExactFull now O(1) via direct SymbolDefinition storage in fileIndex
(shared object references with globalIndex — zero additional memory)
- Added resolveSymbolInternal() preserving { definition, tier, candidateCount }
for test assertions and logging
- Tier 3 now returns null when multiple global candidates exist instead of
arbitrary allDefs[0] — a wrong edge is worse than no edge
- call-processor: renamed fuzzy-global → unique-global, removed dead branch
- 12 new tests: tier assertions, ambiguous refusal per language family,
heritage false-positive guard, O(1) shared reference verification
* fix: critical language support bugs in import resolution and MRO
Phase 5 critical fixes from all-language analysis:
- Python: add relative_import query capture (PEP 328) — `.models`, `..utils`
were silently dropped, producing zero ImportMap entries
- Rust: extract prefix from grouped imports `crate::module::{A, B}` — brace
groups previously failed resolution entirely
- Swift: use normalizedFileList for Windows path compatibility in module
import resolution (matches Go's resolveGoPackage pattern)
- MRO: fix c_sharp → csharp language name mismatch (enum is 'csharp'),
add Kotlin to C#/Java resolution rules (class method wins over interface)
* feat: add strict multi-language integration tests + fix C/C++ import resolution
Add 32 integration tests across 6 language fixtures (TypeScript, C#, C++,
Java, Python, Rust) with exact toBe/toEqual assertions validating heritage
edges, import resolution, and trait implementations.
Fix C/C++ import resolution bug where dot-to-slash conversion mangled
include paths (e.g. "animal.h" became "animal/h"). Now skips conversion
for C/C++ languages which use actual file paths in #include directives.
* fix: language-gate heritage heuristic, add Swift extension heritage, handle Rust grouped imports
- Gate I[A-Z] naming heuristic to C#/Java only (was firing for all languages)
- Swift unresolved types default to IMPLEMENTS (protocol conformance is the norm)
- Add tree-sitter query for Swift extension protocol conformance (extension Foo: Protocol)
- Handle Rust top-level grouped imports (use {crate::a, crate::b}) in both import loops
- Add 4 new heritage-processor tests (TypeScript refusal, Swift default, Swift Tier 1)
* feat: add Go struct embedding heritage + PackageMap optimization
Add Go struct embedding detection (anonymous fields → EXTENDS edges) via
new tree-sitter heritage query with named-field filtering in both
parse-worker and heritage-processor paths.
Implement PackageMap optimization for Go cross-package resolution:
replace O(N) file-level ImportMap expansion with directory-level suffix
matching (Tier 2b in symbol resolver). Graph IMPORTS edges are preserved
via addImportGraphEdge split.
Remove overly broad @definition.type from GO_QUERIES that was
double-matching structs/interfaces as TypeAlias nodes, breaking Tier 3
unique-global resolution.
Add Go fixture (go-pkg) with Admin→User embedding, cross-package calls,
and 7 integration tests covering structs, functions, imports, calls,
and heritage edges.
* test: add Kotlin heritage integration tests
Adds a kotlin-heritage fixture and 7 integration tests validating
class inheritance, interface implementation, JVM-style import
resolution, and symbol-table-driven EXTENDS/IMPLEMENTS disambiguation
via Kotlin delegation specifiers.
* feat: extract resolvers, add PHP tests, ambiguous tests for all languages
- Extract language-specific resolvers from import-processor.ts into
resolvers/ directory (P7): jvm, go, csharp, php, rust, standard, utils
- import-processor.ts reduced from 1412 to 711 lines (50% reduction)
- Add comprehensive PHP integration tests: PSR-4 imports, traits, enums,
heritage edges, method calls, MRO overrides
- Add ambiguous symbol resolution tests for all 9 languages verifying
correct disambiguation via import chains
- Split monolithic lang-resolution.test.ts (1080 lines) into 9 per-language
files under test/integration/resolvers/ with shared helpers
* feat: update integration tests to include resolver tests for multiple languages
* fix: address code review — schema gap, Rust impl name, Property OVERRIDES
Bugs fixed:
- Add 13 missing FROM/TO pairs in RELATION_SCHEMA for HAS_METHOD edges
(Class/Interface/Struct/Trait/Impl/Record to Method/Constructor/Property)
- Fix findEnclosingClassId to pick implementing type for Rust
impl Trait for Struct blocks (was picking trait name)
- Exclude Property nodes from MRO OVERRIDES collision detection
- Change MRO language fallback from typescript to unknown
Tests added:
- Unit: Property OVERRIDES exclusion (2 tests), Rust impl Trait for
Struct name resolution (2 tests), schema HAS_METHOD pair coverage
- Integration: no OVERRIDES targets Property nodes across all 9 languages
- PHP fixture: added shared $status property to both traits to create
real collision scenario for Property OVERRIDES exclusion test
Documentation:
- OVERRIDES edge direction (Class to Method), Go return type gap,
BFS first-reach heuristic limitation
* feat: harden CALLS-edge resolution — Phase 0 validation
- Fix same-file confidence (0.85 → 0.95) to correctly outrank import-scoped (0.9)
- Fix Tier 1 overload preservation: use globalIndex filter instead of fileIndex lookup
- Add callable-kind guard: refuse CALLS edges to Interface and Enum symbols
- Fix Kotlin countCallArguments: handle call_suffix → value_arguments nesting
- Fix Kotlin extractFunctionName: add simple_identifier to fallback search
- Strictly type findParameterList and countCallArguments (remove all `any`)
- Add arity-based call resolution integration tests for 9 languages
- Add unit regression tests for Interface/Enum CALLS refusal
* chore: remove C# build artifacts from fixtures
* feat: add call-form discrimination and ownerId to symbol table (Phase 1)
Add inferCallForm() and extractReceiverName() to distinguish free/member/constructor
calls at the AST level across all 9 languages. Add ownerId field to SymbolDefinition
linking Method/Constructor/Property to their owning class. Includes 36 unit tests
and member-call integration tests for all 9 languages (132 tests, 0 failures).
* feat: constructor/struct-literal resolution across all languages (Phase 2)
Add constructor discrimination to CALLS-edge resolution: new Foo(),
User{...} struct literals, and C# primary constructors now resolve to
Constructor/Class/Struct/Record nodes instead of being filtered out.
Queries: new_expression (C++), object_creation_expression (PHP),
composite_literal (Go), struct_expression (Rust), primary constructor
and implicit_object_creation_expression (C#).
Relaxes global tier in collectTieredCandidates to pass all candidates
through filterCallableCandidates, allowing kind/arity narrowing to
disambiguate at lower confidence.
* feat: receiver-constrained resolution with integration tests for all 9 languages
Add receiver-type filtering (Phase 3): when a member call like `user.save()`
has a known receiver type from TypeEnv, filter candidates by ownerId to
disambiguate methods with the same name across different classes.
Key changes:
- call-processor: build per-file TypeEnv, pass receiverTypeName to resolveCallTarget
- parse-worker: extract receiverTypeName from TypeEnv in worker thread
- resolveCallTarget: new step D filters by ownerId matching receiver type
- utils: extractReceiverName supports C++ field_expression (argument field)
- utils: findEnclosingClassId extracts Go method receiver types
- type-env: handle Go qualified_type, Kotlin user_type/variable_declaration
- parse-worker + parsing-processor: Function added to needsOwner for
Kotlin/Rust/Python class methods captured as Function nodes
Integration tests added for receiver-constrained resolution across all 9
languages: TypeScript, Java, Python, Go, Rust, C++, C#, Kotlin, PHP.
* feat: NamedImportMap, scoped TypeEnv, broadened signatures + TS rest-param variadic fix
Address all 4 PR #238 review items:
1. Remove redundant lookupFuzzy in processRoutesFromExtracted
2. Add NamedImportMap for TS/Python symbol-level import tracking (Tier 2a)
3. Make TypeEnv scope-aware (Map<scopeKey, Map<varName, type>>) to fix
non-deterministic receiver resolution across functions
4. Broaden extractMethodSignature: Go/Rust/C++ return types, variadic
detection for Go/Java/Python/C++/Kotlin/TypeScript rest params
Discovered and fixed: TS rest params (...args) were not detected as
variadic — added rest_pattern detection inside required_parameter nodes.
Integration tests added: scoped receiver, named import disambiguation,
and variadic call resolution for both TypeScript and Python.
* fix: alias import resolution, Go multi-assign TypeEnv, dead code removal
- NamedImportMap now stores {sourcePath, exportedName} so aliased imports
(import { User as U }) resolve U → User in the source file
- Named binding check moved before empty-allDefs early return in both
call-processor and symbol-resolver, fixing constructor calls via aliases
- Go extractFromGoShortVarDeclaration iterates all LHS/RHS pairs for
multi-assignment (user, repo := User{}, Repo{}) instead of only first
- Remove unused TYPED_DECLARATION_TYPES set (TYPED_PARAMETER_TYPES kept)
- Integration tests for both fixes (go-multi-assign, typescript-alias-imports)
* feat: alias import extraction for Kotlin, Rust, PHP, C# + integration tests
Add named import alias extraction to both pipeline paths
(import-processor.ts and parse-worker.ts) for Kotlin, Rust, PHP,
and C#. Add integration test fixtures and tests for all 5 languages
(Python alias extraction already worked, just needed the test).
Each test verifies: class detection, member call resolution through
aliases to correct target files, and IMPORTS edge emission.
* refactor: use SupportedLanguages enum everywhere instead of raw strings
Replace all raw language string literals and `language: string` types
with the SupportedLanguages enum across 10 files. This ensures
compile-time safety for language dispatch and eliminates dead
`language === 'tsx'` checks (tsx maps to TypeScript in the enum).
* fix: tier-ordering bug, re-export chains, PHP grouped imports, Java named imports
- Fix collectTieredCandidates tier-ordering: same-file now checked before
named bindings, preventing imports from shadowing local definitions
(matches resolveSymbolInternal priority order)
- Add re-export chain resolution for TypeScript/JavaScript barrel files:
export { X } from './base' and export type { X } from './base' now
followed up to 5 hops through NamedImportMap
- Fix PHP grouped import alias extraction: use App\Models\{User, Repo as R}
now correctly handled in both parse-worker and import-processor
- Add Java NamedImportMap support: import com.example.models.User now
records User as a named binding for precise disambiguation
- Add 16 new integration tests across TypeScript, PHP, and Java resolvers
(220 total resolver tests, all passing)
* refactor: consolidate alias extraction + add variadic/constructor/shadow integration tests
- Extract shared named-binding-extraction.ts from duplicate logic in
import-processor.ts and parse-worker.ts (net -200 lines)
- Deduplicate appendKotlinWildcard (now imported from resolvers/index.ts)
- Add integration tests: constructor calls (Kotlin, Python), variadic
resolution (Go, Java, C#, C++, Kotlin), re-export chains (Python),
local definition shadowing (Python, Go)
- Add TODO(stack-graph) for TypeEnv scope key collision
- 225 integration tests passing (was 223)
* fix: PHP non-aliased imports, Python node identity, re-export chain dedup + local-shadow tests
- PHP flat non-aliased imports (use App\Models\User) now stored in NamedImportMap
- PHP grouped non-aliased imports ({User} in {User, Repo as R}) now stored in NamedImportMap
- Python: replace non-public child.id with child.startIndex for node identity
- Extract shared walkBindingChain() from symbol-resolver and call-processor
- Add PHP variadic resolution fixture + test (variadic_parameter already covers PHP)
- Add local-shadow integration tests for Java, C#, Kotlin, Rust, PHP, C++ (6 languages)
* feat: Rust non-aliased use bindings, Kotlin non-aliased imports, re-export chain resolution
Extend NamedImportMap coverage for Rust and Kotlin non-aliased imports:
- Rust: rename collectUseAsClauses → collectRustBindings, extract terminal
scoped_identifier (use crate::models::User) and identifier in use_list
(use crate::models::{User, Repo}) into NamedImportMap. This also enables
pub use re-export chain following via walkBindingChain.
- Kotlin: extend extractKotlinNamedBindings to handle non-aliased imports
(import com.example.User), skipping wildcard imports.
- Add rust-reexport-chain fixture + 3 integration tests verifying Handler{}
resolves through mod.rs pub use to handler.rs.
- Add Kotlin heritage + constructor-calls reason assertions for non-aliased
import-resolved resolution.
- Add C# heritage test documenting namespace import tier behavior.
* fix: skip Kotlin lowercase member imports in NamedImportMap
Member imports like `import util.OneArg.writeAudit` (lowercase last
segment) must not populate NamedImportMap — same-named function imports
from different classes collide, breaking arity-based disambiguation.
Apply the same guard Java already uses: skip lowercase last segments.
* fix: skip spurious path-prefix bindings in Rust grouped imports
collectRustBindings was extracting the path segment (e.g. "models") from
`use crate::models::{User, Repo}` as a spurious NamedImportMap entry.
Skip scoped_identifier nodes that are direct children of scoped_use_list
since they are path prefixes, not importable symbols.
Adds rust-grouped-imports fixture and 4 integration tests verifying both
symbols resolve correctly and no spurious binding leaks through.
* fix: use startIndex in TypeEnv scope key to prevent same-name method collision
Two methods named identically in different classes within the same file
previously shared a scope key, causing non-deterministic type resolution.
Now keys use funcName@startIndex for uniqueness.
Also adds tests documenting destructuring assignment extraction gap.
* test: document C# namespace-level import limitation in named binding extraction
* test: document same-arity overload discrimination limitation in call processor
* perf: parallelize calls/heritage/routes processing in worker path
Worker path now runs processCallsFromExtracted, processHeritageFromExtracted,
and processRoutesFromExtracted via Promise.all instead of sequentially.
Safe because all three only read shared state and write via addRelationship's
dedup guard. Sequential fallback path stays sequential (shared LRU astCache).
Also fixes Rust collectRustBindings spurious path-prefix bindings for 3+ level
grouped imports, and adds @param JSDoc for walkBindingChain's allDefs invariant.
* docs: improve Promise.all safety comment and walkBindingChain JSDoc
Clarify that the parallelization safety comes from disjoint relationship
types + idempotent id-keyed Maps, not from lack of shared state (the
graph is shared). Strengthen allDefs JSDoc to describe silent-miss
consequence of passing pre-filtered results.
* refactor: extract language-specific processing into modular dispatch tables
Phase 1: Extract type binding logic from type-env.ts (635→125 LOC) into
type-extractors/ directory with per-language files and Record<SupportedLanguages,
LanguageTypeConfig> + satisfies dispatch.
Phase 2: Extract 5 config loaders from import-processor.ts into
language-config.ts (removed ~196 LOC of inline loaders).
Phase 3: Convert export-detection.ts switch/case to exhaustive
Record<SupportedLanguages, ExportChecker> + satisfies dispatch table,
fix node: any → SyntaxNode.
Also adds language feature matrix to README.
All 1146 unit tests and 433 integration tests pass.
* refactor: extract type binding logic into type-extractors/ directory (Phase 1)
Extract per-language type extraction from type-env.ts (635→125 LOC) into
type-extractors/ with Record<SupportedLanguages, LanguageTypeConfig> + satisfies
dispatch. 9 per-language files, shared helpers, and barrel index.
* refactor: extract config loaders to language-config.ts (Phase 2)
Move 5 language-specific config loaders and their type interfaces from
import-processor.ts into standalone language-config.ts module.
|
||
|
|
1be910f54a
|
fix: skip unavailable native Swift parsers in sequential ingestion (#188)
* fix: skip unavailable native Swift parsers in sequential ingestion * fix: warn when ingestion skips languages in verbose mode * test: cover verbose skip warnings * docs: update analyze flags * docs: clarify verbose default --------- Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
1ed34a0007 | docs: update supported languages list to include PHP and Swift | ||
|
|
984316d260 | feat: add Codex MCP and skills support | ||
|
|
91289404c2 |
feat(gitnexus): v1.2.9 — impact enrichment, cypher markdown, Windows setup fix
- Impact tool now returns risk score, affected processes/modules, and summary - Cypher tool formats results as markdown tables for LLM readability - Context tool includes module (functional area) field - Semantic search skips model init when embeddings are disabled - Setup: wrap npx in cmd /c on Windows for .cmd script compatibility - Embedder: silence stderr during ONNX model load to protect MCP stdio - API: use executeCypher directly to avoid double formatting - Add community integrations section to READMEs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
8dd1c19bec |
feat(web): wire agent/chat through HTTP in backend mode
Add HTTP wrapper functions (executeQuery, textSearch) to the ingestion worker and a new initializeBackendAgent method that creates the agent with HTTP-backed tool closures instead of local KuzuDB. useAppState detects backend mode and routes agent initialization accordingly. |
||
|
|
76d1538c5e | docs: update READMEs for local backend mode | ||
|
|
8e2ee26834 | docs: remove architecture docs and deep implementation details from markdown files | ||
|
|
6ce715b62c | repowiki CLI command implemented | ||
|
|
dda8de41a3 | MCP and cli fixes | ||
|
|
2a205af78b | moved Readme to root | ||
|
|
e153d30c8f | MCP bridge added |
Renamed from README.md (Browse further)