Compare commits

..
Author SHA1 Message Date
Gergo Magyar 76ed0fa53b fix: address PR #409 review findings (P0-P3) and simplify import resolution API
Bug fixes (P0):
- Narrow Go /cmd/ entry-point detection to only match /main.go
- Fix Rust scoped grouped imports (use crate::models::{User, Repo}) resolution
- Filter PHP use function/use const from class-type namedImportMap bindings

Improvements (P1):
- Add C# resolveStandard fallback when .csproj discovery fails
- Change preprocessImportPath return type to string | null with caller guards
- Add Q_SIGNALS/Q_SLOTS (standard plural Qt macros)
- Fix stale "11 supported languages" comment → 13

API simplification (P2):
- Replace buildImportResolvers() factory with const importResolvers table
- Move configs onto ResolveCtx (extends ImportResolutionContext)
- Eliminate tsconfigPaths parameter threading through 6 non-TS resolvers
- Split utils.ts (1,476 lines) into ast-helpers.ts + call-analysis.ts + utils.ts
- Consolidate findChild/findChildByType into single source of truth
- Match multi-file import bindings to files by basename for namedImportMap

Cleanup (P3):
- EMPTY_INDEX returns shared frozen empty array instead of allocating per call
- Type appendKotlinWildcard parameter as SyntaxNode instead of any
- Document call-routing validation requirement on CallRouter type

Tests:
- Add unit tests for preprocessImportPath (13 tests)
- Add integration tests: Rust scoped multi-file, PHP use function/const,
  C# without .csproj, Go cmd/ helper scoring (14 tests, 4 fixtures)

All 3579 tests pass.
2026-03-21 14:15:15 +00:00
Gergo Magyar 2c17a4642c refactor: unify language dispatch with compile-time exhaustive tables
Replace the 120-line if-chain in resolveLanguageImport() and the 7-branch
dispatch in extractNamedBindings() with per-language dispatch tables using
`satisfies Record<SupportedLanguages, T>` for compile-time exhaustiveness.

Key changes:
- New import-resolution.ts: buildImportResolvers factory + namedBindingExtractors
  table + preprocessImportPath with control character rejection
- Extracted loadImportConfigs(), createImportEdgeHelpers(), getLabelFromCaptures()
  to eliminate duplication across import-processor.ts and parse-worker.ts
- Added isCppDuplicateClassFunction and getLabelFromCaptures shared helpers
- Migrated ENTRY_POINT_PATTERNS and AST_FRAMEWORK_PATTERNS_BY_LANGUAGE to
  satisfies Record<SupportedLanguages, T> with compile-time exhaustiveness
- Added Kotlin entry-point patterns (Android lifecycle, Ktor, MVVM)
- Expanded framework detection: Go (Gin/Echo/Fiber/gRPC), Rust (Actix/Axum/
  Rocket/Tokio), C++ (Qt), Swift (UIKit/SwiftUI/Vapor), Ruby (Rails/Sinatra)
- Fixed Go cmd/ entry-point detection operator precedence bug
- Added EMPTY_INDEX frozen sentinel for type-safe memory cleanup
- Unified ImportResolutionContext (suffixIndex->index, removed unused dispose())
- Extracted NamedBinding interface (replaced 16 inline occurrences)
- Replaced any with SyntaxNode on all tree-sitter node parameters
- Added contributor checklist to SupportedLanguages enum

14 files changed, +318/-550 (net -232 lines). All 3550+ tests pass.
2026-03-21 11:48:11 +00:00
Gergő Magyar 1af9c028c5 Merge pull request #397 from abhigyanpatwari/feat/phase14-cross-file-binding-propagation
feat(type-resolution): cross-file binding propagation
2026-03-21 08:36:23 +00:00
Gergo Magyar fb20a3c752 feat: implement cross-file binding propagation for multiple languages
- Enhance C++ tree-sitter queries to support inline class method declarations and return types.
- Introduce `importedRawReturnTypes` in `BuildTypeEnvOptions` for cross-file raw return type handling.
- Add `FileTypeEnvBindings` interface to capture file-scope type bindings for exported symbols.
- Implement logic in `parse-worker.ts` to extract and serialize file-scope type bindings for cross-file type resolution.
- Create test fixtures for C++, Go, Ruby, and Rust to validate cross-file binding propagation.
- Update integration tests to verify correct resolution of method calls across files for C++, Go, Ruby, and Rust.
- Document Phase 14: Cross-File Binding Propagation in the type resolution roadmap and system documentation.
2026-03-21 07:47:04 +00:00
Gergo Magyar f1fbe643df Merge remote-tracking branch 'origin' into feat/phase14-cross-file-binding-propagation 2026-03-21 06:09:57 +00:00
0xfabs 11575cf6c8 fix: hydrate worker DB in server mode + fix LadybugDB getAll API mismatch (#398) (#404)
Fix server/bridge mode leaving the web UI with 0 nodes and broken
Query/Processes/embeddings by hydrating the worker-side LadybugDB
and BM25 indexes after loading graph data from the backend.

Also fix LadybugDB QueryResult API mismatch where result.getAll()
does not exist in some @ladybugdb/wasm-core versions — falls back
to getAllObjects() or getAllRows().
2026-03-21 06:09:25 +05:30
abhigyanpatwariandClaude Opus 4.6 01ff2a7540 docs: add gitnexus-stable-ops to community integrations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 05:15:54 +05:30
Abhigyan PatwariandDennis Palatov 1f7764c49b fix: register Section in NODE_TABLES and NODE_SCHEMA_QUERIES (#401)
* feat: add markdown file indexing (headings + cross-links)

Parse .md/.mdx files using regex (no tree-sitter dependency) to extract:
- Section nodes from headings (h1-h6) with hierarchy via CONTAINS edges
- Cross-file IMPORTS edges from markdown links to other repo files

Ported from #286 to resolve conflicts with kuzu→lbug rename.

Co-Authored-By: Dennis Palatov <dp-web4@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Section to NODE_TABLES and NODE_SCHEMA_QUERIES

The Section schema was defined but not registered in NODE_TABLES or
NODE_SCHEMA_QUERIES, so the table was never created in the database.
Also adds missing FROM File TO Section relation entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update schema test counts for Section node type

NODE_TABLES: 27→28, NODE_SCHEMA_QUERIES: 27→28, SCHEMA_QUERIES: 29→30

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add diagnostic output to skills-e2e idempotency test

Show stdout/stderr in assertion message so CI failures reveal
why the second analyze --skills run exits with code 1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Section COPY query with level column in lbug-adapter

Section table has 8 columns (includes level) but getCopyQuery fell
through to the default 7-column multi-language path. Adds explicit
Section cases to getCopyQuery and insertNodeToLbug/upsertNodeToLbug.

Error was: COPY failed for Section: Number of columns mismatch. Expected 7 but got 8.

---------

Co-authored-by: Dennis Palatov <dp-web4@users.noreply.github.com>
2026-03-21 04:00:16 +05:30
00758b102a feat: add markdown file indexing (headings + cross-links) (#399)
feat: add markdown file indexing (headings + cross-links)

Ports #286 by @dp-web4 onto current main, resolving conflicts from kuzu→lbug rename.
Closes #286

Co-Authored-By: Dennis Palatov <dp-web4@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 03:11:42 +05:30
Gergő Magyar abeb52e5e5 Merge pull request #69 from x0m4ek/feat/codex-support 2026-03-20 21:22:32 +00:00
Gergo Magyar 6c972079e0 feat(type-resolution): per-language cross-file binding tests + resolver fixes
Add cross-file binding propagation integration tests for 6 languages
(Python, JavaScript, Java, PHP, C#, Kotlin) with fixture repos and
HAS_METHOD edge assertions.

Fix 3 language resolver issues uncovered by tests:
- Kotlin: class methods now labeled Method (not Function) via shared
  isKotlinClassMethod() utility; non-aliased imports create NamedImportMap
  entries; 2-segment package-directory fallback for top-level function imports
- PHP: namespace-directory fallback for use-function imports; SuffixIndex
  preferred over linear scan; path traversal rejection; PSR-4 sort cached
- JVM: root-level package path matching; indexOf→lastIndexOf for correctness

Address code review findings:
- Extract runCrossFileBindingPropagation() from 810-line pipeline function
- Replace (importCtx as any) casts with typed dispose() method
- Remove Tarjan's SCC (dev-only YAGNI, ~85 lines)
- Remove dead PARALLEL_RE_RESOLUTION_THRESHOLD constant
- Fix constructor handling divergence in getLabelFromCaptures
- Optimize gap pre-scan with early exit once threshold exceeded
- Fix findEnclosingFunction any→SyntaxNode type
2026-03-20 15:38:27 +00:00
Dmytro cbfdae0303 test(cli): cover full Codex setup flow 2026-03-20 15:28:42 +01:00
ximi 66c1ffa370 feat: add MiniMax provider support (#224)
Add MiniMax as a new LLM provider using the Anthropic-compatible API.

Changes:
- Add MiniMax to LLMProvider type and MiniMaxConfig interface
- Add MiniMax chat model creation via ChatAnthropic with custom base URL
- Add MiniMax settings persistence and model list (MiniMax-M2.5, MiniMax-M2.5-highspeed)
- Add MiniMax provider UI in SettingsPanel with API key and model selection
2026-03-20 19:48:31 +05:30
Gergő Magyar 88e0034771 Merge pull request #396 from hiromima/fix/kuzu-concurrent-query-segfault-and-stale-data
fix: sequential enrichment queries + stale data detection (#285, #290, #292, #297)
2026-03-20 12:29:00 +00:00
Gergo Magyar 0736bb23bc fix(type-resolution): address Phase 14 enhancement code review findings
1. Replace fragile regex in buildExportedTypeMapFromGraph with
   extractReturnTypeName() for consistent generic unwrapping.

2. Fix gap detection precision: check upstream.has(binding.exportedName)
   instead of exportedTypeMap.has(binding.sourcePath) to avoid
   over-counting files that import only unrelated symbols.
2026-03-20 12:28:22 +00:00
hiromima 2ff4d93314 fix(test): unify staleness-and-stability into single withTestLbugDB block
All 4 test blocks now share one DB lifecycle to avoid cross-block
"Database is closed" errors caused by LadybugDB's shared global DB
in a single vitest fork. Staleness detection (which triggers closeLbug)
runs last to avoid invalidating connections for other blocks.

11/11 tests pass on macOS, Ubuntu, and Windows.
2026-03-20 21:06:30 +09:00
Gergo Magyar fff716dd92 feat(type-resolution): Phase 14 enhancements — single-pass seeding, Tarjan's SCC, cross-file return types
E0: Fix AST cache thrashing in re-resolution loop (was creating size-1
cache per file), batch file reads per topological level, add
MAX_CROSS_FILE_REPROCESS=2000 cap for adversarial repos.

E1: seedCrossFileReceiverTypes() — enrich ExtractedCall.receiverTypeName
from ExportedTypeMap+namedImportMap in O(1) Map lookups, eliminating
re-parse for ~80-90% of single-hop cross-file receiver types.

E2: computeImportCycleSCCs() — iterative Tarjan's SCC on cycle subgraph
from Kahn's output. Dev-mode diagnostic logging of individual import
cycle components.

E3: buildImportedReturnTypes() + ReturnTypeLookup extension — cross-file
return type propagation with corrected local-first priority (SymbolTable
checked first, cross-file fallback only on 0 matches, ambiguous 2+
returns undefined).

E4: PARALLEL_RE_RESOLUTION_THRESHOLD constant, timing metrics, worker
parallelization design comments (deferred implementation).

24 new tests (6 E1 + 6 E2 + 7 E3 unit + 5 E3 integration). All 3478
tests pass.
2026-03-20 12:00:36 +00:00
Gergo Magyar 56bc226a1c fix(type-resolution): address Phase 14 code review findings
Critical fixes:
- Re-resolution pass now actually re-resolves CALLS edges by calling
  processCalls with importedBindingsMap (was building typeEnv but
  discarding it without producing edges)
- Worker path populates ExportedTypeMap via buildExportedTypeMapFromGraph
  using graph node isExported + SymbolTable returnType/declaredType
  (was dead parameter in processCallsFromExtracted)

Important fixes:
- Skip threshold denominator uses totalFiles (was exportedTypeMap.size +
  filesWithGaps which made threshold nearly useless)
- processCalls accepts importedBindingsMap parameter to thread cross-file
  bindings into buildTypeEnv during re-resolution

All 3454 tests pass.
2026-03-20 09:57:24 +00:00
Gergo Magyar a6a1004e82 feat(type-resolution): Phase 14 — cross-file binding propagation
Add ExportedTypeMap infrastructure to propagate resolved type bindings
across file boundaries. When file A exports `const user = getUser()`
(resolved to `User`), file B importing `user` now gets seeded with
`user → User`, enabling `user.save()` to produce CALLS edges.

Key components:
- `importedBindings` option on BuildTypeEnvOptions with scopeEnv seeding
  AFTER walk() to respect first-writer-wins (local declarations win)
- `collectExportedBindings()` in call-processor using graph node
  isExported flag (no SymbolDefinition changes needed)
- Inline Kahn's algorithm topological sort with level grouping for
  parallel-safe file ordering and cycle detection
- Re-resolution pass in pipeline.ts: topological order, 3% skip
  threshold, path validation, per-file export caps (500)
- 32 new tests: 11 topological sort, 6 seeding, 15 integration
  (simple cross-file, re-export chain, circular imports)

All 3454 tests pass (32 net new, 0 regressions).
2026-03-20 09:31:20 +00:00
hiromima 5b012c3351 test: add e2e tests for stale detection, sequential enrichment, stability (#396)
- Stale data detection: verify ensureInitialized() detects meta.json
  changes and re-opens pool without SIGSEGV or WAL corruption
- Staleness throttle: verify 5s throttle window doesn't cause errors
- Sequential enrichment: impact() enrichment queries complete on arm64
- Consecutive stability: 10+ sequential cypher calls, mixed tool cycles
- Watchdog guard: parallel queries with activeQueryCount protection
- stdout restoration: verify process.stdout.write is properly restored

Covers test plan items from PR #396 (issues #285, #290, #292, #297)
2026-03-20 18:04:27 +09:00
Gergő Magyar 862fdf9185 Merge pull request #392 from abhigyanpatwari/feat/phase-p-polymorphism-overloading
feat(type-resolution): polymorphism and method overloading support
2026-03-20 08:54:43 +00:00
Gergo Magyar 228c993bb7 fix(type-resolution): review fixes, sizeBefore optimization, and test coverage
Address code review findings from PR #392 senior compiler review:
- Fix Java "Yes" → "No" in optional-param-arity matrix (Java has no defaults)
- Simplify Kotlin hasDefaultValue while-as-if to direct const/if check
- Update OPTIONAL_PARAM_TYPES comment to include Ruby
- Replace per-declaration Set allocation with size-based Map iteration skip
- Add 11 unit tests for multi-declarator type association and constructorTypeMap
2026-03-20 08:45:42 +00:00
Gergo Magyar c3a2815186 feat(type-resolution): optional parameter arity resolution
Add requiredParameterCount to SymbolDefinition and MethodSignature,
enabling range-based arity filtering in filterCallableCandidates.
Calls with omitted optional/default arguments now resolve correctly.

Supported: TS, Python, Kotlin, C#, C++, PHP, Ruby (7 languages).
Detection via OPTIONAL_PARAM_TYPES set + hasDefaultValue helper.

9 integration tests added across all 7 languages.
2026-03-20 08:14:58 +00:00
hiromima 893f77ae89 fix: address review feedback — watchdog, TOCTOU race, null guard, platform guard
- Watchdog timer now exempts in-flight queries via activeQueryCount,
  preventing premature stdout restoration during long queries (>1s)
- Stale detection uses reinitPromises Map to prevent TOCTOU race where
  concurrent callers double-close the connection pool
- Throttle meta.json staleness checks to once per 5s per repo
- Add null guard for i.id in IN-clause construction
- Enrichment queries run in parallel on non-arm64 platforms to preserve
  performance; sequential only on arm64 macOS where SIGSEGV occurs
2026-03-20 16:52:33 +09:00
Gergo Magyar d49c76ddc5 feat: Implement virtual dispatch and overload disambiguation enhancements
- Updated AGENTS.md and CLAUDE.md to reflect new indexing metrics.
- Enhanced call-processor.ts to support cross-file inheritance tracking and improved virtual dispatch resolution.
- Added support for TypeScript overload signatures in tree-sitter queries.
- Improved type extraction for C++, C#, and Kotlin to handle smart pointers and constructor types.
- Introduced inferLiteralType for overload disambiguation across multiple languages.
- Added tests for C++ smart pointer dispatch and Kotlin virtual dispatch scenarios.
- Updated type-resolution-roadmap.md to reflect completion of phases P.1 to P.3 and outline future work on covariant return types.
2026-03-20 07:23:14 +00:00
hiromima 999fbf5b11 fix: sequential enrichment queries + stale data detection
Fixes three related issues that cause SIGSEGV crashes and stale data:

1. Impact enrichment queries (Promise.all → sequential await)
   The impact() method ran 3 enrichment queries concurrently via
   Promise.all against the same LadybugDB connection pool. On arm64
   macOS, concurrent native DB access triggers SIGSEGV. Changed to
   sequential await. Also caps IN-clause to 100 IDs to prevent
   oversized queries. (#285, #290, #292)

2. Silence stdout during query execution
   silenceStdout()/restoreStdout() only wrapped createConnection() and
   initLbug(). Now also wraps executeQuery() and executeParameterized()
   to prevent native stdout writes from corrupting the MCP stdio
   stream during all DB operations. (#285)

3. Stale data after re-index
   ensureInitialized() checked pool existence but never verified whether
   the underlying index was rebuilt. Now reads meta.json's indexedAt
   timestamp on each call and closes/re-opens the pool when the index
   has changed. (#297)
2026-03-20 13:33:20 +09:00
Zander Raycraft c14a78a341 Merge pull request #394 from zander-raycraft/fix/claude-review
SEC/ spam guards for the PR and issue claude-review workflow for contributions
2026-03-19 20:35:23 -05:00
Zander Raycraft 7a0a83e45c added author_association to @claude calls to amke it to where only maintainers and above can call @claude not just anyone 2026-03-19 20:25:43 -05:00
Zander Raycraft f2edbb4f82 CI sticky note test 2026-03-19 18:26:56 -05:00
Gergo Magyar 1d27ad09a2 test(type-resolution): assert parameterTypes on graph nodes in integration tests
Add parameterTypes to graph node properties (parse-worker + parsing-processor)
so integration tests can verify extracted parameter types per language:
- Java: ['int'] on lookup(int) overload
- C#: ['int'] on Lookup(int) overload
- C++: ['int'] on lookup(int) overload
- Kotlin: ['Int'] on lookup(Int) overload

Add getNodesByLabelFull helper for property-level assertions.
2026-03-19 22:51:16 +00:00
Gergő Magyar 68d4c48aba Merge pull request #391 from zander-raycraft/fix/claude-review
CI sticky notes
2026-03-19 22:49:17 +00:00
Gergo Magyar bc771574d8 test(type-resolution): Phase P integration tests + fixes for all overloading languages
Integration tests for overload disambiguation (Java, Kotlin, C#, C++)
and virtual dispatch (Java, TypeScript) with strict toBe() assertions.

Unit tests verify exact parameterTypes extraction per language:
- Java: ['int'], ['String'], ['int', 'String']
- Kotlin: ['Int'], ['String']
- C#: ['int'], ['string']
- C++: ['int'], ['string']

Fixes discovered during testing:
- extractSimpleTypeName: handle Java integral_type/boolean_type/etc
- tryOverloadDisambiguation: unwrap C# argument + Kotlin value_argument
  wrapper nodes; traverse Kotlin call_suffix for value_arguments
- Kotlin boxed→primitive normalization (Int→int, Long→long, etc.)
- C++ tree-sitter queries: capture pointer-returning inline class methods
- extractFunctionName: handle C++ field_identifier for inline methods
2026-03-19 22:47:24 +00:00
Zander Raycraft 5d6e15eea3 minor bug fixes 2026-03-19 17:19:05 -05:00
Zander Raycraft eb74eb8590 CI sticky notes 2026-03-19 17:06:32 -05:00
Gergo Magyar 700c9d16e4 feat(type-resolution): constructor-visible virtual dispatch via constructorTypeMap
Add constructorTypeMap to buildTypeEnv — populated during walk when a
declaration has both a type annotation and a constructor initializer.
Add isSubclassOf helper (BFS, depth-5, cycle-safe).
In call-processor, consult constructorTypeMap to override receiver type
when constructor creates a known subclass (same-file only).
2026-03-19 21:49:21 +00:00
Gergo Magyar 19ff84fa31 feat(type-resolution): overload disambiguation via argument literal types
Add inferLiteralType to LanguageTypeConfig for Java, Kotlin, C#, C++.
In resolveCallTarget, when multiple candidates survive arity filtering,
lazily infer argument literal types and filter by parameterTypes match.
Worker path falls through gracefully (no AST available).
2026-03-19 21:45:34 +00:00
Gergo Magyar 2fe03d2a21 feat(type-resolution): extract parameterTypes in extractMethodSignature
Add parameterTypes?: string[] to SymbolDefinition and MethodSignature.
Extract per-parameter type names via extractSimpleTypeName during
parsing for overload disambiguation (Java, Kotlin, C#, C++).
Thread through both sequential (parsing-processor) and worker
(parse-worker) paths.
2026-03-19 21:41:58 +00:00
Gergo Magyar 3e29f4e4b9 fix(symbol-table): store all overloads in fileIndex instead of last-write-wins
The fileIndex Map stored SymbolDefinition per name, silently dropping
earlier overloads via Map.set(). Changed to SymbolDefinition[] so all
same-name methods (e.g., Java overloads) survive in same-file resolution.

Added lookupExactAll() for resolution-context to pass all same-file
candidates through to candidate filtering.
2026-03-19 21:37:21 +00:00
Gergő Magyar 71353512a1 Merge pull request #388 from zander-raycraft/fix/claude-review
FIX - Claude review fork 403 for PR reviews
2026-03-19 21:29:09 +00:00
Gergő Magyar 00c5126b24 Merge pull request #387 from abhigyanpatwari/feat/type-resolution-milestone-d
feat(type-resolution): Milestone D — Phases A, B, C + Kotlin fix + 11-language integration tests
2026-03-19 21:26:19 +00:00
Gergo Magyar 06994e474a fix(type-resolution): address PR #387 review — dead code, nullable_type, scope boundaries + integration tests
- Remove dead replayPendingItems array and inert if-block in type-env.ts
- Add nullable_type fallback in extractKotlinDeclaration for val x: User? local vars
- Tighten isCSharpNullableDecl to avoid substring false positives on type names
- Add missing scope boundaries: function_expression (TS), constructor_declaration/
  local_function_statement/lambda_expression (C#) in null-check narrowing walkers
- Extend null-check narrowing fixtures and add 4 integration tests covering:
  Kotlin local variable nullable, C# constructor + lambda, TS function expression
2026-03-19 21:06:05 +00:00
Zander Raycraft c507b4b197 remove manual test 2026-03-19 15:34:56 -05:00
Gergo Magyar b6947b0c02 feat(type-resolution): consolidate Phases 10–13 into Milestone D — enhance completeness with full integration test coverage across 11 languages 2026-03-19 20:31:35 +00:00
Gergo Magyar e9ccec1a52 test(type-resolution): add integration tests for Milestone D across all 11 languages + fix Kotlin null-check narrowing
Adds 17 new fixture directories and 23 new describe blocks covering every
feature in Milestone D (Phases A, B, C) with full cross-language integration
test coverage:

Phase A — Fixpoint Completeness:
- TS/JS object destructuring (const { field } = obj → fieldAccess resolution)
- TS/JS post-fixpoint for-loop replay (iterable var resolved by fixpoint)
- Rust struct_pattern destructuring (let Point { x, y } = p)

Phase B — Inheritance & Receivers:
- Grandparent MRO (depth-2 C→B→A) for all 9 OOP languages:
  TS, Kotlin, C#, C++, Java, PHP, Python, Ruby, JS
- Go inc/dec write access (obj.Field++/-- emit ACCESSES write edges)

Phase C — Branch-Sensitive Narrowing:
- Null-check narrowing for TS (!==null, !=null, !==undefined),
  C# (!=null, is not null), and Kotlin (!=null)

Bug fix — Kotlin null-check narrowing (3 issues in jvm.ts):
1. patternBindingNodeTypes registered 'comparison_expression' but
   tree-sitter-kotlin produces 'equality_expression' for !=
2. Handler checked for 'null_literal' named child but 'null' is an
   anonymous node in the Kotlin grammar
3. extractKotlinParameter only searched for 'user_type' direct child,
   missing 'nullable_type' wrapper (so x: User? never got a base binding)

17 fixtures, 23 describe blocks, 705 new lines of test code, 0 failures.
2026-03-19 20:23:06 +00:00
Zander Raycraft 0122d9e694 manual trigger for testing on pr comments 2026-03-19 14:12:53 -05:00
Zander Raycraft 00e2476eca claude reviewer update 2026-03-19 14:08:48 -05:00
Zander Raycraft 217efcf015 Merge remote-tracking branch 'upstream/main' 2026-03-19 13:30:46 -05:00
Gergo Magyar 7c72cefd8d feat(type-resolution): implement Milestone D — Phases A, B, C
Phase A — Fixpoint Completeness:
- Extract fixpoint loop into resolveFixpointBindings() with exhaustive switch guard
- Add classDefCache to memoize lookupFuzzy results during fixpoint iteration
- Post-fixpoint for-loop replay: bridge walk-time/fixpoint gap (ex-Phase 9B)
- Object destructuring via fieldAccess items (TS/JS object_pattern, Rust struct_pattern)
- PendingAssignmentExtractor now supports returning arrays for multi-binding patterns

Phase B — Inheritance & Receivers:
- BuildTypeEnvOptions object replaces positional params (future-proof API)
- Heritage pre-pass: thread parent class data from query matches into buildTypeEnv
- walkParentChain() helper: MRO-aware field/method resolution (depth 5, cycle-safe)
- this/self/$this/Me receiver substitution at extractPendingAssignment call site
- Go inc/dec write-access detection via tree-sitter queries

Phase C — Branch-Sensitive Narrowing:
- Rename PATTERN_BRANCH_TYPES → NARROWING_BRANCH_TYPES (semantic expansion)
- Null-check narrowing: != null / !== undefined strips nullable wrapper in truthy branch
- Position-indexed patternOverrides with extractor-provided narrowing ranges
- TS, Kotlin, C# null-check narrowing extractors with if-body range detection

All 3315 existing tests pass. 9 new null-check narrowing tests added.
2026-03-19 17:29:47 +00:00
Gergo Magyar 58f67d07f7 fix(ci): grant actions:read to publish workflow for pr-report job 2026-03-19 13:07:48 +00:00
Gergo Magyar a4863605e1 chore: bump version to 1.4.7 and update CHANGELOG 2026-03-19 12:50:24 +00:00
Gergő Magyar fc58c415f7 Merge pull request #379 from abhigyanpatwari/feat/phase9-call-result-binding
feat: Phase 9 — Return-type-aware variable binding with unified fixpoint chain propagation
2026-03-19 12:43:45 +00:00
Gergo Magyar 790d1d5b0f refactor: update Type Resolution Roadmap to clarify phases and principles for static analysis evolution 2026-03-19 12:39:19 +00:00
Gergo Magyar 8273324f3c fix: address PR review — Rust await unwrap, stale doc claims, this-receiver footnote
Review follow-ups from compiler front-end review (#379):

- Rust extractPendingAssignment now calls unwrapAwait() on value before
  type checks, so `let user = get_user().await` resolves correctly
- type-resolution-system.md: removed "no fixpoint inference" from
  limitations, updated "Single-pass" to "Walk + fixpoint", replaced
  stale single-pass Tier 2 description with fixpoint loop explanation
- type-resolution-roadmap.md: Phase 9 body updated — 9C is delivered,
  9B walk-order dependency documented (for-loop Tier 0b runs before
  fixpoint, so fixpoint-resolved types can't update loop variables)
- Added this/self/$this fixpoint gap footnote to feature matrix
2026-03-19 12:14:54 +00:00
Gergo Magyar 7b71b64427 fix(mcp): update tool descriptions for Phase 9C capabilities
- context tool: remove outdated "Phase 2" ACCESSES reference, document
  that CALLS edges resolve through field/method chains
- cypher tool: fix Property query example to use declaredType (not description)
- schema resource: add node_properties section documenting Method returnType,
  Property declaredType, Function parameterCount etc.
- schema resource: clarify ACCESSES edge read/write coverage
2026-03-19 11:59:10 +00:00
Gergo Magyar e6b8edc1ac feat: Phase 9C unified fixpoint with field access and method-call-result binding
Replace the sequential Tier 2b/2a propagation with a unified fixpoint
loop that handles four binding kinds: callResult, copy, fieldAccess,
and methodCallResult. The loop iterates until no new bindings are
produced (max 10 iterations), enabling arbitrary-depth mixed chains:

  const user = getUser();       // callResult → User
  const addr = user.address;    // fieldAccess → Address
  const city = addr.getCity();  // methodCallResult → City
  city.save();                  // resolves to City#save

Infrastructure:
- PendingAssignment union extended with fieldAccess and methodCallResult
- resolveFieldType helper: typeName → class nodeId → lookupFieldByOwner
- resolveMethodReturnType helper: typeName → class nodeId → lookupFuzzyCallable filtered by ownerId
- Fixpoint also resolves reverse-order copy chains that single-pass missed

Languages: TS, JS, Java, Kotlin, C#, Go, Rust, Python, PHP, Ruby, C++.
Each gets field access and/or method-call-with-receiver detection in
extractPendingAssignment, plus method-chain-binding test fixtures.
2026-03-19 11:50:50 +00:00
Dmytro Semchuk a7b8c302d4 Merge branch 'main' into feat/codex-support 2026-03-19 10:24:45 +01:00
Gergo Magyar 5769872b70 feat: Phase 9 call-result variable binding across 11 languages
Activate the dormant Tier 2b pendingCallResults infrastructure in
type-env.ts by extending each language's extractPendingAssignment to
emit { kind: 'callResult', lhs, callee } when the RHS of an untyped
variable declaration is a simple function call.

This enables `var user = getUser(); user.save()` to resolve at TypeEnv
build time. Tier 2b now runs before Tier 2a copy-propagation, enabling
mixed chains like `const user = getUser(); const alias = user;
alias.save()`.

Languages: TS, JS, Java, Kotlin, C#, Go, Rust, Python, PHP, Ruby, C++.
Swift excluded. Each language gets a call-result-binding test fixture
and integration tests.

Conservative: only simple calls (no method calls with receivers), only
when exactly one callable matches, first-writer-wins.
2026-03-19 08:59:16 +00:00
60c93d7d4a feat: upgrade @ladybugdb/core to 0.15.2 and remove segfault workarounds (#374)
* feat: upgrade @ladybugdb/core to 0.15.2 and remove segfault workarounds

The upstream fix (ladybug-nodejs#1) resolves the child QueryResult lifetime
segfault, making .close() safe on all platforms. This removes 6 workaround
sites:

- Remove `dangerouslyIgnoreUnhandledErrors` from vitest config
- Remove platform-conditional .close() guards in global-setup and test helper
- Delete test/setup.ts (process._getActiveHandles unref hack)
- Replace no-op cleanup in test-indexed-db.ts with real adapter close
- Fix pool adapter closeOne() to properly close connections with shared
  Database refcount guard and orphaned connection handling in checkin()
- Update segfault-related comments across the codebase

Also bumps @ladybugdb/wasm-core to ^0.15.2 in gitnexus-web for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: keep dangerouslyIgnoreUnhandledErrors for macOS N-API exit crash

The N-API destructor ordering crash during worker fork exit on macOS is
independent of the QueryResult lifetime fix in 0.15.2. Tests pass, but
the exit triggers a crash. Keep the flag with an updated comment
explaining the actual cause. Can be removed once LadybugDB fixes all
destructor ordering issues upstream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: unify test run for single-pass coverage

- Update `npm test` to run all tests (unit + integration + lbug-db)
  via `vitest run` instead of `vitest run test/unit`
- Add `test:unit` script for running unit tests only
- Remove `ci-integration.yml` — the per-file lbug-db process isolation
  is no longer needed with `dangerouslyIgnoreUnhandledErrors` and
  `fileParallelism: false` handling fork exit issues
- Update `ci-unit-tests.yml` to run all tests with build + coverage
- Simplify `ci.yml` gate (two jobs: quality + tests)
- Simplify `ci-report.yml` (single coverage artifact, no merge step)

* fix: update cli-commands test for renamed test:all → test:unit script

* fix: set USERPROFILE in setup-skills test for Windows compatibility

os.homedir() checks USERPROFILE on Windows, not HOME.

* fix: add isolate: false to lbug-db project to prevent fork crashes

On macOS, N-API destructors crash fork workers on exit. With
isolate: true (default), vitest recycles the fork between files,
triggering the crash after each file. After several crashes, the
remaining lbug-db files never execute.

isolate: false keeps all 8 lbug-db files in a single fork — the
fork only exits once after all files complete, and that single exit
crash is caught by dangerouslyIgnoreUnhandledErrors.

* fix: add unique sequence.groupOrder to vitest projects

Vitest v4 requires unique groupOrder when projects have different
maxWorkers (lbug-db has fileParallelism: false → maxWorkers: 1).

* fix: await async close() in global-setup and remove isolate: false

global-setup.ts called conn.close() and db.close() without await —
these return Promise<void> in @ladybugdb/core 0.15.2.  The setup
function returned before the DB was fully closed, so vitest forks
hit a stale file lock when opening the same DB path, crashing the
lbug-db worker before any test ran.

isolate: false caused native state corruption after 2-3 open/close
cycles in the same fork (vitest-specific, not reproducible in plain
Node.js).  Without it, each file gets its own module scope and the
N-API destructor crash at fork exit is caught by
dangerouslyIgnoreUnhandledErrors.

Also fixes fire-and-forget close() calls in the pool adapter —
try/catch around an async close() never catches rejections; changed
to .catch(() => {}) for proper unhandled-rejection prevention.

Before: 0/8 lbug-db files ran on macOS CI (fork crash).
After:  8/8 pass, 84 files, 3077 tests, zero errors.

* fix: update project index references in AGENTS.md and CLAUDE.md to reflect correct symbol counts and relationships

* feat: enhance lbug adapter with external database support and write operation validation

* feat: create ci-tests workflow for comprehensive test coverage across platforms

* ci: move PR report inline to ci.yml, delete ci-report.yml

The old ci-report.yml used workflow_run which always runs code from
the default branch (main). This meant the PR comment used main's
stale report template that still referenced the old unit/integration
split architecture — causing "Merge coverage reports" failures.

Moving the report inline to ci.yml means it runs from the PR branch
and uses the current report template. The report now shows:
- per-platform status (Ubuntu/Windows/macOS columns)
- unified test counts from the single vitest run
- coverage with base branch (main) delta comparison
- commit SHA for traceability

Also removes the save-pr-meta job since the report no longer needs
a separate workflow_run trigger.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
2026-03-19 08:25:43 +00:00
Zander Raycraft b0f25e216d Merge branch 'main' of https://github.com/zander-raycraft/GitNexus 2026-03-18 17:36:39 -05:00
Zander Raycraft 84f07e83ee test fix for claude call 2026-03-18 17:35:43 -05:00
Gergo Magyar 1e19986ef3 fix(tests): update property edge and write access expectations across multiple language tests 2026-03-18 22:25:30 +00:00
Gergő Magyar 973c7bfbf0 feat: ACCESSES edge type with read/write field access tracking (#372)
* feat: Phase 1 ACCESSES edge type — read tracking from chain resolution

Add ACCESSES relationship type to track field read access during call
chain resolution. When walkMixedChain resolves a field access (e.g.,
user.address.save()), an ACCESSES edge with reason 'read' is emitted
from the calling function to the Property node.

Schema: ACCESSES added to RelationshipType, REL_TYPES, VALID_RELATION_TYPES,
context queries, tools/resources descriptions. Excluded from default
impact BFS to prevent traversal explosion.

Implementation: resolveFieldAccessType now returns FieldResolution with
fieldNodeId. walkMixedChain accepts optional onFieldResolved callback.
makeAccessEmitter factory provides Set-based dedup per source node.

Bug fix: Added Java 'field_access' to FIELD_ACCESS_NODE_TYPES — was
missing, causing extractMixedChain to fail for Java member access.

* feat: Phase 2 ACCESSES write edges — assignment detection across 12 languages

Add tree-sitter query patterns for field write detection (obj.field = value)
across all supported languages: TS/JS, Python, Java, Go, C++, C#, Rust,
PHP, Ruby (setter syntax), Kotlin, Swift.

Processing: Sequential path handles assignment captures inline. Worker
path extracts ExtractedAssignment data for deferred resolution via new
processAssignmentsFromExtracted function.

Bug fix: Kotlin/Swift assignment queries used invalid navigation_expression
wrapper — fixed to match actual directly_assignable_expression AST structure.

Tests: Write access integration tests for TS, Java, Python, Go with
dedicated fixtures. All use strict toBe() assertions.

* test: add unit tests for call-routing, shared type extractors, and symbol-table branches

Add 215 new unit tests across 3 files to increase branch coverage toward
the 23% global threshold (was 21.49%):

- call-routing.test.ts (49 tests): Ruby call routing — require/require_relative,
  include/extend/prepend heritage, attr_accessor properties with YARD types
- shared-type-extractors.test.ts (108 tests): pure string functions —
  extractElementTypeFromString, stripNullable, extractReturnTypeName,
  methodToTypeArgPosition, getContainerDescriptor
- symbol-table.test.ts (+29 tests): Property/fieldByOwner index, metadata
  spread branches, lazy callable index, lookupExactFull shape

* fix: defer write-access resolution to fix Ruby cross-file property timing

Ruby attr_accessor properties are registered during processCalls (not
the parsing phase), so lookupFieldByOwner fails when service.rb is
processed before models.rb. Fix by collecting pending write-access
edges during the file loop and resolving them after all files are done.

Also adds write-access integration tests and fixtures for 7 languages
(C++, C#, JS, Kotlin, PHP, Ruby, Rust), Ruby compound assignment query,
PHP static property write query, and Kotlin property type extraction.

* fix: address PR #372 review — write-access constructor bindings parity and docs

- Add verified constructor bindings fallback to write-access resolution
  in both sequential path (receiverIndex lookup) and worker path
  (constructorBindings param for processAssignmentsFromExtracted),
  closing the read/write ACCESSES edge asymmetry for factory-returned
  receivers
- Clarify inner guard control flow comment in processCalls match loop
- Document Go inc_statement/dec_statement gap in roadmap
- Clarify PHP nullsafe write footnote (invalid syntax, not just untracked)
- Update symbol-table tests for intentional fieldByOwner behavior change
  (Properties without declaredType now indexed for dynamic language
  write-access tracking)
2026-03-19 03:48:04 +05:30
abhigyanpatwariandClaude Opus 4.6 c0b4098c4e fix(web): add missing Kotlin entries to Record<SupportedLanguages> maps
The SupportedLanguages enum includes Kotlin but the web project's
LANGUAGE_QUERIES and languageFileMap Records were missing it, breaking
the Vercel build with TS2741.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 02:17:03 +05:30
Gergő Magyar 11a3d0515c feat: Phase 8 field/property type resolution (#354)
* feat: Phase 8 field/property type resolution — resolve chained member access

Add field/property type extraction to the type resolution system so that
chained member access like `user.address.save()` resolves the intermediate
receiver type (`address → Address`) through Property symbols in SymbolTable.

Key changes:
- SymbolTable: add `declaredType` field, `fieldByOwner` O(1) index,
  `lookupFieldByOwner()` method, P0 conditional callableIndex invalidation,
  P2 exclude Properties from globalIndex to prevent namespace pollution
- tree-sitter queries: add `definition.property` for TypeScript, Java, Go
- parse-worker: extract declared types for Property nodes via
  `extractPropertyDeclaredType()`, capture field-access receiver info
- call-processor: add `resolveFieldAccessType()` helper and field-access
  branch in both sequential and worker receiver resolution paths
- Integration tests: new field-types test suite verifying end-to-end
  `user.address.save() → Address#save` resolution

* fix: Go tree-sitter query captures field_declaration not field_declaration_list

Post-review fix: the Go struct field query incorrectly put @definition.property
on field_declaration_list (the list container) instead of field_declaration
(the individual field). Also removed unused `language` parameter from
extractPropertyDeclaredType.

* feat: expand field-type tests to 6 languages, fix Go ownerId and Kotlin navigation_expression

- Add integration test fixtures for Java, C#, Go, Kotlin, PHP (alongside existing TS)
- Fix Go: add type_declaration handling in findEnclosingClassId for struct fields
  (field_declaration → field_declaration_list → struct_type → type_spec → type_declaration)
- Fix Kotlin: add navigation_expression handling in field-access resolution
  (Kotlin uses navigation_expression + navigation_suffix, not member_expression)
- Add extractMemberAccessParts helper in call-processor for cross-language member access
- All 24 field-type tests pass across 6 languages, 181 Go+Kotlin tests pass with no regressions

* refactor: split HAS_METHOD into HAS_METHOD + HAS_PROPERTY edge types

Property nodes now use HAS_PROPERTY edges instead of HAS_METHOD, giving
the graph schema proper semantic separation between methods and fields.

- HAS_METHOD: Method, Constructor, Function (when inside a class)
- HAS_PROPERTY: Property nodes (class fields, struct fields, attributes)

MRO processor only reads HAS_METHOD — properties correctly excluded from
method resolution order. Impact analysis accepts both edge types.

Updated 12 files: graph types, schema, tools docs, parse-worker,
parsing-processor, call-processor, and 6 test files.

* fix(test): update security test to expect 7 VALID_RELATION_TYPES (added HAS_PROPERTY)

* test: add unit tests for Phase 8 SymbolTable features (39 tests, up from 19)

Cover all new branches: declaredType metadata, Property exclusion from
globalIndex, conditional callableIndex invalidation, lookupFieldByOwner
(happy path + edge cases), lookupFuzzyCallable filtering, and clear()
with fieldByOwner. Fixes branch coverage threshold (21.8% → 23%+).

* feat: Phase 8B mixed field+method chain resolution, C++/Rust chain fixes

Unify field and method chain resolution into a single `extractMixedChain`
walker that handles interleaved patterns like `svc.getUser().address.save()`.
Fix C++ chain calls (tree-sitter-cpp `field_expression` uses `argument` not
`object`), Rust unit struct instantiation (`let svc = TypeName;`), and add
stdlib passthrough for `unwrap()`/`clone()`/`expect()` in chain loops.

Key changes:
- Replace `receiverCallChain` + `receiverFieldAccess` with unified
  `receiverMixedChain: MixedChainStep[]` on ExtractedCall
- Add `extractMixedChain` in utils.ts (handles both call_expression and
  field_expression nodes, including C++ `argument` field)
- Add `TYPE_PRESERVING_METHODS` set for stdlib identity operations
- Add C++ inline method double-indexing guard in parsing-processor.ts
  and parse-worker.ts
- Add Rust unit struct recognition in type-extractors/rust.ts
- Split field-types.test.ts into per-language test files
- Add ts-mixed-chain fixture and integration tests
- Resolve rust.test.ts todo: Option<T>.unwrap().save() now works
- Update roadmap: Phases 7+8 complete, Phase 9 is next

* fix: Python declaredType extraction and sequential-path property registration

- Move @definition.property capture from expression_statement to assignment
  node in Python queries so Strategy 1 childForFieldName('type') succeeds
- Pass item.declaredType through ctx.symbols.add in sequential call-processor
  path, matching worker path behavior (fixes Ruby YARD declaredType drop)
- Add Python chain resolution integration test (user.address.save → Address#save)
- Update Rust/Python status in roadmap and system docs to reflect actual coverage

* fix: Python/Ruby field type disambiguation and Rust chain test

Three fixes from PR #354 third review:

1. Python typed_parameter name extraction: tree-sitter-python's
   typed_parameter uses positional children for the name, not a named
   field. TypeEnv and extractParameter now fall back to firstNamedChild.

2. Ruby/Python call-step field resolution: Ruby's AST uses `call` nodes
   for both property access and method calls. The chain walker now tries
   resolveFieldAccessType before resolveCallTarget for call steps, so
   attr_accessor properties resolve via declaredType.

3. Rust chain resolution test: added missing integration test asserting
   user.address.save() resolves to Address#save.

Also splits C/C++ and TS/JS columns in type-resolution-system.md
language matrix with footnotes for accuracy.

1062 resolver integration tests passing, 0 failures.

* refactor: Phase 8 code review cleanup — extract walkMixedChain, fix MCP agent gaps

- Extract duplicated chain resolution loop into shared walkMixedChain() helper,
  eliminating ~60 lines of copy-pasted code between sequential and worker paths
- Add returnType to ResolveResult, removing redundant lookupFuzzy+find per chain step
- Fix context() tool to include HAS_METHOD, HAS_PROPERTY, OVERRIDES in queries
  so agents can discover class members
- Fix p.declaredType Cypher example (column doesn't exist) → p.description
- Add HAS_METHOD, HAS_PROPERTY, OVERRIDES to schema resource
- Document HAS_METHOD/HAS_PROPERTY in impact tool description
- Delete dead code extractMemberAccessParts (superseded by extractMixedChain)
- Replace any with SyntaxNode on extractPropertyDeclaredType
- Add Rust deep-field-chain test (5 tests), Java mixed-chain (4), Go mixed-chain (4)
- All 1075 tests pass (13 new, 0 regressions)

* refactor: type SymbolDefinition.type as NodeLabel, add O(1) receiver index

- Change SymbolDefinition.type from string to NodeLabel union (35 members)
  across symbol-table.ts, parse-worker.ts, parsing-processor.ts — compiler
  now enforces correctness at all comparison/assignment sites
- Replace O(N*M) linear scan in lookupReceiverType with pre-built
  ReceiverTypeIndex (Map<funcName, Map<varName, Entry>>) for O(1) lookups
  with proper ambiguity handling and file-level fallback
- All 1075 tests pass, 0 regressions

* fix: capture C++ pointer/ref fields, Kotlin data class props, PHP constructor promotion

Add tree-sitter query patterns for three previously missed property declaration
forms: C++ pointer/reference member fields (Address* addr; Address& ref;),
Kotlin primary constructor val/var parameters (data class User(val name: String)),
and PHP 8.0+ constructor property promotion (public Address $address).

Fix "10 languages" off-by-one in docs (Ruby is single-level only, not deep chain).
Update Python feature matrix cell from No* to Yes* after 31b95f0 fix.

11 new integration tests with per-language fixtures verify property capture,
HAS_PROPERTY edge emission, and field-access chain resolution.
2026-03-18 18:47:33 +00:00
Berk Demirci e0a6c40b45 Fix undefined parsing error on languages missing from call routers (#364)
* fix: mapping all supported languages to callRouters to fix undefined apply error (#352)

* fix(cli): remove duplicate Swift/Kotlin keys in callRouters causing TS1117

* refactor: remove runtime callRouter fallbacks, rely on Record<SupportedLanguages> compile-time enforcement

* refactor: use 'satisfies' keyword for callRouters compile-time enforcement
2026-03-18 14:52:54 +00:00
Chirag Nighutandchirag-nighut aa1bab597b feat: add Python enumerate() for-loop support with nested tuple patterns (#356)
- Handle `for i, k, v in enumerate(d.items())` — flat pattern
- Handle `for i, (k, v) in enumerate(d.items())` — nested tuple_pattern
- Handle `for (k, v) in enumerate(users)` — parenthesized tuple as top-level

Extract helper functions for cleaner code:
- `extractMethodCall()` — deduplicate method call parsing
- `collectPatternIdentifiers()` — recursively collect identifiers from patterns

Add unit tests for TypeEnv and integration tests verifying CALLS edges.

Made-with: Cursor

Co-authored-by: chirag-nighut <chiragnighut@gmail.com>
2026-03-18 13:05:10 +00:00
Hazem 60ede20a11 fix: MCP server crashes under parallel tool calls (#326) (#349)
* fix: MCP server crashes under parallel tool calls (#326)

* fix: ensure full connection pool is pre-created to avoid race conditions during query execution

* fix: improve graceful shutdown handling with exit codes

* fix: resolve critical concurrency bugs in connection pool init

- Add initPromises dedup map to prevent double-init race when parallel
  tool calls trigger initLbug for the same repoId simultaneously
- Move pool.set() after FTS load so concurrent checkout can't grab a
  connection mid-async-init (FTS race on available[0])
- Replace lazy createConnection growth path with integrity error — pool
  is pre-warmed, lazy creation would silence stdout during active queries
- Add preWarmActive flag so watchdog timer skips stdout restore during
  the synchronous pre-warm loop
- Unify stdout capture: server.ts imports realStdoutWrite from
  lbug-adapter instead of capturing its own copy

* test: add connection pool parallel stability tests

7 integration tests covering concurrent query safety, waiter queue
overflow, stdout.write restoration, connection leak detection, initLbug
deduplication, atomic pool visibility, and mixed query types.

* fix: run LadybugDB tests sequentially via vitest projects config

Vitest's projects feature splits test files into two groups: lbug-db
(fileParallelism: false) and default (parallel). This prevents native
mmap file-lock conflicts on Windows without requiring the CI shell loop
locally.

* test: add enrichment Promise.all regression test for #292/#316

Verifies that 3 concurrent queries via Promise.all (the exact pattern
from the impact command's enrichment phase at local-backend.ts:1415)
complete without SIGSEGV on a pre-warmed connection pool.
2026-03-18 13:02:01 +00:00
Gergo Magyar fb5270c260 chore: bump version to 1.4.6 and update CHANGELOG 2026-03-18 08:47:45 +00:00
Gergő Magyar 604b575e4b feat: Phase 7 type resolution — return-aware loop inference & PHP class-property iterables (#341)
* feat(type-resolution): Phase 7.1+7.2 foundation — ReturnTypeLookup, context object, pendingCallResults

- Move extractReturnTypeName + helpers from call-processor.ts to type-extractors/shared.ts
  (breaks circular import risk: call-processor → type-env → type-extractors → call-processor)
- Add SymbolTable.lookupFuzzyCallable(name) — lazy callable-only index, O(1) per call,
  invalidated on add(); avoids per-call .filter() on lookupFuzzy results
- Add ReturnTypeLookup interface (conservative: undefined when 0 or 2+ callables match)
- Add ForLoopExtractorContext interface — replaces 4 positional params with context object;
  update all 10 language extractor implementations (go, ts, py, jvm×2, cs, rs, rb, php, c-cpp)
- Add PendingAssignment discriminated union (kind: 'copy' | 'callResult');
  update PendingAssignmentExtractor in all 9 language extractors that implement it
- Wire buildTypeEnv: build ReturnTypeLookup from optional symbolTable; split pendingAssignments
  into pendingCopies + pendingCallResults; add Tier 2b call-result propagation loop
- Update call-processor.test.ts to import extractReturnTypeName from shared.ts

* feat(type-resolution): Phase 7.3 — call_expression iterables in for-loop extractors (7 languages)

Extends for-loop type extraction in all 7 typed-iteration languages to
resolve element types when the iterable is a direct function call.

**New capability**: `for (var u : getUsers())` in Java, `for u in get_users()`
in Python, `for user in getUsers()` in TypeScript, etc. now resolve
`u`/`user` to the callee's return element type via lookupRawReturnType +
extractElementTypeFromString.

Changes per language:
- types.ts: extend ReturnTypeLookup with lookupRawReturnType (raw return
  string for container-type extraction); update ForLoopExtractorContext
  with returnTypeLookup field
- type-env.ts: implement lookupRawReturnType on the concrete ReturnTypeLookup
  built in buildTypeEnv (same guards as lookupReturnType, no extractReturnTypeName)
- go.ts: call_expression branch in range_clause — identifier func or
  selector_expression method; existing isChannelType guards updated
- typescript.ts: identifier fn branch inside call_expression handler
- python.ts: identifier fn branch inside call handler
- jvm.ts (Java): method_invocation without object field in enhanced_for_statement
- jvm.ts (Kotlin): simple_identifier callee branch in call_expression node
- csharp.ts: identifier fn branch in invocation_expression handler
- rust.ts: identifier func branch in call_expression handler (alongside
  existing field_expression/method-call path)

All branches follow the same conservative pattern:
  lookupRawReturnType(callee) → extractElementTypeFromString → bind loop var

* feat(type-resolution): Phase 7.4 — PHP \$this->property iterable via @var class property scan

Adds Strategy C to PHP's extractForLoopBinding for the pattern:

  foreach (\$this->property as \$item)

when Strategy A (resolveIterableElementType) and Strategy B (scopeEnv lookup)
both fail to find the element type.

Strategy C: when the iterable is a member_access_expression with object '$this',
walk up the AST to the enclosing class_declaration, scan its declaration_list
for a property_declaration whose variable_name matches the property, and extract
the element type from:
  1. PHPDoc @var annotation on a preceding comment sibling (/** @var User[] */)
  2. PHP 7.4+ native type field (e.g. UserRepo \$repo — skips generic 'array')

This eliminates the @param workaround that was previously required in the
php-foreach-member-access fixture (which used @param User[] \$users on the method
to populate the method's scopeEnv with a \$users binding).

New helpers in php.ts:
- PHPDOC_VAR_RE: regex for @var extraction
- extractClassPropertyElementType: reads @var or native type from a property_declaration
- findClassPropertyElementType: scans class body for a named property

Tests added (type-env.test.ts):
- PHP: resolves from @var User[] without @param workaround
- PHP: conservative — no binding for unknown property
- PHP: multi-class file — both classes resolve independently

Fixture updated (php-foreach-member-access/App.php):
- Removed the @param User[] \$users workaround from processMembers()
- Test now validates the natural class-property-based resolution path

* docs: mark Phase 7 complete in type-resolution-roadmap.md

Records that 7A (call_expression iterables, 7 languages), 7B (PHP
$this->property via @var scan), and 7C (ReturnTypeLookup + context object)
are all shipped. Adds implementation notes and strikethroughs on resolved
language-specific gaps.

* fix(docs): update project references to feat-phase7-type-resolution in AGENTS.md and CLAUDE.md

* feat(type-resolution): Phase 7.5 — PHP call_expression foreach + integration tests for 7 languages

Add integration test coverage for Phase 7.3's call_expression iterable
resolution across all 7 languages (Go, TypeScript, Python, Java, Kotlin,
PHP, Rust). Each test creates a fixture with competing User/Repo classes
that both define save(), then verifies for-loop iteration over a function
call's return value resolves to the correct class.

PHP was missing function_call_expression support in its for-loop extractor.
Three changes fix this:
- php.ts extractForLoopBinding: handle function_call_expression and
  member_call_expression iterables via returnTypeLookup
- php.ts normalizePhpReturnType: preserve array notation (User[]) in
  SymbolTable so lookupRawReturnType returns useful container types
- parse-worker.ts + parsing-processor.ts: upgrade uninformative AST
  return types (array, iterable) with PHPDoc @return annotations

35 new integration tests (5 per language), 2525 total tests passing.

* fix(type-resolution): address PR #341 review findings — PHP asymmetry + dormant infrastructure docs

- Replace normalizePhpType with extractElementTypeFromString in PHP call-expression
  foreach paths, aligning with all 6 other language extractors and preventing
  incorrect binding of bare non-container types like User
- Add NOTE comments clarifying pendingCallResults Tier 2b is infrastructure-ready
  but no extractor populates it yet
- Expand Go channel-type comments explaining why non-channel assumption is safe

* fix(type-resolution): address verification review — docs accuracy + PHP fallback guard

- Roadmap lines 86/100: correct pendingCallResults from "active" to "dormant infrastructure (Phase 9)"
- type-resolution-system.md line 363: update to reflect Phase 7.3 loop inference is delivered
- type-resolution-system.md line 409: clarify for-loop call-expression resolution (done) vs general assignment propagation (pending)
- php.ts:127: add declaration_list type guard on fallback to prevent silent wrong results
2026-03-18 08:39:38 +00:00
Gergo Magyar 02dfab578c fix(test): add --repo to CLI e2e tool tests for multi-repo environment 2026-03-18 08:12:25 +00:00
Gergo Magyar 1326490a5b fix(workflow): use prefixed temporary branch name for fork PRs to prevent overwriting real branches 2026-03-18 07:40:53 +00:00
林 駿甫 (Shunsuke Hayashi) b48cfe9894 fix(impact): return structured error + partial results instead of crashing (#321) (#345)
* fix(impact): return structured error + partial results instead of crashing (#321)

- Wrap impact() in try-catch to return structured error JSON instead of
  process crash (SIGSEGV/exit 139)
- Extract core logic to _impactImpl() for clean error boundary
- Break out of depth traversal loop on query failure, return partial
  results collected so far (previously silently swallowed errors)
- Add 'partial' flag to response when traversal was interrupted
- Add try-catch in CLI impactCommand with structured error output
- Improve formatImpactResult to show suggestion text and partial warning
- Add 3 new unit tests for error/suggestion/partial scenarios

Fixes #321

* fix: address review feedback — 4 bugs from @claude review

Per @claude's review (requested by @magyargergo):

- [BUG 1] Consistent target field shape: error responses now return
  {name: string} instead of raw string, matching success response schema
- [BUG 2] Remove misleading partial:true from total-failure responses
  (partial is only meaningful when some depth levels succeeded)
- [BUG 3] Move getBackend() inside try-catch in impactCommand so
  backend init failures return structured JSON instead of crashing
- [BUG 4] Safe error message extraction: use instanceof Error check
  to handle thrown strings correctly (err?.message is undefined for
  non-Error thrown values)
- [MINOR] Add radix argument to parseInt (10)

* test: add integration tests for impact error handling (#321)

Per @claude's recommendation (requested by @magyargergo):

- impact: structured error for unknown symbol (no crash)
- impact: error response has consistent {name: string} target shape
- impact: partial:true only set when some results were collected

Tests use existing withTestLbugDB + seeded graph fixture.
2026-03-18 06:45:43 +00:00
林 駿甫 (Shunsuke Hayashi) c1703fc0a9 fix(cli): write tool output to stdout via fd 1 instead of stderr (#324) (#346) 2026-03-18 06:21:32 +00:00
Karesansui 480fae933b fix(impact): add HAS_METHOD and OVERRIDES to VALID_RELATION_TYPES (#350) 2026-03-18 06:01:02 +00:00
林 駿甫 (Shunsuke Hayashi) 3879490817 fix: add postinstall permission fix for CLI and hook scripts (#330) (#348) 2026-03-18 05:41:37 +00:00
Gergo Magyar 50dbd03779 chore: add .worktrees/ to .gitignore 2026-03-17 21:39:09 +00:00
Gergo Magyar 1003d8b6a5 test: add coverage for perf optimizations — fastStripNullable, skipGraphPhases, AST pruning
- 6 new unit tests for fastStripNullable branches (simple id, nullable union, bare keyword)
- 4 new integration tests for skipGraphPhases pipeline option
- Tests for SKIP_SUBTREE_TYPES and interestingNodeTypes code paths
2026-03-17 17:31:24 +00:00
Gergo Magyar 74b9701509 chore: bump version to 1.4.5, add CHANGELOG.md 2026-03-17 17:18:35 +00:00
Gergő Magyar f0132c1077 feat: Phase 6 type resolution — for-loop Tier 1c, pattern matching, container descriptors, 10-language coverage (#318)
* feat: Phase 6 type resolution — pattern matching, for-loop Tier 1c, coverage completion

- Add patternBindingNodeTypes gate to LanguageTypeConfig for 50% perf improvement
- Expand ForLoopExtractor signature with optional declarationTypeNodes + scope
- Add extractElementTypeFromString shared utility for container type parsing
- Python match/case: extractPatternBinding for `case User() as u:` pattern
- C# refactor: move is_pattern_expression from extractDeclaration to extractPatternBinding
- Ruby: add extractPendingAssignment for assignment chain propagation
- TS/JS: add for-loop Tier 1c for `for (const user of users)` with User[] inference
- Python: add for-loop Tier 1c for `for user in users:` with type annotation inference
- Go: add for-loop Tier 1c for `for _, user := range users` with []User inference
- Fix 'Property' as any stale cast in call-processor.ts
- Add dual return-type string length cap (2048 pre-cap, 512 post-cap)
- Add chain call integration tests for C#, Go, Rust, Python, JS, C++
- Add Python match/case integration test fixtures
- 27 new extractElementTypeFromString unit tests
- 3 for-loop edge cases skipped (declarationTypeNodes scope key lookup)

* fix: address code review findings for Phase 6

- Add missing patternBindingNodeTypes to C# typeConfig (perf gate)
- Add 2048-char input length guard to extractElementTypeFromString
- Skip Python match/case integration tests (call extraction needs query updates)

* reorganise

* fix: Phase 1 bug fixes — Go range semantics, typed_parameter, bracket depth

- Go single-var range correctly returns early for slices/maps (index, not element)
- Go single-var range on channels correctly resolves element type
- Added map_type and channel_type to extractGoElementTypeFromTypeNode
- Added isChannelType helper for channel detection before skip decision
- Added 'typed_parameter' to TYPED_PARAMETER_TYPES for Python annotated params
- Fixed bracket depth tracking in extractElementTypeFromString — only match
  selected closeChar at depth 0, return undefined for mismatched brackets
- Un-skipped 3 prematurely skipped tests (TS local const, Python List/Sequence)
- Added tests for map range, single-var range semantics, bracket edge cases

* refactor: Phase 2 architecture — shared helper, required params, decoupled type nodes

- Extract resolveIterableElementType shared helper in shared.ts implementing
  3-strategy fallback (declarationTypeNodes → scopeEnv string → AST walk)
- Refactor TS, Python, Go extractors to use shared helper (eliminates 3x duplication)
- Make ForLoopExtractor params required (aligned with PatternBindingExtractor)
- Update Java, Kotlin, C# extractor signatures to accept required params
- Decouple declarationTypeNodes from scopeEnv — capture raw type annotation
  nodes BEFORE extractDeclaration for container types (User[], []User, List[User])
- Hybrid approach: direct name extraction + keysBefore fallback for multi-declarator
- Document declarationTypeNodes invariant change (superset of scopeEnv)

* feat: Phase 3 partial — Rust for-loop + C# var foreach Tier 1c

- Rust: add extractForLoopBinding with for_expression support
  - Handles &users, &mut users via reference_expression unwrapping
  - extractRustElementTypeFromTypeNode: generic_type, reference_type, slice/array
  - findRustParamElementType: AST walk with reference/mut pattern unwrapping
  - 4 unit tests (Vec<User>, &[User], range expr negative, no-annotation negative)

- C#: upgrade foreach to handle var (implicit_type) via Tier 1c
  - extractCSharpElementTypeFromTypeNode: generic_name, array_type, nullable_type
  - findCSharpParamElementType: AST walk to method_declaration parameters
  - 3 unit tests (var foreach, explicit type regression, no-annotation negative)

* feat: Phase 3 complete — all language gaps + pattern matching

Kotlin Tier 1c:
- Unannotated for-loop resolves via shared helper
- extractKotlinElementTypeFromTypeNode handles type_projection unwrapping
- findKotlinParamElementType walks to function_declaration

Java Tier 1c:
- var foreach resolves via shared helper
- extractJavaElementTypeFromTypeNode handles generic_type, array_type
- findJavaParamElementType walks to method_declaration

TypeScript:
- readonly User[] unwrapped via readonly_type → array_type recursion

C# switch patterns:
- declaration_pattern added to patternBindingNodeTypes
- extractPatternBinding handles standalone declaration_pattern (switch case/expr)

Rust match arms:
- match_arm added to patternBindingNodeTypes
- extractPatternBinding extended with match_arm → match_expression parent traversal

Python:
- as_pattern tries childForFieldName('alias') before positional fallback

Tests: 237 pass (was 224), 13 new tests added

* feat: Phase 4 — known limitation tests, match arm fix, final verification

- Fix Rust match_arm pattern extraction: unwrap match_pattern to get
  tuple_struct_pattern inside (tree-sitter-rust wraps in match_pattern node)
- Add first-writer-wins regression test for match arm scope leakage
- Add 5 documented skip tests for known limitations:
  - TS destructured for-of (tuple destructuring)
  - Python tuple unpacking in for-loops
  - TS instanceof narrowing (block-level scoping)
  - Rust for with .iter() (method call iterable)
  - Ruby block parameters (closure param inference)

Final: 238 passed, 5 skipped (documented limitations), tsc clean

* test: integration tests for all Phase 6 language gaps + fix Rust param pattern field

Integration test fixtures and tests (30 new tests, all with exact match + negative):

Rust for-loop (5 tests):
- for user in &users with Vec<User> → User#save, negative Repo#save
- for repo in &repos with Vec<Repo> → Repo#save, negative User#save

Rust match arm (5 tests):
- match opt { Some(user) => user.save() } → User#save, negative Repo#save
- if let Ok(repo) = res → Repo#save, negative User#save

C# var foreach (5 tests):
- foreach (var user in users) with List<User> → User#Save, negative Repo#Save
- foreach (var repo in repos) with List<Repo> → Repo#Save

C# switch pattern (4 tests):
- is User user → User#Save, case Repo repo → Repo#Save

Kotlin unannotated for (4 tests):
- for (user in users) with List<User> → user.save, negative repo.save

Go map range (3 tests):
- for _, user := range userMap with map[string]User → User#Save, negative

TypeScript readonly (4 tests):
- for (const user of users) with readonly User[] → user.save, negative

Bug fix: type-env.ts parameter branch now falls back to childForFieldName('pattern')
for Rust parameters (Rust uses 'pattern' not 'name' for parameter names)

* test: add assertion bodies to known limitation skip tests

Convert empty skip test stubs to proper tests with parse/buildTypeEnv/expect
assertions following the codebase convention (e.g., call-processor.test.ts:319).
Each skip test now documents the exact expected behavior, so removing .skip
will cause a meaningful failure when the limitation is eventually fixed.

Also clarify Python integration skip tests as call-extraction issues (not
type-env) and Swift integration skips as build-dep issues (self/super
resolution code already exists in type-env.ts).

* feat: resolve 4 known limitation skip tests + method-aware type arg selection

Unskip 4 of 5 type-env known limitations with full integration test coverage:

1. TS destructured for-of: handle array_pattern by binding last named child
   to element type. Fix Map<K,V> to return last generic arg (value type).
2. Python dict.items() loop: handle `call` iterables + `pattern_list` left
   side. Fix dict[K,V] extraction via type_parameter with last-arg heuristic.
   Unwrap `type` wrapper in extractPyElementTypeFromAnnotation.
3. TS instanceof narrowing: add extractPatternBinding for binary_expression
   with positional child access. First-writer-wins (not block-scoped).
4. Rust .iter() for-loops: handle call_expression in for_expression value
   node by extracting receiver from field_expression.

Method-aware type arg resolution:
- Add TypeArgPosition ('first'|'last') to resolveIterableElementType
- .keys()/.keySet()/.Keys → first type arg (key); all else → last (value)
- Thread position through all 3 strategy callbacks in TS/Rust/Python
- Add predefined_type to extractSimpleTypeName for TS primitives (string etc)

New fixtures: rust-iter-for-loop, typescript-destructured-for-of,
typescript-instanceof-narrowing, python-dict-items-loop.
248 unit tests pass (6 new), 1 skip (Ruby block params).

* feat: container descriptor table for generic type arg resolution

Replace simple KEY_METHODS heuristic with CONTAINER_DESCRIPTORS table
that maps 30+ container types across all languages to their type parameter
semantics per access method.

Key improvements:
- Container-aware resolution: HashMap.iter() correctly yields V (arity 2),
  while Vec.iter() yields T (arity 1) — same method, different semantics
- Cross-language coverage: Map/HashMap/BTreeMap/dict/Dict/Dictionary/
  ConcurrentHashMap + List/Vec/Set/HashSet/Queue/Deque/Stack etc.
- Method categorization: keyMethods (keys/keySet/Keys) vs valueMethods
  (values/get/pop/iter/first/last) per container type
- Fallback for unknown containers: still uses method name heuristic,
  so MyCache<K,V>.keys() correctly returns first arg
- Exported getContainerDescriptor() for future heritage-chain lookups

Each language extractor now passes containerTypeName from scopeEnv to
methodToTypeArgPosition for descriptor-aware resolution.

252 unit tests pass (4 new descriptor tests), 1 skip (Ruby).

* feat: method-aware for-loop extractors + integration tests for all languages

Upgrade 4 existing extractors + create 3 new ones for full cross-language
coverage of call_expression iterables and container descriptor resolution:

Upgraded (add call expr iterable + methodToTypeArgPosition):
- Java: method_invocation (data.keySet(), data.values())
- Kotlin: navigation_expression + call_expression (data.keys, data.values())
- C#: member_access_expression + invocation_expression (data.Keys, data.Values)
- Go: TypeArgPosition threading for Go 1.18+ generics

New for-loop extractors:
- C++: for_range_loop with auto& unwrapping, template_type + qualified_identifier
  (std::vector<User>) extraction, explicit vs auto type handling
- PHP: foreach_statement with simple/key-value/by-reference forms, PHPDoc
  @param priority over AST array type
- Ruby: for-in with YARD @param type resolution via comment parsing

Integration test fixtures + tests for all 6 languages:
- java-map-keys-values (Map.values() + List iteration)
- kotlin-map-keys-values (HashMap.values + List iteration)
- csharp-dictionary-keys-values (Dictionary.Values foreach)
- cpp-range-for (auto& + const auto& range-based for)
- php-foreach-loop (foreach with PHPDoc @param User[])
- ruby-for-in-loop (for-in with YARD @param Array<User>)

Bugs fixed during integration testing:
- C++: qualified_identifier (std::vector) not unwrapped to template_type
- PHP: extractParameter overwrote PHPDoc-derived types with bare 'array'

252 unit tests pass, 201 integration tests pass across 6 languages.

* fix: update extractElementTypeFromString tests for last-arg default

TypeArgPosition change (default 'last') broke 5 existing tests expecting
first arg from multi-arg generics. Updated expectations and added explicit
pos='first' tests for key type extraction.

* fix: rename C++ fixture files to correct case for case-sensitive CI

On case-sensitive filesystems (Linux/macOS CI), git tracked both the old
lowercase files (app.cpp, user.h) and the new uppercase files (App.cpp,
User.h) as separate files. The pipeline processed both, causing the old
app.cpp (with explicit User& type) to interfere with the new auto& test.

Removes old lowercase entries and re-adds with uppercase casing to match
the #include directives in the fixture.

* feat: PR #318 review findings — pattern bindings, member access iterables, structured bindings

Address all 7 genuine gaps identified in PR #318 deep code review:

- Kotlin: add extractKotlinPatternBinding for when/is (type_test AST node)
  with allowPatternBindingOverwrite for smart-cast semantics
- Java: add type_pattern branch for Java 17+ switch pattern variables
- TypeScript: explicit object_pattern skip in for-of (no false bindings)
- Cross-language: member access iterables (self.users, this.users, repo.users)
  across all 10 language extractors
- C++: structured_binding_declarator handling in range-for (last-child heuristic)
- Rust: closure_parameter added to TYPED_PARAMETER_TYPES
- PHP: normalizePhpType handles angle-bracket generics (Collection<User>)

Code review fixes applied:
- Remove 4 debug console.log statements (c-cpp.ts, call-processor.ts)
- Hoist KNOWN_CONTAINER_PROPS to module scope (csharp.ts)
- Guard keysBefore allocation behind typeNode check (type-env.ts)
- Add depth limits (50) to 7 recursive type extraction functions
- Add 2048-char length cap to extractSimpleTypeName
- Fix PHP/Ruby missing typeArgPos parameter in resolveIterableElementType

Integration test fixtures: kotlin-when-pattern, java-switch-pattern,
cpp-structured-binding, typescript-member-access-for-loop,
python-member-access-for-loop

* fix: position-indexed when/is bindings, Kotlin param extraction, HashMap.values for-loop

Three root causes for failing Kotlin integration tests:

1. When/is multi-arm resolution: flat scopeEnv stored only the last arm's
   type (last-writer-wins). Added PatternOverrides with AST range indexing
   so each when arm resolves to its narrowed type independently.

2. HashMap.values for-loop: navigation_expression without call_suffix was
   classified as bare property access (iterableName='values' instead of
   'data'). Now tries object-as-iterable + property-as-method first, with
   fallback to property-as-iterable for this.users patterns.

3. Kotlin parameter extraction: tree-sitter-kotlin parameter nodes use
   positional children (simple_identifier, user_type) not named fields
   (name, type). Added fallback to findChildByType in both
   extractKotlinParameter and extractTypeBinding.

Integration tests added for .keys/.values/Set/MutableMap iteration,
3-arm when/is, multi-call within arms, and when+else branch.

* feat: enhance PHP type resolution for generics and member access in foreach loops

* feat: Phase 6.1 type resolution gap closure — container descriptors, recursive_pattern, class fields

Add 13 missing container type descriptors (Collection, MutableMap, Stream, SortedSet, etc.)
to CONTAINER_DESCRIPTORS for correct element type extraction across C#, Kotlin, and Java.

Extend C# pattern binding to handle recursive_pattern (obj is User { Name: "Alice" } u)
in both is-expression and switch expression contexts.

Add TypeScript class field declaration support (public_field_definition) so for-loop
iteration over this.fieldName resolves element types from class field type annotations.
Includes file-scope fallback in resolveIterableElementType and nested member_expression
handling for this.field.method() patterns.

* docs: add type resolution system documentation with roadmap

Covers the full architecture, resolution tiers (0-2), scope model,
language feature matrix, container descriptors, pipeline integration,
and the Phase 7-9 roadmap for cross-scope propagation, field-type
resolution, and return-type-aware binding.

* feat: Phase 6.2 review findings — C# nested member foreach, C++ deref range-for, Java field_access

Close two gaps found during fourth-pass review of PR #318:

- C# foreach (var user in this.data.Values): nested member_access_expression
  now extracts intermediate property name for scopeEnv lookup
- C++ for (auto& user : *ptr): pointer_expression dereference now recognized
  as range-for iterable

Root causes fixed in shared infrastructure:
- extractSimpleTypeName: add template_type (C++) and generic_name (C#)
- extractGenericTypeArgs: add generic_name for consistency
- type-env.ts: unwrap variable_declaration wrapper in field_declaration
  for declarationTypeNodes capture (zero-allocation manual loop)

Additional review findings addressed:
- Java: add field_access handler for this.data.values() in method_invocation
- C++ pointer_expression: document limitation (*identifier only)
- TypeScript: fix stale comment about property_identifier

All 525 tests pass (278 unit + 247 integration).

* perf: optimize type resolution pipeline — worker threshold, skip graph phases, AST pruning

- Skip worker pool creation for small repos (<15 files or <512KB) — saves 100-400ms
- Add skipGraphPhases option to runPipelineFromRepo to skip MRO/community/process phases
- Add conservative SKIP_SUBTREE_TYPES for leaf-only AST nodes (string, comment, number)
- Pre-compute interestingNodeTypes set — single Set.has() replaces 3 checks per node
- Add fastStripNullable — skip full stripNullable for simple identifiers (90%+ case)
- Replace .children?.find() with manual for loops in extractFunctionName (no array alloc)
- Add hookTimeout: 120000 to vitest.config.ts for CI beforeAll hooks

* fix: review findings — remove template_string from SKIP_SUBTREE_TYPES, handle bare nullable keywords

- Remove template_string and concatenated_string from SKIP_SUBTREE_TYPES
  (template literals contain interpolated expressions with typed code)
- Add FAST_NULLABLE_KEYWORDS check to fastStripNullable for behavioral
  parity with stripNullable on bare null/undefined/void/None/nil
- Add explanatory comment on extractPendingAssignment scopeEnv guard

* feat: add type resolution system and roadmap documentation
2026-03-17 17:10:22 +00:00
Chirag Nighutandchirag-nighut f6b92d4f13 fix(resolver): fix for same-directory python imports (#328)
* fix(resolver): prefer same-directory file for Python bare imports

Python's sys.path searches the importing script's own directory first,
so `import user` from services/auth.py should resolve to services/user.py
even if models/user.py was indexed first in the suffix index.

Add a proximity check in resolveImportPath that consults the existing
dirMap index (O(1)) before falling back to global suffix matching, for
single-segment bare Python imports only.

Made-with: Cursor

* refactor(resolver): replace dirMap scan with O(1) allFiles.has() for proximity check

The previous implementation used index.getFilesInDir() + siblings.find()
which had two issues:
- dirMap stores all suffix levels, so getFilesInDir('services') matched
  files from every directory named 'services/' across the repo — false
  positives in monorepos
- siblings.find() was an O(n) linear scan despite the O(1) claim

Replace with a direct allFiles.has(importerDir + '/' + name + '.py') lookup.
allFiles is a Set<string> of full repo-relative paths, so the lookup is
truly O(1) and exact — no suffix ambiguity possible.

Also fixes: dead code (the '.rb' branch was unreachable since the outer if
gates on Python), and Windows backslash handling via normalize before split.

Made-with: Cursor

* test: remove flag-based demo from unit tests

Made-with: Cursor

* fix(resolver): cover package __init__.py in proximity check and add end-to-end CALLS test

- Also try importerDir/name/__init__.py as a second O(1) candidate so that
  `import user` resolves to services/user/__init__.py when the target is a
  package rather than a bare module file
- Add unit tests for package proximity, __init__.py fallback, and Windows
  backslash path handling
- Add end-to-end CALLS assertion to the bare-import integration test:
  svc.execute() must resolve to UserService#execute in services/user.py,
  proving the fix propagates correctly through the type inference pipeline

Made-with: Cursor

* refactor: extract Python import resolution into resolvers/python.ts

- Move PEP 328 relative import and proximity-based bare import logic
  from standard.ts into a dedicated resolvers/python.ts (resolvePythonImport)
- Dispatch Python imports from resolveLanguageImport in import-processor.ts,
  consistent with how Ruby, PHP, and other languages are handled
- standard.ts is now language-agnostic (TS/JS aliases, Rust paths, suffix fallback)
- Add inline comment on __init__.py vs .py resolution order edge case
- Update unit tests to call resolvePythonImport directly

Made-with: Cursor

* docs: add PEP 302/328/451 references to python.ts comments

Made-with: Cursor

* fix(python): address reviewer comments on PEP compliance

- Guard dirParts.pop() against over-traversal: return null when dot
  count exceeds directory depth, matching CPython's ImportError for
  'attempted relative import beyond top-level package' (PEP 328)
- Swap __init__.py / .py check order to match CPython's finder
  precedence (PEP 451 §4); coexistence is physically impossible so
  order only matters for spec compliance
- Fix overstated PEP 302 comment: proximity check is a static
  heuristic, not a sys.path[0] implementation
- Acknowledge namespace package gap (PEP 420) in docstring
- Add unit test for over-traversal guard

Made-with: Cursor

* test(python): document namespace package resolution behaviour

Add two unit tests for PEP 420 namespace packages (directory with no
__init__.py): bare import returns null (expected — no file exists to
resolve to, CPython sets __file__ = None), while the submodule form
(import user.model) resolves correctly via suffixResolve fallback.

Made-with: Cursor

---------

Co-authored-by: chirag-nighut <chiragnighut@gmail.com>
2026-03-17 16:32:34 +00:00
Zak 64b7ff0061 docs: add Codex MCP configuration to README (#236)
- Add Codex to Editor Support table
- Add Codex manual config example (~/.codex/config.toml)
- Update editor list in usage table

Fixes #131

Made-with: Cursor
2026-03-16 21:23:14 +00:00
Gergő Magyar f2d3df48f6 feat: Phase 5 type resolution — chained calls, pattern matching, class-as-receiver (#315)
* feat: Phase 5 type resolution — chained calls, pattern matching, class-as-receiver, code review fixes

Phase 5.1: Chained method call resolution (depth-capped at 3)
- resolveChainedReceiver() resolves a.getUser().save() by walking the chain
  and looking up intermediate return types from the SymbolTable
- extractReceiverNode() + extractCallChain() shared in utils.ts
- receiverCallChain on ExtractedCall for worker path parity
- MAX_CHAIN_DEPTH=3 enforced in both extraction and resolution

Phase 5.2: Pattern matching binding extractors
- PatternBindingExtractor type added to LanguageTypeConfig
- declarationTypeNodes map tracks original type AST nodes for generic unwrapping
- Rust: if let Some(x)/Ok(x) unwrapping with extractGenericTypeArgs
- Java: instanceof pattern variables (Java 16+)
- C#: is-pattern disambiguation fixture (already working via extractDeclaration)

Phase 5.5d: Python standalone type annotations (name: str)
- expression_statement with type child now captured in DECLARATION_NODE_TYPES

Phase 5.5e: ReceiverKey collision fix for overloaded methods
- receiverKey preserves @startIndex to prevent same-name method collisions
- lookupReceiverType does prefix scan with ambiguity refusal

Class-as-receiver for static method calls (#289)
- UserService.find_user() now resolves via ctx.resolve() tiered lookup
- Respects import scoping — no false positives from unrelated packages

Code review fixes:
- Extracted CALL_EXPRESSION_TYPES + extractCallChain to utils.ts (eliminated duplication)
- Converted resolveChainedReceiver from recursion to loop (no exposed depth param)
- Added depth cap to extractReturnTypeName (defense against nested wrapper types)
- Replaced lookupFuzzy with ctx.resolve for class-as-receiver (architecturally consistent)

Closes #289

Test coverage: 6 new fixtures, 12+ new unit tests, 7 new integration test suites

* fix: Ruby chain calls, Rust Err(x) unwrap, Enum class-as-receiver (#315)

Address three per-language gaps identified in Phase 5 code review:

- Ruby: add `method`/`receiver` field fallbacks to extractCallChain
  (tree-sitter-ruby uses different field names than other grammars)
- Rust: handle `Err(e)` pattern binding via typeArgs[1] from Result<T,E>
- Enum: include Enum type in class-as-receiver filter (both paths)

Integration tests added for all three fixes.

* fix: chain base type resolution parity between serial and worker paths (#315)

- Worker path: add typeEnv.lookup for chain base receiver after extraction
  (typed parameters like `fn process(svc: &UserService)` were silently lost)
- Serial path: add ctx.resolve class-as-receiver fallback for chain base
  (class-name chains like `UserService.find_user().save()` failed)
- Fix misleading comment in parse-worker.ts that described unimplemented logic
- Integration tests: typed-parameter chain, static class-name chain

* fix: Kotlin chain call extraction, createClassNameLookup Enum/Struct (#315)

- Kotlin: extractCallChain now handles navigation_expression → navigation_suffix
  AST structure (Kotlin's call_expression has no 'function' field)
- createClassNameLookup: include Enum and Struct alongside Class for consistent
  constructor recognition in extractInitializer
- Integration test: kotlin-chain-call fixture verifying svc.getUser().save()
2026-03-16 19:38:09 +00:00
Gergő Magyar 5fa73bafdf feat: Phase 4 type resolution — nullable unwrapping, for-loop typing, assignment chains, code review fixes (#310)
* feat: Phase 4 type resolution — nullable unwrapping, for-loop typing, assignment chains, Kotlin return types

Phase 4.1: Nullable/optional chain unwrapping
- Add stripNullable utility in shared.ts for stripping nullable wrappers
- Apply in lookupInEnv to unwrap User | null → User, User? → User before receiver lookup
- Handles TS union, Kotlin/C#/Swift nullable suffix, Python Union[T, None], Rust Option<T>
- Enables receiver-type disambiguation through ?. optional chaining

Phase 4.2: For-loop element typing (Tier 0 — Java/C#/Kotlin)
- Add ForLoopExtractor type and forLoopNodeTypes to LanguageTypeConfig
- Java enhanced_for_statement, C# foreach_statement, Kotlin for_statement extractors
- Only explicit element types in AST (Tier 0); inference-based languages deferred

Phase 4.3: Assignment chain propagation (single-pass, depth-1)
- Add PendingAssignmentExtractor to LanguageTypeConfig with per-language implementations
- Handles TS/JS variable_declarator, Rust let_declaration, Python assignment,
  Go short_var_declaration, C# equals_value_clause, Java/Kotlin variable_declarator
- Single post-walk propagation pass (no fixpoint iteration per Sorbet/Pyright design)
- Resolves const b = a; b.save() when a has known type from Tier 0/1/1b

Phase 4.5: Kotlin return type extraction (bug fix)
- Fix extractMethodSignature to handle Kotlin user_type after function_value_parameters
- Remove lenient test assertions, add strict disambiguation proof

Integration tests across 10+ languages with competing same-name methods
and negative assertions proving disambiguation.

* fix: per-language assignment chain gaps from code review

- Kotlin: new extractKotlinPendingAssignment for property_declaration →
  variable_declaration AST (Java's variable_declarator doesn't exist in Kotlin)
- Go: handle var_spec (var b = u) alongside short_var_declaration (:=)
- PHP: add extractPendingAssignment for $alias = $user with $ prefix preserved

Integration tests added for all three languages with competing
same-name methods and negative disambiguation assertions.

* fix: code review fixes — DRY nullable keywords, avoid array allocations, clarify depth comment

Addresses findings from 6-agent code review on PR #310:

- Move stripNullable JSDoc to correct position (was orphaned above NULLABLE_KEYWORDS)
- DRY: reuse NULLABLE_KEYWORDS set in pipe-split filter instead of inline strings
- Replace node.children.find() with findChildByType/manual loops in jvm.ts,
  go.ts, csharp.ts to avoid unnecessary array allocations per tree-sitter call
- Clarify "depth-1" comment in type-env.ts: single-pass resolves multi-hop
  chains when forward-declared; reverse-order is depth-1 only
- Annotate extractGenericTypeArgs as Phase 5 infrastructure (zero production callers)
- Re-export PendingAssignmentExtractor from index.ts for API consistency
- Add explicit return undefined in Go extractPendingAssignment
- Remove redundant child.text === '=' check in Kotlin extractor

Test coverage:
- 20 new unit tests: stripNullable edge cases, per-language assignment chains,
  reverse-order depth limitation, nullable lookup resolution
- 15 new integration tests: multi-hop chains (a→b→c), nullable+chain combined
  (User|null + alias), Python User|None through stripNullable path
- 3 new fixtures: ts-multi-hop-chain, ts-nullable-chain, python-nullable-chain

* fix: third-pass review — walrus chain, scanner allocations, Kotlin variable_declaration, C# type guard

Addresses 4 new findings from third-pass CI review:

1. Python walrus operator (:=) now handled by extractPendingAssignment —
   named_expression nodes propagate alias chains alongside regular assignment
2. Scanner .namedChildren.find()/.some() in jvm.ts replaced with
   findChildByType() — consistent with 98daed4 code review fixes
3. Kotlin extractPendingAssignment extended to handle variable_declaration
   nodes in addition to property_declaration (function-local val/var)
4. C# extractPendingAssignment early-returns for is_pattern_expression and
   field_declaration nodes (never contain variable_declarator children)

Integration tests:
- Python: walrus chain (alias := u) with disambiguation (5 tests, 1 fixture)
- Kotlin: assignment chain with typed declarations (5 tests, 1 fixture)
- C#: assignment chain + is-pattern coexistence (6 tests, 1 fixture)
- Unit: Python walrus propagation (1 test)

* feat: nullable wrapper unwrapping + C++ assignment chains

Gaps 1, 2, 4 from code review — architectural changes to type resolution:

1. extractSimpleTypeName now unwraps nullable wrapper generics:
   - Optional<User> → "User" (Java), Option<User> → "User" (Rust),
     Maybe<User> → "User" (Kotlin Arrow/Haskell-style)
   - Containers (List, Map) and async wrappers (Promise, Future) are NOT
     unwrapped — methods are called on the container, not the inner type
   - Uses existing extractGenericTypeArgs (now production-active, was dead code)
   - NULLABLE_WRAPPER_TYPES set: Optional, Option, Maybe

2. C++ extractPendingAssignment added for auto alias chains:
   - auto alias = user; alias.save() now propagates User type
   - Handles pointer/reference declarators, auto/decltype(auto)

3. Updated existing Rust test: Option<User> parameter now correctly
   stores "User" instead of "Option" in TypeEnv

Integration tests with fixtures for Java Optional, Rust Option, C++ auto
chain. Full pipeline resolution marked .todo — requires call-processor
enhancement (TypeEnv stores correct types but call-processor needs
additional work to produce CALLS edges for these patterns).

Unit tests: 196 passed (7 new). Integration: all 9 languages green.

* fix: resolve .todo tests — stale dist/ was the root cause

The Rust Option<User> and C++ auto assignment chain integration tests
were marked .todo because the pipeline didn't produce CALLS edges.
Root cause: dist/ was compiled from pre-Phase 4 source and lacked:
- NULLABLE_WRAPPER_TYPES unwrapping in extractSimpleTypeName
- C++ extractPendingAssignment

After npm run build, all tests pass as real assertions:
- Rust: alias.save() resolves to User#save via Option<User> unwrap + chain
- C++: alias.save() and rAlias.save() resolve via auto assignment chain
  with correct disambiguation (User vs Repo)

Only remaining .todo: Rust user.unwrap().save() (Phase 5 — chained
return type inference, not a TypeEnv issue).
2026-03-16 15:21:54 +00:00
fbff6d08c0 feat(ingestion): respect .gitignore and .gitnexusignore during file discovery (#231)
* feat(ingestion): respect .gitignore and .gitnexusignore during file discovery

Add support for excluding files from indexing based on .gitignore and
.gitnexusignore patterns. Previously, GitNexus used only a hardcoded
ignore list, causing significant index pollution in repositories with
git-ignored directories containing code (e.g., Docker-mounted volumes).

Changes:
- Add `ignore` package for gitignore-spec pattern matching
- Add `loadIgnoreRules()` to parse .gitignore + .gitnexusignore
- Add `createIgnoreFilter()` returning glob-compatible IgnoreLike object
- Integrate filter into glob's `ignore` option for directory-level pruning
- Remove post-glob `.filter()` call (now handled during traversal)

The hardcoded DEFAULT_IGNORE_LIST remains as fallback for non-git repos.

Closes #228

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ingestion): address review feedback on ignore filtering

- Distinguish ENOENT vs EACCES in loadIgnoreRules (warn on permission errors)
- Add GITNEXUS_NO_GITIGNORE env var to bypass .gitignore parsing
- Fix bare-name pattern matching in childrenIgnored (check both with/without trailing slash)
- Rename isIgnoredDirectory to isHardcodedIgnoredDirectory for clarity
- Add clarifying comments for design decisions (D2 negation, D3 dot:false redundancy)
- Add tests for bare-name patterns, file-glob patterns, EACCES handling, env var

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ingestion): address second round of review feedback

- G1: Document GITNEXUS_NO_GITIGNORE in `analyze --help` and log when active
- G2: Add comment clarifying path-scurry POSIX normalization contract
- G3: Add IgnoreOptions interface — env var now falls back, callers can
  pass `noGitignore` explicitly for testability and future CLI flag
- G4: Add integration test verifying walkRepositoryPaths respects the env var

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ingestion): gracefully skip files with unavailable tree-sitter grammars

Port unsupported language resilience from PR #301 by @jecanore.
- Make Kotlin import optional (like Swift) in parser-loader and parse-worker
- Add worker-local isLanguageAvailable() with filePath param for tsx distinction
- Track and log skipped files per language in both sequential and worker paths
- Add skippedLanguages to ParseWorkerResult for worker→main aggregation
- Add isLanguageAvailable unit tests

Refs: #301, #155, #228

Co-Authored-By: jecanore <juan@housingbase.io>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(e2e): add ignore + language-skip end-to-end test with fixture repo

Add a fixture repo (test/fixtures/ignore-and-skip-repo/) with .gitignore,
.gitnexusignore, TypeScript source files, and a Swift file to exercise
all three features end-to-end:

- File discovery: verifies .gitignore excludes data/ and *.log,
  .gitnexusignore excludes vendor/, source files are discovered
- Parsing: verifies TypeScript files produce Function nodes and DEFINES
  relationships, Swift files are skipped gracefully when grammar is
  unavailable

Add the test to the standalone group in ci-integration.yml and coverage job.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): move ignore-and-skip-e2e test to e2e group per review feedback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): use temp directory instead of fixture for e2e ignore test

The fixture's .gitignore prevented data/seed.json and debug.log from
being committed — these files would be missing after checkout in CI.

Switch to creating the entire test structure in a temp directory via
beforeAll (matching filesystem-walker.test.ts pattern). This ensures
all files exist regardless of git ignore rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): correct graph API usage in e2e ignore test

Use graph.nodes property getter instead of graph.getNodes(), and check
Function node filePath instead of non-existent File nodes (File nodes
are created by processStructure, not processParsing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add workflows permission to ci-integration.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: change workflows permission to write per review

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: move workflows permission from ci-integration.yml to ci.yml caller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): fix Claude workflows for fork PRs, remove misplaced workflows perm

Three issues prevented Claude from running on fork PRs:

1. claude-code-review.yml lacked workflows:write — push failed when
   fork PRs modify .github/workflows/ files
2. claude.yml had no fork PR support — checked out main and couldn't
   fetch the fork's branch from origin
3. Cleanup step unconditionally deleted branches even when push failed,
   breaking the concurrent claude.yml workflow

Also removes workflows:write from ci.yml's integration job — CI tests
don't need that permission. The permission belongs on the claude
workflows that push fork branches.

Changes:
- Add workflows:write to both claude workflow permissions blocks
- Add fork PR detection + branch push/cleanup to claude.yml
- Add step id to push-fork; cleanup only runs if push succeeded
- Pass branch names via env vars to prevent shell injection (security)
- Add concurrency groups to prevent race conditions between workflows
- Remove misplaced workflows:write from ci.yml integration job

* fix(ci): use GitHub API for fork branch refs instead of git push

GITHUB_TOKEN cannot have 'workflows' permission — it's only valid for
PATs and GitHub Apps. This means git push fails whenever a fork PR
modifies .github/workflows/ files.

Replace git push with the GitHub REST API (POST/PATCH /git/refs) to
create temporary branch refs. The API creates a pointer to the
already-existing PR head commit without triggering the workflow file
push protection. Similarly, cleanup uses DELETE /git/refs instead of
git push --delete.

Also removes the invalid 'workflows: write' from permissions blocks.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jecanore <juan@housingbase.io>
Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
2026-03-16 13:26:20 +00:00
Gergő Magyar 6c18ae08f7 feat: return type inference, doc-comment parsing, and per-language type extractors (#284)
* feat: Phase 3 — return type inference, generic args extraction, Ruby YARD type extractor

Three architectural improvements to the type resolution system:

1. Return type inference — wire extractMethodSignature returnType through
   SymbolDefinition into call-processor. When var = callee() and callee
   has a known return type, bind var to that type. Handles Promise<T>
   unwrapping, nullable stripping, pointer/reference removal.

2. Generic type argument extraction — new extractGenericTypeArgs() utility
   that extracts type parameters from List<User> → ['User']. Handles
   TS/Java/Kotlin/C#/Rust generic syntax. Building block for for-loop
   variable typing.

3. Ruby dedicated type extractor — replaces the stub with YARD annotation
   parsing (@param name [Type]), handling qualified types, nullable types,
   and singleton methods. Ruby now has real type resolution.

Unit tests: 127 → 192+ (type-env) + 65 (symbol-table, call-processor) + 18 (generics)
Integration tests: 8+ new test cases with fixtures across TS/Python/Go/Java/Ruby

* fix: Phase 3 gaps — WRAPPER_GENERICS correctness, Ruby :: qualifier, namespaced constructors

- Remove collection types (List, Array, Vec, Set) from WRAPPER_GENERICS to prevent
  false CALLS edges (e.g. List<User> no longer unwraps to User)
- Add :: qualifier handling in extractReturnTypeName for Ruby/C++/Rust namespaced types
- Add Ruby `constant` and `scope_resolution` node types to shared extractors
- Extract shared extractRubyConstructorAssignment helper (dedup type-env.ts + ruby.ts)
- Add integration tests for return type inference: Python, TypeScript, Go, Java, Ruby
- Add Ruby namespaced constructor fixture (Models::UserService.new)
- Add unit tests for collection reclassification and :: qualifiers

* feat: Phase 4 — CONSTRUCTOR_BINDING_SCANNERS for all languages + return type inference tests

Add CONSTRUCTOR_BINDING_SCANNERS for 6 missing languages, completing
return type inference coverage across all 11 supported languages:

- TypeScript/JS: variable_declarator with call_expression, unwraps await
- Go: short_var_declaration single-assignment (skips multi-return, new/make)
- Java: local_variable_declaration with `var` type + method_invocation
- C#: variable_declaration with implicit_type (var) + invocation_expression
- Rust: let_declaration without type annotation, handles mut_pattern
- PHP: assignment_expression with function_call_expression

Also adds property_identifier to extractSimpleTypeName for qualified
member calls (repo.getUser → getUser), fixing namespaced constructor
inference that was previously a known limitation.

Integration tests added for all 11 languages with correct label
assertions (Function vs Method per language's tree-sitter queries).

* refactor: merge CONSTRUCTOR_BINDING_SCANNERS into per-language LanguageTypeConfig

Eliminates the parallel dispatch map in type-env.ts by moving all 11
constructor binding scanners into their respective type-extractors/*.ts
files as `scanConstructorBinding` on LanguageTypeConfig.

- Add ConstructorBindingScanner type to types.ts
- Add shared helpers: hasTypeAnnotation, unwrapAwait, extractCalleeName
- Move scanners to typescript.ts, jvm.ts, python.ts, php.ts, go.ts,
  rust.ts, swift.ts, c-cpp.ts, csharp.ts, ruby.ts
- Fix `any` types in C# scanner → SyntaxNode | null
- Delete ~300 lines from type-env.ts (CONSTRUCTOR_BINDING_SCANNERS map)
- Update buildTypeEnv to use config.scanConstructorBinding

All 143 type-env unit tests and all 10 language integration suites pass.

* fix: remove unused import, fix any type in Java scanner, update stale comment

- Remove unused extractCalleeName import from jvm.ts
- Fix (c: any) → (c: SyntaxNode) in Java scanner
- Update stale CONSTRUCTOR_BINDING_SCANNERS reference in ruby.ts comment

* fix: C# and PHP return type inference — scanner fixes, method signature extraction, and cross-file resolution

Addresses code review findings on PR #284:

C# scanner (csharp.ts):
- Fix type node lookup: iterate children instead of childForFieldName('type')
  which returns undefined in tree-sitter-c-sharp
- Fix initializer lookup: handle direct invocation_expression children
  (no equals_value_clause wrapper in tree-sitter-c-sharp)

C# return type extraction (utils.ts):
- Add 'returns' field check to extractMethodSignature — tree-sitter-c-sharp
  uses 'returns', not 'type', for method return types

C# cross-file resolution (call-processor.ts + fixture):
- Add constructor binding verification to sequential processCalls path
  (was only in the worker processCallsFromExtracted path)
- Add ReturnType.csproj to csharp-return-type fixture
- Update fixture namespaces to use ReturnType.Models/ReturnType.Services
  prefix (matches real C# project conventions)

PHP scanner (php.ts):
- Extend scanConstructorBinding to handle member_call_expression
  ($this->getUser() patterns), not just function_call_expression

Shared (shared.ts):
- Add member_access_expression to extractSimpleTypeName qualified-names
  block (C# method calls like svc.GetUser())

Tests:
- Add Repo.cs/Repo.php disambiguation fixtures (two Save methods)
- Strengthen C# and PHP return type tests with hard disambiguation assertions
- Add C# scanner unit tests and return type extraction test

* feat: per-language ReturnTypeExtractor + doc-comment @param parsing for PHP, JS, Ruby

Add ReturnTypeExtractor to LanguageTypeConfig interface with implementations
for Ruby (YARD @return), PHP (PHPDoc @return), and JS/TS (JSDoc @returns).
The fallback is wired in both parsing-processor and parse-worker paths,
activating only when extractMethodSignature finds no AST-based return type.

Also add doc-comment @param type extraction for PHP and JS/TS, following
Ruby's existing collectYardParams pattern. This enables parameter.method()
resolution in loosely-typed codebases using PHPDoc @param or JSDoc @param.

Additional fixes from PR #284 code review:
- Go: add selector_expression + field_identifier to extractSimpleTypeName
  (enables package-qualified factory calls like models.NewUser())
- Ruby: broaden scanConstructorBinding to capture plain call assignments
  (user = get_user()) in addition to Class.new patterns
- Ruby: harden return-type fixture with disambiguation (two save methods)

Test coverage: +14 new integration tests across Go, Ruby, PHP, JS/TS

* fix: JSDoc async return type, PHP attribute walkers, and $this receiver disambiguation

Three fixes from fourth-pass code review on PR #284:

1. JSDoc `@returns {Promise<User>}` no longer stripped to `Promise` — extractReturnType
   now uses sanitizeReturnType (preserves generics) instead of normalizeJsDocType
   (which stripped them before extractReturnTypeName could unwrap WRAPPER_GENERICS).

2. PHP 8+ `#[Attribute]` and JS `@decorator` nodes no longer break doc-comment walkers.
   Both extractReturnType and collect*Params functions now skip attribute_list/decorator
   nodes instead of breaking on them as named siblings.

3. PHP `$this->method()` now provides receiverClassName for disambiguation.
   When two classes define the same method, the enclosing class narrows candidates
   via ownerId matching in call-processor, preventing false no-binding results.

* fix: sanitizeReturnType dot corruption, JS test assertions, Ruby constant receiver

- Remove redundant dot-path stripping from sanitizeReturnType that corrupted
  qualified names inside generics (e.g. Promise<models.User> → User>)
- Split JS async fixture into separate files and add negative assertions
  to properly verify disambiguation (mirroring PHP test pattern)
- Accept 'constant' node type in Ruby scanConstructorBinding for factory
  call assignments (SERVICE = build_service())
- Add 'constant' to SIMPLE_RECEIVER_TYPES so extractReceiverName handles
  Ruby constant receivers (SERVICE.process)

* fix: nested generic arg splitting, JS/Ruby test false positives

- Replace naive comma split in extractReturnTypeName with bracket-balanced
  extractFirstGenericArg so nested types like Future<Result<User, Error>>
  unwrap correctly instead of producing malformed "Result<User"
- Add CompletableFuture to WRAPPER_GENERICS for Java async unwrapping
- Split js-jsdoc-return-type fixture models.js into user.js/repo.js and
  add negative assertions to prove disambiguation (not just file match)
- Split ruby-constant-factory-call fixture into separate service files
  and add negative assertions against AdminService resolution

* fix: review findings — receiverClassName parity, Rust wrappers, Go multi-return, Kotlin/Swift qualified calls

P1: Sequential path now includes receiverClassName narrowing for PHP
$this->method() disambiguation (was missing vs worker path).

P2: Added Rc/Arc/Weak/MutexGuard/Cow + 6 more Rust Deref types to
WRAPPER_GENERICS (Box excluded — Java Swing collision). Extended
Kotlin/Swift scanners to handle navigation_expression callees.
Added Go multi-return support (user, err := f()) with blank/_/err/ok
guard + AST-level first-return extraction in extractMethodSignature.

P3: Extracted shared verifyConstructorBindings() eliminating 60 lines
of duplication between sequential and worker paths. Added return-type
inference integration tests for C++, Rust, Swift with competing
methods and negative disambiguation assertions.

* fix: Swift navigation_suffix unwrapping, Rust lifetime skipping, Kotlin disambiguation tests

- Swift scanConstructorBinding: handle tree-sitter wrapping qualified
  identifiers in navigation_suffix nodes
- Add extractFirstTypeArg to skip Rust lifetime parameters ('a, '_)
  when unwrapping wrapper generics like Ref<'_, User>
- Kotlin tests: add Repo class fixture with competing save() methods
  to prove disambiguation; assert no spurious edges on known gap
- Remove tree-sitter-kotlin from optionalDependencies (now regular dep)

* fix: C# null-conditional calls, Ruby YARD bracket-balanced split, PHPDoc alternate order, escapeValue hardening

- Add C# null-conditional call support (user?.Save()): tree-sitter query for
  conditional_access_expression, member_binding_expression in MEMBER_ACCESS_NODE_TYPES,
  receiver extraction via conditional_access_expression parent walk
- Fix Ruby YARD type parsing for nested generics (Hash<Symbol, User>): replace
  naive split(',') with bracket-balanced splitter respecting <> depth
- Add alternate YARD format (@param [Type] name) alongside standard (@param name [Type])
- Add alternate PHPDoc format (@param $name Type) alongside standard (@param Type $name)
- Harden escapeValue in kuzu-adapter.ts: escape \n and \r to prevent Cypher injection
- Integration tests: C# null-conditional fixture (5 tests), Ruby YARD generics fixture (6 tests)
- Unit tests: PHPDoc alternate order (2 tests), C# null-conditional call-form (updated)

* test: add Python static/classmethod integration tests (issue #289)

Verifies that classes using only @staticmethod/@classmethod have HAS_METHOD
edges connecting them to their child methods. This was the root cause of
issue #289 where context() and impact() returned empty for such classes.

Tests cover: HAS_METHOD edge emission, unique static method resolution
(create_user, delete_user), and ambiguous same-named method handling
(find_user on both UserService and AdminService — safely refused).

* fix: lbug batch escapeValue newline hardening, Rust ::default() scanner exclusion

- Apply \n/\r escaping to batch upsert escapeValue in lbug-adapter.ts:429
  (missed instance of the CREATE-path fix from ec4dca4)
- Exclude Rust ::default() from scanConstructorBinding to match
  extractInitializer behavior — avoids wasted cross-file lookups on
  the broadly-implemented Default trait
- Unit tests: 2 new scanner exclusion tests (::default and ::new)
- Integration tests: 6 new Rust ::default() constructor resolution tests
  with disambiguation fixture (User::default vs Repo::default)

* fix: C#/Rust async await unwrap, PHP backslash namespace, fallback escaping

- C# scanConstructorBinding: unwrap await_expression to find invocation_expression
  (var user = await svc.GetUserAsync() now produces constructor binding)
- Rust scanConstructorBinding: unwrap .await postfix via shared unwrapAwait helper
  (let user = get_user().await now produces constructor binding)
- extractReturnTypeName: handle PHP backslash namespace separator (\App\Models\User → User)
- fallbackRelationshipInserts: match batch escapeValue hardening with \n/\r escaping

Tests: 2 unit (type-env), 3 unit (call-processor), 7 integration (csharp+rust), 7 fixtures

* fix: C#/Rust async-binding test false positives — add competing types and negative assertions

C# fixture: add Order.cs with Order.Save(), change OrderService to return
Task<Order> via GetOrderAsync, add negative assertion proving user.Save()
does not resolve to Order#Save.

Rust fixture: split models.rs into user.rs/repo.rs, make process_user and
process_repo async fn, add bidirectional negative assertions proving no
cross-contamination between User#save and Repo#save.

* fix: C# async-binding broken assertion, bare wrapper type leak, JSDoc optional params

- Split Program.cs Main into ProcessUser/ProcessOrder so negative
  assertions use strict toBeUndefined() (matching Rust pattern)
- Guard bare wrapper types (Task, Promise, Option…) in
  extractReturnTypeName — return undefined instead of the wrapper name
- Update JSDOC_PARAM_RE to capture @param {Type} [optionalName] syntax

* fix: update symbol and relationship counts in documentation
2026-03-15 18:49:40 +00:00
Candido Sales GomesandClaude Opus 4.6 5a5850832c refactor: migrate from KuzuDB to LadybugDB v0.15 (#275)
* refactor: migrate from KuzuDB to LadybugDB v0.15

KuzuDB was archived (Apple acquisition, Oct 2025). LadybugDB is the
community fork with full API compatibility.

- Package swap: kuzu → @ladybugdb/core, kuzu-wasm → @ladybugdb/wasm-core
- Rename all internal paths: kuzu → lbug (adapters, schema, storage)
- Storage path: .gitnexus/kuzu → .gitnexus/lbug (with auto-cleanup)
- Add explicit VECTOR extension loading (required in v0.15)
- Update CI workflow, documentation, and all tests
- 1151 unit + 27 integration tests passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address code review findings (P1-P3)

P1: Fix WASM adapter to use getAll() API, wire cleanupOldKuzuFiles
into analyze command, add symlink path traversal protection.
P2: Cache VECTOR extension load state, batch augmentation engine
queries (20→4), fix web getCopyQuery for multi-language tables,
fix stale KuzuDB references, correct brainstorm package names.
P3: Complete lbug-wasm.d.ts type declarations, batch semantic
search per-label, update stale BM25 comment.

* chore: remove outdated KuzuDB migration brainstorming document

* fix: load FTS extension in MCP pool adapter on init

The read-only pool adapter never loaded the FTS extension, so all
QUERY_FTS_INDEX calls failed silently. This broke search-pool and
augmentation integration tests, and caused empty results in the
web UI server mode.

* feat: implement shared Database caching and connection reference counting

* feat: enhance KuzuDB migration handling and status reporting

* fix: mock cleanupOldKuzuFiles in local backend callTool tests

* fix: update mock for cleanupOldKuzuFiles and adjust imports in callTool tests

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 15:53:01 +00:00
Gergő Magyar 62242d5f44 feat: TypeEnvironment API with constructor inference, self/this/super resolution (#274)
* feat(type-env): constructor-call type inference for TypeEnv (Phase 1)

Add extractInitializer as a Tier 1 fallback in buildTypeEnv: when a
declaration node has no explicit type annotation, infer the type from
constructor-call patterns (new X(), X::new(), X::default(), $x = new X()).

Languages covered: TypeScript/JS, Java (var), Rust, PHP, C++ (auto).
Python/Kotlin/Swift deferred — need symbol-table access to distinguish
class constructors from function calls.

Adds 20 new unit tests covering constructor inference, annotation
precedence, and known limitations across all supported languages.

* fix(type-env): class-aware constructor resolution, multi-declarator fix

- Add collectClassNames pre-scan: walks AST to build Set<string> of
  class/struct names defined in the file
- C++ extractInitializer uses classNames.has() to verify identifier is
  a known class before inferring (auto x = User() resolves, auto x =
  getUser() does not — no false positives)
- Add InitializerExtractor type that receives classNames parameter
- Fix env.size gating: always call extractInitializer when available,
  so mixed declarators like const a: A = x, b = new B() resolve both
- Add env.has() guard in Java extractInitializer to skip already-bound vars
- Document Rust new/default whitelist rationale
- Pin all test assertions, add mixed multi-declarator test case

* fix(type-env): resolve Self/self/static/parent to actual type names

- Rust: Self::new()/Self::default() resolves to enclosing impl type
- PHP: new self()/static() resolves to enclosing class, parent() to superclass
- Rust: Tier 0 annotation guard prevents overwrite by constructor inference
- Rust: mut_pattern handling in extractVarName for let mut bindings
- TS: fix misleading comment in extractInitializer
- 58 tests passing (3 new Self/self resolution tests)

* perf(type-env): single-pass AST walk with closure-scoped state

Refactors buildTypeEnv to use closures instead of passing mutable state
as parameters. classNames, env, and config are captured by the inner
walk and extractTypeBinding functions — no parameter mutation.

- Eliminates separate collectClassNames pre-scan (O(2n) → O(n))
- config looked up once per file instead of per-node
- 29 fewer lines

* feat(type-env): constructor-inferred type resolution for all languages

Add cross-file constructor type inference to the ingestion pipeline,
enabling receiver-type disambiguation for member calls like
`user.save()` when the variable is assigned from a constructor without
explicit type annotations.

Pipeline changes:
- Add extractInitializer to Python and Swift type extractors
- Add CONSTRUCTOR_BINDING_SCANNERS for Python, Swift, C/C++ in type-env
- Wire constructorBindings through parse-worker → parsing-processor →
  pipeline → processCallsFromExtracted
- Rewrite resolveCallTarget receiver-type filtering (step D) to use
  tiered import resolution (same-file → import-scoped → global) before
  falling back to fuzzy ownerId matching
- Use collectTieredCandidates for constructor binding verification
  instead of raw lookupFuzzy

Bug fixes:
- Fix C++ inline method query: @definition.method was captured on
  field_declaration_list instead of function_definition, causing wrong
  parameterCount for all inline class methods
- Fix parse-worker accumulated/flush results missing constructorBindings

CI changes:
- Add swift.test.ts to ci-integration pipeline group and coverage job
- Update ci-report to fetch base branch (main) coverage for delta
  reporting instead of showing config thresholds
- Add per-suite timing breakdown table (unit/integration/total)
- Add expandable skipped test details section

Tests: 288 passed, 4 skipped (swift — macOS only) across 10 languages
- 36 new constructor-inferred integration tests (4 per language)
- 10 fixture directories with cross-file constructor patterns
- TypeScript, JavaScript, Java, Kotlin, Python, PHP, Rust, Go, C++, Swift

* fix(type-extractors): add type assertion for LanguageTypeConfig

* feat(ruby): constructor-inferred type resolution and self-receiver mapping

Add Ruby User.new constructor binding scanner to type-env, enabling
receiver-type disambiguation for member calls like user.save vs repo.save.
Add self/this → enclosing class resolution in lookupTypeEnv so self.method()
calls resolve to the correct class even when the method name is ambiguous.

* docs: update README with constructor inference and self/this resolution details

* refactor(ingestion): unified ResolutionContext replaces fragmented map passing

Introduce createResolutionContext() as the single resolution API for all
processors. Eliminates duplicated tier-selection logic, fixes heritage
namedImportMap bug, and adds per-file resolution caching.

- NEW resolution-context.ts: closure-factory with resolve(), per-file cache,
  TIER_CONFIDENCE constant, and shared ResolutionTier type
- DELETE symbol-resolver.ts: zero production importers, logic now in
  resolution-context.ts
- call-processor: all functions take ctx instead of 6 separate maps,
  collectTieredCandidates removed (ctx.resolve replaces it),
  D4 redundant re-resolve eliminated
- heritage-processor: takes ctx, resolveHeritageId helper extracts
  repeated 14-line fallback pattern, namedImportMap now included
- import-processor: takes ctx, dead createImportMap/createPackageMap/
  createNamedImportMap factories removed
- pipeline: creates single ctx, wires onProgress to all processors,
  logs cache hit rate in dev mode
- Tier renamed: unique-global → global (honest about returning all candidates)
- Tests migrated: 1178 unit + 84 integration passing

* feat(type-env): self/this/super resolution, TypeEnvironment API, and review fixes

Add cross-language receiver keyword resolution:
- self/this/$this → enclosing class name via AST walk
- super/base/parent → parent class name via heritage AST extraction
  (8 grammar variants: TS/JS, Java, Python, Ruby, C#, PHP, Kotlin, C++, Swift)
- D-phase widening in resolveCallTarget for super→parent method dispatch

Introduce TypeEnvironment API replacing loose TypeEnvResult + lookupTypeEnv:
- buildTypeEnv() returns TypeEnvironment with .lookup() method
- Single-pass AST walk merges constructor binding scan (was separate traversal)
- ClassNameLookup type replaces over-broad ReadonlySet<string> facade
- Memoized class name lookups to avoid redundant SymbolTable scans

Code review fixes (6 agents, 11 findings):
- Replace ctx.resolve(name, '') hack with direct symbols.lookupFuzzy()
- Extract scope key helpers (extractFuncNameFromScope, receiverKey)
- Simplify D-phase from 5 steps to 4 with deduped typeNodeIds
- Remove C from CONSTRUCTOR_BINDING_SCANNERS (YAGNI — C has no constructors)
- Cache Map reuse in ResolutionContext to reduce GC pressure
- Remove unused TieredCandidates import

Integration tests for self/this, parent, and super resolution across all
12 supported languages with per-language fixture directories.

* fix(type-env): generic parent resolution, TS cast inference, C++ brace-init

Fix generic parent class breaking super resolution:
- extractParentClassFromNode now uses extractSimpleTypeName to strip
  generic params (Base<T> → Base) and qualified names (models.Model → Model)
- Affects TS, Java, Python, C# heritage extraction

Fix TypeScript new X() as T / new X()! missed inference:
- Unwrap as_expression and non_null_expression before checking for
  new_expression in extractInitializer

Fix C++ brace-init User{} missed inference:
- Handle compound_literal_expression with type_identifier child
  in extractInitializer

Clean up deprecated lookupTypeEnv:
- Remove standalone lookupTypeEnv export, migrate all callers to
  TypeEnvironment.lookup() method
- Update all 80+ test assertions to use the new API

Integration test fixtures added:
- typescript-cast-constructor-inference (new X() as T, new X()!)
- typescript/java/csharp/kotlin-generic-parent-resolution
- cpp-brace-init-inference (auto x = User{})

* fix(type-extractors): Go &User{}, TS double-cast, Swift .init inference

Fix Go pointer-to-struct literal not inferred:
- Unwrap unary_expression (address-of &) before composite_literal check
- user := &User{} now correctly infers type User

Fix TypeScript double-cast only unwrapping one level:
- Change if to while loop for nested as_expression/non_null_expression
- new User() as unknown as Admin now correctly infers type User

Fix Swift User.init(name:) explicit init call missed:
- Handle navigation_expression callee with .init suffix in extractInitializer

Integration test fixtures:
- go-pointer-constructor-inference (&User{}, &Repo{})
- typescript-double-cast-inference (as unknown as T)

* feat: Rust struct literal, Python qualified ctor, Go new(), Swift .init scanner

- Rust: handle struct_expression in extractInitializer (User { name: "alice" })
- Python: support attribute nodes in extractInitializer (models.User("alice"))
  and the cross-file scanner — extractSimpleTypeName handles qualified names
- Go: handle new(User) built-in in extractGoShortVarDeclaration
- Swift: extend CONSTRUCTOR_BINDING_SCANNERS to handle navigation_expression
  callee for User.init(name:) cross-file resolution

Unit tests: 87 → 96 (Rust struct literal, Go new(), Python qualified ctor,
Python scanner qualified, plus edge cases)
Integration tests: 4 new describe blocks with fixtures

* fix: Rust Self{} resolution, C++ scoped brace-init, PHP promotion params, Ruby constants

- Rust: resolve Self {} struct literal to enclosing impl type (was stored as "Self")
- C++: replace type_identifier guard with extractSimpleTypeName for compound_literal_expression,
  enabling ns::User{} scoped brace-init (closes previously deferred gap)
- PHP: add property_promotion_parameter to TYPED_PARAMETER_TYPES for PHP 8.0+
  constructor property promotion (__construct(private Foo $x))
- Ruby: extend extractRubyConstructorBinding to accept constant left-hand side
  (REPO = Repo.new)

Unit tests: 96 → 101 (+5: Rust Self{} ×2, C++ ns::User{} ×1, PHP promotion ×1,
Ruby constant ×1)
Integration tests: 4 new describe blocks with fixtures

* feat: Phase 1 type resolution gaps — walrus, PHP properties, nullable, Go make/assert

Phase 1 quick wins from the type resolution gap analysis:

1. Python walrus operator := (named_expression) — extractInitializer + scanner
2. PHP 7.4+ typed class properties — property_declaration in extractDeclaration
3. Nullable union unwrapping — User | null → User in extractSimpleTypeName
4. Go make() builtin — slice/map element type extraction
5. Go type assertions — iface.(User) type extraction

Also: PHP primitive_type handling in extractSimpleTypeName (string, int, etc.)

Unit tests: 101 → 114 (+13)
Integration tests: 8 new describe blocks with fixtures

* feat: Phase 2 type resolution gaps — C++ range-for, Rust if-let, C# pattern matching, Python class annotations

Phase 2 medium-effort improvements:

1. C++ range-for with explicit type — for (User& u : vec) binds u: User
2. Rust if-let/while-let captured_pattern — user @ User { .. } binds user: User
3. C# is-pattern matching — if (obj is User user) binds user: User
4. Python class-level annotations — confirmed already working, added tests

Unit tests: 114 → 127 (+13)
Integration tests: 11 new test cases with fixtures
2026-03-14 19:05:49 +00:00
Gergő Magyar 6e38db879e fix(ruby): method-level call resolution, HAS_METHOD edges, and dispatch table (#278)
* fix(ruby): method-level call resolution, HAS_METHOD edges, and dispatch table refactoring

- Replace all `if (language === Ruby)` checks in processors with a
  `callRouters` dispatch table in call-routing.ts (renamed from
  ruby-call-routing.ts to preserve git history)
- Add Ruby `method` and `singleton_method` to FUNCTION_NODE_TYPES so
  findEnclosingFunction produces Method-level CALLS sources
- Add Ruby `class` and `module` to CLASS_CONTAINER_TYPES for HAS_METHOD
  edge generation
- Add bare call capture via tree-sitter query `(body_statement (identifier))`
  for Ruby methods called without parentheses
- Add Ruby member call detection (`call` node with `receiver` field) to
  inferCallForm and extractReceiverName
- Wire resolveRubyImport into resolveLanguageImport
- Add 24 integration tests across 5 suites: heritage/properties, arity
  filtering, member calls, ambiguous disambiguation, local shadow
- Add ruby.test.ts to CI integration workflow

* fix(ruby): resolve 6 Ruby resolution gaps from PR review

- Fix singleton_method label mismatch: @definition.function → @definition.method
  so CALLS edges from `def self.foo` bodies get correct sourceId
- Add ownerId and HAS_METHOD edges to attr_* Property nodes by calling
  findEnclosingClassId in both parse-worker and call-processor property branches
- Distinguish include/extend/prepend heritage: add heritageKind to
  RubyHeritageItem, propagate through heritage pipeline as IMPLEMENTS reason
- Document bare call over-capture limitation in tree-sitter query comment
- Add bare `require` (non-relative) import test coverage
- Add prepend/extend test coverage with distinct Loggable/Cacheable modules

31 Ruby integration tests passing, no regressions in other language resolvers.

* fix(ruby): web package parity — heritage reasons, property HAS_METHOD, singleton_method label

- Web call-processor: use item.heritageKind as IMPLEMENTS reason instead of
  hardcoded 'trait-impl', add :${kind} suffix to edge ID for uniqueness
- Web call-processor: port findEnclosingClassId, add HAS_METHOD edges for
  attr_* Property nodes to match CLI fix
- Web call-processor: singleton_method label 'Function' → 'Method' to match
  CLI tree-sitter query fix
- CLI parse-worker: update stale ExtractedHeritage.kind JSDoc to include
  'include' | 'extend' | 'prepend'

* fix(web): add HAS_METHOD to RelationshipType union

Web package was missing HAS_METHOD in the RelationshipType union,
causing a type mismatch with the HAS_METHOD edges emitted by the
attr_* property fix in call-processor.ts.
2026-03-14 11:34:30 +00:00
Candido Sales Gomes 0999595444 feat(ruby): Add Ruby language support for CLI and web (#111) 2026-03-13 21:46:59 +00:00
Chirag Nighut 649ad80dbb fix(cli): dynamically discover and install agent skills (#270) 2026-03-13 19:09:03 +00:00
Gergo Magyar 3dbe08fab6 chore: release v1.4.0 2026-03-13 13:18:14 +00:00
Gergő Magyar 1afe9166aa feat: language-aware code intelligence — symbol resolution, MRO, constructor discrimination (#238)
* feat: add Method Resolution Order (MRO) with language-specific rules

Implement full MRO computation for multi-language inheritance hierarchies:

- HAS_METHOD edges: Class→Method ownership edges emitted during parsing
  (both worker pool and sequential fallback paths)
- Method signatures: extract parameterCount and returnType from AST nodes
- C# heritage fix: distinguish EXTENDS vs IMPLEMENTS for base_list captures
  using symbol table lookup + I[A-Z] naming heuristic fallback
- MRO processor (Phase 4.5): walks inheritance DAG, detects method-name
  collisions across parents, applies language-specific resolution:
  - C++: leftmost base class in declaration order wins
  - C#/Java: class method wins over interface default
  - Python: C3 linearization with cycle detection
  - Rust: no auto-resolution (requires qualified syntax)
  - Default: first definition in BFS order wins
- OVERRIDES edges emitted for resolved method collisions
- KuzuDB schema: Method table extended with parameterCount/returnType;
  dedicated CSV writer and COPY query for 10-column Method rows
- MCP tools: updated Cypher examples for HAS_METHOD, OVERRIDES, diamond

72 tests across 5 test files covering MRO resolution, HAS_METHOD edges,
method signature extraction, C# heritage resolution, and integration
tests across C#/Rust/Python/TS/Java/C++.

* feat: add scope-based symbol resolution replacing raw lookupFuzzy

Introduces a shared 3-tier resolveSymbol function used by both
heritage-processor and call-processor:
1. Same-file (lookupExactFull — authoritative)
2. Import-scoped (filtered by ImportMap — high confidence)
3. Global fuzzy (first match — low confidence fallback)

Adds lookupExactFull to SymbolTable returning full SymbolDefinition
with type info needed for heritage Class/Interface disambiguation.

* refactor: tighten symbol resolution — Tier 3 refuses ambiguous matches

- lookupExactFull now O(1) via direct SymbolDefinition storage in fileIndex
  (shared object references with globalIndex — zero additional memory)
- Added resolveSymbolInternal() preserving { definition, tier, candidateCount }
  for test assertions and logging
- Tier 3 now returns null when multiple global candidates exist instead of
  arbitrary allDefs[0] — a wrong edge is worse than no edge
- call-processor: renamed fuzzy-global → unique-global, removed dead branch
- 12 new tests: tier assertions, ambiguous refusal per language family,
  heritage false-positive guard, O(1) shared reference verification

* fix: critical language support bugs in import resolution and MRO

Phase 5 critical fixes from all-language analysis:
- Python: add relative_import query capture (PEP 328) — `.models`, `..utils`
  were silently dropped, producing zero ImportMap entries
- Rust: extract prefix from grouped imports `crate::module::{A, B}` — brace
  groups previously failed resolution entirely
- Swift: use normalizedFileList for Windows path compatibility in module
  import resolution (matches Go's resolveGoPackage pattern)
- MRO: fix c_sharp → csharp language name mismatch (enum is 'csharp'),
  add Kotlin to C#/Java resolution rules (class method wins over interface)

* feat: add strict multi-language integration tests + fix C/C++ import resolution

Add 32 integration tests across 6 language fixtures (TypeScript, C#, C++,
Java, Python, Rust) with exact toBe/toEqual assertions validating heritage
edges, import resolution, and trait implementations.

Fix C/C++ import resolution bug where dot-to-slash conversion mangled
include paths (e.g. "animal.h" became "animal/h"). Now skips conversion
for C/C++ languages which use actual file paths in #include directives.

* fix: language-gate heritage heuristic, add Swift extension heritage, handle Rust grouped imports

- Gate I[A-Z] naming heuristic to C#/Java only (was firing for all languages)
- Swift unresolved types default to IMPLEMENTS (protocol conformance is the norm)
- Add tree-sitter query for Swift extension protocol conformance (extension Foo: Protocol)
- Handle Rust top-level grouped imports (use {crate::a, crate::b}) in both import loops
- Add 4 new heritage-processor tests (TypeScript refusal, Swift default, Swift Tier 1)

* feat: add Go struct embedding heritage + PackageMap optimization

Add Go struct embedding detection (anonymous fields → EXTENDS edges) via
new tree-sitter heritage query with named-field filtering in both
parse-worker and heritage-processor paths.

Implement PackageMap optimization for Go cross-package resolution:
replace O(N) file-level ImportMap expansion with directory-level suffix
matching (Tier 2b in symbol resolver). Graph IMPORTS edges are preserved
via addImportGraphEdge split.

Remove overly broad @definition.type from GO_QUERIES that was
double-matching structs/interfaces as TypeAlias nodes, breaking Tier 3
unique-global resolution.

Add Go fixture (go-pkg) with Admin→User embedding, cross-package calls,
and 7 integration tests covering structs, functions, imports, calls,
and heritage edges.

* test: add Kotlin heritage integration tests

Adds a kotlin-heritage fixture and 7 integration tests validating
class inheritance, interface implementation, JVM-style import
resolution, and symbol-table-driven EXTENDS/IMPLEMENTS disambiguation
via Kotlin delegation specifiers.

* feat: extract resolvers, add PHP tests, ambiguous tests for all languages

- Extract language-specific resolvers from import-processor.ts into
  resolvers/ directory (P7): jvm, go, csharp, php, rust, standard, utils
- import-processor.ts reduced from 1412 to 711 lines (50% reduction)
- Add comprehensive PHP integration tests: PSR-4 imports, traits, enums,
  heritage edges, method calls, MRO overrides
- Add ambiguous symbol resolution tests for all 9 languages verifying
  correct disambiguation via import chains
- Split monolithic lang-resolution.test.ts (1080 lines) into 9 per-language
  files under test/integration/resolvers/ with shared helpers

* feat: update integration tests to include resolver tests for multiple languages

* fix: address code review — schema gap, Rust impl name, Property OVERRIDES

Bugs fixed:
- Add 13 missing FROM/TO pairs in RELATION_SCHEMA for HAS_METHOD edges
  (Class/Interface/Struct/Trait/Impl/Record to Method/Constructor/Property)
- Fix findEnclosingClassId to pick implementing type for Rust
  impl Trait for Struct blocks (was picking trait name)
- Exclude Property nodes from MRO OVERRIDES collision detection
- Change MRO language fallback from typescript to unknown

Tests added:
- Unit: Property OVERRIDES exclusion (2 tests), Rust impl Trait for
  Struct name resolution (2 tests), schema HAS_METHOD pair coverage
- Integration: no OVERRIDES targets Property nodes across all 9 languages
- PHP fixture: added shared $status property to both traits to create
  real collision scenario for Property OVERRIDES exclusion test

Documentation:
- OVERRIDES edge direction (Class to Method), Go return type gap,
  BFS first-reach heuristic limitation

* feat: harden CALLS-edge resolution — Phase 0 validation

- Fix same-file confidence (0.85 → 0.95) to correctly outrank import-scoped (0.9)
- Fix Tier 1 overload preservation: use globalIndex filter instead of fileIndex lookup
- Add callable-kind guard: refuse CALLS edges to Interface and Enum symbols
- Fix Kotlin countCallArguments: handle call_suffix → value_arguments nesting
- Fix Kotlin extractFunctionName: add simple_identifier to fallback search
- Strictly type findParameterList and countCallArguments (remove all `any`)
- Add arity-based call resolution integration tests for 9 languages
- Add unit regression tests for Interface/Enum CALLS refusal

* chore: remove C# build artifacts from fixtures

* feat: add call-form discrimination and ownerId to symbol table (Phase 1)

Add inferCallForm() and extractReceiverName() to distinguish free/member/constructor
calls at the AST level across all 9 languages. Add ownerId field to SymbolDefinition
linking Method/Constructor/Property to their owning class. Includes 36 unit tests
and member-call integration tests for all 9 languages (132 tests, 0 failures).

* feat: constructor/struct-literal resolution across all languages (Phase 2)

Add constructor discrimination to CALLS-edge resolution: new Foo(),
User{...} struct literals, and C# primary constructors now resolve to
Constructor/Class/Struct/Record nodes instead of being filtered out.

Queries: new_expression (C++), object_creation_expression (PHP),
composite_literal (Go), struct_expression (Rust), primary constructor
and implicit_object_creation_expression (C#).

Relaxes global tier in collectTieredCandidates to pass all candidates
through filterCallableCandidates, allowing kind/arity narrowing to
disambiguate at lower confidence.

* feat: receiver-constrained resolution with integration tests for all 9 languages

Add receiver-type filtering (Phase 3): when a member call like `user.save()`
has a known receiver type from TypeEnv, filter candidates by ownerId to
disambiguate methods with the same name across different classes.

Key changes:
- call-processor: build per-file TypeEnv, pass receiverTypeName to resolveCallTarget
- parse-worker: extract receiverTypeName from TypeEnv in worker thread
- resolveCallTarget: new step D filters by ownerId matching receiver type
- utils: extractReceiverName supports C++ field_expression (argument field)
- utils: findEnclosingClassId extracts Go method receiver types
- type-env: handle Go qualified_type, Kotlin user_type/variable_declaration
- parse-worker + parsing-processor: Function added to needsOwner for
  Kotlin/Rust/Python class methods captured as Function nodes

Integration tests added for receiver-constrained resolution across all 9
languages: TypeScript, Java, Python, Go, Rust, C++, C#, Kotlin, PHP.

* feat: NamedImportMap, scoped TypeEnv, broadened signatures + TS rest-param variadic fix

Address all 4 PR #238 review items:
1. Remove redundant lookupFuzzy in processRoutesFromExtracted
2. Add NamedImportMap for TS/Python symbol-level import tracking (Tier 2a)
3. Make TypeEnv scope-aware (Map<scopeKey, Map<varName, type>>) to fix
   non-deterministic receiver resolution across functions
4. Broaden extractMethodSignature: Go/Rust/C++ return types, variadic
   detection for Go/Java/Python/C++/Kotlin/TypeScript rest params

Discovered and fixed: TS rest params (...args) were not detected as
variadic — added rest_pattern detection inside required_parameter nodes.

Integration tests added: scoped receiver, named import disambiguation,
and variadic call resolution for both TypeScript and Python.

* fix: alias import resolution, Go multi-assign TypeEnv, dead code removal

- NamedImportMap now stores {sourcePath, exportedName} so aliased imports
  (import { User as U }) resolve U → User in the source file
- Named binding check moved before empty-allDefs early return in both
  call-processor and symbol-resolver, fixing constructor calls via aliases
- Go extractFromGoShortVarDeclaration iterates all LHS/RHS pairs for
  multi-assignment (user, repo := User{}, Repo{}) instead of only first
- Remove unused TYPED_DECLARATION_TYPES set (TYPED_PARAMETER_TYPES kept)
- Integration tests for both fixes (go-multi-assign, typescript-alias-imports)

* feat: alias import extraction for Kotlin, Rust, PHP, C# + integration tests

Add named import alias extraction to both pipeline paths
(import-processor.ts and parse-worker.ts) for Kotlin, Rust, PHP,
and C#. Add integration test fixtures and tests for all 5 languages
(Python alias extraction already worked, just needed the test).

Each test verifies: class detection, member call resolution through
aliases to correct target files, and IMPORTS edge emission.

* refactor: use SupportedLanguages enum everywhere instead of raw strings

Replace all raw language string literals and `language: string` types
with the SupportedLanguages enum across 10 files. This ensures
compile-time safety for language dispatch and eliminates dead
`language === 'tsx'` checks (tsx maps to TypeScript in the enum).

* fix: tier-ordering bug, re-export chains, PHP grouped imports, Java named imports

- Fix collectTieredCandidates tier-ordering: same-file now checked before
  named bindings, preventing imports from shadowing local definitions
  (matches resolveSymbolInternal priority order)
- Add re-export chain resolution for TypeScript/JavaScript barrel files:
  export { X } from './base' and export type { X } from './base' now
  followed up to 5 hops through NamedImportMap
- Fix PHP grouped import alias extraction: use App\Models\{User, Repo as R}
  now correctly handled in both parse-worker and import-processor
- Add Java NamedImportMap support: import com.example.models.User now
  records User as a named binding for precise disambiguation
- Add 16 new integration tests across TypeScript, PHP, and Java resolvers
  (220 total resolver tests, all passing)

* refactor: consolidate alias extraction + add variadic/constructor/shadow integration tests

- Extract shared named-binding-extraction.ts from duplicate logic in
  import-processor.ts and parse-worker.ts (net -200 lines)
- Deduplicate appendKotlinWildcard (now imported from resolvers/index.ts)
- Add integration tests: constructor calls (Kotlin, Python), variadic
  resolution (Go, Java, C#, C++, Kotlin), re-export chains (Python),
  local definition shadowing (Python, Go)
- Add TODO(stack-graph) for TypeEnv scope key collision
- 225 integration tests passing (was 223)

* fix: PHP non-aliased imports, Python node identity, re-export chain dedup + local-shadow tests

- PHP flat non-aliased imports (use App\Models\User) now stored in NamedImportMap
- PHP grouped non-aliased imports ({User} in {User, Repo as R}) now stored in NamedImportMap
- Python: replace non-public child.id with child.startIndex for node identity
- Extract shared walkBindingChain() from symbol-resolver and call-processor
- Add PHP variadic resolution fixture + test (variadic_parameter already covers PHP)
- Add local-shadow integration tests for Java, C#, Kotlin, Rust, PHP, C++ (6 languages)

* feat: Rust non-aliased use bindings, Kotlin non-aliased imports, re-export chain resolution

Extend NamedImportMap coverage for Rust and Kotlin non-aliased imports:

- Rust: rename collectUseAsClauses → collectRustBindings, extract terminal
  scoped_identifier (use crate::models::User) and identifier in use_list
  (use crate::models::{User, Repo}) into NamedImportMap. This also enables
  pub use re-export chain following via walkBindingChain.
- Kotlin: extend extractKotlinNamedBindings to handle non-aliased imports
  (import com.example.User), skipping wildcard imports.
- Add rust-reexport-chain fixture + 3 integration tests verifying Handler{}
  resolves through mod.rs pub use to handler.rs.
- Add Kotlin heritage + constructor-calls reason assertions for non-aliased
  import-resolved resolution.
- Add C# heritage test documenting namespace import tier behavior.

* fix: skip Kotlin lowercase member imports in NamedImportMap

Member imports like `import util.OneArg.writeAudit` (lowercase last
segment) must not populate NamedImportMap — same-named function imports
from different classes collide, breaking arity-based disambiguation.
Apply the same guard Java already uses: skip lowercase last segments.

* fix: skip spurious path-prefix bindings in Rust grouped imports

collectRustBindings was extracting the path segment (e.g. "models") from
`use crate::models::{User, Repo}` as a spurious NamedImportMap entry.
Skip scoped_identifier nodes that are direct children of scoped_use_list
since they are path prefixes, not importable symbols.

Adds rust-grouped-imports fixture and 4 integration tests verifying both
symbols resolve correctly and no spurious binding leaks through.

* fix: use startIndex in TypeEnv scope key to prevent same-name method collision

Two methods named identically in different classes within the same file
previously shared a scope key, causing non-deterministic type resolution.
Now keys use funcName@startIndex for uniqueness.

Also adds tests documenting destructuring assignment extraction gap.

* test: document C# namespace-level import limitation in named binding extraction

* test: document same-arity overload discrimination limitation in call processor

* perf: parallelize calls/heritage/routes processing in worker path

Worker path now runs processCallsFromExtracted, processHeritageFromExtracted,
and processRoutesFromExtracted via Promise.all instead of sequentially.
Safe because all three only read shared state and write via addRelationship's
dedup guard. Sequential fallback path stays sequential (shared LRU astCache).

Also fixes Rust collectRustBindings spurious path-prefix bindings for 3+ level
grouped imports, and adds @param JSDoc for walkBindingChain's allDefs invariant.

* docs: improve Promise.all safety comment and walkBindingChain JSDoc

Clarify that the parallelization safety comes from disjoint relationship
types + idempotent id-keyed Maps, not from lack of shared state (the
graph is shared). Strengthen allDefs JSDoc to describe silent-miss
consequence of passing pre-filtered results.

* refactor: extract language-specific processing into modular dispatch tables

Phase 1: Extract type binding logic from type-env.ts (635→125 LOC) into
type-extractors/ directory with per-language files and Record<SupportedLanguages,
LanguageTypeConfig> + satisfies dispatch.

Phase 2: Extract 5 config loaders from import-processor.ts into
language-config.ts (removed ~196 LOC of inline loaders).

Phase 3: Convert export-detection.ts switch/case to exhaustive
Record<SupportedLanguages, ExportChecker> + satisfies dispatch table,
fix node: any → SyntaxNode.

Also adds language feature matrix to README.

All 1146 unit tests and 433 integration tests pass.

* refactor: extract type binding logic into type-extractors/ directory (Phase 1)

Extract per-language type extraction from type-env.ts (635→125 LOC) into
type-extractors/ with Record<SupportedLanguages, LanguageTypeConfig> + satisfies
dispatch. 9 per-language files, shared helpers, and barrel index.

* refactor: extract config loaders to language-config.ts (Phase 2)

Move 5 language-specific config loaders and their type interfaces from
import-processor.ts into standalone language-config.ts module.
2026-03-13 13:12:23 +00:00
Zander Raycraft 03bfa3c4d9 FEAT: Added support for optional skill generation based on KuzuDB after initial repo analysis (npx gitnexus analyze --skills) (#171)
* calm fix 4 adding skills to repo [ISSUE #140]

* inspect

* unit and integration tests

* fixed hardcoded cohesion miss

* e2e tests for --skills flag for langauge/repo support

* Cohesion test e2e tests
2026-03-13 08:29:13 +00:00
Subham Kundu 74c0e462c3 Merge pull request #217 from JasonOA888/feat/issue-215-deepseek-model
feat(models): add DeepSeek model configurations
2026-03-13 00:31:02 +05:30
Gergő Magyar 7376e92063 fix: consolidate C/C++/C#/Rust language support from 6 overlapping PRs (#237)
* fix: consolidate C/C++/C#/Rust language support from 6 overlapping PRs

Merges fixes from PRs #163, #170, #178, #216, #227, #234 into a single
coherent changeset with shared modules and deduplication.

Phase 0 — Pre-merge consolidation:
- Extract isNodeExported to shared export-detection.ts module
- Extract TREE_SITTER_BUFFER_SIZE to shared constants.ts with adaptive sizing
- Consolidate FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise
  from duplicated call-processor.ts and parse-worker.ts into shared utils.ts
- Add query compilation smoke tests for all 12 languages

Language fixes:
- fix(c/cpp): isExported checks static linkage instead of returning false
- fix(c/cpp): .h files parsed as C++ (tree-sitter-cpp is superset of C)
- fix(c/cpp): expanded entry point patterns (~30 new for C, ~18 for C++)
- fix(cpp): add typedef, union, macro, prototype, inline method queries
- fix(c#): isExported scans sibling modifiers instead of parent walk
- fix(c#): heritage queries use correct base_list AST structure
- fix(c#): add framework detection, import resolution, entry point scoring
- fix(rust): isExported scans sibling visibility_modifier in declaration
- fix(builtins): remove open/read/write/close (real C POSIX syscalls)
- fix(buffer): adaptive bufferSize (2x fileSize, 512KB-32MB range)
- feat(ts/js): add call_expression query patterns for const assignments

Deduplication:
- call-processor.ts: -226 lines (uses shared utils)
- parse-worker.ts: -320 lines (uses shared utils)
- parsing-processor.ts: -156 lines (uses shared export-detection)

* perf: fix review findings — hoist Sets, deduplicate DEFINITION_CAPTURE_KEYS

- Hoist CSHARP_DECL_TYPES and RUST_DECL_TYPES to module-level constants
  in export-detection.ts (was allocating new Set on every isNodeExported call)
- Extract DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures to
  shared utils.ts (was duplicated in parsing-processor.ts and parse-worker.ts)
- Pre-compute merged entry point patterns to avoid per-call array spread
  in calculateEntryPointScore

* test: add C, C++, and Tree-sitter buffer size tests

* fix: C/C++/Rust review findings + comprehensive test coverage (+72 tests)

Source fixes:
- Add Rust built-in noise (unwrap, clone, into, collect, panic, etc.)
- C++ anonymous namespace → internal linkage (not exported)
- Replace .text regex with storage_class_specifier child scan (perf)
- Raise file skip threshold from 512KB to 32MB (TREE_SITTER_MAX_BUFFER)
- Export TREE_SITTER_MAX_BUFFER from constants.ts
- Add C++ double pointer query patterns to CPP_QUERIES
- Add C#: record_struct, record_class, file_scoped_namespace to decl types
- Add Rust: union_item to visibility scanning set

Tests (214 → 286):
- ingestion-utils: +24 (Rust/C# noise, pointer/ref/destructor extraction, buffer)
- parsing: +36 (real AST C/C++ static/namespace, Rust/C#/Java/PHP/Swift edge cases)
- tree-sitter-languages: +12 (query accuracy for C/C++/C#/Rust captures)
2026-03-10 23:03:32 +00:00
RyanbaandGergo Magyar 1be910f54a fix: skip unavailable native Swift parsers in sequential ingestion (#188)
* fix: skip unavailable native Swift parsers in sequential ingestion

* fix: warn when ingestion skips languages in verbose mode

* test: cover verbose skip warnings

* docs: update analyze flags

* docs: clarify verbose default

---------

Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
2026-03-10 14:26:36 +00:00
Gergo Magyar fa9ba8925c fix(ci): support fork PRs in Claude Code Review workflow
claude-code-action fetches branches by name from origin, which fails
for fork PRs since the branch only exists on the fork remote. Work
around by detecting fork PRs and temporarily pushing the branch to
origin before the action runs, then cleaning up afterwards.

Also changed trigger from automatic (every push) to on-demand only
(label "claude-review" or comment "@claude" / "/review").
2026-03-09 15:33:41 +00:00
8efc272609 fix(ci): move PR report to workflow_run for fork PR support (#225)
* Initial plan

* fix: add pull-requests write permissions to GitHub Actions workflows

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

* fix(ci): remove ineffective job-level permissions from reusable workflow

* fix(ci): pass PR write permission from caller to reusable unit-tests workflow

* fix(ci): harden CI/CD workflows with security fixes and reliability improvements

- Pin all actions to commit SHAs to prevent supply-chain attacks
- Fix shell injection in ci-integration.yml by using env vars instead of direct interpolation
- Scope permissions per-job in publish.yml (was granting pull-requests:write to publish job)
- Restrict claude-code-review to trusted contributors only (OWNER/MEMBER/COLLABORATOR)
- Switch claude-code-review to pull_request_target for fork PR support
- Fix fail-fast: false in ci-unit-tests.yml cross-platform matrix
- Remove duplicate ubuntu-latest from unit test matrix
- Add timeouts to all workflow jobs
- Improve kuzu-db test loop to continue on failure and report per-file errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): read thresholds from `vitest.config.ts`

* fix(ci): move PR report to workflow_run for fork PR support

The sticky-pull-request-comment and vitest-coverage-report-action
both fail on fork PRs because pull_request events receive a read-only
GITHUB_TOKEN. This extracts PR reporting into a separate ci-report.yml
workflow triggered by workflow_run, which always gets read/write tokens.

Changes:
- ci.yml: replace pr-report job with save-pr-meta artifact upload
- ci-unit-tests.yml: remove davelosert/vitest-coverage-report-action,
  add coverage-final.json to artifact for merging
- ci-integration.yml: add ubuntu coverage job for non-kuzu groups
- ci-report.yml (new): workflow_run handler that downloads artifacts,
  merges unit + integration coverage via Istanbul, and posts combined
  PR comment with sticky-pull-request-comment

* feat(ci): show unit, integration, and merged coverage in PR report

- Disable coverage thresholds for integration-only run (partial coverage)
- Display combined coverage as the primary metric
- Show per-suite breakdown (unit / integration) in expandable details
- Thresholds applied against combined coverage, not individual suites

* fix(ci): add coverage collection input for PR reports and validate job results

* fix(ci): refine Claude Code Review workflow to support issue comments and enhance trusted contributor checks

---------

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: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:07:46 +00:00
c990d7e6c6 fix(ci): harden CI/CD workflows with security fixes and reliability improvements (#222)
* Initial plan

* fix: add pull-requests write permissions to GitHub Actions workflows

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

* fix(ci): remove ineffective job-level permissions from reusable workflow

* fix(ci): pass PR write permission from caller to reusable unit-tests workflow

* fix(ci): harden CI/CD workflows with security fixes and reliability improvements

- Pin all actions to commit SHAs to prevent supply-chain attacks
- Fix shell injection in ci-integration.yml by using env vars instead of direct interpolation
- Scope permissions per-job in publish.yml (was granting pull-requests:write to publish job)
- Restrict claude-code-review to trusted contributors only (OWNER/MEMBER/COLLABORATOR)
- Switch claude-code-review to pull_request_target for fork PR support
- Fix fail-fast: false in ci-unit-tests.yml cross-platform matrix
- Remove duplicate ubuntu-latest from unit test matrix
- Add timeouts to all workflow jobs
- Improve kuzu-db test loop to continue on failure and report per-file errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): read thresholds from `vitest.config.ts`

---------

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: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 08:25:03 +00:00
Gergo Magyar b4fbf33bd6 fix(ci): remove workflow-level permissions from reusable workflows 2026-03-08 18:20:05 +00:00
Gergő MagyarandClaude Opus 4.6 892e1d6088 test: add integration test coverage and fix KuzuDB fork crashes (#209)
* ci: add macOS to cross-platform test matrix

* ci: run integration tests on all platforms, add macOS to matrix

* ci: add build step before cross-platform integration tests

Worker pool requires compiled parse-worker.js in dist/.
Without build, falls back to sequential parsing which times out
on macOS runners.

* fix(pipeline): resolve worker path to dist/ when running under vitest

import.meta.url points to src/ under vitest where no .js exists.
Fall back to dist/core/ingestion/workers/parse-worker.js so worker
threads spawn correctly on all platforms instead of sequential fallback
that times out on slower macOS CI runners.

* ci: split cross-platform unit and integration tests into parallel jobs

* test: add integration tests for worker pool and hooks e2e

- worker-pool.test.ts: 7 tests verifying dist/ worker spawning,
  multi-file parsing, progress reporting, and clean termination
- hooks-e2e.test.ts: 28 tests with real git repos testing staleness
  detection, embeddings flag, mutation regex, cwd validation,
  and .gitnexus directory discovery

* refactor: extract shared hook test helpers and simplify worker fallback

- Extract runHook/parseHookOutput into test/utils/hook-test-helpers.ts
- Deduplicate fileURLToPath calls in pipeline.ts worker resolution
- Add isDev logging for worker pool creation failures

* fix(test): accept timeout as valid outcome for PreToolUse CLI spawn

The Plugin hook spawns `gitnexus augment` which may hang on macOS
when the CLI is unavailable, causing a 10s timeout (status=null)
instead of a clean exit (status=0). Accept both as non-crash outcomes.

* test: add integration test coverage and fix KuzuDB fork crashes

- Add new integration tests: search, enrichment, CLI e2e (968 total tests)
- Fix KuzuDB native destructor segfault in vitest fork pool by adding
  detachKuzu() that nulls refs without calling .close()
- Merge core adapter test blocks to share one coreHandle (prevents
  multiple coreInitKuzu calls that re-open native DB handles)
- Fix FTS Cypher injection: escape backslashes in bm25-index.ts and
  kuzu-adapter.ts queryFTS
- Add worker script existence check in worker-pool.ts to prevent
  MODULE_NOT_FOUND crashes in worker threads
- Add test/setup.ts global teardown that detaches native refs
- Add test/helpers/test-indexed-db.ts shared KuzuDB test lifecycle helper

* fix(test): update worker-pool test to expect throw on invalid path

The fs.existsSync validation in createWorkerPool now throws
synchronously for missing worker scripts. Update the test assertion
from .not.toThrow() to .toThrow(/Worker script not found/).

* fix(test): use fileParallelism instead of deprecated singleFork

vitest 4.x removed poolOptions.forks.singleFork. The top-level
singleFork was silently ignored, causing multiple forks to spawn
and timeout during KuzuDB native cleanup on CI.

* fix(test): add maxWorkers: 1 to prevent per-file kuzu native addon reload

On Ubuntu CI, vitest forks pool creates a new child process per test
file. Each fork loads the KuzuDB native addon (~40s on Ubuntu runners),
causing 12 files × 40s = 8 minutes of overhead that exceeds the
10-minute CI timeout.

maxWorkers: 1 forces vitest to reuse a single fork process, loading
the native addon once. Combined with fileParallelism: false, all test
files run sequentially in that single fork.

* fix(test): prevent KuzuDB native destructor hangs on fork worker exit

- setup.ts: closeKuzu() first (marks native handles closed so destructors
  are no-ops), then detachKuzu() as safety net
- test-indexed-db.ts: use detachKuzu() in per-test cleanup instead of
  closeKuzu() which could hang during teardown

* refactor(test): add withTestKuzuDB lifecycle wrapper with declarative options

withTestKuzuDB now manages the full KuzuDB test lifecycle so test files
never call initKuzu/closeCoreKuzu/poolInitKuzu/loadFTSExtension directly.

Options: seed, ftsIndexes, poolAdapter, afterSetup, timeout.
Each call is wrapped in its own describe block to isolate lifecycle hooks.

Migrated search.test.ts, enrichment-and-augmentation.test.ts, and
kuzu-pool.test.ts core adapter block to use the wrapper.

* refactor(test): migrate all integration tests to withTestKuzuDB

- Split enrichment-and-augmentation.test.ts into enrichment.test.ts
  and augmentation.test.ts for focused test isolation
- Migrate kuzu-pool.test.ts pool lifecycle tests to withTestKuzuDB
- Migrate local-backend.test.ts to two withTestKuzuDB blocks
  (pool queries + callTool dispatch)
- Zero direct kuzu.Database/Connection usage remains in test files

* refactor(test): enforce one describe per test file

- Split search.test.ts → search-core.test.ts + search-pool.test.ts
- Split kuzu-pool.test.ts → kuzu-pool.test.ts + kuzu-core-adapter.test.ts
- Split local-backend.test.ts → local-backend.test.ts + local-backend-calltool.test.ts
- Wrap enrichment.test.ts in single top-level describe
- Wrap parsing.test.ts in single top-level describe
- Every integration test file now has exactly 1 top-level block

* refactor(test): extract shared seed data into fixture files

- Create test/fixtures/search-seed.ts with SEARCH_SEED_DATA and SEARCH_FTS_INDEXES
- Create test/fixtures/local-backend-seed.ts with LOCAL_BACKEND_SEED_DATA and LOCAL_BACKEND_FTS_INDEXES
- Remove duplicated constants from split test files
- Remove dead vi.mock from local-backend.test.ts
- Prefix unused handle param with underscore in search-core.test.ts

* fix(test): prevent KuzuDB C++ destructor hang on Ubuntu CI

Add process.on('beforeExit', () => process.exit(0)) to force
immediate exit before GC can trigger native C++ destructors on
orphaned KuzuDB Database/Connection objects.

Root cause: detachKuzu() nulls JS refs but native C++ objects
remain in V8 heap. During fork worker exit, GC runs finalizers
that invoke C++ destructors on a torn-down runtime — hangs on
Ubuntu, segfaults on Windows.

The beforeExit event fires when the event loop has drained
(test results already sent via IPC), so process.exit(0) is safe.

Also simplifies afterAll: removes closeKuzu() calls (always
no-ops since withTestKuzuDB detaches first) — only detachKuzu().

* perf(test): share single KuzuDB instance across integration tests

Create schema once in globalSetup instead of per-file, eliminating
29 DDL queries × 7 test files. Each file now only clears and reseeds
data via DETACH DELETE, reducing DB open/close cycles significantly.

* fix(test): improve KuzuDB cleanup to prevent C++ destructor hangs on exit

* fix(test): replace async close calls with synchronous counterparts to prevent potential hangs

* feat(ci): enhance integration test matrix with detailed test groups and improved reporting

* test: add diagnostic output to analyze CLI e2e assertion for CI debugging

* fix: pass NODE_OPTIONS in runCli to prevent ensureHeap re-exec in tests

* update gitnexus analysis md files

* feat(ci): modular workflow architecture with artifact reporting

Refactor monolithic ci.yml into orchestrator calling three reusable
workflows (quality, unit-tests, integration) via workflow_call.

- Add composite action for shared Node.js 20 setup and npm ci
- Add ci-quality.yml for TypeScript typecheck
- Add ci-unit-tests.yml with coverage reporting, JSON test results,
  and artifact upload for PR summary comments
- Add ci-integration.yml with 4 test groups x 3 OS matrix (12 jobs)
- Add PR report job with sticky comment showing coverage metrics
- Add unified CI Gate status check for branch protection
- Add explicit permissions blocks to all child workflows

* test: add comprehensive unhappy path coverage across all 16 integration test files

Add 80+ error handling, edge case, and unhappy path tests covering:
- KuzuDB core adapter: invalid Cypher, duplicate FTS index, empty queries, missing paths
- CLI e2e: non-git dirs, non-indexed repos, unknown commands, help flag
- Local backend callTool: missing params, invalid Cypher, nonexistent symbols
- Tree-sitter: unsupported languages, malformed code, empty content, binary files
- Worker pool: dispatch after terminate, double terminate, empty content, zero-size pool
- Pipeline: empty content parsing, flexible file count assertions
- Search, enrichment, augmentation, CSV, hooks, filesystem: various edge cases

Also fixes pre-existing test issues:
- isWriteQuery CREATED test (CYPHER_WRITE_RE uses \b word boundaries)
- KuzuDB throws Binder exception for unknown tables (not empty result)
- runPipelineFromRepo requires onProgress callback

All 1,086 tests pass (53 files).

* fix: prevent KuzuDB worker hang with handle unref strategy and safety-net timer

Replace beforeExit force-exit with per-file handle unref + safety-net timer
that doesn't leak across files in single-fork mode.

* refactor: improve KuzuDB test isolation and cleanup strategy

* fix: prevent KuzuDB N-API destructor hang on Linux/macOS

Pool adapter closeOne() now just deletes the pool entry without calling
native close methods — read-only DBs have no WAL to flush, so GC/process
exit safely reclaims native resources without triggering the C++ destructor
segfault.

withTestKuzuDB wrapper handles core adapter close platform-conditionally:
Windows needs explicit closeKuzu() due to file locks, Linux/macOS skips
it to avoid deadlock. kuzu-pool.test.ts now uses poolAdapter: true instead
of manual afterSetup. pipeline.test.ts assertion fixed to match actual
behavior (resolves with empty result, not rejects).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore vitest safety nets and skip globalSetup close on Linux

- Restore dangerouslyIgnoreUnhandledErrors and teardownTimeout in
  vitest.config.ts — KuzuDB N-API destructor segfaults on fork exit
  are not real test failures (all 839 unit tests pass).
- Skip conn.close()/db.close() in globalSetup on Linux/macOS to
  prevent N-API destructor crash that kills the vitest process before
  fork workers can start (fixes search-core.test.ts EPIPE on Ubuntu CI).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: enable coverage auto-ratcheting with bumped thresholds

- Bump vitest coverage thresholds to match actual CI values (26/23/28/27)
- Enable thresholds.autoUpdate for automatic local ratcheting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ci): rich PR report with coverage bars, test counts, and threshold tracking

- Fix coverage N/A bug: use find instead of hardcoded artifact path
- Add emoji status icons and overall pass/fail banner
- Show covered/total counts alongside percentages
- Add visual progress bars with green/red threshold indicators
- Show test suite count and duration
- Add collapsible auto-ratchet explainer
- Graceful fallback when coverage data is unavailable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: bump version to 1.3.11, update CHANGELOG, add release.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:00:45 +00:00
Jason c2bd8667a3 feat(models): add DeepSeek model configurations
Add configurations for DeepSeek-V3 and DeepSeek-Chat models
via OpenRouter integration.

- DeepSeek-V3: Reasoning model (input: /usr/bin/bash.27, output: .10)
- DeepSeek-Chat: Chat model (input: /usr/bin/bash.14, output: /usr/bin/bash.28)

Fixes #215
2026-03-08 15:50:25 +08:00
Gergő Magyar 1952c2c346 ci: add macOS to cross-platform test matrix (#208)
* ci: add macOS to cross-platform test matrix

* ci: run integration tests on all platforms, add macOS to matrix

* ci: add build step before cross-platform integration tests

Worker pool requires compiled parse-worker.js in dist/.
Without build, falls back to sequential parsing which times out
on macOS runners.

* fix(pipeline): resolve worker path to dist/ when running under vitest

import.meta.url points to src/ under vitest where no .js exists.
Fall back to dist/core/ingestion/workers/parse-worker.js so worker
threads spawn correctly on all platforms instead of sequential fallback
that times out on slower macOS CI runners.

* ci: split cross-platform unit and integration tests into parallel jobs

* test: add integration tests for worker pool and hooks e2e

- worker-pool.test.ts: 7 tests verifying dist/ worker spawning,
  multi-file parsing, progress reporting, and clean termination
- hooks-e2e.test.ts: 28 tests with real git repos testing staleness
  detection, embeddings flag, mutation regex, cwd validation,
  and .gitnexus directory discovery

* refactor: extract shared hook test helpers and simplify worker fallback

- Extract runHook/parseHookOutput into test/utils/hook-test-helpers.ts
- Deduplicate fileURLToPath calls in pipeline.ts worker resolution
- Add isDev logging for worker pool creation failures

* fix(test): accept timeout as valid outcome for PreToolUse CLI spawn

The Plugin hook spawns `gitnexus augment` which may hang on macOS
when the CLI is unavailable, causing a 10s timeout (status=null)
instead of a clean exit (status=0). Accept both as non-crash outcomes.
2026-03-07 10:38:32 +00:00
Linus Beckhaus c4eaf45ab1 feat(hooks): auto-reindex notification with cross-platform hardening (#205)
Adds PostToolUse hook that detects stale GitNexus index after git mutations (commit, merge, rebase, cherry-pick, pull) and notifies the agent to reindex. Uses lightweight staleness check (git rev-parse HEAD vs meta.json) instead of running gitnexus analyze synchronously, avoiding KuzuDB corruption and 120s blocks. Security and cross-platform hardening: remove shell:true from all spawnSync calls, use .cmd extensions on Windows, add path.isAbsolute(cwd) guards, fix setup.ts path escaping with JSON.stringify, use sendHookResponse() consistently. Includes 73 regression tests.
2026-03-07 08:59:54 +00:00
Gergo Magyar 0796e1e68c chore: bump version to 1.3.10 and add CHANGELOG
Add CHANGELOG.md with release notes for v1.3.10 covering MCP transport
security hardening, dual-framing compatibility, lazy CLI loading, and
bug fixes from recent PRs.
2026-03-07 08:04:55 +00:00
ShockangandGergo Magyar 9d5ec5d19a Improve MCP startup compatibility and lazy-load CLI commands (#207)
* Fix MCP startup transport compatibility

* Preserve CLI flags in MCP startup fix

* Harden MCP transport error handling

* Harden transport security and improve type safety

Transport hardening:
- Add MAX_BUFFER_SIZE (10 MB) cap to prevent OOM from oversized
  Content-Length or unbounded newline-delimited input
- Replace recursive readNewlineMessage with iterative loop to prevent
  stack overflow from consecutive empty lines
- Tighten looksLikeContentLength to require 14+ bytes before matching
- Add closed-state guard and error handling to send()
- Simplify processReadBuffer loop to break on error
- Fix loose equality (==) to strict (===)
- Widen constructor param types to ReadableStream/WritableStream

Type safety:
- Constrain createLazyAction generics so export name is validated
  against the module's actual exports at compile time
- Use proper type guard instead of lint suppression
- Fix test tsconfig type errors

Regression tests for all hardening fixes (13 tests passing).

---------

Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
2026-03-07 07:47:09 +00:00
abhigyanpatwariandClaude Opus 4.6 4de40e4011 chore: update AI context files with inline imperative instructions
Regenerated CLAUDE.md and AGENTS.md using gitnexus@1.3.9 which replaces
the old skill-router format with inline imperative instructions (PR #190).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:12:00 +05:30
Gergő Magyar 20e8c52028 Merge pull request #144 from magyargergo/fix/lru-cache-zero-max-crash
fix: guard createASTCache against zero maxSize to prevent LRU cache crash
2026-03-06 09:22:17 +00:00
Gary Magyar 2868da5ddb Merge remote-tracking branch 'origin/main' into fix/lru-cache-zero-max-crash 2026-03-06 09:02:50 +00:00
Abhigyan PatwariandClaude Opus 4.6 3db47f7ee5 fix(ingestion): align CALLS edge sourceId with node ID format (#194)
findEnclosingFunctionId generated IDs without :startLine suffix,
but node creation includes it. This caused every CALLS edge to
reference a non-existent source node, making the process detector
find 0 entry points and produce 0 execution flows.

Bumps to 1.3.9.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:57:20 +05:30
Abhigyan PatwariandClaude Opus 4.6 821871cec1 chore: bump version to 1.3.8 (#193)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:23:01 +05:30
Abhigyan PatwariandClaude Opus 4.6 f9a54cd588 fix(cli): force-exit after analyze to prevent KuzuDB hang (#192)
KuzuDB's native module holds open handles that prevent Node.js from
exiting cleanly. Previously only force-exited when embeddings were used
(for ONNX Runtime segfault workaround), but the same issue affects all
analyze runs. Now always calls process.exit(0) after completion.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:17:41 +05:30
Abhigyan PatwariandClaude Opus 4.6 8c6b064d18 chore: bump version to 1.3.7 (#191)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:58:35 +05:30
Abhigyan PatwariandClaude Opus 4.6 84ef6524bc feat(ai-context): replace skill router with inline imperative instructions (#190)
CLAUDE.md and AGENTS.md now contain direct enforcement instructions instead
of a passive skill router table. Based on Vercel eval data showing skills
are skipped 56% of the time, and industry research on effective AGENTS.md
patterns from 2,500+ repos.

Key changes:
- Always/When/Never three-tier boundary structure
- RFC 2119 language (MUST, NEVER) for critical rules
- Exact tool commands with parameters inline
- Self-check checklist forcing model to verify its own work
- ~77 lines, well within the <150 line adherence threshold

Skills are still installed as bonus depth for Claude Code's skill system.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:47:23 +05:30
abhigyanpatwariandClaude Opus 4.6 5674b2201d feat: merge Laravel route detection (PR #133), revert unwanted doc changes
Merged PR #133 which adds AST-based Laravel Route::* extraction.
Reverted AGENTS.md, CLAUDE.md, and README.md to preserve current config,
crypto warning, Discord link, and correct language support count (12,
including Kotlin/Swift).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:20:27 +05:30
abhigyanpatwari 6915a9350b Merge branch 'pr-133' 2026-03-03 00:19:44 +05:30
Gary Magyar 76e0e5a35a fix: guard createASTCache against zero maxSize to prevent LRU cache crash
When a repo has no parseable files (e.g., unsupported languages or all
files filtered out), chunks.reduce returns 0, causing createASTCache(0)
to pass max:0 to LRUCache which throws TypeError. This clamps maxSize
to at least 1 and adds a progress message when no parseable files exist.
2026-03-02 08:47:20 +00:00
abhigyanpatwariandClaude Opus 4.6 8e7d976c2a fix: gracefully skip files when language parser is unavailable (#136)
Instead of crashing the pipeline when a native tree-sitter binding
(e.g. tree-sitter-swift) fails to build, skip those files early and
warn the user with an actionable message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:23:05 +05:30
Güneş Bizim 46b4b7e157 Merge origin/main — add Kotlin/Swift support, resolve conflicts
- Resolved FUNCTION_NODE_TYPES: keep 'anonymous_function' for PHP (php_only grammar),
  add Kotlin 'lambda_literal' and Swift 'init_declaration'/'deinit_declaration'
- Resolved pipeline.ts: adopt chunked pipeline structure, integrate
  processRoutesFromExtracted into per-chunk worker data processing
- Resolved framework-detection.ts: use upstream AST-BASED FRAMEWORK DETECTION heading
- Fixed accumulated/mergeResult in parse-worker to include routes field
2026-03-01 22:33:23 +03:00
abhigyanpatwariandClaude Opus 4.6 cbeb0e231a docs: add Kotlin to supported languages in README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:23:04 +05:30
abhigyanpatwariandClaude Opus 4.6 3431edcea0 fix(test): update ingestion-utils test for Kotlin support
Move .kt from unsupported list to supported, add Kotlin test case
for .kt and .kts extensions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:18:21 +05:30
abhigyanpatwariandClaude Opus 4.6 40cb863cb4 chore: update package-lock.json with tree-sitter-kotlin
The Kotlin PR added tree-sitter-kotlin to package.json but didn't
include the lockfile update, causing npm ci to fail in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:14:36 +05:30
abhigyanpatwari ee95808478 Merge pull request #84 from magyargergo/feat/kotlin-language-support
feat: add Kotlin language support
2026-03-01 23:11:25 +05:30
abhigyanpatwari 3e3ea86ce4 Merge origin/main into feat/kotlin-language-support 2026-03-01 23:10:52 +05:30
abhigyanpatwariandClaude Opus 4.6 1a52d05131 fix(test): use dangerouslyIgnoreUnhandledErrors instead of forceExit
forceExit killed the fork worker before local-backend.test.ts finished,
losing 12 test results. The real issue is KuzuDB's C++ destructor
segfaulting during fork process exit — all tests pass but vitest
reports the post-test crash as a failure.

dangerouslyIgnoreUnhandledErrors ignores the process-level crash
without affecting test results (98/98 tests still run and report).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:36:38 +05:30
abhigyanpatwariandClaude Opus 4.6 3d64e26f8f fix(test): add forceExit to prevent KuzuDB native cleanup hang in CI
KuzuDB's C++ destructor crashes the vitest fork worker on exit,
causing a ~7 minute hang before timeout. forceExit kills the
worker immediately after tests complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:33:18 +05:30
abhigyanpatwariandClaude Opus 4.6 3576802574 fix(test): use HEAD~1 instead of root commit in staleness test
GitHub Actions shallow clones don't have the root commit available,
causing checkStaleness to fail silently. HEAD~1 is always available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:23:06 +05:30
abhigyanpatwariandClaude Opus 4.6 20ebd6b781 feat: security hardening, MCP improvements, skills, hooks, and CLI updates
- Export security primitives (CYPHER_WRITE_RE, isWriteQuery, isTestFilePath,
  VALID_NODE_LABELS, VALID_RELATION_TYPES) from local-backend
- Improve MCP kuzu-adapter with better query handling
- Add PR review skill for Claude, Cursor, and npm package
- Add CLI guide and CLI skills
- Update hooks for Claude plugin and Cursor integration
- Remove deprecated claude-hooks.ts CLI module
- Update eval-server, setup, and analyze CLI commands
- Improve CSV generator and ingestion processors
- Update CLAUDE.md and AGENTS.md configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:13:42 +05:30
abhigyanpatwariandClaude Opus 4.6 8a100a76d3 test: add test suite with vitest (unit + integration + fixtures)
- 59 test files covering unit and integration tests
- vitest config with coverage thresholds and fork pooling
- Test fixtures (mini-repo + multi-language sample code)
- Add vitest + coverage-v8 to devDependencies
- Add test scripts (test, test:integration, test:all, test:watch, test:coverage)
- Move typescript to devDependencies where it belongs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:07:02 +05:30
abhigyanpatwariandClaude Opus 4.6 c129e71ee7 ci: harden publish pipeline with CI gate, version check, and provenance
- Add workflow_call trigger to ci.yml so publish can reuse it as a gate
- Replace minimal publish.yml with hardened pipeline:
  - Full CI must pass before publish (typecheck + tests + cross-platform)
  - Verify git tag matches package.json version
  - Explicit build step + dry-run before real publish
  - npm provenance attestation enabled
  - Auto-create GitHub Release with generated notes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:02:10 +05:30
Abhigyan Patwari 80eff73459 Add notice about GitNexus cryptocurrency claims
Added important notice regarding cryptocurrency affiliations.
2026-03-01 11:29:00 +05:30
Gary Magyar 48c8e6fe57 chore: merge upstream main (swift optional deps) and reorder kotlin entries
Merge origin/main which moves tree-sitter-swift to optionalDependencies
with conditional imports. Reorder Kotlin entries before C/C++/PHP in all
files so they don't sit adjacent to Swift entries, preventing future
merge conflicts when upstream modifies Swift support.
2026-02-28 12:55:55 +00:00
abhigyanpatwariandClaude Opus 4.6 2eca3e0da3 fix(swift): move tree-sitter-swift to optionalDependencies and use conditional imports
The PR merge reverted the Swift install fix. tree-sitter-swift must be
in optionalDependencies with conditional createRequire imports, otherwise
npm install fails on systems where the native build can't succeed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:14:53 +05:30
Gary Magyar e046bf734d chore: merge upstream main into feat/kotlin-language-support
Resolve conflicts between Kotlin and Swift language support additions.
Both languages are now fully supported side by side.
2026-02-28 12:30:53 +00:00
Bhaskar Lalwani b30248f969 Updated README with Discord and badge updates
Added Discord link and updated badges for npm and license.
2026-02-28 17:44:00 +05:30
abhigyanpatwariandClaude Opus 4.6 eb48c7352e fix: read CLI version from package.json instead of hardcoding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:32:28 +05:30
abhigyanpatwariandClaude Opus 4.6 29db66c304 chore: bump version to 1.3.5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:07:34 +05:30
Abhigyan Patwari b7c582de76 Merge pull request #94 from jandyx/feat/swift-language-support
feat(swift): full Swift / iOS language support with SPM import resolution
2026-02-28 16:03:27 +05:30
Gary Magyar 508402fd4a feat: add full Kotlin language support 2026-02-28 10:06:52 +00:00
Gary Magyar da63281a5a Merge remote-tracking branch 'origin/main' into feat/kotlin-language-support
# Conflicts:
#	gitnexus/src/core/ingestion/parsing-processor.ts
#	gitnexus/src/core/ingestion/workers/parse-worker.ts
2026-02-28 08:57:40 +00:00
abhigyanpatwariandClaude Opus 4.6 c758f4eaf0 chore: bump version to 1.3.4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 07:56:08 +05:30
Abhigyan Patwari fd507a19ae Merge pull request #105 from christopheralex-cc/fix/web-ui-server-connect-path-mismatch
fix(web): map API path field to repoPath in fetchRepoInfo
2026-02-28 07:53:01 +05:30
Abhigyan Patwari 799de20172 Merge pull request #102 from PurpleNewNew/feat/ast-decorator-detection
feat(ingestion): add AST decorator-based entrypoint hints
2026-02-28 07:41:57 +05:30
Abhigyan Patwari 2be88ae1f8 Merge pull request #61 from strazzere/fix/refactor_shell_commands
fix: ensure exec usage does not allow poisoning
2026-02-28 07:13:25 +05:30
Dmytro 2915e60630 fix(cli): run codex mcp add via shell on Windows 2026-02-27 14:01:10 +01:00
Abhigyan Patwari 019ed3ff85 Merge pull request #99 from abhigyanpatwari/fix/lazy-embed-import
fix: lazy-import embeddings to avoid onnxruntime crash on Node v24+
2026-02-27 18:14:59 +05:30
abhigyanpatwariandClaude Opus 4.6 6b4f10cae1 fix: remove unconditional embedder import from disconnect() to prevent crash on Node v24+
The disconnect() method was unconditionally importing embedder.js on
every graceful shutdown, which loads @huggingface/transformers and
onnxruntime-node — triggering the exact crash this branch fixes.
Since process.exit(0) follows immediately, the OS reclaims all
resources without needing disposeEmbedder(). Matches the pattern
already established in analyze.ts (lines 318-320).

Fixes #89

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:41:06 +05:30
Gary Magyar 43f525d056 fix(kotlin): guard against double-appending .* to wildcard import paths
Add endsWith('.*') check before appending wildcard suffix to prevent
possible double-append if grammar returns identifier text that already
includes the wildcard.
2026-02-27 10:28:39 +00:00
Gary Magyar e2a8bfa5ab fix(kotlin): enable import dependency tree resolution for Kotlin files
Add .kt/.kts to EXTENSIONS array, parameterize Java resolvers into JVM
resolvers (resolveJvmWildcard, resolveJvmMemberImport), and unify
Java+Kotlin dispatch in both import processing paths. Detect wildcard
imports via AST child node inspection in parse worker.

Validated against okhttp repo: 524 .kt files detected, imports resolve
correctly to .kt files (e.g. okhttp3.OkHttpClient -> OkHttpClient.kt).
2026-02-27 10:26:23 +00:00
Gary Magyar ee6753bf05 fix(kotlin): capture constructor-based heritage (class Foo : Bar())
The heritage query only matched bare user_type delegation specifiers
(interface implementation), missing constructor_invocation patterns
used for class extension. Adds a second heritage pattern for
constructor invocations, capturing ~3x more heritage edges.
2026-02-27 09:28:58 +00:00
Gary Magyar 1b8c3c77af feat(kotlin): distinguish interfaces from classes in knowledge graph
tree-sitter-kotlin (fwcd) has no interface_declaration node — both
interfaces and classes are class_declaration nodes. Use anonymous
keyword literal matching ("interface" vs "class") to produce the
correct @definition.interface / @definition.class captures.

Verified against two real Kotlin repos: a small one (3 Interface,
92 Class) and a large one (35 Interface, 677 Class, 5998 Function).
2026-02-27 09:09:26 +00:00
Güneş Bizim a7fc9d2f88 feat(laravel): add route detection and route group support
Parse Route::* calls from PHP AST using a procedural walk that tracks
group nesting state (middleware, prefix, controller cascade). Creates
CALLS edges from route files to controller methods.

Supported patterns:
- Route::get/post/put/patch/delete/any/match with [Controller::class, 'method']
- Route::resource / Route::apiResource (expanded to individual actions)
- Invokable controllers (Controller::class -> __invoke)
- String syntax ('Controller@method')
- Route::middleware()->group() fluent chains (arbitrary depth)
- Route::prefix()->name()->group() chains
- Route::controller(X::class)->group() shared controller
- Route::group(['middleware' => ...], fn) array API
- Nested groups with full middleware/prefix cascade

Implementation notes:
- Uses anonymous_function node type (php_only grammar, not anonymous_function_creation_expression)
- File paths from workers are relative; check startsWith('routes/') not includes('/routes/')
- Edges: File -> Method with reason='laravel-route', confidence 0.9 (import-resolved)
- Mirrored to gitnexus-web inline in processCalls (no worker layer)
2026-02-27 11:17:36 +03:00
christopher 0074fd71ff fix(web): map API path field to repoPath in fetchRepoInfo
The backend `/api/repo` endpoint returns `path` but `ServerRepoInfo`
expects `repoPath`, causing `undefined.split('/')` crash in App.tsx
when connecting to a local gitnexus serve instance.

Fixes #92
2026-02-27 16:05:47 +08:00
PurpleNewNew de935a4f4c feat(ingestion): add AST decorator-based entrypoint hints 2026-02-27 15:40:42 +08:00
abhigyanpatwariandClaude Opus 4.6 989673a624 fix: lazy-import embeddings to avoid onnxruntime crash on unsupported Node versions
Convert static imports of @huggingface/transformers (which triggers
onnxruntime-node native binary loading) to dynamic import() calls.
This prevents crashes on Node versions whose ABI isn't supported by
the prebuilt onnxruntime binaries (e.g. Node v24).

Affected entry points:
- cli/analyze.ts: embedding pipeline only loaded when --embeddings is passed
- mcp/local/local-backend.ts: embedder only loaded on first semantic search
- server/api.ts: embedder only loaded when search endpoint needs embeddings

Fixes #89

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:09:17 +05:30
Abhigyan Patwari 8c41970631 Merge pull request #96 from abhigyanpatwari/fix/mcp-no-repos-crash
fix(mcp): don't crash server when no repos are indexed
2026-02-27 11:35:31 +05:30
abhigyanpatwariandClaude Opus 4.6 5c3a32d0c6 fix(kuzu): remove duplicate ftsLoaded declaration that broke typecheck
The module-level `let ftsLoaded` was declared twice (line 19 and 679),
causing TS2451. Removed the duplicate and cleaned up redundant
assignments in loadFTSExtension.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:31:07 +05:30
abhigyanpatwariandClaude Opus 4.6 a8b3c6b23f fix(mcp): don't crash server when no repos are indexed (#91)
The MCP server called process.exit(1) at startup when no repositories
were found in the registry. This prevented users from configuring the
MCP integration before running `gitnexus analyze`.

The server now starts gracefully with 0 repos and discovers newly
indexed repos lazily via refreshRepos() on each tool call.

Closes #91

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:36:35 +05:30
jandyx 15caf1e014 fix(swift): add missing Enum→Enum CodeRelation pair to schema 2026-02-27 12:00:41 +08:00
jandyx 1ed34a0007 docs: update supported languages list to include PHP and Swift 2026-02-27 11:45:05 +08:00
jandyx 7a4bc9a260 docs: improve postinstall script comments with background and TODO 2026-02-27 11:39:14 +08:00
jandyx 3872a73875 feat(swift): add prebuilt Swift WASM binary for web package
Sourced from tree-sitter-wasms@0.1.13 prebuilt collection.
2026-02-27 11:22:17 +08:00
jandyx f557716998 fix(swift): improve postinstall to auto-rebuild after patching binding.gyp
The script now detects missing native binding and runs node-gyp rebuild
after patching. This handles the case where tree-sitter-swift's own
postinstall fails during npm install — our postinstall picks up,
patches binding.gyp, and rebuilds successfully.
2026-02-27 11:14:45 +08:00
jandyx ae8a76511d fix(swift): address review gaps — schema, call-processor, web package, postinstall
- Add init_declaration/deinit_declaration to call-processor FUNCTION_NODE_TYPES
  and findEnclosingFunction (syncs with parse-worker, avoids Dart PR #83 rejection)
- Add 7 missing CodeRelation FROM-TO pairs in schema.ts to eliminate analyze warnings
  (Function→Property, Constructor→Property/Typedef, Enum→Class/Interface,
   Struct→Interface, TypeAlias→Class)
- Mirror all Swift support to gitnexus-web: supported-languages, utils, queries,
  framework-detection, entry-point-scoring, parser-loader WASM path
- Add postinstall script to patch tree-sitter-swift binding.gyp actions array
2026-02-27 09:47:05 +08:00
jandyx e803e7e9d6 feat(swift): add comprehensive Swift/iOS language support
- Enable Swift in supported languages enum
- Add tree-sitter-swift parser loading (v0.6.0)
- Add .swift file extension mapping
- Implement full tree-sitter queries (class, struct, enum, protocol,
  extension, actor, function, property, init, imports, calls, heritage)
- Add Swift export detection (public/open modifiers)
- Add Swift/iOS built-in name filtering (~70 entries: stdlib, UIKit,
  Foundation, GCD, Combine, collection methods)
- Add SPM module import resolution (Sources/<Target>/ scanning)
- Add iOS/SwiftUI framework path detection with entry point multipliers
- Add Swift entry point scoring patterns (UIKit lifecycle, SwiftUI body,
  Coordinator, AppDelegate/SceneDelegate)
- Add Swift test file detection patterns
2026-02-27 08:40:47 +08:00
abhigyanpatwariandClaude Opus 4.6 50fc8df2a1 fix(web): replace stale isBackendMode ref with serverBaseUrl
PR 66 refactored isBackendMode to serverBaseUrl in useAppState but
missed updating EmbeddingStatus.tsx, causing TypeScript build failure
on Vercel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 05:08:45 +05:30
Gary Magyar c37b63ae8b feat: add Kotlin language support
Add end-to-end Kotlin parsing, symbol extraction, and visibility detection.
Extract findSiblingChild helper into utils.ts for clean AST traversal of
Kotlin's modifiers/visibility_modifier sibling pattern. Fix pre-existing
duplicate ftsLoaded declaration in kuzu-adapter.ts.

Files changed:
- supported-languages.ts: add Kotlin enum member
- parser-loader.ts, parse-worker.ts: register tree-sitter-kotlin
- tree-sitter-queries.ts: add Kotlin queries for classes, interfaces,
  objects, functions, properties, imports, calls, and heritage
- parsing-processor.ts, parse-worker.ts: add Kotlin visibility detection
- call-processor.ts, parse-worker.ts: add Kotlin builtins and node types
- utils.ts: add .kt/.kts extension mapping and findSiblingChild helper
- package.json: add tree-sitter-kotlin dependency
2026-02-26 17:01:35 +00:00
abhigyanpatwariandClaude Opus 4.6 7fe8830402 merge: resolve conflicts with main for remote server mode (PR #66)
Keep main's barLog implementation, preserve both currentDbPath and
ftsLoaded reset in closeKuzu, take PR's new resolveRepo pattern
for /api/query. Path traversal guard confirmed intact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:40:25 +05:30
abhigyanpatwariandClaude Opus 4.6 39b01f101e feat(skills): rewrite skill descriptions for better auto-invocation
Skill descriptions were too tool-centric ("using knowledge graph", "blast
radius") which prevented Claude Code from matching them to user intent.
Rewritten to user-intent-driven format with "Use when..." phrasing and
example trigger phrases so Claude can semantically match user requests.

Updated across all 3 sources: gitnexus/skills/, gitnexus-claude-plugin/skills/,
.claude/skills/, and the ai-context.ts fallback generator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:24:41 +05:30
Abhigyan Patwari 7cb88707a4 Merge pull request #58 from BlockSecCA/pr/cuda-fallback
Probe for CUDA before attempting GPU embeddings
2026-02-26 18:22:37 +05:30
abhigyanpatwariandClaude Opus 4.6 2a444acf1d fix(plugin): hook logic bug and version mismatch
- Fix hook exit status check: || → && so failed augment errors
  don't get injected into Claude's context as graph results
- Add exit status check to npx fallback (same bug)
- Update plugin version from 1.2.11 to 1.3.3 in both
  marketplace.json and plugin.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:08:32 +05:30
abhigyanpatwari d97d43f1b8 Merge branch 'pr-64' 2026-02-26 17:38:05 +05:30
Nico Prieto 04be81f655 fix(server): harden multi-repo API and MCP safety 2026-02-26 13:01:30 +01:00
Nico PrietoandClaude Opus 4.6 f047a84d82 fix(server): restore security guards and error handling
Address code review feedback on the server-mode PR:

Critical fixes:
- Restore CORS whitelist (localhost + gitnexus.vercel.app only)
- Bind to 127.0.0.1 by default; add --host CLI flag for opt-in remote access
- Restore path traversal guard on /api/file (resolve + startsWith check)
- Restore try/catch on all route handlers + global error middleware
- Restore SIGINT/SIGTERM graceful shutdown handlers

Bug fixes:
- Add mutex to core initKuzu to prevent race conditions on concurrent
  DB switches (two requests for different repos no longer corrupt state)
- Track ftsLoaded flag and reset on DB switch / close so FTS extension
  is reloaded for each new database connection
- Restore input validation on /api/query (cypher required) and
  /api/search (query required)

Improvements:
- Add TTL-based cleanup for orphaned MCP sessions (30min idle eviction)
  to prevent memory leaks from network drops that skip onclose

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:42:00 +01:00
abhigyanpatwariandClaude Opus 4.6 0421fcbc76 merge: resolve conflicts with main for PHP/Laravel support
Merge main into feat/php-laravel-support, resolving conflicts in:
- csv-generator.ts: add description column to streaming CSV architecture
- kuzu-adapter.ts: add description to COPY queries and insert/merge ops
- schema.ts: add description STRING to all code element tables, FROM Method TO Property
- parse-worker.ts: integrate PHP built-ins and Eloquent extraction with sub-batch worker
- import-processor.ts: integrate PHP PSR-4 resolution with ImportResolutionContext
- package-lock.json: regenerate from main's 1.3.3 base with tree-sitter-php

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:39:17 +05:30
Abhigyan Patwari 1403cdbf6d Merge pull request #75 from CrazyBunQnQ/main
feat(ui): Add a copy button to the Nexus AI and copy the md result
2026-02-26 13:35:44 +05:30
abhigyanpatwariandClaude Opus 4.6 0e8eed4a8a merge: resolve conflicts with main in CLAUDE.md and AGENTS.md
Keep main's stats line (GitnexusV2, 1348 symbols) with PR's skill path renames.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 13:10:27 +05:30
CrazyBunQnQ d6738c51c1 feat(ui): Add a copy button to the Nexus AI and copy the markdown results after clicking it 2026-02-26 15:14:08 +08:00
abhigyanpatwariandClaude Opus 4.6 a5096e8029 revert: restore 512KB file size limit, keep improved skip message
2MB limit caused FTS crash on large codebases. 512KB is safe and only
skips generated/vendored files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:20:15 +05:30
abhigyanpatwariandClaude Opus 4.6 36e64e892f fix(ux): raise file size limit to 2MB, quiet warning output
- Raise MAX_FILE_SIZE from 512KB to 2MB to capture more real source files
- Replace verbose per-warning output with single summary line
- Soften skip message wording ("likely generated/vendored")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:46:10 +05:30
abhigyanpatwariandClaude Opus 4.6 bb6c22a22c fix(schema): add 6 missing FROM/TO pairs for LLVM-style codebases
Class→Namespace, Class→Typedef, Struct→Struct, Struct→Class,
Struct→Enum, Namespace→Struct

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:43:55 +05:30
abhigyanpatwariandClaude Opus 4.6 420122065a chore: bump version to 1.3.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:28:38 +05:30
abhigyanpatwariandClaude Opus 4.6 bef319491a feat(gitnexus): large-repo optimizations, multi-language support, bug fixes
Optimize ingestion pipeline for massive codebases (Linux kernel ~75K files):
- Worker pool: 8 threads, 1500 sub-batch, 30s per-batch timeout
- Streaming CSV generation with per-stream MaxListeners fix
- FileContentCache 3000 entries, resolveCache LRU eviction (20% at 100K cap)
- Leiden 60s timeout with single-community fallback
- SIGINT graceful shutdown, FTS dedup flag

Multi-language node support (Struct, Enum, Macro, Impl, Trait, etc.):
- Add 16 multi-language types to NodeLabel union
- Separate CSV writers with correct schema (no isExported)
- KuzuDB schema: backtick-escape reserved words (Macro, Union, Enum)
- Add all FROM/TO pairs for multi-language relationship edges

Fix bugs: getKuzuStats/deleteNodesForFile backtick escaping,
insertNodeToKuzu/batchInsertNodesToKuzu missing escaping + isExported,
progress bar flickering and timer display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:23:23 +05:30
Dmytro 984316d260 feat: add Codex MCP and skills support 2026-02-25 15:43:12 +01:00
Linus Beckhaus 238abbd947 refactor(skills): prefix all skill names with gitnexus- for disambiguation
Skill folder names determine invocation paths in Claude Code plugins
(e.g. plugin:gitnexus:gitnexus-cli). Generic names like "cli" or
"debugging" could collide with other plugins, so prefix them all with
gitnexus- for clarity.

Updated across plugin dirs, main package source files, ai-context.ts
generator, setup.ts installer, and all CLAUDE.md/AGENTS.md routing tables.
2026-02-25 14:15:42 +01:00
Linus Beckhaus 3f4c4cb4aa remove local claude settings from git 2026-02-25 14:11:53 +01:00
Linus Beckhaus 4f4fe9e587 update version 2026-02-25 13:40:49 +01:00
Linus Beckhaus dbf3495713 fix(skills): remove gitnexus- prefix from skill frontmatter names
Skill names should match folder names since the plugin namespace
(gitnexus:) already provides context. Avoids redundant display like
gitnexus:gitnexus-cli → now gitnexus:cli.
2026-02-25 13:39:25 +01:00
Linus Beckhaus 5b8ce44537 format: format skills 2026-02-25 13:36:14 +01:00
Linus Beckhaus ffc4b69004 feat(plugin): add marketplace, fix manifest, and add CLI commands skill
Add .claude-plugin/marketplace.json at repo root so users can permanently
install via `/plugin marketplace add nicosxt/gitnexus`. Remove invalid
hooks/mcpServers fields from plugin.json (auto-discovered at default
locations). Add cli skill covering all agent-relevant CLI commands
(analyze, status, clean, wiki, list) with correct flags. Update guide
skill and CLAUDE.md generator routing tables.
2026-02-25 13:30:47 +01:00
abhigyanpatwariandClaude Opus 4.6 470a3377b3 fix(eval): import paths, patch extraction, model configs
- Fix imports from eval.agents/eval.environments to relative (agents/environments)
- Add hatch wheel config for correct package discovery
- Extract git diff patch from container for SWE-bench submission
- Use sys.executable instead of hardcoded "python" for venv compat
- Upgrade claude-haiku config to 4.5
- Add minimax-m2.1 model config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:11:00 +05:30
abhigyanpatwariandClaude Opus 4.6 91289404c2 feat(gitnexus): v1.2.9 — impact enrichment, cypher markdown, Windows setup fix
- Impact tool now returns risk score, affected processes/modules, and summary
- Cypher tool formats results as markdown tables for LLM readability
- Context tool includes module (functional area) field
- Semantic search skips model init when embeddings are disabled
- Setup: wrap npx in cmd /c on Windows for .cmd script compatibility
- Embedder: silence stderr during ONNX model load to protect MCP stdio
- API: use executeCypher directly to avoid double formatting
- Add community integrations section to READMEs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:10:51 +05:30
Linus Beckhaus 397dad8ec4 feat(plugin): transform Claude Code plugin into self-contained installable package
Bundle MCP server config (.mcp.json), fix hook to use spawnSync with npx
fallback and read stderr (KuzuDB stdout workaround), wire plugin.json with
hooks/mcpServers paths, add guide skill with tools/resources/schema reference,
add AMP-compatible mcp.json to all skill dirs, and slim CLAUDE.md generator
by moving reference content into the guide skill.
2026-02-25 11:47:16 +01:00
Nico PrietoandClaude Opus 4.6 7ee2dd1087 feat: add remote server connection mode and multi-repo switching
Replace the old local-only backend mode with a new server connection
flow that lets the web UI connect to any running GitNexus server,
download the pre-built knowledge graph, and explore it without WASM.

- Add server-connection service with streaming download and progress
- Replace DropZone backend tab with server connect UI (URL input, progress bar, cancel)
- Add repo switcher dropdown in Header when multiple repos are indexed
- Mount MCP server over StreamableHTTP at /api/mcp for remote AI tool access
- Refactor api.ts to query KuzuDB directly instead of routing through LocalBackend
- Fix KuzuDB adapter to support switching between databases (close old before opening new)
- Extract createMCPServer() from startMCPServer() for transport-agnostic reuse
- Support ?server= query param for bookmarkable auto-connect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:38:28 +01:00
Güneş BizimandClaude Sonnet 4.6 6aab580f93 fix(schema): add missing CodeRelation pairs for Class->Trait and Method->Property
Laravel projects commonly use traits (HasFactory, SoftDeletes, etc.) on classes
and methods that reference model properties. These relation pairs were missing
from the CodeRelation table, causing edges to be dropped during indexing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 11:33:54 +03:00
Güneş BizimandClaude Sonnet 4.6 1c02a06d1b feat(graph): persist multi-language nodes to KuzuDB and add description field
- Add description STRING column to all code element schemas (Function, Class,
  Interface, Method, CodeElement, and all CODE_ELEMENT_BASE types including Property)
- Add generateMultiLangNodeCSV() to write Struct, Enum, Trait, Property, etc.
  nodes to KuzuDB (previously they existed in-memory only, never reached the DB)
- Update generateAllCSVs() to generate CSVs for all 19 multi-language types
- Fix getCopyQuery() to use correct column list per table type:
  - Multi-language tables: id,name,filePath,startLine,endLine,content,description
  - Core tables: id,name,filePath,startLine,endLine,isExported,content,description
- Update batchInsertNodesToKuzu and insertNodeToKuzu to include description field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 10:31:53 +03:00
Güneş BizimandClaude Sonnet 4.6 890fedaa09 feat(php): track Eloquent model properties and relationships with structured metadata
- Add PHP property_declaration query to capture class properties ($fillable, $casts, etc.)
- Extract array values from Eloquent model properties as description field:
  - $fillable → comma-separated field names (e.g. "name, email, password")
  - $casts → key:type pairs (e.g. "email_verified_at:datetime, active:boolean")
  - $hidden, $guarded, $with, $appends similarly
- Detect Eloquent relationship methods (hasMany, hasOne, belongsTo, etc.) and
  annotate them with relation type and target model (e.g. "hasMany(Post)")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 10:31:35 +03:00
Güneş BizimandClaude Sonnet 4.6 8a79465cbf feat(laravel): add Service/Repository pattern support
- Add /services/ (1.8x) and /repositories/ (1.5x) path multipliers
- Add Service$, Repository$, find, findAll, save, delete to PHP entry point patterns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:28:42 +03:00
Güneş BizimandClaude Sonnet 4.6 945235ce56 fix(php): correct PHP query node type names for php_only grammar
- Replace static_call_expression -> scoped_call_expression (php_only name)
- Replace class_implements -> class_interface_clause
- Use [(name) (qualified_name)] alternatives in heritage patterns to match
  both simple names (Controller) and fully-qualified names (App\Models\User)

All patterns verified against tree-sitter-php node-types.json and live parse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:13:26 +03:00
Güneş BizimandClaude Sonnet 4.6 e67d6c63d3 feat(php): add PHP WASM support for web UI
- Copy tree-sitter-php_only.wasm to public/wasm/php/tree-sitter-php.wasm
- Register PHP WASM path in web parser-loader languageFileMap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:05:02 +03:00
Güneş BizimandClaude Sonnet 4.6 58063ca9ed feat(laravel): add Laravel framework path detection and entry point scoring
- Add Laravel route/controller/job/listener/middleware/provider/policy/model
  path patterns with appropriate multipliers (1.5–3.0)
- Add 'laravel' entry to FRAMEWORK_AST_PATTERNS for documentation
- Add PHP ENTRY_POINT_PATTERNS: RESTful methods, __invoke, handle, boot, etc.
- Add PHP/Laravel test file detection (Test.php, Spec.php, tests/feature/, tests/unit/)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:49 +03:00
Güneş BizimandClaude Sonnet 4.6 804d975cd0 feat(php): add PHP/PSR-4 import resolution with composer.json support
- Add .php, .phtml to EXTENSIONS array
- Add ComposerConfig interface and loadComposerConfig() (mirrors TsconfigPaths pattern)
- Add resolvePhpImport() with PSR-4 longest-match resolution + suffix fallback
- Wire PHP handling into processImports() and processImportsFromExtracted()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:39 +03:00
Güneş BizimandClaude Sonnet 4.6 2164dc22f1 feat(php): add PHP tree-sitter queries for all PHP constructs
Add PHP_QUERIES covering: namespaces, classes, interfaces, traits,
enums (PHP 8.1), functions, methods, use-statement imports, function/
method/static/nullsafe calls, and heritage (extends, implements, trait use).
Trait query captures enclosing class name to satisfy parse-worker heritage logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:32 +03:00
Güneş BizimandClaude Sonnet 4.6 30aba01188 feat(php): register PHP tree-sitter grammar in parser loader and parse worker
- Import PHP from tree-sitter-php and use PHP.php_only in languageMap
- Add anonymous_function_creation_expression to FUNCTION_NODE_TYPES
- Add PHP export detection (public modifier / top-level = exported)
- Add PHP built-in functions to BUILT_INS to reduce call graph noise

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:06 +03:00
Güneş BizimandClaude Sonnet 4.6 92a5d026c8 feat(php): install tree-sitter-php dependency
Add tree-sitter-php ^0.23.0 to gitnexus/package.json. Package exports
{ php, php_only } grammars; we use php_only for pure PHP files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:03:57 +03:00
Güneş BizimandClaude Sonnet 4.6 7883bf2cf0 feat(php): add PHP to SupportedLanguages enum and file extension detection
Uncomment PHP = 'php' in both CLI and web SupportedLanguages enums.
Add .php, .phtml, .php3/.4/.5/.8 extension detection to getLanguageFromFilename()
in both packages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:03:44 +03:00
Tim Strazzere 73590b2862 fix: ensure exec usage does not allow poisoning
Previous usage was vulnerable to "poisoned" tags
which could enduce commands to be run when a
`detectChanges` command was hit. This was primarily
fixed in `local-backend.ts` however I changes the
`execSync` usages where any injection was potentially
able to be performed (e.g. staleness).

Skipped touching wiki and generator as those use static
input, though these should potentially be changed over
in the future.
2026-02-24 13:42:29 -08:00
CarlosandClaude bdda9afdca Rework CUDA probe: use ldconfig + env vars instead of nvidia-smi
Addresses review feedback:
- Remove nvidia-smi early return (driver ≠ runtime libs)
- Use ldconfig -p as primary check (covers all architectures and paths)
- Fall back to CUDA_PATH / LD_LIBRARY_PATH for conda, /opt/cuda, etc.
- Switch from execSync to execFileSync (avoids spawning a shell)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-24 10:16:39 -05:00
abhigyanpatwariandClaude Opus 4.6 6be54ce9d3 fix(analyze): replace nonexistent bar.log with origLog
SingleBar from cli-progress doesn't have a .log() method (that's
MultiBar-only), causing a crash when any code calls console.log
during the pipeline.

Fixes #53
Thanks to @TranDatk for reporting in #56.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 11:05:32 +05:30
abhigyanpatwariandClaude Opus 4.6 2e390583fc fix(ci): remove tracked worktrees and gitignore them
Worktree directories were committed as gitlink submodule references,
causing CI failures during checkout. Remove them from tracking and
add .claude/worktrees/ to .gitignore to prevent recurrence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:42:53 +05:30
CarlosandClaude 575a4978f2 Probe for CUDA before attempting GPU embeddings
ONNX Runtime crashes with an uncatchable native error when CUDA
libraries are missing. This adds a lightweight probe (nvidia-smi +
libcublasLt.so.12 path check) before selecting the CUDA device,
falling back to CPU gracefully on systems without NVIDIA GPUs.

Fixes #53

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-23 22:50:46 -05:00
Abhigyan Patwari c379c39ae1 Merge pull request #49 from paulrobello/feat/local-backend-mode
feat: local backend mode for web UI
2026-02-23 21:06:24 +05:30
Paul Robello acd918f44e refactor(api): use executeCypher directly in buildGraph
Add public executeCypher(repoName, query) method to LocalBackend,
bypassing the callTool string dispatch and resolving the repo handle
directly. Update buildGraph in api.ts to call it instead of going
through callTool('cypher', ...) on every query.
2026-02-23 07:23:46 -08:00
Paul Robello a71924f774 fix: address PR review — CORS, search quality, and embedding flags
- Add https://gitnexus.vercel.app to CORS allowed origins so the
  deployed site can connect to the local backend server.
- Replace naive createHttpTextSearch (substring matching via Cypher)
  with createHttpHybridSearch that calls /api/search for full
  BM25 + semantic + RRF hybrid search on the server.
- Set isEmbeddingReady to false in backend mode (no local embedder),
  which also fixes {{QUERY_VECTOR}} handling — the existing error
  path in tools.ts now correctly returns a helpful message.
- Set isBM25Ready to true (available via server's hybrid search).
- Removes the Cypher injection bug (wrong escape char) since the
  function that contained it was replaced entirely.
2026-02-22 17:14:56 -08:00
Paul Robello 8dd1c19bec feat(web): wire agent/chat through HTTP in backend mode
Add HTTP wrapper functions (executeQuery, textSearch) to the ingestion
worker and a new initializeBackendAgent method that creates the agent
with HTTP-backed tool closures instead of local KuzuDB. useAppState
detects backend mode and routes agent initialization accordingly.
2026-02-22 17:14:18 -08:00
Paul Robello 56f92ca1ad fix(web): skip embeddings pipeline in backend mode
The WASM worker DB is never initialized in backend mode, so
startEmbeddings() now returns early. Also hides the embedding
status button in the header when using the local backend.
2026-02-22 17:14:18 -08:00
Paul Robello 36c7b3ed12 fix: address final code review issues
- Fix field name mismatch in /api/repo (repoPath -> path)
- Add setProgress(null) on successful backend repo load
- Prevent DropZone tab auto-switch from re-triggering
- Make isDatabaseReady() return true in backend mode
- Increase fetchGraph timeout to 60s for large repos
- Replace dynamic import with static import for runCypherQuery
2026-02-22 17:14:18 -08:00
Paul Robello c80bcccba4 chore: add docs/plans/ to .gitignore 2026-02-22 17:14:18 -08:00
Paul Robello 76d1538c5e docs: update READMEs for local backend mode 2026-02-22 17:14:18 -08:00
Paul Robello d1cac0515d fix(web): wire backend props to SettingsPanel 2026-02-22 17:14:18 -08:00
Paul Robello cb70fc8d6c feat(web): add backend URL setting to settings panel 2026-02-22 17:14:18 -08:00
Paul Robello 3603178266 feat(web): add backend connection indicator to header 2026-02-22 17:14:18 -08:00
Paul Robello c7519c8493 feat(web): integrate backend mode into app state and loading flow 2026-02-22 17:14:18 -08:00
Paul Robello 6726340059 feat(web): add Local Server tab to DropZone onboarding 2026-02-22 17:14:18 -08:00
Paul Robello e1d3959273 feat(web): add BackendRepoSelector component 2026-02-22 17:14:18 -08:00
Paul Robello 6de13ac800 feat(web): add useBackend hook for local server connection 2026-02-22 17:14:18 -08:00
Paul Robello d7380de683 feat(web): add backend HTTP service module 2026-02-22 17:14:18 -08:00
Paul Robello 102850455d fix(serve): address code review - security, error handling, CORS 2026-02-22 17:14:18 -08:00
Paul Robello 8e8bb90fe4 refactor(serve): rewrite API server to use LocalBackend for multi-repo support
Replace direct single-repo KuzuDB access with LocalBackend, the same
backend the MCP server uses. The serve command now works with ALL
indexed repos via the global registry instead of only the CWD repo.

New/updated endpoints:
- GET /api/repos (list all indexed repos)
- GET /api/repo?name=X (repo metadata)
- GET /api/graph?repo=X (full knowledge graph)
- POST /api/query (raw Cypher via backend)
- POST /api/search (process-grouped search via backend)
- GET /api/file?repo=X&path=Y (file read with path traversal protection)
- GET /api/processes?repo=X, /api/process?repo=X&name=Y
- GET /api/clusters?repo=X, /api/cluster?repo=X&name=Y
2026-02-22 17:14:18 -08:00
abhigyanpatwari 28339c995d chore: bump gitnexus to v1.2.8 2026-02-23 04:12:02 +05:30
Abhigyan Patwari e849f017f2 Merge pull request #51 from abhigyanpatwari/fix/disable-embeddings-by-default
fix: disable embeddings by default, fix segfault on macOS/Linux
2026-02-23 04:05:47 +05:30
abhigyanpatwari 5c7d905150 ci: add GitHub Actions for PR type-check and npm publish on tags 2026-02-23 03:59:35 +05:30
abhigyanpatwari 302c7ba7f0 fix: disable embeddings by default, fix segfault on macOS/Linux
Embeddings (ONNX Runtime) caused segfaults on macOS (#38) and CUDA
install failures on Linux (#40). Since embeddings only power semantic
vector search in query() and BM25 + graph traversal already provide
strong retrieval, disable embeddings by default with minimal quality
impact.

Changes:
- Replace --skip-embeddings with --embeddings opt-in flag
- Remove disposeEmbedder() call (ONNX native cleanup segfaults)
- Add process.exit(0) when embeddings are used to bypass ONNX atexit hooks
- Suppress ONNX session warnings via logSeverityLevel
- Fix progress bar duplication by routing console output through bar.log()
- Add elapsed time indicator for long-running phases

Fixes #38
Fixes #40
2026-02-23 03:47:34 +05:30
Abhigyan Patwari 93fc2b67ca Add Trendshift badge to README
Added a badge for GitNexus repository on Trendshift.
2026-02-23 02:38:18 +05:30
Abhigyan Patwari ad92b82723 Update README.md 2026-02-23 02:09:03 +05:30
Abhigyan Patwari 3400fccf8c Merge pull request #48 from abhigyanpatwari/claude/issue-47-20260222-1745
fix: use cross-platform npx command in .mcp.json
2026-02-22 23:21:48 +05:30
claude[bot]andAbhigyan Patwari af1e455e77 fix: use cross-platform npx command in .mcp.json
Replace Windows-specific 'cmd /c npx' invocation with portable 'npx'
command that works on macOS, Linux, and Windows.

Fixes #47

Co-authored-by: Abhigyan Patwari <abhigyanpatwari@users.noreply.github.com>
2026-02-22 17:46:04 +00:00
Abhigyan Patwari 053af03caa Merge pull request #46 from abhigyanpatwari/add-claude-github-actions-1771779387558
Add Claude Code GitHub Workflow
2026-02-22 22:27:11 +05:30
Abhigyan Patwari 63f1cd1ec9 "Claude Code Review workflow" 2026-02-22 22:26:31 +05:30
Abhigyan Patwari f42513f5b6 "Claude PR Assistant workflow" 2026-02-22 22:26:30 +05:30
abhigyanpatwariandClaude Opus 4.6 54ae315336 chore: bump gitnexus to v1.2.7
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 17:13:02 +05:30
abhigyanpatwari 19181e9ff6 Merge branch 'linux_stress_test' 2026-02-21 17:12:38 +05:30
abhigyanpatwariandClaude Opus 4.6 d1fb34d636 feat: MCP hot-reload — discover newly indexed repos without restart
listRepos() now re-reads ~/.gitnexus/registry.json on every call.
resolveRepo() retries with a registry refresh on cache miss.
Extracted refreshRepos() from init() for reuse. All callers updated
with await for the new async signatures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 17:07:57 +05:30
abhigyanpatwari 8e2ee26834 docs: remove architecture docs and deep implementation details from markdown files 2026-02-21 13:00:09 +05:30
abhigyanpatwari a7c6903322 docs: remove deep architecture details from README 2026-02-21 04:33:23 +05:30
abhigyanpatwariandClaude Opus 4.6 33aa774f03 fix: worker pool robustness — timeout, exit handler, 512KB guards, error reporting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:24:48 +05:30
Abhigyan Patwari 366bc8b14b Revise README with new links and enhanced details
Updated links and improved project description.
2026-02-20 05:02:50 +05:30
Abhigyan Patwari e79133daaa Merge pull request #36 from abhigyanpatwari/gitnexus_cli
Gitnexus cli
2026-02-17 16:32:17 +05:30
abhigyanpatwari 79501abb6c fixed MCP concurrency issue with connection pool on kuzu 2026-02-17 16:31:05 +05:30
abhigyanpatwari 6ce715b62c repowiki CLI command implemented 2026-02-17 02:25:07 +05:30
abhigyanpatwariandCursor 3425fdeffd simplify: remove incremental graph pipeline, keep only embedding cache
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 01:13:37 +05:30
abhigyanpatwariandCursor 49eaf576fd chore: revert test whitespace change in embedder.ts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 01:05:27 +05:30
abhigyanpatwariandCursor abdb3b4e70 test: whitespace change for incremental query test
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 01:03:47 +05:30
abhigyanpatwariandCursor 2f7be29f59 perf: hybrid incremental - workerData fast path + cross-file edge preservation
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 01:01:14 +05:30
abhigyanpatwariandCursor c4c863887b fix: use canonical DEFINES rel IDs and remove dead code in loadExistingGraph
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 00:49:19 +05:30
abhigyanpatwariandCursor dcb7521386 fix: use full import/call/heritage resolution in incremental mode for cross-file correctness
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 00:47:05 +05:30
abhigyanpatwariandCursor b0320e6b02 fix: exclude CONTAINS rels from preloaded data to avoid duplicates
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 00:44:14 +05:30
abhigyanpatwariandCursor d9a139957a fix: include uncommitted working tree changes in incremental diff detection
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 00:40:03 +05:30
abhigyanpatwariandCursor 65e14605b4 test: add comment to git.ts for incremental test
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 00:32:34 +05:30
abhigyanpatwari 44572ad0bd vercel pointed to gitnexus-web for FE deployment 2026-02-14 13:40:00 +05:30
abhigyanpatwariandClaude Opus 4.6 58972af084 Fix Claude Code hooks: correct settings path, matcher format, and hook script
- Hook config goes in ~/.claude/settings.json (not hooks.json)
- Matcher uses string format ("Grep|Glob|Bash") per new Claude Code schema
- Rename gitnexus-hook.js → gitnexus-hook.cjs for CommonJS compatibility
- Fix setup.ts: correct hook filename and timeout (8000ms instead of 10ms)
- Bump to v1.1.9 and publish to npm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:05:36 +05:30
abhigyanpatwari e90622aa24 MCP tools fixed 2026-02-14 00:16:53 +05:30
abhigyanpatwari 2560a9532d logging global registry 2026-02-13 22:39:12 +05:30
abhigyanpatwari eca55aacd7 fixed resource count multiplying issue ( using resource templates now ) 2026-02-13 21:28:36 +05:30
abhigyanpatwari 96e1d799c8 agent md experiments 2026-02-07 23:39:16 +05:30
abhigyanpatwari dda8de41a3 MCP and cli fixes 2026-02-07 06:21:21 +05:30
abhigyanpatwari 1735c0ffcd gitnexus wal cleanup preventing reindexing issue fixed 2026-02-06 06:35:15 +05:30
abhigyanpatwari 6019e25126 Louvian to leindes, removed smar clustering in UI 2026-02-06 04:43:21 +05:30
abhigyanpatwari 86b171edac test: add FTS live test file 2026-02-05 07:24:53 +05:30
abhigyanpatwari 6c3c47edc3 resources implemented and agents.md and skills updated to use it 2026-02-05 05:13:48 +05:30
abhigyanpatwari d1e53d7030 implemented skills, CLI and MCP merged into /gitnexus 2026-02-04 04:55:09 +05:30
abhigyanpatwari 664aa820f5 fix: move typescript to dependencies, add prepare script for production builds 2026-02-04 03:52:49 +05:30
abhigyanpatwari e480888cf0 fix: clean command --force flag, hide debug logs in production 2026-02-04 03:06:19 +05:30
abhigyanpatwari 747cf003b8 feat: improved AI context - full inline content in AGENTS.md and CLAUDE.md with markers for updates 2026-02-04 02:50:51 +05:30
abhigyanpatwari 5830a50288 feat: analyze command auto-finds git root when run from subdirectory 2026-02-04 02:30:16 +05:30
abhigyanpatwari 1ed9d286ae chore: cleanup empty csv directory after kuzu load 2026-02-04 01:40:59 +05:30
abhigyanpatwari 778ff10707 feat: auto-generate AI context files after analyze (AGENTS.md, .cursorrules, .windsurfrules) 2026-02-04 01:29:09 +05:30
abhigyanpatwari b723ce70c3 chore: remove gitnexus-mcp (merged into gitnexus) 2026-02-04 01:16:15 +05:30
abhigyanpatwari c90576442e feat: merge gitnexus-mcp into gitnexus package - unified CLI+MCP 2026-02-04 01:12:41 +05:30
abhigyanpatwari 789e7809be docs: update license copyright holder 2026-02-03 22:54:01 +05:30
abhigyanpatwari 951423cd15 Merge branch 'main' into gitnexus_cli
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-02-03 22:53:49 +05:30
abhigyanpatwari 9cd380966d docs: add PolyForm Noncommercial License 1.0.0 2026-02-03 22:20:15 +05:30
abhigyanpatwari 1ae08ee9fc refactor: make mcp standalone-only, remove legacy browser bridge 2026-02-03 22:19:44 +05:30
abhigyanpatwari fcbb6f9e92 standalone MCP working 2026-02-03 04:53:10 +05:30
abhigyanpatwari 4357a48fae CLI implemented, UI connection to CLI works 2026-02-02 06:08:06 +05:30
abhigyanpatwari 6a7f837577 mreged 2026-02-01 03:05:52 +05:30
abhigyanpatwari 9b99c6baa9 haiku architecture generation with gitnexus MCP test 2026-02-01 03:02:09 +05:30
Abhigyan Patwari 42115f0ad7 Remove AI tools comparison table from README
Removed comparison table for AI coding tools and their limitations.
2026-01-31 01:47:37 +05:30
Abhigyan Patwari 67c506816a Merge pull request #35 from abhigyanpatwari/leidens_algorithm_and_processmap
updated readme
2026-01-30 04:10:37 +05:30
Abhigyan Patwari 1e39f77718 Merge branch 'main' into leidens_algorithm_and_processmap 2026-01-30 04:10:28 +05:30
abhigyanpatwari e3f4d4b365 updated readme 2026-01-30 04:08:55 +05:30
Abhigyan Patwari 1dfd80d2ea Update asset link and deployment information in README
Updated asset link in README and added a note about deployment.
2026-01-29 14:22:04 +05:30
Abhigyan Patwari e35cb2b920 Update asset links in README
Updated asset links in README.md.
2026-01-29 02:50:46 +05:30
Abhigyan Patwari 4495632e82 Merge pull request #34 from abhigyanpatwari/leidens_algorithm_and_processmap
Leidens algorithm and processmap
2026-01-29 00:11:19 +05:30
abhigyanpatwari f8b4a5c31f Process Map mermaid and other UI tweaks 2026-01-29 00:03:15 +05:30
abhigyanpatwari 4541468320 process maps and funding.yml 2026-01-28 13:14:16 +05:30
Abhigyan Patwari fe3c4dd978 Merge pull request #33 from abhigyanpatwari/leidens_algorithm_and_processmap
Leidens algorithm and processmap
2026-01-28 01:13:58 +05:30
abhigyanpatwari d3dbdb1aff enhanced process maps 2026-01-28 01:13:10 +05:30
abhigyanpatwari 32c3544d0d impact tool test 2026-01-27 23:05:34 +05:30
abhigyanpatwari c2ce19f7a4 schema fixes to handle previously unhandled relations in Go 2026-01-26 23:37:53 +05:30
abhigyanpatwari 53f17ddf17 fixed constructor to method relation not getting stored in kuzu issue 2026-01-26 22:58:15 +05:30
Abhigyan Patwari d02f861309 Merge pull request #31 from abhigyanpatwari/leidens_algorithm_and_processmap
Leidens algorithm and processmap
2026-01-23 19:35:49 +05:30
abhigyanpatwari 46330fa301 go c c# java python ts js rust now supported 2026-01-23 19:11:24 +05:30
abhigyanpatwari 1b5a3bbc1b leidens algo implemented 2026-01-21 01:45:54 +05:30
Abhigyan Patwari fa80520080 Merge pull request #28 from abhigyanpatwari/MCP-bridge
Mcp bridge
2026-01-19 09:08:12 +05:30
Abhigyan Patwari c7449d1804 Merge pull request #26 from abhigyanpatwari/Schema_Experiments
readme mermain fix
2026-01-15 05:39:40 +05:30
1567 changed files with 122213 additions and 14339 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"name": "gitnexus-marketplace",
"owner": {
"name": "GitNexus",
"email": "nico@gitnexus.dev"
},
"metadata": {
"description": "Code intelligence powered by a knowledge graph — execution flows, blast radius, and semantic search",
"homepage": "https://github.com/nicosxt/gitnexus"
},
"plugins": [
{
"name": "gitnexus",
"version": "1.3.3",
"source": "./gitnexus-claude-plugin",
"description": "Code intelligence powered by a knowledge graph. Provides execution flow tracing, blast radius analysis, and augmented search across your codebase."
}
]
}
@@ -0,0 +1,82 @@
---
name: gitnexus-cli
description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\""
---
# GitNexus CLI Commands
All commands work via `npx` — no global install required.
## Commands
### analyze — Build or refresh the index
```bash
npx gitnexus analyze
```
Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
| Flag | Effect |
| -------------- | ---------------------------------------------------------------- |
| `--force` | Force full re-index even if up to date |
| `--embeddings` | Enable embedding generation for semantic search (off by default) |
**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook runs `analyze` automatically after `git commit` and `git merge`, preserving embeddings if previously generated.
### status — Check index freshness
```bash
npx gitnexus status
```
Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed.
### clean — Delete the index
```bash
npx gitnexus clean
```
Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project.
| Flag | Effect |
| --------- | ------------------------------------------------- |
| `--force` | Skip confirmation prompt |
| `--all` | Clean all indexed repos, not just the current one |
### wiki — Generate documentation from the graph
```bash
npx gitnexus wiki
```
Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use).
| Flag | Effect |
| ------------------- | ----------------------------------------- |
| `--force` | Force full regeneration |
| `--model <model>` | LLM model (default: minimax/minimax-m2.5) |
| `--base-url <url>` | LLM API base URL |
| `--api-key <key>` | LLM API key |
| `--concurrency <n>` | Parallel LLM calls (default: 3) |
| `--gist` | Publish wiki as a public GitHub Gist |
### list — Show all indexed repos
```bash
npx gitnexus list
```
Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information.
## After Indexing
1. **Read `gitnexus://repo/{name}/context`** to verify the index loaded
2. Use the other GitNexus skills (`exploring`, `debugging`, `impact-analysis`, `refactoring`) for your task
## Troubleshooting
- **"Not inside a git repository"**: Run from a directory inside a git repo
- **Index is stale after re-analyzing**: Restart Claude Code to reload the MCP server
- **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding
@@ -0,0 +1,89 @@
---
name: gitnexus-debugging
description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\""
---
# Debugging with GitNexus
## When to Use
- "Why is this function failing?"
- "Trace where this error comes from"
- "Who calls this method?"
- "This endpoint returns 500"
- Investigating bugs, errors, or unexpected behavior
## Workflow
```
1. gitnexus_query({query: "<error or symptom>"}) → Find related execution flows
2. gitnexus_context({name: "<suspect>"}) → See callers/callees/processes
3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow
4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] Understand the symptom (error message, unexpected behavior)
- [ ] gitnexus_query for error text or related code
- [ ] Identify the suspect function from returned processes
- [ ] gitnexus_context to see callers and callees
- [ ] Trace execution flow via process resource if applicable
- [ ] gitnexus_cypher for custom call chain traces if needed
- [ ] Read source files to confirm root cause
```
## Debugging Patterns
| Symptom | GitNexus Approach |
| -------------------- | ---------------------------------------------------------- |
| Error message | `gitnexus_query` for error text → `context` on throw sites |
| Wrong return value | `context` on the function → trace callees for data flow |
| Intermittent failure | `context` → look for external calls, async deps |
| Performance issue | `context` → find symbols with many callers (hot paths) |
| Recent regression | `detect_changes` to see what your changes affect |
## Tools
**gitnexus_query** — find code related to error:
```
gitnexus_query({query: "payment validation error"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError, PaymentException
```
**gitnexus_context** — full context for a suspect:
```
gitnexus_context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates (external API!)
→ Processes: CheckoutFlow (step 3/7)
```
**gitnexus_cypher** — custom call chain traces:
```cypher
MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"})
RETURN [n IN nodes(path) | n.name] AS chain
```
## Example: "Payment endpoint returns 500 intermittently"
```
1. gitnexus_query({query: "payment error handling"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError
2. gitnexus_context({name: "validatePayment"})
→ Outgoing calls: verifyCard, fetchRates (external API!)
3. READ gitnexus://repo/my-app/process/CheckoutFlow
→ Step 3: validatePayment → calls fetchRates (external)
4. Root cause: fetchRates calls external API without proper timeout
```
@@ -0,0 +1,78 @@
---
name: gitnexus-exploring
description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\""
---
# Exploring Codebases with GitNexus
## When to Use
- "How does authentication work?"
- "What's the project structure?"
- "Show me the main components"
- "Where is the database logic?"
- Understanding code you haven't seen before
## Workflow
```
1. READ gitnexus://repos → Discover indexed repos
2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness
3. gitnexus_query({query: "<what you want to understand>"}) → Find related execution flows
4. gitnexus_context({name: "<symbol>"}) → Deep dive on specific symbol
5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow
```
> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] READ gitnexus://repo/{name}/context
- [ ] gitnexus_query for the concept you want to understand
- [ ] Review returned processes (execution flows)
- [ ] gitnexus_context on key symbols for callers/callees
- [ ] READ process resource for full execution traces
- [ ] Read source files for implementation details
```
## Resources
| Resource | What you get |
| --------------------------------------- | ------------------------------------------------------- |
| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) |
| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) |
| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) |
## Tools
**gitnexus_query** — find execution flows related to a concept:
```
gitnexus_query({query: "payment processing"})
→ Processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Symbols grouped by flow with file locations
```
**gitnexus_context** — 360-degree view of a symbol:
```
gitnexus_context({name: "validateUser"})
→ Incoming calls: loginHandler, apiMiddleware
→ Outgoing calls: checkToken, getUserById
→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3)
```
## Example: "How does payment processing work?"
```
1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes
2. gitnexus_query({query: "payment processing"})
→ CheckoutFlow: processPayment → validateCard → chargeStripe
→ RefundFlow: initiateRefund → calculateRefund → processRefund
3. gitnexus_context({name: "processPayment"})
→ Incoming: checkoutHandler, webhookHandler
→ Outgoing: validateCard, chargeStripe, saveTransaction
4. Read src/payments/processor.ts for implementation details
```
@@ -0,0 +1,64 @@
---
name: gitnexus-guide
description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\""
---
# GitNexus Guide
Quick reference for all GitNexus MCP tools, resources, and the knowledge graph schema.
## Always Start Here
For any task involving code understanding, debugging, impact analysis, or refactoring:
1. **Read `gitnexus://repo/{name}/context`** — codebase overview + check index freshness
2. **Match your task to a skill below** and **read that skill file**
3. **Follow the skill's workflow and checklist**
> If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first.
## Skills
| Task | Skill to read |
| -------------------------------------------- | ------------------- |
| Understand architecture / "How does X work?" | `gitnexus-exploring` |
| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` |
| Trace bugs / "Why is X failing?" | `gitnexus-debugging` |
| Rename / extract / split / refactor | `gitnexus-refactoring` |
| Tools, resources, schema reference | `gitnexus-guide` (this file) |
| Index, status, clean, wiki CLI commands | `gitnexus-cli` |
## Tools Reference
| Tool | What it gives you |
| ---------------- | ------------------------------------------------------------------------ |
| `query` | Process-grouped code intelligence — execution flows related to a concept |
| `context` | 360-degree symbol view — categorized refs, processes it participates in |
| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence |
| `detect_changes` | Git-diff impact — what do your current changes affect |
| `rename` | Multi-file coordinated rename with confidence-tagged edits |
| `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) |
| `list_repos` | Discover indexed repos |
## Resources Reference
Lightweight reads (~100-500 tokens) for navigation:
| Resource | Content |
| ---------------------------------------------- | ----------------------------------------- |
| `gitnexus://repo/{name}/context` | Stats, staleness check |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores |
| `gitnexus://repo/{name}/cluster/{clusterName}` | Area members |
| `gitnexus://repo/{name}/processes` | All execution flows |
| `gitnexus://repo/{name}/process/{processName}` | Step-by-step trace |
| `gitnexus://repo/{name}/schema` | Graph schema for Cypher |
## Graph Schema
**Nodes:** File, Function, Class, Interface, Method, Community, Process
**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
RETURN caller.name, caller.filePath
```
@@ -0,0 +1,97 @@
---
name: gitnexus-impact-analysis
description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\""
---
# Impact Analysis with GitNexus
## When to Use
- "Is it safe to change this function?"
- "What will break if I modify X?"
- "Show me the blast radius"
- "Who uses this code?"
- Before making non-trivial code changes
- Before committing — to understand what your changes affect
## Workflow
```
1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this
2. READ gitnexus://repo/{name}/processes → Check affected execution flows
3. gitnexus_detect_changes() → Map current git changes to affected flows
4. Assess risk and report to user
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents
- [ ] Review d=1 items first (these WILL BREAK)
- [ ] Check high-confidence (>0.8) dependencies
- [ ] READ processes to check affected execution flows
- [ ] gitnexus_detect_changes() for pre-commit check
- [ ] Assess risk level and report to user
```
## Understanding Output
| Depth | Risk Level | Meaning |
| ----- | ---------------- | ------------------------ |
| d=1 | **WILL BREAK** | Direct callers/importers |
| d=2 | LIKELY AFFECTED | Indirect dependencies |
| d=3 | MAY NEED TESTING | Transitive effects |
## Risk Assessment
| Affected | Risk |
| ------------------------------ | -------- |
| <5 symbols, few processes | LOW |
| 5-15 symbols, 2-5 processes | MEDIUM |
| >15 symbols or many processes | HIGH |
| Critical path (auth, payments) | CRITICAL |
## Tools
**gitnexus_impact** — the primary tool for symbol blast radius:
```
gitnexus_impact({
target: "validateUser",
direction: "upstream",
minConfidence: 0.8,
maxDepth: 3
})
→ d=1 (WILL BREAK):
- loginHandler (src/auth/login.ts:42) [CALLS, 100%]
- apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- authRouter (src/routes/auth.ts:22) [CALLS, 95%]
```
**gitnexus_detect_changes** — git-diff based impact analysis:
```
gitnexus_detect_changes({scope: "staged"})
→ Changed: 5 symbols in 3 files
→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline
→ Risk: MEDIUM
```
## Example: "What breaks if I change validateUser?"
```
1. gitnexus_impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware (WILL BREAK)
→ d=2: authRouter, sessionManager (LIKELY AFFECTED)
2. READ gitnexus://repo/my-app/processes
→ LoginFlow and TokenRefresh touch validateUser
3. Risk: 2 direct callers, 2 processes = MEDIUM
```
@@ -0,0 +1,163 @@
---
name: gitnexus-pr-review
description: "Use when the user wants to review a pull request, understand what a PR changes, assess risk of merging, or check for missing test coverage. Examples: \"Review this PR\", \"What does PR #42 change?\", \"Is this PR safe to merge?\""
---
# PR Review with GitNexus
## When to Use
- "Review this PR"
- "What does PR #42 change?"
- "Is this safe to merge?"
- "What's the blast radius of this PR?"
- "Are there missing tests for this PR?"
- Reviewing someone else's code changes before merge
## Workflow
```
1. gh pr diff <number> → Get the raw diff
2. gitnexus_detect_changes({scope: "compare", base_ref: "main"}) → Map diff to affected flows
3. For each changed symbol:
gitnexus_impact({target: "<symbol>", direction: "upstream"}) → Blast radius per change
4. gitnexus_context({name: "<key symbol>"}) → Understand callers/callees
5. READ gitnexus://repo/{name}/processes → Check affected execution flows
6. Summarize findings with risk assessment
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal before reviewing.
## Checklist
```
- [ ] Fetch PR diff (gh pr diff or git diff base...head)
- [ ] gitnexus_detect_changes to map changes to affected execution flows
- [ ] gitnexus_impact on each non-trivial changed symbol
- [ ] Review d=1 items (WILL BREAK) — are callers updated?
- [ ] gitnexus_context on key changed symbols to understand full picture
- [ ] Check if affected processes have test coverage
- [ ] Assess overall risk level
- [ ] Write review summary with findings
```
## Review Dimensions
| Dimension | How GitNexus Helps |
| --- | --- |
| **Correctness** | `context` shows callers — are they all compatible with the change? |
| **Blast radius** | `impact` shows d=1/d=2/d=3 dependents — anything missed? |
| **Completeness** | `detect_changes` shows all affected flows — are they all handled? |
| **Test coverage** | `impact({includeTests: true})` shows which tests touch changed code |
| **Breaking changes** | d=1 upstream items that aren't updated in the PR = potential breakage |
## Risk Assessment
| Signal | Risk |
| --- | --- |
| Changes touch <3 symbols, 0-1 processes | LOW |
| Changes touch 3-10 symbols, 2-5 processes | MEDIUM |
| Changes touch >10 symbols or many processes | HIGH |
| Changes touch auth, payments, or data integrity code | CRITICAL |
| d=1 callers exist outside the PR diff | Potential breakage — flag it |
## Tools
**gitnexus_detect_changes** — map PR diff to affected execution flows:
```
gitnexus_detect_changes({scope: "compare", base_ref: "main"})
→ Changed: 8 symbols in 4 files
→ Affected processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Risk: MEDIUM
```
**gitnexus_impact** — blast radius per changed symbol:
```
gitnexus_impact({target: "validatePayment", direction: "upstream"})
→ d=1 (WILL BREAK):
- processCheckout (src/checkout.ts:42) [CALLS, 100%]
- webhookHandler (src/webhooks.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- checkoutRouter (src/routes/checkout.ts:22) [CALLS, 95%]
```
**gitnexus_impact with tests** — check test coverage:
```
gitnexus_impact({target: "validatePayment", direction: "upstream", includeTests: true})
→ Tests that cover this symbol:
- validatePayment.test.ts [direct]
- checkout.integration.test.ts [via processCheckout]
```
**gitnexus_context** — understand a changed symbol's role:
```
gitnexus_context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates
→ Processes: CheckoutFlow (step 3/7), RefundFlow (step 1/5)
```
## Example: "Review PR #42"
```
1. gh pr diff 42 > /tmp/pr42.diff
→ 4 files changed: payments.ts, checkout.ts, types.ts, utils.ts
2. gitnexus_detect_changes({scope: "compare", base_ref: "main"})
→ Changed symbols: validatePayment, PaymentInput, formatAmount
→ Affected processes: CheckoutFlow, RefundFlow
→ Risk: MEDIUM
3. gitnexus_impact({target: "validatePayment", direction: "upstream"})
→ d=1: processCheckout, webhookHandler (WILL BREAK)
→ webhookHandler is NOT in the PR diff — potential breakage!
4. gitnexus_impact({target: "PaymentInput", direction: "upstream"})
→ d=1: validatePayment (in PR), createPayment (NOT in PR)
→ createPayment uses the old PaymentInput shape — breaking change!
5. gitnexus_context({name: "formatAmount"})
→ Called by 12 functions — but change is backwards-compatible (added optional param)
6. Review summary:
- MEDIUM risk — 3 changed symbols affect 2 execution flows
- BUG: webhookHandler calls validatePayment but isn't updated for new signature
- BUG: createPayment depends on PaymentInput type which changed
- OK: formatAmount change is backwards-compatible
- Tests: checkout.test.ts covers processCheckout path, but no webhook test
```
## Review Output Format
Structure your review as:
```markdown
## PR Review: <title>
**Risk: LOW / MEDIUM / HIGH / CRITICAL**
### Changes Summary
- <N> symbols changed across <M> files
- <P> execution flows affected
### Findings
1. **[severity]** Description of finding
- Evidence from GitNexus tools
- Affected callers/flows
### Missing Coverage
- Callers not updated in PR: ...
- Untested flows: ...
### Recommendation
APPROVE / REQUEST CHANGES / NEEDS DISCUSSION
```
@@ -0,0 +1,121 @@
---
name: gitnexus-refactoring
description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\""
---
# Refactoring with GitNexus
## When to Use
- "Rename this function safely"
- "Extract this into a module"
- "Split this service"
- "Move this to a new file"
- Any task involving renaming, extracting, splitting, or restructuring code
## Workflow
```
1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents
2. gitnexus_query({query: "X"}) → Find execution flows involving X
3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs
4. Plan update order: interfaces → implementations → callers → tests
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklists
### Rename Symbol
```
- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits
- [ ] Review graph edits (high confidence) and ast_search edits (review carefully)
- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits
- [ ] gitnexus_detect_changes() — verify only expected files changed
- [ ] Run tests for affected processes
```
### Extract Module
```
- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs
- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers
- [ ] Define new module interface
- [ ] Extract code, update imports
- [ ] gitnexus_detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
### Split Function/Service
```
- [ ] gitnexus_context({name: target}) — understand all callees
- [ ] Group callees by responsibility
- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update
- [ ] Create new functions/services
- [ ] Update callers
- [ ] gitnexus_detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
## Tools
**gitnexus_rename** — automated multi-file rename:
```
gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits across 8 files
→ 10 graph edits (high confidence), 2 ast_search edits (review)
→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}]
```
**gitnexus_impact** — map all dependents first:
```
gitnexus_impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware, testUtils
→ Affected Processes: LoginFlow, TokenRefresh
```
**gitnexus_detect_changes** — verify your changes after refactoring:
```
gitnexus_detect_changes({scope: "all"})
→ Changed: 8 files, 12 symbols
→ Affected processes: LoginFlow, TokenRefresh
→ Risk: MEDIUM
```
**gitnexus_cypher** — custom reference queries:
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"})
RETURN caller.name, caller.filePath ORDER BY caller.filePath
```
## Risk Rules
| Risk Factor | Mitigation |
| ------------------- | ----------------------------------------- |
| Many callers (>5) | Use gitnexus_rename for automated updates |
| Cross-area refs | Use detect_changes after to verify scope |
| String/dynamic refs | gitnexus_query to find them |
| External/public API | Version and deprecate properly |
## Example: Rename `validateUser` to `authenticateUser`
```
1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits: 10 graph (safe), 2 ast_search (review)
→ Files: validator.ts, login.ts, middleware.ts, config.json...
2. Review ast_search edits (config.json: dynamic reference!)
3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false})
→ Applied 12 edits across 8 files
4. gitnexus_detect_changes({scope: "all"})
→ Affected: LoginFlow, TokenRefresh
→ Risk: MEDIUM — run tests for these flows
```
+5
View File
@@ -0,0 +1,5 @@
# AI Agent Rules
Follow .gitnexus/RULES.md for all project context and coding guidelines.
This project uses GitNexus MCP for code intelligence. See .gitnexus/RULES.md for available tools and best practices.
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: abhigyanpatwari
+28
View File
@@ -0,0 +1,28 @@
name: Setup GitNexus
description: Setup Node.js 20, install dependencies, and optionally build
inputs:
build:
description: Whether to run npm run build after install
required: false
default: 'false'
runs:
using: composite
steps:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
cache: npm
cache-dependency-path: gitnexus/package-lock.json
- name: Install dependencies
run: npm ci
shell: bash
working-directory: gitnexus
- name: Build
if: ${{ inputs.build == 'true' }}
run: npm run build
shell: bash
working-directory: gitnexus
+45
View File
@@ -0,0 +1,45 @@
changelog:
exclude:
labels:
- chore
authors:
- dependabot
- dependabot[bot]
categories:
- title: "\U0001F6A8 Security"
labels:
- security
- title: "\U0001F4A5 Breaking Changes"
labels:
- breaking
- title: "\U0001F680 Features"
labels:
- enhancement
- title: "\U0001F41B Bug Fixes"
labels:
- bug
- title: "\U0001F3CE\uFE0F Performance"
labels:
- performance
- title: "\U0001F9EA Tests"
labels:
- test
- title: "\U0001F504 Refactoring"
labels:
- refactor
- title: "\U0001F477 CI/CD"
labels:
- ci
- title: "\U0001F4E6 Dependencies"
labels:
- dependencies
- title: "\U0001F4DD Other Changes"
labels:
- "*"
exclude:
labels:
- dependencies
- ci
- test
- refactor
- chore
+14
View File
@@ -0,0 +1,14 @@
name: Quality Checks
on:
workflow_call:
jobs:
typecheck:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup-gitnexus
- run: npx tsc --noEmit
working-directory: gitnexus
+348
View File
@@ -0,0 +1,348 @@
name: CI Report
on:
workflow_run:
workflows: ['CI']
types: [completed]
permissions:
actions: read
contents: read
pull-requests: write
jobs:
pr-report:
name: PR Report
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Download PR metadata
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const fs = require('fs');
const path = require('path');
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
});
const meta = artifacts.data.artifacts.find(a => a.name === 'pr-meta');
if (!meta) {
core.setFailed('pr-meta artifact not found — skipping report');
return;
}
const zip = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: meta.id,
archive_format: 'zip',
});
const dest = path.join(process.env.RUNNER_TEMP, 'pr-meta');
fs.mkdirSync(dest, { recursive: true });
fs.writeFileSync(path.join(dest, 'pr-meta.zip'), Buffer.from(zip.data));
- name: Extract PR metadata
id: meta
shell: bash
run: |
cd "$RUNNER_TEMP/pr-meta"
unzip -o pr-meta.zip
PR_NUMBER=$(cat pr-number | tr -d '[:space:]')
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid PR number: '$PR_NUMBER'"
exit 1
fi
echo "pr-number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "quality=$(cat quality-result | tr -d '[:space:]')" >> "$GITHUB_OUTPUT"
echo "tests=$(cat tests-result | tr -d '[:space:]')" >> "$GITHUB_OUTPUT"
- name: Download test reports
id: download-test-reports
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const fs = require('fs');
const path = require('path');
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
});
const reports = artifacts.data.artifacts.find(a => a.name === 'test-reports');
if (!reports) {
core.warning('test-reports artifact not found');
return;
}
const zip = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: reports.id,
archive_format: 'zip',
});
const dest = path.join(process.env.RUNNER_TEMP, 'test-reports');
fs.mkdirSync(dest, { recursive: true });
fs.writeFileSync(path.join(dest, 'test-reports.zip'), Buffer.from(zip.data));
- name: Extract test reports
if: steps.download-test-reports.outcome == 'success'
shell: bash
run: |
cd "$RUNNER_TEMP/test-reports"
unzip -o test-reports.zip || true
- name: Fetch cross-platform job results
id: jobs
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const jobs = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
per_page: 50,
});
const results = {};
for (const job of jobs.data.jobs) {
if (job.name.includes('ubuntu')) results.ubuntu = job.conclusion || 'pending';
else if (job.name.includes('windows')) results.windows = job.conclusion || 'pending';
else if (job.name.includes('macos')) results.macos = job.conclusion || 'pending';
}
core.setOutput('ubuntu', results.ubuntu || 'unknown');
core.setOutput('windows', results.windows || 'unknown');
core.setOutput('macos', results.macos || 'unknown');
- name: Fetch base branch coverage
id: base-coverage
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const fs = require('fs');
const path = require('path');
const runs = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'ci.yml',
branch: 'main',
status: 'success',
per_page: 1,
});
if (runs.data.workflow_runs.length === 0) {
core.setOutput('found', 'false');
return;
}
const mainRunId = runs.data.workflow_runs[0].id;
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: mainRunId,
});
const testReports = artifacts.data.artifacts.find(a => a.name === 'test-reports');
if (!testReports) {
core.setOutput('found', 'false');
return;
}
const zip = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: testReports.id,
archive_format: 'zip',
});
const dest = path.join(process.env.RUNNER_TEMP, 'base-coverage');
fs.mkdirSync(dest, { recursive: true });
fs.writeFileSync(path.join(dest, 'base.zip'), Buffer.from(zip.data));
core.setOutput('found', 'true');
core.setOutput('dir', dest);
- name: Extract base coverage
if: steps.base-coverage.outputs.found == 'true'
shell: bash
run: |
cd "${{ steps.base-coverage.outputs.dir }}"
unzip -o base.zip -d base
- name: Build and post report
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
env:
PR_NUMBER: ${{ steps.meta.outputs.pr-number }}
QUALITY: ${{ steps.meta.outputs.quality }}
TESTS: ${{ steps.meta.outputs.tests }}
UBUNTU: ${{ steps.jobs.outputs.ubuntu }}
WINDOWS: ${{ steps.jobs.outputs.windows }}
MACOS: ${{ steps.jobs.outputs.macos }}
BASE_FOUND: ${{ steps.base-coverage.outputs.found }}
BASE_DIR: ${{ steps.base-coverage.outputs.dir }}
RUN_ID: ${{ github.event.workflow_run.id }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
with:
script: |
const fs = require('fs');
const path = require('path');
const icon = (s) => ({ success: '✅', failure: '❌', cancelled: '⏭️' }[s] || '❓');
const temp = process.env.RUNNER_TEMP;
// ── Read coverage ──
function readCov(dir) {
const out = { stmts: 'N/A', branch: 'N/A', funcs: 'N/A', lines: 'N/A',
stmtsCov: '', branchCov: '', funcsCov: '', linesCov: '' };
try {
const files = require('child_process')
.execSync(`find "${dir}" -name coverage-summary.json -type f`, { encoding: 'utf8' })
.trim().split('\n').filter(Boolean);
if (!files.length) return out;
const d = JSON.parse(fs.readFileSync(files[0], 'utf8')).total;
out.stmts = d.statements.pct; out.branch = d.branches.pct;
out.funcs = d.functions.pct; out.lines = d.lines.pct;
out.stmtsCov = `${d.statements.covered}/${d.statements.total}`;
out.branchCov = `${d.branches.covered}/${d.branches.total}`;
out.funcsCov = `${d.functions.covered}/${d.functions.total}`;
out.linesCov = `${d.lines.covered}/${d.lines.total}`;
} catch {}
return out;
}
const cov = readCov(path.join(temp, 'test-reports'));
const base = process.env.BASE_FOUND === 'true'
? readCov(path.join(process.env.BASE_DIR, 'base'))
: { stmts: 'N/A', branch: 'N/A', funcs: 'N/A', lines: 'N/A' };
// ── Read test results ──
let total = 0, passed = 0, failed = 0, skipped = 0, suites = 0, duration = '0s';
let skippedTests = [];
try {
const files = require('child_process')
.execSync(`find "${path.join(temp, 'test-reports')}" -name test-results.json -type f`, { encoding: 'utf8' })
.trim().split('\n').filter(Boolean);
if (files.length) {
const r = JSON.parse(fs.readFileSync(files[0], 'utf8'));
total = r.numTotalTests || 0;
passed = r.numPassedTests || 0;
failed = r.numFailedTests || 0;
skipped = r.numPendingTests || 0;
suites = r.numTotalTestSuites || 0;
const durS = Math.floor((Math.max(...r.testResults.map(t => t.endTime)) - r.startTime) / 1000);
duration = durS >= 60 ? `${Math.floor(durS / 60)}m ${durS % 60}s` : `${durS}s`;
// Collect skipped test names
for (const suite of r.testResults) {
for (const t of (suite.assertionResults || [])) {
if (t.status === 'pending' || t.status === 'skipped') {
skippedTests.push(`- ${t.ancestorTitles.join(' > ')} > ${t.title}`);
}
}
}
}
} catch {}
// ── Coverage delta ──
function delta(pct, basePct) {
if (pct === 'N/A' || basePct === 'N/A') return '—';
const d = (pct - basePct).toFixed(1);
const dNum = parseFloat(d);
if (dNum > 0) return `📈 +${d}%`;
if (dNum < 0) return `📉 ${d}%`;
return '=';
}
// ── Build markdown ──
const { PR_NUMBER, QUALITY, TESTS, UBUNTU, WINDOWS, MACOS, RUN_ID, HEAD_SHA } = process.env;
const prNumber = parseInt(PR_NUMBER, 10);
const overall = (QUALITY === 'success' && TESTS === 'success')
? '✅ **All checks passed**' : '❌ **Some checks failed**';
const sha = HEAD_SHA.slice(0, 7);
let body = `## CI Report\n\n${overall} &ensp; \`${sha}\`\n\n`;
body += `### Pipeline\n\n`;
body += `| Stage | Status | Ubuntu | Windows | macOS |\n`;
body += `|-------|--------|--------|---------|-------|\n`;
body += `| Typecheck | ${icon(QUALITY)} \`${QUALITY}\` | — | — | — |\n`;
body += `| Tests | ${icon(TESTS)} \`${TESTS}\` | ${icon(UBUNTU)} | ${icon(WINDOWS)} | ${icon(MACOS)} |\n\n`;
if (total > 0) {
body += `### Tests\n\n`;
body += `| Metric | Value |\n|--------|-------|\n`;
body += `| Total | **${total}** |\n`;
body += `| Passed | **${passed}** |\n`;
if (failed > 0) body += `| Failed | **${failed}** |\n`;
if (skipped > 0) body += `| Skipped | ${skipped} |\n`;
body += `| Files | ${suites} |\n`;
body += `| Duration | ${duration} |\n\n`;
if (failed === 0) {
body += `✅ All **${passed}** tests passed across **${suites}** files\n`;
} else {
body += `❌ **${failed}** failed / **${passed}** passed\n`;
}
if (skippedTests.length > 0) {
body += `\n<details>\n<summary>${skipped} test(s) skipped</summary>\n\n`;
body += skippedTests.join('\n') + '\n\n</details>\n';
}
body += '\n';
}
if (cov.stmts !== 'N/A') {
body += `### Coverage\n\n`;
body += `| Metric | Coverage | Covered | Base (main) | Delta |\n`;
body += `|--------|----------|---------|-------------|-------|\n`;
body += `| Statements | **${cov.stmts}%** | ${cov.stmtsCov} | ${base.stmts}% | ${delta(cov.stmts, base.stmts)} |\n`;
body += `| Branches | **${cov.branch}%** | ${cov.branchCov} | ${base.branch}% | ${delta(cov.branch, base.branch)} |\n`;
body += `| Functions | **${cov.funcs}%** | ${cov.funcsCov} | ${base.funcs}% | ${delta(cov.funcs, base.funcs)} |\n`;
body += `| Lines | **${cov.lines}%** | ${cov.linesCov} | ${base.lines}% | ${delta(cov.lines, base.lines)} |\n\n`;
} else {
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${RUN_ID}`;
body += `### Coverage\n\n⚠️ Coverage data unavailable — check the [test job](${runUrl}) for details.\n\n`;
}
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${RUN_ID}`;
body += `---\n<sub>📋 [Full run](${runUrl}) · Coverage from Ubuntu · Generated by CI</sub>`;
// ── Post sticky comment ──
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
direction: 'desc',
});
const marker = '<!-- ci-report -->';
const existing = comments.find(c => c.body?.includes(marker));
const fullBody = marker + '\n' + body;
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: fullBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: fullBody,
});
}
+57
View File
@@ -0,0 +1,57 @@
name: Tests
on:
workflow_call:
jobs:
tests:
name: ubuntu / coverage
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup-gitnexus
with:
build: 'true'
- name: Run all tests with coverage
run: >-
npx vitest run
--reporter=default
--reporter=json
--outputFile=test-results.json
--coverage
--coverage.reporter=json-summary
--coverage.reporter=json
--coverage.reporter=text
--coverage.thresholdAutoUpdate=false
--coverage.reportOnFailure=true
working-directory: gitnexus
- name: Upload test reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-reports
path: |
gitnexus/coverage/coverage-summary.json
gitnexus/coverage/coverage-final.json
gitnexus/test-results.json
retention-days: 5
cross-platform:
name: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Ubuntu already covered by the coverage job above
os: [windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 25
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup-gitnexus
with:
build: 'true'
- run: npx vitest run
working-directory: gitnexus
+83
View File
@@ -0,0 +1,83 @@
name: CI
on:
push:
branches: [main]
paths-ignore: ['**.md', 'docs/**', 'LICENSE']
pull_request:
branches: [main]
paths-ignore: ['**.md', 'docs/**', 'LICENSE']
workflow_call:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
# ── Reusable workflow orchestration ─────────────────────────────────
# Each concern lives in its own workflow file for maintainability:
# ci-quality.yml — typecheck (tsc --noEmit)
# ci-tests.yml — all tests with coverage (ubuntu) + cross-platform
# ci-report.yml — PR comment (workflow_run trigger for fork write access)
jobs:
quality:
uses: ./.github/workflows/ci-quality.yml
permissions:
contents: read
tests:
uses: ./.github/workflows/ci-tests.yml
permissions:
contents: read
# ── Unified CI gate ──────────────────────────────────────────────
# Single required check for branch protection.
ci-status:
name: CI Gate
needs: [quality, tests]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check all jobs passed
shell: bash
env:
QUALITY: ${{ needs.quality.result }}
TESTS: ${{ needs.tests.result }}
run: |
echo "Quality: $QUALITY"
echo "Tests: $TESTS"
if [[ "$QUALITY" != "success" ]] ||
[[ "$TESTS" != "success" ]]; then
echo "::error::One or more CI jobs failed"
exit 1
fi
# ── PR metadata for ci-report.yml ────────────────────────────────
# Saves PR number and job results so the workflow_run-triggered
# report can post comments with a write token (works for forks).
save-pr-meta:
name: Save PR Metadata
if: always() && github.event_name == 'pull_request'
needs: [quality, tests]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Write PR metadata
shell: bash
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
QUALITY: ${{ needs.quality.result }}
TESTS: ${{ needs.tests.result }}
run: |
mkdir -p pr-meta
echo "$PR_NUMBER" > pr-meta/pr-number
echo "$QUALITY" > pr-meta/quality-result
echo "$TESTS" > pr-meta/tests-result
- name: Upload PR metadata
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: pr-meta
path: pr-meta/
retention-days: 1
+95
View File
@@ -0,0 +1,95 @@
name: Claude Code Review
# Uses pull_request_target so the workflow runs as defined on the default branch,
# which allows access to secrets for posting review comments on fork PRs.
# SECURITY: The checkout pins the fork's HEAD SHA (not the branch name) to
# prevent TOCTOU races (force-push between trigger and checkout). The
# claude-code-action sandboxes execution — it does NOT run arbitrary code
# from the checked-out source.
on:
# Trigger only when explicitly requested:
# - Add the "claude-review" label to a PR, OR
# - Comment "@claude" or "/review" on a PR
pull_request_target:
types: [labeled]
issue_comment:
types: [created]
# Serialize per-PR to avoid racing review comments.
concurrency:
group: claude-review-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
jobs:
claude-review:
# Run only when:
# 1. The "claude-review" label is added to a non-draft PR by a trusted contributor, OR
# 2. A trusted contributor comments "@claude" or "/review" on a PR
if: |
(
github.event_name == 'pull_request_target' &&
github.event.label.name == 'claude-review' &&
github.event.pull_request.draft == false &&
(github.event.pull_request.author_association == 'OWNER' ||
github.event.pull_request.author_association == 'MEMBER' ||
github.event.pull_request.author_association == 'COLLABORATOR')
) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(contains(github.event.comment.body, '@claude') ||
contains(github.event.comment.body, '/review')) &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
)
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
pull-requests: write
issues: read
id-token: write
steps:
# For issue_comment triggers, resolve the PR number, head SHA, and fork repo
- name: Resolve PR context
id: pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
let pr;
if (context.eventName === 'issue_comment') {
const resp = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.issue.number,
});
pr = resp.data;
} else {
pr = context.payload.pull_request;
}
core.setOutput('number', pr.number);
core.setOutput('sha', pr.head.sha);
core.setOutput('repo', pr.head.repo.full_name);
core.setOutput('branch', pr.head.ref);
- name: Checkout PR head
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: ${{ steps.pr.outputs.repo }}
ref: ${{ steps.pr.outputs.sha }}
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@9469d113c6afd29550c402740f22d1a97dd1209b # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: '*'
show_full_output: true
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ steps.pr.outputs.number }}'
+110
View File
@@ -0,0 +1,110 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
# Serialize per-PR/issue to avoid racing comments.
concurrency:
group: claude-code-${{ github.event.issue.number || github.event.pull_request.number || github.event.issue.id }}
cancel-in-progress: false
jobs:
claude:
if: |
(
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@claude') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
) ||
(
github.event_name == 'pull_request_review_comment' &&
contains(github.event.comment.body, '@claude') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
) ||
(
github.event_name == 'pull_request_review' &&
contains(github.event.review.body, '@claude') &&
(github.event.review.author_association == 'OWNER' ||
github.event.review.author_association == 'MEMBER' ||
github.event.review.author_association == 'COLLABORATOR')
) ||
(
github.event_name == 'issues' &&
(contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
(github.event.issue.author_association == 'OWNER' ||
github.event.issue.author_association == 'MEMBER' ||
github.event.issue.author_association == 'COLLABORATOR')
)
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
actions: read # required for Claude to read CI results on PRs
steps:
# For PR-related triggers, resolve the fork repo so we can checkout correctly.
- name: Resolve PR context
id: pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
// Determine if this event is PR-related
let prNumber = null;
if (context.eventName === 'issue_comment' && context.payload.issue.pull_request) {
prNumber = context.payload.issue.number;
} else if (context.eventName === 'pull_request_review_comment') {
prNumber = context.payload.pull_request.number;
} else if (context.eventName === 'pull_request_review') {
prNumber = context.payload.pull_request.number;
}
if (!prNumber) {
core.setOutput('is_pr', 'false');
return;
}
const resp = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const pr = resp.data;
core.setOutput('is_pr', 'true');
core.setOutput('number', String(prNumber));
core.setOutput('sha', pr.head.sha);
core.setOutput('repo', pr.head.repo.full_name);
core.setOutput('branch', pr.head.ref);
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: ${{ steps.pr.outputs.is_pr == 'true' && steps.pr.outputs.repo || github.repository }}
ref: ${{ steps.pr.outputs.is_pr == 'true' && steps.pr.outputs.sha || '' }}
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@9469d113c6afd29550c402740f22d1a97dd1209b # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: '*'
show_full_output: true
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
+69
View File
@@ -0,0 +1,69 @@
name: Publish to npm
on:
push:
tags:
- 'v*'
# No workflow-level permissions — scoped per job below.
jobs:
ci:
uses: ./.github/workflows/ci.yml
permissions:
contents: read
actions: read
pull-requests: write
publish:
needs: ci
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
cache: npm
cache-dependency-path: gitnexus/package-lock.json
- run: npm ci
working-directory: gitnexus
- name: Verify version consistency
shell: bash
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
if ! [[ "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Tag does not follow semver: v$TAG_VERSION"
exit 1
fi
PKG_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "::error::Tag version (v$TAG_VERSION) does not match package.json version ($PKG_VERSION)"
exit 1
fi
echo "Version verified: $PKG_VERSION"
working-directory: gitnexus
- name: Build
run: npm run build
working-directory: gitnexus
- name: Dry-run publish
run: npm publish --dry-run
working-directory: gitnexus
- name: Publish to npm
run: npm publish --provenance --access public
working-directory: gitnexus
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
generate_release_notes: true
+30
View File
@@ -17,6 +17,8 @@ dist/
.DS_Store
Thumbs.db
.claude/settings.local.json
# Environment variables
.env
.env.local
@@ -40,3 +42,31 @@ coverage/
.env*.local
.gitnexus
.claude/settings.local.json
# Claude Code worktrees
.claude/worktrees/
# Claude code skills
.claude/skills/generated/
# Assets (screenshots, images)
assets/
# Generated files (should not be indexed)
repomix-output*
# Design docs (local only)
docs/plans/
gitnexus/test/fixtures/mini-repo/*.md
gitnexus/test/fixtures/mini-repo/.claude
gitnexus/test/fixtures/mini-repo/.gitignore
# Ignore csharp generated obj and bin folders
gitnexus/test/fixtures/lang-resolution/**/obj
gitnexus/test/fixtures/lang-resolution/**/bin
GitNexus.sln
# Git worktrees
.worktrees/
@@ -0,0 +1,33 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globalSetup: ['test/global-setup.ts'],
include: ['test/**/*.test.ts'],
testTimeout: 30000,
hookTimeout: 120000,
pool: 'forks',
globals: true,
setupFiles: ['test/setup.ts'],
teardownTimeout: 3000,
dangerouslyIgnoreUnhandledErrors: true, // LadybugDB N-API destructor segfaults on fork exit — not a test failure
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: [
'src/cli/index.ts', // CLI entry point (commander wiring)
'src/server/**', // HTTP server (requires network)
'src/core/wiki/**', // Wiki generation (requires LLM)
],
// Auto-ratchet: vitest bumps thresholds when coverage exceeds them.
// CI will fail if a PR drops below these floors.
thresholds: {
statements: 26,
branches: 23,
functions: 28,
lines: 27,
autoUpdate: true,
},
},
},
});
+9
View File
@@ -0,0 +1,9 @@
{
"mcpServers": {
"gitnexus": {
"type": "stdio",
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,18 @@
# Draft: Gitnexus Brainstorming - Clustering & Process Maps
## Initial Context
- Project: **GitnexusV2**
- Structure:
- `gitnexus/` (Likely the core application)
- `gitnexus-mcp/` (Likely a Model Context Protocol server)
- Goal: Make it accurate and usable for smaller/dumber models.
- Current Focus: Implementing **Clustering** and **Process Maps**.
## Findings
- **Clustering**: Found `gitnexus/src/core/ingestion/cluster-enricher.ts`.
- **Process Maps**: No files matched `*process*map*` yet. Searching content next.
## Open Questions
- How is "process map" defined in this context? (Graph, mermaid diagram, flowchart?)
- What is the input for clustering? (Code chunks, files, commits?)
- What is the intended output for "smaller models"? (Simplified context, summaries?)
+34
View File
@@ -0,0 +1,34 @@
# Draft: Gitnexus vs Noodlbox Strategy
## Objectives
- Understand GitnexusV2 current state and goals.
- Analyze Noodlbox capabilities from provided URL.
- Compare features, architecture, and value proposition.
- Provide strategic views and recommendations.
## Research Findings
- [GitnexusV2]: Zero-server, browser-native (WASM), KuzuDB based. Graph + Vector hybrid search.
- [Noodlbox]: CLI-first, heavy install. Has "Session Hooks" and "Search Hooks" via plugins/CLI.
## Comparison Points
- **Core Philosophy**: Both bet on "Knowledge Graph + MCP" as the future. Noodlbox validates Gitnexus's direction.
- **Architecture**:
- *Noodlbox*: CLI/Binary based. Likely local server management.
- *Gitnexus*: Zero-server, Browser-native (WASM). Lower friction, higher privacy.
- **Features**:
- *Communities/Processes*: Both have them. Noodlbox uses them for "context injection". Gitnexus uses them for "visual exploration + query".
- *Impact Analysis*: Noodlbox has polished workflows (e.g., `detect_impact staged`). Gitnexus has the engine (`blastRadius`) but maybe not the specific workflow wrappers yet.
- **UX/Integration**:
- *Noodlbox*: "Hooks" (Session/Search) are a killer feature. Proactively injecting context into the agent's session.
- *Gitnexus*: Powerful tools, but relies on agent *pulling* data?
## Strategic Views
1. **Validation**: The market direction is confirmed. You are building the right thing.
2. **differentiation**: Lean into "Zero-Setup / Browser-Native". Noodlbox requires `noodl init` and CLI handling. Gitnexus could just *be*.
3. **Opportunity**: Steal the "Session/Search Hooks" pattern. Make the agent smarter *automatically* without the user asking "check impact".
4. **Workflow Polish**: Noodlbox's `/detect_impact staged` is a great specific use case. Gitnexus should wrap `blastRadius` into similar concrete workflows.
## Technical Feasibility (Interception)
- **Cursor**: Use `.cursorrules` to "shadow" default tools. Instruct agent to ALWAYS use `gitnexus_search` instead of `grep`.
- **Claude Code**: Likely uses a private plugin API for `PreToolUse`. We can't match this exactly without an official plugin, but we can approximate it with strong prompt instructions in `AGENTS.md`.
- **MCP Shadowing**: Define tools with names that conflict (e.g., `grep`)? No, unsafe. Better to use "Virtual Hooks" via system prompt instructions.
+5
View File
@@ -0,0 +1,5 @@
# AI Agent Rules
Follow .gitnexus/RULES.md for all project context and coding guidelines.
This project uses GitNexus MCP for code intelligence. See .gitnexus/RULES.md for available tools and best practices.
+101
View File
@@ -0,0 +1,101 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **GitNexus** (2227 symbols, 5390 relationships, 170 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/GitNexus/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
## Tools Quick Reference
| Tool | When to use | Command |
|------|-------------|---------|
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
## Impact Risk Levels
| Depth | Meaning | Action |
|-------|---------|--------|
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
| `gitnexus://repo/GitNexus/processes` | All execution flows |
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
## Self-Check Before Finishing
Before completing any code modification task, verify:
1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
<!-- gitnexus:end -->
+109
View File
@@ -0,0 +1,109 @@
# Changelog
All notable changes to GitNexus will be documented in this file.
## [Unreleased]
### Changed
- Migrated from KuzuDB to LadybugDB v0.15 (`@ladybugdb/core`, `@ladybugdb/wasm-core`)
- Renamed all internal paths from `kuzu` to `lbug` (storage: `.gitnexus/kuzu` → `.gitnexus/lbug`)
- Added automatic cleanup of stale KuzuDB index files
- LadybugDB v0.15 requires explicit VECTOR extension loading for semantic search
## [1.4.0] - 2026-03-13
### Added
- **Language-aware symbol resolution engine** with 3-tier resolver: exact FQN → scope-walk → guarded fuzzy fallback that refuses ambiguous matches (#238) — @magyargergo
- **Method Resolution Order (MRO)** with 5 language-specific strategies: C++ leftmost-base, C#/Java class-over-interface, Python C3 linearization, Rust qualified syntax, default BFS (#238) — @magyargergo
- **Constructor & struct literal resolution** across all languages — `new Foo()`, `User{...}`, C# primary constructors, target-typed new (#238) — @magyargergo
- **Receiver-constrained resolution** using per-file TypeEnv — disambiguates `user.save()` vs `repo.save()` via `ownerId` matching (#238) — @magyargergo
- **Heritage & ownership edges** — HAS_METHOD, OVERRIDES, Go struct embedding, Swift extension heritage, method signatures (`parameterCount`, `returnType`) (#238) — @magyargergo
- **Language-specific resolver directory** (`resolvers/`) — extracted JVM, Go, C#, PHP, Rust resolvers from monolithic import-processor (#238) — @magyargergo
- **Type extractor directory** (`type-extractors/`) — per-language type binding extraction with `Record<SupportedLanguages, Handler>` + `satisfies` dispatch (#238) — @magyargergo
- **Export detection dispatch table** — compile-time exhaustive `Record` + `satisfies` pattern replacing switch/if chains (#238) — @magyargergo
- **Language config module** (`language-config.ts`) — centralized tsconfig, go.mod, composer.json, .csproj, Swift package config loaders (#238) — @magyargergo
- **Optional skill generation** via `npx gitnexus analyze --skills` — generates AI agent skills from KuzuDB knowledge graph (#171) — @zander-raycraft
- **First-class C# support** — sibling-based modifier scanning, record/delegate/property/field/event declaration types (#163, #170, #178 via #237) — @Alice523, @benny-yamagata, @jnMetaCode
- **C/C++ support fixes** — `.h` → C++ mapping, static-linkage export detection, qualified/parenthesized declarators, 48 entry point patterns (#163, #227 via #237) — @Alice523, @bitgineer
- **Rust support fixes** — sibling-based `visibility_modifier` scanning for `pub` detection (#227 via #237) — @bitgineer
- **Adaptive tree-sitter buffer sizing** — `Math.min(Math.max(contentLength * 2, 512KB), 32MB)` (#216 via #237) — @JasonOA888
- **Call expression matching** in tree-sitter queries (#234 via #237) — @ex-nihilo-jg
- **DeepSeek model configurations** (#217) — @JasonOA888
- 282+ new unit tests, 178 integration resolver tests across 9 languages, 53 test files, 1146 total tests passing
### Fixed
- Skip unavailable native Swift parsers in sequential ingestion (#188) — @Gujiassh
- Heritage heuristic language-gated — no longer applies class/interface rules to wrong languages (#238) — @magyargergo
- C# `base_list` distinguishes EXTENDS vs IMPLEMENTS via symbol table + `I[A-Z]` heuristic (#238) — @magyargergo
- Go `qualified_type` (`models.User`) correctly unwrapped in TypeEnv (#238) — @magyargergo
- Global tier no longer blocks resolution when kind/arity filtering can narrow to 1 candidate (#238) — @magyargergo
### Changed
- `import-processor.ts` reduced from 1412 → 711 lines (50% reduction) via resolver and config extraction (#238) — @magyargergo
- `type-env.ts` reduced from 635 → ~125 lines via type-extractor extraction (#238) — @magyargergo
- CI/CD workflows hardened with security fixes and fork PR support (#222, #225) — @magyargergo
## [1.3.11] - 2026-03-08
### Security
- Fix FTS Cypher injection by escaping backslashes in search queries (#209) — @magyargergo
### Added
- Auto-reindex hook that runs `gitnexus analyze` after commits and merges, with automatic embeddings preservation (#205) — @L1nusB
- 968 integration tests (up from ~840) covering unhappy paths across search, enrichment, CLI, pipeline, worker pool, and KuzuDB (#209) — @magyargergo
- Coverage auto-ratcheting so thresholds bump automatically on CI (#209) — @magyargergo
- Rich CI PR report with coverage bars, test counts, and threshold tracking (#209) — @magyargergo
- Modular CI workflow architecture with separate unit-test, integration-test, and orchestrator jobs (#209) — @magyargergo
### Fixed
- KuzuDB native addon crashes on Linux/macOS by running integration tests in isolated vitest processes with `--pool=forks` (#209) — @magyargergo
- Worker pool `MODULE_NOT_FOUND` crash when script path is invalid (#209) — @magyargergo
### Changed
- Added macOS to the cross-platform CI test matrix (#208) — @magyargergo
## [1.3.10] - 2026-03-07
### Security
- **MCP transport buffer cap**: Added 10 MB `MAX_BUFFER_SIZE` limit to prevent out-of-memory attacks via oversized `Content-Length` headers or unbounded newline-delimited input
- **Content-Length validation**: Reject `Content-Length` values exceeding the buffer cap before allocating memory
- **Stack overflow prevention**: Replaced recursive `readNewlineMessage` with iterative loop to prevent stack overflow from consecutive empty lines
- **Ambiguous prefix hardening**: Tightened `looksLikeContentLength` to require 14+ bytes before matching, preventing false framing detection on short input
- **Closed transport guard**: `send()` now rejects with a clear error when called after `close()`, with proper write-error propagation
### Added
- **Dual-framing MCP transport** (`CompatibleStdioServerTransport`): Auto-detects Content-Length (Codex/OpenCode) and newline-delimited JSON (Cursor/Claude Code) framing on the first message, responds in the same format (#207)
- **Lazy CLI module loading**: All CLI subcommands now use `createLazyAction()` to defer heavy imports (tree-sitter, ONNX, KuzuDB) until invocation, significantly improving `gitnexus mcp` startup time (#207)
- **Type-safe lazy actions**: `createLazyAction` uses constrained generics to validate export names against module types at compile time
- **Regression test suite**: 13 unit tests covering transport framing, security hardening, buffer limits, and lazy action loading
### Fixed
- **CALLS edge sourceId alignment**: `findEnclosingFunctionId` now generates IDs with `:startLine` suffix matching node creation format, fixing process detector finding 0 entry points (#194)
- **LRU cache zero maxSize crash**: Guard `createASTCache` against `maxSize=0` when repos have no parseable files (#144)
### Changed
- Transport constructor accepts `NodeJS.ReadableStream` / `NodeJS.WritableStream` (widened from concrete `ReadStream`/`WriteStream`)
- `processReadBuffer` simplified to break on first error instead of stale-buffer retry loop
## [1.3.9] - 2026-03-06
### Fixed
- Aligned CALLS edge sourceId with node ID format in parse worker (#194)
## [1.3.8] - 2026-03-05
### Fixed
- Force-exit after analyze to prevent KuzuDB native cleanup hang (#192)
+101
View File
@@ -0,0 +1,101 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **GitNexus** (2227 symbols, 5390 relationships, 170 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/GitNexus/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
## Tools Quick Reference
| Tool | When to use | Command |
|------|-------------|---------|
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
## Impact Risk Levels
| Depth | Meaning | Action |
|-------|---------|--------|
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
| `gitnexus://repo/GitNexus/processes` | All execution flows |
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
## Self-Check Before Finishing
Before completing any code modification task, verify:
1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
<!-- gitnexus:end -->
+73
View File
@@ -0,0 +1,73 @@
PolyForm Noncommercial License 1.0.0
<https://polyformproject.org/licenses/noncommercial/1.0.0>
## Acceptance
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
## Copyright License
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
## Distribution License
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
## Notices
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
> Required Notice: Copyright Abhigyan Patwari (https://github.com/abhigyanpatwari/GitNexus)
## Changes and New Works License
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
## Patent License
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
## Noncommercial Purposes
Any noncommercial purpose is a permitted purpose.
## Personal Uses
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
## Noncommercial Organizations
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
## Fair Use
You may have "fair use" rights for the software under the law. These terms do not limit them.
## No Other Rights
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
## Patent Defense
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
## No Liability
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
**You** refers to the individual or entity agreeing to these terms.
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the software under these terms.
**Use** means anything you do with the software requiring one of your licenses.
+458 -393
View File
@@ -1,486 +1,551 @@
# GitNexus V2
# GitNexus
⚠️ Important Notice:** GitNexus has NO official cryptocurrency, token, or coin. Any token/coin using the GitNexus name on Pump.fun or any other platform is **not affiliated with, endorsed by, or created by** this project or its maintainers. Do not purchase any cryptocurrency claiming association with GitNexus.
**Zero-Server, Graph-Based Code Intelligence Engine**
Works fully in-browser through WebAssembly. (DB engine, Embeddings model, AST parsing, all happens inside browser)
<div align="center">
https://github.com/user-attachments/assets/2fb7c522-20d1-48f6-9583-36c3969aa4dc
<a href="https://trendshift.io/repositories/19809" target="_blank">
<img src="https://trendshift.io/api/badge/repositories/19809" alt="abhigyanpatwari%2FGitNexus | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>
https://gitnexus.vercel.app
Being client sided, it costs me zero to deploy, so you can use it for free :-) (would love a ⭐ though)
<h2>Join the official Discord to discuss ideas, issues etc!</h2>
> *Like DeepWiki, but deeper.* 😉
<a href="https://discord.gg/AAsRVT6fGb">
<img src="https://img.shields.io/discord/1477255801545429032?color=5865F2&logo=discord&logoColor=white" alt="Discord"/>
</a>
<a href="https://www.npmjs.com/package/gitnexus">
<img src="https://img.shields.io/npm/v/gitnexus.svg" alt="npm version"/>
</a>
<a href="https://polyformproject.org/licenses/noncommercial/1.0.0/">
<img src="https://img.shields.io/badge/License-PolyForm%20Noncommercial-blue.svg" alt="License: PolyForm Noncommercial"/>
</a>
DeepWiki helps you *understand* code. GitNexus lets you *analyze* it—because a knowledge graph tracks every dependency, call chain, and relationship.
</div>
That's the difference between:
- "What does this function do?" → *understanding*
- "What breaks if I change this function?" → *analysis*
**Building nervous system for agent context.**
**Some quick tech jargon:**
- **Enhanced Search**: BM25 + Semantic + 1-hop graph expansion via Cypher
- **Full WASM Stack**: Tree-sitter parsing + KuzuDB graph database, all in-browser
- **Repo Map**: Complete code knowledge graph with CALLS, IMPORTS, EXTENDS relations
- **Vector Index**: HNSW embeddings for semantic similarity search
- **Cypher Queries**: Relational analysis for accurate context retrieval
- **Grounded AI**: Every answer cites `[[file:line]]` as proof
Indexes any codebase into a knowledge graph — every dependency, call chain, cluster, and execution flow — then exposes it through smart tools so AI agents never miss code.
**What you can do:**
| Capability | Description |
|------------|-------------|
| **Codebase-wide audits** | Find layer violations, forbidden dependencies |
| **Blast radius analysis** | See every function affected by a change |
| **Dead code detection** | Identify orphaned nodes with zero incoming calls |
| **Dependency tracing** | Follow import chains across the entire codebase |
| **AI analyses with citations** | Ask questions, analyze, get answers with `[[file:line]]` proof |
**100% client-side.** Your code never leaves your browser.
**Supports:** TypeScript, JavaScript, Python (Go, Java, C in progress)
https://github.com/user-attachments/assets/172685ba-8e54-4ea7-9ad1-e31a3398da72
> *Like DeepWiki, but deeper.* DeepWiki helps you *understand* code. GitNexus lets you *analyze* it — because a knowledge graph tracks every relationship, not just descriptions.
**TL;DR:** The **Web UI** is a quick way to chat with any repo. The **CLI + MCP** is how you make your AI agent actually reliable — it gives Cursor, Claude Code, Codex, and friends a deep architectural view of your codebase so they stop missing dependencies, breaking call chains, and shipping blind edits. Even smaller models get full architectural clarity, making it compete with goliath models.
---
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=abhigyanpatwari/GitNexus&type=date&legend=top-left)](https://www.star-history.com/#abhigyanpatwari/GitNexus&type=date&legend=top-left)
## Two Ways to Use GitNexus
| | **CLI + MCP** | **Web UI** |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------------------ |
| **What** | Index repos locally, connect AI agents via MCP | Visual graph explorer + AI chat in browser |
| **For** | Daily development with Cursor, Claude Code, Codex, Windsurf, OpenCode | Quick exploration, demos, one-off analysis |
| **Scale** | Full repos, any size | Limited by browser memory (~5k files), or unlimited via backend mode |
| **Install** | `npm install -g gitnexus` | No install —[gitnexus.vercel.app](https://gitnexus.vercel.app) |
| **Storage** | LadybugDB native (fast, persistent) | LadybugDB WASM (in-memory, per session) |
| **Parsing** | Tree-sitter native bindings | Tree-sitter WASM |
| **Privacy** | Everything local, no network | Everything in-browser, no server |
> **Bridge mode:** `gitnexus serve` connects the two — the web UI auto-detects the local server and can browse all your CLI-indexed repos without re-uploading or re-indexing.
---
## CLI + MCP (recommended)
The CLI indexes your repository and runs an MCP server that gives AI agents deep codebase awareness.
### Quick Start
```bash
# Index your repo (run from repo root)
npx gitnexus analyze
```
That's it. This indexes the codebase, installs agent skills, registers Claude Code hooks, and creates `AGENTS.md` / `CLAUDE.md` context files — all in one command.
To configure MCP for your editor, run `npx gitnexus setup` once — or set it up manually below.
### MCP Setup
`gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once.
### Editor Support
| Editor | MCP | Skills | Hooks (auto-augment) | Support |
| --------------------- | --- | ------ | -------------------- | -------------- |
| **Claude Code** | Yes | Yes | Yes (PreToolUse + PostToolUse) | **Full** |
| **Cursor** | Yes | Yes | — | MCP + Skills |
| **Codex** | Yes | Yes | — | MCP + Skills |
| **Windsurf** | Yes | — | — | MCP |
| **OpenCode** | Yes | Yes | — | MCP + Skills |
| **Codex** | Yes | — | — | MCP |
> **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that enrich searches with graph context + PostToolUse hooks that auto-reindex after commits.
## Community Integrations
Built by the community — not officially maintained, but worth checking out.
| Project | Author | Description |
|---------|--------|-------------|
| [pi-gitnexus](https://github.com/tintinweb/pi-gitnexus) | [@tintinweb](https://github.com/tintinweb) | GitNexus plugin for [pi](https://pi.dev) — `pi install npm:pi-gitnexus` |
| [gitnexus-stable-ops](https://github.com/ShunsukeHayashi/gitnexus-stable-ops) | [@ShunsukeHayashi](https://github.com/ShunsukeHayashi) | Stable ops & deployment workflows (Miyabi ecosystem) |
> Have a project built on GitNexus? Open a PR to add it here!
If you prefer manual configuration:
**Claude Code** (full support — MCP + skills + hooks):
```bash
claude mcp add gitnexus -- npx -y gitnexus@latest mcp
```
**Codex** (full support — MCP + skills):
```bash
codex mcp add gitnexus -- npx -y gitnexus@latest mcp
```
**Cursor** (`~/.cursor/mcp.json` — global, works for all projects):
```json
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
```
**OpenCode** (`~/.config/opencode/config.json`):
```json
{
"mcp": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
```
**Codex** (`~/.codex/config.toml` for system scope, or `.codex/config.toml` for project scope):
```toml
[mcp_servers.gitnexus]
command = "npx"
args = ["-y", "gitnexus@latest", "mcp"]
```
### CLI Commands
```bash
gitnexus setup # Configure MCP for your editors (one-time)
gitnexus analyze [path] # Index a repository (or update stale index)
gitnexus analyze --force # Force full re-index
gitnexus analyze --skills # Generate repo-specific skill files from detected communities
gitnexus analyze --skip-embeddings # Skip embedding generation (faster)
gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
gitnexus analyze --verbose # Log skipped files when parsers are unavailable
gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
gitnexus serve # Start local HTTP server (multi-repo) for web UI connection
gitnexus list # List all indexed repositories
gitnexus status # Show index status for current repo
gitnexus clean # Delete index for current repo
gitnexus clean --all --force # Delete all indexes
gitnexus wiki [path] # Generate repository wiki from knowledge graph
gitnexus wiki --model <model> # Wiki with custom LLM model (default: gpt-4o-mini)
gitnexus wiki --base-url <url> # Wiki with custom LLM API base URL
```
### What Your AI Agent Gets
**7 tools** exposed via MCP:
| Tool | What It Does | `repo` Param |
| ------------------ | ----------------------------------------------------------------- | -------------- |
| `list_repos` | Discover all indexed repositories | — |
| `query` | Process-grouped hybrid search (BM25 + semantic + RRF) | Optional |
| `context` | 360-degree symbol view — categorized refs, process participation | Optional |
| `impact` | Blast radius analysis with depth grouping and confidence | Optional |
| `detect_changes` | Git-diff impact — maps changed lines to affected processes | Optional |
| `rename` | Multi-file coordinated rename with graph + text search | Optional |
| `cypher` | Raw Cypher graph queries | Optional |
> When only one repo is indexed, the `repo` parameter is optional. With multiple repos, specify which one: `query({query: "auth", repo: "my-app"})`.
**Resources** for instant context:
| Resource | Purpose |
| ----------------------------------------- | ---------------------------------------------------- |
| `gitnexus://repos` | List all indexed repositories (read this first) |
| `gitnexus://repo/{name}/context` | Codebase stats, staleness check, and available tools |
| `gitnexus://repo/{name}/clusters` | All functional clusters with cohesion scores |
| `gitnexus://repo/{name}/cluster/{name}` | Cluster members and details |
| `gitnexus://repo/{name}/processes` | All execution flows |
| `gitnexus://repo/{name}/process/{name}` | Full process trace with steps |
| `gitnexus://repo/{name}/schema` | Graph schema for Cypher queries |
**2 MCP prompts** for guided workflows:
| Prompt | What It Does |
| ----------------- | ------------------------------------------------------------------------- |
| `detect_impact` | Pre-commit change analysis — scope, affected processes, risk level |
| `generate_map` | Architecture documentation from the knowledge graph with mermaid diagrams |
**4 agent skills** installed to `.claude/skills/` automatically:
- **Exploring** — Navigate unfamiliar code using the knowledge graph
- **Debugging** — Trace bugs through call chains
- **Impact Analysis** — Analyze blast radius before changes
- **Refactoring** — Plan safe refactors using dependency mapping
**Repo-specific skills** generated with `--skills`:
When you run `gitnexus analyze --skills`, GitNexus detects the functional areas of your codebase (via Leiden community detection) and generates a `SKILL.md` file for each one under `.claude/skills/generated/`. Each skill describes a module's key files, entry points, execution flows, and cross-area connections — so your AI agent gets targeted context for the exact area of code you're working in. Skills are regenerated on each `--skills` run to stay current with the codebase.
---
## Multi-Repo MCP Architecture
GitNexus uses a **global registry** so one MCP server can serve multiple indexed repos. No per-project MCP config needed — set it up once and it works everywhere.
```mermaid
flowchart TD
subgraph CLI [CLI Commands]
Setup["gitnexus setup"]
Analyze["gitnexus analyze"]
Clean["gitnexus clean"]
List["gitnexus list"]
end
subgraph Registry ["~/.gitnexus/"]
RegFile["registry.json"]
end
subgraph Repos [Project Repos]
RepoA[".gitnexus/ in repo A"]
RepoB[".gitnexus/ in repo B"]
end
subgraph MCP [MCP Server]
Server["server.ts"]
Backend["LocalBackend"]
Pool["Connection Pool"]
ConnA["LadybugDB conn A"]
ConnB["LadybugDB conn B"]
end
Setup -->|"writes global MCP config"| CursorConfig["~/.cursor/mcp.json"]
Analyze -->|"registers repo"| RegFile
Analyze -->|"stores index"| RepoA
Clean -->|"unregisters repo"| RegFile
List -->|"reads"| RegFile
Server -->|"reads registry"| RegFile
Server --> Backend
Backend --> Pool
Pool -->|"lazy open"| ConnA
Pool -->|"lazy open"| ConnB
ConnA -->|"queries"| RepoA
ConnB -->|"queries"| RepoB
```
**How it works:** Each `gitnexus analyze` stores the index in `.gitnexus/` inside the repo (portable, gitignored) and registers a pointer in `~/.gitnexus/registry.json`. When an AI agent starts, the MCP server reads the registry and can serve any indexed repo. LadybugDB connections are opened lazily on first query and evicted after 5 minutes of inactivity (max 5 concurrent). If only one repo is indexed, the `repo` parameter is optional on all tools — agents don't need to change anything.
---
## Web UI (browser-based)
A fully client-side graph explorer and AI chat. No server, no install — your code never leaves the browser.
**Try it now:** [gitnexus.vercel.app](https://gitnexus.vercel.app) — drag & drop a ZIP and start exploring.
<img width="2550" height="1343" alt="gitnexus_img" src="https://github.com/user-attachments/assets/cc5d637d-e0e5-48e6-93ff-5bcfdb929285" />
---
## 🔍 The Problem with AI Coding Tools
Tools like **Cursor**, **Claude Code**, **Cline**, **Roo Code**, and **Windsurf** are powerful—but they share a fundamental limitation: **they don't truly know your codebase structure**.
| Tool | Context Strategy | The Gap |
|------|------------------|---------|
| **Cursor** | Files in tabs + embeddings | No call graph. Can't trace "what calls this?" |
| **Claude Code** | File search + grep | Text-based. Misses semantic connections |
| **Cline/Roo Code** | Repo map + tree-sitter | Static structure. No runtime dependencies tracked |
| **Windsurf** | Cascade context | Limited dependency depth |
**What happens:**
1. AI edits `UserService.validate()`
2. Doesn't know 47 functions depend on its return type
3. **Breaking changes ship** 💥
### The Solution: Graph Coverage
A knowledge graph tracks **actual relationships**, not just file contents:
```mermaid
graph LR
EDIT[AI wants to edit UserService.validate] --> QUERY[Graph Query: What depends on this?]
QUERY --> DEPS["47 callers across 12 files"]
DEPS --> SAFE[AI sees full blast radius first]
```
**Current state:** GitNexus is a standalone tool—a better DeepWiki that's 100% client-side with graph-powered analysis.
**Future goal (MCP):** Expose GitNexus as an MCP server so tools like Cursor and Claude Code can query it for accurate context. They ask "what calls X?", GitNexus returns the actual call graph. No more guessing.
---
## 🚀 Quick Start
Or run locally:
```bash
git clone <repository-url>
cd gitnexus
git clone https://github.com/abhigyanpatwari/gitnexus.git
cd gitnexus/gitnexus-web
npm install
npm run dev
```
Open http://localhost:5173, drag & drop a ZIP of your codebase, and start exploring.
The web UI uses the same indexing pipeline as the CLI but runs entirely in WebAssembly (Tree-sitter WASM, LadybugDB WASM, in-browser embeddings). It's great for quick exploration but limited by browser memory for larger repos.
**Local Backend Mode:** Run `gitnexus serve` and open the web UI locally — it auto-detects the server and shows all your indexed repos, with full AI chat support. No need to re-upload or re-index. The agent's tools (Cypher queries, search, code navigation) route through the backend HTTP API automatically.
---
## 🏗️ Indexing Architecture
## The Problem GitNexus Solves
Two-phase indexing: **Knowledge Graph** (blocking) → **Embeddings** (background).
Tools like **Cursor**, **Claude Code**, **Codex**, **Cline**, **Roo Code**, and **Windsurf** are powerful — but they don't truly know your codebase structure.
### Phase 1-5: Knowledge Graph Creation
**What happens:**
1. AI edits `UserService.validate()`
2. Doesn't know 47 functions depend on its return type
3. **Breaking changes ship**
### Traditional Graph RAG vs GitNexus
Traditional approaches give the LLM raw graph edges and hope it explores enough. GitNexus **precomputes structure at index time** — clustering, tracing, scoring — so tools return complete context in one call:
```mermaid
flowchart TD
subgraph P1["Phase 1: Extract (0-15%)"]
E1[Decompress ZIP] --> E2[Collect file paths]
flowchart TB
subgraph Traditional["Traditional Graph RAG"]
direction TB
U1["User: What depends on UserService?"]
U1 --> LLM1["LLM receives raw graph"]
LLM1 --> Q1["Query 1: Find callers"]
Q1 --> Q2["Query 2: What files?"]
Q2 --> Q3["Query 3: Filter tests?"]
Q3 --> Q4["Query 4: High-risk?"]
Q4 --> OUT1["Answer after 4+ queries"]
end
subgraph P2["Phase 2: Structure (15-30%)"]
S1[Build folder tree] --> S2[Create CONTAINS edges]
subgraph GN["GitNexus Smart Tools"]
direction TB
U2["User: What depends on UserService?"]
U2 --> TOOL["impact UserService upstream"]
TOOL --> PRECOMP["Pre-structured response:
8 callers, 3 clusters, all 90%+ confidence"]
PRECOMP --> OUT2["Complete answer, 1 query"]
end
subgraph P3["Phase 3: Parse (30-70%)"]
PA1[Load Tree-sitter WASM] --> PA2[Generate ASTs]
PA2 --> PA3[Extract symbols]
PA3 --> PA4[Populate Symbol Table]
end
subgraph P4["Phase 4: Imports (70-82%)"]
I1[Find import statements] --> I2[Resolve paths]
I2 --> I3[Create IMPORTS edges]
end
subgraph P5["Phase 5: Calls + Heritage (82-100%)"]
C1[Find function calls] --> C2[Resolve via Symbol Table]
C2 --> C3[Create CALLS edges]
C3 --> H1[Find extends/implements]
H1 --> H2[Create EXTENDS/IMPLEMENTS edges]
end
P1 --> P2 --> P3 --> P4 --> P5
P5 --> DB[(KuzuDB WASM)]
DB --> READY[Graph Ready!]
```
### Symbol Table: Dual HashMap
**Core innovation: Precomputed Relational Intelligence**
Resolution strategy for function calls:
```mermaid
flowchart TD
CALL[Found call: validateUser] --> CHECK1{In Import Map?}
CHECK1 -->|Yes| FOUND1[Use imported definition]
CHECK1 -->|No| CHECK2{In Current File?}
CHECK2 -->|Yes| FOUND2[Use local definition]
CHECK2 -->|No| CHECK3{Global Search}
CHECK3 -->|Found| FOUND3[Use first match]
CHECK3 -->|Not Found| SKIP[Skip - unresolved]
FOUND1 --> EDGE[Create CALLS edge]
FOUND2 --> EDGE
FOUND3 --> EDGE
```
**Data structure:**
```
File-Scoped: Map<FilePath, Map<SymbolName, NodeID>>
Global: Map<SymbolName, SymbolDefinition[]>
```
### Phase 6+: Background Embeddings
```mermaid
flowchart LR
subgraph BG["Background (Non-blocking)"]
M1[Load snowflake-arctic-embed-xs] --> M2[Initialize WebGPU/WASM]
M2 --> E1[Batch embed nodes]
E1 --> E2[INSERT into CodeEmbedding table]
E2 --> V1[Create HNSW Vector Index]
V1 --> B1[Build BM25 Index]
end
BG --> AI[AI Search Ready!]
```
User can explore the graph during embedding. AI features unlock when complete.
- **Reliability** — LLM can't miss context, it's already in the tool response
- **Token efficiency** — No 10-query chains to understand one function
- **Model democratization** — Smaller LLMs work because tools do the heavy lifting
---
## 📊 Graph Schema
## How It Works
### Node Types
GitNexus builds a complete knowledge graph of your codebase through a multi-phase indexing pipeline:
| Label | Description | Properties |
|-------|-------------|------------|
| `Folder` | Directory | `name`, `filePath` |
| `File` | Source file | `name`, `filePath`, `language` |
| `Function` | Function def | `name`, `filePath`, `startLine`, `endLine`, `isExported` |
| `Class` | Class def | `name`, `filePath`, `startLine`, `endLine` |
| `Interface` | Interface def | `name`, `filePath`, `startLine`, `endLine` |
| `Method` | Class method | `name`, `filePath`, `startLine`, `endLine` |
| `CodeElement` | Generic symbol | `name`, `filePath` |
1. **Structure** — Walks the file tree and maps folder/file relationships
2. **Parsing** — Extracts functions, classes, methods, and interfaces using Tree-sitter ASTs
3. **Resolution** — Resolves imports, function calls, heritage, constructor inference, and `self`/`this` receiver types across files with language-aware logic
4. **Clustering** — Groups related symbols into functional communities
5. **Processes** — Traces execution flows from entry points through call chains
6. **Search** — Builds hybrid search indexes for fast retrieval
### Relationship Table: `CodeRelation`
### Supported Languages
Single edge table with `type` property:
| Language | Imports | Named Bindings | Exports | Heritage | Type Annotations | Constructor Inference | Config | Frameworks | Entry Points |
|----------|---------|----------------|---------|----------|-----------------|---------------------|--------|------------|-------------|
| TypeScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| JavaScript | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| Python | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Java | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
| Kotlin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
| C# | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Go | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Rust | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
| PHP | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ | ✓ |
| Ruby | ✓ | — | ✓ | ✓ | — | ✓ | — | ✓ | ✓ |
| Swift | — | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| C | — | — | ✓ | — | ✓ | ✓ | — | ✓ | ✓ |
| C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
| Type | From | To | Description |
|------|------|-----|-------------|
| `CONTAINS` | Folder | File/Folder | Directory structure |
| `DEFINES` | File | Function/Class/etc | Code definitions |
| `IMPORTS` | File | File | Module dependencies |
| `CALLS` | Function/Method | Function/Method | Call graph |
| `EXTENDS` | Class | Class | Inheritance |
| `IMPLEMENTS` | Class | Interface | Interface implementation |
**Imports** — cross-file import resolution · **Named Bindings** — `import { X as Y }` / re-export tracking · **Exports** — public/exported symbol detection · **Heritage** — class inheritance, interfaces, mixins · **Type Annotations** — explicit type extraction for receiver resolution · **Constructor Inference** — infer receiver type from constructor calls (`self`/`this` resolution included for all languages) · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics
---
## 🛠️ Agent Tools Architecture
## Tool Examples
The LangChain ReAct agent has **5 tools** for code exploration. These tools **use the graph** built during indexing.
### Impact Analysis
### Tool 1: `search` — Hybrid Search with Graph Context
```
impact({target: "UserService", direction: "upstream", minConfidence: 0.8})
Combines **BM25** (keyword) + **Semantic** (vector) + **1-hop expansion**:
TARGET: Class UserService (src/services/user.ts)
```mermaid
flowchart TD
Q[Query: auth middleware] --> BM25[BM25 Keyword Search]
Q --> SEM[Semantic Vector Search]
BM25 --> RRF[Reciprocal Rank Fusion]
SEM --> RRF
RRF --> TOP[Top K Results]
TOP --> HOP[1-Hop Graph Expansion]
HOP --> OUT["Each result includes:
• ID, file, score
• Incoming connections (who calls this)
• Outgoing connections (what this calls)"]
UPSTREAM (what depends on this):
Depth 1 (WILL BREAK):
handleLogin [CALLS 90%] -> src/api/auth.ts:45
handleRegister [CALLS 90%] -> src/api/auth.ts:78
UserController [CALLS 85%] -> src/controllers/user.ts:12
Depth 2 (LIKELY AFFECTED):
authRouter [IMPORTS] -> src/routes/auth.ts
```
**How 1-hop works:**
```cypher
MATCH (n {id: $nodeId})
OPTIONAL MATCH (n)-[r1:CodeRelation]->(dst)
OPTIONAL MATCH (src)-[r2:CodeRelation]->(n)
RETURN collect(dst.name), collect(src.name)
Options: `maxDepth`, `minConfidence`, `relationTypes` (`CALLS`, `IMPORTS`, `EXTENDS`, `IMPLEMENTS`), `includeTests`
### Process-Grouped Search
```
query({query: "authentication middleware"})
processes:
- summary: "LoginFlow"
priority: 0.042
symbol_count: 4
process_type: cross_community
step_count: 7
process_symbols:
- name: validateUser
type: Function
filePath: src/auth/validate.ts
process_id: proc_login
step_index: 2
definitions:
- name: AuthConfig
type: Interface
filePath: src/types/auth.ts
```
The agent sees not just *what matches*, but *what connects to it*.
### Context (360-degree Symbol View)
---
```
context({name: "validateUser"})
### Tool 2: `cypher` — Raw Graph Queries with Auto-Embedding
symbol:
uid: "Function:validateUser"
kind: Function
filePath: src/auth/validate.ts
startLine: 15
Execute Cypher directly. If you include `{{QUERY_VECTOR}}`, it auto-embeds:
incoming:
calls: [handleLogin, handleRegister, UserController]
imports: [authRouter]
```mermaid
flowchart LR
CQ[Cypher with placeholder] --> CHECK{Contains QUERY_VECTOR?}
CHECK -->|Yes| EMBED[Embed query text]
EMBED --> REPLACE[Replace placeholder with vector]
CHECK -->|No| EXEC
REPLACE --> EXEC[Execute Cypher]
EXEC --> RES[Return Results]
outgoing:
calls: [checkPassword, createSession]
processes:
- name: LoginFlow (step 2/7)
- name: RegistrationFlow (step 3/5)
```
**Example with auto-embedding:**
```cypher
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'idx', {{QUERY_VECTOR}}, 10)
YIELD node, distance
WHERE distance < 0.4
MATCH (caller:Function)-[:CodeRelation {type: 'CALLS'}]->(n:Function {id: node.nodeId})
RETURN caller.name, n.name
### Detect Changes (Pre-Commit)
```
detect_changes({scope: "all"})
summary:
changed_count: 12
affected_count: 3
changed_files: 4
risk_level: medium
changed_symbols: [validateUser, AuthService, ...]
affected_processes: [LoginFlow, RegistrationFlow, ...]
```
The agent provides `query: "authentication"` → system embeds it → injects the vector.
### Rename (Multi-File)
---
```
rename({symbol_name: "validateUser", new_name: "verifyUser", dry_run: true})
### Tool 3: `grep` — Regex Pattern Matching
For exact strings, error codes, TODOs:
```mermaid
flowchart LR
PAT["Pattern: TODO|FIXME"] --> REGEX[Compile Regex]
REGEX --> SCAN[Scan all files]
SCAN --> MATCH[Match per line]
MATCH --> RES["file:line: content"]
status: success
files_affected: 5
total_edits: 8
graph_edits: 6 (high confidence)
text_search_edits: 2 (review carefully)
changes: [...]
```
---
### Tool 4: `read` — Smart File Reader
Fuzzy path matching with suggestions:
```mermaid
flowchart TD
REQ[Request: src/utils.ts] --> EXACT{Exact match?}
EXACT -->|Yes| RET[Return content]
EXACT -->|No| FUZZY[Fuzzy match by segments]
FUZZY --> FOUND{Found?}
FOUND -->|Yes| RET
FOUND -->|No| SUGGEST[Suggest similar files]
```
---
### Tool 5: `highlight` — Visual Graph Feedback
Emits a marker that the UI parses to highlight nodes:
```
[HIGHLIGHT_NODES:Function:src/auth.ts:validate,Class:src/user.ts:UserService]
```
---
## 💡 Key Discovery: Unified Vector + Graph
Most Graph RAG systems use **separate databases**—vector DB for semantic search, graph DB for traversal.
KuzuDB supports **native vector indexing (HNSW)**, so we do both in **one Cypher query**:
### Cypher Queries
```cypher
-- Semantic search + graph traversal in ONE query
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx', $queryVector, 20)
YIELD node AS emb, distance
WITH emb, distance WHERE distance < 0.4
MATCH (n:Function {id: emb.nodeId})<-[:CodeRelation {type: 'CALLS'}]-(caller:Function)
RETURN n.name, caller.name, distance
ORDER BY distance
-- Find what calls auth functions with high confidence
MATCH (c:Community {heuristicLabel: 'Authentication'})<-[:CodeRelation {type: 'MEMBER_OF'}]-(fn)
MATCH (caller)-[r:CodeRelation {type: 'CALLS'}]->(fn)
WHERE r.confidence > 0.8
RETURN caller.name, fn.name, r.confidence
ORDER BY r.confidence DESC
```
**Why this matters:**
- 🎯 **Single query execution** — No round-trips between systems
- 📊 **Built-in relevance ranking** — Distance IS the score
- ⚡ **No separate vector DB** — One database, one query language
- 🌳 **LLM-friendly** — Agent writes one Cypher, gets semantic + structural results
---
## 🔬 Deep Dive: Copy-on-Write Memory Issue
## Wiki Generation
Hit an interesting problem storing embeddings worth documenting.
Generate LLM-powered documentation from your knowledge graph:
**Setup:** Store 384-dim embeddings alongside code nodes.
```cypher
MATCH (n:CodeNode {id: $id}) SET n.embedding = $vec
```bash
# Requires an LLM API key (OPENAI_API_KEY, etc.)
gitnexus wiki
# Use a custom model or provider
gitnexus wiki --model gpt-4o
gitnexus wiki --base-url https://api.anthropic.com/v1
# Force full regeneration
gitnexus wiki --force
```
**Problem:** Worked for ~20 nodes, exploded at ~1000:
```
Buffer manager exception: Unable to allocate memory!
```
**Root cause: Copy-on-Write.** Each `UPDATE` copies the entire record (~2KB of code content). 1000 updates = massive memory duplication in WASM.
```mermaid
flowchart LR
subgraph COW["Copy-on-Write Effect"]
OLD[Old: 2KB] --> NEW[New: 3.5KB]
end
COW -->|"× 1000 nodes"| BOOM[💥 Buffer Exhausted]
```
**Fix:** Separate `CodeEmbedding` table with `INSERT` only:
```mermaid
flowchart TD
subgraph Old["❌ Single Table"]
CN1[CodeNode with embedding<br/>UPDATE triggers COW]
end
subgraph New["✅ Separate Table"]
CN2[CodeNode<br/>id, name, content]
CE[CodeEmbedding<br/>nodeId, embedding<br/>INSERT only]
end
Old -->|"Memory explosion"| FAIL
New -->|"Works at scale"| WIN
```
**Lesson:** In-memory WASM DBs have hard limits. Profile at scale, not happy path.
The wiki generator reads the indexed graph structure, groups files into modules via LLM, generates per-module documentation pages, and creates an overview page — all with cross-references to the knowledge graph.
---
## ⚡ V2 Technical Improvements
## Tech Stack
### Sigma.js + WebGL
- V1: D3.js, choked at ~3k nodes
- V2: Sigma.js + GPU rendering, smooth at 10k+
### Dual HashMap Symbol Table
- V1: Trie (prefix tree) - clever but slow
- V2: File-scoped + Global hashmaps - **~2x speedup**
### LRU AST Cache
- Tree-sitter ASTs live in WASM memory
- LRU cache (50 slots) with `tree.delete()` for cleanup
- Memory stays bounded even for huge codebases
### ForceAtlas2 in Web Worker
- Layout algorithm runs off main thread
- UI stays responsive during graph positioning
| Layer | CLI | Web |
| ------------------------- | ------------------------------------- | --------------------------------------- |
| **Runtime** | Node.js (native) | Browser (WASM) |
| **Parsing** | Tree-sitter native bindings | Tree-sitter WASM |
| **Database** | LadybugDB native | LadybugDB WASM |
| **Embeddings** | HuggingFace transformers.js (GPU/CPU) | transformers.js (WebGPU/WASM) |
| **Search** | BM25 + semantic + RRF | BM25 + semantic + RRF |
| **Agent Interface** | MCP (stdio) | LangChain ReAct agent |
| **Visualization** | — | Sigma.js + Graphology (WebGL) |
| **Frontend** | — | React 18, TypeScript, Vite, Tailwind v4 |
| **Clustering** | Graphology | Graphology |
| **Concurrency** | Worker threads + async | Web Workers + Comlink |
---
## 🚧 Roadmap
## Roadmap
### Actively Building
- [ ] **MCP Support** - Model Context Protocol for tool extensibility
- [ ] **External DB Support** - Connect to Neo4j (hosted or Docker)
- [ ] **Blast Radius Analysis Tool** - Dedicated UI for impact analysis
- [ ] **Multi-Worker Pool** - Parallel parsing across Web Workers
- [ ] **Ollama Support** - Local LLM integration
- [ ] **CSV Export** - Export node/relationship tables
- [ ] **LLM Cluster Enrichment** — Semantic cluster names via LLM API
- [ ] **AST Decorator Detection** — Parse @Controller, @Get, etc.
- [ ] **Incremental Indexing** — Only re-index changed files
### 🎯 The Vision: Browser-Based MCP Server
### Recently Completed
**Goal:** Expose GitNexus as a local MCP server directly from the browser.
This would let AI coding tools like **Cursor**, **Claude Code**, **Windsurf**, etc. connect to your running GitNexus instance and use its knowledge graph for:
- 🔍 **Reliable context gathering** — AI gets actual dependencies, not grep guesses
- 💥 **Blast radius detection** — Before making changes, query what would break
- 🔐 **Codebase-wide audits** — Find violations, dead code, circular dependencies
- 🧠 **Grounded answers** — Every response backed by graph traversal, not hallucination
```mermaid
graph LR
subgraph Browser["GitNexus (Browser)"]
KG[Knowledge Graph]
MCP[MCP Server]
end
subgraph Tools["AI Coding Tools"]
CURSOR[Cursor]
CLAUDE[Claude Code]
WIND[Windsurf]
end
KG --> MCP
MCP <-->|localhost| CURSOR
MCP <-->|localhost| CLAUDE
MCP <-->|localhost| WIND
```
**Why this matters:** Current AI coding tools are blind to real dependencies. They use grep or embeddings—better than nothing, but not enough to prevent breaking changes. A knowledge graph MCP would give them the accurate, structural context they need.
### Recently Completed ✅
- [x] Graph RAG Agent with 5 tools (search, cypher, grep, read, highlight)
- [x] Browser embeddings (snowflake-arctic-embed-xs, 22M params)
- [x] Vector index with HNSW in KuzuDB
- [x] Hybrid search (BM25 + semantic + RRF)
- [x] Streaming AI chat with tool visibility
- [x] Grounded citations (`[[file:line]]` format)
- [x] Multiple LLM providers (OpenAI, Azure, Gemini, Anthropic)
- [X] Constructor-Inferred Type Resolution, `self`/`this` Receiver Mapping
- [X] Wiki Generation, Multi-File Rename, Git-Diff Impact Analysis
- [X] Process-Grouped Search, 360-Degree Context, Claude Code Hooks
- [X] Multi-Repo MCP, Zero-Config Setup, 13 Language Support
- [X] Community Detection, Process Detection, Confidence Scoring
- [X] Hybrid Search, Vector Index
---
## 🛠 Tech Stack
## Security & Privacy
| Layer | Technology |
|-------|------------|
| **Frontend** | React 18, TypeScript, Vite, Tailwind v4 |
| **Visualization** | Sigma.js, Graphology, ForceAtlas2 (WebGL) |
| **Parsing** | Tree-sitter WASM (TS, JS, Python) |
| **Database** | KuzuDB WASM (graph + vector HNSW) |
| **Embeddings** | transformers.js, snowflake-arctic-embed-xs (22M) |
| **AI** | LangChain ReAct agent, streaming |
| **Concurrency** | Web Workers + Comlink |
- **CLI**: Everything runs locally on your machine. No network calls. Index stored in `.gitnexus/` (gitignored). Global registry at `~/.gitnexus/` stores only paths and metadata.
- **Web**: Everything runs in your browser. No code uploaded to any server. API keys stored in localStorage only.
- Open source — audit the code yourself.
---
## 🔐 Security & Privacy
## Acknowledgments
- All processing happens in your browser
- No code uploaded to any server
- API keys stored in localStorage only
- Open source—audit the code yourself
---
## 📝 License
MIT License
---
## 🙏 Acknowledgments
- [Tree-sitter](https://tree-sitter.github.io/) - AST parsing
- [KuzuDB](https://kuzudb.com/) - Embedded graph database with vector support
- [Sigma.js](https://www.sigmajs.org/) - WebGL graph rendering
- [transformers.js](https://huggingface.co/docs/transformers.js) - Browser ML
- [LangChain](https://langchain.com/) - Agent orchestration
- [Tree-sitter](https://tree-sitter.github.io/) — AST parsing
- [LadybugDB](https://ladybugdb.com/) — Embedded graph database with vector support (formerly KuzuDB)
- [Sigma.js](https://www.sigmajs.org/) — WebGL graph rendering
- [transformers.js](https://huggingface.co/docs/transformers.js) — Browser ML
- [Graphology](https://graphology.github.io/) — Graph data structures
- [MCP](https://modelcontextprotocol.io/) — Model Context Protocol
+57
View File
@@ -0,0 +1,57 @@
---
review_agents: [kieran-typescript-reviewer, pattern-recognition-specialist, architecture-strategist, data-integrity-guardian, security-sentinel, performance-oracle, code-simplicity-reviewer]
plan_review_agents: [kieran-typescript-reviewer, architecture-strategist, code-simplicity-reviewer]
voltagent_agents: [voltagent-lang:typescript-pro, voltagent-qa-sec:security-auditor, voltagent-data-ai:database-optimizer]
---
# Review Context
## Project Overview
GitNexus is a code intelligence tool that builds a knowledge graph from source code using tree-sitter AST parsing across 12 languages and KuzuDB for graph storage. Two packages: `gitnexus/` (CLI/MCP, TypeScript) and `gitnexus-web/` (browser).
## Cross-Language Pattern Consistency (pattern-recognition-specialist)
- 12 language-specific type extractors in `gitnexus/src/core/ingestion/type-extractors/` must follow identical patterns for: async unwrapping, constructor binding, namespace handling, nullable type stripping, for-loop element typing.
- Past bugs: C#/Rust missing `await_expression` unwrapping that TypeScript handled correctly; PHP backslash namespace splitting inconsistent with other languages' `::` / `.` splitting.
- When reviewing type extractor changes, verify the same pattern exists in ALL applicable language files — asymmetry is the #1 source of bugs.
## Data Integrity (data-integrity-guardian)
- KuzuDB graph operations: schema in `gitnexus/src/core/kuzu/schema.ts`, adapter in `kuzu-adapter.ts`.
- The ingestion pipeline writes symbols and relationships to the graph — changes to node/relation schemas or the ingestion pipeline can corrupt the index.
- Known issue: KuzuDB `close()` hangs on Linux due to C++ destructor — use `detachKuzu()` pattern.
- `lbug-adapter.ts` fallback path needs quote/newline escaping for Cypher injection prevention.
## Security (security-sentinel)
- Cypher query construction in `lbug-adapter.ts` and `kuzu-adapter.ts` — watch for injection via unescaped user-provided symbol names.
- CLI accepts `--repo` parameter and file paths — validate against path traversal.
- MCP server exposes tools to external AI agents — all tool inputs are untrusted.
## Performance (performance-oracle)
- Tree-sitter buffer size is adaptive (512KB–32MB) via `getTreeSitterBufferSize()` in `constants.ts`.
- The ingestion pipeline processes entire repositories — O(n) per file with potential O(n²) in cross-file resolution.
- KuzuDB batch inserts vs individual inserts matter for large repos.
## Architecture (architecture-strategist)
- Ingestion pipeline phases: structure → parsing → imports → calls → heritage → processes → type resolution.
- Shared modules: `export-detection.ts`, `constants.ts`, `utils.ts` — changes here have wide blast radius.
- `gitnexus-web` package drifts behind CLI — flag if a change should be mirrored.
## Voltagent Supplementary Agents
Invoke these via the Agent tool alongside `/ce:review` for deeper specialist analysis. These cover gaps that compound-engineering agents don't:
### voltagent-lang:typescript-pro
**When:** Changes touch type-resolution logic, generics, conditional types, or complex type-level programming in `type-env.ts`, `type-extractors/*.ts`, or `types.ts`.
**Why:** The type resolution system uses advanced TypeScript patterns (discriminated unions, mapped types, recursive generics) that benefit from deep TS type-system review beyond what kieran-typescript-reviewer covers.
### voltagent-qa-sec:security-auditor
**When:** Changes touch MCP tool handlers, Cypher query construction, CLI argument parsing, or any code that processes external input.
**Why:** GitNexus is an MCP server — all tool inputs come from untrusted AI agents. Systematic OWASP-level audit catches injection vectors that spot-checking misses. Past finding: `lbug-adapter.ts` fallback path had unescaped newlines in Cypher queries.
### voltagent-data-ai:database-optimizer
**When:** Changes touch `kuzu-adapter.ts`, `schema.ts`, `lbug-adapter.ts`, or any Cypher query construction/execution.
**Why:** No CE agent specializes in graph database optimization. KuzuDB batch insert patterns, index usage, and query planning directly affect analysis speed on large repos.
## Review Tooling
- Use `gitnexus_impact()` before approving changes to any symbol — check d=1 (WILL BREAK) callers.
- Use `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` to map PR diffs to affected execution flows.
- Use claude-mem to surface past architectural decisions relevant to the code under review.
+23
View File
@@ -0,0 +1,23 @@
# ─── GitNexus SWE-bench Eval — API Keys ───
# Copy this file to .env and fill in the keys you have.
# You only need keys for the models you plan to test.
# OpenRouter (covers Claude, MiniMax, GLM, and 200+ other models)
# Get yours at: https://openrouter.ai/keys
OPENROUTER_API_KEY=
# Anthropic (direct — optional if using OpenRouter)
# Get yours at: https://console.anthropic.com/
ANTHROPIC_API_KEY=
# ZhipuAI / GLM (direct — optional if using OpenRouter)
# Get yours at: https://open.bigmodel.cn/
ZHIPUAI_API_KEY=
# MiniMax (direct — optional if using OpenRouter)
MINIMAX_API_KEY=
# ─── Optional ───
# Cost tracking: set to "ignore_errors" if litellm can't find pricing for a model
# MSWEA_COST_TRACKING=ignore_errors
+16
View File
@@ -0,0 +1,16 @@
# Evaluation results (large, should not be committed)
results/
*.traj.json
preds.json
# Python
__pycache__/
*.pyc
*.egg-info/
.eggs/
dist/
build/
# Environment
.env
.venv/
+210
View File
@@ -0,0 +1,210 @@
# GitNexus SWE-bench Evaluation Harness
Evaluate whether GitNexus code intelligence improves AI agent performance on real software engineering tasks. Runs SWE-bench instances across multiple models and compares baseline (no graph) vs GitNexus-enhanced configurations.
## What This Tests
**Hypothesis**: Giving AI agents structural code intelligence (call graphs, execution flows, blast radius analysis) improves their ability to resolve real GitHub issues — measured by resolve rate, cost, and efficiency.
**Evaluation modes:**
| Mode | What the agent gets |
|------|-------------------|
| `baseline` | Standard bash tools (grep, find, cat, sed) — control group |
| `native` | Baseline + explicit GitNexus tools via eval-server (~100ms) |
| `native_augment` | Native tools + grep results automatically enriched with graph context (**recommended**) |
> **Recommended**: Use `native_augment` mode. It mirrors the Claude Code model — the agent gets both explicit GitNexus tools (fast bash commands) AND automatic enrichment of grep results with callers, callees, and execution flows. The agent decides when to use explicit tools vs rely on enriched search output.
**Models supported:**
- Claude 3.5 Haiku, Claude Sonnet 4, Claude Opus 4
- MiniMax M1 2.5
- GLM 4.7, GLM 5
- Any model supported by litellm (add a YAML config)
## Prerequisites
- Python 3.11+
- Docker (for SWE-bench containers)
- Node.js 18+ (for GitNexus)
- API keys for your chosen models
## Setup
```bash
cd eval
# Install dependencies
pip install -e .
# Set up API keys — copy the template and fill in your keys
cp .env.example .env
# Then edit .env and paste your key(s)
```
All models are routed through **OpenRouter** by default, so a single `OPENROUTER_API_KEY` is all you need. To use provider APIs directly (Anthropic, ZhipuAI, etc.), edit the model YAML in `configs/models/` and set the corresponding key in `.env`.
```bash
# Pull SWE-bench Docker images (pulled on-demand, but you can pre-pull)
docker pull swebench/sweb.eval.x86_64.django_1776_django-16527:latest
```
## Quick Start
### Debug a single instance
```bash
# Fastest way to verify everything works
python run_eval.py debug -m claude-haiku -i django__django-16527 --subset lite
```
### Run a single configuration
```bash
# 5 instances, Claude Sonnet, native_augment mode (default)
python run_eval.py single -m claude-sonnet --subset lite --slice 0:5
# Baseline comparison (no GitNexus)
python run_eval.py single -m claude-sonnet --mode baseline --subset lite --slice 0:5
# Full Lite benchmark, 4 parallel workers
python run_eval.py single -m claude-sonnet --subset lite -w 4
```
### Run the full matrix
```bash
# All models x all modes
python run_eval.py matrix --subset lite -w 4
# Key comparison: baseline vs native_augment
python run_eval.py matrix -m claude-sonnet -m claude-haiku --modes baseline --modes native_augment --subset lite --slice 0:50
```
### Analyze results
```bash
# Summary table
python -m analysis.analyze_results results/
# Compare modes for a specific model
python -m analysis.analyze_results compare-modes results/ -m claude-sonnet
# GitNexus tool usage analysis
python -m analysis.analyze_results gitnexus-usage results/
# Export as CSV for further analysis
python -m analysis.analyze_results summary results/ --format csv > results.csv
# Run official SWE-bench test evaluation
python -m analysis.analyze_results summary results/ --swebench-eval
```
### List available configurations
```bash
python run_eval.py list-configs
```
## Architecture
```
eval/
run_eval.py # Main entry point (single, matrix, debug commands)
agents/
gitnexus_agent.py # GitNexusAgent: extends DefaultAgent with augmentation + metrics
environments/
gitnexus_docker.py # Docker env with GitNexus + eval-server + standalone tool scripts
bridge/
gitnexus_tools.sh # Bash wrappers (legacy — now standalone scripts are installed directly)
mcp_bridge.py # Legacy MCP bridge (kept for reference)
prompts/
system_baseline.jinja # System: persona + format rules
instance_baseline.jinja # Instance: task + workflow
system_native.jinja # System: + GitNexus tool reference
instance_native.jinja # Instance: + GitNexus debugging workflow
system_native_augment.jinja # System: + GitNexus tools + grep enrichment docs
instance_native_augment.jinja # Instance: + GitNexus workflow + risk assessment
configs/
models/ # Per-model YAML configs
modes/ # Per-mode YAML configs (baseline, native, native_augment)
analysis/
analyze_results.py # Post-run comparative analysis
results/ # Output directory (gitignored)
```
## How It Works
### Template structure
mini-swe-agent requires two Jinja templates:
- **system_template** → system message: persona, format rules, tool reference (static)
- **instance_template** → first user message: task, workflow, rules, examples (contains `{{task}}`)
Each mode has a `system_{mode}.jinja` + `instance_{mode}.jinja` pair. The agent loads both automatically based on the configured mode.
### Per-instance flow
1. Docker container starts with SWE-bench instance (repo at specific commit)
2. **GitNexus setup**: Node.js + gitnexus installed, `gitnexus analyze` runs (or restores from cache)
3. **Eval-server starts**: `gitnexus eval-server` daemon (persistent HTTP server, keeps LadybugDB warm)
4. **Standalone tool scripts installed** in `/usr/local/bin/` — works with `subprocess.run` (no `.bashrc` needed)
5. Agent runs with the configured model + system prompt + GitNexus tools
6. Agent's patch is extracted as a git diff
7. Metrics collected: cost, tokens, tool calls, GitNexus usage, augmentation stats
### Tool architecture
```
Agent → bash command → /usr/local/bin/gitnexus-query
→ curl localhost:4848/tool/query (fast path: eval-server, ~100ms)
→ npx gitnexus query (fallback: cold CLI, ~5-10s)
```
Each tool script in `/usr/local/bin/` is standalone — no sourcing, no env inheritance needed. This is critical because mini-swe-agent runs every command via `subprocess.run` in a fresh subshell.
### Eval-server
The eval-server is a lightweight HTTP daemon that:
- Keeps LadybugDB warm in memory (no cold start per tool call)
- Returns LLM-friendly text (not raw JSON — saves tokens)
- Includes next-step hints to guide tool chaining (query → context → impact → fix)
- Auto-shuts down after idle timeout
### Index caching
SWE-bench repos repeat (Django has 200+ instances at different commits). The harness caches GitNexus indexes per `(repo, commit)` hash in `~/.gitnexus-eval-cache/` to avoid redundant re-indexing.
### Grep augmentation (native_augment mode)
When the agent runs `grep` or `rg`, the observation is post-processed: the agent class calls `gitnexus-augment` on the search pattern and appends `[GitNexus]` annotations showing callers, callees, and execution flows for matched symbols. This mirrors the Claude Code / Cursor hook integration.
## Adding Models
Create a YAML file in `configs/models/`:
```yaml
# configs/models/my-model.yaml
model:
model_name: "openrouter/provider/model-name"
cost_tracking: "ignore_errors" # if not in litellm's cost DB
model_kwargs:
max_tokens: 8192
temperature: 0
```
The model name follows [litellm conventions](https://docs.litellm.ai/docs/providers).
## Metrics Collected
| Metric | Description |
|--------|-------------|
| Patch Rate | % of instances where agent produced a patch |
| Resolve Rate | % of instances where patch passes tests (requires --swebench-eval) |
| Total Cost | API cost across all instances |
| Avg Cost/Instance | Cost efficiency |
| API Calls | Number of LLM calls |
| GN Tool Calls | How many GitNexus tools the agent used |
| Augment Hits | How many grep/find results got enriched |
| Augment Hit Rate | % of search commands that got useful enrichment |
+1
View File
@@ -0,0 +1 @@
# GitNexus SWE-bench Evaluation Harness
View File
+209
View File
@@ -0,0 +1,209 @@
"""
GitNexus-Enhanced Agent for SWE-bench Evaluation
Extends mini-swe-agent's DefaultAgent with:
1. Native augment mode: GitNexus tools via eval-server + grep enrichment (recommended)
2. Native mode: GitNexus tools via eval-server only
3. Baseline mode: Pure mini-swe-agent (no GitNexus — control group)
The agent class itself is minimal — the heavy lifting is in:
- Prompt selection (system + instance templates per mode)
- Observation post-processing (grep result augmentation)
- Metrics tracking (which tools the agent actually uses)
Template structure (matches mini-swe-agent's expectations):
system_template → system message: persona + format rules + tool reference
instance_template → first user message: task + workflow + rules + examples
"""
import logging
import re
import time
from enum import Enum
from pathlib import Path
from minisweagent import Environment, Model
from minisweagent.agents.default import AgentConfig, DefaultAgent
logger = logging.getLogger("gitnexus_agent")
PROMPTS_DIR = Path(__file__).parent.parent / "prompts"
class GitNexusMode(str, Enum):
"""Evaluation modes for GitNexus integration."""
BASELINE = "baseline" # No GitNexus — pure mini-swe-agent
NATIVE = "native" # GitNexus tools via eval-server
NATIVE_AUGMENT = "native_augment" # Native tools + grep enrichment (recommended)
class GitNexusAgentConfig(AgentConfig):
"""Extended config for GitNexus evaluation agent."""
gitnexus_mode: GitNexusMode = GitNexusMode.BASELINE
augment_timeout: float = 5.0
augment_min_pattern_length: int = 3
track_gitnexus_usage: bool = True
class GitNexusAgent(DefaultAgent):
"""
Agent that optionally enriches its capabilities with GitNexus code intelligence.
In BASELINE mode, behaves identically to DefaultAgent.
In NATIVE mode, GitNexus tools are available as bash commands via eval-server.
In NATIVE_AUGMENT mode, GitNexus tools + automatic grep result enrichment.
"""
def __init__(self, model: Model, env: Environment, *, config_class: type = GitNexusAgentConfig, **kwargs):
mode = kwargs.get("gitnexus_mode", GitNexusMode.BASELINE)
if isinstance(mode, str):
mode = GitNexusMode(mode)
# Load system template
system_file = PROMPTS_DIR / f"system_{mode.value}.jinja"
if system_file.exists() and "system_template" not in kwargs:
kwargs["system_template"] = system_file.read_text()
# Load instance template
instance_file = PROMPTS_DIR / f"instance_{mode.value}.jinja"
if instance_file.exists() and "instance_template" not in kwargs:
kwargs["instance_template"] = instance_file.read_text()
super().__init__(model, env, config_class=config_class, **kwargs)
self.gitnexus_mode = mode
self.gitnexus_metrics = GitNexusMetrics()
def execute_actions(self, message: dict) -> list[dict]:
"""Execute actions with optional GitNexus augmentation and tracking."""
if self.config.track_gitnexus_usage:
self._track_tool_usage(message)
outputs = [self.env.execute(action) for action in message.get("extra", {}).get("actions", [])]
# Augment grep/find observations in NATIVE_AUGMENT mode
if self.gitnexus_mode == GitNexusMode.NATIVE_AUGMENT:
actions = message.get("extra", {}).get("actions", [])
for i, (action, output) in enumerate(zip(actions, outputs)):
augmented = self._maybe_augment(action, output)
if augmented:
outputs[i] = augmented
return self.add_messages(
*self.model.format_observation_messages(message, outputs, self.get_template_vars())
)
def _maybe_augment(self, action: dict, output: dict) -> dict | None:
"""
If the action is a search command (grep, find, rg, ag), augment the output
with GitNexus knowledge graph context.
"""
command = action.get("command", "")
if not command:
return None
pattern = self._extract_search_pattern(command)
if not pattern or len(pattern) < self.config.augment_min_pattern_length:
return None
start = time.time()
try:
augment_result = self.env.execute({
"command": f'gitnexus-augment "{pattern}" 2>&1 || true',
"timeout": self.config.augment_timeout,
})
elapsed = time.time() - start
self.gitnexus_metrics.augmentation_calls += 1
self.gitnexus_metrics.augmentation_time += elapsed
augment_text = augment_result.get("output", "").strip()
if augment_text and "[GitNexus]" in augment_text:
original_output = output.get("output", "")
output = dict(output)
output["output"] = f"{original_output}\n\n{augment_text}"
self.gitnexus_metrics.augmentation_hits += 1
return output
except Exception as e:
logger.debug(f"Augmentation failed for pattern '{pattern}': {e}")
self.gitnexus_metrics.augmentation_errors += 1
return None
@staticmethod
def _extract_search_pattern(command: str) -> str | None:
"""Extract the search pattern from a grep/find/rg command."""
patterns = [
r'(?:grep|rg|ag)\s+(?:-[a-zA-Z]*\s+)*["\']([^"\']+)["\']',
r'(?:grep|rg|ag)\s+(?:-[a-zA-Z]*\s+)*(\S+)',
]
for pat in patterns:
match = re.search(pat, command)
if match:
result = match.group(1)
if result.startswith("/") or result.startswith("."):
continue
if result.startswith("-"):
continue
return result
return None
def _track_tool_usage(self, message: dict):
"""Track which GitNexus tools the agent uses."""
for action in message.get("extra", {}).get("actions", []):
command = action.get("command", "")
if "gitnexus-query" in command:
self.gitnexus_metrics.tool_calls["query"] += 1
elif "gitnexus-context" in command:
self.gitnexus_metrics.tool_calls["context"] += 1
elif "gitnexus-impact" in command:
self.gitnexus_metrics.tool_calls["impact"] += 1
elif "gitnexus-cypher" in command:
self.gitnexus_metrics.tool_calls["cypher"] += 1
elif "gitnexus-overview" in command:
self.gitnexus_metrics.tool_calls["overview"] += 1
def serialize(self, *extra_dicts) -> dict:
"""Serialize with GitNexus-specific metrics."""
gitnexus_data = {
"info": {
"gitnexus": {
"mode": self.gitnexus_mode.value,
"metrics": self.gitnexus_metrics.to_dict(),
},
},
}
return super().serialize(gitnexus_data, *extra_dicts)
class GitNexusMetrics:
"""Tracks GitNexus-specific metrics during evaluation."""
def __init__(self):
self.tool_calls: dict[str, int] = {
"query": 0,
"context": 0,
"impact": 0,
"cypher": 0,
"overview": 0,
}
self.augmentation_calls: int = 0
self.augmentation_hits: int = 0
self.augmentation_errors: int = 0
self.augmentation_time: float = 0.0
self.index_time: float = 0.0
@property
def total_tool_calls(self) -> int:
return sum(self.tool_calls.values())
def to_dict(self) -> dict:
return {
"tool_calls": dict(self.tool_calls),
"total_tool_calls": self.total_tool_calls,
"augmentation_calls": self.augmentation_calls,
"augmentation_hits": self.augmentation_hits,
"augmentation_errors": self.augmentation_errors,
"augmentation_time_seconds": round(self.augmentation_time, 2),
"index_time_seconds": round(self.index_time, 2),
}
View File
+446
View File
@@ -0,0 +1,446 @@
#!/usr/bin/env python3
"""
Results Analyzer for GitNexus SWE-bench Evaluation
Reads evaluation results and generates comparative analysis:
- Resolve rate by model x mode
- Cost comparison (total, per-instance)
- Token/API call efficiency
- GitNexus tool usage patterns
- Augmentation hit rates
Usage:
python -m analysis.analyze_results /path/to/results
python -m analysis.analyze_results /path/to/results --format markdown
python -m analysis.analyze_results /path/to/results --swebench-eval # run actual test verification
"""
import json
import logging
import os
import subprocess
import sys
from pathlib import Path
from typing import Any
import typer
from rich.console import Console
from rich.table import Table
logger = logging.getLogger("analyze_results")
console = Console()
app = typer.Typer(rich_markup_mode="rich", add_completion=False)
def load_run_results(results_dir: Path) -> dict[str, dict]:
"""
Load all run results from the results directory.
Returns: {run_id: {summary, preds, instances}}
"""
runs = {}
for run_dir in sorted(results_dir.iterdir()):
if not run_dir.is_dir():
continue
run_id = run_dir.name
run_data: dict[str, Any] = {"run_id": run_id, "dir": run_dir}
# Load summary
summary_path = run_dir / "summary.json"
if summary_path.exists():
run_data["summary"] = json.loads(summary_path.read_text())
# Load predictions
preds_path = run_dir / "preds.json"
if preds_path.exists():
run_data["preds"] = json.loads(preds_path.read_text())
# Load individual trajectories for detailed metrics
run_data["trajectories"] = {}
for traj_dir in run_dir.iterdir():
if not traj_dir.is_dir():
continue
for traj_file in traj_dir.glob("*.traj.json"):
try:
traj = json.loads(traj_file.read_text())
instance_id = traj.get("instance_id", traj_dir.name)
run_data["trajectories"][instance_id] = traj
except Exception:
pass
if run_data.get("preds") or run_data.get("summary"):
runs[run_id] = run_data
return runs
def parse_run_id(run_id: str) -> tuple[str, str]:
"""Parse 'model_mode' into (model, mode)."""
# Handle multi-word model names like 'minimax-2.5'
# Modes are: baseline, mcp, augment, full
known_modes = {"baseline", "mcp", "augment", "full"}
parts = run_id.rsplit("_", 1)
if len(parts) == 2 and parts[1] in known_modes:
return parts[0], parts[1]
return run_id, "unknown"
def compute_metrics(run_data: dict) -> dict:
"""Compute evaluation metrics for a single run."""
preds = run_data.get("preds", {})
summary = run_data.get("summary", {})
trajectories = run_data.get("trajectories", {})
n_instances = len(preds)
n_with_patch = sum(1 for p in preds.values() if p.get("model_patch", "").strip())
# Cost and API call metrics from trajectories
costs = []
api_calls = []
gn_tool_calls = []
gn_augment_hits = []
gn_augment_calls = []
for instance_id, traj in trajectories.items():
info = traj.get("info", {})
model_stats = info.get("model_stats", {})
costs.append(model_stats.get("instance_cost", 0))
api_calls.append(model_stats.get("api_calls", 0))
gn = info.get("gitnexus", {}).get("metrics", {})
if gn:
gn_tool_calls.append(gn.get("total_tool_calls", 0))
gn_augment_hits.append(gn.get("augmentation_hits", 0))
gn_augment_calls.append(gn.get("augmentation_calls", 0))
# Also try summary-level metrics
if not costs and summary:
results = summary.get("results", [])
for r in results:
costs.append(r.get("cost", 0))
api_calls.append(r.get("n_calls", 0))
gn = r.get("gitnexus_metrics", {})
if gn:
gn_tool_calls.append(gn.get("total_tool_calls", 0))
gn_augment_hits.append(gn.get("augmentation_hits", 0))
gn_augment_calls.append(gn.get("augmentation_calls", 0))
total_cost = sum(costs)
total_calls = sum(api_calls)
return {
"n_instances": n_instances,
"n_with_patch": n_with_patch,
"patch_rate": n_with_patch / max(n_instances, 1),
"total_cost": total_cost,
"avg_cost": total_cost / max(n_instances, 1),
"total_api_calls": total_calls,
"avg_api_calls": total_calls / max(n_instances, 1),
"total_gn_tool_calls": sum(gn_tool_calls),
"avg_gn_tool_calls": sum(gn_tool_calls) / max(len(gn_tool_calls), 1) if gn_tool_calls else 0,
"total_augment_hits": sum(gn_augment_hits),
"total_augment_calls": sum(gn_augment_calls),
"augment_hit_rate": sum(gn_augment_hits) / max(sum(gn_augment_calls), 1) if gn_augment_calls else 0,
}
def run_swebench_evaluation(results_dir: Path, run_id: str, subset: str = "lite") -> dict | None:
"""
Run the official SWE-bench evaluation on predictions.
Requires: pip install swebench
"""
preds_path = results_dir / run_id / "preds.json"
if not preds_path.exists():
return None
dataset_mapping = {
"lite": "princeton-nlp/SWE-Bench_Lite",
"verified": "princeton-nlp/SWE-Bench_Verified",
"full": "princeton-nlp/SWE-Bench",
}
try:
eval_output = results_dir / run_id / "swebench_eval"
cmd = [
sys.executable, "-m", "swebench.harness.run_evaluation",
"--dataset_name", dataset_mapping.get(subset, subset),
"--predictions_path", str(preds_path),
"--max_workers", "4",
"--run_id", run_id,
"--output_dir", str(eval_output),
]
logger.info(f"Running SWE-bench evaluation for {run_id}...")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
if result.returncode == 0:
# Parse evaluation results
report_path = eval_output / run_id / "results.json"
if report_path.exists():
return json.loads(report_path.read_text())
logger.error(f"SWE-bench eval failed: {result.stderr[:500]}")
return None
except Exception as e:
logger.error(f"SWE-bench eval error: {e}")
return None
# ─── CLI Commands ───────────────────────────────────────────────────────────
@app.command()
def summary(
results_dir: str = typer.Argument(..., help="Path to results directory"),
format: str = typer.Option("table", "--format", help="Output format: table, markdown, json, csv"),
swebench_eval: bool = typer.Option(False, "--swebench-eval", help="Run official SWE-bench test evaluation"),
subset: str = typer.Option("lite", "--subset", help="SWE-bench subset (for --swebench-eval)"),
):
"""Generate comparative analysis of evaluation results."""
results_path = Path(results_dir)
if not results_path.exists():
console.print(f"[red]Results directory not found: {results_path}[/red]")
raise typer.Exit(1)
runs = load_run_results(results_path)
if not runs:
console.print("[yellow]No evaluation results found[/yellow]")
raise typer.Exit(0)
console.print(f"\n[bold]Found {len(runs)} evaluation runs[/bold]\n")
# Compute metrics per run
all_metrics = {}
for run_id, run_data in runs.items():
model, mode = parse_run_id(run_id)
metrics = compute_metrics(run_data)
metrics["model"] = model
metrics["mode"] = mode
# Optionally run SWE-bench evaluation
if swebench_eval:
eval_result = run_swebench_evaluation(results_path, run_id, subset)
if eval_result:
metrics["resolved"] = eval_result.get("resolved", 0)
metrics["resolve_rate"] = eval_result.get("resolved", 0) / max(metrics["n_instances"], 1)
all_metrics[run_id] = metrics
if format == "table":
_print_table(all_metrics)
elif format == "markdown":
_print_markdown(all_metrics)
elif format == "json":
console.print(json.dumps(all_metrics, indent=2))
elif format == "csv":
_print_csv(all_metrics)
@app.command()
def compare_modes(
results_dir: str = typer.Argument(..., help="Path to results directory"),
model: str = typer.Option(..., "-m", "--model", help="Model to compare across modes"),
):
"""Compare modes for a specific model (baseline vs mcp vs augment vs full)."""
results_path = Path(results_dir)
runs = load_run_results(results_path)
# Filter to the specified model
model_runs = {
run_id: data for run_id, data in runs.items()
if parse_run_id(run_id)[0] == model
}
if not model_runs:
console.print(f"[yellow]No results found for model: {model}[/yellow]")
raise typer.Exit(1)
console.print(f"\n[bold]Mode comparison for {model}[/bold]\n")
metrics = {}
for run_id, run_data in model_runs.items():
_, mode = parse_run_id(run_id)
metrics[mode] = compute_metrics(run_data)
# Print comparison table
table = Table(title=f"Mode Comparison: {model}")
table.add_column("Metric", style="bold")
for mode in ["baseline", "mcp", "augment", "full"]:
if mode in metrics:
table.add_column(mode, justify="right")
rows = [
("Instances", "n_instances", "d"),
("With Patch", "n_with_patch", "d"),
("Patch Rate", "patch_rate", ".1%"),
("Total Cost", "total_cost", "$.4f"),
("Avg Cost", "avg_cost", "$.4f"),
("Total API Calls", "total_api_calls", "d"),
("Avg API Calls", "avg_api_calls", ".1f"),
("GN Tool Calls", "total_gn_tool_calls", "d"),
("Augment Hits", "total_augment_hits", "d"),
("Augment Hit Rate", "augment_hit_rate", ".1%"),
]
for label, key, fmt in rows:
values = []
for mode in ["baseline", "mcp", "augment", "full"]:
if mode in metrics:
v = metrics[mode].get(key, 0)
if fmt == ".1%":
values.append(f"{v:.1%}")
elif fmt == "$.4f":
values.append(f"${v:.4f}")
elif fmt == ".1f":
values.append(f"{v:.1f}")
else:
values.append(str(v))
table.add_row(label, *values)
# Add delta rows (improvement over baseline)
if "baseline" in metrics:
baseline_cost = metrics["baseline"]["avg_cost"]
baseline_calls = metrics["baseline"]["avg_api_calls"]
table.add_section()
for mode in ["mcp", "augment", "full"]:
if mode not in metrics:
continue
mode_cost = metrics[mode]["avg_cost"]
mode_calls = metrics[mode]["avg_api_calls"]
cost_delta = ((mode_cost - baseline_cost) / max(baseline_cost, 0.001)) * 100
calls_delta = ((mode_calls - baseline_calls) / max(baseline_calls, 1)) * 100
cost_str = f"{cost_delta:+.1f}%"
calls_str = f"{calls_delta:+.1f}%"
# Color-code: negative is good (cheaper/fewer calls)
cost_color = "green" if cost_delta < 0 else "red"
calls_color = "green" if calls_delta < 0 else "red"
console.print(f" {mode} vs baseline: cost [{cost_color}]{cost_str}[/{cost_color}], calls [{calls_color}]{calls_str}[/{calls_color}]")
console.print(table)
@app.command()
def gitnexus_usage(
results_dir: str = typer.Argument(..., help="Path to results directory"),
):
"""Analyze GitNexus tool usage patterns across all runs."""
results_path = Path(results_dir)
runs = load_run_results(results_path)
console.print("\n[bold]GitNexus Tool Usage Analysis[/bold]\n")
table = Table(title="Tool Usage by Run")
table.add_column("Run", style="bold")
table.add_column("query", justify="right")
table.add_column("context", justify="right")
table.add_column("impact", justify="right")
table.add_column("cypher", justify="right")
table.add_column("Total", justify="right")
table.add_column("Augment Hits", justify="right")
for run_id, run_data in sorted(runs.items()):
_, mode = parse_run_id(run_id)
if mode == "baseline":
continue
# Aggregate tool calls across trajectories
tool_totals: dict[str, int] = {"query": 0, "context": 0, "impact": 0, "cypher": 0, "overview": 0}
augment_hits = 0
for traj in run_data.get("trajectories", {}).values():
gn = traj.get("info", {}).get("gitnexus", {}).get("metrics", {})
for tool, count in gn.get("tool_calls", {}).items():
tool_totals[tool] = tool_totals.get(tool, 0) + count
augment_hits += gn.get("augmentation_hits", 0)
# Also check summary
for r in run_data.get("summary", {}).get("results", []):
gn = r.get("gitnexus_metrics", {})
for tool, count in gn.get("tool_calls", {}).items():
tool_totals[tool] = tool_totals.get(tool, 0) + count
augment_hits += gn.get("augmentation_hits", 0)
total = sum(tool_totals.values())
if total > 0 or augment_hits > 0:
table.add_row(
run_id,
str(tool_totals.get("query", 0)),
str(tool_totals.get("context", 0)),
str(tool_totals.get("impact", 0)),
str(tool_totals.get("cypher", 0)),
str(total),
str(augment_hits),
)
console.print(table)
# ─── Output Formatters ─────────────────────────────────────────────────────
def _print_table(all_metrics: dict):
"""Print rich table summary."""
table = Table(title="Evaluation Results")
table.add_column("Run", style="bold")
table.add_column("Model")
table.add_column("Mode")
table.add_column("N", justify="right")
table.add_column("Patched", justify="right")
table.add_column("Rate", justify="right")
table.add_column("Cost", justify="right")
table.add_column("Calls", justify="right")
table.add_column("GN Tools", justify="right")
for run_id, m in sorted(all_metrics.items()):
resolved_str = ""
if "resolve_rate" in m:
resolved_str = f" ({m['resolve_rate']:.0%})"
table.add_row(
run_id,
m["model"],
m["mode"],
str(m["n_instances"]),
str(m["n_with_patch"]),
f"{m['patch_rate']:.0%}{resolved_str}",
f"${m['total_cost']:.2f}",
str(m["total_api_calls"]),
str(m["total_gn_tool_calls"]) if m["total_gn_tool_calls"] > 0 else "-",
)
console.print(table)
def _print_markdown(all_metrics: dict):
"""Print markdown table."""
print("| Run | Model | Mode | N | Patched | Rate | Cost | Calls | GN Tools |")
print("|-----|-------|------|---|---------|------|------|-------|----------|")
for run_id, m in sorted(all_metrics.items()):
gn = str(m["total_gn_tool_calls"]) if m["total_gn_tool_calls"] > 0 else "-"
print(f"| {run_id} | {m['model']} | {m['mode']} | {m['n_instances']} | {m['n_with_patch']} | {m['patch_rate']:.0%} | ${m['total_cost']:.2f} | {m['total_api_calls']} | {gn} |")
def _print_csv(all_metrics: dict):
"""Print CSV output."""
print("run_id,model,mode,n_instances,n_with_patch,patch_rate,total_cost,avg_cost,total_api_calls,avg_api_calls,total_gn_tool_calls,total_augment_hits,augment_hit_rate")
for run_id, m in sorted(all_metrics.items()):
print(
f"{run_id},{m['model']},{m['mode']},{m['n_instances']},{m['n_with_patch']},"
f"{m['patch_rate']:.4f},{m['total_cost']:.4f},{m['avg_cost']:.4f},"
f"{m['total_api_calls']},{m['avg_api_calls']:.1f},{m['total_gn_tool_calls']},"
f"{m['total_augment_hits']},{m['augment_hit_rate']:.4f}"
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
app()
View File
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
# GitNexus CLI tool wrappers for SWE-bench evaluation
#
# These functions call the GitNexus eval-server (HTTP daemon) for near-instant
# tool responses. The eval-server keeps KuzuDB warm in memory.
#
# If the eval-server is not running, falls back to direct CLI commands.
#
# Usage:
# gitnexus-query "how does authentication work"
# gitnexus-context "validateUser"
# gitnexus-impact "AuthService" upstream
# gitnexus-cypher "MATCH (n:Function) RETURN n.name LIMIT 10"
# gitnexus-overview
GITNEXUS_EVAL_PORT="${GITNEXUS_EVAL_PORT:-4848}"
GITNEXUS_EVAL_URL="http://127.0.0.1:${GITNEXUS_EVAL_PORT}"
_gitnexus_call() {
local tool="$1"
shift
local json_body="$1"
# Try eval-server first (fastest path — KuzuDB stays warm)
local result
result=$(curl -sf -X POST "${GITNEXUS_EVAL_URL}/tool/${tool}" \
-H "Content-Type: application/json" \
-d "${json_body}" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then
echo "$result"
return 0
fi
# Fallback: direct CLI (cold start, slower but always works)
case "$tool" in
query)
local q=$(echo "$json_body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('query',''))" 2>/dev/null)
npx gitnexus query "$q" 2>&1
;;
context)
local n=$(echo "$json_body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('name',''))" 2>/dev/null)
npx gitnexus context "$n" 2>&1
;;
impact)
local t=$(echo "$json_body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('target',''))" 2>/dev/null)
local d=$(echo "$json_body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('direction','upstream'))" 2>/dev/null)
npx gitnexus impact "$t" --direction "$d" 2>&1
;;
cypher)
local cq=$(echo "$json_body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('query',''))" 2>/dev/null)
npx gitnexus cypher "$cq" 2>&1
;;
*)
echo "Unknown tool: $tool" >&2
return 1
;;
esac
}
gitnexus-query() {
local query="$1"
local task_context="${2:-}"
local goal="${3:-}"
if [ -z "$query" ]; then
echo "Usage: gitnexus-query <query> [task_context] [goal]"
echo "Search the code knowledge graph for execution flows related to a concept."
echo ""
echo "Examples:"
echo ' gitnexus-query "authentication flow"'
echo ' gitnexus-query "database connection" "fixing connection pool leak"'
return 1
fi
local args="{\"query\": \"$query\""
[ -n "$task_context" ] && args="$args, \"task_context\": \"$task_context\""
[ -n "$goal" ] && args="$args, \"goal\": \"$goal\""
args="$args}"
_gitnexus_call query "$args"
}
gitnexus-context() {
local name="$1"
local file_path="${2:-}"
if [ -z "$name" ]; then
echo "Usage: gitnexus-context <symbol_name> [file_path]"
echo "Get a 360-degree view of a code symbol: callers, callees, processes, file location."
echo ""
echo "Examples:"
echo ' gitnexus-context "validateUser"'
echo ' gitnexus-context "AuthService" "src/auth/service.py"'
return 1
fi
local args="{\"name\": \"$name\""
[ -n "$file_path" ] && args="$args, \"file_path\": \"$file_path\""
args="$args}"
_gitnexus_call context "$args"
}
gitnexus-impact() {
local target="$1"
local direction="${2:-upstream}"
if [ -z "$target" ]; then
echo "Usage: gitnexus-impact <symbol_name> [upstream|downstream]"
echo "Analyze the blast radius of changing a code symbol."
echo ""
echo " upstream = what depends on this (what breaks if you change it)"
echo " downstream = what this depends on (what it uses)"
echo ""
echo "Examples:"
echo ' gitnexus-impact "AuthService" upstream'
echo ' gitnexus-impact "validateUser" downstream'
return 1
fi
_gitnexus_call impact "{\"target\": \"$target\", \"direction\": \"$direction\"}"
}
gitnexus-cypher() {
local query="$1"
if [ -z "$query" ]; then
echo "Usage: gitnexus-cypher <cypher_query>"
echo "Execute a raw Cypher query against the code knowledge graph."
echo ""
echo "Schema: Nodes: File, Function, Class, Method, Interface, Community, Process"
echo "Edges via CodeRelation.type: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS"
echo ""
echo "Examples:"
echo " gitnexus-cypher 'MATCH (a)-[:CodeRelation {type: \"CALLS\"}]->(b:Function {name: \"save\"}) RETURN a.name, a.filePath'"
echo " gitnexus-cypher 'MATCH (n:Class) RETURN n.name, n.filePath LIMIT 20'"
return 1
fi
_gitnexus_call cypher "{\"query\": \"$query\"}"
}
gitnexus-overview() {
echo "=== Code Knowledge Graph Overview ==="
_gitnexus_call list_repos '{}'
}
# Export functions so they're available in subshells
export -f _gitnexus_call 2>/dev/null
export -f gitnexus-query 2>/dev/null
export -f gitnexus-context 2>/dev/null
export -f gitnexus-impact 2>/dev/null
export -f gitnexus-cypher 2>/dev/null
export -f gitnexus-overview 2>/dev/null
+336
View File
@@ -0,0 +1,336 @@
"""
MCP Bridge for GitNexus
Starts the GitNexus MCP server as a subprocess and provides a Python interface
to call MCP tools. Used by the bash wrapper scripts and the augmentation layer..
The bridge communicates with the MCP server via stdio using the JSON-RPC protocol.
"""
import json
import logging
import os
import subprocess
import sys
import threading
import time
from pathlib import Path
from typing import Any
logger = logging.getLogger("mcp_bridge")
class MCPBridge:
"""
Manages a GitNexus MCP server subprocess and proxies tool calls to it.
Usage:
bridge = MCPBridge(repo_path="/path/to/repo")
bridge.start()
result = bridge.call_tool("query", {"query": "authentication"})
bridge.stop()
"""
def __init__(self, repo_path: str | None = None):
self.repo_path = repo_path or os.getcwd()
self.process: subprocess.Popen | None = None
self._request_id = 0
self._lock = threading.Lock()
self._started = False
def start(self) -> bool:
"""Start the GitNexus MCP server subprocess."""
if self._started:
return True
try:
# Find gitnexus binary
gitnexus_bin = self._find_gitnexus()
if not gitnexus_bin:
logger.error("GitNexus not found. Install with: npm install -g gitnexus")
return False
self.process = subprocess.Popen(
[gitnexus_bin, "mcp"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.repo_path,
text=False,
)
# Send initialize request
init_result = self._send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "gitnexus-eval", "version": "0.1.0"},
})
if init_result is None:
logger.error("MCP server failed to initialize")
self.stop()
return False
# Send initialized notification
self._send_notification("notifications/initialized", {})
self._started = True
logger.info("MCP bridge started successfully")
return True
except Exception as e:
logger.error(f"Failed to start MCP bridge: {e}")
self.stop()
return False
def stop(self):
"""Stop the MCP server subprocess."""
if self.process:
try:
self.process.stdin.close()
self.process.terminate()
self.process.wait(timeout=5)
except Exception:
try:
self.process.kill()
except Exception:
pass
self.process = None
self._started = False
def call_tool(self, tool_name: str, arguments: dict[str, Any] | None = None) -> dict[str, Any] | None:
"""
Call a GitNexus MCP tool and return the result.
Returns the tool result content or None on error.
"""
if not self._started:
logger.error("MCP bridge not started")
return None
result = self._send_request("tools/call", {
"name": tool_name,
"arguments": arguments or {},
})
if result is None:
return None
# Extract text content from MCP response
content = result.get("content", [])
if content and isinstance(content, list):
texts = [item.get("text", "") for item in content if item.get("type") == "text"]
return {"text": "\n".join(texts), "raw": content}
return {"text": "", "raw": content}
def list_tools(self) -> list[dict]:
"""List available MCP tools."""
result = self._send_request("tools/list", {})
if result:
return result.get("tools", [])
return []
def read_resource(self, uri: str) -> str | None:
"""Read an MCP resource by URI."""
result = self._send_request("resources/read", {"uri": uri})
if result:
contents = result.get("contents", [])
if contents:
return contents[0].get("text", "")
return None
def _find_gitnexus(self) -> str | None:
"""Find the gitnexus CLI binary."""
# Check if npx is available (preferred - uses local install)
for cmd in ["npx"]:
try:
result = subprocess.run(
[cmd, "gitnexus", "--version"],
capture_output=True, text=True, timeout=15,
cwd=self.repo_path,
)
if result.returncode == 0:
return cmd # Will use "npx gitnexus mcp"
except Exception:
continue
# Check for global install
try:
result = subprocess.run(
["gitnexus", "--version"],
capture_output=True, text=True, timeout=10,
)
if result.returncode == 0:
return "gitnexus"
except Exception:
pass
return None
def _next_id(self) -> int:
with self._lock:
self._request_id += 1
return self._request_id
def _send_request(self, method: str, params: dict) -> dict | None:
"""Send a JSON-RPC request and wait for response."""
if not self.process or not self.process.stdin or not self.process.stdout:
return None
request_id = self._next_id()
request = {
"jsonrpc": "2.0",
"id": request_id,
"method": method,
"params": params,
}
try:
message = json.dumps(request)
# MCP uses Content-Length header framing
header = f"Content-Length: {len(message.encode('utf-8'))}\r\n\r\n"
self.process.stdin.write(header.encode("utf-8"))
self.process.stdin.write(message.encode("utf-8"))
self.process.stdin.flush()
# Read response
response = self._read_response(timeout=30)
if response and response.get("id") == request_id:
if "error" in response:
logger.error(f"MCP error: {response['error']}")
return None
return response.get("result")
return None
except Exception as e:
logger.error(f"MCP request failed: {e}")
return None
def _send_notification(self, method: str, params: dict):
"""Send a JSON-RPC notification (no response expected)."""
if not self.process or not self.process.stdin:
return
notification = {
"jsonrpc": "2.0",
"method": method,
"params": params,
}
try:
message = json.dumps(notification)
header = f"Content-Length: {len(message.encode('utf-8'))}\r\n\r\n"
self.process.stdin.write(header.encode("utf-8"))
self.process.stdin.write(message.encode("utf-8"))
self.process.stdin.flush()
except Exception as e:
logger.error(f"MCP notification failed: {e}")
def _read_response(self, timeout: float = 30) -> dict | None:
"""Read a JSON-RPC response from the MCP server."""
if not self.process or not self.process.stdout:
return None
start = time.time()
try:
while time.time() - start < timeout:
# Read Content-Length header
header_line = b""
while True:
byte = self.process.stdout.read(1)
if not byte:
return None
header_line += byte
if header_line.endswith(b"\r\n\r\n"):
break
if header_line.endswith(b"\n\n"):
break
# Parse content length
header_str = header_line.decode("utf-8").strip()
content_length = None
for line in header_str.split("\r\n"):
if line.lower().startswith("content-length:"):
content_length = int(line.split(":")[1].strip())
break
if content_length is None:
continue
# Read body
body = self.process.stdout.read(content_length)
if not body:
return None
message = json.loads(body.decode("utf-8"))
# Skip notifications (no id), return responses
if "id" in message:
return message
return None
except Exception as e:
logger.error(f"Error reading MCP response: {e}")
return None
class MCPToolCLI:
"""
CLI wrapper that exposes MCP tools as simple command-line calls.
Used by the bash wrapper scripts inside Docker containers.
Usage from bash:
python -m bridge.mcp_bridge query '{"query": "authentication"}'
python -m bridge.mcp_bridge context '{"name": "validateUser"}'
"""
def __init__(self):
self.bridge = MCPBridge()
def run(self, tool_name: str, args_json: str = "{}") -> int:
"""Run a single tool call and print the result."""
try:
args = json.loads(args_json)
except json.JSONDecodeError:
# Try to parse as simple key=value pairs
args = self._parse_simple_args(args_json)
if not self.bridge.start():
print("ERROR: Failed to start GitNexus MCP bridge", file=sys.stderr)
return 1
try:
result = self.bridge.call_tool(tool_name, args)
if result:
print(result.get("text", ""))
return 0
else:
print("No results", file=sys.stderr)
return 1
finally:
self.bridge.stop()
@staticmethod
def _parse_simple_args(args_str: str) -> dict:
"""Parse 'key=value key2=value2' style arguments."""
args = {}
for part in args_str.split():
if "=" in part:
key, value = part.split("=", 1)
args[key] = value
return args
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python -m bridge.mcp_bridge <tool_name> [args_json]", file=sys.stderr)
print("Tools: query, context, impact, cypher, list_repos, detect_changes, rename", file=sys.stderr)
sys.exit(1)
tool = sys.argv[1]
args_json = sys.argv[2] if len(sys.argv) > 2 else "{}"
cli = MCPToolCLI()
sys.exit(cli.run(tool, args_json))
+8
View File
@@ -0,0 +1,8 @@
# Claude Haiku 4.5 — fast, cheap, good baseline
# Via OpenRouter (set OPENROUTER_API_KEY in .env)
model:
model_name: "openrouter/anthropic/claude-haiku-4.5"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 8192
temperature: 0
+9
View File
@@ -0,0 +1,9 @@
# Claude Opus 4 — most capable, highest cost
# Via OpenRouter (set OPENROUTER_API_KEY in .env)
# To use Anthropic directly, change to: anthropic/claude-opus-4-20250514
model:
model_name: "openrouter/anthropic/claude-opus-4"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 16384
temperature: 0
+9
View File
@@ -0,0 +1,9 @@
# Claude Sonnet 4 — strong all-around model
# Via OpenRouter (set OPENROUTER_API_KEY in .env)
# To use Anthropic directly, change to: anthropic/claude-sonnet-4-20250514
model:
model_name: "openrouter/anthropic/claude-sonnet-4"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 16384
temperature: 0
+13
View File
@@ -0,0 +1,13 @@
model: deepseek-ai/deepseek-chat
provider: openrouter
cost:
input: 0.14 # per 1M tokens
output: 0.28 # per 1M tokens
# Native DeepSeek API (direct)
api_key: null
base_url: null
# For OpenRouter, uncomment below and comment out direct config above
# api_key: \${OPENROUTER_API_KEY}
# base_url: https://openrouter.ai/api/v1
+15
View File
@@ -0,0 +1,15 @@
model: deepseek-ai/DeepSeek-V3
provider: openrouter
cost:
input: 0.27 # per 1M tokens
output: 1.10 # per 1M tokens
# Native DeepSeek API (direct)
# Get your API key at: https://platform.deepseek.com/
# Or use OpenRouter with: OPENROUTER_API_KEY
api_key: null
base_url: null
# For OpenRouter, uncomment below and comment out direct config above
# api_key: \${OPENROUTER_API_KEY}
# base_url: https://openrouter.ai/api/v1
+7
View File
@@ -0,0 +1,7 @@
# GLM 4.7 — via OpenRouter (set OPENROUTER_API_KEY in .env)
model:
model_name: "openrouter/zhipuai/glm-4.7"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 8192
temperature: 0
+7
View File
@@ -0,0 +1,7 @@
# GLM 5 — via OpenRouter (set OPENROUTER_API_KEY in .env)
model:
model_name: "openrouter/zhipuai/glm-5"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 8192
temperature: 0
+7
View File
@@ -0,0 +1,7 @@
# MiniMax M1 2.5 — via OpenRouter (set OPENROUTER_API_KEY in .env)
model:
model_name: "openrouter/minimax/minimax-m1-2.5"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 8192
temperature: 0
+11
View File
@@ -0,0 +1,11 @@
# MiniMax M2.5 — via OpenRouter (set OPENROUTER_API_KEY in .env)
# Uses text-based model class because MiniMax doesn't support tool_calls natively.
# The action_regex tells mini-swe-agent to parse ```bash blocks from responses.
model:
model_class: litellm_textbased
model_name: "openrouter/minimax/minimax-m2.5"
action_regex: "```(?:bash|mswea_bash_command)\\s*\\n(.*?)\\n```"
cost_tracking: "ignore_errors"
model_kwargs:
max_tokens: 8192
temperature: 0
+9
View File
@@ -0,0 +1,9 @@
# Baseline mode — no GitNexus, pure mini-swe-agent (control group)
agent:
agent_class: "eval.agents.gitnexus_agent.GitNexusAgent"
gitnexus_mode: "baseline"
step_limit: 30
cost_limit: 3.0
environment:
environment_class: "docker"
+19
View File
@@ -0,0 +1,19 @@
# Native mode — GitNexus tools only, no grep enrichment
#
# Explicit tools: gitnexus-query, gitnexus-context, gitnexus-impact, gitnexus-cypher
# Available as fast bash commands (~100ms via eval-server)
#
# Use this mode to isolate the value of explicit tools without grep augmentation.
agent:
agent_class: "eval.agents.gitnexus_agent.GitNexusAgent"
gitnexus_mode: "native"
step_limit: 30
cost_limit: 3.0
track_gitnexus_usage: true
environment:
environment_class: "eval.environments.gitnexus_docker.GitNexusDockerEnvironment"
enable_gitnexus: true
skip_embeddings: true
gitnexus_timeout: 120
eval_server_port: 4848
+24
View File
@@ -0,0 +1,24 @@
# Native + Augment mode — the primary evaluation mode
#
# Combines two capabilities (mirroring the Claude Code model):
# 1. Explicit GitNexus tools: gitnexus-query, gitnexus-context, gitnexus-impact, gitnexus-cypher
# Available as fast bash commands (~100ms via eval-server)
# 2. Automatic grep enrichment: grep/rg results are transparently augmented with
# [GitNexus] annotations showing callers, callees, and execution flows
#
# The agent decides when to use explicit tools vs rely on enriched grep results.
agent:
agent_class: "eval.agents.gitnexus_agent.GitNexusAgent"
gitnexus_mode: "native_augment"
step_limit: 30
cost_limit: 3.0
augment_timeout: 5.0
augment_min_pattern_length: 3
track_gitnexus_usage: true
environment:
environment_class: "eval.environments.gitnexus_docker.GitNexusDockerEnvironment"
enable_gitnexus: true
skip_embeddings: true
gitnexus_timeout: 120
eval_server_port: 4848
View File
+397
View File
@@ -0,0 +1,397 @@
"""
GitNexus Docker Environment for SWE-bench Evaluation
Extends mini-swe-agent's Docker environment to:
1. Install GitNexus (Node.js + npm + gitnexus package)
2. Run `gitnexus analyze` on the repository
3. Start the eval-server daemon (persistent HTTP server with warm KuzuDB)
4. Install standalone tool scripts in /usr/local/bin/ (works with subprocess.run)
5. Cache indexes per (repo, base_commit) to avoid re-indexing
IMPORTANT: mini-swe-agent runs every command with subprocess.run in a fresh subshell.
This means .bashrc is NOT sourced, exported functions are NOT available, and env vars
don't persist. The tool scripts must be standalone executables in $PATH.
Architecture:
Agent bash cmd → /usr/local/bin/gitnexus-query → curl localhost:4848/tool/query → eval-server → KuzuDB
Fallback: → npx gitnexus query (cold start, slower)
Tool call latency: ~50-100ms via eval-server, ~5-10s via CLI fallback.
"""
import hashlib
import json
import logging
import shutil
import time
from pathlib import Path
from minisweagent.environments.docker import DockerEnvironment
logger = logging.getLogger("gitnexus_docker")
DEFAULT_CACHE_DIR = Path.home() / ".gitnexus-eval-cache"
EVAL_SERVER_PORT = 4848
# Standalone tool scripts installed into /usr/local/bin/ inside the container.
# Each script calls the eval-server via curl, with a CLI fallback.
# These are standalone — no sourcing, no env inheritance needed.
TOOL_SCRIPT_QUERY = r'''#!/bin/bash
PORT="${GITNEXUS_EVAL_PORT:-__PORT__}"
query="$1"; task_ctx="${2:-}"; goal="${3:-}"
[ -z "$query" ] && echo "Usage: gitnexus-query <query> [task_context] [goal]" && exit 1
args="{\"query\": \"$query\""
[ -n "$task_ctx" ] && args="$args, \"task_context\": \"$task_ctx\""
[ -n "$goal" ] && args="$args, \"goal\": \"$goal\""
args="$args}"
result=$(curl -sf -X POST "http://127.0.0.1:${PORT}/tool/query" -H "Content-Type: application/json" -d "$args" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then echo "$result"; exit 0; fi
cd /testbed && npx gitnexus query "$query" 2>&1
'''
TOOL_SCRIPT_CONTEXT = r'''#!/bin/bash
PORT="${GITNEXUS_EVAL_PORT:-__PORT__}"
name="$1"; file_path="${2:-}"
[ -z "$name" ] && echo "Usage: gitnexus-context <symbol_name> [file_path]" && exit 1
args="{\"name\": \"$name\""
[ -n "$file_path" ] && args="$args, \"file_path\": \"$file_path\""
args="$args}"
result=$(curl -sf -X POST "http://127.0.0.1:${PORT}/tool/context" -H "Content-Type: application/json" -d "$args" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then echo "$result"; exit 0; fi
cd /testbed && npx gitnexus context "$name" 2>&1
'''
TOOL_SCRIPT_IMPACT = r'''#!/bin/bash
PORT="${GITNEXUS_EVAL_PORT:-__PORT__}"
target="$1"; direction="${2:-upstream}"
[ -z "$target" ] && echo "Usage: gitnexus-impact <symbol_name> [upstream|downstream]" && exit 1
result=$(curl -sf -X POST "http://127.0.0.1:${PORT}/tool/impact" -H "Content-Type: application/json" -d "{\"target\": \"$target\", \"direction\": \"$direction\"}" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then echo "$result"; exit 0; fi
cd /testbed && npx gitnexus impact "$target" --direction "$direction" 2>&1
'''
TOOL_SCRIPT_CYPHER = r'''#!/bin/bash
PORT="${GITNEXUS_EVAL_PORT:-__PORT__}"
query="$1"
[ -z "$query" ] && echo "Usage: gitnexus-cypher <cypher_query>" && exit 1
result=$(curl -sf -X POST "http://127.0.0.1:${PORT}/tool/cypher" -H "Content-Type: application/json" -d "{\"query\": \"$query\"}" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then echo "$result"; exit 0; fi
cd /testbed && npx gitnexus cypher "$query" 2>&1
'''
TOOL_SCRIPT_AUGMENT = r'''#!/bin/bash
cd /testbed && npx gitnexus augment "$1" 2>&1 || true
'''
TOOL_SCRIPT_OVERVIEW = r'''#!/bin/bash
PORT="${GITNEXUS_EVAL_PORT:-__PORT__}"
echo "=== Code Knowledge Graph Overview ==="
result=$(curl -sf -X POST "http://127.0.0.1:${PORT}/tool/list_repos" -H "Content-Type: application/json" -d "{}" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$result" ]; then echo "$result"; exit 0; fi
cd /testbed && npx gitnexus list 2>&1
'''
class GitNexusDockerEnvironment(DockerEnvironment):
"""
Docker environment with GitNexus pre-installed, indexed, and eval-server running.
Setup flow:
1. Start Docker container (base SWE-bench image)
2. Install Node.js + gitnexus inside the container
3. Run `gitnexus analyze` (or restore from cache)
4. Start `gitnexus eval-server` daemon (keeps KuzuDB warm)
5. Install standalone tool scripts in /usr/local/bin/
6. Agent runs with near-instant GitNexus tool calls
"""
def __init__(
self,
*,
enable_gitnexus: bool = True,
cache_dir: str | Path | None = None,
skip_embeddings: bool = True,
gitnexus_timeout: int = 120,
eval_server_port: int = EVAL_SERVER_PORT,
**kwargs,
):
super().__init__(**kwargs)
self.enable_gitnexus = enable_gitnexus
self.cache_dir = Path(cache_dir) if cache_dir else DEFAULT_CACHE_DIR
self.skip_embeddings = skip_embeddings
self.gitnexus_timeout = gitnexus_timeout
self.eval_server_port = eval_server_port
self.index_time: float = 0.0
self._gitnexus_ready = False
def start(self) -> dict:
"""Start the container and set up GitNexus."""
result = super().start()
if self.enable_gitnexus:
try:
self._setup_gitnexus()
except Exception as e:
logger.warning(f"GitNexus setup failed, continuing without it: {e}")
self._gitnexus_ready = False
return result
def _setup_gitnexus(self):
"""Install and configure GitNexus in the container."""
start = time.time()
self._ensure_nodejs()
self._install_gitnexus()
self._index_repository()
self._start_eval_server()
self._install_tools()
self.index_time = time.time() - start
self._gitnexus_ready = True
logger.info(f"GitNexus setup completed in {self.index_time:.1f}s")
def _ensure_nodejs(self):
"""Ensure Node.js >= 18 is available in the container."""
check = self.execute({"command": "node --version 2>/dev/null || echo 'NOT_FOUND'"})
output = check.get("output", "").strip()
if "NOT_FOUND" in output:
logger.info("Installing Node.js in container...")
install_cmds = [
"apt-get update -qq",
"apt-get install -y -qq curl ca-certificates",
"curl -fsSL https://deb.nodesource.com/setup_20.x | bash -",
"apt-get install -y -qq nodejs",
]
for cmd in install_cmds:
result = self.execute({"command": cmd, "timeout": 60})
if result.get("returncode", 1) != 0:
raise RuntimeError(f"Failed to install Node.js: {result.get('output', '')}")
else:
logger.info(f"Node.js already available: {output}")
def _install_gitnexus(self):
"""Install the gitnexus npm package globally."""
check = self.execute({"command": "npx gitnexus --version 2>/dev/null || echo 'NOT_FOUND'"})
if "NOT_FOUND" in check.get("output", ""):
logger.info("Installing gitnexus...")
result = self.execute({
"command": "npm install -g gitnexus",
"timeout": 60,
})
if result.get("returncode", 1) != 0:
raise RuntimeError(f"Failed to install gitnexus: {result.get('output', '')}")
def _index_repository(self):
"""Run gitnexus analyze on the repo, using cache if available."""
repo_info = self._get_repo_info()
cache_key = self._make_cache_key(repo_info)
cache_path = self.cache_dir / cache_key
if cache_path.exists():
logger.info(f"Restoring GitNexus index from cache: {cache_key}")
self._restore_cache(cache_path)
return
logger.info("Running gitnexus analyze...")
skip_flag = "--skip-embeddings" if self.skip_embeddings else ""
result = self.execute({
"command": f"cd /testbed && npx gitnexus analyze . {skip_flag} 2>&1",
"timeout": self.gitnexus_timeout,
})
if result.get("returncode", 1) != 0:
output = result.get("output", "")
if "error" in output.lower() and "indexed" not in output.lower():
raise RuntimeError(f"gitnexus analyze failed: {output[-500:]}")
self._save_cache(cache_path, repo_info)
def _start_eval_server(self):
"""Start the GitNexus eval-server daemon in the background."""
logger.info(f"Starting eval-server on port {self.eval_server_port}...")
self.execute({
"command": (
f"nohup npx gitnexus eval-server --port {self.eval_server_port} "
f"--idle-timeout 600 "
f"> /tmp/gitnexus-eval-server.log 2>&1 &"
),
"timeout": 5,
})
# Wait for the server to be ready (up to 15s for KuzuDB init)
for i in range(30):
time.sleep(0.5)
health = self.execute({
"command": f"curl -sf http://127.0.0.1:{self.eval_server_port}/health 2>/dev/null || echo 'NOT_READY'",
"timeout": 3,
})
output = health.get("output", "").strip()
if "NOT_READY" not in output and "ok" in output:
logger.info(f"Eval-server ready after {(i + 1) * 0.5:.1f}s")
return
log_output = self.execute({
"command": "cat /tmp/gitnexus-eval-server.log 2>/dev/null | tail -20",
})
logger.warning(
f"Eval-server didn't become ready in 15s. "
f"Tools will fall back to direct CLI.\n"
f"Server log: {log_output.get('output', 'N/A')}"
)
def _install_tools(self):
"""
Install standalone GitNexus tool scripts in /usr/local/bin/.
Each script is a self-contained bash script that:
1. Calls the eval-server via curl (fast path, ~100ms)
2. Falls back to direct CLI if eval-server is unavailable
These are standalone executables — no sourcing, env inheritance, or .bashrc
needed. This is critical because mini-swe-agent runs every command via
subprocess.run in a fresh subshell.
Uses heredocs with quoted delimiter to avoid all quoting/escaping issues.
"""
port = str(self.eval_server_port)
tools = {
"gitnexus-query": TOOL_SCRIPT_QUERY,
"gitnexus-context": TOOL_SCRIPT_CONTEXT,
"gitnexus-impact": TOOL_SCRIPT_IMPACT,
"gitnexus-cypher": TOOL_SCRIPT_CYPHER,
"gitnexus-augment": TOOL_SCRIPT_AUGMENT,
"gitnexus-overview": TOOL_SCRIPT_OVERVIEW,
}
for name, script in tools.items():
script_content = script.replace("__PORT__", port).strip()
# Use heredoc with quoted delimiter — prevents all variable expansion and quoting issues
self.execute({
"command": f"cat << 'GITNEXUS_SCRIPT_EOF' > /usr/local/bin/{name}\n{script_content}\nGITNEXUS_SCRIPT_EOF\nchmod +x /usr/local/bin/{name}",
"timeout": 5,
})
logger.info(f"Installed {len(tools)} GitNexus tool scripts in /usr/local/bin/")
def _get_repo_info(self) -> dict:
"""Get repository identity info from the container."""
repo_result = self.execute({
"command": "cd /testbed && basename $(git remote get-url origin 2>/dev/null || basename $(pwd)) .git"
})
commit_result = self.execute({"command": "cd /testbed && git rev-parse HEAD 2>/dev/null || echo unknown"})
return {
"repo": repo_result.get("output", "unknown").strip(),
"commit": commit_result.get("output", "unknown").strip(),
}
@staticmethod
def _make_cache_key(repo_info: dict) -> str:
"""Create a deterministic cache key from repo info."""
content = f"{repo_info['repo']}:{repo_info['commit']}"
return hashlib.sha256(content.encode()).hexdigest()[:16]
def _save_cache(self, cache_path: Path, repo_info: dict):
"""Save the GitNexus index to the host cache directory."""
try:
cache_path.mkdir(parents=True, exist_ok=True)
find_result = self.execute({
"command": "find /root/.gitnexus -name 'kuzu' -type d 2>/dev/null | head -1"
})
gitnexus_dir = find_result.get("output", "").strip()
if gitnexus_dir:
parent = str(Path(gitnexus_dir).parent)
self.execute({
"command": f"cd {parent} && tar czf /tmp/gitnexus-cache.tar.gz .",
"timeout": 30,
})
container_id = getattr(self, "_container_id", None) or getattr(self, "container_id", None)
if container_id:
import subprocess as sp
sp.run(
["docker", "cp", f"{container_id}:/tmp/gitnexus-cache.tar.gz",
str(cache_path / "index.tar.gz")],
check=True, capture_output=True,
)
(cache_path / "metadata.json").write_text(json.dumps(repo_info, indent=2))
logger.info(f"Cached GitNexus index: {cache_path}")
except Exception as e:
logger.warning(f"Failed to cache GitNexus index: {e}")
if cache_path.exists():
shutil.rmtree(cache_path, ignore_errors=True)
def _restore_cache(self, cache_path: Path):
"""Restore a cached GitNexus index into the container."""
try:
cache_tarball = cache_path / "index.tar.gz"
if not cache_tarball.exists():
logger.warning("Cache tarball not found, re-indexing")
self._index_repository()
return
container_id = getattr(self, "_container_id", None) or getattr(self, "container_id", None)
if container_id:
import subprocess as sp
self.execute({"command": "mkdir -p /root/.gitnexus"})
storage_result = self.execute({
"command": "npx gitnexus list 2>/dev/null | grep -o '/root/.gitnexus/[^ ]*' | head -1 || echo '/root/.gitnexus/repos/default'"
})
storage_path = storage_result.get("output", "").strip() or "/root/.gitnexus/repos/default"
self.execute({"command": f"mkdir -p {storage_path}"})
sp.run(
["docker", "cp", str(cache_tarball), f"{container_id}:/tmp/gitnexus-cache.tar.gz"],
check=True, capture_output=True,
)
self.execute({
"command": f"cd {storage_path} && tar xzf /tmp/gitnexus-cache.tar.gz",
"timeout": 30,
})
logger.info("GitNexus index restored from cache")
except Exception as e:
logger.warning(f"Failed to restore cache, re-indexing: {e}")
self._index_repository()
def stop(self) -> dict:
"""Stop the container, shutting down eval-server first."""
if self._gitnexus_ready:
try:
self.execute({
"command": f"curl -sf -X POST http://127.0.0.1:{self.eval_server_port}/shutdown 2>/dev/null || true",
"timeout": 3,
})
except Exception:
pass
return super().stop()
def get_template_vars(self) -> dict:
"""Add GitNexus-specific template variables."""
base_vars = super().get_template_vars()
base_vars["gitnexus_ready"] = self._gitnexus_ready
base_vars["gitnexus_index_time"] = self.index_time
return base_vars
def serialize(self) -> dict:
"""Include GitNexus environment info in serialization."""
base = super().serialize()
base.setdefault("info", {})["gitnexus_env"] = {
"enabled": self.enable_gitnexus,
"ready": self._gitnexus_ready,
"index_time_seconds": round(self.index_time, 2),
"skip_embeddings": self.skip_embeddings,
"eval_server_port": self.eval_server_port,
}
return base
+80
View File
@@ -0,0 +1,80 @@
Please solve this issue: {{task}}
You can execute bash commands and edit files to implement the necessary changes.
## Recommended Workflow
This workflows should be done step-by-step so that you can iterate on your changes and any possible problems.
1. Analyze the codebase by finding and reading relevant files
2. Create a script to reproduce the issue
3. Edit the source code to resolve the issue
4. Verify your fix works by running your script again
5. Test edge cases to ensure your fix is robust
6. Submit your changes and finish your work by issuing the following command: `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT`.
Do not combine it with any other command. After this command, you cannot continue working on this task.
## Important Rules
1. Every response must contain exactly one action
2. The action must be enclosed in triple backticks
3. Directory or environment variable changes are not persistent. Every action is executed in a new subshell.
However, you can prefix any action with `MY_ENV_VAR=MY_VALUE cd /path/to/working/dir && ...` or write/load environment variables from files
<system_info>
{{system}} {{release}} {{version}} {{machine}}
</system_info>
## Formatting your response
Here is an example of a correct response:
<example_response>
THOUGHT: I need to understand the structure of the repository first. Let me check what files are in the current directory to get a better understanding of the codebase.
```mswea_bash_command
ls -la
```
</example_response>
## Useful command examples
### Create a new file:
```bash
cat <<'EOF' > newfile.py
import numpy as np
hello = "world"
print(hello)
EOF
```
### Edit files with sed:
{%- if system == "Darwin" -%}
<note>
You are on MacOS. For all the below examples, you need to use `sed -i ''` instead of `sed -i`.
</note>
{%- endif -%}
```bash
# Replace all occurrences
sed -i 's/old_string/new_string/g' filename.py
# Replace only first occurrence
sed -i 's/old_string/new_string/' filename.py
# Replace all occurrences in lines 1-10
sed -i '1,10s/old_string/new_string/g' filename.py
```
### View file content:
```bash
# View specific lines with numbers
nl -ba filename.py | sed -n '10,20p'
```
### Any other command you want to run
```bash
anything
```
+102
View File
@@ -0,0 +1,102 @@
Please solve this issue: {{task}}
You can execute bash commands and edit files to implement the necessary changes.
## Recommended Workflow
Work step-by-step so you can iterate on your changes and catch problems early.
1. **Understand the issue** — read the problem statement, identify the symptom and affected area
2. **Find the relevant code** — use `gitnexus-query "<feature area>"` to find execution flows, or `grep` for specific strings
3. **Understand the suspect** — use `gitnexus-context "<symbol>"` to see all callers and callees, then `cat` to read the source
4. **Check blast radius** — before editing shared code, run `gitnexus-impact "<symbol>" upstream` to see what depends on it
5. **Implement the fix** — make minimal, targeted changes
6. **Verify** — run relevant tests, check edge cases
7. **Submit** — issue: `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT`
Do not combine it with any other command. After this command, you cannot continue working on this task.
## Debugging Patterns
| Symptom | Approach |
|---------|----------|
| Error message / exception | `gitnexus-query` for error text → `gitnexus-context` on throw sites |
| Wrong return value | `gitnexus-context` on the function → trace callees for data flow |
| Missing feature / incomplete behavior | `gitnexus-query` for feature area → find the execution flow → locate the gap |
| Need to understand callers | `gitnexus-context` — graph-complete, finds callers grep would miss |
## Risk Assessment
Before editing shared code, check the blast radius:
| Impact | Risk | Action |
|--------|------|--------|
| <5 symbols at d=1 | Low | Fix with confidence |
| 5-15 symbols at d=1 | Medium | Fix carefully, run broader tests |
| >15 symbols at d=1 | High | Minimal change, run full test suite |
## Important Rules
1. Every response must contain exactly one action
2. The action must be enclosed in triple backticks
3. Directory or environment variable changes are not persistent. Every action is executed in a new subshell.
However, you can prefix any action with `MY_ENV_VAR=MY_VALUE cd /path/to/working/dir && ...` or write/load environment variables from files
4. Make minimal, targeted changes. Don't refactor unrelated code.
5. GitNexus tools are ~100ms. Use them when they save you multiple grep iterations.
<system_info>
{{system}} {{release}} {{version}} {{machine}}
</system_info>
## Formatting your response
Here is an example of a correct response:
<example_response>
THOUGHT: The issue mentions a problem with form field validation. Let me search the code knowledge graph for the relevant execution flows to understand how validation works in this codebase.
```mswea_bash_command
gitnexus-query "form field validation"
```
</example_response>
## Useful command examples
### Create a new file:
```bash
cat <<'EOF' > newfile.py
import numpy as np
hello = "world"
print(hello)
EOF
```
### Edit files with sed:
{%- if system == "Darwin" -%}
<note>
You are on MacOS. For all the below examples, you need to use `sed -i ''` instead of `sed -i`.
</note>
{%- endif -%}
```bash
# Replace all occurrences
sed -i 's/old_string/new_string/g' filename.py
# Replace only first occurrence
sed -i 's/old_string/new_string/' filename.py
# Replace all occurrences in lines 1-10
sed -i '1,10s/old_string/new_string/g' filename.py
```
### View file content:
```bash
# View specific lines with numbers
nl -ba filename.py | sed -n '10,20p'
```
### Any other command you want to run
```bash
anything
```
+103
View File
@@ -0,0 +1,103 @@
Please solve this issue: {{task}}
You can execute bash commands and edit files to implement the necessary changes.
## Recommended Workflow
Work step-by-step so you can iterate on your changes and catch problems early.
1. **Understand the issue** — read the problem statement, identify the symptom and affected area
2. **Find the relevant code** — use `gitnexus-query "<feature area>"` to find execution flows, or `grep` for specific strings
3. **Understand the suspect** — use `gitnexus-context "<symbol>"` to see all callers and callees, then `cat` to read the source
4. **Check blast radius** — before editing shared code, run `gitnexus-impact "<symbol>" upstream` to see what depends on it
5. **Implement the fix** — make minimal, targeted changes
6. **Verify** — run relevant tests, check edge cases
7. **Submit** — issue: `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT`
Do not combine it with any other command. After this command, you cannot continue working on this task.
## Debugging Patterns
| Symptom | Approach |
|---------|----------|
| Error message / exception | `gitnexus-query` for error text → `gitnexus-context` on throw sites |
| Wrong return value | `gitnexus-context` on the function → trace callees for data flow |
| Missing feature / incomplete behavior | `gitnexus-query` for feature area → find the execution flow → locate the gap |
| Need to understand callers | `gitnexus-context` — graph-complete, finds callers grep would miss |
## Risk Assessment
Before editing shared code, check the blast radius:
| Impact | Risk | Action |
|--------|------|--------|
| <5 symbols at d=1 | Low | Fix with confidence |
| 5-15 symbols at d=1 | Medium | Fix carefully, run broader tests |
| >15 symbols at d=1 | High | Minimal change, run full test suite |
## Important Rules
1. Every response must contain exactly one action
2. The action must be enclosed in triple backticks
3. Directory or environment variable changes are not persistent. Every action is executed in a new subshell.
However, you can prefix any action with `MY_ENV_VAR=MY_VALUE cd /path/to/working/dir && ...` or write/load environment variables from files
4. Make minimal, targeted changes. Don't refactor unrelated code.
5. GitNexus tools are ~100ms. Use them when they save you multiple grep iterations.
6. When grep results show `[GitNexus]` enrichments, use those for navigation.
<system_info>
{{system}} {{release}} {{version}} {{machine}}
</system_info>
## Formatting your response
Here is an example of a correct response:
<example_response>
THOUGHT: The issue mentions a problem with form field validation. Let me search the code knowledge graph for the relevant execution flows to understand how validation works in this codebase.
```mswea_bash_command
gitnexus-query "form field validation"
```
</example_response>
## Useful command examples
### Create a new file:
```bash
cat <<'EOF' > newfile.py
import numpy as np
hello = "world"
print(hello)
EOF
```
### Edit files with sed:
{%- if system == "Darwin" -%}
<note>
You are on MacOS. For all the below examples, you need to use `sed -i ''` instead of `sed -i`.
</note>
{%- endif -%}
```bash
# Replace all occurrences
sed -i 's/old_string/new_string/g' filename.py
# Replace only first occurrence
sed -i 's/old_string/new_string/' filename.py
# Replace all occurrences in lines 1-10
sed -i '1,10s/old_string/new_string/g' filename.py
```
### View file content:
```bash
# View specific lines with numbers
nl -ba filename.py | sed -n '10,20p'
```
### Any other command you want to run
```bash
anything
```
+15
View File
@@ -0,0 +1,15 @@
You are a helpful assistant that can interact with a computer to solve software engineering tasks.
Your response must contain exactly ONE bash code block with ONE command (or commands connected with && or ||).
Include a THOUGHT section before your command where you explain your reasoning process.
Format your response as shown in.
<example_response>
Your reasoning and analysis here. Explain why you want to perform the action.
```mswea_bash_command
your_command_here
```
</example_response>
Failure to follow these rules will cause your response to be rejected.
+54
View File
@@ -0,0 +1,54 @@
You are a helpful assistant that can interact with a computer to solve software engineering tasks.
Your response must contain exactly ONE bash code block with ONE command (or commands connected with && or ||).
Include a THOUGHT section before your command where you explain your reasoning process.
Format your response as shown in.
<example_response>
Your reasoning and analysis here. Explain why you want to perform the action.
```mswea_bash_command
your_command_here
```
</example_response>
Failure to follow these rules will cause your response to be rejected.
## Code Intelligence
You have **GitNexus** — a knowledge graph over this entire codebase. It knows every function call chain, class hierarchy, execution flow, and symbol relationship. These are fast bash commands (~100ms). Use them when useful, skip them when a simple grep suffices.
### GitNexus Commands
**gitnexus-query "<concept>"** — Find execution flows related to a concept.
Returns ranked execution flow traces with participating symbols and file locations.
```bash
gitnexus-query "form field validation"
```
**gitnexus-context "<symbol>" ["<file_path>"]** — 360-degree view of a symbol.
Returns ALL callers, ALL callees, and execution flows. Graph-complete — finds callers that grep misses.
```bash
gitnexus-context "BoundField" "django/forms/boundfield.py"
```
**gitnexus-impact "<symbol>" [upstream|downstream]** — Blast radius analysis.
What breaks if you change this: d=1 WILL BREAK, d=2 LIKELY AFFECTED, d=3 MAY NEED TESTING.
```bash
gitnexus-impact "BoundField" upstream
```
**gitnexus-cypher "<query>"** — Raw Cypher query against the code graph.
```bash
gitnexus-cypher 'MATCH (a)-[:CodeRelation {type: "CALLS"}]->(b:Function {name: "clean"}) RETURN a.name, a.filePath'
```
### When to Use What
| I need to... | Use |
|---|---|
| Understand how a feature works end-to-end | `gitnexus-query` |
| Find ALL callers of a function | `gitnexus-context` |
| Know what breaks if I change something | `gitnexus-impact` upstream |
| Find a string literal or error message | `grep` |
| Read source code | `cat` / `nl -ba` |
+56
View File
@@ -0,0 +1,56 @@
You are a helpful assistant that can interact with a computer to solve software engineering tasks.
Your response must contain exactly ONE bash code block with ONE command (or commands connected with && or ||).
Include a THOUGHT section before your command where you explain your reasoning process.
Format your response as shown in.
<example_response>
Your reasoning and analysis here. Explain why you want to perform the action.
```mswea_bash_command
your_command_here
```
</example_response>
Failure to follow these rules will cause your response to be rejected.
## Code Intelligence
You have **GitNexus** — a knowledge graph over this entire codebase. It knows every function call chain, class hierarchy, execution flow, and symbol relationship. These are fast bash commands (~100ms). Use them when useful, skip them when a simple grep suffices.
Your `grep` results are also automatically enriched with `[GitNexus]` annotations showing callers, callees, and execution flows for matched symbols. Pay attention to these — they often point you to the right code without extra tool calls.
### GitNexus Commands
**gitnexus-query "<concept>"** — Find execution flows related to a concept.
Returns ranked execution flow traces with participating symbols and file locations.
```bash
gitnexus-query "form field validation"
```
**gitnexus-context "<symbol>" ["<file_path>"]** — 360-degree view of a symbol.
Returns ALL callers, ALL callees, and execution flows. Graph-complete — finds callers that grep misses.
```bash
gitnexus-context "BoundField" "django/forms/boundfield.py"
```
**gitnexus-impact "<symbol>" [upstream|downstream]** — Blast radius analysis.
What breaks if you change this: d=1 WILL BREAK, d=2 LIKELY AFFECTED, d=3 MAY NEED TESTING.
```bash
gitnexus-impact "BoundField" upstream
```
**gitnexus-cypher "<query>"** — Raw Cypher query against the code graph.
```bash
gitnexus-cypher 'MATCH (a)-[:CodeRelation {type: "CALLS"}]->(b:Function {name: "clean"}) RETURN a.name, a.filePath'
```
### When to Use What
| I need to... | Use |
|---|---|
| Understand how a feature works end-to-end | `gitnexus-query` |
| Find ALL callers of a function | `gitnexus-context` |
| Know what breaks if I change something | `gitnexus-impact` upstream |
| Find a string literal or error message | `grep` |
| Read source code | `cat` / `nl -ba` |
+39
View File
@@ -0,0 +1,39 @@
[project]
name = "gitnexus-swebench-eval"
version = "0.1.0"
description = "SWE-bench evaluation harness with GitNexus code intelligence integration"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"mini-swe-agent>=2.0.0",
"litellm>=1.50.0",
"datasets>=3.0.0",
"typer>=0.12.0",
"rich>=13.0.0",
"pyyaml>=6.0",
"pandas>=2.0.0",
"tabulate>=0.9.0",
"python-dotenv>=1.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"ruff>=0.5.0",
]
[project.scripts]
gitnexus-eval = "run_eval:app"
gitnexus-eval-analyze = "analysis.analyze_results:app"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["agents", "environments", "analysis", "bridge"]
extra-files = ["run_eval.py"]
[tool.ruff]
line-length = 120
target-version = "py311"
+515
View File
@@ -0,0 +1,515 @@
#!/usr/bin/env python3
"""
GitNexus SWE-bench Evaluation Runner
Main entry point for running SWE-bench evaluations with and without GitNexus.
Supports running a single configuration or a full matrix of models x modes.
Usage:
# Single run (default: native_augment mode — GitNexus tools + grep enrichment)
python run_eval.py single -m claude-sonnet --subset lite --slice 0:5
# Baseline comparison (no GitNexus)
python run_eval.py single -m claude-sonnet --mode baseline --subset lite --slice 0:5
# Matrix run (all models x all modes)
python run_eval.py matrix --subset lite --slice 0:50 --workers 4
# Single instance for debugging
python run_eval.py debug -m claude-haiku -i django__django-16527
"""
import concurrent.futures
import json
import logging
import os
import threading
import time
import traceback
from itertools import product
from pathlib import Path
from typing import Any
import typer
import yaml
from rich.console import Console
from rich.live import Live
from rich.table import Table
# Load .env file from eval/ directory
_env_file = Path(__file__).parent / ".env"
if _env_file.exists():
for line in _env_file.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
key, _, value = line.partition("=")
key, value = key.strip(), value.strip()
if value and key not in os.environ: # Don't override existing env vars
os.environ[key] = value
logger = logging.getLogger("gitnexus_eval")
console = Console()
app = typer.Typer(rich_markup_mode="rich", add_completion=False)
# Directory paths
EVAL_DIR = Path(__file__).parent
CONFIGS_DIR = EVAL_DIR / "configs"
MODELS_DIR = CONFIGS_DIR / "models"
MODES_DIR = CONFIGS_DIR / "modes"
DEFAULT_OUTPUT_DIR = EVAL_DIR / "results"
# Available models and modes (discovered from config files)
AVAILABLE_MODELS = sorted([p.stem for p in MODELS_DIR.glob("*.yaml")])
AVAILABLE_MODES = sorted([p.stem for p in MODES_DIR.glob("*.yaml")])
# SWE-bench dataset mapping (same as mini-swe-agent)
DATASET_MAPPING = {
"full": "princeton-nlp/SWE-Bench",
"verified": "princeton-nlp/SWE-Bench_Verified",
"lite": "princeton-nlp/SWE-Bench_Lite",
}
_output_lock = threading.Lock()
def load_yaml_config(path: Path) -> dict:
"""Load a YAML config file."""
with open(path) as f:
return yaml.safe_load(f) or {}
def merge_configs(*configs: dict) -> dict:
"""Recursively merge multiple config dicts (later values win)."""
result = {}
for config in configs:
for key, value in config.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = merge_configs(result[key], value)
else:
result[key] = value
return result
def build_config(model_name: str, mode_name: str) -> dict:
"""Build a complete config from model + mode YAML files."""
model_file = MODELS_DIR / f"{model_name}.yaml"
mode_file = MODES_DIR / f"{mode_name}.yaml"
if not model_file.exists():
raise FileNotFoundError(f"Model config not found: {model_file}")
if not mode_file.exists():
raise FileNotFoundError(f"Mode config not found: {mode_file}")
model_config = load_yaml_config(model_file)
mode_config = load_yaml_config(mode_file)
return merge_configs(mode_config, model_config)
def load_instances(subset: str, split: str, slice_spec: str = "", filter_spec: str = "") -> list[dict]:
"""Load SWE-bench instances."""
from datasets import load_dataset
import re
dataset_path = DATASET_MAPPING.get(subset, subset)
logger.info(f"Loading dataset: {dataset_path}, split: {split}")
instances = list(load_dataset(dataset_path, split=split))
if filter_spec:
instances = [i for i in instances if re.match(filter_spec, i["instance_id"])]
if slice_spec:
values = [int(x) if x else None for x in slice_spec.split(":")]
instances = instances[slice(*values)]
logger.info(f"Loaded {len(instances)} instances")
return instances
def get_swebench_docker_image(instance: dict) -> str:
"""Get Docker image name for a SWE-bench instance."""
image_name = instance.get("image_name")
if image_name is None:
iid = instance["instance_id"]
id_docker = iid.replace("__", "_1776_")
image_name = f"docker.io/swebench/sweb.eval.x86_64.{id_docker}:latest".lower()
return image_name
def process_instance(
instance: dict,
config: dict,
output_dir: Path,
model_name: str,
mode_name: str,
) -> dict:
"""
Process a single SWE-bench instance with the given config.
Returns result dict with instance_id, exit_status, submission, metrics.
"""
from minisweagent.models import get_model
instance_id = instance["instance_id"]
run_id = f"{model_name}_{mode_name}"
instance_dir = output_dir / run_id / instance_id
instance_dir.mkdir(parents=True, exist_ok=True)
result = {
"instance_id": instance_id,
"model": model_name,
"mode": mode_name,
"exit_status": None,
"submission": "",
"cost": 0.0,
"n_calls": 0,
"gitnexus_metrics": {},
}
agent = None
try:
# Build model
model = get_model(config=config.get("model", {}))
# Build environment
env_config = dict(config.get("environment", {}))
env_class_name = env_config.pop("environment_class", "docker")
if env_class_name == "eval.environments.gitnexus_docker.GitNexusDockerEnvironment":
from environments.gitnexus_docker import GitNexusDockerEnvironment
env_config["image"] = get_swebench_docker_image(instance)
env = GitNexusDockerEnvironment(**env_config)
else:
from minisweagent.environments.docker import DockerEnvironment
env = DockerEnvironment(image=get_swebench_docker_image(instance), **env_config)
# Build agent
agent_config = dict(config.get("agent", {}))
agent_class_name = agent_config.pop("agent_class", "eval.agents.gitnexus_agent.GitNexusAgent")
from agents.gitnexus_agent import GitNexusAgent
traj_path = instance_dir / f"{instance_id}.traj.json"
agent_config["output_path"] = traj_path
agent = GitNexusAgent(model, env, **agent_config)
# Run
logger.info(f"[{run_id}] Starting {instance_id}")
info = agent.run(instance["problem_statement"])
result["exit_status"] = info.get("exit_status")
result["cost"] = agent.cost
result["n_calls"] = agent.n_calls
result["gitnexus_metrics"] = agent.gitnexus_metrics.to_dict()
# Extract git diff patch from the container (SWE-bench needs the model_patch)
try:
patch_output = env.execute({"command": "cd /testbed && git diff"})
result["submission"] = patch_output.get("output", "").strip()
except Exception as patch_err:
logger.warning(f"[{run_id}] Failed to extract patch: {patch_err}")
result["submission"] = info.get("submission", "")
except Exception as e:
logger.error(f"[{run_id}] Error on {instance_id}: {e}")
result["exit_status"] = type(e).__name__
result["error"] = str(e)
result["traceback"] = traceback.format_exc()
finally:
if agent:
agent.save(
instance_dir / f"{instance_id}.traj.json",
{"instance_id": instance_id, "run_id": run_id},
)
# Update predictions file
_update_preds(output_dir / run_id / "preds.json", instance_id, model_name, result)
return result
def _update_preds(preds_path: Path, instance_id: str, model_name: str, result: dict):
"""Thread-safe update of predictions file."""
with _output_lock:
preds_path.parent.mkdir(parents=True, exist_ok=True)
data = {}
if preds_path.exists():
data = json.loads(preds_path.read_text())
data[instance_id] = {
"model_name_or_path": model_name,
"instance_id": instance_id,
"model_patch": result.get("submission", ""),
}
preds_path.write_text(json.dumps(data, indent=2))
def run_configuration(
model_name: str,
mode_name: str,
instances: list[dict],
output_dir: Path,
workers: int = 1,
redo_existing: bool = False,
) -> list[dict]:
"""Run a single (model, mode) configuration across all instances."""
config = build_config(model_name, mode_name)
run_id = f"{model_name}_{mode_name}"
run_dir = output_dir / run_id
# Skip existing instances
if not redo_existing and (run_dir / "preds.json").exists():
existing = set(json.loads((run_dir / "preds.json").read_text()).keys())
instances = [i for i in instances if i["instance_id"] not in existing]
if not instances:
logger.info(f"[{run_id}] All instances already completed, skipping")
return []
console.print(f" [bold]{run_id}[/bold]: {len(instances)} instances, {workers} workers")
results = []
if workers <= 1:
for instance in instances:
result = process_instance(instance, config, output_dir, model_name, mode_name)
results.append(result)
else:
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(
process_instance, instance, config, output_dir, model_name, mode_name
): instance["instance_id"]
for instance in instances
}
for future in concurrent.futures.as_completed(futures):
try:
results.append(future.result())
except Exception as e:
iid = futures[future]
logger.error(f"[{run_id}] Uncaught error for {iid}: {e}")
# Save run summary
summary = {
"run_id": run_id,
"model": model_name,
"mode": mode_name,
"config": config,
"total_instances": len(results),
"completed": sum(1 for r in results if r["exit_status"] not in [None, "error"]),
"total_cost": sum(r.get("cost", 0) for r in results),
"total_api_calls": sum(r.get("n_calls", 0) for r in results),
"results": results,
}
(run_dir / "summary.json").mkdir(parents=True, exist_ok=True) if not run_dir.exists() else None
run_dir.mkdir(parents=True, exist_ok=True)
(run_dir / "summary.json").write_text(json.dumps(summary, indent=2, default=str))
return results
# ─── CLI Commands ───────────────────────────────────────────────────────────
@app.command()
def single(
model: str = typer.Option(..., "-m", "--model", help=f"Model config name. Available: {', '.join(AVAILABLE_MODELS)}"),
mode: str = typer.Option("native_augment", "--mode", help=f"Evaluation mode. Available: {', '.join(AVAILABLE_MODES)}"),
subset: str = typer.Option("lite", "--subset", help="SWE-bench subset: lite, verified, full"),
split: str = typer.Option("dev", "--split", help="Dataset split"),
slice_spec: str = typer.Option("", "--slice", help="Slice spec (e.g., '0:5')"),
filter_spec: str = typer.Option("", "--filter", help="Filter instance IDs by regex"),
workers: int = typer.Option(1, "-w", "--workers", help="Parallel workers"),
output: str = typer.Option(str(DEFAULT_OUTPUT_DIR), "-o", "--output", help="Output directory"),
redo: bool = typer.Option(False, "--redo", help="Redo existing instances"),
):
"""Run a single (model, mode) configuration on SWE-bench."""
output_dir = Path(output)
instances = load_instances(subset, split, slice_spec, filter_spec)
console.print(f"\n[bold]Running evaluation:[/bold] {model} + {mode}")
console.print(f" Instances: {len(instances)}")
console.print(f" Output: {output_dir}\n")
results = run_configuration(model, mode, instances, output_dir, workers, redo)
# Print summary
_print_summary(results, model, mode)
@app.command()
def matrix(
models: list[str] = typer.Option(AVAILABLE_MODELS, "-m", "--models", help="Models to evaluate (comma-separated or repeated)"),
modes: list[str] = typer.Option(AVAILABLE_MODES, "--modes", help="Modes to evaluate"),
subset: str = typer.Option("lite", "--subset", help="SWE-bench subset"),
split: str = typer.Option("dev", "--split", help="Dataset split"),
slice_spec: str = typer.Option("", "--slice", help="Slice spec"),
filter_spec: str = typer.Option("", "--filter", help="Filter instances by regex"),
workers: int = typer.Option(1, "-w", "--workers", help="Parallel workers per config"),
output: str = typer.Option(str(DEFAULT_OUTPUT_DIR), "-o", "--output", help="Output directory"),
redo: bool = typer.Option(False, "--redo", help="Redo existing instances"),
):
"""Run the full evaluation matrix: all models x all modes."""
output_dir = Path(output)
instances = load_instances(subset, split, slice_spec, filter_spec)
combos = list(product(models, modes))
console.print(f"\n[bold]Matrix evaluation:[/bold] {len(models)} models x {len(modes)} modes = {len(combos)} configs")
console.print(f" Models: {', '.join(models)}")
console.print(f" Modes: {', '.join(modes)}")
console.print(f" Instances per config: {len(instances)}")
console.print(f" Total runs: {len(combos) * len(instances)}")
console.print(f" Output: {output_dir}\n")
all_results = {}
for model_name, mode_name in combos:
run_id = f"{model_name}_{mode_name}"
console.print(f"\n[bold cyan]━━━ {run_id} ━━━[/bold cyan]")
results = run_configuration(model_name, mode_name, instances, output_dir, workers, redo)
all_results[run_id] = results
# Print comparative summary
_print_matrix_summary(all_results)
# Save master summary
master = {
"timestamp": time.time(),
"models": models,
"modes": modes,
"subset": subset,
"n_instances": len(instances),
"runs": {
run_id: {
"total": len(results),
"cost": sum(r.get("cost", 0) for r in results),
"api_calls": sum(r.get("n_calls", 0) for r in results),
}
for run_id, results in all_results.items()
},
}
output_dir.mkdir(parents=True, exist_ok=True)
(output_dir / "matrix_summary.json").write_text(json.dumps(master, indent=2, default=str))
console.print(f"\n[green]Results saved to {output_dir}[/green]")
@app.command()
def debug(
model: str = typer.Option("claude-haiku", "-m", "--model", help="Model config name"),
mode: str = typer.Option("native_augment", "--mode", help="Evaluation mode"),
instance_id: str = typer.Option(..., "-i", "--instance", help="SWE-bench instance ID"),
subset: str = typer.Option("lite", "--subset", help="SWE-bench subset"),
split: str = typer.Option("dev", "--split"),
output: str = typer.Option(str(DEFAULT_OUTPUT_DIR / "debug"), "-o", "--output"),
):
"""Debug a single SWE-bench instance."""
from datasets import load_dataset
dataset_path = DATASET_MAPPING.get(subset, subset)
instances = {inst["instance_id"]: inst for inst in load_dataset(dataset_path, split=split)}
if instance_id not in instances:
console.print(f"[red]Instance '{instance_id}' not found in {subset}/{split}[/red]")
raise typer.Exit(1)
instance = instances[instance_id]
config = build_config(model, mode)
output_dir = Path(output)
console.print(f"\n[bold]Debug run:[/bold] {model} + {mode}")
console.print(f" Instance: {instance_id}")
console.print(f" Problem: {instance['problem_statement'][:200]}...\n")
result = process_instance(instance, config, output_dir, model, mode)
_print_summary([result], model, mode)
@app.command()
def list_configs():
"""List available model and mode configurations."""
console.print("\n[bold]Available Models:[/bold]")
for name in AVAILABLE_MODELS:
config = load_yaml_config(MODELS_DIR / f"{name}.yaml")
model_name = config.get("model", {}).get("model_name", "unknown")
console.print(f" {name:<20} {model_name}")
console.print("\n[bold]Available Modes:[/bold]")
for name in AVAILABLE_MODES:
config = load_yaml_config(MODES_DIR / f"{name}.yaml")
gn_mode = config.get("agent", {}).get("gitnexus_mode", "baseline")
console.print(f" {name:<20} gitnexus_mode={gn_mode}")
console.print(f"\n[bold]Matrix:[/bold] {len(AVAILABLE_MODELS)} models x {len(AVAILABLE_MODES)} modes = {len(AVAILABLE_MODELS) * len(AVAILABLE_MODES)} configurations")
# ─── Summary Output ────────────────────────────────────────────────────────
def _print_summary(results: list[dict], model: str, mode: str):
"""Print a summary table for a single run."""
if not results:
console.print("[yellow]No results to display[/yellow]")
return
table = Table(title=f"{model} + {mode}")
table.add_column("Metric", style="bold")
table.add_column("Value")
total = len(results)
completed = sum(1 for r in results if r.get("submission"))
total_cost = sum(r.get("cost", 0) for r in results)
total_calls = sum(r.get("n_calls", 0) for r in results)
table.add_row("Instances", str(total))
table.add_row("Completed", f"{completed}/{total}")
table.add_row("Total Cost", f"${total_cost:.4f}")
table.add_row("Total API Calls", str(total_calls))
table.add_row("Avg Cost/Instance", f"${total_cost / max(total, 1):.4f}")
table.add_row("Avg Calls/Instance", f"{total_calls / max(total, 1):.1f}")
# GitNexus-specific metrics
gn_tool_calls = sum(
r.get("gitnexus_metrics", {}).get("total_tool_calls", 0) for r in results
)
gn_augment_hits = sum(
r.get("gitnexus_metrics", {}).get("augmentation_hits", 0) for r in results
)
if gn_tool_calls > 0:
table.add_row("GitNexus Tool Calls", str(gn_tool_calls))
if gn_augment_hits > 0:
table.add_row("Augmentation Hits", str(gn_augment_hits))
console.print(table)
def _print_matrix_summary(all_results: dict[str, list[dict]]):
"""Print a comparative matrix summary."""
table = Table(title="Evaluation Matrix Summary")
table.add_column("Configuration", style="bold")
table.add_column("Instances")
table.add_column("Completed")
table.add_column("Cost")
table.add_column("API Calls")
table.add_column("GN Tools")
for run_id, results in sorted(all_results.items()):
total = len(results)
completed = sum(1 for r in results if r.get("submission"))
cost = sum(r.get("cost", 0) for r in results)
calls = sum(r.get("n_calls", 0) for r in results)
gn_calls = sum(r.get("gitnexus_metrics", {}).get("total_tool_calls", 0) for r in results)
table.add_row(
run_id,
str(total),
f"{completed}/{total}",
f"${cost:.2f}",
str(calls),
str(gn_calls) if gn_calls > 0 else "-",
)
console.print(table)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
app()
@@ -0,0 +1,11 @@
{
"name": "gitnexus",
"description": "Code intelligence powered by a knowledge graph. Provides execution flow tracing, blast radius analysis, and augmented search across your codebase.",
"version": "1.3.6",
"author": {
"name": "GitNexus"
},
"homepage": "https://github.com/abhigyanpatwari/GitNexus",
"repository": "https://github.com/abhigyanpatwari/GitNexus",
"keywords": ["code-intelligence", "knowledge-graph", "mcp", "static-analysis"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,231 @@
#!/usr/bin/env node
/**
* GitNexus Claude Code Plugin Hook
*
* PreToolUse — intercepts Grep/Glob/Bash searches and augments
* with graph context from the GitNexus index.
* PostToolUse — detects stale index after git mutations and notifies
* the agent to reindex.
*
* NOTE: SessionStart hooks are broken on Windows (Claude Code bug #23576).
* Session context is injected via CLAUDE.md / skills instead.
*/
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
/**
* Read JSON input from stdin synchronously.
*/
function readInput() {
try {
const data = fs.readFileSync(0, 'utf-8');
return JSON.parse(data);
} catch {
return {};
}
}
/**
* Find the .gitnexus directory by walking up from startDir.
* Returns the path to .gitnexus/ or null if not found.
*/
function findGitNexusDir(startDir) {
let dir = startDir || process.cwd();
for (let i = 0; i < 5; i++) {
const candidate = path.join(dir, '.gitnexus');
if (fs.existsSync(candidate)) return candidate;
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}
/**
* Extract search pattern from tool input.
*/
function extractPattern(toolName, toolInput) {
if (toolName === 'Grep') {
return toolInput.pattern || null;
}
if (toolName === 'Glob') {
const raw = toolInput.pattern || '';
const match = raw.match(/[*\/]([a-zA-Z][a-zA-Z0-9_-]{2,})/);
return match ? match[1] : null;
}
if (toolName === 'Bash') {
const cmd = toolInput.command || '';
if (!/\brg\b|\bgrep\b/.test(cmd)) return null;
const tokens = cmd.split(/\s+/);
let foundCmd = false;
let skipNext = false;
const flagsWithValues = new Set(['-e', '-f', '-m', '-A', '-B', '-C', '-g', '--glob', '-t', '--type', '--include', '--exclude']);
for (const token of tokens) {
if (skipNext) { skipNext = false; continue; }
if (!foundCmd) {
if (/\brg$|\bgrep$/.test(token)) foundCmd = true;
continue;
}
if (token.startsWith('-')) {
if (flagsWithValues.has(token)) skipNext = true;
continue;
}
const cleaned = token.replace(/['"]/g, '');
return cleaned.length >= 3 ? cleaned : null;
}
return null;
}
return null;
}
/**
* Spawn a gitnexus CLI command synchronously.
* Detects binary on PATH once, then runs exactly once.
*
* SECURITY: Never use shell: true with user-controlled arguments.
* On Windows, invoke gitnexus.cmd directly (no shell needed).
*/
function runGitNexusCli(args, cwd, timeout) {
const isWin = process.platform === 'win32';
// Detect whether 'gitnexus' is on PATH (cheap check, no execution)
let useDirectBinary = false;
try {
const which = spawnSync(
isWin ? 'where' : 'which', ['gitnexus'],
{ encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }
);
useDirectBinary = which.status === 0;
} catch { /* not on PATH */ }
if (useDirectBinary) {
return spawnSync(
isWin ? 'gitnexus.cmd' : 'gitnexus', args,
{ encoding: 'utf-8', timeout, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
);
}
// npx fallback needs shell on Windows since npx is a .cmd script
return spawnSync(
isWin ? 'npx.cmd' : 'npx', ['-y', 'gitnexus', ...args],
{ encoding: 'utf-8', timeout: timeout + 5000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
);
}
/**
* Emit a hook response with additional context for the agent.
*/
function sendHookResponse(hookEventName, message) {
console.log(JSON.stringify({
hookSpecificOutput: { hookEventName, additionalContext: message }
}));
}
/**
* PreToolUse handler — augment searches with graph context.
*/
function handlePreToolUse(input) {
const cwd = input.cwd || process.cwd();
if (!path.isAbsolute(cwd)) return;
if (!findGitNexusDir(cwd)) return;
const toolName = input.tool_name || '';
const toolInput = input.tool_input || {};
if (toolName !== 'Grep' && toolName !== 'Glob' && toolName !== 'Bash') return;
const pattern = extractPattern(toolName, toolInput);
if (!pattern || pattern.length < 3) return;
let result = '';
try {
const child = runGitNexusCli(['augment', '--', pattern], cwd, 7000);
if (!child.error && child.status === 0) {
result = child.stderr || '';
}
} catch { /* graceful failure */ }
if (result && result.trim()) {
sendHookResponse('PreToolUse', result.trim());
}
}
/**
* PostToolUse handler — detect index staleness after git mutations.
*
* Instead of spawning a full `gitnexus analyze` synchronously (which blocks
* the agent for up to 120s and risks LadybugDB corruption on timeout), we do a
* lightweight staleness check: compare `git rev-parse HEAD` against the
* lastCommit stored in `.gitnexus/meta.json`. If they differ, notify the
* agent so it can decide when to reindex.
*/
function handlePostToolUse(input) {
const toolName = input.tool_name || '';
if (toolName !== 'Bash') return;
const command = (input.tool_input || {}).command || '';
if (!/\bgit\s+(commit|merge|rebase|cherry-pick|pull)(\s|$)/.test(command)) return;
// Only proceed if the command succeeded
const toolOutput = input.tool_output || {};
if (toolOutput.exit_code !== undefined && toolOutput.exit_code !== 0) return;
const cwd = input.cwd || process.cwd();
if (!path.isAbsolute(cwd)) return;
const gitNexusDir = findGitNexusDir(cwd);
if (!gitNexusDir) return;
// Compare HEAD against last indexed commit — skip if unchanged
let currentHead = '';
try {
const headResult = spawnSync('git', ['rev-parse', 'HEAD'], {
encoding: 'utf-8', timeout: 3000, cwd, stdio: ['pipe', 'pipe', 'pipe'],
});
currentHead = (headResult.stdout || '').trim();
} catch { return; }
if (!currentHead) return;
let lastCommit = '';
let hadEmbeddings = false;
try {
const meta = JSON.parse(fs.readFileSync(path.join(gitNexusDir, 'meta.json'), 'utf-8'));
lastCommit = meta.lastCommit || '';
hadEmbeddings = (meta.stats && meta.stats.embeddings > 0);
} catch { /* no meta — treat as stale */ }
// If HEAD matches last indexed commit, no reindex needed
if (currentHead && currentHead === lastCommit) return;
const analyzeCmd = `npx gitnexus analyze${hadEmbeddings ? ' --embeddings' : ''}`;
sendHookResponse('PostToolUse',
`GitNexus index is stale (last indexed: ${lastCommit ? lastCommit.slice(0, 7) : 'never'}). ` +
`Run \`${analyzeCmd}\` to update the knowledge graph.`
);
}
// Dispatch map for hook events
const handlers = {
PreToolUse: handlePreToolUse,
PostToolUse: handlePostToolUse,
};
function main() {
try {
const input = readInput();
const handler = handlers[input.hook_event_name || ''];
if (handler) handler(input);
} catch (err) {
if (process.env.GITNEXUS_DEBUG) {
console.error('GitNexus hook error:', (err.message || '').slice(0, 200));
}
}
}
main();
+30
View File
@@ -0,0 +1,30 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Grep|Glob|Bash",
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/gitnexus-hook.js",
"timeout": 10,
"statusMessage": "Enriching with GitNexus graph context..."
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/gitnexus-hook.js",
"timeout": 10,
"statusMessage": "Checking GitNexus index freshness..."
}
]
}
]
}
}
@@ -0,0 +1,82 @@
---
name: gitnexus-cli
description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\""
---
# GitNexus CLI Commands
All commands work via `npx` — no global install required.
## Commands
### analyze — Build or refresh the index
```bash
npx gitnexus analyze
```
Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
| Flag | Effect |
|------|--------|
| `--force` | Force full re-index even if up to date |
| `--embeddings` | Enable embedding generation for semantic search (off by default) |
**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale.
### status — Check index freshness
```bash
npx gitnexus status
```
Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed.
### clean — Delete the index
```bash
npx gitnexus clean
```
Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project.
| Flag | Effect |
|------|--------|
| `--force` | Skip confirmation prompt |
| `--all` | Clean all indexed repos, not just the current one |
### wiki — Generate documentation from the graph
```bash
npx gitnexus wiki
```
Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use).
| Flag | Effect |
|------|--------|
| `--force` | Force full regeneration |
| `--model <model>` | LLM model (default: minimax/minimax-m2.5) |
| `--base-url <url>` | LLM API base URL |
| `--api-key <key>` | LLM API key |
| `--concurrency <n>` | Parallel LLM calls (default: 3) |
| `--gist` | Publish wiki as a public GitHub Gist |
### list — Show all indexed repos
```bash
npx gitnexus list
```
Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information.
## After Indexing
1. **Read `gitnexus://repo/{name}/context`** to verify the index loaded
2. Use the other GitNexus skills (`exploring`, `debugging`, `impact-analysis`, `refactoring`) for your task
## Troubleshooting
- **"Not inside a git repository"**: Run from a directory inside a git repo
- **Index is stale after re-analyzing**: Restart Claude Code to reload the MCP server
- **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,89 @@
---
name: gitnexus-debugging
description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\""
---
# Debugging with GitNexus
## When to Use
- "Why is this function failing?"
- "Trace where this error comes from"
- "Who calls this method?"
- "This endpoint returns 500"
- Investigating bugs, errors, or unexpected behavior
## Workflow
```
1. gitnexus_query({query: "<error or symptom>"}) → Find related execution flows
2. gitnexus_context({name: "<suspect>"}) → See callers/callees/processes
3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow
4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] Understand the symptom (error message, unexpected behavior)
- [ ] gitnexus_query for error text or related code
- [ ] Identify the suspect function from returned processes
- [ ] gitnexus_context to see callers and callees
- [ ] Trace execution flow via process resource if applicable
- [ ] gitnexus_cypher for custom call chain traces if needed
- [ ] Read source files to confirm root cause
```
## Debugging Patterns
| Symptom | GitNexus Approach |
| -------------------- | ---------------------------------------------------------- |
| Error message | `gitnexus_query` for error text → `context` on throw sites |
| Wrong return value | `context` on the function → trace callees for data flow |
| Intermittent failure | `context` → look for external calls, async deps |
| Performance issue | `context` → find symbols with many callers (hot paths) |
| Recent regression | `detect_changes` to see what your changes affect |
## Tools
**gitnexus_query** — find code related to error:
```
gitnexus_query({query: "payment validation error"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError, PaymentException
```
**gitnexus_context** — full context for a suspect:
```
gitnexus_context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates (external API!)
→ Processes: CheckoutFlow (step 3/7)
```
**gitnexus_cypher** — custom call chain traces:
```cypher
MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"})
RETURN [n IN nodes(path) | n.name] AS chain
```
## Example: "Payment endpoint returns 500 intermittently"
```
1. gitnexus_query({query: "payment error handling"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError
2. gitnexus_context({name: "validatePayment"})
→ Outgoing calls: verifyCard, fetchRates (external API!)
3. READ gitnexus://repo/my-app/process/CheckoutFlow
→ Step 3: validatePayment → calls fetchRates (external)
4. Root cause: fetchRates calls external API without proper timeout
```
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,78 @@
---
name: gitnexus-exploring
description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\""
---
# Exploring Codebases with GitNexus
## When to Use
- "How does authentication work?"
- "What's the project structure?"
- "Show me the main components"
- "Where is the database logic?"
- Understanding code you haven't seen before
## Workflow
```
1. READ gitnexus://repos → Discover indexed repos
2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness
3. gitnexus_query({query: "<what you want to understand>"}) → Find related execution flows
4. gitnexus_context({name: "<symbol>"}) → Deep dive on specific symbol
5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow
```
> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] READ gitnexus://repo/{name}/context
- [ ] gitnexus_query for the concept you want to understand
- [ ] Review returned processes (execution flows)
- [ ] gitnexus_context on key symbols for callers/callees
- [ ] READ process resource for full execution traces
- [ ] Read source files for implementation details
```
## Resources
| Resource | What you get |
| --------------------------------------- | ------------------------------------------------------- |
| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) |
| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) |
| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) |
## Tools
**gitnexus_query** — find execution flows related to a concept:
```
gitnexus_query({query: "payment processing"})
→ Processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Symbols grouped by flow with file locations
```
**gitnexus_context** — 360-degree view of a symbol:
```
gitnexus_context({name: "validateUser"})
→ Incoming calls: loginHandler, apiMiddleware
→ Outgoing calls: checkToken, getUserById
→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3)
```
## Example: "How does payment processing work?"
```
1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes
2. gitnexus_query({query: "payment processing"})
→ CheckoutFlow: processPayment → validateCard → chargeStripe
→ RefundFlow: initiateRefund → calculateRefund → processRefund
3. gitnexus_context({name: "processPayment"})
→ Incoming: checkoutHandler, webhookHandler
→ Outgoing: validateCard, chargeStripe, saveTransaction
4. Read src/payments/processor.ts for implementation details
```
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,64 @@
---
name: gitnexus-guide
description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\""
---
# GitNexus Guide
Quick reference for all GitNexus MCP tools, resources, and the knowledge graph schema.
## Always Start Here
For any task involving code understanding, debugging, impact analysis, or refactoring:
1. **Read `gitnexus://repo/{name}/context`** — codebase overview + check index freshness
2. **Match your task to a skill below** and **read that skill file**
3. **Follow the skill's workflow and checklist**
> If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first.
## Skills
| Task | Skill to read |
| -------------------------------------------- | ------------------- |
| Understand architecture / "How does X work?" | `gitnexus-exploring` |
| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` |
| Trace bugs / "Why is X failing?" | `gitnexus-debugging` |
| Rename / extract / split / refactor | `gitnexus-refactoring` |
| Tools, resources, schema reference | `gitnexus-guide` (this file) |
| Index, status, clean, wiki CLI commands | `gitnexus-cli` |
## Tools Reference
| Tool | What it gives you |
| ---------------- | ------------------------------------------------------------------------ |
| `query` | Process-grouped code intelligence — execution flows related to a concept |
| `context` | 360-degree symbol view — categorized refs, processes it participates in |
| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence |
| `detect_changes` | Git-diff impact — what do your current changes affect |
| `rename` | Multi-file coordinated rename with confidence-tagged edits |
| `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) |
| `list_repos` | Discover indexed repos |
## Resources Reference
Lightweight reads (~100-500 tokens) for navigation:
| Resource | Content |
| ---------------------------------------------- | ----------------------------------------- |
| `gitnexus://repo/{name}/context` | Stats, staleness check |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores |
| `gitnexus://repo/{name}/cluster/{clusterName}` | Area members |
| `gitnexus://repo/{name}/processes` | All execution flows |
| `gitnexus://repo/{name}/process/{processName}` | Step-by-step trace |
| `gitnexus://repo/{name}/schema` | Graph schema for Cypher |
## Graph Schema
**Nodes:** File, Function, Class, Interface, Method, Community, Process
**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
RETURN caller.name, caller.filePath
```
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,97 @@
---
name: gitnexus-impact-analysis
description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\""
---
# Impact Analysis with GitNexus
## When to Use
- "Is it safe to change this function?"
- "What will break if I modify X?"
- "Show me the blast radius"
- "Who uses this code?"
- Before making non-trivial code changes
- Before committing — to understand what your changes affect
## Workflow
```
1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this
2. READ gitnexus://repo/{name}/processes → Check affected execution flows
3. gitnexus_detect_changes() → Map current git changes to affected flows
4. Assess risk and report to user
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents
- [ ] Review d=1 items first (these WILL BREAK)
- [ ] Check high-confidence (>0.8) dependencies
- [ ] READ processes to check affected execution flows
- [ ] gitnexus_detect_changes() for pre-commit check
- [ ] Assess risk level and report to user
```
## Understanding Output
| Depth | Risk Level | Meaning |
| ----- | ---------------- | ------------------------ |
| d=1 | **WILL BREAK** | Direct callers/importers |
| d=2 | LIKELY AFFECTED | Indirect dependencies |
| d=3 | MAY NEED TESTING | Transitive effects |
## Risk Assessment
| Affected | Risk |
| ------------------------------ | -------- |
| <5 symbols, few processes | LOW |
| 5-15 symbols, 2-5 processes | MEDIUM |
| >15 symbols or many processes | HIGH |
| Critical path (auth, payments) | CRITICAL |
## Tools
**gitnexus_impact** — the primary tool for symbol blast radius:
```
gitnexus_impact({
target: "validateUser",
direction: "upstream",
minConfidence: 0.8,
maxDepth: 3
})
→ d=1 (WILL BREAK):
- loginHandler (src/auth/login.ts:42) [CALLS, 100%]
- apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- authRouter (src/routes/auth.ts:22) [CALLS, 95%]
```
**gitnexus_detect_changes** — git-diff based impact analysis:
```
gitnexus_detect_changes({scope: "staged"})
→ Changed: 5 symbols in 3 files
→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline
→ Risk: MEDIUM
```
## Example: "What breaks if I change validateUser?"
```
1. gitnexus_impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware (WILL BREAK)
→ d=2: authRouter, sessionManager (LIKELY AFFECTED)
2. READ gitnexus://repo/my-app/processes
→ LoginFlow and TokenRefresh touch validateUser
3. Risk: 2 direct callers, 2 processes = MEDIUM
```
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,163 @@
---
name: gitnexus-pr-review
description: "Use when the user wants to review a pull request, understand what a PR changes, assess risk of merging, or check for missing test coverage. Examples: \"Review this PR\", \"What does PR #42 change?\", \"Is this PR safe to merge?\""
---
# PR Review with GitNexus
## When to Use
- "Review this PR"
- "What does PR #42 change?"
- "Is this safe to merge?"
- "What's the blast radius of this PR?"
- "Are there missing tests for this PR?"
- Reviewing someone else's code changes before merge
## Workflow
```
1. gh pr diff <number> → Get the raw diff
2. gitnexus_detect_changes({scope: "compare", base_ref: "main"}) → Map diff to affected flows
3. For each changed symbol:
gitnexus_impact({target: "<symbol>", direction: "upstream"}) → Blast radius per change
4. gitnexus_context({name: "<key symbol>"}) → Understand callers/callees
5. READ gitnexus://repo/{name}/processes → Check affected execution flows
6. Summarize findings with risk assessment
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal before reviewing.
## Checklist
```
- [ ] Fetch PR diff (gh pr diff or git diff base...head)
- [ ] gitnexus_detect_changes to map changes to affected execution flows
- [ ] gitnexus_impact on each non-trivial changed symbol
- [ ] Review d=1 items (WILL BREAK) — are callers updated?
- [ ] gitnexus_context on key changed symbols to understand full picture
- [ ] Check if affected processes have test coverage
- [ ] Assess overall risk level
- [ ] Write review summary with findings
```
## Review Dimensions
| Dimension | How GitNexus Helps |
| --- | --- |
| **Correctness** | `context` shows callers — are they all compatible with the change? |
| **Blast radius** | `impact` shows d=1/d=2/d=3 dependents — anything missed? |
| **Completeness** | `detect_changes` shows all affected flows — are they all handled? |
| **Test coverage** | `impact({includeTests: true})` shows which tests touch changed code |
| **Breaking changes** | d=1 upstream items that aren't updated in the PR = potential breakage |
## Risk Assessment
| Signal | Risk |
| --- | --- |
| Changes touch <3 symbols, 0-1 processes | LOW |
| Changes touch 3-10 symbols, 2-5 processes | MEDIUM |
| Changes touch >10 symbols or many processes | HIGH |
| Changes touch auth, payments, or data integrity code | CRITICAL |
| d=1 callers exist outside the PR diff | Potential breakage — flag it |
## Tools
**gitnexus_detect_changes** — map PR diff to affected execution flows:
```
gitnexus_detect_changes({scope: "compare", base_ref: "main"})
→ Changed: 8 symbols in 4 files
→ Affected processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Risk: MEDIUM
```
**gitnexus_impact** — blast radius per changed symbol:
```
gitnexus_impact({target: "validatePayment", direction: "upstream"})
→ d=1 (WILL BREAK):
- processCheckout (src/checkout.ts:42) [CALLS, 100%]
- webhookHandler (src/webhooks.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- checkoutRouter (src/routes/checkout.ts:22) [CALLS, 95%]
```
**gitnexus_impact with tests** — check test coverage:
```
gitnexus_impact({target: "validatePayment", direction: "upstream", includeTests: true})
→ Tests that cover this symbol:
- validatePayment.test.ts [direct]
- checkout.integration.test.ts [via processCheckout]
```
**gitnexus_context** — understand a changed symbol's role:
```
gitnexus_context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates
→ Processes: CheckoutFlow (step 3/7), RefundFlow (step 1/5)
```
## Example: "Review PR #42"
```
1. gh pr diff 42 > /tmp/pr42.diff
→ 4 files changed: payments.ts, checkout.ts, types.ts, utils.ts
2. gitnexus_detect_changes({scope: "compare", base_ref: "main"})
→ Changed symbols: validatePayment, PaymentInput, formatAmount
→ Affected processes: CheckoutFlow, RefundFlow
→ Risk: MEDIUM
3. gitnexus_impact({target: "validatePayment", direction: "upstream"})
→ d=1: processCheckout, webhookHandler (WILL BREAK)
→ webhookHandler is NOT in the PR diff — potential breakage!
4. gitnexus_impact({target: "PaymentInput", direction: "upstream"})
→ d=1: validatePayment (in PR), createPayment (NOT in PR)
→ createPayment uses the old PaymentInput shape — breaking change!
5. gitnexus_context({name: "formatAmount"})
→ Called by 12 functions — but change is backwards-compatible (added optional param)
6. Review summary:
- MEDIUM risk — 3 changed symbols affect 2 execution flows
- BUG: webhookHandler calls validatePayment but isn't updated for new signature
- BUG: createPayment depends on PaymentInput type which changed
- OK: formatAmount change is backwards-compatible
- Tests: checkout.test.ts covers processCheckout path, but no webhook test
```
## Review Output Format
Structure your review as:
```markdown
## PR Review: <title>
**Risk: LOW / MEDIUM / HIGH / CRITICAL**
### Changes Summary
- <N> symbols changed across <M> files
- <P> execution flows affected
### Findings
1. **[severity]** Description of finding
- Evidence from GitNexus tools
- Affected callers/flows
### Missing Coverage
- Callers not updated in PR: ...
- Untested flows: ...
### Recommendation
APPROVE / REQUEST CHANGES / NEEDS DISCUSSION
```
@@ -0,0 +1,121 @@
---
name: gitnexus-refactoring
description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\""
---
# Refactoring with GitNexus
## When to Use
- "Rename this function safely"
- "Extract this into a module"
- "Split this service"
- "Move this to a new file"
- Any task involving renaming, extracting, splitting, or restructuring code
## Workflow
```
1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents
2. gitnexus_query({query: "X"}) → Find execution flows involving X
3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs
4. Plan update order: interfaces → implementations → callers → tests
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklists
### Rename Symbol
```
- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits
- [ ] Review graph edits (high confidence) and ast_search edits (review carefully)
- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits
- [ ] gitnexus_detect_changes() — verify only expected files changed
- [ ] Run tests for affected processes
```
### Extract Module
```
- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs
- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers
- [ ] Define new module interface
- [ ] Extract code, update imports
- [ ] gitnexus_detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
### Split Function/Service
```
- [ ] gitnexus_context({name: target}) — understand all callees
- [ ] Group callees by responsibility
- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update
- [ ] Create new functions/services
- [ ] Update callers
- [ ] gitnexus_detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
## Tools
**gitnexus_rename** — automated multi-file rename:
```
gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits across 8 files
→ 10 graph edits (high confidence), 2 ast_search edits (review)
→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}]
```
**gitnexus_impact** — map all dependents first:
```
gitnexus_impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware, testUtils
→ Affected Processes: LoginFlow, TokenRefresh
```
**gitnexus_detect_changes** — verify your changes after refactoring:
```
gitnexus_detect_changes({scope: "all"})
→ Changed: 8 files, 12 symbols
→ Affected processes: LoginFlow, TokenRefresh
→ Risk: MEDIUM
```
**gitnexus_cypher** — custom reference queries:
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"})
RETURN caller.name, caller.filePath ORDER BY caller.filePath
```
## Risk Rules
| Risk Factor | Mitigation |
| ------------------- | ----------------------------------------- |
| Many callers (>5) | Use gitnexus_rename for automated updates |
| Cross-area refs | Use detect_changes after to verify scope |
| String/dynamic refs | gitnexus_query to find them |
| External/public API | Version and deprecate properly |
## Example: Rename `validateUser` to `authenticateUser`
```
1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits: 10 graph (safe), 2 ast_search (review)
→ Files: validator.ts, login.ts, middleware.ts, config.json...
2. Review ast_search edits (config.json: dynamic reference!)
3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false})
→ Applied 12 edits across 8 files
4. gitnexus_detect_changes({scope: "all"})
→ Affected: LoginFlow, TokenRefresh
→ Risk: MEDIUM — run tests for these flows
```
@@ -0,0 +1,8 @@
{
"mcpServers": {
"gitnexus": {
"command": "npx",
"args": ["-y", "gitnexus@latest", "mcp"]
}
}
}
@@ -0,0 +1,50 @@
#!/bin/bash
# GitNexus beforeShellExecution hook for Cursor
# Receives JSON on stdin with { command, cwd, timeout }
# Returns JSON on stdout with { permission, agent_message }
#
# Extracts search pattern from grep/rg commands, runs gitnexus augment,
# and injects the enriched context via agent_message.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.command // empty' 2>/dev/null)
if [ -z "$COMMAND" ]; then
echo '{"permission":"allow"}'
exit 0
fi
# Skip non-search commands
case "$COMMAND" in
cd\ *|npm\ *|yarn\ *|pnpm\ *|git\ commit*|git\ push*|git\ pull*|mkdir\ *|rm\ *|cp\ *|mv\ *|echo\ *|cat\ *)
echo '{"permission":"allow"}'
exit 0
;;
esac
# Extract search pattern from rg/grep commands
PATTERN=""
if echo "$COMMAND" | grep -qE '\brg\b'; then
PATTERN=$(echo "$COMMAND" | sed -n "s/.*\brg\s\+\(--[^ ]*\s\+\)*['\"]\\?\([^'\";\| >]*\\).*/\2/p")
elif echo "$COMMAND" | grep -qE '\bgrep\b'; then
PATTERN=$(echo "$COMMAND" | sed -n "s/.*\bgrep\s\+\(-[^ ]*\s\+\)*['\"]\\?\([^'\";\| >]*\\).*/\2/p")
fi
if [ -z "$PATTERN" ] || [ ${#PATTERN} -lt 3 ]; then
echo '{"permission":"allow"}'
exit 0
fi
# Run gitnexus augment
RESULT=$(npx -y gitnexus augment "$PATTERN" 2>/dev/null)
if [ -n "$RESULT" ]; then
# Escape for JSON
ESCAPED=$(echo "$RESULT" | jq -Rs .)
echo "{\"permission\":\"allow\",\"agent_message\":$ESCAPED}"
else
echo '{"permission":"allow"}'
fi
exit 0
@@ -0,0 +1,12 @@
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": "./hooks/augment-shell.sh",
"timeout": 5,
"matcher": "\\brg\\b|\\bgrep\\b"
}
]
}
}
@@ -0,0 +1,85 @@
---
name: gitnexus-debugging
description: Trace bugs through call chains using knowledge graph
---
# Debugging with GitNexus
## When to Use
- "Why is this function failing?"
- "Trace where this error comes from"
- "Who calls this method?"
- "This endpoint returns 500"
- Investigating bugs, errors, or unexpected behavior
## Workflow
```
1. gitnexus_query({query: "<error or symptom>"}) → Find related execution flows
2. gitnexus_context({name: "<suspect>"}) → See callers/callees/processes
3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow
4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] Understand the symptom (error message, unexpected behavior)
- [ ] gitnexus_query for error text or related code
- [ ] Identify the suspect function from returned processes
- [ ] gitnexus_context to see callers and callees
- [ ] Trace execution flow via process resource if applicable
- [ ] gitnexus_cypher for custom call chain traces if needed
- [ ] Read source files to confirm root cause
```
## Debugging Patterns
| Symptom | GitNexus Approach |
|---------|-------------------|
| Error message | `gitnexus_query` for error text → `context` on throw sites |
| Wrong return value | `context` on the function → trace callees for data flow |
| Intermittent failure | `context` → look for external calls, async deps |
| Performance issue | `context` → find symbols with many callers (hot paths) |
| Recent regression | `detect_changes` to see what your changes affect |
## Tools
**gitnexus_query** — find code related to error:
```
gitnexus_query({query: "payment validation error"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError, PaymentException
```
**gitnexus_context** — full context for a suspect:
```
gitnexus_context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates (external API!)
→ Processes: CheckoutFlow (step 3/7)
```
**gitnexus_cypher** — custom call chain traces:
```cypher
MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"})
RETURN [n IN nodes(path) | n.name] AS chain
```
## Example: "Payment endpoint returns 500 intermittently"
```
1. gitnexus_query({query: "payment error handling"})
→ Processes: CheckoutFlow, ErrorHandling
→ Symbols: validatePayment, handlePaymentError
2. gitnexus_context({name: "validatePayment"})
→ Outgoing calls: verifyCard, fetchRates (external API!)
3. READ gitnexus://repo/my-app/process/CheckoutFlow
→ Step 3: validatePayment → calls fetchRates (external)
4. Root cause: fetchRates calls external API without proper timeout
```
@@ -0,0 +1,75 @@
---
name: gitnexus-exploring
description: Navigate unfamiliar code using GitNexus knowledge graph
---
# Exploring Codebases with GitNexus
## When to Use
- "How does authentication work?"
- "What's the project structure?"
- "Show me the main components"
- "Where is the database logic?"
- Understanding code you haven't seen before
## Workflow
```
1. READ gitnexus://repos → Discover indexed repos
2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness
3. gitnexus_query({query: "<what you want to understand>"}) → Find related execution flows
4. gitnexus_context({name: "<symbol>"}) → Deep dive on specific symbol
5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow
```
> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] READ gitnexus://repo/{name}/context
- [ ] gitnexus_query for the concept you want to understand
- [ ] Review returned processes (execution flows)
- [ ] gitnexus_context on key symbols for callers/callees
- [ ] READ process resource for full execution traces
- [ ] Read source files for implementation details
```
## Resources
| Resource | What you get |
|----------|-------------|
| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) |
| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) |
| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) |
| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) |
## Tools
**gitnexus_query** — find execution flows related to a concept:
```
gitnexus_query({query: "payment processing"})
→ Processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Symbols grouped by flow with file locations
```
**gitnexus_context** — 360-degree view of a symbol:
```
gitnexus_context({name: "validateUser"})
→ Incoming calls: loginHandler, apiMiddleware
→ Outgoing calls: checkToken, getUserById
→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3)
```
## Example: "How does payment processing work?"
```
1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes
2. gitnexus_query({query: "payment processing"})
→ CheckoutFlow: processPayment → validateCard → chargeStripe
→ RefundFlow: initiateRefund → calculateRefund → processRefund
3. gitnexus_context({name: "processPayment"})
→ Incoming: checkoutHandler, webhookHandler
→ Outgoing: validateCard, chargeStripe, saveTransaction
4. Read src/payments/processor.ts for implementation details
```
@@ -0,0 +1,94 @@
---
name: gitnexus-impact-analysis
description: Analyze blast radius before making code changes
---
# Impact Analysis with GitNexus
## When to Use
- "Is it safe to change this function?"
- "What will break if I modify X?"
- "Show me the blast radius"
- "Who uses this code?"
- Before making non-trivial code changes
- Before committing — to understand what your changes affect
## Workflow
```
1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this
2. READ gitnexus://repo/{name}/processes → Check affected execution flows
3. gitnexus_detect_changes() → Map current git changes to affected flows
4. Assess risk and report to user
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklist
```
- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents
- [ ] Review d=1 items first (these WILL BREAK)
- [ ] Check high-confidence (>0.8) dependencies
- [ ] READ processes to check affected execution flows
- [ ] gitnexus_detect_changes() for pre-commit check
- [ ] Assess risk level and report to user
```
## Understanding Output
| Depth | Risk Level | Meaning |
|-------|-----------|---------|
| d=1 | **WILL BREAK** | Direct callers/importers |
| d=2 | LIKELY AFFECTED | Indirect dependencies |
| d=3 | MAY NEED TESTING | Transitive effects |
## Risk Assessment
| Affected | Risk |
|----------|------|
| <5 symbols, few processes | LOW |
| 5-15 symbols, 2-5 processes | MEDIUM |
| >15 symbols or many processes | HIGH |
| Critical path (auth, payments) | CRITICAL |
## Tools
**gitnexus_impact** — the primary tool for symbol blast radius:
```
gitnexus_impact({
target: "validateUser",
direction: "upstream",
minConfidence: 0.8,
maxDepth: 3
})
→ d=1 (WILL BREAK):
- loginHandler (src/auth/login.ts:42) [CALLS, 100%]
- apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- authRouter (src/routes/auth.ts:22) [CALLS, 95%]
```
**gitnexus_detect_changes** — git-diff based impact analysis:
```
gitnexus_detect_changes({scope: "staged"})
→ Changed: 5 symbols in 3 files
→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline
→ Risk: MEDIUM
```
## Example: "What breaks if I change validateUser?"
```
1. gitnexus_impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware (WILL BREAK)
→ d=2: authRouter, sessionManager (LIKELY AFFECTED)
2. READ gitnexus://repo/my-app/processes
→ LoginFlow and TokenRefresh touch validateUser
3. Risk: 2 direct callers, 2 processes = MEDIUM
```
@@ -0,0 +1,163 @@
---
name: gitnexus-pr-review
description: "Use when the user wants to review a pull request, understand what a PR changes, assess risk of merging, or check for missing test coverage. Examples: \"Review this PR\", \"What does PR #42 change?\", \"Is this PR safe to merge?\""
---
# PR Review with GitNexus
## When to Use
- "Review this PR"
- "What does PR #42 change?"
- "Is this safe to merge?"
- "What's the blast radius of this PR?"
- "Are there missing tests for this PR?"
- Reviewing someone else's code changes before merge
## Workflow
```
1. gh pr diff <number> → Get the raw diff
2. gitnexus_detect_changes({scope: "compare", base_ref: "main"}) → Map diff to affected flows
3. For each changed symbol:
gitnexus_impact({target: "<symbol>", direction: "upstream"}) → Blast radius per change
4. gitnexus_context({name: "<key symbol>"}) → Understand callers/callees
5. READ gitnexus://repo/{name}/processes → Check affected execution flows
6. Summarize findings with risk assessment
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal before reviewing.
## Checklist
```
- [ ] Fetch PR diff (gh pr diff or git diff base...head)
- [ ] gitnexus_detect_changes to map changes to affected execution flows
- [ ] gitnexus_impact on each non-trivial changed symbol
- [ ] Review d=1 items (WILL BREAK) — are callers updated?
- [ ] gitnexus_context on key changed symbols to understand full picture
- [ ] Check if affected processes have test coverage
- [ ] Assess overall risk level
- [ ] Write review summary with findings
```
## Review Dimensions
| Dimension | How GitNexus Helps |
| --- | --- |
| **Correctness** | `context` shows callers — are they all compatible with the change? |
| **Blast radius** | `impact` shows d=1/d=2/d=3 dependents — anything missed? |
| **Completeness** | `detect_changes` shows all affected flows — are they all handled? |
| **Test coverage** | `impact({includeTests: true})` shows which tests touch changed code |
| **Breaking changes** | d=1 upstream items that aren't updated in the PR = potential breakage |
## Risk Assessment
| Signal | Risk |
| --- | --- |
| Changes touch <3 symbols, 0-1 processes | LOW |
| Changes touch 3-10 symbols, 2-5 processes | MEDIUM |
| Changes touch >10 symbols or many processes | HIGH |
| Changes touch auth, payments, or data integrity code | CRITICAL |
| d=1 callers exist outside the PR diff | Potential breakage — flag it |
## Tools
**gitnexus_detect_changes** — map PR diff to affected execution flows:
```
gitnexus_detect_changes({scope: "compare", base_ref: "main"})
→ Changed: 8 symbols in 4 files
→ Affected processes: CheckoutFlow, RefundFlow, WebhookHandler
→ Risk: MEDIUM
```
**gitnexus_impact** — blast radius per changed symbol:
```
gitnexus_impact({target: "validatePayment", direction: "upstream"})
→ d=1 (WILL BREAK):
- processCheckout (src/checkout.ts:42) [CALLS, 100%]
- webhookHandler (src/webhooks.ts:15) [CALLS, 100%]
→ d=2 (LIKELY AFFECTED):
- checkoutRouter (src/routes/checkout.ts:22) [CALLS, 95%]
```
**gitnexus_impact with tests** — check test coverage:
```
gitnexus_impact({target: "validatePayment", direction: "upstream", includeTests: true})
→ Tests that cover this symbol:
- validatePayment.test.ts [direct]
- checkout.integration.test.ts [via processCheckout]
```
**gitnexus_context** — understand a changed symbol's role:
```
gitnexus_context({name: "validatePayment"})
→ Incoming calls: processCheckout, webhookHandler
→ Outgoing calls: verifyCard, fetchRates
→ Processes: CheckoutFlow (step 3/7), RefundFlow (step 1/5)
```
## Example: "Review PR #42"
```
1. gh pr diff 42 > /tmp/pr42.diff
→ 4 files changed: payments.ts, checkout.ts, types.ts, utils.ts
2. gitnexus_detect_changes({scope: "compare", base_ref: "main"})
→ Changed symbols: validatePayment, PaymentInput, formatAmount
→ Affected processes: CheckoutFlow, RefundFlow
→ Risk: MEDIUM
3. gitnexus_impact({target: "validatePayment", direction: "upstream"})
→ d=1: processCheckout, webhookHandler (WILL BREAK)
→ webhookHandler is NOT in the PR diff — potential breakage!
4. gitnexus_impact({target: "PaymentInput", direction: "upstream"})
→ d=1: validatePayment (in PR), createPayment (NOT in PR)
→ createPayment uses the old PaymentInput shape — breaking change!
5. gitnexus_context({name: "formatAmount"})
→ Called by 12 functions — but change is backwards-compatible (added optional param)
6. Review summary:
- MEDIUM risk — 3 changed symbols affect 2 execution flows
- BUG: webhookHandler calls validatePayment but isn't updated for new signature
- BUG: createPayment depends on PaymentInput type which changed
- OK: formatAmount change is backwards-compatible
- Tests: checkout.test.ts covers processCheckout path, but no webhook test
```
## Review Output Format
Structure your review as:
```markdown
## PR Review: <title>
**Risk: LOW / MEDIUM / HIGH / CRITICAL**
### Changes Summary
- <N> symbols changed across <M> files
- <P> execution flows affected
### Findings
1. **[severity]** Description of finding
- Evidence from GitNexus tools
- Affected callers/flows
### Missing Coverage
- Callers not updated in PR: ...
- Untested flows: ...
### Recommendation
APPROVE / REQUEST CHANGES / NEEDS DISCUSSION
```
@@ -0,0 +1,113 @@
---
name: gitnexus-refactoring
description: Plan safe refactors using blast radius and dependency mapping
---
# Refactoring with GitNexus
## When to Use
- "Rename this function safely"
- "Extract this into a module"
- "Split this service"
- "Move this to a new file"
- Any task involving renaming, extracting, splitting, or restructuring code
## Workflow
```
1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents
2. gitnexus_query({query: "X"}) → Find execution flows involving X
3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs
4. Plan update order: interfaces → implementations → callers → tests
```
> If "Index is stale" → run `npx gitnexus analyze` in terminal.
## Checklists
### Rename Symbol
```
- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits
- [ ] Review graph edits (high confidence) and ast_search edits (review carefully)
- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits
- [ ] gitnexus_detect_changes() — verify only expected files changed
- [ ] Run tests for affected processes
```
### Extract Module
```
- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs
- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers
- [ ] Define new module interface
- [ ] Extract code, update imports
- [ ] gitnexus_detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
### Split Function/Service
```
- [ ] gitnexus_context({name: target}) — understand all callees
- [ ] Group callees by responsibility
- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update
- [ ] Create new functions/services
- [ ] Update callers
- [ ] gitnexus_detect_changes() — verify affected scope
- [ ] Run tests for affected processes
```
## Tools
**gitnexus_rename** — automated multi-file rename:
```
gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits across 8 files
→ 10 graph edits (high confidence), 2 ast_search edits (review)
→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}]
```
**gitnexus_impact** — map all dependents first:
```
gitnexus_impact({target: "validateUser", direction: "upstream"})
→ d=1: loginHandler, apiMiddleware, testUtils
→ Affected Processes: LoginFlow, TokenRefresh
```
**gitnexus_detect_changes** — verify your changes after refactoring:
```
gitnexus_detect_changes({scope: "all"})
→ Changed: 8 files, 12 symbols
→ Affected processes: LoginFlow, TokenRefresh
→ Risk: MEDIUM
```
**gitnexus_cypher** — custom reference queries:
```cypher
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"})
RETURN caller.name, caller.filePath ORDER BY caller.filePath
```
## Risk Rules
| Risk Factor | Mitigation |
|-------------|------------|
| Many callers (>5) | Use gitnexus_rename for automated updates |
| Cross-area refs | Use detect_changes after to verify scope |
| String/dynamic refs | gitnexus_query to find them |
| External/public API | Version and deprecate properly |
## Example: Rename `validateUser` to `authenticateUser`
```
1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true})
→ 12 edits: 10 graph (safe), 2 ast_search (review)
→ Files: validator.ts, login.ts, middleware.ts, config.json...
2. Review ast_search edits (config.json: dynamic reference!)
3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false})
→ Applied 12 edits across 8 files
4. gitnexus_detect_changes({scope: "all"})
→ Affected: LoginFlow, TokenRefresh
→ Risk: MEDIUM — run tests for these flows
```
-1994
View File
File diff suppressed because it is too large Load Diff
-47
View File
@@ -1,47 +0,0 @@
{
"name": "gitnexus-mcp",
"version": "0.1.1",
"description": "MCP server for GitNexus code intelligence - connect Cursor, Claude, and other AI agents to your codebase",
"author": "Abhigyan Patwari",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/abhigyanpatwari/GitNexus"
},
"keywords": [
"mcp",
"model-context-protocol",
"code-intelligence",
"cursor",
"claude",
"ai-agent",
"gitnexus"
],
"type": "module",
"bin": {
"gitnexus-mcp": "./dist/cli.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"dev": "tsx watch src/cli.ts",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"uuid": "^13.0.0",
"ws": "^8.16.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.10",
"tsx": "^4.0.0",
"typescript": "^5.4.0"
},
"engines": {
"node": ">=18.0.0"
}
}
-36
View File
@@ -1,36 +0,0 @@
/**
* Bridge Protocol Types
*
* JSON-RPC-like protocol for communication between bridge and browser.
*/
export interface BridgeMessage {
id: string;
type?: 'register_peer' | 'tool_call' | 'tool_result' | 'agent_info' | 'handshake' | 'handshake_ack' | 'context';
method?: string;
params?: any;
result?: any;
error?: {
code?: number;
message: string;
};
agentName?: string;
peerId?: string;
}
export type ToolCallRequest = BridgeMessage & { method: string };
export type ToolCallResponse = BridgeMessage & ({ result: any } | { error: any });
/**
* Check if message is a request (has method)
*/
export function isRequest(msg: BridgeMessage): msg is ToolCallRequest {
return typeof msg.method === 'string';
}
/**
* Check if message is a response (has result or error)
*/
export function isResponse(msg: BridgeMessage): msg is ToolCallResponse {
return 'result' in msg || 'error' in msg;
}
-397
View File
@@ -1,397 +0,0 @@
import { WebSocketServer, WebSocket } from 'ws';
import { createServer as createNetServer } from 'net';
import { BridgeMessage, isRequest, isResponse } from './protocol.js';
import { v4 as uuidv4 } from 'uuid';
/**
* Codebase context sent from the GitNexus browser app
*/
export interface CodebaseContext {
projectName: string;
stats: {
fileCount: number;
functionCount: number;
classCount: number;
interfaceCount: number;
methodCount: number;
};
hotspots: Array<{
name: string;
type: string;
filePath: string;
connections: number;
}>;
folderTree: string;
}
/**
* Check if a Port is available
*/
async function isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = createNetServer();
server.once('error', () => resolve(false));
server.once('listening', () => {
server.close();
resolve(true);
});
server.listen(port);
});
}
export class WebSocketBridge {
private wss: WebSocketServer | null = null; // Used if we are the Hub
private client: WebSocket | null = null; // Used if we are a Peer (connecting to Hub), OR if we are Hub (clients connecting to us)
// Hub State
private browserClient: WebSocket | null = null;
private peerClients: Map<string, WebSocket> = new Map();
// Common State
private pendingRequests: Map<string, { resolve: (val: any) => void, reject: (err: any) => void }> = new Map();
private requestId = 0;
private started = false;
private _context: any | null = null; // CodebaseContext
private contextListeners: Set<(context: any | null) => void> = new Set();
private agentName: string;
private isHub = false;
private port = 54319;
constructor(port: number = 54319, agentName?: string) {
this.port = port;
this.agentName = agentName || process.env.GITNEXUS_AGENT || this.detectAgent();
}
private detectAgent(): string {
if (process.env.CURSOR_SESSION_ID) return 'Cursor';
if (process.env.CLAUDE_CODE) return 'Claude Code';
if (process.env.WINDSURF_SESSION) return 'Windsurf';
return 'Unknown Agent';
}
async start(): Promise<boolean> {
const available = await isPortAvailable(this.port);
if (available) {
return this.startAsHub();
} else {
return this.startAsPeer();
}
}
// -------------------------------------------------------------------------
// Hub Implementation (Master)
// -------------------------------------------------------------------------
private async startAsHub(): Promise<boolean> {
console.error(`Starting as MCP Hub on port ${this.port}`);
this.isHub = true;
return new Promise((resolve) => {
this.wss = new WebSocketServer({ port: this.port });
this.wss.on('connection', (ws, req) => {
// Security: Origin check could go here if req.headers.origin available
ws.on('message', (data) => this.handleHubMessage(ws, data));
ws.on('close', () => this.handleHubDisconnect(ws));
ws.on('error', (err) => console.error('Hub client error:', err));
});
this.wss.on('listening', () => {
this.started = true;
resolve(true);
});
this.wss.on('error', (err) => {
console.error('Hub server error:', err);
resolve(false);
});
});
}
private handleHubMessage(ws: WebSocket, data: any) {
try {
const msg: BridgeMessage = JSON.parse(data.toString());
if (msg.type === 'handshake') {
// Peer verifying we are GitNexus
ws.send(JSON.stringify({ type: 'handshake_ack', id: msg.id }));
return;
}
if (msg.type === 'register_peer') {
// Peer registering itself
const peerId = uuidv4();
this.peerClients.set(peerId, ws);
(ws as any).peerId = peerId;
(ws as any).agentName = msg.agentName;
console.error(`Peer connected: ${msg.agentName} (${peerId})`);
// Forward current context to new peer if available
if (this._context) {
ws.send(JSON.stringify({ type: 'context', params: this._context }));
}
return;
}
// Handle Context updates (from Browser)
if (msg.type === 'context') {
// Browser identified itself (implicitly)
if (this.browserClient !== ws) {
if (this.browserClient) this.browserClient.close();
this.browserClient = ws;
console.error('Browser connected to Hub');
}
this._context = msg.params;
this.notifyContextListeners();
// Broadcast context to all peers
this.broadcastToPeers(msg);
return;
}
// Handle Tool Calls (Peer/Hub -> Browser)
if (isRequest(msg)) {
// If it came from a ws client (Peer), validation needed?
// We assume it's destined for the Browser
if (this.browserClient && this.browserClient.readyState === WebSocket.OPEN) {
// Attach agent info if missing (for UI)
if (!msg.agentName && (ws as any).agentName) {
msg.agentName = (ws as any).agentName;
}
// Attach peerId so we can route response back
if (!msg.peerId && (ws as any).peerId) {
msg.peerId = (ws as any).peerId;
}
this.browserClient.send(JSON.stringify(msg));
} else {
// Browser not connected, fail
if (msg.id) {
ws.send(JSON.stringify({
id: msg.id,
error: { message: "Browser not connected. Open GitNexus." }
}));
}
}
return;
}
// Handle Tool Results (Browser -> Peer/Hub)
if (isResponse(msg)) {
// Route to the correct peer
if (msg.peerId && this.peerClients.has(msg.peerId)) {
const peer = this.peerClients.get(msg.peerId);
if (peer?.readyState === WebSocket.OPEN) {
peer.send(JSON.stringify(msg));
}
} else {
// It might be for Us (the Hub)
this.handleResponseLocal(msg);
}
return;
}
} catch (e) {
console.error('Hub: Failed to parse message', e);
}
}
private handleHubDisconnect(ws: WebSocket) {
if (ws === this.browserClient) {
console.error('Browser disconnected from Hub');
this.browserClient = null;
this._context = null;
this.notifyContextListeners();
} else {
const peerId = (ws as any).peerId;
if (peerId) {
this.peerClients.delete(peerId);
console.error(`Peer disconnected: ${peerId}`);
}
}
}
private broadcastToPeers(msg: any) {
for (const client of this.peerClients.values()) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(msg));
}
}
}
// -------------------------------------------------------------------------
// Peer Implementation (Spoke)
// -------------------------------------------------------------------------
private async startAsPeer(): Promise<boolean> {
console.error(`Port ${this.port} busy. Attempting to connect as Peer...`);
return new Promise((resolve) => {
const ws = new WebSocket(`ws://localhost:${this.port}`);
const timeout = setTimeout(() => {
console.error('Handshake timeout. Port is busy by unknown app.');
ws.close();
resolve(false);
}, 1000);
ws.on('open', () => {
// Send Handshake
ws.send(JSON.stringify({ type: 'handshake', id: 'init' }));
});
ws.on('message', (data) => {
try {
const msg = JSON.parse(data.toString());
// Handshake success?
if (msg.type === 'handshake_ack') {
clearTimeout(timeout);
console.error('Handshake successful. Joining as Peer.');
// Register ourselves
ws.send(JSON.stringify({
type: 'register_peer',
agentName: this.agentName
}));
this.client = ws;
this.started = true;
resolve(true);
return;
}
// Normal messages from Hub
this.handlePeerMessage(msg);
} catch (e) {
// ignore garbage
}
});
ws.on('error', (err) => {
console.error('Peer connection error:', err);
resolve(false);
});
// If connection fails immediately
ws.on('close', () => {
if (!this.started) resolve(false);
else {
this.client = null;
this._context = null;
this.notifyContextListeners();
}
});
});
}
private handlePeerMessage(msg: BridgeMessage) {
if (msg.type === 'context') {
this._context = msg.params;
this.notifyContextListeners();
return;
}
if (isResponse(msg)) {
this.handleResponseLocal(msg);
}
}
// -------------------------------------------------------------------------
// Shared / Public API
// -------------------------------------------------------------------------
private handleResponseLocal(msg: any) {
if (msg.id && this.pendingRequests.has(msg.id)) {
const { resolve, reject } = this.pendingRequests.get(msg.id)!;
this.pendingRequests.delete(msg.id);
if (msg.error) {
// We'll reject the promise so caller knows
reject(new Error(msg.error.message));
} else {
resolve(msg.result);
}
}
}
get isConnected(): boolean {
if (this.isHub) {
return this.browserClient !== null && this.browserClient.readyState === WebSocket.OPEN;
} else {
return this.client !== null && this.client.readyState === WebSocket.OPEN;
}
}
get context(): any {
return this._context;
}
onContextChange(listener: (context: any) => void) {
this.contextListeners.add(listener);
return () => this.contextListeners.delete(listener);
}
private notifyContextListeners() {
this.contextListeners.forEach((listener) => listener(this._context));
}
async callTool(method: string, params: any): Promise<any> {
if (!this.isConnected) {
if (this.isHub) throw new Error('GitNexus Browser not connected.');
else throw new Error('GitNexus Hub disonnected.');
}
const id = `req_${++this.requestId}`;
return new Promise((resolve, reject) => {
this.pendingRequests.set(id, { resolve, reject });
const msg: BridgeMessage = {
id,
method,
params,
agentName: this.agentName,
// type is implicitly request because of method
};
if (this.isHub) {
// Send directly to browser
if (this.browserClient && this.browserClient.readyState === WebSocket.OPEN) {
this.browserClient.send(JSON.stringify(msg));
} else {
this.pendingRequests.delete(id);
reject(new Error('Browser not connected'));
}
} else {
// Send to Hub (who forwards to browser)
if (this.client && this.client.readyState === WebSocket.OPEN) {
this.client.send(JSON.stringify(msg));
} else {
this.pendingRequests.delete(id);
reject(new Error('Hub disconnected'));
}
}
setTimeout(() => {
if (this.pendingRequests.has(id)) {
this.pendingRequests.delete(id);
reject(new Error('Request timeout'));
}
}, 30000);
});
}
close() {
this.wss?.close();
this.client?.close();
}
disconnect() {
this.close();
}
}
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env node
/**
* GitNexus MCP CLI
*
* Bridge between external AI agents (Cursor, Claude Code, Windsurf)
* and GitNexus code intelligence running in the browser.
*/
import { serveCommand } from './commands/serve.js';
/**
* Minimal CLI:
* - Default: start MCP stdio server + local browser WebSocket bridge
* - Optional: `serve` alias, and `--port <port>`
*
* This is designed for MCP clients (Cursor/Claude/Windsurf) which spawn this
* process automatically; users should not need to run commands manually.
*/
function parsePort(argv: string[]): string {
const portFlagIndex = argv.findIndex((a) => a === '--port' || a === '-p');
if (portFlagIndex !== -1) {
const value = argv[portFlagIndex + 1];
if (value) return value;
}
// Support `--port=54319`
const portEq = argv.find((a) => a.startsWith('--port='));
if (portEq) return portEq.split('=')[1] || '54319';
return '54319';
}
async function main() {
const argv = process.argv.slice(2);
const first = argv[0];
const port = parsePort(argv);
// Allow `gitnexus-mcp serve` for compatibility, but default to serve anyway
if (!first || first === 'serve') {
await serveCommand({ port });
return;
}
// Minimal help for unknown commands
if (first === '--help' || first === '-h') {
// eslint-disable-next-line no-console
console.log('gitnexus-mcp\n\nUsage:\n gitnexus-mcp [serve] [--port <port>]\n');
process.exit(0);
}
// eslint-disable-next-line no-console
console.error(`Unknown command: ${first}`);
// eslint-disable-next-line no-console
console.error('Usage: gitnexus-mcp [serve] [--port <port>]');
process.exit(1);
}
main().catch((err) => {
// eslint-disable-next-line no-console
console.error(err instanceof Error ? err.message : err);
process.exit(1);
});
-31
View File
@@ -1,31 +0,0 @@
/**
* Serve Command
*
* Starts the MCP server that bridges external AI agents to GitNexus.
* - Listens on stdio for MCP protocol (from AI tools)
* - Hosts a local WebSocket bridge for the GitNexus browser app
*/
import { startMCPServer } from '../mcp/server.js';
import { WebSocketBridge } from '../bridge/websocket-server.js';
interface ServeOptions {
port: string;
}
export async function serveCommand(options: ServeOptions) {
const port = parseInt(options.port, 10);
// Start local WebSocket bridge (browser connects to ws://localhost:<port>)
const client = new WebSocketBridge(port);
const started = await client.start();
if (!started) {
console.error(`Failed to start GitNexus browser bridge on port ${port}.`);
console.error('Another process is already using this port.');
process.exit(1);
}
// Start MCP server on stdio (AI tools connect here)
await startMCPServer(client);
}
-220
View File
@@ -1,220 +0,0 @@
/**
* MCP Server
*
* Model Context Protocol server that runs on stdio.
* External AI tools (Cursor, Claude Code) spawn this process and
* communicate via stdin/stdout using the MCP protocol.
*
* Exposes:
* - Tools: search, cypher, blastRadius, highlight
* - Resources: codebase context (stats, hotspots, folder tree)
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { GITNEXUS_TOOLS } from './tools.js';
import type { CodebaseContext } from '../bridge/websocket-server.js';
// Interface for anything that can call tools (DaemonClient or WebSocketBridge)
interface ToolCaller {
callTool(method: string, params: any): Promise<any>;
disconnect?(): void;
context?: CodebaseContext | null;
onContextChange?: (listener: (context: CodebaseContext | null) => void) => () => void;
}
/**
* Format context as markdown for the resource
*/
function formatContextAsMarkdown(context: CodebaseContext): string {
const { projectName, stats, hotspots, folderTree } = context;
const lines: string[] = [];
lines.push(`# GitNexus: ${projectName}`);
lines.push('');
lines.push('This codebase is currently loaded in GitNexus. Use the tools below to explore it.');
lines.push('');
// Stats
lines.push('## 📊 Statistics');
lines.push(`- **Files**: ${stats.fileCount}`);
lines.push(`- **Functions**: ${stats.functionCount}`);
if (stats.classCount > 0) lines.push(`- **Classes**: ${stats.classCount}`);
if (stats.interfaceCount > 0) lines.push(`- **Interfaces**: ${stats.interfaceCount}`);
if (stats.methodCount > 0) lines.push(`- **Methods**: ${stats.methodCount}`);
lines.push('');
// Hotspots
if (hotspots.length > 0) {
lines.push('## 🔥 Hotspots (Most Connected Nodes)');
lines.push('');
hotspots.forEach(h => {
lines.push(`- \`${h.name}\` (${h.type}) — ${h.connections} connections — ${h.filePath}`);
});
lines.push('');
}
// Folder tree
if (folderTree) {
lines.push('## 📁 Project Structure');
lines.push('```');
lines.push(projectName + '/');
lines.push(folderTree);
lines.push('```');
lines.push('');
}
// Usage hints
lines.push('## 🛠️ Available Tools');
lines.push('');
lines.push('- **search**: Semantic search across the codebase');
lines.push('- **cypher**: Execute Cypher queries on the knowledge graph');
lines.push('- **blastRadius**: Analyze impact of changes to a node');
lines.push('- **highlight**: Visualize nodes in the graph');
lines.push('');
lines.push('## 📝 Graph Schema');
lines.push('');
lines.push('**Node Types**: File, Folder, Function, Class, Interface, Method');
lines.push('');
lines.push('**Relation**: `CodeRelation` with `type` property:');
lines.push('- CONTAINS, DEFINES, IMPORTS, CALLS, EXTENDS, IMPLEMENTS');
lines.push('');
lines.push('**Example Cypher Queries**:');
lines.push('```cypher');
lines.push('MATCH (f:Function) RETURN f.name LIMIT 10');
lines.push("MATCH (f:File)-[:CodeRelation {type: 'IMPORTS'}]->(g:File) RETURN f.name, g.name");
lines.push('```');
return lines.join('\n');
}
export async function startMCPServer(client: ToolCaller): Promise<void> {
const server = new Server(
{
name: 'gitnexus',
version: '0.1.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
// Handle list resources request
server.setRequestHandler(ListResourcesRequestSchema, async () => {
const context = client.context;
if (!context) {
return { resources: [] };
}
return {
resources: [
{
uri: 'gitnexus://codebase/context',
name: `GitNexus: ${context.projectName}`,
description: `Codebase context for ${context.projectName} (${context.stats.fileCount} files, ${context.stats.functionCount} functions)`,
mimeType: 'text/markdown',
},
],
};
});
// Handle read resource request
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'gitnexus://codebase/context') {
const context = client.context;
if (!context) {
return {
contents: [
{
uri,
mimeType: 'text/plain',
text: 'No codebase loaded. Open GitNexus in your browser and load a repository.',
},
],
};
}
return {
contents: [
{
uri,
mimeType: 'text/markdown',
text: formatContextAsMarkdown(context),
},
],
};
}
throw new Error(`Unknown resource: ${uri}`);
});
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: GITNEXUS_TOOLS.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Forward the tool call to the browser via daemon
const result = await client.callTool(name, args);
return {
content: [
{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
return {
content: [
{
type: 'text',
text: `Error: ${message}`,
},
],
isError: true,
};
}
});
// Connect to stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
// Handle graceful shutdown
process.on('SIGINT', async () => {
client.disconnect?.();
await server.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
client.disconnect?.();
await server.close();
process.exit(0);
});
}
-181
View File
@@ -1,181 +0,0 @@
/**
* MCP Tool Definitions
*
* Defines the tools that GitNexus exposes to external AI agents.
* Each tool has a rich description with examples to help agents use them correctly.
*/
export interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, {
type: string;
description?: string;
default?: any;
items?: { type: string };
}>;
required: string[];
};
}
export const GITNEXUS_TOOLS: ToolDefinition[] = [
{
name: 'context',
description: `Get GitNexus codebase context. CALL THIS FIRST before using other tools.
Returns:
- Project name and stats (files, functions, classes)
- Hotspots (most connected/important nodes)
- Directory structure (TOON format for token efficiency)
- Tool usage guidance
ALWAYS call this first to understand the codebase before searching or querying.`,
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'search',
description: `Hybrid search (keyword + semantic) across the codebase.
Returns code nodes with their graph connections.
WHEN TO USE:
- Finding implementations ("where is auth handled?")
- Understanding code flow ("what calls UserService?")
- Locating patterns ("find all API endpoints")
RETURNS: Array of {name, type, filePath, code, connections[]}`,
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Natural language or keyword search query' },
limit: { type: 'number', description: 'Max results to return', default: 10 },
},
required: ['query'],
},
},
{
name: 'cypher',
description: `Execute Cypher query against the code knowledge graph.
SCHEMA:
- Nodes: File, Function, Class, Interface, Method
- Edges: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS
EXAMPLES:
• Find callers of a function:
MATCH (a)-[:CALLS]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
• Find class hierarchy:
MATCH (c:Class)-[:EXTENDS*]->(base) WHERE c.name = "AdminUser" RETURN base.name
• Impact analysis (what depends on X):
MATCH (target:Function {name: $name})<-[:CALLS*1..3]-(caller) RETURN DISTINCT caller
TIPS:
- Relationship types are UPPERCASE: CALLS, IMPORTS, EXTENDS
- Node labels are PascalCase: Function, Class, Interface
- Properties: name, filePath, code, startLine, endLine`,
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Cypher query to execute' },
},
required: ['query'],
},
},
{
name: 'grep',
description: `Regex search for exact patterns in file contents.
WHEN TO USE:
- Finding exact strings: error codes, TODOs, specific API keys
- Pattern matching: all console.log, all fetch calls
- Finding imports of specific modules
BETTER THAN search for: exact matches, regex patterns, case-sensitive
RETURNS: Array of {filePath, line, lineNumber, match}`,
inputSchema: {
type: 'object',
properties: {
pattern: { type: 'string', description: 'Regex pattern to search for' },
caseSensitive: { type: 'boolean', description: 'Case-sensitive search', default: false },
maxResults: { type: 'number', description: 'Max results to return', default: 50 },
},
required: ['pattern'],
},
},
{
name: 'read',
description: `Read file content from the codebase.
WHEN TO USE:
- After search/grep to see full context
- To understand implementation details
- Before making changes
ALWAYS read before concluding - don't guess from names alone.
RETURNS: {filePath, content, language, lines}`,
inputSchema: {
type: 'object',
properties: {
filePath: { type: 'string', description: 'Path to file to read' },
startLine: { type: 'number', description: 'Start line (optional)' },
endLine: { type: 'number', description: 'End line (optional)' },
},
required: ['filePath'],
},
},
{
name: 'blastRadius',
description: `Analyze the impact of changing a code element.
Returns all nodes affected by modifying the target, with distance, edge type, and confidence.
USE BEFORE making changes to understand ripple effects.
Output format (compact tabular):
Type|Name|File:Line|EdgeType|Confidence%
EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS
Confidence: 100% = certain, <80% = fuzzy match [fuzzy]
Depth groups:
- d=1: WILL BREAK (direct callers/importers)
- d=2: LIKELY AFFECTED (indirect)
- d=3: MAY NEED TESTING (transitive)`,
inputSchema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Name of function, class, or file to analyze' },
direction: { type: 'string', description: 'upstream (what depends on this) or downstream (what this depends on)' },
maxDepth: { type: 'number', description: 'Max relationship depth (default: 3)', default: 3 },
relationTypes: { type: 'array', items: { type: 'string' }, description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS, DEFINES (default: usage-based)' },
includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
minConfidence: { type: 'number', description: 'Minimum confidence 0-1 (default: 0.7)' },
},
required: ['target', 'direction'],
},
},
{
name: 'highlight',
description: `Highlight nodes in the GitNexus graph visualization.
Use after search/analysis to show the user what you found.
The user will see the nodes glow in the graph view.
Great for visual confirmation of your findings.`,
inputSchema: {
type: 'object',
properties: {
nodeIds: { type: 'array', items: { type: 'string' }, description: 'Array of node IDs to highlight' },
color: { type: 'string', description: 'Highlight color (optional, default: cyan)' },
},
required: ['nodeIds'],
},
},
];
-27
View File
@@ -1,27 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"ES2022"
],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
+7
View File
@@ -0,0 +1,7 @@
# GitNexus AI Context
.gitnexus-rules.md
.cursorrules
.windsurfrules
CLAUDE.md
.github/copilot-instructions.md
+2
View File
@@ -0,0 +1,2 @@
.vercel
.env*.local

Some files were not shown because too many files have changed in this diff Show More