gitnexus 1.6.13-rc.34 → 1.6.13-rc.36

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.
Files changed (97) hide show
  1. package/README.md +30 -2
  2. package/dist/cli/ai-context.js +11 -3
  3. package/dist/cli/analyze-options.d.ts +4 -0
  4. package/dist/cli/analyze.js +2 -0
  5. package/dist/cli/clean.d.ts +2 -0
  6. package/dist/cli/clean.js +95 -5
  7. package/dist/cli/doctor.js +7 -0
  8. package/dist/cli/editor-targets.d.ts +1 -1
  9. package/dist/cli/editor-targets.js +12 -0
  10. package/dist/cli/embeddings-sync.js +9 -1
  11. package/dist/cli/help-i18n.js +4 -0
  12. package/dist/cli/i18n/en.d.ts +20 -1
  13. package/dist/cli/i18n/en.js +20 -1
  14. package/dist/cli/i18n/resources.d.ts +39 -1
  15. package/dist/cli/i18n/zh-CN.d.ts +19 -0
  16. package/dist/cli/i18n/zh-CN.js +20 -1
  17. package/dist/cli/index.js +9 -1
  18. package/dist/cli/remove.js +3 -3
  19. package/dist/cli/setup.js +57 -0
  20. package/dist/cli/status.js +46 -0
  21. package/dist/core/augmentation/engine.js +4 -1
  22. package/dist/core/group/sync.js +2 -2
  23. package/dist/core/ingestion/class-extractors/configs/ruby.d.ts +13 -1
  24. package/dist/core/ingestion/class-extractors/configs/ruby.js +50 -2
  25. package/dist/core/ingestion/languages/ruby.js +17 -2
  26. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  27. package/dist/core/ingestion/tree-sitter-queries.js +74 -0
  28. package/dist/core/ingestion/utils/ast-helpers.js +16 -0
  29. package/dist/core/run-analyze.d.ts +7 -0
  30. package/dist/core/run-analyze.js +127 -13
  31. package/dist/core/shared-store-analyze.d.ts +89 -0
  32. package/dist/core/shared-store-analyze.js +570 -0
  33. package/dist/mcp/local/local-backend.d.ts +8 -0
  34. package/dist/mcp/local/local-backend.js +23 -3
  35. package/dist/server/analyze-launch.js +27 -8
  36. package/dist/server/analyze-worker-ipc.d.ts +1 -1
  37. package/dist/server/analyze-worker-ipc.js +3 -0
  38. package/dist/server/api.js +39 -10
  39. package/dist/storage/git.d.ts +13 -0
  40. package/dist/storage/git.js +55 -0
  41. package/dist/storage/index-lock.d.ts +5 -0
  42. package/dist/storage/index-lock.js +1 -1
  43. package/dist/storage/repo-manager.d.ts +21 -3
  44. package/dist/storage/repo-manager.js +27 -5
  45. package/dist/storage/repo-meta.d.ts +6 -0
  46. package/dist/storage/shared-store-lifecycle.d.ts +95 -0
  47. package/dist/storage/shared-store-lifecycle.js +393 -0
  48. package/dist/storage/shared-store.d.ts +65 -0
  49. package/dist/storage/shared-store.js +297 -0
  50. package/dist/storage/storage-resolver.d.ts +2 -2
  51. package/dist/storage/storage-resolver.js +27 -32
  52. package/dist/storage/storage-slot.d.ts +17 -0
  53. package/dist/storage/storage-slot.js +48 -0
  54. package/hooks/claude/hook-db-lock-probe.cjs +74 -43
  55. package/hooks/claude/hook-lock.cjs +130 -6
  56. package/hooks/claude/registry-query.cjs +61 -29
  57. package/package.json +1 -1
  58. package/scripts/cross-platform-tests.ts +1 -0
  59. package/scripts/sync-plugin-manifests.mjs +13 -0
  60. package/web/assets/{abnfDiagram-PAR5IXJI-nRjsvWp-.js → abnfDiagram-PAR5IXJI-CM9OPETj.js} +1 -1
  61. package/web/assets/{agent-Bv5z78CW.js → agent-C8bWDcKv.js} +2 -2
  62. package/web/assets/{architectureDiagram-2PNWDENW-56fl8SSH.js → architectureDiagram-2PNWDENW-CFGPVOFL.js} +1 -1
  63. package/web/assets/{chunk-32MCUW2P-xLjc0mcF.js → chunk-32MCUW2P-BAKWSoJ_.js} +1 -1
  64. package/web/assets/{chunk-CLS4B6BI-DPDLP81t.js → chunk-CLS4B6BI-C6Eyf-Zq.js} +1 -1
  65. package/web/assets/{chunk-NF366YI6-D2OzsNXY.js → chunk-NF366YI6-BjQUpZZg.js} +1 -1
  66. package/web/assets/{chunk-SLDQ463B-DUcQswS-.js → chunk-SLDQ463B-DAs7sJcm.js} +1 -1
  67. package/web/assets/{classDiagram-FGAMPOII-q43g3Dpn.js → classDiagram-FGAMPOII-ipJcwT2g.js} +1 -1
  68. package/web/assets/{classDiagram-v2-AI5RJEK4-q43g3Dpn.js → classDiagram-v2-AI5RJEK4-ipJcwT2g.js} +1 -1
  69. package/web/assets/{cynefinDiagram-2OOWUY4M-Cfoko1Ws.js → cynefinDiagram-2OOWUY4M-BfCkzyqC.js} +1 -1
  70. package/web/assets/{dagre-MPVFI544-CTob3D2r.js → dagre-MPVFI544-CMeiFj2H.js} +1 -1
  71. package/web/assets/{diagram-AE4NWXFW-Dt2xfADa.js → diagram-AE4NWXFW-B7JUvrjH.js} +1 -1
  72. package/web/assets/{diagram-BUJID5UG-D-RUCl07.js → diagram-BUJID5UG-KMWjNniB.js} +1 -1
  73. package/web/assets/{diagram-C44CZ5NI-B9gPH4Gl.js → diagram-C44CZ5NI-Bx93uHLb.js} +1 -1
  74. package/web/assets/{diagram-EYOLEXXS-wR__cI23.js → diagram-EYOLEXXS-Dsja58oR.js} +1 -1
  75. package/web/assets/{ebnfDiagram-FTPZ3PV6-uw79FGz3.js → ebnfDiagram-FTPZ3PV6-Iwxb9NAS.js} +1 -1
  76. package/web/assets/{erDiagram-H3ERR26V-ZP_VIa5d.js → erDiagram-H3ERR26V-CJU6qpcb.js} +1 -1
  77. package/web/assets/{flowDiagram-YHGXBVSY-DEsmG3vH.js → flowDiagram-YHGXBVSY-Dz6dhw6j.js} +1 -1
  78. package/web/assets/{index-DofRiYRj.js → index-dXsywuPQ.js} +7 -7
  79. package/web/assets/{infoDiagram-LV547AFH-D1JPzJlf.js → infoDiagram-LV547AFH-P074dR6u.js} +1 -1
  80. package/web/assets/{ishikawaDiagram-PUJQGNDS-DxWZMRPX.js → ishikawaDiagram-PUJQGNDS-CZlaIkzm.js} +1 -1
  81. package/web/assets/{kanban-definition-JSQERAZB-S1lKJN_M.js → kanban-definition-JSQERAZB-BLX38hbF.js} +1 -1
  82. package/web/assets/{mindmap-definition-VBJCLQLM-JwX0Gc3J.js → mindmap-definition-VBJCLQLM-Bnu-7Z_9.js} +1 -1
  83. package/web/assets/{node.browser-C9G7Ehhe.js → node.browser-DIc1knUO.js} +1 -1
  84. package/web/assets/{pegDiagram-7WILOQTJ-DJWiAJJM.js → pegDiagram-7WILOQTJ-SH-3F770.js} +1 -1
  85. package/web/assets/{pieDiagram-EFA7CBSB-3i7IEboS.js → pieDiagram-EFA7CBSB-BGEXuMLa.js} +1 -1
  86. package/web/assets/{railroadDiagram-5NIQVNLQ-CQGcjlaN.js → railroadDiagram-5NIQVNLQ-DZMK703w.js} +1 -1
  87. package/web/assets/{requirementDiagram-SXREGOKZ-DH-LMk5X.js → requirementDiagram-SXREGOKZ-DLW6bNTm.js} +1 -1
  88. package/web/assets/{sequenceDiagram-52ZFIFDL-CrUW-Kdw.js → sequenceDiagram-52ZFIFDL-Bu6IKvYQ.js} +1 -1
  89. package/web/assets/{stateDiagram-3QAKPIVI-BuvX8wUJ.js → stateDiagram-3QAKPIVI-BrLxhUMB.js} +1 -1
  90. package/web/assets/{stateDiagram-v2-HN2P4RDZ-N7DodpOh.js → stateDiagram-v2-HN2P4RDZ-yZRLNv94.js} +1 -1
  91. package/web/assets/{swimlanes-QLVP42QD-DpvGI6UP.js → swimlanes-QLVP42QD-BN3CFF8X.js} +1 -1
  92. package/web/assets/{swimlanesDiagram-JKPDBFCN-Rd-o-as4.js → swimlanesDiagram-JKPDBFCN-BQ7NwLky.js} +1 -1
  93. package/web/assets/{timeline-definition-GS45XAXW-B9Rw1d9t.js → timeline-definition-GS45XAXW-DMlAtIe0.js} +1 -1
  94. package/web/assets/{vennDiagram-QIQU2ZRJ-CGo08RCI.js → vennDiagram-QIQU2ZRJ-DWmjtOEV.js} +1 -1
  95. package/web/assets/{wardleyDiagram-U6YNM66N-Dk7T5hBn.js → wardleyDiagram-U6YNM66N-BRdmEKKB.js} +1 -1
  96. package/web/assets/{xychartDiagram-XJAZEDNM-BJr5UGSi.js → xychartDiagram-XJAZEDNM-roHne1SF.js} +1 -1
  97. package/web/index.html +1 -1
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**, **CodeBuddy** (Tencent), **Qoder** (Alibaba), and any MCP-compatible tool.
5
+ Works with **Cursor**, **Claude Code**, **Antigravity** (Google), **Codex**, **Factory** (Droid), **Windsurf**, **Cline**, **OpenCode**, **CodeBuddy** (Tencent), **Qoder** (Alibaba), and any MCP-compatible tool.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/gitnexus.svg)](https://www.npmjs.com/package/gitnexus)
8
8
  [![License: PolyForm Noncommercial](https://img.shields.io/badge/License-PolyForm%20Noncommercial-blue.svg)](https://polyformproject.org/licenses/noncommercial/1.0.0/)
@@ -44,12 +44,13 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
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 | Yes (PreToolUse + PostToolUse, [Codex hooks](https://developers.openai.com/codex/hooks)) | **Full** |
47
+ | **Factory** (Droid) | Yes | Yes | Yes (PostToolUse, [plugin](../gitnexus-factory-plugin/)) | **Full** |
47
48
  | **OpenCode** | Yes | Yes | — | MCP + Skills |
48
49
  | **CodeBuddy** (Tencent) | Yes | Yes | — | MCP + Skills |
49
50
  | **Qoder** (Alibaba) | Yes | Yes | — | MCP + Skills |
50
51
  | **Windsurf** | Yes | — | — | MCP |
51
52
 
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.
53
+ > **Full** means MCP tools + agent skills + hooks that enrich searches with graph context. **Claude Code** and **Codex** go deepest: their PreToolUse hooks enrich the search before it runs, and their PostToolUse hooks also detect a stale index after commits and prompt the agent to reindex. **Cursor**, **Antigravity**, and **Factory** augment from a post-tool hook only, so they enrich the result rather than the query and do not carry the stale-index hint.
53
54
 
54
55
  ### Community Integrations
55
56
 
@@ -88,6 +89,33 @@ codex plugin marketplace add abhigyanpatwari/GitNexus
88
89
 
89
90
  > **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
91
 
92
+ ### Factory (Droid) (full support — MCP + skills + hooks)
93
+
94
+ `gitnexus setup -c droid` writes the MCP server to `~/.factory/mcp.json` and installs skills to
95
+ `~/.factory/skills/`. To configure MCP by hand instead, add to `~/.factory/mcp.json`
96
+ ([user scope](https://docs.factory.ai/cli/configuration/mcp) — applies to all projects):
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "gitnexus": {
102
+ "command": "npx",
103
+ "args": ["-y", "gitnexus@latest", "mcp"]
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ For the PostToolUse search-augment hook, install the bundled
110
+ [`gitnexus-factory-plugin/`](../gitnexus-factory-plugin/) with `droid plugin install gitnexus@<marketplace>`
111
+ from a marketplace that includes this repo, or point Droid at it via `extraKnownMarketplaces` in
112
+ `.factory/settings.json`.
113
+
114
+ > **Factory notes:** Droid reads [AGENTS.md natively](https://docs.factory.ai/), which already carries the
115
+ > GitNexus context block, so no SessionStart hook is registered. Pick **one** install route
116
+ > (`gitnexus setup -c droid` **or** the plugin) — the plugin ships its own MCP entry, so installing both
117
+ > can register the server twice.
118
+
91
119
  ### Cursor / Windsurf
92
120
 
93
121
  Add to `~/.cursor/mcp.json` (global — works for all projects):
@@ -5,6 +5,8 @@
5
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
+ import { GITNEXUS_DIR } from '../storage/storage-constants.js';
9
+ import { storeRootOfCheckoutSlot } from '../storage/shared-store.js';
8
10
  import fs from 'fs/promises';
9
11
  import path from 'path';
10
12
  import { fileURLToPath } from 'url';
@@ -470,11 +472,17 @@ export async function generateAIContextFiles(repoPath, storagePath, projectName,
470
472
  // CLI and hooks already share; failure to copy is non-fatal (docs carry a
471
473
  // bootstrap fallback). `runnerPath` is project-relative with POSIX separators
472
474
  // so the emitted command is identical across platforms.
473
- const runnerPath = path.relative(repoPath, path.join(storagePath, 'run.cjs')).replace(/\\/g, '/');
475
+ // A shared-store slot (#3352) lives under the GitNexus home, so its path
476
+ // would be machine- and worktree-specific in committed docs; the runner goes
477
+ // next to the checkout's store pointer instead.
478
+ const runnerDir = storeRootOfCheckoutSlot(storagePath)
479
+ ? path.join(repoPath, GITNEXUS_DIR)
480
+ : storagePath;
481
+ const runnerPath = path.relative(repoPath, path.join(runnerDir, 'run.cjs')).replace(/\\/g, '/');
474
482
  try {
475
483
  const runnerSrc = path.join(__dirname, '..', '..', 'hooks', 'claude', 'resolve-analyze-cmd.cjs');
476
- await fs.mkdir(storagePath, { recursive: true });
477
- await fs.copyFile(runnerSrc, path.join(storagePath, 'run.cjs'));
484
+ await fs.mkdir(runnerDir, { recursive: true });
485
+ await fs.copyFile(runnerSrc, path.join(runnerDir, 'run.cjs'));
478
486
  }
479
487
  catch (err) {
480
488
  logger.warn(`Could not write GitNexus runner to ${runnerPath}: ${String(err)}`);
@@ -103,6 +103,10 @@ export interface AnalyzeOptions {
103
103
  * `allowDuplicateName` option end-to-end.
104
104
  */
105
105
  allowDuplicateName?: boolean;
106
+ /** `--share-with <repo>`: join that checkout's shared store (#3352). */
107
+ shareWith?: string;
108
+ /** `--no-share` sets this to false: leave the shared store (#3352). */
109
+ share?: boolean;
106
110
  /**
107
111
  * Override the walker's large-file skip threshold (#991). Value in KB;
108
112
  * clamped downstream to the tree-sitter 32 MB ceiling. Sets
@@ -1128,6 +1128,8 @@ const analyzeCommandImpl = async (inputPath, cliOptions, runnerIdentityAtBootstr
1128
1128
  // be able to accept the duplicate name without also paying the
1129
1129
  // cost of a full pipeline re-index. See #829 review round 2.
1130
1130
  allowDuplicateName: options.allowDuplicateName,
1131
+ shareWith: options.shareWith,
1132
+ noShare: options.share === false,
1131
1133
  // Worker pool size threaded from --workers, replacing the previous
1132
1134
  // GITNEXUS_WORKER_POOL_SIZE env mutation. `undefined` defers to the
1133
1135
  // env / auto-formula fallback inside the pipeline.
@@ -10,4 +10,6 @@ export declare const cleanCommand: (options?: {
10
10
  lbugSidecars?: boolean;
11
11
  stale?: boolean;
12
12
  branch?: string;
13
+ gc?: boolean;
14
+ localIndex?: boolean;
13
15
  }) => Promise<void>;
package/dist/cli/clean.js CHANGED
@@ -9,11 +9,14 @@ import path from 'path';
9
9
  import { logger } from '../core/logger.js';
10
10
  import { findRegistryEntryByRepoPath, findRepo, unregisterRepo, listRegisteredRepos, getStoragePaths, } from '../storage/repo-manager.js';
11
11
  import { requireDeletableStoragePath, StorageDeletionError } from '../storage/storage-resolver.js';
12
- import { formatStaleSlotLine } from './stale-branch-format.js';
12
+ import { formatSlotSize, formatStaleSlotLine } from './stale-branch-format.js';
13
13
  import { listLocalHeads } from '../storage/git.js';
14
14
  import { isContainedBranchDir, isDeleteCandidate, listStaleBranchSlots, removeBranchSlot, staleListingBlock, } from '../storage/stale-branch-slots.js';
15
15
  import { cleanParkedLbugSidecars, inspectLbugSidecars, listParkedLbugSidecars, } from '../core/lbug/sidecar-recovery.js';
16
16
  import { t } from './i18n/index.js';
17
+ import { getGlobalDir } from '../storage/global-dir.js';
18
+ import { STORES_DIR } from '../storage/shared-store.js';
19
+ import { findLegacyLocalIndex, reclaimAfterSlotRemoval, reclaimSharedStore, removeLegacyLocalIndex, removeCheckoutStorage, } from '../storage/shared-store-lifecycle.js';
17
20
  const resolveOwnedCwdStorage = async (refusePrefix) => {
18
21
  const repo = await findRepo(process.cwd());
19
22
  if (!repo) {
@@ -115,7 +118,91 @@ const cleanStaleBranchSlots = async (force) => {
115
118
  printStaleSlotLines(t('clean.stale.probeFailed'), probeFailed);
116
119
  }
117
120
  };
121
+ const reportReclaim = (result) => {
122
+ if (!result)
123
+ return;
124
+ if (result.removed.length > 0) {
125
+ console.log(t('clean.shared.reclaimed', { count: result.removed.length }));
126
+ }
127
+ if (result.kept.length > 0)
128
+ console.log(t('clean.shared.kept', { count: result.kept.length }));
129
+ };
130
+ /** `clean --gc`: collect every shared store under GITNEXUS_HOME (#3352). */
131
+ const collectSharedStores = async (force) => {
132
+ const storesDir = path.join(getGlobalDir(), STORES_DIR);
133
+ // Only a missing stores root means "nothing to collect"; an unreadable one
134
+ // must fail loudly rather than report success.
135
+ const names = await fs.readdir(storesDir).catch((err) => {
136
+ if (err.code === 'ENOENT')
137
+ return [];
138
+ throw err;
139
+ });
140
+ // Stray files (`.DS_Store`) are not stores, and a symlink is not followed:
141
+ // reclaim deletes under the root it is given. A store another collector
142
+ // removed meanwhile is simply gone. The lstat skips a stray link; it is not
143
+ // a race guard, since stores/ belongs to the user running clean and anyone
144
+ // able to swap an entry there can already delete the store itself.
145
+ const roots = [];
146
+ for (const name of names) {
147
+ const root = path.join(storesDir, name);
148
+ const stat = await fs.lstat(root).catch((err) => {
149
+ if (err.code === 'ENOENT')
150
+ return null;
151
+ throw err;
152
+ });
153
+ if (stat?.isDirectory())
154
+ roots.push(root);
155
+ }
156
+ if (roots.length === 0) {
157
+ console.log(t('clean.gc.none'));
158
+ return;
159
+ }
160
+ for (const root of roots) {
161
+ // Without --force this is a preview: same selection, nothing deleted.
162
+ const result = await reclaimSharedStore(root, { gc: true, dryRun: !force });
163
+ console.log(t(force ? 'clean.gc.store' : 'clean.gc.preview', {
164
+ path: root,
165
+ members: result.droppedMembers.length,
166
+ graphs: result.removed.length,
167
+ }));
168
+ if (result.keptMembers.length > 0) {
169
+ console.log(t('clean.gc.keptMembers', { count: result.keptMembers.length }));
170
+ }
171
+ if (result.kept.length > 0)
172
+ console.log(t('clean.shared.kept', { count: result.kept.length }));
173
+ if (result.storeRemoved)
174
+ console.log(t('clean.shared.storeRemoved', { path: root }));
175
+ }
176
+ if (!force)
177
+ console.log(`\n${t('common.runForceConfirm')}`);
178
+ };
118
179
  export const cleanCommand = async (options) => {
180
+ if (options?.gc) {
181
+ await collectSharedStores(options.force === true);
182
+ return;
183
+ }
184
+ // --local-index: delete a pre-adoption index left in <checkout>/.gitnexus
185
+ // after the checkout moved into a shared store (#3352). Keeps the pointer.
186
+ if (options?.localIndex) {
187
+ const repo = await findRepo(process.cwd());
188
+ if (!repo) {
189
+ console.log(t('clean.notFoundHere'));
190
+ return;
191
+ }
192
+ const legacy = await findLegacyLocalIndex(repo.repoPath, repo.storagePath);
193
+ if (!legacy) {
194
+ console.log(t('clean.localIndex.none'));
195
+ return;
196
+ }
197
+ if (!options.force) {
198
+ console.log(t('clean.localIndex.preview', { path: legacy.dir, size: formatSlotSize(legacy.bytes) }));
199
+ console.log(`\n${t('common.runForceConfirm')}`);
200
+ return;
201
+ }
202
+ await removeLegacyLocalIndex(repo.repoPath, repo.storagePath);
203
+ console.log(t('clean.localIndex.deleted', { path: legacy.dir, size: formatSlotSize(legacy.bytes) }));
204
+ return;
205
+ }
119
206
  // --stale: reclaim leftover per-branch slots whose recorded branch is not
120
207
  // a live local head (#3331). Exclusive arm before --branch.
121
208
  if (options?.stale) {
@@ -246,9 +333,12 @@ export const cleanCommand = async (options) => {
246
333
  for (const entry of entries) {
247
334
  try {
248
335
  const storagePath = await requireDeletableStoragePath(entry);
249
- await fs.rm(storagePath, { recursive: true, force: true });
250
- await unregisterRepo(entry.path);
336
+ // A shared slot is unregistered before it is deleted: its lock file
337
+ // lives inside it, so it must go last. A failed delete throws with the
338
+ // `clean --gc --force` recovery (shared-store-clean.test.ts).
339
+ await removeCheckoutStorage(storagePath, () => unregisterRepo(entry.path), entry.path);
251
340
  console.log(t('clean.deletedRepo', { name: entry.name, storagePath }));
341
+ reportReclaim(await reclaimAfterSlotRemoval(storagePath));
252
342
  }
253
343
  catch (err) {
254
344
  if (err instanceof StorageDeletionError) {
@@ -289,9 +379,9 @@ export const cleanCommand = async (options) => {
289
379
  return;
290
380
  }
291
381
  try {
292
- await fs.rm(storagePath, { recursive: true, force: true });
293
- await unregisterRepo(repo.repoPath);
382
+ await removeCheckoutStorage(storagePath, () => unregisterRepo(repo.repoPath), repo.repoPath);
294
383
  console.log(t('common.deleted', { target: storagePath }));
384
+ reportReclaim(await reclaimAfterSlotRemoval(storagePath));
295
385
  }
296
386
  catch (err) {
297
387
  logger.error({ err }, 'Failed to delete:');
@@ -1,4 +1,5 @@
1
1
  import { getRuntimeCapabilities, getRuntimeFingerprint } from '../core/platform/capabilities.js';
2
+ import { findLegacyLocalIndex } from '../storage/shared-store-lifecycle.js';
2
3
  import { resolveEmbeddingConfig } from '../core/embeddings/config.js';
3
4
  import { isHttpMode } from '../core/embeddings/http-client.js';
4
5
  import { getLocalEmbeddingRuntimeBlocker, localEmbeddingPrefixUnloadableMessage, localEmbeddingStackMissingMessage, } from '../core/embeddings/runtime-support.js';
@@ -327,6 +328,12 @@ export const doctorCommand = async () => {
327
328
  branches: entry?.branches,
328
329
  });
329
330
  const leftoverLines = leftoverBranchSlotDoctorLines(slots);
331
+ // A pre-adoption index left in <repo>/.gitnexus after this checkout moved
332
+ // into a shared store (#3352).
333
+ const legacy = await findLegacyLocalIndex(cwdRepo.repoPath, cwdRepo.storagePath);
334
+ if (legacy) {
335
+ leftoverLines.push(t('status.legacyLocalIndex', { path: legacy.dir, size: formatSlotSize(legacy.bytes) }));
336
+ }
330
337
  if (leftoverLines.length === 0)
331
338
  return;
332
339
  console.log('');
@@ -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' | 'codebuddy' | 'qoder' | 'codex';
18
+ export type EditorId = 'cursor' | 'claude' | 'antigravity' | 'opencode' | 'codebuddy' | 'qoder' | 'codex' | 'droid';
19
19
  /** An editor whose MCP config is a JSONC document (server keyed by name). */
20
20
  export interface McpJsoncTarget {
21
21
  id: EditorId;
@@ -76,6 +76,15 @@ export function getEditorTargets(home = os.homedir()) {
76
76
  file: path.join(home, '.qoder.json'),
77
77
  keyPath: ['mcpServers', 'gitnexus'],
78
78
  },
79
+ {
80
+ id: 'droid',
81
+ label: 'Factory Droid',
82
+ // Factory's user-scope MCP config (https://docs.factory.ai/cli/configuration/mcp).
83
+ // Same `mcpServers` object shape as Cursor/Claude; user config takes
84
+ // precedence over project-level .factory/mcp.json.
85
+ file: path.join(home, '.factory', 'mcp.json'),
86
+ keyPath: ['mcpServers', 'gitnexus'],
87
+ },
79
88
  ];
80
89
  const codex = {
81
90
  id: 'codex',
@@ -98,6 +107,9 @@ export function getEditorTargets(home = os.homedir()) {
98
107
  { id: 'qoder', label: 'Qoder', dir: path.join(home, '.qoder', 'skills') },
99
108
  // Codex reads skills from ~/.agents/skills (not ~/.codex).
100
109
  { id: 'codex', label: 'Codex', dir: path.join(home, '.agents', 'skills') },
110
+ // Factory Droid reads user-scope skills from ~/.factory/skills/{name}/SKILL.md
111
+ // (https://docs.factory.ai/cli/configuration/skills).
112
+ { id: 'droid', label: 'Factory Droid', dir: path.join(home, '.factory', 'skills') },
101
113
  ];
102
114
  const hooks = [
103
115
  {
@@ -1,4 +1,6 @@
1
1
  import { lstat } from 'node:fs/promises';
2
+ import { ensurePrivateSharedGraph } from '../core/shared-store-analyze.js';
3
+ import { LBUG_DIRECTORY } from '../storage/storage-constants.js';
2
4
  import path from 'node:path';
3
5
  import { cliInfo } from './cli-message.js';
4
6
  import { getGitRoot } from '../storage/git.js';
@@ -17,11 +19,17 @@ export const embeddingsSyncCommand = async (inputPath) => {
17
19
  const repoPath = inputPath ? path.resolve(inputPath) : getGitRoot(process.cwd());
18
20
  if (!repoPath)
19
21
  throw new Error('Not inside a git repository. Pass a repository path.');
20
- const { lbugPath, metaPath } = getStoragePaths(repoPath);
22
+ const { metaPath } = getStoragePaths(repoPath);
21
23
  const metaDir = path.dirname(metaPath);
24
+ // Writes go to the slot's own graph. A shared-store checkout that reads an
25
+ // immutable commit graph (#3352) takes a private copy first.
26
+ const lbugPath = path.join(metaDir, LBUG_DIRECTORY);
22
27
  const lock = await acquireIndexLock(metaDir);
23
28
  try {
24
29
  requireExclusiveIndexLock(lock, `Cannot acquire the index lock at ${metaDir}; refusing an unlocked embeddings sync.`);
30
+ if (!(await ensurePrivateSharedGraph(metaDir, (m) => console.log(` ${m}`)))) {
31
+ throw new Error('The shared graph this checkout reads is gone. Run gitnexus analyze first.');
32
+ }
25
33
  const meta = await loadMeta(metaDir);
26
34
  if (!meta)
27
35
  throw new Error(`No GitNexus index found for ${repoPath}. Run gitnexus analyze first.`);
@@ -63,6 +63,8 @@ const OPTION_DESCRIPTION_KEYS = {
63
63
  'analyze|--skip-git': 'help.option.skipGit',
64
64
  'analyze|--name <alias>': 'help.option.analyze.name',
65
65
  'analyze|--allow-duplicate-name': 'help.option.analyze.allowDuplicateName',
66
+ 'analyze|--share-with <repo>': 'help.option.analyze.shareWith',
67
+ 'analyze|--no-share': 'help.option.analyze.noShare',
66
68
  'analyze|-v, --verbose': 'help.option.verbose',
67
69
  'analyze|--max-file-size <kb>': 'help.option.analyze.maxFileSize',
68
70
  'analyze|--worker-timeout <seconds>': 'help.option.analyze.workerTimeout',
@@ -93,6 +95,8 @@ const OPTION_DESCRIPTION_KEYS = {
93
95
  'clean|--branch <name>': 'help.option.clean.branch',
94
96
  'clean|--lbug-sidecars': 'help.option.clean.lbugSidecars',
95
97
  'clean|--stale': 'help.option.clean.stale',
98
+ 'clean|--gc': 'help.option.clean.gc',
99
+ 'clean|--local-index': 'help.option.clean.localIndex',
96
100
  'remove|-f, --force': 'help.option.force.confirmation',
97
101
  'wiki|-f, --force': 'help.option.wiki.force',
98
102
  'wiki|--provider <provider>': 'help.option.wiki.provider',
@@ -25,6 +25,11 @@ export declare const en: {
25
25
  readonly 'list.clusters': 'Clusters';
26
26
  readonly 'list.processes': 'Processes';
27
27
  readonly 'list.unknown': 'unknown';
28
+ readonly 'status.sharedStoreShared': 'Shared index: store {{key}}, shared graph for commit {{commit}}';
29
+ readonly 'status.sharedStorePrivate': 'Shared index: store {{key}}, private graph (local changes or a pinned branch index)';
30
+ readonly 'status.sharedStoreCloneCow': ' Copied copy-on-write: unchanged pages are shared with the commit graph on disk';
31
+ readonly 'status.sharedStoreCloneCopy': ' Full copy: this filesystem cannot clone copy-on-write (APFS, btrfs and XFS can)';
32
+ readonly 'status.legacyLocalIndex': 'Leftover local index: {{path}} ({{size}}); remove it with `gitnexus clean --local-index --force`';
28
33
  readonly 'status.notGitRepo': 'Not a git repository.';
29
34
  readonly 'status.staleKuzu': 'Repository has a stale KuzuDB index from a previous version.';
30
35
  readonly 'status.rebuildLadybug': 'Run: gitnexus analyze (rebuilds the index with LadybugDB)';
@@ -52,6 +57,16 @@ export declare const en: {
52
57
  readonly 'clean.deleteAll': 'This will delete GitNexus indexes for {{count}} repo(s):';
53
58
  readonly 'clean.deletedRepo': 'Deleted: {{name}} ({{storagePath}})';
54
59
  readonly 'clean.notFoundHere': 'No indexed repository found in this directory.';
60
+ readonly 'clean.shared.reclaimed': 'Shared store: removed {{count}} commit graph(s) no checkout references.';
61
+ readonly 'clean.shared.kept': 'Shared store: kept {{count}} unreferenced commit graph(s) that could not be removed (in use or not writable); run `gitnexus clean --gc` later.';
62
+ readonly 'clean.shared.storeRemoved': 'Shared store: removed {{path}} (no checkouts remain).';
63
+ readonly 'clean.gc.none': 'No shared stores to collect.';
64
+ readonly 'clean.gc.keptMembers': 'Shared store: kept {{count}} checkout(s) it could not delete; run `gitnexus clean --gc --force` later.';
65
+ readonly 'clean.gc.store': 'Shared store {{path}}: dropped {{members}} checkout(s), removed {{graphs}} commit graph(s).';
66
+ readonly 'clean.gc.preview': 'Shared store {{path}}: would drop {{members}} checkout(s) and remove {{graphs}} commit graph(s).';
67
+ readonly 'clean.localIndex.none': 'No leftover local index in this checkout.';
68
+ readonly 'clean.localIndex.preview': 'This will delete the leftover local index at {{path}} ({{size}}). The shared index is not affected.';
69
+ readonly 'clean.localIndex.deleted': 'Deleted the leftover local index at {{path}} ({{size}}).';
55
70
  readonly 'clean.deleteCurrent': 'This will delete the GitNexus index for: {{repoName}}';
56
71
  readonly 'clean.branchNotIndexed': 'No indexed branch named "{{branch}}" for this repository. Use `gitnexus clean --stale` to reclaim leftover branch indexes, or `gitnexus list` to see recorded names.';
57
72
  readonly 'clean.stale.none': 'No leftover branch indexes to reclaim.';
@@ -152,7 +167,7 @@ export declare const en: {
152
167
  readonly 'help.command.help.description': 'display help for command';
153
168
  readonly 'help.option.help': 'display help for command';
154
169
  readonly 'help.option.version': 'output the version number';
155
- readonly 'help.command.setup.description': 'One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder, Codex';
170
+ readonly 'help.command.setup.description': 'One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder, Codex, Factory Droid';
156
171
  readonly 'help.command.uninstall.description': 'Reverse `setup`: remove GitNexus MCP entries, skills, and hooks from all detected editors';
157
172
  readonly 'help.command.autoSync.description': 'Control scheduled repository clone/pull and analysis from GITNEXUS_HOME/watch_config.yml';
158
173
  readonly 'help.autoSync.details': '\nActions: init, start (default), restart, stop, status, reset\nConfiguration: GITNEXUS_HOME/watch_config.yml\nRuntime files: GITNEXUS_HOME/watch/watch.pid, watch.mutex, watch.owner.json, watch.status.json, auto-sync-state.json\nRecovery: mutexes with verified dead owners are reclaimed automatically; invalid or legacy mutexes fail closed and require manual removal after confirming no watch process is running.\nWrites: GITNEXUS_HOME/watch/project_commit_info.txt\nRemote URLs: only SSH URLs on github.com, gitlab.com, and gitee.com are allowed.\nRuns once immediately, then repeats on sync_interval_minutes.';
@@ -208,6 +223,8 @@ export declare const en: {
208
223
  readonly 'help.option.skipGit': 'Treat the provided path/cwd as the index root and skip parent git-root discovery';
209
224
  readonly 'help.option.analyze.name': 'Register this repo under a custom name in ~/.gitnexus/registry.json (disambiguates repos whose paths share a basename, e.g. two different .../app folders)';
210
225
  readonly 'help.option.analyze.allowDuplicateName': 'Register this repo even if another path already uses the same --name alias. Leaves `-r <name>` ambiguous for the two paths; use -r <path> to disambiguate.';
226
+ readonly 'help.option.analyze.shareWith': 'Join the shared index store of a registered checkout of the same repository (name or path); the remote URL must match. Clones join a sibling clone’s store automatically; this names one explicitly and clears a --no-share opt-out.';
227
+ readonly 'help.option.analyze.noShare': 'Clones only: leave the shared index store, index into <repo>/.gitnexus again, and stop joining sibling clones automatically until --share-with (linked worktrees always share; set GITNEXUS_SHARED_STORE=off instead)';
211
228
  readonly 'help.option.verbose': 'Enable verbose output';
212
229
  readonly 'help.option.analyze.maxFileSize': 'Skip files larger than this (KB). Default: 512. Hard cap: 32768 (tree-sitter limit).';
213
230
  readonly 'help.option.analyze.workerTimeout': 'Worker sub-batch idle timeout before retry/fallback. Default: 30.';
@@ -236,6 +253,8 @@ export declare const en: {
236
253
  readonly 'help.option.clean.branch': 'Delete only the named branch index (not the workspace index)';
237
254
  readonly 'help.option.clean.lbugSidecars': 'Clean parked LadybugDB recovery sidecars (missing-shadow WAL quarantines and dirty-recovery parks)';
238
255
  readonly 'help.option.clean.stale': 'Reclaim leftover branch indexes that are not a live local head';
256
+ readonly 'help.option.clean.gc': 'Drop shared-store checkouts no registry entry uses and delete commit graphs nothing references';
257
+ readonly 'help.option.clean.localIndex': 'Delete the index left in <repo>/.gitnexus after this checkout moved into a shared store';
239
258
  readonly 'help.option.wiki.force': 'Force full regeneration even if up to date';
240
259
  readonly 'help.option.wiki.provider': 'LLM provider: minimax, openai, openrouter, azure, custom, cursor, claude, codex, opencode, or grok (default: minimax)';
241
260
  readonly 'help.option.wiki.model': 'LLM model or deployment name (default: MiniMax-M3)';
@@ -25,6 +25,11 @@ export const en = {
25
25
  'list.clusters': 'Clusters',
26
26
  'list.processes': 'Processes',
27
27
  'list.unknown': 'unknown',
28
+ 'status.sharedStoreShared': 'Shared index: store {{key}}, shared graph for commit {{commit}}',
29
+ 'status.sharedStorePrivate': 'Shared index: store {{key}}, private graph (local changes or a pinned branch index)',
30
+ 'status.sharedStoreCloneCow': ' Copied copy-on-write: unchanged pages are shared with the commit graph on disk',
31
+ 'status.sharedStoreCloneCopy': ' Full copy: this filesystem cannot clone copy-on-write (APFS, btrfs and XFS can)',
32
+ 'status.legacyLocalIndex': 'Leftover local index: {{path}} ({{size}}); remove it with `gitnexus clean --local-index --force`',
28
33
  'status.notGitRepo': 'Not a git repository.',
29
34
  'status.staleKuzu': 'Repository has a stale KuzuDB index from a previous version.',
30
35
  'status.rebuildLadybug': 'Run: gitnexus analyze (rebuilds the index with LadybugDB)',
@@ -52,6 +57,16 @@ export const en = {
52
57
  'clean.deleteAll': 'This will delete GitNexus indexes for {{count}} repo(s):',
53
58
  'clean.deletedRepo': 'Deleted: {{name}} ({{storagePath}})',
54
59
  'clean.notFoundHere': 'No indexed repository found in this directory.',
60
+ 'clean.shared.reclaimed': 'Shared store: removed {{count}} commit graph(s) no checkout references.',
61
+ 'clean.shared.kept': 'Shared store: kept {{count}} unreferenced commit graph(s) that could not be removed (in use or not writable); run `gitnexus clean --gc` later.',
62
+ 'clean.shared.storeRemoved': 'Shared store: removed {{path}} (no checkouts remain).',
63
+ 'clean.gc.none': 'No shared stores to collect.',
64
+ 'clean.gc.keptMembers': 'Shared store: kept {{count}} checkout(s) it could not delete; run `gitnexus clean --gc --force` later.',
65
+ 'clean.gc.store': 'Shared store {{path}}: dropped {{members}} checkout(s), removed {{graphs}} commit graph(s).',
66
+ 'clean.gc.preview': 'Shared store {{path}}: would drop {{members}} checkout(s) and remove {{graphs}} commit graph(s).',
67
+ 'clean.localIndex.none': 'No leftover local index in this checkout.',
68
+ 'clean.localIndex.preview': 'This will delete the leftover local index at {{path}} ({{size}}). The shared index is not affected.',
69
+ 'clean.localIndex.deleted': 'Deleted the leftover local index at {{path}} ({{size}}).',
55
70
  'clean.deleteCurrent': 'This will delete the GitNexus index for: {{repoName}}',
56
71
  'clean.branchNotIndexed': 'No indexed branch named "{{branch}}" for this repository. Use `gitnexus clean --stale` to reclaim leftover branch indexes, or `gitnexus list` to see recorded names.',
57
72
  'clean.stale.none': 'No leftover branch indexes to reclaim.',
@@ -154,7 +169,7 @@ export const en = {
154
169
  'help.command.help.description': 'display help for command',
155
170
  'help.option.help': 'display help for command',
156
171
  'help.option.version': 'output the version number',
157
- 'help.command.setup.description': 'One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder, Codex',
172
+ 'help.command.setup.description': 'One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder, Codex, Factory Droid',
158
173
  'help.command.uninstall.description': 'Reverse `setup`: remove GitNexus MCP entries, skills, and hooks from all detected editors',
159
174
  'help.command.autoSync.description': 'Control scheduled repository clone/pull and analysis from GITNEXUS_HOME/watch_config.yml',
160
175
  'help.autoSync.details': '\nActions: init, start (default), restart, stop, status, reset\nConfiguration: GITNEXUS_HOME/watch_config.yml\nRuntime files: GITNEXUS_HOME/watch/watch.pid, watch.mutex, watch.owner.json, watch.status.json, auto-sync-state.json\nRecovery: mutexes with verified dead owners are reclaimed automatically; invalid or legacy mutexes fail closed and require manual removal after confirming no watch process is running.\nWrites: GITNEXUS_HOME/watch/project_commit_info.txt\nRemote URLs: only SSH URLs on github.com, gitlab.com, and gitee.com are allowed.\nRuns once immediately, then repeats on sync_interval_minutes.',
@@ -210,6 +225,8 @@ export const en = {
210
225
  'help.option.skipGit': 'Treat the provided path/cwd as the index root and skip parent git-root discovery',
211
226
  'help.option.analyze.name': 'Register this repo under a custom name in ~/.gitnexus/registry.json (disambiguates repos whose paths share a basename, e.g. two different .../app folders)',
212
227
  'help.option.analyze.allowDuplicateName': 'Register this repo even if another path already uses the same --name alias. Leaves `-r <name>` ambiguous for the two paths; use -r <path> to disambiguate.',
228
+ 'help.option.analyze.shareWith': 'Join the shared index store of a registered checkout of the same repository (name or path); the remote URL must match. Clones join a sibling clone’s store automatically; this names one explicitly and clears a --no-share opt-out.',
229
+ 'help.option.analyze.noShare': 'Clones only: leave the shared index store, index into <repo>/.gitnexus again, and stop joining sibling clones automatically until --share-with (linked worktrees always share; set GITNEXUS_SHARED_STORE=off instead)',
213
230
  'help.option.verbose': 'Enable verbose output',
214
231
  'help.option.analyze.maxFileSize': 'Skip files larger than this (KB). Default: 512. Hard cap: 32768 (tree-sitter limit).',
215
232
  'help.option.analyze.workerTimeout': 'Worker sub-batch idle timeout before retry/fallback. Default: 30.',
@@ -238,6 +255,8 @@ export const en = {
238
255
  'help.option.clean.branch': 'Delete only the named branch index (not the workspace index)',
239
256
  'help.option.clean.lbugSidecars': 'Clean parked LadybugDB recovery sidecars (missing-shadow WAL quarantines and dirty-recovery parks)',
240
257
  'help.option.clean.stale': 'Reclaim leftover branch indexes that are not a live local head',
258
+ 'help.option.clean.gc': 'Drop shared-store checkouts no registry entry uses and delete commit graphs nothing references',
259
+ 'help.option.clean.localIndex': 'Delete the index left in <repo>/.gitnexus after this checkout moved into a shared store',
241
260
  'help.option.wiki.force': 'Force full regeneration even if up to date',
242
261
  'help.option.wiki.provider': 'LLM provider: minimax, openai, openrouter, azure, custom, cursor, claude, codex, opencode, or grok (default: minimax)',
243
262
  'help.option.wiki.model': 'LLM model or deployment name (default: MiniMax-M3)',
@@ -26,6 +26,11 @@ export declare const cliResources: {
26
26
  readonly 'list.clusters': 'Clusters';
27
27
  readonly 'list.processes': 'Processes';
28
28
  readonly 'list.unknown': 'unknown';
29
+ readonly 'status.sharedStoreShared': 'Shared index: store {{key}}, shared graph for commit {{commit}}';
30
+ readonly 'status.sharedStorePrivate': 'Shared index: store {{key}}, private graph (local changes or a pinned branch index)';
31
+ readonly 'status.sharedStoreCloneCow': ' Copied copy-on-write: unchanged pages are shared with the commit graph on disk';
32
+ readonly 'status.sharedStoreCloneCopy': ' Full copy: this filesystem cannot clone copy-on-write (APFS, btrfs and XFS can)';
33
+ readonly 'status.legacyLocalIndex': 'Leftover local index: {{path}} ({{size}}); remove it with `gitnexus clean --local-index --force`';
29
34
  readonly 'status.notGitRepo': 'Not a git repository.';
30
35
  readonly 'status.staleKuzu': 'Repository has a stale KuzuDB index from a previous version.';
31
36
  readonly 'status.rebuildLadybug': 'Run: gitnexus analyze (rebuilds the index with LadybugDB)';
@@ -53,6 +58,16 @@ export declare const cliResources: {
53
58
  readonly 'clean.deleteAll': 'This will delete GitNexus indexes for {{count}} repo(s):';
54
59
  readonly 'clean.deletedRepo': 'Deleted: {{name}} ({{storagePath}})';
55
60
  readonly 'clean.notFoundHere': 'No indexed repository found in this directory.';
61
+ readonly 'clean.shared.reclaimed': 'Shared store: removed {{count}} commit graph(s) no checkout references.';
62
+ readonly 'clean.shared.kept': 'Shared store: kept {{count}} unreferenced commit graph(s) that could not be removed (in use or not writable); run `gitnexus clean --gc` later.';
63
+ readonly 'clean.shared.storeRemoved': 'Shared store: removed {{path}} (no checkouts remain).';
64
+ readonly 'clean.gc.none': 'No shared stores to collect.';
65
+ readonly 'clean.gc.keptMembers': 'Shared store: kept {{count}} checkout(s) it could not delete; run `gitnexus clean --gc --force` later.';
66
+ readonly 'clean.gc.store': 'Shared store {{path}}: dropped {{members}} checkout(s), removed {{graphs}} commit graph(s).';
67
+ readonly 'clean.gc.preview': 'Shared store {{path}}: would drop {{members}} checkout(s) and remove {{graphs}} commit graph(s).';
68
+ readonly 'clean.localIndex.none': 'No leftover local index in this checkout.';
69
+ readonly 'clean.localIndex.preview': 'This will delete the leftover local index at {{path}} ({{size}}). The shared index is not affected.';
70
+ readonly 'clean.localIndex.deleted': 'Deleted the leftover local index at {{path}} ({{size}}).';
56
71
  readonly 'clean.deleteCurrent': 'This will delete the GitNexus index for: {{repoName}}';
57
72
  readonly 'clean.branchNotIndexed': 'No indexed branch named "{{branch}}" for this repository. Use `gitnexus clean --stale` to reclaim leftover branch indexes, or `gitnexus list` to see recorded names.';
58
73
  readonly 'clean.stale.none': 'No leftover branch indexes to reclaim.';
@@ -153,7 +168,7 @@ export declare const cliResources: {
153
168
  readonly 'help.command.help.description': 'display help for command';
154
169
  readonly 'help.option.help': 'display help for command';
155
170
  readonly 'help.option.version': 'output the version number';
156
- readonly 'help.command.setup.description': 'One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder, Codex';
171
+ readonly 'help.command.setup.description': 'One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, CodeBuddy, Qoder, Codex, Factory Droid';
157
172
  readonly 'help.command.uninstall.description': 'Reverse `setup`: remove GitNexus MCP entries, skills, and hooks from all detected editors';
158
173
  readonly 'help.command.autoSync.description': 'Control scheduled repository clone/pull and analysis from GITNEXUS_HOME/watch_config.yml';
159
174
  readonly 'help.autoSync.details': '\nActions: init, start (default), restart, stop, status, reset\nConfiguration: GITNEXUS_HOME/watch_config.yml\nRuntime files: GITNEXUS_HOME/watch/watch.pid, watch.mutex, watch.owner.json, watch.status.json, auto-sync-state.json\nRecovery: mutexes with verified dead owners are reclaimed automatically; invalid or legacy mutexes fail closed and require manual removal after confirming no watch process is running.\nWrites: GITNEXUS_HOME/watch/project_commit_info.txt\nRemote URLs: only SSH URLs on github.com, gitlab.com, and gitee.com are allowed.\nRuns once immediately, then repeats on sync_interval_minutes.';
@@ -209,6 +224,8 @@ export declare const cliResources: {
209
224
  readonly 'help.option.skipGit': 'Treat the provided path/cwd as the index root and skip parent git-root discovery';
210
225
  readonly 'help.option.analyze.name': 'Register this repo under a custom name in ~/.gitnexus/registry.json (disambiguates repos whose paths share a basename, e.g. two different .../app folders)';
211
226
  readonly 'help.option.analyze.allowDuplicateName': 'Register this repo even if another path already uses the same --name alias. Leaves `-r <name>` ambiguous for the two paths; use -r <path> to disambiguate.';
227
+ readonly 'help.option.analyze.shareWith': 'Join the shared index store of a registered checkout of the same repository (name or path); the remote URL must match. Clones join a sibling clone’s store automatically; this names one explicitly and clears a --no-share opt-out.';
228
+ readonly 'help.option.analyze.noShare': 'Clones only: leave the shared index store, index into <repo>/.gitnexus again, and stop joining sibling clones automatically until --share-with (linked worktrees always share; set GITNEXUS_SHARED_STORE=off instead)';
212
229
  readonly 'help.option.verbose': 'Enable verbose output';
213
230
  readonly 'help.option.analyze.maxFileSize': 'Skip files larger than this (KB). Default: 512. Hard cap: 32768 (tree-sitter limit).';
214
231
  readonly 'help.option.analyze.workerTimeout': 'Worker sub-batch idle timeout before retry/fallback. Default: 30.';
@@ -237,6 +254,8 @@ export declare const cliResources: {
237
254
  readonly 'help.option.clean.branch': 'Delete only the named branch index (not the workspace index)';
238
255
  readonly 'help.option.clean.lbugSidecars': 'Clean parked LadybugDB recovery sidecars (missing-shadow WAL quarantines and dirty-recovery parks)';
239
256
  readonly 'help.option.clean.stale': 'Reclaim leftover branch indexes that are not a live local head';
257
+ readonly 'help.option.clean.gc': 'Drop shared-store checkouts no registry entry uses and delete commit graphs nothing references';
258
+ readonly 'help.option.clean.localIndex': 'Delete the index left in <repo>/.gitnexus after this checkout moved into a shared store';
240
259
  readonly 'help.option.wiki.force': 'Force full regeneration even if up to date';
241
260
  readonly 'help.option.wiki.provider': 'LLM provider: minimax, openai, openrouter, azure, custom, cursor, claude, codex, opencode, or grok (default: minimax)';
242
261
  readonly 'help.option.wiki.model': 'LLM model or deployment name (default: MiniMax-M3)';
@@ -333,6 +352,11 @@ export declare const cliResources: {
333
352
  'list.clusters': string;
334
353
  'list.processes': string;
335
354
  'list.unknown': string;
355
+ 'status.sharedStoreShared': string;
356
+ 'status.sharedStorePrivate': string;
357
+ 'status.sharedStoreCloneCow': string;
358
+ 'status.sharedStoreCloneCopy': string;
359
+ 'status.legacyLocalIndex': string;
336
360
  'status.notGitRepo': string;
337
361
  'status.staleKuzu': string;
338
362
  'status.rebuildLadybug': string;
@@ -360,6 +384,16 @@ export declare const cliResources: {
360
384
  'clean.deleteAll': string;
361
385
  'clean.deletedRepo': string;
362
386
  'clean.notFoundHere': string;
387
+ 'clean.shared.reclaimed': string;
388
+ 'clean.shared.kept': string;
389
+ 'clean.shared.storeRemoved': string;
390
+ 'clean.gc.none': string;
391
+ 'clean.gc.keptMembers': string;
392
+ 'clean.gc.store': string;
393
+ 'clean.gc.preview': string;
394
+ 'clean.localIndex.none': string;
395
+ 'clean.localIndex.preview': string;
396
+ 'clean.localIndex.deleted': string;
363
397
  'clean.deleteCurrent': string;
364
398
  'clean.branchNotIndexed': string;
365
399
  'clean.stale.none': string;
@@ -516,6 +550,8 @@ export declare const cliResources: {
516
550
  'help.option.skipGit': string;
517
551
  'help.option.analyze.name': string;
518
552
  'help.option.analyze.allowDuplicateName': string;
553
+ 'help.option.analyze.shareWith': string;
554
+ 'help.option.analyze.noShare': string;
519
555
  'help.option.verbose': string;
520
556
  'help.option.analyze.maxFileSize': string;
521
557
  'help.option.analyze.workerTimeout': string;
@@ -544,6 +580,8 @@ export declare const cliResources: {
544
580
  'help.option.clean.branch': string;
545
581
  'help.option.clean.lbugSidecars': string;
546
582
  'help.option.clean.stale': string;
583
+ 'help.option.clean.gc': string;
584
+ 'help.option.clean.localIndex': string;
547
585
  'help.option.wiki.force': string;
548
586
  'help.option.wiki.provider': string;
549
587
  'help.option.wiki.model': string;
@@ -25,6 +25,11 @@ export declare const zhCN: {
25
25
  'list.clusters': string;
26
26
  'list.processes': string;
27
27
  'list.unknown': string;
28
+ 'status.sharedStoreShared': string;
29
+ 'status.sharedStorePrivate': string;
30
+ 'status.sharedStoreCloneCow': string;
31
+ 'status.sharedStoreCloneCopy': string;
32
+ 'status.legacyLocalIndex': string;
28
33
  'status.notGitRepo': string;
29
34
  'status.staleKuzu': string;
30
35
  'status.rebuildLadybug': string;
@@ -52,6 +57,16 @@ export declare const zhCN: {
52
57
  'clean.deleteAll': string;
53
58
  'clean.deletedRepo': string;
54
59
  'clean.notFoundHere': string;
60
+ 'clean.shared.reclaimed': string;
61
+ 'clean.shared.kept': string;
62
+ 'clean.shared.storeRemoved': string;
63
+ 'clean.gc.none': string;
64
+ 'clean.gc.keptMembers': string;
65
+ 'clean.gc.store': string;
66
+ 'clean.gc.preview': string;
67
+ 'clean.localIndex.none': string;
68
+ 'clean.localIndex.preview': string;
69
+ 'clean.localIndex.deleted': string;
55
70
  'clean.deleteCurrent': string;
56
71
  'clean.branchNotIndexed': string;
57
72
  'clean.stale.none': string;
@@ -208,6 +223,8 @@ export declare const zhCN: {
208
223
  'help.option.skipGit': string;
209
224
  'help.option.analyze.name': string;
210
225
  'help.option.analyze.allowDuplicateName': string;
226
+ 'help.option.analyze.shareWith': string;
227
+ 'help.option.analyze.noShare': string;
211
228
  'help.option.verbose': string;
212
229
  'help.option.analyze.maxFileSize': string;
213
230
  'help.option.analyze.workerTimeout': string;
@@ -236,6 +253,8 @@ export declare const zhCN: {
236
253
  'help.option.clean.branch': string;
237
254
  'help.option.clean.lbugSidecars': string;
238
255
  'help.option.clean.stale': string;
256
+ 'help.option.clean.gc': string;
257
+ 'help.option.clean.localIndex': string;
239
258
  'help.option.wiki.force': string;
240
259
  'help.option.wiki.provider': string;
241
260
  'help.option.wiki.model': string;