codesearch-mcp 0.2.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [0.2.1-alpha] - 2026-10-02
9
+
10
+ ### Added
11
+
12
+ - **`find_symbol` indexes every member kind, not just top-level declarations** — class methods (`abstract`, `static`, `async`, generators, `#private`, computed and string-literal names), get/set accessors, constructors, class properties initialised with a function, object-literal methods, and interface/type-literal method signatures all get a `code_symbol` row now, with the dotted `container` of their enclosing scopes (`RepairsService`, `NS.Parser`, `api.handlers`) and a `static` flag. Classes are collected from the whole file — inside namespaces, as class expressions, as `export default class {}`, and inside functions or methods — not only at the top level.
13
+ - **Qualified, quoted and computed lookup** — `find_symbol` accepts `Class.method` to disambiguate same-named members (exact container first, then a `.`-boundary suffix match), a quoted name for string-literal members (`Router.'kebab-name'`), and a computed name (`Stack.[Symbol.iterator]`), alongside the existing bare-name lookup. `find_symbol` falls back to grep whenever the index returns **no hits** (not only when it errors), and marks grep-sourced hits so the caller knows they may be stale.
14
+ - **Qualified call graph** — `code_call.fromSymbol`/`calleeSymbol` now carry the qualified name of the enclosing/resolved declaration (`RepairsService.createRepairOrder`, including constructors and accessors, which previously had no `fromSymbol` at all). `get_callers` accepts a qualified symbol with the same suffix-matching rules as `find_symbol`.
15
+ - **Per-project symbol-index version, enforced full re-index** — `rag-index` without `--reindex` now refuses to run on a project indexed before member indexing (mixed old/new-format rows would silently hide methods from `find_symbol`); `find_symbol`/`get_callers` show a warning when a project in scope is outdated.
16
+
17
+ ### Changed (breaking)
18
+
19
+ - **Requires PostgreSQL 15+** — the symbol index's new unique index relies on `NULLS NOT DISTINCT`, added in PostgreSQL 15. `rag-index` checks the server version before any schema change and refuses on an older server.
20
+ - **Every project must be re-indexed** — run `rag-index --reindex` on each project after upgrading. Old-format rows (no members, bare call-graph names) are incompatible with the new lookup and are cleared by `--reindex`.
21
+
6
22
  ## [0.2.0-alpha] - 2026-09-29
7
23
 
8
24
  ### Added
package/README.md CHANGED
@@ -19,6 +19,8 @@ See `CHANGELOG.md` for version history.
19
19
 
20
20
  ## How it works
21
21
 
22
+ ![Architecture](docs/architecture.svg)
23
+
22
24
  1. `rag-index` scans your projects, chunks source files, embeds them using your configured embedding backend (default: [jina-embeddings-v2-base-code](https://huggingface.co/jinaai/jina-embeddings-v2-base-code) via a local [Infinity](https://github.com/michaelfeil/infinity) container), and stores vectors in PostgreSQL
23
25
  2. `rag-mcp` exposes an MCP server — Claude calls it automatically when you ask about code
24
26
  3. Incremental — only changed files are re-indexed on subsequent runs
@@ -29,6 +31,7 @@ See `CHANGELOG.md` for version history.
29
31
  ## Requirements
30
32
 
31
33
  - Node.js 18+
34
+ - PostgreSQL 15+ with pgvector (the symbol index's unique index relies on `NULLS NOT DISTINCT`, added in PostgreSQL 15)
32
35
  - Docker (for local DB + Infinity) — or bring your own PostgreSQL with pgvector + an embedding backend (see [Embedding providers](#embedding-providers))
33
36
 
34
37
  ## Installation
@@ -98,13 +101,13 @@ See [docs/web-dashboard.md](docs/web-dashboard.md) for the full reference.
98
101
  |------|-------------|
99
102
  | `search_codebase` | Semantic search across all indexed projects. Accepts `include_graph_context` to append direct imports/dependents to each result, `include_neighbors` for surrounding chunks, and `threshold` to override the similarity threshold for that query |
100
103
  | `list_projects` | List projects with metadata and chunk counts |
101
- | `find_symbol` | Find a function, class, or type by exact name — faster than semantic search |
104
+ | `find_symbol` | Find a declaration or member by exact name — bare, qualified (`Class.method`), quoted, or computed — faster than semantic search |
102
105
  | `list_files` | Browse the directory tree of a project |
103
106
  | `read_file` | Read a complete file by absolute path |
104
107
  | `read_file_range` | Read a specific line range from an indexed file |
105
108
  | `get_dependencies` | Show what files a given file imports (direct or transitive) |
106
109
  | `get_dependents` | Show which files import a given file (direct or transitive) |
107
- | `get_callers` | Find all call sites of a function or method by exact name |
110
+ | `get_callers` | Find all call sites of a function or method by exact name (bare or qualified) |
108
111
 
109
112
  ## Project metadata
110
113
 
package/dist/app.js CHANGED
@@ -42,7 +42,6 @@ const config_1 = require("./src/config");
42
42
  const config_store_1 = require("./src/config-store");
43
43
  const IndexCommand_1 = require("./src/commands/IndexCommand");
44
44
  const McpCommand_1 = require("./src/commands/McpCommand");
45
- const BackfillCommand_1 = require("./src/commands/BackfillCommand");
46
45
  const ConfigCommand_1 = require("./src/commands/ConfigCommand");
47
46
  const WebCommand_1 = require("./src/commands/WebCommand");
48
47
  const setup_1 = require("./src/setup");
@@ -87,9 +86,7 @@ async function main() {
87
86
  const isSetup = argv1 === 'rag-setup' || process.argv[2] === 'setup' || process.argv[2] === 'rag-setup';
88
87
  const isConfig = argv1 === 'rag-config' || process.argv[2] === 'config' || process.argv[2] === 'rag-config';
89
88
  const isWeb = argv1 === 'rag-web' || process.argv[2] === 'web' || process.argv[2] === 'rag-web';
90
- const isBackfill = process.argv[2] === 'backfill' ||
91
- (process.argv[2] === 'rag-index' && process.argv[3] === 'backfill');
92
- const mode = isBackfill ? 'backfill' : isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : isWeb ? 'web' : (process.argv[2] ?? '');
89
+ const mode = isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : isWeb ? 'web' : (process.argv[2] ?? '');
93
90
  const isBinary = ['rag-index', 'rag-mcp', 'rag-setup', 'rag-config', 'rag-web'].includes(argv1);
94
91
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
95
92
  (0, help_1.printHelp)(isBinary ? argv1 : mode);
@@ -131,17 +128,6 @@ async function main() {
131
128
  process.exit(1);
132
129
  }
133
130
  }
134
- else if (mode === 'backfill') {
135
- const knownBackfillFlags = new Set(['--calls']);
136
- const backfillIdx = process.argv.indexOf('backfill');
137
- const backfillArgs = backfillIdx >= 0 ? process.argv.slice(backfillIdx + 1) : [];
138
- const unknown = backfillArgs.filter(a => !knownBackfillFlags.has(a));
139
- if (unknown.length > 0) {
140
- console.error(`Unexpected argument: ${unknown[0]}\n`);
141
- (0, help_1.printHelp)('rag-index');
142
- process.exit(1);
143
- }
144
- }
145
131
  if (mode === 'setup') {
146
132
  await (0, setup_1.runSetup)();
147
133
  process.exit(process.exitCode ?? 0);
@@ -151,7 +137,7 @@ async function main() {
151
137
  process.exit(0);
152
138
  }
153
139
  // All other modes require a valid config.json
154
- const loggerName = mode === 'index' || mode === 'backfill' ? 'indexer' : mode === 'mcp' ? 'mcp' : mode === 'web' ? 'web' : undefined;
140
+ const loggerName = mode === 'index' ? 'indexer' : mode === 'mcp' ? 'mcp' : mode === 'web' ? 'web' : undefined;
155
141
  let container;
156
142
  try {
157
143
  container = (0, container_1.createContainer)(loggerName);
@@ -176,7 +162,6 @@ async function main() {
176
162
  const factories = {
177
163
  index: () => new IndexCommand_1.IndexCommand(container, argv1),
178
164
  mcp: () => new McpCommand_1.McpCommand(container),
179
- backfill: () => new BackfillCommand_1.BackfillCommand(container),
180
165
  web: () => new WebCommand_1.WebCommand(container, effectiveArgs),
181
166
  };
182
167
  const factory = factories[mode];
@@ -43,6 +43,33 @@ class IndexCommand {
43
43
  return;
44
44
  }
45
45
  }
46
+ const discovered = discovery.discover(config.workspaceRoot);
47
+ const selected = await new project_selector_1.ProjectSelector(config.workspaceRoot).select(discovered, forceSelect);
48
+ if (selected.length === 0)
49
+ return;
50
+ // Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
51
+ // sub-projects; ordinary projects pass through unchanged.
52
+ const projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
53
+ // A project indexed before member extraction mixes old-format rows (no
54
+ // members, bare calleeSymbol) with new ones if left to an incremental run.
55
+ // Refuse before any backend readiness check, Docker command, or schema
56
+ // work — the same pattern as the legacy-Ollama guard above.
57
+ if (!force) {
58
+ let outdated;
59
+ try {
60
+ outdated = await db.listOutdatedProjects(projects.map(p => p.name));
61
+ }
62
+ catch (err) {
63
+ console.error(`\n${err.message}`);
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ if (outdated.length > 0) {
68
+ console.error(`\nIndexed before member indexing: ${[...outdated].sort().join(', ')} — run \`rag-index --reindex\`.`);
69
+ process.exitCode = 1;
70
+ return;
71
+ }
72
+ }
46
73
  // Ensure the local model is served (Infinity only — no-op for cloud), then
47
74
  // resolve the active embedding dimension and reconcile it with index_meta
48
75
  // (recreate code_chunk on a dimension change, clear rows on a model change)
@@ -60,13 +87,6 @@ class IndexCommand {
60
87
  const controller = new AbortController();
61
88
  const teardown = (0, interrupt_1.setupInterrupt)(controller);
62
89
  try {
63
- const discovered = discovery.discover(config.workspaceRoot);
64
- const selected = await new project_selector_1.ProjectSelector(config.workspaceRoot).select(discovered, forceSelect);
65
- if (selected.length === 0)
66
- return;
67
- // Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
68
- // sub-projects; ordinary projects pass through unchanged.
69
- const projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
70
90
  let pruneResult;
71
91
  try {
72
92
  pruneResult = await indexer.pruneProjects(projects.map(p => p.name));
@@ -1,2 +1,2 @@
1
- export { codeChunk, codeDependency, codeSymbol, codeCall, indexMeta } from './schema';
1
+ export { codeChunk, codeDependency, codeSymbol, codeCall, indexMeta, symbolIndexVersion } from './schema';
2
2
  export type { CodeChunkInsert } from './schema';
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.indexMeta = exports.codeCall = exports.codeSymbol = exports.codeDependency = exports.codeChunk = void 0;
3
+ exports.symbolIndexVersion = exports.indexMeta = exports.codeCall = exports.codeSymbol = exports.codeDependency = exports.codeChunk = void 0;
4
4
  var schema_1 = require("./schema");
5
5
  Object.defineProperty(exports, "codeChunk", { enumerable: true, get: function () { return schema_1.codeChunk; } });
6
6
  Object.defineProperty(exports, "codeDependency", { enumerable: true, get: function () { return schema_1.codeDependency; } });
7
7
  Object.defineProperty(exports, "codeSymbol", { enumerable: true, get: function () { return schema_1.codeSymbol; } });
8
8
  Object.defineProperty(exports, "codeCall", { enumerable: true, get: function () { return schema_1.codeCall; } });
9
9
  Object.defineProperty(exports, "indexMeta", { enumerable: true, get: function () { return schema_1.indexMeta; } });
10
+ Object.defineProperty(exports, "symbolIndexVersion", { enumerable: true, get: function () { return schema_1.symbolIndexVersion; } });
@@ -430,6 +430,81 @@ export declare const codeSymbol: import("drizzle-orm/pg-core").PgTableWithColumn
430
430
  identity: undefined;
431
431
  generated: undefined;
432
432
  }, {}, {}>;
433
+ container: import("drizzle-orm/pg-core").PgColumn<{
434
+ name: "container";
435
+ tableName: "code_symbol";
436
+ dataType: "string";
437
+ columnType: "PgText";
438
+ data: string;
439
+ driverParam: string;
440
+ notNull: false;
441
+ hasDefault: false;
442
+ isPrimaryKey: false;
443
+ isAutoincrement: false;
444
+ hasRuntimeDefault: false;
445
+ enumValues: [string, ...string[]];
446
+ baseColumn: never;
447
+ identity: undefined;
448
+ generated: undefined;
449
+ }, {}, {}>;
450
+ isStatic: import("drizzle-orm/pg-core").PgColumn<{
451
+ name: "isStatic";
452
+ tableName: "code_symbol";
453
+ dataType: "boolean";
454
+ columnType: "PgBoolean";
455
+ data: boolean;
456
+ driverParam: boolean;
457
+ notNull: true;
458
+ hasDefault: true;
459
+ isPrimaryKey: false;
460
+ isAutoincrement: false;
461
+ hasRuntimeDefault: false;
462
+ enumValues: undefined;
463
+ baseColumn: never;
464
+ identity: undefined;
465
+ generated: undefined;
466
+ }, {}, {}>;
467
+ };
468
+ dialect: "pg";
469
+ }>;
470
+ export declare const symbolIndexVersion: import("drizzle-orm/pg-core").PgTableWithColumns<{
471
+ name: "symbol_index_version";
472
+ schema: undefined;
473
+ columns: {
474
+ project: import("drizzle-orm/pg-core").PgColumn<{
475
+ name: "project";
476
+ tableName: "symbol_index_version";
477
+ dataType: "string";
478
+ columnType: "PgText";
479
+ data: string;
480
+ driverParam: string;
481
+ notNull: true;
482
+ hasDefault: false;
483
+ isPrimaryKey: true;
484
+ isAutoincrement: false;
485
+ hasRuntimeDefault: false;
486
+ enumValues: [string, ...string[]];
487
+ baseColumn: never;
488
+ identity: undefined;
489
+ generated: undefined;
490
+ }, {}, {}>;
491
+ version: import("drizzle-orm/pg-core").PgColumn<{
492
+ name: "version";
493
+ tableName: "symbol_index_version";
494
+ dataType: "number";
495
+ columnType: "PgInteger";
496
+ data: number;
497
+ driverParam: string | number;
498
+ notNull: true;
499
+ hasDefault: false;
500
+ isPrimaryKey: false;
501
+ isAutoincrement: false;
502
+ hasRuntimeDefault: false;
503
+ enumValues: undefined;
504
+ baseColumn: never;
505
+ identity: undefined;
506
+ generated: undefined;
507
+ }, {}, {}>;
433
508
  };
434
509
  dialect: "pg";
435
510
  }>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.indexMeta = exports.codeCall = exports.codeSymbol = exports.codeDependency = exports.codeChunk = void 0;
3
+ exports.indexMeta = exports.codeCall = exports.symbolIndexVersion = exports.codeSymbol = exports.codeDependency = exports.codeChunk = void 0;
4
4
  const pg_core_1 = require("drizzle-orm/pg-core");
5
5
  // NOTE: the `dimensions` literal below is nominal only. `dataType()` is invoked
6
6
  // solely by drizzle-kit DDL generation, which this project does not use — the
@@ -38,6 +38,15 @@ exports.codeSymbol = (0, pg_core_1.pgTable)('code_symbol', {
38
38
  signature: (0, pg_core_1.text)('signature'),
39
39
  line: (0, pg_core_1.integer)('line'),
40
40
  col: (0, pg_core_1.integer)('col'),
41
+ container: (0, pg_core_1.text)('container'),
42
+ isStatic: (0, pg_core_1.boolean)('isStatic').notNull().default(false),
43
+ });
44
+ // Per-project marker of the symbol/call-graph row format. Not one of the four
45
+ // index tables (see the `index-consistency` pruning requirement) — a project
46
+ // with only a marker row is not "indexed" and is never pruned for it.
47
+ exports.symbolIndexVersion = (0, pg_core_1.pgTable)('symbol_index_version', {
48
+ project: (0, pg_core_1.text)('project').primaryKey(),
49
+ version: (0, pg_core_1.integer)('version').notNull(),
41
50
  });
42
51
  exports.codeCall = (0, pg_core_1.pgTable)('code_call', {
43
52
  id: (0, pg_core_1.uuid)('id').primaryKey().defaultRandom(),
@@ -0,0 +1,8 @@
1
+ import type { ChunkPiece } from './interfaces/IChunker';
2
+ import type { DepEdge, SymbolEntry } from './interfaces/IAstParser';
3
+ /**
4
+ * Build the embedding-input prompt for one chunk. `symbols` is the file's
5
+ * top-level declarations only (never members) — the prompt is unaffected by
6
+ * this change (see the `symbol-index-members` non-goal).
7
+ */
8
+ export declare function getEnrichedPrompt(piece: ChunkPiece, relPath: string, serviceName: string, edges: DepEdge[], symbols: SymbolEntry[]): string;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getEnrichedPrompt = getEnrichedPrompt;
37
+ const path = __importStar(require("path"));
38
+ function inferRole(relPath) {
39
+ const p = relPath.toLowerCase();
40
+ if (p.includes('/services/') || p.endsWith('.service.ts'))
41
+ return 'Service';
42
+ if (p.includes('/controllers/') || p.endsWith('.controller.ts'))
43
+ return 'Controller';
44
+ if (p.includes('/entities/') || p.endsWith('.entity.ts'))
45
+ return 'Entity';
46
+ if (p.includes('/guards/') || p.endsWith('.guard.ts'))
47
+ return 'Guard';
48
+ if (p.includes('/commands/'))
49
+ return 'Command';
50
+ if (p.includes('/interfaces/'))
51
+ return 'Interface';
52
+ if (p.includes('/middleware/'))
53
+ return 'Middleware';
54
+ if (p.includes('/interceptors/'))
55
+ return 'Interceptor';
56
+ if (p.endsWith('.test.ts') || p.endsWith('.spec.ts') || p.includes('/tests/'))
57
+ return 'Test';
58
+ if (p.includes('/utils/') || p.includes('/helpers/') || p.endsWith('-utils.ts'))
59
+ return 'Utility';
60
+ return null;
61
+ }
62
+ /**
63
+ * Build the embedding-input prompt for one chunk. `symbols` is the file's
64
+ * top-level declarations only (never members) — the prompt is unaffected by
65
+ * this change (see the `symbol-index-members` non-goal).
66
+ */
67
+ function getEnrichedPrompt(piece, relPath, serviceName, edges, symbols) {
68
+ const chunkStart = piece.startLine;
69
+ const chunkEnd = piece.endLine;
70
+ const lines = [];
71
+ lines.push(`Project: ${serviceName} | File: ${relPath}`);
72
+ const role = inferRole(relPath);
73
+ if (role)
74
+ lines.push(`Role: ${role}`);
75
+ const overlapping = symbols.filter(s => s.line != null &&
76
+ s.line <= chunkEnd &&
77
+ (s.endLine ?? s.line) >= chunkStart);
78
+ if (overlapping.length > 0) {
79
+ lines.push(`Symbols: ${overlapping.map(s => `${s.kind} ${s.name}`).join(', ')}`);
80
+ }
81
+ if (edges.length > 0) {
82
+ const depNames = [...new Set(edges.map(e => path.basename(e.toFile, path.extname(e.toFile)).split('.')[0]))];
83
+ lines.push(`Dependencies: ${depNames.join(', ')}`);
84
+ }
85
+ lines.push(`Code: ${piece.content}`);
86
+ return lines.join('\n');
87
+ }
@@ -6,15 +6,25 @@ export interface DepEdge {
6
6
  toSymbol: string | null;
7
7
  depType: string;
8
8
  }
9
+ export type SymbolKind = 'function' | 'class' | 'interface' | 'type' | 'variable' | 'method' | 'get_accessor' | 'set_accessor' | 'constructor' | 'function_property' | 'method_signature';
9
10
  export interface SymbolEntry {
10
11
  project: string;
11
12
  filePath: string;
12
13
  name: string;
13
- kind: 'function' | 'class' | 'interface' | 'type' | 'variable' | 'method';
14
+ kind: SymbolKind;
14
15
  signature: string | null;
15
16
  line: number | null;
16
17
  endLine: number | null;
17
18
  col: number | null;
19
+ /** Dotted path of enclosing named scopes, outermost first; null for top-level declarations. */
20
+ container: string | null;
21
+ isStatic: boolean;
22
+ /**
23
+ * Whether this declaration node has a body (vs. an overload signature).
24
+ * Internal — used only to pick an overload set's representative row before
25
+ * writing to `code_symbol`; never persisted.
26
+ */
27
+ hasBody?: boolean;
18
28
  }
19
29
  export interface CallEdge {
20
30
  project: string;
@@ -40,9 +50,15 @@ export interface OwnerRange {
40
50
  }
41
51
  export interface FileAnalysis {
42
52
  edges: DepEdge[];
53
+ /** Top-level declarations only — feeds the chunker and the embedding prompt. */
43
54
  symbols: SymbolEntry[];
55
+ /** Members, namespace-level declarations and classes collected from the whole
56
+ * file. Written to `code_symbol` alongside `symbols`; never fed to the
57
+ * chunker or the prompt (see the `symbol-index-members` retrieval-invariance
58
+ * requirement). */
59
+ members: SymbolEntry[];
44
60
  calls: CallEdge[];
45
- /** Method/accessor/constructor ranges for owner-path resolution (Phase 2). */
61
+ /** Ownership ranges (decorator-inclusive) for every indexed declaration except `method_signature`. */
46
62
  ownerRanges?: OwnerRange[];
47
63
  }
48
64
  export interface AnalyzeProjectOpts {
@@ -41,4 +41,18 @@ export interface IDatabase {
41
41
  listIndexedProjects(): Promise<string[]>;
42
42
  /** Delete all rows for `name` from the four index tables in one transaction. */
43
43
  deleteProject(name: string): Promise<void>;
44
+ /** Every project with at least one row in `code_chunk` or `code_symbol` — the
45
+ * symbol/call-graph lookup scope (distinct from `listIndexedProjects`, which
46
+ * also covers `code_call`/`code_dependency`). */
47
+ listProjectsWithIndexRows(): Promise<string[]>;
48
+ /**
49
+ * Among `projectNames` (or every project with rows when null), those that
50
+ * are outdated: at least one row in `code_chunk`/`code_symbol` and a
51
+ * `symbol_index_version` below the current format version (missing row = 1).
52
+ * A missing `symbol_index_version` table counts as "no marker rows" for every
53
+ * project. Any other database error propagates.
54
+ */
55
+ listOutdatedProjects(projectNames: string[] | null): Promise<string[]>;
56
+ /** Upsert a project's `symbol_index_version` marker to the current format version. */
57
+ upsertSymbolIndexVersion(project: string): Promise<void>;
44
58
  }
@@ -27,7 +27,10 @@ export interface ISearcher {
27
27
  findSymbol(name: string, project: string | null, kind: SymbolKind): Promise<{
28
28
  hits: SymbolHit[];
29
29
  truncated: boolean;
30
+ grepFailed: boolean;
30
31
  }>;
32
+ /** One-line warning when a project in scope is outdated (see `SearcherService.outdatedNotice`), else null. */
33
+ outdatedNotice(project: string | null): Promise<string | null>;
31
34
  listFiles(relativePath: string, project: string, depth: number, pattern: string | null): {
32
35
  tree: string;
33
36
  error?: string;
@@ -8,18 +8,50 @@ export declare class AstParserService implements IAstParser {
8
8
  clearCache(projectRoot?: string): void;
9
9
  analyzeFile(filePath: string, serviceName: string, projectRoot?: string): Promise<FileAnalysis>;
10
10
  extractImportEdges(filePath: string, serviceName: string, projectRoot?: string): Promise<DepEdge[]>;
11
+ private analyzeSourceFile;
11
12
  private createMorphProject;
12
13
  private getMorphProject;
13
14
  private extractEdges;
14
15
  private resolveFromSymbol;
15
16
  private buildUsageMap;
17
+ private getPos;
18
+ /** Trimmed text of the source line containing `node`'s start — the signature
19
+ * rule for every kind (name node for members, the position node for
20
+ * constructors/anonymous default classes). */
21
+ private lineTextAt;
16
22
  private extractSymbols;
17
- /**
18
- * Method / constructor / accessor line ranges with their enclosing class, for
19
- * owner-path resolution only. Deliberately NOT fed into `code_symbol` (methods
20
- * stay grep-based in find_symbol), so this is separate from extractSymbols.
21
- */
22
- private extractOwnerRanges;
23
+ private extractMembers;
24
+ /** Chunk-ownership ranges from every indexed declaration (top-level symbols +
25
+ * members) except `method_signature`. Each range spans the declaration node's
26
+ * own first-to-last line — decorator-inclusive, comment-exclusive, and (for a
27
+ * variable) the declarator alone, never the whole statement. */
28
+ private buildOwnerRanges;
29
+ private static isValidSegment;
30
+ private static memberName;
31
+ private static unwrapExpr;
32
+ private static unwrapParent;
33
+ private isModuleOrNamespaceLevelVariable;
34
+ private classExpressionRoot;
35
+ private objectLiteralRootValid;
36
+ private isTopLevelOrNamespaceDirect;
37
+ /** One step of the ancestor walk used by `containerSegments`: does `node`
38
+ * contribute a named segment, pass through transparently, or disqualify
39
+ * (exclude) everything nested inside it? */
40
+ private walkStep;
41
+ /** The dotted container path of `node`'s enclosing named scopes, outermost
42
+ * first, or null when any contributing ancestor is invalid (a computed /
43
+ * string-literal segment, a string-named module, `declare global`, or an
44
+ * unrooted class expression / object literal). */
45
+ private containerSegments;
46
+ /** If `node` is a declaration this service indexes (top-level symbol, member,
47
+ * or collected class), its own name + container. Otherwise null. Shared by
48
+ * member extraction, ownership ranges and the call graph so all three name a
49
+ * declaration identically. */
50
+ private describeIndexedNode;
51
+ private qualifiedName;
52
+ /** The qualified name of the nearest ancestor of `node` that is itself an
53
+ * indexed declaration (method, accessor, constructor, function property,
54
+ * namespace/top-level function, or module-/namespace-level variable). */
55
+ private findFromSymbol;
23
56
  private extractCalls;
24
- private getContainerName;
25
57
  }