@liendev/lien 0.8.1

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/dist/index.js ADDED
@@ -0,0 +1,3695 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/constants.ts
13
+ var DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_OVERLAP, DEFAULT_CONCURRENCY, DEFAULT_EMBEDDING_BATCH_SIZE, EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, DEFAULT_PORT, VERSION_CHECK_INTERVAL_MS, DEFAULT_GIT_POLL_INTERVAL_MS, DEFAULT_DEBOUNCE_MS, CURRENT_CONFIG_VERSION;
14
+ var init_constants = __esm({
15
+ "src/constants.ts"() {
16
+ "use strict";
17
+ DEFAULT_CHUNK_SIZE = 75;
18
+ DEFAULT_CHUNK_OVERLAP = 10;
19
+ DEFAULT_CONCURRENCY = 4;
20
+ DEFAULT_EMBEDDING_BATCH_SIZE = 50;
21
+ EMBEDDING_DIMENSIONS = 384;
22
+ DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
23
+ DEFAULT_PORT = 7133;
24
+ VERSION_CHECK_INTERVAL_MS = 2e3;
25
+ DEFAULT_GIT_POLL_INTERVAL_MS = 1e4;
26
+ DEFAULT_DEBOUNCE_MS = 1e3;
27
+ CURRENT_CONFIG_VERSION = "0.3.0";
28
+ }
29
+ });
30
+
31
+ // src/config/schema.ts
32
+ function isLegacyConfig(config) {
33
+ return "indexing" in config && !("frameworks" in config);
34
+ }
35
+ function isModernConfig(config) {
36
+ return "frameworks" in config;
37
+ }
38
+ var defaultConfig;
39
+ var init_schema = __esm({
40
+ "src/config/schema.ts"() {
41
+ "use strict";
42
+ init_constants();
43
+ defaultConfig = {
44
+ version: CURRENT_CONFIG_VERSION,
45
+ core: {
46
+ chunkSize: DEFAULT_CHUNK_SIZE,
47
+ chunkOverlap: DEFAULT_CHUNK_OVERLAP,
48
+ concurrency: DEFAULT_CONCURRENCY,
49
+ embeddingBatchSize: DEFAULT_EMBEDDING_BATCH_SIZE
50
+ },
51
+ mcp: {
52
+ port: DEFAULT_PORT,
53
+ transport: "stdio",
54
+ autoIndexOnFirstRun: true
55
+ },
56
+ gitDetection: {
57
+ enabled: true,
58
+ pollIntervalMs: DEFAULT_GIT_POLL_INTERVAL_MS
59
+ },
60
+ fileWatching: {
61
+ enabled: false,
62
+ // Opt-in feature
63
+ debounceMs: DEFAULT_DEBOUNCE_MS
64
+ },
65
+ frameworks: []
66
+ // Will be populated by lien init via framework detection
67
+ };
68
+ }
69
+ });
70
+
71
+ // src/config/merge.ts
72
+ function deepMergeConfig(defaults, user) {
73
+ return {
74
+ version: user.version ?? defaults.version,
75
+ core: {
76
+ ...defaults.core,
77
+ ...user.core
78
+ },
79
+ mcp: {
80
+ ...defaults.mcp,
81
+ ...user.mcp
82
+ },
83
+ gitDetection: {
84
+ ...defaults.gitDetection,
85
+ ...user.gitDetection
86
+ },
87
+ fileWatching: {
88
+ ...defaults.fileWatching,
89
+ ...user.fileWatching
90
+ },
91
+ frameworks: user.frameworks ?? defaults.frameworks
92
+ };
93
+ }
94
+ function detectNewFields(before, after) {
95
+ const newFields = [];
96
+ for (const key of Object.keys(after)) {
97
+ if (!(key in before)) {
98
+ newFields.push(key);
99
+ continue;
100
+ }
101
+ if (typeof after[key] === "object" && after[key] !== null && !Array.isArray(after[key])) {
102
+ const beforeSection = before[key] || {};
103
+ const afterSection = after[key];
104
+ for (const nestedKey of Object.keys(afterSection)) {
105
+ if (!(nestedKey in beforeSection)) {
106
+ newFields.push(`${key}.${nestedKey}`);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return newFields;
112
+ }
113
+ var init_merge = __esm({
114
+ "src/config/merge.ts"() {
115
+ "use strict";
116
+ }
117
+ });
118
+
119
+ // src/utils/banner.ts
120
+ var banner_exports = {};
121
+ __export(banner_exports, {
122
+ showBanner: () => showBanner,
123
+ showCompactBanner: () => showCompactBanner
124
+ });
125
+ import figlet from "figlet";
126
+ import chalk from "chalk";
127
+ import { createRequire } from "module";
128
+ import { fileURLToPath } from "url";
129
+ import { dirname, join } from "path";
130
+ function wrapInBox(text, footer, padding = 1) {
131
+ const lines = text.split("\n").filter((line) => line.trim().length > 0);
132
+ const maxLength = Math.max(...lines.map((line) => line.length));
133
+ const horizontalBorder = "\u2500".repeat(maxLength + padding * 2);
134
+ const top = `\u250C${horizontalBorder}\u2510`;
135
+ const bottom = `\u2514${horizontalBorder}\u2518`;
136
+ const separator = `\u251C${horizontalBorder}\u2524`;
137
+ const paddedLines = lines.map((line) => {
138
+ const padRight = " ".repeat(maxLength - line.length + padding);
139
+ const padLeft = " ".repeat(padding);
140
+ return `\u2502${padLeft}${line}${padRight}\u2502`;
141
+ });
142
+ const totalPad = maxLength - footer.length;
143
+ const leftPad = Math.floor(totalPad / 2);
144
+ const rightPad = totalPad - leftPad;
145
+ const centeredFooter = " ".repeat(leftPad) + footer + " ".repeat(rightPad);
146
+ const paddedFooter = `\u2502${" ".repeat(padding)}${centeredFooter}${" ".repeat(padding)}\u2502`;
147
+ return [top, ...paddedLines, separator, paddedFooter, bottom].join("\n");
148
+ }
149
+ function showBanner() {
150
+ const banner = figlet.textSync("LIEN", {
151
+ font: "ANSI Shadow",
152
+ horizontalLayout: "fitted",
153
+ verticalLayout: "fitted"
154
+ });
155
+ const footer = `${PACKAGE_NAME} - v${VERSION}`;
156
+ const boxedBanner = wrapInBox(banner.trim(), footer);
157
+ console.error(chalk.cyan(boxedBanner));
158
+ console.error();
159
+ }
160
+ function showCompactBanner() {
161
+ const banner = figlet.textSync("LIEN", {
162
+ font: "ANSI Shadow",
163
+ horizontalLayout: "fitted",
164
+ verticalLayout: "fitted"
165
+ });
166
+ const footer = `${PACKAGE_NAME} - v${VERSION}`;
167
+ const boxedBanner = wrapInBox(banner.trim(), footer);
168
+ console.log(chalk.cyan(boxedBanner));
169
+ console.log();
170
+ }
171
+ var __filename, __dirname, require2, packageJson, PACKAGE_NAME, VERSION;
172
+ var init_banner = __esm({
173
+ "src/utils/banner.ts"() {
174
+ "use strict";
175
+ __filename = fileURLToPath(import.meta.url);
176
+ __dirname = dirname(__filename);
177
+ require2 = createRequire(import.meta.url);
178
+ try {
179
+ packageJson = require2(join(__dirname, "../package.json"));
180
+ } catch {
181
+ packageJson = require2(join(__dirname, "../../package.json"));
182
+ }
183
+ PACKAGE_NAME = packageJson.name;
184
+ VERSION = packageJson.version;
185
+ }
186
+ });
187
+
188
+ // src/config/migration.ts
189
+ function needsMigration(config) {
190
+ if (!config) {
191
+ return false;
192
+ }
193
+ if (config.frameworks !== void 0) {
194
+ return false;
195
+ }
196
+ if (config.indexing !== void 0) {
197
+ return true;
198
+ }
199
+ if (config.version && config.version.startsWith("0.2")) {
200
+ return true;
201
+ }
202
+ return false;
203
+ }
204
+ function migrateConfig(oldConfig) {
205
+ const newConfig = {
206
+ version: "0.3.0",
207
+ core: {
208
+ chunkSize: oldConfig.indexing?.chunkSize ?? defaultConfig.core.chunkSize,
209
+ chunkOverlap: oldConfig.indexing?.chunkOverlap ?? defaultConfig.core.chunkOverlap,
210
+ concurrency: oldConfig.indexing?.concurrency ?? defaultConfig.core.concurrency,
211
+ embeddingBatchSize: oldConfig.indexing?.embeddingBatchSize ?? defaultConfig.core.embeddingBatchSize
212
+ },
213
+ mcp: {
214
+ port: oldConfig.mcp?.port ?? defaultConfig.mcp.port,
215
+ transport: oldConfig.mcp?.transport ?? defaultConfig.mcp.transport,
216
+ autoIndexOnFirstRun: oldConfig.mcp?.autoIndexOnFirstRun ?? defaultConfig.mcp.autoIndexOnFirstRun
217
+ },
218
+ gitDetection: {
219
+ enabled: oldConfig.gitDetection?.enabled ?? defaultConfig.gitDetection.enabled,
220
+ pollIntervalMs: oldConfig.gitDetection?.pollIntervalMs ?? defaultConfig.gitDetection.pollIntervalMs
221
+ },
222
+ fileWatching: {
223
+ enabled: oldConfig.fileWatching?.enabled ?? defaultConfig.fileWatching.enabled,
224
+ debounceMs: oldConfig.fileWatching?.debounceMs ?? defaultConfig.fileWatching.debounceMs
225
+ },
226
+ frameworks: []
227
+ };
228
+ if (oldConfig.indexing) {
229
+ const genericFramework = {
230
+ name: "generic",
231
+ path: ".",
232
+ enabled: true,
233
+ config: {
234
+ include: oldConfig.indexing.include ?? ["**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,cs}"],
235
+ exclude: oldConfig.indexing.exclude ?? [
236
+ "**/node_modules/**",
237
+ "**/dist/**",
238
+ "**/build/**",
239
+ "**/.git/**",
240
+ "**/coverage/**",
241
+ "**/.next/**",
242
+ "**/.nuxt/**",
243
+ "**/vendor/**"
244
+ ]
245
+ }
246
+ };
247
+ newConfig.frameworks.push(genericFramework);
248
+ } else {
249
+ const genericFramework = {
250
+ name: "generic",
251
+ path: ".",
252
+ enabled: true,
253
+ config: {
254
+ include: ["**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,cs}"],
255
+ exclude: [
256
+ "**/node_modules/**",
257
+ "**/dist/**",
258
+ "**/build/**",
259
+ "**/.git/**",
260
+ "**/coverage/**",
261
+ "**/.next/**",
262
+ "**/.nuxt/**",
263
+ "**/vendor/**"
264
+ ]
265
+ }
266
+ };
267
+ newConfig.frameworks.push(genericFramework);
268
+ }
269
+ return newConfig;
270
+ }
271
+ var init_migration = __esm({
272
+ "src/config/migration.ts"() {
273
+ "use strict";
274
+ init_schema();
275
+ }
276
+ });
277
+
278
+ // src/errors/index.ts
279
+ function wrapError(error, context, additionalContext) {
280
+ const message = error instanceof Error ? error.message : String(error);
281
+ const stack = error instanceof Error ? error.stack : void 0;
282
+ const wrappedError = new LienError(
283
+ `${context}: ${message}`,
284
+ "WRAPPED_ERROR",
285
+ additionalContext
286
+ );
287
+ if (stack) {
288
+ wrappedError.stack = `${wrappedError.stack}
289
+
290
+ Caused by:
291
+ ${stack}`;
292
+ }
293
+ return wrappedError;
294
+ }
295
+ var LienError, ConfigError, EmbeddingError, DatabaseError;
296
+ var init_errors = __esm({
297
+ "src/errors/index.ts"() {
298
+ "use strict";
299
+ LienError = class extends Error {
300
+ constructor(message, code, context) {
301
+ super(message);
302
+ this.code = code;
303
+ this.context = context;
304
+ this.name = "LienError";
305
+ if (Error.captureStackTrace) {
306
+ Error.captureStackTrace(this, this.constructor);
307
+ }
308
+ }
309
+ };
310
+ ConfigError = class extends LienError {
311
+ constructor(message, context) {
312
+ super(message, "CONFIG_ERROR", context);
313
+ this.name = "ConfigError";
314
+ }
315
+ };
316
+ EmbeddingError = class extends LienError {
317
+ constructor(message, context) {
318
+ super(message, "EMBEDDING_ERROR", context);
319
+ this.name = "EmbeddingError";
320
+ }
321
+ };
322
+ DatabaseError = class extends LienError {
323
+ constructor(message, context) {
324
+ super(message, "DATABASE_ERROR", context);
325
+ this.name = "DatabaseError";
326
+ }
327
+ };
328
+ }
329
+ });
330
+
331
+ // src/config/service.ts
332
+ import fs5 from "fs/promises";
333
+ import path5 from "path";
334
+ var ConfigService, configService;
335
+ var init_service = __esm({
336
+ "src/config/service.ts"() {
337
+ "use strict";
338
+ init_schema();
339
+ init_merge();
340
+ init_migration();
341
+ init_errors();
342
+ ConfigService = class _ConfigService {
343
+ static CONFIG_FILENAME = ".lien.config.json";
344
+ /**
345
+ * Load configuration from the specified directory.
346
+ * Automatically handles migration if needed.
347
+ *
348
+ * @param rootDir - Root directory containing the config file
349
+ * @returns Loaded and validated configuration
350
+ * @throws {ConfigError} If config is invalid or cannot be loaded
351
+ */
352
+ async load(rootDir = process.cwd()) {
353
+ const configPath = this.getConfigPath(rootDir);
354
+ try {
355
+ const configContent = await fs5.readFile(configPath, "utf-8");
356
+ const userConfig = JSON.parse(configContent);
357
+ if (this.needsMigration(userConfig)) {
358
+ console.log("\u{1F504} Migrating config from v0.2.0 to v0.3.0...");
359
+ const result = await this.migrate(rootDir);
360
+ if (result.migrated && result.backupPath) {
361
+ const backupFilename = path5.basename(result.backupPath);
362
+ console.log(`\u2705 Migration complete! Backup saved as ${backupFilename}`);
363
+ console.log("\u{1F4DD} Your config now uses the framework-based structure.");
364
+ }
365
+ return result.config;
366
+ }
367
+ const mergedConfig = deepMergeConfig(defaultConfig, userConfig);
368
+ const validation = this.validate(mergedConfig);
369
+ if (!validation.valid) {
370
+ throw new ConfigError(
371
+ `Invalid configuration:
372
+ ${validation.errors.join("\n")}`,
373
+ { errors: validation.errors, warnings: validation.warnings }
374
+ );
375
+ }
376
+ if (validation.warnings.length > 0) {
377
+ console.warn("\u26A0\uFE0F Configuration warnings:");
378
+ validation.warnings.forEach((warning) => console.warn(` ${warning}`));
379
+ }
380
+ return mergedConfig;
381
+ } catch (error) {
382
+ if (error.code === "ENOENT") {
383
+ return defaultConfig;
384
+ }
385
+ if (error instanceof ConfigError) {
386
+ throw error;
387
+ }
388
+ if (error instanceof SyntaxError) {
389
+ throw new ConfigError(
390
+ "Failed to parse config file: Invalid JSON syntax",
391
+ { path: configPath, originalError: error.message }
392
+ );
393
+ }
394
+ throw wrapError(error, "Failed to load configuration", { path: configPath });
395
+ }
396
+ }
397
+ /**
398
+ * Save configuration to the specified directory.
399
+ * Validates the config before saving.
400
+ *
401
+ * @param rootDir - Root directory to save the config file
402
+ * @param config - Configuration to save
403
+ * @throws {ConfigError} If config is invalid or cannot be saved
404
+ */
405
+ async save(rootDir, config) {
406
+ const configPath = this.getConfigPath(rootDir);
407
+ const validation = this.validate(config);
408
+ if (!validation.valid) {
409
+ throw new ConfigError(
410
+ `Cannot save invalid configuration:
411
+ ${validation.errors.join("\n")}`,
412
+ { errors: validation.errors }
413
+ );
414
+ }
415
+ try {
416
+ const configJson = JSON.stringify(config, null, 2) + "\n";
417
+ await fs5.writeFile(configPath, configJson, "utf-8");
418
+ } catch (error) {
419
+ throw wrapError(error, "Failed to save configuration", { path: configPath });
420
+ }
421
+ }
422
+ /**
423
+ * Check if a configuration file exists in the specified directory.
424
+ *
425
+ * @param rootDir - Root directory to check
426
+ * @returns True if config file exists
427
+ */
428
+ async exists(rootDir = process.cwd()) {
429
+ const configPath = this.getConfigPath(rootDir);
430
+ try {
431
+ await fs5.access(configPath);
432
+ return true;
433
+ } catch {
434
+ return false;
435
+ }
436
+ }
437
+ /**
438
+ * Migrate configuration from v0.2.0 to v0.3.0 format.
439
+ * Creates a backup of the original config file.
440
+ *
441
+ * @param rootDir - Root directory containing the config file
442
+ * @returns Migration result with status and new config
443
+ * @throws {ConfigError} If migration fails
444
+ */
445
+ async migrate(rootDir = process.cwd()) {
446
+ const configPath = this.getConfigPath(rootDir);
447
+ try {
448
+ const configContent = await fs5.readFile(configPath, "utf-8");
449
+ const oldConfig = JSON.parse(configContent);
450
+ if (!this.needsMigration(oldConfig)) {
451
+ return {
452
+ migrated: false,
453
+ config: oldConfig
454
+ };
455
+ }
456
+ const newConfig = migrateConfig(oldConfig);
457
+ const validation = this.validate(newConfig);
458
+ if (!validation.valid) {
459
+ throw new ConfigError(
460
+ `Migration produced invalid configuration:
461
+ ${validation.errors.join("\n")}`,
462
+ { errors: validation.errors }
463
+ );
464
+ }
465
+ const backupPath = `${configPath}.v0.2.0.backup`;
466
+ await fs5.copyFile(configPath, backupPath);
467
+ await this.save(rootDir, newConfig);
468
+ return {
469
+ migrated: true,
470
+ backupPath,
471
+ config: newConfig
472
+ };
473
+ } catch (error) {
474
+ if (error.code === "ENOENT") {
475
+ return {
476
+ migrated: false,
477
+ config: defaultConfig
478
+ };
479
+ }
480
+ if (error instanceof ConfigError) {
481
+ throw error;
482
+ }
483
+ throw wrapError(error, "Configuration migration failed", { path: configPath });
484
+ }
485
+ }
486
+ /**
487
+ * Check if a config object needs migration from v0.2.0 to v0.3.0.
488
+ *
489
+ * @param config - Config object to check
490
+ * @returns True if migration is needed
491
+ */
492
+ needsMigration(config) {
493
+ return needsMigration(config);
494
+ }
495
+ /**
496
+ * Validate a configuration object.
497
+ * Checks all constraints and returns detailed validation results.
498
+ *
499
+ * @param config - Configuration to validate
500
+ * @returns Validation result with errors and warnings
501
+ */
502
+ validate(config) {
503
+ const errors = [];
504
+ const warnings = [];
505
+ if (!config || typeof config !== "object") {
506
+ return {
507
+ valid: false,
508
+ errors: ["Configuration must be an object"],
509
+ warnings: []
510
+ };
511
+ }
512
+ const cfg = config;
513
+ if (!cfg.version) {
514
+ errors.push("Missing required field: version");
515
+ }
516
+ if (isModernConfig(cfg)) {
517
+ this.validateModernConfig(cfg, errors, warnings);
518
+ } else if (isLegacyConfig(cfg)) {
519
+ this.validateLegacyConfig(cfg, errors, warnings);
520
+ } else {
521
+ errors.push('Configuration format not recognized. Must have either "frameworks" or "indexing" field');
522
+ }
523
+ return {
524
+ valid: errors.length === 0,
525
+ errors,
526
+ warnings
527
+ };
528
+ }
529
+ /**
530
+ * Validate a partial configuration object.
531
+ * Useful for validating user input before merging with defaults.
532
+ *
533
+ * @param config - Partial configuration to validate
534
+ * @returns Validation result with errors and warnings
535
+ */
536
+ validatePartial(config) {
537
+ const errors = [];
538
+ const warnings = [];
539
+ if (config.core) {
540
+ this.validateCoreConfig(config.core, errors, warnings);
541
+ }
542
+ if (config.mcp) {
543
+ this.validateMCPConfig(config.mcp, errors, warnings);
544
+ }
545
+ if (config.gitDetection) {
546
+ this.validateGitDetectionConfig(config.gitDetection, errors, warnings);
547
+ }
548
+ if (config.fileWatching) {
549
+ this.validateFileWatchingConfig(config.fileWatching, errors, warnings);
550
+ }
551
+ if (config.frameworks) {
552
+ this.validateFrameworks(config.frameworks, errors, warnings);
553
+ }
554
+ return {
555
+ valid: errors.length === 0,
556
+ errors,
557
+ warnings
558
+ };
559
+ }
560
+ /**
561
+ * Get the full path to the config file
562
+ */
563
+ getConfigPath(rootDir) {
564
+ return path5.join(rootDir, _ConfigService.CONFIG_FILENAME);
565
+ }
566
+ /**
567
+ * Validate modern (v0.3.0+) configuration
568
+ */
569
+ validateModernConfig(config, errors, warnings) {
570
+ if (!config.core) {
571
+ errors.push("Missing required field: core");
572
+ return;
573
+ }
574
+ this.validateCoreConfig(config.core, errors, warnings);
575
+ if (!config.mcp) {
576
+ errors.push("Missing required field: mcp");
577
+ return;
578
+ }
579
+ this.validateMCPConfig(config.mcp, errors, warnings);
580
+ if (!config.gitDetection) {
581
+ errors.push("Missing required field: gitDetection");
582
+ return;
583
+ }
584
+ this.validateGitDetectionConfig(config.gitDetection, errors, warnings);
585
+ if (!config.fileWatching) {
586
+ errors.push("Missing required field: fileWatching");
587
+ return;
588
+ }
589
+ this.validateFileWatchingConfig(config.fileWatching, errors, warnings);
590
+ if (!config.frameworks) {
591
+ errors.push("Missing required field: frameworks");
592
+ return;
593
+ }
594
+ this.validateFrameworks(config.frameworks, errors, warnings);
595
+ }
596
+ /**
597
+ * Validate legacy (v0.2.0) configuration
598
+ */
599
+ validateLegacyConfig(config, errors, warnings) {
600
+ warnings.push('Using legacy configuration format. Consider running "lien init" to migrate to v0.3.0');
601
+ if (!config.indexing) {
602
+ errors.push("Missing required field: indexing");
603
+ return;
604
+ }
605
+ const { indexing } = config;
606
+ if (typeof indexing.chunkSize !== "number" || indexing.chunkSize <= 0) {
607
+ errors.push("indexing.chunkSize must be a positive number");
608
+ }
609
+ if (typeof indexing.chunkOverlap !== "number" || indexing.chunkOverlap < 0) {
610
+ errors.push("indexing.chunkOverlap must be a non-negative number");
611
+ }
612
+ if (typeof indexing.concurrency !== "number" || indexing.concurrency < 1 || indexing.concurrency > 16) {
613
+ errors.push("indexing.concurrency must be between 1 and 16");
614
+ }
615
+ if (typeof indexing.embeddingBatchSize !== "number" || indexing.embeddingBatchSize <= 0) {
616
+ errors.push("indexing.embeddingBatchSize must be a positive number");
617
+ }
618
+ if (config.mcp) {
619
+ this.validateMCPConfig(config.mcp, errors, warnings);
620
+ }
621
+ }
622
+ /**
623
+ * Validate core configuration settings
624
+ */
625
+ validateCoreConfig(core, errors, warnings) {
626
+ if (core.chunkSize !== void 0) {
627
+ if (typeof core.chunkSize !== "number" || core.chunkSize <= 0) {
628
+ errors.push("core.chunkSize must be a positive number");
629
+ } else if (core.chunkSize < 50) {
630
+ warnings.push("core.chunkSize is very small (<50 lines). This may result in poor search quality");
631
+ } else if (core.chunkSize > 500) {
632
+ warnings.push("core.chunkSize is very large (>500 lines). This may impact performance");
633
+ }
634
+ }
635
+ if (core.chunkOverlap !== void 0) {
636
+ if (typeof core.chunkOverlap !== "number" || core.chunkOverlap < 0) {
637
+ errors.push("core.chunkOverlap must be a non-negative number");
638
+ }
639
+ }
640
+ if (core.concurrency !== void 0) {
641
+ if (typeof core.concurrency !== "number" || core.concurrency < 1 || core.concurrency > 16) {
642
+ errors.push("core.concurrency must be between 1 and 16");
643
+ }
644
+ }
645
+ if (core.embeddingBatchSize !== void 0) {
646
+ if (typeof core.embeddingBatchSize !== "number" || core.embeddingBatchSize <= 0) {
647
+ errors.push("core.embeddingBatchSize must be a positive number");
648
+ } else if (core.embeddingBatchSize > 100) {
649
+ warnings.push("core.embeddingBatchSize is very large (>100). This may cause memory issues");
650
+ }
651
+ }
652
+ }
653
+ /**
654
+ * Validate MCP configuration settings
655
+ */
656
+ validateMCPConfig(mcp, errors, _warnings) {
657
+ if (mcp.port !== void 0) {
658
+ if (typeof mcp.port !== "number" || mcp.port < 1024 || mcp.port > 65535) {
659
+ errors.push("mcp.port must be between 1024 and 65535");
660
+ }
661
+ }
662
+ if (mcp.transport !== void 0) {
663
+ if (mcp.transport !== "stdio" && mcp.transport !== "socket") {
664
+ errors.push('mcp.transport must be either "stdio" or "socket"');
665
+ }
666
+ }
667
+ if (mcp.autoIndexOnFirstRun !== void 0) {
668
+ if (typeof mcp.autoIndexOnFirstRun !== "boolean") {
669
+ errors.push("mcp.autoIndexOnFirstRun must be a boolean");
670
+ }
671
+ }
672
+ }
673
+ /**
674
+ * Validate git detection configuration settings
675
+ */
676
+ validateGitDetectionConfig(gitDetection, errors, _warnings) {
677
+ if (gitDetection.enabled !== void 0) {
678
+ if (typeof gitDetection.enabled !== "boolean") {
679
+ errors.push("gitDetection.enabled must be a boolean");
680
+ }
681
+ }
682
+ if (gitDetection.pollIntervalMs !== void 0) {
683
+ if (typeof gitDetection.pollIntervalMs !== "number" || gitDetection.pollIntervalMs < 100) {
684
+ errors.push("gitDetection.pollIntervalMs must be at least 100ms");
685
+ } else if (gitDetection.pollIntervalMs < 1e3) {
686
+ _warnings.push("gitDetection.pollIntervalMs is very short (<1s). This may impact performance");
687
+ }
688
+ }
689
+ }
690
+ /**
691
+ * Validate file watching configuration settings
692
+ */
693
+ validateFileWatchingConfig(fileWatching, errors, warnings) {
694
+ if (fileWatching.enabled !== void 0) {
695
+ if (typeof fileWatching.enabled !== "boolean") {
696
+ errors.push("fileWatching.enabled must be a boolean");
697
+ }
698
+ }
699
+ if (fileWatching.debounceMs !== void 0) {
700
+ if (typeof fileWatching.debounceMs !== "number" || fileWatching.debounceMs < 0) {
701
+ errors.push("fileWatching.debounceMs must be a non-negative number");
702
+ } else if (fileWatching.debounceMs < 100) {
703
+ warnings.push("fileWatching.debounceMs is very short (<100ms). This may cause excessive reindexing");
704
+ }
705
+ }
706
+ }
707
+ /**
708
+ * Validate frameworks configuration
709
+ */
710
+ validateFrameworks(frameworks, errors, warnings) {
711
+ if (!Array.isArray(frameworks)) {
712
+ errors.push("frameworks must be an array");
713
+ return;
714
+ }
715
+ frameworks.forEach((framework, index) => {
716
+ if (!framework || typeof framework !== "object") {
717
+ errors.push(`frameworks[${index}] must be an object`);
718
+ return;
719
+ }
720
+ const fw = framework;
721
+ if (!fw.name) {
722
+ errors.push(`frameworks[${index}] missing required field: name`);
723
+ }
724
+ if (fw.path === void 0) {
725
+ errors.push(`frameworks[${index}] missing required field: path`);
726
+ } else if (typeof fw.path !== "string") {
727
+ errors.push(`frameworks[${index}].path must be a string`);
728
+ } else if (path5.isAbsolute(fw.path)) {
729
+ errors.push(`frameworks[${index}].path must be relative, got: ${fw.path}`);
730
+ }
731
+ if (fw.enabled === void 0) {
732
+ errors.push(`frameworks[${index}] missing required field: enabled`);
733
+ } else if (typeof fw.enabled !== "boolean") {
734
+ errors.push(`frameworks[${index}].enabled must be a boolean`);
735
+ }
736
+ if (!fw.config) {
737
+ errors.push(`frameworks[${index}] missing required field: config`);
738
+ } else {
739
+ this.validateFrameworkConfig(fw.config, `frameworks[${index}].config`, errors, warnings);
740
+ }
741
+ });
742
+ }
743
+ /**
744
+ * Validate framework-specific configuration
745
+ */
746
+ validateFrameworkConfig(config, prefix, errors, _warnings) {
747
+ if (!config || typeof config !== "object") {
748
+ errors.push(`${prefix} must be an object`);
749
+ return;
750
+ }
751
+ if (!Array.isArray(config.include)) {
752
+ errors.push(`${prefix}.include must be an array`);
753
+ } else {
754
+ config.include.forEach((pattern, i) => {
755
+ if (typeof pattern !== "string") {
756
+ errors.push(`${prefix}.include[${i}] must be a string`);
757
+ }
758
+ });
759
+ }
760
+ if (!Array.isArray(config.exclude)) {
761
+ errors.push(`${prefix}.exclude must be an array`);
762
+ } else {
763
+ config.exclude.forEach((pattern, i) => {
764
+ if (typeof pattern !== "string") {
765
+ errors.push(`${prefix}.exclude[${i}] must be a string`);
766
+ }
767
+ });
768
+ }
769
+ }
770
+ };
771
+ configService = new ConfigService();
772
+ }
773
+ });
774
+
775
+ // src/vectordb/version.ts
776
+ import fs7 from "fs/promises";
777
+ import path7 from "path";
778
+ async function writeVersionFile(indexPath) {
779
+ try {
780
+ const versionFilePath = path7.join(indexPath, VERSION_FILE);
781
+ const timestamp = Date.now().toString();
782
+ await fs7.writeFile(versionFilePath, timestamp, "utf-8");
783
+ } catch (error) {
784
+ console.error(`Warning: Failed to write version file: ${error}`);
785
+ }
786
+ }
787
+ async function readVersionFile(indexPath) {
788
+ try {
789
+ const versionFilePath = path7.join(indexPath, VERSION_FILE);
790
+ const content = await fs7.readFile(versionFilePath, "utf-8");
791
+ const timestamp = parseInt(content.trim(), 10);
792
+ return isNaN(timestamp) ? 0 : timestamp;
793
+ } catch (error) {
794
+ return 0;
795
+ }
796
+ }
797
+ var VERSION_FILE;
798
+ var init_version = __esm({
799
+ "src/vectordb/version.ts"() {
800
+ "use strict";
801
+ VERSION_FILE = ".lien-index-version";
802
+ }
803
+ });
804
+
805
+ // src/indexer/scanner.ts
806
+ import { glob } from "glob";
807
+ import ignore from "ignore";
808
+ import fs9 from "fs/promises";
809
+ import path9 from "path";
810
+ async function scanCodebaseWithFrameworks(rootDir, config) {
811
+ const allFiles = [];
812
+ for (const framework of config.frameworks) {
813
+ if (!framework.enabled) {
814
+ continue;
815
+ }
816
+ const frameworkFiles = await scanFramework(rootDir, framework);
817
+ allFiles.push(...frameworkFiles);
818
+ }
819
+ return allFiles;
820
+ }
821
+ async function scanFramework(rootDir, framework) {
822
+ const frameworkPath = path9.join(rootDir, framework.path);
823
+ const gitignorePath = path9.join(frameworkPath, ".gitignore");
824
+ let ig = ignore();
825
+ try {
826
+ const gitignoreContent = await fs9.readFile(gitignorePath, "utf-8");
827
+ ig = ignore().add(gitignoreContent);
828
+ } catch (e) {
829
+ const rootGitignorePath = path9.join(rootDir, ".gitignore");
830
+ try {
831
+ const gitignoreContent = await fs9.readFile(rootGitignorePath, "utf-8");
832
+ ig = ignore().add(gitignoreContent);
833
+ } catch (e2) {
834
+ }
835
+ }
836
+ ig.add([
837
+ ...framework.config.exclude,
838
+ ".lien/**"
839
+ ]);
840
+ const allFiles = [];
841
+ for (const pattern of framework.config.include) {
842
+ const files = await glob(pattern, {
843
+ cwd: frameworkPath,
844
+ absolute: false,
845
+ // Get paths relative to framework path
846
+ nodir: true,
847
+ ignore: framework.config.exclude
848
+ });
849
+ allFiles.push(...files);
850
+ }
851
+ const uniqueFiles = Array.from(new Set(allFiles));
852
+ return uniqueFiles.filter((file) => !ig.ignores(file)).map((file) => {
853
+ return framework.path === "." ? file : path9.join(framework.path, file);
854
+ });
855
+ }
856
+ async function scanCodebase(options) {
857
+ const { rootDir, includePatterns = [], excludePatterns = [] } = options;
858
+ const gitignorePath = path9.join(rootDir, ".gitignore");
859
+ let ig = ignore();
860
+ try {
861
+ const gitignoreContent = await fs9.readFile(gitignorePath, "utf-8");
862
+ ig = ignore().add(gitignoreContent);
863
+ } catch (e) {
864
+ }
865
+ ig.add([
866
+ "node_modules/**",
867
+ ".git/**",
868
+ "dist/**",
869
+ "build/**",
870
+ "*.min.js",
871
+ "*.min.css",
872
+ ".lien/**",
873
+ ...excludePatterns
874
+ ]);
875
+ const patterns = includePatterns.length > 0 ? includePatterns : ["**/*.{ts,tsx,js,jsx,py,go,rs,java,cpp,c,h,md,mdx}"];
876
+ const allFiles = [];
877
+ for (const pattern of patterns) {
878
+ const files = await glob(pattern, {
879
+ cwd: rootDir,
880
+ absolute: true,
881
+ nodir: true,
882
+ ignore: ["node_modules/**", ".git/**"]
883
+ });
884
+ allFiles.push(...files);
885
+ }
886
+ const uniqueFiles = Array.from(new Set(allFiles));
887
+ return uniqueFiles.filter((file) => {
888
+ const relativePath = path9.relative(rootDir, file);
889
+ return !ig.ignores(relativePath);
890
+ });
891
+ }
892
+ function detectLanguage(filepath) {
893
+ const ext = path9.extname(filepath).toLowerCase();
894
+ const languageMap = {
895
+ ".ts": "typescript",
896
+ ".tsx": "typescript",
897
+ ".js": "javascript",
898
+ ".jsx": "javascript",
899
+ ".mjs": "javascript",
900
+ ".cjs": "javascript",
901
+ ".vue": "vue",
902
+ ".py": "python",
903
+ ".go": "go",
904
+ ".rs": "rust",
905
+ ".java": "java",
906
+ ".cpp": "cpp",
907
+ ".cc": "cpp",
908
+ ".cxx": "cpp",
909
+ ".c": "c",
910
+ ".h": "c",
911
+ ".hpp": "cpp",
912
+ ".php": "php",
913
+ ".rb": "ruby",
914
+ ".swift": "swift",
915
+ ".kt": "kotlin",
916
+ ".cs": "csharp",
917
+ ".scala": "scala",
918
+ ".md": "markdown",
919
+ ".mdx": "markdown",
920
+ ".markdown": "markdown"
921
+ };
922
+ return languageMap[ext] || "unknown";
923
+ }
924
+ var init_scanner = __esm({
925
+ "src/indexer/scanner.ts"() {
926
+ "use strict";
927
+ }
928
+ });
929
+
930
+ // src/indexer/symbol-extractor.ts
931
+ function extractSymbols(content, language) {
932
+ const symbols = {
933
+ functions: [],
934
+ classes: [],
935
+ interfaces: []
936
+ };
937
+ const normalizedLang = language.toLowerCase();
938
+ switch (normalizedLang) {
939
+ case "typescript":
940
+ case "tsx":
941
+ symbols.functions = extractTSFunctions(content);
942
+ symbols.classes = extractTSClasses(content);
943
+ symbols.interfaces = extractTSInterfaces(content);
944
+ break;
945
+ case "javascript":
946
+ case "jsx":
947
+ symbols.functions = extractJSFunctions(content);
948
+ symbols.classes = extractJSClasses(content);
949
+ break;
950
+ case "python":
951
+ case "py":
952
+ symbols.functions = extractPythonFunctions(content);
953
+ symbols.classes = extractPythonClasses(content);
954
+ break;
955
+ case "php":
956
+ symbols.functions = extractPHPFunctions(content);
957
+ symbols.classes = extractPHPClasses(content);
958
+ symbols.interfaces = extractPHPInterfaces(content);
959
+ break;
960
+ case "vue":
961
+ symbols.functions = extractVueFunctions(content);
962
+ symbols.classes = extractVueComponents(content);
963
+ break;
964
+ case "go":
965
+ symbols.functions = extractGoFunctions(content);
966
+ symbols.interfaces = extractGoInterfaces(content);
967
+ break;
968
+ case "java":
969
+ symbols.functions = extractJavaFunctions(content);
970
+ symbols.classes = extractJavaClasses(content);
971
+ symbols.interfaces = extractJavaInterfaces(content);
972
+ break;
973
+ case "csharp":
974
+ case "cs":
975
+ symbols.functions = extractCSharpFunctions(content);
976
+ symbols.classes = extractCSharpClasses(content);
977
+ symbols.interfaces = extractCSharpInterfaces(content);
978
+ break;
979
+ case "ruby":
980
+ case "rb":
981
+ symbols.functions = extractRubyFunctions(content);
982
+ symbols.classes = extractRubyClasses(content);
983
+ break;
984
+ case "rust":
985
+ case "rs":
986
+ symbols.functions = extractRustFunctions(content);
987
+ break;
988
+ }
989
+ return symbols;
990
+ }
991
+ function extractTSFunctions(content) {
992
+ const names = /* @__PURE__ */ new Set();
993
+ const functionMatches = content.matchAll(/(?:async\s+)?function\s+(\w+)\s*\(/g);
994
+ for (const match of functionMatches) {
995
+ names.add(match[1]);
996
+ }
997
+ const arrowMatches = content.matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g);
998
+ for (const match of arrowMatches) {
999
+ names.add(match[1]);
1000
+ }
1001
+ const methodMatches = content.matchAll(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/g);
1002
+ for (const match of methodMatches) {
1003
+ if (!["if", "for", "while", "switch", "catch"].includes(match[1])) {
1004
+ names.add(match[1]);
1005
+ }
1006
+ }
1007
+ const exportMatches = content.matchAll(/export\s+(?:async\s+)?function\s+(\w+)\s*\(/g);
1008
+ for (const match of exportMatches) {
1009
+ names.add(match[1]);
1010
+ }
1011
+ return Array.from(names);
1012
+ }
1013
+ function extractJSFunctions(content) {
1014
+ return extractTSFunctions(content);
1015
+ }
1016
+ function extractTSClasses(content) {
1017
+ const names = /* @__PURE__ */ new Set();
1018
+ const classMatches = content.matchAll(/(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g);
1019
+ for (const match of classMatches) {
1020
+ names.add(match[1]);
1021
+ }
1022
+ return Array.from(names);
1023
+ }
1024
+ function extractJSClasses(content) {
1025
+ return extractTSClasses(content);
1026
+ }
1027
+ function extractTSInterfaces(content) {
1028
+ const names = /* @__PURE__ */ new Set();
1029
+ const interfaceMatches = content.matchAll(/(?:export\s+)?interface\s+(\w+)/g);
1030
+ for (const match of interfaceMatches) {
1031
+ names.add(match[1]);
1032
+ }
1033
+ const typeMatches = content.matchAll(/(?:export\s+)?type\s+(\w+)\s*=/g);
1034
+ for (const match of typeMatches) {
1035
+ names.add(match[1]);
1036
+ }
1037
+ return Array.from(names);
1038
+ }
1039
+ function extractPythonFunctions(content) {
1040
+ const names = /* @__PURE__ */ new Set();
1041
+ const functionMatches = content.matchAll(/def\s+(\w+)\s*\(/g);
1042
+ for (const match of functionMatches) {
1043
+ names.add(match[1]);
1044
+ }
1045
+ const asyncMatches = content.matchAll(/async\s+def\s+(\w+)\s*\(/g);
1046
+ for (const match of asyncMatches) {
1047
+ names.add(match[1]);
1048
+ }
1049
+ return Array.from(names);
1050
+ }
1051
+ function extractPythonClasses(content) {
1052
+ const names = /* @__PURE__ */ new Set();
1053
+ const classMatches = content.matchAll(/class\s+(\w+)(?:\s*\(|:)/g);
1054
+ for (const match of classMatches) {
1055
+ names.add(match[1]);
1056
+ }
1057
+ return Array.from(names);
1058
+ }
1059
+ function extractPHPFunctions(content) {
1060
+ const names = /* @__PURE__ */ new Set();
1061
+ const functionMatches = content.matchAll(/(?:public|private|protected)?\s*function\s+(\w+)\s*\(/g);
1062
+ for (const match of functionMatches) {
1063
+ names.add(match[1]);
1064
+ }
1065
+ return Array.from(names);
1066
+ }
1067
+ function extractPHPClasses(content) {
1068
+ const names = /* @__PURE__ */ new Set();
1069
+ const classMatches = content.matchAll(/(?:abstract\s+)?class\s+(\w+)/g);
1070
+ for (const match of classMatches) {
1071
+ names.add(match[1]);
1072
+ }
1073
+ return Array.from(names);
1074
+ }
1075
+ function extractPHPInterfaces(content) {
1076
+ const names = /* @__PURE__ */ new Set();
1077
+ const interfaceMatches = content.matchAll(/interface\s+(\w+)/g);
1078
+ for (const match of interfaceMatches) {
1079
+ names.add(match[1]);
1080
+ }
1081
+ const traitMatches = content.matchAll(/trait\s+(\w+)/g);
1082
+ for (const match of traitMatches) {
1083
+ names.add(match[1]);
1084
+ }
1085
+ return Array.from(names);
1086
+ }
1087
+ function extractGoFunctions(content) {
1088
+ const names = /* @__PURE__ */ new Set();
1089
+ const functionMatches = content.matchAll(/func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/g);
1090
+ for (const match of functionMatches) {
1091
+ names.add(match[1]);
1092
+ }
1093
+ return Array.from(names);
1094
+ }
1095
+ function extractGoInterfaces(content) {
1096
+ const names = /* @__PURE__ */ new Set();
1097
+ const interfaceMatches = content.matchAll(/type\s+(\w+)\s+interface\s*\{/g);
1098
+ for (const match of interfaceMatches) {
1099
+ names.add(match[1]);
1100
+ }
1101
+ const structMatches = content.matchAll(/type\s+(\w+)\s+struct\s*\{/g);
1102
+ for (const match of structMatches) {
1103
+ names.add(match[1]);
1104
+ }
1105
+ return Array.from(names);
1106
+ }
1107
+ function extractJavaFunctions(content) {
1108
+ const names = /* @__PURE__ */ new Set();
1109
+ const methodMatches = content.matchAll(/(?:public|private|protected)\s+(?:static\s+)?(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/g);
1110
+ for (const match of methodMatches) {
1111
+ names.add(match[1]);
1112
+ }
1113
+ return Array.from(names);
1114
+ }
1115
+ function extractJavaClasses(content) {
1116
+ const names = /* @__PURE__ */ new Set();
1117
+ const classMatches = content.matchAll(/(?:public\s+)?(?:abstract\s+)?class\s+(\w+)/g);
1118
+ for (const match of classMatches) {
1119
+ names.add(match[1]);
1120
+ }
1121
+ return Array.from(names);
1122
+ }
1123
+ function extractJavaInterfaces(content) {
1124
+ const names = /* @__PURE__ */ new Set();
1125
+ const interfaceMatches = content.matchAll(/(?:public\s+)?interface\s+(\w+)/g);
1126
+ for (const match of interfaceMatches) {
1127
+ names.add(match[1]);
1128
+ }
1129
+ return Array.from(names);
1130
+ }
1131
+ function extractCSharpFunctions(content) {
1132
+ const names = /* @__PURE__ */ new Set();
1133
+ const methodMatches = content.matchAll(/(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/g);
1134
+ for (const match of methodMatches) {
1135
+ names.add(match[1]);
1136
+ }
1137
+ return Array.from(names);
1138
+ }
1139
+ function extractCSharpClasses(content) {
1140
+ const names = /* @__PURE__ */ new Set();
1141
+ const classMatches = content.matchAll(/(?:public|internal)?\s*(?:abstract\s+)?class\s+(\w+)/g);
1142
+ for (const match of classMatches) {
1143
+ names.add(match[1]);
1144
+ }
1145
+ return Array.from(names);
1146
+ }
1147
+ function extractCSharpInterfaces(content) {
1148
+ const names = /* @__PURE__ */ new Set();
1149
+ const interfaceMatches = content.matchAll(/(?:public|internal)?\s*interface\s+(\w+)/g);
1150
+ for (const match of interfaceMatches) {
1151
+ names.add(match[1]);
1152
+ }
1153
+ return Array.from(names);
1154
+ }
1155
+ function extractRubyFunctions(content) {
1156
+ const names = /* @__PURE__ */ new Set();
1157
+ const methodMatches = content.matchAll(/def\s+(?:self\.)?(\w+)/g);
1158
+ for (const match of methodMatches) {
1159
+ names.add(match[1]);
1160
+ }
1161
+ return Array.from(names);
1162
+ }
1163
+ function extractRubyClasses(content) {
1164
+ const names = /* @__PURE__ */ new Set();
1165
+ const classMatches = content.matchAll(/class\s+(\w+)/g);
1166
+ for (const match of classMatches) {
1167
+ names.add(match[1]);
1168
+ }
1169
+ const moduleMatches = content.matchAll(/module\s+(\w+)/g);
1170
+ for (const match of moduleMatches) {
1171
+ names.add(match[1]);
1172
+ }
1173
+ return Array.from(names);
1174
+ }
1175
+ function extractRustFunctions(content) {
1176
+ const names = /* @__PURE__ */ new Set();
1177
+ const functionMatches = content.matchAll(/(?:pub\s+)?fn\s+(\w+)\s*\(/g);
1178
+ for (const match of functionMatches) {
1179
+ names.add(match[1]);
1180
+ }
1181
+ const structMatches = content.matchAll(/(?:pub\s+)?struct\s+(\w+)/g);
1182
+ for (const match of structMatches) {
1183
+ names.add(match[1]);
1184
+ }
1185
+ const traitMatches = content.matchAll(/(?:pub\s+)?trait\s+(\w+)/g);
1186
+ for (const match of traitMatches) {
1187
+ names.add(match[1]);
1188
+ }
1189
+ return Array.from(names);
1190
+ }
1191
+ function extractVueFunctions(content) {
1192
+ const names = /* @__PURE__ */ new Set();
1193
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
1194
+ if (!scriptMatch) return [];
1195
+ const scriptContent = scriptMatch[1];
1196
+ const compositionMatches = scriptContent.matchAll(/(?:const|function)\s+(\w+)\s*=/g);
1197
+ for (const match of compositionMatches) {
1198
+ names.add(match[1]);
1199
+ }
1200
+ const methodMatches = scriptContent.matchAll(/(\w+)\s*\([^)]*\)\s*{/g);
1201
+ for (const match of methodMatches) {
1202
+ names.add(match[1]);
1203
+ }
1204
+ return Array.from(names);
1205
+ }
1206
+ function extractVueComponents(content) {
1207
+ const names = /* @__PURE__ */ new Set();
1208
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
1209
+ if (!scriptMatch) return [];
1210
+ const scriptContent = scriptMatch[1];
1211
+ const nameMatch = scriptContent.match(/name:\s*['"](\w+)['"]/);
1212
+ if (nameMatch) {
1213
+ names.add(nameMatch[1]);
1214
+ }
1215
+ const defineComponentMatch = scriptContent.match(/defineComponent\s*\(/);
1216
+ if (defineComponentMatch) {
1217
+ names.add("VueComponent");
1218
+ }
1219
+ return Array.from(names);
1220
+ }
1221
+ var init_symbol_extractor = __esm({
1222
+ "src/indexer/symbol-extractor.ts"() {
1223
+ "use strict";
1224
+ }
1225
+ });
1226
+
1227
+ // src/indexer/chunker.ts
1228
+ function chunkFile(filepath, content, options = {}) {
1229
+ const { chunkSize = 75, chunkOverlap = 10 } = options;
1230
+ const lines = content.split("\n");
1231
+ const chunks = [];
1232
+ const language = detectLanguage(filepath);
1233
+ if (lines.length === 0 || lines.length === 1 && lines[0].trim() === "") {
1234
+ return chunks;
1235
+ }
1236
+ for (let i = 0; i < lines.length; i += chunkSize - chunkOverlap) {
1237
+ const endLine = Math.min(i + chunkSize, lines.length);
1238
+ const chunkLines = lines.slice(i, endLine);
1239
+ const chunkContent = chunkLines.join("\n");
1240
+ if (chunkContent.trim().length === 0) {
1241
+ continue;
1242
+ }
1243
+ const symbols = extractSymbols(chunkContent, language);
1244
+ chunks.push({
1245
+ content: chunkContent,
1246
+ metadata: {
1247
+ file: filepath,
1248
+ startLine: i + 1,
1249
+ endLine,
1250
+ type: "block",
1251
+ // MVP: all chunks are 'block' type
1252
+ language,
1253
+ symbols
1254
+ }
1255
+ });
1256
+ if (endLine >= lines.length) {
1257
+ break;
1258
+ }
1259
+ }
1260
+ return chunks;
1261
+ }
1262
+ var init_chunker = __esm({
1263
+ "src/indexer/chunker.ts"() {
1264
+ "use strict";
1265
+ init_scanner();
1266
+ init_symbol_extractor();
1267
+ }
1268
+ });
1269
+
1270
+ // src/embeddings/local.ts
1271
+ import { pipeline, env } from "@xenova/transformers";
1272
+ var LocalEmbeddings;
1273
+ var init_local = __esm({
1274
+ "src/embeddings/local.ts"() {
1275
+ "use strict";
1276
+ init_errors();
1277
+ init_constants();
1278
+ env.allowRemoteModels = true;
1279
+ env.allowLocalModels = true;
1280
+ LocalEmbeddings = class {
1281
+ extractor = null;
1282
+ modelName = DEFAULT_EMBEDDING_MODEL;
1283
+ initPromise = null;
1284
+ async initialize() {
1285
+ if (this.initPromise) {
1286
+ return this.initPromise;
1287
+ }
1288
+ if (this.extractor) {
1289
+ return;
1290
+ }
1291
+ this.initPromise = (async () => {
1292
+ try {
1293
+ this.extractor = await pipeline("feature-extraction", this.modelName);
1294
+ } catch (error) {
1295
+ this.initPromise = null;
1296
+ throw wrapError(error, "Failed to initialize embedding model");
1297
+ }
1298
+ })();
1299
+ return this.initPromise;
1300
+ }
1301
+ async embed(text) {
1302
+ await this.initialize();
1303
+ if (!this.extractor) {
1304
+ throw new EmbeddingError("Embedding model not initialized");
1305
+ }
1306
+ try {
1307
+ const output = await this.extractor(text, {
1308
+ pooling: "mean",
1309
+ normalize: true
1310
+ });
1311
+ return output.data;
1312
+ } catch (error) {
1313
+ throw wrapError(error, "Failed to generate embedding", { textLength: text.length });
1314
+ }
1315
+ }
1316
+ async embedBatch(texts) {
1317
+ await this.initialize();
1318
+ if (!this.extractor) {
1319
+ throw new EmbeddingError("Embedding model not initialized");
1320
+ }
1321
+ try {
1322
+ const results = await Promise.all(
1323
+ texts.map((text) => this.embed(text))
1324
+ );
1325
+ return results;
1326
+ } catch (error) {
1327
+ throw wrapError(error, "Failed to generate batch embeddings", { batchSize: texts.length });
1328
+ }
1329
+ }
1330
+ };
1331
+ }
1332
+ });
1333
+
1334
+ // src/embeddings/types.ts
1335
+ var EMBEDDING_DIMENSION;
1336
+ var init_types = __esm({
1337
+ "src/embeddings/types.ts"() {
1338
+ "use strict";
1339
+ init_constants();
1340
+ EMBEDDING_DIMENSION = EMBEDDING_DIMENSIONS;
1341
+ }
1342
+ });
1343
+
1344
+ // src/vectordb/relevance.ts
1345
+ function calculateRelevance(score) {
1346
+ if (score < 1) return "highly_relevant";
1347
+ if (score < 1.3) return "relevant";
1348
+ if (score < 1.5) return "loosely_related";
1349
+ return "not_relevant";
1350
+ }
1351
+ var init_relevance = __esm({
1352
+ "src/vectordb/relevance.ts"() {
1353
+ "use strict";
1354
+ }
1355
+ });
1356
+
1357
+ // src/vectordb/intent-classifier.ts
1358
+ function classifyQueryIntent(query) {
1359
+ const lower = query.toLowerCase().trim();
1360
+ if (lower.match(/where\s+(is|are|does|can\s+i\s+find)/) || lower.match(/find\s+the\s+/) || lower.match(/locate\s+/)) {
1361
+ return "location" /* LOCATION */;
1362
+ }
1363
+ if (lower.match(/how\s+does\s+.*\s+work/) || lower.match(/what\s+(is|are|does)/) || lower.match(/explain\s+/) || lower.match(/understand\s+/) || lower.match(/\b(process|workflow|architecture)\b/)) {
1364
+ return "conceptual" /* CONCEPTUAL */;
1365
+ }
1366
+ if (lower.match(/how\s+(is|are)\s+.*\s+(implemented|built|coded)/) || lower.match(/implementation\s+of/) || lower.match(/source\s+code\s+for/)) {
1367
+ return "implementation" /* IMPLEMENTATION */;
1368
+ }
1369
+ return "implementation" /* IMPLEMENTATION */;
1370
+ }
1371
+ var init_intent_classifier = __esm({
1372
+ "src/vectordb/intent-classifier.ts"() {
1373
+ "use strict";
1374
+ }
1375
+ });
1376
+
1377
+ // src/vectordb/lancedb.ts
1378
+ var lancedb_exports = {};
1379
+ __export(lancedb_exports, {
1380
+ VectorDB: () => VectorDB
1381
+ });
1382
+ import * as lancedb from "vectordb";
1383
+ import path10 from "path";
1384
+ import os2 from "os";
1385
+ import crypto2 from "crypto";
1386
+ function isDocumentationFile(filepath) {
1387
+ const lower = filepath.toLowerCase();
1388
+ const filename = path10.basename(filepath).toLowerCase();
1389
+ if (filename.startsWith("readme")) return true;
1390
+ if (filename.startsWith("changelog")) return true;
1391
+ if (filename.endsWith(".md") || filename.endsWith(".mdx") || filename.endsWith(".markdown")) {
1392
+ return true;
1393
+ }
1394
+ if (lower.includes("/docs/") || lower.includes("/documentation/") || lower.includes("/wiki/") || lower.includes("/.github/")) {
1395
+ return true;
1396
+ }
1397
+ if (lower.includes("architecture") || lower.includes("workflow") || lower.includes("/flow/")) {
1398
+ return true;
1399
+ }
1400
+ return false;
1401
+ }
1402
+ function isTestFile(filepath) {
1403
+ const lower = filepath.toLowerCase();
1404
+ if (lower.includes("/test/") || lower.includes("/tests/") || lower.includes("/__tests__/")) {
1405
+ return true;
1406
+ }
1407
+ if (lower.includes(".test.") || lower.includes(".spec.") || lower.includes("_test.") || lower.includes("_spec.")) {
1408
+ return true;
1409
+ }
1410
+ return false;
1411
+ }
1412
+ function isUtilityFile(filepath) {
1413
+ const lower = filepath.toLowerCase();
1414
+ if (lower.includes("/utils/") || lower.includes("/utilities/") || lower.includes("/helpers/") || lower.includes("/lib/")) {
1415
+ return true;
1416
+ }
1417
+ if (lower.includes(".util.") || lower.includes(".helper.") || lower.includes("-util.") || lower.includes("-helper.")) {
1418
+ return true;
1419
+ }
1420
+ return false;
1421
+ }
1422
+ function boostPathRelevance(query, filepath, baseScore) {
1423
+ const queryTokens = query.toLowerCase().split(/\s+/);
1424
+ const pathSegments = filepath.toLowerCase().split("/");
1425
+ let boostFactor = 1;
1426
+ for (const token of queryTokens) {
1427
+ if (token.length <= 2) continue;
1428
+ if (pathSegments.some((seg) => seg.includes(token))) {
1429
+ boostFactor *= 0.9;
1430
+ }
1431
+ }
1432
+ return baseScore * boostFactor;
1433
+ }
1434
+ function boostFilenameRelevance(query, filepath, baseScore) {
1435
+ const filename = path10.basename(filepath, path10.extname(filepath)).toLowerCase();
1436
+ const queryTokens = query.toLowerCase().split(/\s+/);
1437
+ let boostFactor = 1;
1438
+ for (const token of queryTokens) {
1439
+ if (token.length <= 2) continue;
1440
+ if (filename === token) {
1441
+ boostFactor *= 0.7;
1442
+ } else if (filename.includes(token)) {
1443
+ boostFactor *= 0.8;
1444
+ }
1445
+ }
1446
+ return baseScore * boostFactor;
1447
+ }
1448
+ function boostForLocationIntent(query, filepath, baseScore) {
1449
+ let score = baseScore;
1450
+ const filename = path10.basename(filepath, path10.extname(filepath)).toLowerCase();
1451
+ const queryTokens = query.toLowerCase().split(/\s+/);
1452
+ for (const token of queryTokens) {
1453
+ if (token.length <= 2) continue;
1454
+ if (filename === token) {
1455
+ score *= 0.6;
1456
+ } else if (filename.includes(token)) {
1457
+ score *= 0.7;
1458
+ }
1459
+ }
1460
+ score = boostPathRelevance(query, filepath, score);
1461
+ if (isTestFile(filepath)) {
1462
+ score *= 1.1;
1463
+ }
1464
+ return score;
1465
+ }
1466
+ function boostForConceptualIntent(query, filepath, baseScore) {
1467
+ let score = baseScore;
1468
+ if (isDocumentationFile(filepath)) {
1469
+ score *= 0.65;
1470
+ const lower = filepath.toLowerCase();
1471
+ if (lower.includes("architecture") || lower.includes("workflow") || lower.includes("flow")) {
1472
+ score *= 0.9;
1473
+ }
1474
+ }
1475
+ if (isUtilityFile(filepath)) {
1476
+ score *= 1.05;
1477
+ }
1478
+ const filename = path10.basename(filepath, path10.extname(filepath)).toLowerCase();
1479
+ const queryTokens = query.toLowerCase().split(/\s+/);
1480
+ for (const token of queryTokens) {
1481
+ if (token.length <= 2) continue;
1482
+ if (filename.includes(token)) {
1483
+ score *= 0.9;
1484
+ }
1485
+ }
1486
+ const pathSegments = filepath.toLowerCase().split(path10.sep);
1487
+ for (const token of queryTokens) {
1488
+ if (token.length <= 2) continue;
1489
+ for (const segment of pathSegments) {
1490
+ if (segment.includes(token)) {
1491
+ score *= 0.95;
1492
+ break;
1493
+ }
1494
+ }
1495
+ }
1496
+ return score;
1497
+ }
1498
+ function boostForImplementationIntent(query, filepath, baseScore) {
1499
+ let score = baseScore;
1500
+ score = boostFilenameRelevance(query, filepath, score);
1501
+ score = boostPathRelevance(query, filepath, score);
1502
+ if (isTestFile(filepath)) {
1503
+ score *= 0.9;
1504
+ }
1505
+ return score;
1506
+ }
1507
+ function applyRelevanceBoosting(query, filepath, baseScore) {
1508
+ if (!query) {
1509
+ return baseScore;
1510
+ }
1511
+ const intent = classifyQueryIntent(query);
1512
+ switch (intent) {
1513
+ case "location" /* LOCATION */:
1514
+ return boostForLocationIntent(query, filepath, baseScore);
1515
+ case "conceptual" /* CONCEPTUAL */:
1516
+ return boostForConceptualIntent(query, filepath, baseScore);
1517
+ case "implementation" /* IMPLEMENTATION */:
1518
+ return boostForImplementationIntent(query, filepath, baseScore);
1519
+ default:
1520
+ return boostForImplementationIntent(query, filepath, baseScore);
1521
+ }
1522
+ }
1523
+ var VectorDB;
1524
+ var init_lancedb = __esm({
1525
+ "src/vectordb/lancedb.ts"() {
1526
+ "use strict";
1527
+ init_types();
1528
+ init_version();
1529
+ init_errors();
1530
+ init_relevance();
1531
+ init_intent_classifier();
1532
+ VectorDB = class _VectorDB {
1533
+ db = null;
1534
+ table = null;
1535
+ dbPath;
1536
+ tableName = "code_chunks";
1537
+ lastVersionCheck = 0;
1538
+ currentVersion = 0;
1539
+ constructor(projectRoot) {
1540
+ const projectName = path10.basename(projectRoot);
1541
+ const pathHash = crypto2.createHash("md5").update(projectRoot).digest("hex").substring(0, 8);
1542
+ this.dbPath = path10.join(
1543
+ os2.homedir(),
1544
+ ".lien",
1545
+ "indices",
1546
+ `${projectName}-${pathHash}`
1547
+ );
1548
+ }
1549
+ async initialize() {
1550
+ try {
1551
+ this.db = await lancedb.connect(this.dbPath);
1552
+ try {
1553
+ this.table = await this.db.openTable(this.tableName);
1554
+ } catch {
1555
+ this.table = null;
1556
+ }
1557
+ try {
1558
+ this.currentVersion = await readVersionFile(this.dbPath);
1559
+ } catch {
1560
+ this.currentVersion = 0;
1561
+ }
1562
+ } catch (error) {
1563
+ throw wrapError(error, "Failed to initialize vector database", { dbPath: this.dbPath });
1564
+ }
1565
+ }
1566
+ async insertBatch(vectors, metadatas, contents) {
1567
+ if (!this.db) {
1568
+ throw new DatabaseError("Vector database not initialized");
1569
+ }
1570
+ if (vectors.length !== metadatas.length || vectors.length !== contents.length) {
1571
+ throw new DatabaseError("Vectors, metadatas, and contents arrays must have the same length", {
1572
+ vectorsLength: vectors.length,
1573
+ metadatasLength: metadatas.length,
1574
+ contentsLength: contents.length
1575
+ });
1576
+ }
1577
+ try {
1578
+ const records = vectors.map((vector, i) => ({
1579
+ vector: Array.from(vector),
1580
+ content: contents[i],
1581
+ file: metadatas[i].file,
1582
+ startLine: metadatas[i].startLine,
1583
+ endLine: metadatas[i].endLine,
1584
+ type: metadatas[i].type,
1585
+ language: metadatas[i].language,
1586
+ // Ensure arrays have at least empty string for Arrow type inference
1587
+ functionNames: metadatas[i].symbols?.functions && metadatas[i].symbols.functions.length > 0 ? metadatas[i].symbols.functions : [""],
1588
+ classNames: metadatas[i].symbols?.classes && metadatas[i].symbols.classes.length > 0 ? metadatas[i].symbols.classes : [""],
1589
+ interfaceNames: metadatas[i].symbols?.interfaces && metadatas[i].symbols.interfaces.length > 0 ? metadatas[i].symbols.interfaces : [""]
1590
+ }));
1591
+ if (!this.table) {
1592
+ this.table = await this.db.createTable(this.tableName, records);
1593
+ } else {
1594
+ await this.table.add(records);
1595
+ }
1596
+ } catch (error) {
1597
+ throw wrapError(error, "Failed to insert batch into vector database");
1598
+ }
1599
+ }
1600
+ async search(queryVector, limit = 5, query) {
1601
+ if (!this.table) {
1602
+ throw new DatabaseError("Vector database not initialized");
1603
+ }
1604
+ try {
1605
+ const results = await this.table.search(Array.from(queryVector)).limit(limit + 20).execute();
1606
+ const filtered = results.filter(
1607
+ (r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
1608
+ ).map((r) => {
1609
+ const baseScore = r._distance ?? 0;
1610
+ const boostedScore = applyRelevanceBoosting(query, r.file, baseScore);
1611
+ return {
1612
+ content: r.content,
1613
+ metadata: {
1614
+ file: r.file,
1615
+ startLine: r.startLine,
1616
+ endLine: r.endLine,
1617
+ type: r.type,
1618
+ language: r.language
1619
+ },
1620
+ score: boostedScore,
1621
+ relevance: calculateRelevance(boostedScore)
1622
+ };
1623
+ }).sort((a, b) => a.score - b.score).slice(0, limit);
1624
+ return filtered;
1625
+ } catch (error) {
1626
+ const errorMsg = String(error);
1627
+ if (errorMsg.includes("Not found:") || errorMsg.includes(".lance")) {
1628
+ try {
1629
+ await this.initialize();
1630
+ const results = await this.table.search(Array.from(queryVector)).limit(limit + 20).execute();
1631
+ return results.filter(
1632
+ (r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
1633
+ ).map((r) => {
1634
+ const baseScore = r._distance ?? 0;
1635
+ const boostedScore = applyRelevanceBoosting(query, r.file, baseScore);
1636
+ return {
1637
+ content: r.content,
1638
+ metadata: {
1639
+ file: r.file,
1640
+ startLine: r.startLine,
1641
+ endLine: r.endLine,
1642
+ type: r.type,
1643
+ language: r.language
1644
+ },
1645
+ score: boostedScore,
1646
+ relevance: calculateRelevance(boostedScore)
1647
+ };
1648
+ }).sort((a, b) => a.score - b.score).slice(0, limit);
1649
+ } catch (retryError) {
1650
+ throw new DatabaseError(
1651
+ `Index appears corrupted or outdated. Please restart the MCP server or run 'lien reindex' in the project directory.`,
1652
+ { originalError: retryError }
1653
+ );
1654
+ }
1655
+ }
1656
+ throw wrapError(error, "Failed to search vector database");
1657
+ }
1658
+ }
1659
+ async scanWithFilter(options) {
1660
+ if (!this.table) {
1661
+ throw new DatabaseError("Vector database not initialized");
1662
+ }
1663
+ const { language, pattern, limit = 100 } = options;
1664
+ try {
1665
+ const zeroVector = Array(EMBEDDING_DIMENSION).fill(0);
1666
+ const query = this.table.search(zeroVector).where('file != ""').limit(Math.max(limit * 5, 200));
1667
+ const results = await query.execute();
1668
+ let filtered = results.filter(
1669
+ (r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
1670
+ );
1671
+ if (language) {
1672
+ filtered = filtered.filter(
1673
+ (r) => r.language && r.language.toLowerCase() === language.toLowerCase()
1674
+ );
1675
+ }
1676
+ if (pattern) {
1677
+ const regex = new RegExp(pattern, "i");
1678
+ filtered = filtered.filter(
1679
+ (r) => regex.test(r.content) || regex.test(r.file)
1680
+ );
1681
+ }
1682
+ return filtered.slice(0, limit).map((r) => {
1683
+ const score = 0;
1684
+ return {
1685
+ content: r.content,
1686
+ metadata: {
1687
+ file: r.file,
1688
+ startLine: r.startLine,
1689
+ endLine: r.endLine,
1690
+ type: r.type,
1691
+ language: r.language
1692
+ },
1693
+ score,
1694
+ relevance: calculateRelevance(score)
1695
+ };
1696
+ });
1697
+ } catch (error) {
1698
+ throw wrapError(error, "Failed to scan with filter");
1699
+ }
1700
+ }
1701
+ async querySymbols(options) {
1702
+ if (!this.table) {
1703
+ throw new DatabaseError("Vector database not initialized");
1704
+ }
1705
+ const { language, pattern, symbolType, limit = 50 } = options;
1706
+ try {
1707
+ const zeroVector = Array(EMBEDDING_DIMENSION).fill(0);
1708
+ const query = this.table.search(zeroVector).where('file != ""').limit(Math.max(limit * 10, 500));
1709
+ const results = await query.execute();
1710
+ let filtered = results.filter((r) => {
1711
+ if (!r.content || r.content.trim().length === 0) {
1712
+ return false;
1713
+ }
1714
+ if (!r.file || r.file.length === 0) {
1715
+ return false;
1716
+ }
1717
+ if (language && (!r.language || r.language.toLowerCase() !== language.toLowerCase())) {
1718
+ return false;
1719
+ }
1720
+ const symbols = symbolType === "function" ? r.functionNames || [] : symbolType === "class" ? r.classNames || [] : symbolType === "interface" ? r.interfaceNames || [] : [...r.functionNames || [], ...r.classNames || [], ...r.interfaceNames || []];
1721
+ if (symbols.length === 0) {
1722
+ return false;
1723
+ }
1724
+ if (pattern) {
1725
+ const regex = new RegExp(pattern, "i");
1726
+ return symbols.some((s) => regex.test(s));
1727
+ }
1728
+ return true;
1729
+ });
1730
+ return filtered.slice(0, limit).map((r) => {
1731
+ const score = 0;
1732
+ return {
1733
+ content: r.content,
1734
+ metadata: {
1735
+ file: r.file,
1736
+ startLine: r.startLine,
1737
+ endLine: r.endLine,
1738
+ type: r.type,
1739
+ language: r.language,
1740
+ symbols: {
1741
+ functions: r.functionNames || [],
1742
+ classes: r.classNames || [],
1743
+ interfaces: r.interfaceNames || []
1744
+ }
1745
+ },
1746
+ score,
1747
+ relevance: calculateRelevance(score)
1748
+ };
1749
+ });
1750
+ } catch (error) {
1751
+ throw wrapError(error, "Failed to query symbols");
1752
+ }
1753
+ }
1754
+ async clear() {
1755
+ if (!this.db) {
1756
+ throw new DatabaseError("Vector database not initialized");
1757
+ }
1758
+ try {
1759
+ if (this.table) {
1760
+ await this.db.dropTable(this.tableName);
1761
+ }
1762
+ this.table = null;
1763
+ } catch (error) {
1764
+ throw wrapError(error, "Failed to clear vector database");
1765
+ }
1766
+ }
1767
+ /**
1768
+ * Deletes all chunks from a specific file.
1769
+ * Used for incremental reindexing when a file is deleted or needs to be re-indexed.
1770
+ *
1771
+ * @param filepath - Path to the file whose chunks should be deleted
1772
+ */
1773
+ async deleteByFile(filepath) {
1774
+ if (!this.table) {
1775
+ throw new DatabaseError("Vector database not initialized");
1776
+ }
1777
+ try {
1778
+ await this.table.delete(`file = "${filepath}"`);
1779
+ } catch (error) {
1780
+ throw wrapError(error, "Failed to delete file from vector database");
1781
+ }
1782
+ }
1783
+ /**
1784
+ * Updates a file in the index by atomically deleting old chunks and inserting new ones.
1785
+ * This is the primary method for incremental reindexing.
1786
+ *
1787
+ * @param filepath - Path to the file being updated
1788
+ * @param vectors - New embedding vectors
1789
+ * @param metadatas - New chunk metadata
1790
+ * @param contents - New chunk contents
1791
+ */
1792
+ async updateFile(filepath, vectors, metadatas, contents) {
1793
+ if (!this.table) {
1794
+ throw new DatabaseError("Vector database not initialized");
1795
+ }
1796
+ try {
1797
+ await this.deleteByFile(filepath);
1798
+ if (vectors.length > 0) {
1799
+ await this.insertBatch(vectors, metadatas, contents);
1800
+ }
1801
+ await writeVersionFile(this.dbPath);
1802
+ } catch (error) {
1803
+ throw wrapError(error, "Failed to update file in vector database");
1804
+ }
1805
+ }
1806
+ /**
1807
+ * Checks if the index version has changed since last check.
1808
+ * Uses caching to minimize I/O overhead (checks at most once per second).
1809
+ *
1810
+ * @returns true if version has changed, false otherwise
1811
+ */
1812
+ async checkVersion() {
1813
+ const now = Date.now();
1814
+ if (now - this.lastVersionCheck < 1e3) {
1815
+ return false;
1816
+ }
1817
+ this.lastVersionCheck = now;
1818
+ try {
1819
+ const version = await readVersionFile(this.dbPath);
1820
+ if (version > this.currentVersion) {
1821
+ this.currentVersion = version;
1822
+ return true;
1823
+ }
1824
+ return false;
1825
+ } catch (error) {
1826
+ return false;
1827
+ }
1828
+ }
1829
+ /**
1830
+ * Reconnects to the database by reinitializing the connection.
1831
+ * Used when the index has been rebuilt/reindexed.
1832
+ * Forces a complete reload from disk by closing existing connections first.
1833
+ */
1834
+ async reconnect() {
1835
+ try {
1836
+ this.table = null;
1837
+ this.db = null;
1838
+ await this.initialize();
1839
+ } catch (error) {
1840
+ throw wrapError(error, "Failed to reconnect to vector database");
1841
+ }
1842
+ }
1843
+ /**
1844
+ * Gets the current index version (timestamp of last reindex).
1845
+ *
1846
+ * @returns Version timestamp, or 0 if unknown
1847
+ */
1848
+ getCurrentVersion() {
1849
+ return this.currentVersion;
1850
+ }
1851
+ /**
1852
+ * Gets the current index version as a human-readable date string.
1853
+ *
1854
+ * @returns Formatted date string, or 'Unknown' if no version
1855
+ */
1856
+ getVersionDate() {
1857
+ if (this.currentVersion === 0) {
1858
+ return "Unknown";
1859
+ }
1860
+ return new Date(this.currentVersion).toLocaleString();
1861
+ }
1862
+ /**
1863
+ * Checks if the database contains real indexed data.
1864
+ * Used to detect first run and trigger auto-indexing.
1865
+ *
1866
+ * @returns true if database has real code chunks, false if empty or only schema rows
1867
+ */
1868
+ async hasData() {
1869
+ if (!this.table) {
1870
+ return false;
1871
+ }
1872
+ try {
1873
+ const count = await this.table.countRows();
1874
+ if (count === 0) {
1875
+ return false;
1876
+ }
1877
+ const sample = await this.table.search(Array(EMBEDDING_DIMENSION).fill(0)).limit(Math.min(count, 5)).execute();
1878
+ const hasRealData = sample.some(
1879
+ (r) => r.content && r.content.trim().length > 0
1880
+ );
1881
+ return hasRealData;
1882
+ } catch {
1883
+ return false;
1884
+ }
1885
+ }
1886
+ static async load(projectRoot) {
1887
+ const db = new _VectorDB(projectRoot);
1888
+ await db.initialize();
1889
+ return db;
1890
+ }
1891
+ };
1892
+ }
1893
+ });
1894
+
1895
+ // src/indexer/index.ts
1896
+ var indexer_exports = {};
1897
+ __export(indexer_exports, {
1898
+ indexCodebase: () => indexCodebase
1899
+ });
1900
+ import fs10 from "fs/promises";
1901
+ import ora from "ora";
1902
+ import chalk4 from "chalk";
1903
+ import pLimit from "p-limit";
1904
+ async function indexCodebase(options = {}) {
1905
+ const rootDir = options.rootDir ?? process.cwd();
1906
+ const spinner = ora("Starting indexing process...").start();
1907
+ try {
1908
+ spinner.text = "Loading configuration...";
1909
+ const config = await configService.load(rootDir);
1910
+ spinner.text = "Scanning codebase...";
1911
+ let files;
1912
+ if (isModernConfig(config) && config.frameworks.length > 0) {
1913
+ files = await scanCodebaseWithFrameworks(rootDir, config);
1914
+ } else if (isLegacyConfig(config)) {
1915
+ files = await scanCodebase({
1916
+ rootDir,
1917
+ includePatterns: config.indexing.include,
1918
+ excludePatterns: config.indexing.exclude
1919
+ });
1920
+ } else {
1921
+ files = await scanCodebase({
1922
+ rootDir,
1923
+ includePatterns: [],
1924
+ excludePatterns: []
1925
+ });
1926
+ }
1927
+ if (files.length === 0) {
1928
+ spinner.fail("No files found to index");
1929
+ return;
1930
+ }
1931
+ spinner.text = `Found ${files.length} files`;
1932
+ spinner.text = "Loading embedding model (this may take a minute on first run)...";
1933
+ const embeddings = new LocalEmbeddings();
1934
+ await embeddings.initialize();
1935
+ spinner.succeed("Embedding model loaded");
1936
+ spinner.start("Initializing vector database...");
1937
+ const vectorDB = new VectorDB(rootDir);
1938
+ await vectorDB.initialize();
1939
+ spinner.succeed("Vector database initialized");
1940
+ const concurrency = isModernConfig(config) ? config.core.concurrency : 4;
1941
+ const batchSize = isModernConfig(config) ? config.core.embeddingBatchSize : 50;
1942
+ spinner.start(`Processing files with ${concurrency}x concurrency...`);
1943
+ const startTime = Date.now();
1944
+ let processedFiles = 0;
1945
+ let processedChunks = 0;
1946
+ const chunkAccumulator = [];
1947
+ const limit = pLimit(concurrency);
1948
+ const processAccumulatedChunks = async () => {
1949
+ if (chunkAccumulator.length === 0) return;
1950
+ const toProcess = chunkAccumulator.splice(0, chunkAccumulator.length);
1951
+ for (let i = 0; i < toProcess.length; i += batchSize) {
1952
+ const batch = toProcess.slice(i, Math.min(i + batchSize, toProcess.length));
1953
+ const texts = batch.map((item) => item.content);
1954
+ const embeddingVectors = await embeddings.embedBatch(texts);
1955
+ await vectorDB.insertBatch(
1956
+ embeddingVectors,
1957
+ batch.map((item) => item.chunk.metadata),
1958
+ texts
1959
+ );
1960
+ processedChunks += batch.length;
1961
+ }
1962
+ };
1963
+ const filePromises = files.map(
1964
+ (file) => limit(async () => {
1965
+ try {
1966
+ const content = await fs10.readFile(file, "utf-8");
1967
+ const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
1968
+ const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : 10;
1969
+ const chunks = chunkFile(file, content, {
1970
+ chunkSize,
1971
+ chunkOverlap
1972
+ });
1973
+ if (chunks.length === 0) {
1974
+ processedFiles++;
1975
+ return;
1976
+ }
1977
+ for (const chunk of chunks) {
1978
+ chunkAccumulator.push({
1979
+ chunk,
1980
+ content: chunk.content
1981
+ });
1982
+ }
1983
+ if (chunkAccumulator.length >= batchSize) {
1984
+ await processAccumulatedChunks();
1985
+ }
1986
+ processedFiles++;
1987
+ const elapsed = (Date.now() - startTime) / 1e3;
1988
+ const rate = processedFiles / elapsed;
1989
+ const eta = rate > 0 ? Math.round((files.length - processedFiles) / rate) : 0;
1990
+ spinner.text = `Indexed ${processedFiles}/${files.length} files (${processedChunks} chunks) | ${concurrency}x concurrency | ETA: ${eta}s`;
1991
+ } catch (error) {
1992
+ if (options.verbose) {
1993
+ console.error(chalk4.yellow(`
1994
+ \u26A0\uFE0F Skipping ${file}: ${error}`));
1995
+ }
1996
+ processedFiles++;
1997
+ }
1998
+ })
1999
+ );
2000
+ await Promise.all(filePromises);
2001
+ await processAccumulatedChunks();
2002
+ await writeVersionFile(vectorDB.dbPath);
2003
+ const totalTime = ((Date.now() - startTime) / 1e3).toFixed(1);
2004
+ spinner.succeed(
2005
+ `Indexed ${processedFiles} files (${processedChunks} chunks) in ${totalTime}s using ${concurrency}x concurrency`
2006
+ );
2007
+ console.log(chalk4.dim("\nNext step: Run"), chalk4.bold("lien serve"), chalk4.dim("to start the MCP server"));
2008
+ } catch (error) {
2009
+ spinner.fail(`Indexing failed: ${error}`);
2010
+ throw error;
2011
+ }
2012
+ }
2013
+ var init_indexer = __esm({
2014
+ "src/indexer/index.ts"() {
2015
+ "use strict";
2016
+ init_scanner();
2017
+ init_chunker();
2018
+ init_local();
2019
+ init_lancedb();
2020
+ init_service();
2021
+ init_version();
2022
+ init_schema();
2023
+ }
2024
+ });
2025
+
2026
+ // src/cli/index.ts
2027
+ import { Command } from "commander";
2028
+ import { createRequire as createRequire3 } from "module";
2029
+ import { fileURLToPath as fileURLToPath4 } from "url";
2030
+ import { dirname as dirname3, join as join3 } from "path";
2031
+
2032
+ // src/cli/init.ts
2033
+ init_schema();
2034
+ init_merge();
2035
+ init_banner();
2036
+ init_migration();
2037
+ import fs4 from "fs/promises";
2038
+ import path4 from "path";
2039
+ import { fileURLToPath as fileURLToPath2 } from "url";
2040
+ import chalk2 from "chalk";
2041
+ import inquirer from "inquirer";
2042
+
2043
+ // src/frameworks/detector-service.ts
2044
+ import fs3 from "fs/promises";
2045
+ import path3 from "path";
2046
+
2047
+ // src/frameworks/types.ts
2048
+ var defaultDetectionOptions = {
2049
+ maxDepth: 3,
2050
+ skipDirs: [
2051
+ "node_modules",
2052
+ "vendor",
2053
+ "dist",
2054
+ "build",
2055
+ ".next",
2056
+ ".nuxt",
2057
+ "coverage",
2058
+ ".git",
2059
+ ".idea",
2060
+ ".vscode",
2061
+ "tmp",
2062
+ "temp"
2063
+ ]
2064
+ };
2065
+
2066
+ // src/frameworks/nodejs/detector.ts
2067
+ import fs from "fs/promises";
2068
+ import path from "path";
2069
+
2070
+ // src/frameworks/nodejs/config.ts
2071
+ async function generateNodeJsConfig(_rootDir, _relativePath) {
2072
+ return {
2073
+ include: [
2074
+ "src/**/*.ts",
2075
+ "src/**/*.tsx",
2076
+ "src/**/*.js",
2077
+ "src/**/*.jsx",
2078
+ "src/**/*.mjs",
2079
+ "src/**/*.cjs",
2080
+ "lib/**/*.ts",
2081
+ "lib/**/*.js",
2082
+ "*.ts",
2083
+ "*.js",
2084
+ "*.mjs",
2085
+ "*.cjs",
2086
+ "**/*.md",
2087
+ "**/*.mdx",
2088
+ "docs/**/*.md",
2089
+ "README.md",
2090
+ "CHANGELOG.md",
2091
+ "CONTRIBUTING.md"
2092
+ ],
2093
+ exclude: [
2094
+ "node_modules/**",
2095
+ "dist/**",
2096
+ "build/**",
2097
+ "coverage/**",
2098
+ ".next/**",
2099
+ ".nuxt/**",
2100
+ "out/**",
2101
+ "*.min.js",
2102
+ "*.min.css",
2103
+ "*.bundle.js"
2104
+ ]
2105
+ };
2106
+ }
2107
+
2108
+ // src/frameworks/nodejs/detector.ts
2109
+ var nodejsDetector = {
2110
+ name: "nodejs",
2111
+ priority: 50,
2112
+ // Generic, yields to specific frameworks like Laravel
2113
+ async detect(rootDir, relativePath) {
2114
+ const fullPath = path.join(rootDir, relativePath);
2115
+ const result = {
2116
+ detected: false,
2117
+ name: "nodejs",
2118
+ path: relativePath,
2119
+ confidence: "low",
2120
+ evidence: []
2121
+ };
2122
+ const packageJsonPath = path.join(fullPath, "package.json");
2123
+ let packageJson4 = null;
2124
+ try {
2125
+ const content = await fs.readFile(packageJsonPath, "utf-8");
2126
+ packageJson4 = JSON.parse(content);
2127
+ result.evidence.push("Found package.json");
2128
+ } catch {
2129
+ return result;
2130
+ }
2131
+ result.detected = true;
2132
+ result.confidence = "high";
2133
+ if (packageJson4.devDependencies?.typescript || packageJson4.dependencies?.typescript) {
2134
+ result.evidence.push("TypeScript detected");
2135
+ }
2136
+ const testFrameworks = [
2137
+ { name: "jest", display: "Jest" },
2138
+ { name: "vitest", display: "Vitest" },
2139
+ { name: "mocha", display: "Mocha" },
2140
+ { name: "ava", display: "AVA" },
2141
+ { name: "@playwright/test", display: "Playwright" }
2142
+ ];
2143
+ for (const framework of testFrameworks) {
2144
+ if (packageJson4.devDependencies?.[framework.name] || packageJson4.dependencies?.[framework.name]) {
2145
+ result.evidence.push(`${framework.display} test framework detected`);
2146
+ break;
2147
+ }
2148
+ }
2149
+ const frameworks = [
2150
+ { name: "next", display: "Next.js" },
2151
+ { name: "react", display: "React" },
2152
+ { name: "vue", display: "Vue" },
2153
+ { name: "express", display: "Express" },
2154
+ { name: "@nestjs/core", display: "NestJS" }
2155
+ ];
2156
+ for (const fw of frameworks) {
2157
+ if (packageJson4.dependencies?.[fw.name]) {
2158
+ result.evidence.push(`${fw.display} detected`);
2159
+ break;
2160
+ }
2161
+ }
2162
+ if (packageJson4.engines?.node) {
2163
+ result.version = packageJson4.engines.node;
2164
+ }
2165
+ return result;
2166
+ },
2167
+ async generateConfig(rootDir, relativePath) {
2168
+ return generateNodeJsConfig(rootDir, relativePath);
2169
+ }
2170
+ };
2171
+
2172
+ // src/frameworks/laravel/detector.ts
2173
+ import fs2 from "fs/promises";
2174
+ import path2 from "path";
2175
+
2176
+ // src/frameworks/laravel/config.ts
2177
+ async function generateLaravelConfig(_rootDir, _relativePath) {
2178
+ return {
2179
+ include: [
2180
+ // PHP backend
2181
+ "app/**/*.php",
2182
+ "routes/**/*.php",
2183
+ "config/**/*.php",
2184
+ "database/**/*.php",
2185
+ "resources/**/*.php",
2186
+ "tests/**/*.php",
2187
+ "*.php",
2188
+ // Frontend assets (Vue/React/Inertia)
2189
+ "resources/js/**/*.js",
2190
+ "resources/js/**/*.ts",
2191
+ "resources/js/**/*.jsx",
2192
+ "resources/js/**/*.tsx",
2193
+ "resources/js/**/*.vue",
2194
+ // Blade templates
2195
+ "resources/views/**/*.blade.php",
2196
+ // Documentation
2197
+ "**/*.md",
2198
+ "**/*.mdx",
2199
+ "docs/**/*.md",
2200
+ "README.md",
2201
+ "CHANGELOG.md"
2202
+ ],
2203
+ exclude: [
2204
+ "vendor/**",
2205
+ "storage/**",
2206
+ "bootstrap/cache/**",
2207
+ "public/**",
2208
+ "node_modules/**"
2209
+ ]
2210
+ };
2211
+ }
2212
+
2213
+ // src/frameworks/laravel/detector.ts
2214
+ var laravelDetector = {
2215
+ name: "laravel",
2216
+ priority: 100,
2217
+ // Laravel takes precedence over Node.js
2218
+ async detect(rootDir, relativePath) {
2219
+ const fullPath = path2.join(rootDir, relativePath);
2220
+ const result = {
2221
+ detected: false,
2222
+ name: "laravel",
2223
+ path: relativePath,
2224
+ confidence: "low",
2225
+ evidence: []
2226
+ };
2227
+ const composerJsonPath = path2.join(fullPath, "composer.json");
2228
+ let composerJson = null;
2229
+ try {
2230
+ const content = await fs2.readFile(composerJsonPath, "utf-8");
2231
+ composerJson = JSON.parse(content);
2232
+ result.evidence.push("Found composer.json");
2233
+ } catch {
2234
+ return result;
2235
+ }
2236
+ const hasLaravel = composerJson.require?.["laravel/framework"] || composerJson["require-dev"]?.["laravel/framework"];
2237
+ if (!hasLaravel) {
2238
+ return result;
2239
+ }
2240
+ result.evidence.push("Laravel framework detected in composer.json");
2241
+ const artisanPath = path2.join(fullPath, "artisan");
2242
+ try {
2243
+ await fs2.access(artisanPath);
2244
+ result.evidence.push("Found artisan file");
2245
+ result.confidence = "high";
2246
+ } catch {
2247
+ result.confidence = "medium";
2248
+ }
2249
+ const laravelDirs = ["app", "routes", "config", "database"];
2250
+ let foundDirs = 0;
2251
+ for (const dir of laravelDirs) {
2252
+ try {
2253
+ const dirPath = path2.join(fullPath, dir);
2254
+ const stats = await fs2.stat(dirPath);
2255
+ if (stats.isDirectory()) {
2256
+ foundDirs++;
2257
+ }
2258
+ } catch {
2259
+ }
2260
+ }
2261
+ if (foundDirs >= 2) {
2262
+ result.evidence.push(`Laravel directory structure detected (${foundDirs}/${laravelDirs.length} dirs)`);
2263
+ result.confidence = "high";
2264
+ }
2265
+ const testDirsToCheck = [
2266
+ path2.join(fullPath, "tests", "Feature"),
2267
+ path2.join(fullPath, "tests", "Unit")
2268
+ ];
2269
+ for (const testDir of testDirsToCheck) {
2270
+ try {
2271
+ const stats = await fs2.stat(testDir);
2272
+ if (stats.isDirectory()) {
2273
+ const dirName = path2.basename(path2.dirname(testDir)) + "/" + path2.basename(testDir);
2274
+ result.evidence.push(`Found ${dirName} test directory`);
2275
+ }
2276
+ } catch {
2277
+ }
2278
+ }
2279
+ if (composerJson.require?.["laravel/framework"]) {
2280
+ result.version = composerJson.require["laravel/framework"];
2281
+ }
2282
+ result.detected = true;
2283
+ return result;
2284
+ },
2285
+ async generateConfig(rootDir, relativePath) {
2286
+ return generateLaravelConfig(rootDir, relativePath);
2287
+ }
2288
+ };
2289
+
2290
+ // src/frameworks/registry.ts
2291
+ var frameworkDetectors = [
2292
+ nodejsDetector,
2293
+ laravelDetector
2294
+ ];
2295
+ function getFrameworkDetector(name) {
2296
+ return frameworkDetectors.find((d) => d.name === name);
2297
+ }
2298
+
2299
+ // src/frameworks/detector-service.ts
2300
+ async function detectAllFrameworks(rootDir, options = {}) {
2301
+ const opts = { ...defaultDetectionOptions, ...options };
2302
+ const results = [];
2303
+ const visited = /* @__PURE__ */ new Set();
2304
+ await detectAtPath(rootDir, ".", results, visited);
2305
+ await scanSubdirectories(rootDir, ".", results, visited, 0, opts);
2306
+ return results;
2307
+ }
2308
+ async function detectAtPath(rootDir, relativePath, results, visited) {
2309
+ const fullPath = path3.join(rootDir, relativePath);
2310
+ if (visited.has(fullPath)) {
2311
+ return;
2312
+ }
2313
+ visited.add(fullPath);
2314
+ const detectedAtPath = [];
2315
+ for (const detector of frameworkDetectors) {
2316
+ try {
2317
+ const result = await detector.detect(rootDir, relativePath);
2318
+ if (result.detected) {
2319
+ detectedAtPath.push({
2320
+ ...result,
2321
+ priority: detector.priority ?? 0
2322
+ });
2323
+ }
2324
+ } catch (error) {
2325
+ console.error(`Error running detector '${detector.name}' at ${relativePath}:`, error);
2326
+ }
2327
+ }
2328
+ if (detectedAtPath.length > 1) {
2329
+ detectedAtPath.sort((a, b) => b.priority - a.priority);
2330
+ const winner = detectedAtPath[0];
2331
+ results.push(winner);
2332
+ const skipped = detectedAtPath.slice(1);
2333
+ if (skipped.length > 0) {
2334
+ const skippedNames = skipped.map((d) => d.name).join(", ");
2335
+ console.log(` \u2192 Skipping ${skippedNames} at ${relativePath} (${winner.name} takes precedence)`);
2336
+ }
2337
+ } else if (detectedAtPath.length === 1) {
2338
+ results.push(detectedAtPath[0]);
2339
+ }
2340
+ }
2341
+ async function scanSubdirectories(rootDir, relativePath, results, visited, depth, options) {
2342
+ if (depth >= options.maxDepth) {
2343
+ return;
2344
+ }
2345
+ const fullPath = path3.join(rootDir, relativePath);
2346
+ try {
2347
+ const entries = await fs3.readdir(fullPath, { withFileTypes: true });
2348
+ const dirs = entries.filter((e) => e.isDirectory());
2349
+ for (const dir of dirs) {
2350
+ if (options.skipDirs.includes(dir.name)) {
2351
+ continue;
2352
+ }
2353
+ if (dir.name.startsWith(".")) {
2354
+ continue;
2355
+ }
2356
+ const subPath = relativePath === "." ? dir.name : path3.join(relativePath, dir.name);
2357
+ await detectAtPath(rootDir, subPath, results, visited);
2358
+ await scanSubdirectories(rootDir, subPath, results, visited, depth + 1, options);
2359
+ }
2360
+ } catch (error) {
2361
+ return;
2362
+ }
2363
+ }
2364
+
2365
+ // src/cli/init.ts
2366
+ var __filename2 = fileURLToPath2(import.meta.url);
2367
+ var __dirname2 = path4.dirname(__filename2);
2368
+ async function initCommand(options = {}) {
2369
+ const rootDir = options.path || process.cwd();
2370
+ const configPath = path4.join(rootDir, ".lien.config.json");
2371
+ try {
2372
+ let configExists = false;
2373
+ try {
2374
+ await fs4.access(configPath);
2375
+ configExists = true;
2376
+ } catch {
2377
+ }
2378
+ if (configExists && options.upgrade) {
2379
+ await upgradeConfig(configPath);
2380
+ return;
2381
+ }
2382
+ if (configExists && !options.upgrade) {
2383
+ console.log(chalk2.yellow("\u26A0\uFE0F .lien.config.json already exists"));
2384
+ console.log(chalk2.dim("Run"), chalk2.bold("lien init --upgrade"), chalk2.dim("to merge new config options"));
2385
+ return;
2386
+ }
2387
+ if (!configExists) {
2388
+ await createNewConfig(rootDir, options);
2389
+ }
2390
+ } catch (error) {
2391
+ console.error(chalk2.red("Error creating config file:"), error);
2392
+ process.exit(1);
2393
+ }
2394
+ }
2395
+ async function createNewConfig(rootDir, options) {
2396
+ showCompactBanner();
2397
+ console.log(chalk2.bold("Initializing Lien...\n"));
2398
+ console.log(chalk2.dim("\u{1F50D} Detecting frameworks in"), chalk2.bold(rootDir));
2399
+ const detections = await detectAllFrameworks(rootDir);
2400
+ let frameworks = [];
2401
+ if (detections.length === 0) {
2402
+ console.log(chalk2.yellow("\n\u26A0\uFE0F No frameworks detected"));
2403
+ if (!options.yes) {
2404
+ const { useGeneric } = await inquirer.prompt([
2405
+ {
2406
+ type: "confirm",
2407
+ name: "useGeneric",
2408
+ message: "Create a generic config (index all supported file types)?",
2409
+ default: true
2410
+ }
2411
+ ]);
2412
+ if (!useGeneric) {
2413
+ console.log(chalk2.dim("Aborted."));
2414
+ return;
2415
+ }
2416
+ }
2417
+ frameworks.push({
2418
+ name: "generic",
2419
+ path: ".",
2420
+ enabled: true,
2421
+ config: {
2422
+ include: ["**/*.{ts,tsx,js,jsx,py,go,rs,java,c,cpp,cs}"],
2423
+ exclude: [
2424
+ "**/node_modules/**",
2425
+ "**/dist/**",
2426
+ "**/build/**",
2427
+ "**/.git/**",
2428
+ "**/coverage/**",
2429
+ "**/.next/**",
2430
+ "**/.nuxt/**",
2431
+ "**/vendor/**"
2432
+ ]
2433
+ }
2434
+ });
2435
+ } else {
2436
+ console.log(chalk2.green(`
2437
+ \u2713 Found ${detections.length} framework(s):
2438
+ `));
2439
+ for (const det of detections) {
2440
+ const pathDisplay = det.path === "." ? "root" : det.path;
2441
+ console.log(chalk2.bold(` ${det.name}`), chalk2.dim(`(${det.confidence} confidence)`));
2442
+ console.log(chalk2.dim(` Location: ${pathDisplay}`));
2443
+ if (det.evidence.length > 0) {
2444
+ det.evidence.forEach((e) => {
2445
+ console.log(chalk2.dim(` \u2022 ${e}`));
2446
+ });
2447
+ }
2448
+ console.log();
2449
+ }
2450
+ if (!options.yes) {
2451
+ const { confirm } = await inquirer.prompt([
2452
+ {
2453
+ type: "confirm",
2454
+ name: "confirm",
2455
+ message: "Configure these frameworks?",
2456
+ default: true
2457
+ }
2458
+ ]);
2459
+ if (!confirm) {
2460
+ console.log(chalk2.dim("Aborted."));
2461
+ return;
2462
+ }
2463
+ }
2464
+ for (const det of detections) {
2465
+ const detector = getFrameworkDetector(det.name);
2466
+ if (!detector) {
2467
+ console.warn(chalk2.yellow(`\u26A0\uFE0F No detector found for ${det.name}, skipping`));
2468
+ continue;
2469
+ }
2470
+ const frameworkConfig = await detector.generateConfig(rootDir, det.path);
2471
+ let shouldCustomize = false;
2472
+ if (!options.yes) {
2473
+ const { customize } = await inquirer.prompt([
2474
+ {
2475
+ type: "confirm",
2476
+ name: "customize",
2477
+ message: `Customize ${det.name} settings?`,
2478
+ default: false
2479
+ }
2480
+ ]);
2481
+ shouldCustomize = customize;
2482
+ }
2483
+ let finalConfig = frameworkConfig;
2484
+ if (shouldCustomize) {
2485
+ const customized = await promptForCustomization(det.name, frameworkConfig);
2486
+ finalConfig = { ...frameworkConfig, ...customized };
2487
+ } else {
2488
+ const pathDisplay = det.path === "." ? "root" : det.path;
2489
+ console.log(chalk2.dim(` \u2192 Using defaults for ${det.name} at ${pathDisplay}`));
2490
+ }
2491
+ frameworks.push({
2492
+ name: det.name,
2493
+ path: det.path,
2494
+ enabled: true,
2495
+ config: finalConfig
2496
+ });
2497
+ }
2498
+ }
2499
+ if (!options.yes) {
2500
+ const { installCursorRules } = await inquirer.prompt([
2501
+ {
2502
+ type: "confirm",
2503
+ name: "installCursorRules",
2504
+ message: "Install recommended Cursor rules?",
2505
+ default: true
2506
+ }
2507
+ ]);
2508
+ if (installCursorRules) {
2509
+ try {
2510
+ const cursorRulesDir = path4.join(rootDir, ".cursor");
2511
+ await fs4.mkdir(cursorRulesDir, { recursive: true });
2512
+ const templatePath = path4.join(__dirname2, "../CURSOR_RULES_TEMPLATE.md");
2513
+ const rulesPath = path4.join(cursorRulesDir, "rules");
2514
+ let targetPath;
2515
+ let isDirectory = false;
2516
+ let isFile = false;
2517
+ try {
2518
+ const stats = await fs4.stat(rulesPath);
2519
+ isDirectory = stats.isDirectory();
2520
+ isFile = stats.isFile();
2521
+ } catch {
2522
+ }
2523
+ if (isDirectory) {
2524
+ targetPath = path4.join(rulesPath, "lien.mdc");
2525
+ await fs4.copyFile(templatePath, targetPath);
2526
+ console.log(chalk2.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
2527
+ } else if (isFile) {
2528
+ const { convertToDir } = await inquirer.prompt([
2529
+ {
2530
+ type: "confirm",
2531
+ name: "convertToDir",
2532
+ message: "Existing .cursor/rules file found. Convert to directory and preserve your rules?",
2533
+ default: true
2534
+ }
2535
+ ]);
2536
+ if (convertToDir) {
2537
+ const existingRules = await fs4.readFile(rulesPath, "utf-8");
2538
+ await fs4.unlink(rulesPath);
2539
+ await fs4.mkdir(rulesPath);
2540
+ await fs4.writeFile(path4.join(rulesPath, "project.mdc"), existingRules);
2541
+ await fs4.copyFile(templatePath, path4.join(rulesPath, "lien.mdc"));
2542
+ console.log(chalk2.green("\u2713 Converted .cursor/rules to directory"));
2543
+ console.log(chalk2.green(" - Your project rules: .cursor/rules/project.mdc"));
2544
+ console.log(chalk2.green(" - Lien rules: .cursor/rules/lien.mdc"));
2545
+ } else {
2546
+ console.log(chalk2.dim("Skipped Cursor rules installation (preserving existing file)"));
2547
+ }
2548
+ } else {
2549
+ await fs4.mkdir(rulesPath, { recursive: true });
2550
+ targetPath = path4.join(rulesPath, "lien.mdc");
2551
+ await fs4.copyFile(templatePath, targetPath);
2552
+ console.log(chalk2.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
2553
+ }
2554
+ } catch (error) {
2555
+ console.log(chalk2.yellow("\u26A0\uFE0F Could not install Cursor rules"));
2556
+ console.log(chalk2.dim(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2557
+ console.log(chalk2.dim("You can manually copy CURSOR_RULES_TEMPLATE.md to .cursor/rules/lien.mdc"));
2558
+ }
2559
+ }
2560
+ }
2561
+ const config = {
2562
+ ...defaultConfig,
2563
+ frameworks
2564
+ };
2565
+ const configPath = path4.join(rootDir, ".lien.config.json");
2566
+ await fs4.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2567
+ console.log(chalk2.green("\n\u2713 Created .lien.config.json"));
2568
+ console.log(chalk2.green(`\u2713 Configured ${frameworks.length} framework(s)`));
2569
+ console.log(chalk2.dim("\nNext steps:"));
2570
+ console.log(chalk2.dim(" 1. Run"), chalk2.bold("lien index"), chalk2.dim("to index your codebase"));
2571
+ console.log(chalk2.dim(" 2. Run"), chalk2.bold("lien serve"), chalk2.dim("to start the MCP server"));
2572
+ console.log(chalk2.dim(" 3. Configure Cursor to use the MCP server (see README.md)"));
2573
+ }
2574
+ async function promptForCustomization(frameworkName, config) {
2575
+ console.log(chalk2.bold(`
2576
+ Customizing ${frameworkName} settings:`));
2577
+ const answers = await inquirer.prompt([
2578
+ {
2579
+ type: "input",
2580
+ name: "include",
2581
+ message: "File patterns to include (comma-separated):",
2582
+ default: config.include.join(", "),
2583
+ filter: (input) => input.split(",").map((s) => s.trim())
2584
+ },
2585
+ {
2586
+ type: "input",
2587
+ name: "exclude",
2588
+ message: "File patterns to exclude (comma-separated):",
2589
+ default: config.exclude.join(", "),
2590
+ filter: (input) => input.split(",").map((s) => s.trim())
2591
+ }
2592
+ ]);
2593
+ return {
2594
+ include: answers.include,
2595
+ exclude: answers.exclude
2596
+ };
2597
+ }
2598
+ async function upgradeConfig(configPath) {
2599
+ try {
2600
+ const backupPath = `${configPath}.backup`;
2601
+ await fs4.copyFile(configPath, backupPath);
2602
+ const existingContent = await fs4.readFile(configPath, "utf-8");
2603
+ const existingConfig = JSON.parse(existingContent);
2604
+ let upgradedConfig;
2605
+ let migrated = false;
2606
+ if (needsMigration(existingConfig)) {
2607
+ console.log(chalk2.blue("\u{1F504} Migrating config from v0.2.0 to v0.3.0..."));
2608
+ upgradedConfig = migrateConfig(existingConfig);
2609
+ migrated = true;
2610
+ } else {
2611
+ const newFields = detectNewFields(existingConfig, defaultConfig);
2612
+ upgradedConfig = deepMergeConfig(defaultConfig, existingConfig);
2613
+ if (newFields.length > 0) {
2614
+ console.log(chalk2.dim("\nNew options added:"));
2615
+ newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
2616
+ }
2617
+ }
2618
+ await fs4.writeFile(
2619
+ configPath,
2620
+ JSON.stringify(upgradedConfig, null, 2) + "\n",
2621
+ "utf-8"
2622
+ );
2623
+ console.log(chalk2.green("\u2713 Config upgraded successfully"));
2624
+ console.log(chalk2.dim("Backup saved to:"), backupPath);
2625
+ if (migrated) {
2626
+ console.log(chalk2.dim("\n\u{1F4DD} Your config now uses the framework-based structure."));
2627
+ }
2628
+ } catch (error) {
2629
+ console.error(chalk2.red("Error upgrading config:"), error);
2630
+ throw error;
2631
+ }
2632
+ }
2633
+
2634
+ // src/cli/status.ts
2635
+ init_service();
2636
+ import chalk3 from "chalk";
2637
+ import fs8 from "fs/promises";
2638
+ import path8 from "path";
2639
+ import os from "os";
2640
+ import crypto from "crypto";
2641
+
2642
+ // src/git/utils.ts
2643
+ import { exec } from "child_process";
2644
+ import { promisify } from "util";
2645
+ import fs6 from "fs/promises";
2646
+ import path6 from "path";
2647
+ var execAsync = promisify(exec);
2648
+ async function isGitRepo(rootDir) {
2649
+ try {
2650
+ const gitDir = path6.join(rootDir, ".git");
2651
+ await fs6.access(gitDir);
2652
+ return true;
2653
+ } catch {
2654
+ return false;
2655
+ }
2656
+ }
2657
+ async function getCurrentBranch(rootDir) {
2658
+ try {
2659
+ const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
2660
+ cwd: rootDir,
2661
+ timeout: 5e3
2662
+ // 5 second timeout
2663
+ });
2664
+ return stdout.trim();
2665
+ } catch (error) {
2666
+ throw new Error(`Failed to get current branch: ${error}`);
2667
+ }
2668
+ }
2669
+ async function getCurrentCommit(rootDir) {
2670
+ try {
2671
+ const { stdout } = await execAsync("git rev-parse HEAD", {
2672
+ cwd: rootDir,
2673
+ timeout: 5e3
2674
+ });
2675
+ return stdout.trim();
2676
+ } catch (error) {
2677
+ throw new Error(`Failed to get current commit: ${error}`);
2678
+ }
2679
+ }
2680
+ async function getChangedFiles(rootDir, fromRef, toRef) {
2681
+ try {
2682
+ const { stdout } = await execAsync(
2683
+ `git diff --name-only ${fromRef}...${toRef}`,
2684
+ {
2685
+ cwd: rootDir,
2686
+ timeout: 1e4
2687
+ // 10 second timeout for diffs
2688
+ }
2689
+ );
2690
+ const files = stdout.trim().split("\n").filter(Boolean).map((file) => path6.join(rootDir, file));
2691
+ return files;
2692
+ } catch (error) {
2693
+ throw new Error(`Failed to get changed files: ${error}`);
2694
+ }
2695
+ }
2696
+ async function getChangedFilesBetweenCommits(rootDir, fromCommit, toCommit) {
2697
+ try {
2698
+ const { stdout } = await execAsync(
2699
+ `git diff --name-only ${fromCommit} ${toCommit}`,
2700
+ {
2701
+ cwd: rootDir,
2702
+ timeout: 1e4
2703
+ }
2704
+ );
2705
+ const files = stdout.trim().split("\n").filter(Boolean).map((file) => path6.join(rootDir, file));
2706
+ return files;
2707
+ } catch (error) {
2708
+ throw new Error(`Failed to get changed files between commits: ${error}`);
2709
+ }
2710
+ }
2711
+ async function isGitAvailable() {
2712
+ try {
2713
+ await execAsync("git --version", { timeout: 3e3 });
2714
+ return true;
2715
+ } catch {
2716
+ return false;
2717
+ }
2718
+ }
2719
+
2720
+ // src/cli/status.ts
2721
+ init_version();
2722
+ init_banner();
2723
+ init_schema();
2724
+ async function statusCommand() {
2725
+ const rootDir = process.cwd();
2726
+ const projectName = path8.basename(rootDir);
2727
+ const pathHash = crypto.createHash("md5").update(rootDir).digest("hex").substring(0, 8);
2728
+ const indexPath = path8.join(os.homedir(), ".lien", "indices", `${projectName}-${pathHash}`);
2729
+ showCompactBanner();
2730
+ console.log(chalk3.bold("Status\n"));
2731
+ const hasConfig = await configService.exists(rootDir);
2732
+ console.log(chalk3.dim("Configuration:"), hasConfig ? chalk3.green("\u2713 Found") : chalk3.red("\u2717 Not initialized"));
2733
+ if (!hasConfig) {
2734
+ console.log(chalk3.yellow("\nRun"), chalk3.bold("lien init"), chalk3.yellow("to initialize"));
2735
+ return;
2736
+ }
2737
+ try {
2738
+ const stats = await fs8.stat(indexPath);
2739
+ console.log(chalk3.dim("Index location:"), indexPath);
2740
+ console.log(chalk3.dim("Index status:"), chalk3.green("\u2713 Exists"));
2741
+ try {
2742
+ const files = await fs8.readdir(indexPath, { recursive: true });
2743
+ console.log(chalk3.dim("Index files:"), files.length);
2744
+ } catch (e) {
2745
+ }
2746
+ console.log(chalk3.dim("Last modified:"), stats.mtime.toLocaleString());
2747
+ try {
2748
+ const version = await readVersionFile(indexPath);
2749
+ if (version > 0) {
2750
+ const versionDate = new Date(version);
2751
+ console.log(chalk3.dim("Last reindex:"), versionDate.toLocaleString());
2752
+ }
2753
+ } catch {
2754
+ }
2755
+ } catch (error) {
2756
+ console.log(chalk3.dim("Index status:"), chalk3.yellow("\u2717 Not indexed"));
2757
+ console.log(chalk3.yellow("\nRun"), chalk3.bold("lien index"), chalk3.yellow("to index your codebase"));
2758
+ }
2759
+ try {
2760
+ const config = await configService.load(rootDir);
2761
+ console.log(chalk3.bold("\nFeatures:"));
2762
+ const isRepo = await isGitRepo(rootDir);
2763
+ if (config.gitDetection.enabled && isRepo) {
2764
+ console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
2765
+ console.log(chalk3.dim(" Poll interval:"), `${config.gitDetection.pollIntervalMs / 1e3}s`);
2766
+ try {
2767
+ const branch = await getCurrentBranch(rootDir);
2768
+ const commit = await getCurrentCommit(rootDir);
2769
+ console.log(chalk3.dim(" Current branch:"), branch);
2770
+ console.log(chalk3.dim(" Current commit:"), commit.substring(0, 8));
2771
+ const gitStateFile = path8.join(indexPath, ".git-state.json");
2772
+ try {
2773
+ const gitStateContent = await fs8.readFile(gitStateFile, "utf-8");
2774
+ const gitState = JSON.parse(gitStateContent);
2775
+ if (gitState.branch !== branch || gitState.commit !== commit) {
2776
+ console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
2777
+ }
2778
+ } catch {
2779
+ }
2780
+ } catch {
2781
+ }
2782
+ } else if (config.gitDetection.enabled && !isRepo) {
2783
+ console.log(chalk3.dim("Git detection:"), chalk3.yellow("Enabled (not a git repo)"));
2784
+ } else {
2785
+ console.log(chalk3.dim("Git detection:"), chalk3.gray("Disabled"));
2786
+ }
2787
+ if (config.fileWatching.enabled) {
2788
+ console.log(chalk3.dim("File watching:"), chalk3.green("\u2713 Enabled"));
2789
+ console.log(chalk3.dim(" Debounce:"), `${config.fileWatching.debounceMs}ms`);
2790
+ } else {
2791
+ console.log(chalk3.dim("File watching:"), chalk3.gray("Disabled"));
2792
+ console.log(chalk3.dim(" Enable with:"), chalk3.bold("lien serve --watch"));
2793
+ }
2794
+ console.log(chalk3.bold("\nIndexing Settings:"));
2795
+ if (isModernConfig(config)) {
2796
+ console.log(chalk3.dim("Concurrency:"), config.core.concurrency);
2797
+ console.log(chalk3.dim("Batch size:"), config.core.embeddingBatchSize);
2798
+ console.log(chalk3.dim("Chunk size:"), config.core.chunkSize);
2799
+ console.log(chalk3.dim("Chunk overlap:"), config.core.chunkOverlap);
2800
+ }
2801
+ } catch (error) {
2802
+ console.log(chalk3.yellow("\nWarning: Could not load configuration"));
2803
+ }
2804
+ }
2805
+
2806
+ // src/cli/index-cmd.ts
2807
+ init_indexer();
2808
+ init_banner();
2809
+ import chalk5 from "chalk";
2810
+ async function indexCommand(options) {
2811
+ showCompactBanner();
2812
+ try {
2813
+ await indexCodebase({
2814
+ rootDir: process.cwd(),
2815
+ verbose: options.verbose || false
2816
+ });
2817
+ if (options.watch) {
2818
+ console.log(chalk5.yellow("\n\u26A0\uFE0F Watch mode not yet implemented"));
2819
+ }
2820
+ } catch (error) {
2821
+ console.error(chalk5.red("Error during indexing:"), error);
2822
+ process.exit(1);
2823
+ }
2824
+ }
2825
+
2826
+ // src/cli/serve.ts
2827
+ import chalk6 from "chalk";
2828
+
2829
+ // src/mcp/server.ts
2830
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2831
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2832
+ import {
2833
+ CallToolRequestSchema,
2834
+ ListToolsRequestSchema
2835
+ } from "@modelcontextprotocol/sdk/types.js";
2836
+ import { createRequire as createRequire2 } from "module";
2837
+ import { fileURLToPath as fileURLToPath3 } from "url";
2838
+ import { dirname as dirname2, join as join2 } from "path";
2839
+
2840
+ // src/mcp/tools.ts
2841
+ var tools = [
2842
+ {
2843
+ name: "semantic_search",
2844
+ description: "Search the codebase semantically for relevant code using natural language. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
2845
+ inputSchema: {
2846
+ type: "object",
2847
+ properties: {
2848
+ query: {
2849
+ type: "string",
2850
+ description: 'Natural language search query (e.g., "authentication logic", "database connection handling")'
2851
+ },
2852
+ limit: {
2853
+ type: "number",
2854
+ description: "Maximum number of results to return",
2855
+ default: 5
2856
+ }
2857
+ },
2858
+ required: ["query"]
2859
+ }
2860
+ },
2861
+ {
2862
+ name: "find_similar",
2863
+ description: "Find code similar to a given code snippet. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
2864
+ inputSchema: {
2865
+ type: "object",
2866
+ properties: {
2867
+ code: {
2868
+ type: "string",
2869
+ description: "Code snippet to find similar implementations"
2870
+ },
2871
+ limit: {
2872
+ type: "number",
2873
+ description: "Maximum number of results to return",
2874
+ default: 5
2875
+ }
2876
+ },
2877
+ required: ["code"]
2878
+ }
2879
+ },
2880
+ {
2881
+ name: "get_file_context",
2882
+ description: "Get all chunks and related context for a specific file. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
2883
+ inputSchema: {
2884
+ type: "object",
2885
+ properties: {
2886
+ filepath: {
2887
+ type: "string",
2888
+ description: "Path to the file (relative to project root)"
2889
+ },
2890
+ includeRelated: {
2891
+ type: "boolean",
2892
+ description: "Include semantically related chunks from other files",
2893
+ default: true
2894
+ }
2895
+ },
2896
+ required: ["filepath"]
2897
+ }
2898
+ },
2899
+ {
2900
+ name: "list_functions",
2901
+ description: "List functions, classes, and interfaces by name pattern and language",
2902
+ inputSchema: {
2903
+ type: "object",
2904
+ properties: {
2905
+ pattern: {
2906
+ type: "string",
2907
+ description: 'Regex pattern to match symbol names (e.g., ".*Service$", "handle.*")'
2908
+ },
2909
+ language: {
2910
+ type: "string",
2911
+ description: 'Language filter (e.g., "typescript", "python", "php")'
2912
+ }
2913
+ }
2914
+ }
2915
+ }
2916
+ ];
2917
+
2918
+ // src/mcp/server.ts
2919
+ init_lancedb();
2920
+ init_local();
2921
+
2922
+ // src/git/tracker.ts
2923
+ import fs11 from "fs/promises";
2924
+ import path11 from "path";
2925
+ var GitStateTracker = class {
2926
+ stateFile;
2927
+ rootDir;
2928
+ currentState = null;
2929
+ constructor(rootDir, indexPath) {
2930
+ this.rootDir = rootDir;
2931
+ this.stateFile = path11.join(indexPath, ".git-state.json");
2932
+ }
2933
+ /**
2934
+ * Loads the last known git state from disk.
2935
+ * Returns null if no state file exists (first run).
2936
+ */
2937
+ async loadState() {
2938
+ try {
2939
+ const content = await fs11.readFile(this.stateFile, "utf-8");
2940
+ return JSON.parse(content);
2941
+ } catch {
2942
+ return null;
2943
+ }
2944
+ }
2945
+ /**
2946
+ * Saves the current git state to disk.
2947
+ */
2948
+ async saveState(state) {
2949
+ try {
2950
+ const content = JSON.stringify(state, null, 2);
2951
+ await fs11.writeFile(this.stateFile, content, "utf-8");
2952
+ } catch (error) {
2953
+ console.error(`[Lien] Warning: Failed to save git state: ${error}`);
2954
+ }
2955
+ }
2956
+ /**
2957
+ * Gets the current git state from the repository.
2958
+ *
2959
+ * @returns Current git state
2960
+ * @throws Error if git commands fail
2961
+ */
2962
+ async getCurrentGitState() {
2963
+ const branch = await getCurrentBranch(this.rootDir);
2964
+ const commit = await getCurrentCommit(this.rootDir);
2965
+ return {
2966
+ branch,
2967
+ commit,
2968
+ timestamp: Date.now()
2969
+ };
2970
+ }
2971
+ /**
2972
+ * Initializes the tracker by loading saved state and checking current state.
2973
+ * Should be called once when MCP server starts.
2974
+ *
2975
+ * @returns Array of changed files if state changed, null if no changes or first run
2976
+ */
2977
+ async initialize() {
2978
+ const isRepo = await isGitRepo(this.rootDir);
2979
+ if (!isRepo) {
2980
+ return null;
2981
+ }
2982
+ try {
2983
+ this.currentState = await this.getCurrentGitState();
2984
+ const previousState = await this.loadState();
2985
+ if (!previousState) {
2986
+ await this.saveState(this.currentState);
2987
+ return null;
2988
+ }
2989
+ const branchChanged = previousState.branch !== this.currentState.branch;
2990
+ const commitChanged = previousState.commit !== this.currentState.commit;
2991
+ if (!branchChanged && !commitChanged) {
2992
+ return null;
2993
+ }
2994
+ let changedFiles = [];
2995
+ if (branchChanged) {
2996
+ try {
2997
+ changedFiles = await getChangedFiles(
2998
+ this.rootDir,
2999
+ previousState.branch,
3000
+ this.currentState.branch
3001
+ );
3002
+ } catch (error) {
3003
+ console.error(`[Lien] Branch diff failed, using commit diff: ${error}`);
3004
+ changedFiles = await getChangedFilesBetweenCommits(
3005
+ this.rootDir,
3006
+ previousState.commit,
3007
+ this.currentState.commit
3008
+ );
3009
+ }
3010
+ } else if (commitChanged) {
3011
+ changedFiles = await getChangedFilesBetweenCommits(
3012
+ this.rootDir,
3013
+ previousState.commit,
3014
+ this.currentState.commit
3015
+ );
3016
+ }
3017
+ await this.saveState(this.currentState);
3018
+ return changedFiles;
3019
+ } catch (error) {
3020
+ console.error(`[Lien] Failed to initialize git tracker: ${error}`);
3021
+ return null;
3022
+ }
3023
+ }
3024
+ /**
3025
+ * Checks for git state changes since last check.
3026
+ * This is called periodically by the MCP server.
3027
+ *
3028
+ * @returns Array of changed files if state changed, null if no changes
3029
+ */
3030
+ async detectChanges() {
3031
+ const isRepo = await isGitRepo(this.rootDir);
3032
+ if (!isRepo) {
3033
+ return null;
3034
+ }
3035
+ try {
3036
+ const newState = await this.getCurrentGitState();
3037
+ if (!this.currentState) {
3038
+ this.currentState = newState;
3039
+ await this.saveState(newState);
3040
+ return null;
3041
+ }
3042
+ const branchChanged = this.currentState.branch !== newState.branch;
3043
+ const commitChanged = this.currentState.commit !== newState.commit;
3044
+ if (!branchChanged && !commitChanged) {
3045
+ return null;
3046
+ }
3047
+ let changedFiles = [];
3048
+ if (branchChanged) {
3049
+ try {
3050
+ changedFiles = await getChangedFiles(
3051
+ this.rootDir,
3052
+ this.currentState.branch,
3053
+ newState.branch
3054
+ );
3055
+ } catch (error) {
3056
+ console.error(`[Lien] Branch diff failed, using commit diff: ${error}`);
3057
+ changedFiles = await getChangedFilesBetweenCommits(
3058
+ this.rootDir,
3059
+ this.currentState.commit,
3060
+ newState.commit
3061
+ );
3062
+ }
3063
+ } else if (commitChanged) {
3064
+ changedFiles = await getChangedFilesBetweenCommits(
3065
+ this.rootDir,
3066
+ this.currentState.commit,
3067
+ newState.commit
3068
+ );
3069
+ }
3070
+ this.currentState = newState;
3071
+ await this.saveState(newState);
3072
+ return changedFiles;
3073
+ } catch (error) {
3074
+ console.error(`[Lien] Failed to detect git changes: ${error}`);
3075
+ return null;
3076
+ }
3077
+ }
3078
+ /**
3079
+ * Gets the current git state.
3080
+ * Useful for status display.
3081
+ */
3082
+ getState() {
3083
+ return this.currentState;
3084
+ }
3085
+ /**
3086
+ * Manually updates the saved state.
3087
+ * Useful after manual reindexing to sync state.
3088
+ */
3089
+ async updateState() {
3090
+ try {
3091
+ this.currentState = await this.getCurrentGitState();
3092
+ await this.saveState(this.currentState);
3093
+ } catch (error) {
3094
+ console.error(`[Lien] Failed to update git state: ${error}`);
3095
+ }
3096
+ }
3097
+ };
3098
+
3099
+ // src/indexer/incremental.ts
3100
+ init_chunker();
3101
+ init_schema();
3102
+ import fs12 from "fs/promises";
3103
+ async function indexSingleFile(filepath, vectorDB, embeddings, config, options = {}) {
3104
+ const { verbose } = options;
3105
+ try {
3106
+ try {
3107
+ await fs12.access(filepath);
3108
+ } catch {
3109
+ if (verbose) {
3110
+ console.error(`[Lien] File deleted: ${filepath}`);
3111
+ }
3112
+ await vectorDB.deleteByFile(filepath);
3113
+ return;
3114
+ }
3115
+ const content = await fs12.readFile(filepath, "utf-8");
3116
+ const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
3117
+ const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
3118
+ const chunks = chunkFile(filepath, content, {
3119
+ chunkSize,
3120
+ chunkOverlap
3121
+ });
3122
+ if (chunks.length === 0) {
3123
+ if (verbose) {
3124
+ console.error(`[Lien] Empty file: ${filepath}`);
3125
+ }
3126
+ await vectorDB.deleteByFile(filepath);
3127
+ return;
3128
+ }
3129
+ const texts = chunks.map((c) => c.content);
3130
+ const vectors = await embeddings.embedBatch(texts);
3131
+ await vectorDB.updateFile(
3132
+ filepath,
3133
+ vectors,
3134
+ chunks.map((c) => c.metadata),
3135
+ texts
3136
+ );
3137
+ if (verbose) {
3138
+ console.error(`[Lien] \u2713 Updated ${filepath} (${chunks.length} chunks)`);
3139
+ }
3140
+ } catch (error) {
3141
+ console.error(`[Lien] \u26A0\uFE0F Failed to index ${filepath}: ${error}`);
3142
+ }
3143
+ }
3144
+ async function indexMultipleFiles(filepaths, vectorDB, embeddings, config, options = {}) {
3145
+ const { verbose } = options;
3146
+ let successCount = 0;
3147
+ for (const filepath of filepaths) {
3148
+ try {
3149
+ await indexSingleFile(filepath, vectorDB, embeddings, config, options);
3150
+ successCount++;
3151
+ } catch (error) {
3152
+ if (verbose) {
3153
+ console.error(`[Lien] Failed to process ${filepath}`);
3154
+ }
3155
+ }
3156
+ }
3157
+ return successCount;
3158
+ }
3159
+
3160
+ // src/mcp/server.ts
3161
+ init_service();
3162
+
3163
+ // src/watcher/index.ts
3164
+ init_schema();
3165
+ import chokidar from "chokidar";
3166
+ var FileWatcher = class {
3167
+ watcher = null;
3168
+ debounceTimers = /* @__PURE__ */ new Map();
3169
+ config;
3170
+ rootDir;
3171
+ onChangeHandler = null;
3172
+ constructor(rootDir, config) {
3173
+ this.rootDir = rootDir;
3174
+ this.config = config;
3175
+ }
3176
+ /**
3177
+ * Starts watching files for changes.
3178
+ *
3179
+ * @param handler - Callback function called when files change
3180
+ */
3181
+ async start(handler) {
3182
+ if (this.watcher) {
3183
+ throw new Error("File watcher is already running");
3184
+ }
3185
+ this.onChangeHandler = handler;
3186
+ let includePatterns;
3187
+ let excludePatterns;
3188
+ if (isLegacyConfig(this.config)) {
3189
+ includePatterns = this.config.indexing.include;
3190
+ excludePatterns = this.config.indexing.exclude;
3191
+ } else if (isModernConfig(this.config)) {
3192
+ includePatterns = this.config.frameworks.flatMap((f) => f.config.include);
3193
+ excludePatterns = this.config.frameworks.flatMap((f) => f.config.exclude);
3194
+ } else {
3195
+ includePatterns = ["**/*"];
3196
+ excludePatterns = [];
3197
+ }
3198
+ this.watcher = chokidar.watch(includePatterns, {
3199
+ cwd: this.rootDir,
3200
+ ignored: excludePatterns,
3201
+ persistent: true,
3202
+ ignoreInitial: true,
3203
+ // Don't trigger for existing files
3204
+ awaitWriteFinish: {
3205
+ stabilityThreshold: 500,
3206
+ // Wait 500ms for file to stop changing
3207
+ pollInterval: 100
3208
+ },
3209
+ // Performance optimizations
3210
+ usePolling: false,
3211
+ interval: 100,
3212
+ binaryInterval: 300
3213
+ });
3214
+ this.watcher.on("add", (filepath) => this.handleChange("add", filepath)).on("change", (filepath) => this.handleChange("change", filepath)).on("unlink", (filepath) => this.handleChange("unlink", filepath)).on("error", (error) => {
3215
+ console.error(`[Lien] File watcher error: ${error}`);
3216
+ });
3217
+ await new Promise((resolve) => {
3218
+ this.watcher.on("ready", () => {
3219
+ resolve();
3220
+ });
3221
+ });
3222
+ }
3223
+ /**
3224
+ * Handles a file change event with debouncing.
3225
+ * Debouncing prevents rapid reindexing when files are saved multiple times quickly.
3226
+ */
3227
+ handleChange(type, filepath) {
3228
+ const existingTimer = this.debounceTimers.get(filepath);
3229
+ if (existingTimer) {
3230
+ clearTimeout(existingTimer);
3231
+ }
3232
+ const timer = setTimeout(() => {
3233
+ this.debounceTimers.delete(filepath);
3234
+ if (this.onChangeHandler) {
3235
+ const absolutePath = filepath.startsWith("/") ? filepath : `${this.rootDir}/${filepath}`;
3236
+ try {
3237
+ const result = this.onChangeHandler({
3238
+ type,
3239
+ filepath: absolutePath
3240
+ });
3241
+ if (result instanceof Promise) {
3242
+ result.catch((error) => {
3243
+ console.error(`[Lien] Error handling file change: ${error}`);
3244
+ });
3245
+ }
3246
+ } catch (error) {
3247
+ console.error(`[Lien] Error handling file change: ${error}`);
3248
+ }
3249
+ }
3250
+ }, this.config.fileWatching.debounceMs);
3251
+ this.debounceTimers.set(filepath, timer);
3252
+ }
3253
+ /**
3254
+ * Stops the file watcher and cleans up resources.
3255
+ */
3256
+ async stop() {
3257
+ if (!this.watcher) {
3258
+ return;
3259
+ }
3260
+ for (const timer of this.debounceTimers.values()) {
3261
+ clearTimeout(timer);
3262
+ }
3263
+ this.debounceTimers.clear();
3264
+ await this.watcher.close();
3265
+ this.watcher = null;
3266
+ this.onChangeHandler = null;
3267
+ }
3268
+ /**
3269
+ * Gets the list of files currently being watched.
3270
+ */
3271
+ getWatchedFiles() {
3272
+ if (!this.watcher) {
3273
+ return [];
3274
+ }
3275
+ const watched = this.watcher.getWatched();
3276
+ const files = [];
3277
+ for (const [dir, filenames] of Object.entries(watched)) {
3278
+ for (const filename of filenames) {
3279
+ files.push(`${dir}/${filename}`);
3280
+ }
3281
+ }
3282
+ return files;
3283
+ }
3284
+ /**
3285
+ * Checks if the watcher is currently running.
3286
+ */
3287
+ isRunning() {
3288
+ return this.watcher !== null;
3289
+ }
3290
+ };
3291
+
3292
+ // src/mcp/server.ts
3293
+ init_constants();
3294
+ var __filename3 = fileURLToPath3(import.meta.url);
3295
+ var __dirname3 = dirname2(__filename3);
3296
+ var require3 = createRequire2(import.meta.url);
3297
+ var packageJson2;
3298
+ try {
3299
+ packageJson2 = require3(join2(__dirname3, "../package.json"));
3300
+ } catch {
3301
+ packageJson2 = require3(join2(__dirname3, "../../package.json"));
3302
+ }
3303
+ async function startMCPServer(options) {
3304
+ const { rootDir, verbose, watch } = options;
3305
+ const log = (message) => {
3306
+ if (verbose) {
3307
+ console.error(`[Lien MCP] ${message}`);
3308
+ }
3309
+ };
3310
+ log("Initializing MCP server...");
3311
+ const embeddings = new LocalEmbeddings();
3312
+ const vectorDB = new VectorDB(rootDir);
3313
+ try {
3314
+ log("Loading embedding model...");
3315
+ await embeddings.initialize();
3316
+ log("Loading vector database...");
3317
+ await vectorDB.initialize();
3318
+ log("Embeddings and vector DB ready");
3319
+ } catch (error) {
3320
+ console.error(`Failed to initialize: ${error}`);
3321
+ process.exit(1);
3322
+ }
3323
+ const server = new Server(
3324
+ {
3325
+ name: "lien",
3326
+ version: packageJson2.version
3327
+ },
3328
+ {
3329
+ capabilities: {
3330
+ tools: {}
3331
+ }
3332
+ }
3333
+ );
3334
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
3335
+ tools
3336
+ }));
3337
+ const checkAndReconnect = async () => {
3338
+ try {
3339
+ const versionChanged = await vectorDB.checkVersion();
3340
+ if (versionChanged) {
3341
+ log("Index version changed, reconnecting to database...");
3342
+ await vectorDB.reconnect();
3343
+ log("Reconnected to updated index");
3344
+ }
3345
+ } catch (error) {
3346
+ log(`Version check failed: ${error}`);
3347
+ }
3348
+ };
3349
+ const getIndexMetadata = () => ({
3350
+ indexVersion: vectorDB.getCurrentVersion(),
3351
+ indexDate: vectorDB.getVersionDate()
3352
+ });
3353
+ const versionCheckInterval = setInterval(async () => {
3354
+ await checkAndReconnect();
3355
+ }, VERSION_CHECK_INTERVAL_MS);
3356
+ process.on("SIGINT", () => {
3357
+ clearInterval(versionCheckInterval);
3358
+ process.exit(0);
3359
+ });
3360
+ process.on("SIGTERM", () => {
3361
+ clearInterval(versionCheckInterval);
3362
+ process.exit(0);
3363
+ });
3364
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3365
+ const { name, arguments: args } = request.params;
3366
+ try {
3367
+ log(`Handling tool call: ${name}`);
3368
+ switch (name) {
3369
+ case "semantic_search": {
3370
+ const query = args?.query;
3371
+ const limit = args?.limit || 5;
3372
+ log(`Searching for: "${query}"`);
3373
+ await checkAndReconnect();
3374
+ const queryEmbedding = await embeddings.embed(query);
3375
+ const results = await vectorDB.search(queryEmbedding, limit, query);
3376
+ log(`Found ${results.length} results`);
3377
+ const response = {
3378
+ indexInfo: getIndexMetadata(),
3379
+ results
3380
+ };
3381
+ return {
3382
+ content: [
3383
+ {
3384
+ type: "text",
3385
+ text: JSON.stringify(response, null, 2)
3386
+ }
3387
+ ]
3388
+ };
3389
+ }
3390
+ case "find_similar": {
3391
+ const code = args?.code;
3392
+ const limit = args?.limit || 5;
3393
+ log(`Finding similar code...`);
3394
+ await checkAndReconnect();
3395
+ const codeEmbedding = await embeddings.embed(code);
3396
+ const results = await vectorDB.search(codeEmbedding, limit, code);
3397
+ log(`Found ${results.length} similar chunks`);
3398
+ const response = {
3399
+ indexInfo: getIndexMetadata(),
3400
+ results
3401
+ };
3402
+ return {
3403
+ content: [
3404
+ {
3405
+ type: "text",
3406
+ text: JSON.stringify(response, null, 2)
3407
+ }
3408
+ ]
3409
+ };
3410
+ }
3411
+ case "get_file_context": {
3412
+ const filepath = args?.filepath;
3413
+ const includeRelated = args?.includeRelated ?? true;
3414
+ log(`Getting context for: ${filepath}`);
3415
+ await checkAndReconnect();
3416
+ const fileEmbedding = await embeddings.embed(filepath);
3417
+ const allResults = await vectorDB.search(fileEmbedding, 50, filepath);
3418
+ const fileChunks = allResults.filter(
3419
+ (r) => r.metadata.file.includes(filepath) || filepath.includes(r.metadata.file)
3420
+ );
3421
+ let results = fileChunks;
3422
+ if (includeRelated && fileChunks.length > 0) {
3423
+ const relatedEmbedding = await embeddings.embed(fileChunks[0].content);
3424
+ const related = await vectorDB.search(relatedEmbedding, 5, fileChunks[0].content);
3425
+ const relatedOtherFiles = related.filter(
3426
+ (r) => !r.metadata.file.includes(filepath) && !filepath.includes(r.metadata.file)
3427
+ );
3428
+ results = [...fileChunks, ...relatedOtherFiles];
3429
+ }
3430
+ log(`Found ${results.length} chunks`);
3431
+ const response = {
3432
+ indexInfo: getIndexMetadata(),
3433
+ file: filepath,
3434
+ chunks: results
3435
+ };
3436
+ return {
3437
+ content: [
3438
+ {
3439
+ type: "text",
3440
+ text: JSON.stringify(response, null, 2)
3441
+ }
3442
+ ]
3443
+ };
3444
+ }
3445
+ case "list_functions": {
3446
+ const pattern = args?.pattern;
3447
+ const language = args?.language;
3448
+ log("Listing functions with symbol metadata...");
3449
+ await checkAndReconnect();
3450
+ let results;
3451
+ let usedMethod = "symbols";
3452
+ try {
3453
+ results = await vectorDB.querySymbols({
3454
+ language,
3455
+ pattern,
3456
+ limit: 50
3457
+ });
3458
+ if (results.length === 0 && (language || pattern)) {
3459
+ log("No symbol results, falling back to content scan...");
3460
+ results = await vectorDB.scanWithFilter({
3461
+ language,
3462
+ pattern,
3463
+ limit: 50
3464
+ });
3465
+ usedMethod = "content";
3466
+ }
3467
+ } catch (error) {
3468
+ log(`Symbol query failed, falling back to content scan: ${error}`);
3469
+ results = await vectorDB.scanWithFilter({
3470
+ language,
3471
+ pattern,
3472
+ limit: 50
3473
+ });
3474
+ usedMethod = "content";
3475
+ }
3476
+ log(`Found ${results.length} matches using ${usedMethod} method`);
3477
+ const response = {
3478
+ indexInfo: getIndexMetadata(),
3479
+ method: usedMethod,
3480
+ results,
3481
+ note: usedMethod === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
3482
+ };
3483
+ return {
3484
+ content: [
3485
+ {
3486
+ type: "text",
3487
+ text: JSON.stringify(response, null, 2)
3488
+ }
3489
+ ]
3490
+ };
3491
+ }
3492
+ default:
3493
+ throw new Error(`Unknown tool: ${name}`);
3494
+ }
3495
+ } catch (error) {
3496
+ console.error(`Error handling tool call ${name}:`, error);
3497
+ return {
3498
+ content: [
3499
+ {
3500
+ type: "text",
3501
+ text: JSON.stringify({
3502
+ error: String(error),
3503
+ tool: name
3504
+ })
3505
+ }
3506
+ ],
3507
+ isError: true
3508
+ };
3509
+ }
3510
+ });
3511
+ const config = await configService.load(rootDir);
3512
+ const hasIndex = await vectorDB.hasData();
3513
+ if (!hasIndex && config.mcp.autoIndexOnFirstRun) {
3514
+ log("\u{1F4E6} No index found - running initial indexing...");
3515
+ log("\u23F1\uFE0F This may take 5-20 minutes depending on project size");
3516
+ try {
3517
+ const { indexCodebase: indexCodebase2 } = await Promise.resolve().then(() => (init_indexer(), indexer_exports));
3518
+ await indexCodebase2({ rootDir, verbose: true });
3519
+ log("\u2705 Initial indexing complete!");
3520
+ } catch (error) {
3521
+ log(`\u26A0\uFE0F Initial indexing failed: ${error}`);
3522
+ log("You can manually run: lien index");
3523
+ }
3524
+ } else if (!hasIndex) {
3525
+ log("\u26A0\uFE0F No index found. Auto-indexing is disabled in config.");
3526
+ log('Run "lien index" to index your codebase.');
3527
+ }
3528
+ let gitTracker = null;
3529
+ let gitPollInterval = null;
3530
+ let fileWatcher = null;
3531
+ if (config.gitDetection.enabled) {
3532
+ const gitAvailable = await isGitAvailable();
3533
+ const isRepo = await isGitRepo(rootDir);
3534
+ if (gitAvailable && isRepo) {
3535
+ log("\u2713 Detected git repository");
3536
+ gitTracker = new GitStateTracker(rootDir, vectorDB.dbPath);
3537
+ try {
3538
+ log("Checking for git changes...");
3539
+ const changedFiles = await gitTracker.initialize();
3540
+ if (changedFiles && changedFiles.length > 0) {
3541
+ log(`\u{1F33F} Git changes detected: ${changedFiles.length} files changed`);
3542
+ log("Reindexing changed files...");
3543
+ const count = await indexMultipleFiles(
3544
+ changedFiles,
3545
+ vectorDB,
3546
+ embeddings,
3547
+ config,
3548
+ { verbose }
3549
+ );
3550
+ log(`\u2713 Reindexed ${count} files`);
3551
+ } else {
3552
+ log("\u2713 Index is up to date with git state");
3553
+ }
3554
+ } catch (error) {
3555
+ log(`Warning: Failed to check git state on startup: ${error}`);
3556
+ }
3557
+ log(`\u2713 Git detection enabled (checking every ${config.gitDetection.pollIntervalMs / 1e3}s)`);
3558
+ gitPollInterval = setInterval(async () => {
3559
+ try {
3560
+ const changedFiles = await gitTracker.detectChanges();
3561
+ if (changedFiles && changedFiles.length > 0) {
3562
+ log(`\u{1F33F} Git change detected: ${changedFiles.length} files changed`);
3563
+ log("Reindexing in background...");
3564
+ indexMultipleFiles(
3565
+ changedFiles,
3566
+ vectorDB,
3567
+ embeddings,
3568
+ config,
3569
+ { verbose }
3570
+ ).then((count) => {
3571
+ log(`\u2713 Background reindex complete: ${count} files`);
3572
+ }).catch((error) => {
3573
+ log(`Warning: Background reindex failed: ${error}`);
3574
+ });
3575
+ }
3576
+ } catch (error) {
3577
+ log(`Warning: Git detection check failed: ${error}`);
3578
+ }
3579
+ }, config.gitDetection.pollIntervalMs);
3580
+ } else {
3581
+ if (!gitAvailable) {
3582
+ log("Git not available - git detection disabled");
3583
+ } else if (!isRepo) {
3584
+ log("Not a git repository - git detection disabled");
3585
+ }
3586
+ }
3587
+ } else {
3588
+ log("Git detection disabled by configuration");
3589
+ }
3590
+ const fileWatchingEnabled = watch || config.fileWatching.enabled;
3591
+ if (fileWatchingEnabled) {
3592
+ log("\u{1F440} Starting file watcher...");
3593
+ fileWatcher = new FileWatcher(rootDir, config);
3594
+ try {
3595
+ await fileWatcher.start(async (event) => {
3596
+ const { type, filepath } = event;
3597
+ if (type === "unlink") {
3598
+ log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
3599
+ try {
3600
+ await vectorDB.deleteByFile(filepath);
3601
+ log(`\u2713 Removed ${filepath} from index`);
3602
+ } catch (error) {
3603
+ log(`Warning: Failed to remove ${filepath}: ${error}`);
3604
+ }
3605
+ } else {
3606
+ const action = type === "add" ? "added" : "changed";
3607
+ log(`\u{1F4DD} File ${action}: ${filepath}`);
3608
+ indexSingleFile(filepath, vectorDB, embeddings, config, { verbose }).catch((error) => {
3609
+ log(`Warning: Failed to reindex ${filepath}: ${error}`);
3610
+ });
3611
+ }
3612
+ });
3613
+ const watchedCount = fileWatcher.getWatchedFiles().length;
3614
+ log(`\u2713 File watching enabled (watching ${watchedCount} files)`);
3615
+ } catch (error) {
3616
+ log(`Warning: Failed to start file watcher: ${error}`);
3617
+ fileWatcher = null;
3618
+ }
3619
+ }
3620
+ const cleanup = async () => {
3621
+ log("Shutting down MCP server...");
3622
+ if (gitPollInterval) {
3623
+ clearInterval(gitPollInterval);
3624
+ }
3625
+ if (fileWatcher) {
3626
+ await fileWatcher.stop();
3627
+ }
3628
+ process.exit(0);
3629
+ };
3630
+ process.on("SIGINT", cleanup);
3631
+ process.on("SIGTERM", cleanup);
3632
+ const transport = new StdioServerTransport();
3633
+ await server.connect(transport);
3634
+ log("MCP server started and listening on stdio");
3635
+ }
3636
+
3637
+ // src/cli/serve.ts
3638
+ init_banner();
3639
+ async function serveCommand(options) {
3640
+ const rootDir = process.cwd();
3641
+ try {
3642
+ showBanner();
3643
+ console.error(chalk6.bold("Starting MCP server...\n"));
3644
+ await startMCPServer({
3645
+ rootDir,
3646
+ verbose: true,
3647
+ watch: options.watch
3648
+ });
3649
+ } catch (error) {
3650
+ console.error(chalk6.red("Failed to start MCP server:"), error);
3651
+ process.exit(1);
3652
+ }
3653
+ }
3654
+
3655
+ // src/cli/index.ts
3656
+ var __filename4 = fileURLToPath4(import.meta.url);
3657
+ var __dirname4 = dirname3(__filename4);
3658
+ var require4 = createRequire3(import.meta.url);
3659
+ var packageJson3;
3660
+ try {
3661
+ packageJson3 = require4(join3(__dirname4, "../package.json"));
3662
+ } catch {
3663
+ packageJson3 = require4(join3(__dirname4, "../../package.json"));
3664
+ }
3665
+ var program = new Command();
3666
+ program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);
3667
+ program.command("init").description("Initialize Lien in the current directory").option("-u, --upgrade", "Upgrade existing config with new options").option("-y, --yes", "Skip interactive prompts and use defaults").option("-p, --path <path>", "Path to initialize (defaults to current directory)").action(initCommand);
3668
+ program.command("index").description("Index the codebase for semantic search").option("-w, --watch", "Watch for changes and re-index automatically").option("-v, --verbose", "Show detailed logging during indexing").action(indexCommand);
3669
+ program.command("serve").description("Start the MCP server for Cursor integration").option("-p, --port <port>", "Port number (for future use)", "7133").option("-w, --watch", "Enable file watching for real-time reindexing").action(serveCommand);
3670
+ program.command("status").description("Show indexing status and statistics").action(statusCommand);
3671
+ program.command("reindex").description("Clear index and re-index the entire codebase").option("-v, --verbose", "Show detailed logging during indexing").action(async (options) => {
3672
+ const { showCompactBanner: showCompactBanner2 } = await Promise.resolve().then(() => (init_banner(), banner_exports));
3673
+ const chalk7 = (await import("chalk")).default;
3674
+ const { VectorDB: VectorDB2 } = await Promise.resolve().then(() => (init_lancedb(), lancedb_exports));
3675
+ const { indexCodebase: indexCodebase2 } = await Promise.resolve().then(() => (init_indexer(), indexer_exports));
3676
+ showCompactBanner2();
3677
+ try {
3678
+ console.log(chalk7.yellow("Clearing existing index..."));
3679
+ const vectorDB = new VectorDB2(process.cwd());
3680
+ await vectorDB.initialize();
3681
+ await vectorDB.clear();
3682
+ console.log(chalk7.green("\u2713 Index cleared\n"));
3683
+ await indexCodebase2({
3684
+ rootDir: process.cwd(),
3685
+ verbose: options.verbose || false
3686
+ });
3687
+ } catch (error) {
3688
+ console.error(chalk7.red("Error during re-indexing:"), error);
3689
+ process.exit(1);
3690
+ }
3691
+ });
3692
+
3693
+ // src/index.ts
3694
+ program.parse();
3695
+ //# sourceMappingURL=index.js.map