gitnexus 1.6.9-rc.44 → 1.6.9-rc.46

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 CHANGED
@@ -40,14 +40,14 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
40
40
 
41
41
  | Editor | MCP | Skills | Hooks (auto-augment) | Support |
42
42
  | ------------------------ | --- | ------ | ------------------------------------------------------------------------------------------ | ------------ |
43
- | **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
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
46
  | **Codex** | Yes | Yes | — | MCP + Skills |
47
- | **Windsurf** | Yes | — | — | MCP |
48
47
  | **OpenCode** | Yes | Yes | — | MCP + Skills |
48
+ | **Windsurf** | Yes | — | — | MCP |
49
49
 
50
- > **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context.
50
+ > **Claude Code** gets 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
51
 
52
52
  ### Community Integrations
53
53
 
@@ -122,31 +122,44 @@ The result is a **LadybugDB graph database** stored locally in `.gitnexus/` with
122
122
 
123
123
  ## MCP Tools
124
124
 
125
- Your AI agent gets these tools automatically:
126
-
127
- | Tool | What It Does | `repo` Param |
128
- | ---------------- | ---------------------------------------------------------------- | ------------ |
129
- | `list_repos` | Discover all indexed repositories (paginated — `limit`/`offset`) | — |
130
- | `query` | Process-grouped hybrid search (BM25 + semantic + RRF) | Optional |
131
- | `context` | 360-degree symbol view — categorized refs, process participation | Optional |
132
- | `impact` | Blast radius analysis with depth grouping and confidence | Optional |
133
- | `detect_changes` | Git-diff impact maps changed lines to affected processes | Optional |
134
- | `rename` | Multi-file coordinated rename with graph + text search | Optional |
135
- | `cypher` | Raw Cypher graph queries | Optional |
136
-
137
- > With one indexed repo, the `repo` param is optional. With multiple, specify which: `query({search_query: "auth", repo: "my-app"})`.
125
+ Your AI agent gets **17 tools** (15 per-repo + 2 group) automatically:
126
+
127
+ | 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 |
146
+
147
+ > 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`.
138
148
 
139
149
  ## MCP Resources
140
150
 
141
151
  | Resource | Purpose |
142
152
  | --------------------------------------- | ---------------------------------------------------- |
143
153
  | `gitnexus://repos` | List all indexed repositories (read first) |
154
+ | `gitnexus://setup` | Setup and usage guidance for agents |
144
155
  | `gitnexus://repo/{name}/context` | Codebase stats, staleness check, and available tools |
145
156
  | `gitnexus://repo/{name}/clusters` | All functional clusters with cohesion scores |
146
157
  | `gitnexus://repo/{name}/cluster/{name}` | Cluster members and details |
147
158
  | `gitnexus://repo/{name}/processes` | All execution flows |
148
159
  | `gitnexus://repo/{name}/process/{name}` | Full process trace with steps |
149
160
  | `gitnexus://repo/{name}/schema` | Graph schema for Cypher queries |
161
+ | `gitnexus://group/{name}/contracts` | A group's extracted contracts and cross-links |
162
+ | `gitnexus://group/{name}/status` | Staleness of repos in a group |
150
163
 
151
164
  ## MCP Prompts
152
165
 
@@ -164,7 +177,11 @@ gitnexus analyze [path] # Index a repository (or update stale index)
164
177
  gitnexus analyze --repair-fts # Fast path: rebuild/verify only FTS indexes on existing index data
165
178
  gitnexus analyze --force # Full rebuild: re-parse + graph rebuild + FTS rebuild
166
179
  gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
180
+ gitnexus analyze --skills # Generate repo-specific skill files from detected communities
167
181
  gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
182
+ gitnexus analyze --skip-skills # Skip installing .claude/skills/gitnexus/ skill files
183
+ gitnexus analyze --skip-git # Index folders that are not Git repositories
184
+ gitnexus analyze --workers <n> # Parse worker pool size (>=1; default: cores-1, capped at 16)
168
185
  gitnexus analyze --verbose # Log skipped files when parsers are unavailable
169
186
  gitnexus analyze --max-file-size 1024 # Skip files larger than N KB (default: 512, cap: 32768)
170
187
  gitnexus analyze --worker-timeout 60 # Increase worker idle timeout for slow parses
@@ -177,13 +194,16 @@ gitnexus status # Show index status for current repo
177
194
  gitnexus clean # Delete index for current repo
178
195
  gitnexus clean --all --force # Delete all indexes
179
196
  gitnexus wiki [path] # Generate LLM-powered docs from knowledge graph
180
- gitnexus wiki --model <model> # Wiki with custom LLM model (default: gpt-4o-mini)
197
+ gitnexus wiki --model <model> # Wiki with custom LLM model (default: minimax/minimax-m2.5)
198
+ gitnexus doctor # Show runtime platform capabilities and embedding configuration
181
199
 
182
200
  # Direct graph queries — the same tools the MCP server exposes, no MCP daemon needed
183
201
  gitnexus query "<concept>" # Process-grouped hybrid search
184
202
  gitnexus context <symbol> [--uid <uid> | --file <path>] # 360° symbol view; flags disambiguate a shared name
185
203
  gitnexus impact <symbol> [--uid <uid> | --file <path> | --kind <kind>] # Blast radius; flags disambiguate a shared name
204
+ gitnexus trace <from> <to> # Shortest directed path between two symbols
186
205
  gitnexus detect-changes # Map the working-tree diff to affected symbols and execution flows
206
+ gitnexus check # Read-only structural checks against the indexed graph
187
207
  gitnexus cypher "<query>" # Run a raw Cypher query against the knowledge graph
188
208
 
189
209
  # Repository groups (multi-repo / monorepo service tracking)
@@ -195,6 +215,7 @@ gitnexus group sync <name> # Extract contrac
195
215
  gitnexus group contracts <name> # Inspect extracted contracts and cross-links
196
216
  gitnexus group query <name> <q> # Search execution flows across all repos in a group
197
217
  gitnexus group status <name> # Check staleness of repos in a group
218
+ gitnexus group impact <name> --target <symbol> --repo <groupPath> # Cross-repo blast radius
198
219
  ```
199
220
 
200
221
  > **`gitnexus uninstall`** reverses `gitnexus setup` — it removes the GitNexus MCP entries, hooks, and skill directories it added to each detected editor. Skill directories are identified **by bundled gitnexus skill name** (e.g. `gitnexus-cli/`), so if you customized files inside an installed skill directory, back them up first. It is a dry-run preview by default and prints the exact paths it would remove; pass `--force` to apply. Per-repo indexes (`gitnexus clean --all`) and the global npm package (`npm uninstall -g gitnexus`) are left for you to remove.
@@ -219,7 +240,7 @@ GitNexus supports indexing multiple repositories. Each `gitnexus analyze` regist
219
240
 
220
241
  ## Supported Languages
221
242
 
222
- TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift, Ruby
243
+ TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift, Ruby, Dart
223
244
 
224
245
  ### Language Feature Matrix
225
246
 
@@ -238,6 +259,7 @@ TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift,
238
259
  | Swift | — | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
239
260
  | C | — | — | ✓ | — | ✓ | ✓ | — | ✓ | ✓ |
240
261
  | C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
262
+ | Dart | ✓ | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
241
263
 
242
264
  **Imports** — cross-file import resolution · **Named Bindings** — `import { X as Y }` / re-export tracking · **Exports** — public/exported symbol detection · **Heritage** — class inheritance, interfaces, mixins · **Type Annotations** — explicit type extraction for receiver resolution · **Constructor Inference** — infer receiver type from constructor calls (`self`/`this` resolution included for all languages) · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics
243
265
 
@@ -249,12 +271,14 @@ GitNexus ships with skill files that teach AI agents how to use the tools effect
249
271
  - **Debugging** — Trace bugs through call chains
250
272
  - **Impact Analysis** — Analyze blast radius before changes
251
273
  - **Refactoring** — Plan safe refactors using dependency mapping
274
+ - **Guide** — GitNexus tool/resource/schema reference for the agent
275
+ - **CLI** — Run analyze/status/clean/wiki commands on request
252
276
 
253
- Installed automatically by both `gitnexus analyze` (per-repo) and `gitnexus setup` (global).
277
+ Installed automatically by both `gitnexus analyze` (per-repo) and `gitnexus setup` (global). Run `gitnexus analyze --skills` to additionally generate repo-specific skills for each detected functional area under `.claude/skills/generated/`.
254
278
 
255
279
  ## Requirements
256
280
 
257
- - Node.js >= 18
281
+ - Node.js >= 22
258
282
  - Git repository (uses git for commit tracking)
259
283
 
260
284
  ## Release candidates
@@ -340,7 +364,7 @@ gitnexus serve
340
364
 
341
365
  ### Installation fails with native module errors
342
366
 
343
- Some optional language grammars (Dart, Kotlin, Swift) require native compilation. If they fail, GitNexus still works — those languages will be skipped.
367
+ Some optional language grammars (Dart, Proto, Swift, Kotlin) require native compilation. If they fail, GitNexus still works — those languages will be skipped. To skip them intentionally (no C++ toolchain needed), set `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` before installing.
344
368
 
345
369
  If `npm install -g gitnexus` fails on native modules:
346
370
 
@@ -1005,10 +1005,11 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
1005
1005
  // preserving the rest of the block (incl. --skills community rows). No-op
1006
1006
  // when the value already matches, so a routine up-to-date run is silent
1007
1007
  // (#1996 tri-review P2).
1008
- // Only refresh the repo-root AGENTS.md/CLAUDE.md base_ref for the
1009
- // PRIMARY/flat index (#2106 R2). A non-primary branch's up-to-date
1010
- // analyze must not churn the committed AGENTS.md — this mirrors the
1011
- // in-pipeline `if (!placement.branch)` gate around generateAIContextFiles.
1008
+ // Only refresh the repo-root AGENTS.md/CLAUDE.md base_ref for the flat
1009
+ // WORKSPACE index (#2106 R2, #2354). A pinned --branch sub-index's
1010
+ // up-to-date analyze must not churn the committed AGENTS.md — this
1011
+ // mirrors the in-pipeline `if (!placement.branch)` gate around
1012
+ // generateAIContextFiles.
1012
1013
  let baseRefRefreshed = [];
1013
1014
  if (result.isPrimaryBranch !== false) {
1014
1015
  try {
@@ -28,7 +28,7 @@ export declare const en: {
28
28
  readonly 'status.currentCommit': "Current commit";
29
29
  readonly 'status.branch': "Branch";
30
30
  readonly 'status.detached': "(detached HEAD)";
31
- readonly 'status.branchNotIndexed': "⚠️ current branch not indexed (primary index is for '{{primary}}'; run gitnexus analyze)";
31
+ readonly 'status.workspaceIndexLabel': "Workspace index: last analyzed on '{{primary}}' (re-run gitnexus analyze to follow the current branch)";
32
32
  readonly 'status.status': "Status";
33
33
  readonly 'status.upToDate': "✅ up-to-date";
34
34
  readonly 'status.stale': "⚠️ stale (re-run gitnexus analyze)";
@@ -162,7 +162,7 @@ export declare const en: {
162
162
  readonly 'help.option.analyze.embeddingBatchSize': "Number of nodes per embedding batch";
163
163
  readonly 'help.option.analyze.embeddingSubBatchSize': "Number of chunks per embedding model call";
164
164
  readonly 'help.option.analyze.embeddingDevice': "Embedding device: auto, cpu, dml, cuda, or wasm";
165
- readonly 'help.option.index.force': "Register even if meta.json is missing (stats will be empty)";
165
+ readonly 'help.option.index.force': "Register even if index metadata is missing (stats will be empty)";
166
166
  readonly 'help.option.index.allowNonGit': "Allow registering folders that are not Git repositories";
167
167
  readonly 'help.option.port': "Port number";
168
168
  readonly 'help.option.serve.host': "Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)";
@@ -172,7 +172,7 @@ export declare const en: {
172
172
  readonly 'help.option.force.confirmation': "Skip confirmation prompt";
173
173
  readonly 'help.option.uninstall.force': "Apply the changes (default is a dry-run preview)";
174
174
  readonly 'help.option.clean.all': "Clean all indexed repos";
175
- readonly 'help.option.clean.branch': "Delete only the named branch index (not the primary)";
175
+ readonly 'help.option.clean.branch': "Delete only the named branch index (not the workspace index)";
176
176
  readonly 'help.option.clean.lbugSidecars': "Clean quarantined LadybugDB missing-shadow WAL sidecars";
177
177
  readonly 'help.option.wiki.force': "Force full regeneration even if up to date";
178
178
  readonly 'help.option.wiki.provider': "LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)";
@@ -28,7 +28,7 @@ export const en = {
28
28
  'status.currentCommit': 'Current commit',
29
29
  'status.branch': 'Branch',
30
30
  'status.detached': '(detached HEAD)',
31
- 'status.branchNotIndexed': "⚠️ current branch not indexed (primary index is for '{{primary}}'; run gitnexus analyze)",
31
+ 'status.workspaceIndexLabel': "Workspace index: last analyzed on '{{primary}}' (re-run gitnexus analyze to follow the current branch)",
32
32
  'status.status': 'Status',
33
33
  'status.upToDate': '✅ up-to-date',
34
34
  'status.stale': '⚠️ stale (re-run gitnexus analyze)',
@@ -162,7 +162,7 @@ export const en = {
162
162
  'help.option.analyze.embeddingBatchSize': 'Number of nodes per embedding batch',
163
163
  'help.option.analyze.embeddingSubBatchSize': 'Number of chunks per embedding model call',
164
164
  'help.option.analyze.embeddingDevice': 'Embedding device: auto, cpu, dml, cuda, or wasm',
165
- 'help.option.index.force': 'Register even if meta.json is missing (stats will be empty)',
165
+ 'help.option.index.force': 'Register even if index metadata is missing (stats will be empty)',
166
166
  'help.option.index.allowNonGit': 'Allow registering folders that are not Git repositories',
167
167
  'help.option.port': 'Port number',
168
168
  'help.option.serve.host': 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)',
@@ -172,7 +172,7 @@ export const en = {
172
172
  'help.option.force.confirmation': 'Skip confirmation prompt',
173
173
  'help.option.uninstall.force': 'Apply the changes (default is a dry-run preview)',
174
174
  'help.option.clean.all': 'Clean all indexed repos',
175
- 'help.option.clean.branch': 'Delete only the named branch index (not the primary)',
175
+ 'help.option.clean.branch': 'Delete only the named branch index (not the workspace index)',
176
176
  'help.option.clean.lbugSidecars': 'Clean quarantined LadybugDB missing-shadow WAL sidecars',
177
177
  'help.option.wiki.force': 'Force full regeneration even if up to date',
178
178
  'help.option.wiki.provider': 'LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)',
@@ -29,7 +29,7 @@ export declare const cliResources: {
29
29
  readonly 'status.currentCommit': "Current commit";
30
30
  readonly 'status.branch': "Branch";
31
31
  readonly 'status.detached': "(detached HEAD)";
32
- readonly 'status.branchNotIndexed': "⚠️ current branch not indexed (primary index is for '{{primary}}'; run gitnexus analyze)";
32
+ readonly 'status.workspaceIndexLabel': "Workspace index: last analyzed on '{{primary}}' (re-run gitnexus analyze to follow the current branch)";
33
33
  readonly 'status.status': "Status";
34
34
  readonly 'status.upToDate': "✅ up-to-date";
35
35
  readonly 'status.stale': "⚠️ stale (re-run gitnexus analyze)";
@@ -163,7 +163,7 @@ export declare const cliResources: {
163
163
  readonly 'help.option.analyze.embeddingBatchSize': "Number of nodes per embedding batch";
164
164
  readonly 'help.option.analyze.embeddingSubBatchSize': "Number of chunks per embedding model call";
165
165
  readonly 'help.option.analyze.embeddingDevice': "Embedding device: auto, cpu, dml, cuda, or wasm";
166
- readonly 'help.option.index.force': "Register even if meta.json is missing (stats will be empty)";
166
+ readonly 'help.option.index.force': "Register even if index metadata is missing (stats will be empty)";
167
167
  readonly 'help.option.index.allowNonGit': "Allow registering folders that are not Git repositories";
168
168
  readonly 'help.option.port': "Port number";
169
169
  readonly 'help.option.serve.host': "Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)";
@@ -173,7 +173,7 @@ export declare const cliResources: {
173
173
  readonly 'help.option.force.confirmation': "Skip confirmation prompt";
174
174
  readonly 'help.option.uninstall.force': "Apply the changes (default is a dry-run preview)";
175
175
  readonly 'help.option.clean.all': "Clean all indexed repos";
176
- readonly 'help.option.clean.branch': "Delete only the named branch index (not the primary)";
176
+ readonly 'help.option.clean.branch': "Delete only the named branch index (not the workspace index)";
177
177
  readonly 'help.option.clean.lbugSidecars': "Clean quarantined LadybugDB missing-shadow WAL sidecars";
178
178
  readonly 'help.option.wiki.force': "Force full regeneration even if up to date";
179
179
  readonly 'help.option.wiki.provider': "LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)";
@@ -271,7 +271,7 @@ export declare const cliResources: {
271
271
  'status.currentCommit': string;
272
272
  'status.branch': string;
273
273
  'status.detached': string;
274
- 'status.branchNotIndexed': string;
274
+ 'status.workspaceIndexLabel': string;
275
275
  'status.status': string;
276
276
  'status.upToDate': string;
277
277
  'status.stale': string;
@@ -28,7 +28,7 @@ export declare const zhCN: {
28
28
  'status.currentCommit': string;
29
29
  'status.branch': string;
30
30
  'status.detached': string;
31
- 'status.branchNotIndexed': string;
31
+ 'status.workspaceIndexLabel': string;
32
32
  'status.status': string;
33
33
  'status.upToDate': string;
34
34
  'status.stale': string;
@@ -28,7 +28,7 @@ export const zhCN = {
28
28
  'status.currentCommit': '当前提交',
29
29
  'status.branch': '分支',
30
30
  'status.detached': '(分离 HEAD)',
31
- 'status.branchNotIndexed': "⚠️ 当前分支未索引(主索引对应 '{{primary}}';请运行 gitnexus analyze",
31
+ 'status.workspaceIndexLabel': "工作区索引:最近在 '{{primary}}' 分支上分析(重新运行 gitnexus analyze 以跟随当前分支)",
32
32
  'status.status': '状态',
33
33
  'status.upToDate': '✅ 已是最新',
34
34
  'status.stale': '⚠️ 已过期(重新运行 gitnexus analyze)',
@@ -162,7 +162,7 @@ export const zhCN = {
162
162
  'help.option.analyze.embeddingBatchSize': '每个嵌入批次的节点数',
163
163
  'help.option.analyze.embeddingSubBatchSize': '每次嵌入模型调用的分块数',
164
164
  'help.option.analyze.embeddingDevice': '嵌入设备:auto、cpu、dml、cuda 或 wasm',
165
- 'help.option.index.force': '即使缺少 meta.json 也注册(统计为空)',
165
+ 'help.option.index.force': '即使缺少索引元数据也注册(统计为空)',
166
166
  'help.option.index.allowNonGit': '允许注册非 Git 仓库文件夹',
167
167
  'help.option.port': '端口号',
168
168
  'help.option.serve.host': '绑定地址(默认:127.0.0.1;远程访问可用 0.0.0.0)',
@@ -172,7 +172,7 @@ export const zhCN = {
172
172
  'help.option.force.confirmation': '跳过确认提示',
173
173
  'help.option.uninstall.force': '应用更改(默认仅为预演预览)',
174
174
  'help.option.clean.all': '清理所有已索引仓库',
175
- 'help.option.clean.branch': '仅删除指定分支的索引(不影响主索引)',
175
+ 'help.option.clean.branch': '仅删除指定分支的索引(不影响工作区索引)',
176
176
  'help.option.clean.lbugSidecars': '清理已隔离的 LadybugDB missing-shadow WAL sidecar',
177
177
  'help.option.wiki.force': '即使已是最新也强制完整重新生成',
178
178
  'help.option.wiki.provider': 'LLM 提供商:openai、openrouter、azure、custom、cursor、claude、codex 或 opencode(默认:openai)',
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Index Command
3
3
  *
4
- * Registers an existing .gitnexus/ folder into the global registry so the
4
+ * Registers an existing GitNexus index into the global registry so the
5
5
  * MCP server can discover the repo without running a full `gitnexus analyze`.
6
6
  *
7
- * Useful when a pre-built .gitnexus/ directory is already present (e.g. after
7
+ * The index can be either:
8
+ * - A per-worktree gitnexus.json file under .gitnexus/ (new format, worktree-compatible)
9
+ * - A legacy .gitnexus/meta.json file (auto-migrated on analyze)
10
+ *
11
+ * Useful when a pre-built index is already present (e.g. after
8
12
  * cloning a repo that ships its index, restoring from backup, or using a
9
13
  * shared team index).
10
14
  */
@@ -1,16 +1,20 @@
1
1
  /**
2
2
  * Index Command
3
3
  *
4
- * Registers an existing .gitnexus/ folder into the global registry so the
4
+ * Registers an existing GitNexus index into the global registry so the
5
5
  * MCP server can discover the repo without running a full `gitnexus analyze`.
6
6
  *
7
- * Useful when a pre-built .gitnexus/ directory is already present (e.g. after
7
+ * The index can be either:
8
+ * - A per-worktree gitnexus.json file under .gitnexus/ (new format, worktree-compatible)
9
+ * - A legacy .gitnexus/meta.json file (auto-migrated on analyze)
10
+ *
11
+ * Useful when a pre-built index is already present (e.g. after
8
12
  * cloning a repo that ships its index, restoring from backup, or using a
9
13
  * shared team index).
10
14
  */
11
15
  import path from 'path';
12
16
  import fs from 'fs/promises';
13
- import { getStoragePaths, loadMeta, ensureGitNexusIgnored, registerRepo, } from '../storage/repo-manager.js';
17
+ import { getStoragePaths, INDEX_METADATA_FILE, loadMeta, ensureGitNexusIgnored, registerRepo, } from '../storage/repo-manager.js';
14
18
  import { getGitRoot, getRemoteUrl, isGitRepo } from '../storage/git.js';
15
19
  export const indexCommand = async (inputPathParts, options) => {
16
20
  console.log('\n GitNexus Index\n');
@@ -50,22 +54,35 @@ export const indexCommand = async (inputPathParts, options) => {
50
54
  return;
51
55
  }
52
56
  const { storagePath, lbugPath } = getStoragePaths(repoPath);
53
- // ── Verify .gitnexus/ exists ──────────────────────────────────────
57
+ // ── Verify index exists (metadata file, legacy metadata, or restorable DB) ─
58
+ let hasMetadataIndex = false;
59
+ let hasLegacyIndex = false;
60
+ let hasLbugIndex = false;
61
+ try {
62
+ await fs.access(path.join(storagePath, INDEX_METADATA_FILE));
63
+ hasMetadataIndex = true;
64
+ }
65
+ catch { }
54
66
  try {
55
- await fs.access(storagePath);
67
+ await fs.access(path.join(storagePath, 'meta.json'));
68
+ hasLegacyIndex = true;
56
69
  }
57
- catch {
58
- console.log(` No .gitnexus/ folder found at: ${storagePath}`);
70
+ catch { }
71
+ try {
72
+ await fs.access(lbugPath);
73
+ hasLbugIndex = true;
74
+ }
75
+ catch { }
76
+ if (!hasMetadataIndex && !hasLegacyIndex && !hasLbugIndex) {
77
+ console.log(` No GitNexus index found.`);
78
+ console.log(` Expected gitnexus.json, .gitnexus/meta.json, or LadybugDB at: ${storagePath}`);
59
79
  console.log(' Run `gitnexus analyze` to build the index first.\n');
60
80
  process.exitCode = 1;
61
81
  return;
62
82
  }
63
83
  // ── Verify lbug database exists ───────────────────────────────────
64
- try {
65
- await fs.access(lbugPath);
66
- }
67
- catch {
68
- console.log(` .gitnexus/ folder exists but contains no LadybugDB index.`);
84
+ if (!hasLbugIndex) {
85
+ console.log(` Index exists but contains no LadybugDB database.`);
69
86
  console.log(' Run `gitnexus analyze` to build the index.\n');
70
87
  process.exitCode = 1;
71
88
  return;
@@ -74,7 +91,7 @@ export const indexCommand = async (inputPathParts, options) => {
74
91
  let meta = await loadMeta(storagePath);
75
92
  if (!meta) {
76
93
  if (!options?.force) {
77
- console.log(` .gitnexus/ exists but meta.json is missing.`);
94
+ console.log(` gitnexus.json or .gitnexus/meta.json is missing.`);
78
95
  console.log(' Use --force to register anyway (stats will be empty),');
79
96
  console.log(' or run `gitnexus analyze` to rebuild properly.\n');
80
97
  process.exitCode = 1;
package/dist/cli/index.js CHANGED
@@ -49,9 +49,9 @@ program
49
49
  'for supported languages. Opt-in; off by default. (#2081 M1)')
50
50
  .option('--default-branch <branch>', 'Default branch used in the generated regression-compare example (base_ref). ' +
51
51
  'Falls back to .gitnexusrc, then auto-detected origin/HEAD, then "main".')
52
- .option('--branch <name>', 'Index the working tree under a specific branch slot (multi-branch indexing). ' +
53
- 'Defaults to the checked-out branch; the primary/first-indexed branch keeps the ' +
54
- 'flat index and others get their own. Distinct from --default-branch (cosmetic base_ref).')
52
+ .option('--branch <name>', 'Pin the working tree into a dedicated per-branch index slot (multi-branch indexing). ' +
53
+ 'Without this flag, analyze always updates the workspace index, which follows the ' +
54
+ 'checked-out working tree. Distinct from --default-branch (cosmetic base_ref).')
55
55
  .option('--no-stats', 'Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md')
56
56
  .option('--skip-skills', 'Skip installing standard GitNexus skill files under .claude/skills/gitnexus/. ' +
57
57
  'Does not suppress community skills from --skills (those use .claude/skills/generated/). ' +
@@ -123,7 +123,7 @@ program
123
123
  program
124
124
  .command('index [path...]')
125
125
  .description('Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)')
126
- .option('-f, --force', 'Register even if meta.json is missing (stats will be empty)')
126
+ .option('-f, --force', 'Register even if index metadata is missing (stats will be empty)')
127
127
  .option('--allow-non-git', 'Allow registering folders that are not Git repositories')
128
128
  .action(createLazyAction(() => import('./index-repo.js'), 'indexCommand'));
129
129
  program
@@ -158,7 +158,7 @@ program
158
158
  .description('Delete GitNexus index for current repo')
159
159
  .option('-f, --force', 'Skip confirmation prompt')
160
160
  .option('--all', 'Clean all indexed repos')
161
- .option('--branch <name>', 'Delete only the named branch index (not the primary)')
161
+ .option('--branch <name>', 'Delete only the named branch index (not the workspace index)')
162
162
  .option('--lbug-sidecars', 'Clean quarantined LadybugDB missing-shadow WAL sidecars')
163
163
  .action(createLazyAction(() => import('./clean.js'), 'cleanCommand'));
164
164
  program
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Remove Command (#664)
3
3
  *
4
- * Delete the `.gitnexus/` index for a registered repo and unregister it
5
- * from the global registry (~/.gitnexus/registry.json). The target is
6
- * identified by alias / basename-derived name / remote-inferred name /
4
+ * Delete the `.gitnexus/` index directory for a registered repo (including
5
+ * both metadata filenames gitnexus.json and its legacy meta.json mirror
6
+ * which live inside it) and unregister it from the global registry
7
+ * (~/.gitnexus/registry.json).
8
+ *
9
+ * The target is identified by alias / basename-derived name / remote-inferred name /
7
10
  * absolute path — no `--repo` flag, just a positional argument so the
8
11
  * destructive-command ergonomics match `clean` (which is also
9
12
  * destructive but scoped to `process.cwd()`).
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Remove Command (#664)
3
3
  *
4
- * Delete the `.gitnexus/` index for a registered repo and unregister it
5
- * from the global registry (~/.gitnexus/registry.json). The target is
6
- * identified by alias / basename-derived name / remote-inferred name /
4
+ * Delete the `.gitnexus/` index directory for a registered repo (including
5
+ * both metadata filenames gitnexus.json and its legacy meta.json mirror
6
+ * which live inside it) and unregister it from the global registry
7
+ * (~/.gitnexus/registry.json).
8
+ *
9
+ * The target is identified by alias / basename-derived name / remote-inferred name /
7
10
  * absolute path — no `--repo` flag, just a positional argument so the
8
11
  * destructive-command ergonomics match `clean` (which is also
9
12
  * destructive but scoped to `process.cwd()`).
@@ -30,25 +30,25 @@ export const statusCommand = async () => {
30
30
  }
31
31
  const currentCommit = getCurrentCommit(repo.repoPath);
32
32
  const currentBranch = getCurrentBranch(repo.repoPath);
33
- // Pick the index matching the checked-out branch (#2106). The flat index
34
- // belongs to the primary branch (repo.meta.branch); when the current branch
35
- // differs and has its own index, report that one. Legacy/no-branch metas and
36
- // detached HEAD fall through to the flat index (unchanged behavior).
33
+ // Pick the index matching the checked-out branch (#2106/#2354). A pinned
34
+ // `--branch` sub-index for the current branch wins; otherwise report the
35
+ // flat workspace index, which follows the checked-out working tree — the
36
+ // commit comparison below then says whether it needs a re-analyze. Legacy/
37
+ // no-branch metas and detached HEAD also fall through to the flat index.
37
38
  let activeMeta = repo.meta;
38
- let currentBranchIndexed = true;
39
+ let workspaceLagsBranch = false;
39
40
  if (currentBranch && repo.meta.branch && currentBranch !== repo.meta.branch) {
40
41
  const { metaPath } = getStoragePaths(repo.repoPath, currentBranch);
41
42
  const branchMeta = await loadMeta(path.dirname(metaPath));
42
43
  if (branchMeta)
43
44
  activeMeta = branchMeta;
44
45
  else
45
- currentBranchIndexed = false;
46
+ workspaceLagsBranch = true;
46
47
  }
47
48
  console.log(`${t('status.repository')}: ${repo.repoPath}`);
48
49
  console.log(`${t('status.branch')}: ${currentBranch ?? t('status.detached')}`);
49
- if (!currentBranchIndexed) {
50
- console.log(`${t('status.status')}: ${t('status.branchNotIndexed', { primary: repo.meta.branch ?? '' })}`);
51
- return;
50
+ if (workspaceLagsBranch) {
51
+ console.log(t('status.workspaceIndexLabel', { primary: repo.meta.branch ?? '' }));
52
52
  }
53
53
  const isUpToDate = currentCommit === activeMeta.lastCommit;
54
54
  console.log(`${t('status.indexed')}: ${new Date(activeMeta.indexedAt).toLocaleString()}`);
@@ -110,7 +110,6 @@ export declare function queryBridge<T>(handle: BridgeHandle, cypher: string, par
110
110
  * is a lease release, so the file may stay open under other readers.
111
111
  */
112
112
  export declare function closeBridgeDb(handle: BridgeHandle): Promise<void>;
113
- export declare function retryRename(src: string, dst: string, attempts?: number): Promise<void>;
114
113
  export declare function writeBridgeMeta(groupDir: string, meta: BridgeMeta): Promise<void>;
115
114
  export declare function readBridgeMeta(groupDir: string): Promise<BridgeMeta>;
116
115
  export interface WriteBridgeInput {
@@ -6,6 +6,7 @@ import { BRIDGE_SCHEMA_QUERIES, BRIDGE_SCHEMA_VERSION } from './bridge-schema.js
6
6
  import { closeLbugConnection, openLbugConnection, } from '../lbug/lbug-config.js';
7
7
  import { dedupeContracts, dedupeCrossLinks } from './normalization.js';
8
8
  import { createLogger } from '../logger.js';
9
+ import { retryRename } from '../../storage/fs-atomic.js';
9
10
  const bridgeLogger = createLogger('bridge-db', {
10
11
  debugEnvVar: 'GITNEXUS_DEBUG_BRIDGE',
11
12
  });
@@ -512,24 +513,6 @@ export async function closeBridgeDb(handle) {
512
513
  // The read-only CHECKPOINT skip above remains the load-bearing fix on
513
514
  // Linux/macOS.
514
515
  /* ------------------------------------------------------------------ */
515
- /* retryRename — handles transient EBUSY/EPERM/EACCES on Windows */
516
- /* ------------------------------------------------------------------ */
517
- const RETRY_CODES = new Set(['EBUSY', 'EPERM', 'EACCES']);
518
- export async function retryRename(src, dst, attempts = 3) {
519
- for (let i = 1; i <= attempts; i++) {
520
- try {
521
- await fsp.rename(src, dst);
522
- return;
523
- }
524
- catch (err) {
525
- const code = err.code;
526
- if (!code || !RETRY_CODES.has(code) || i === attempts)
527
- throw err;
528
- await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i - 1)));
529
- }
530
- }
531
- }
532
- /* ------------------------------------------------------------------ */
533
516
  /* writeBridgeMeta / readBridgeMeta */
534
517
  /* ------------------------------------------------------------------ */
535
518
  export async function writeBridgeMeta(groupDir, meta) {
@@ -5,6 +5,7 @@
5
5
  import fsp from 'node:fs/promises';
6
6
  import path from 'node:path';
7
7
  import { checkStaleness } from '../git-staleness.js';
8
+ import { loadMeta } from '../../storage/repo-manager.js';
8
9
  import { GroupNotFoundError, loadGroupConfig } from './config-parser.js';
9
10
  import { fileMatchesServicePrefix, normalizeServicePrefix, repoInSubgroup, } from './group-path-utils.js';
10
11
  import { getDefaultGitnexusDir, getGroupDir, listGroups, readContractRegistry } from './storage.js';
@@ -370,9 +371,7 @@ export class GroupService {
370
371
  for (const [repoPath, registryName] of Object.entries(config.repos)) {
371
372
  try {
372
373
  const repoObj = await this.port.resolveRepo(registryName);
373
- const metaPath = path.join(repoObj.storagePath, 'meta.json');
374
- const metaRaw = await fsp.readFile(metaPath, 'utf-8').catch(() => '{}');
375
- const meta = JSON.parse(metaRaw);
374
+ const meta = (await loadMeta(repoObj.storagePath)) ?? {};
376
375
  const staleness = meta.lastCommit
377
376
  ? checkStaleness(repoObj.repoPath, meta.lastCommit)
378
377
  : { isStale: true, commitsBehind: -1 };
@@ -3,7 +3,7 @@ import * as fsp from 'node:fs/promises';
3
3
  import * as path from 'node:path';
4
4
  import * as os from 'node:os';
5
5
  import { randomBytes } from 'node:crypto';
6
- import { retryRename } from './bridge-db.js';
6
+ import { retryRename } from '../../storage/fs-atomic.js';
7
7
  /**
8
8
  * Build an unpredictable suffix for atomic-write tmp files. Replaces the
9
9
  * previous `Date.now()` pattern which CodeQL flagged as