gitnexus 1.4.7 → 1.4.9

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 (242) hide show
  1. package/README.md +29 -1
  2. package/dist/cli/ai-context.d.ts +1 -1
  3. package/dist/cli/ai-context.js +1 -1
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +54 -21
  6. package/dist/cli/index-repo.d.ts +15 -0
  7. package/dist/cli/index-repo.js +115 -0
  8. package/dist/cli/index.js +13 -3
  9. package/dist/cli/setup.js +90 -10
  10. package/dist/cli/wiki.d.ts +4 -0
  11. package/dist/cli/wiki.js +174 -53
  12. package/dist/config/supported-languages.d.ts +33 -1
  13. package/dist/config/supported-languages.js +32 -0
  14. package/dist/core/embeddings/embedder.d.ts +6 -1
  15. package/dist/core/embeddings/embedder.js +65 -5
  16. package/dist/core/embeddings/embedding-pipeline.js +11 -9
  17. package/dist/core/embeddings/http-client.d.ts +31 -0
  18. package/dist/core/embeddings/http-client.js +179 -0
  19. package/dist/core/embeddings/index.d.ts +1 -0
  20. package/dist/core/embeddings/index.js +1 -0
  21. package/dist/core/embeddings/types.d.ts +1 -1
  22. package/dist/core/graph/graph.js +9 -1
  23. package/dist/core/graph/types.d.ts +11 -2
  24. package/dist/core/ingestion/call-processor.d.ts +66 -2
  25. package/dist/core/ingestion/call-processor.js +650 -30
  26. package/dist/core/ingestion/call-routing.d.ts +9 -18
  27. package/dist/core/ingestion/call-routing.js +0 -19
  28. package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
  29. package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
  30. package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
  31. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
  32. package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
  33. package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
  34. package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
  35. package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
  36. package/dist/core/ingestion/cobol-processor.d.ts +54 -0
  37. package/dist/core/ingestion/cobol-processor.js +1186 -0
  38. package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
  39. package/dist/core/ingestion/entry-point-scoring.js +52 -28
  40. package/dist/core/ingestion/export-detection.d.ts +47 -8
  41. package/dist/core/ingestion/export-detection.js +29 -50
  42. package/dist/core/ingestion/field-extractor.d.ts +29 -0
  43. package/dist/core/ingestion/field-extractor.js +25 -0
  44. package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
  45. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
  46. package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
  48. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
  49. package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
  50. package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
  51. package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
  52. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
  53. package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
  54. package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
  55. package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
  56. package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
  57. package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
  58. package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
  59. package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
  60. package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
  61. package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
  62. package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
  63. package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
  64. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
  65. package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
  66. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
  67. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
  68. package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
  69. package/dist/core/ingestion/field-extractors/generic.js +111 -0
  70. package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
  71. package/dist/core/ingestion/field-extractors/typescript.js +291 -0
  72. package/dist/core/ingestion/field-types.d.ts +59 -0
  73. package/dist/core/ingestion/field-types.js +2 -0
  74. package/dist/core/ingestion/framework-detection.d.ts +97 -2
  75. package/dist/core/ingestion/framework-detection.js +114 -14
  76. package/dist/core/ingestion/heritage-processor.js +62 -66
  77. package/dist/core/ingestion/import-processor.d.ts +9 -10
  78. package/dist/core/ingestion/import-processor.js +150 -196
  79. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
  80. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
  81. package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
  82. package/dist/core/ingestion/import-resolvers/dart.js +44 -0
  83. package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
  84. package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
  85. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +10 -1
  86. package/dist/core/ingestion/import-resolvers/jvm.js +159 -0
  87. package/dist/core/ingestion/import-resolvers/php.d.ts +25 -0
  88. package/dist/core/ingestion/import-resolvers/php.js +80 -0
  89. package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
  90. package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
  91. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
  92. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
  93. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
  94. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
  95. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
  96. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
  97. package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
  98. package/dist/core/ingestion/import-resolvers/swift.js +23 -0
  99. package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
  100. package/dist/core/ingestion/import-resolvers/types.js +6 -0
  101. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +2 -0
  102. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +7 -0
  103. package/dist/core/ingestion/language-config.d.ts +6 -0
  104. package/dist/core/ingestion/language-config.js +13 -0
  105. package/dist/core/ingestion/language-provider.d.ts +121 -0
  106. package/dist/core/ingestion/language-provider.js +24 -0
  107. package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
  108. package/dist/core/ingestion/languages/c-cpp.js +71 -0
  109. package/dist/core/ingestion/languages/cobol.d.ts +1 -0
  110. package/dist/core/ingestion/languages/cobol.js +26 -0
  111. package/dist/core/ingestion/languages/csharp.d.ts +8 -0
  112. package/dist/core/ingestion/languages/csharp.js +49 -0
  113. package/dist/core/ingestion/languages/dart.d.ts +12 -0
  114. package/dist/core/ingestion/languages/dart.js +58 -0
  115. package/dist/core/ingestion/languages/go.d.ts +11 -0
  116. package/dist/core/ingestion/languages/go.js +28 -0
  117. package/dist/core/ingestion/languages/index.d.ts +38 -0
  118. package/dist/core/ingestion/languages/index.js +63 -0
  119. package/dist/core/ingestion/languages/java.d.ts +9 -0
  120. package/dist/core/ingestion/languages/java.js +29 -0
  121. package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
  122. package/dist/core/ingestion/languages/kotlin.js +53 -0
  123. package/dist/core/ingestion/languages/php.d.ts +8 -0
  124. package/dist/core/ingestion/languages/php.js +145 -0
  125. package/dist/core/ingestion/languages/python.d.ts +12 -0
  126. package/dist/core/ingestion/languages/python.js +39 -0
  127. package/dist/core/ingestion/languages/ruby.d.ts +9 -0
  128. package/dist/core/ingestion/languages/ruby.js +44 -0
  129. package/dist/core/ingestion/languages/rust.d.ts +12 -0
  130. package/dist/core/ingestion/languages/rust.js +44 -0
  131. package/dist/core/ingestion/languages/swift.d.ts +12 -0
  132. package/dist/core/ingestion/languages/swift.js +133 -0
  133. package/dist/core/ingestion/languages/typescript.d.ts +10 -0
  134. package/dist/core/ingestion/languages/typescript.js +60 -0
  135. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  136. package/dist/core/ingestion/markdown-processor.js +124 -0
  137. package/dist/core/ingestion/mro-processor.js +22 -18
  138. package/dist/core/ingestion/named-binding-processor.d.ts +18 -0
  139. package/dist/core/ingestion/named-binding-processor.js +42 -0
  140. package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
  141. package/dist/core/ingestion/named-bindings/csharp.js +37 -0
  142. package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
  143. package/dist/core/ingestion/named-bindings/java.js +29 -0
  144. package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
  145. package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
  146. package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
  147. package/dist/core/ingestion/named-bindings/php.js +61 -0
  148. package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
  149. package/dist/core/ingestion/named-bindings/python.js +49 -0
  150. package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
  151. package/dist/core/ingestion/named-bindings/rust.js +64 -0
  152. package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
  153. package/dist/core/ingestion/named-bindings/types.js +6 -0
  154. package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
  155. package/dist/core/ingestion/named-bindings/typescript.js +58 -0
  156. package/dist/core/ingestion/parsing-processor.d.ts +6 -2
  157. package/dist/core/ingestion/parsing-processor.js +125 -85
  158. package/dist/core/ingestion/pipeline.d.ts +10 -0
  159. package/dist/core/ingestion/pipeline.js +1235 -317
  160. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  161. package/dist/core/ingestion/resolution-context.js +8 -5
  162. package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
  163. package/dist/core/ingestion/route-extractors/expo.js +36 -0
  164. package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
  165. package/dist/core/ingestion/route-extractors/middleware.js +143 -0
  166. package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
  167. package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
  168. package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
  169. package/dist/core/ingestion/route-extractors/php.js +21 -0
  170. package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
  171. package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
  172. package/dist/core/ingestion/symbol-table.d.ts +16 -0
  173. package/dist/core/ingestion/symbol-table.js +20 -6
  174. package/dist/core/ingestion/tree-sitter-queries.d.ts +10 -9
  175. package/dist/core/ingestion/tree-sitter-queries.js +274 -11
  176. package/dist/core/ingestion/type-env.d.ts +42 -18
  177. package/dist/core/ingestion/type-env.js +481 -106
  178. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  179. package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
  180. package/dist/core/ingestion/type-extractors/csharp.js +149 -16
  181. package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
  182. package/dist/core/ingestion/type-extractors/dart.js +371 -0
  183. package/dist/core/ingestion/type-extractors/jvm.js +169 -66
  184. package/dist/core/ingestion/type-extractors/rust.js +35 -1
  185. package/dist/core/ingestion/type-extractors/shared.d.ts +1 -15
  186. package/dist/core/ingestion/type-extractors/shared.js +14 -112
  187. package/dist/core/ingestion/type-extractors/swift.js +338 -7
  188. package/dist/core/ingestion/type-extractors/types.d.ts +40 -8
  189. package/dist/core/ingestion/type-extractors/typescript.js +141 -9
  190. package/dist/core/ingestion/utils/ast-helpers.d.ts +83 -0
  191. package/dist/core/ingestion/utils/ast-helpers.js +817 -0
  192. package/dist/core/ingestion/utils/call-analysis.d.ts +73 -0
  193. package/dist/core/ingestion/utils/call-analysis.js +527 -0
  194. package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
  195. package/dist/core/ingestion/utils/event-loop.js +5 -0
  196. package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
  197. package/dist/core/ingestion/utils/language-detection.js +70 -0
  198. package/dist/core/ingestion/utils/verbose.d.ts +1 -0
  199. package/dist/core/ingestion/utils/verbose.js +7 -0
  200. package/dist/core/ingestion/workers/parse-worker.d.ts +55 -5
  201. package/dist/core/ingestion/workers/parse-worker.js +415 -225
  202. package/dist/core/lbug/csv-generator.js +51 -1
  203. package/dist/core/lbug/lbug-adapter.d.ts +10 -0
  204. package/dist/core/lbug/lbug-adapter.js +75 -4
  205. package/dist/core/lbug/schema.d.ts +8 -4
  206. package/dist/core/lbug/schema.js +65 -4
  207. package/dist/core/tree-sitter/parser-loader.js +7 -1
  208. package/dist/core/wiki/cursor-client.d.ts +31 -0
  209. package/dist/core/wiki/cursor-client.js +127 -0
  210. package/dist/core/wiki/generator.d.ts +28 -9
  211. package/dist/core/wiki/generator.js +115 -18
  212. package/dist/core/wiki/graph-queries.d.ts +4 -0
  213. package/dist/core/wiki/graph-queries.js +7 -1
  214. package/dist/core/wiki/llm-client.d.ts +2 -0
  215. package/dist/core/wiki/llm-client.js +8 -4
  216. package/dist/core/wiki/prompts.d.ts +3 -3
  217. package/dist/core/wiki/prompts.js +6 -0
  218. package/dist/mcp/core/embedder.js +11 -3
  219. package/dist/mcp/core/lbug-adapter.d.ts +5 -0
  220. package/dist/mcp/core/lbug-adapter.js +23 -2
  221. package/dist/mcp/local/local-backend.d.ts +38 -5
  222. package/dist/mcp/local/local-backend.js +804 -63
  223. package/dist/mcp/resources.js +2 -0
  224. package/dist/mcp/tools.js +73 -4
  225. package/dist/server/api.d.ts +19 -1
  226. package/dist/server/api.js +66 -6
  227. package/dist/storage/git.d.ts +12 -0
  228. package/dist/storage/git.js +21 -0
  229. package/dist/storage/repo-manager.d.ts +3 -0
  230. package/package.json +25 -16
  231. package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
  232. package/dist/core/ingestion/named-binding-extraction.js +0 -363
  233. package/dist/core/ingestion/resolvers/index.d.ts +0 -18
  234. package/dist/core/ingestion/resolvers/index.js +0 -13
  235. package/dist/core/ingestion/resolvers/jvm.js +0 -87
  236. package/dist/core/ingestion/resolvers/php.d.ts +0 -15
  237. package/dist/core/ingestion/resolvers/php.js +0 -35
  238. package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
  239. package/dist/core/ingestion/type-extractors/index.js +0 -31
  240. package/dist/core/ingestion/utils.d.ts +0 -138
  241. package/dist/core/ingestion/utils.js +0 -1290
  242. package/scripts/patch-tree-sitter-swift.cjs +0 -74
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**, **Windsurf**, **Cline**, **OpenCode**, and any MCP-compatible tool.
5
+ Works with **Cursor**, **Claude Code**, **Codex**, **Windsurf**, **Cline**, **OpenCode**, 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/)
@@ -34,6 +34,7 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
34
34
  |--------|-----|--------|---------------------|---------|
35
35
  | **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
36
36
  | **Cursor** | Yes | Yes | — | MCP + Skills |
37
+ | **Codex** | Yes | Yes | — | MCP + Skills |
37
38
  | **Windsurf** | Yes | — | — | MCP |
38
39
  | **OpenCode** | Yes | Yes | — | MCP + Skills |
39
40
 
@@ -52,7 +53,17 @@ If you prefer to configure manually instead of using `gitnexus setup`:
52
53
  ### Claude Code (full support — MCP + skills + hooks)
53
54
 
54
55
  ```bash
56
+ # macOS / Linux
55
57
  claude mcp add gitnexus -- npx -y gitnexus@latest mcp
58
+
59
+ # Windows
60
+ claude mcp add gitnexus -- cmd /c npx -y gitnexus@latest mcp
61
+ ```
62
+
63
+ ### Codex (full support — MCP + skills)
64
+
65
+ ```bash
66
+ codex mcp add gitnexus -- npx -y gitnexus@latest mcp
56
67
  ```
57
68
 
58
69
  ### Cursor / Windsurf
@@ -92,6 +103,8 @@ GitNexus builds a complete knowledge graph of your codebase through a multi-phas
92
103
  1. **Structure** — Walks the file tree and maps folder/file relationships
93
104
  2. **Parsing** — Extracts functions, classes, methods, and interfaces using Tree-sitter ASTs
94
105
  3. **Resolution** — Resolves imports and function calls across files with language-aware logic
106
+ - **Field & Property Type Resolution** — Tracks field types across classes and interfaces for deep chain resolution (e.g., `user.address.city.getName()`)
107
+ - **Return-Type-Aware Variable Binding** — Infers variable types from function return types, enabling accurate call-result binding
95
108
  4. **Clustering** — Groups related symbols into functional communities
96
109
  5. **Processes** — Traces execution flows from entry points through call chains
97
110
  6. **Search** — Builds hybrid search indexes for fast retrieval
@@ -143,6 +156,7 @@ gitnexus analyze --embeddings # Enable embedding generation (slower, better
143
156
  gitnexus analyze --verbose # Log skipped files when parsers are unavailable
144
157
  gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
145
158
  gitnexus serve # Start local HTTP server (multi-repo) for web UI
159
+ gitnexus index # Register an existing .gitnexus/ folder into the global registry
146
160
  gitnexus list # List all indexed repositories
147
161
  gitnexus status # Show index status for current repo
148
162
  gitnexus clean # Delete index for current repo
@@ -151,6 +165,20 @@ gitnexus wiki [path] # Generate LLM-powered docs from knowledge grap
151
165
  gitnexus wiki --model <model> # Wiki with custom LLM model (default: gpt-4o-mini)
152
166
  ```
153
167
 
168
+ ## Remote Embeddings
169
+
170
+ Set these env vars to use a remote OpenAI-compatible `/v1/embeddings` endpoint instead of the local model:
171
+
172
+ ```bash
173
+ export GITNEXUS_EMBEDDING_URL=http://your-server:8080/v1
174
+ export GITNEXUS_EMBEDDING_MODEL=BAAI/bge-large-en-v1.5
175
+ export GITNEXUS_EMBEDDING_DIMS=1024 # optional, default 384
176
+ export GITNEXUS_EMBEDDING_API_KEY=your-key # optional, default: "unused"
177
+ gitnexus analyze . --embeddings
178
+ ```
179
+
180
+ Works with Infinity, vLLM, TEI, llama.cpp, Ollama, LM Studio, or OpenAI. When unset, local embeddings are used unchanged.
181
+
154
182
  ## Multi-Repo Support
155
183
 
156
184
  GitNexus supports indexing multiple repositories. Each `gitnexus analyze` registers the repo in a global registry (`~/.gitnexus/registry.json`). The MCP server serves all indexed repos automatically.
@@ -2,7 +2,7 @@
2
2
  * AI Context Generator
3
3
  *
4
4
  * Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
5
- * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
5
+ * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
6
6
  * CLAUDE.md is for Claude Code which only reads that file.
7
7
  */
8
8
  import { type GeneratedSkillInfo } from './skill-gen.js';
@@ -2,7 +2,7 @@
2
2
  * AI Context Generator
3
3
  *
4
4
  * Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
5
- * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
5
+ * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
6
6
  * CLAUDE.md is for Claude Code which only reads that file.
7
7
  */
8
8
  import fs from 'fs/promises';
@@ -8,5 +8,7 @@ export interface AnalyzeOptions {
8
8
  embeddings?: boolean;
9
9
  skills?: boolean;
10
10
  verbose?: boolean;
11
+ /** Index the folder even when no .git directory is present. */
12
+ skipGit?: boolean;
11
13
  }
12
14
  export declare const analyzeCommand: (inputPath?: string, options?: AnalyzeOptions) => Promise<void>;
@@ -14,7 +14,7 @@ import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReuse
14
14
  // versions whose ABI is not yet supported by the native binary (#89).
15
15
  // disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
16
16
  import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, cleanupOldKuzuFiles } from '../storage/repo-manager.js';
17
- import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
17
+ import { getCurrentCommit, getGitRoot, hasGitDir } from '../storage/git.js';
18
18
  import { generateAIContextFiles } from './ai-context.js';
19
19
  import { generateSkillFiles } from './skill-gen.js';
20
20
  import fs from 'fs/promises';
@@ -70,17 +70,27 @@ export const analyzeCommand = async (inputPath, options) => {
70
70
  else {
71
71
  const gitRoot = getGitRoot(process.cwd());
72
72
  if (!gitRoot) {
73
- console.log(' Not inside a git repository\n');
74
- process.exitCode = 1;
75
- return;
73
+ if (!options?.skipGit) {
74
+ console.log(' Not inside a git repository.\n Tip: pass --skip-git to index any folder without a .git directory.\n');
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ // --skip-git: fall back to cwd as the root
79
+ repoPath = path.resolve(process.cwd());
80
+ }
81
+ else {
82
+ repoPath = gitRoot;
76
83
  }
77
- repoPath = gitRoot;
78
84
  }
79
- if (!isGitRepo(repoPath)) {
80
- console.log(' Not a git repository\n');
85
+ const repoHasGit = hasGitDir(repoPath);
86
+ if (!repoHasGit && !options?.skipGit) {
87
+ console.log(' Not a git repository.\n Tip: pass --skip-git to index any folder without a .git directory.\n');
81
88
  process.exitCode = 1;
82
89
  return;
83
90
  }
91
+ if (!repoHasGit) {
92
+ console.log(' Warning: no .git directory found \u2014 commit-tracking and incremental updates disabled.\n');
93
+ }
84
94
  const { storagePath, lbugPath } = getStoragePaths(repoPath);
85
95
  // Clean up stale KuzuDB files from before the LadybugDB migration.
86
96
  // If kuzu existed but lbug doesn't, we're doing a migration re-index — say so.
@@ -88,11 +98,14 @@ export const analyzeCommand = async (inputPath, options) => {
88
98
  if (kuzuResult.found && kuzuResult.needsReindex) {
89
99
  console.log(' Migrating from KuzuDB to LadybugDB — rebuilding index...\n');
90
100
  }
91
- const currentCommit = getCurrentCommit(repoPath);
101
+ const currentCommit = repoHasGit ? getCurrentCommit(repoPath) : '';
92
102
  const existingMeta = await loadMeta(storagePath);
93
103
  if (existingMeta && !options?.force && !options?.skills && existingMeta.lastCommit === currentCommit) {
94
- console.log(' Already up to date\n');
95
- return;
104
+ // Non-git folders have currentCommit = '' always rebuild since we can't detect changes
105
+ if (currentCommit !== '') {
106
+ console.log(' Already up to date\n');
107
+ return;
108
+ }
96
109
  }
97
110
  if (process.env.GITNEXUS_NO_GITIGNORE) {
98
111
  console.log(' GITNEXUS_NO_GITIGNORE is set — skipping .gitignore (still reading .gitnexusignore)\n');
@@ -218,15 +231,26 @@ export const analyzeCommand = async (inputPath, options) => {
218
231
  const ftsTime = ((Date.now() - t0Fts) / 1000).toFixed(1);
219
232
  // ── Phase 3.5: Re-insert cached embeddings ────────────────────────
220
233
  if (cachedEmbeddings.length > 0) {
221
- updateBar(88, `Restoring ${cachedEmbeddings.length} cached embeddings...`);
222
- const EMBED_BATCH = 200;
223
- for (let i = 0; i < cachedEmbeddings.length; i += EMBED_BATCH) {
224
- const batch = cachedEmbeddings.slice(i, i + EMBED_BATCH);
225
- const paramsList = batch.map(e => ({ nodeId: e.nodeId, embedding: e.embedding }));
226
- try {
227
- await executeWithReusedStatement(`CREATE (e:CodeEmbedding {nodeId: $nodeId, embedding: $embedding})`, paramsList);
234
+ // Check if cached embedding dimensions match current schema
235
+ const cachedDims = cachedEmbeddings[0].embedding.length;
236
+ const { EMBEDDING_DIMS } = await import('../core/lbug/schema.js');
237
+ if (cachedDims !== EMBEDDING_DIMS) {
238
+ // Dimensions changed (e.g. switched embedding model) discard cache and re-embed all
239
+ console.error(`⚠️ Embedding dimensions changed (${cachedDims}d → ${EMBEDDING_DIMS}d), discarding cache`);
240
+ cachedEmbeddings = [];
241
+ cachedEmbeddingNodeIds = new Set();
242
+ }
243
+ else {
244
+ updateBar(88, `Restoring ${cachedEmbeddings.length} cached embeddings...`);
245
+ const EMBED_BATCH = 200;
246
+ for (let i = 0; i < cachedEmbeddings.length; i += EMBED_BATCH) {
247
+ const batch = cachedEmbeddings.slice(i, i + EMBED_BATCH);
248
+ const paramsList = batch.map(e => ({ nodeId: e.nodeId, embedding: e.embedding }));
249
+ try {
250
+ await executeWithReusedStatement(`CREATE (e:CodeEmbedding {nodeId: $nodeId, embedding: $embedding})`, paramsList);
251
+ }
252
+ catch { /* some may fail if node was removed, that's fine */ }
228
253
  }
229
- catch { /* some may fail if node was removed, that's fine */ }
230
254
  }
231
255
  }
232
256
  // ── Phase 4: Embeddings (90–98%) ──────────────────────────────────
@@ -243,12 +267,16 @@ export const analyzeCommand = async (inputPath, options) => {
243
267
  }
244
268
  }
245
269
  if (!embeddingSkipped) {
246
- updateBar(90, 'Loading embedding model...');
270
+ const { isHttpMode } = await import('../core/embeddings/http-client.js');
271
+ const httpMode = isHttpMode();
272
+ updateBar(90, httpMode ? 'Connecting to embedding endpoint...' : 'Loading embedding model...');
247
273
  const t0Emb = Date.now();
248
274
  const { runEmbeddingPipeline } = await import('../core/embeddings/embedding-pipeline.js');
249
275
  await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
250
276
  const scaled = 90 + Math.round((progress.percent / 100) * 8);
251
- const label = progress.phase === 'loading-model' ? 'Loading embedding model...' : `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
277
+ const label = progress.phase === 'loading-model'
278
+ ? (httpMode ? 'Connecting to embedding endpoint...' : 'Loading embedding model...')
279
+ : `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
252
280
  updateBar(scaled, label);
253
281
  }, {}, cachedEmbeddingNodeIds.size > 0 ? cachedEmbeddingNodeIds : undefined);
254
282
  embeddingTime = ((Date.now() - t0Emb) / 1000).toFixed(1);
@@ -277,7 +305,12 @@ export const analyzeCommand = async (inputPath, options) => {
277
305
  };
278
306
  await saveMeta(storagePath, meta);
279
307
  await registerRepo(repoPath, meta);
280
- await addToGitignore(repoPath);
308
+ // Only attempt to update .gitignore when a .git directory is present.
309
+ // Use hasGitDir (filesystem check) rather than git CLI subprocess
310
+ // so we skip correctly for --skip-git folders even if git CLI is available.
311
+ if (hasGitDir(repoPath)) {
312
+ await addToGitignore(repoPath);
313
+ }
281
314
  const projectName = path.basename(repoPath);
282
315
  let aggregatedClusterCount = 0;
283
316
  if (pipelineResult.communityResult?.communities) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Index Command
3
+ *
4
+ * Registers an existing .gitnexus/ folder into the global registry so the
5
+ * MCP server can discover the repo without running a full `gitnexus analyze`.
6
+ *
7
+ * Useful when a pre-built .gitnexus/ directory is already present (e.g. after
8
+ * cloning a repo that ships its index, restoring from backup, or using a
9
+ * shared team index).
10
+ */
11
+ export interface IndexOptions {
12
+ force?: boolean;
13
+ allowNonGit?: boolean;
14
+ }
15
+ export declare const indexCommand: (inputPathParts?: string[], options?: IndexOptions) => Promise<void>;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Index Command
3
+ *
4
+ * Registers an existing .gitnexus/ folder into the global registry so the
5
+ * MCP server can discover the repo without running a full `gitnexus analyze`.
6
+ *
7
+ * Useful when a pre-built .gitnexus/ directory is already present (e.g. after
8
+ * cloning a repo that ships its index, restoring from backup, or using a
9
+ * shared team index).
10
+ */
11
+ import path from "path";
12
+ import fs from "fs/promises";
13
+ import { getStoragePaths, loadMeta, addToGitignore, registerRepo, } from "../storage/repo-manager.js";
14
+ import { getGitRoot, isGitRepo } from "../storage/git.js";
15
+ export const indexCommand = async (inputPathParts, options) => {
16
+ console.log("\n GitNexus Index\n");
17
+ const inputPath = inputPathParts?.length
18
+ ? inputPathParts.join(" ")
19
+ : undefined;
20
+ if (inputPathParts && inputPathParts.length > 1) {
21
+ const resolvedCombinedPath = path.resolve(inputPath);
22
+ try {
23
+ await fs.access(resolvedCombinedPath);
24
+ }
25
+ catch {
26
+ console.log(" The `index` command accepts a single path only.");
27
+ console.log(" If your path contains spaces, wrap it in quotes.");
28
+ console.log(` Received multiple path parts: ${inputPathParts.join(", ")}`);
29
+ console.log("");
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+ }
34
+ let repoPath;
35
+ if (inputPath) {
36
+ repoPath = path.resolve(inputPath);
37
+ }
38
+ else {
39
+ const gitRoot = getGitRoot(process.cwd());
40
+ if (!gitRoot) {
41
+ console.log(" Not inside a git repository, try to run git init\n");
42
+ process.exitCode = 1;
43
+ return;
44
+ }
45
+ repoPath = gitRoot;
46
+ }
47
+ if (!options?.allowNonGit && !isGitRepo(repoPath)) {
48
+ console.log(` Not a git repository: ${repoPath}`);
49
+ console.log(" Initialize one with `git init` or choose a valid repo path.\n");
50
+ console.log(" Or use --allow-non-git to register an existing .gitnexus index anyway.\n");
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ const { storagePath, lbugPath } = getStoragePaths(repoPath);
55
+ // ── Verify .gitnexus/ exists ──────────────────────────────────────
56
+ try {
57
+ await fs.access(storagePath);
58
+ }
59
+ catch {
60
+ console.log(` No .gitnexus/ folder found at: ${storagePath}`);
61
+ console.log(" Run `gitnexus analyze` to build the index first.\n");
62
+ process.exitCode = 1;
63
+ return;
64
+ }
65
+ // ── Verify lbug database exists ───────────────────────────────────
66
+ try {
67
+ await fs.access(lbugPath);
68
+ }
69
+ catch {
70
+ console.log(` .gitnexus/ folder exists but contains no LadybugDB index.`);
71
+ console.log(" Run `gitnexus analyze` to build the index.\n");
72
+ process.exitCode = 1;
73
+ return;
74
+ }
75
+ // ── Load or reconstruct meta ──────────────────────────────────────
76
+ let meta = await loadMeta(storagePath);
77
+ if (!meta) {
78
+ if (!options?.force) {
79
+ console.log(` .gitnexus/ exists but meta.json is missing.`);
80
+ console.log(" Use --force to register anyway (stats will be empty),");
81
+ console.log(" or run `gitnexus analyze` to rebuild properly.\n");
82
+ process.exitCode = 1;
83
+ return;
84
+ }
85
+ // --force: build a minimal meta so the repo can be registered
86
+ meta = {
87
+ repoPath,
88
+ lastCommit: "",
89
+ indexedAt: new Date().toISOString(),
90
+ };
91
+ }
92
+ // ── Register in global registry ───────────────────────────────────
93
+ await registerRepo(repoPath, meta);
94
+ await addToGitignore(repoPath);
95
+ const projectName = path.basename(repoPath);
96
+ const { stats } = meta;
97
+ console.log(` Repository registered: ${projectName}`);
98
+ if (stats) {
99
+ const parts = [];
100
+ if (stats.nodes != null) {
101
+ parts.push(`${stats.nodes.toLocaleString()} nodes`);
102
+ }
103
+ if (stats.edges != null) {
104
+ parts.push(`${stats.edges.toLocaleString()} edges`);
105
+ }
106
+ if (stats.communities != null)
107
+ parts.push(`${stats.communities} clusters`);
108
+ if (stats.processes != null)
109
+ parts.push(`${stats.processes} flows`);
110
+ if (parts.length)
111
+ console.log(` ${parts.join(" | ")}`);
112
+ }
113
+ console.log(` ${repoPath}`);
114
+ console.log("");
115
+ };
package/dist/cli/index.js CHANGED
@@ -13,7 +13,7 @@ program
13
13
  .version(pkg.version);
14
14
  program
15
15
  .command('setup')
16
- .description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode')
16
+ .description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode, Codex')
17
17
  .action(createLazyAction(() => import('./setup.js'), 'setupCommand'));
18
18
  program
19
19
  .command('analyze [path]')
@@ -21,9 +21,16 @@ program
21
21
  .option('-f, --force', 'Force full re-index even if up to date')
22
22
  .option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
23
23
  .option('--skills', 'Generate repo-specific skill files from detected communities')
24
+ .option('--skip-git', 'Index a folder without requiring a .git directory')
24
25
  .option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
25
26
  .addHelpText('after', '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)')
26
27
  .action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
28
+ program
29
+ .command('index [path...]')
30
+ .description('Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)')
31
+ .option('-f, --force', 'Register even if meta.json is missing (stats will be empty)')
32
+ .option('--allow-non-git', 'Allow registering folders that are not Git repositories')
33
+ .action(createLazyAction(() => import('./index-repo.js'), 'indexCommand'));
27
34
  program
28
35
  .command('serve')
29
36
  .description('Start local HTTP server for web UI connection')
@@ -52,11 +59,14 @@ program
52
59
  .command('wiki [path]')
53
60
  .description('Generate repository wiki from knowledge graph')
54
61
  .option('-f, --force', 'Force full regeneration even if up to date')
55
- .option('--model <model>', 'LLM model name (default: minimax/minimax-m2.5)')
56
- .option('--base-url <url>', 'LLM API base URL (default: OpenAI)')
62
+ .option('--provider <provider>', 'LLM provider: openai or cursor (default: openai)')
63
+ .option('--model <model>', 'LLM model name (default depends on provider)')
64
+ .option('--base-url <url>', 'LLM API base URL (for openai provider)')
57
65
  .option('--api-key <key>', 'LLM API key (saved to ~/.gitnexus/config.json)')
58
66
  .option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
59
67
  .option('--gist', 'Publish wiki as a public GitHub Gist after generation')
68
+ .option('-v, --verbose', 'Enable verbose output (show LLM commands and responses)')
69
+ .option('--review', 'Stop after grouping to review module structure before generating pages')
60
70
  .action(createLazyAction(() => import('./wiki.js'), 'wikiCommand'));
61
71
  program
62
72
  .command('augment <pattern>')
package/dist/cli/setup.js CHANGED
@@ -8,11 +8,14 @@
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import os from 'os';
11
+ import { execFile } from 'child_process';
12
+ import { promisify } from 'util';
11
13
  import { fileURLToPath } from 'url';
12
14
  import { glob } from 'glob';
13
15
  import { getGlobalDir } from '../storage/repo-manager.js';
14
16
  const __filename = fileURLToPath(import.meta.url);
15
17
  const __dirname = path.dirname(__filename);
18
+ const execFileAsync = promisify(execFile);
16
19
  /**
17
20
  * The MCP server entry for all editors.
18
21
  * On Windows, npx must be invoked via cmd /c since it's a .cmd script.
@@ -94,18 +97,21 @@ async function setupCursor(result) {
94
97
  }
95
98
  async function setupClaudeCode(result) {
96
99
  const claudeDir = path.join(os.homedir(), '.claude');
97
- const hasClaude = await dirExists(claudeDir);
98
- if (!hasClaude) {
100
+ if (!(await dirExists(claudeDir))) {
99
101
  result.skipped.push('Claude Code (not installed)');
100
102
  return;
101
103
  }
102
- // Claude Code uses a JSON settings file at ~/.claude.json or claude mcp add
103
- console.log('');
104
- console.log(' Claude Code detected. Run this command to add GitNexus MCP:');
105
- console.log('');
106
- console.log(' claude mcp add gitnexus -- npx -y gitnexus mcp');
107
- console.log('');
108
- result.configured.push('Claude Code (MCP manual step printed)');
104
+ // Claude Code stores MCP config in ~/.claude.json
105
+ const mcpPath = path.join(os.homedir(), '.claude.json');
106
+ try {
107
+ const existing = await readJsonFile(mcpPath);
108
+ const updated = mergeMcpConfig(existing);
109
+ await writeJsonFile(mcpPath, updated);
110
+ result.configured.push('Claude Code');
111
+ }
112
+ catch (err) {
113
+ result.errors.push(`Claude Code: ${err.message}`);
114
+ }
109
115
  }
110
116
  /**
111
117
  * Install GitNexus skills to ~/.claude/skills/ for Claude Code.
@@ -201,11 +207,65 @@ async function setupOpenCode(result) {
201
207
  result.errors.push(`OpenCode: ${err.message}`);
202
208
  }
203
209
  }
210
+ /**
211
+ * Build a TOML section for Codex MCP config (~/.codex/config.toml).
212
+ */
213
+ function getCodexMcpTomlSection() {
214
+ const entry = getMcpEntry();
215
+ const command = JSON.stringify(entry.command);
216
+ const args = `[${entry.args.map(arg => JSON.stringify(arg)).join(', ')}]`;
217
+ return `[mcp_servers.gitnexus]\ncommand = ${command}\nargs = ${args}\n`;
218
+ }
219
+ /**
220
+ * Append GitNexus MCP server config to Codex's config.toml if missing.
221
+ */
222
+ async function upsertCodexConfigToml(configPath) {
223
+ let existing = '';
224
+ try {
225
+ existing = await fs.readFile(configPath, 'utf-8');
226
+ }
227
+ catch {
228
+ existing = '';
229
+ }
230
+ if (existing.includes('[mcp_servers.gitnexus]')) {
231
+ return;
232
+ }
233
+ const section = getCodexMcpTomlSection();
234
+ const nextContent = existing.trim().length > 0
235
+ ? `${existing.trimEnd()}\n\n${section}`
236
+ : section;
237
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
238
+ await fs.writeFile(configPath, `${nextContent.trimEnd()}\n`, 'utf-8');
239
+ }
240
+ async function setupCodex(result) {
241
+ const codexDir = path.join(os.homedir(), '.codex');
242
+ if (!(await dirExists(codexDir))) {
243
+ result.skipped.push('Codex (not installed)');
244
+ return;
245
+ }
246
+ try {
247
+ const entry = getMcpEntry();
248
+ await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], { shell: process.platform === 'win32' });
249
+ result.configured.push('Codex');
250
+ return;
251
+ }
252
+ catch {
253
+ // Fallback for environments where `codex` binary isn't on PATH.
254
+ }
255
+ try {
256
+ const configPath = path.join(codexDir, 'config.toml');
257
+ await upsertCodexConfigToml(configPath);
258
+ result.configured.push('Codex (MCP added to ~/.codex/config.toml)');
259
+ }
260
+ catch (err) {
261
+ result.errors.push(`Codex: ${err.message}`);
262
+ }
263
+ }
204
264
  // ─── Skill Installation ───────────────────────────────────────────
205
265
  /**
206
266
  * Install GitNexus skills to a target directory.
207
267
  * Each skill is installed as {targetDir}/gitnexus-{skillName}/SKILL.md
208
- * following the Agent Skills standard (both Cursor and Claude Code).
268
+ * following the Agent Skills standard (Cursor, Claude Code, and Codex).
209
269
  *
210
270
  * Supports two source layouts:
211
271
  * - Flat file: skills/{name}.md → copied as SKILL.md
@@ -310,6 +370,24 @@ async function installOpenCodeSkills(result) {
310
370
  result.errors.push(`OpenCode skills: ${err.message}`);
311
371
  }
312
372
  }
373
+ /**
374
+ * Install global Codex skills to ~/.agents/skills/gitnexus/
375
+ */
376
+ async function installCodexSkills(result) {
377
+ const codexDir = path.join(os.homedir(), '.codex');
378
+ if (!(await dirExists(codexDir)))
379
+ return;
380
+ const skillsDir = path.join(os.homedir(), '.agents', 'skills');
381
+ try {
382
+ const installed = await installSkillsTo(skillsDir);
383
+ if (installed.length > 0) {
384
+ result.configured.push(`Codex skills (${installed.length} skills → ~/.agents/skills/)`);
385
+ }
386
+ }
387
+ catch (err) {
388
+ result.errors.push(`Codex skills: ${err.message}`);
389
+ }
390
+ }
313
391
  // ─── Main command ──────────────────────────────────────────────────
314
392
  export const setupCommand = async () => {
315
393
  console.log('');
@@ -328,11 +406,13 @@ export const setupCommand = async () => {
328
406
  await setupCursor(result);
329
407
  await setupClaudeCode(result);
330
408
  await setupOpenCode(result);
409
+ await setupCodex(result);
331
410
  // Install global skills for platforms that support them
332
411
  await installClaudeCodeSkills(result);
333
412
  await installClaudeCodeHooks(result);
334
413
  await installCursorSkills(result);
335
414
  await installOpenCodeSkills(result);
415
+ await installCodexSkills(result);
336
416
  // Print results
337
417
  if (result.configured.length > 0) {
338
418
  console.log(' Configured:');
@@ -4,6 +4,7 @@
4
4
  * Generates repository documentation from the knowledge graph.
5
5
  * Usage: gitnexus wiki [path] [options]
6
6
  */
7
+ import { type LLMProvider } from '../core/wiki/llm-client.js';
7
8
  export interface WikiCommandOptions {
8
9
  force?: boolean;
9
10
  model?: string;
@@ -11,5 +12,8 @@ export interface WikiCommandOptions {
11
12
  apiKey?: string;
12
13
  concurrency?: string;
13
14
  gist?: boolean;
15
+ provider?: LLMProvider;
16
+ verbose?: boolean;
17
+ review?: boolean;
14
18
  }
15
19
  export declare const wikiCommand: (inputPath?: string, options?: WikiCommandOptions) => Promise<void>;