bluera-knowledge 0.17.2 → 0.18.0

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.
@@ -14,7 +14,8 @@ import {
14
14
  import { homedir } from "os";
15
15
  import { dirname, join } from "path";
16
16
  import { fileURLToPath } from "url";
17
- var logDir = join(homedir(), ".bluera", "bluera-knowledge", "logs");
17
+ var envProjectRoot = process.env["PROJECT_ROOT"];
18
+ var logDir = envProjectRoot !== void 0 && envProjectRoot !== "" ? join(envProjectRoot, ".bluera", "bluera-knowledge", "logs") : join(homedir(), ".bluera", "bluera-knowledge", "logs");
18
19
  var logFile = join(logDir, "app.log");
19
20
  var log = (level, msg, data) => {
20
21
  try {
@@ -57,8 +58,26 @@ function installWithPackageManager() {
57
58
  })();
58
59
  const cmd = hasBun ? "bun install --frozen-lockfile" : "npm ci --silent";
59
60
  log("info", "Installing dependencies with package manager", { hasBun, cmd });
60
- execSync(cmd, { cwd: pluginRoot, stdio: "inherit" });
61
- log("info", "Dependencies installed via package manager");
61
+ try {
62
+ const output = execSync(cmd, { cwd: pluginRoot, stdio: "pipe" });
63
+ if (output.length > 0) {
64
+ log("debug", "Install output", { output: output.toString().trim() });
65
+ }
66
+ log("info", "Dependencies installed via package manager");
67
+ } catch (error) {
68
+ const errorMessage = error instanceof Error ? error.message : String(error);
69
+ const errorDetails = { message: errorMessage };
70
+ if (typeof error === "object" && error !== null) {
71
+ if ("stdout" in error && Buffer.isBuffer(error.stdout)) {
72
+ errorDetails["stdout"] = error.stdout.toString().trim();
73
+ }
74
+ if ("stderr" in error && Buffer.isBuffer(error.stderr)) {
75
+ errorDetails["stderr"] = error.stderr.toString().trim();
76
+ }
77
+ }
78
+ log("error", "Dependency installation failed", errorDetails);
79
+ throw error;
80
+ }
62
81
  }
63
82
  function ensureDependencies() {
64
83
  const nodeModulesPath = join(pluginRoot, "node_modules");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/bootstrap.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * MCP Server Bootstrap - installs dependencies before starting server.\n *\n * Uses only Node.js built-ins (no external dependencies required).\n * Self-locates plugin root via import.meta.url (doesn't rely on CLAUDE_PLUGIN_ROOT).\n *\n * Dependency installation strategy:\n * 1. Fast path: node_modules already exists → skip\n * 2. Package manager: Run bun install or npm ci\n *\n * IMPORTANT: MCP servers must NOT log to stderr - Claude Code treats stderr output\n * as an error and may mark the MCP server as failed. All logging goes to file.\n */\nimport { execSync } from 'node:child_process';\nimport {\n appendFileSync,\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Logging helper - writes to file since MCP servers must NOT use stderr\n// (Claude Code treats stderr as error and may fail the server)\n// JSON format matches pino output for consistency\nconst logDir = join(homedir(), '.bluera', 'bluera-knowledge', 'logs');\nconst logFile = join(logDir, 'app.log');\n\nconst log = (\n level: 'info' | 'error' | 'debug',\n msg: string,\n data?: Record<string, unknown>\n): void => {\n try {\n mkdirSync(logDir, { recursive: true });\n const entry = {\n time: new Date().toISOString(),\n level,\n module: 'bootstrap',\n msg,\n ...data,\n };\n appendFileSync(logFile, `${JSON.stringify(entry)}\\n`);\n } catch {\n // Silently fail - we cannot use stderr for MCP servers\n }\n};\n\n// Self-locate plugin root from this file's path\n// dist/mcp/bootstrap.js -> plugin root (two directories up)\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pluginRoot = join(__dirname, '..', '..');\n\n// Lock file to detect interrupted installs\nconst installLockFile = join(pluginRoot, '.node_modules_installing');\n\n// Get version from package.json for logging\nconst getVersion = (): string => {\n try {\n const pkg: unknown = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf-8'));\n if (\n typeof pkg === 'object' &&\n pkg !== null &&\n 'version' in pkg &&\n typeof pkg.version === 'string'\n ) {\n return `v${pkg.version}`;\n }\n return 'unknown';\n } catch {\n return 'unknown';\n }\n};\n\n/**\n * Install dependencies using bun or npm.\n */\nfunction installWithPackageManager(): void {\n const hasBun = ((): boolean => {\n try {\n execSync('which bun', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n })();\n\n const cmd = hasBun ? 'bun install --frozen-lockfile' : 'npm ci --silent';\n log('info', 'Installing dependencies with package manager', { hasBun, cmd });\n execSync(cmd, { cwd: pluginRoot, stdio: 'inherit' });\n log('info', 'Dependencies installed via package manager');\n}\n\n/**\n * Ensure dependencies are available.\n * Uses a lock file to detect and recover from interrupted installs.\n */\nfunction ensureDependencies(): void {\n const nodeModulesPath = join(pluginRoot, 'node_modules');\n\n // Check for interrupted install - lock file exists means previous install was killed\n if (existsSync(installLockFile)) {\n log('info', 'Detected interrupted install, cleaning up');\n rmSync(nodeModulesPath, { recursive: true, force: true });\n unlinkSync(installLockFile);\n }\n\n // Fast path: already installed\n if (existsSync(nodeModulesPath)) {\n log('info', 'Dependencies already installed');\n return;\n }\n\n // Create lock file before install (left behind if install interrupted/fails)\n writeFileSync(installLockFile, new Date().toISOString());\n\n installWithPackageManager();\n\n // Remove lock file on success\n unlinkSync(installLockFile);\n}\n\n// Main entry point\nconst VERSION = getVersion();\nlog('info', 'Bootstrap starting', { pluginRoot, version: VERSION });\n\nensureDependencies();\n\n// Now that dependencies are installed, import and run the server\n// Dynamic import required because @modelcontextprotocol/sdk wouldn't be available before install\nlog('info', 'Loading server module');\nconst { runMCPServer } = await import('./server.js');\n\nconst projectRoot = process.env['PROJECT_ROOT'];\nif (projectRoot === undefined) {\n throw new Error('PROJECT_ROOT environment variable is required');\n}\n\nlog('info', 'Starting MCP server', {\n projectRoot,\n dataDir: process.env['DATA_DIR'],\n});\n\nawait runMCPServer({\n dataDir: process.env['DATA_DIR'],\n config: process.env['CONFIG_PATH'],\n projectRoot,\n});\n"],"mappings":";;;AAcA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAK9B,IAAM,SAAS,KAAK,QAAQ,GAAG,WAAW,oBAAoB,MAAM;AACpE,IAAM,UAAU,KAAK,QAAQ,SAAS;AAEtC,IAAM,MAAM,CACV,OACA,KACA,SACS;AACT,MAAI;AACF,cAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACrC,UAAM,QAAQ;AAAA,MACZ,OAAM,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC7B;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,GAAG;AAAA,IACL;AACA,mBAAe,SAAS,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AAAA,EACtD,QAAQ;AAAA,EAER;AACF;AAIA,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AACpC,IAAM,aAAa,KAAK,WAAW,MAAM,IAAI;AAG7C,IAAM,kBAAkB,KAAK,YAAY,0BAA0B;AAGnE,IAAM,aAAa,MAAc;AAC/B,MAAI;AACF,UAAM,MAAe,KAAK,MAAM,aAAa,KAAK,YAAY,cAAc,GAAG,OAAO,CAAC;AACvF,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,OAAO,IAAI,YAAY,UACvB;AACA,aAAO,IAAI,IAAI,OAAO;AAAA,IACxB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,4BAAkC;AACzC,QAAM,UAAU,MAAe;AAC7B,QAAI;AACF,eAAS,aAAa,EAAE,OAAO,SAAS,CAAC;AACzC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,MAAM,SAAS,kCAAkC;AACvD,MAAI,QAAQ,gDAAgD,EAAE,QAAQ,IAAI,CAAC;AAC3E,WAAS,KAAK,EAAE,KAAK,YAAY,OAAO,UAAU,CAAC;AACnD,MAAI,QAAQ,4CAA4C;AAC1D;AAMA,SAAS,qBAA2B;AAClC,QAAM,kBAAkB,KAAK,YAAY,cAAc;AAGvD,MAAI,WAAW,eAAe,GAAG;AAC/B,QAAI,QAAQ,2CAA2C;AACvD,WAAO,iBAAiB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACxD,eAAW,eAAe;AAAA,EAC5B;AAGA,MAAI,WAAW,eAAe,GAAG;AAC/B,QAAI,QAAQ,gCAAgC;AAC5C;AAAA,EACF;AAGA,gBAAc,kBAAiB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAEvD,4BAA0B;AAG1B,aAAW,eAAe;AAC5B;AAGA,IAAM,UAAU,WAAW;AAC3B,IAAI,QAAQ,sBAAsB,EAAE,YAAY,SAAS,QAAQ,CAAC;AAElE,mBAAmB;AAInB,IAAI,QAAQ,uBAAuB;AACnC,IAAM,EAAE,aAAa,IAAI,MAAM,OAAO,aAAa;AAEnD,IAAM,cAAc,QAAQ,IAAI,cAAc;AAC9C,IAAI,gBAAgB,QAAW;AAC7B,QAAM,IAAI,MAAM,+CAA+C;AACjE;AAEA,IAAI,QAAQ,uBAAuB;AAAA,EACjC;AAAA,EACA,SAAS,QAAQ,IAAI,UAAU;AACjC,CAAC;AAED,MAAM,aAAa;AAAA,EACjB,SAAS,QAAQ,IAAI,UAAU;AAAA,EAC/B,QAAQ,QAAQ,IAAI,aAAa;AAAA,EACjC;AACF,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/mcp/bootstrap.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * MCP Server Bootstrap - installs dependencies before starting server.\n *\n * Uses only Node.js built-ins (no external dependencies required).\n * Self-locates plugin root via import.meta.url (doesn't rely on CLAUDE_PLUGIN_ROOT).\n *\n * Dependency installation strategy:\n * 1. Fast path: node_modules already exists → skip\n * 2. Package manager: Run bun install or npm ci\n *\n * IMPORTANT: MCP servers must NOT log to stderr - Claude Code treats stderr output\n * as an error and may mark the MCP server as failed. All logging goes to file.\n */\nimport { execSync } from 'node:child_process';\nimport {\n appendFileSync,\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Logging helper - writes to file since MCP servers must NOT use stderr\n// (Claude Code treats stderr as error and may fail the server)\n// JSON format matches pino output for consistency\n// Use PROJECT_ROOT if available (set by Claude Code), else fall back to home dir\nconst envProjectRoot = process.env['PROJECT_ROOT'];\nconst logDir =\n envProjectRoot !== undefined && envProjectRoot !== ''\n ? join(envProjectRoot, '.bluera', 'bluera-knowledge', 'logs')\n : join(homedir(), '.bluera', 'bluera-knowledge', 'logs');\nconst logFile = join(logDir, 'app.log');\n\nconst log = (\n level: 'info' | 'error' | 'debug',\n msg: string,\n data?: Record<string, unknown>\n): void => {\n try {\n mkdirSync(logDir, { recursive: true });\n const entry = {\n time: new Date().toISOString(),\n level,\n module: 'bootstrap',\n msg,\n ...data,\n };\n appendFileSync(logFile, `${JSON.stringify(entry)}\\n`);\n } catch {\n // Silently fail - we cannot use stderr for MCP servers\n }\n};\n\n// Self-locate plugin root from this file's path\n// dist/mcp/bootstrap.js -> plugin root (two directories up)\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pluginRoot = join(__dirname, '..', '..');\n\n// Lock file to detect interrupted installs\nconst installLockFile = join(pluginRoot, '.node_modules_installing');\n\n// Get version from package.json for logging\nconst getVersion = (): string => {\n try {\n const pkg: unknown = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf-8'));\n if (\n typeof pkg === 'object' &&\n pkg !== null &&\n 'version' in pkg &&\n typeof pkg.version === 'string'\n ) {\n return `v${pkg.version}`;\n }\n return 'unknown';\n } catch {\n return 'unknown';\n }\n};\n\n/**\n * Install dependencies using bun or npm.\n * Uses stdio: 'pipe' to capture output and avoid corrupting MCP stdio transport.\n */\nfunction installWithPackageManager(): void {\n const hasBun = ((): boolean => {\n try {\n execSync('which bun', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n })();\n\n const cmd = hasBun ? 'bun install --frozen-lockfile' : 'npm ci --silent';\n log('info', 'Installing dependencies with package manager', { hasBun, cmd });\n\n try {\n // Use stdio: 'pipe' to capture output - 'inherit' would corrupt MCP stdio transport\n const output = execSync(cmd, { cwd: pluginRoot, stdio: 'pipe' });\n if (output.length > 0) {\n log('debug', 'Install output', { output: output.toString().trim() });\n }\n log('info', 'Dependencies installed via package manager');\n } catch (error) {\n // Log installation failure details before re-throwing\n // execSync throws an object with stdout/stderr buffers on failure\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorDetails: Record<string, unknown> = { message: errorMessage };\n\n if (typeof error === 'object' && error !== null) {\n if ('stdout' in error && Buffer.isBuffer(error.stdout)) {\n errorDetails['stdout'] = error.stdout.toString().trim();\n }\n if ('stderr' in error && Buffer.isBuffer(error.stderr)) {\n errorDetails['stderr'] = error.stderr.toString().trim();\n }\n }\n\n log('error', 'Dependency installation failed', errorDetails);\n throw error;\n }\n}\n\n/**\n * Ensure dependencies are available.\n * Uses a lock file to detect and recover from interrupted installs.\n */\nfunction ensureDependencies(): void {\n const nodeModulesPath = join(pluginRoot, 'node_modules');\n\n // Check for interrupted install - lock file exists means previous install was killed\n if (existsSync(installLockFile)) {\n log('info', 'Detected interrupted install, cleaning up');\n rmSync(nodeModulesPath, { recursive: true, force: true });\n unlinkSync(installLockFile);\n }\n\n // Fast path: already installed\n if (existsSync(nodeModulesPath)) {\n log('info', 'Dependencies already installed');\n return;\n }\n\n // Create lock file before install (left behind if install interrupted/fails)\n writeFileSync(installLockFile, new Date().toISOString());\n\n installWithPackageManager();\n\n // Remove lock file on success\n unlinkSync(installLockFile);\n}\n\n// Main entry point\nconst VERSION = getVersion();\nlog('info', 'Bootstrap starting', { pluginRoot, version: VERSION });\n\nensureDependencies();\n\n// Now that dependencies are installed, import and run the server\n// Dynamic import required because @modelcontextprotocol/sdk wouldn't be available before install\nlog('info', 'Loading server module');\nconst { runMCPServer } = await import('./server.js');\n\nconst projectRoot = process.env['PROJECT_ROOT'];\nif (projectRoot === undefined) {\n throw new Error('PROJECT_ROOT environment variable is required');\n}\n\nlog('info', 'Starting MCP server', {\n projectRoot,\n dataDir: process.env['DATA_DIR'],\n});\n\nawait runMCPServer({\n dataDir: process.env['DATA_DIR'],\n config: process.env['CONFIG_PATH'],\n projectRoot,\n});\n"],"mappings":"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","names":[]}
@@ -156,6 +156,22 @@ type DocumentId = string & {
156
156
  readonly [DocumentIdBrand]: typeof DocumentIdBrand;
157
157
  };
158
158
 
159
+ /**
160
+ * Events emitted when cached data becomes stale.
161
+ * Used to notify dependent services (e.g., SearchService) when
162
+ * source data (e.g., code graphs) has been updated or deleted.
163
+ */
164
+ interface CacheInvalidationEvent {
165
+ /** Type of invalidation */
166
+ type: 'graph-updated' | 'graph-deleted';
167
+ /** The store whose data changed */
168
+ storeId: StoreId;
169
+ }
170
+ /**
171
+ * Listener function for cache invalidation events.
172
+ */
173
+ type CacheInvalidationListener = (event: CacheInvalidationEvent) => void;
174
+
159
175
  /**
160
176
  * Service for building, persisting, and querying code graphs.
161
177
  * Code graphs track relationships between code elements (functions, classes, etc.)
@@ -166,7 +182,17 @@ declare class CodeGraphService {
166
182
  private readonly parser;
167
183
  private readonly parserFactory;
168
184
  private readonly graphCache;
185
+ private readonly cacheListeners;
169
186
  constructor(dataDir: string, pythonBridge?: PythonBridge);
187
+ /**
188
+ * Subscribe to cache invalidation events.
189
+ * Returns an unsubscribe function.
190
+ */
191
+ onCacheInvalidation(listener: CacheInvalidationListener): () => void;
192
+ /**
193
+ * Emit a cache invalidation event to all listeners.
194
+ */
195
+ private emitCacheInvalidation;
170
196
  /**
171
197
  * Build a code graph from source files.
172
198
  */
@@ -232,7 +258,6 @@ declare class CodeGraphService {
232
258
  interface EmbeddingConfig {
233
259
  readonly model: string;
234
260
  readonly batchSize: number;
235
- readonly dimensions: number;
236
261
  }
237
262
  interface IndexingConfig {
238
263
  readonly concurrency: number;
@@ -243,12 +268,6 @@ interface IndexingConfig {
243
268
  interface SearchConfig {
244
269
  readonly defaultMode: 'vector' | 'fts' | 'hybrid';
245
270
  readonly defaultLimit: number;
246
- readonly minScore: number;
247
- readonly rrf: {
248
- readonly k: number;
249
- readonly vectorWeight: number;
250
- readonly ftsWeight: number;
251
- };
252
271
  }
253
272
  interface CrawlConfig {
254
273
  readonly userAgent: string;
@@ -272,8 +291,13 @@ interface AppConfig {
272
291
  declare class ConfigService {
273
292
  private readonly configPath;
274
293
  private readonly dataDir;
294
+ private readonly projectRoot;
275
295
  private config;
276
296
  constructor(configPath?: string, dataDir?: string, projectRoot?: string);
297
+ /**
298
+ * Get the resolved project root directory.
299
+ */
300
+ resolveProjectRoot(): string;
277
301
  load(): Promise<AppConfig>;
278
302
  save(config: AppConfig): Promise<void>;
279
303
  resolveDataDir(): string;
@@ -281,15 +305,87 @@ declare class ConfigService {
281
305
  private expandPath;
282
306
  }
283
307
 
308
+ /**
309
+ * Type-safe manifest with branded StoreId.
310
+ * Use this in service code for proper type safety.
311
+ */
312
+ interface TypedStoreManifest {
313
+ version: 1;
314
+ storeId: StoreId;
315
+ indexedAt: string;
316
+ files: Record<string, TypedFileState>;
317
+ }
318
+ /**
319
+ * Type-safe file state with branded DocumentIds.
320
+ */
321
+ interface TypedFileState {
322
+ mtime: number;
323
+ size: number;
324
+ hash: string;
325
+ documentIds: DocumentId[];
326
+ }
327
+
328
+ /**
329
+ * Service for managing store manifests.
330
+ *
331
+ * Manifests track the state of indexed files to enable incremental re-indexing.
332
+ * They are stored in the data directory under manifests/{storeId}.manifest.json.
333
+ */
334
+ declare class ManifestService {
335
+ private readonly manifestsDir;
336
+ constructor(dataDir: string);
337
+ /**
338
+ * Initialize the manifests directory.
339
+ */
340
+ initialize(): Promise<void>;
341
+ /**
342
+ * Get the file path for a store's manifest.
343
+ */
344
+ getManifestPath(storeId: StoreId): string;
345
+ /**
346
+ * Load a store's manifest.
347
+ * Returns an empty manifest if one doesn't exist.
348
+ * Throws on parse/validation errors (fail fast).
349
+ */
350
+ load(storeId: StoreId): Promise<TypedStoreManifest>;
351
+ /**
352
+ * Save a store's manifest atomically.
353
+ */
354
+ save(manifest: TypedStoreManifest): Promise<void>;
355
+ /**
356
+ * Delete a store's manifest.
357
+ * Called when a store is deleted or during full re-index.
358
+ */
359
+ delete(storeId: StoreId): Promise<void>;
360
+ /**
361
+ * Check if a file exists.
362
+ */
363
+ private fileExists;
364
+ /**
365
+ * Convert a parsed manifest to a typed manifest with branded types.
366
+ */
367
+ private toTypedManifest;
368
+ }
369
+
284
370
  declare class EmbeddingEngine {
285
371
  private extractor;
372
+ private _dimensions;
286
373
  private readonly modelName;
287
- private readonly dimensions;
288
- constructor(modelName?: string, dimensions?: number);
374
+ private readonly batchSize;
375
+ constructor(modelName?: string, batchSize?: number);
289
376
  initialize(): Promise<void>;
290
377
  embed(text: string): Promise<number[]>;
291
378
  embedBatch(texts: string[]): Promise<number[][]>;
379
+ /**
380
+ * Get cached embedding dimensions. Throws if embed() hasn't been called yet.
381
+ * Use ensureDimensions() if you need to guarantee dimensions are available.
382
+ */
292
383
  getDimensions(): number;
384
+ /**
385
+ * Ensure dimensions are available, initializing the model if needed.
386
+ * Returns the embedding dimensions for the current model.
387
+ */
388
+ ensureDimensions(): Promise<number>;
293
389
  /**
294
390
  * Dispose the embedding pipeline to free resources.
295
391
  * Should be called before process exit to prevent ONNX runtime cleanup issues on macOS.
@@ -308,7 +404,7 @@ interface DocumentMetadata {
308
404
  readonly url?: string | undefined;
309
405
  readonly type: DocumentType;
310
406
  readonly storeId: StoreId;
311
- readonly indexedAt: Date;
407
+ readonly indexedAt: string;
312
408
  readonly fileHash?: string | undefined;
313
409
  readonly chunkIndex?: number | undefined;
314
410
  readonly totalChunks?: number | undefined;
@@ -325,10 +421,18 @@ declare class LanceStore {
325
421
  private connection;
326
422
  private readonly tables;
327
423
  private readonly dataDir;
424
+ private _dimensions;
328
425
  constructor(dataDir: string);
426
+ /**
427
+ * Set the embedding dimensions. Must be called before initialize().
428
+ * This allows dimensions to be derived from the embedding model at runtime.
429
+ * Idempotent: subsequent calls are ignored if dimensions are already set.
430
+ */
431
+ setDimensions(dimensions: number): void;
329
432
  initialize(storeId: StoreId): Promise<void>;
330
433
  addDocuments(storeId: StoreId, documents: Document[]): Promise<void>;
331
434
  deleteDocuments(storeId: StoreId, documentIds: DocumentId[]): Promise<void>;
435
+ clearAllDocuments(storeId: StoreId): Promise<void>;
332
436
  search(storeId: StoreId, vector: number[], limit: number, _threshold?: number): Promise<Array<{
333
437
  id: DocumentId;
334
438
  content: string;
@@ -391,17 +495,20 @@ interface RepoStore extends BaseStore {
391
495
  readonly path: string;
392
496
  readonly url?: string | undefined;
393
497
  readonly branch?: string | undefined;
498
+ readonly depth?: number | undefined;
394
499
  }
395
500
  interface WebStore extends BaseStore {
396
501
  readonly type: 'web';
397
502
  readonly url: string;
398
503
  readonly depth: number;
399
504
  readonly maxPages?: number | undefined;
505
+ readonly crawlInstructions?: string | undefined;
506
+ readonly extractInstructions?: string | undefined;
400
507
  }
401
508
  type Store = FileStore | RepoStore | WebStore;
402
509
 
403
510
  interface IndexResult {
404
- documentsIndexed: number;
511
+ filesIndexed: number;
405
512
  chunksCreated: number;
406
513
  timeMs: number;
407
514
  }
@@ -410,15 +517,36 @@ interface IndexOptions {
410
517
  chunkOverlap?: number;
411
518
  codeGraphService?: CodeGraphService;
412
519
  concurrency?: number;
520
+ manifestService?: ManifestService;
521
+ ignorePatterns?: readonly string[];
522
+ }
523
+ interface IncrementalIndexResult extends IndexResult {
524
+ filesAdded: number;
525
+ filesModified: number;
526
+ filesDeleted: number;
527
+ filesUnchanged: number;
413
528
  }
414
529
  declare class IndexService {
415
530
  private readonly lanceStore;
416
531
  private readonly embeddingEngine;
417
532
  private readonly chunker;
418
533
  private readonly codeGraphService;
534
+ private readonly manifestService;
535
+ private readonly driftService;
419
536
  private readonly concurrency;
537
+ private readonly ignoreDirs;
538
+ private readonly ignoreFilePatterns;
420
539
  constructor(lanceStore: LanceStore, embeddingEngine: EmbeddingEngine, options?: IndexOptions);
421
540
  indexStore(store: Store, onProgress?: ProgressCallback): Promise<Result<IndexResult>>;
541
+ /**
542
+ * Incrementally index a store, only processing changed files.
543
+ * Requires manifestService to be configured.
544
+ *
545
+ * @param store - The store to index
546
+ * @param onProgress - Optional progress callback
547
+ * @returns Result with incremental index statistics
548
+ */
549
+ indexStoreIncremental(store: Store, onProgress?: ProgressCallback): Promise<Result<IncrementalIndexResult>>;
422
550
  private indexFileStore;
423
551
  /**
424
552
  * Process a single file: read, chunk, embed, and return documents.
@@ -440,6 +568,11 @@ declare class IndexService {
440
568
  }
441
569
 
442
570
  type SearchMode = 'vector' | 'fts' | 'hybrid';
571
+ /**
572
+ * Search intent hints for context-aware ranking.
573
+ * These align with the MCP API contract.
574
+ */
575
+ type SearchIntent = 'find-pattern' | 'find-implementation' | 'find-usage' | 'find-definition' | 'find-documentation';
443
576
  interface CodeUnit {
444
577
  type: 'function' | 'class' | 'interface' | 'type' | 'const' | 'documentation' | 'example';
445
578
  name: string;
@@ -484,9 +617,7 @@ interface SearchQuery {
484
617
  readonly limit?: number | undefined;
485
618
  readonly threshold?: number | undefined;
486
619
  readonly minRelevance?: number | undefined;
487
- readonly filter?: Record<string, unknown> | undefined;
488
- readonly includeContent?: boolean | undefined;
489
- readonly contextLines?: number | undefined;
620
+ readonly intent?: SearchIntent | undefined;
490
621
  readonly detail?: DetailLevel | undefined;
491
622
  }
492
623
  interface SearchResult {
@@ -529,7 +660,14 @@ declare class SearchService {
529
660
  private readonly codeUnitService;
530
661
  private readonly codeGraphService;
531
662
  private readonly graphCache;
532
- constructor(lanceStore: LanceStore, embeddingEngine: EmbeddingEngine, codeGraphService?: CodeGraphService);
663
+ private readonly searchConfig;
664
+ private readonly unsubscribeCacheInvalidation;
665
+ constructor(lanceStore: LanceStore, embeddingEngine: EmbeddingEngine, codeGraphService?: CodeGraphService, searchConfig?: SearchConfig);
666
+ /**
667
+ * Clean up resources (unsubscribe from events).
668
+ * Call this when destroying the service.
669
+ */
670
+ cleanup(): void;
533
671
  /**
534
672
  * Load code graph for a store, with caching.
535
673
  * Returns null if no graph is available.
@@ -565,7 +703,6 @@ declare class SearchService {
565
703
  * Returns results with raw cosine similarity scores [0-1].
566
704
  */
567
705
  private vectorSearchRaw;
568
- private vectorSearch;
569
706
  private ftsSearch;
570
707
  /**
571
708
  * Internal hybrid search result with additional metadata for confidence calculation.
@@ -671,7 +808,7 @@ declare const StoreDefinitionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
671
808
  description: z.ZodOptional<z.ZodString>;
672
809
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
673
810
  type: z.ZodLiteral<"repo">;
674
- url: z.ZodURL;
811
+ url: z.ZodString;
675
812
  branch: z.ZodOptional<z.ZodString>;
676
813
  depth: z.ZodOptional<z.ZodNumber>;
677
814
  }, z.core.$strip>, z.ZodObject<{
@@ -703,7 +840,7 @@ declare const StoreDefinitionsConfigSchema: z.ZodObject<{
703
840
  description: z.ZodOptional<z.ZodString>;
704
841
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
705
842
  type: z.ZodLiteral<"repo">;
706
- url: z.ZodURL;
843
+ url: z.ZodString;
707
844
  branch: z.ZodOptional<z.ZodString>;
708
845
  depth: z.ZodOptional<z.ZodNumber>;
709
846
  }, z.core.$strip>, z.ZodObject<{
@@ -800,12 +937,17 @@ interface CreateStoreInput {
800
937
  tags?: string[] | undefined;
801
938
  branch?: string | undefined;
802
939
  depth?: number | undefined;
940
+ maxPages?: number | undefined;
941
+ crawlInstructions?: string | undefined;
942
+ extractInstructions?: string | undefined;
803
943
  }
804
944
  interface StoreServiceOptions {
805
945
  /** Optional definition service for auto-updating git-committable config */
806
946
  definitionService?: StoreDefinitionService;
807
947
  /** Optional gitignore service for ensuring .gitignore patterns */
808
948
  gitignoreService?: GitignoreService;
949
+ /** Optional project root for resolving relative paths */
950
+ projectRoot?: string;
809
951
  }
810
952
  interface OperationOptions {
811
953
  /** Skip syncing to store definitions (used by stores:sync command) */
@@ -815,13 +957,21 @@ declare class StoreService {
815
957
  private readonly dataDir;
816
958
  private readonly definitionService;
817
959
  private readonly gitignoreService;
960
+ private readonly projectRoot;
818
961
  private registry;
819
962
  constructor(dataDir: string, options?: StoreServiceOptions);
820
963
  initialize(): Promise<void>;
821
964
  /**
822
965
  * Convert a Store and CreateStoreInput to a StoreDefinition for persistence.
966
+ * Returns undefined for stores that shouldn't be persisted (e.g., local repo stores).
823
967
  */
824
968
  private createDefinitionFromStore;
969
+ /**
970
+ * Create a StoreDefinition from an existing store (without original input).
971
+ * Used when updating/renaming stores where we don't have the original input.
972
+ * Returns undefined for stores that shouldn't be persisted (e.g., local repo stores).
973
+ */
974
+ private createDefinitionFromExistingStore;
825
975
  create(input: CreateStoreInput, options?: OperationOptions): Promise<Result<Store>>;
826
976
  list(type?: StoreType): Promise<Store[]>;
827
977
  get(id: StoreId): Promise<Store | undefined>;
@@ -842,6 +992,7 @@ interface ServiceContainer {
842
992
  embeddings: EmbeddingEngine;
843
993
  codeGraph: CodeGraphService;
844
994
  pythonBridge: PythonBridge;
995
+ manifest: ManifestService;
845
996
  }
846
997
 
847
998
  /**
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  createMCPServer,
3
3
  runMCPServer
4
- } from "../chunk-YMDXPECI.js";
5
- import "../chunk-WMALVLFW.js";
6
- import "../chunk-HRQD3MPH.js";
4
+ } from "../chunk-EZXJ3W5X.js";
5
+ import "../chunk-RDDGZIDL.js";
6
+ import "../chunk-CLIMKLTW.js";
7
+ import "../chunk-HXBIIMYL.js";
7
8
  export {
8
9
  createMCPServer,
9
10
  runMCPServer
@@ -0,0 +1,7 @@
1
+ import {
2
+ WatchService
3
+ } from "./chunk-HXBIIMYL.js";
4
+ export {
5
+ WatchService
6
+ };
7
+ //# sourceMappingURL=watch.service-VDSUQ72Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  IntelligentCrawler
4
- } from "../chunk-PFHK5Q22.js";
4
+ } from "../chunk-VUGQ7HAR.js";
5
5
  import {
6
6
  JobService,
7
- createDocumentId,
8
7
  createLogger,
9
8
  createServices,
10
- createStoreId,
11
9
  destroyServices,
12
10
  shutdownLogger
13
- } from "../chunk-WMALVLFW.js";
14
- import "../chunk-HRQD3MPH.js";
11
+ } from "../chunk-RDDGZIDL.js";
12
+ import {
13
+ createDocumentId,
14
+ createStoreId
15
+ } from "../chunk-CLIMKLTW.js";
16
+ import "../chunk-HXBIIMYL.js";
15
17
 
16
18
  // src/workers/background-worker-cli.ts
17
19
  import { platform } from "os";
@@ -24,12 +26,13 @@ function calculateIndexProgress(current, total, scale = 100) {
24
26
  return current / total * scale;
25
27
  }
26
28
  var BackgroundWorker = class {
27
- constructor(jobService, storeService, indexService, lanceStore, embeddingEngine) {
29
+ constructor(jobService, storeService, indexService, lanceStore, embeddingEngine, crawlConfig) {
28
30
  this.jobService = jobService;
29
31
  this.storeService = storeService;
30
32
  this.indexService = indexService;
31
33
  this.lanceStore = lanceStore;
32
34
  this.embeddingEngine = embeddingEngine;
35
+ this.crawlConfig = crawlConfig;
33
36
  }
34
37
  /**
35
38
  * Execute a job based on its type
@@ -104,6 +107,8 @@ var BackgroundWorker = class {
104
107
  message: "Repository cloned, starting indexing...",
105
108
  progress: 30
106
109
  });
110
+ this.lanceStore.setDimensions(await this.embeddingEngine.ensureDimensions());
111
+ await this.lanceStore.initialize(store.id);
107
112
  const result = await this.indexService.indexStore(
108
113
  store,
109
114
  (event) => {
@@ -140,6 +145,8 @@ var BackgroundWorker = class {
140
145
  if (!store) {
141
146
  throw new Error(`Store ${storeId} not found`);
142
147
  }
148
+ this.lanceStore.setDimensions(await this.embeddingEngine.ensureDimensions());
149
+ await this.lanceStore.initialize(store.id);
143
150
  const result = await this.indexService.indexStore(
144
151
  store,
145
152
  (event) => {
@@ -179,7 +186,7 @@ var BackgroundWorker = class {
179
186
  throw new Error(`Web store ${storeId} not found`);
180
187
  }
181
188
  const resolvedMaxPages = typeof maxPages === "number" ? maxPages : 50;
182
- const crawler = new IntelligentCrawler();
189
+ const crawler = new IntelligentCrawler(this.crawlConfig);
183
190
  crawler.on("progress", (progress) => {
184
191
  const currentJob = this.jobService.getJob(job.id);
185
192
  if (currentJob?.status === "cancelled") {
@@ -193,6 +200,7 @@ var BackgroundWorker = class {
193
200
  });
194
201
  });
195
202
  try {
203
+ this.lanceStore.setDimensions(await this.embeddingEngine.ensureDimensions());
196
204
  await this.lanceStore.initialize(store.id);
197
205
  const docs = [];
198
206
  const crawlOptions = {
@@ -225,7 +233,7 @@ var BackgroundWorker = class {
225
233
  title: result.title,
226
234
  extracted: result.extracted !== void 0,
227
235
  depth: result.depth,
228
- indexedAt: /* @__PURE__ */ new Date()
236
+ indexedAt: (/* @__PURE__ */ new Date()).toISOString()
229
237
  }
230
238
  });
231
239
  }
@@ -234,6 +242,7 @@ var BackgroundWorker = class {
234
242
  message: "Indexing crawled documents...",
235
243
  progress: 85
236
244
  });
245
+ await this.lanceStore.clearAllDocuments(store.id);
237
246
  await this.lanceStore.addDocuments(store.id, docs);
238
247
  await this.lanceStore.createFtsIndex(store.id);
239
248
  }
@@ -330,12 +339,14 @@ async function main() {
330
339
  }
331
340
  void shutdownLogger().finally(() => process.exit(0));
332
341
  });
342
+ const appConfig = await services.config.load();
333
343
  const worker = new BackgroundWorker(
334
344
  jobService,
335
345
  services.store,
336
346
  services.index,
337
347
  services.lance,
338
- services.embeddings
348
+ services.embeddings,
349
+ appConfig.crawl
339
350
  );
340
351
  try {
341
352
  await worker.executeJob(jobId);