mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-24 00:51:53 +00:00
640 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2e83d2ab09
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
a3eef48ce3
|
fix(cli): make --no-stats actually omit volatile counts (#1477) (#1478)
* fix(cli): make --no-stats actually omit volatile counts (#1477) Closes #1477. The `--no-stats` flag on `gitnexus analyze` was advertised as "Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md" but had no effect: every reindex still rewrote the markdown with fresh count phrases, producing chore-commit churn on every run — the exact problem the flag was added to solve in #704. Root cause is commander.js negation-flag semantics. `.option( '--no-stats', ...)` registers the option under the accessor `stats` (boolean, default `true`; `false` when the flag is passed), NOT `noStats`. The two action-handler reads in `analyze.ts` (lines 414 and 500 pre-fix) read `options?.noStats`, which is always `undefined`, so the `noStats` payload always reached `runFullAnalysis` / `generateAIContextFiles` as `undefined`/falsy and the count branch in the template always fired. Fixed by replacing `options?.noStats` with `options?.stats === false` at both reads. The strict `=== false` check (rather than `!options?.stats`) means absent options or absent `.stats` field fall through as no-stats=false, preserving the documented default-on behaviour. Also updated the `AnalyzeOptions` interface to declare `stats?: boolean` (matching commander's actual output) with a JSDoc explaining the negation, since the prior `noStats?: boolean` shape was a static-type misrepresentation of what commander provides at runtime. Internal call sites that re-pack `{ noStats: ... }` for downstream consumers (`run-analyze.ts`, `ai-context.ts`) keep their existing field name — those interfaces are not commander- shaped, so `noStats` is the correct name there. ## Regression tests Two new unit tests in `test/unit/ai-context.test.ts`: * `omits volatile counts when noStats option is set (#1477)` — asserts the count parenthetical is absent from both CLAUDE.md and AGENTS.md when `noStats: true` is passed. * `preserves volatile counts when noStats is not set (default)` — documents the default-on path so a future refactor can't silently flip the default. Both call `generateAIContextFiles` directly with distinctive numbers that would unmistakably leak through if the omit branch is broken. ## Manual verification * `vitest run test/unit/ai-context.test.ts` → 13/13 pass (11 prior + 2 new). * Verified before-fix behaviour by checking out main, running `npx gitnexus analyze --no-stats` against an indexed repo, and observing the count phrase still present. Re-running on the fix branch with the same flag strips the phrase as documented. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(cli): resolve merge conflict markers in analyze.ts (PR #1478) Remove leftover conflict hunks from main merge; keep commander stats shape (stats?: boolean), wire noStats: options?.stats === false into runFullAnalysis and generateAIContextFiles, and retain indexOnly / skipSkills / skipAgentsMd wiring from main. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): cover analyzeCommand → runFullAnalysis noStats bridge (#1477) Assert commander-shaped options.stats maps to the internal noStats payload (including explicit true/false and skipAgentsMd combination) so the CLI bridge cannot regress without failing tests. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): cover AGENTS.md default stats + skills noStats bridge (#1478) - Assert volatile stats phrase in both CLAUDE.md and AGENTS.md when noStats is omitted - Add bridge test for --skills regeneration path with stats:false → generateAIContextFiles noStats - Note shared noStats expression beside skills-path call; stub process.exit for full analyze path Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
6229417bd5
|
feat: gitnexus:keep marker preserves custom context sections (resubmit of #605) (#1508)
* feat: gitnexus:keep marker preserves custom context sections When <!-- gitnexus:keep --> is present inside the gitnexus block, analyze only updates the stats line instead of replacing the entire section with the verbose template. Lets users maintain lean custom context without it being overwritten on every reindex. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: improve gitnexus:keep marker to reliably preserve custom sections The `<!-- gitnexus:keep -->` marker inside a GitNexus block tells `analyze` to only update the stats line (node/edge/flow counts) while preserving the user's custom layout. This lets teams trim the verbose default template to a lean format without having it overwritten on every reindex. Changes: - Broaden stats-line regex to match both "Indexed as" and "indexed by GitNexus as" formats - Improve stats extraction from generated content (prefer structured match over greedy parentheses) - If keep marker is present but no stats line found, preserve the section as-is instead of falling through to full replace - Add tests for keep preservation and no-keep replacement Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR #1508 review findings (F1-F5) Refactor the keep-marker stats-update path and close the test-coverage gaps surfaced by the production-readiness review. ## Findings 2 + 3 (high) — fragile extraction → silent corruption Stop re-extracting `newName` (first `**bold**`) and `newStats` (first `(...)`, with fallback) from generated content. Both are structurally fragile: - F2: newName silently picks the wrong value if the template ever emits bold text before the project-name line (no current bug; an unstated contract with no enforcement) - F3: newStats fallback `\(([^)]+)\)` matches `({target: "symbolName", direction: "upstream"})` from the Always-Do bullet when `noStats: true` suppresses the canonical stats line, silently corrupting the stats output Fix: pass `projectName: string` and `stats: RepoStats` directly into `upsertGitNexusSection`. Build the stats line from those values. Both callers in `generateAIContextFiles` already have them in scope. ## Finding 1 (high) — misleading return value When a keep marker is present but no stats line matches the pattern, the function previously returned `'updated'` without writing, producing `CLAUDE.md (updated)` in CLI output for a file that was not touched. Add a distinct `'preserved'` return variant; CLI now reports `CLAUDE.md (preserved)` honestly. ## Finding 4 (medium) — unanchored stats regex `/(?:Indexed as|...) \*\*[^*]+\*\* \([^)]+\)/` could match prose embedded mid-paragraph in user content (e.g. "you'll see it Indexed as **Foo** (note: ...)"). Anchor with `^...$` plus the `m` flag so only standalone stats lines match. ## Finding 5 — test coverage gaps Seven new tests, each cross-referenced to the review finding: - keep marker OUTSIDE the GitNexus section has no effect - AGENTS.md keep path preserves custom layout (parity with CLAUDE.md) - idempotent: second run produces byte-identical output - CRLF file with keep marker: stats line updates correctly - noStats + keep marker: not corrupted by Always-Do tuple text (F3 regression guard) - returns 'preserved' (not 'updated') when no stats line matches (F1 regression guard) - project name with markdown punctuation (hyphens/slash/dot) lands intact All 23 ai-context tests pass; typecheck, prettier, eslint clean. * docs(ai-context): address PR #1508 review findings on keep-marker path - Clarify that noStats affects generated template only, not keep-section stats updates - Fix stats-line regex comment to match behavior (no end anchor; trailing suffix kept) - Assert '. MCP tools.' survives stats replacement in preserve-custom-section test - Document LF normalization when rewriting CRLF seed in keep-marker CRLF test Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: dp-web4 <dp@web4.ai> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
e923c597f2
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
88d3df77cc
|
feat:(wiki) added --timeout and --retries flags for large module pages to mitigate timeout aborts (#1543)
* feat:(wiki) added --timeout and --retries flags for large module pages to mitigate timeout aborts * docs(wiki): document --timeout and --retries options * docs(wiki): document --timeout and --retries in SKILL.md --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
87ce4eaaa3
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
4cc4e9c84b
|
fix(build): use platform-aware tsc command for win32 (#1531) | ||
|
|
6832f1329a
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
48cd55a120
|
fix(search): guard against undefined bm25Results when FTS unavailable (#1489) (#1540)
* fix(search): guard against undefined bm25Results when FTS unavailable (#1489) When the FTS extension is unavailable in the MCP process, searchFTSFromLbug can return an unexpected shape or throw, leaving bm25Results undefined. The for-loop then crashes with "bm25Results is not iterable". - mergeWithRRF: default both inputs via ?? [] so undefined never reaches the iteration loops - hybridSearch: wrap searchFTSFromLbug in try/catch and fall back to semantic-only search instead of crashing - local-backend query handler: guard bm25SearchResult?.results and semanticResults with ?? [] - bm25Search: wrap the dynamic import in try/catch for sandboxed MCP contexts; guard ftsResponse?.results Adds 6 regression tests covering undefined inputs and FTS failure fallback. Fixes #1489 * fix(search): address review findings on #1489 crash guards - Guard ftsResponse.results with ?? [] in hybridSearch (Finding 1) - Add logger.warn on bm25-index.js import failure (Finding 3) - Add unit test for callTool query FTS throw path (Finding 2) --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
fd48347348
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
e8c8ddec8a
|
fix(wiki): sanitize generated mermaid diagrams (#1539)
* fix(wiki): sanitize generated mermaid diagrams * fix(wiki): address mermaid sanitizer review --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
2d4ee30631
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
ec4624af87
|
fix(hooks): cap concurrent augment subprocesses (#1486) (#1510)
* fix(hooks): cap concurrent augment subprocesses to prevent runaway process spawn (#1486) When Claude Code fires PreToolUse hooks for parallel Grep/Glob/Bash tool calls, each invocation spawned its own `gitnexus augment` subprocess — a Node + LadybugDB cold start that holds resources for several seconds. Under heavy parallel search load (issue #1486: 180+ piled-up processes, load avg > 100), these accumulated faster than they completed because nothing capped concurrent in-flight augments. Add a lockfile-based concurrency guard under `<.gitnexus>/.hook-locks/`: each running hook claims a `<pid>.lock`, the guard counts live PIDs and prunes stale entries (>30s mtime or pid no longer alive), and bails silently when MAX_INFLIGHT (3) is reached. Augment is best-effort enrichment — missing a few fires under burst load is preferable to melting the system. Applied to all three hook variants that spawn augment: - gitnexus/hooks/claude/gitnexus-hook.cjs (npm-installed Claude hook) - gitnexus-claude-plugin/hooks/gitnexus-hook.js (plugin Claude hook) - gitnexus-cursor-integration/hooks/gitnexus-hook.cjs (Cursor hook) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(hooks): make augment concurrency cap a hard cap via atomic slot files Address Claude's review of #1510. The original count-then-claim guard had a TOCTOU window: N hooks could each read `active < MAX_INFLIGHT` between readdirSync and the per-pid `wx` write and all proceed, briefly exceeding the cap. The PR title's "cap" language overstated this. Replace with fixed-name `slot-0.lock` ... `slot-N.lock` under `.hook-locks/`. `O_CREAT|O_EXCL` on a fixed path is OS-atomic — exactly one process wins each slot, so the cap is hard regardless of burst arrival timing. Each slot file contains the owning PID so stale-takeover still works when a hook crashes without releasing. PID liveness is checked before age (Claude's Finding 3): a slow-but-alive hook is never wrongly evicted. The 30s age window only kicks in to defend against PID reuse on a long-abandoned slot, well above the 7s augment timeout so a healthy run never hits it. Also adds the missing concurrency-guard tests to cursor-hook.test.ts (Claude's Finding 2): source-level wiring + dead-PID reclaim + 3-slots-full bail. Previously only the CJS and Plugin variants had test coverage for the guard; the Cursor variant was validated only by code inspection. Tests: 5726 passing, +9 from baseline (1 hard-cap burst test + 4 source regressions in hooks.test.ts; 3 source + 2 integration in cursor-hook.test.ts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(hooks): inspect slot mtime + content via single fd (codeql TOCTOU) CodeQL flagged the stale-takeover path in acquireHookSlot as a potential filesystem race (js/file-system-race): statSync(slotPath) followed by readFileSync(slotPath) gives a TOCTOU window where the file could be swapped between the metadata check and the content read. Replace the two separate path-based calls with a single openSync + fstatSync + readSync + closeSync sequence. Both mtime and owner PID now come from the same file descriptor, so the operations are atomic on one inode. No behavioral change beyond closing the race. Applied to all three hook variants (CJS, Plugin, Cursor). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(hooks): distinguish EPERM from ESRCH in PID liveness check Cursor Bugbot caught a contradiction with the stated design: the bare `catch` after `process.kill(owner, 0)` was treating EPERM (process exists but owned by another user) the same as ESRCH (process gone), which would evict a live slot whenever the lock dir straddled user boundaries. Inspect the error code: ESRCH → dead, evict; EPERM → still alive, keep the slot; anything else → assume alive (be conservative under unexpected failure rather than over-evict). Applied to all three hook variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(hooks): fail closed when lock dir cannot be created Previously the mkdirSync catch in acquireHookSlot returned `() => {}` (a truthy no-op). The caller checks `if (!release) return;` to skip augment when the guard can't be established — but a truthy no-op slipped through that check and let augment spawn unguarded. On a cross-user shared `.gitnexus/` or read-only filesystem, N concurrent hooks would each take that branch and reintroduce the #1486 fan-out the guard exists to prevent. Return `null` instead so the caller's `if (!release) return;` skips augment cleanly. Augment is best-effort enrichment — skipping it when the guard fails is strictly safer than running unguarded. Also clarify the stale-slot comment: PID-liveness wins for slots younger than HOOK_LOCK_STALE_MS, but age is the final arbiter beyond 30s (PID-reuse defense). The previous wording said "PID-liveness wins over age" without qualifying it, which contradicted the >30s branch. Add source-level regression tests in hooks.test.ts and cursor-hook.test.ts asserting acquireHookSlot returns null (not () => {}) on lock-dir failure. Note in the Cursor test file that the 10-spawner burst test is not duplicated because the algorithm is byte-for-byte identical to the CJS hook and already covered there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(hooks): extract lock guard into helper modules Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/04dd20c5-28fd-433a-83cf-ad83fd03fb32 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
3035359981
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
6a23616873
|
chore(deps)(deps): bump protobufjs from 7.5.5 to 7.5.8 in /gitnexus (#1536) | ||
|
|
7637bd1c83
|
chore(deps)(deps): bump @protobufjs/utf8 in /gitnexus (#1535)
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
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
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
|
||
|
|
82b8488473
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
8083c39f6d
|
feat(php): migrate PHP to scope-based resolution model (#938) [supersedes #1124] (#1497) | ||
|
|
a2f1b07700
|
fix: resolve TypeScript ESM .js extension imports to .ts source files (#1525)
* fix: resolve TypeScript ESM .js extension imports to .ts source files TypeScript ESM requires imports to use .js extensions even when source files are .ts (moduleResolution: node16/bundler). The import resolver now strips JS-family extensions (.js/.jsx/.mjs/.cjs) and retries with TS equivalents (.ts/.tsx/.mts/.cts) when the literal .js file does not exist. This fallback only applies to TypeScript/JavaScript languages. Also adds .mts/.cts to the EXTENSIONS list for completeness. Fixes #1503 * fix: address review findings — normalization, edge-case tests, integration test - Fix makeCtx to use production normalization (.replace backslash) instead of .toLowerCase() (Finding 3) - Add tests for .mjs/.cjs with competing .ts/.mts siblings (Finding 1) - Add tests for ./dir.js → dir/index.ts boundary (Finding 2) - Add integration test verifying full pipeline CALLS edges for ESM .js imports (Finding 4) - Document path alias .js limitation as known follow-up (Finding 5) * chore(autofix): apply prettier + eslint fixes via /autofix command * chore: retrigger CI after bot-only tip commit Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
0daae93701
|
fix(lbug): drain checkpoint result before close (#1506)
* fix(lbug): drain checkpoint result before close * test(lbug): cover checkpoint drain lifecycle * fix(lbug): close query results after reads * fix(lbug): close all stream query results * fix(lbug): harden query result cleanup --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
b88200a64a
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
4fa40e9881
|
feat(analyze): incremental indexing (parse cache + DB writeback + scope-res short-circuit) (#1479)
* docs: incremental indexing design spec Captures the design agreed in brainstorming on 2026-05-10: - Transitive importer closure with public-surface-change optimization - Git-only change detection (non-git repos: full rebuild as today) - New default behavior; --force opts out - New hydratePhase + loadGraphFromLbug primitive - Iterative closure expansion with parseCache reuse - incrementalInProgress dirty flag for crash recovery Prior art: PR #592 (zenprocess), PR #533 (davidbeesley), PR #1146 (azeemshaik025) — referenced and credited. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(communities): seed Leiden RNG for deterministic community detection The vendored Leiden algorithm defaults to Math.random for tie-breaking and randomized walks, which produces non-deterministic community assignments and modularity values across runs on the same graph. Pass a seeded mulberry32 RNG (LEIDEN_SEED=0xC0DE) so: - The same graph always produces the same partition - Modularity values are reproducible - Equivalence tests for incremental indexing can compare community assignments byte-for-byte This is foundational for the upcoming incremental-indexing feature (see docs/superpowers/specs/2026-05-10-incremental-indexing-design.md) where the correctness contract is incremental output ≡ full rebuild output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(incremental): change-detection, surface signatures, closure expansion Three new modules supporting the incremental-indexing pipeline: * core/incremental/git-diff.ts — getChangedFilesSinceCommit() unions 'git diff lastCommit HEAD' (committed) with 'git status --porcelain' (dirty tree). Renames flattened to delete(orig) + add(new). Throws LastCommitMissingError when lastCommit is gone (caller falls back to full rebuild). * core/incremental/surface.ts — extractSurfaceSignature() produces a stable hash of a file's publicly-visible symbols (functions, classes, methods, interfaces, types, heritage). Body-only edits → same hash. Signature/heritage changes → different hash. Drives the closure scoping optimization. * core/incremental/closure.ts — computeImporterClosure() iterative fixpoint: parse each closure file, extract surface, query DB importers, expand. Uses a parseCache so each file is parsed once. Generic over TParseResult so closure logic is decoupled from the pipeline's parse representation. 32 unit tests across the three modules. Tests cover edge cases: clean tree, dirty-only, mixed, renames, deletes, multi-hop cascade, cycle termination, surface invariance, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(lbug): loadGraphFromLbug, queryImporters, deleteAllCommunitiesAndProcesses Three new primitives in lbug-adapter.ts to support incremental indexing: * loadGraphFromLbug(graph, unchangedFilePaths) — streams all nodes for files in the set across every hydratable node table (excludes Community/Process — graph-wide, regenerated downstream). Then loads edges where both endpoints belong to loaded nodes, excluding MEMBER_OF / STEP_IN_PROCESS edges (also graph-wide). FilePaths chunked at 200 per query to keep statement size bounded on huge repos. Endpoint-level join filters by source-side filePath in the query, target-side checked JS-side via the loadedNodeIds set. * queryImporters(targetFilePath) — returns DISTINCT a.filePath where a -[IMPORTS]-> b and b.filePath = target. Powers closure expansion: when a changed file's surface signature changes, all its importers must be re-parsed. * deleteAllCommunitiesAndProcesses() — drops Community/Process nodes (and their edges via DETACH DELETE) at the start of each incremental run so the communities/processes phases regenerate them from the fully-merged graph. Required for the 'Leiden runs on full graph' correctness invariant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(pipeline): hydrate phase + parse-filter for incremental indexing Wires the incremental-indexing infrastructure into the phase-based pipeline. Three coordinated changes: * New hydratePhase (deps: structure) — loads node/edge state for files OUTSIDE ctx.options.filesToParse from the existing LadybugDB index. Runs before parse so the parse phase can produce a partial graph while downstream phases (mro, communities, processes) still see the full graph. No-op in full-rebuild mode (filesToParse unset). * PipelineOptions.filesToParse: optional ReadonlySet<string>. When set, parse phase filters scanned files to this set; hydrate fills the complement. Set by runFullAnalysis when it detects an eligible incremental run; never set by callers directly. * gitnexus-shared PipelinePhase enum: 'hydrate' added so progress callbacks can report the new phase distinctly from 'structure'. Phase order: scan → structure → hydrate → markdown,cobol → parse → routes,tools,orm → crossFile → scopeResolution → mro → communities → processes. Communities (Leiden) still runs on the full graph, satisfying the correctness invariant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(analyze): incremental orchestrator branch + meta schema Wires incremental indexing into runFullAnalysis. Highlights: * RepoMeta schema extended: schemaVersion, surfaceSignatures, and incrementalInProgress fields. INCREMENTAL_SCHEMA_VERSION = 1. * core/incremental/file-hash.ts — v1 surface signature: SHA-256 of file content. v2 will switch to a true surface-only signature (defined in surface.ts) so body-only edits don't expand the closure. The plumbing is signature-agnostic so the swap is local. * core/incremental/orchestrator.ts — eligibility check, closure computation (uses file-hash as the surface signal), dirty-flag management, subgraph extraction, signature merge. * run-analyze.ts adds: - hasDirtyTree() check on the existing 'lastCommit==HEAD' early-exit so an uncommitted edit triggers re-index (was a coarse equality check before). - incremental branch: try incremental first; fall through to full rebuild on any setup failure or eligibility miss. - runIncrementalBranch() — opens existing DB, deletes closure-file rows + Community/Process, runs pipeline with filesToParse, writes only the changed-subgraph back, refreshes FTS, updates meta with new surfaceSignatures and clears the dirty flag. - Full-rebuild path now populates surfaceSignatures + schemaVersion in meta.json so the next run is eligible for incremental. Crash recovery: incrementalInProgress is set BEFORE any DB mutation and cleared on success by overwriting meta.json. A crash anywhere in between leaves the flag set, and the next analyze run forces a full rebuild (cheapest path back to a known-good index). v1 limitation documented: body-only edits trigger 1-hop closure expansion (content-hash signal). True surface-only optimization is deferred to v2 — see design doc for the integration path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(incremental): drop invalid --no-renames=false from git diff The flag --no-renames=false isn't valid git syntax (it's parsed as a file path). Git's default rename detection is on; removing the flag keeps that behavior. Caught while running an end-to-end smoke test against a small fixture repo: incremental setup failed with 'Command failed: git diff --name-status -z --no-renames=false ...'. After the fix, the incremental path runs cleanly: closure is computed, hydrate phase loads unchanged-file state from DB, parse phase only re-parses files in closure, and the writeback updates only changed nodes/edges. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert v1 incremental indexing (5 commits) Reverts the v1 design that parsed only closure files into a fresh graph and tried to hydrate the rest from DB. Real-repo equivalence test failed: cross-file resolution operates on partial parse data (closure files only), so CALLS edges that resolve through unchanged files silently fall off. Diff against full rebuild on the same edited state: -50 nodes, -425 edges, -5 communities, -48 processes. Architecture pivot: switch to PR #533-style content-addressed parse cache. Pipeline parses every file (cache-served when possible), giving cross-file resolution full data, with DB writeback then restricted to changed-file rows. Reverts: |
||
|
|
d8ca776422
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
d4f34905bc
|
feat: migrate Java to scope-based registry resolution (RFC #909 Ring 3) (#1482)
* Initial plan * feat: implement Java scope-based resolution (RFC #909 Ring 3) Add scope-resolution pipeline for Java, following the C# pattern: - query.ts: tree-sitter query for scopes, declarations, imports, type bindings, and references against tree-sitter-java grammar - captures.ts: orchestrator synthesizing import decomposition, receiver bindings (this/super), arity metadata, and reference arity - import-decomposer.ts: decompose import_declaration nodes into kind/source/name markers (named, wildcard, static, static-wildcard) - interpret.ts: convert captures to ParsedImport/ParsedTypeBinding - receiver-binding.ts: synthesize this/super type-bindings on instance methods with superclass support - arity-metadata.ts: extract parameter count/types using javaMethodConfig - arity.ts: Java arity compatibility check with varargs support - merge-bindings.ts: Java shadowing precedence (local > import > wildcard) - simple-hooks.ts: bindingScopeFor, importOwningScope, receiverBinding - import-target.ts: package path to file path resolution - scope-resolver.ts: ScopeResolver implementation registered in registry Wire scope hooks into javaProvider (java.ts) and register javaScopeResolver in SCOPE_RESOLVERS registry. Add createResolverParityIt wrapper to java.test.ts for parity testing. All 172 existing Java tests pass. Java is NOT added to MIGRATED_LANGUAGES — the resolver sits idle until the migration flag is flipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix: address review findings 1-4 — varargs arity, static import resolution, importOwningScope, stripGeneric Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/22308da3-59c9-47e6-8e52-738305b1b80a Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docs: document registry-primary parity status and CI visibility gap in scope-resolver Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/22308da3-59c9-47e6-8e52-738305b1b80a Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: add generic type erasure fallback in stripGeneric + update scope-resolver docs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/223f77ac-59a7-4487-9316-f2be05eac5d3 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: improve stripGeneric fallback regex — use valid Java identifier chars and handle nested generics Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/223f77ac-59a7-4487-9316-f2be05eac5d3 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address adversarial review findings 1-6 — flaky test, wildcard import fixture, varargs fixed-prefix test, qualified generic stripping, JSDoc updates Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/172c8a1a-cdf3-4de8-9142-f2c12c14b0a6 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * docs: add inline comment explaining stripQualifier/stripGeneric call order Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/172c8a1a-cdf3-4de8-9142-f2c12c14b0a6 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: add varargs 0-arg fixture and strengthen wildcard import assertions Finding 1: Added `badCall()` method with 0-arg `fmt.format()` call to the varargs fixture. Test documents that legacy mode still resolves this call (arity rejection is registry-primary only). The fixture now exercises both the success path (2-arg, 3-arg) and the undersupplied path (0-arg). Finding 2: Strengthened wildcard import test to assert `targetFilePath` on the CALLS edge (`com/example/models/User.java`), confirming the call resolved through the wildcard-imported type to the correct file. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/2b4e5602-9833-485c-ab48-e1d54fdf8465 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore(autofix): apply prettier + eslint fixes via /autofix command --------- 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: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
0594af5b8d |
Merge branch 'main' of https://github.com/prajapatisparsh/GitNexus into feat/Desktop-app
# Conflicts: # gitnexus/test/unit/u8-redos-resource-exhaustion.test.ts |
||
|
|
0e2c0c77ec | chore(tests): remove flaky regression test for resource exhaustion | ||
|
|
76d50ca8e0 | Merge branch 'main' of https://github.com/prajapatisparsh/GitNexus into feat/Desktop-app | ||
|
|
fcab1e2e82
|
fix(augment): add CONTAINS fallback when FTS indexes unavailable (#1476)
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
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
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
* fix(augment): add CONTAINS fallback when FTS indexes unavailable
When the MCP server holds the KuzuDB write lock, the augment CLI opens
the DB read-only. FTS indexes cannot be created in read-only mode, so
searchFTSFromLbug returns ftsAvailable=false and an empty results array.
The existing early-return path silently produced no enrichment.
Add a Cypher name CONTAINS fallback that fires only when ftsAvailable is
false and BM25 produced no symbol matches. This covers the read-only DB
case (concurrent MCP server) and the first-run case (indexes not yet
built). The fallback is wrapped in .catch(() => []) and cannot throw.
When FTS indexes exist, this branch is never reached — behaviour is
unchanged for users without a concurrent MCP server.
* fix(augment): guard against CONTAINS '' and add no-FTS test coverage
Blocker 1 — CONTAINS '' on whitespace-leading patterns:
pattern.split(/\s+/)[0] returns "" when the input has leading whitespace
(e.g. " ".split(/\s+/) → ["", ""]). In Kuzu, CONTAINS '' matches every
node with a name property, injecting arbitrary graph nodes into LLM context.
Fix: trim() before split, then guard on !firstWord || firstWord.length < 2.
No behaviour change for normal non-empty patterns.
Blocker 2 — zero test coverage on the FTS-unavailable code path:
The new CONTAINS fallback block (engine.ts lines 146-166) was exercised by
no existing test — all existing tests run with FTS indexes built. A second
withTestLbugDB fixture is added with no ftsIndexes, forcing searchFTSFromLbug
to return ftsAvailable: false, and asserts:
1. augment('login', ...) returns non-empty enrichment (fallback works)
2. augment(' ', ...) returns '' (CONTAINS '' guard holds)
3. augment('nxyz_notfound', ...) returns '' (no matching nodes)
4. executeQuery throwing returns '' (.catch(() => []) path)
* fix(augment): extend CONTAINS '' guard to FTS happy path and consolidate
The same split(/\s+/)[0] bug existed at line 125 (BM25 symbol filter,
FTS-available path) — a leading-whitespace pattern produced CONTAINS ''
there too, matching every node in BM25-matched files.
Fix: hoist patternFirstWord computation with trim() and the length guard
to the top of augment(), before any DB interaction. Both CONTAINS sites
(BM25 symbol filter and CONTAINS fallback) now use the single pre-validated
value. No behaviour change for normal patterns; the guard fires once for
all callers instead of being duplicated.
Also tighten the whitespace test in the no-FTS suite from 3 spaces to
4 spaces so it unambiguously exercises the patternFirstWord guard rather
than straddling the outer pattern.length < 3 boundary.
* test(augment): negative-safety test for ftsAvailable=true gate
Asserts the CONTAINS fallback does NOT fire when FTS is available but
BM25 returns zero results. Pins the safety property promised by the PR
description: behavior is unchanged for users without the read-only-DB
condition.
If anyone later loosens the gate to `symbolMatches.length === 0` alone,
this test fails.
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
|
||
|
|
fdf1effb2a
|
feat(cli): add --skip-skills and --index-only flags to analyze (resubmit of #742) (#1485)
* feat(cli): add --skip-skills and --index-only flags to analyze command The `installSkills()` call in `generateAIContextFiles()` runs unconditionally, injecting 6 skill files into `.claude/skills/gitnexus/` even when `--skip-agents-md` is passed. This is problematic for bulk indexing operations on read-only mirrors or third-party repos. Add two new flags: - `--skip-skills`: suppress standard GitNexus skill file injection - `--index-only`: pure index mode that suppresses all file injection (AGENTS.md, CLAUDE.md, and skills), writing only to `.gitnexus/` This gives users three levels of control: - `--skip-agents-md` — suppress only root context files - `--skip-skills` — suppress only skill injection - `--index-only` — suppress everything (pure indexing) Discovery context: while bulk-indexing 176 repos with `--skip-agents-md`, all 144 indexed repos were contaminated with `.claude/skills/gitnexus/` files requiring manual cleanup. * fix(cli): address PR #742 review — gate community skills, drop dangling refs, add tests Bot review (#742) flagged three issues with the original commit: 1. `--index-only --skills` still wrote community-derived skill files to `.claude/skills/generated/`. The `--skills` branch in analyze.ts was not gated by `skipAll`, so the "skip all file injection" contract was violated. Gate `generateSkillFiles()` with `!skipAll` so `--index-only` truly wins over `--skills`. 2. `--skip-skills` without `--skip-agents-md` produced AGENTS.md / CLAUDE.md that still referenced `.claude/skills/gitnexus/*/SKILL.md` files that were never installed — every agent load incurred 6 failed reads. Pass `skipSkills` through to `generateGitNexusContent()` and omit the standard-skill rows (and the entire `## CLI` heading when the table is empty). Community skills, when present via `--skills`, are unaffected. 3. No filesystem tests for `skipSkills` / `indexOnly`. Add three regression guards to `test/unit/ai-context.test.ts`: - `.claude/skills/gitnexus/` is NOT created when skipSkills=true - Nothing is written when both skipAgentsMd and skipSkills are true (the resolved-flag state from --index-only) - AGENTS.md/CLAUDE.md routing table omits standard skill references when skipSkills=true, but preserves the load-bearing imperative sections (Always Do / Never Do / Resources) * test(cli): PR 1485 review follow-ups (help text, gate test, --skip-skills docs) - Assert --skip-skills and --index-only in analyze --help (skip-git-cli.test.ts). - Export shouldGenerateCommunitySkillFiles; unit-test index-only+skills gate. - Clarify --skip-skills does not suppress --skills community files; --index-only for full skip. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): warn when --index-only silently overrides --skills Address review findings on PR 1485 follow-ups: - analyze.ts emits a one-line note when both --index-only and --skills are set, so users see why a pipeline re-index ran with no skill files written. - index.ts --skills help text now flags the --index-only override. - shouldGenerateCommunitySkillFiles JSDoc documents the dual role of the gate (community skills + AGENTS.md/CLAUDE.md re-generation). - skip-git-cli.test.ts pins the override-warning surface end-to-end. --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
622f98ade5
|
feat(embeddings): forward dimensions param in HTTP embedding requests (#1498)
* feat(embeddings): forward GITNEXUS_EMBEDDING_DIMS as dimensions in HTTP request body
When GITNEXUS_EMBEDDING_DIMS is set, include it as the `dimensions` field
in the /v1/embeddings request body. This enables Matryoshka-capable models
(OpenAI text-embedding-3-*, Cohere embed-v3, Voyage) to return truncated
vectors at the requested size.
When the env var is unset, the request body remains `{ input, model }` —
no breaking change for backends that reject unknown fields.
Adds 4 unit tests covering both paths (with/without dimensions) on both
the batch embed and single-query embed code paths.
* fix(embeddings): address review findings — strict parseInt, multi-batch test, comment wording
1. Strict parseInt validation: reject non-numeric strings like '1024abc'
by checking /^\d+$/ before parseInt (Finding 1).
2. Add multi-batch test asserting dimensions is forwarded in every fetch
call when inputs exceed batch size (Finding 2).
3. Soften JSDoc comment: backends may ignore or reject the dimensions
field rather than universally ignoring it (Finding 3).
4. Add test for invalid GITNEXUS_EMBEDDING_DIMS values.
---------
Co-authored-by: henry <zhangwei2017@unipus.cn>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
|
||
|
|
55b7a79beb
|
fix(group): detect httpx async consumers (#1408)
* fix(group): detect httpx async consumers * test(group): tighten httpx consumer coverage * test(group): create extractor temp dirs safely * fix(group): scope httpx async client tracking * fix(group): tighten httpx module-scope tracking Prevent module-scope httpx.AsyncClient tracking from matching same-name local variables inside functions. Also documents the intentionally unsupported direct-import, alias, and typed-assignment forms, and extends the httpx extractor regression fixture to cover module-scope shadowing while keeping module-scope calls detected. --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
a966627d1b | feat: update electron-builder configuration to include node binary in extraResources for Windows | ||
|
|
7c712d25e6
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
6a8947217c
|
fix(server): sanitize repo name to prevent argument injection (#1305)
* fix(server): sanitize repo name to prevent argument injection
Sanitizes the extracted repository name to prevent argument injection during git clone operations and ensures compatibility with various file systems.
1. Strips leading dashes to prevent git command-line argument injection.
2. Replaces unsafe directory characters with underscores.
3. Blocks path traversal segments ('.' and '..') and Windows reserved names.
4. Fixes ReDoS vulnerability in parseRepoNameFromUrl regex.
5. Added unit tests for sanitization and path traversal edge cases.
* fix(server): expand Windows reserved name check to include extensions
- Updated sanitizeRepoName to block Windows reserved names (CON, NUL, etc.) even when they have extensions (e.g., CON.txt).
- Corrected regex and added unit tests for these edge cases to resolve CI failures on Windows.
- Ref: https://github.com/abhigyanpatwari/GitNexus/pull/1305#issuecomment-4407200914
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
|
||
|
|
a5b4cda1d6
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
e412d292fe
|
feat: migrate C to scope-based resolution (RFC #909 Ring 3) (#1481)
* Initial plan * feat: add C scope resolution files for language migration (RFC #909) Add 11 C language scope resolution files following the Go pattern: - query.ts: tree-sitter-c query and parser for C constructs - captures.ts: emit scope captures with arity enrichment - import-decomposer.ts: decompose #include into structured captures - arity-metadata.ts: C function declaration/call arity computation - interpret.ts: interpret C imports and type bindings - import-target.ts: resolve #include paths via suffix matching - arity.ts: C arity compatibility (variadic detection) - merge-bindings.ts: first-wins binding merge by tier - simple-hooks.ts: null hooks (no receivers/methods in C) - index.ts: barrel re-exports - scope-resolver.ts: ScopeResolver implementation for C Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * feat: migrate C to scope-based resolution (RFC #909 Ring 3) Add C ScopeResolver with: - tree-sitter-c scope query (structs, unions, enums, functions, macros, variables, includes) - emitCScopeCaptures with arity enrichment and typedef-struct dedup - interpretCImport for #include directives (system headers filtered) - resolveCImportTarget with suffix matching - cArityCompatibility with variadic detection - cMergeBindings (first-wins by tier) - Header file scanning for cross-language #include resolution - Register in SCOPE_RESOLVERS and MIGRATED_LANGUAGES - Integration test with 4 passing test cases Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/ddcbc075-2999-492c-a0ac-47cddd401a4b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: update registry-primary-flag test and add C legacy parity expected failures Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/ddcbc075-2999-492c-a0ac-47cddd401a4b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * refactor: improve arity-metadata readability per review feedback Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/ddcbc075-2999-492c-a0ac-47cddd401a4b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address CI failures — unused import, Dirent types, null comparison, lint, formatting Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/f4b6e20d-8d56-4834-8296-db82af27f8e1 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: replace loose comparisons with strict equality, remove optional chaining from childForFieldName Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/20628629-9dc7-45ec-8ae4-f3a14ad29d93 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Comparison between inconvertible types' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix: remove unnecessary optional chaining on non-null decl in findFuncDeclarator Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/9b012c35-7494-4180-8c6a-b83da8d8abb9 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address 5 production readiness review findings Finding 1: Enforce static functions as file-local via expandsWildcardTo hook - Add static-linkage.ts tracking module with markStaticName/isStaticName/expandCWildcardNames - Update captures.ts to detect storage_class_specifier static on functions - Wire expandsWildcardTo in scope-resolver.ts Finding 2: Expand test coverage to ≥30 cases (74 unit tests added) - c-captures.test.ts: 55 tests (scopes, structs, unions, enums, functions, typedef, field, variable, macro, imports, references, type bindings, arity, static) - c-imports.test.ts: 12 tests (decomposition, interpretation, target resolution, determinism, edge cases) - c-arity.test.ts: 18 tests (declaration arity, call arity, compatibility) Finding 3: Deterministic #include resolution on depth ties - Add lexicographic tiebreak in import-target.ts when candidates tie on path depth Finding 4: Revert unexplained package-lock.json change - Restored to pre-PR state (node >=20.0.0) Finding 5: Planning artifact commit acknowledged (squash on merge) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/22ee780c-2b44-4e69-b9c4-8843ad6ec1ee Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address code review feedback — Set-based dedup, SyntaxNode type alias Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/22ee780c-2b44-4e69-b9c4-8843ad6ec1ee Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix: address second review findings 1-4 — isFileLocalDef hook, singleton docs, fn-ptr typedef, static isolation test Finding 1: Added `isFileLocalDef` hook to ScopeResolver contract + implementation in free-call-fallback.ts to filter C static functions from global free-call fallback. Threads caller filePath through pickUniqueGlobalCallable so static defs in other files are excluded. Finding 2: Documented single-invocation assumption on staticNames Map. Added clearStaticNames() call in loadResolutionConfig to prevent cross-repo contamination in server-mode scenarios. Finding 3: Added tree-sitter query pattern for function pointer typedef aliases (typedef void (*callback)(int, int)) in query.ts. Added unit test. Finding 4: Added c-static-isolation integration fixture (a.c with static helper, b.c with non-static helper, caller.c) and test asserting no CALLS edge from caller to a.c's static helper. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/5b948327-9f59-4ca2-9d8c-8c8087feb510 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix: skip static isolation integration test in legacy parity mode The `caller.c calls b:helper via include, NOT a:static helper` test requires scope-based wildcard import binding + isFileLocalDef filtering which is only available in the registry-primary path. The legacy DAG path does not resolve cross-file calls through #include → prototype chains. Added to LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/3be48249-b375-4446-973b-657400f530fb Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address 3 review findings — static leakage in Phase 2, build-dir skip list, same-directory preference Finding 1: Apply isFileLocalDef filtering in Phase 2 of pickUniqueGlobalCallable so cross-file static defs cannot leak through the SemanticModel fallback path. Finding 2: Expand scanHeaderFiles skip list with dist, build, out, target, _build, .next, cmake-build-* to avoid generated headers shadowing source ones. Finding 3: Implement same-directory sibling preference in resolveCImportTarget, matching C compiler #include "…" relative-lookup semantics. Sibling check now runs before exact match and suffix fallback. Tests: 11 new header-scan tests, 4 new import-target tests (96 total C tests). Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/5236f2b8-72a0-476d-bf39-cca041781014 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * refactor: skip redundant sibling path computation when targetRaw has no backslashes Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/5236f2b8-72a0-476d-bf39-cca041781014 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix: normalize header-scan paths to forward slashes for Windows compatibility On Windows, path.relative() produces backslash-separated paths (e.g. src\foo.h) which caused test failures when comparing against forward-slash expectations. Normalize output with .replace(/\\/g, '/') matching the convention used throughout the scope-resolution pipeline. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/88420759-8a8c-4e6d-affa-ecb5458a235a Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address 4 findings — K&R arity, function-pointer docs, prototype docs, _filePath rename Finding 1: K&R empty parameter list `int foo()` now returns unknown arity `{}` instead of `{parameterCount:0}`. Distinguishes from explicit `int foo(void)`. 3 unit tests cover K&R definition, prototype, and void comparison. Finding 2: Added code comment documenting function-pointer-variable call capture as known architectural trade-off (same as Go resolver). Finding 3: Added code comment documenting prototype/definition duplication as graph-quality concern (no false CALLS edges). Finding 4: Renamed `_filePath` → `filePath` in captures.ts since it is actively used in markStaticName(). Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/ab2164f7-972f-4ea8-81fa-a14ce20d7cce 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> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
fe0a2c381a
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
2a00970565 | Merge branch 'main' of https://github.com/prajapatisparsh/GitNexus into feat/Desktop-app | ||
|
|
d69eadfb7f
|
fix(windows): 32767-char tree-sitter crash + VECTOR extension SIGSEGV (#1433)
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
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
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
* fix(windows): 32767-char tree-sitter crash + VECTOR extension SIGSEGV
tree-sitter 0.21.x on Windows crashes with SIGSEGV when parsing source
strings longer than 32 767 chars (signed 16-bit integer overflow in the
native binding). Five call sites passed raw file content without any
length guard:
- captures.ts (C# scope extraction)
- namespace-siblings.ts (extractFileStructure)
- parse-worker.ts (worker thread parse path)
- parsing-processor.ts (sequential parse fallback)
Fix: truncate at the last newline before the limit so the fragment stays
syntactically coherent. Files truncated mid-class produce ERROR roots;
captures.ts returns [] for any ERROR-root tree so the legacy DAG handles
the file silently without orphaned scope errors.
Additional C# scope fixes:
- scope-tree.ts: Module scopes may share the same range as a top-level
namespace_declaration (files with no leading `using` directives). The
rangeStrictlyContains check rejects equal ranges. Added
rangeNonStrictlyContains for Module parents.
- scope-extractor.ts: pass1BuildScopes stack-pop used strict containment;
same Module == Namespace range case caused orphaned scopes. Added
moduleAwareContains helper.
- scope-extractor-bridge.ts: empty captures from ERROR-root files still
called extractScope -> "no Module scope found" warning. Added early
return for empty/non-array captures.
- namespace-siblings.ts: three sites pushed onto binding arrays frozen by
finalize-algorithm. Fixed with spread-copy before mutation.
lbug-adapter.ts: INSTALL VECTOR in loadVectorExtension calls the KuzuDB
native extension installer, which crashes with SIGSEGV on Windows via an
unhandled error path in native code. JS try/catch cannot intercept native
signals. Skip extension loading on win32 — vector/embedding search is
unavailable on Windows but all graph index queries work correctly.
Verified on: Windows 11, Node.js 24, gitnexus 1.6.3, pcf8-game codebase
(61 757 nodes / 111 796 edges / 300 flows after fix).
* fix(windows): skip FTS extension load in pool-adapter on Windows to prevent SIGSEGV
LOAD EXTENSION fts crashes the process with SIGSEGV on Windows when the
FTS extension binary is not installed locally. This is an @ladybugdb/core
native bug — the extension loader hits an unhandled error path that raises
a native signal instead of a JS exception, so try/catch cannot protect here.
Add a process.platform === 'win32' guard in both doInitLbug and
initLbugWithDb. When skipped, bm25-index.js catches the resulting
Kuzu catalog errors (CREATE_FTS_INDEX not defined) and returns empty
BM25 results gracefully. All graph queries (cypher, context, impact)
are unaffected.
This is patch 9 of the Windows fix series for gitnexus on Windows:
patch 8 (same PR) already fixed INSTALL VECTOR SIGSEGV in lbug-adapter.ts.
pool-adapter.ts is the separate MCP-server code path that was not covered.
* fix: address codeql findings on PR #1433
The four `lastIndexOf('\n', ...)` calls were committed with a literal
newline inside the single-quoted string instead of the `\n` escape, so
the files do not parse — `tsc` and CodeQL both flagged them. Replace
the embedded newline with `'\n'`.
Also remove the two helpers that were superseded during review and
became dead code: `rangeNonStrictlyContains` in scope-tree.ts (the
equal-range carve-out is handled by `rangeStrictlyContains` +
`rangesEqual` in `canParentScope`) and `moduleAwareContains` in
scope-extractor.ts (`pass1BuildScopes` calls `canParentScope` directly).
* fix(windows): replace 32767-char truncation with chunked-input parsing
The tree-sitter 0.21.x Node binding crashes (SIGSEGV) on Windows when
parser.parse(string, ...) is handed a JS string longer than 32 767 chars.
The crash is in the bindings V8 string-to-buffer conversion and cannot
be intercepted from JS. Previous mitigation truncated source at the last
newline before that boundary, silently losing the file tail and producing
ERROR-root trees from mid-class cuts.
Switch to the callback (Parser.Input) overload via a new parseSourceSafe
helper. tree-sitter pulls source in 16 KiB chunks via repeated callback
invocations, bypassing the broken conversion path. Files are parsed in
full, no data loss, no platform-specific code path.
Removes the now-unnecessary ERROR-root short-circuit in csharp/captures.ts
and the empty-captures shim in scope-extractor-bridge.ts; both existed only
to swallow truncation-induced parse failures.
* fix(windows): cover all parse sites and correct vector-extension state
Address adversarial review on PR #1433:
1. Extend parseSourceSafe to all remaining parser.parse() call sites that
handle full file content. The first commit only converted the four
sites with active truncation hacks; cache-miss paths in
call-processor (x2), heritage-processor (x2), import-processor, and
the Go/Python/TypeScript captures + Go range-binding still called
parser.parse() directly. On Windows those would still SIGSEGV for
files > 32767 chars.
2. Stop setting vectorExtensionLoaded = true on the win32 short-circuit
in lbug-adapter.ts. The flag means "successfully loaded" and is
checked by an early-return at the top of loadVectorExtension; setting
it on the skip path made the second call return true and let
QUERY_VECTOR_INDEX run against a DB without the extension.
3. Drop the placeholder issues/... URL in the same comment.
4. Add unit tests for parseSourceSafe at boundary values: 16 KiB
(direct/callback boundary), the 32 767 Windows crash boundary,
single-line > chunk size, CRLF near boundary, and large all-Chinese
source. Confirms the callback path is correct for non-ASCII content,
which is also exercised by the existing csharp-captures large-file
test.
Researched the chunking concern: tree-sitter Node binding sets
TSInputEncodingUTF16 and divides byte_index by 2 in ByteCountToJS before
calling the JS callback, so the index argument is a UTF-16 code-unit
offset — matching String.prototype.slice. Splitting tokens across chunks
is safe by API contract; the lexer is chunk-agnostic.
* fix(windows): extend parseSourceSafe to group/embeddings + lint enforcement
Closes the remaining Windows SIGSEGV exposure flagged by the Codex
adversarial review on PR #1433. Six pre-existing parser.parse(content)
call sites bypassed parseSourceSafe and could crash the process on
Windows when a contract IDL, route file, or embedding-target source
exceeded 32 767 chars. Adds a lint rule so the regression vector closes
permanently.
Production code:
- Relocate parseSourceSafe from ingestion/utils/ to core/tree-sitter/
so group/ and embeddings/ can import without crossing into ingestion
internals. core/tree-sitter/ already houses parser-loader.ts and is
the natural shared facade. All 11 existing importers updated; no shim
left behind in the old location.
- Route through parseSourceSafe in 5 group extractors (grpc, thrift,
http-route, include, tree-sitter-scanner) and the embeddings
ensureAndParse helper.
- The seventh direct .parse() call in grpc-patterns/proto.ts:49 is a
module-load grammar smoke test parsing a 36-char literal. Trivially
safe by inspection, intentionally direct, filtered out by the lint
rule via the string-literal-arg skip.
Tests:
- 5 caller-side regression tests with a vi.spyOn assertion on
parseSourceSafe. The spy is what catches a regression: parser.parse
on a 40 000-char input succeeds on Linux/macOS, so a "no throw"
assertion alone would silently pass with the bypass reintroduced.
- The vi.mock boilerplate is centralised in
gitnexus/test/helpers/parse-source-safe-mock.ts, dynamic-imported
inside each mock factory so vitest's hoister does not race the
static import binding.
Lint:
- New custom ESLint rule gitnexus/require-safe-parse, scoped to
gitnexus/src/core/**, fails on direct <parser>.parse(<non-literal>,
...) calls and auto-fixes them to parseSourceSafe(<parser>, ...).
Skips JSON/URL/marked/Number/Math, string-literal first args
(smoke tests), test files, and the helper itself. Auto-fix rewrites
the call site only; the developer adds the import after tsc
surfaces the missing identifier — same tradeoff as
unused-imports/no-unused-imports.
Plan: docs/plans/2026-05-10-001-fix-windows-parse-safety-group-and-embeddings-plan.md
* fix(test): use mkdtempSync in http-route-extractor regression test
Address CodeQL js/insecure-temporary-file warning on the new Windows-
SIGSEGV regression test. The test was using path.join(tmpDir, "large-input")
which, when nested inside a Date.now()-based parent tmpDir, lets CodeQL flag
the directory as a predictable-name temp file with race-condition risk.
Switch to fs.mkdtempSync(path.join(tmpDir, "large-input-")) so the suffix
is a secure unique random string.
---------
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>
|
||
|
|
4848dce9ea
|
test(u8): de-flake regex linearity assertions (#1475)
* test(u8): de-flake regex linearity assertions The single-trial 2x input + 3x ratio bound was razor-thin: a real macOS CI run failed at ratio 3.01x with small=7.41ms / large=22.31ms - both above the 5ms noise floor but close enough that single-shot scheduler jitter pushed the ratio over. Replace the methodology with four stacked techniques: 1. Warmup runs before timing (let the JIT tier up) 2. Median of 5 trials per measurement (eliminates GC + jitter) 3. 4x input ratio (was 2x) - linear gives ~4x, O(n^2) gives ~16x 4. 8x ratio bound with a 20ms noise floor on the LARGE measurement Headroom: linear is expected at ~4x, bound is 8x = 2x safety margin. A real O(n^2) regression on a 4x input would clock 16x, well outside. Catastrophic backtracking is still caught by the absolute <500ms cap. Verified: 10 consecutive local runs all passed. * test(u8): address PR #1475 review — tighten floor + rename for accuracy Two follow-ups from Claude's review: 1. Floor semantics: revert to 'skip when BOTH measurements below floor' (AND, not single-check) and lower threshold from 20ms back to 5ms. Median-of-5 makes 5ms reliably resolvable above performance.now()'s ~10-100us band, so the higher floor was unnecessary defense. Closes the gap where an O(n^2) regression on a fast runner could stay under 500ms AND below 20ms-large to escape both detectors. 2. Rename assertSubLinearRatio -> assertNearLinearScaling. The bound is SIZE_RATIO * 2 = 8x on a 4x input = sub-quadratic with 2x headroom over linear, not strict sub-linearity. New name reflects the actual semantics. |
||
|
|
936a3bed6f
|
chore: release v1.6.4 (#1473)
* chore: release v1.6.4 * chore: expand v1.6.4 CHANGELOG with additional crash-issue closes |
||
|
|
6db5b32005 | fix(build): add missing execSync import to build.js | ||
|
|
ea313579c6
|
Merge branch 'main' into feat/Desktop-app | ||
|
|
666041d608
|
fix(security): log-injection, http-to-file-access, client-side-request-forgery (#1456)
* fix(security): U11 log-injection, http-to-file-access, client-side-request-forgery U11.1: Add validateLLMBaseUrl() in llm-client.ts; called at the top of callLLM() to reject non-http/https schemes and http:// to non-loopback hosts before any fetch that writes LLM output to disk. U11.2: Strip CRLF from groupDir in bridge-db.ts openBridgeDbReadOnly before logging (defence-in-depth on top of pino's JSON escaping). U11.3: Replace console.log with logger.debug and sanitize normalizedName / job.id in api.ts resolveRepo to close js/log-injection alerts. U11.4: Add validateBackendUrl() in backend-client.ts; called inside setBackendUrl() to reject non-http/https schemes before the URL is stored as a fetch target, closing js/client-side-request-forgery alerts. U11.5: Tests added: - wiki-llm-client.test.ts: validateLLMBaseUrl happy/error paths - server-connection.test.ts: validateBackendUrl and setBackendUrl rejection paths All new tests pass (30/30 wiki-llm-client, 18/18 server-connection, 30/30 bridge-db). Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0452a6ce-711f-4203-9ae6-5dd0b77fb157 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: correct IPv6 loopback check in validateLLMBaseUrl Node's URL parser preserves brackets in hostname for IPv6 addresses (e.g. http://[::1]:11434 yields hostname '[::1]'), so strip them before comparing against '::1'. Add a test to cover this case. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0452a6ce-711f-4203-9ae6-5dd0b77fb157 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: also sanitize error message in bridge-db log call Sanitize lastErr.message (which may contain a file path from ENOENT errors) alongside groupDir to prevent CRLF injection from error message content. Addressed code review feedback. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0452a6ce-711f-4203-9ae6-5dd0b77fb157 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: address security review findings — credential hygiene and test coverage [LOW] Redact credentials from URL validation error messages: - validateLLMBaseUrl: malformed URL no longer echoes raw input; scheme error shows protocol only; http-non-loopback error uses parsed.origin (scheme+host+port) instead of full URL - validateBackendUrl: same treatment — no raw input in any error path [INFO] Add state-preservation test for setBackendUrl: - Proves _backendUrl is unchanged after a rejected call, covering the validation-before-assignment ordering. [INFO] Expand validateLLMBaseUrl adversarial test coverage: - LOCALHOST uppercase (case-fold path) - RFC 1918 / IMDS IPs (10.x, 169.254.x) - Hostname-spoofing (localhost.evil.com, 127.0.0.1.evil.com, localhost.) - Non-loopback IPv6 (fe80::1, ::ffff:127.0.0.1) - ftp:// scheme - Credential-hygiene assertion (sk-secret not in error message) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7bb18fa2-3e66-4fe0-949f-6d493fbd351b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * style: prettier autoformat U11 security fix files Fixes the failing 'quality / format' check on PR #1456 by running 'prettier --write' over the 6 files touched by the security fix. Formatting only — no logic change. --------- 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: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
e02c56f653
|
fix(security): Pin Docker Node base images, remove runtime package-manager CVE surface, verify Trivy on PRs, and harden Dependabot policy (#1455)
* fix: pin Docker node base images and remediate bundled npm CVEs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e0605c79-296e-4b3a-b6c3-4ad375950935 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: run trivy on docker PR changes and remove corepack Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/4d714047-4fc1-4af1-9734-91400a15568f Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore: add docker digest updates and normalize dockerfile comments Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7d980908-a823-4c28-b074-9134ec672e84 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: add dependabot cooldown policies Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/a8531b8d-384b-4c54-84dd-a98b31993c44 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix: remove unsupported dependabot cooldown keys Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/166df50e-c2fe-4d7f-ab41-e94c703338f6 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * chore(node): bump CI + engines to Node 22; centralize NPM_VERSION via build ARG Closes the LOW findings from Claude Final Re-Review on PR #1455: - Bump engines.node to >=22.0.0 and align all CI workflows (ci-quality, pr-autofix, publish, release-candidate) and the composite setup actions on Node 22. Node 20 reached EOL on 2026-04-30; the test Docker image was already on 22. - Centralize the bootstrapped npm version in a single ARG NPM_VERSION per Dockerfile (cli, web, gitnexus/Dockerfile.test) so a security bump only requires updating one default per file with a clear cross-reference comment. --------- 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: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
152a0506c9
|
feat: shared resilient-fetch (retries + circuit breaker) (#1448)
* feat: shared resilient-fetch (retries + circuit breaker)
Add a small, runtime-agnostic resilience layer in gitnexus-shared and
migrate every backend HTTP outbound call (CLI, MCP, wiki LLM, web → backend)
through it.
Helpers (gitnexus-shared/src/integrations/):
- retry.ts — withRetry(fn, opts) with caller-supplied
retryability classification and full-jitter
exponential backoff.
- circuit-breaker.ts — closed/open/half-open per-process breaker with
injectable clock, plus a keyed registry so
callers targeting the same endpoint share state.
- resilient-fetch.ts — composed wrapper: retries 5xx + 429 + retryable
network throws, treats AbortSignal.timeout()
and 4xx (other than 429) as terminal, honors
Retry-After (capped at 30s), throws
CircuitOpenError when the breaker opens.
Migrations (no behaviour regression — all existing tests pass):
- gitnexus/src/core/embeddings/http-client.ts (covers analyze + MCP
query path) — replaces inline linear-backoff retry.
- gitnexus/src/core/wiki/llm-client.ts — preserves Azure content-filter
branch; resilientFetch handles 5xx/429.
- gitnexus-web/src/services/backend-client.ts (fetchWithTimeout helper)
— small retry budget (2 attempts, 250–1500 ms) so a dead local
backend still fails fast for the user.
- gitnexus-web/src/core/llm/settings-service.ts (OpenRouter model list).
Deliberately not migrated:
- gitnexus-web/src/services/backend-client.ts streamJob() — Server-Sent
Events stream; the existing reconnect-with-Last-Event-ID logic is
not unary-fetch shaped.
- gitnexus-web/src/components/SettingsPanel.tsx checkOllamaStatus() —
one-shot health probe; retrying delays the "Ollama not running"
error rather than improving UX.
41 new helper tests cover backoff math, breaker state transitions,
Retry-After parsing (delta-seconds + HTTP-date), 401/422 terminal
classification, and breaker fail-fast on three exhausted retry batches.
* fix(review): apply autofix feedback
Address Claude's two MEDIUM blocking findings on PR #1448 plus the
CodeQL SSRF false-positive flag.
- backend-client `fetchWithTimeout` now uses `AbortSignal.timeout()`
merged with the caller's signal via `AbortSignal.any()`. Timer-fired
aborts surface as `DOMException(name='TimeoutError')` so
resilientFetch routes them through the terminal-network branch
(no retry, no breaker hit), instead of incrementing the breaker
for user-side network slowness.
- Method-aware retry budget in `fetchWithTimeout`: idempotent verbs
(GET/HEAD/OPTIONS) keep the 2-attempt budget; POST/PATCH/PUT/DELETE
default to single-attempt so a 5xx on `startAnalyze` cannot start
a duplicate job. New `forceRetry` parameter for callers that
know-idempotent mutations (e.g. DELETE of a known-deleted resource).
- `resilient-fetch.ts` carries a documented suppression for CodeQL
js/server-side-request-forgery on the inner fetch call. Every
concrete caller passes a hardcoded URL constant or a value from
configuration (env vars, saved settings); user request input never
flows into the URL parameter.
- New test file `backend-client-retry.test.ts` covers all three
paths: GET retries on 503, POST does not retry, timeout does not
increment the breaker.
* fix(resilient-fetch): address Codex adversarial findings
Closes the three blocking issues from Codex's review on PR #1448.
U1 — Add `recordNeutral()` to CircuitBreaker.
Third outcome path that's an explicit no-op for state and the
consecutive-failure counter. Distinct from `recordSuccess` (closes
the breaker) and `recordFailure` (may open it). Used for outcomes
that are neither evidence of backend health nor evidence of
backend failure.
U2 — Route terminal-client / terminal-network through `recordNeutral`.
Previously a 401 or local timeout called `recordSuccess`, which
reset `consecutiveFailures` to 0. A 5xx → 401 → 5xx → 401 → 5xx
sequence would NEVER trip the breaker because each 4xx in between
erased the running count. Also classify external `AbortError` as
terminal-network (was retryable-network), so caller-driven
cancellation no longer retries against an already-aborted signal
or counts toward breaker failures on exhaustion.
U3 — Per-origin breaker key in web `fetchWithTimeout`.
Was hardcoded to `'web-backend'` even though `_backendUrl` is
mutable via `setBackendUrl`. Switching backend URLs after a
circuit tripped on host-A would strand the user during the full
cooldown. Key is now `web-backend:<origin>`, so each backend URL
gets its own breaker state.
Tests: +5 recordNeutral, +4 resilient-fetch (interleaved 4xx/5xx,
external AbortError, prior-state preservation), +1 web switch-backend
regression. All 70 gitnexus integration tests + 15 web tests green.
* fix(resilient-fetch): tolerate header-less fetch mocks on 429
`classifyOutcome` called `resp.headers.get('Retry-After')` directly,
which crashed when a test stubs `fetch` with a plain object like
`{ ok: false, status: 429 }` (no `headers` field). Real `Response`
always has Headers, so this surfaces only in test setups, but the
helper has no business assuming caller-side correctness on this — the
defensive guard is cheap and a missing `Retry-After` falls through to
exponential-backoff retry like any 429 without the header.
Surfaced by `gitnexus/test/unit/http-embedder.test.ts > retries on
rate limit`, which the embeddings migration exercises against a
plain-object 429 stub. Locked in with a new
`classifies 429 from a header-less fetch mock without throwing` case.
* fix(review): apply autofix feedback
Closes findings from the third multi-agent review pass on PR #1448.
#1 (P1) callLLM had no per-attempt timeout
Wiki LLM calls passed no `signal` to resilientFetch; each of three
retry attempts could hang indefinitely on a frozen TCP connection.
Add `signal: AbortSignal.timeout(60_000)` so the per-attempt budget
matches what http-client.ts and backend-client.ts already provide.
#2 (P2) drop dead `lastRetryableResp` post-loop fallback
Variable was set in one switch arm but only read in unreachable code
after the loop. The retry loop always returns/throws on every
iteration. Keep only the defensive `throw` so TypeScript's
control-flow analysis still sees `Promise<Response>` as the return.
#5 (P2) gate test-only exports behind a subpath
`__resetBreakerRegistry__` and `classifyOutcome` were reachable from
the main `gitnexus-shared` barrel — production code calling
`__resetBreakerRegistry__` from a tool implementation would silently
nuke every circuit breaker process-wide. Move to a new
`gitnexus-shared/test-helpers` subpath export. Production callers
see the cleaner public API; tests import via the explicit
`gitnexus-shared/test-helpers` path.
#6 (P2) exhaustiveness guard on Outcome switch
Add a `default: const _: never = outcome` arm so a future sixth
`Outcome.kind` won't compile silently — it'll surface at the switch
site rather than fall through to a retry/no-retry default.
#9 (P3) document cumulative wall-clock budget
Add a "Cumulative wall-clock budget" paragraph to resilientFetch's
JSDoc explaining the worst-case total wait (`maxAttempts × (per-attempt
timeout + capDelayMs)` ≈ 60s with defaults) and pointing callers at
outer `AbortSignal.timeout()` when they want a tighter bound.
Deferred to follow-up PRs (per review's Auto-resolve recommendation):
- #3 idempotency knob to shared API (forceRetry into ResilientFetchOptions)
- #4 publish.ts migration to resilientFetch
- #7 parseRetryAfter past-HTTP-date / negative-seconds asymmetry
- #8 recordNeutral counter time-decay (documented breaker semantic)
* fix(circuit-breaker): gate half-open to a single in-flight probe
Closes the Codex adversarial-review finding on PR #1448 that flagged a
recovery-time thundering herd: when cooldown expired, every concurrent
caller transitioned the breaker to half-open and probed the still-
recovering dependency in lockstep, defeating the breaker's "fail fast"
promise.
U1 — probe-permit gate in CircuitBreaker.check()
Added a `probeInFlight: boolean` field. After cooldown expires, the
first `check()` admits the probe and consumes the permit; subsequent
callers throw `CircuitOpenError` with a configurable
`halfOpenRetryAfterMs` (default 1000ms) until the probe resolves.
Critical design point: `recordNeutral` now RELEASES the permit but
does NOT transition state. Without that split, a single `TimeoutError`
from per-attempt `AbortSignal.timeout` (which routes through neutral
classification) would permanently park the breaker in half-open. By
separating permit-release from state-resolution, we keep the
"neutral doesn't claim health" semantic without creating that wedge.
Other changes:
- `halfOpenRetryAfterMs` is now a constructor option for consumers
with long-running protected ops (LLM streaming, large uploads).
- `getState()` is documented as a pure read; the implicit
Open -> Half-Open transition lives in `check()` only, so tests
that inspect state never inadvertently consume a probe permit.
- `isProbeInFlight()` test-only accessor for assertion clarity.
- JSDoc on `check()` records the JS event-loop atomicity dependency
and the load-bearing `try/finally` pairing invariant.
U2 — End-to-end concurrency regression through resilientFetch
Three new scenarios in resilient-fetch.test.ts (26 -> 29):
- 3 concurrent calls + probe gets 200 -> 1 hits fetch, 2 throw
CircuitOpenError, breaker closes.
- 3 concurrent calls + probe gets 503 -> ResilientFetchExhaustedError
on probe; concurrent callers see halfOpenRetryAfterMs (1000ms);
fresh caller after probe resolves sees the FULL new cooldown
(10000ms), not the probe-in-flight default.
- Probe cancelled mid-flight via AbortError -> permit released,
state stays half-open, next caller becomes the new probe and
succeeds.
Plus 9 new circuit-breaker unit tests (16 -> 25) covering the permit
gate, recordNeutral-releases-permit semantic, fresh-cooldown distinction,
default vs configurable halfOpenRetryAfterMs, getState() purity, and
the three-probes-via-neutrals chain.
Total integration test count: 70 -> 82. All 106 gitnexus + 15 web
tests pass; both packages typecheck.
Maintainer decisions (deferred per plan 003 Open Questions):
- Plan 002's deferral judgement was reversed on Codex's argument
without new measurement / incident data. The reversal is defensible
on principle (Hystrix / Resilience4j alignment) but lacks workload-
driven evidence.
- Probe-blocked callers throw silently (no log / event hook). R4's
"no new public API" prevents adding observability; loosen if a
debug log on probe-blocked is wanted.
* refactor(embeddings): replace bespoke HF breaker with shared CircuitBreaker
Deleted the local `HfDownloadCircuitBreaker` class and the manual
retry loop in `withHfDownloadRetry`. Both are now backed by the
shared `gitnexus-shared` primitives:
- `hfDownloadCircuit` is `new CircuitBreaker({ failureThreshold,
cooldownMs, key: 'hf-download' })` — same state machine as before
PLUS the single-permit half-open gate that prevents recovery-time
stampedes when CLI + MCP embedders concurrently re-load the model.
- `withHfDownloadRetry` delegates the loop to `withRetry` from the
shared package. Per-attempt timeout (`withDownloadTimeout`),
network-vs-non-network classification, circuit recording, and the
`onRetry` callback wire through `withRetry`'s `isRetryable`
callback.
Behaviour preserved:
- Pre-flight `CIRCUIT_OPEN_TAG` rejection when the breaker is open.
- Mid-loop `CIRCUIT_OPEN_TAG` "opened after N consecutive failures"
when a network error trips the threshold.
- Non-network errors (e.g. CUDA unavailable) bypass retry and go
through `recordNeutral` instead of resetting the breaker's
failure-count progress.
- `onRetry(attempt+1, max, err)` fires only when there's a next
attempt, matching the prior semantic.
Generic CircuitBreaker gained two inspection accessors:
- `getOpenedAt(): number | null`
- `getCooldownMs(): number`
Used by `withHfDownloadRetry` to compute `secsUntilReset` without
consuming a probe permit (which `check()` would do).
Test consolidation: the 7 bespoke `HfDownloadCircuitBreaker`
state-machine tests in hf-env.test.ts were 1:1 duplicates of
existing tests in `circuit-breaker.test.ts` and were deleted.
Remaining 42 hf-env tests all pass; full integration sweep (148
gitnexus + 15 web) green.
|
||
|
|
248cb1e634
|
Add regression coverage for .gitnexusignore behavior with --skip-git (#1450)
* Initial plan * test: cover --skip-git with .gitnexusignore regression Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7ca9afd5-10b9-4f39-a260-60e60bde6874 * test: reuse cli path constant in skip-git tests Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7ca9afd5-10b9-4f39-a260-60e60bde6874 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
3daf8c9984
|
feat(extractors): strip Unreal Engine reflection macros before C++ parsing (#1439)
* feat(extractors): strip Unreal Engine reflection macros before C++ parsing Tree-sitter does not expand C preprocessor macros, so Unreal Engine reflection markers (UCLASS, UFUNCTION, UPROPERTY, MODULENAME_API, GENERATED_BODY, ...) are parsed verbatim. The result is mis-parsed UE class/function declarations: in 'class BRAWLUI_API UMyClass : public UObject', tree-sitter-cpp captures BRAWLUI_API as the class name, leaving the actual class without an entry in the graph. This patch adds an optional 'preprocessSource' hook to LanguageProvider and implements it for C++ via a new 'stripUeMacros' module. The transform is length-preserving (each elided byte becomes a space, newlines preserved) so byte offsets and line/column positions tree-sitter reports remain identical to the original file -- symbol locations in the graph stay accurate. A cheap detection guard short-circuits files that don't look like UE sources, so non-UE C++ codebases pay no cost (single regex test then bail). 27 unit tests cover the detection guard, length preservation across multiple UE samples, macro removal for UCLASS/UFUNCTION/UPROPERTY/USTRUCT/GENERATED_BODY/MODULE_API/DECLARE_*_DELEGATE/UE_DEPRECATED, false-positive guards (substring matches, balanced parens inside string literals, Qt macros left alone), and class-name extraction sanity. Full unit suite still passes (5337 tests, 0 regressions). Verified end-to-end against an Unreal Engine 5.7 game project (Brawl). * fix(extractors): address PR review findings on UE macro preprocessor Resolves three blocking issues raised by automated review: 1. Prettier format: ran prettier --write on call-processor.ts, heritage-processor.ts, import-processor.ts (the three sites where the cache-miss reparse hook insertion landed unformatted). 2. Byte-length contract narrowed: language-provider.ts docblock now states the contract precisely (UTF-16 .length + newline-position preservation, not UTF-8 byte length). Notes that startIndex byte offsets only match the original file when the elided range is pure ASCII -- which is the practical UE case (reflection macros and module-export tokens are ASCII-only). 3. Tree-sitter extraction tests added: new end-to-end tests parse the preprocessed source with tree-sitter-cpp and assert the captured class/struct name is the real UClass identifier (UMyClass, FMyData), never the MODULE_API export macro. Also asserts source positions (startPosition.row) survive the transform. Plus one moderate fix: 4. _API stripping is now scoped to UE files only. The HAS_UE_HINT guard previously included [A-Z]_API tokens, which would fire on non-UE codebases that use REST_API / HTTP_API / MY_LIB_API as constants or enum values, silently erasing them. The guard now requires a strong UE marker (UCLASS|UFUNCTION|UPROPERTY|USTRUCT|UENUM|UINTERFACE|GENERATED_BODY|UE_DEPRECATED|DECLARE_*_DELEGATE) to be present before any stripping runs. Two new tests confirm REST_API and DECLARE_HANDLER style identifiers in non-UE files are left untouched. Plus one minor fix: 5. stripUeMacros signature now accepts (source, _filePath?) to match the LanguageProvider.preprocessSource hook contract exactly. The filePath argument is unused; UE detection is purely content-based. Verification: 34/34 preprocessor tests pass (was 27, +7 new for non-ASCII preservation, REST_API safety, tree-sitter extraction, struct extraction, source position preservation). Full unit suite 5349 pass, 0 regressions. Typecheck clean. Prettier --check clean on all 9 changed files. --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
d91428ad9d
|
feat(cli): add gitnexus publish for opt-in understand-quickly registry (#1425)
* feat(cli): add `gitnexus publish` for opt-in understand-quickly registry Adds a small, opt-in command that fires a single `repository_dispatch` event at `looptech-ai/understand-quickly` to ask the registry for an instant resync of the current repo's entry. No graph file is uploaded; the registry pulls from raw.githubusercontent.com per the protocol at https://github.com/looptech-ai/understand-quickly/blob/main/docs/integrations/protocol.md. - Pure helpers (id parsing, payload construction, validation) live in `gitnexus-shared/src/integrations/understand-quickly.ts` so the package stays Node-free and the same logic is testable in isolation. - The CLI command lives in `gitnexus/src/cli/publish.ts`. Without `UNDERSTAND_QUICKLY_TOKEN` it is a no-op (exits 0 with one informational line); with the token it POSTs the dispatch and surfaces 204 / 401 / 404 / 5xx distinctly. - The id defaults to `<owner>/<repo>` parsed from the `origin` remote and can be overridden with `--id`. - Refuses to publish when no `.gitnexus/` index exists, with a `gitnexus analyze` hint. Tests: a new vitest unit covers the pure helpers (8 + 8 + 2 cases) and the no-token no-op path with a `fetch` spy that fails the test if the network is touched. README gets a one-paragraph "Publishing to understand-quickly" section near the existing CLI docs. * fix(uq-publish): address review blockers + high-severity items Addresses CodeQL polynomial-regex (HIGH), token-gate ordering, distinct 401/403/404/422 response branches, fetch timeout, expanded test coverage, tightened owner/repo validation, and non-GitHub remote rejection. See response thread on PR #1425 for the per-finding rationale. Signed-off-by: amacsmith <alex.mac@looptech.ai> * fix(publish): address Claude review on PR #1425 - AbortError → TimeoutError: AbortSignal.timeout() throws a DOMException with name 'TimeoutError', not Error{name:'AbortError'}. Match the pattern used in core/embeddings/http-client.ts so the user-facing "timed out after 15000ms" message actually fires. Update the regression test to throw a real DOMException — the previous fake was a false-green. - isValidOwnerRepo: forbid trailing hyphen in the owner segment. GitHub rejects this at account-creation time; allowing it here meant hand-typed --id values like 'my-org-/repo' would pass our regex and 422 from GitHub. - Add publish-command coverage to cli-index-help.test.ts (asserts on --id, --skip-git, the registry name, and the token env var) and cli-commands.test.ts (asserts publishCommand is exported as a function). Catches accidental command-registration deletion. --------- Signed-off-by: amacsmith <alex.mac@looptech.ai> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |