@liendev/lien 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -329,8 +329,8 @@ var init_errors = __esm({
329
329
  });
330
330
 
331
331
  // src/config/service.ts
332
- import fs5 from "fs/promises";
333
- import path5 from "path";
332
+ import fs6 from "fs/promises";
333
+ import path6 from "path";
334
334
  var ConfigService, configService;
335
335
  var init_service = __esm({
336
336
  "src/config/service.ts"() {
@@ -352,13 +352,13 @@ var init_service = __esm({
352
352
  async load(rootDir = process.cwd()) {
353
353
  const configPath = this.getConfigPath(rootDir);
354
354
  try {
355
- const configContent = await fs5.readFile(configPath, "utf-8");
355
+ const configContent = await fs6.readFile(configPath, "utf-8");
356
356
  const userConfig = JSON.parse(configContent);
357
357
  if (this.needsMigration(userConfig)) {
358
358
  console.log("\u{1F504} Migrating config from v0.2.0 to v0.3.0...");
359
359
  const result = await this.migrate(rootDir);
360
360
  if (result.migrated && result.backupPath) {
361
- const backupFilename = path5.basename(result.backupPath);
361
+ const backupFilename = path6.basename(result.backupPath);
362
362
  console.log(`\u2705 Migration complete! Backup saved as ${backupFilename}`);
363
363
  console.log("\u{1F4DD} Your config now uses the framework-based structure.");
364
364
  }
@@ -414,7 +414,7 @@ ${validation.errors.join("\n")}`,
414
414
  }
415
415
  try {
416
416
  const configJson = JSON.stringify(config, null, 2) + "\n";
417
- await fs5.writeFile(configPath, configJson, "utf-8");
417
+ await fs6.writeFile(configPath, configJson, "utf-8");
418
418
  } catch (error) {
419
419
  throw wrapError(error, "Failed to save configuration", { path: configPath });
420
420
  }
@@ -428,7 +428,7 @@ ${validation.errors.join("\n")}`,
428
428
  async exists(rootDir = process.cwd()) {
429
429
  const configPath = this.getConfigPath(rootDir);
430
430
  try {
431
- await fs5.access(configPath);
431
+ await fs6.access(configPath);
432
432
  return true;
433
433
  } catch {
434
434
  return false;
@@ -445,7 +445,7 @@ ${validation.errors.join("\n")}`,
445
445
  async migrate(rootDir = process.cwd()) {
446
446
  const configPath = this.getConfigPath(rootDir);
447
447
  try {
448
- const configContent = await fs5.readFile(configPath, "utf-8");
448
+ const configContent = await fs6.readFile(configPath, "utf-8");
449
449
  const oldConfig = JSON.parse(configContent);
450
450
  if (!this.needsMigration(oldConfig)) {
451
451
  return {
@@ -463,7 +463,7 @@ ${validation.errors.join("\n")}`,
463
463
  );
464
464
  }
465
465
  const backupPath = `${configPath}.v0.2.0.backup`;
466
- await fs5.copyFile(configPath, backupPath);
466
+ await fs6.copyFile(configPath, backupPath);
467
467
  await this.save(rootDir, newConfig);
468
468
  return {
469
469
  migrated: true,
@@ -561,7 +561,7 @@ ${validation.errors.join("\n")}`,
561
561
  * Get the full path to the config file
562
562
  */
563
563
  getConfigPath(rootDir) {
564
- return path5.join(rootDir, _ConfigService.CONFIG_FILENAME);
564
+ return path6.join(rootDir, _ConfigService.CONFIG_FILENAME);
565
565
  }
566
566
  /**
567
567
  * Validate modern (v0.3.0+) configuration
@@ -725,7 +725,7 @@ ${validation.errors.join("\n")}`,
725
725
  errors.push(`frameworks[${index}] missing required field: path`);
726
726
  } else if (typeof fw.path !== "string") {
727
727
  errors.push(`frameworks[${index}].path must be a string`);
728
- } else if (path5.isAbsolute(fw.path)) {
728
+ } else if (path6.isAbsolute(fw.path)) {
729
729
  errors.push(`frameworks[${index}].path must be relative, got: ${fw.path}`);
730
730
  }
731
731
  if (fw.enabled === void 0) {
@@ -773,21 +773,21 @@ ${validation.errors.join("\n")}`,
773
773
  });
774
774
 
775
775
  // src/vectordb/version.ts
776
- import fs7 from "fs/promises";
777
- import path7 from "path";
776
+ import fs8 from "fs/promises";
777
+ import path8 from "path";
778
778
  async function writeVersionFile(indexPath) {
779
779
  try {
780
- const versionFilePath = path7.join(indexPath, VERSION_FILE);
780
+ const versionFilePath = path8.join(indexPath, VERSION_FILE);
781
781
  const timestamp = Date.now().toString();
782
- await fs7.writeFile(versionFilePath, timestamp, "utf-8");
782
+ await fs8.writeFile(versionFilePath, timestamp, "utf-8");
783
783
  } catch (error) {
784
784
  console.error(`Warning: Failed to write version file: ${error}`);
785
785
  }
786
786
  }
787
787
  async function readVersionFile(indexPath) {
788
788
  try {
789
- const versionFilePath = path7.join(indexPath, VERSION_FILE);
790
- const content = await fs7.readFile(versionFilePath, "utf-8");
789
+ const versionFilePath = path8.join(indexPath, VERSION_FILE);
790
+ const content = await fs8.readFile(versionFilePath, "utf-8");
791
791
  const timestamp = parseInt(content.trim(), 10);
792
792
  return isNaN(timestamp) ? 0 : timestamp;
793
793
  } catch (error) {
@@ -805,8 +805,8 @@ var init_version = __esm({
805
805
  // src/indexer/scanner.ts
806
806
  import { glob } from "glob";
807
807
  import ignore from "ignore";
808
- import fs9 from "fs/promises";
809
- import path9 from "path";
808
+ import fs10 from "fs/promises";
809
+ import path10 from "path";
810
810
  async function scanCodebaseWithFrameworks(rootDir, config) {
811
811
  const allFiles = [];
812
812
  for (const framework of config.frameworks) {
@@ -819,16 +819,16 @@ async function scanCodebaseWithFrameworks(rootDir, config) {
819
819
  return allFiles;
820
820
  }
821
821
  async function scanFramework(rootDir, framework) {
822
- const frameworkPath = path9.join(rootDir, framework.path);
823
- const gitignorePath = path9.join(frameworkPath, ".gitignore");
822
+ const frameworkPath = path10.join(rootDir, framework.path);
823
+ const gitignorePath = path10.join(frameworkPath, ".gitignore");
824
824
  let ig = ignore();
825
825
  try {
826
- const gitignoreContent = await fs9.readFile(gitignorePath, "utf-8");
826
+ const gitignoreContent = await fs10.readFile(gitignorePath, "utf-8");
827
827
  ig = ignore().add(gitignoreContent);
828
828
  } catch (e) {
829
- const rootGitignorePath = path9.join(rootDir, ".gitignore");
829
+ const rootGitignorePath = path10.join(rootDir, ".gitignore");
830
830
  try {
831
- const gitignoreContent = await fs9.readFile(rootGitignorePath, "utf-8");
831
+ const gitignoreContent = await fs10.readFile(rootGitignorePath, "utf-8");
832
832
  ig = ignore().add(gitignoreContent);
833
833
  } catch (e2) {
834
834
  }
@@ -850,15 +850,15 @@ async function scanFramework(rootDir, framework) {
850
850
  }
851
851
  const uniqueFiles = Array.from(new Set(allFiles));
852
852
  return uniqueFiles.filter((file) => !ig.ignores(file)).map((file) => {
853
- return framework.path === "." ? file : path9.join(framework.path, file);
853
+ return framework.path === "." ? file : path10.join(framework.path, file);
854
854
  });
855
855
  }
856
856
  async function scanCodebase(options) {
857
857
  const { rootDir, includePatterns = [], excludePatterns = [] } = options;
858
- const gitignorePath = path9.join(rootDir, ".gitignore");
858
+ const gitignorePath = path10.join(rootDir, ".gitignore");
859
859
  let ig = ignore();
860
860
  try {
861
- const gitignoreContent = await fs9.readFile(gitignorePath, "utf-8");
861
+ const gitignoreContent = await fs10.readFile(gitignorePath, "utf-8");
862
862
  ig = ignore().add(gitignoreContent);
863
863
  } catch (e) {
864
864
  }
@@ -885,12 +885,12 @@ async function scanCodebase(options) {
885
885
  }
886
886
  const uniqueFiles = Array.from(new Set(allFiles));
887
887
  return uniqueFiles.filter((file) => {
888
- const relativePath = path9.relative(rootDir, file);
888
+ const relativePath = path10.relative(rootDir, file);
889
889
  return !ig.ignores(relativePath);
890
890
  });
891
891
  }
892
892
  function detectLanguage(filepath) {
893
- const ext = path9.extname(filepath).toLowerCase();
893
+ const ext = path10.extname(filepath).toLowerCase();
894
894
  const languageMap = {
895
895
  ".ts": "typescript",
896
896
  ".tsx": "typescript",
@@ -915,6 +915,7 @@ function detectLanguage(filepath) {
915
915
  ".kt": "kotlin",
916
916
  ".cs": "csharp",
917
917
  ".scala": "scala",
918
+ ".liquid": "liquid",
918
919
  ".md": "markdown",
919
920
  ".mdx": "markdown",
920
921
  ".markdown": "markdown"
@@ -1380,12 +1381,12 @@ __export(lancedb_exports, {
1380
1381
  VectorDB: () => VectorDB
1381
1382
  });
1382
1383
  import * as lancedb from "vectordb";
1383
- import path10 from "path";
1384
+ import path11 from "path";
1384
1385
  import os2 from "os";
1385
1386
  import crypto2 from "crypto";
1386
1387
  function isDocumentationFile(filepath) {
1387
1388
  const lower = filepath.toLowerCase();
1388
- const filename = path10.basename(filepath).toLowerCase();
1389
+ const filename = path11.basename(filepath).toLowerCase();
1389
1390
  if (filename.startsWith("readme")) return true;
1390
1391
  if (filename.startsWith("changelog")) return true;
1391
1392
  if (filename.endsWith(".md") || filename.endsWith(".mdx") || filename.endsWith(".markdown")) {
@@ -1432,7 +1433,7 @@ function boostPathRelevance(query, filepath, baseScore) {
1432
1433
  return baseScore * boostFactor;
1433
1434
  }
1434
1435
  function boostFilenameRelevance(query, filepath, baseScore) {
1435
- const filename = path10.basename(filepath, path10.extname(filepath)).toLowerCase();
1436
+ const filename = path11.basename(filepath, path11.extname(filepath)).toLowerCase();
1436
1437
  const queryTokens = query.toLowerCase().split(/\s+/);
1437
1438
  let boostFactor = 1;
1438
1439
  for (const token of queryTokens) {
@@ -1447,7 +1448,7 @@ function boostFilenameRelevance(query, filepath, baseScore) {
1447
1448
  }
1448
1449
  function boostForLocationIntent(query, filepath, baseScore) {
1449
1450
  let score = baseScore;
1450
- const filename = path10.basename(filepath, path10.extname(filepath)).toLowerCase();
1451
+ const filename = path11.basename(filepath, path11.extname(filepath)).toLowerCase();
1451
1452
  const queryTokens = query.toLowerCase().split(/\s+/);
1452
1453
  for (const token of queryTokens) {
1453
1454
  if (token.length <= 2) continue;
@@ -1475,7 +1476,7 @@ function boostForConceptualIntent(query, filepath, baseScore) {
1475
1476
  if (isUtilityFile(filepath)) {
1476
1477
  score *= 1.05;
1477
1478
  }
1478
- const filename = path10.basename(filepath, path10.extname(filepath)).toLowerCase();
1479
+ const filename = path11.basename(filepath, path11.extname(filepath)).toLowerCase();
1479
1480
  const queryTokens = query.toLowerCase().split(/\s+/);
1480
1481
  for (const token of queryTokens) {
1481
1482
  if (token.length <= 2) continue;
@@ -1483,7 +1484,7 @@ function boostForConceptualIntent(query, filepath, baseScore) {
1483
1484
  score *= 0.9;
1484
1485
  }
1485
1486
  }
1486
- const pathSegments = filepath.toLowerCase().split(path10.sep);
1487
+ const pathSegments = filepath.toLowerCase().split(path11.sep);
1487
1488
  for (const token of queryTokens) {
1488
1489
  if (token.length <= 2) continue;
1489
1490
  for (const segment of pathSegments) {
@@ -1537,9 +1538,9 @@ var init_lancedb = __esm({
1537
1538
  lastVersionCheck = 0;
1538
1539
  currentVersion = 0;
1539
1540
  constructor(projectRoot) {
1540
- const projectName = path10.basename(projectRoot);
1541
+ const projectName = path11.basename(projectRoot);
1541
1542
  const pathHash = crypto2.createHash("md5").update(projectRoot).digest("hex").substring(0, 8);
1542
- this.dbPath = path10.join(
1543
+ this.dbPath = path11.join(
1543
1544
  os2.homedir(),
1544
1545
  ".lien",
1545
1546
  "indices",
@@ -1897,7 +1898,7 @@ var indexer_exports = {};
1897
1898
  __export(indexer_exports, {
1898
1899
  indexCodebase: () => indexCodebase
1899
1900
  });
1900
- import fs10 from "fs/promises";
1901
+ import fs11 from "fs/promises";
1901
1902
  import ora from "ora";
1902
1903
  import chalk4 from "chalk";
1903
1904
  import pLimit from "p-limit";
@@ -1963,7 +1964,7 @@ async function indexCodebase(options = {}) {
1963
1964
  const filePromises = files.map(
1964
1965
  (file) => limit(async () => {
1965
1966
  try {
1966
- const content = await fs10.readFile(file, "utf-8");
1967
+ const content = await fs11.readFile(file, "utf-8");
1967
1968
  const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
1968
1969
  const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : 10;
1969
1970
  const chunks = chunkFile(file, content, {
@@ -2034,15 +2035,15 @@ init_schema();
2034
2035
  init_merge();
2035
2036
  init_banner();
2036
2037
  init_migration();
2037
- import fs4 from "fs/promises";
2038
- import path4 from "path";
2038
+ import fs5 from "fs/promises";
2039
+ import path5 from "path";
2039
2040
  import { fileURLToPath as fileURLToPath2 } from "url";
2040
2041
  import chalk2 from "chalk";
2041
2042
  import inquirer from "inquirer";
2042
2043
 
2043
2044
  // src/frameworks/detector-service.ts
2044
- import fs3 from "fs/promises";
2045
- import path3 from "path";
2045
+ import fs4 from "fs/promises";
2046
+ import path4 from "path";
2046
2047
 
2047
2048
  // src/frameworks/types.ts
2048
2049
  var defaultDetectionOptions = {
@@ -2071,24 +2072,17 @@ import path from "path";
2071
2072
  async function generateNodeJsConfig(_rootDir, _relativePath) {
2072
2073
  return {
2073
2074
  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",
2075
+ // Broader patterns to catch all common project structures
2076
+ // (frontend/, src/, lib/, app/, components/, etc.)
2077
+ "**/*.ts",
2078
+ "**/*.tsx",
2079
+ "**/*.js",
2080
+ "**/*.jsx",
2081
+ "**/*.vue",
2082
+ "**/*.mjs",
2083
+ "**/*.cjs",
2086
2084
  "**/*.md",
2087
- "**/*.mdx",
2088
- "docs/**/*.md",
2089
- "README.md",
2090
- "CHANGELOG.md",
2091
- "CONTRIBUTING.md"
2085
+ "**/*.mdx"
2092
2086
  ],
2093
2087
  exclude: [
2094
2088
  "node_modules/**",
@@ -2097,10 +2091,22 @@ async function generateNodeJsConfig(_rootDir, _relativePath) {
2097
2091
  "coverage/**",
2098
2092
  ".next/**",
2099
2093
  ".nuxt/**",
2094
+ ".vite/**",
2095
+ ".lien/**",
2100
2096
  "out/**",
2101
2097
  "*.min.js",
2102
2098
  "*.min.css",
2103
- "*.bundle.js"
2099
+ "*.bundle.js",
2100
+ // Test artifacts (source files are indexed, but not output)
2101
+ "playwright-report/**",
2102
+ "test-results/**",
2103
+ // Build/generated artifacts
2104
+ "__generated__/**",
2105
+ // Common build/cache directories
2106
+ ".cache/**",
2107
+ ".turbo/**",
2108
+ ".vercel/**",
2109
+ ".netlify/**"
2104
2110
  ]
2105
2111
  };
2106
2112
  }
@@ -2185,12 +2191,12 @@ async function generateLaravelConfig(_rootDir, _relativePath) {
2185
2191
  "resources/**/*.php",
2186
2192
  "tests/**/*.php",
2187
2193
  "*.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
+ // Frontend assets (Vue/React/Inertia) - Broadened for flexibility
2195
+ "**/*.js",
2196
+ "**/*.ts",
2197
+ "**/*.jsx",
2198
+ "**/*.tsx",
2199
+ "**/*.vue",
2194
2200
  // Blade templates
2195
2201
  "resources/views/**/*.blade.php",
2196
2202
  // Documentation
@@ -2205,7 +2211,19 @@ async function generateLaravelConfig(_rootDir, _relativePath) {
2205
2211
  "storage/**",
2206
2212
  "bootstrap/cache/**",
2207
2213
  "public/**",
2208
- "node_modules/**"
2214
+ "node_modules/**",
2215
+ "dist/**",
2216
+ "build/**",
2217
+ // Test artifacts (source files are indexed, but not output)
2218
+ "playwright-report/**",
2219
+ "test-results/**",
2220
+ "coverage/**",
2221
+ // Build/generated artifacts
2222
+ "__generated__/**",
2223
+ // Frontend build outputs
2224
+ ".vite/**",
2225
+ ".nuxt/**",
2226
+ ".next/**"
2209
2227
  ]
2210
2228
  };
2211
2229
  }
@@ -2287,10 +2305,142 @@ var laravelDetector = {
2287
2305
  }
2288
2306
  };
2289
2307
 
2308
+ // src/frameworks/shopify/detector.ts
2309
+ import fs3 from "fs/promises";
2310
+ import path3 from "path";
2311
+
2312
+ // src/frameworks/shopify/config.ts
2313
+ async function generateShopifyConfig(_rootDir, _relativePath) {
2314
+ return {
2315
+ include: [
2316
+ // Core Liquid templates
2317
+ "layout/**/*.liquid",
2318
+ "sections/**/*.liquid",
2319
+ "snippets/**/*.liquid",
2320
+ "templates/**/*.liquid",
2321
+ // Matches any nesting level (e.g., templates/customers/account.liquid)
2322
+ // Theme editor blocks (Online Store 2.0)
2323
+ "blocks/**/*.liquid",
2324
+ // Assets (CSS, JS with optional Liquid templating)
2325
+ "assets/**/*.js",
2326
+ "assets/**/*.js.liquid",
2327
+ "assets/**/*.css",
2328
+ "assets/**/*.css.liquid",
2329
+ "assets/**/*.scss",
2330
+ "assets/**/*.scss.liquid",
2331
+ // Configuration files
2332
+ "config/*.json",
2333
+ // Locales (i18n)
2334
+ "locales/*.json",
2335
+ // Documentation
2336
+ "*.md",
2337
+ "docs/**/*.md",
2338
+ // Shopify-specific config files
2339
+ "shopify.theme.toml",
2340
+ ".shopifyignore"
2341
+ ],
2342
+ exclude: [
2343
+ "node_modules/**",
2344
+ "dist/**",
2345
+ "build/**",
2346
+ ".git/**",
2347
+ // Playwright/testing artifacts
2348
+ "playwright-report/**",
2349
+ "test-results/**",
2350
+ // Build/generated artifacts
2351
+ "__generated__/**",
2352
+ // Common frontend build outputs
2353
+ ".vite/**",
2354
+ ".nuxt/**",
2355
+ ".next/**"
2356
+ ]
2357
+ };
2358
+ }
2359
+
2360
+ // src/frameworks/shopify/detector.ts
2361
+ var shopifyDetector = {
2362
+ name: "shopify",
2363
+ priority: 100,
2364
+ // High priority (same as Laravel)
2365
+ async detect(rootDir, relativePath) {
2366
+ const fullPath = path3.join(rootDir, relativePath);
2367
+ const result = {
2368
+ detected: false,
2369
+ name: "shopify",
2370
+ path: relativePath,
2371
+ confidence: "low",
2372
+ evidence: []
2373
+ };
2374
+ const settingsSchemaPath = path3.join(fullPath, "config", "settings_schema.json");
2375
+ let hasSettingsSchema = false;
2376
+ try {
2377
+ await fs3.access(settingsSchemaPath);
2378
+ hasSettingsSchema = true;
2379
+ result.evidence.push("Found config/settings_schema.json");
2380
+ } catch {
2381
+ }
2382
+ const themeLayoutPath = path3.join(fullPath, "layout", "theme.liquid");
2383
+ let hasThemeLayout = false;
2384
+ try {
2385
+ await fs3.access(themeLayoutPath);
2386
+ hasThemeLayout = true;
2387
+ result.evidence.push("Found layout/theme.liquid");
2388
+ } catch {
2389
+ }
2390
+ const shopifyDirs = ["sections", "snippets", "templates", "locales"];
2391
+ let foundDirs = 0;
2392
+ for (const dir of shopifyDirs) {
2393
+ try {
2394
+ const dirPath = path3.join(fullPath, dir);
2395
+ const stats = await fs3.stat(dirPath);
2396
+ if (stats.isDirectory()) {
2397
+ foundDirs++;
2398
+ }
2399
+ } catch {
2400
+ }
2401
+ }
2402
+ if (foundDirs >= 2) {
2403
+ result.evidence.push(`Shopify directory structure detected (${foundDirs}/${shopifyDirs.length} dirs)`);
2404
+ }
2405
+ try {
2406
+ const tomlPath = path3.join(fullPath, "shopify.theme.toml");
2407
+ await fs3.access(tomlPath);
2408
+ result.evidence.push("Found shopify.theme.toml");
2409
+ } catch {
2410
+ }
2411
+ try {
2412
+ const ignorePath = path3.join(fullPath, ".shopifyignore");
2413
+ await fs3.access(ignorePath);
2414
+ result.evidence.push("Found .shopifyignore");
2415
+ } catch {
2416
+ }
2417
+ if (hasSettingsSchema && foundDirs >= 2) {
2418
+ result.detected = true;
2419
+ result.confidence = "high";
2420
+ return result;
2421
+ }
2422
+ if (hasSettingsSchema || hasThemeLayout && foundDirs >= 1) {
2423
+ result.detected = true;
2424
+ result.confidence = "medium";
2425
+ return result;
2426
+ }
2427
+ if (foundDirs >= 3) {
2428
+ result.detected = true;
2429
+ result.confidence = "medium";
2430
+ return result;
2431
+ }
2432
+ return result;
2433
+ },
2434
+ async generateConfig(rootDir, relativePath) {
2435
+ return generateShopifyConfig(rootDir, relativePath);
2436
+ }
2437
+ };
2438
+
2290
2439
  // src/frameworks/registry.ts
2291
2440
  var frameworkDetectors = [
2292
2441
  nodejsDetector,
2293
- laravelDetector
2442
+ laravelDetector,
2443
+ shopifyDetector
2294
2444
  ];
2295
2445
  function getFrameworkDetector(name) {
2296
2446
  return frameworkDetectors.find((d) => d.name === name);
@@ -2306,7 +2456,7 @@ async function detectAllFrameworks(rootDir, options = {}) {
2306
2456
  return results;
2307
2457
  }
2308
2458
  async function detectAtPath(rootDir, relativePath, results, visited) {
2309
- const fullPath = path3.join(rootDir, relativePath);
2459
+ const fullPath = path4.join(rootDir, relativePath);
2310
2460
  if (visited.has(fullPath)) {
2311
2461
  return;
2312
2462
  }
@@ -2326,25 +2476,56 @@ async function detectAtPath(rootDir, relativePath, results, visited) {
2326
2476
  }
2327
2477
  }
2328
2478
  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)`);
2479
+ const highConfidence = detectedAtPath.filter((d) => d.confidence === "high");
2480
+ const mediumConfidence = detectedAtPath.filter((d) => d.confidence === "medium");
2481
+ const lowConfidence = detectedAtPath.filter((d) => d.confidence === "low");
2482
+ if (highConfidence.length > 1) {
2483
+ const cleanResults = highConfidence.map(({ priority, ...result }) => result);
2484
+ results.push(...cleanResults);
2485
+ const names = highConfidence.map((d) => d.name).join(" + ");
2486
+ console.log(` \u2192 Detected hybrid project: ${names}`);
2487
+ if (mediumConfidence.length > 0 || lowConfidence.length > 0) {
2488
+ const skippedNames = [...mediumConfidence, ...lowConfidence].map((d) => d.name).join(", ");
2489
+ console.log(` \u2192 Skipping lower confidence detections: ${skippedNames}`);
2490
+ }
2491
+ } else if (highConfidence.length === 1) {
2492
+ const { priority, ...result } = highConfidence[0];
2493
+ results.push(result);
2494
+ if (mediumConfidence.length > 0 || lowConfidence.length > 0) {
2495
+ const skippedNames = [...mediumConfidence, ...lowConfidence].map((d) => d.name).join(", ");
2496
+ console.log(` \u2192 Skipping lower confidence detections: ${skippedNames}`);
2497
+ }
2498
+ } else if (mediumConfidence.length > 0) {
2499
+ mediumConfidence.sort((a, b) => b.priority - a.priority);
2500
+ const { priority, ...winner } = mediumConfidence[0];
2501
+ results.push(winner);
2502
+ const skipped = [...mediumConfidence.slice(1), ...lowConfidence];
2503
+ if (skipped.length > 0) {
2504
+ const skippedNames = skipped.map((d) => d.name).join(", ");
2505
+ console.log(` \u2192 Skipping ${skippedNames} at ${relativePath} (${winner.name} takes precedence)`);
2506
+ }
2507
+ } else if (lowConfidence.length > 0) {
2508
+ lowConfidence.sort((a, b) => b.priority - a.priority);
2509
+ const { priority, ...winner } = lowConfidence[0];
2510
+ results.push(winner);
2511
+ const skipped = lowConfidence.slice(1);
2512
+ if (skipped.length > 0) {
2513
+ const skippedNames = skipped.map((d) => d.name).join(", ");
2514
+ console.log(` \u2192 Skipping ${skippedNames} at ${relativePath} (${winner.name} takes precedence)`);
2515
+ }
2336
2516
  }
2337
2517
  } else if (detectedAtPath.length === 1) {
2338
- results.push(detectedAtPath[0]);
2518
+ const { priority, ...result } = detectedAtPath[0];
2519
+ results.push(result);
2339
2520
  }
2340
2521
  }
2341
2522
  async function scanSubdirectories(rootDir, relativePath, results, visited, depth, options) {
2342
2523
  if (depth >= options.maxDepth) {
2343
2524
  return;
2344
2525
  }
2345
- const fullPath = path3.join(rootDir, relativePath);
2526
+ const fullPath = path4.join(rootDir, relativePath);
2346
2527
  try {
2347
- const entries = await fs3.readdir(fullPath, { withFileTypes: true });
2528
+ const entries = await fs4.readdir(fullPath, { withFileTypes: true });
2348
2529
  const dirs = entries.filter((e) => e.isDirectory());
2349
2530
  for (const dir of dirs) {
2350
2531
  if (options.skipDirs.includes(dir.name)) {
@@ -2353,7 +2534,7 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
2353
2534
  if (dir.name.startsWith(".")) {
2354
2535
  continue;
2355
2536
  }
2356
- const subPath = relativePath === "." ? dir.name : path3.join(relativePath, dir.name);
2537
+ const subPath = relativePath === "." ? dir.name : path4.join(relativePath, dir.name);
2357
2538
  await detectAtPath(rootDir, subPath, results, visited);
2358
2539
  await scanSubdirectories(rootDir, subPath, results, visited, depth + 1, options);
2359
2540
  }
@@ -2364,14 +2545,14 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
2364
2545
 
2365
2546
  // src/cli/init.ts
2366
2547
  var __filename2 = fileURLToPath2(import.meta.url);
2367
- var __dirname2 = path4.dirname(__filename2);
2548
+ var __dirname2 = path5.dirname(__filename2);
2368
2549
  async function initCommand(options = {}) {
2369
2550
  const rootDir = options.path || process.cwd();
2370
- const configPath = path4.join(rootDir, ".lien.config.json");
2551
+ const configPath = path5.join(rootDir, ".lien.config.json");
2371
2552
  try {
2372
2553
  let configExists = false;
2373
2554
  try {
2374
- await fs4.access(configPath);
2555
+ await fs5.access(configPath);
2375
2556
  configExists = true;
2376
2557
  } catch {
2377
2558
  }
@@ -2507,22 +2688,22 @@ async function createNewConfig(rootDir, options) {
2507
2688
  ]);
2508
2689
  if (installCursorRules) {
2509
2690
  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");
2691
+ const cursorRulesDir = path5.join(rootDir, ".cursor");
2692
+ await fs5.mkdir(cursorRulesDir, { recursive: true });
2693
+ const templatePath = path5.join(__dirname2, "../CURSOR_RULES_TEMPLATE.md");
2694
+ const rulesPath = path5.join(cursorRulesDir, "rules");
2514
2695
  let targetPath;
2515
2696
  let isDirectory = false;
2516
2697
  let isFile = false;
2517
2698
  try {
2518
- const stats = await fs4.stat(rulesPath);
2699
+ const stats = await fs5.stat(rulesPath);
2519
2700
  isDirectory = stats.isDirectory();
2520
2701
  isFile = stats.isFile();
2521
2702
  } catch {
2522
2703
  }
2523
2704
  if (isDirectory) {
2524
- targetPath = path4.join(rulesPath, "lien.mdc");
2525
- await fs4.copyFile(templatePath, targetPath);
2705
+ targetPath = path5.join(rulesPath, "lien.mdc");
2706
+ await fs5.copyFile(templatePath, targetPath);
2526
2707
  console.log(chalk2.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
2527
2708
  } else if (isFile) {
2528
2709
  const { convertToDir } = await inquirer.prompt([
@@ -2534,11 +2715,11 @@ async function createNewConfig(rootDir, options) {
2534
2715
  }
2535
2716
  ]);
2536
2717
  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"));
2718
+ const existingRules = await fs5.readFile(rulesPath, "utf-8");
2719
+ await fs5.unlink(rulesPath);
2720
+ await fs5.mkdir(rulesPath);
2721
+ await fs5.writeFile(path5.join(rulesPath, "project.mdc"), existingRules);
2722
+ await fs5.copyFile(templatePath, path5.join(rulesPath, "lien.mdc"));
2542
2723
  console.log(chalk2.green("\u2713 Converted .cursor/rules to directory"));
2543
2724
  console.log(chalk2.green(" - Your project rules: .cursor/rules/project.mdc"));
2544
2725
  console.log(chalk2.green(" - Lien rules: .cursor/rules/lien.mdc"));
@@ -2546,9 +2727,9 @@ async function createNewConfig(rootDir, options) {
2546
2727
  console.log(chalk2.dim("Skipped Cursor rules installation (preserving existing file)"));
2547
2728
  }
2548
2729
  } else {
2549
- await fs4.mkdir(rulesPath, { recursive: true });
2550
- targetPath = path4.join(rulesPath, "lien.mdc");
2551
- await fs4.copyFile(templatePath, targetPath);
2730
+ await fs5.mkdir(rulesPath, { recursive: true });
2731
+ targetPath = path5.join(rulesPath, "lien.mdc");
2732
+ await fs5.copyFile(templatePath, targetPath);
2552
2733
  console.log(chalk2.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
2553
2734
  }
2554
2735
  } catch (error) {
@@ -2562,8 +2743,8 @@ async function createNewConfig(rootDir, options) {
2562
2743
  ...defaultConfig,
2563
2744
  frameworks
2564
2745
  };
2565
- const configPath = path4.join(rootDir, ".lien.config.json");
2566
- await fs4.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2746
+ const configPath = path5.join(rootDir, ".lien.config.json");
2747
+ await fs5.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2567
2748
  console.log(chalk2.green("\n\u2713 Created .lien.config.json"));
2568
2749
  console.log(chalk2.green(`\u2713 Configured ${frameworks.length} framework(s)`));
2569
2750
  console.log(chalk2.dim("\nNext steps:"));
@@ -2598,8 +2779,8 @@ Customizing ${frameworkName} settings:`));
2598
2779
  async function upgradeConfig(configPath) {
2599
2780
  try {
2600
2781
  const backupPath = `${configPath}.backup`;
2601
- await fs4.copyFile(configPath, backupPath);
2602
- const existingContent = await fs4.readFile(configPath, "utf-8");
2782
+ await fs5.copyFile(configPath, backupPath);
2783
+ const existingContent = await fs5.readFile(configPath, "utf-8");
2603
2784
  const existingConfig = JSON.parse(existingContent);
2604
2785
  let upgradedConfig;
2605
2786
  let migrated = false;
@@ -2615,7 +2796,7 @@ async function upgradeConfig(configPath) {
2615
2796
  newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
2616
2797
  }
2617
2798
  }
2618
- await fs4.writeFile(
2799
+ await fs5.writeFile(
2619
2800
  configPath,
2620
2801
  JSON.stringify(upgradedConfig, null, 2) + "\n",
2621
2802
  "utf-8"
@@ -2634,21 +2815,21 @@ async function upgradeConfig(configPath) {
2634
2815
  // src/cli/status.ts
2635
2816
  init_service();
2636
2817
  import chalk3 from "chalk";
2637
- import fs8 from "fs/promises";
2638
- import path8 from "path";
2818
+ import fs9 from "fs/promises";
2819
+ import path9 from "path";
2639
2820
  import os from "os";
2640
2821
  import crypto from "crypto";
2641
2822
 
2642
2823
  // src/git/utils.ts
2643
2824
  import { exec } from "child_process";
2644
2825
  import { promisify } from "util";
2645
- import fs6 from "fs/promises";
2646
- import path6 from "path";
2826
+ import fs7 from "fs/promises";
2827
+ import path7 from "path";
2647
2828
  var execAsync = promisify(exec);
2648
2829
  async function isGitRepo(rootDir) {
2649
2830
  try {
2650
- const gitDir = path6.join(rootDir, ".git");
2651
- await fs6.access(gitDir);
2831
+ const gitDir = path7.join(rootDir, ".git");
2832
+ await fs7.access(gitDir);
2652
2833
  return true;
2653
2834
  } catch {
2654
2835
  return false;
@@ -2687,7 +2868,7 @@ async function getChangedFiles(rootDir, fromRef, toRef) {
2687
2868
  // 10 second timeout for diffs
2688
2869
  }
2689
2870
  );
2690
- const files = stdout.trim().split("\n").filter(Boolean).map((file) => path6.join(rootDir, file));
2871
+ const files = stdout.trim().split("\n").filter(Boolean).map((file) => path7.join(rootDir, file));
2691
2872
  return files;
2692
2873
  } catch (error) {
2693
2874
  throw new Error(`Failed to get changed files: ${error}`);
@@ -2702,7 +2883,7 @@ async function getChangedFilesBetweenCommits(rootDir, fromCommit, toCommit) {
2702
2883
  timeout: 1e4
2703
2884
  }
2704
2885
  );
2705
- const files = stdout.trim().split("\n").filter(Boolean).map((file) => path6.join(rootDir, file));
2886
+ const files = stdout.trim().split("\n").filter(Boolean).map((file) => path7.join(rootDir, file));
2706
2887
  return files;
2707
2888
  } catch (error) {
2708
2889
  throw new Error(`Failed to get changed files between commits: ${error}`);
@@ -2723,9 +2904,9 @@ init_banner();
2723
2904
  init_schema();
2724
2905
  async function statusCommand() {
2725
2906
  const rootDir = process.cwd();
2726
- const projectName = path8.basename(rootDir);
2907
+ const projectName = path9.basename(rootDir);
2727
2908
  const pathHash = crypto.createHash("md5").update(rootDir).digest("hex").substring(0, 8);
2728
- const indexPath = path8.join(os.homedir(), ".lien", "indices", `${projectName}-${pathHash}`);
2909
+ const indexPath = path9.join(os.homedir(), ".lien", "indices", `${projectName}-${pathHash}`);
2729
2910
  showCompactBanner();
2730
2911
  console.log(chalk3.bold("Status\n"));
2731
2912
  const hasConfig = await configService.exists(rootDir);
@@ -2735,11 +2916,11 @@ async function statusCommand() {
2735
2916
  return;
2736
2917
  }
2737
2918
  try {
2738
- const stats = await fs8.stat(indexPath);
2919
+ const stats = await fs9.stat(indexPath);
2739
2920
  console.log(chalk3.dim("Index location:"), indexPath);
2740
2921
  console.log(chalk3.dim("Index status:"), chalk3.green("\u2713 Exists"));
2741
2922
  try {
2742
- const files = await fs8.readdir(indexPath, { recursive: true });
2923
+ const files = await fs9.readdir(indexPath, { recursive: true });
2743
2924
  console.log(chalk3.dim("Index files:"), files.length);
2744
2925
  } catch (e) {
2745
2926
  }
@@ -2768,9 +2949,9 @@ async function statusCommand() {
2768
2949
  const commit = await getCurrentCommit(rootDir);
2769
2950
  console.log(chalk3.dim(" Current branch:"), branch);
2770
2951
  console.log(chalk3.dim(" Current commit:"), commit.substring(0, 8));
2771
- const gitStateFile = path8.join(indexPath, ".git-state.json");
2952
+ const gitStateFile = path9.join(indexPath, ".git-state.json");
2772
2953
  try {
2773
- const gitStateContent = await fs8.readFile(gitStateFile, "utf-8");
2954
+ const gitStateContent = await fs9.readFile(gitStateFile, "utf-8");
2774
2955
  const gitState = JSON.parse(gitStateContent);
2775
2956
  if (gitState.branch !== branch || gitState.commit !== commit) {
2776
2957
  console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
@@ -2825,6 +3006,8 @@ async function indexCommand(options) {
2825
3006
 
2826
3007
  // src/cli/serve.ts
2827
3008
  import chalk6 from "chalk";
3009
+ import fs14 from "fs/promises";
3010
+ import path13 from "path";
2828
3011
 
2829
3012
  // src/mcp/server.ts
2830
3013
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -2920,15 +3103,15 @@ init_lancedb();
2920
3103
  init_local();
2921
3104
 
2922
3105
  // src/git/tracker.ts
2923
- import fs11 from "fs/promises";
2924
- import path11 from "path";
3106
+ import fs12 from "fs/promises";
3107
+ import path12 from "path";
2925
3108
  var GitStateTracker = class {
2926
3109
  stateFile;
2927
3110
  rootDir;
2928
3111
  currentState = null;
2929
3112
  constructor(rootDir, indexPath) {
2930
3113
  this.rootDir = rootDir;
2931
- this.stateFile = path11.join(indexPath, ".git-state.json");
3114
+ this.stateFile = path12.join(indexPath, ".git-state.json");
2932
3115
  }
2933
3116
  /**
2934
3117
  * Loads the last known git state from disk.
@@ -2936,7 +3119,7 @@ var GitStateTracker = class {
2936
3119
  */
2937
3120
  async loadState() {
2938
3121
  try {
2939
- const content = await fs11.readFile(this.stateFile, "utf-8");
3122
+ const content = await fs12.readFile(this.stateFile, "utf-8");
2940
3123
  return JSON.parse(content);
2941
3124
  } catch {
2942
3125
  return null;
@@ -2948,7 +3131,7 @@ var GitStateTracker = class {
2948
3131
  async saveState(state) {
2949
3132
  try {
2950
3133
  const content = JSON.stringify(state, null, 2);
2951
- await fs11.writeFile(this.stateFile, content, "utf-8");
3134
+ await fs12.writeFile(this.stateFile, content, "utf-8");
2952
3135
  } catch (error) {
2953
3136
  console.error(`[Lien] Warning: Failed to save git state: ${error}`);
2954
3137
  }
@@ -3099,12 +3282,12 @@ var GitStateTracker = class {
3099
3282
  // src/indexer/incremental.ts
3100
3283
  init_chunker();
3101
3284
  init_schema();
3102
- import fs12 from "fs/promises";
3285
+ import fs13 from "fs/promises";
3103
3286
  async function indexSingleFile(filepath, vectorDB, embeddings, config, options = {}) {
3104
3287
  const { verbose } = options;
3105
3288
  try {
3106
3289
  try {
3107
- await fs12.access(filepath);
3290
+ await fs13.access(filepath);
3108
3291
  } catch {
3109
3292
  if (verbose) {
3110
3293
  console.error(`[Lien] File deleted: ${filepath}`);
@@ -3112,7 +3295,7 @@ async function indexSingleFile(filepath, vectorDB, embeddings, config, options =
3112
3295
  await vectorDB.deleteByFile(filepath);
3113
3296
  return;
3114
3297
  }
3115
- const content = await fs12.readFile(filepath, "utf-8");
3298
+ const content = await fs13.readFile(filepath, "utf-8");
3116
3299
  const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
3117
3300
  const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
3118
3301
  const chunks = chunkFile(filepath, content, {
@@ -3353,14 +3536,6 @@ async function startMCPServer(options) {
3353
3536
  const versionCheckInterval = setInterval(async () => {
3354
3537
  await checkAndReconnect();
3355
3538
  }, 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
3539
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
3365
3540
  const { name, arguments: args } = request.params;
3366
3541
  try {
@@ -3619,6 +3794,7 @@ async function startMCPServer(options) {
3619
3794
  }
3620
3795
  const cleanup = async () => {
3621
3796
  log("Shutting down MCP server...");
3797
+ clearInterval(versionCheckInterval);
3622
3798
  if (gitPollInterval) {
3623
3799
  clearInterval(gitPollInterval);
3624
3800
  }
@@ -3630,6 +3806,13 @@ async function startMCPServer(options) {
3630
3806
  process.on("SIGINT", cleanup);
3631
3807
  process.on("SIGTERM", cleanup);
3632
3808
  const transport = new StdioServerTransport();
3809
+ transport.onclose = () => {
3810
+ log("Transport closed, parent process likely terminated");
3811
+ cleanup().catch(() => process.exit(0));
3812
+ };
3813
+ transport.onerror = (error) => {
3814
+ log(`Transport error: ${error}`);
3815
+ };
3633
3816
  await server.connect(transport);
3634
3817
  log("MCP server started and listening on stdio");
3635
3818
  }
@@ -3637,10 +3820,33 @@ async function startMCPServer(options) {
3637
3820
  // src/cli/serve.ts
3638
3821
  init_banner();
3639
3822
  async function serveCommand(options) {
3640
- const rootDir = process.cwd();
3823
+ const rootDir = options.root ? path13.resolve(options.root) : process.cwd();
3641
3824
  try {
3825
+ if (options.root) {
3826
+ try {
3827
+ const stats = await fs14.stat(rootDir);
3828
+ if (!stats.isDirectory()) {
3829
+ console.error(chalk6.red(`Error: --root path is not a directory: ${rootDir}`));
3830
+ process.exit(1);
3831
+ }
3832
+ } catch (error) {
3833
+ if (error.code === "ENOENT") {
3834
+ console.error(chalk6.red(`Error: --root directory does not exist: ${rootDir}`));
3835
+ } else if (error.code === "EACCES") {
3836
+ console.error(chalk6.red(`Error: --root directory is not accessible: ${rootDir}`));
3837
+ } else {
3838
+ console.error(chalk6.red(`Error: Failed to access --root directory: ${rootDir}`));
3839
+ console.error(chalk6.dim(error.message));
3840
+ }
3841
+ process.exit(1);
3842
+ }
3843
+ }
3642
3844
  showBanner();
3643
3845
  console.error(chalk6.bold("Starting MCP server...\n"));
3846
+ if (options.root) {
3847
+ console.error(chalk6.dim(`Serving from: ${rootDir}
3848
+ `));
3849
+ }
3644
3850
  await startMCPServer({
3645
3851
  rootDir,
3646
3852
  verbose: true,
@@ -3666,7 +3872,7 @@ var program = new Command();
3666
3872
  program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);
3667
3873
  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
3874
  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);
3875
+ 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").option("-r, --root <path>", "Root directory to serve (defaults to current directory)").action(serveCommand);
3670
3876
  program.command("status").description("Show indexing status and statistics").action(statusCommand);
3671
3877
  program.command("reindex").description("Clear index and re-index the entire codebase").option("-v, --verbose", "Show detailed logging during indexing").action(async (options) => {
3672
3878
  const { showCompactBanner: showCompactBanner2 } = await Promise.resolve().then(() => (init_banner(), banner_exports));