@liendev/lien 0.37.0 → 0.38.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/README.md CHANGED
@@ -109,7 +109,7 @@ Lien tracks code complexity with intuitive outputs:
109
109
 
110
110
  TypeScript • JavaScript • Vue • Python • PHP • Liquid • Go • Rust • Java • C/C++ • Ruby • Swift • Kotlin • C# • Scala • Markdown
111
111
 
112
- **Ecosystem Presets:** Node.js, Laravel, Python, Rust (auto-detected)
112
+ **Ecosystem Presets:** 12 ecosystem presets including Node.js, Python, PHP, Laravel, Ruby, Rails, Rust, JVM, Swift, .NET, Django, and Astro (auto-detected)
113
113
 
114
114
  ## Contributing
115
115
 
package/dist/index.js CHANGED
@@ -58,6 +58,7 @@ function wrapInBox(text, footer, padding = 1) {
58
58
  return [top, ...paddedLines, separator, paddedFooter, bottom].join("\n");
59
59
  }
60
60
  function showBanner() {
61
+ if (!process.stderr.isTTY) return;
61
62
  const banner = figlet.textSync("LIEN", {
62
63
  font: "ANSI Shadow",
63
64
  horizontalLayout: "fitted",
@@ -69,6 +70,7 @@ function showBanner() {
69
70
  console.error();
70
71
  }
71
72
  function showCompactBanner() {
73
+ if (!process.stdout.isTTY) return;
72
74
  const banner = figlet.textSync("LIEN", {
73
75
  font: "ANSI Shadow",
74
76
  horizontalLayout: "fitted",
@@ -3656,9 +3658,9 @@ var require_dist = __commonJS({
3656
3658
  });
3657
3659
 
3658
3660
  // src/cli/index.ts
3659
- import { Command } from "commander";
3660
- import { createRequire as createRequire3 } from "module";
3661
- import { fileURLToPath as fileURLToPath3 } from "url";
3661
+ import { Command, Option } from "commander";
3662
+ import { createRequire as createRequire4 } from "module";
3663
+ import { fileURLToPath as fileURLToPath4 } from "url";
3662
3664
  import { dirname as dirname3, join as join3 } from "path";
3663
3665
 
3664
3666
  // src/cli/init.ts
@@ -3715,6 +3717,8 @@ import chalk3 from "chalk";
3715
3717
  import fs2 from "fs/promises";
3716
3718
  import path2 from "path";
3717
3719
  import os from "os";
3720
+ import { createRequire as createRequire2 } from "module";
3721
+ import { fileURLToPath as fileURLToPath2 } from "url";
3718
3722
  import {
3719
3723
  isGitRepo,
3720
3724
  getCurrentBranch,
@@ -3727,75 +3731,179 @@ import {
3727
3731
  DEFAULT_CHUNK_OVERLAP,
3728
3732
  DEFAULT_GIT_POLL_INTERVAL_MS
3729
3733
  } from "@liendev/core";
3730
- async function statusCommand() {
3731
- const rootDir = process.cwd();
3732
- const repoId = extractRepoId(rootDir);
3733
- const indexPath = path2.join(os.homedir(), ".lien", "indices", repoId);
3734
- showCompactBanner();
3735
- console.log(chalk3.bold("Status\n"));
3736
- console.log(
3737
- chalk3.dim("Configuration:"),
3738
- chalk3.green("\u2713 Using defaults (no per-project config needed)")
3739
- );
3734
+ var VALID_FORMATS = ["text", "json"];
3735
+ async function getFileStats(filePath) {
3740
3736
  try {
3741
- const stats = await fs2.stat(indexPath);
3742
- console.log(chalk3.dim("Index location:"), indexPath);
3743
- console.log(chalk3.dim("Index status:"), chalk3.green("\u2713 Exists"));
3744
- try {
3745
- const files = await fs2.readdir(indexPath, { recursive: true });
3746
- console.log(chalk3.dim("Index files:"), files.length);
3747
- } catch {
3748
- }
3749
- console.log(chalk3.dim("Last modified:"), stats.mtime.toLocaleString());
3750
- try {
3751
- const version = await readVersionFile(indexPath);
3752
- if (version > 0) {
3753
- const versionDate = new Date(version);
3754
- console.log(chalk3.dim("Last reindex:"), versionDate.toLocaleString());
3755
- }
3756
- } catch {
3757
- }
3737
+ return await fs2.stat(filePath);
3738
+ } catch {
3739
+ return null;
3740
+ }
3741
+ }
3742
+ async function getFileCount(dirPath) {
3743
+ try {
3744
+ const files = await fs2.readdir(dirPath, { recursive: true });
3745
+ return files.length;
3746
+ } catch {
3747
+ return null;
3748
+ }
3749
+ }
3750
+ async function getLastReindex(indexPath) {
3751
+ try {
3752
+ const version = await readVersionFile(indexPath);
3753
+ return version > 0 ? version : null;
3754
+ } catch {
3755
+ return null;
3756
+ }
3757
+ }
3758
+ function getPackageVersion() {
3759
+ const __filename4 = fileURLToPath2(import.meta.url);
3760
+ const __dirname4 = path2.dirname(__filename4);
3761
+ const require5 = createRequire2(import.meta.url);
3762
+ try {
3763
+ return require5(path2.join(__dirname4, "../package.json")).version;
3764
+ } catch {
3765
+ return require5(path2.join(__dirname4, "../../package.json")).version;
3766
+ }
3767
+ }
3768
+ async function getGitState(rootDir) {
3769
+ try {
3770
+ const branch = await getCurrentBranch(rootDir);
3771
+ const commit = await getCurrentCommit(rootDir);
3772
+ return { branch, commit };
3758
3773
  } catch {
3774
+ return null;
3775
+ }
3776
+ }
3777
+ async function getStoredGitState(indexPath) {
3778
+ try {
3779
+ const content = await fs2.readFile(path2.join(indexPath, ".git-state.json"), "utf-8");
3780
+ return JSON.parse(content);
3781
+ } catch {
3782
+ return null;
3783
+ }
3784
+ }
3785
+ async function printIndexStatus(indexPath) {
3786
+ const stats = await getFileStats(indexPath);
3787
+ if (!stats) {
3759
3788
  console.log(chalk3.dim("Index status:"), chalk3.yellow("\u2717 Not indexed"));
3760
3789
  console.log(
3761
3790
  chalk3.yellow("\nRun"),
3762
3791
  chalk3.bold("lien index"),
3763
3792
  chalk3.yellow("to index your codebase")
3764
3793
  );
3794
+ return;
3765
3795
  }
3766
- console.log(chalk3.bold("\nFeatures:"));
3796
+ console.log(chalk3.dim("Index location:"), indexPath);
3797
+ console.log(chalk3.dim("Index status:"), chalk3.green("\u2713 Exists"));
3798
+ const fileCount = await getFileCount(indexPath);
3799
+ if (fileCount !== null) {
3800
+ console.log(chalk3.dim("Index files:"), fileCount);
3801
+ }
3802
+ console.log(chalk3.dim("Last modified:"), stats.mtime.toLocaleString());
3803
+ const reindexTs = await getLastReindex(indexPath);
3804
+ if (reindexTs !== null) {
3805
+ console.log(chalk3.dim("Last reindex:"), new Date(reindexTs).toLocaleString());
3806
+ }
3807
+ }
3808
+ async function printGitStatus(rootDir, indexPath) {
3767
3809
  const isRepo = await isGitRepo(rootDir);
3768
- if (isRepo) {
3769
- console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
3770
- console.log(chalk3.dim(" Poll interval:"), `${DEFAULT_GIT_POLL_INTERVAL_MS / 1e3}s`);
3771
- try {
3772
- const branch = await getCurrentBranch(rootDir);
3773
- const commit = await getCurrentCommit(rootDir);
3774
- console.log(chalk3.dim(" Current branch:"), branch);
3775
- console.log(chalk3.dim(" Current commit:"), commit.substring(0, 8));
3776
- const gitStateFile = path2.join(indexPath, ".git-state.json");
3777
- try {
3778
- const gitStateContent = await fs2.readFile(gitStateFile, "utf-8");
3779
- const gitState = JSON.parse(gitStateContent);
3780
- if (gitState.branch !== branch || gitState.commit !== commit) {
3781
- console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
3782
- }
3783
- } catch {
3784
- }
3785
- } catch {
3786
- }
3787
- } else {
3810
+ if (!isRepo) {
3788
3811
  console.log(chalk3.dim("Git detection:"), chalk3.yellow("Not a git repo"));
3812
+ return;
3813
+ }
3814
+ console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
3815
+ console.log(chalk3.dim(" Poll interval:"), `${DEFAULT_GIT_POLL_INTERVAL_MS / 1e3}s`);
3816
+ const gitState = await getGitState(rootDir);
3817
+ if (!gitState) return;
3818
+ console.log(chalk3.dim(" Current branch:"), gitState.branch);
3819
+ console.log(chalk3.dim(" Current commit:"), gitState.commit.substring(0, 8));
3820
+ const storedGit = await getStoredGitState(indexPath);
3821
+ if (storedGit && (storedGit.branch !== gitState.branch || storedGit.commit !== gitState.commit)) {
3822
+ console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
3789
3823
  }
3824
+ }
3825
+ function printWatchStatus() {
3790
3826
  console.log(chalk3.dim("File watching:"), chalk3.green("\u2713 Enabled (default)"));
3791
3827
  console.log(chalk3.dim(" Batch window:"), "500ms (collects rapid changes, force-flush after 5s)");
3792
3828
  console.log(chalk3.dim(" Disable with:"), chalk3.bold("lien serve --no-watch"));
3829
+ }
3830
+ function printIndexingSettings() {
3793
3831
  console.log(chalk3.bold("\nIndexing Settings (defaults):"));
3794
3832
  console.log(chalk3.dim("Concurrency:"), DEFAULT_CONCURRENCY);
3795
3833
  console.log(chalk3.dim("Batch size:"), DEFAULT_EMBEDDING_BATCH_SIZE);
3796
3834
  console.log(chalk3.dim("Chunk size:"), DEFAULT_CHUNK_SIZE);
3797
3835
  console.log(chalk3.dim("Chunk overlap:"), DEFAULT_CHUNK_OVERLAP);
3798
3836
  }
3837
+ async function statusCommand(options = {}) {
3838
+ const format = options.format || "text";
3839
+ if (!VALID_FORMATS.includes(format)) {
3840
+ console.error(
3841
+ chalk3.red(`Error: Invalid --format value "${format}". Must be one of: text, json`)
3842
+ );
3843
+ process.exit(1);
3844
+ }
3845
+ const rootDir = process.cwd();
3846
+ const repoId = extractRepoId(rootDir);
3847
+ const indexPath = path2.join(os.homedir(), ".lien", "indices", repoId);
3848
+ if (format === "json") {
3849
+ await outputJson(rootDir, indexPath);
3850
+ return;
3851
+ }
3852
+ showCompactBanner();
3853
+ console.log(chalk3.bold("Status\n"));
3854
+ console.log(
3855
+ chalk3.dim("Configuration:"),
3856
+ chalk3.green("\u2713 Using defaults (no per-project config needed)")
3857
+ );
3858
+ await printIndexStatus(indexPath);
3859
+ console.log(chalk3.bold("\nFeatures:"));
3860
+ await printGitStatus(rootDir, indexPath);
3861
+ printWatchStatus();
3862
+ if (options.verbose) {
3863
+ printIndexingSettings();
3864
+ }
3865
+ }
3866
+ async function outputJson(rootDir, indexPath) {
3867
+ const data = {
3868
+ version: getPackageVersion(),
3869
+ indexPath,
3870
+ indexStatus: "not_indexed",
3871
+ indexFiles: 0,
3872
+ lastModified: null,
3873
+ lastReindex: null,
3874
+ git: { enabled: false, branch: null, commit: null },
3875
+ features: { fileWatching: true, gitDetection: true },
3876
+ settings: {
3877
+ concurrency: DEFAULT_CONCURRENCY,
3878
+ batchSize: DEFAULT_EMBEDDING_BATCH_SIZE,
3879
+ chunkSize: DEFAULT_CHUNK_SIZE,
3880
+ chunkOverlap: DEFAULT_CHUNK_OVERLAP
3881
+ }
3882
+ };
3883
+ const stats = await getFileStats(indexPath);
3884
+ if (stats) {
3885
+ data.indexStatus = "exists";
3886
+ data.lastModified = stats.mtime.toISOString();
3887
+ const fileCount = await getFileCount(indexPath);
3888
+ if (fileCount !== null) {
3889
+ data.indexFiles = fileCount;
3890
+ }
3891
+ const reindexTs = await getLastReindex(indexPath);
3892
+ if (reindexTs !== null) {
3893
+ data.lastReindex = new Date(reindexTs).toISOString();
3894
+ }
3895
+ }
3896
+ const isRepo = await isGitRepo(rootDir);
3897
+ if (isRepo) {
3898
+ const gitState = await getGitState(rootDir);
3899
+ data.git = {
3900
+ enabled: true,
3901
+ branch: gitState?.branch ?? null,
3902
+ commit: gitState ? gitState.commit.substring(0, 8) : null
3903
+ };
3904
+ }
3905
+ console.log(JSON.stringify(data, null, 2));
3906
+ }
3799
3907
 
3800
3908
  // src/cli/index-cmd.ts
3801
3909
  init_banner();
@@ -4022,13 +4130,13 @@ async function indexCommand(options) {
4022
4130
  // src/cli/serve.ts
4023
4131
  import chalk6 from "chalk";
4024
4132
  import fs5 from "fs/promises";
4025
- import path4 from "path";
4133
+ import path7 from "path";
4026
4134
 
4027
4135
  // src/mcp/server.ts
4028
4136
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4029
4137
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4030
- import { createRequire as createRequire2 } from "module";
4031
- import { fileURLToPath as fileURLToPath2 } from "url";
4138
+ import { createRequire as createRequire3 } from "module";
4139
+ import { fileURLToPath as fileURLToPath3 } from "url";
4032
4140
  import { dirname as dirname2, join as join2 } from "path";
4033
4141
  import { WorkerEmbeddings, VERSION_CHECK_INTERVAL_MS, createVectorDB } from "@liendev/core";
4034
4142
 
@@ -4930,8 +5038,8 @@ function getErrorMap() {
4930
5038
 
4931
5039
  // ../../node_modules/zod/v3/helpers/parseUtil.js
4932
5040
  var makeIssue = (params) => {
4933
- const { data, path: path7, errorMaps, issueData } = params;
4934
- const fullPath = [...path7, ...issueData.path || []];
5041
+ const { data, path: path10, errorMaps, issueData } = params;
5042
+ const fullPath = [...path10, ...issueData.path || []];
4935
5043
  const fullIssue = {
4936
5044
  ...issueData,
4937
5045
  path: fullPath
@@ -5047,11 +5155,11 @@ var errorUtil;
5047
5155
 
5048
5156
  // ../../node_modules/zod/v3/types.js
5049
5157
  var ParseInputLazyPath = class {
5050
- constructor(parent, value, path7, key) {
5158
+ constructor(parent, value, path10, key) {
5051
5159
  this._cachedPath = [];
5052
5160
  this.parent = parent;
5053
5161
  this.data = value;
5054
- this._path = path7;
5162
+ this._path = path10;
5055
5163
  this._key = key;
5056
5164
  }
5057
5165
  get path() {
@@ -8524,10 +8632,15 @@ var FindSimilarSchema = external_exports.object({
8524
8632
  });
8525
8633
 
8526
8634
  // src/mcp/schemas/file.schema.ts
8635
+ import path4 from "path";
8636
+ var safeFilepath = external_exports.string().min(1, "Filepath cannot be empty").max(1e3).refine((p) => {
8637
+ const normalized = p.replace(/\\/g, "/");
8638
+ return !path4.isAbsolute(normalized) && !normalized.split("/").includes("..");
8639
+ }, 'Path must be relative and cannot contain ".." traversal');
8527
8640
  var GetFilesContextSchema = external_exports.object({
8528
8641
  filepaths: external_exports.union([
8529
- external_exports.string().min(1, "Filepath cannot be empty").max(1e3),
8530
- external_exports.array(external_exports.string().min(1, "Filepath cannot be empty").max(1e3)).min(1, "Array must contain at least one filepath").max(50, "Maximum 50 files per request")
8642
+ safeFilepath,
8643
+ external_exports.array(safeFilepath).min(1, "Array must contain at least one filepath").max(50, "Maximum 50 files per request")
8531
8644
  ]).describe(
8532
8645
  "Single filepath or array of filepaths (relative to workspace root).\n\nSingle file: 'src/components/Button.tsx'\nMultiple files: ['src/auth.ts', 'src/user.ts']\n\nMaximum 50 files per request for batch operations."
8533
8646
  ),
@@ -8554,8 +8667,12 @@ var ListFunctionsSchema = external_exports.object({
8554
8667
  });
8555
8668
 
8556
8669
  // src/mcp/schemas/dependents.schema.ts
8670
+ import path5 from "path";
8557
8671
  var GetDependentsSchema = external_exports.object({
8558
- filepath: external_exports.string().min(1, "Filepath cannot be empty").max(1e3).describe(
8672
+ filepath: external_exports.string().min(1, "Filepath cannot be empty").max(1e3).refine((p) => {
8673
+ const normalized = p.replace(/\\/g, "/");
8674
+ return !path5.isAbsolute(normalized) && !normalized.split("/").includes("..");
8675
+ }, 'Path must be relative and cannot contain ".." traversal').describe(
8559
8676
  "Path to file to find dependents for (relative to workspace root).\n\nExample: 'src/utils/validate.ts'\n\nReturns all files that import or depend on this file.\n\nNote: Scans up to 10,000 code chunks. For very large codebases,\nresults may be incomplete (a warning will be included if truncated)."
8560
8677
  ),
8561
8678
  symbol: external_exports.string().min(1, "Symbol cannot be an empty string").max(500).optional().describe(
@@ -8570,8 +8687,14 @@ var GetDependentsSchema = external_exports.object({
8570
8687
  });
8571
8688
 
8572
8689
  // src/mcp/schemas/complexity.schema.ts
8690
+ import path6 from "path";
8573
8691
  var GetComplexitySchema = external_exports.object({
8574
- files: external_exports.array(external_exports.string().min(1, "Filepath cannot be empty").max(1e3)).optional().describe(
8692
+ files: external_exports.array(
8693
+ external_exports.string().min(1, "Filepath cannot be empty").max(1e3).refine((p) => {
8694
+ const normalized = p.replace(/\\/g, "/");
8695
+ return !path6.isAbsolute(normalized) && !normalized.split("/").includes("..");
8696
+ }, 'Path must be relative and cannot contain ".." traversal')
8697
+ ).optional().describe(
8575
8698
  "Specific files to analyze. If omitted, analyzes entire codebase.\n\nExample: ['src/auth.ts', 'src/api/user.ts']"
8576
8699
  ),
8577
8700
  top: external_exports.number().int().min(1, "Top must be at least 1").max(50, "Top cannot exceed 50").default(10).describe(
@@ -9091,7 +9214,7 @@ async function handleSemanticSearch(args, ctx) {
9091
9214
  const shaped = shapeResults(results, "semantic_search");
9092
9215
  if (shaped.length === 0) {
9093
9216
  notes.push(
9094
- '0 results. Try rephrasing as a full question (e.g. "How does X work?"), or use grep for exact string matches. If the codebase was recently updated, run "lien reindex".'
9217
+ '0 results. Try rephrasing as a full question (e.g. "How does X work?"), or use grep for exact string matches. If the codebase was recently updated, run "lien index".'
9095
9218
  );
9096
9219
  }
9097
9220
  return {
@@ -9209,10 +9332,10 @@ async function findRelatedChunks(filepaths, fileChunksMap, ctx) {
9209
9332
  }
9210
9333
  function createPathCache(workspaceRoot) {
9211
9334
  const cache = /* @__PURE__ */ new Map();
9212
- const normalize = (path7) => {
9213
- if (cache.has(path7)) return cache.get(path7);
9214
- const normalized = normalizePath(path7, workspaceRoot);
9215
- cache.set(path7, normalized);
9335
+ const normalize = (path10) => {
9336
+ if (cache.has(path10)) return cache.get(path10);
9337
+ const normalized = normalizePath(path10, workspaceRoot);
9338
+ cache.set(path10, normalized);
9216
9339
  return normalized;
9217
9340
  };
9218
9341
  return { normalize, cache };
@@ -9400,7 +9523,7 @@ async function handleListFunctions(args, ctx) {
9400
9523
  );
9401
9524
  }
9402
9525
  if (queryResult.method === "content") {
9403
- notes.push('Using content search. Run "lien reindex" to enable faster symbol-based queries.');
9526
+ notes.push('Using content search. Run "lien index" to enable faster symbol-based queries.');
9404
9527
  }
9405
9528
  return {
9406
9529
  indexInfo: getIndexMetadata(),
@@ -9587,11 +9710,11 @@ async function scanChunksPaginated(vectorDB, crossRepo, log, normalizePathCached
9587
9710
  function createPathNormalizer() {
9588
9711
  const workspaceRoot = process.cwd().replace(/\\/g, "/");
9589
9712
  const cache = /* @__PURE__ */ new Map();
9590
- return (path7) => {
9591
- if (!cache.has(path7)) {
9592
- cache.set(path7, normalizePath2(path7, workspaceRoot));
9713
+ return (path10) => {
9714
+ if (!cache.has(path10)) {
9715
+ cache.set(path10, normalizePath2(path10, workspaceRoot));
9593
9716
  }
9594
- return cache.get(path7);
9717
+ return cache.get(path10);
9595
9718
  };
9596
9719
  }
9597
9720
  function groupChunksByFile(chunks) {
@@ -10351,9 +10474,8 @@ import {
10351
10474
  normalizeToRelativePath as normalizeToRelativePath2,
10352
10475
  createGitignoreFilter
10353
10476
  } from "@liendev/core";
10354
- async function handleFileDeletion(filepath, vectorDB, log) {
10477
+ async function handleFileDeletion(filepath, vectorDB, manifest, log) {
10355
10478
  log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
10356
- const manifest = new ManifestManager(vectorDB.dbPath);
10357
10479
  try {
10358
10480
  await vectorDB.deleteByFile(filepath);
10359
10481
  await manifest.removeFile(filepath);
@@ -10363,8 +10485,7 @@ async function handleFileDeletion(filepath, vectorDB, log) {
10363
10485
  throw error;
10364
10486
  }
10365
10487
  }
10366
- async function handleBatchDeletions(deletedFiles, vectorDB, log) {
10367
- const manifest = new ManifestManager(vectorDB.dbPath);
10488
+ async function handleBatchDeletions(deletedFiles, vectorDB, manifest, log) {
10368
10489
  const failures = [];
10369
10490
  for (const filepath of deletedFiles) {
10370
10491
  log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
@@ -10381,8 +10502,7 @@ async function handleBatchDeletions(deletedFiles, vectorDB, log) {
10381
10502
  throw new Error(`Failed to delete ${failures.length} file(s): ${failures.join(", ")}`);
10382
10503
  }
10383
10504
  }
10384
- async function canSkipReindex(filepath, rootDir, vectorDB, log) {
10385
- const manifest = new ManifestManager(vectorDB.dbPath);
10505
+ async function canSkipReindex(filepath, rootDir, manifest, log) {
10386
10506
  const normalizedPath = normalizeToRelativePath2(filepath, rootDir);
10387
10507
  const manifestData = await manifest.load();
10388
10508
  const existingEntry = manifestData?.files[normalizedPath];
@@ -10400,11 +10520,11 @@ async function canSkipReindex(filepath, rootDir, vectorDB, log) {
10400
10520
  }
10401
10521
  return false;
10402
10522
  }
10403
- async function handleSingleFileChange(filepath, type, rootDir, vectorDB, embeddings, log, reindexStateManager) {
10523
+ async function handleSingleFileChange(filepath, type, rootDir, vectorDB, embeddings, manifest, log, reindexStateManager) {
10404
10524
  const action = type === "add" ? "added" : "changed";
10405
10525
  if (type === "change") {
10406
10526
  try {
10407
- if (await canSkipReindex(filepath, rootDir, vectorDB, log)) return;
10527
+ if (await canSkipReindex(filepath, rootDir, manifest, log)) return;
10408
10528
  } catch (error) {
10409
10529
  log(`Content hash check failed, will reindex: ${error}`, "warning");
10410
10530
  }
@@ -10462,9 +10582,8 @@ async function updateUnchangedMtimes(manifest, results) {
10462
10582
  return null;
10463
10583
  });
10464
10584
  }
10465
- async function filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log) {
10585
+ async function filterModifiedFilesByHash(modifiedFiles, rootDir, manifest, log) {
10466
10586
  if (modifiedFiles.length === 0) return [];
10467
- const manifest = new ManifestManager(vectorDB.dbPath);
10468
10587
  const manifestData = await manifest.load();
10469
10588
  if (!manifestData) return modifiedFiles;
10470
10589
  const checkResults = await checkFilesAgainstManifest(
@@ -10476,13 +10595,13 @@ async function filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log)
10476
10595
  await updateUnchangedMtimes(manifest, checkResults);
10477
10596
  return checkResults.filter((r) => r.shouldReindex).map((r) => r.filepath);
10478
10597
  }
10479
- async function prepareFilesForReindexing(event, rootDir, vectorDB, log) {
10598
+ async function prepareFilesForReindexing(event, rootDir, manifest, log) {
10480
10599
  const addedFiles = event.added || [];
10481
10600
  const modifiedFiles = event.modified || [];
10482
10601
  const deletedFiles = event.deleted || [];
10483
10602
  let modifiedFilesToReindex = [];
10484
10603
  try {
10485
- modifiedFilesToReindex = await filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log);
10604
+ modifiedFilesToReindex = await filterModifiedFilesByHash(modifiedFiles, rootDir, manifest, log);
10486
10605
  } catch (error) {
10487
10606
  log(`Hash-based filtering failed, will reindex all modified files: ${error}`, "warning");
10488
10607
  modifiedFilesToReindex = modifiedFiles;
@@ -10490,24 +10609,20 @@ async function prepareFilesForReindexing(event, rootDir, vectorDB, log) {
10490
10609
  const filesToIndex = [...addedFiles, ...modifiedFilesToReindex];
10491
10610
  return { filesToIndex, deletedFiles };
10492
10611
  }
10493
- async function executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, log) {
10494
- const operations = [];
10612
+ async function executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, manifest, log) {
10495
10613
  if (filesToIndex.length > 0) {
10496
10614
  log(`\u{1F4C1} ${filesToIndex.length} file(s) changed, reindexing...`);
10497
- operations.push(
10498
- indexMultipleFiles(filesToIndex, vectorDB, embeddings, { verbose: false, rootDir })
10499
- );
10615
+ await indexMultipleFiles(filesToIndex, vectorDB, embeddings, { verbose: false, rootDir });
10500
10616
  }
10501
10617
  if (deletedFiles.length > 0) {
10502
- operations.push(handleBatchDeletions(deletedFiles, vectorDB, log));
10618
+ await handleBatchDeletions(deletedFiles, vectorDB, manifest, log);
10503
10619
  }
10504
- await Promise.all(operations);
10505
10620
  }
10506
- async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reindexStateManager) {
10621
+ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, manifest, log, reindexStateManager) {
10507
10622
  const { filesToIndex, deletedFiles } = await prepareFilesForReindexing(
10508
10623
  event,
10509
10624
  rootDir,
10510
- vectorDB,
10625
+ manifest,
10511
10626
  log
10512
10627
  );
10513
10628
  const allFiles = [...filesToIndex, ...deletedFiles];
@@ -10517,7 +10632,15 @@ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reind
10517
10632
  const startTime = Date.now();
10518
10633
  reindexStateManager.startReindex(allFiles);
10519
10634
  try {
10520
- await executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, log);
10635
+ await executeReindexOperations(
10636
+ filesToIndex,
10637
+ deletedFiles,
10638
+ rootDir,
10639
+ vectorDB,
10640
+ embeddings,
10641
+ manifest,
10642
+ log
10643
+ );
10521
10644
  const duration = Date.now() - startTime;
10522
10645
  reindexStateManager.completeReindex(duration);
10523
10646
  log(
@@ -10528,11 +10651,11 @@ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reind
10528
10651
  log(`Batch reindex failed: ${error}`, "warning");
10529
10652
  }
10530
10653
  }
10531
- async function handleUnlinkEvent(filepath, vectorDB, log, reindexStateManager) {
10654
+ async function handleUnlinkEvent(filepath, vectorDB, manifest, log, reindexStateManager) {
10532
10655
  const startTime = Date.now();
10533
10656
  reindexStateManager.startReindex([filepath]);
10534
10657
  try {
10535
- await handleFileDeletion(filepath, vectorDB, log);
10658
+ await handleFileDeletion(filepath, vectorDB, manifest, log);
10536
10659
  const duration = Date.now() - startTime;
10537
10660
  reindexStateManager.completeReindex(duration);
10538
10661
  } catch (error) {
@@ -10564,6 +10687,7 @@ function hasGitignoreChange(event) {
10564
10687
  }
10565
10688
  function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStateManager, checkAndReconnect) {
10566
10689
  let ignoreFilter = null;
10690
+ const manifest = new ManifestManager(vectorDB.dbPath);
10567
10691
  return async (event) => {
10568
10692
  if (hasGitignoreChange(event)) {
10569
10693
  ignoreFilter = null;
@@ -10577,10 +10701,18 @@ function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStat
10577
10701
  const totalToProcess = filtered.added.length + filtered.modified.length + filtered.deleted.length;
10578
10702
  if (totalToProcess === 0) return;
10579
10703
  await checkAndReconnect();
10580
- await handleBatchEvent(filtered, rootDir, vectorDB, embeddings, log, reindexStateManager);
10704
+ await handleBatchEvent(
10705
+ filtered,
10706
+ rootDir,
10707
+ vectorDB,
10708
+ embeddings,
10709
+ manifest,
10710
+ log,
10711
+ reindexStateManager
10712
+ );
10581
10713
  } else if (type === "unlink") {
10582
10714
  await checkAndReconnect();
10583
- await handleUnlinkEvent(event.filepath, vectorDB, log, reindexStateManager);
10715
+ await handleUnlinkEvent(event.filepath, vectorDB, manifest, log, reindexStateManager);
10584
10716
  } else {
10585
10717
  if (isFileIgnored(event.filepath, rootDir, ignoreFilter)) return;
10586
10718
  await checkAndReconnect();
@@ -10590,6 +10722,7 @@ function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStat
10590
10722
  rootDir,
10591
10723
  vectorDB,
10592
10724
  embeddings,
10725
+ manifest,
10593
10726
  log,
10594
10727
  reindexStateManager
10595
10728
  );
@@ -10852,9 +10985,9 @@ function setupCleanupHandlers(server, versionCheckInterval, gitPollInterval, fil
10852
10985
  }
10853
10986
 
10854
10987
  // src/mcp/server.ts
10855
- var __filename2 = fileURLToPath2(import.meta.url);
10988
+ var __filename2 = fileURLToPath3(import.meta.url);
10856
10989
  var __dirname2 = dirname2(__filename2);
10857
- var require3 = createRequire2(import.meta.url);
10990
+ var require3 = createRequire3(import.meta.url);
10858
10991
  var packageJson2;
10859
10992
  try {
10860
10993
  packageJson2 = require3(join2(__dirname2, "../package.json"));
@@ -11067,7 +11200,7 @@ async function startMCPServer(options) {
11067
11200
  // src/cli/serve.ts
11068
11201
  init_banner();
11069
11202
  async function serveCommand(options) {
11070
- const rootDir = options.root ? path4.resolve(options.root) : process.cwd();
11203
+ const rootDir = options.root ? path7.resolve(options.root) : process.cwd();
11071
11204
  try {
11072
11205
  if (options.root) {
11073
11206
  try {
@@ -11113,12 +11246,12 @@ async function serveCommand(options) {
11113
11246
  // src/cli/complexity.ts
11114
11247
  import chalk7 from "chalk";
11115
11248
  import fs6 from "fs";
11116
- import path5 from "path";
11249
+ import path8 from "path";
11117
11250
  import { VectorDB } from "@liendev/core";
11118
11251
  import { ComplexityAnalyzer as ComplexityAnalyzer2 } from "@liendev/core";
11119
11252
  import { formatReport } from "@liendev/core";
11120
11253
  var VALID_FAIL_ON = ["error", "warning"];
11121
- var VALID_FORMATS = ["text", "json", "sarif"];
11254
+ var VALID_FORMATS2 = ["text", "json", "sarif"];
11122
11255
  function validateFailOn(failOn) {
11123
11256
  if (failOn && !VALID_FAIL_ON.includes(failOn)) {
11124
11257
  console.error(
@@ -11128,7 +11261,7 @@ function validateFailOn(failOn) {
11128
11261
  }
11129
11262
  }
11130
11263
  function validateFormat(format) {
11131
- if (!VALID_FORMATS.includes(format)) {
11264
+ if (!VALID_FORMATS2.includes(format)) {
11132
11265
  console.error(
11133
11266
  chalk7.red(`Error: Invalid --format value "${format}". Must be one of: text, json, sarif`)
11134
11267
  );
@@ -11138,7 +11271,7 @@ function validateFormat(format) {
11138
11271
  function validateFilesExist(files, rootDir) {
11139
11272
  if (!files || files.length === 0) return;
11140
11273
  const missingFiles = files.filter((file) => {
11141
- const fullPath = path5.isAbsolute(file) ? file : path5.join(rootDir, file);
11274
+ const fullPath = path8.isAbsolute(file) ? file : path8.join(rootDir, file);
11142
11275
  return !fs6.existsSync(fullPath);
11143
11276
  });
11144
11277
  if (missingFiles.length > 0) {
@@ -11184,10 +11317,10 @@ async function complexityCommand(options) {
11184
11317
 
11185
11318
  // src/cli/config.ts
11186
11319
  import chalk8 from "chalk";
11187
- import path6 from "path";
11320
+ import path9 from "path";
11188
11321
  import os2 from "os";
11189
11322
  import { loadGlobalConfig, mergeGlobalConfig } from "@liendev/core";
11190
- var CONFIG_PATH = path6.join(os2.homedir(), ".lien", "config.json");
11323
+ var CONFIG_PATH = path9.join(os2.homedir(), ".lien", "config.json");
11191
11324
  var ALLOWED_KEYS = {
11192
11325
  backend: {
11193
11326
  values: ["lancedb", "qdrant"],
@@ -11274,9 +11407,9 @@ async function configListCommand() {
11274
11407
  }
11275
11408
 
11276
11409
  // src/cli/index.ts
11277
- var __filename3 = fileURLToPath3(import.meta.url);
11410
+ var __filename3 = fileURLToPath4(import.meta.url);
11278
11411
  var __dirname3 = dirname3(__filename3);
11279
- var require4 = createRequire3(import.meta.url);
11412
+ var require4 = createRequire4(import.meta.url);
11280
11413
  var packageJson3;
11281
11414
  try {
11282
11415
  packageJson3 = require4(join3(__dirname3, "../package.json"));
@@ -11289,13 +11422,18 @@ program.command("init").description("Initialize Lien in the current directory").
11289
11422
  program.command("index").description("Index the codebase for semantic search").option("-f, --force", "Force full reindex (skip incremental)").option("-v, --verbose", "Show detailed logging during indexing").action(indexCommand);
11290
11423
  program.command("serve").description(
11291
11424
  "Start the MCP server (works with Cursor, Claude Code, Windsurf, and any MCP client)"
11292
- ).option("-p, --port <port>", "Port number (for future use)", "7133").option("--no-watch", "Disable file watching for this session").option("-w, --watch", "[DEPRECATED] File watching is now enabled by default").option("-r, --root <path>", "Root directory to serve (defaults to current directory)").action(serveCommand);
11293
- program.command("status").description("Show indexing status and statistics").action(statusCommand);
11425
+ ).option("-p, --port <port>", "Port number (for future use)", "7133").option("--no-watch", "Disable file watching for this session").addOption(
11426
+ new Option("-w, --watch", "[DEPRECATED] File watching is now enabled by default").hideHelp()
11427
+ ).option("-r, --root <path>", "Root directory to serve (defaults to current directory)").action(serveCommand);
11428
+ program.command("status").description("Show indexing status and statistics").option("-v, --verbose", "Show detailed settings").option("--format <type>", "Output format: text, json", "text").action(statusCommand);
11294
11429
  program.command("complexity").description("Analyze code complexity").option("--files <paths...>", "Specific files to analyze").option("--format <type>", "Output format: text, json, sarif", "text").option("--fail-on <severity>", "Exit 1 if violations: error, warning").action(complexityCommand);
11295
11430
  var configCmd = program.command("config").description("Manage global configuration (~/.lien/config.json)");
11296
11431
  configCmd.command("set <key> <value>").description("Set a global config value").action(configSetCommand);
11297
11432
  configCmd.command("get <key>").description("Get a config value").action(configGetCommand);
11298
11433
  configCmd.command("list").description("Show all current config").action(configListCommand);
11434
+ program.action(() => {
11435
+ program.help();
11436
+ });
11299
11437
  program.addHelpText("beforeAll", `Quick start: run 'lien serve' in your project directory
11300
11438
  `);
11301
11439