codesearch-mcp 0.1.0-alpha → 0.2.1-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 (64) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +146 -41
  3. package/dist/app.js +4 -19
  4. package/dist/src/commands/IndexCommand.js +68 -7
  5. package/dist/src/config-error.d.ts +7 -0
  6. package/dist/src/config-error.js +14 -0
  7. package/dist/src/config-store.d.ts +30 -12
  8. package/dist/src/config-store.js +81 -26
  9. package/dist/src/config.d.ts +5 -4
  10. package/dist/src/config.js +14 -16
  11. package/dist/src/constants.d.ts +8 -2
  12. package/dist/src/constants.js +8 -5
  13. package/dist/src/db/index.d.ts +1 -1
  14. package/dist/src/db/index.js +2 -1
  15. package/dist/src/db/schema.d.ts +75 -0
  16. package/dist/src/db/schema.js +10 -1
  17. package/dist/src/enriched-prompt.d.ts +8 -0
  18. package/dist/src/enriched-prompt.js +87 -0
  19. package/dist/src/help.js +1 -1
  20. package/dist/src/interfaces/IAstParser.d.ts +18 -2
  21. package/dist/src/interfaces/IDatabase.d.ts +31 -0
  22. package/dist/src/interfaces/ILogger.d.ts +7 -0
  23. package/dist/src/interfaces/ISearcher.d.ts +3 -0
  24. package/dist/src/services/AstChunker.d.ts +11 -0
  25. package/dist/src/services/AstChunker.js +44 -0
  26. package/dist/src/services/AstParserService.d.ts +39 -7
  27. package/dist/src/services/AstParserService.js +504 -104
  28. package/dist/src/services/DatabaseService.d.ts +17 -0
  29. package/dist/src/services/DatabaseService.js +130 -2
  30. package/dist/src/services/EmbedderFactory.d.ts +1 -1
  31. package/dist/src/services/EmbedderFactory.js +2 -5
  32. package/dist/src/services/IndexerService.d.ts +4 -14
  33. package/dist/src/services/IndexerService.js +54 -156
  34. package/dist/src/services/InfinityEmbedder.d.ts +14 -55
  35. package/dist/src/services/InfinityEmbedder.js +30 -141
  36. package/dist/src/services/LoggerService.d.ts +3 -1
  37. package/dist/src/services/LoggerService.js +17 -3
  38. package/dist/src/services/McpServer.js +3 -3
  39. package/dist/src/services/SearcherService.d.ts +39 -4
  40. package/dist/src/services/SearcherService.js +254 -81
  41. package/dist/src/services/WebServerService.d.ts +41 -3
  42. package/dist/src/services/WebServerService.js +90 -2
  43. package/dist/src/services/infinity-provisioning.d.ts +115 -0
  44. package/dist/src/services/infinity-provisioning.js +516 -0
  45. package/dist/src/services/legacy-ollama-index.d.ts +24 -0
  46. package/dist/src/services/legacy-ollama-index.js +61 -0
  47. package/dist/src/services/reranker.d.ts +3 -3
  48. package/dist/src/services/reranker.js +3 -3
  49. package/dist/src/setup.d.ts +37 -0
  50. package/dist/src/setup.js +111 -157
  51. package/dist/src/symbol-name.d.ts +44 -0
  52. package/dist/src/symbol-name.js +68 -0
  53. package/dist/src/symbol-rows.d.ts +12 -0
  54. package/dist/src/symbol-rows.js +38 -0
  55. package/dist/src/tools/FindSymbolTool.js +30 -7
  56. package/dist/src/tools/GetCallersTool.js +20 -5
  57. package/docker/docker-compose.yml +5 -15
  58. package/docs/architecture.svg +103 -0
  59. package/docs/web-dashboard.md +36 -17
  60. package/package.json +1 -6
  61. package/dist/src/commands/BackfillCommand.d.ts +0 -7
  62. package/dist/src/commands/BackfillCommand.js +0 -37
  63. package/dist/src/services/OllamaEmbedder.d.ts +0 -25
  64. package/dist/src/services/OllamaEmbedder.js +0 -111
@@ -33,167 +33,56 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.InfinityEmbedder = void 0;
37
- exports.serverRoot = serverRoot;
38
- exports.probeInfinityHealth = probeInfinityHealth;
39
- exports.listInfinityModels = listInfinityModels;
40
- const fs = __importStar(require("fs"));
41
- const path = __importStar(require("path"));
42
- const child_process_1 = require("child_process");
36
+ exports.InfinityEmbedder = exports.listInfinityModels = exports.probeInfinityHealth = exports.serverRoot = void 0;
43
37
  const LoggerService_1 = require("./LoggerService");
44
38
  const config_store_1 = require("../config-store");
45
39
  const OpenAICompatibleEmbedder_1 = require("./OpenAICompatibleEmbedder");
46
- /**
47
- * Strip trailing slashes and a trailing `/embeddings` or `/v1` to get the server
48
- * root — Infinity serves `/health`, `/models`, `/rerank`, `/embeddings` from the
49
- * root, so a baseURL written with an OpenAI-style suffix must be normalized here
50
- * to keep the probe/models URLs aligned with the embeddings URL.
51
- */
52
- function serverRoot(baseURL) {
53
- return baseURL.replace(/\/+$/, '').replace(/\/(embeddings|v1)$/, '');
54
- }
55
- /**
56
- * Probe an Infinity-style `/health` endpoint. Resolves `true` when the server
57
- * answers with a 2xx, `false` on any error or non-2xx. Reused by setup's
58
- * `waitForInfinity`.
59
- */
60
- async function probeInfinityHealth(baseURL, fetchImpl = fetch, timeoutMs = 2000) {
61
- try {
62
- const res = await fetchImpl(`${serverRoot(baseURL)}/health`, {
63
- method: 'GET',
64
- signal: AbortSignal.timeout(timeoutMs),
65
- });
66
- return res.ok;
67
- }
68
- catch {
69
- return false;
70
- }
71
- }
72
- /**
73
- * List the model ids an Infinity server currently serves via its OpenAI-style
74
- * `/models` listing (`{ data: [{ id }] }`). Returns `[]` on any error.
75
- */
76
- async function listInfinityModels(baseURL, fetchImpl = fetch, timeoutMs = 5000) {
77
- try {
78
- const res = await fetchImpl(`${serverRoot(baseURL)}/models`, {
79
- method: 'GET',
80
- signal: AbortSignal.timeout(timeoutMs),
81
- });
82
- if (!res.ok)
83
- return [];
84
- const data = await res.json();
85
- return (data.data ?? []).map(m => m.id).filter((id) => Boolean(id));
86
- }
87
- catch {
88
- return [];
89
- }
90
- }
40
+ const infinity_provisioning_1 = require("./infinity-provisioning");
41
+ Object.defineProperty(exports, "serverRoot", { enumerable: true, get: function () { return infinity_provisioning_1.serverRoot; } });
42
+ Object.defineProperty(exports, "probeInfinityHealth", { enumerable: true, get: function () { return infinity_provisioning_1.probeInfinityHealth; } });
43
+ Object.defineProperty(exports, "listInfinityModels", { enumerable: true, get: function () { return infinity_provisioning_1.listInfinityModels; } });
44
+ const path = __importStar(require("path"));
91
45
  /**
92
46
  * Embedder for a local Infinity server. Inherits the OpenAI-style `/embeddings`
93
47
  * HTTP path, batching, retry, and reranker wiring from `OpenAICompatibleEmbedder`
94
- * unchanged — it adds ONLY container-readiness behavior (`ensureModelAvailable`),
48
+ * unchanged — it adds ONLY container-readiness behavior (`ensureModelAvailable`,
49
+ * `listModels`), delegating to the standalone `infinity-provisioning` primitive
95
50
  * because Infinity binds its model set at container startup and exposes no
96
51
  * runtime model-pull API.
97
52
  */
98
53
  class InfinityEmbedder extends OpenAICompatibleEmbedder_1.OpenAICompatibleEmbedder {
99
54
  base;
100
- doFetchImpl;
101
- runCommand;
102
55
  composePath;
103
- pollIntervalMs;
104
- log;
56
+ provisioningDeps;
105
57
  constructor(config, logger = LoggerService_1.LoggerService.silent(), deps = {}) {
106
58
  super(config, logger, deps);
107
- this.base = serverRoot(config.embedding.baseURL);
108
- this.doFetchImpl = deps.fetchImpl ?? fetch;
109
- this.runCommand = deps.runCommand ?? ((cmd, args, env) => {
110
- const r = (0, child_process_1.spawnSync)(cmd, args, { stdio: 'inherit', env: env ? { ...process.env, ...env } : process.env });
111
- return { status: r.status, stdout: r.stdout?.toString() };
112
- });
59
+ this.base = (0, infinity_provisioning_1.serverRoot)(config.embedding.baseURL);
113
60
  this.composePath = deps.composePath ?? path.join(config_store_1.CONFIG_DIR, 'docker-compose.yml');
114
- this.pollIntervalMs = deps.pollIntervalMs ?? 2000;
115
- this.log = logger;
116
- }
117
- isHealthy() {
118
- return probeInfinityHealth(this.base, this.doFetchImpl);
119
- }
120
- servedModels() {
121
- return listInfinityModels(this.base, this.doFetchImpl);
122
- }
123
- /** True when a managed compose file exists AND declares an `infinity` service. */
124
- hasManagedService() {
125
- try {
126
- const content = fs.readFileSync(this.composePath, 'utf-8');
127
- return /^\s*infinity:\s*$/m.test(content);
128
- }
129
- catch {
130
- return false;
131
- }
61
+ this.provisioningDeps = {
62
+ fetchImpl: deps.fetchImpl,
63
+ runCommand: deps.runCommand,
64
+ pollIntervalMs: deps.pollIntervalMs,
65
+ readinessTimeoutMs: deps.readinessTimeoutMs,
66
+ };
132
67
  }
133
68
  /**
134
- * Bring the Infinity backend to a serving state per the D4 decision tree:
135
- * healthy + model served → OK
136
- * healthy + model NOT served → throw (restart container with correct --model-id)
137
- * unreachable + managed → `docker compose up -d infinity`, wait /health
138
- * unreachable + external → print manual instructions (no Docker touched)
139
- *
140
- * Called by `IndexCommand` before `prepareEmbeddingSchema()`.
69
+ * Index-time embed readiness (see `local-backend-provisioning`): verifies only
70
+ * the embedding model — indexing does not use the reranker, so a missing
71
+ * reranker model never blocks or restarts indexing. Whenever a recreate is
72
+ * needed, the full configured set is written so an already-served reranker
73
+ * model is never dropped. Called by `IndexCommand` before indexing.
141
74
  */
142
75
  async ensureModelAvailable() {
143
- const model = this.config.embedding.model;
144
- if (await this.isHealthy()) {
145
- const served = await this.servedModels();
146
- // A server that lists no models (older Infinity, or listing disabled) is
147
- // trusted — the embed request will surface a real error if the model is
148
- // truly absent.
149
- if (served.length === 0 || served.includes(model))
150
- return;
151
- throw new Error(`Infinity at ${this.base} serves [${served.join(', ')}] but embedding.model is "${model}". ` +
152
- `Restart the container with the correct --model-id (e.g. recreate the managed container ` +
153
- `via rag-setup, or fix embedding.model).`);
154
- }
155
- // Unreachable.
156
- if (this.hasManagedService()) {
157
- console.log(`Infinity not reachable at ${this.base} — starting the managed container...`);
158
- this.log.info('InfinityEmbedder: starting managed container', { composePath: this.composePath });
159
- // Bind the SAME model set the config expects (embed + reranker when enabled),
160
- // so the auto-started container matches the config — otherwise compose would
161
- // (re)create it with the compose default and silently drop the reranker /
162
- // serve the wrong embed model. `--force-recreate` picks up a changed set.
163
- const modelIds = [
164
- this.config.embedding.model,
165
- ...(this.config.reranker.enabled && this.config.reranker.model ? [this.config.reranker.model] : []),
166
- ].join(';');
167
- const r = this.runCommand('docker', ['compose', '-f', this.composePath, 'up', '-d', '--force-recreate', 'infinity'], { INFINITY_MODEL_ID: modelIds });
168
- if (r.status !== 0) {
169
- throw new Error(`Failed to start the managed Infinity container (docker compose exited ${r.status}). ` +
170
- `Ensure Docker is running, or start Infinity manually.`);
171
- }
172
- await this.waitForHealthy();
173
- return;
174
- }
175
- throw new Error(`Infinity is not reachable at ${this.base} and no managed container is configured. ` +
176
- `Start it manually, e.g.:\n` +
177
- ` docker run -it --gpus all -p 7997:7997 michaelf34/infinity:latest \\\n` +
178
- ` v2 --model-id ${model} --port 7997\n` +
179
- `(drop --gpus all to run on CPU), or re-run rag-setup to provision it.`);
76
+ await (0, infinity_provisioning_1.ensureBackendReady)({
77
+ baseURL: this.config.embedding.baseURL,
78
+ requiredModels: [this.config.embedding.model],
79
+ modelSetToWrite: (0, infinity_provisioning_1.requiredModelSet)(this.config),
80
+ composePath: this.composePath,
81
+ deps: this.provisioningDeps,
82
+ });
180
83
  }
181
- /**
182
- * Poll `/health` until ready or the timeout elapses. Generous default (30 min)
183
- * because a CPU first boot downloads the model set and runs a lengthy per-model
184
- * warmup before `/health` flips to 200.
185
- */
186
- async waitForHealthy(maxWaitSec = 1800) {
187
- console.log('Waiting for Infinity to be ready (first boot downloads + warms up the model set, this can take several minutes on CPU)...');
188
- const deadline = Date.now() + maxWaitSec * 1000;
189
- while (Date.now() < deadline) {
190
- if (await this.isHealthy()) {
191
- console.log(' ✓ Infinity ready.');
192
- return;
193
- }
194
- await new Promise(r => setTimeout(r, this.pollIntervalMs));
195
- }
196
- throw new Error(`Infinity did not become ready within ${maxWaitSec}s at ${this.base}.`);
84
+ async listModels() {
85
+ return (0, infinity_provisioning_1.listInfinityModels)(this.base, this.provisioningDeps.fetchImpl ?? fetch, 2000);
197
86
  }
198
87
  }
199
88
  exports.InfinityEmbedder = InfinityEmbedder;
@@ -3,7 +3,8 @@ import type { ILogger } from '../interfaces/ILogger';
3
3
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
4
4
  export declare class LoggerService implements ILogger {
5
5
  private readonly logger;
6
- constructor(logger: pino.Logger);
6
+ private readonly metricsLogger;
7
+ constructor(logger: pino.Logger, metricsLogger?: pino.Logger | null);
7
8
  static silent(): LoggerService;
8
9
  static fromConfig(cfg: {
9
10
  level: string;
@@ -14,4 +15,5 @@ export declare class LoggerService implements ILogger {
14
15
  info(msg: string, data?: Record<string, unknown>): void;
15
16
  warn(msg: string, data?: Record<string, unknown>): void;
16
17
  error(msg: string, data?: Record<string, unknown>): void;
18
+ metric(msg: string, data?: Record<string, unknown>): void;
17
19
  }
@@ -44,8 +44,13 @@ const fs = __importStar(require("fs"));
44
44
  const DEFAULT_LOG_DIR = path.join(os.homedir(), '.config', 'codesearch-mcp', 'logs');
45
45
  class LoggerService {
46
46
  logger;
47
- constructor(logger) {
47
+ // Dedicated `info`-level channel for search telemetry, independent of the
48
+ // main logger's configured level. Null when file logging is disabled so
49
+ // metrics never reach stdout (owned by the MCP JSON transport).
50
+ metricsLogger;
51
+ constructor(logger, metricsLogger = null) {
48
52
  this.logger = logger;
53
+ this.metricsLogger = metricsLogger;
49
54
  }
50
55
  static silent() {
51
56
  return new LoggerService((0, pino_1.default)({ level: 'silent' }));
@@ -66,6 +71,10 @@ class LoggerService {
66
71
  static buildFromPath(level, filePath) {
67
72
  if (filePath)
68
73
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
74
+ // The transport target must accept `info` so the metrics channel can emit
75
+ // even when the main level is `warn`/`error`; the main logger still gates
76
+ // normal calls at `level`, so nothing else leaks below it.
77
+ const targetLevel = pino_1.default.levels.values[level] < pino_1.default.levels.values.info ? level : 'info';
69
78
  const transport = filePath
70
79
  ? pino_1.default.transport({
71
80
  targets: [{
@@ -79,18 +88,23 @@ class LoggerService {
79
88
  limit: { count: 30 },
80
89
  size: '50m',
81
90
  },
82
- level,
91
+ level: targetLevel,
83
92
  }],
84
93
  })
85
94
  : undefined;
86
95
  const logger = transport
87
96
  ? (0, pino_1.default)({ level }, transport)
88
97
  : (0, pino_1.default)({ level: 'silent' });
89
- return new LoggerService(logger);
98
+ // Child logger pinned at `info` regardless of the parent level; only used by
99
+ // metric(). Null when there is no file transport (default stdout must stay
100
+ // clean for the MCP transport).
101
+ const metricsLogger = transport ? logger.child({}, { level: 'info' }) : null;
102
+ return new LoggerService(logger, metricsLogger);
90
103
  }
91
104
  debug(msg, data) { this.logger.debug(data ?? {}, msg); }
92
105
  info(msg, data) { this.logger.info(data ?? {}, msg); }
93
106
  warn(msg, data) { this.logger.warn(data ?? {}, msg); }
94
107
  error(msg, data) { this.logger.error(data ?? {}, msg); }
108
+ metric(msg, data) { this.metricsLogger?.info(data ?? {}, msg); }
95
109
  }
96
110
  exports.LoggerService = LoggerService;
@@ -73,8 +73,8 @@ Pick one workflow based on what you need:
73
73
  - **read_file_range** — read a specific line range from any indexed file. Expects a **relative path** as returned by search_codebase.
74
74
  - **get_dependencies** — show what files a given file imports (direct or transitive). Use to understand what a file depends on.
75
75
  - **get_dependents** — show which files import a given file (reverse graph). Use before refactoring to find all callers.
76
- - **find_symbol** — find where a symbol (function, class, type, interface, variable, method) is defined by exact name. Faster than semantic search when you know the name.
77
- - **get_callers** — find all call sites of a function or method by exact name. Uses the call graph index — faster and more precise than grep. Use after find_symbol to trace where a symbol is actually called.
76
+ - **find_symbol** — find where a symbol is defined by exact name: top-level declarations, class/object-literal members (methods, get/set accessors, constructors, function properties), interface and type-literal method signatures, and namespace-level declarations. Accepts a bare name (\`assertUsersInOrg\`), a qualified name (\`RepairsService.assertUsersInOrg\`, \`NS.Parser.parse\`) to disambiguate same-named members, a quoted name for string-literal members (\`Router.'kebab-name'\`), or a computed name (\`Stack.[Symbol.iterator]\`). Faster than semantic search when you know the name.
77
+ - **get_callers** — find all call sites of a function or method by exact name. Accepts a bare or qualified (\`Class.method\`) symbol, using the same suffix matching as \`find_symbol\`. Uses the call graph index — faster and more precise than grep. A call through an interface-typed receiver is recorded under the interface member (e.g. \`IEmbedder.embedBatch\`), not the implementing class. Use after find_symbol to trace where a symbol is actually called.
78
78
  - **list_files** — browse the directory tree of a project path. Use to understand project structure before reading files.
79
79
  - **read_file** — read a complete file by **absolute path** with line numbers. Truncates at max_lines with a hint to continue.
80
80
 
@@ -86,7 +86,7 @@ Pick one workflow based on what you need:
86
86
  3. If a result looks **truncated or incomplete**, call \`read_file_range\` with the returned \`file_path\` and \`start_line\`.
87
87
  4. For exploratory searches where results span chunk boundaries, pass \`include_neighbors: true\`.
88
88
  5. To immediately see a file's imports and callers without extra tool calls, pass \`include_graph_context: true\` to \`search_codebase\`. Use this when doing a quick structural survey; switch to \`get_dependencies\`/\`get_dependents\` for full transitive graphs.
89
- 6. Use \`find_symbol\` when you know the exact name — faster than semantic search. Use \`kind: "method"\` for class methods — grep matches lines where \`public\`, \`async\`, \`static\`, or \`override\` appears before the name. After locating a symbol, use \`get_callers\` to find all call sites — faster and more precise than grep (requires \`rag-index backfill --calls\` to have been run).
89
+ 6. Use \`find_symbol\` when you know the exact name — faster than semantic search. Use a qualified name (\`Class.method\`) to disambiguate same-named methods of different classes; a bare name searches every container. Hits marked \`(grep — not in index; may be stale or imprecise)\` come from a fallback text search, not the index — treat them as less reliable. After locating a symbol, use \`get_callers\` to find all call sites — faster and more precise than grep.
90
90
  7. Use \`list_files\` to explore project structure before diving into files. Use \`read_file\` to read a complete file by absolute path.
91
91
 
92
92
  ### Flow audit (mandatory steps — do not skip 3 and 4)
@@ -55,7 +55,7 @@ export interface GrepMatch {
55
55
  filePath: string;
56
56
  line: number;
57
57
  }
58
- export type SymbolKind = 'function' | 'class' | 'type' | 'interface' | 'variable' | 'method' | 'all';
58
+ export type SymbolKind = 'function' | 'class' | 'type' | 'interface' | 'variable' | 'method' | 'get_accessor' | 'set_accessor' | 'constructor' | 'function_property' | 'method_signature' | 'all';
59
59
  export interface CallerRow {
60
60
  fromFile: string;
61
61
  fromSymbol: string | null;
@@ -69,6 +69,10 @@ export interface SymbolHit {
69
69
  column: number;
70
70
  kind: Exclude<SymbolKind, 'all'>;
71
71
  signature: string;
72
+ /** `container.name`, or `name` when the container is null. */
73
+ qualified: string;
74
+ isStatic: boolean;
75
+ source: 'index' | 'grep';
72
76
  }
73
77
  export declare class SearcherService {
74
78
  private db;
@@ -106,16 +110,38 @@ export declare class SearcherService {
106
110
  /**
107
111
  * Refuse when the configured embedding model disagrees with the one that built
108
112
  * the index — embedding a query into a foreign vector space returns noise. A
109
- * missing `index_meta` (un-migrated DB / not yet indexed) does not block.
113
+ * missing `index_meta` row with an empty (or altogether absent) `code_chunk`
114
+ * does not block — but a *populated* `code_chunk` with no `index_meta` row is a
115
+ * pre-`index_meta` install, treated as the legacy `ollama`/`bge-m3` index (same
116
+ * interpretation as the `rag-index` legacy-Ollama guard), read-only — this check
117
+ * never writes `index_meta`.
110
118
  */
111
119
  assertIndexMatches(): Promise<void>;
120
+ private compareIndexed;
112
121
  listProjects(): Promise<ProjectRow[]>;
113
122
  searchChunks(query: string, limit: number, project: string | null, threshold?: number): Promise<SearchResult[]>;
123
+ /**
124
+ * Emit one structured search-tuning line (via `logger.metric`, at `info`
125
+ * regardless of `logging.level`). `candidates` are in cosine order, so a
126
+ * candidate's array index is its cosine rank — `maxCosineRankSurvived` is the
127
+ * deepest cosine position that survived reranking into the returned set. Read
128
+ * it alongside `poolSaturated` to tune `candidateFloor`/`overfetch`/threshold
129
+ * per hardware. Never logs chunk content, embeddings, or credentials.
130
+ */
131
+ private emitSearchMetrics;
114
132
  fetchNeighbors(results: SearchResult[]): Promise<NeighborResult[]>;
115
133
  fetchGraphContext(results: SearchResult[]): Promise<Map<string, GraphContext>>;
116
134
  getDependencies(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
117
135
  getDependents(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
118
136
  getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
137
+ /**
138
+ * One-line warning when any project in `scope` (the given `project`, or
139
+ * every project with `code_chunk`/`code_symbol` rows) is outdated — shown
140
+ * first in `find_symbol`/`get_callers` output. Any failure to determine the
141
+ * scope or read markers only drops the notice (warn logged); the lookup
142
+ * itself is unaffected.
143
+ */
144
+ outdatedNotice(project: string | null): Promise<string | null>;
119
145
  grepSymbol(symbol: string, project: string, excludeFile: string): Promise<{
120
146
  matches: GrepMatch[];
121
147
  truncated: boolean;
@@ -123,6 +149,7 @@ export declare class SearcherService {
123
149
  findSymbol(name: string, project: string | null, kind: SymbolKind): Promise<{
124
150
  hits: SymbolHit[];
125
151
  truncated: boolean;
152
+ grepFailed: boolean;
126
153
  }>;
127
154
  private findSymbolFromIndex;
128
155
  private grepFindSymbol;
@@ -133,11 +160,19 @@ export declare class SearcherService {
133
160
  readFileAbsolute(absPath: string, maxLines: number): Promise<FileRangeResult>;
134
161
  readFileRange(filePath: string, startLine: number, endLine: number): Promise<FileRangeResult>;
135
162
  private walkDir;
163
+ /**
164
+ * ERE pattern for a grep-fallback kind, or null when that kind is never
165
+ * grepped (`function_property`/`method_signature` are index-only; a bare
166
+ * `constructor` search only makes sense when the name itself is `constructor`).
167
+ */
136
168
  private buildFindSymbolPattern;
169
+ /** 1-based index of the first whole-word occurrence of `name` in `line`, or -1. */
170
+ private static wholeWordIndex;
137
171
  private inferKind;
138
172
  /**
139
- * Async wrapper around grep to avoid blocking the event loop.
140
- * Returns stdout as a string, or null if grep returned no results or errored.
173
+ * Async wrapper around grep to avoid blocking the event loop. `ok: false`
174
+ * means grep could not be spawned or exited with status ≥ 2 (distinguishable
175
+ * from a clean "no matches", which is `ok: true, output: null`).
141
176
  */
142
177
  private spawnGrep;
143
178
  }