gitnexus 1.6.9 → 1.6.10-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -41
- package/dist/cli/ai-context.d.ts +1 -1
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.js +106 -3
- package/dist/cli/cli-message.d.ts +1 -1
- package/dist/cli/doctor.d.ts +5 -0
- package/dist/cli/doctor.js +41 -3
- package/dist/cli/editor-targets.d.ts +17 -2
- package/dist/cli/editor-targets.js +44 -0
- package/dist/cli/embeddings.d.ts +12 -0
- package/dist/cli/embeddings.js +49 -0
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +5 -1
- package/dist/cli/i18n/en.js +5 -1
- package/dist/cli/i18n/resources.d.ts +9 -1
- package/dist/cli/i18n/zh-CN.d.ts +4 -0
- package/dist/cli/i18n/zh-CN.js +5 -1
- package/dist/cli/index.js +12 -1
- package/dist/cli/setup.js +204 -28
- package/dist/cli/uninstall.js +70 -16
- package/dist/core/embeddings/embedder.js +16 -2
- package/dist/core/embeddings/http-client.d.ts +28 -1
- package/dist/core/embeddings/http-client.js +107 -15
- package/dist/core/embeddings/node-module-compat.d.ts +23 -0
- package/dist/core/embeddings/node-module-compat.js +23 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +6 -3
- package/dist/core/embeddings/onnxruntime-node-resolver.js +31 -5
- package/dist/core/embeddings/runtime-install.d.ts +119 -0
- package/dist/core/embeddings/runtime-install.js +372 -0
- package/dist/core/embeddings/runtime-support.d.ts +48 -17
- package/dist/core/embeddings/runtime-support.js +96 -0
- package/dist/core/group/extractors/http-patterns/python.js +246 -108
- package/dist/core/group/extractors/manifest-extractor.js +4 -0
- package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
- package/dist/core/ingestion/cobol-processor.js +27 -26
- package/dist/core/ingestion/community-processor.d.ts +51 -1
- package/dist/core/ingestion/community-processor.js +316 -63
- package/dist/core/ingestion/emit-references.js +3 -2
- package/dist/core/ingestion/markdown-processor.js +3 -2
- package/dist/core/ingestion/parsing-processor.d.ts +3 -1
- package/dist/core/ingestion/parsing-processor.js +4 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +42 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +12 -1
- package/dist/core/ingestion/utils/line-base.d.ts +20 -0
- package/dist/core/ingestion/utils/line-base.js +20 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
- package/dist/core/ingestion/utils/symbol-labels.js +45 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
- package/dist/core/ingestion/workers/parse-worker.js +39 -4
- package/dist/core/ingestion/workers/result-merge.js +4 -0
- package/dist/core/lbug/csv-generator.js +8 -2
- package/dist/core/lbug/extension-load-error.d.ts +67 -0
- package/dist/core/lbug/extension-load-error.js +320 -0
- package/dist/core/lbug/extension-loader.d.ts +17 -2
- package/dist/core/lbug/extension-loader.js +38 -13
- package/dist/core/lbug/lbug-adapter.js +7 -16
- package/dist/core/lbug/native-check.d.ts +22 -0
- package/dist/core/lbug/native-check.js +66 -0
- package/dist/core/lbug/pool-adapter.js +10 -1
- package/dist/core/lbug/sidecar-recovery.d.ts +32 -0
- package/dist/core/lbug/sidecar-recovery.js +91 -7
- package/dist/core/platform/capabilities.js +27 -1
- package/dist/core/run-analyze.js +33 -6
- package/dist/core/search/fts-indexes.d.ts +7 -0
- package/dist/core/search/fts-indexes.js +37 -0
- package/dist/mcp/core/embedder.js +16 -2
- package/dist/mcp/local/line-display.d.ts +22 -0
- package/dist/mcp/local/line-display.js +3 -0
- package/dist/mcp/local/local-backend.d.ts +14 -0
- package/dist/mcp/local/local-backend.js +63 -19
- package/dist/mcp/local/pdg-impact.d.ts +5 -3
- package/dist/mcp/local/pdg-impact.js +5 -2
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.js +1 -1
- package/dist/server/api.js +2 -2
- package/dist/storage/parse-cache.js +1 -1
- package/dist/storage/repo-manager.d.ts +6 -1
- package/dist/storage/repo-manager.js +6 -1
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
- package/hooks/claude/gitnexus-hook.cjs +59 -9
- package/package.json +5 -3
- package/scripts/cross-platform-tests.ts +26 -0
- package/scripts/ensure-fts.ts +32 -0
- package/scripts/install-duckdb-extension.mjs +85 -24
- package/scripts/run-cross-platform.ts +46 -6
- package/scripts/shard-arg.ts +30 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Graph-powered code intelligence for AI agents.** Index any codebase into a knowledge graph, then query it via MCP or CLI.
|
|
4
4
|
|
|
5
|
-
Works with **Cursor**, **Claude Code**, **Antigravity** (Google), **Codex**, **Windsurf**, **Cline**, **OpenCode**, and any MCP-compatible tool.
|
|
5
|
+
Works with **Cursor**, **Claude Code**, **Antigravity** (Google), **Codex**, **Windsurf**, **Cline**, **OpenCode**, **CodeBuddy** (Tencent), **Qoder** (Alibaba), and any MCP-compatible tool.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/gitnexus)
|
|
8
8
|
[](https://polyformproject.org/licenses/noncommercial/1.0.0/)
|
|
@@ -43,11 +43,13 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
|
|
|
43
43
|
| **Claude Code** | Yes | Yes | Yes (PreToolUse + PostToolUse) | **Full** |
|
|
44
44
|
| **Cursor** | Yes | Yes | Yes (postToolUse, [manual install](../gitnexus-cursor-integration/README.md#hook-install)) | **Full** |
|
|
45
45
|
| **Antigravity** (Google) | Yes | Yes | Yes (AfterTool, [Gemini CLI hooks schema](https://geminicli.com/docs/hooks/reference/)) | **Full** |
|
|
46
|
-
| **Codex** | Yes | Yes |
|
|
46
|
+
| **Codex** | Yes | Yes | Yes (PreToolUse + PostToolUse, [Codex hooks](https://developers.openai.com/codex/hooks)) | **Full** |
|
|
47
47
|
| **OpenCode** | Yes | Yes | — | MCP + Skills |
|
|
48
|
+
| **CodeBuddy** (Tencent) | Yes | Yes | — | MCP + Skills |
|
|
49
|
+
| **Qoder** (Alibaba) | Yes | Yes | — | MCP + Skills |
|
|
48
50
|
| **Windsurf** | Yes | — | — | MCP |
|
|
49
51
|
|
|
50
|
-
> **Claude Code**
|
|
52
|
+
> **Claude Code** and **Codex** get the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context + PostToolUse hooks that detect a stale index after commits and prompt the agent to reindex.
|
|
51
53
|
|
|
52
54
|
### Community Integrations
|
|
53
55
|
|
|
@@ -69,12 +71,23 @@ claude mcp add gitnexus -- npx -y gitnexus@latest mcp
|
|
|
69
71
|
claude mcp add gitnexus -- cmd /c npx -y gitnexus@latest mcp
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
### Codex (full support — MCP + skills)
|
|
74
|
+
### Codex (full support — MCP + skills + hooks)
|
|
73
75
|
|
|
74
76
|
```bash
|
|
75
77
|
codex mcp add gitnexus -- npx -y gitnexus@latest mcp
|
|
76
78
|
```
|
|
77
79
|
|
|
80
|
+
Codex hooks (PreToolUse graph enrichment + PostToolUse stale-index detection in `~/.codex/hooks.json`, [same schema as Claude Code](https://developers.openai.com/codex/hooks)) need the bundled adapter script, so they are installed by `gitnexus setup -c codex` rather than manually.
|
|
81
|
+
|
|
82
|
+
Alternatively, install everything as a [Codex plugin](https://developers.openai.com/codex/plugins/build) (MCP + skills + hooks in one step):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
codex plugin marketplace add abhigyanpatwari/GitNexus
|
|
86
|
+
# then inside Codex: /plugins → install "GitNexus"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> **Codex notes:** SessionStart is intentionally not registered — Codex reads [AGENTS.md natively](https://developers.openai.com/codex/guides/agents-md), which already carries the GitNexus context block. Newly installed hooks need a one-time approval in Codex via `/hooks` before they run. Pick **one** install route (`gitnexus setup -c codex` **or** the plugin): plugin hooks load alongside `~/.codex/hooks.json`, so installing both can fire duplicate hooks per tool call.
|
|
90
|
+
|
|
78
91
|
### Cursor / Windsurf
|
|
79
92
|
|
|
80
93
|
Add to `~/.cursor/mcp.json` (global — works for all projects):
|
|
@@ -105,6 +118,36 @@ Add to `~/.config/opencode/config.json`:
|
|
|
105
118
|
}
|
|
106
119
|
```
|
|
107
120
|
|
|
121
|
+
### CodeBuddy
|
|
122
|
+
|
|
123
|
+
CodeBuddy reads only the **first existing file** in its config priority chain: `~/.codebuddy/.mcp.json` (recommended) → `~/.codebuddy/mcp.json` (deprecated) → `~/.codebuddy.json` (legacy). Edit the first non-empty file that exists — creating a higher-priority file would hide the servers in the ones below it. If none exist, create `~/.codebuddy/.mcp.json`:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mcpServers": {
|
|
128
|
+
"gitnexus": {
|
|
129
|
+
"command": "npx",
|
|
130
|
+
"args": ["-y", "gitnexus@latest", "mcp"]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Qoder
|
|
137
|
+
|
|
138
|
+
Add to `~/.qoder.json`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"mcpServers": {
|
|
143
|
+
"gitnexus": {
|
|
144
|
+
"command": "npx",
|
|
145
|
+
"args": ["-y", "gitnexus@latest", "mcp"]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
108
151
|
## How It Works
|
|
109
152
|
|
|
110
153
|
GitNexus builds a complete knowledge graph of your codebase through a multi-phase indexing pipeline:
|
|
@@ -120,29 +163,39 @@ GitNexus builds a complete knowledge graph of your codebase through a multi-phas
|
|
|
120
163
|
|
|
121
164
|
The result is a **LadybugDB graph database** stored locally in `.gitnexus/` with full-text search and semantic embeddings.
|
|
122
165
|
|
|
166
|
+
### Experimental community detection engine
|
|
167
|
+
|
|
168
|
+
Community detection uses the bundled Graphology Leiden implementation by default. To test the #2337 Icebug migration path without changing default analyze behavior, set:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
GITNEXUS_COMMUNITY_ENGINE=icebug npx gitnexus analyze
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Supported values are `graphology`, `icebug`, and `auto`. The Icebug path is an experimental probe: GitNexus does not bundle an Icebug native package yet, and if a separately resolvable module is unavailable or its API does not match the expected `Graph.fromCSR` / `ParallelLeidenView` shape, analyze falls back to Graphology and reports the fallback in progress output. Today `auto` is behaviorally identical to `icebug`: both try Icebug and fall back to Graphology, while `graphology` skips the Icebug probe entirely.
|
|
175
|
+
|
|
123
176
|
## MCP Tools
|
|
124
177
|
|
|
125
178
|
Your AI agent gets **17 tools** (15 per-repo + 2 group) automatically:
|
|
126
179
|
|
|
127
180
|
| Tool | What It Does |
|
|
128
|
-
| ---------------- |
|
|
129
|
-
| `list_repos` | Discover all indexed repositories (paginated — `limit`/`offset`)
|
|
130
|
-
| `query` | Process-grouped hybrid search (BM25 + semantic + RRF)
|
|
131
|
-
| `context` | 360-degree symbol view — categorized refs, process participation
|
|
132
|
-
| `impact` | Blast radius analysis with depth grouping and confidence
|
|
133
|
-
| `trace` | Shortest directed path between two symbols (call + class-member edges)
|
|
134
|
-
| `detect_changes` | Git-diff impact — maps changed lines to affected processes
|
|
135
|
-
| `check` | Read-only structural checks against the indexed graph
|
|
136
|
-
| `rename` | Multi-file coordinated rename with graph + text search
|
|
137
|
-
| `cypher` | Raw Cypher graph queries
|
|
138
|
-
| `route_map` | API route map — which components fetch which endpoints, and handlers
|
|
139
|
-
| `tool_map` | MCP/RPC tool definitions — where they're defined and handled
|
|
140
|
-
| `shape_check` | Validate API response shapes against consumers' property accesses
|
|
141
|
-
| `api_impact` | Pre-change impact report for an API route handler
|
|
142
|
-
| `explain` | Explain persisted taint findings (source→sink flows, `--pdg` indexes)
|
|
143
|
-
| `pdg_query` | Query control/data dependence at statement level (`--pdg` indexes)
|
|
144
|
-
| `group_list` | List configured repository groups
|
|
145
|
-
| `group_sync` | Rebuild a group's Contract Registry and cross-repo links
|
|
181
|
+
| ---------------- | ---------------------------------------------------------------------- |
|
|
182
|
+
| `list_repos` | Discover all indexed repositories (paginated — `limit`/`offset`) |
|
|
183
|
+
| `query` | Process-grouped hybrid search (BM25 + semantic + RRF) |
|
|
184
|
+
| `context` | 360-degree symbol view — categorized refs, process participation |
|
|
185
|
+
| `impact` | Blast radius analysis with depth grouping and confidence |
|
|
186
|
+
| `trace` | Shortest directed path between two symbols (call + class-member edges) |
|
|
187
|
+
| `detect_changes` | Git-diff impact — maps changed lines to affected processes |
|
|
188
|
+
| `check` | Read-only structural checks against the indexed graph |
|
|
189
|
+
| `rename` | Multi-file coordinated rename with graph + text search |
|
|
190
|
+
| `cypher` | Raw Cypher graph queries |
|
|
191
|
+
| `route_map` | API route map — which components fetch which endpoints, and handlers |
|
|
192
|
+
| `tool_map` | MCP/RPC tool definitions — where they're defined and handled |
|
|
193
|
+
| `shape_check` | Validate API response shapes against consumers' property accesses |
|
|
194
|
+
| `api_impact` | Pre-change impact report for an API route handler |
|
|
195
|
+
| `explain` | Explain persisted taint findings (source→sink flows, `--pdg` indexes) |
|
|
196
|
+
| `pdg_query` | Query control/data dependence at statement level (`--pdg` indexes) |
|
|
197
|
+
| `group_list` | List configured repository groups |
|
|
198
|
+
| `group_sync` | Rebuild a group's Contract Registry and cross-repo links |
|
|
146
199
|
|
|
147
200
|
> With one indexed repo, the `repo` param is optional. With multiple, specify which: `query({search_query: "auth", repo: "my-app"})`. Per-repo tools also take an optional `branch` for indexes pinned with `gitnexus analyze --branch`; omitting it queries the workspace index, which follows your checked-out working tree. `explain` and `pdg_query` need an index built with `gitnexus analyze --pdg`.
|
|
148
201
|
|
|
@@ -177,6 +230,7 @@ gitnexus analyze [path] # Index a repository (or update stale index)
|
|
|
177
230
|
gitnexus analyze --repair-fts # Fast path: rebuild/verify only FTS indexes on existing index data
|
|
178
231
|
gitnexus analyze --force # Full rebuild: re-parse + graph rebuild + FTS rebuild
|
|
179
232
|
gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
|
|
233
|
+
gitnexus embeddings install # Fetch the optional local embedding stack on demand (--cuda, --force)
|
|
180
234
|
gitnexus analyze --skills # Generate repo-specific skill files from detected communities
|
|
181
235
|
gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
|
|
182
236
|
gitnexus analyze --skip-skills # Skip installing .claude/skills/gitnexus/ skill files
|
|
@@ -377,19 +431,41 @@ If `npm install -g gitnexus` fails on native modules:
|
|
|
377
431
|
npm install -g gitnexus
|
|
378
432
|
```
|
|
379
433
|
|
|
434
|
+
### Installation fails behind an HTTP proxy (`onnxruntime-node` postinstall)
|
|
435
|
+
|
|
436
|
+
`onnxruntime-node`'s postinstall downloads optional CUDA GPU binaries from `api.nuget.org` — outside the npm registry, so registry mirrors don't cover it, and its proxy layer (`global-agent`) ignores the standard `HTTP_PROXY`/`HTTPS_PROXY` variables and rejects 302 redirects ([#2370](https://github.com/abhigyanpatwari/GitNexus/issues/2370)).
|
|
437
|
+
|
|
438
|
+
Since the packages are optional dependencies, a failed download no longer breaks `npm install -g gitnexus` — npm skips the embedding stack and everything else works. The stack then **self-heals on demand**: the first `gitnexus analyze --embeddings` (or an explicit `gitnexus embeddings install`) fetches it through your configured npm registry — mirrors and proxies apply, no NuGet download involved — into `~/.gitnexus/embedding-runtime`.
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
# heal a proxy-degraded install manually (CPU embeddings; registry-only)
|
|
442
|
+
gitnexus embeddings install
|
|
443
|
+
|
|
444
|
+
# reinstall into the prefix even when the stack already resolves
|
|
445
|
+
gitnexus embeddings install --force
|
|
446
|
+
|
|
447
|
+
# CUDA GPU hosts: also fetch GPU binaries (NuGet; set the proxy global-agent reads)
|
|
448
|
+
GLOBAL_AGENT_HTTPS_PROXY=<proxy-url> gitnexus embeddings install --cuda
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
The prefix defaults to `~/.gitnexus/embedding-runtime`; set `GITNEXUS_EMBEDDING_RUNTIME_DIR` to install it elsewhere (e.g. a writable path in a container).
|
|
452
|
+
|
|
453
|
+
> **Node requirement for the on-demand prefix:** the self-heal loads the prefixed packages via `module.registerHooks`, available on Node **≥ 22.15** (on the 22.x line) or **≥ 23.5** (on the 23.x line). On an older Node the packages install but can't be loaded from the prefix — reinstall them into the install itself instead (works on every supported Node): `ONNXRUNTIME_NODE_INSTALL=skip npm install -g gitnexus` (Windows: `set ONNXRUNTIME_NODE_INSTALL=skip && npm install -g gitnexus`). Skipping only the CUDA download keeps full CPU embeddings (CPU embeddings don't need it). Check the result any time with `gitnexus doctor` (Embeddings → Support line).
|
|
454
|
+
|
|
380
455
|
### Analyze warns about unavailable FTS or VECTOR extensions
|
|
381
456
|
|
|
382
|
-
GitNexus uses optional DuckDB extensions for BM25 and vector search. The `gitnexus serve` and MCP read paths only ever try to `LOAD` the extensions — they never block on a network install. The `analyze` command, by default, attempts one bounded out-of-process `INSTALL`
|
|
457
|
+
GitNexus uses optional DuckDB extensions for BM25 and vector search. The `gitnexus serve` and MCP read paths only ever try to `LOAD` the extensions — they never block on a network install. The `analyze` command, by default, attempts one bounded out-of-process install if `LOAD` fails (a plain `INSTALL` to download a missing extension, escalating to `FORCE INSTALL` only when the `LOAD` error shows the existing file is broken or truncated, so a permanent non-file failure does not re-download on every run) and proceeds even when that install times out, so the index is always written to disk; BM25/vector search degrade gracefully until the extensions become available.
|
|
383
458
|
|
|
384
459
|
Configure the behavior with these environment variables:
|
|
385
460
|
|
|
386
|
-
| Variable | Values | Default | Effect
|
|
387
|
-
| -------------------------------------------- | ---------------------------- | ------------------- |
|
|
388
|
-
| `GITNEXUS_LBUG_EXTENSION_INSTALL` | `auto`, `load-only`, `never` | `auto` | `auto` runs one bounded
|
|
389
|
-
| `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` | positive integer | `15000` | Wall-clock budget for the out-of-process
|
|
390
|
-
| `GITNEXUS_FTS_STEMMER` | supported LadybugDB stemmer | `porter` | Stemmer used when rebuilding BM25/FTS indexes. Use `none` for CJK-heavy repositories, or a language stemmer such as `german`, `french`, or `spanish` when that better matches repository comments and identifiers. Re-run `gitnexus analyze --repair-fts` after changing it.
|
|
461
|
+
| Variable | Values | Default | Effect |
|
|
462
|
+
| -------------------------------------------- | ---------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
463
|
+
| `GITNEXUS_LBUG_EXTENSION_INSTALL` | `auto`, `load-only`, `never` | `auto` | `auto` runs one bounded install if LOAD fails — a plain `INSTALL`, escalating to `FORCE INSTALL` only when the LOAD error shows the present extension file is broken. `load-only` only uses already-installed extensions (recommended for offline / firewalled environments). `never` skips optional extensions entirely. |
|
|
464
|
+
| `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` | positive integer | `15000` | Wall-clock budget for the out-of-process extension-install child before it is killed. |
|
|
465
|
+
| `GITNEXUS_FTS_STEMMER` | supported LadybugDB stemmer | `porter` | Stemmer used when rebuilding BM25/FTS indexes. Use `none` for CJK-heavy repositories, or a language stemmer such as `german`, `french`, or `spanish` when that better matches repository comments and identifiers. Re-run `gitnexus analyze --repair-fts` after changing it. |
|
|
391
466
|
| `GITNEXUS_FTS_CJK_SEGMENTATION` | `none`, `bigram` | `none` | `bigram` inserts overlapping character-bigram boundaries into Chinese/Japanese Han-ideograph spans in `content`/`description` before FTS indexing, so LadybugDB's space-only tokenizer can see sub-phrase word boundaries. Scoped to CJK Unified Ideographs only — Japanese Hiragana/Katakana and Korean Hangul are not currently segmented. Unlike `GITNEXUS_FTS_STEMMER`, this rewrites stored text — enabling it on an already-indexed repo requires a full `gitnexus analyze --force`; neither `--repair-fts` nor a plain incremental `analyze` applies it to previously-indexed files. Set the same value wherever `analyze` and search-serving processes (CLI query, MCP server, web server) run. |
|
|
392
|
-
| `
|
|
467
|
+
| `GITNEXUS_COMMUNITY_ENGINE` | `graphology`, `icebug`, `auto` | `graphology` | Community-detection engine used during analyze. `graphology` uses the bundled default path. `icebug` and `auto` currently behave identically: both try the experimental Icebug CSR path and fall back to Graphology if the optional native module is unavailable or incompatible. |
|
|
468
|
+
| `GITNEXUS_WAL_CHECKPOINT_THRESHOLD` | integer `>= -1` | `67108864` (64 MiB) | LadybugDB WAL auto-checkpoint threshold during analyze (bytes). Auto-checkpoint remains enabled; `-1` keeps Ladybug's stock ~16 MiB. Larger thresholds reduce checkpoint frequency but increase the WAL size at rotation time — choose a smaller value on disk-constrained environments. |
|
|
393
469
|
|
|
394
470
|
```bash
|
|
395
471
|
# Offline/airgapped: never reach the network for extensions
|
|
@@ -464,23 +540,32 @@ Three env vars expose the pool's resilience layers (respawn budget, cumulative-t
|
|
|
464
540
|
|
|
465
541
|
After scope resolution, analyze prunes inert block-local value symbols (a function-local `const`/`let`/`var` that ends up with only its structural `File→DEFINES` edge) to keep the graph focused on cross-symbol relationships. Module/file-scope symbols, class members, and any local with a real edge are always kept.
|
|
466
542
|
|
|
467
|
-
| Variable
|
|
468
|
-
|
|
|
469
|
-
| `GITNEXUS_KEEP_LOCAL_VALUE_SYMBOLS`
|
|
543
|
+
| Variable | Default | Effect |
|
|
544
|
+
| ----------------------------------- | ------- | ---------------------------------------------------------------------------------- |
|
|
545
|
+
| `GITNEXUS_KEEP_LOCAL_VALUE_SYMBOLS` | unset | Set to `1`/`true` to keep inert block-local value symbols instead of pruning them. |
|
|
470
546
|
|
|
471
547
|
Programmatic callers can pass `keepLocalValueSymbols: true` in `PipelineOptions` instead of setting the env var.
|
|
472
548
|
|
|
473
|
-
### Hook augmentation
|
|
549
|
+
### Hook augmentation and skip diagnostics
|
|
474
550
|
|
|
475
|
-
The Claude Code / Antigravity hooks
|
|
551
|
+
The Claude Code / Antigravity hooks keep their **stderr** silent on normal skip
|
|
476
552
|
paths so strict hook runners (e.g. Codex `PreToolUse`) never see unexpected
|
|
477
|
-
output.
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
553
|
+
diagnostic output.
|
|
554
|
+
|
|
555
|
+
When a GitNexus process holds the repo DB write lock (the common case — the MCP
|
|
556
|
+
server is running, or the DB-lock probe timed out and failed closed), the local
|
|
557
|
+
CLI `augment` can't run (LadybugDB is single-writer). Rather than drop the
|
|
558
|
+
augmentation, the hook hands the agent a short, conditional MCP-query hint on
|
|
559
|
+
stdout (the sanctioned `additionalContext` channel) — _"if the GitNexus MCP tools
|
|
560
|
+
are live in this session, call `query` …"_ — so an agent that has the tools can
|
|
561
|
+
still fetch graph-ranked context. The hint is throttled to at most once per repo
|
|
562
|
+
per window (`GITNEXUS_MCP_HINT_THROTTLE_MS`, default 10 min; `0` disables), so an
|
|
563
|
+
owner-locked session isn't nudged on every search. A stale-index reminder, or an
|
|
564
|
+
already-current index, stays silent.
|
|
565
|
+
|
|
566
|
+
To see why a hook skipped the CLI augment, set `GITNEXUS_DEBUG=1` and re-run the
|
|
567
|
+
action — the hook writes the reason (e.g. `[GitNexus] augment skipped: MCP server
|
|
568
|
+
owns DB`) and the stale-index hint to its stderr:
|
|
484
569
|
|
|
485
570
|
```bash
|
|
486
571
|
GITNEXUS_DEBUG=1 <your command> # surfaces hook skip/diagnostic reasons on stderr
|
package/dist/cli/ai-context.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI Context Generator
|
|
3
3
|
*
|
|
4
4
|
* Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
|
|
5
|
-
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
|
|
5
|
+
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, CodeBuddy, Qoder, etc.
|
|
6
6
|
* CLAUDE.md is for Claude Code which only reads that file.
|
|
7
7
|
*/
|
|
8
8
|
import { type GeneratedSkillInfo } from './skill-gen.js';
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI Context Generator
|
|
3
3
|
*
|
|
4
4
|
* Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
|
|
5
|
-
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
|
|
5
|
+
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, CodeBuddy, Qoder, etc.
|
|
6
6
|
* CLAUDE.md is for Claude Code which only reads that file.
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
package/dist/cli/analyze.js
CHANGED
|
@@ -27,8 +27,9 @@ import { cliError } from './cli-message.js';
|
|
|
27
27
|
import { EMBEDDING_DIMS_ERROR, normalizeEmbeddingDims } from './embedding-dims.js';
|
|
28
28
|
import { formatElapsed } from './format-elapsed.js';
|
|
29
29
|
import { isHfDownloadFailure } from '../core/embeddings/hf-env.js';
|
|
30
|
-
import { safeUrl } from '../core/embeddings/http-client.js';
|
|
31
|
-
import { isLocalEmbeddingRuntimeBlockerMessage } from '../core/embeddings/runtime-support.js';
|
|
30
|
+
import { isHttpEmbeddingDimsError, isHttpEmbeddingError, isHttpMode, safeUrl, } from '../core/embeddings/http-client.js';
|
|
31
|
+
import { isLocalEmbeddingRuntimeBlockerMessage, isMissingLocalEmbeddingStackMessage, localEmbeddingPrefixUnloadableMessage, localEmbeddingStackMissingMessage, } from '../core/embeddings/runtime-support.js';
|
|
32
|
+
import { ANALYZE_EMBEDDING_INSTALL_TIMEOUT_MS, getEmbeddingInstallTimeoutMs, getEmbeddingRuntimeDir, installEmbeddingRuntime, isPrefixRuntimeLoadable, resolveEmbeddingRuntime, } from '../core/embeddings/runtime-install.js';
|
|
32
33
|
import { warnIfNpm11NpxRisk } from './resolve-invocation.js';
|
|
33
34
|
// Capture stderr.write at module load BEFORE anything (LadybugDB native
|
|
34
35
|
// init, progress bar, console redirection) can monkey-patch it. The
|
|
@@ -797,6 +798,52 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
|
|
|
797
798
|
console.log(' Note: custom HTTP embeddings require BOTH --embedding-base-url and --embedding-model ' +
|
|
798
799
|
'(or the matching env vars). Falling back to local ONNX embeddings.\n');
|
|
799
800
|
}
|
|
801
|
+
// On-demand embedding runtime (#2370): when the optional stack was pruned at
|
|
802
|
+
// install time (proxy-blocked NuGet download in onnxruntime-node's
|
|
803
|
+
// postinstall), heal it here instead of failing later in the pipeline. The
|
|
804
|
+
// install goes through the user's npm registry config (mirrors/proxies
|
|
805
|
+
// apply) with --ignore-scripts, so no NuGet download is attempted. Runs
|
|
806
|
+
// before bar.start() like the sibling validations above.
|
|
807
|
+
if (embeddingsEnabled && !isHttpMode()) {
|
|
808
|
+
const resolved = resolveEmbeddingRuntime();
|
|
809
|
+
// Resolved-but-unloadable (a populated prefix on a Node with no
|
|
810
|
+
// module.registerHooks), or nothing installed on such a Node: fail fast with
|
|
811
|
+
// capability guidance instead of dying mid-pipeline over an unusable prefix
|
|
812
|
+
// or downloading a runtime the loader can't reach. A package-sourced stack
|
|
813
|
+
// never needs the hook, so it is excluded. --embeddings was explicitly
|
|
814
|
+
// requested and this failure is deterministic, so fail fast rather than
|
|
815
|
+
// silently degrading to BM25 (distinct from a transient install timeout).
|
|
816
|
+
if (!isPrefixRuntimeLoadable() && (resolved === null || resolved.source === 'runtime-prefix')) {
|
|
817
|
+
cliError(` ${localEmbeddingPrefixUnloadableMessage().replace(/\n/g, '\n ')}\n`, {
|
|
818
|
+
recoveryHint: 'local-embedding-stack-missing',
|
|
819
|
+
});
|
|
820
|
+
process.exitCode = 1;
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
// On-demand embedding runtime (#2370): when the optional stack was pruned at
|
|
824
|
+
// install time (proxy-blocked NuGet download in onnxruntime-node's
|
|
825
|
+
// postinstall), heal it here instead of failing later in the pipeline. The
|
|
826
|
+
// install goes through the user's npm registry config (mirrors/proxies
|
|
827
|
+
// apply) with --ignore-scripts, so no NuGet download is attempted.
|
|
828
|
+
if (resolved === null) {
|
|
829
|
+
console.log(` Local embedding runtime is not installed (optional packages were skipped at install time).\n` +
|
|
830
|
+
` Downloading it now from your npm registry into ${getEmbeddingRuntimeDir()} …\n` +
|
|
831
|
+
` (one-time; rerun manually anytime with \`gitnexus embeddings install\`)\n`);
|
|
832
|
+
try {
|
|
833
|
+
// Short deadline (env override still wins): analyze is interactive, so a
|
|
834
|
+
// blackholed proxy must not stall the whole index run for the 10-minute
|
|
835
|
+
// default — fail over to the guidance below instead.
|
|
836
|
+
await installEmbeddingRuntime({}, getEmbeddingInstallTimeoutMs(ANALYZE_EMBEDDING_INSTALL_TIMEOUT_MS));
|
|
837
|
+
console.log(' Embedding runtime installed.\n');
|
|
838
|
+
}
|
|
839
|
+
catch (err) {
|
|
840
|
+
cliError(` Could not install the embedding runtime: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
841
|
+
` ${localEmbeddingStackMissingMessage().replace(/\n/g, '\n ')}\n`, { recoveryHint: 'local-embedding-stack-missing' });
|
|
842
|
+
process.exitCode = 1;
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
800
847
|
if (options.repairFts && options.force) {
|
|
801
848
|
cliError(' Cannot combine `--repair-fts` with `--force`. ' +
|
|
802
849
|
'Use `--repair-fts` for fast FTS-only repair, or `--force` for a full rebuild.\n');
|
|
@@ -1203,10 +1250,66 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
|
|
|
1203
1250
|
process.exitCode = 1;
|
|
1204
1251
|
return;
|
|
1205
1252
|
}
|
|
1253
|
+
// The optional embedding stack (@huggingface/transformers → onnxruntime-node)
|
|
1254
|
+
// was pruned at install time — usually a proxy-blocked NuGet download during
|
|
1255
|
+
// onnxruntime-node's postinstall (#2370). Checked before the generic
|
|
1256
|
+
// module-not-found "installation may be corrupt" hint below, which would
|
|
1257
|
+
// otherwise misdiagnose a deliberate optional-dependency skip.
|
|
1258
|
+
if (isMissingLocalEmbeddingStackMessage(msg)) {
|
|
1259
|
+
cliError(` ${msg.replace(/\n/g, '\n ')}\n`, {
|
|
1260
|
+
recoveryHint: 'local-embedding-stack-missing',
|
|
1261
|
+
});
|
|
1262
|
+
process.exitCode = 1;
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
// Malformed GITNEXUS_EMBEDDING_DIMS env var (#2385). readConfig() throws a
|
|
1266
|
+
// plain Error (a config mistake, not an endpoint failure), surfacing here from
|
|
1267
|
+
// httpEmbed()->readConfig() inside the analysis run. Show a clean config
|
|
1268
|
+
// message rather than a raw stack dump. The --embedding-dims CLI flag is
|
|
1269
|
+
// validated up front (EMBEDDING_DIMS_ERROR); this covers the env-var path.
|
|
1270
|
+
// Checked before the endpoint/HF branches: it is a plain Error, so
|
|
1271
|
+
// isHttpEmbeddingError() is false and the HF network heuristic must not claim it.
|
|
1272
|
+
if (isHttpEmbeddingDimsError(msg)) {
|
|
1273
|
+
cliError(` ${msg.replace(/\n/g, '\n ')}\n`, {
|
|
1274
|
+
recoveryHint: 'embedding-dims-invalid',
|
|
1275
|
+
});
|
|
1276
|
+
process.exitCode = 1;
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
// Custom HTTP embedding endpoint failure (#2385). When a `--embedding-base-url`
|
|
1280
|
+
// is configured, HTTP mode never downloads a model — so a failure talking to
|
|
1281
|
+
// that endpoint must NOT show the huggingface-download guidance. Keyed on the
|
|
1282
|
+
// error *type* (HttpEmbeddingError), not its message text, so it stays correct
|
|
1283
|
+
// regardless of locale or wording. Checked before the HF branch, whose network
|
|
1284
|
+
// heuristic (`fetch failed` / `ECONNREFUSED`) would otherwise also match a
|
|
1285
|
+
// wrapped endpoint-connection error. The header is deliberately neutral: this
|
|
1286
|
+
// type covers both never-reached failures (connection/timeout/DNS) and
|
|
1287
|
+
// reached-but-failed ones (4xx/5xx, dimension/shape mismatch), so it must not
|
|
1288
|
+
// assert "unreachable". The thrown `msg` carries the specific reason (and the
|
|
1289
|
+
// masked URL where one applies), so it is surfaced verbatim.
|
|
1290
|
+
if (isHttpEmbeddingError(err)) {
|
|
1291
|
+
cliError(` The custom embedding endpoint request failed.\n` +
|
|
1292
|
+
` ${msg.replace(/\n/g, '\n ')}\n` +
|
|
1293
|
+
` Suggestions:\n` +
|
|
1294
|
+
` 1. Verify the endpoint URL is reachable and running ` +
|
|
1295
|
+
`(--embedding-base-url / GITNEXUS_EMBEDDING_URL: host, port, /v1 path).\n` +
|
|
1296
|
+
` 2. Confirm the model name and embedding dimensions match what the endpoint serves.\n` +
|
|
1297
|
+
` 3. Re-run without --embeddings to index without vectors.\n`, { recoveryHint: 'http-embedding-endpoint-error' });
|
|
1298
|
+
process.exitCode = 1;
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
// isHttpMode() is a pure presence probe (URL+MODEL) that never throws — a
|
|
1302
|
+
// malformed GITNEXUS_EMBEDDING_DIMS is handled by the dims branch above — so
|
|
1303
|
+
// no defensive try/catch is needed here (#2385).
|
|
1304
|
+
const inHttpMode = isHttpMode();
|
|
1206
1305
|
// HF download failure — show clean guidance without the raw stack trace.
|
|
1207
1306
|
// Checked before writeFatalToStderr so the user sees one focused message
|
|
1208
1307
|
// rather than a stack-trace dump followed by a second remediation block.
|
|
1209
|
-
|
|
1308
|
+
// Gated on !inHttpMode: with a custom endpoint configured no model download
|
|
1309
|
+
// is ever attempted, so a network error there is the endpoint's, handled by
|
|
1310
|
+
// the HttpEmbeddingError branch above — never HF's (#2385).
|
|
1311
|
+
if ((isHfDownloadFailure(msg) || msg.includes('Failed to download embedding model')) &&
|
|
1312
|
+
!inHttpMode) {
|
|
1210
1313
|
cliError(` The embedding model could not be downloaded.\n` +
|
|
1211
1314
|
` huggingface.co may be unreachable from your network\n` +
|
|
1212
1315
|
` (e.g. behind a corporate proxy or a regional firewall).\n` +
|
|
@@ -12,7 +12,7 @@ import { type CliMessageKey, type CliMessageVars } from './i18n/index.js';
|
|
|
12
12
|
* Consumers can import this type to narrow log-record `recoveryHint`
|
|
13
13
|
* fields without restating the literal list.
|
|
14
14
|
*/
|
|
15
|
-
export type RecoveryHint = 'wal-corruption' | 'wal-checkpoint-threshold' | 'heap-oom-respawn' | 'native-worker-abort' | 'hf-endpoint-unreachable' | 'local-embedding-unsupported' | 'large-repo' | 'npm-resolution' | 'module-not-found' | 'gitnexusrc-invalid' | 'default-branch-invalid';
|
|
15
|
+
export type RecoveryHint = 'wal-corruption' | 'wal-checkpoint-threshold' | 'heap-oom-respawn' | 'native-worker-abort' | 'hf-endpoint-unreachable' | 'http-embedding-endpoint-error' | 'embedding-dims-invalid' | 'local-embedding-unsupported' | 'local-embedding-stack-missing' | 'large-repo' | 'npm-resolution' | 'module-not-found' | 'gitnexusrc-invalid' | 'default-branch-invalid';
|
|
16
16
|
/**
|
|
17
17
|
* Common shape for the optional structured-field bag passed to
|
|
18
18
|
* `cliError`/`cliWarn`/`cliInfo`. Typed so the `recoveryHint` slot is
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type EmbeddingRuntimeResolution } from '../core/embeddings/runtime-install.js';
|
|
1
2
|
export declare function displayWidth(value: string): number;
|
|
2
3
|
export declare function padDisplayEnd(value: string, columns: number): string;
|
|
3
4
|
/**
|
|
@@ -14,6 +15,10 @@ export declare function localEmbeddingDoctorStatus(opts: {
|
|
|
14
15
|
httpMode: boolean;
|
|
15
16
|
platform?: NodeJS.Platform;
|
|
16
17
|
arch?: NodeJS.Architecture;
|
|
18
|
+
/** Injectable for tests; defaults to probing the real install. */
|
|
19
|
+
resolution?: EmbeddingRuntimeResolution | null;
|
|
20
|
+
/** Injectable for tests; defaults to this Node's registerHooks capability. */
|
|
21
|
+
prefixLoadable?: boolean;
|
|
17
22
|
}): {
|
|
18
23
|
status: string;
|
|
19
24
|
detail: string | null;
|
package/dist/cli/doctor.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getRuntimeCapabilities, getRuntimeFingerprint } from '../core/platform/capabilities.js';
|
|
2
2
|
import { resolveEmbeddingConfig } from '../core/embeddings/config.js';
|
|
3
3
|
import { isHttpMode } from '../core/embeddings/http-client.js';
|
|
4
|
-
import { getLocalEmbeddingRuntimeBlocker } from '../core/embeddings/runtime-support.js';
|
|
4
|
+
import { getLocalEmbeddingRuntimeBlocker, localEmbeddingPrefixUnloadableMessage, localEmbeddingStackMissingMessage, } from '../core/embeddings/runtime-support.js';
|
|
5
|
+
import { isPrefixRuntimeLoadable, resolveEmbeddingRuntime, } from '../core/embeddings/runtime-install.js';
|
|
5
6
|
import { cudaRedirectDoctorStatus } from '../core/embeddings/onnxruntime-node-resolver.js';
|
|
6
|
-
import { checkLbugNative } from '../core/lbug/native-check.js';
|
|
7
|
+
import { checkLbugNative, probeFtsExtensionLoad } from '../core/lbug/native-check.js';
|
|
8
|
+
import { diagnoseExtensionLoad } from '../core/lbug/extension-load-error.js';
|
|
7
9
|
import { getExtensionInstallPolicy } from '../core/lbug/extension-loader.js';
|
|
8
10
|
import { t } from './i18n/index.js';
|
|
9
11
|
function isCombiningMark(codePoint) {
|
|
@@ -64,6 +66,25 @@ export function localEmbeddingDoctorStatus(opts) {
|
|
|
64
66
|
if (blocker) {
|
|
65
67
|
return { status: `✗ local embeddings unavailable on ${platform}/${arch}`, detail: blocker };
|
|
66
68
|
}
|
|
69
|
+
// The stack is an optionalDependency — npm prunes it when onnxruntime-node's
|
|
70
|
+
// postinstall can't download its CUDA binaries (proxy/firewall, #2370).
|
|
71
|
+
const resolution = opts.resolution !== undefined ? opts.resolution : resolveEmbeddingRuntime();
|
|
72
|
+
if (resolution === null) {
|
|
73
|
+
return {
|
|
74
|
+
status: '✗ optional embedding stack not installed',
|
|
75
|
+
detail: localEmbeddingStackMissingMessage(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// A prefix-sourced stack needs module.registerHooks to load; on Node < 22.15 /
|
|
79
|
+
// < 23.5 it is present but unreachable (#2372). Report loadability, not bare
|
|
80
|
+
// presence, so the diagnostic stops claiming a ✓ the loader can't honour.
|
|
81
|
+
const prefixLoadable = opts.prefixLoadable ?? isPrefixRuntimeLoadable();
|
|
82
|
+
if (resolution.source === 'runtime-prefix' && !prefixLoadable) {
|
|
83
|
+
return {
|
|
84
|
+
status: '✗ embedding stack installed in the prefix but not loadable on this Node',
|
|
85
|
+
detail: localEmbeddingPrefixUnloadableMessage(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
67
88
|
return { status: '✓ local embeddings supported', detail: null };
|
|
68
89
|
}
|
|
69
90
|
export const doctorCommand = async () => {
|
|
@@ -88,7 +109,24 @@ export const doctorCommand = async () => {
|
|
|
88
109
|
console.log('');
|
|
89
110
|
console.log(t('doctor.capabilities'));
|
|
90
111
|
console.log(` ${label('doctor.labels.graphStore', 18)}${capabilities.graph}`);
|
|
91
|
-
|
|
112
|
+
// Live LOAD probe, not the static platform capability — the static value
|
|
113
|
+
// said "available" while analyze failed to load the extension (#2374).
|
|
114
|
+
const ftsProbe = nativeCheck.ok
|
|
115
|
+
? await probeFtsExtensionLoad()
|
|
116
|
+
: { loaded: false, reason: 'LadybugDB native module (lbugjs.node) failed to load' };
|
|
117
|
+
console.log(` ${label('doctor.labels.fullTextSearch', 18)}${ftsProbe.loaded ? 'available' : 'unavailable'}`);
|
|
118
|
+
if (!ftsProbe.loaded && ftsProbe.reason) {
|
|
119
|
+
console.log(` ${padDisplayEnd('', 18)}${ftsProbe.reason}`);
|
|
120
|
+
// Add an actionable remedy for recognized failure classes (#2374). The
|
|
121
|
+
// Windows missing-dependency case is the point of this: the raw error 126
|
|
122
|
+
// ("specified module could not be found") is opaque, so name the fix (VC++
|
|
123
|
+
// redist, then OpenSSL) instead of leaving the user to reinstall in vain.
|
|
124
|
+
// `unknown`'s remedy is "run doctor", which would be circular here.
|
|
125
|
+
const { kind, remedy } = diagnoseExtensionLoad(ftsProbe.reason);
|
|
126
|
+
if (kind !== 'unknown') {
|
|
127
|
+
console.log(` ${padDisplayEnd('', 18)}${remedy}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
92
130
|
console.log(` ${label('doctor.labels.vectorIndex', 18)}${capabilities.vector}`);
|
|
93
131
|
console.log(` ${label('doctor.labels.semanticMode', 18)}${capabilities.semanticMode}`);
|
|
94
132
|
// Surface the optional-extension install policy so offline users can see
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* implementations remain behaviourally symmetrical on top of this shared
|
|
16
16
|
* structure.
|
|
17
17
|
*/
|
|
18
|
-
export type EditorId = 'cursor' | 'claude' | 'antigravity' | 'opencode' | 'codex';
|
|
18
|
+
export type EditorId = 'cursor' | 'claude' | 'antigravity' | 'opencode' | 'codebuddy' | 'qoder' | 'codex';
|
|
19
19
|
/** An editor whose MCP config is a JSONC document (server keyed by name). */
|
|
20
20
|
export interface McpJsoncTarget {
|
|
21
21
|
id: EditorId;
|
|
@@ -29,6 +29,14 @@ export interface McpJsoncTarget {
|
|
|
29
29
|
* without either side needing a cast.
|
|
30
30
|
*/
|
|
31
31
|
keyPath: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Older config locations the editor still reads when `file` is absent
|
|
34
|
+
* (CodeBuddy reads only the FIRST existing file in its priority chain).
|
|
35
|
+
* Setup writes into the first existing candidate of [file, ...legacyFiles]
|
|
36
|
+
* so it never shadows a user's servers living in a deprecated file;
|
|
37
|
+
* uninstall sweeps all of them.
|
|
38
|
+
*/
|
|
39
|
+
legacyFiles?: string[];
|
|
32
40
|
}
|
|
33
41
|
/** Codex stores MCP config as a TOML table, not JSONC. */
|
|
34
42
|
export interface CodexMcpTarget {
|
|
@@ -58,7 +66,7 @@ export interface HookTarget {
|
|
|
58
66
|
scriptDir: string;
|
|
59
67
|
}
|
|
60
68
|
export interface EditorTargets {
|
|
61
|
-
/** JSONC-format MCP entries: Cursor, Claude Code, Antigravity, OpenCode. */
|
|
69
|
+
/** JSONC-format MCP entries: Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder. */
|
|
62
70
|
mcpJsonc: McpJsoncTarget[];
|
|
63
71
|
/** Codex MCP (TOML). */
|
|
64
72
|
codex: CodexMcpTarget;
|
|
@@ -80,6 +88,13 @@ export declare function mcpTarget(id: EditorId, home?: string): McpJsoncTarget;
|
|
|
80
88
|
export declare function skillTarget(id: EditorId, home?: string): SkillTarget;
|
|
81
89
|
/** Look up a single hook target by editor id (throws if unknown). */
|
|
82
90
|
export declare function hookTarget(id: EditorId, home?: string): HookTarget;
|
|
91
|
+
/**
|
|
92
|
+
* True when err is a Node fs error with code ENOENT (file/dir absent).
|
|
93
|
+
* Shared by setup and uninstall: both must swallow ONLY absence when reading
|
|
94
|
+
* editor configs — any other read/stat failure (EACCES, EIO) is surfaced so an
|
|
95
|
+
* unreadable config is never treated as empty and rewritten gitnexus-only.
|
|
96
|
+
*/
|
|
97
|
+
export declare function isEnoent(err: unknown): boolean;
|
|
83
98
|
/**
|
|
84
99
|
* Detect indentation style from file content so JSONC edits preserve the file's
|
|
85
100
|
* existing formatting. Shared by setup (writes) and uninstall (removes).
|
|
@@ -50,6 +50,26 @@ export function getEditorTargets(home = os.homedir()) {
|
|
|
50
50
|
// OpenCode nests servers under `mcp`, not `mcpServers`.
|
|
51
51
|
keyPath: ['mcp', 'gitnexus'],
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
id: 'codebuddy',
|
|
55
|
+
label: 'CodeBuddy',
|
|
56
|
+
// Recommended user-scope path per https://www.codebuddy.ai/docs/cli/mcp;
|
|
57
|
+
// CodeBuddy reads only the first existing file in this priority chain.
|
|
58
|
+
file: path.join(home, '.codebuddy', '.mcp.json'),
|
|
59
|
+
legacyFiles: [
|
|
60
|
+
path.join(home, '.codebuddy', 'mcp.json'), // deprecated
|
|
61
|
+
path.join(home, '.codebuddy.json'), // legacy
|
|
62
|
+
],
|
|
63
|
+
keyPath: ['mcpServers', 'gitnexus'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'qoder',
|
|
67
|
+
label: 'Qoder',
|
|
68
|
+
// Qoder's documented user-scope MCP config (https://docs.qoder.com/cli/using-cli);
|
|
69
|
+
// the IDE manages MCP via its Settings UI with no documented file path.
|
|
70
|
+
file: path.join(home, '.qoder.json'),
|
|
71
|
+
keyPath: ['mcpServers', 'gitnexus'],
|
|
72
|
+
},
|
|
53
73
|
];
|
|
54
74
|
const codex = {
|
|
55
75
|
id: 'codex',
|
|
@@ -66,6 +86,10 @@ export function getEditorTargets(home = os.homedir()) {
|
|
|
66
86
|
},
|
|
67
87
|
{ id: 'cursor', label: 'Cursor', dir: path.join(home, '.cursor', 'skills') },
|
|
68
88
|
{ id: 'opencode', label: 'OpenCode', dir: path.join(home, '.config', 'opencode', 'skills') },
|
|
89
|
+
{ id: 'codebuddy', label: 'CodeBuddy', dir: path.join(home, '.codebuddy', 'skills') },
|
|
90
|
+
// Qoder skills live at ~/.qoder/skills/{name}/SKILL.md
|
|
91
|
+
// (https://docs.qoder.com/extensions/skills).
|
|
92
|
+
{ id: 'qoder', label: 'Qoder', dir: path.join(home, '.qoder', 'skills') },
|
|
69
93
|
// Codex reads skills from ~/.agents/skills (not ~/.codex).
|
|
70
94
|
{ id: 'codex', label: 'Codex', dir: path.join(home, '.agents', 'skills') },
|
|
71
95
|
];
|
|
@@ -78,6 +102,17 @@ export function getEditorTargets(home = os.homedir()) {
|
|
|
78
102
|
needle: 'gitnexus-hook',
|
|
79
103
|
scriptDir: path.join(home, '.claude', 'hooks', 'gitnexus'),
|
|
80
104
|
},
|
|
105
|
+
{
|
|
106
|
+
id: 'codex',
|
|
107
|
+
label: 'Codex',
|
|
108
|
+
// Codex hooks use Claude Code's exact {hooks: {Event: [...]}} JSON shape
|
|
109
|
+
// and hookSpecificOutput response contract, in a dedicated hooks.json
|
|
110
|
+
// (https://developers.openai.com/codex/hooks).
|
|
111
|
+
settingsFile: path.join(home, '.codex', 'hooks.json'),
|
|
112
|
+
events: ['PreToolUse', 'PostToolUse'],
|
|
113
|
+
needle: 'gitnexus-hook',
|
|
114
|
+
scriptDir: path.join(home, '.codex', 'hooks', 'gitnexus'),
|
|
115
|
+
},
|
|
81
116
|
{
|
|
82
117
|
id: 'antigravity',
|
|
83
118
|
label: 'Antigravity',
|
|
@@ -110,6 +145,15 @@ export function hookTarget(id, home) {
|
|
|
110
145
|
throw new Error(`No hook target for editor "${id}"`);
|
|
111
146
|
return t;
|
|
112
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* True when err is a Node fs error with code ENOENT (file/dir absent).
|
|
150
|
+
* Shared by setup and uninstall: both must swallow ONLY absence when reading
|
|
151
|
+
* editor configs — any other read/stat failure (EACCES, EIO) is surfaced so an
|
|
152
|
+
* unreadable config is never treated as empty and rewritten gitnexus-only.
|
|
153
|
+
*/
|
|
154
|
+
export function isEnoent(err) {
|
|
155
|
+
return err?.code === 'ENOENT';
|
|
156
|
+
}
|
|
113
157
|
/**
|
|
114
158
|
* Detect indentation style from file content so JSONC edits preserve the file's
|
|
115
159
|
* existing formatting. Shared by setup (writes) and uninstall (removes).
|