gitnexus 1.6.7 → 1.6.8-rc.10

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 (82) hide show
  1. package/README.md +20 -0
  2. package/dist/cli/analyze-config.js +39 -0
  3. package/dist/cli/analyze.d.ts +16 -0
  4. package/dist/cli/analyze.js +34 -6
  5. package/dist/cli/clean.d.ts +1 -0
  6. package/dist/cli/clean.js +43 -1
  7. package/dist/cli/eval-server.js +43 -0
  8. package/dist/cli/help-i18n.js +6 -0
  9. package/dist/cli/i18n/en.d.ts +11 -0
  10. package/dist/cli/i18n/en.js +11 -0
  11. package/dist/cli/i18n/resources.d.ts +22 -0
  12. package/dist/cli/i18n/zh-CN.d.ts +11 -0
  13. package/dist/cli/i18n/zh-CN.js +11 -0
  14. package/dist/cli/index.js +9 -0
  15. package/dist/cli/list.js +12 -0
  16. package/dist/cli/optional-grammars.d.ts +6 -8
  17. package/dist/cli/optional-grammars.js +8 -11
  18. package/dist/cli/status.js +26 -5
  19. package/dist/cli/tool.d.ts +5 -0
  20. package/dist/cli/tool.js +5 -0
  21. package/dist/core/embeddings/embedder.js +4 -0
  22. package/dist/core/embeddings/embedding-pipeline.js +27 -16
  23. package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
  24. package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
  25. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  26. package/dist/core/group/extractors/http-patterns/java.js +3 -50
  27. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  28. package/dist/core/group/extractors/include-extractor.js +7 -7
  29. package/dist/core/ingestion/language-provider.d.ts +25 -2
  30. package/dist/core/ingestion/languages/c-cpp.js +11 -4
  31. package/dist/core/ingestion/languages/java.js +3 -0
  32. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  33. package/dist/core/ingestion/languages/kotlin.js +5 -1
  34. package/dist/core/ingestion/parsing-processor.js +21 -0
  35. package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
  36. package/dist/core/ingestion/pipeline.d.ts +9 -0
  37. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
  38. package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
  39. package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
  40. package/dist/core/ingestion/route-extractors/spring.js +136 -0
  41. package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
  42. package/dist/core/ingestion/workers/clone-safety.js +465 -0
  43. package/dist/core/ingestion/workers/parse-worker.d.ts +10 -0
  44. package/dist/core/ingestion/workers/parse-worker.js +27 -52
  45. package/dist/core/ingestion/workers/post-result.d.ts +22 -0
  46. package/dist/core/ingestion/workers/post-result.js +87 -0
  47. package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
  48. package/dist/core/ingestion/workers/result-merge.js +43 -0
  49. package/dist/core/ingestion/workers/worker-pool.d.ts +6 -0
  50. package/dist/core/ingestion/workers/worker-pool.js +28 -14
  51. package/dist/core/lbug/lbug-adapter.d.ts +19 -0
  52. package/dist/core/lbug/lbug-adapter.js +56 -1
  53. package/dist/core/run-analyze.d.ts +45 -0
  54. package/dist/core/run-analyze.js +175 -24
  55. package/dist/core/tree-sitter/parser-loader.js +5 -4
  56. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  57. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  58. package/dist/mcp/core/embedder.js +4 -0
  59. package/dist/mcp/local/local-backend.d.ts +55 -2
  60. package/dist/mcp/local/local-backend.js +363 -25
  61. package/dist/mcp/tools.js +32 -0
  62. package/dist/server/analyze-worker-ipc.d.ts +58 -0
  63. package/dist/server/analyze-worker-ipc.js +16 -0
  64. package/dist/server/analyze-worker.js +7 -1
  65. package/dist/storage/branch-index.d.ts +52 -0
  66. package/dist/storage/branch-index.js +65 -0
  67. package/dist/storage/git.d.ts +11 -0
  68. package/dist/storage/git.js +28 -0
  69. package/dist/storage/parse-cache.js +3 -0
  70. package/dist/storage/repo-manager.d.ts +57 -2
  71. package/dist/storage/repo-manager.js +132 -18
  72. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +24 -4
  73. package/hooks/claude/gitnexus-hook.cjs +18 -3
  74. package/package.json +2 -2
  75. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  76. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  77. package/vendor/tree-sitter-c/package.json +1 -1
  78. package/vendor/tree-sitter-dart/package.json +1 -1
  79. package/vendor/tree-sitter-kotlin/package.json +1 -1
  80. package/vendor/tree-sitter-proto/package.json +1 -1
  81. package/vendor/tree-sitter-swift/package.json +1 -1
  82. package/scripts/materialize-vendor-grammars.cjs +0 -97
package/dist/cli/index.js CHANGED
@@ -34,6 +34,9 @@ program
34
34
  .option('--skip-agents-md', 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md')
35
35
  .option('--default-branch <branch>', 'Default branch used in the generated regression-compare example (base_ref). ' +
36
36
  'Falls back to .gitnexusrc, then auto-detected origin/HEAD, then "main".')
37
+ .option('--branch <name>', 'Index the working tree under a specific branch slot (multi-branch indexing). ' +
38
+ 'Defaults to the checked-out branch; the primary/first-indexed branch keeps the ' +
39
+ 'flat index and others get their own. Distinct from --default-branch (cosmetic base_ref).')
37
40
  .option('--no-stats', 'Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md')
38
41
  .option('--skip-skills', 'Skip installing standard GitNexus skill files under .claude/skills/gitnexus/. ' +
39
42
  'Does not suppress community skills from --skills (those use .claude/skills/generated/). ' +
@@ -89,6 +92,7 @@ program
89
92
  .description('Delete GitNexus index for current repo')
90
93
  .option('-f, --force', 'Skip confirmation prompt')
91
94
  .option('--all', 'Clean all indexed repos')
95
+ .option('--branch <name>', 'Delete only the named branch index (not the primary)')
92
96
  .option('--lbug-sidecars', 'Clean quarantined LadybugDB missing-shadow WAL sidecars')
93
97
  .action(createLazyAction(() => import('./clean.js'), 'cleanCommand'));
94
98
  program
@@ -135,6 +139,7 @@ program
135
139
  .command('query <search_query>')
136
140
  .description('Search the knowledge graph for execution flows related to a concept')
137
141
  .option('-r, --repo <name>', 'Target repository (omit if only one indexed)')
142
+ .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
138
143
  .option('-c, --context <text>', 'Task context to improve ranking')
139
144
  .option('-g, --goal <text>', 'What you want to find')
140
145
  .option('-l, --limit <n>', 'Max processes to return (default: 5)')
@@ -144,6 +149,7 @@ program
144
149
  .command('context [name]')
145
150
  .description('360-degree view of a code symbol: callers, callees, processes')
146
151
  .option('-r, --repo <name>', 'Target repository')
152
+ .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
147
153
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
148
154
  .option('-f, --file <path>', 'File path to disambiguate common names')
149
155
  .option('--content', 'Include full symbol source code')
@@ -153,6 +159,7 @@ program
153
159
  .description('Blast radius analysis: what breaks if you change a symbol')
154
160
  .option('-d, --direction <dir>', 'upstream (dependants) or downstream (dependencies)', 'upstream')
155
161
  .option('-r, --repo <name>', 'Target repository')
162
+ .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
156
163
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
157
164
  .option('-f, --file <path>', 'File path to disambiguate common names')
158
165
  .option('--kind <kind>', 'Kind filter to disambiguate common names (e.g. Function, Class, Method)')
@@ -166,6 +173,7 @@ program
166
173
  .command('cypher <query>')
167
174
  .description('Execute raw Cypher query against the knowledge graph')
168
175
  .option('-r, --repo <name>', 'Target repository')
176
+ .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
169
177
  .action(createLbugLazyAction(() => import('./tool.js'), 'cypherCommand'));
170
178
  program
171
179
  .command('detect-changes')
@@ -174,6 +182,7 @@ program
174
182
  .option('-s, --scope <scope>', 'What to analyze: unstaged, staged, all, or compare', 'unstaged')
175
183
  .option('-b, --base-ref <ref>', 'Branch/commit for compare scope (e.g. main)')
176
184
  .option('-r, --repo <name>', 'Target repository')
185
+ .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
177
186
  .action(createLbugLazyAction(() => import('./tool.js'), 'detectChangesCommand'));
178
187
  // ─── Eval Server (persistent daemon for SWE-bench) ─────────────────
179
188
  program
package/dist/cli/list.js CHANGED
@@ -31,6 +31,8 @@ export const listCommand = async () => {
31
31
  console.log(` ${t('common.path')}: ${entry.path}`);
32
32
  console.log(` ${t('list.indexed')}: ${indexedDate}`);
33
33
  console.log(` ${t('list.commit')}: ${commitShort}`);
34
+ if (entry.branch)
35
+ console.log(` ${t('list.branch')}: ${entry.branch}`);
34
36
  console.log(` ${t('list.stats')}: ${t('list.statsValue', {
35
37
  files: stats.files ?? 0,
36
38
  symbols: stats.nodes ?? 0,
@@ -40,6 +42,16 @@ export const listCommand = async () => {
40
42
  console.log(` ${t('list.clusters')}: ${stats.communities}`);
41
43
  if (stats.processes)
42
44
  console.log(` ${t('list.processes')}: ${stats.processes}`);
45
+ // Per-branch indexes (#2106). Only rendered when extra branches were
46
+ // indexed for this path, so single-branch output is unchanged.
47
+ if (entry.branches && entry.branches.length > 0) {
48
+ console.log(` ${t('list.branchIndexes')}:`);
49
+ for (const b of entry.branches) {
50
+ const bCommit = b.lastCommit?.slice(0, 7) || t('list.unknown');
51
+ const bIndexed = new Date(b.indexedAt).toLocaleString();
52
+ console.log(` ${t('list.branchLine', { branch: b.branch, commit: bCommit, indexed: bIndexed })}`);
53
+ }
54
+ }
43
55
  console.log('');
44
56
  }
45
57
  };
@@ -1,15 +1,13 @@
1
1
  /**
2
2
  * Optional grammar availability check.
3
3
  *
4
- * tree-sitter-dart, tree-sitter-proto, and tree-sitter-swift are vendored
5
- * under vendor/ and materialized into node_modules/ at postinstall. Dart
6
- * and Proto are built from source with node-gyp; Swift ships platform
7
- * prebuilds activated via node-gyp-build. tree-sitter-kotlin is a declared
8
- * optionalDependency (not vendored). All can be skipped via
4
+ * tree-sitter-dart, -proto, -swift, and -kotlin are vendored under vendor/ and
5
+ * loaded from there by absolute path (NEVER copied into node_modules see
6
+ * core/tree-sitter/vendored-grammars.ts / #2111). Each ships committed platform
7
+ * prebuilds activated via node-gyp-build. All can be skipped via
9
8
  * GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
10
- * soft-fail when the toolchain is missing (Dart/Proto), when no prebuild
11
- * matches the host platform (Swift), or when the optional install was
12
- * skipped or its native build failed (Kotlin).
9
+ * soft-fail when no prebuild matches the host platform (and a source build was
10
+ * unavailable / not attempted).
13
11
  *
14
12
  * Either path produces the same observable: the .node binding is absent
15
13
  * at runtime. This helper detects that condition and surfaces a single
@@ -1,26 +1,23 @@
1
1
  /**
2
2
  * Optional grammar availability check.
3
3
  *
4
- * tree-sitter-dart, tree-sitter-proto, and tree-sitter-swift are vendored
5
- * under vendor/ and materialized into node_modules/ at postinstall. Dart
6
- * and Proto are built from source with node-gyp; Swift ships platform
7
- * prebuilds activated via node-gyp-build. tree-sitter-kotlin is a declared
8
- * optionalDependency (not vendored). All can be skipped via
4
+ * tree-sitter-dart, -proto, -swift, and -kotlin are vendored under vendor/ and
5
+ * loaded from there by absolute path (NEVER copied into node_modules see
6
+ * core/tree-sitter/vendored-grammars.ts / #2111). Each ships committed platform
7
+ * prebuilds activated via node-gyp-build. All can be skipped via
9
8
  * GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
10
- * soft-fail when the toolchain is missing (Dart/Proto), when no prebuild
11
- * matches the host platform (Swift), or when the optional install was
12
- * skipped or its native build failed (Kotlin).
9
+ * soft-fail when no prebuild matches the host platform (and a source build was
10
+ * unavailable / not attempted).
13
11
  *
14
12
  * Either path produces the same observable: the .node binding is absent
15
13
  * at runtime. This helper detects that condition and surfaces a single
16
14
  * stderr line per missing grammar so users learn why .dart/.proto/.swift/.kt
17
15
  * support is unavailable instead of silently getting a degraded index.
18
16
  */
19
- import { createRequire } from 'module';
20
17
  import { SupportedLanguages } from '../_shared/index.js';
21
18
  import { isGrammarRuntimeSkipped } from '../core/tree-sitter/parser-loader.js';
19
+ import { requireVendoredGrammar } from '../core/tree-sitter/vendored-grammars.js';
22
20
  import { cliWarn } from './cli-message.js';
23
- const _require = createRequire(import.meta.url);
24
21
  const OPTIONAL_GRAMMARS = [
25
22
  {
26
23
  name: 'tree-sitter-dart',
@@ -76,7 +73,7 @@ export function detectMissingOptionalGrammars() {
76
73
  continue;
77
74
  }
78
75
  try {
79
- _require(g.pkg);
76
+ requireVendoredGrammar(g.pkg);
80
77
  }
81
78
  catch (err) {
82
79
  const code = err?.code;
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * Shows the indexing status of the current repository.
5
5
  */
6
- import { findRepo, getStoragePaths, hasKuzuIndex } from '../storage/repo-manager.js';
7
- import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
6
+ import path from 'path';
7
+ import { findRepo, getStoragePaths, loadMeta, hasKuzuIndex } from '../storage/repo-manager.js';
8
+ import { getCurrentCommit, getCurrentBranch, isGitRepo, getGitRoot } from '../storage/git.js';
8
9
  import { t } from './i18n/index.js';
9
10
  export const statusCommand = async () => {
10
11
  const cwd = process.cwd();
@@ -28,10 +29,30 @@ export const statusCommand = async () => {
28
29
  return;
29
30
  }
30
31
  const currentCommit = getCurrentCommit(repo.repoPath);
31
- const isUpToDate = currentCommit === repo.meta.lastCommit;
32
+ const currentBranch = getCurrentBranch(repo.repoPath);
33
+ // Pick the index matching the checked-out branch (#2106). The flat index
34
+ // belongs to the primary branch (repo.meta.branch); when the current branch
35
+ // differs and has its own index, report that one. Legacy/no-branch metas and
36
+ // detached HEAD fall through to the flat index (unchanged behavior).
37
+ let activeMeta = repo.meta;
38
+ let currentBranchIndexed = true;
39
+ if (currentBranch && repo.meta.branch && currentBranch !== repo.meta.branch) {
40
+ const { metaPath } = getStoragePaths(repo.repoPath, currentBranch);
41
+ const branchMeta = await loadMeta(path.dirname(metaPath));
42
+ if (branchMeta)
43
+ activeMeta = branchMeta;
44
+ else
45
+ currentBranchIndexed = false;
46
+ }
32
47
  console.log(`${t('status.repository')}: ${repo.repoPath}`);
33
- console.log(`${t('status.indexed')}: ${new Date(repo.meta.indexedAt).toLocaleString()}`);
34
- console.log(`${t('status.indexedCommit')}: ${repo.meta.lastCommit?.slice(0, 7)}`);
48
+ console.log(`${t('status.branch')}: ${currentBranch ?? t('status.detached')}`);
49
+ if (!currentBranchIndexed) {
50
+ console.log(`${t('status.status')}: ${t('status.branchNotIndexed', { primary: repo.meta.branch ?? '' })}`);
51
+ return;
52
+ }
53
+ const isUpToDate = currentCommit === activeMeta.lastCommit;
54
+ console.log(`${t('status.indexed')}: ${new Date(activeMeta.indexedAt).toLocaleString()}`);
55
+ console.log(`${t('status.indexedCommit')}: ${activeMeta.lastCommit?.slice(0, 7)}`);
35
56
  console.log(`${t('status.currentCommit')}: ${currentCommit?.slice(0, 7)}`);
36
57
  console.log(`${t('status.status')}: ${isUpToDate ? t('status.upToDate') : t('status.stale')}`);
37
58
  };
@@ -16,6 +16,7 @@
16
16
  */
17
17
  export declare function queryCommand(queryText: string, options?: {
18
18
  repo?: string;
19
+ branch?: string;
19
20
  context?: string;
20
21
  goal?: string;
21
22
  limit?: string;
@@ -23,6 +24,7 @@ export declare function queryCommand(queryText: string, options?: {
23
24
  }): Promise<void>;
24
25
  export declare function contextCommand(name: string, options?: {
25
26
  repo?: string;
27
+ branch?: string;
26
28
  file?: string;
27
29
  uid?: string;
28
30
  content?: boolean;
@@ -30,6 +32,7 @@ export declare function contextCommand(name: string, options?: {
30
32
  export declare function impactCommand(target?: string, options?: {
31
33
  direction?: string;
32
34
  repo?: string;
35
+ branch?: string;
33
36
  uid?: string;
34
37
  file?: string;
35
38
  kind?: string;
@@ -41,9 +44,11 @@ export declare function impactCommand(target?: string, options?: {
41
44
  }): Promise<void>;
42
45
  export declare function cypherCommand(query: string, options?: {
43
46
  repo?: string;
47
+ branch?: string;
44
48
  }): Promise<void>;
45
49
  export declare function detectChangesCommand(options?: {
46
50
  scope?: string;
47
51
  baseRef?: string;
48
52
  repo?: string;
53
+ branch?: string;
49
54
  }): Promise<void>;
package/dist/cli/tool.js CHANGED
@@ -68,6 +68,7 @@ export async function queryCommand(queryText, options) {
68
68
  limit: options?.limit ? parseInt(options.limit) : undefined,
69
69
  include_content: options?.content ?? false,
70
70
  repo: options?.repo,
71
+ branch: options?.branch,
71
72
  });
72
73
  output(result);
73
74
  }
@@ -88,6 +89,7 @@ export async function contextCommand(name, options) {
88
89
  file_path: options?.file,
89
90
  include_content: options?.content ?? false,
90
91
  repo: options?.repo,
92
+ branch: options?.branch,
91
93
  });
92
94
  output(result);
93
95
  }
@@ -126,6 +128,7 @@ export async function impactCommand(target, options) {
126
128
  maxDepth: options?.depth ? parseInt(options.depth, 10) : undefined,
127
129
  includeTests: options?.includeTests ?? false,
128
130
  repo: options?.repo,
131
+ branch: options?.branch,
129
132
  limit: parsedLimit,
130
133
  offset: parsedOffset,
131
134
  summaryOnly: options?.summaryOnly ?? undefined,
@@ -153,6 +156,7 @@ export async function cypherCommand(query, options) {
153
156
  const result = await backend.callTool('cypher', {
154
157
  query,
155
158
  repo: options?.repo,
159
+ branch: options?.branch,
156
160
  });
157
161
  output(result);
158
162
  }
@@ -162,6 +166,7 @@ export async function detectChangesCommand(options) {
162
166
  scope: options?.scope || 'unstaged',
163
167
  base_ref: options?.baseRef,
164
168
  repo: options?.repo,
169
+ branch: options?.branch,
165
170
  });
166
171
  output(formatDetectChangesResult(result));
167
172
  }
@@ -21,6 +21,7 @@ import { isHttpMode, getHttpDimensions, httpEmbed } from './http-client.js';
21
21
  import { resolveEmbeddingConfig } from './config.js';
22
22
  import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry } from './hf-env.js';
23
23
  import { getLocalEmbeddingRuntimeBlocker } from './runtime-support.js';
24
+ import { ensureOnnxRuntimeCommonResolvable } from './onnxruntime-common-resolver.js';
24
25
  import { logger } from '../logger.js';
25
26
  /**
26
27
  * Check whether the onnxruntime-node package that @huggingface/transformers
@@ -147,6 +148,9 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
147
148
  try {
148
149
  // Lazy-load transformers.js only after the runtime guard has passed, so
149
150
  // unsupported platforms never reach the native ONNX import (#1515).
151
+ // Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
152
+ // import is unresolvable; register the fallback resolver first (#307).
153
+ ensureOnnxRuntimeCommonResolvable();
150
154
  const { pipeline, env } = await import('@huggingface/transformers');
151
155
  // Configure transformers.js environment
152
156
  env.allowLocalModels = false;
@@ -16,8 +16,8 @@ import { extractStructuralNames } from './structural-extractor.js';
16
16
  import { EMBEDDABLE_LABELS, isShortLabel, LABEL_METHOD, LABELS_WITH_EXPORTED, STRUCTURAL_LABELS, collectBestChunks, } from './types.js';
17
17
  import { resolveEmbeddingConfig } from './config.js';
18
18
  import { rankExactEmbeddingRows } from './exact-search.js';
19
- import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME, CREATE_VECTOR_INDEX_QUERY, STALE_HASH_SENTINEL, } from '../lbug/schema.js';
20
- import { loadVectorExtension } from '../lbug/lbug-adapter.js';
19
+ import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME, STALE_HASH_SENTINEL } from '../lbug/schema.js';
20
+ import { loadVectorExtension, createVectorIndex } from '../lbug/lbug-adapter.js';
21
21
  import { getExactScanLimit } from '../platform/capabilities.js';
22
22
  import { logger } from '../logger.js';
23
23
  const isDev = process.env.NODE_ENV === 'development';
@@ -153,24 +153,35 @@ export const batchInsertEmbeddings = async (executeWithReusedStatement, updates)
153
153
  await executeWithReusedStatement(cypher, paramsList);
154
154
  };
155
155
  /**
156
- * Create the vector index for semantic search
157
-
158
- * Now indexes the separate CodeEmbedding table.
159
- * Delegates extension loading to lbug-adapter's loadVectorExtension(),
160
- * which owns the VECTOR extension lifecycle and state tracking.
161
-
156
+ * Create the vector index for semantic search (indexes the CodeEmbedding table).
157
+ *
158
+ * Keeps the embedding-specific extension-install policy gate here
159
+ * (ensureVectorExtensionAvailable resolveEmbeddingInstallPolicy, default
160
+ * `auto` for the analyze write path), then delegates the actual
161
+ * `CALL CREATE_VECTOR_INDEX(...)` to the adapter, which runs it through the
162
+ * unprepared `conn.query()` path. It must NOT go through the injected
163
+ * `executeQuery` (prepared `conn.prepare()`): LadybugDB cannot prepare that
164
+ * procedure and fails with "We do not support prepare multiple statements" —
165
+ * the silent degrade in #2114.
162
166
  */
163
- const createVectorIndex = async (executeQuery) => {
167
+ const buildVectorIndex = async () => {
168
+ // This pre-check applies the embedding-specific install policy
169
+ // (resolveEmbeddingInstallPolicy, default `auto` for analyze) before reaching
170
+ // the adapter. The adapter's createVectorIndex() calls loadVectorExtension()
171
+ // again, but that's a no-op here: once this gate loads VECTOR the module-level
172
+ // `vectorExtensionLoaded` flag is set, so the adapter's second call
173
+ // short-circuits without re-resolving the policy — no double install.
164
174
  if (!(await ensureVectorExtensionAvailable()))
165
175
  return false;
166
176
  try {
167
- await executeQuery(CREATE_VECTOR_INDEX_QUERY);
168
- return true;
177
+ return await createVectorIndex();
169
178
  }
170
179
  catch (error) {
171
- if (isDev) {
172
- logger.warn({ error }, 'Vector index creation warning:');
173
- }
180
+ // Surface this even outside dev: it silently downgrades a user-requested
181
+ // feature (semantic search) to exact scan. Log under `err` so pino's
182
+ // standard serializer captures the message/stack — logging under `error`
183
+ // serialized an Error to `{}` (the empty `{"error":{}}` reported in #2114).
184
+ logger.warn({ err: error }, 'Vector index creation failed; semantic search will use exact-scan fallback');
174
185
  return false;
175
186
  }
176
187
  };
@@ -283,7 +294,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
283
294
  // Ensure the vector index exists even when no new nodes need embedding.
284
295
  // A prior crash or first-time incremental run may have left CodeEmbedding
285
296
  // rows without ever reaching index creation.
286
- const vectorIndexReady = await createVectorIndex(executeQuery);
297
+ const vectorIndexReady = await buildVectorIndex();
287
298
  onProgress({
288
299
  phase: 'ready',
289
300
  percent: 100,
@@ -403,7 +414,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
403
414
  if (isDev) {
404
415
  logger.info('📇 Creating vector index...');
405
416
  }
406
- const vectorIndexReady = await createVectorIndex(executeQuery);
417
+ const vectorIndexReady = await buildVectorIndex();
407
418
  onProgress({
408
419
  phase: 'ready',
409
420
  percent: 100,
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Idempotently install the onnxruntime-common resolution fallback. Call once
3
+ * immediately before the dynamic `import('@huggingface/transformers')` on the
4
+ * local-embedding path.
5
+ */
6
+ export declare const ensureOnnxRuntimeCommonResolvable: () => void;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Make `@huggingface/transformers`' phantom `onnxruntime-common` import
3
+ * resolvable under strict package-manager layouts (#307, #2069).
4
+ *
5
+ * ## Why
6
+ * transformers' shipped `dist/transformers.node.mjs` does a bare
7
+ * `import 'onnxruntime-common'`, but transformers' `package.json` never declares
8
+ * onnxruntime-common (it lists onnxruntime-node / onnxruntime-web / sharp). With
9
+ * npm's flat `node_modules` — or pnpm with hoisting — the package is hoisted to
10
+ * a directory on transformers' resolution path and the import resolves by
11
+ * accident. Under pnpm's isolated store (and therefore `pnpm dlx` / `pnpx`), a
12
+ * package only sees its *declared* deps, so the import dies with
13
+ * `ERR_MODULE_NOT_FOUND` before `analyze --embeddings` can run.
14
+ *
15
+ * Declaring onnxruntime-common in gitnexus' own dependencies (#2074) does NOT
16
+ * fix this under pnpm: Node resolves the bare specifier from *transformers'*
17
+ * module scope, not ours, and overrides/resolutions can only re-version an
18
+ * existing edge, never add the missing one.
19
+ *
20
+ * ## What this does
21
+ * Install a synchronous, in-thread ESM resolution hook (`module.registerHooks`,
22
+ * Node >= 22.15) that redirects `onnxruntime-common` to a copy gitnexus can
23
+ * resolve — but only when the default resolver fails. The redirect target is
24
+ * preferentially the `onnxruntime-common` that `onnxruntime-node` (the native
25
+ * binding transformers actually loads) itself depends on, so the redirected copy
26
+ * is version-matched to that binding even under `pnpm dlx` — where gitnexus'
27
+ * npm-style `overrides` block does NOT apply, because it is honoured only from a
28
+ * root manifest and gitnexus is a transitive dependency there. It falls back to
29
+ * gitnexus' own direct `onnxruntime-common` dependency when that chain can't be
30
+ * walked. onnxruntime-common is a stable, pure-JS package whose `Tensor` surface
31
+ * is unchanged across 1.24–1.26, so either target is API-compatible. On working
32
+ * layouts the default resolver succeeds first and the hook never fires, so
33
+ * behaviour is unchanged.
34
+ *
35
+ * `registerHooks` (synchronous, in-thread) is preferred over the older
36
+ * `module.register` (async, off-thread, now deprecated — DEP0205, removed in
37
+ * Node 26): the redirect is a one-line conditional that needs no worker thread,
38
+ * no separate hook module, and no `data` marshalling.
39
+ *
40
+ * ## Safety
41
+ * Best-effort and idempotent. The hook is installed lazily, only on the
42
+ * local-embedding code path (after parsing), so it is never registered during
43
+ * analysis, in the parse workers, or in HTTP embedding mode. Once installed it
44
+ * is process-global: its resolve closure runs for every subsequent module
45
+ * resolution, but it passes all of them through untouched and only substitutes a
46
+ * result for the exact `onnxruntime-common` specifier when that specifier is
47
+ * genuinely absent — so it cannot mask an unrelated resolution error, and the
48
+ * per-resolution cost is a single string comparison.
49
+ *
50
+ * `module.registerHooks` is marked `@experimental` and requires Node >= 22.15
51
+ * (the gitnexus engines floor is >= 22.0.0). On older runtimes it is absent and
52
+ * this is a graceful no-op: embeddings then resolve onnxruntime-common exactly
53
+ * as before — fine on hoisted layouts. Any failure during installation is
54
+ * swallowed.
55
+ */
56
+ import { registerHooks, createRequire } from 'node:module';
57
+ import { pathToFileURL } from 'node:url';
58
+ import { logger } from '../logger.js';
59
+ let attempted = false;
60
+ /**
61
+ * Compute the file: URL the hook redirects `onnxruntime-common` to.
62
+ *
63
+ * Prefer the copy `onnxruntime-node` (the native binding transformers loads)
64
+ * depends on, so the redirected module is version-matched to the binding even
65
+ * under `pnpm dlx`, where transformers keeps its own pinned onnxruntime-node.
66
+ * The walk resolves transformers' MAIN entry — NOT `@huggingface/transformers/
67
+ * package.json`, which transformers' `exports` map blocks
68
+ * (`ERR_PACKAGE_PATH_NOT_EXPORTED`) — then onnxruntime-node, then its
69
+ * onnxruntime-common. Falls back to gitnexus' own direct dependency (always
70
+ * resolvable from our scope) when any step fails.
71
+ */
72
+ const resolveOnnxRuntimeCommonUrl = () => {
73
+ const require = createRequire(import.meta.url);
74
+ try {
75
+ const transformersMain = require.resolve('@huggingface/transformers');
76
+ const ortNodePkg = createRequire(transformersMain).resolve('onnxruntime-node/package.json');
77
+ const common = createRequire(ortNodePkg).resolve('onnxruntime-common');
78
+ return pathToFileURL(common).href;
79
+ }
80
+ catch {
81
+ return pathToFileURL(require.resolve('onnxruntime-common')).href;
82
+ }
83
+ };
84
+ /**
85
+ * Idempotently install the onnxruntime-common resolution fallback. Call once
86
+ * immediately before the dynamic `import('@huggingface/transformers')` on the
87
+ * local-embedding path.
88
+ */
89
+ export const ensureOnnxRuntimeCommonResolvable = () => {
90
+ if (attempted)
91
+ return;
92
+ // Mark attempted up-front: a failed attempt must not retry on every
93
+ // initEmbedder() call, and the hook is process-global — once is enough.
94
+ attempted = true;
95
+ try {
96
+ // Node < 22.15 (the gitnexus engines floor is >= 22.0.0): no synchronous
97
+ // hooks API. Degrade gracefully — the import still works on hoisted layouts.
98
+ if (typeof registerHooks !== 'function')
99
+ return;
100
+ const redirectUrl = resolveOnnxRuntimeCommonUrl();
101
+ registerHooks({
102
+ resolve(specifier, context, nextResolve) {
103
+ if (specifier !== 'onnxruntime-common')
104
+ return nextResolve(specifier, context);
105
+ // Honour a real, package-manager-provided copy when one is on the path
106
+ // (npm / hoisted pnpm); only substitute ours when the specifier is
107
+ // genuinely absent.
108
+ try {
109
+ return nextResolve(specifier, context);
110
+ }
111
+ catch (err) {
112
+ // The phantom import surfaces as ERR_MODULE_NOT_FOUND (or, for a
113
+ // present-but-exports-broken copy, ERR_PACKAGE_PATH_NOT_EXPORTED).
114
+ // Rethrow anything else so a genuinely broken install is not masked.
115
+ const code = err?.code;
116
+ if (code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
117
+ return { url: redirectUrl, shortCircuit: true };
118
+ }
119
+ throw err;
120
+ }
121
+ },
122
+ });
123
+ logger.debug({ redirectUrl }, 'Installed onnxruntime-common resolution fallback (#307)');
124
+ }
125
+ catch (err) {
126
+ // Never block embeddings on the fallback. On layouts where the package
127
+ // manager already resolves onnxruntime-common this is unnecessary anyway.
128
+ logger.debug({ err: err instanceof Error ? err.message : String(err) }, 'onnxruntime-common resolution fallback not installed');
129
+ }
130
+ };
@@ -1,23 +1,27 @@
1
1
  import { createRequire } from 'node:module';
2
+ import { requireVendoredGrammar } from '../../../tree-sitter/vendored-grammars.js';
2
3
  import { compilePatterns, runCompiledPatterns, } from '../tree-sitter-scanner.js';
3
4
  /**
4
5
  * Protobuf (.proto) tree-sitter plugin for gRPC contract extraction.
5
6
  *
6
- * Uses `tree-sitter-proto` (coder3101/tree-sitter-proto) as an
7
- * optionalDependency if the grammar is not installed (e.g. native
8
- * compilation failed on an unusual platform), the plugin exports
9
- * `null` and the orchestrator falls back to the existing manual
10
- * string-sanitizing parser.
7
+ * Uses `tree-sitter-proto` (coder3101/tree-sitter-proto), loaded from
8
+ * `vendor/` by absolute path (NEVER copied into node_modules see
9
+ * vendored-grammars.ts / #2111). If the grammar's binding cannot be loaded
10
+ * (e.g. no prebuild for an unusual platform), the plugin exports `null` and the
11
+ * orchestrator falls back to the existing manual string-sanitizing parser.
11
12
  *
12
13
  * The grammar is vendored in `vendor/tree-sitter-proto/` with
13
14
  * parser.c regenerated against tree-sitter-cli 0.24 (ABI version 14)
14
15
  * so it is compatible with the project's tree-sitter 0.21.1 runtime
15
16
  * (which loads ABI 13–14).
16
17
  */
18
+ // Only for `tree-sitter` (a real npm dependency) in the smoke-test below;
19
+ // the vendored grammar goes through requireVendoredGrammar (never a bare
20
+ // `_require('tree-sitter-proto')`, which would force a node_modules copy — #2111).
17
21
  const _require = createRequire(import.meta.url);
18
22
  let ProtoGrammar = null;
19
23
  try {
20
- ProtoGrammar = _require('tree-sitter-proto');
24
+ ProtoGrammar = requireVendoredGrammar('tree-sitter-proto');
21
25
  }
22
26
  catch {
23
27
  // Grammar not installed — PROTO_GRPC_PLUGIN will be null.
@@ -1,31 +1,6 @@
1
1
  import Java from 'tree-sitter-java';
2
2
  import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
- /**
4
- * Java HTTP plugin. Handles:
5
- * - Spring `@RequestMapping` class prefixes + `@(Get|Post|...)Mapping` method annotations
6
- * - Spring `RestTemplate.getForObject/...`, `exchange(...)`
7
- * - Spring `WebClient.method(HttpMethod.X, ...)`, `WebClient.get().uri(...)`
8
- * - OkHttp `new Request.Builder().url("...")`
9
- * - OpenFeign interfaces with Spring MVC method annotations or
10
- * native `@RequestLine("METHOD /path")` annotations
11
- * - Java / Apache HttpClient literal request construction
12
- *
13
- * Every route-defining annotation (class/interface `@RequestMapping`
14
- * prefixes, `@FeignClient(path)` prefixes, `@(Get|...)Mapping` method
15
- * routes and native `@RequestLine`s) is matched by a single consolidated
16
- * query (`JAVA_ROUTE_ANNOTATION_PATTERNS`) in one pass via
17
- * `scanRouteAnnotations`. The `scan` function then walks up from each
18
- * matched method to its enclosing class/interface to combine the prefix
19
- * with the method path. Call-site consumers (RestTemplate, WebClient,
20
- * OkHttp, Java/Apache HttpClient) keep their own focused queries.
21
- */
22
- const METHOD_ANNOTATION_TO_HTTP = {
23
- GetMapping: 'GET',
24
- PostMapping: 'POST',
25
- PutMapping: 'PUT',
26
- DeleteMapping: 'DELETE',
27
- PatchMapping: 'PATCH',
28
- };
3
+ import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, } from '../../../ingestion/route-extractors/spring-shared.js';
29
4
  // ─── Route-defining annotations (one generic query, one pass) ─────────
30
5
  // Every Java route-mapper annotation shares one shape: an annotation carrying a
31
6
  // single string argument — positional `"..."` or named `key = "..."` — on a
@@ -300,19 +275,9 @@ const APACHE_HTTP_CLIENT_PATTERNS = compilePatterns({
300
275
  ],
301
276
  });
302
277
  /**
303
- * Find the nearest enclosing class/interface declaration ancestor for
304
- * a node, or null if the node is top-level. Tree-sitter's
305
- * SyntaxNode.parent walks one level at a time.
278
+ * Find the nearest enclosing interface declaration ancestor for a node, or
279
+ * null if the node is top-level.
306
280
  */
307
- function findEnclosingClass(node) {
308
- let cur = node.parent;
309
- while (cur) {
310
- if (cur.type === 'class_declaration')
311
- return cur;
312
- cur = cur.parent;
313
- }
314
- return null;
315
- }
316
281
  function findEnclosingInterface(node) {
317
282
  let cur = node.parent;
318
283
  while (cur) {
@@ -369,18 +334,6 @@ function hasAnnotation(node, names) {
369
334
  }
370
335
  return false;
371
336
  }
372
- /**
373
- * A named annotation argument contributes a route only when its member key is
374
- * `path` or `value`; a positional argument (no key node) always qualifies.
375
- * This is the JS-side replacement for the in-query `^(path|value)$` filter and
376
- * drops Spring's non-route string attributes (`produces`, `consumes`,
377
- * `headers`, `name`, `params`) that would otherwise be mis-read as routes.
378
- */
379
- function isRouteMemberKey(keyNode) {
380
- if (!keyNode)
381
- return true;
382
- return keyNode.text === 'path' || keyNode.text === 'value';
383
- }
384
337
  /**
385
338
  * Resolve every Java route-defining annotation in a single tree-sitter pass.
386
339
  *
@@ -1,4 +1,4 @@
1
- import { createRequire } from 'node:module';
1
+ import { requireVendoredGrammar } from '../../../tree-sitter/vendored-grammars.js';
2
2
  import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
3
  /**
4
4
  * Kotlin HTTP plugin (Spring providers + consumers).
@@ -52,15 +52,15 @@ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-s
52
52
  * value_argument
53
53
  * string_literal ← the path
54
54
  *
55
- * tree-sitter-kotlin is an optional npm dependency when its native
56
- * binding is unavailable the plugin gracefully exports `null` and
57
- * `http-patterns/index.ts` skips registration for `.kt`/`.kts` files.
55
+ * tree-sitter-kotlin is a vendored grammar loaded from `vendor/` by absolute
56
+ * path (NEVER copied into node_modules see vendored-grammars.ts / #2111) —
57
+ * when its native binding is unavailable the plugin gracefully exports `null`
58
+ * and `http-patterns/index.ts` skips registration for `.kt`/`.kts` files.
58
59
  */
59
- const _require = createRequire(import.meta.url);
60
- /** Loaded lazily; null when the grammar binding isn't installed. */
60
+ /** Loaded lazily; null when the grammar binding isn't available. */
61
61
  let Kotlin = null;
62
62
  try {
63
- Kotlin = _require('tree-sitter-kotlin');
63
+ Kotlin = requireVendoredGrammar('tree-sitter-kotlin');
64
64
  }
65
65
  catch {
66
66
  Kotlin = null;