gitnexus 1.4.10 → 1.5.0

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 (186) hide show
  1. package/README.md +6 -5
  2. package/dist/cli/ai-context.d.ts +4 -1
  3. package/dist/cli/ai-context.js +19 -11
  4. package/dist/cli/analyze.d.ts +6 -0
  5. package/dist/cli/analyze.js +105 -251
  6. package/dist/cli/eval-server.js +20 -11
  7. package/dist/cli/index-repo.js +20 -22
  8. package/dist/cli/index.js +8 -7
  9. package/dist/cli/mcp.js +1 -1
  10. package/dist/cli/serve.js +29 -1
  11. package/dist/cli/setup.js +9 -9
  12. package/dist/cli/skill-gen.js +15 -9
  13. package/dist/cli/wiki.d.ts +2 -0
  14. package/dist/cli/wiki.js +141 -26
  15. package/dist/config/ignore-service.js +102 -22
  16. package/dist/config/supported-languages.d.ts +8 -42
  17. package/dist/config/supported-languages.js +8 -43
  18. package/dist/core/augmentation/engine.js +19 -7
  19. package/dist/core/embeddings/embedder.js +19 -15
  20. package/dist/core/embeddings/embedding-pipeline.js +6 -6
  21. package/dist/core/embeddings/http-client.js +3 -3
  22. package/dist/core/embeddings/text-generator.js +9 -24
  23. package/dist/core/embeddings/types.d.ts +1 -1
  24. package/dist/core/embeddings/types.js +1 -7
  25. package/dist/core/graph/graph.js +6 -2
  26. package/dist/core/graph/types.d.ts +9 -59
  27. package/dist/core/ingestion/ast-cache.js +3 -3
  28. package/dist/core/ingestion/call-processor.d.ts +20 -2
  29. package/dist/core/ingestion/call-processor.js +347 -144
  30. package/dist/core/ingestion/call-routing.js +10 -4
  31. package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
  32. package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
  33. package/dist/core/ingestion/call-sites/java.d.ts +9 -0
  34. package/dist/core/ingestion/call-sites/java.js +30 -0
  35. package/dist/core/ingestion/cluster-enricher.js +6 -8
  36. package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
  37. package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
  38. package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
  39. package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
  40. package/dist/core/ingestion/cobol-processor.js +102 -56
  41. package/dist/core/ingestion/community-processor.js +21 -15
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +5 -6
  44. package/dist/core/ingestion/export-detection.js +32 -9
  45. package/dist/core/ingestion/field-extractor.d.ts +1 -1
  46. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
  48. package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
  49. package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
  50. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
  51. package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
  52. package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
  53. package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
  54. package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
  55. package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
  56. package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
  57. package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
  58. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
  59. package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
  60. package/dist/core/ingestion/field-extractors/generic.js +6 -0
  61. package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
  62. package/dist/core/ingestion/field-extractors/typescript.js +1 -1
  63. package/dist/core/ingestion/field-types.d.ts +4 -2
  64. package/dist/core/ingestion/filesystem-walker.js +3 -3
  65. package/dist/core/ingestion/framework-detection.d.ts +1 -1
  66. package/dist/core/ingestion/framework-detection.js +355 -85
  67. package/dist/core/ingestion/heritage-processor.d.ts +24 -0
  68. package/dist/core/ingestion/heritage-processor.js +99 -8
  69. package/dist/core/ingestion/import-processor.js +44 -15
  70. package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
  71. package/dist/core/ingestion/import-resolvers/dart.js +1 -1
  72. package/dist/core/ingestion/import-resolvers/go.js +4 -2
  73. package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
  74. package/dist/core/ingestion/import-resolvers/php.js +4 -4
  75. package/dist/core/ingestion/import-resolvers/python.js +1 -1
  76. package/dist/core/ingestion/import-resolvers/rust.js +9 -3
  77. package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
  78. package/dist/core/ingestion/import-resolvers/standard.js +6 -5
  79. package/dist/core/ingestion/import-resolvers/swift.js +2 -1
  80. package/dist/core/ingestion/import-resolvers/utils.js +26 -7
  81. package/dist/core/ingestion/language-config.js +5 -4
  82. package/dist/core/ingestion/language-provider.d.ts +7 -2
  83. package/dist/core/ingestion/languages/c-cpp.js +106 -21
  84. package/dist/core/ingestion/languages/cobol.js +1 -1
  85. package/dist/core/ingestion/languages/csharp.js +96 -19
  86. package/dist/core/ingestion/languages/dart.js +23 -7
  87. package/dist/core/ingestion/languages/go.js +1 -1
  88. package/dist/core/ingestion/languages/index.d.ts +1 -1
  89. package/dist/core/ingestion/languages/index.js +2 -3
  90. package/dist/core/ingestion/languages/java.js +4 -1
  91. package/dist/core/ingestion/languages/kotlin.js +60 -13
  92. package/dist/core/ingestion/languages/php.js +102 -25
  93. package/dist/core/ingestion/languages/python.js +28 -5
  94. package/dist/core/ingestion/languages/ruby.js +56 -14
  95. package/dist/core/ingestion/languages/rust.js +55 -11
  96. package/dist/core/ingestion/languages/swift.js +112 -27
  97. package/dist/core/ingestion/languages/typescript.js +95 -19
  98. package/dist/core/ingestion/markdown-processor.js +5 -5
  99. package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
  100. package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
  101. package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
  102. package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
  103. package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
  104. package/dist/core/ingestion/method-extractors/generic.js +137 -0
  105. package/dist/core/ingestion/method-types.d.ts +61 -0
  106. package/dist/core/ingestion/method-types.js +2 -0
  107. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  108. package/dist/core/ingestion/mro-processor.js +12 -8
  109. package/dist/core/ingestion/named-binding-processor.js +2 -2
  110. package/dist/core/ingestion/named-bindings/rust.js +3 -1
  111. package/dist/core/ingestion/parsing-processor.js +74 -24
  112. package/dist/core/ingestion/pipeline.d.ts +2 -1
  113. package/dist/core/ingestion/pipeline.js +208 -102
  114. package/dist/core/ingestion/process-processor.js +12 -10
  115. package/dist/core/ingestion/resolution-context.js +3 -3
  116. package/dist/core/ingestion/route-extractors/middleware.js +31 -7
  117. package/dist/core/ingestion/route-extractors/php.js +2 -1
  118. package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
  119. package/dist/core/ingestion/structure-processor.d.ts +1 -1
  120. package/dist/core/ingestion/structure-processor.js +4 -4
  121. package/dist/core/ingestion/symbol-table.d.ts +1 -1
  122. package/dist/core/ingestion/symbol-table.js +22 -6
  123. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.js +1 -1
  125. package/dist/core/ingestion/type-env.d.ts +2 -2
  126. package/dist/core/ingestion/type-env.js +75 -50
  127. package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
  128. package/dist/core/ingestion/type-extractors/csharp.js +24 -14
  129. package/dist/core/ingestion/type-extractors/dart.js +6 -8
  130. package/dist/core/ingestion/type-extractors/go.js +7 -6
  131. package/dist/core/ingestion/type-extractors/jvm.js +10 -21
  132. package/dist/core/ingestion/type-extractors/php.js +26 -13
  133. package/dist/core/ingestion/type-extractors/python.js +11 -15
  134. package/dist/core/ingestion/type-extractors/ruby.js +8 -3
  135. package/dist/core/ingestion/type-extractors/rust.js +6 -8
  136. package/dist/core/ingestion/type-extractors/shared.js +134 -50
  137. package/dist/core/ingestion/type-extractors/swift.js +16 -13
  138. package/dist/core/ingestion/type-extractors/typescript.js +23 -15
  139. package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
  140. package/dist/core/ingestion/utils/ast-helpers.js +72 -35
  141. package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
  142. package/dist/core/ingestion/utils/call-analysis.js +96 -49
  143. package/dist/core/ingestion/utils/event-loop.js +1 -1
  144. package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
  145. package/dist/core/ingestion/workers/parse-worker.js +364 -84
  146. package/dist/core/ingestion/workers/worker-pool.js +5 -10
  147. package/dist/core/lbug/csv-generator.js +54 -15
  148. package/dist/core/lbug/lbug-adapter.d.ts +5 -0
  149. package/dist/core/lbug/lbug-adapter.js +86 -23
  150. package/dist/core/lbug/schema.d.ts +3 -6
  151. package/dist/core/lbug/schema.js +6 -30
  152. package/dist/core/run-analyze.d.ts +49 -0
  153. package/dist/core/run-analyze.js +257 -0
  154. package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
  155. package/dist/core/tree-sitter/parser-loader.js +1 -1
  156. package/dist/core/wiki/cursor-client.js +2 -7
  157. package/dist/core/wiki/generator.js +38 -23
  158. package/dist/core/wiki/graph-queries.js +10 -10
  159. package/dist/core/wiki/html-viewer.js +7 -3
  160. package/dist/core/wiki/llm-client.d.ts +23 -2
  161. package/dist/core/wiki/llm-client.js +96 -26
  162. package/dist/core/wiki/prompts.js +7 -6
  163. package/dist/mcp/core/embedder.js +1 -1
  164. package/dist/mcp/core/lbug-adapter.d.ts +4 -1
  165. package/dist/mcp/core/lbug-adapter.js +17 -7
  166. package/dist/mcp/local/local-backend.js +247 -95
  167. package/dist/mcp/resources.js +14 -6
  168. package/dist/mcp/server.js +13 -5
  169. package/dist/mcp/staleness.js +5 -1
  170. package/dist/mcp/tools.js +100 -23
  171. package/dist/server/analyze-job.d.ts +53 -0
  172. package/dist/server/analyze-job.js +146 -0
  173. package/dist/server/analyze-worker.d.ts +13 -0
  174. package/dist/server/analyze-worker.js +59 -0
  175. package/dist/server/api.js +795 -44
  176. package/dist/server/git-clone.d.ts +25 -0
  177. package/dist/server/git-clone.js +91 -0
  178. package/dist/storage/git.js +1 -3
  179. package/dist/storage/repo-manager.d.ts +5 -2
  180. package/dist/storage/repo-manager.js +4 -4
  181. package/dist/types/pipeline.d.ts +1 -21
  182. package/dist/types/pipeline.js +1 -18
  183. package/hooks/claude/gitnexus-hook.cjs +52 -22
  184. package/package.json +3 -2
  185. package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
  186. package/dist/core/ingestion/utils/language-detection.js +0 -70
@@ -15,27 +15,27 @@
15
15
  export async function resolveLLMConfig(overrides) {
16
16
  const { loadCLIConfig } = await import('../../storage/repo-manager.js');
17
17
  const savedConfig = await loadCLIConfig();
18
- const provider = overrides?.provider || savedConfig.provider || 'openai';
19
- const apiKey = overrides?.apiKey
20
- || process.env.GITNEXUS_API_KEY
21
- || process.env.OPENAI_API_KEY
22
- || savedConfig.apiKey
23
- || '';
24
- // For cursor provider, only use model if explicitly provided (default is 'auto' handled by CLI)
25
- // For openai provider, use model with fallback to default
26
- const model = provider === 'cursor'
27
- ? (overrides?.model || savedConfig.cursorModel || '')
28
- : (overrides?.model || process.env.GITNEXUS_MODEL || savedConfig.model || 'minimax/minimax-m2.5');
18
+ const apiKey = overrides?.apiKey ||
19
+ process.env.GITNEXUS_API_KEY ||
20
+ process.env.OPENAI_API_KEY ||
21
+ savedConfig.apiKey ||
22
+ '';
29
23
  return {
30
24
  apiKey,
31
- baseUrl: overrides?.baseUrl
32
- || process.env.GITNEXUS_LLM_BASE_URL
33
- || savedConfig.baseUrl
34
- || 'https://openrouter.ai/api/v1',
35
- model,
25
+ baseUrl: overrides?.baseUrl ||
26
+ process.env.GITNEXUS_LLM_BASE_URL ||
27
+ savedConfig.baseUrl ||
28
+ 'https://openrouter.ai/api/v1',
29
+ model: overrides?.model ||
30
+ process.env.GITNEXUS_MODEL ||
31
+ (savedConfig.provider === 'cursor' ? savedConfig.cursorModel : undefined) ||
32
+ savedConfig.model ||
33
+ 'minimax/minimax-m2.5',
36
34
  maxTokens: overrides?.maxTokens ?? 16_384,
37
35
  temperature: overrides?.temperature ?? 0,
38
- provider,
36
+ provider: overrides?.provider ?? savedConfig.provider ?? 'openai',
37
+ apiVersion: overrides?.apiVersion || process.env.GITNEXUS_AZURE_API_VERSION || savedConfig.apiVersion,
38
+ isReasoningModel: overrides?.isReasoningModel ?? savedConfig.isReasoningModel,
39
39
  };
40
40
  }
41
41
  /**
@@ -44,6 +44,39 @@ export async function resolveLLMConfig(overrides) {
44
44
  export function estimateTokens(text) {
45
45
  return Math.ceil(text.length / 4);
46
46
  }
47
+ /**
48
+ * Returns true if the given base URL is an Azure OpenAI endpoint.
49
+ * Uses proper hostname matching to avoid spoofed URLs like
50
+ * "https://myresource.openai.azure.com.evil.com/v1".
51
+ */
52
+ export function isAzureProvider(baseUrl) {
53
+ try {
54
+ const { hostname } = new URL(baseUrl);
55
+ return hostname.endsWith('.openai.azure.com') || hostname.endsWith('.services.ai.azure.com');
56
+ }
57
+ catch {
58
+ // If URL is malformed, fall back to substring check
59
+ return baseUrl.includes('.openai.azure.com') || baseUrl.includes('.services.ai.azure.com');
60
+ }
61
+ }
62
+ /**
63
+ * Returns true if the model name matches a known reasoning model pattern,
64
+ * or if the explicit override is true.
65
+ * Pass override=false to force non-reasoning even for o-series names.
66
+ */
67
+ export function isReasoningModel(model, override) {
68
+ if (override !== undefined)
69
+ return override;
70
+ // Match known bare reasoning models (o1, o3) and any o-series with -mini/-preview suffix
71
+ return /^o[1-9]\d*(-mini|-preview)$|^o1$|^o3$/i.test(model);
72
+ }
73
+ /**
74
+ * Build the full chat completions URL, appending ?api-version when provided.
75
+ */
76
+ export function buildRequestUrl(baseUrl, apiVersion) {
77
+ const base = `${baseUrl.replace(/\/+$/, '')}/chat/completions`;
78
+ return apiVersion ? `${base}?api-version=${encodeURIComponent(apiVersion)}` : base;
79
+ }
47
80
  /**
48
81
  * Call an OpenAI-compatible LLM API.
49
82
  * Uses streaming when onChunk callback is provided for real-time progress.
@@ -55,16 +88,35 @@ export async function callLLM(prompt, config, systemPrompt, options) {
55
88
  messages.push({ role: 'system', content: systemPrompt });
56
89
  }
57
90
  messages.push({ role: 'user', content: prompt });
58
- const url = `${config.baseUrl.replace(/\/+$/, '')}/chat/completions`;
91
+ // Detect Azure endpoint (by provider field or URL pattern)
92
+ const azure = config.provider === 'azure' || isAzureProvider(config.baseUrl);
93
+ // Warn when using Azure legacy deployment URL without api-version
94
+ if (azure && !config.apiVersion && config.baseUrl.includes('/deployments/')) {
95
+ console.warn('[gitnexus] Warning: Azure legacy deployment URL detected but no api-version set. Add --api-version 2024-10-21 or use the v1 API format.');
96
+ }
97
+ // Detect reasoning model (o1, o3, o4-mini etc.) or explicit override
98
+ const reasoning = isReasoningModel(config.model, config.isReasoningModel);
99
+ const url = buildRequestUrl(config.baseUrl, azure ? config.apiVersion : undefined);
59
100
  const useStream = !!options?.onChunk;
101
+ // Build request body — reasoning models reject temperature and use max_completion_tokens
60
102
  const body = {
61
103
  model: config.model,
62
104
  messages,
63
- max_tokens: config.maxTokens,
64
- temperature: config.temperature,
65
105
  };
106
+ if (reasoning) {
107
+ body.max_completion_tokens = config.maxTokens;
108
+ // Do NOT include temperature, top_p, presence_penalty, frequency_penalty
109
+ }
110
+ else {
111
+ body.max_tokens = config.maxTokens;
112
+ body.temperature = config.temperature;
113
+ }
66
114
  if (useStream)
67
115
  body.stream = true;
116
+ // Build auth headers — Azure uses api-key header, everyone else uses Authorization: Bearer
117
+ const authHeaders = azure
118
+ ? { 'api-key': config.apiKey }
119
+ : { Authorization: `Bearer ${config.apiKey}` };
68
120
  const MAX_RETRIES = 3;
69
121
  let lastError = null;
70
122
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
@@ -73,16 +125,23 @@ export async function callLLM(prompt, config, systemPrompt, options) {
73
125
  method: 'POST',
74
126
  headers: {
75
127
  'Content-Type': 'application/json',
76
- 'Authorization': `Bearer ${config.apiKey}`,
128
+ ...authHeaders,
77
129
  },
78
130
  body: JSON.stringify(body),
79
131
  });
80
132
  if (!response.ok) {
81
133
  const errorText = await response.text().catch(() => 'unknown error');
134
+ // Azure content filter — surface a clear message instead of a generic API error
135
+ if (azure &&
136
+ response.status === 400 &&
137
+ (errorText.includes('content_filter') ||
138
+ errorText.includes('ResponsibleAIPolicyViolation'))) {
139
+ throw new Error(`Azure content filter blocked this request. The prompt triggered content policy. Details: ${errorText.slice(0, 300)}`);
140
+ }
82
141
  // Rate limit — wait with exponential backoff and retry
83
142
  if (response.status === 429 && attempt < MAX_RETRIES - 1) {
84
143
  const retryAfter = parseInt(response.headers.get('retry-after') || '0', 10);
85
- const delay = retryAfter > 0 ? retryAfter * 1000 : (2 ** attempt) * 3000;
144
+ const delay = retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 3000;
86
145
  await sleep(delay);
87
146
  continue;
88
147
  }
@@ -98,7 +157,7 @@ export async function callLLM(prompt, config, systemPrompt, options) {
98
157
  return await readSSEStream(response.body, options.onChunk);
99
158
  }
100
159
  // Non-streaming path
101
- const json = await response.json();
160
+ const json = (await response.json());
102
161
  const choice = json.choices?.[0];
103
162
  if (!choice?.message?.content) {
104
163
  throw new Error('LLM returned empty response');
@@ -112,7 +171,8 @@ export async function callLLM(prompt, config, systemPrompt, options) {
112
171
  catch (err) {
113
172
  lastError = err;
114
173
  // Network error — retry with backoff
115
- if (attempt < MAX_RETRIES - 1 && (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.message?.includes('fetch'))) {
174
+ if (attempt < MAX_RETRIES - 1 &&
175
+ (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.message?.includes('fetch'))) {
116
176
  await sleep((attempt + 1) * 3000);
117
177
  continue;
118
178
  }
@@ -129,6 +189,7 @@ async function readSSEStream(body, onChunk) {
129
189
  const reader = body.getReader();
130
190
  let content = '';
131
191
  let buffer = '';
192
+ let contentFilterTriggered = false;
132
193
  while (true) {
133
194
  const { done, value } = await reader.read();
134
195
  if (done)
@@ -145,7 +206,13 @@ async function readSSEStream(body, onChunk) {
145
206
  continue;
146
207
  try {
147
208
  const parsed = JSON.parse(data);
148
- const delta = parsed.choices?.[0]?.delta?.content;
209
+ const choice = parsed.choices?.[0];
210
+ // Detect content filter finish reason — skip delta from this chunk
211
+ if (choice?.finish_reason === 'content_filter') {
212
+ contentFilterTriggered = true;
213
+ continue;
214
+ }
215
+ const delta = choice?.delta?.content;
149
216
  if (delta) {
150
217
  content += delta;
151
218
  onChunk(content.length);
@@ -156,11 +223,14 @@ async function readSSEStream(body, onChunk) {
156
223
  }
157
224
  }
158
225
  }
226
+ if (contentFilterTriggered) {
227
+ throw new Error('content filter triggered mid-stream. The generated content was blocked by content policy. Adjust your prompt and retry.');
228
+ }
159
229
  if (!content) {
160
230
  throw new Error('LLM returned empty streaming response');
161
231
  }
162
232
  return { content };
163
233
  }
164
234
  function sleep(ms) {
165
- return new Promise(resolve => setTimeout(resolve, ms));
235
+ return new Promise((resolve) => setTimeout(resolve, ms));
166
236
  }
@@ -121,9 +121,9 @@ export function fillTemplate(template, vars) {
121
121
  */
122
122
  export function formatFileListForGrouping(files) {
123
123
  return files
124
- .map(f => {
124
+ .map((f) => {
125
125
  const exports = f.symbols.length > 0
126
- ? f.symbols.map(s => `${s.name} (${s.type})`).join(', ')
126
+ ? f.symbols.map((s) => `${s.name} (${s.type})`).join(', ')
127
127
  : 'no exports';
128
128
  return `- ${f.filePath}: ${exports}`;
129
129
  })
@@ -143,7 +143,8 @@ export function formatDirectoryTree(filePaths) {
143
143
  const sorted = Array.from(dirs).sort();
144
144
  if (sorted.length === 0)
145
145
  return '(flat structure)';
146
- return sorted.slice(0, 50).join('\n') + (sorted.length > 50 ? `\n... and ${sorted.length - 50} more directories` : '');
146
+ return (sorted.slice(0, 50).join('\n') +
147
+ (sorted.length > 50 ? `\n... and ${sorted.length - 50} more directories` : ''));
147
148
  }
148
149
  /**
149
150
  * Format call edges as readable text.
@@ -153,7 +154,7 @@ export function formatCallEdges(edges) {
153
154
  return 'None';
154
155
  return edges
155
156
  .slice(0, 30)
156
- .map(e => `${e.fromName} (${shortPath(e.fromFile)}) → ${e.toName} (${shortPath(e.toFile)})`)
157
+ .map((e) => `${e.fromName} (${shortPath(e.fromFile)}) → ${e.toName} (${shortPath(e.toFile)})`)
157
158
  .join('\n');
158
159
  }
159
160
  /**
@@ -163,9 +164,9 @@ export function formatProcesses(processes) {
163
164
  if (processes.length === 0)
164
165
  return 'No execution flows detected for this module.';
165
166
  return processes
166
- .map(p => {
167
+ .map((p) => {
167
168
  const stepsText = p.steps
168
- .map(s => ` ${s.step}. ${s.name} (${shortPath(s.filePath)})`)
169
+ .map((s) => ` ${s.step}. ${s.name} (${shortPath(s.filePath)})`)
169
170
  .join('\n');
170
171
  return `**${p.label}** (${p.type}):\n${stepsText}`;
171
172
  })
@@ -5,7 +5,7 @@
5
5
  * For MCP, we only need to compute query embeddings, not batch embed.
6
6
  */
7
7
  import { pipeline, env } from '@huggingface/transformers';
8
- import { isHttpMode, getHttpDimensions, httpEmbedQuery } from '../../core/embeddings/http-client.js';
8
+ import { isHttpMode, getHttpDimensions, httpEmbedQuery, } from '../../core/embeddings/http-client.js';
9
9
  // Model config
10
10
  const MODEL_ID = 'Snowflake/snowflake-arctic-embed-xs';
11
11
  // Module-level state for singleton pattern
@@ -56,7 +56,10 @@ export declare const closeLbug: (repoId?: string) => Promise<void>;
56
56
  * Check if a specific repo's pool is active
57
57
  */
58
58
  export declare const isLbugReady: (repoId: string) => boolean;
59
- /** Regex to detect write operations in user-supplied Cypher queries */
59
+ /** Regex to detect write operations in user-supplied Cypher queries.
60
+ * Note: CALL is NOT blocked — it's used for read-only FTS (CALL QUERY_FTS_INDEX)
61
+ * and vector search (CALL QUERY_VECTOR_INDEX). The database is opened in
62
+ * read-only mode as defense-in-depth against write procedures. */
60
63
  export declare const CYPHER_WRITE_RE: RegExp;
61
64
  /** Check if a Cypher query contains write operations */
62
65
  export declare function isWriteQuery(query: string): boolean;
@@ -222,11 +222,10 @@ async function doInitLbug(repoId, dbPath) {
222
222
  catch (err) {
223
223
  restoreStdout();
224
224
  lastError = err instanceof Error ? err : new Error(String(err));
225
- const isLockError = lastError.message.includes('Could not set lock')
226
- || lastError.message.includes('lock');
225
+ const isLockError = lastError.message.includes('Could not set lock') || lastError.message.includes('lock');
227
226
  if (!isLockError || attempt === LOCK_RETRY_ATTEMPTS)
228
227
  break;
229
- await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_DELAY_MS * attempt));
228
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_DELAY_MS * attempt));
230
229
  }
231
230
  }
232
231
  if (!shared) {
@@ -264,7 +263,15 @@ async function doInitLbug(repoId, dbPath) {
264
263
  // Register pool entry only after all connections are pre-warmed and FTS is
265
264
  // loaded. Concurrent executeQuery calls see either "not initialized"
266
265
  // (and throw cleanly) or a fully ready pool — never a half-built one.
267
- pool.set(repoId, { db, available, checkedOut: 0, waiters: [], lastUsed: Date.now(), dbPath, closed: false });
266
+ pool.set(repoId, {
267
+ db,
268
+ available,
269
+ checkedOut: 0,
270
+ waiters: [],
271
+ lastUsed: Date.now(),
272
+ dbPath,
273
+ closed: false,
274
+ });
268
275
  ensureIdleTimer();
269
276
  }
270
277
  /**
@@ -318,7 +325,7 @@ export async function initLbugWithDb(repoId, existingDb, dbPath) {
318
325
  waiters: [],
319
326
  lastUsed: Date.now(),
320
327
  dbPath,
321
- closed: false
328
+ closed: false,
322
329
  });
323
330
  ensureIdleTimer();
324
331
  }
@@ -468,8 +475,11 @@ export const closeLbug = async (repoId) => {
468
475
  * Check if a specific repo's pool is active
469
476
  */
470
477
  export const isLbugReady = (repoId) => pool.has(repoId);
471
- /** Regex to detect write operations in user-supplied Cypher queries */
472
- export const CYPHER_WRITE_RE = /(?<!:)\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH|FOREACH)\b/i;
478
+ /** Regex to detect write operations in user-supplied Cypher queries.
479
+ * Note: CALL is NOT blocked — it's used for read-only FTS (CALL QUERY_FTS_INDEX)
480
+ * and vector search (CALL QUERY_VECTOR_INDEX). The database is opened in
481
+ * read-only mode as defense-in-depth against write procedures. */
482
+ export const CYPHER_WRITE_RE = /(?<!:)\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH|FOREACH|INSTALL|LOAD)\b/i;
473
483
  /** Check if a Cypher query contains write operations */
474
484
  export function isWriteQuery(query) {
475
485
  return CYPHER_WRITE_RE.test(query);