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.
- package/dist/cli/i18n/en.d.ts +1 -1
- package/dist/cli/i18n/en.js +1 -1
- package/dist/cli/i18n/resources.d.ts +1 -1
- package/dist/cli/i18n/zh-CN.js +1 -1
- package/dist/core/embeddings/config.d.ts +15 -0
- package/dist/core/embeddings/config.js +37 -0
- package/dist/core/embeddings/embedding-pipeline.js +3 -3
- package/dist/core/group/extractors/http-route-extractor.d.ts +2 -0
- package/dist/core/group/extractors/http-route-extractor.js +21 -10
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -0
- package/dist/core/group/extractors/manifest-extractor.js +35 -12
- package/dist/mcp/local/local-backend.js +4 -2
- package/package.json +1 -1
package/dist/cli/i18n/en.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -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;
|
package/dist/cli/i18n/zh-CN.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
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
|
|
83
|
-
|
|
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
|
|
94
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
222
|
-
// named after a library.
|
|
223
|
-
|
|
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(
|
|
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 <
|
|
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,
|
|
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