Compare commits
192
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
707b7d2ac5 | ||
|
|
8c6b064d18 | ||
|
|
84ef6524bc | ||
|
|
5674b2201d | ||
|
|
6915a9350b | ||
|
|
8e7d976c2a | ||
|
|
46b4b7e157 | ||
|
|
cbeb0e231a | ||
|
|
3431edcea0 | ||
|
|
40cb863cb4 | ||
|
|
ee95808478 | ||
|
|
3e3ea86ce4 | ||
|
|
1a52d05131 | ||
|
|
3d64e26f8f | ||
|
|
3576802574 | ||
|
|
20ebd6b781 | ||
|
|
8a100a76d3 | ||
|
|
c129e71ee7 | ||
|
|
80eff73459 | ||
|
|
48c8e6fe57 | ||
|
|
2eca3e0da3 | ||
|
|
e046bf734d | ||
|
|
b30248f969 | ||
|
|
eb48c7352e | ||
|
|
29db66c304 | ||
|
|
b7c582de76 | ||
|
|
508402fd4a | ||
|
|
da63281a5a | ||
|
|
c758f4eaf0 | ||
|
|
fd507a19ae | ||
|
|
799de20172 | ||
|
|
2be88ae1f8 | ||
|
|
019ed3ff85 | ||
|
|
6b4f10cae1 | ||
|
|
43f525d056 | ||
|
|
e2a8bfa5ab | ||
|
|
ee6753bf05 | ||
|
|
1b8c3c77af | ||
|
|
a7fc9d2f88 | ||
|
|
0074fd71ff | ||
|
|
de935a4f4c | ||
|
|
989673a624 | ||
|
|
8c41970631 | ||
|
|
5c3a32d0c6 | ||
|
|
a8b3c6b23f | ||
|
|
15caf1e014 | ||
|
|
1ed34a0007 | ||
|
|
7a4bc9a260 | ||
|
|
3872a73875 | ||
|
|
f557716998 | ||
|
|
ae8a76511d | ||
|
|
e803e7e9d6 | ||
|
|
50fc8df2a1 | ||
|
|
c37b63ae8b | ||
|
|
7fe8830402 | ||
|
|
39b01f101e | ||
|
|
7cb88707a4 | ||
|
|
2a444acf1d | ||
|
|
d97d43f1b8 | ||
|
|
04be81f655 | ||
|
|
f047a84d82 | ||
|
|
0421fcbc76 | ||
|
|
1403cdbf6d | ||
|
|
0e8eed4a8a | ||
|
|
d6738c51c1 | ||
|
|
a5096e8029 | ||
|
|
36e64e892f | ||
|
|
bb6c22a22c | ||
|
|
420122065a | ||
|
|
bef319491a | ||
|
|
238abbd947 | ||
|
|
3f4c4cb4aa | ||
|
|
4f4fe9e587 | ||
|
|
dbf3495713 | ||
|
|
5b8ce44537 | ||
|
|
ffc4b69004 | ||
|
|
470a3377b3 | ||
|
|
91289404c2 | ||
|
|
397dad8ec4 | ||
|
|
7ee2dd1087 | ||
|
|
6aab580f93 | ||
|
|
1c02a06d1b | ||
|
|
890fedaa09 | ||
|
|
8a79465cbf | ||
|
|
945235ce56 | ||
|
|
e67d6c63d3 | ||
|
|
58063ca9ed | ||
|
|
804d975cd0 | ||
|
|
2164dc22f1 | ||
|
|
30aba01188 | ||
|
|
92a5d026c8 | ||
|
|
7883bf2cf0 | ||
|
|
73590b2862 | ||
|
|
bdda9afdca | ||
|
|
6be54ce9d3 | ||
|
|
2e390583fc | ||
|
|
575a4978f2 | ||
|
|
c379c39ae1 | ||
|
|
acd918f44e | ||
|
|
a71924f774 | ||
|
|
8dd1c19bec | ||
|
|
56f92ca1ad | ||
|
|
36c7b3ed12 | ||
|
|
c80bcccba4 | ||
|
|
76d1538c5e | ||
|
|
d1cac0515d | ||
|
|
cb70fc8d6c | ||
|
|
3603178266 | ||
|
|
c7519c8493 | ||
|
|
6726340059 | ||
|
|
e1d3959273 | ||
|
|
6de13ac800 | ||
|
|
d7380de683 | ||
|
|
102850455d | ||
|
|
8e8bb90fe4 | ||
|
|
28339c995d | ||
|
|
e849f017f2 | ||
|
|
5c7d905150 | ||
|
|
302c7ba7f0 | ||
|
|
93fc2b67ca | ||
|
|
ad92b82723 | ||
|
|
3400fccf8c | ||
|
|
af1e455e77 | ||
|
|
053af03caa | ||
|
|
63f1cd1ec9 | ||
|
|
f42513f5b6 | ||
|
|
54ae315336 | ||
|
|
19181e9ff6 | ||
|
|
d1fb34d636 | ||
|
|
8e2ee26834 | ||
|
|
a7c6903322 | ||
|
|
33aa774f03 | ||
|
|
366bc8b14b | ||
|
|
e79133daaa | ||
|
|
79501abb6c | ||
|
|
6ce715b62c | ||
|
|
3425fdeffd | ||
|
|
49eaf576fd | ||
|
|
abdb3b4e70 | ||
|
|
2f7be29f59 | ||
|
|
c4c863887b | ||
|
|
dcb7521386 | ||
|
|
b0320e6b02 | ||
|
|
d9a139957a | ||
|
|
65e14605b4 | ||
|
|
44572ad0bd | ||
|
|
58972af084 | ||
|
|
e90622aa24 | ||
|
|
2560a9532d | ||
|
|
eca55aacd7 | ||
|
|
96e1d799c8 | ||
|
|
dda8de41a3 | ||
|
|
1735c0ffcd | ||
|
|
6019e25126 | ||
|
|
86b171edac | ||
|
|
6c3c47edc3 | ||
|
|
d1e53d7030 | ||
|
|
664aa820f5 | ||
|
|
e480888cf0 | ||
|
|
747cf003b8 | ||
|
|
5830a50288 | ||
|
|
1ed9d286ae | ||
|
|
778ff10707 | ||
|
|
b723ce70c3 | ||
|
|
c90576442e | ||
|
|
789e7809be | ||
|
|
951423cd15 | ||
|
|
9cd380966d | ||
|
|
1ae08ee9fc | ||
|
|
fcbb6f9e92 | ||
|
|
4357a48fae | ||
|
|
6a7f837577 | ||
|
|
9b99c6baa9 | ||
|
|
42115f0ad7 | ||
|
|
67c506816a | ||
|
|
1e39f77718 | ||
|
|
e3f4d4b365 | ||
|
|
1dfd80d2ea | ||
|
|
e35cb2b920 | ||
|
|
4495632e82 | ||
|
|
f8b4a5c31f | ||
|
|
4541468320 | ||
|
|
fe3c4dd978 | ||
|
|
d3dbdb1aff | ||
|
|
32c3544d0d | ||
|
|
c2ce19f7a4 | ||
|
|
53f17ddf17 | ||
|
|
d02f861309 | ||
|
|
46330fa301 | ||
|
|
1b5a3bbc1b | ||
|
|
fa80520080 | ||
|
|
c7449d1804 |
@@ -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.
|
||||
|
||||
### 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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: abhigyanpatwari
|
||||
@@ -0,0 +1,69 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: gitnexus/package-lock.json
|
||||
- run: npm ci
|
||||
working-directory: gitnexus
|
||||
- run: npx tsc --noEmit
|
||||
working-directory: gitnexus
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: gitnexus/package-lock.json
|
||||
- run: npm ci
|
||||
working-directory: gitnexus
|
||||
- run: npx vitest run test/unit --coverage --coverage.thresholdAutoUpdate=false
|
||||
working-directory: gitnexus
|
||||
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: gitnexus/package-lock.json
|
||||
- run: npm ci
|
||||
working-directory: gitnexus
|
||||
- run: npx vitest run test/integration
|
||||
working-directory: gitnexus
|
||||
|
||||
cross-platform:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: gitnexus/package-lock.json
|
||||
- run: npm ci
|
||||
working-directory: gitnexus
|
||||
- run: npx vitest run test/unit
|
||||
working-directory: gitnexus
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, ready_for_review, reopened]
|
||||
# Optional: Only run on specific file changes
|
||||
# paths:
|
||||
# - "src/**/*.ts"
|
||||
# - "src/**/*.tsx"
|
||||
# - "src/**/*.js"
|
||||
# - "src/**/*.jsx"
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
# Optional: Filter by PR author
|
||||
# if: |
|
||||
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||
# github.event.pull_request.user.login == 'new-developer' ||
|
||||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
|
||||
plugins: 'code-review@claude-code-plugins'
|
||||
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||
|
||||
# Optional: Add claude_args to customize behavior and configuration
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Publish to npm
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
uses: ./.github/workflows/ci.yml
|
||||
|
||||
publish:
|
||||
needs: ci
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@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
|
||||
run: |
|
||||
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
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@v2
|
||||
with:
|
||||
generate_release_notes: true
|
||||
+16
@@ -17,6 +17,8 @@ dist/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
.claude/settings.local.json
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
@@ -40,3 +42,17 @@ coverage/
|
||||
|
||||
|
||||
.env*.local
|
||||
.gitnexus
|
||||
.claude/settings.local.json
|
||||
|
||||
# Claude Code worktrees
|
||||
.claude/worktrees/
|
||||
|
||||
# Assets (screenshots, images)
|
||||
assets/
|
||||
|
||||
# Generated files (should not be indexed)
|
||||
repomix-output*
|
||||
|
||||
# Design docs (local only)
|
||||
docs/plans/
|
||||
|
||||
@@ -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?)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,25 @@
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus MCP
|
||||
|
||||
This project is indexed by GitNexus as **GitnexusV2** (1444 symbols, 3700 relationships, 111 execution flows).
|
||||
|
||||
## Always Start Here
|
||||
|
||||
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 | 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 -->
|
||||
@@ -0,0 +1,25 @@
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus MCP
|
||||
|
||||
This project is indexed by GitNexus as **GitnexusV2** (1444 symbols, 3700 relationships, 111 execution flows).
|
||||
|
||||
## Always Start Here
|
||||
|
||||
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 | 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 -->
|
||||
@@ -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.
|
||||
@@ -1,486 +1,506 @@
|
||||
# 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, 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
|
||||
|
||||
[](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, 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** | KuzuDB native (fast, persistent) | KuzuDB 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) | **Full** |
|
||||
| **Cursor** | Yes | Yes | — | MCP + Skills |
|
||||
| **Windsurf** | Yes | — | — | MCP |
|
||||
| **OpenCode** | Yes | Yes | — | MCP + Skills |
|
||||
|
||||
> **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context.
|
||||
|
||||
### Community Integrations
|
||||
|
||||
| Agent | Install | Source |
|
||||
|-------|---------|--------|
|
||||
| [pi](https://pi.dev) | `pi install npm:pi-gitnexus` | [pi-gitnexus](https://github.com/tintinweb/pi-gitnexus) |
|
||||
|
||||
If you prefer manual configuration:
|
||||
|
||||
**Claude Code** (full support — MCP + skills + hooks):
|
||||
|
||||
```bash
|
||||
claude 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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 --skip-embeddings # Skip embedding generation (faster)
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## 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["KuzuDB conn A"]
|
||||
ConnB["KuzuDB 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. KuzuDB 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, KuzuDB 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**, **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 and function calls 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:
|
||||
|
||||
| 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 |
|
||||
TypeScript, JavaScript, Python, Java, Kotlin, C, C++, C#, Go, Rust, PHP, Swift
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 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** | KuzuDB native | KuzuDB 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] 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, 11 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
|
||||
- [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
|
||||
- [Graphology](https://graphology.github.io/) — Graph data structures
|
||||
- [MCP](https://modelcontextprotocol.io/) — Model Context Protocol
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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 KuzuDB 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 KuzuDB 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 |
|
||||
@@ -0,0 +1 @@
|
||||
# GitNexus SWE-bench Evaluation Harness
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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` |
|
||||
@@ -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` |
|
||||
@@ -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"
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"gitnexus": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "gitnexus@latest", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* GitNexus Claude Code Plugin Hook
|
||||
*
|
||||
* PreToolUse handler — intercepts Grep/Glob/Bash searches
|
||||
* and augments with graph context from the GitNexus index.
|
||||
*
|
||||
* 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 {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory (or ancestor) has a .gitnexus index.
|
||||
*/
|
||||
function findGitNexusIndex(startDir) {
|
||||
let dir = startDir || process.cwd();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (fs.existsSync(path.join(dir, '.gitnexus'))) {
|
||||
return true;
|
||||
}
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) break;
|
||||
dir = parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
function main() {
|
||||
try {
|
||||
const input = readInput();
|
||||
const hookEvent = input.hook_event_name || '';
|
||||
|
||||
if (hookEvent !== 'PreToolUse') return;
|
||||
|
||||
const cwd = input.cwd || process.cwd();
|
||||
if (!findGitNexusIndex(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;
|
||||
|
||||
// augment CLI writes result to stderr (KuzuDB's native module captures
|
||||
// stdout fd at OS level, making it unusable in subprocess contexts).
|
||||
let result = '';
|
||||
|
||||
const isWin = process.platform === 'win32';
|
||||
|
||||
// Try direct gitnexus binary first (faster if globally installed)
|
||||
try {
|
||||
const child = spawnSync(
|
||||
'gitnexus',
|
||||
['augment', pattern],
|
||||
{ encoding: 'utf-8', timeout: 8000, cwd, stdio: ['pipe', 'pipe', 'pipe'], shell: isWin }
|
||||
);
|
||||
if (child.status === 0 && child.stderr && child.stderr.trim()) {
|
||||
result = child.stderr;
|
||||
}
|
||||
} catch { /* not on PATH */ }
|
||||
|
||||
// Fallback to npx if direct binary didn't produce output
|
||||
if (!result || !result.trim()) {
|
||||
try {
|
||||
const child = spawnSync(
|
||||
'npx',
|
||||
['-y', 'gitnexus', 'augment', pattern],
|
||||
{ encoding: 'utf-8', timeout: 15000, cwd, stdio: ['pipe', 'pipe', 'pipe'], shell: isWin }
|
||||
);
|
||||
if (child.status === 0 && child.stderr && child.stderr.trim()) {
|
||||
result = child.stderr;
|
||||
}
|
||||
} catch { /* graceful failure */ }
|
||||
}
|
||||
|
||||
if (result && result.trim()) {
|
||||
console.log(JSON.stringify({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PreToolUse',
|
||||
additionalContext: result.trim()
|
||||
}
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// Graceful failure
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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..."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
Generated
-1994
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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'],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
# GitNexus AI Context
|
||||
.gitnexus-rules.md
|
||||
.cursorrules
|
||||
.windsurfrules
|
||||
CLAUDE.md
|
||||
.github/copilot-instructions.md
|
||||
@@ -0,0 +1,2 @@
|
||||
.vercel
|
||||
.env*.local
|
||||
Generated
+10203
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "gitnexus",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@huggingface/transformers": "^3.0.0",
|
||||
"@isomorphic-git/lightning-fs": "^4.6.2",
|
||||
"@langchain/anthropic": "^1.3.10",
|
||||
"@langchain/core": "^1.1.15",
|
||||
"@langchain/google-genai": "^2.1.10",
|
||||
"@langchain/langgraph": "^1.1.0",
|
||||
"@langchain/ollama": "^1.2.0",
|
||||
"@langchain/openai": "^1.2.2",
|
||||
"@sigma/edge-curve": "^3.1.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"axios": "^1.13.2",
|
||||
"buffer": "^6.0.3",
|
||||
"comlink": "^4.4.2",
|
||||
"d3": "^7.9.0",
|
||||
"graphology": "^0.26.0",
|
||||
"graphology-indices": "^0.17.0",
|
||||
"graphology-utils": "^2.3.0",
|
||||
"mnemonist": "^0.39.0",
|
||||
"pandemonium": "^2.4.0",
|
||||
"graphology-layout-force": "^0.2.4",
|
||||
"graphology-layout-forceatlas2": "^0.10.1",
|
||||
"graphology-layout-noverlap": "^0.4.2",
|
||||
"isomorphic-git": "^1.36.1",
|
||||
"jszip": "^3.10.1",
|
||||
"kuzu-wasm": "^0.11.1",
|
||||
"langchain": "^1.2.10",
|
||||
"lru-cache": "^11.2.4",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mermaid": "^11.12.2",
|
||||
"minisearch": "^7.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"react-zoom-pan-pinch": "^3.7.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sigma": "^3.0.2",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"uuid": "^13.0.0",
|
||||
"vite-plugin-top-level-await": "^1.6.0",
|
||||
"vite-plugin-wasm": "^3.5.0",
|
||||
"web-tree-sitter": "^0.20.8",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.28.5",
|
||||
"@types/jszip": "^3.4.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@vercel/node": "^5.5.16",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"tree-sitter-wasms": "^0.1.13",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.0",
|
||||
"vite-plugin-static-copy": "^3.1.4"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user