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
@@ -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
+ }
package/dist/src/help.js CHANGED
@@ -29,7 +29,7 @@ function printHelp(command) {
29
29
  case 'setup':
30
30
  console.log('Usage: rag-setup');
31
31
  console.log('');
32
- console.log('Interactive setup: configure DB, Ollama, and workspaceRoot.');
32
+ console.log('Interactive setup: configure DB, embedding backend, and workspaceRoot.');
33
33
  console.log('Run once before using rag-index or rag-mcp.');
34
34
  console.log('');
35
35
  console.log('Options:');
@@ -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 {
@@ -24,4 +24,35 @@ export interface IDatabase {
24
24
  ensureChunkTable(dimensions: number): Promise<void>;
25
25
  /** Drop and recreate `code_chunk` at `dimensions` (dimension change). */
26
26
  recreateChunkTable(dimensions: number): Promise<void>;
27
+ /**
28
+ * Return all file paths referenced in any of the four index tables for `project`.
29
+ * Union of code_chunk.filePath, code_symbol.filePath, code_call.fromFile,
30
+ * code_dependency.fromFile. calleeFile and toFile are excluded (may belong to other projects).
31
+ */
32
+ listKnownFiles(project: string): Promise<string[]>;
33
+ /**
34
+ * Delete rows for `filePaths` from all four index tables in one transaction.
35
+ * code_call rows where the file is either fromFile OR calleeFile are removed.
36
+ * code_dependency rows where the file is either fromFile OR toFile are removed.
37
+ * No-op when `filePaths` is empty.
38
+ */
39
+ deleteFiles(project: string, filePaths: string[]): Promise<void>;
40
+ /** Return all project names referenced in any of the four index tables. */
41
+ listIndexedProjects(): Promise<string[]>;
42
+ /** Delete all rows for `name` from the four index tables in one transaction. */
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>;
27
58
  }
@@ -7,4 +7,11 @@ export interface ILogger {
7
7
  info(msg: string, data?: Record<string, unknown>): void;
8
8
  warn(msg: string, data?: Record<string, unknown>): void;
9
9
  error(msg: string, data?: Record<string, unknown>): void;
10
+ /**
11
+ * Structured telemetry emitted at `info` **independent of the configured log
12
+ * level** — so an operator can collect it in production without the full
13
+ * `debug` firehose. No-op when file logging is disabled (never writes to
14
+ * stdout, which the MCP transport owns).
15
+ */
16
+ metric(msg: string, data?: Record<string, unknown>): void;
10
17
  }
@@ -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;
@@ -13,6 +13,17 @@ export declare class AstChunker implements IChunker {
13
13
  private readonly chunkSize;
14
14
  constructor(config: Config);
15
15
  chunk(code: string, opts: ChunkOpts): Promise<ChunkPiece[]>;
16
+ private produce;
17
+ /**
18
+ * True iff every significant line (blank and comment lines stripped) belongs
19
+ * to an `import` / re-export statement and at least one is an import or
20
+ * re-export keyword — so the chunk carries no value/type declaration and is
21
+ * never a search answer. Any `export const|let|var|function|class|interface|
22
+ * enum|namespace|default|declare|async function|type X = …` disqualifies
23
+ * exclusion. Pure. Distinguishes `export { … } from` / `export type { … }`
24
+ * (re-export → drop) from `export type X = …` (declaration → keep).
25
+ */
26
+ static isImportOrReexportOnly(content: string): boolean;
16
27
  /** Character-splitter path for a whole file; line numbers from splitter metadata. */
17
28
  private splitWholeFile;
18
29
  private chunkByAst;
@@ -27,6 +27,15 @@ class AstChunker {
27
27
  });
28
28
  }
29
29
  async chunk(code, opts) {
30
+ const pieces = await this.produce(code, opts);
31
+ // Single post-chunk pass: drop pieces whose only significant content is
32
+ // import / re-export statements. Both chunk sources — AST "gap" regions and
33
+ // the whole-file character path for barrel files — can emit these, so we
34
+ // filter once over the final pieces. Dependency edges are untouched (they
35
+ // come from analyzeProject → code_dependency, not from chunk text).
36
+ return pieces.filter(p => !AstChunker.isImportOrReexportOnly(p.content));
37
+ }
38
+ async produce(code, opts) {
30
39
  if (opts.isSchemaFile) {
31
40
  return this.splitWholeFile(code, this.schemaSplitter);
32
41
  }
@@ -35,6 +44,41 @@ class AstChunker {
35
44
  }
36
45
  return this.chunkByAst(code, opts.symbols);
37
46
  }
47
+ /**
48
+ * True iff every significant line (blank and comment lines stripped) belongs
49
+ * to an `import` / re-export statement and at least one is an import or
50
+ * re-export keyword — so the chunk carries no value/type declaration and is
51
+ * never a search answer. Any `export const|let|var|function|class|interface|
52
+ * enum|namespace|default|declare|async function|type X = …` disqualifies
53
+ * exclusion. Pure. Distinguishes `export { … } from` / `export type { … }`
54
+ * (re-export → drop) from `export type X = …` (declaration → keep).
55
+ */
56
+ static isImportOrReexportOnly(content) {
57
+ const lines = content
58
+ .replace(/\/\*[\s\S]*?\*\//g, '') // strip block comments
59
+ .split('\n')
60
+ .map(l => l.trim())
61
+ .filter(l => l !== '' && !l.startsWith('//') && !l.startsWith('*'));
62
+ if (lines.length === 0)
63
+ return false;
64
+ const IMPORT = /^import\b/;
65
+ const REEXPORT = /^export\s+(\*|type\s+\{|\{)/; // export * | export type { | export {
66
+ const DISQUALIFY = /^export\s+(default|async\b|function\b|const\b|let\b|var\b|class\b|abstract\b|interface\b|enum\b|namespace\b|declare\b|type\s+[A-Za-z_$])/;
67
+ const CONTINUATION = /^(\}?\s*from\s+['"][^'"]+['"];?|[{}]|(type\s+)?[A-Za-z_$][\w$]*(\s+as\s+[A-Za-z_$][\w$]*)?,?|['"][^'"]*['"];?)$/;
68
+ let keywords = 0;
69
+ for (const line of lines) {
70
+ if (DISQUALIFY.test(line))
71
+ return false;
72
+ if (IMPORT.test(line) || REEXPORT.test(line)) {
73
+ keywords++;
74
+ continue;
75
+ }
76
+ if (CONTINUATION.test(line))
77
+ continue;
78
+ return false; // unrecognized line → real code, keep the chunk
79
+ }
80
+ return keywords >= 1;
81
+ }
38
82
  /** Character-splitter path for a whole file; line numbers from splitter metadata. */
39
83
  async splitWholeFile(code, splitter) {
40
84
  const docs = await splitter.createDocuments([code]);
@@ -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
  }