@veewo/gitnexus 1.3.11 → 1.4.6-rc

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 (181) hide show
  1. package/README.md +37 -80
  2. package/dist/benchmark/agent-context/tool-runner.js +2 -2
  3. package/dist/benchmark/neonspark-candidates.js +3 -3
  4. package/dist/benchmark/tool-runner.js +2 -2
  5. package/dist/cli/ai-context.d.ts +2 -1
  6. package/dist/cli/ai-context.js +16 -12
  7. package/dist/cli/analyze.d.ts +2 -0
  8. package/dist/cli/analyze.js +68 -48
  9. package/dist/cli/augment.js +1 -1
  10. package/dist/cli/eval-server.d.ts +8 -1
  11. package/dist/cli/eval-server.js +30 -13
  12. package/dist/cli/index.js +28 -82
  13. package/dist/cli/lazy-action.d.ts +6 -0
  14. package/dist/cli/lazy-action.js +18 -0
  15. package/dist/cli/mcp.js +3 -1
  16. package/dist/cli/setup.js +87 -48
  17. package/dist/cli/setup.test.js +18 -13
  18. package/dist/cli/skill-gen.d.ts +26 -0
  19. package/dist/cli/skill-gen.js +549 -0
  20. package/dist/cli/status.js +13 -4
  21. package/dist/cli/tool.d.ts +3 -2
  22. package/dist/cli/tool.js +50 -16
  23. package/dist/cli/wiki.js +8 -4
  24. package/dist/config/ignore-service.d.ts +25 -0
  25. package/dist/config/ignore-service.js +76 -0
  26. package/dist/config/supported-languages.d.ts +4 -1
  27. package/dist/config/supported-languages.js +3 -2
  28. package/dist/core/augmentation/engine.js +94 -67
  29. package/dist/core/embeddings/embedder.d.ts +1 -1
  30. package/dist/core/embeddings/embedder.js +1 -1
  31. package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
  32. package/dist/core/embeddings/embedding-pipeline.js +52 -25
  33. package/dist/core/embeddings/types.d.ts +1 -1
  34. package/dist/core/graph/types.d.ts +7 -2
  35. package/dist/core/ingestion/ast-cache.js +3 -2
  36. package/dist/core/ingestion/call-processor.d.ts +8 -6
  37. package/dist/core/ingestion/call-processor.js +468 -206
  38. package/dist/core/ingestion/call-routing.d.ts +53 -0
  39. package/dist/core/ingestion/call-routing.js +108 -0
  40. package/dist/core/ingestion/constants.d.ts +16 -0
  41. package/dist/core/ingestion/constants.js +16 -0
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +116 -23
  44. package/dist/core/ingestion/export-detection.d.ts +18 -0
  45. package/dist/core/ingestion/export-detection.js +231 -0
  46. package/dist/core/ingestion/filesystem-walker.js +4 -3
  47. package/dist/core/ingestion/framework-detection.d.ts +19 -4
  48. package/dist/core/ingestion/framework-detection.js +182 -6
  49. package/dist/core/ingestion/heritage-processor.d.ts +13 -5
  50. package/dist/core/ingestion/heritage-processor.js +109 -55
  51. package/dist/core/ingestion/import-processor.d.ts +16 -20
  52. package/dist/core/ingestion/import-processor.js +199 -579
  53. package/dist/core/ingestion/language-config.d.ts +46 -0
  54. package/dist/core/ingestion/language-config.js +167 -0
  55. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  56. package/dist/core/ingestion/mro-processor.js +369 -0
  57. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  58. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  59. package/dist/core/ingestion/parsing-processor.d.ts +4 -1
  60. package/dist/core/ingestion/parsing-processor.js +107 -109
  61. package/dist/core/ingestion/pipeline.d.ts +6 -3
  62. package/dist/core/ingestion/pipeline.js +208 -114
  63. package/dist/core/ingestion/process-processor.js +8 -2
  64. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  65. package/dist/core/ingestion/resolution-context.js +132 -0
  66. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  67. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  68. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  69. package/dist/core/ingestion/resolvers/go.js +42 -0
  70. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  71. package/dist/core/ingestion/resolvers/index.js +13 -0
  72. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  73. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  74. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  75. package/dist/core/ingestion/resolvers/php.js +35 -0
  76. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  77. package/dist/core/ingestion/resolvers/python.js +52 -0
  78. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  79. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  80. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  81. package/dist/core/ingestion/resolvers/rust.js +73 -0
  82. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  83. package/dist/core/ingestion/resolvers/standard.js +123 -0
  84. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  85. package/dist/core/ingestion/resolvers/utils.js +122 -0
  86. package/dist/core/ingestion/symbol-table.d.ts +21 -1
  87. package/dist/core/ingestion/symbol-table.js +40 -12
  88. package/dist/core/ingestion/tree-sitter-queries.d.ts +13 -10
  89. package/dist/core/ingestion/tree-sitter-queries.js +297 -7
  90. package/dist/core/ingestion/type-env.d.ts +49 -0
  91. package/dist/core/ingestion/type-env.js +611 -0
  92. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  93. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  94. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  95. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  96. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  97. package/dist/core/ingestion/type-extractors/go.js +467 -0
  98. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  99. package/dist/core/ingestion/type-extractors/index.js +31 -0
  100. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  101. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  102. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  103. package/dist/core/ingestion/type-extractors/php.js +549 -0
  104. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  105. package/dist/core/ingestion/type-extractors/python.js +406 -0
  106. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  107. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  108. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  109. package/dist/core/ingestion/type-extractors/rust.js +449 -0
  110. package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
  111. package/dist/core/ingestion/type-extractors/shared.js +703 -0
  112. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  113. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  114. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  115. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  116. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  117. package/dist/core/ingestion/utils.d.ts +103 -0
  118. package/dist/core/ingestion/utils.js +1085 -4
  119. package/dist/core/ingestion/workers/parse-worker.d.ts +51 -4
  120. package/dist/core/ingestion/workers/parse-worker.js +634 -222
  121. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  122. package/dist/core/{kuzu → lbug}/csv-generator.d.ts +12 -10
  123. package/dist/core/{kuzu → lbug}/csv-generator.js +82 -101
  124. package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +20 -25
  125. package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +150 -122
  126. package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
  127. package/dist/core/{kuzu → lbug}/schema.js +23 -22
  128. package/dist/core/lbug/schema.test.d.ts +1 -0
  129. package/dist/core/search/bm25-index.d.ts +4 -4
  130. package/dist/core/search/bm25-index.js +12 -11
  131. package/dist/core/search/hybrid-search.d.ts +2 -2
  132. package/dist/core/search/hybrid-search.js +6 -6
  133. package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
  134. package/dist/core/tree-sitter/parser-loader.js +19 -0
  135. package/dist/core/wiki/generator.d.ts +2 -2
  136. package/dist/core/wiki/generator.js +6 -6
  137. package/dist/core/wiki/graph-queries.d.ts +4 -4
  138. package/dist/core/wiki/graph-queries.js +7 -7
  139. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  140. package/dist/mcp/compatible-stdio-transport.js +200 -0
  141. package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +11 -10
  142. package/dist/mcp/core/lbug-adapter.js +327 -0
  143. package/dist/mcp/local/local-backend.d.ts +21 -16
  144. package/dist/mcp/local/local-backend.js +306 -706
  145. package/dist/mcp/local/unity-parity-seed-loader.d.ts +6 -1
  146. package/dist/mcp/local/unity-parity-seed-loader.js +119 -9
  147. package/dist/mcp/local/unity-parity-seed-loader.test.js +95 -7
  148. package/dist/mcp/resources.js +2 -2
  149. package/dist/mcp/server.js +28 -13
  150. package/dist/mcp/staleness.js +2 -2
  151. package/dist/mcp/tools.js +12 -3
  152. package/dist/server/api.js +12 -12
  153. package/dist/server/mcp-http.d.ts +1 -1
  154. package/dist/server/mcp-http.js +1 -1
  155. package/dist/storage/git.js +4 -1
  156. package/dist/storage/repo-manager.d.ts +20 -2
  157. package/dist/storage/repo-manager.js +74 -4
  158. package/dist/types/pipeline.d.ts +1 -1
  159. package/hooks/claude/gitnexus-hook.cjs +149 -46
  160. package/hooks/claude/pre-tool-use.sh +2 -1
  161. package/hooks/claude/session-start.sh +0 -0
  162. package/package.json +20 -4
  163. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  164. package/skills/gitnexus-cli.md +8 -8
  165. package/skills/gitnexus-debugging.md +1 -1
  166. package/skills/gitnexus-exploring.md +1 -1
  167. package/skills/gitnexus-guide.md +1 -1
  168. package/skills/gitnexus-impact-analysis.md +1 -1
  169. package/skills/gitnexus-pr-review.md +163 -0
  170. package/skills/gitnexus-refactoring.md +1 -1
  171. package/dist/cli/claude-hooks.d.ts +0 -22
  172. package/dist/cli/claude-hooks.js +0 -97
  173. package/dist/mcp/core/kuzu-adapter.js +0 -231
  174. /package/dist/core/{kuzu/csv-generator.test.d.ts → ingestion/type-extractors/types.js} +0 -0
  175. /package/dist/core/{kuzu/relationship-pair-buckets.test.d.ts → lbug/csv-generator.test.d.ts} +0 -0
  176. /package/dist/core/{kuzu → lbug}/csv-generator.test.js +0 -0
  177. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.d.ts +0 -0
  178. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.js +0 -0
  179. /package/dist/core/{kuzu/schema.test.d.ts → lbug/relationship-pair-buckets.test.d.ts} +0 -0
  180. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.test.js +0 -0
  181. /package/dist/core/{kuzu → lbug}/schema.test.js +0 -0
@@ -35,10 +35,64 @@ export const getStoragePaths = (repoPath) => {
35
35
  const storagePath = getStoragePath(repoPath);
36
36
  return {
37
37
  storagePath,
38
- kuzuPath: path.join(storagePath, 'kuzu'),
38
+ lbugPath: path.join(storagePath, 'lbug'),
39
39
  metaPath: path.join(storagePath, 'meta.json'),
40
40
  };
41
41
  };
42
+ /**
43
+ * Check whether a KuzuDB index exists in the given storage path.
44
+ * Non-destructive — safe to call from status commands.
45
+ */
46
+ export const hasKuzuIndex = async (storagePath) => {
47
+ try {
48
+ await fs.stat(path.join(storagePath, 'kuzu'));
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ };
55
+ /**
56
+ * Clean up stale KuzuDB files after migration to LadybugDB.
57
+ *
58
+ * Returns:
59
+ * found — true if .gitnexus/kuzu existed and was deleted
60
+ * needsReindex — true if kuzu existed but lbug does not (re-analyze required)
61
+ *
62
+ * Callers own the user-facing messaging; this function only deletes files.
63
+ */
64
+ export const cleanupOldKuzuFiles = async (storagePath) => {
65
+ const oldPath = path.join(storagePath, 'kuzu');
66
+ const newPath = path.join(storagePath, 'lbug');
67
+ try {
68
+ await fs.stat(oldPath);
69
+ // Old kuzu file/dir exists — determine if lbug is already present
70
+ let needsReindex = false;
71
+ try {
72
+ await fs.stat(newPath);
73
+ }
74
+ catch {
75
+ needsReindex = true;
76
+ }
77
+ // Delete kuzu database file and its sidecars (.wal, .lock)
78
+ for (const suffix of ['', '.wal', '.lock']) {
79
+ try {
80
+ await fs.unlink(oldPath + suffix);
81
+ }
82
+ catch { }
83
+ }
84
+ // Also handle the case where kuzu was stored as a directory
85
+ try {
86
+ await fs.rm(oldPath, { recursive: true, force: true });
87
+ }
88
+ catch { }
89
+ return { found: true, needsReindex };
90
+ }
91
+ catch {
92
+ // Old path doesn't exist — nothing to do
93
+ return { found: false, needsReindex: false };
94
+ }
95
+ };
42
96
  /**
43
97
  * Load metadata from an indexed repo
44
98
  */
@@ -169,12 +223,20 @@ export const registerRepo = async (repoPath, meta, options) => {
169
223
  const { storagePath } = getStoragePaths(resolved);
170
224
  const entries = await readRegistry();
171
225
  if (alias) {
172
- const aliasConflict = entries.find((e) => e.name === alias && path.resolve(e.path) !== resolved);
226
+ const aliasConflict = entries.find((e) => e.name === alias && (process.platform === 'win32'
227
+ ? path.resolve(e.path).toLowerCase() !== resolved.toLowerCase()
228
+ : path.resolve(e.path) !== resolved));
173
229
  if (aliasConflict) {
174
230
  throw new Error(`Repo alias "${alias}" is already registered for ${aliasConflict.path}`);
175
231
  }
176
232
  }
177
- const existing = entries.findIndex((e) => path.resolve(e.path) === resolved);
233
+ const existing = entries.findIndex((e) => {
234
+ const a = path.resolve(e.path);
235
+ const b = resolved;
236
+ return process.platform === 'win32'
237
+ ? a.toLowerCase() === b.toLowerCase()
238
+ : a === b;
239
+ });
178
240
  const entry = {
179
241
  name,
180
242
  path: resolved,
@@ -253,5 +315,13 @@ export const loadCLIConfig = async () => {
253
315
  export const saveCLIConfig = async (config) => {
254
316
  const dir = getGlobalDir();
255
317
  await fs.mkdir(dir, { recursive: true });
256
- await fs.writeFile(getGlobalConfigPath(), JSON.stringify(config, null, 2), 'utf-8');
318
+ const configPath = getGlobalConfigPath();
319
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
320
+ // Restrict file permissions on Unix (config may contain API keys)
321
+ if (process.platform !== 'win32') {
322
+ try {
323
+ await fs.chmod(configPath, 0o600);
324
+ }
325
+ catch { /* best-effort */ }
326
+ }
257
327
  };
@@ -21,7 +21,7 @@ export interface PipelineRunOptions {
21
21
  }
22
22
  export interface PipelineResult {
23
23
  graph: KnowledgeGraph;
24
- /** Absolute path to the repo root — used for lazy file reads during KuzuDB loading */
24
+ /** Absolute path to the repo root — used for lazy file reads during LadybugDB loading */
25
25
  repoPath: string;
26
26
  /** Total files scanned (for stats) */
27
27
  totalFileCount: number;
@@ -2,8 +2,10 @@
2
2
  /**
3
3
  * GitNexus Claude Code Hook
4
4
  *
5
- * PreToolUse handler — intercepts Grep/Glob/Bash searches
6
- * and augments with graph context from the GitNexus index.
5
+ * PreToolUse — intercepts Grep/Glob/Bash searches and augments
6
+ * with graph context from the GitNexus index.
7
+ * PostToolUse — detects stale index after git mutations and notifies
8
+ * the agent to reindex.
7
9
  *
8
10
  * NOTE: SessionStart hooks are broken on Windows (Claude Code bug).
9
11
  * Session context is injected via CLAUDE.md / skills instead.
@@ -11,7 +13,7 @@
11
13
 
12
14
  const fs = require('fs');
13
15
  const path = require('path');
14
- const { execFileSync } = require('child_process');
16
+ const { spawnSync } = require('child_process');
15
17
 
16
18
  /**
17
19
  * Read JSON input from stdin synchronously.
@@ -26,19 +28,19 @@ function readInput() {
26
28
  }
27
29
 
28
30
  /**
29
- * Check if a directory (or ancestor) has a .gitnexus index.
31
+ * Find the .gitnexus directory by walking up from startDir.
32
+ * Returns the path to .gitnexus/ or null if not found.
30
33
  */
31
- function findGitNexusIndex(startDir) {
34
+ function findGitNexusDir(startDir) {
32
35
  let dir = startDir || process.cwd();
33
36
  for (let i = 0; i < 5; i++) {
34
- if (fs.existsSync(path.join(dir, '.gitnexus'))) {
35
- return true;
36
- }
37
+ const candidate = path.join(dir, '.gitnexus');
38
+ if (fs.existsSync(candidate)) return candidate;
37
39
  const parent = path.dirname(dir);
38
40
  if (parent === dir) break;
39
41
  dir = parent;
40
42
  }
41
- return false;
43
+ return null;
42
44
  }
43
45
 
44
46
  /**
@@ -83,52 +85,153 @@ function extractPattern(toolName, toolInput) {
83
85
  return null;
84
86
  }
85
87
 
86
- function main() {
87
- try {
88
- const input = readInput();
89
- const hookEvent = input.hook_event_name || '';
90
-
91
- if (hookEvent !== 'PreToolUse') return;
88
+ /**
89
+ * Resolve the gitnexus CLI path.
90
+ * 1. Relative path (works when script is inside npm package)
91
+ * 2. require.resolve (works when gitnexus is globally installed)
92
+ * 3. Fall back to npx (returns empty string)
93
+ */
94
+ function resolveCliPath() {
95
+ let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
96
+ if (!fs.existsSync(cliPath)) {
97
+ try {
98
+ cliPath = require.resolve('gitnexus/dist/cli/index.js');
99
+ } catch {
100
+ cliPath = '';
101
+ }
102
+ }
103
+ return cliPath;
104
+ }
92
105
 
93
- const cwd = input.cwd || process.cwd();
94
- if (!findGitNexusIndex(cwd)) return;
106
+ /**
107
+ * Spawn a gitnexus CLI command synchronously.
108
+ * Returns the stderr output (KuzuDB captures stdout at OS level).
109
+ */
110
+ function runGitNexusCli(cliPath, args, cwd, timeout) {
111
+ const isWin = process.platform === 'win32';
112
+ if (cliPath) {
113
+ return spawnSync(
114
+ process.execPath,
115
+ [cliPath, ...args],
116
+ { encoding: 'utf-8', timeout, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
117
+ );
118
+ }
119
+ // On Windows, invoke npx.cmd directly (no shell needed)
120
+ return spawnSync(
121
+ isWin ? 'npx.cmd' : 'npx',
122
+ ['-y', 'gitnexus', ...args],
123
+ { encoding: 'utf-8', timeout: timeout + 5000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
124
+ );
125
+ }
95
126
 
96
- const toolName = input.tool_name || '';
97
- const toolInput = input.tool_input || {};
127
+ /**
128
+ * PreToolUse handler augment searches with graph context.
129
+ */
130
+ function handlePreToolUse(input) {
131
+ const cwd = input.cwd || process.cwd();
132
+ if (!path.isAbsolute(cwd)) return;
133
+ if (!findGitNexusDir(cwd)) return;
98
134
 
99
- if (toolName !== 'Grep' && toolName !== 'Glob' && toolName !== 'Bash') return;
135
+ const toolName = input.tool_name || '';
136
+ const toolInput = input.tool_input || {};
100
137
 
101
- const pattern = extractPattern(toolName, toolInput);
102
- if (!pattern || pattern.length < 3) return;
138
+ if (toolName !== 'Grep' && toolName !== 'Glob' && toolName !== 'Bash') return;
103
139
 
104
- // Resolve CLI path relative to this hook script (same package)
105
- // hooks/claude/gitnexus-hook.cjs dist/cli/index.js
106
- const cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
140
+ const pattern = extractPattern(toolName, toolInput);
141
+ if (!pattern || pattern.length < 3) return;
107
142
 
108
- // augment CLI writes result to stderr (KuzuDB's native module captures
109
- // stdout fd at OS level, making it unusable in subprocess contexts).
110
- const { spawnSync } = require('child_process');
111
- let result = '';
112
- try {
113
- const child = spawnSync(
114
- process.execPath,
115
- [cliPath, 'augment', pattern],
116
- { encoding: 'utf-8', timeout: 8000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
117
- );
143
+ const cliPath = resolveCliPath();
144
+ let result = '';
145
+ try {
146
+ const child = runGitNexusCli(cliPath, ['augment', '--', pattern], cwd, 7000);
147
+ if (!child.error && child.status === 0) {
118
148
  result = child.stderr || '';
119
- } catch { /* graceful failure */ }
120
-
121
- if (result && result.trim()) {
122
- console.log(JSON.stringify({
123
- hookSpecificOutput: {
124
- hookEventName: 'PreToolUse',
125
- additionalContext: result.trim()
126
- }
127
- }));
128
149
  }
150
+ } catch { /* graceful failure */ }
151
+
152
+ if (result && result.trim()) {
153
+ sendHookResponse('PreToolUse', result.trim());
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Emit a PostToolUse hook response with additional context for the agent.
159
+ */
160
+ function sendHookResponse(hookEventName, message) {
161
+ console.log(JSON.stringify({
162
+ hookSpecificOutput: { hookEventName, additionalContext: message }
163
+ }));
164
+ }
165
+
166
+ /**
167
+ * PostToolUse handler — detect index staleness after git mutations.
168
+ *
169
+ * Instead of spawning a full `npx -y @veewo/gitnexus@latest analyze` synchronously (which blocks
170
+ * the agent for up to 120s and risks KuzuDB corruption on timeout), we do a
171
+ * lightweight staleness check: compare `git rev-parse HEAD` against the
172
+ * lastCommit stored in `.gitnexus/meta.json`. If they differ, notify the
173
+ * agent so it can decide when to reindex.
174
+ */
175
+ function handlePostToolUse(input) {
176
+ const toolName = input.tool_name || '';
177
+ if (toolName !== 'Bash') return;
178
+
179
+ const command = (input.tool_input || {}).command || '';
180
+ if (!/\bgit\s+(commit|merge|rebase|cherry-pick|pull)(\s|$)/.test(command)) return;
181
+
182
+ // Only proceed if the command succeeded
183
+ const toolOutput = input.tool_output || {};
184
+ if (toolOutput.exit_code !== undefined && toolOutput.exit_code !== 0) return;
185
+
186
+ const cwd = input.cwd || process.cwd();
187
+ if (!path.isAbsolute(cwd)) return;
188
+ const gitNexusDir = findGitNexusDir(cwd);
189
+ if (!gitNexusDir) return;
190
+
191
+ // Compare HEAD against last indexed commit — skip if unchanged
192
+ let currentHead = '';
193
+ try {
194
+ const headResult = spawnSync('git', ['rev-parse', 'HEAD'], {
195
+ encoding: 'utf-8', timeout: 3000, cwd, stdio: ['pipe', 'pipe', 'pipe'],
196
+ });
197
+ currentHead = (headResult.stdout || '').trim();
198
+ } catch { return; }
199
+
200
+ if (!currentHead) return;
201
+
202
+ let lastCommit = '';
203
+ let hadEmbeddings = false;
204
+ try {
205
+ const meta = JSON.parse(fs.readFileSync(path.join(gitNexusDir, 'meta.json'), 'utf-8'));
206
+ lastCommit = meta.lastCommit || '';
207
+ hadEmbeddings = (meta.stats && meta.stats.embeddings > 0);
208
+ } catch { /* no meta — treat as stale */ }
209
+
210
+ // If HEAD matches last indexed commit, no reindex needed
211
+ if (currentHead && currentHead === lastCommit) return;
212
+
213
+ const analyzeCmd = `npx -y @veewo/gitnexus@latest analyze${hadEmbeddings ? ' --embeddings' : ''}`;
214
+ sendHookResponse('PostToolUse',
215
+ `GitNexus index is stale (last indexed: ${lastCommit ? lastCommit.slice(0, 7) : 'never'}). ` +
216
+ `Run \`${analyzeCmd}\` to update the knowledge graph.`
217
+ );
218
+ }
219
+
220
+ // Dispatch map for hook events
221
+ const handlers = {
222
+ PreToolUse: handlePreToolUse,
223
+ PostToolUse: handlePostToolUse,
224
+ };
225
+
226
+ function main() {
227
+ try {
228
+ const input = readInput();
229
+ const handler = handlers[input.hook_event_name || ''];
230
+ if (handler) handler(input);
129
231
  } catch (err) {
130
- // Graceful failure — log to stderr for debugging
131
- console.error('GitNexus hook error:', err.message);
232
+ if (process.env.GITNEXUS_DEBUG) {
233
+ console.error('GitNexus hook error:', (err.message || '').slice(0, 200));
234
+ }
132
235
  }
133
236
  }
134
237
 
@@ -63,7 +63,8 @@ if [ "$found" = false ]; then
63
63
  fi
64
64
 
65
65
  # Run gitnexus augment — must be fast (<500ms target)
66
- RESULT=$(cd "$CWD" && npx -y gitnexus augment "$PATTERN" 2>/dev/null)
66
+ # augment writes to stderr (KuzuDB captures stdout at OS level), so capture stderr and discard stdout
67
+ RESULT=$(cd "$CWD" && npx -y @veewo/gitnexus@latest augment "$PATTERN" 2>&1 1>/dev/null)
67
68
 
68
69
  if [ -n "$RESULT" ]; then
69
70
  ESCAPED=$(echo "$RESULT" | jq -Rs .)
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veewo/gitnexus",
3
- "version": "1.3.11",
3
+ "version": "1.4.6-rc",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -32,13 +32,21 @@
32
32
  "files": [
33
33
  "dist",
34
34
  "hooks",
35
+ "scripts",
35
36
  "skills",
36
37
  "vendor"
37
38
  ],
38
39
  "scripts": {
39
40
  "build": "rm -rf dist && tsc",
40
41
  "dev": "tsx watch src/cli/index.ts",
42
+ "test": "vitest run test/unit",
43
+ "test:integration": "vitest run test/integration",
44
+ "test:all": "vitest run",
45
+ "test:watch": "vitest",
46
+ "test:coverage": "vitest run --coverage",
41
47
  "prepare": "npm run build",
48
+ "postinstall": "node scripts/patch-tree-sitter-swift.cjs",
49
+ "prepack": "npm run build && chmod +x dist/cli/index.js",
42
50
  "check:release-paths": "node hooks/check-release-path-hygiene.mjs",
43
51
  "check:neonspark-target": "node -e \"const p=(process.env.GITNEXUS_NEONSPARK_TARGET_PATH||'').trim();if(!p){console.error('Missing env: GITNEXUS_NEONSPARK_TARGET_PATH');process.exit(1);}\"",
44
52
  "check:u2-e2e-target": "node -e \"const p=(process.env.GITNEXUS_U2_E2E_TARGET_PATH||'').trim();if(!p){console.error('Missing env: GITNEXUS_U2_E2E_TARGET_PATH');process.exit(1);}\"",
@@ -70,7 +78,8 @@
70
78
  "graphology": "^0.25.4",
71
79
  "graphology-indices": "^0.17.0",
72
80
  "graphology-utils": "^2.3.0",
73
- "kuzu": "^0.11.3",
81
+ "@ladybugdb/core": "^0.15.1",
82
+ "ignore": "^7.0.5",
74
83
  "lru-cache": "^11.0.0",
75
84
  "mnemonist": "^0.39.0",
76
85
  "pandemonium": "^2.4.0",
@@ -83,18 +92,25 @@
83
92
  "tree-sitter-javascript": "^0.21.0",
84
93
  "tree-sitter-php": "^0.23.12",
85
94
  "tree-sitter-python": "^0.21.0",
95
+ "tree-sitter-ruby": "^0.23.1",
86
96
  "tree-sitter-rust": "^0.21.0",
87
97
  "tree-sitter-typescript": "^0.21.0",
88
- "typescript": "^5.4.5",
89
98
  "uuid": "^13.0.0"
90
99
  },
100
+ "optionalDependencies": {
101
+ "tree-sitter-kotlin": "^0.3.8",
102
+ "tree-sitter-swift": "^0.6.0"
103
+ },
91
104
  "devDependencies": {
92
105
  "@types/cli-progress": "^3.11.6",
93
106
  "@types/cors": "^2.8.17",
94
107
  "@types/express": "^4.17.21",
95
108
  "@types/node": "^20.0.0",
96
109
  "@types/uuid": "^10.0.0",
97
- "tsx": "^4.0.0"
110
+ "@vitest/coverage-v8": "^4.0.18",
111
+ "tsx": "^4.0.0",
112
+ "typescript": "^5.4.5",
113
+ "vitest": "^4.0.18"
98
114
  },
99
115
  "engines": {
100
116
  "node": ">=18.0.0"
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * WORKAROUND: tree-sitter-swift@0.6.0 binding.gyp build failure
4
+ *
5
+ * Background:
6
+ * tree-sitter-swift@0.6.0's binding.gyp contains an "actions" array that
7
+ * invokes `tree-sitter generate` to regenerate parser.c from grammar.js.
8
+ * This is intended for grammar developers, but the published npm package
9
+ * already ships pre-generated parser files (parser.c, scanner.c), so the
10
+ * actions are unnecessary for consumers. Since consumers don't have
11
+ * tree-sitter-cli installed, the actions always fail during `npm install`.
12
+ *
13
+ * Why we can't just upgrade:
14
+ * tree-sitter-swift@0.7.1 fixes this (removes postinstall, ships prebuilds),
15
+ * but it requires tree-sitter@^0.22.1. The upstream project pins tree-sitter
16
+ * to ^0.21.0 and all other grammar packages depend on that version.
17
+ * Upgrading tree-sitter would be a separate breaking change.
18
+ *
19
+ * How this workaround works:
20
+ * 1. tree-sitter-swift's own postinstall fails (npm warns but continues)
21
+ * 2. This script runs as gitnexus's postinstall
22
+ * 3. It removes the "actions" array from binding.gyp
23
+ * 4. It rebuilds the native binding with the cleaned binding.gyp
24
+ *
25
+ * TODO: Remove this script when tree-sitter is upgraded to ^0.22.x,
26
+ * which allows using tree-sitter-swift@0.7.1+ directly.
27
+ */
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const { execSync } = require('child_process');
31
+
32
+ const swiftDir = path.join(__dirname, '..', 'node_modules', 'tree-sitter-swift');
33
+ const bindingPath = path.join(swiftDir, 'binding.gyp');
34
+
35
+ try {
36
+ if (!fs.existsSync(bindingPath)) {
37
+ process.exit(0);
38
+ }
39
+
40
+ const content = fs.readFileSync(bindingPath, 'utf8');
41
+ let needsRebuild = false;
42
+
43
+ if (content.includes('"actions"')) {
44
+ // Strip Python-style comments (#) before JSON parsing
45
+ const cleaned = content.replace(/#[^\n]*/g, '');
46
+ const gyp = JSON.parse(cleaned);
47
+
48
+ if (gyp.targets && gyp.targets[0] && gyp.targets[0].actions) {
49
+ delete gyp.targets[0].actions;
50
+ fs.writeFileSync(bindingPath, JSON.stringify(gyp, null, 2) + '\n');
51
+ console.log('[tree-sitter-swift] Patched binding.gyp (removed actions array)');
52
+ needsRebuild = true;
53
+ }
54
+ }
55
+
56
+ // Check if native binding exists
57
+ const bindingNode = path.join(swiftDir, 'build', 'Release', 'tree_sitter_swift_binding.node');
58
+ if (!fs.existsSync(bindingNode)) {
59
+ needsRebuild = true;
60
+ }
61
+
62
+ if (needsRebuild) {
63
+ console.log('[tree-sitter-swift] Rebuilding native binding...');
64
+ execSync('npx node-gyp rebuild', {
65
+ cwd: swiftDir,
66
+ stdio: 'pipe',
67
+ timeout: 120000,
68
+ });
69
+ console.log('[tree-sitter-swift] Native binding built successfully');
70
+ }
71
+ } catch (err) {
72
+ console.warn('[tree-sitter-swift] Could not build native binding:', err.message);
73
+ console.warn('[tree-sitter-swift] You may need to manually run: cd node_modules/tree-sitter-swift && npx node-gyp rebuild');
74
+ }
@@ -12,7 +12,7 @@ All commands work via `npx` — no global install required.
12
12
  ### analyze — Build or refresh the index
13
13
 
14
14
  ```bash
15
- npx gitnexus analyze
15
+ npx -y @veewo/gitnexus@latest analyze
16
16
  ```
17
17
 
18
18
  Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
@@ -22,12 +22,12 @@ Run from the project root. This parses all source files, builds the knowledge gr
22
22
  | `--force` | Force full re-index even if up to date |
23
23
  | `--embeddings` | Enable embedding generation for semantic search (off by default) |
24
24
 
25
- **When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale.
25
+ **When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook runs `analyze` automatically after `git commit` and `git merge`, preserving embeddings if previously generated.
26
26
 
27
27
  ### status — Check index freshness
28
28
 
29
29
  ```bash
30
- npx gitnexus status
30
+ npx -y @veewo/gitnexus@latest status
31
31
  ```
32
32
 
33
33
  Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed.
@@ -35,7 +35,7 @@ Shows whether the current repo has a GitNexus index, when it was last updated, a
35
35
  ### clean — Delete the index
36
36
 
37
37
  ```bash
38
- npx gitnexus clean
38
+ npx -y @veewo/gitnexus@latest clean
39
39
  ```
40
40
 
41
41
  Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project.
@@ -48,7 +48,7 @@ Deletes the `.gitnexus/` directory and unregisters the repo from the global regi
48
48
  ### wiki — Generate documentation from the graph
49
49
 
50
50
  ```bash
51
- npx gitnexus wiki
51
+ npx -y @veewo/gitnexus@latest wiki
52
52
  ```
53
53
 
54
54
  Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use).
@@ -65,7 +65,7 @@ Generates repository documentation from the knowledge graph using an LLM. Requir
65
65
  ### list — Show all indexed repos
66
66
 
67
67
  ```bash
68
- npx gitnexus list
68
+ npx -y @veewo/gitnexus@latest list
69
69
  ```
70
70
 
71
71
  Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information.
@@ -75,11 +75,11 @@ Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_
75
75
  For Unity resource retrieval:
76
76
 
77
77
  ```bash
78
- npx gitnexus context DoorObj --repo neonnew-core --file Assets/NEON/Code/Game/Doors/DoorObj.cs --unity-resources on --unity-hydration compact
78
+ npx -y @veewo/gitnexus@latest context DoorObj --repo neonnew-core --file Assets/NEON/Code/Game/Doors/DoorObj.cs --unity-resources on --unity-hydration compact
79
79
  ```
80
80
 
81
81
  ```bash
82
- npx gitnexus query "DoorObj binding" --repo neonnew-core --unity-resources on --unity-hydration compact
82
+ npx -y @veewo/gitnexus@latest query "DoorObj binding" --repo neonnew-core --unity-resources on --unity-hydration compact
83
83
  ```
84
84
 
85
85
  Rules:
@@ -23,7 +23,7 @@ description: "Use when the user is debugging a bug, tracing an error, or asking
23
23
  5. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed
24
24
  ```
25
25
 
26
- > If "Index is stale" → run `npx gitnexus analyze` in terminal.
26
+ > If "Index is stale" → run `npx -y @veewo/gitnexus@latest analyze` in terminal.
27
27
 
28
28
  ## Checklist
29
29
 
@@ -24,7 +24,7 @@ description: "Use when the user asks how code works, wants to understand archite
24
24
  6. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow
25
25
  ```
26
26
 
27
- > If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal.
27
+ > If step 2 says "Index is stale" → run `npx -y @veewo/gitnexus@latest analyze` in terminal.
28
28
 
29
29
  ## Checklist
30
30
 
@@ -15,7 +15,7 @@ For any task involving code understanding, debugging, impact analysis, or refact
15
15
  2. **Match your task to a skill below** and **read that skill file**
16
16
  3. **Follow the skill's workflow and checklist**
17
17
 
18
- > If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first.
18
+ > If step 1 warns the index is stale, run `npx -y @veewo/gitnexus@latest analyze` in the terminal first.
19
19
 
20
20
  ## Skills
21
21
 
@@ -23,7 +23,7 @@ description: "Use when the user wants to know what will break if they change som
23
23
  4. Assess risk and report to user
24
24
  ```
25
25
 
26
- > If "Index is stale" → run `npx gitnexus analyze` in terminal.
26
+ > If "Index is stale" → run `npx -y @veewo/gitnexus@latest analyze` in terminal.
27
27
 
28
28
  ## Checklist
29
29