gitnexus 1.6.9-rc.31 → 1.6.9-rc.33

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.
@@ -234,5 +234,5 @@ export declare const en: {
234
234
  readonly 'help.option.group.contracts.type': "Filter by contract type";
235
235
  readonly 'help.option.group.contracts.repo': "Filter by repo";
236
236
  readonly 'help.option.group.contracts.unmatched': "Show only unmatched contracts";
237
- readonly 'help.analyze.environment': "\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker idle timeout in milliseconds. Default 30000.\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL auto-checkpoint threshold in bytes (default 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker job byte budget. Default 8388608.\n GITNEXUS_WORKER_POOL_SIZE=N Parse worker count override. Default cores-1 capped at 16.\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N Concurrent in-flight parse chunks. Default 2.\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N Max replacement spawns per slot before drop. Default 3.\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N Total retry wall-time per job. Default 5x sub-batch timeout.\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N Per-slot deaths to trip circuit breaker. Default max(3, poolSize).\n GITNEXUS_EMBEDDING_THREADS=N Limit local ONNX CPU threads for --embeddings.\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N Max embedding chunks for exact-scan fallback. Default 10000.\n\nFlags override the corresponding env vars when both are provided.\n\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n `!__tests__/` to index a directory that is auto-filtered by default (#771).";
237
+ readonly 'help.analyze.environment': "\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker idle timeout in milliseconds. Default 30000.\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL auto-checkpoint threshold in bytes (default 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker job byte budget. Default 8388608.\n GITNEXUS_WORKER_POOL_SIZE=N Parse worker count override. Default cores-1 capped at 16.\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N Concurrent in-flight parse chunks. Default 2.\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N Max replacement spawns per slot before drop. Default 3.\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N Total retry wall-time per job. Default 5x sub-batch timeout.\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N Per-slot deaths to trip circuit breaker. Default max(3, poolSize).\n GITNEXUS_EMBEDDING_THREADS=N Limit local ONNX CPU threads for --embeddings.\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N Max embedding chunks for exact-scan fallback. Default 10000.\n GITNEXUS_VECTOR_MAX_DISTANCE=N Max accepted semantic/vector cosine distance (0 < N <= 2; higher values clamp to 2). Default 0.6 for MCP, 0.5 elsewhere.\n\nFlags override the corresponding env vars when both are provided.\n\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n `!__tests__/` to index a directory that is auto-filtered by default (#771).";
238
238
  };
@@ -234,5 +234,5 @@ export const en = {
234
234
  'help.option.group.contracts.type': 'Filter by contract type',
235
235
  'help.option.group.contracts.repo': 'Filter by repo',
236
236
  'help.option.group.contracts.unmatched': 'Show only unmatched contracts',
237
- 'help.analyze.environment': '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker idle timeout in milliseconds. Default 30000.\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL auto-checkpoint threshold in bytes (default 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker job byte budget. Default 8388608.\n GITNEXUS_WORKER_POOL_SIZE=N Parse worker count override. Default cores-1 capped at 16.\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N Concurrent in-flight parse chunks. Default 2.\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N Max replacement spawns per slot before drop. Default 3.\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N Total retry wall-time per job. Default 5x sub-batch timeout.\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N Per-slot deaths to trip circuit breaker. Default max(3, poolSize).\n GITNEXUS_EMBEDDING_THREADS=N Limit local ONNX CPU threads for --embeddings.\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N Max embedding chunks for exact-scan fallback. Default 10000.\n\nFlags override the corresponding env vars when both are provided.\n\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n `!__tests__/` to index a directory that is auto-filtered by default (#771).',
237
+ 'help.analyze.environment': '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker idle timeout in milliseconds. Default 30000.\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL auto-checkpoint threshold in bytes (default 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker job byte budget. Default 8388608.\n GITNEXUS_WORKER_POOL_SIZE=N Parse worker count override. Default cores-1 capped at 16.\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N Concurrent in-flight parse chunks. Default 2.\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N Max replacement spawns per slot before drop. Default 3.\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N Total retry wall-time per job. Default 5x sub-batch timeout.\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N Per-slot deaths to trip circuit breaker. Default max(3, poolSize).\n GITNEXUS_EMBEDDING_THREADS=N Limit local ONNX CPU threads for --embeddings.\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N Max embedding chunks for exact-scan fallback. Default 10000.\n GITNEXUS_VECTOR_MAX_DISTANCE=N Max accepted semantic/vector cosine distance (0 < N <= 2; higher values clamp to 2). Default 0.6 for MCP, 0.5 elsewhere.\n\nFlags override the corresponding env vars when both are provided.\n\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n `!__tests__/` to index a directory that is auto-filtered by default (#771).',
238
238
  };
@@ -235,7 +235,7 @@ export declare const cliResources: {
235
235
  readonly 'help.option.group.contracts.type': "Filter by contract type";
236
236
  readonly 'help.option.group.contracts.repo': "Filter by repo";
237
237
  readonly 'help.option.group.contracts.unmatched': "Show only unmatched contracts";
238
- readonly 'help.analyze.environment': "\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker idle timeout in milliseconds. Default 30000.\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL auto-checkpoint threshold in bytes (default 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker job byte budget. Default 8388608.\n GITNEXUS_WORKER_POOL_SIZE=N Parse worker count override. Default cores-1 capped at 16.\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N Concurrent in-flight parse chunks. Default 2.\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N Max replacement spawns per slot before drop. Default 3.\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N Total retry wall-time per job. Default 5x sub-batch timeout.\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N Per-slot deaths to trip circuit breaker. Default max(3, poolSize).\n GITNEXUS_EMBEDDING_THREADS=N Limit local ONNX CPU threads for --embeddings.\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N Max embedding chunks for exact-scan fallback. Default 10000.\n\nFlags override the corresponding env vars when both are provided.\n\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n `!__tests__/` to index a directory that is auto-filtered by default (#771).";
238
+ readonly 'help.analyze.environment': "\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker idle timeout in milliseconds. Default 30000.\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL auto-checkpoint threshold in bytes (default 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker job byte budget. Default 8388608.\n GITNEXUS_WORKER_POOL_SIZE=N Parse worker count override. Default cores-1 capped at 16.\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N Concurrent in-flight parse chunks. Default 2.\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N Max replacement spawns per slot before drop. Default 3.\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N Total retry wall-time per job. Default 5x sub-batch timeout.\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N Per-slot deaths to trip circuit breaker. Default max(3, poolSize).\n GITNEXUS_EMBEDDING_THREADS=N Limit local ONNX CPU threads for --embeddings.\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N Max embedding chunks for exact-scan fallback. Default 10000.\n GITNEXUS_VECTOR_MAX_DISTANCE=N Max accepted semantic/vector cosine distance (0 < N <= 2; higher values clamp to 2). Default 0.6 for MCP, 0.5 elsewhere.\n\nFlags override the corresponding env vars when both are provided.\n\nTip: `.gitnexusignore` supports `.gitignore`-style negation. Add e.g.\n `!__tests__/` to index a directory that is auto-filtered by default (#771).";
239
239
  };
240
240
  readonly 'zh-CN': {
241
241
  'common.notIndexed': string;
@@ -234,5 +234,5 @@ export const zhCN = {
234
234
  'help.option.group.contracts.type': '按契约类型过滤',
235
235
  'help.option.group.contracts.repo': '按仓库过滤',
236
236
  'help.option.group.contracts.unmatched': '仅显示未匹配契约',
237
- 'help.analyze.environment': '\n环境变量:\n GITNEXUS_NO_GITIGNORE=1 跳过 .gitignore 解析(仍读取 .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N 覆盖大文件跳过阈值(KB)。默认 512,最大 32768。\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker 空闲超时(毫秒)。默认 30000。\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL 自动 checkpoint 阈值(字节,默认 67108864 = 64 MiB;-1 保持 Ladybug 默认约 16 MiB)。\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker 作业字节预算。默认 8388608。\n GITNEXUS_WORKER_POOL_SIZE=N 解析 worker 数量覆盖值。默认 cores-1,最多 16。\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N 并发进行中的解析分块数。默认 2。\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N 每个 slot 丢弃前允许的最大替换进程数。默认 3。\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N 每个作业的总重试墙钟时间。默认 5 倍子批次超时。\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N 每个 slot 触发熔断的死亡次数。默认 max(3, poolSize)。\n GITNEXUS_EMBEDDING_THREADS=N 限制 --embeddings 的本地 ONNX CPU 线程数。\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N exact-scan 回退的最大嵌入分块数。默认 10000。\n\n当参数和对应环境变量同时提供时,参数优先。\n\n提示:`.gitnexusignore` 支持 `.gitignore` 风格的取反。比如添加\n `!__tests__/` 可以索引默认自动过滤的目录(#771)。',
237
+ 'help.analyze.environment': '\n环境变量:\n GITNEXUS_NO_GITIGNORE=1 跳过 .gitignore 解析(仍读取 .gitnexusignore)\n GITNEXUS_MAX_FILE_SIZE=N 覆盖大文件跳过阈值(KB)。默认 512,最大 32768。\n GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS=N Worker 空闲超时(毫秒)。默认 30000。\n GITNEXUS_WAL_CHECKPOINT_THRESHOLD=N LadybugDB WAL 自动 checkpoint 阈值(字节,默认 67108864 = 64 MiB;-1 保持 Ladybug 默认约 16 MiB)。\n GITNEXUS_WORKER_SUB_BATCH_MAX_BYTES=N Worker 作业字节预算。默认 8388608。\n GITNEXUS_WORKER_POOL_SIZE=N 解析 worker 数量覆盖值。默认 cores-1,最多 16。\n GITNEXUS_PARSE_CHUNK_CONCURRENCY=N 并发进行中的解析分块数。默认 2。\n GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT=N 每个 slot 丢弃前允许的最大替换进程数。默认 3。\n GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS=N 每个作业的总重试墙钟时间。默认 5 倍子批次超时。\n GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD=N 每个 slot 触发熔断的死亡次数。默认 max(3, poolSize)。\n GITNEXUS_EMBEDDING_THREADS=N 限制 --embeddings 的本地 ONNX CPU 线程数。\n GITNEXUS_SEMANTIC_EXACT_SCAN_LIMIT=N exact-scan 回退的最大嵌入分块数。默认 10000。\n GITNEXUS_VECTOR_MAX_DISTANCE=N 语义/向量搜索接受的最大余弦距离(0 < N <= 2;超出则钳制为 2)。MCP 默认 0.6,其他路径默认 0.5。\n\n当参数和对应环境变量同时提供时,参数优先。\n\n提示:`.gitnexusignore` 支持 `.gitignore` 风格的取反。比如添加\n `!__tests__/` 可以索引默认自动过滤的目录(#771)。',
238
238
  };
@@ -1,2 +1,17 @@
1
1
  import { type EmbeddingConfig } from './types.js';
2
+ export declare const DEFAULT_VECTOR_MAX_DISTANCE = 0.5;
3
+ export declare const DEFAULT_MCP_VECTOR_MAX_DISTANCE = 0.6;
4
+ /**
5
+ * Cosine distance over normalized embeddings is bounded to [0, 2], so any threshold
6
+ * above this accepts every row and silently disables the relevance filter. Values
7
+ * over the ceiling are clamped to it rather than passed through.
8
+ */
9
+ export declare const VECTOR_MAX_DISTANCE_CEILING = 2;
10
+ /**
11
+ * Resolve the effective max accepted vector/semantic cosine distance.
12
+ * Reads `GITNEXUS_VECTOR_MAX_DISTANCE`. Unset/empty/whitespace → silent fallback.
13
+ * Invalid (non-numeric, <= 0, non-finite) → fallback plus a one-time warning.
14
+ * Values above the cosine ceiling (2) are clamped to it with a one-time warning.
15
+ */
16
+ export declare const getVectorMaxDistance: (fallback?: number) => number;
2
17
  export declare const resolveEmbeddingConfig: (overrides?: Partial<EmbeddingConfig>) => EmbeddingConfig;
@@ -1,5 +1,42 @@
1
1
  import { defaultEmbeddingThreads } from '../platform/capabilities.js';
2
+ import { logger } from '../logger.js';
2
3
  import { DEFAULT_EMBEDDING_CONFIG } from './types.js';
4
+ export const DEFAULT_VECTOR_MAX_DISTANCE = 0.5;
5
+ export const DEFAULT_MCP_VECTOR_MAX_DISTANCE = 0.6;
6
+ /**
7
+ * Cosine distance over normalized embeddings is bounded to [0, 2], so any threshold
8
+ * above this accepts every row and silently disables the relevance filter. Values
9
+ * over the ceiling are clamped to it rather than passed through.
10
+ */
11
+ export const VECTOR_MAX_DISTANCE_CEILING = 2;
12
+ const warned = new Set();
13
+ const warnOnce = (key, message) => {
14
+ if (warned.has(key))
15
+ return;
16
+ warned.add(key);
17
+ logger.warn(message);
18
+ };
19
+ /**
20
+ * Resolve the effective max accepted vector/semantic cosine distance.
21
+ * Reads `GITNEXUS_VECTOR_MAX_DISTANCE`. Unset/empty/whitespace → silent fallback.
22
+ * Invalid (non-numeric, <= 0, non-finite) → fallback plus a one-time warning.
23
+ * Values above the cosine ceiling (2) are clamped to it with a one-time warning.
24
+ */
25
+ export const getVectorMaxDistance = (fallback = DEFAULT_VECTOR_MAX_DISTANCE) => {
26
+ const raw = process.env.GITNEXUS_VECTOR_MAX_DISTANCE;
27
+ if (raw === undefined || raw.trim() === '')
28
+ return fallback;
29
+ const parsed = Number(raw);
30
+ if (!Number.isFinite(parsed) || parsed <= 0) {
31
+ warnOnce(`invalid:${raw}`, ` GITNEXUS_VECTOR_MAX_DISTANCE must be a positive number in (0, ${VECTOR_MAX_DISTANCE_CEILING}], got "${raw}" — using default ${fallback}`);
32
+ return fallback;
33
+ }
34
+ if (parsed > VECTOR_MAX_DISTANCE_CEILING) {
35
+ warnOnce(`clamp:${raw}`, ` GITNEXUS_VECTOR_MAX_DISTANCE=${parsed} exceeds the cosine-distance ceiling (${VECTOR_MAX_DISTANCE_CEILING}) — clamping`);
36
+ return VECTOR_MAX_DISTANCE_CEILING;
37
+ }
38
+ return parsed;
39
+ };
3
40
  const parsePositiveInt = (name, value, fallback) => {
4
41
  if (value === undefined)
5
42
  return fallback;
@@ -14,7 +14,7 @@ import { generateEmbeddingText } from './text-generator.js';
14
14
  import { chunkNode, characterChunk } from './chunker.js';
15
15
  import { extractStructuralNames } from './structural-extractor.js';
16
16
  import { EMBEDDABLE_LABELS, isShortLabel, LABEL_METHOD, LABELS_WITH_EXPORTED, STRUCTURAL_LABELS, collectBestChunks, } from './types.js';
17
- import { resolveEmbeddingConfig } from './config.js';
17
+ import { DEFAULT_VECTOR_MAX_DISTANCE, getVectorMaxDistance, resolveEmbeddingConfig, } from './config.js';
18
18
  import { rankExactEmbeddingRows } from './exact-search.js';
19
19
  import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME, STALE_HASH_SENTINEL } from '../lbug/schema.js';
20
20
  import { loadVectorExtension, createVectorIndex } from '../lbug/lbug-adapter.js';
@@ -447,7 +447,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
447
447
  /**
448
448
  * Perform semantic search using the vector index with chunk deduplication
449
449
  */
450
- export const semanticSearch = async (executeQuery, query, k = 10, maxDistance = 0.5) => {
450
+ export const semanticSearch = async (executeQuery, query, k = 10, maxDistance = getVectorMaxDistance(DEFAULT_VECTOR_MAX_DISTANCE)) => {
451
451
  if (!isEmbedderReady()) {
452
452
  throw new Error('Embedding model not initialized. Run embedding pipeline first.');
453
453
  }
@@ -569,7 +569,7 @@ export const semanticSearch = async (executeQuery, query, k = 10, maxDistance =
569
569
  * Semantic search with graph expansion (flattened results)
570
570
  */
571
571
  export const semanticSearchWithContext = async (executeQuery, query, k = 5, _hops = 1) => {
572
- const results = await semanticSearch(executeQuery, query, k, 0.5);
572
+ const results = await semanticSearch(executeQuery, query, k);
573
573
  return results.map((r) => ({
574
574
  matchId: r.nodeId,
575
575
  matchName: r.name,
@@ -24,6 +24,8 @@ import type { ExtractedContract, RepoHandle } from '../types.js';
24
24
  * widen `HTTP_SCAN_GLOB` if needed.
25
25
  */
26
26
  export declare const HANDLES_ROUTE_QUERY = "\nMATCH (handlerFile:File)-[r:CodeRelation {type: 'HANDLES_ROUTE'}]->(route:Route)\nRETURN handlerFile.id AS fileId, handlerFile.filePath AS filePath,\n route.name AS routePath, route.id AS routeId,\n route.method AS routeMethod,\n route.handlerSymbolId AS handlerSymbolId,\n route.responseKeys AS responseKeys,\n r.reason AS routeSource";
27
+ export declare const RESOLVE_BY_NAME_QUERY = "\nMATCH (n) WHERE labels(n) IN ['Function','Method','CodeElement']\n AND n.name = $name AND n.filePath <> ''\nRETURN n.id AS uid, n.name AS name, n.filePath AS filePath\nLIMIT 2";
28
+ export declare const RESOLVE_IN_MODULE_QUERY = "\nMATCH (n) WHERE labels(n) IN ['Function','Method','CodeElement']\n AND n.name = $name AND (n.filePath STARTS WITH $fileDot OR n.filePath STARTS WITH $fileSlash)\nRETURN n.id AS uid, n.name AS name, n.filePath AS filePath\nLIMIT 2";
27
29
  /**
28
30
  * Canonicalize a provider-side HTTP path for contract-id generation:
29
31
  * - strip query string
@@ -67,10 +67,21 @@ MATCH (sym:CodeElement)
67
67
  WHERE sym.filePath = $filePath AND sym.startLine IS NOT NULL AND sym.endLine IS NOT NULL
68
68
  RETURN sym.id AS uid, sym.name AS name, sym.filePath AS filePath,
69
69
  sym.startLine AS startLine, sym.endLine AS endLine, labels(sym) AS labels`;
70
- // Repo-wide lookup of a symbol by exact name (label-union, as in
71
- // manifest-extractor.ts). Used to resolve a provider's named handler when it is
72
- // defined in a file OTHER than its route registration — and only honored when
73
- // the result is unique (see resolveSymbolByNameUnique).
70
+ // Repo-wide lookup of a symbol by exact name. Used to resolve a provider's
71
+ // named handler when it is defined in a file OTHER than its route registration —
72
+ // and only honored when the result is unique (see resolveSymbolByNameUnique).
73
+ //
74
+ // Label filtering uses `labels(n) IN [...]` rather than the openCypher
75
+ // disjunction `MATCH (n:A|B|C)`. NOTE: this 3-label set (Function/Method/
76
+ // CodeElement) actually PARSES — LadybugDB only rejects a disjunction that
77
+ // names a reserved keyword (e.g. `Macro`, `Union`) or a missing node table,
78
+ // neither of which applies here. So this query was NOT broken by #2325; it
79
+ // uses the `labels(n) IN` form for consistency with the manifest custom-branch
80
+ // fix (which WAS broken) and to stay immune if a reserved-keyword label is
81
+ // added later. `labels(n)` returns the node's single label as a string here, so
82
+ // `IN [...]` is an exact allowlist. (Exported so integration tests can run the
83
+ // exact production query against a real LadybugDB — the bug shipped because no
84
+ // test ran these strings against the real parser.)
74
85
  //
75
86
  // `n.filePath <> ''` excludes synthetic non-source `CodeElement` nodes that
76
87
  // carry no real file — ORM model/table nodes (orm.ts emits `filePath: ''`) and
@@ -78,9 +89,9 @@ RETURN sym.id AS uid, sym.name AS name, sym.filePath AS filePath,
78
89
  // degenerate edge-less node NOR inflates the uniqueness count and masks the real
79
90
  // handler. `LIMIT 2` bounds materialization: distinguishing unique (1) from
80
91
  // ambiguous (>=2) never needs more than two rows (the count guard stays exact).
81
- const RESOLVE_BY_NAME_QUERY = `
82
- MATCH (n:Function|Method|CodeElement)
83
- WHERE n.name = $name AND n.filePath <> ''
92
+ export const RESOLVE_BY_NAME_QUERY = `
93
+ MATCH (n) WHERE labels(n) IN ['Function','Method','CodeElement']
94
+ AND n.name = $name AND n.filePath <> ''
84
95
  RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
85
96
  LIMIT 2`;
86
97
  // Resolve an IMPORTED handler by pinning it to the import's target module: the
@@ -89,9 +100,9 @@ LIMIT 2`;
89
100
  // the precise rung — it survives aliases and local same-name collisions that a
90
101
  // repo-wide name lookup cannot, and only resolves on a unique match within that
91
102
  // module. `LIMIT 2` keeps the uniqueness count exact (see RESOLVE_BY_NAME_QUERY).
92
- const RESOLVE_IN_MODULE_QUERY = `
93
- MATCH (n:Function|Method|CodeElement)
94
- WHERE n.name = $name AND (n.filePath STARTS WITH $fileDot OR n.filePath STARTS WITH $fileSlash)
103
+ export const RESOLVE_IN_MODULE_QUERY = `
104
+ MATCH (n) WHERE labels(n) IN ['Function','Method','CodeElement']
105
+ AND n.name = $name AND (n.filePath STARTS WITH $fileDot OR n.filePath STARTS WITH $fileSlash)
95
106
  RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
96
107
  LIMIT 2`;
97
108
  // Source-file extensions an import specifier may resolve to (stripped before
@@ -4,6 +4,7 @@ export interface ManifestExtractResult {
4
4
  contracts: StoredContract[];
5
5
  crossLinks: CrossLink[];
6
6
  }
7
+ export declare const CUSTOM_CONTRACT_RESOLVE_QUERY = "MATCH (n)\n WHERE labels(n) IN ['Function','Method','Class','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']\n AND n.name = $symbolName\n RETURN n.id AS uid, n.name AS name, n.filePath AS filePath\n ORDER BY n.filePath ASC\n LIMIT 1";
7
8
  /**
8
9
  * Stable synthetic symbolUid for a manifest-declared contract whose target
9
10
  * symbol could not be resolved against the per-repo graph (resolveSymbol
@@ -1,4 +1,18 @@
1
1
  import { logger } from '../../logger.js';
2
+ // Repo-wide symbol lookup for `custom` workspace contracts. Exported so the
3
+ // #2325 integration test can run the EXACT production query against a real
4
+ // LadybugDB — a hand-copied query string in the test would silently drift
5
+ // from this allowlist. Uses the `labels(n) IN [...]` allowlist form rather
6
+ // than a `MATCH (n:A|B)` disjunction: this 21-label list contains the
7
+ // reserved-keyword labels `Macro` and `Union`, and LadybugDB's parser rejects
8
+ // a disjunction that names a reserved keyword (#2325) — which the resolver's
9
+ // try/catch then swallowed. `labels(n) IN` has no such collision.
10
+ export const CUSTOM_CONTRACT_RESOLVE_QUERY = `MATCH (n)
11
+ WHERE labels(n) IN ['Function','Method','Class','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']
12
+ AND n.name = $symbolName
13
+ RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
14
+ ORDER BY n.filePath ASC
15
+ LIMIT 1`;
2
16
  /**
3
17
  * Canonicalize an HTTP path for matching against Route.name in the graph.
4
18
  * Mirrors core/ingestion/pipeline.ts ensureSlash semantics:
@@ -148,6 +162,18 @@ export class ManifestExtractor {
148
162
  // Cross-impact still works: the bridge query joins on the synthetic
149
163
  // uid, and the local impact engine derives the same uid for the
150
164
  // unresolved symbol — name-based hints are the additional safety net.
165
+ //
166
+ // Label filtering uses `MATCH (n) WHERE labels(n) IN [...]`, NOT the
167
+ // openCypher disjunction `MATCH (n:A|B|C)`. LadybugDB's parser rejects a
168
+ // disjunction that names a reserved keyword (`Macro` and `Union` both are)
169
+ // OR a label with no node table (e.g. the old `lib` branch's `Package`).
170
+ // The `custom` branch (reserved keywords in its list) and `lib` branch
171
+ // (missing `Package` table) genuinely threw (#2325) and the whole try/catch
172
+ // below swallowed it; the other branches parsed but use the same form for
173
+ // consistency and future-proofing. `labels(n)` returns the node's single
174
+ // label as a string here, so `IN [...]` is an exact allowlist that includes
175
+ // listed labels and excludes everything else — and is immune to both
176
+ // failure modes (no keyword collision; an unknown label is just a non-match).
151
177
  try {
152
178
  let rows;
153
179
  if (link.type === 'http') {
@@ -178,7 +204,7 @@ export class ManifestExtractor {
178
204
  // listeners, publishers). Restrict to symbol-like labels to
179
205
  // avoid cross-matching Files/Variables/Imports that happen to
180
206
  // share the topic name.
181
- rows = await executor(`MATCH (n:Function|Method|Class|Interface) WHERE n.name = $contract
207
+ rows = await executor(`MATCH (n) WHERE labels(n) IN ['Function','Method','Class','Interface'] AND n.name = $contract
182
208
  RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
183
209
  ORDER BY n.filePath ASC
184
210
  LIMIT 1`, { contract: link.contract });
@@ -199,13 +225,13 @@ export class ManifestExtractor {
199
225
  const serviceName = link.type === 'thrift' ? (rawServiceName.split('.').pop() ?? '') : rawServiceName;
200
226
  const methodName = parts[1]?.trim() ?? '';
201
227
  if (methodName) {
202
- rows = await executor(`MATCH (n:Function|Method) WHERE n.name = $methodName
228
+ rows = await executor(`MATCH (n) WHERE labels(n) IN ['Function','Method'] AND n.name = $methodName
203
229
  RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
204
230
  ORDER BY n.filePath ASC
205
231
  LIMIT 1`, { methodName });
206
232
  }
207
233
  else if (serviceName) {
208
- rows = await executor(`MATCH (n:Class|Interface) WHERE n.name = $serviceName
234
+ rows = await executor(`MATCH (n) WHERE labels(n) IN ['Class','Interface'] AND n.name = $serviceName
209
235
  RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
210
236
  ORDER BY n.filePath ASC
211
237
  LIMIT 1`, { serviceName });
@@ -217,10 +243,11 @@ export class ManifestExtractor {
217
243
  else if (link.type === 'lib') {
218
244
  // Only exact match on the symbol's name. Previous fallback to
219
245
  // CONTAINS on n.filePath would promote "react" to "react-native"
220
- // or "@types/react" — silent wrong attribution. Restrict to
221
- // package-level labels so we don't return arbitrary symbols
222
- // named after a library.
223
- rows = await executor(`MATCH (n:Package|Module) WHERE n.name = $contract
246
+ // or "@types/react" — silent wrong attribution. Restrict to the
247
+ // package-level `Module` label so we don't return arbitrary symbols
248
+ // named after a library. (There is no `Package` node table — see
249
+ // NODE_TABLES so a `Package` entry only ever matched nothing.)
250
+ rows = await executor(`MATCH (n) WHERE labels(n) IN ['Module'] AND n.name = $contract
224
251
  RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
225
252
  ORDER BY n.filePath ASC
226
253
  LIMIT 1`, { contract: link.contract });
@@ -238,11 +265,7 @@ export class ManifestExtractor {
238
265
  const symbolName = link.contract.includes('::')
239
266
  ? link.contract.split('::').pop()
240
267
  : link.contract;
241
- rows = await executor(`MATCH (n:Function|Method|Class|Interface|Struct|Enum|Trait|Constructor|TypeAlias|Impl|Macro|Union|Typedef|Property|Record|Delegate|Annotation|Template|Const|Static|CodeElement)
242
- WHERE n.name = $symbolName
243
- RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
244
- ORDER BY n.filePath ASC
245
- LIMIT 1`, { symbolName });
268
+ rows = await executor(CUSTOM_CONTRACT_RESOLVE_QUERY, { symbolName });
246
269
  }
247
270
  else {
248
271
  return null;
@@ -21,6 +21,7 @@ import { listRegisteredRepos, cleanupOldKuzuFiles, canonicalizePath, getStorageP
21
21
  import { GroupService, } from '../../core/group/service.js';
22
22
  import { resolveAtGroupMemberRepoPath } from '../../core/group/resolve-at-member.js';
23
23
  import { collectBestChunks } from '../../core/embeddings/types.js';
24
+ import { DEFAULT_MCP_VECTOR_MAX_DISTANCE, getVectorMaxDistance, } from '../../core/embeddings/config.js';
24
25
  import { rankExactEmbeddingRows, } from '../../core/embeddings/exact-search.js';
25
26
  import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/lbug/schema.js';
26
27
  import { getExactScanLimit, isVectorExtensionSupportedByPlatform, } from '../../core/platform/capabilities.js';
@@ -1694,6 +1695,7 @@ export class LocalBackend {
1694
1695
  const queryVec = await embedQuery(query);
1695
1696
  const dims = getEmbeddingDims();
1696
1697
  const queryVecStr = `[${queryVec.join(',')}]`;
1698
+ const maxDistance = getVectorMaxDistance(DEFAULT_MCP_VECTOR_MAX_DISTANCE);
1697
1699
  let bestChunks = new Map();
1698
1700
  if (isVectorExtensionSupportedByPlatform()) {
1699
1701
  try {
@@ -1703,7 +1705,7 @@ export class LocalBackend {
1703
1705
  CAST(${queryVecStr} AS FLOAT[${dims}]), ${fetchLimit})
1704
1706
  YIELD node AS emb, distance
1705
1707
  WITH emb, distance
1706
- WHERE distance < 0.6
1708
+ WHERE distance < ${maxDistance}
1707
1709
  RETURN emb.nodeId AS nodeId, emb.chunkIndex AS chunkIndex,
1708
1710
  emb.startLine AS startLine, emb.endLine AS endLine, distance
1709
1711
  ORDER BY distance
@@ -1747,7 +1749,7 @@ export class LocalBackend {
1747
1749
  endLine: row.endLine ?? row[3] ?? 0,
1748
1750
  embedding: row.embedding ?? row[4] ?? [],
1749
1751
  }));
1750
- bestChunks = new Map(rankExactEmbeddingRows(exactRows, queryVec, limit, 0.6).map((row) => [
1752
+ bestChunks = new Map(rankExactEmbeddingRows(exactRows, queryVec, limit, maxDistance).map((row) => [
1751
1753
  row.nodeId,
1752
1754
  {
1753
1755
  distance: row.distance,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.31",
3
+ "version": "1.6.9-rc.33",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",