codesearch-mcp 0.2.3-alpha → 0.3.0-alpha

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 (141) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +46 -26
  3. package/dist/app.js +5 -19
  4. package/dist/src/chunking/AstChunker.d.ts +12 -0
  5. package/dist/src/chunking/AstChunker.js +30 -0
  6. package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
  7. package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
  8. package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
  9. package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
  10. package/dist/src/chunking/FragmentMerger.d.ts +11 -0
  11. package/dist/src/chunking/FragmentMerger.js +61 -0
  12. package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
  13. package/dist/src/chunking/ImportOnlyFilter.js +14 -0
  14. package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
  15. package/dist/src/chunking/LangchainTextSplitter.js +133 -0
  16. package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
  17. package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
  18. package/dist/src/chunking/RunPacker.d.ts +12 -0
  19. package/dist/src/chunking/RunPacker.js +84 -0
  20. package/dist/src/chunking/create-chunker.d.ts +3 -0
  21. package/dist/src/chunking/create-chunker.js +15 -0
  22. package/dist/src/chunking/interfaces.d.ts +27 -0
  23. package/dist/src/chunking/interfaces.js +2 -0
  24. package/dist/src/commands/ConfigCommand.js +1 -9
  25. package/dist/src/commands/EvalCommand.d.ts +4 -3
  26. package/dist/src/commands/EvalCommand.js +85 -52
  27. package/dist/src/commands/IndexCommand.js +0 -22
  28. package/dist/src/commands/McpCommand.js +2 -8
  29. package/dist/src/commands/WebCommand.js +2 -5
  30. package/dist/src/config-error.d.ts +0 -4
  31. package/dist/src/config-error.js +0 -4
  32. package/dist/src/config-store.d.ts +1 -54
  33. package/dist/src/config-store.js +8 -69
  34. package/dist/src/config.d.ts +8 -17
  35. package/dist/src/config.js +35 -34
  36. package/dist/src/constants.d.ts +2 -28
  37. package/dist/src/constants.js +6 -28
  38. package/dist/src/container.d.ts +6 -0
  39. package/dist/src/container.js +12 -3
  40. package/dist/src/db/schema.d.ts +17 -0
  41. package/dist/src/db/schema.js +1 -6
  42. package/dist/src/declarations/DeclarationTree.d.ts +19 -0
  43. package/dist/src/declarations/DeclarationTree.js +66 -0
  44. package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
  45. package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
  46. package/dist/src/declarations/SourceLines.d.ts +37 -0
  47. package/dist/src/declarations/SourceLines.js +415 -0
  48. package/dist/src/dependency-traversal.d.ts +0 -6
  49. package/dist/src/dependency-traversal.js +0 -6
  50. package/dist/src/enriched-prompt.d.ts +0 -6
  51. package/dist/src/enriched-prompt.js +0 -6
  52. package/dist/src/error-record.d.ts +12 -0
  53. package/dist/src/error-record.js +248 -0
  54. package/dist/src/eval-corpus.d.ts +16 -0
  55. package/dist/src/eval-corpus.js +169 -0
  56. package/dist/src/eval-results.d.ts +6 -3
  57. package/dist/src/eval-results.js +309 -26
  58. package/dist/src/file-utils.d.ts +0 -5
  59. package/dist/src/file-utils.js +0 -5
  60. package/dist/src/graph.d.ts +0 -25
  61. package/dist/src/graph.js +0 -28
  62. package/dist/src/hnsw-settings.d.ts +3 -0
  63. package/dist/src/hnsw-settings.js +24 -0
  64. package/dist/src/interfaces/IAstParser.d.ts +2 -39
  65. package/dist/src/interfaces/IChunker.d.ts +3 -12
  66. package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
  67. package/dist/src/interfaces/ICorpusInspector.js +2 -0
  68. package/dist/src/interfaces/IDatabase.d.ts +2 -22
  69. package/dist/src/interfaces/IEmbedder.d.ts +0 -9
  70. package/dist/src/interfaces/ILogger.d.ts +1 -11
  71. package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
  72. package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
  73. package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
  74. package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
  75. package/dist/src/interfaces/ISearcher.d.ts +16 -5
  76. package/dist/src/interrupt.js +0 -2
  77. package/dist/src/owner-path.d.ts +9 -10
  78. package/dist/src/owner-path.js +11 -23
  79. package/dist/src/project-scope.d.ts +0 -12
  80. package/dist/src/project-scope.js +0 -12
  81. package/dist/src/project-selector.d.ts +0 -5
  82. package/dist/src/project-selector.js +0 -11
  83. package/dist/src/retrieval-eval.d.ts +14 -6
  84. package/dist/src/retrieval-eval.js +345 -59
  85. package/dist/src/search/result-shaping.d.ts +4 -0
  86. package/dist/src/search/result-shaping.js +19 -0
  87. package/dist/src/services/AstParserService.d.ts +2 -62
  88. package/dist/src/services/AstParserService.js +25 -137
  89. package/dist/src/services/DatabaseService.d.ts +2 -7
  90. package/dist/src/services/DatabaseService.js +42 -29
  91. package/dist/src/services/EmbedderFactory.d.ts +0 -4
  92. package/dist/src/services/EmbedderFactory.js +0 -5
  93. package/dist/src/services/IndexerService.d.ts +0 -16
  94. package/dist/src/services/IndexerService.js +16 -45
  95. package/dist/src/services/InfinityEmbedder.d.ts +0 -21
  96. package/dist/src/services/InfinityEmbedder.js +0 -15
  97. package/dist/src/services/LoggerService.d.ts +7 -5
  98. package/dist/src/services/LoggerService.js +37 -41
  99. package/dist/src/services/McpServer.d.ts +22 -14
  100. package/dist/src/services/McpServer.js +70 -37
  101. package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
  102. package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
  103. package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
  104. package/dist/src/services/NxMonorepoResolver.js +5 -56
  105. package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
  106. package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
  107. package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
  108. package/dist/src/services/ProjectDiscoveryService.js +0 -4
  109. package/dist/src/services/SearcherService.d.ts +9 -116
  110. package/dist/src/services/SearcherService.js +95 -176
  111. package/dist/src/services/VoyageEmbedder.d.ts +0 -6
  112. package/dist/src/services/VoyageEmbedder.js +0 -6
  113. package/dist/src/services/WebServerService.d.ts +1 -53
  114. package/dist/src/services/WebServerService.js +11 -57
  115. package/dist/src/services/http-retry.d.ts +0 -7
  116. package/dist/src/services/http-retry.js +1 -10
  117. package/dist/src/services/infinity-provisioning.d.ts +0 -74
  118. package/dist/src/services/infinity-provisioning.js +5 -88
  119. package/dist/src/services/legacy-ollama-index.d.ts +0 -16
  120. package/dist/src/services/legacy-ollama-index.js +0 -23
  121. package/dist/src/services/reranker.d.ts +0 -9
  122. package/dist/src/services/reranker.js +46 -12
  123. package/dist/src/setup.d.ts +11 -23
  124. package/dist/src/setup.js +98 -71
  125. package/dist/src/symbol-name.d.ts +0 -12
  126. package/dist/src/symbol-name.js +0 -13
  127. package/dist/src/symbol-rows.d.ts +0 -14
  128. package/dist/src/symbol-rows.js +0 -14
  129. package/dist/src/tool-call-log.d.ts +2 -0
  130. package/dist/src/tool-call-log.js +24 -0
  131. package/dist/src/tools/GetDependenciesTool.js +1 -1
  132. package/dist/src/tools/GetDependentsTool.js +1 -1
  133. package/dist/src/tools/SearchCodebaseTool.js +6 -3
  134. package/dist/src/tools/format-helpers.d.ts +0 -6
  135. package/dist/src/tools/format-helpers.js +87 -14
  136. package/dist/src/web/dashboard.d.ts +0 -8
  137. package/dist/src/web/dashboard.js +7 -10
  138. package/package.json +2 -2
  139. package/dist/src/services/AstChunker.d.ts +0 -42
  140. package/dist/src/services/AstChunker.js +0 -158
  141. package/docs/web-dashboard.md +0 -206
@@ -8,7 +8,6 @@ const MonorepoAwareDiscoveryService_1 = require("../services/MonorepoAwareDiscov
8
8
  const project_scope_1 = require("../project-scope");
9
9
  const file_utils_1 = require("../file-utils");
10
10
  const legacy_ollama_index_1 = require("../services/legacy-ollama-index");
11
- /** `--project`'s index in `args`; the following token is its name unless absent or itself a flag. */
12
11
  function parseProjectFlag(args) {
13
12
  const i = args.indexOf('--project');
14
13
  if (i === -1)
@@ -38,8 +37,6 @@ class IndexCommand {
38
37
  process.exitCode = 1;
39
38
  return;
40
39
  }
41
- // Resolve --project before any database or embedding work, so a bad name
42
- // (or a cancelled no-name prompt) has zero side effects.
43
40
  let scanTargets;
44
41
  if (projectFlag.present) {
45
42
  let name = projectFlag.name;
@@ -65,11 +62,6 @@ class IndexCommand {
65
62
  scanTargets = targets;
66
63
  }
67
64
  await db.initDbWithSchema();
68
- // A former Ollama installation the config layer cannot recognise (old
69
- // defaults, no `embedding`/`ollama` block) is protected here: without an
70
- // explicit `--reindex`, refuse to touch an index built by `ollama` (or a
71
- // pre-`index_meta` install, which is indistinguishable from one) before
72
- // any backend readiness check, Docker command, or schema work.
73
65
  if (!force) {
74
66
  let identity;
75
67
  try {
@@ -96,17 +88,9 @@ class IndexCommand {
96
88
  const selected = await new project_selector_1.ProjectSelector(config.workspaceRoot).select(discovered, forceSelect);
97
89
  if (selected.length === 0)
98
90
  return;
99
- // Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
100
- // sub-projects; ordinary projects pass through unchanged.
101
91
  projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
102
92
  }
103
- // The post-index graph always reflects the full workspace, regardless of
104
- // what this run actually scanned.
105
93
  const fullLeafProjects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(discovered);
106
- // Ensure the local model is served (Infinity only — no-op for cloud), then
107
- // resolve the active embedding dimension and reconcile it with index_meta
108
- // (recreate code_chunk on a dimension change, clear rows on a model change)
109
- // before scanning. A clear error here beats a mid-run vector width mismatch.
110
94
  try {
111
95
  await embedder.ensureModelAvailable?.();
112
96
  const meta = await indexer.prepareEmbeddingSchema();
@@ -120,8 +104,6 @@ class IndexCommand {
120
104
  const controller = new AbortController();
121
105
  const teardown = (0, interrupt_1.setupInterrupt)(controller);
122
106
  try {
123
- // --project bypasses pruning entirely — it promises "touch only this
124
- // project," so nothing outside the resolved target(s) is ever removed.
125
107
  if (!scanTargets) {
126
108
  let pruneResult;
127
109
  try {
@@ -150,11 +132,7 @@ class IndexCommand {
150
132
  console.log('\nIndexing interrupted.');
151
133
  this.printSummary(allStats);
152
134
  if (!controller.signal.aborted) {
153
- // The graph is a post-index nicety — a failure here (DB dropped after a
154
- // long run, unreadable dir) must not fail the whole run: indexing already
155
- // succeeded and the summary is printed.
156
135
  try {
157
- // Real import edges (code_dependency, just written) → intra-repo graph links.
158
136
  const projectEdges = await searcher.getProjectEdges();
159
137
  (0, graph_1.generateProjectGraph)(fullLeafProjects, projectEdges, config.workspaceRoot);
160
138
  }
@@ -11,25 +11,19 @@ class McpCommand {
11
11
  let startupError;
12
12
  try {
13
13
  await db.initDb();
14
- // Refuse to serve searches embedded into a foreign vector space — surface
15
- // the mismatch through the existing startup-error plumbing so every tool
16
- // call reports it clearly.
17
14
  await searcher.assertIndexMatches();
18
15
  }
19
16
  catch (err) {
20
17
  startupError = err instanceof Error ? err : new Error(String(err));
21
- logger.error('McpCommand: startup check failed', { reason: startupError.message });
18
+ logger.error('McpCommand: startup check failed', { err: startupError });
22
19
  }
23
20
  try {
24
21
  await mcp.start(startupError);
25
22
  }
26
23
  catch (err) {
27
- const reason = err instanceof Error ? err.message : String(err);
28
- logger.error('McpCommand: MCP server failed to start', { reason });
24
+ logger.error('McpCommand: MCP server failed to start', { err });
29
25
  return;
30
26
  }
31
- // server.connect() resolves immediately after setup; keep the process alive
32
- // until the MCP client disconnects (stdin closes)
33
27
  await new Promise(resolve => {
34
28
  process.stdin.once('close', resolve);
35
29
  process.stdin.once('end', resolve);
@@ -17,9 +17,8 @@ class WebCommand {
17
17
  }
18
18
  catch (err) {
19
19
  const reason = err instanceof Error ? err.message : String(err);
20
- logger.error('WebCommand: database connection failed', { reason });
20
+ logger.error('WebCommand: database connection failed', { err });
21
21
  console.error(`Database connection failed: ${reason}`);
22
- // Continue: config editing still works; project/graph tabs will error in-page.
23
22
  }
24
23
  const server = new WebServerService_1.WebServer(searcher, config, logger, embedder);
25
24
  const host = '127.0.0.1';
@@ -28,13 +27,11 @@ class WebCommand {
28
27
  }
29
28
  catch (err) {
30
29
  const reason = err instanceof Error ? err.message : String(err);
31
- logger.error('WebCommand: server failed to start', { reason });
30
+ logger.error('WebCommand: server failed to start', { err });
32
31
  console.error(`Failed to start web server on ${host}:${this.port}: ${reason}`);
33
32
  return;
34
33
  }
35
34
  console.log(`RAG dashboard: http://${host}:${this.port} (Ctrl+C to stop)`);
36
- // Keep execute() pending until a shutdown signal, otherwise app.ts tears
37
- // down the DB and exits immediately (mirrors McpCommand's keep-alive).
38
35
  await new Promise(resolve => {
39
36
  const shutdown = () => { void server.stop().then(resolve); };
40
37
  process.once('SIGINT', shutdown);
@@ -1,7 +1,3 @@
1
- /**
2
- * Own module (not `config.ts`) so `config-store.ts` can throw it too without
3
- * an import cycle — `config.ts` imports from `config-store.ts`.
4
- */
5
1
  export declare class ConfigError extends Error {
6
2
  constructor(message: string);
7
3
  }
@@ -1,10 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConfigError = void 0;
4
- /**
5
- * Own module (not `config.ts`) so `config-store.ts` can throw it too without
6
- * an import cycle — `config.ts` imports from `config-store.ts`.
7
- */
8
4
  class ConfigError extends Error {
9
5
  constructor(message) {
10
6
  super(message);
@@ -42,6 +42,7 @@ declare const jsonSchema: z.ZodObject<{
42
42
  enabled: z.ZodOptional<z.ZodBoolean>;
43
43
  provider: z.ZodOptional<z.ZodEnum<{
44
44
  "openai-compatible": "openai-compatible";
45
+ voyage: "voyage";
45
46
  }>>;
46
47
  baseURL: z.ZodOptional<z.ZodString>;
47
48
  apiKey: z.ZodOptional<z.ZodString>;
@@ -58,24 +59,11 @@ declare const jsonSchema: z.ZodObject<{
58
59
  warn: "warn";
59
60
  }>>;
60
61
  dir: z.ZodOptional<z.ZodString>;
61
- searchMetrics: z.ZodOptional<z.ZodBoolean>;
62
62
  }, z.core.$strip>>;
63
63
  workspaceRoot: z.ZodOptional<z.ZodString>;
64
64
  }, z.core.$strip>;
65
65
  export type JsonConfig = z.infer<typeof jsonSchema>;
66
66
  export declare const ALL_KEYS: string[];
67
- /**
68
- * Single source of truth for built-in defaults, keyed by raw config.json dotted
69
- * key. Consumed both by the web dashboard (`GET /api/defaults`) and by
70
- * `resolvedSchema` in `src/config.ts`, whose `.default(...)` calls reference
71
- * these values — so the runtime default and the dashboard-displayed default
72
- * cannot diverge. `as const` keeps the literal types so those `.default()`
73
- * lookups stay type-safe. Keys omitted here have no static default: required
74
- * fields (db.host/user/password) and values derived at runtime — db.name →
75
- * rag_<user>, and the provider-dependent URLs (embedding.baseURL,
76
- * reranker.baseURL, reranker.url), which are computed in the `Config`
77
- * constructor per provider rather than through a schema `.default(...)`.
78
- */
79
67
  export declare const CONFIG_DEFAULTS: {
80
68
  readonly 'db.port': 5433;
81
69
  readonly 'embedding.provider': "infinity";
@@ -97,54 +85,13 @@ export declare const CONFIG_DEFAULTS: {
97
85
  readonly 'reranker.overfetch': 5;
98
86
  readonly 'reranker.candidateFloor': 80;
99
87
  readonly 'logging.level': "error";
100
- readonly 'logging.searchMetrics': false;
101
88
  };
102
89
  export declare function readConfigJson(): JsonConfig | null;
103
- /**
104
- * Detect a raw (pre-migration) config object that still selects the removed
105
- * `ollama` embedding provider — explicitly, via a legacy top-level `ollama`
106
- * block, or implicitly (no `provider` set, but `baseURL`/`model` point at
107
- * Ollama's defaults) — and throw a `ConfigError` with migration guidance
108
- * instead of letting it validate under a silently reinterpreted provider.
109
- * Runs before `migrateRawConfig` so a legacy `ollama.*` block is never
110
- * converted into an `embedding` block first. Pure — never mutates `raw`.
111
- */
112
90
  export declare function detectFormerOllamaConfig(raw: unknown): void;
113
- /**
114
- * Migrate a legacy raw config object to the current shape, in place of a
115
- * versioned migration system. Pure — returns a new object, never mutates input.
116
- *
117
- * - Removed translation keys (`search.autoTranslate`, `search.translateModel`)
118
- * are dropped so they neither validate-fail nor take effect.
119
- * - Legacy `reranker.provider: 'ollama'` (a dead value — Ollama has no rerank
120
- * API) is rewritten to `openai-compatible`, and a stale `/api/rerank` URL is
121
- * dropped so it re-derives to `/rerank`. Otherwise the tightened enum would
122
- * fail validation on existing configs and lock the user out.
123
- *
124
- * A legacy top-level `ollama` block reaches here only when `detectFormerOllamaConfig`
125
- * did not already reject it (i.e. an `embedding` block is also present); it is
126
- * dropped rather than merged. Exported for unit testing.
127
- */
128
91
  export declare function migrateRawConfig(raw: unknown): unknown;
129
- /**
130
- * Validate a raw (pre-migration) config object against the Zod schema: first
131
- * `detectFormerOllamaConfig` (a `ConfigError` on a former-Ollama config takes
132
- * priority over both migration and the regular Zod enum rejection), then
133
- * `migrateRawConfig`, then the schema itself. Unknown keys (e.g. the "$schema"
134
- * editor reference) are stripped. Throws with a caller-friendly message on
135
- * validation failure. The single entry point for both `readConfigJson` and
136
- * `PUT /api/config`, so both apply identical rules with an identical message.
137
- * Pure — no file I/O.
138
- */
139
92
  export declare function parseConfig(raw: unknown, source?: string): JsonConfig;
140
- /** Build the JSON Schema object generated from the Zod config schema. Pure. */
141
93
  export declare function buildJsonSchema(): Record<string, unknown>;
142
94
  export declare function writeConfigJson(data: JsonConfig): void;
143
- /**
144
- * Generate a JSON Schema from the Zod config schema and write it next to
145
- * config.json. Editors resolve it via the "$schema" reference and provide
146
- * key autocomplete, type hints, and inline validation while editing.
147
- */
148
95
  export declare function writeSchemaFile(): void;
149
96
  export declare function getNestedValue(cfg: JsonConfig, key: string): unknown;
150
97
  export declare function setNestedValue(cfg: JsonConfig, key: string, raw: string): JsonConfig;
@@ -54,11 +54,7 @@ exports.CONFIG_DIR = path.join(os.homedir(), '.config', 'codesearch-mcp');
54
54
  exports.CONFIG_FILE = path.join(exports.CONFIG_DIR, 'config.json');
55
55
  exports.SCHEMA_FILE = path.join(exports.CONFIG_DIR, 'config.schema.json');
56
56
  exports.ENV_FILE = path.join(exports.CONFIG_DIR, '.env');
57
- // Relative $schema reference written into config.json so editors resolve the
58
- // sibling schema file for autocomplete / inline validation.
59
57
  const SCHEMA_REF = './config.schema.json';
60
- // All fields optional — defaults live in config.ts. The file stays minimal.
61
- // Descriptions surface as tooltips in schema-aware editors (VS Code, JetBrains).
62
58
  const jsonSchema = zod_1.z.object({
63
59
  db: zod_1.z.object({
64
60
  host: zod_1.z.string().describe('PostgreSQL host').optional(),
@@ -88,11 +84,11 @@ const jsonSchema = zod_1.z.object({
88
84
  maxLimit: zod_1.z.number().int().describe('Hard cap on results (default 20)').optional(),
89
85
  }).optional(),
90
86
  reranker: zod_1.z.object({
91
- enabled: zod_1.z.boolean().describe('Enable cross-encoder reranking; requires reranker.model').optional(),
92
- provider: zod_1.z.enum(['openai-compatible']).describe('Rerank backend. Only openai-compatible /rerank servers (Infinity, TEI, vLLM).').optional(),
93
- baseURL: zod_1.z.string().describe('Rerank server base URL (defaults to embedding.baseURL — Infinity serves both).').optional(),
94
- apiKey: zod_1.z.string().describe('API key, or env:VAR_NAME. Empty for local servers.').optional(),
95
- model: zod_1.z.string().describe('Reranker model name (e.g. qllama/bge-reranker-v2-m3)').optional(),
87
+ enabled: zod_1.z.boolean().describe('Enable cross-encoder reranking (default true with infinity embedding or when reranker.provider/baseURL/url is set, else false)').optional(),
88
+ provider: zod_1.z.enum(['openai-compatible', 'voyage']).describe('Rerank backend: openai-compatible /rerank servers (Infinity, vLLM) or voyage (Voyage AI, requires reranker.apiKey) (default openai-compatible)').optional(),
89
+ baseURL: zod_1.z.string().describe('Rerank server base URL (default embedding.baseURL for openai-compatible — Infinity serves both; https://api.voyageai.com/v1 for voyage)').optional(),
90
+ apiKey: zod_1.z.string().describe('API key, or env:VAR_NAME. Empty for local servers; required for voyage.').optional(),
91
+ model: zod_1.z.string().describe('Reranker model name (default mixedbread-ai/mxbai-rerank-xsmall-v1 for openai-compatible, rerank-3 for voyage)').optional(),
96
92
  url: zod_1.z.string().describe('Full URL of the rerank endpoint (default derived from provider + baseURL)').optional(),
97
93
  overfetch: zod_1.z.number().int().min(1).max(20).describe('Candidate multiplier before reranking (limit × N, default 5)').optional(),
98
94
  candidateFloor: zod_1.z.number().int().min(1).describe('Minimum candidate pool fed to the reranker regardless of limit; fetchLimit = min(max(limit × overfetch, candidateFloor), 200) (default 80)').optional(),
@@ -100,7 +96,6 @@ const jsonSchema = zod_1.z.object({
100
96
  logging: zod_1.z.object({
101
97
  level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).describe('File log level (default error)').optional(),
102
98
  dir: zod_1.z.string().describe('Base log directory; "false" to disable file logging').optional(),
103
- searchMetrics: zod_1.z.boolean().describe('Emit one structured search-tuning line (info level) per search, independent of level (default false)').optional(),
104
99
  }).optional(),
105
100
  workspaceRoot: zod_1.z.string().describe('Full path to the directory containing all your projects').optional(),
106
101
  });
@@ -134,22 +129,9 @@ const FIELD_TYPES = {
134
129
  'reranker.candidateFloor': 'number',
135
130
  'logging.level': 'string',
136
131
  'logging.dir': 'string',
137
- 'logging.searchMetrics': 'boolean',
138
132
  'workspaceRoot': 'string',
139
133
  };
140
134
  exports.ALL_KEYS = Object.keys(FIELD_TYPES);
141
- /**
142
- * Single source of truth for built-in defaults, keyed by raw config.json dotted
143
- * key. Consumed both by the web dashboard (`GET /api/defaults`) and by
144
- * `resolvedSchema` in `src/config.ts`, whose `.default(...)` calls reference
145
- * these values — so the runtime default and the dashboard-displayed default
146
- * cannot diverge. `as const` keeps the literal types so those `.default()`
147
- * lookups stay type-safe. Keys omitted here have no static default: required
148
- * fields (db.host/user/password) and values derived at runtime — db.name →
149
- * rag_<user>, and the provider-dependent URLs (embedding.baseURL,
150
- * reranker.baseURL, reranker.url), which are computed in the `Config`
151
- * constructor per provider rather than through a schema `.default(...)`.
152
- */
153
135
  exports.CONFIG_DEFAULTS = {
154
136
  'db.port': 5433,
155
137
  'embedding.provider': 'infinity',
@@ -171,7 +153,6 @@ exports.CONFIG_DEFAULTS = {
171
153
  'reranker.overfetch': 5,
172
154
  'reranker.candidateFloor': 80,
173
155
  'logging.level': 'error',
174
- 'logging.searchMetrics': false,
175
156
  };
176
157
  function readConfigJson() {
177
158
  if (!fs.existsSync(exports.CONFIG_FILE))
@@ -181,7 +162,7 @@ function readConfigJson() {
181
162
  raw = fs.readFileSync(exports.CONFIG_FILE, 'utf-8');
182
163
  }
183
164
  catch (e) {
184
- throw new Error(`Cannot read ${exports.CONFIG_FILE}: ${e.message}`);
165
+ throw new Error(`Cannot read ${exports.CONFIG_FILE}: ${e.message}`, { cause: e });
185
166
  }
186
167
  let parsed;
187
168
  try {
@@ -189,7 +170,7 @@ function readConfigJson() {
189
170
  }
190
171
  catch (e) {
191
172
  throw new Error(`Invalid JSON in ${exports.CONFIG_FILE}: ${e.message}\n` +
192
- `Fix the file manually or delete it and run: rag-setup`);
173
+ `Fix the file manually or delete it and run: rag-setup`, { cause: e });
193
174
  }
194
175
  return parseConfig(parsed, exports.CONFIG_FILE);
195
176
  }
@@ -198,19 +179,9 @@ const OLLAMA_MIGRATION_REMEDY = 'The "ollama" embedding provider has been remove
198
179
  'and embedding.baseURL to "http://localhost:11434/v1" — via `rag-config edit` (or by ' +
199
180
  'editing the config file directly) or by re-running `rag-setup`. After switching, rebuild ' +
200
181
  'the index with `rag-index --reindex`.';
201
- /** Strip a trailing `:tag` (Docker-style) from a model name, if present. */
202
182
  function stripModelTag(model) {
203
183
  return model.replace(/:[^:]*$/, '');
204
184
  }
205
- /**
206
- * Detect a raw (pre-migration) config object that still selects the removed
207
- * `ollama` embedding provider — explicitly, via a legacy top-level `ollama`
208
- * block, or implicitly (no `provider` set, but `baseURL`/`model` point at
209
- * Ollama's defaults) — and throw a `ConfigError` with migration guidance
210
- * instead of letting it validate under a silently reinterpreted provider.
211
- * Runs before `migrateRawConfig` so a legacy `ollama.*` block is never
212
- * converted into an `embedding` block first. Pure — never mutates `raw`.
213
- */
214
185
  function detectFormerOllamaConfig(raw) {
215
186
  if (raw == null || typeof raw !== 'object')
216
187
  return;
@@ -227,7 +198,7 @@ function detectFormerOllamaConfig(raw) {
227
198
  try {
228
199
  port = new URL(e.baseURL).port;
229
200
  }
230
- catch { /* unparsable — not a signal */ }
201
+ catch { }
231
202
  if (port === '11434') {
232
203
  throw new config_error_1.ConfigError(`Config's embedding.baseURL targets Ollama's default port (11434) with no embedding.provider set. ${OLLAMA_MIGRATION_REMEDY}`);
233
204
  }
@@ -242,21 +213,6 @@ function detectFormerOllamaConfig(raw) {
242
213
  throw new config_error_1.ConfigError(`Config has a legacy "ollama" configuration block. ${OLLAMA_MIGRATION_REMEDY}`);
243
214
  }
244
215
  }
245
- /**
246
- * Migrate a legacy raw config object to the current shape, in place of a
247
- * versioned migration system. Pure — returns a new object, never mutates input.
248
- *
249
- * - Removed translation keys (`search.autoTranslate`, `search.translateModel`)
250
- * are dropped so they neither validate-fail nor take effect.
251
- * - Legacy `reranker.provider: 'ollama'` (a dead value — Ollama has no rerank
252
- * API) is rewritten to `openai-compatible`, and a stale `/api/rerank` URL is
253
- * dropped so it re-derives to `/rerank`. Otherwise the tightened enum would
254
- * fail validation on existing configs and lock the user out.
255
- *
256
- * A legacy top-level `ollama` block reaches here only when `detectFormerOllamaConfig`
257
- * did not already reject it (i.e. an `embedding` block is also present); it is
258
- * dropped rather than merged. Exported for unit testing.
259
- */
260
216
  function migrateRawConfig(raw) {
261
217
  if (raw == null || typeof raw !== 'object')
262
218
  return raw;
@@ -274,16 +230,6 @@ function migrateRawConfig(raw) {
274
230
  }
275
231
  return obj;
276
232
  }
277
- /**
278
- * Validate a raw (pre-migration) config object against the Zod schema: first
279
- * `detectFormerOllamaConfig` (a `ConfigError` on a former-Ollama config takes
280
- * priority over both migration and the regular Zod enum rejection), then
281
- * `migrateRawConfig`, then the schema itself. Unknown keys (e.g. the "$schema"
282
- * editor reference) are stripped. Throws with a caller-friendly message on
283
- * validation failure. The single entry point for both `readConfigJson` and
284
- * `PUT /api/config`, so both apply identical rules with an identical message.
285
- * Pure — no file I/O.
286
- */
287
233
  function parseConfig(raw, source = exports.CONFIG_FILE) {
288
234
  detectFormerOllamaConfig(raw);
289
235
  const migrated = migrateRawConfig(raw);
@@ -295,7 +241,6 @@ function parseConfig(raw, source = exports.CONFIG_FILE) {
295
241
  }
296
242
  return result.data;
297
243
  }
298
- /** Build the JSON Schema object generated from the Zod config schema. Pure. */
299
244
  function buildJsonSchema() {
300
245
  return {
301
246
  $id: 'https://github.com/oleksandr-antoniuk/RAG/config.schema.json',
@@ -306,15 +251,9 @@ function buildJsonSchema() {
306
251
  function writeConfigJson(data) {
307
252
  fs.mkdirSync(exports.CONFIG_DIR, { recursive: true });
308
253
  writeSchemaFile();
309
- // $schema first so editors pick it up; readConfigJson strips it on validation.
310
254
  const withSchema = { $schema: SCHEMA_REF, ...data };
311
255
  fs.writeFileSync(exports.CONFIG_FILE, JSON.stringify(withSchema, null, 2) + '\n', { mode: 0o600 });
312
256
  }
313
- /**
314
- * Generate a JSON Schema from the Zod config schema and write it next to
315
- * config.json. Editors resolve it via the "$schema" reference and provide
316
- * key autocomplete, type hints, and inline validation while editing.
317
- */
318
257
  function writeSchemaFile() {
319
258
  fs.mkdirSync(exports.CONFIG_DIR, { recursive: true });
320
259
  fs.writeFileSync(exports.SCHEMA_FILE, JSON.stringify(buildJsonSchema(), null, 2) + '\n');
@@ -1,13 +1,12 @@
1
+ import { type JsonConfig } from './config-store';
1
2
  import { ConfigError } from './config-error';
2
3
  export { ConfigError };
3
- /**
4
- * Resolve a config value that may reference an environment variable. A value of
5
- * the form `env:VAR_NAME` resolves to `process.env.VAR_NAME`; any other string
6
- * (including empty) is returned verbatim. A referenced-but-unset variable throws
7
- * a clear error naming the variable and the `.env` file. Pure aside from the
8
- * injected env map. See `src/app.ts` for how `.env` is loaded into the process.
9
- */
10
4
  export declare function resolveEnvRef(value: string, env?: NodeJS.ProcessEnv): string;
5
+ export declare function rerankContext(raw: JsonConfig): {
6
+ wanted: boolean;
7
+ missingKey: boolean;
8
+ model: string;
9
+ };
11
10
  export declare class Config {
12
11
  private readonly data;
13
12
  get db(): {
@@ -17,13 +16,6 @@ export declare class Config {
17
16
  password: string;
18
17
  database: string;
19
18
  };
20
- /**
21
- * Embedding config. `apiKey` is returned **raw** (possibly an `env:VAR` ref) —
22
- * callers resolve it lazily via `resolveEnvRef` at the moment they build a
23
- * request, so an unset referenced variable fails only when the key is actually
24
- * needed (during embed/search) rather than crashing container construction and
25
- * locking the user out of `rag-web`/`rag-mcp`.
26
- */
27
19
  get embedding(): {
28
20
  provider: "openai-compatible" | "voyage" | "infinity";
29
21
  baseURL: string;
@@ -32,14 +24,14 @@ export declare class Config {
32
24
  dimensions: number | null;
33
25
  batchSize: number;
34
26
  };
35
- /** Reranker config. `apiKey` is raw — resolve lazily with `resolveEnvRef`. */
36
27
  get reranker(): {
37
28
  enabled: boolean;
38
- provider: "openai-compatible";
29
+ provider: "openai-compatible" | "voyage";
39
30
  baseURL: string;
40
31
  apiKey: string;
41
32
  model: string;
42
33
  url: string;
34
+ missingApiKey: boolean;
43
35
  overfetch: number;
44
36
  candidateFloor: number;
45
37
  };
@@ -57,7 +49,6 @@ export declare class Config {
57
49
  };
58
50
  get logging(): {
59
51
  level: "error" | "debug" | "info" | "warn";
60
- searchMetrics: boolean;
61
52
  dir?: string | undefined;
62
53
  };
63
54
  get workspaceRoot(): string;
@@ -35,18 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Config = exports.ConfigError = void 0;
37
37
  exports.resolveEnvRef = resolveEnvRef;
38
+ exports.rerankContext = rerankContext;
38
39
  const os = __importStar(require("os"));
39
40
  const zod_1 = require("zod");
40
41
  const config_store_1 = require("./config-store");
42
+ const constants_1 = require("./constants");
41
43
  const config_error_1 = require("./config-error");
42
44
  Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return config_error_1.ConfigError; } });
43
- /**
44
- * Resolve a config value that may reference an environment variable. A value of
45
- * the form `env:VAR_NAME` resolves to `process.env.VAR_NAME`; any other string
46
- * (including empty) is returned verbatim. A referenced-but-unset variable throws
47
- * a clear error naming the variable and the `.env` file. Pure aside from the
48
- * injected env map. See `src/app.ts` for how `.env` is loaded into the process.
49
- */
50
45
  function resolveEnvRef(value, env = process.env) {
51
46
  if (!value.startsWith('env:'))
52
47
  return value;
@@ -66,12 +61,19 @@ const DEFAULT_BASE_URLS = {
66
61
  function defaultBaseURL(provider) {
67
62
  return DEFAULT_BASE_URLS[provider] ?? DEFAULT_BASE_URLS.infinity;
68
63
  }
69
- // Reranking is served only by OpenAI-compatible `/rerank` endpoints (Infinity,
70
- // TEI, vLLM, Jina/Cohere-shape) — a single endpoint shape. Normalize a trailing
71
- // `/embeddings` or `/v1` to the server root so a baseURL written with an
72
- // OpenAI-style suffix still yields `<root>/rerank`.
73
- function deriveRerankUrl(baseURL) {
74
- const root = baseURL.replace(/\/+$/, '').replace(/\/(embeddings|v1)$/, '');
64
+ function rerankContext(raw) {
65
+ const rr = raw.reranker;
66
+ const embProvider = raw.embedding?.provider ?? config_store_1.CONFIG_DEFAULTS['embedding.provider'];
67
+ const isVoyage = (rr?.provider ?? config_store_1.CONFIG_DEFAULTS['reranker.provider']) === 'voyage';
68
+ return {
69
+ wanted: embProvider === 'infinity' || rr?.provider != null || rr?.baseURL != null || rr?.url != null,
70
+ missingKey: isVoyage && !rr?.apiKey,
71
+ model: isVoyage ? constants_1.VOYAGE_RERANK_MODELS[0] : config_store_1.CONFIG_DEFAULTS['reranker.model'],
72
+ };
73
+ }
74
+ function deriveRerankUrl(provider, baseURL) {
75
+ const trimmed = baseURL.replace(/\/+$/, '');
76
+ const root = provider === 'voyage' ? trimmed : trimmed.replace(/\/(embeddings|v1)$/, '');
75
77
  return `${root}/rerank`;
76
78
  }
77
79
  const resolvedSchema = zod_1.z.object({
@@ -91,12 +93,13 @@ const resolvedSchema = zod_1.z.object({
91
93
  batchSize: zod_1.z.number().int().positive().default(config_store_1.CONFIG_DEFAULTS['embedding.batchSize']),
92
94
  }),
93
95
  reranker: zod_1.z.object({
94
- enabled: zod_1.z.boolean().default(config_store_1.CONFIG_DEFAULTS['reranker.enabled']),
95
- provider: zod_1.z.enum(['openai-compatible']).default(config_store_1.CONFIG_DEFAULTS['reranker.provider']),
96
+ enabled: zod_1.z.boolean(),
97
+ provider: zod_1.z.enum(['openai-compatible', 'voyage']).default(config_store_1.CONFIG_DEFAULTS['reranker.provider']),
96
98
  baseURL: zod_1.z.string().min(1),
97
99
  apiKey: zod_1.z.string().default(config_store_1.CONFIG_DEFAULTS['reranker.apiKey']),
98
- model: zod_1.z.string().default(config_store_1.CONFIG_DEFAULTS['reranker.model']),
100
+ model: zod_1.z.string(),
99
101
  url: zod_1.z.string().min(1),
102
+ missingApiKey: zod_1.z.boolean(),
100
103
  overfetch: zod_1.z.number().int().min(1).max(20).default(config_store_1.CONFIG_DEFAULTS['reranker.overfetch']),
101
104
  candidateFloor: zod_1.z.number().int().min(1).default(config_store_1.CONFIG_DEFAULTS['reranker.candidateFloor']),
102
105
  }),
@@ -115,22 +118,13 @@ const resolvedSchema = zod_1.z.object({
115
118
  logging: zod_1.z.object({
116
119
  level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).default(config_store_1.CONFIG_DEFAULTS['logging.level']),
117
120
  dir: zod_1.z.string().optional(),
118
- searchMetrics: zod_1.z.boolean().default(config_store_1.CONFIG_DEFAULTS['logging.searchMetrics']),
119
121
  }),
120
122
  workspaceRoot: zod_1.z.string().default(''),
121
123
  });
122
124
  class Config {
123
125
  data;
124
126
  get db() { return this.data.db; }
125
- /**
126
- * Embedding config. `apiKey` is returned **raw** (possibly an `env:VAR` ref) —
127
- * callers resolve it lazily via `resolveEnvRef` at the moment they build a
128
- * request, so an unset referenced variable fails only when the key is actually
129
- * needed (during embed/search) rather than crashing container construction and
130
- * locking the user out of `rag-web`/`rag-mcp`.
131
- */
132
127
  get embedding() { return this.data.embedding; }
133
- /** Reranker config. `apiKey` is raw — resolve lazily with `resolveEnvRef`. */
134
128
  get reranker() { return this.data.reranker; }
135
129
  get indexer() { return this.data.indexer; }
136
130
  get search() { return this.data.search; }
@@ -142,8 +136,15 @@ class Config {
142
136
  throw new config_error_1.ConfigError('Config not found. Run rag-setup.');
143
137
  const embProvider = (raw.embedding?.provider ?? config_store_1.CONFIG_DEFAULTS['embedding.provider']);
144
138
  const embBaseURL = raw.embedding?.baseURL ?? defaultBaseURL(embProvider);
145
- const rerankBaseURL = raw.reranker?.baseURL ?? embBaseURL;
146
- const rerankUrl = raw.reranker?.url ?? deriveRerankUrl(rerankBaseURL);
139
+ const rr = raw.reranker;
140
+ const rerankProvider = (rr?.provider ?? config_store_1.CONFIG_DEFAULTS['reranker.provider']);
141
+ const isVoyageRerank = rerankProvider === 'voyage';
142
+ const rerankBaseURL = rr?.baseURL ?? (isVoyageRerank ? DEFAULT_BASE_URLS.voyage : embBaseURL);
143
+ const rerankUrl = rr?.url ?? deriveRerankUrl(rerankProvider, rerankBaseURL);
144
+ const rerankCtx = rerankContext(raw);
145
+ const rerankModel = rr?.model ?? rerankCtx.model;
146
+ const rerankWanted = rr?.enabled ?? rerankCtx.wanted;
147
+ const missingApiKey = rerankWanted && rerankCtx.missingKey;
147
148
  const result = resolvedSchema.safeParse({
148
149
  db: {
149
150
  host: raw.db?.host,
@@ -161,14 +162,15 @@ class Config {
161
162
  batchSize: raw.embedding?.batchSize,
162
163
  },
163
164
  reranker: {
164
- enabled: raw.reranker?.enabled,
165
- provider: raw.reranker?.provider,
165
+ enabled: rerankWanted && !missingApiKey,
166
+ provider: rr?.provider,
166
167
  baseURL: rerankBaseURL,
167
- apiKey: raw.reranker?.apiKey,
168
- model: raw.reranker?.model,
168
+ apiKey: rr?.apiKey,
169
+ model: rerankModel,
169
170
  url: rerankUrl,
170
- overfetch: raw.reranker?.overfetch,
171
- candidateFloor: raw.reranker?.candidateFloor,
171
+ missingApiKey,
172
+ overfetch: rr?.overfetch,
173
+ candidateFloor: rr?.candidateFloor,
172
174
  },
173
175
  indexer: {
174
176
  chunkSize: raw.indexing?.chunkSize,
@@ -185,7 +187,6 @@ class Config {
185
187
  logging: {
186
188
  level: raw.logging?.level,
187
189
  dir: raw.logging?.dir,
188
- searchMetrics: raw.logging?.searchMetrics,
189
190
  },
190
191
  workspaceRoot: raw.workspaceRoot,
191
192
  });
@@ -1,42 +1,16 @@
1
1
  export type EmbeddingProvider = 'openai-compatible' | 'voyage' | 'infinity';
2
- /**
3
- * pgvector's HNSW / IVFFlat index hard limit. The effective embedding dimension
4
- * must not exceed this; larger cloud models must reduce via the Matryoshka
5
- * `dimensions` request parameter (`embedding.dimensions`).
6
- */
2
+ export type RerankProvider = 'openai-compatible' | 'voyage';
3
+ export declare const VOYAGE_RERANK_MODELS: readonly ["rerank-3", "rerank-3-lite"];
7
4
  export declare const MAX_EMBED_DIM = 2000;
8
5
  export interface ModelInfo {
9
6
  provider: EmbeddingProvider;
10
- /** Native / default output width. */
11
7
  dimensions: number;
12
- /** Model supports Matryoshka dimension reduction via a request parameter. */
13
8
  dimConfigurable?: boolean;
14
- /** Max width that can be indexed under the pgvector cap (advisory). */
15
9
  maxIndexable?: number;
16
- /** Max width the model itself can emit (advisory). */
17
10
  maxDim?: number;
18
11
  }
19
- /**
20
- * Advisory (NOT exhaustive) registry of known embedding models. Used to resolve
21
- * a model's dimension without a runtime probe and to suggest models in the
22
- * dashboard. Models absent here still work — their dimension is probed at index
23
- * time (see `resolveDimension`).
24
- */
25
12
  export declare const MODEL_REGISTRY: Record<string, ModelInfo>;
26
13
  export declare const DEFAULT_EMBED_MODEL = "jinaai/jina-embeddings-v2-base-code";
27
- /**
28
- * Former Ollama registry model names, kept only so config validation can
29
- * recognise an implicit (provider-less) config that still targets Ollama —
30
- * see `detectFormerOllamaConfig` in `config-store.ts`.
31
- */
32
14
  export declare const FORMER_OLLAMA_MODELS: readonly ["bge-m3", "nomic-embed-text", "mxbai-embed-large"];
33
- /** Advisory dimension for a model, or `undefined` when the model is unknown. */
34
15
  export declare function registryDimension(model: string): number | undefined;
35
- /**
36
- * Resolve the effective vector dimension: explicit override → registry → probe.
37
- * Validates the result is ≤ MAX_EMBED_DIM, throwing an error that names
38
- * `embedding.dimensions` as the fix. `probeFn` embeds a short string once and
39
- * returns the observed length; only called when neither override nor registry
40
- * yields a value. Pure aside from the injected probe.
41
- */
42
16
  export declare function resolveDimension(override: number | null | undefined, model: string, probeFn: () => Promise<number>, registry?: Record<string, ModelInfo>): Promise<number>;