@liendev/lien 0.38.0 → 0.39.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
@@ -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,50 +3658,154 @@ 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
3665
3667
  init_banner();
3666
3668
  import fs from "fs/promises";
3669
+ import os from "os";
3667
3670
  import path from "path";
3668
3671
  import chalk2 from "chalk";
3669
- var MCP_CONFIG = {
3670
- command: "lien",
3671
- args: ["serve"]
3672
+ var EDITORS = {
3673
+ cursor: {
3674
+ name: "Cursor",
3675
+ configPath: (rootDir) => path.join(rootDir, ".cursor", "mcp.json"),
3676
+ configKey: "mcpServers",
3677
+ buildEntry: () => ({ command: "lien", args: ["serve"] }),
3678
+ restartMessage: "Restart Cursor to activate."
3679
+ },
3680
+ "claude-code": {
3681
+ name: "Claude Code",
3682
+ configPath: (rootDir) => path.join(rootDir, ".mcp.json"),
3683
+ configKey: "mcpServers",
3684
+ buildEntry: () => ({ command: "lien", args: ["serve"] }),
3685
+ restartMessage: "Restart Claude Code to activate."
3686
+ },
3687
+ windsurf: {
3688
+ name: "Windsurf",
3689
+ configPath: () => path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
3690
+ configKey: "mcpServers",
3691
+ buildEntry: (rootDir) => ({
3692
+ command: "lien",
3693
+ args: ["serve", "--root", path.resolve(rootDir)]
3694
+ }),
3695
+ restartMessage: "Restart Windsurf to activate."
3696
+ },
3697
+ opencode: {
3698
+ name: "OpenCode",
3699
+ configPath: (rootDir) => path.join(rootDir, "opencode.json"),
3700
+ configKey: "mcp",
3701
+ buildEntry: () => ({ type: "local", command: ["lien", "serve"] }),
3702
+ restartMessage: "Restart OpenCode to activate."
3703
+ },
3704
+ "kilo-code": {
3705
+ name: "Kilo Code",
3706
+ configPath: (rootDir) => path.join(rootDir, ".kilocode", "mcp.json"),
3707
+ configKey: "mcpServers",
3708
+ buildEntry: () => ({ command: "lien", args: ["serve"] }),
3709
+ restartMessage: "Restart VS Code to activate."
3710
+ },
3711
+ antigravity: {
3712
+ name: "Antigravity",
3713
+ configPath: null,
3714
+ configKey: "mcpServers",
3715
+ buildEntry: () => ({ command: "lien", args: ["serve"] }),
3716
+ restartMessage: "Add this to your Antigravity MCP settings."
3717
+ }
3672
3718
  };
3673
- async function initCommand(options = {}) {
3674
- showCompactBanner();
3675
- const rootDir = options.path || process.cwd();
3676
- const cursorDir = path.join(rootDir, ".cursor");
3677
- const mcpConfigPath = path.join(cursorDir, "mcp.json");
3678
- let existingConfig = null;
3719
+ function isPlainObject(value) {
3720
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3721
+ }
3722
+ function displayPath(configPath, rootDir) {
3723
+ const rel = path.relative(rootDir, configPath);
3724
+ if (!rel.startsWith("..")) return rel;
3725
+ const home = os.homedir();
3726
+ if (configPath.startsWith(home)) return "~" + configPath.slice(home.length);
3727
+ return configPath;
3728
+ }
3729
+ async function readJsonFile(filePath) {
3679
3730
  try {
3680
- const raw = await fs.readFile(mcpConfigPath, "utf-8");
3731
+ const raw = await fs.readFile(filePath, "utf-8");
3681
3732
  const parsed = JSON.parse(raw);
3682
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3683
- existingConfig = parsed;
3684
- }
3733
+ return isPlainObject(parsed) ? parsed : null;
3685
3734
  } catch {
3735
+ return null;
3736
+ }
3737
+ }
3738
+ async function writeEditorConfig(editor, rootDir) {
3739
+ const configPath = editor.configPath(rootDir);
3740
+ const entry = editor.buildEntry(rootDir);
3741
+ const key = editor.configKey;
3742
+ const label = displayPath(configPath, rootDir);
3743
+ const existingConfig = await readJsonFile(configPath);
3744
+ const existingSection = existingConfig?.[key];
3745
+ if (existingSection?.lien) {
3746
+ console.log(chalk2.green(`
3747
+ \u2713 Already configured \u2014 ${label} contains lien entry`));
3748
+ return;
3686
3749
  }
3687
- if (existingConfig?.mcpServers?.lien) {
3688
- console.log(chalk2.green("\n\u2713 Already configured \u2014 .cursor/mcp.json contains lien entry"));
3689
- } else if (existingConfig) {
3690
- const servers = existingConfig.mcpServers;
3691
- const safeServers = servers && typeof servers === "object" && !Array.isArray(servers) ? servers : {};
3692
- safeServers.lien = MCP_CONFIG;
3693
- existingConfig.mcpServers = safeServers;
3694
- await fs.writeFile(mcpConfigPath, JSON.stringify(existingConfig, null, 2) + "\n");
3695
- console.log(chalk2.green("\n\u2713 Added lien to existing .cursor/mcp.json"));
3750
+ if (existingConfig) {
3751
+ const section = isPlainObject(existingSection) ? { ...existingSection } : {};
3752
+ section.lien = entry;
3753
+ existingConfig[key] = section;
3754
+ await fs.writeFile(configPath, JSON.stringify(existingConfig, null, 2) + "\n");
3755
+ console.log(chalk2.green(`
3756
+ \u2713 Added lien to existing ${label}`));
3696
3757
  } else {
3697
- await fs.mkdir(cursorDir, { recursive: true });
3698
- const config = { mcpServers: { lien: MCP_CONFIG } };
3699
- await fs.writeFile(mcpConfigPath, JSON.stringify(config, null, 2) + "\n");
3700
- console.log(chalk2.green("\n\u2713 Created .cursor/mcp.json"));
3758
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
3759
+ const config = { [key]: { lien: entry } };
3760
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
3761
+ console.log(chalk2.green(`
3762
+ \u2713 Created ${label}`));
3763
+ }
3764
+ }
3765
+ async function promptForEditor() {
3766
+ const { default: inquirer } = await import("inquirer");
3767
+ const { editor } = await inquirer.prompt([
3768
+ {
3769
+ type: "list",
3770
+ name: "editor",
3771
+ message: "Which editor are you using?",
3772
+ choices: [
3773
+ { name: "Cursor", value: "cursor" },
3774
+ { name: "Claude Code", value: "claude-code" },
3775
+ { name: "Windsurf", value: "windsurf" },
3776
+ { name: "OpenCode", value: "opencode" },
3777
+ { name: "Kilo Code", value: "kilo-code" },
3778
+ { name: "Antigravity", value: "antigravity" }
3779
+ ],
3780
+ default: "cursor"
3781
+ }
3782
+ ]);
3783
+ return editor;
3784
+ }
3785
+ async function initCommand(options = {}) {
3786
+ showCompactBanner();
3787
+ const rootDir = options.path || process.cwd();
3788
+ let editorId;
3789
+ if (options.editor) {
3790
+ editorId = options.editor;
3791
+ } else if (!process.stdout.isTTY) {
3792
+ console.error(chalk2.red("Error: Use --editor to specify your editor in non-interactive mode."));
3793
+ process.exit(1);
3794
+ } else {
3795
+ editorId = await promptForEditor();
3796
+ }
3797
+ const editor = EDITORS[editorId];
3798
+ if (editor.configPath) {
3799
+ await writeEditorConfig(editor, rootDir);
3800
+ console.log(chalk2.dim(` ${editor.restartMessage}
3801
+ `));
3802
+ } else {
3803
+ const entry = editor.buildEntry(rootDir);
3804
+ const snippet = { [editor.configKey]: { lien: entry } };
3805
+ console.log(chalk2.yellow(`
3806
+ ${editor.restartMessage}`));
3807
+ console.log(JSON.stringify(snippet, null, 2));
3701
3808
  }
3702
- console.log(chalk2.dim(" Restart Cursor to activate.\n"));
3703
3809
  const legacyConfigPath = path.join(rootDir, ".lien.config.json");
3704
3810
  try {
3705
3811
  await fs.access(legacyConfigPath);
@@ -3714,7 +3820,9 @@ init_banner();
3714
3820
  import chalk3 from "chalk";
3715
3821
  import fs2 from "fs/promises";
3716
3822
  import path2 from "path";
3717
- import os from "os";
3823
+ import os2 from "os";
3824
+ import { createRequire as createRequire2 } from "module";
3825
+ import { fileURLToPath as fileURLToPath2 } from "url";
3718
3826
  import {
3719
3827
  isGitRepo,
3720
3828
  getCurrentBranch,
@@ -3727,75 +3835,179 @@ import {
3727
3835
  DEFAULT_CHUNK_OVERLAP,
3728
3836
  DEFAULT_GIT_POLL_INTERVAL_MS
3729
3837
  } 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
- );
3838
+ var VALID_FORMATS = ["text", "json"];
3839
+ async function getFileStats(filePath) {
3740
3840
  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
- }
3841
+ return await fs2.stat(filePath);
3842
+ } catch {
3843
+ return null;
3844
+ }
3845
+ }
3846
+ async function getFileCount(dirPath) {
3847
+ try {
3848
+ const files = await fs2.readdir(dirPath, { recursive: true });
3849
+ return files.length;
3850
+ } catch {
3851
+ return null;
3852
+ }
3853
+ }
3854
+ async function getLastReindex(indexPath) {
3855
+ try {
3856
+ const version = await readVersionFile(indexPath);
3857
+ return version > 0 ? version : null;
3858
+ } catch {
3859
+ return null;
3860
+ }
3861
+ }
3862
+ function getPackageVersion() {
3863
+ const __filename4 = fileURLToPath2(import.meta.url);
3864
+ const __dirname4 = path2.dirname(__filename4);
3865
+ const require5 = createRequire2(import.meta.url);
3866
+ try {
3867
+ return require5(path2.join(__dirname4, "../package.json")).version;
3868
+ } catch {
3869
+ return require5(path2.join(__dirname4, "../../package.json")).version;
3870
+ }
3871
+ }
3872
+ async function getGitState(rootDir) {
3873
+ try {
3874
+ const branch = await getCurrentBranch(rootDir);
3875
+ const commit = await getCurrentCommit(rootDir);
3876
+ return { branch, commit };
3758
3877
  } catch {
3878
+ return null;
3879
+ }
3880
+ }
3881
+ async function getStoredGitState(indexPath) {
3882
+ try {
3883
+ const content = await fs2.readFile(path2.join(indexPath, ".git-state.json"), "utf-8");
3884
+ return JSON.parse(content);
3885
+ } catch {
3886
+ return null;
3887
+ }
3888
+ }
3889
+ async function printIndexStatus(indexPath) {
3890
+ const stats = await getFileStats(indexPath);
3891
+ if (!stats) {
3759
3892
  console.log(chalk3.dim("Index status:"), chalk3.yellow("\u2717 Not indexed"));
3760
3893
  console.log(
3761
3894
  chalk3.yellow("\nRun"),
3762
3895
  chalk3.bold("lien index"),
3763
3896
  chalk3.yellow("to index your codebase")
3764
3897
  );
3898
+ return;
3765
3899
  }
3766
- console.log(chalk3.bold("\nFeatures:"));
3900
+ console.log(chalk3.dim("Index location:"), indexPath);
3901
+ console.log(chalk3.dim("Index status:"), chalk3.green("\u2713 Exists"));
3902
+ const fileCount = await getFileCount(indexPath);
3903
+ if (fileCount !== null) {
3904
+ console.log(chalk3.dim("Index files:"), fileCount);
3905
+ }
3906
+ console.log(chalk3.dim("Last modified:"), stats.mtime.toLocaleString());
3907
+ const reindexTs = await getLastReindex(indexPath);
3908
+ if (reindexTs !== null) {
3909
+ console.log(chalk3.dim("Last reindex:"), new Date(reindexTs).toLocaleString());
3910
+ }
3911
+ }
3912
+ async function printGitStatus(rootDir, indexPath) {
3767
3913
  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 {
3914
+ if (!isRepo) {
3788
3915
  console.log(chalk3.dim("Git detection:"), chalk3.yellow("Not a git repo"));
3916
+ return;
3917
+ }
3918
+ console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
3919
+ console.log(chalk3.dim(" Poll interval:"), `${DEFAULT_GIT_POLL_INTERVAL_MS / 1e3}s`);
3920
+ const gitState = await getGitState(rootDir);
3921
+ if (!gitState) return;
3922
+ console.log(chalk3.dim(" Current branch:"), gitState.branch);
3923
+ console.log(chalk3.dim(" Current commit:"), gitState.commit.substring(0, 8));
3924
+ const storedGit = await getStoredGitState(indexPath);
3925
+ if (storedGit && (storedGit.branch !== gitState.branch || storedGit.commit !== gitState.commit)) {
3926
+ console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
3789
3927
  }
3928
+ }
3929
+ function printWatchStatus() {
3790
3930
  console.log(chalk3.dim("File watching:"), chalk3.green("\u2713 Enabled (default)"));
3791
3931
  console.log(chalk3.dim(" Batch window:"), "500ms (collects rapid changes, force-flush after 5s)");
3792
3932
  console.log(chalk3.dim(" Disable with:"), chalk3.bold("lien serve --no-watch"));
3933
+ }
3934
+ function printIndexingSettings() {
3793
3935
  console.log(chalk3.bold("\nIndexing Settings (defaults):"));
3794
3936
  console.log(chalk3.dim("Concurrency:"), DEFAULT_CONCURRENCY);
3795
3937
  console.log(chalk3.dim("Batch size:"), DEFAULT_EMBEDDING_BATCH_SIZE);
3796
3938
  console.log(chalk3.dim("Chunk size:"), DEFAULT_CHUNK_SIZE);
3797
3939
  console.log(chalk3.dim("Chunk overlap:"), DEFAULT_CHUNK_OVERLAP);
3798
3940
  }
3941
+ async function statusCommand(options = {}) {
3942
+ const format = options.format || "text";
3943
+ if (!VALID_FORMATS.includes(format)) {
3944
+ console.error(
3945
+ chalk3.red(`Error: Invalid --format value "${format}". Must be one of: text, json`)
3946
+ );
3947
+ process.exit(1);
3948
+ }
3949
+ const rootDir = process.cwd();
3950
+ const repoId = extractRepoId(rootDir);
3951
+ const indexPath = path2.join(os2.homedir(), ".lien", "indices", repoId);
3952
+ if (format === "json") {
3953
+ await outputJson(rootDir, indexPath);
3954
+ return;
3955
+ }
3956
+ showCompactBanner();
3957
+ console.log(chalk3.bold("Status\n"));
3958
+ console.log(
3959
+ chalk3.dim("Configuration:"),
3960
+ chalk3.green("\u2713 Using defaults (no per-project config needed)")
3961
+ );
3962
+ await printIndexStatus(indexPath);
3963
+ console.log(chalk3.bold("\nFeatures:"));
3964
+ await printGitStatus(rootDir, indexPath);
3965
+ printWatchStatus();
3966
+ if (options.verbose) {
3967
+ printIndexingSettings();
3968
+ }
3969
+ }
3970
+ async function outputJson(rootDir, indexPath) {
3971
+ const data = {
3972
+ version: getPackageVersion(),
3973
+ indexPath,
3974
+ indexStatus: "not_indexed",
3975
+ indexFiles: 0,
3976
+ lastModified: null,
3977
+ lastReindex: null,
3978
+ git: { enabled: false, branch: null, commit: null },
3979
+ features: { fileWatching: true, gitDetection: true },
3980
+ settings: {
3981
+ concurrency: DEFAULT_CONCURRENCY,
3982
+ batchSize: DEFAULT_EMBEDDING_BATCH_SIZE,
3983
+ chunkSize: DEFAULT_CHUNK_SIZE,
3984
+ chunkOverlap: DEFAULT_CHUNK_OVERLAP
3985
+ }
3986
+ };
3987
+ const stats = await getFileStats(indexPath);
3988
+ if (stats) {
3989
+ data.indexStatus = "exists";
3990
+ data.lastModified = stats.mtime.toISOString();
3991
+ const fileCount = await getFileCount(indexPath);
3992
+ if (fileCount !== null) {
3993
+ data.indexFiles = fileCount;
3994
+ }
3995
+ const reindexTs = await getLastReindex(indexPath);
3996
+ if (reindexTs !== null) {
3997
+ data.lastReindex = new Date(reindexTs).toISOString();
3998
+ }
3999
+ }
4000
+ const isRepo = await isGitRepo(rootDir);
4001
+ if (isRepo) {
4002
+ const gitState = await getGitState(rootDir);
4003
+ data.git = {
4004
+ enabled: true,
4005
+ branch: gitState?.branch ?? null,
4006
+ commit: gitState ? gitState.commit.substring(0, 8) : null
4007
+ };
4008
+ }
4009
+ console.log(JSON.stringify(data, null, 2));
4010
+ }
3799
4011
 
3800
4012
  // src/cli/index-cmd.ts
3801
4013
  init_banner();
@@ -4027,8 +4239,8 @@ import path7 from "path";
4027
4239
  // src/mcp/server.ts
4028
4240
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4029
4241
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4030
- import { createRequire as createRequire2 } from "module";
4031
- import { fileURLToPath as fileURLToPath2 } from "url";
4242
+ import { createRequire as createRequire3 } from "module";
4243
+ import { fileURLToPath as fileURLToPath3 } from "url";
4032
4244
  import { dirname as dirname2, join as join2 } from "path";
4033
4245
  import { WorkerEmbeddings, VERSION_CHECK_INTERVAL_MS, createVectorDB } from "@liendev/core";
4034
4246
 
@@ -8595,6 +8807,7 @@ var GetComplexitySchema = external_exports.object({
8595
8807
  threshold: external_exports.number().int().min(1, "Threshold must be at least 1").optional().describe(
8596
8808
  "Only return functions above this complexity threshold.\n\nNote: Violations are first identified using the threshold from lien.config.json (default: 15). This parameter filters those violations to show only items above the specified value. Setting threshold below the config threshold will not show additional functions."
8597
8809
  ),
8810
+ metricType: external_exports.enum(["cyclomatic", "cognitive", "halstead_effort", "halstead_bugs"]).optional().describe("Filter violations to a specific metric type. If omitted, returns all types."),
8598
8811
  crossRepo: external_exports.boolean().default(false).describe(
8599
8812
  "If true, analyze complexity across all repos in the organization (requires a cross-repo-capable backend, currently Qdrant).\n\nDefault: false (single-repo analysis)\nWhen enabled, results are aggregated by repository."
8600
8813
  ),
@@ -8750,7 +8963,7 @@ Use for tech debt analysis and refactoring prioritization:
8750
8963
 
8751
8964
  Examples:
8752
8965
  get_complexity({ top: 10 })
8753
- get_complexity({ files: ["src/auth.ts", "src/api/user.ts"] })
8966
+ get_complexity({ files: ["src/auth.ts"], metricType: "cognitive" })
8754
8967
  get_complexity({ threshold: 15 })
8755
8968
 
8756
8969
  Returns:
@@ -10075,15 +10288,15 @@ async function fetchCrossRepoChunks(vectorDB, crossRepo, repoIds, log) {
10075
10288
  }
10076
10289
  return { chunks: [], fallback: true };
10077
10290
  }
10078
- function processViolations(report, threshold, top) {
10291
+ function processViolations(report, threshold, top, metricType) {
10079
10292
  const allViolations = (0, import_collect.default)(Object.entries(report.files)).flatMap(
10080
10293
  ([
10081
10294
  ,
10082
10295
  /* filepath unused */
10083
10296
  fileData
10084
- ]) => fileData.violations.map((v) => transformViolation(v, fileData))
10297
+ ]) => fileData.violations.filter((v) => !metricType || v.metricType === metricType).filter((v) => threshold === void 0 || v.complexity >= threshold).map((v) => transformViolation(v, fileData))
10085
10298
  ).sortByDesc("complexity").all();
10086
- const violations = threshold !== void 0 ? allViolations.filter((v) => v.complexity >= threshold) : allViolations;
10299
+ const violations = allViolations;
10087
10300
  const severityCounts = (0, import_collect.default)(violations).countBy("severity").all();
10088
10301
  return {
10089
10302
  violations,
@@ -10100,7 +10313,7 @@ function buildCrossRepoFallbackNote(fallback) {
10100
10313
  async function handleGetComplexity(args, ctx) {
10101
10314
  const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
10102
10315
  return await wrapToolHandler(GetComplexitySchema, async (validatedArgs) => {
10103
- const { crossRepo, repoIds, files, top, threshold } = validatedArgs;
10316
+ const { crossRepo, repoIds, files, top, threshold, metricType } = validatedArgs;
10104
10317
  log(`Analyzing complexity${crossRepo ? " (cross-repo)" : ""}...`);
10105
10318
  await checkAndReconnect();
10106
10319
  const { chunks: allChunks, fallback } = await fetchCrossRepoChunks(
@@ -10115,7 +10328,8 @@ async function handleGetComplexity(args, ctx) {
10115
10328
  const { violations, topViolations, bySeverity } = processViolations(
10116
10329
  report,
10117
10330
  threshold,
10118
- top ?? 10
10331
+ top ?? 10,
10332
+ metricType
10119
10333
  );
10120
10334
  const note = buildCrossRepoFallbackNote(fallback);
10121
10335
  if (note) {
@@ -10366,9 +10580,8 @@ import {
10366
10580
  normalizeToRelativePath as normalizeToRelativePath2,
10367
10581
  createGitignoreFilter
10368
10582
  } from "@liendev/core";
10369
- async function handleFileDeletion(filepath, vectorDB, log) {
10583
+ async function handleFileDeletion(filepath, vectorDB, manifest, log) {
10370
10584
  log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
10371
- const manifest = new ManifestManager(vectorDB.dbPath);
10372
10585
  try {
10373
10586
  await vectorDB.deleteByFile(filepath);
10374
10587
  await manifest.removeFile(filepath);
@@ -10378,8 +10591,7 @@ async function handleFileDeletion(filepath, vectorDB, log) {
10378
10591
  throw error;
10379
10592
  }
10380
10593
  }
10381
- async function handleBatchDeletions(deletedFiles, vectorDB, log) {
10382
- const manifest = new ManifestManager(vectorDB.dbPath);
10594
+ async function handleBatchDeletions(deletedFiles, vectorDB, manifest, log) {
10383
10595
  const failures = [];
10384
10596
  for (const filepath of deletedFiles) {
10385
10597
  log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
@@ -10396,8 +10608,7 @@ async function handleBatchDeletions(deletedFiles, vectorDB, log) {
10396
10608
  throw new Error(`Failed to delete ${failures.length} file(s): ${failures.join(", ")}`);
10397
10609
  }
10398
10610
  }
10399
- async function canSkipReindex(filepath, rootDir, vectorDB, log) {
10400
- const manifest = new ManifestManager(vectorDB.dbPath);
10611
+ async function canSkipReindex(filepath, rootDir, manifest, log) {
10401
10612
  const normalizedPath = normalizeToRelativePath2(filepath, rootDir);
10402
10613
  const manifestData = await manifest.load();
10403
10614
  const existingEntry = manifestData?.files[normalizedPath];
@@ -10415,11 +10626,11 @@ async function canSkipReindex(filepath, rootDir, vectorDB, log) {
10415
10626
  }
10416
10627
  return false;
10417
10628
  }
10418
- async function handleSingleFileChange(filepath, type, rootDir, vectorDB, embeddings, log, reindexStateManager) {
10629
+ async function handleSingleFileChange(filepath, type, rootDir, vectorDB, embeddings, manifest, log, reindexStateManager) {
10419
10630
  const action = type === "add" ? "added" : "changed";
10420
10631
  if (type === "change") {
10421
10632
  try {
10422
- if (await canSkipReindex(filepath, rootDir, vectorDB, log)) return;
10633
+ if (await canSkipReindex(filepath, rootDir, manifest, log)) return;
10423
10634
  } catch (error) {
10424
10635
  log(`Content hash check failed, will reindex: ${error}`, "warning");
10425
10636
  }
@@ -10477,9 +10688,8 @@ async function updateUnchangedMtimes(manifest, results) {
10477
10688
  return null;
10478
10689
  });
10479
10690
  }
10480
- async function filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log) {
10691
+ async function filterModifiedFilesByHash(modifiedFiles, rootDir, manifest, log) {
10481
10692
  if (modifiedFiles.length === 0) return [];
10482
- const manifest = new ManifestManager(vectorDB.dbPath);
10483
10693
  const manifestData = await manifest.load();
10484
10694
  if (!manifestData) return modifiedFiles;
10485
10695
  const checkResults = await checkFilesAgainstManifest(
@@ -10491,13 +10701,13 @@ async function filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log)
10491
10701
  await updateUnchangedMtimes(manifest, checkResults);
10492
10702
  return checkResults.filter((r) => r.shouldReindex).map((r) => r.filepath);
10493
10703
  }
10494
- async function prepareFilesForReindexing(event, rootDir, vectorDB, log) {
10704
+ async function prepareFilesForReindexing(event, rootDir, manifest, log) {
10495
10705
  const addedFiles = event.added || [];
10496
10706
  const modifiedFiles = event.modified || [];
10497
10707
  const deletedFiles = event.deleted || [];
10498
10708
  let modifiedFilesToReindex = [];
10499
10709
  try {
10500
- modifiedFilesToReindex = await filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log);
10710
+ modifiedFilesToReindex = await filterModifiedFilesByHash(modifiedFiles, rootDir, manifest, log);
10501
10711
  } catch (error) {
10502
10712
  log(`Hash-based filtering failed, will reindex all modified files: ${error}`, "warning");
10503
10713
  modifiedFilesToReindex = modifiedFiles;
@@ -10505,24 +10715,20 @@ async function prepareFilesForReindexing(event, rootDir, vectorDB, log) {
10505
10715
  const filesToIndex = [...addedFiles, ...modifiedFilesToReindex];
10506
10716
  return { filesToIndex, deletedFiles };
10507
10717
  }
10508
- async function executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, log) {
10509
- const operations = [];
10718
+ async function executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, manifest, log) {
10510
10719
  if (filesToIndex.length > 0) {
10511
10720
  log(`\u{1F4C1} ${filesToIndex.length} file(s) changed, reindexing...`);
10512
- operations.push(
10513
- indexMultipleFiles(filesToIndex, vectorDB, embeddings, { verbose: false, rootDir })
10514
- );
10721
+ await indexMultipleFiles(filesToIndex, vectorDB, embeddings, { verbose: false, rootDir });
10515
10722
  }
10516
10723
  if (deletedFiles.length > 0) {
10517
- operations.push(handleBatchDeletions(deletedFiles, vectorDB, log));
10724
+ await handleBatchDeletions(deletedFiles, vectorDB, manifest, log);
10518
10725
  }
10519
- await Promise.all(operations);
10520
10726
  }
10521
- async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reindexStateManager) {
10727
+ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, manifest, log, reindexStateManager) {
10522
10728
  const { filesToIndex, deletedFiles } = await prepareFilesForReindexing(
10523
10729
  event,
10524
10730
  rootDir,
10525
- vectorDB,
10731
+ manifest,
10526
10732
  log
10527
10733
  );
10528
10734
  const allFiles = [...filesToIndex, ...deletedFiles];
@@ -10532,7 +10738,15 @@ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reind
10532
10738
  const startTime = Date.now();
10533
10739
  reindexStateManager.startReindex(allFiles);
10534
10740
  try {
10535
- await executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, log);
10741
+ await executeReindexOperations(
10742
+ filesToIndex,
10743
+ deletedFiles,
10744
+ rootDir,
10745
+ vectorDB,
10746
+ embeddings,
10747
+ manifest,
10748
+ log
10749
+ );
10536
10750
  const duration = Date.now() - startTime;
10537
10751
  reindexStateManager.completeReindex(duration);
10538
10752
  log(
@@ -10543,11 +10757,11 @@ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reind
10543
10757
  log(`Batch reindex failed: ${error}`, "warning");
10544
10758
  }
10545
10759
  }
10546
- async function handleUnlinkEvent(filepath, vectorDB, log, reindexStateManager) {
10760
+ async function handleUnlinkEvent(filepath, vectorDB, manifest, log, reindexStateManager) {
10547
10761
  const startTime = Date.now();
10548
10762
  reindexStateManager.startReindex([filepath]);
10549
10763
  try {
10550
- await handleFileDeletion(filepath, vectorDB, log);
10764
+ await handleFileDeletion(filepath, vectorDB, manifest, log);
10551
10765
  const duration = Date.now() - startTime;
10552
10766
  reindexStateManager.completeReindex(duration);
10553
10767
  } catch (error) {
@@ -10579,6 +10793,7 @@ function hasGitignoreChange(event) {
10579
10793
  }
10580
10794
  function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStateManager, checkAndReconnect) {
10581
10795
  let ignoreFilter = null;
10796
+ const manifest = new ManifestManager(vectorDB.dbPath);
10582
10797
  return async (event) => {
10583
10798
  if (hasGitignoreChange(event)) {
10584
10799
  ignoreFilter = null;
@@ -10592,10 +10807,18 @@ function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStat
10592
10807
  const totalToProcess = filtered.added.length + filtered.modified.length + filtered.deleted.length;
10593
10808
  if (totalToProcess === 0) return;
10594
10809
  await checkAndReconnect();
10595
- await handleBatchEvent(filtered, rootDir, vectorDB, embeddings, log, reindexStateManager);
10810
+ await handleBatchEvent(
10811
+ filtered,
10812
+ rootDir,
10813
+ vectorDB,
10814
+ embeddings,
10815
+ manifest,
10816
+ log,
10817
+ reindexStateManager
10818
+ );
10596
10819
  } else if (type === "unlink") {
10597
10820
  await checkAndReconnect();
10598
- await handleUnlinkEvent(event.filepath, vectorDB, log, reindexStateManager);
10821
+ await handleUnlinkEvent(event.filepath, vectorDB, manifest, log, reindexStateManager);
10599
10822
  } else {
10600
10823
  if (isFileIgnored(event.filepath, rootDir, ignoreFilter)) return;
10601
10824
  await checkAndReconnect();
@@ -10605,6 +10828,7 @@ function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStat
10605
10828
  rootDir,
10606
10829
  vectorDB,
10607
10830
  embeddings,
10831
+ manifest,
10608
10832
  log,
10609
10833
  reindexStateManager
10610
10834
  );
@@ -10867,9 +11091,9 @@ function setupCleanupHandlers(server, versionCheckInterval, gitPollInterval, fil
10867
11091
  }
10868
11092
 
10869
11093
  // src/mcp/server.ts
10870
- var __filename2 = fileURLToPath2(import.meta.url);
11094
+ var __filename2 = fileURLToPath3(import.meta.url);
10871
11095
  var __dirname2 = dirname2(__filename2);
10872
- var require3 = createRequire2(import.meta.url);
11096
+ var require3 = createRequire3(import.meta.url);
10873
11097
  var packageJson2;
10874
11098
  try {
10875
11099
  packageJson2 = require3(join2(__dirname2, "../package.json"));
@@ -11133,7 +11357,7 @@ import { VectorDB } from "@liendev/core";
11133
11357
  import { ComplexityAnalyzer as ComplexityAnalyzer2 } from "@liendev/core";
11134
11358
  import { formatReport } from "@liendev/core";
11135
11359
  var VALID_FAIL_ON = ["error", "warning"];
11136
- var VALID_FORMATS = ["text", "json", "sarif"];
11360
+ var VALID_FORMATS2 = ["text", "json", "sarif"];
11137
11361
  function validateFailOn(failOn) {
11138
11362
  if (failOn && !VALID_FAIL_ON.includes(failOn)) {
11139
11363
  console.error(
@@ -11143,7 +11367,7 @@ function validateFailOn(failOn) {
11143
11367
  }
11144
11368
  }
11145
11369
  function validateFormat(format) {
11146
- if (!VALID_FORMATS.includes(format)) {
11370
+ if (!VALID_FORMATS2.includes(format)) {
11147
11371
  console.error(
11148
11372
  chalk7.red(`Error: Invalid --format value "${format}". Must be one of: text, json, sarif`)
11149
11373
  );
@@ -11200,9 +11424,9 @@ async function complexityCommand(options) {
11200
11424
  // src/cli/config.ts
11201
11425
  import chalk8 from "chalk";
11202
11426
  import path9 from "path";
11203
- import os2 from "os";
11427
+ import os3 from "os";
11204
11428
  import { loadGlobalConfig, mergeGlobalConfig } from "@liendev/core";
11205
- var CONFIG_PATH = path9.join(os2.homedir(), ".lien", "config.json");
11429
+ var CONFIG_PATH = path9.join(os3.homedir(), ".lien", "config.json");
11206
11430
  var ALLOWED_KEYS = {
11207
11431
  backend: {
11208
11432
  values: ["lancedb", "qdrant"],
@@ -11289,9 +11513,9 @@ async function configListCommand() {
11289
11513
  }
11290
11514
 
11291
11515
  // src/cli/index.ts
11292
- var __filename3 = fileURLToPath3(import.meta.url);
11516
+ var __filename3 = fileURLToPath4(import.meta.url);
11293
11517
  var __dirname3 = dirname3(__filename3);
11294
- var require4 = createRequire3(import.meta.url);
11518
+ var require4 = createRequire4(import.meta.url);
11295
11519
  var packageJson3;
11296
11520
  try {
11297
11521
  packageJson3 = require4(join3(__dirname3, "../package.json"));
@@ -11300,17 +11524,31 @@ try {
11300
11524
  }
11301
11525
  var program = new Command();
11302
11526
  program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);
11303
- 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);
11527
+ program.command("init").description("Initialize Lien in the current directory").addOption(
11528
+ new Option("-e, --editor <editor>", "Editor to configure MCP for").choices([
11529
+ "cursor",
11530
+ "claude-code",
11531
+ "windsurf",
11532
+ "opencode",
11533
+ "kilo-code",
11534
+ "antigravity"
11535
+ ])
11536
+ ).option("-p, --path <path>", "Path to initialize (defaults to current directory)").action(initCommand);
11304
11537
  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);
11305
11538
  program.command("serve").description(
11306
11539
  "Start the MCP server (works with Cursor, Claude Code, Windsurf, and any MCP client)"
11307
- ).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);
11308
- program.command("status").description("Show indexing status and statistics").action(statusCommand);
11540
+ ).option("-p, --port <port>", "Port number (for future use)", "7133").option("--no-watch", "Disable file watching for this session").addOption(
11541
+ new Option("-w, --watch", "[DEPRECATED] File watching is now enabled by default").hideHelp()
11542
+ ).option("-r, --root <path>", "Root directory to serve (defaults to current directory)").action(serveCommand);
11543
+ 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);
11309
11544
  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);
11310
11545
  var configCmd = program.command("config").description("Manage global configuration (~/.lien/config.json)");
11311
11546
  configCmd.command("set <key> <value>").description("Set a global config value").action(configSetCommand);
11312
11547
  configCmd.command("get <key>").description("Get a config value").action(configGetCommand);
11313
11548
  configCmd.command("list").description("Show all current config").action(configListCommand);
11549
+ program.action(() => {
11550
+ program.help();
11551
+ });
11314
11552
  program.addHelpText("beforeAll", `Quick start: run 'lien serve' in your project directory
11315
11553
  `);
11316
11554