@liendev/lien 0.20.0 → 0.21.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
@@ -3590,7 +3590,7 @@ var require_dist = __commonJS({
3590
3590
 
3591
3591
  // src/cli/index.ts
3592
3592
  import { Command } from "commander";
3593
- import { createRequire as createRequire4 } from "module";
3593
+ import { createRequire as createRequire3 } from "module";
3594
3594
  import { fileURLToPath as fileURLToPath4 } from "url";
3595
3595
  import { dirname as dirname3, join as join3 } from "path";
3596
3596
 
@@ -3598,15 +3598,8 @@ import { dirname as dirname3, join as join3 } from "path";
3598
3598
  import fs from "fs/promises";
3599
3599
  import path from "path";
3600
3600
  import { fileURLToPath as fileURLToPath2 } from "url";
3601
- import { createRequire as createRequire2 } from "module";
3602
3601
  import chalk2 from "chalk";
3603
3602
  import inquirer from "inquirer";
3604
- import {
3605
- defaultConfig,
3606
- MigrationManager,
3607
- detectAllFrameworks,
3608
- getFrameworkDetector
3609
- } from "@liendev/core";
3610
3603
 
3611
3604
  // src/utils/banner.ts
3612
3605
  import figlet from "figlet";
@@ -3670,151 +3663,39 @@ function showCompactBanner() {
3670
3663
  // src/cli/init.ts
3671
3664
  var __filename2 = fileURLToPath2(import.meta.url);
3672
3665
  var __dirname2 = path.dirname(__filename2);
3673
- var require3 = createRequire2(import.meta.url);
3674
- var CLI_VERSION;
3675
- try {
3676
- CLI_VERSION = require3("../package.json").version;
3677
- } catch {
3678
- CLI_VERSION = require3("../../package.json").version;
3679
- }
3680
3666
  async function initCommand(options = {}) {
3667
+ showCompactBanner();
3668
+ console.log(chalk2.bold("\nLien Initialization\n"));
3669
+ console.log(chalk2.green("\u2713 No per-project configuration needed!"));
3670
+ console.log(chalk2.dim("\nLien now uses:"));
3671
+ console.log(chalk2.dim(" \u2022 Auto-detected frameworks"));
3672
+ console.log(chalk2.dim(" \u2022 Sensible defaults for all settings"));
3673
+ console.log(chalk2.dim(" \u2022 Global config (optional) at ~/.lien/config.json"));
3674
+ console.log(chalk2.bold("\nNext steps:"));
3675
+ console.log(chalk2.dim(" 1. Run"), chalk2.bold("lien index"), chalk2.dim("to index your codebase"));
3676
+ console.log(chalk2.dim(" 2. Run"), chalk2.bold("lien serve"), chalk2.dim("to start the MCP server"));
3677
+ console.log(chalk2.bold("\nGlobal Configuration (optional):"));
3678
+ console.log(chalk2.dim(" To use Qdrant backend, create ~/.lien/config.json:"));
3679
+ console.log(chalk2.dim(" {"));
3680
+ console.log(chalk2.dim(' "backend": "qdrant",'));
3681
+ console.log(chalk2.dim(' "qdrant": {'));
3682
+ console.log(chalk2.dim(' "url": "http://localhost:6333",'));
3683
+ console.log(chalk2.dim(' "apiKey": "optional-api-key"'));
3684
+ console.log(chalk2.dim(" }"));
3685
+ console.log(chalk2.dim(" }"));
3686
+ console.log(chalk2.dim("\n Or use environment variables:"));
3687
+ console.log(chalk2.dim(" LIEN_BACKEND=qdrant"));
3688
+ console.log(chalk2.dim(" LIEN_QDRANT_URL=http://localhost:6333"));
3689
+ console.log(chalk2.dim(" LIEN_QDRANT_API_KEY=your-key"));
3681
3690
  const rootDir = options.path || process.cwd();
3682
3691
  const configPath = path.join(rootDir, ".lien.config.json");
3683
3692
  try {
3684
- let configExists = false;
3685
- try {
3686
- await fs.access(configPath);
3687
- configExists = true;
3688
- } catch {
3689
- }
3690
- if (configExists && options.upgrade) {
3691
- const migrationManager = new MigrationManager(rootDir, CLI_VERSION);
3692
- await migrationManager.upgradeInteractive();
3693
- return;
3694
- }
3695
- if (configExists && !options.upgrade) {
3696
- console.log(chalk2.yellow("\u26A0\uFE0F .lien.config.json already exists"));
3697
- console.log(chalk2.dim("Run"), chalk2.bold("lien init --upgrade"), chalk2.dim("to merge new config options"));
3698
- return;
3699
- }
3700
- if (!configExists) {
3701
- await createNewConfig(rootDir, options);
3702
- }
3703
- } catch (error) {
3704
- console.error(chalk2.red("Error creating config file:"), error);
3705
- process.exit(1);
3706
- }
3707
- }
3708
- function createGenericFramework() {
3709
- return {
3710
- name: "generic",
3711
- path: ".",
3712
- enabled: true,
3713
- config: {
3714
- include: ["**/*.{ts,tsx,js,jsx,py,php,go,rs,java,c,cpp,cs}"],
3715
- exclude: [
3716
- "**/node_modules/**",
3717
- "**/dist/**",
3718
- "**/build/**",
3719
- "**/.git/**",
3720
- "**/coverage/**",
3721
- "**/.next/**",
3722
- "**/.nuxt/**",
3723
- "**/vendor/**"
3724
- ]
3725
- }
3726
- };
3727
- }
3728
- async function handleNoFrameworksDetected(options) {
3729
- console.log(chalk2.yellow("\n\u26A0\uFE0F No frameworks detected"));
3730
- if (!options.yes) {
3731
- const { useGeneric } = await inquirer.prompt([{
3732
- type: "confirm",
3733
- name: "useGeneric",
3734
- message: "Create a generic config (index all supported file types)?",
3735
- default: true
3736
- }]);
3737
- if (!useGeneric) {
3738
- console.log(chalk2.dim("Aborted."));
3739
- return null;
3740
- }
3741
- } else {
3742
- console.log(chalk2.dim("Creating generic config (no frameworks detected)..."));
3743
- }
3744
- return [createGenericFramework()];
3745
- }
3746
- function displayDetectedFrameworks(detections) {
3747
- console.log(chalk2.green(`
3748
- \u2713 Found ${detections.length} framework(s):
3749
- `));
3750
- for (const det of detections) {
3751
- const pathDisplay = det.path === "." ? "root" : det.path;
3752
- console.log(chalk2.bold(` ${det.name}`), chalk2.dim(`(${det.confidence} confidence)`));
3753
- console.log(chalk2.dim(` Location: ${pathDisplay}`));
3754
- if (det.evidence.length > 0) {
3755
- det.evidence.forEach((e) => console.log(chalk2.dim(` \u2022 ${e}`)));
3756
- }
3757
- console.log();
3758
- }
3759
- }
3760
- async function confirmFrameworkConfiguration(options) {
3761
- if (options.yes) return true;
3762
- const { confirm } = await inquirer.prompt([{
3763
- type: "confirm",
3764
- name: "confirm",
3765
- message: "Configure these frameworks?",
3766
- default: true
3767
- }]);
3768
- return confirm;
3769
- }
3770
- async function generateSingleFrameworkConfig(det, rootDir, options) {
3771
- const detector = getFrameworkDetector(det.name);
3772
- if (!detector) {
3773
- console.warn(chalk2.yellow(`\u26A0\uFE0F No detector found for ${det.name}, skipping`));
3774
- return null;
3775
- }
3776
- const frameworkConfig = await detector.generateConfig(rootDir, det.path);
3777
- let finalConfig = frameworkConfig;
3778
- const pathDisplay = det.path === "." ? "root" : det.path;
3779
- if (!options.yes) {
3780
- const { customize } = await inquirer.prompt([{
3781
- type: "confirm",
3782
- name: "customize",
3783
- message: `Customize ${det.name} settings?`,
3784
- default: false
3785
- }]);
3786
- if (customize) {
3787
- const customized = await promptForCustomization(det.name, frameworkConfig);
3788
- finalConfig = { ...frameworkConfig, ...customized };
3789
- } else {
3790
- console.log(chalk2.dim(` \u2192 Using defaults for ${det.name} at ${pathDisplay}`));
3791
- }
3792
- } else {
3793
- console.log(chalk2.dim(` \u2192 Using defaults for ${det.name} at ${pathDisplay}`));
3794
- }
3795
- return {
3796
- name: det.name,
3797
- path: det.path,
3798
- enabled: true,
3799
- config: finalConfig
3800
- };
3801
- }
3802
- async function handleFrameworksDetected(detections, rootDir, options) {
3803
- displayDetectedFrameworks(detections);
3804
- if (!await confirmFrameworkConfiguration(options)) {
3805
- console.log(chalk2.dim("Aborted."));
3806
- return null;
3807
- }
3808
- const frameworks = [];
3809
- for (const det of detections) {
3810
- const framework = await generateSingleFrameworkConfig(det, rootDir, options);
3811
- if (framework) frameworks.push(framework);
3812
- }
3813
- if (frameworks.length === 0) {
3814
- console.log(chalk2.yellow("\n\u26A0\uFE0F No framework configs could be generated"));
3815
- return null;
3693
+ await fs.access(configPath);
3694
+ console.log(chalk2.yellow("\n\u26A0\uFE0F Note: .lien.config.json found but no longer used"));
3695
+ console.log(chalk2.dim(" You can safely delete it."));
3696
+ } catch {
3816
3697
  }
3817
- return frameworks;
3698
+ await promptAndInstallCursorRules(rootDir, options);
3818
3699
  }
3819
3700
  async function getPathType(filepath) {
3820
3701
  try {
@@ -3938,51 +3819,6 @@ async function promptAndInstallCursorRules(rootDir, options) {
3938
3819
  console.log(chalk2.dim("You can manually copy CURSOR_RULES_TEMPLATE.md to .cursor/rules/lien.mdc"));
3939
3820
  }
3940
3821
  }
3941
- async function writeConfigAndShowSuccess(rootDir, frameworks) {
3942
- const config = { ...defaultConfig, version: CLI_VERSION, frameworks };
3943
- const configPath = path.join(rootDir, ".lien.config.json");
3944
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3945
- console.log(chalk2.green("\n\u2713 Created .lien.config.json"));
3946
- console.log(chalk2.green(`\u2713 Configured ${frameworks.length} framework(s)`));
3947
- console.log(chalk2.dim("\nNext steps:"));
3948
- console.log(chalk2.dim(" 1. Run"), chalk2.bold("lien index"), chalk2.dim("to index your codebase"));
3949
- console.log(chalk2.dim(" 2. Run"), chalk2.bold("lien serve"), chalk2.dim("to start the MCP server"));
3950
- console.log(chalk2.dim(" 3. Configure Cursor to use the MCP server (see README.md)"));
3951
- }
3952
- async function createNewConfig(rootDir, options) {
3953
- showCompactBanner();
3954
- console.log(chalk2.bold("Initializing Lien...\n"));
3955
- console.log(chalk2.dim("\u{1F50D} Detecting frameworks in"), chalk2.bold(rootDir));
3956
- const detections = await detectAllFrameworks(rootDir);
3957
- const frameworks = detections.length === 0 ? await handleNoFrameworksDetected(options) : await handleFrameworksDetected(detections, rootDir, options);
3958
- if (!frameworks) return;
3959
- await promptAndInstallCursorRules(rootDir, options);
3960
- await writeConfigAndShowSuccess(rootDir, frameworks);
3961
- }
3962
- async function promptForCustomization(frameworkName, config) {
3963
- console.log(chalk2.bold(`
3964
- Customizing ${frameworkName} settings:`));
3965
- const answers = await inquirer.prompt([
3966
- {
3967
- type: "input",
3968
- name: "include",
3969
- message: "File patterns to include (comma-separated):",
3970
- default: config.include.join(", "),
3971
- filter: (input) => input.split(",").map((s) => s.trim())
3972
- },
3973
- {
3974
- type: "input",
3975
- name: "exclude",
3976
- message: "File patterns to exclude (comma-separated):",
3977
- default: config.exclude.join(", "),
3978
- filter: (input) => input.split(",").map((s) => s.trim())
3979
- }
3980
- ]);
3981
- return {
3982
- include: answers.include,
3983
- exclude: answers.exclude
3984
- };
3985
- }
3986
3822
 
3987
3823
  // src/cli/status.ts
3988
3824
  import chalk3 from "chalk";
@@ -3991,12 +3827,16 @@ import path2 from "path";
3991
3827
  import os from "os";
3992
3828
  import crypto from "crypto";
3993
3829
  import {
3994
- configService,
3995
3830
  isGitRepo,
3996
3831
  getCurrentBranch,
3997
3832
  getCurrentCommit,
3998
3833
  readVersionFile,
3999
- isModernConfig
3834
+ DEFAULT_CONCURRENCY,
3835
+ DEFAULT_EMBEDDING_BATCH_SIZE,
3836
+ DEFAULT_CHUNK_SIZE,
3837
+ DEFAULT_CHUNK_OVERLAP,
3838
+ DEFAULT_GIT_POLL_INTERVAL_MS,
3839
+ DEFAULT_DEBOUNCE_MS
4000
3840
  } from "@liendev/core";
4001
3841
  async function statusCommand() {
4002
3842
  const rootDir = process.cwd();
@@ -4005,12 +3845,7 @@ async function statusCommand() {
4005
3845
  const indexPath = path2.join(os.homedir(), ".lien", "indices", `${projectName}-${pathHash}`);
4006
3846
  showCompactBanner();
4007
3847
  console.log(chalk3.bold("Status\n"));
4008
- const hasConfig = await configService.exists(rootDir);
4009
- console.log(chalk3.dim("Configuration:"), hasConfig ? chalk3.green("\u2713 Found") : chalk3.red("\u2717 Not initialized"));
4010
- if (!hasConfig) {
4011
- console.log(chalk3.yellow("\nRun"), chalk3.bold("lien init"), chalk3.yellow("to initialize"));
4012
- return;
4013
- }
3848
+ console.log(chalk3.dim("Configuration:"), chalk3.green("\u2713 Using defaults (no per-project config needed)"));
4014
3849
  try {
4015
3850
  const stats = await fs2.stat(indexPath);
4016
3851
  console.log(chalk3.dim("Index location:"), indexPath);
@@ -4033,51 +3868,38 @@ async function statusCommand() {
4033
3868
  console.log(chalk3.dim("Index status:"), chalk3.yellow("\u2717 Not indexed"));
4034
3869
  console.log(chalk3.yellow("\nRun"), chalk3.bold("lien index"), chalk3.yellow("to index your codebase"));
4035
3870
  }
4036
- try {
4037
- const config = await configService.load(rootDir);
4038
- console.log(chalk3.bold("\nFeatures:"));
4039
- const isRepo = await isGitRepo(rootDir);
4040
- if (config.gitDetection.enabled && isRepo) {
4041
- console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
4042
- console.log(chalk3.dim(" Poll interval:"), `${config.gitDetection.pollIntervalMs / 1e3}s`);
3871
+ console.log(chalk3.bold("\nFeatures:"));
3872
+ const isRepo = await isGitRepo(rootDir);
3873
+ if (isRepo) {
3874
+ console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
3875
+ console.log(chalk3.dim(" Poll interval:"), `${DEFAULT_GIT_POLL_INTERVAL_MS / 1e3}s`);
3876
+ try {
3877
+ const branch = await getCurrentBranch(rootDir);
3878
+ const commit = await getCurrentCommit(rootDir);
3879
+ console.log(chalk3.dim(" Current branch:"), branch);
3880
+ console.log(chalk3.dim(" Current commit:"), commit.substring(0, 8));
3881
+ const gitStateFile = path2.join(indexPath, ".git-state.json");
4043
3882
  try {
4044
- const branch = await getCurrentBranch(rootDir);
4045
- const commit = await getCurrentCommit(rootDir);
4046
- console.log(chalk3.dim(" Current branch:"), branch);
4047
- console.log(chalk3.dim(" Current commit:"), commit.substring(0, 8));
4048
- const gitStateFile = path2.join(indexPath, ".git-state.json");
4049
- try {
4050
- const gitStateContent = await fs2.readFile(gitStateFile, "utf-8");
4051
- const gitState = JSON.parse(gitStateContent);
4052
- if (gitState.branch !== branch || gitState.commit !== commit) {
4053
- console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
4054
- }
4055
- } catch {
3883
+ const gitStateContent = await fs2.readFile(gitStateFile, "utf-8");
3884
+ const gitState = JSON.parse(gitStateContent);
3885
+ if (gitState.branch !== branch || gitState.commit !== commit) {
3886
+ console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
4056
3887
  }
4057
3888
  } catch {
4058
3889
  }
4059
- } else if (config.gitDetection.enabled && !isRepo) {
4060
- console.log(chalk3.dim("Git detection:"), chalk3.yellow("Enabled (not a git repo)"));
4061
- } else {
4062
- console.log(chalk3.dim("Git detection:"), chalk3.gray("Disabled"));
4063
- }
4064
- if (config.fileWatching.enabled) {
4065
- console.log(chalk3.dim("File watching:"), chalk3.green("\u2713 Enabled"));
4066
- console.log(chalk3.dim(" Debounce:"), `${config.fileWatching.debounceMs}ms`);
4067
- } else {
4068
- console.log(chalk3.dim("File watching:"), chalk3.gray("Disabled"));
4069
- console.log(chalk3.dim(" Enable with:"), chalk3.bold("lien serve --watch"));
4070
- }
4071
- console.log(chalk3.bold("\nIndexing Settings:"));
4072
- if (isModernConfig(config)) {
4073
- console.log(chalk3.dim("Concurrency:"), config.core.concurrency);
4074
- console.log(chalk3.dim("Batch size:"), config.core.embeddingBatchSize);
4075
- console.log(chalk3.dim("Chunk size:"), config.core.chunkSize);
4076
- console.log(chalk3.dim("Chunk overlap:"), config.core.chunkOverlap);
3890
+ } catch {
4077
3891
  }
4078
- } catch (error) {
4079
- console.log(chalk3.yellow("\nWarning: Could not load configuration"));
4080
- }
3892
+ } else {
3893
+ console.log(chalk3.dim("Git detection:"), chalk3.yellow("Not a git repo"));
3894
+ }
3895
+ console.log(chalk3.dim("File watching:"), chalk3.green("\u2713 Enabled (default)"));
3896
+ console.log(chalk3.dim(" Debounce:"), `${DEFAULT_DEBOUNCE_MS}ms`);
3897
+ console.log(chalk3.dim(" Disable with:"), chalk3.bold("lien serve --no-watch"));
3898
+ console.log(chalk3.bold("\nIndexing Settings (defaults):"));
3899
+ console.log(chalk3.dim("Concurrency:"), DEFAULT_CONCURRENCY);
3900
+ console.log(chalk3.dim("Batch size:"), DEFAULT_EMBEDDING_BATCH_SIZE);
3901
+ console.log(chalk3.dim("Chunk size:"), DEFAULT_CHUNK_SIZE);
3902
+ console.log(chalk3.dim("Chunk overlap:"), DEFAULT_CHUNK_OVERLAP);
4081
3903
  }
4082
3904
 
4083
3905
  // src/cli/index-cmd.ts
@@ -4166,10 +3988,10 @@ async function indexCommand(options) {
4166
3988
  showCompactBanner();
4167
3989
  try {
4168
3990
  if (options.force) {
4169
- const { VectorDB: VectorDB3 } = await import("@liendev/core");
3991
+ const { VectorDB: VectorDB2 } = await import("@liendev/core");
4170
3992
  const { ManifestManager: ManifestManager2 } = await import("@liendev/core");
4171
3993
  console.log(chalk4.yellow("Clearing existing index and manifest..."));
4172
- const vectorDB = new VectorDB3(process.cwd());
3994
+ const vectorDB = new VectorDB2(process.cwd());
4173
3995
  await vectorDB.initialize();
4174
3996
  await vectorDB.clear();
4175
3997
  const manifest = new ManifestManager2(vectorDB.dbPath);
@@ -4271,13 +4093,234 @@ import path4 from "path";
4271
4093
  // src/mcp/server.ts
4272
4094
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4273
4095
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4096
+ import { createRequire as createRequire2 } from "module";
4097
+ import { fileURLToPath as fileURLToPath3 } from "url";
4098
+ import { dirname as dirname2, join as join2 } from "path";
4099
+ import {
4100
+ LocalEmbeddings,
4101
+ GitStateTracker,
4102
+ indexMultipleFiles,
4103
+ indexSingleFile,
4104
+ ManifestManager,
4105
+ isGitAvailable,
4106
+ isGitRepo as isGitRepo2,
4107
+ VERSION_CHECK_INTERVAL_MS,
4108
+ DEFAULT_GIT_POLL_INTERVAL_MS as DEFAULT_GIT_POLL_INTERVAL_MS2,
4109
+ createVectorDB
4110
+ } from "@liendev/core";
4111
+
4112
+ // src/watcher/index.ts
4113
+ import chokidar from "chokidar";
4114
+ import path3 from "path";
4115
+ import { detectAllFrameworks, getFrameworkDetector, DEFAULT_DEBOUNCE_MS as DEFAULT_DEBOUNCE_MS2 } from "@liendev/core";
4116
+ var FileWatcher = class {
4117
+ watcher = null;
4118
+ debounceTimers = /* @__PURE__ */ new Map();
4119
+ rootDir;
4120
+ onChangeHandler = null;
4121
+ constructor(rootDir) {
4122
+ this.rootDir = rootDir;
4123
+ }
4124
+ /**
4125
+ * Detect watch patterns from frameworks or use defaults.
4126
+ */
4127
+ async getWatchPatterns() {
4128
+ try {
4129
+ const detectedFrameworks = await detectAllFrameworks(this.rootDir);
4130
+ if (detectedFrameworks.length > 0) {
4131
+ const frameworks = await Promise.all(
4132
+ detectedFrameworks.map(async (detection) => {
4133
+ const detector = getFrameworkDetector(detection.name);
4134
+ if (!detector) {
4135
+ return null;
4136
+ }
4137
+ const config = await detector.generateConfig(this.rootDir, detection.path);
4138
+ return {
4139
+ name: detection.name,
4140
+ path: detection.path,
4141
+ enabled: true,
4142
+ config
4143
+ };
4144
+ })
4145
+ );
4146
+ const validFrameworks = frameworks.filter((f) => f !== null);
4147
+ const includePatterns = validFrameworks.flatMap((f) => f.config.include);
4148
+ const excludePatterns = validFrameworks.flatMap((f) => f.config.exclude);
4149
+ if (includePatterns.length === 0) {
4150
+ return this.getDefaultPatterns();
4151
+ }
4152
+ return { include: includePatterns, exclude: excludePatterns };
4153
+ } else {
4154
+ return this.getDefaultPatterns();
4155
+ }
4156
+ } catch (error) {
4157
+ return this.getDefaultPatterns();
4158
+ }
4159
+ }
4160
+ /**
4161
+ * Get default watch patterns.
4162
+ */
4163
+ getDefaultPatterns() {
4164
+ return {
4165
+ include: ["**/*"],
4166
+ exclude: [
4167
+ "**/node_modules/**",
4168
+ "**/vendor/**",
4169
+ "**/dist/**",
4170
+ "**/build/**",
4171
+ "**/.git/**"
4172
+ ]
4173
+ };
4174
+ }
4175
+ /**
4176
+ * Create chokidar watcher configuration.
4177
+ */
4178
+ createWatcherConfig(patterns) {
4179
+ return {
4180
+ cwd: this.rootDir,
4181
+ ignored: patterns.exclude,
4182
+ persistent: true,
4183
+ ignoreInitial: true,
4184
+ // Don't trigger for existing files
4185
+ // Handle atomic saves from modern editors (VS Code, Sublime, etc.)
4186
+ // Editors write to temp file then rename - without this, we get unlink+add instead of change
4187
+ atomic: true,
4188
+ awaitWriteFinish: {
4189
+ stabilityThreshold: 300,
4190
+ // Reduced from 500ms for faster detection
4191
+ pollInterval: 100
4192
+ },
4193
+ // Performance optimizations
4194
+ usePolling: false,
4195
+ interval: 100,
4196
+ binaryInterval: 300
4197
+ };
4198
+ }
4199
+ /**
4200
+ * Register event handlers on the watcher.
4201
+ */
4202
+ registerEventHandlers() {
4203
+ if (!this.watcher) {
4204
+ return;
4205
+ }
4206
+ this.watcher.on("add", (filepath) => this.handleChange("add", filepath)).on("change", (filepath) => this.handleChange("change", filepath)).on("unlink", (filepath) => this.handleChange("unlink", filepath)).on("error", (error) => {
4207
+ console.error(`[Lien] File watcher error: ${error}`);
4208
+ });
4209
+ }
4210
+ /**
4211
+ * Wait for watcher to be ready with timeout fallback.
4212
+ */
4213
+ async waitForReady() {
4214
+ if (!this.watcher) {
4215
+ return;
4216
+ }
4217
+ let readyFired = false;
4218
+ await Promise.race([
4219
+ new Promise((resolve) => {
4220
+ const readyHandler = () => {
4221
+ readyFired = true;
4222
+ resolve();
4223
+ };
4224
+ this.watcher.once("ready", readyHandler);
4225
+ }),
4226
+ new Promise((resolve) => {
4227
+ setTimeout(() => {
4228
+ if (!readyFired) {
4229
+ resolve();
4230
+ }
4231
+ }, 1e3);
4232
+ })
4233
+ ]);
4234
+ }
4235
+ /**
4236
+ * Starts watching files for changes.
4237
+ *
4238
+ * @param handler - Callback function called when files change
4239
+ */
4240
+ async start(handler) {
4241
+ if (this.watcher) {
4242
+ throw new Error("File watcher is already running");
4243
+ }
4244
+ this.onChangeHandler = handler;
4245
+ const patterns = await this.getWatchPatterns();
4246
+ this.watcher = chokidar.watch(patterns.include, this.createWatcherConfig(patterns));
4247
+ this.registerEventHandlers();
4248
+ await this.waitForReady();
4249
+ }
4250
+ /**
4251
+ * Handles a file change event with debouncing.
4252
+ * Debouncing prevents rapid reindexing when files are saved multiple times quickly.
4253
+ */
4254
+ handleChange(type, filepath) {
4255
+ const existingTimer = this.debounceTimers.get(filepath);
4256
+ if (existingTimer) {
4257
+ clearTimeout(existingTimer);
4258
+ }
4259
+ const timer = setTimeout(() => {
4260
+ this.debounceTimers.delete(filepath);
4261
+ if (this.onChangeHandler) {
4262
+ const absolutePath = path3.isAbsolute(filepath) ? filepath : path3.join(this.rootDir, filepath);
4263
+ try {
4264
+ const result = this.onChangeHandler({
4265
+ type,
4266
+ filepath: absolutePath
4267
+ });
4268
+ if (result instanceof Promise) {
4269
+ result.catch((error) => {
4270
+ console.error(`[Lien] Error handling file change: ${error}`);
4271
+ });
4272
+ }
4273
+ } catch (error) {
4274
+ console.error(`[Lien] Error handling file change: ${error}`);
4275
+ }
4276
+ }
4277
+ }, DEFAULT_DEBOUNCE_MS2);
4278
+ this.debounceTimers.set(filepath, timer);
4279
+ }
4280
+ /**
4281
+ * Stops the file watcher and cleans up resources.
4282
+ */
4283
+ async stop() {
4284
+ if (!this.watcher) {
4285
+ return;
4286
+ }
4287
+ for (const timer of this.debounceTimers.values()) {
4288
+ clearTimeout(timer);
4289
+ }
4290
+ this.debounceTimers.clear();
4291
+ await this.watcher.close();
4292
+ this.watcher = null;
4293
+ this.onChangeHandler = null;
4294
+ }
4295
+ /**
4296
+ * Gets the list of files currently being watched.
4297
+ */
4298
+ getWatchedFiles() {
4299
+ if (!this.watcher) {
4300
+ return [];
4301
+ }
4302
+ const watched = this.watcher.getWatched();
4303
+ const files = [];
4304
+ for (const [dir, filenames] of Object.entries(watched)) {
4305
+ for (const filename of filenames) {
4306
+ files.push(`${dir}/${filename}`);
4307
+ }
4308
+ }
4309
+ return files;
4310
+ }
4311
+ /**
4312
+ * Checks if the watcher is currently running.
4313
+ */
4314
+ isRunning() {
4315
+ return this.watcher !== null;
4316
+ }
4317
+ };
4318
+
4319
+ // src/mcp/server-config.ts
4274
4320
  import {
4275
4321
  CallToolRequestSchema,
4276
4322
  ListToolsRequestSchema
4277
4323
  } from "@modelcontextprotocol/sdk/types.js";
4278
- import { createRequire as createRequire3 } from "module";
4279
- import { fileURLToPath as fileURLToPath3 } from "url";
4280
- import { dirname as dirname2, join as join2 } from "path";
4281
4324
 
4282
4325
  // src/mcp/utils/zod-to-json-schema.ts
4283
4326
  import { zodToJsonSchema } from "zod-to-json-schema";
@@ -8340,7 +8383,13 @@ var SemanticSearchSchema = external_exports.object({
8340
8383
  ),
8341
8384
  limit: external_exports.number().int().min(1, "Limit must be at least 1").max(50, "Limit cannot exceed 50").default(5).describe(
8342
8385
  "Number of results to return.\n\nDefault: 5\nIncrease to 10-15 for broad exploration."
8343
- )
8386
+ ),
8387
+ crossRepo: external_exports.boolean().default(false).describe(
8388
+ "If true, search across all repos in the organization (requires Qdrant backend).\n\nDefault: false (single-repo search)\nWhen enabled, results are grouped by repository."
8389
+ ),
8390
+ repoIds: external_exports.array(external_exports.string()).optional().describe(
8391
+ "Optional: Filter to specific repos when crossRepo=true.\n\nIf provided, only searches within the specified repositories.\nIf omitted and crossRepo=true, searches all repos in the organization."
8392
+ )
8344
8393
  });
8345
8394
 
8346
8395
  // src/mcp/schemas/similarity.schema.ts
@@ -8383,6 +8432,9 @@ var GetDependentsSchema = external_exports.object({
8383
8432
  ),
8384
8433
  depth: external_exports.number().int().min(1).max(1).default(1).describe(
8385
8434
  "Depth of transitive dependencies. Only depth=1 (direct dependents) is currently supported.\n\n1 = Direct dependents only"
8435
+ ),
8436
+ crossRepo: external_exports.boolean().default(false).describe(
8437
+ "If true, find dependents across all repos in the organization (requires Qdrant backend).\n\nDefault: false (single-repo search)\nWhen enabled, results are grouped by repository."
8386
8438
  )
8387
8439
  });
8388
8440
 
@@ -8396,6 +8448,12 @@ var GetComplexitySchema = external_exports.object({
8396
8448
  ),
8397
8449
  threshold: external_exports.number().int().min(1, "Threshold must be at least 1").optional().describe(
8398
8450
  "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."
8451
+ ),
8452
+ crossRepo: external_exports.boolean().default(false).describe(
8453
+ "If true, analyze complexity across all repos in the organization (requires Qdrant backend).\n\nDefault: false (single-repo analysis)\nWhen enabled, results are aggregated by repository."
8454
+ ),
8455
+ repoIds: external_exports.array(external_exports.string()).optional().describe(
8456
+ "Optional: Filter to specific repos when crossRepo=true.\n\nIf provided, only analyzes the specified repositories.\nIf omitted and crossRepo=true, analyzes all repos in the organization."
8399
8457
  )
8400
8458
  });
8401
8459
 
@@ -8575,20 +8633,46 @@ function wrapToolHandler(schema, handler) {
8575
8633
  }
8576
8634
 
8577
8635
  // src/mcp/handlers/semantic-search.ts
8636
+ import { QdrantDB } from "@liendev/core";
8637
+ function groupResultsByRepo(results) {
8638
+ const grouped = {};
8639
+ for (const result of results) {
8640
+ const repoId = result.metadata.repoId || "unknown";
8641
+ if (!grouped[repoId]) {
8642
+ grouped[repoId] = [];
8643
+ }
8644
+ grouped[repoId].push(result);
8645
+ }
8646
+ return grouped;
8647
+ }
8578
8648
  async function handleSemanticSearch(args, ctx) {
8579
8649
  const { vectorDB, embeddings, log, checkAndReconnect, getIndexMetadata } = ctx;
8580
8650
  return await wrapToolHandler(
8581
8651
  SemanticSearchSchema,
8582
8652
  async (validatedArgs) => {
8583
- log(`Searching for: "${validatedArgs.query}"`);
8653
+ const { crossRepo, repoIds, query, limit } = validatedArgs;
8654
+ log(`Searching for: "${query}"${crossRepo ? " (cross-repo)" : ""}`);
8584
8655
  await checkAndReconnect();
8585
- const queryEmbedding = await embeddings.embed(validatedArgs.query);
8586
- const results = await vectorDB.search(queryEmbedding, validatedArgs.limit, validatedArgs.query);
8587
- log(`Found ${results.length} results`);
8588
- return {
8656
+ const queryEmbedding = await embeddings.embed(query);
8657
+ let results;
8658
+ if (crossRepo && vectorDB instanceof QdrantDB) {
8659
+ results = await vectorDB.searchCrossRepo(queryEmbedding, limit, repoIds);
8660
+ log(`Found ${results.length} results across ${Object.keys(groupResultsByRepo(results)).length} repos`);
8661
+ } else {
8662
+ if (crossRepo) {
8663
+ log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo search.");
8664
+ }
8665
+ results = await vectorDB.search(queryEmbedding, limit, query);
8666
+ log(`Found ${results.length} results`);
8667
+ }
8668
+ const response = {
8589
8669
  indexInfo: getIndexMetadata(),
8590
8670
  results
8591
8671
  };
8672
+ if (crossRepo && vectorDB instanceof QdrantDB) {
8673
+ response.groupedByRepo = groupResultsByRepo(results);
8674
+ }
8675
+ return response;
8592
8676
  }
8593
8677
  )(args);
8594
8678
  }
@@ -8874,14 +8958,11 @@ async function handleListFunctions(args, ctx) {
8874
8958
  }
8875
8959
 
8876
8960
  // src/mcp/handlers/get-dependents.ts
8877
- var DEPENDENT_COUNT_THRESHOLDS = {
8878
- LOW: 5,
8879
- // Few dependents, safe to change
8880
- MEDIUM: 15,
8881
- // Moderate impact, review dependents
8882
- HIGH: 30
8883
- // High impact, careful planning needed
8884
- };
8961
+ import { QdrantDB as QdrantDB3 } from "@liendev/core";
8962
+
8963
+ // src/mcp/handlers/dependency-analyzer.ts
8964
+ import { QdrantDB as QdrantDB2 } from "@liendev/core";
8965
+ var SCAN_LIMIT2 = 1e4;
8885
8966
  var COMPLEXITY_THRESHOLDS = {
8886
8967
  HIGH_COMPLEXITY_DEPENDENT: 10,
8887
8968
  // Individual file is complex
@@ -8898,8 +8979,52 @@ var COMPLEXITY_THRESHOLDS = {
8898
8979
  MEDIUM_MAX: 15
8899
8980
  // Occasional branching
8900
8981
  };
8901
- var SCAN_LIMIT2 = 1e4;
8902
- var RISK_ORDER = { low: 0, medium: 1, high: 2, critical: 3 };
8982
+ async function findDependents(vectorDB, filepath, crossRepo, log) {
8983
+ let allChunks;
8984
+ if (crossRepo && vectorDB instanceof QdrantDB2) {
8985
+ allChunks = await vectorDB.scanCrossRepo({ limit: SCAN_LIMIT2 });
8986
+ } else {
8987
+ if (crossRepo) {
8988
+ log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo search.", "warning");
8989
+ }
8990
+ allChunks = await vectorDB.scanWithFilter({ limit: SCAN_LIMIT2 });
8991
+ }
8992
+ const hitLimit = allChunks.length === SCAN_LIMIT2;
8993
+ if (hitLimit) {
8994
+ log(`Scanned ${SCAN_LIMIT2} chunks (limit reached). Results may be incomplete.`, "warning");
8995
+ }
8996
+ log(`Scanning ${allChunks.length} chunks for imports...`);
8997
+ const workspaceRoot = process.cwd().replace(/\\/g, "/");
8998
+ const pathCache = /* @__PURE__ */ new Map();
8999
+ const normalizePathCached = (path6) => {
9000
+ if (!pathCache.has(path6)) pathCache.set(path6, normalizePath(path6, workspaceRoot));
9001
+ return pathCache.get(path6);
9002
+ };
9003
+ const importIndex = buildImportIndex(allChunks, normalizePathCached);
9004
+ const normalizedTarget = normalizePathCached(filepath);
9005
+ const dependentChunks = findDependentChunks(importIndex, normalizedTarget);
9006
+ const chunksByFile = /* @__PURE__ */ new Map();
9007
+ for (const chunk of dependentChunks) {
9008
+ const canonical = getCanonicalPath(chunk.metadata.file, workspaceRoot);
9009
+ const existing = chunksByFile.get(canonical) || [];
9010
+ existing.push(chunk);
9011
+ chunksByFile.set(canonical, existing);
9012
+ }
9013
+ const fileComplexities = calculateFileComplexities(chunksByFile);
9014
+ const complexityMetrics = calculateOverallComplexityMetrics(fileComplexities);
9015
+ const uniqueFiles = Array.from(chunksByFile.keys()).map((filepath2) => ({
9016
+ filepath: filepath2,
9017
+ isTestFile: isTestFile(filepath2)
9018
+ }));
9019
+ return {
9020
+ dependents: uniqueFiles,
9021
+ chunksByFile,
9022
+ fileComplexities,
9023
+ complexityMetrics,
9024
+ hitLimit,
9025
+ allChunks
9026
+ };
9027
+ }
8903
9028
  function buildImportIndex(allChunks, normalizePathCached) {
8904
9029
  const importIndex = /* @__PURE__ */ new Map();
8905
9030
  for (const chunk of allChunks) {
@@ -8992,65 +9117,82 @@ function calculateComplexityRiskBoost(avgComplexity, maxComplexity) {
8992
9117
  return "low";
8993
9118
  }
8994
9119
  function calculateRiskLevel(dependentCount, complexityRiskBoost) {
9120
+ const DEPENDENT_COUNT_THRESHOLDS = {
9121
+ LOW: 5,
9122
+ MEDIUM: 15,
9123
+ HIGH: 30
9124
+ };
9125
+ const RISK_ORDER = { low: 0, medium: 1, high: 2, critical: 3 };
8995
9126
  let riskLevel = dependentCount === 0 ? "low" : dependentCount <= DEPENDENT_COUNT_THRESHOLDS.LOW ? "low" : dependentCount <= DEPENDENT_COUNT_THRESHOLDS.MEDIUM ? "medium" : dependentCount <= DEPENDENT_COUNT_THRESHOLDS.HIGH ? "high" : "critical";
8996
9127
  if (RISK_ORDER[complexityRiskBoost] > RISK_ORDER[riskLevel]) {
8997
9128
  riskLevel = complexityRiskBoost;
8998
9129
  }
8999
9130
  return riskLevel;
9000
9131
  }
9132
+ function groupDependentsByRepo(dependents, chunks) {
9133
+ const repoMap = /* @__PURE__ */ new Map();
9134
+ for (const chunk of chunks) {
9135
+ const repoId = chunk.metadata.repoId || "unknown";
9136
+ const filepath = chunk.metadata.file;
9137
+ if (!repoMap.has(repoId)) {
9138
+ repoMap.set(repoId, /* @__PURE__ */ new Set());
9139
+ }
9140
+ repoMap.get(repoId).add(filepath);
9141
+ }
9142
+ const grouped = {};
9143
+ for (const dependent of dependents) {
9144
+ let foundRepo = "unknown";
9145
+ for (const [repoId, files] of repoMap.entries()) {
9146
+ if (files.has(dependent.filepath)) {
9147
+ foundRepo = repoId;
9148
+ break;
9149
+ }
9150
+ }
9151
+ if (!grouped[foundRepo]) {
9152
+ grouped[foundRepo] = [];
9153
+ }
9154
+ grouped[foundRepo].push(dependent);
9155
+ }
9156
+ return grouped;
9157
+ }
9158
+
9159
+ // src/mcp/handlers/get-dependents.ts
9001
9160
  async function handleGetDependents(args, ctx) {
9002
9161
  const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
9003
9162
  return await wrapToolHandler(
9004
9163
  GetDependentsSchema,
9005
9164
  async (validatedArgs) => {
9006
- log(`Finding dependents of: ${validatedArgs.filepath}`);
9165
+ const { crossRepo, filepath } = validatedArgs;
9166
+ log(`Finding dependents of: ${filepath}${crossRepo ? " (cross-repo)" : ""}`);
9007
9167
  await checkAndReconnect();
9008
- const allChunks = await vectorDB.scanWithFilter({ limit: SCAN_LIMIT2 });
9009
- const hitLimit = allChunks.length === SCAN_LIMIT2;
9010
- if (hitLimit) {
9011
- log(`Scanned ${SCAN_LIMIT2} chunks (limit reached). Results may be incomplete.`, "warning");
9012
- }
9013
- log(`Scanning ${allChunks.length} chunks for imports...`);
9014
- const workspaceRoot = process.cwd().replace(/\\/g, "/");
9015
- const pathCache = /* @__PURE__ */ new Map();
9016
- const normalizePathCached = (path6) => {
9017
- if (!pathCache.has(path6)) pathCache.set(path6, normalizePath(path6, workspaceRoot));
9018
- return pathCache.get(path6);
9019
- };
9020
- const importIndex = buildImportIndex(allChunks, normalizePathCached);
9021
- const normalizedTarget = normalizePathCached(validatedArgs.filepath);
9022
- const dependentChunks = findDependentChunks(importIndex, normalizedTarget);
9023
- const chunksByFile = /* @__PURE__ */ new Map();
9024
- for (const chunk of dependentChunks) {
9025
- const canonical = getCanonicalPath(chunk.metadata.file, workspaceRoot);
9026
- const existing = chunksByFile.get(canonical) || [];
9027
- existing.push(chunk);
9028
- chunksByFile.set(canonical, existing);
9029
- }
9030
- const fileComplexities = calculateFileComplexities(chunksByFile);
9031
- const complexityMetrics = calculateOverallComplexityMetrics(fileComplexities);
9032
- const uniqueFiles = Array.from(chunksByFile.keys()).map((filepath) => ({
9033
- filepath,
9034
- isTestFile: isTestFile(filepath)
9035
- }));
9036
- const riskLevel = calculateRiskLevel(uniqueFiles.length, complexityMetrics.complexityRiskBoost);
9037
- log(`Found ${uniqueFiles.length} dependent files (risk: ${riskLevel}${complexityMetrics.filesWithComplexityData > 0 ? ", complexity-boosted" : ""})`);
9038
- return {
9168
+ const analysis = await findDependents(vectorDB, filepath, crossRepo ?? false, log);
9169
+ const riskLevel = calculateRiskLevel(
9170
+ analysis.dependents.length,
9171
+ analysis.complexityMetrics.complexityRiskBoost
9172
+ );
9173
+ log(
9174
+ `Found ${analysis.dependents.length} dependent files (risk: ${riskLevel}${analysis.complexityMetrics.filesWithComplexityData > 0 ? ", complexity-boosted" : ""})`
9175
+ );
9176
+ const response = {
9039
9177
  indexInfo: getIndexMetadata(),
9040
9178
  filepath: validatedArgs.filepath,
9041
- dependentCount: uniqueFiles.length,
9179
+ dependentCount: analysis.dependents.length,
9042
9180
  riskLevel,
9043
- dependents: uniqueFiles,
9044
- complexityMetrics,
9045
- note: hitLimit ? `Warning: Scanned ${SCAN_LIMIT2} chunks (limit reached). Results may be incomplete.` : void 0
9181
+ dependents: analysis.dependents,
9182
+ complexityMetrics: analysis.complexityMetrics,
9183
+ note: analysis.hitLimit ? `Warning: Scanned 10000 chunks (limit reached). Results may be incomplete.` : void 0
9046
9184
  };
9185
+ if (crossRepo && vectorDB instanceof QdrantDB3) {
9186
+ response.groupedByRepo = groupDependentsByRepo(analysis.dependents, analysis.allChunks);
9187
+ }
9188
+ return response;
9047
9189
  }
9048
9190
  )(args);
9049
9191
  }
9050
9192
 
9051
9193
  // src/mcp/handlers/get-complexity.ts
9052
9194
  var import_collect = __toESM(require_dist(), 1);
9053
- import { ComplexityAnalyzer } from "@liendev/core";
9195
+ import { ComplexityAnalyzer, QdrantDB as QdrantDB4 } from "@liendev/core";
9054
9196
  function transformViolation(v, fileData) {
9055
9197
  return {
9056
9198
  filepath: v.filepath,
@@ -9069,23 +9211,48 @@ function transformViolation(v, fileData) {
9069
9211
  ...v.halsteadDetails && { halsteadDetails: v.halsteadDetails }
9070
9212
  };
9071
9213
  }
9214
+ function groupViolationsByRepo(violations, allChunks) {
9215
+ const fileToRepo = /* @__PURE__ */ new Map();
9216
+ for (const chunk of allChunks) {
9217
+ const repoId = chunk.metadata.repoId || "unknown";
9218
+ fileToRepo.set(chunk.metadata.file, repoId);
9219
+ }
9220
+ const grouped = {};
9221
+ for (const violation of violations) {
9222
+ const repoId = fileToRepo.get(violation.filepath) || "unknown";
9223
+ if (!grouped[repoId]) {
9224
+ grouped[repoId] = [];
9225
+ }
9226
+ grouped[repoId].push(violation);
9227
+ }
9228
+ return grouped;
9229
+ }
9072
9230
  async function handleGetComplexity(args, ctx) {
9073
- const { vectorDB, config, log, checkAndReconnect, getIndexMetadata } = ctx;
9231
+ const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
9074
9232
  return await wrapToolHandler(
9075
9233
  GetComplexitySchema,
9076
9234
  async (validatedArgs) => {
9077
- log("Analyzing complexity...");
9235
+ const { crossRepo, repoIds, files, top, threshold } = validatedArgs;
9236
+ log(`Analyzing complexity${crossRepo ? " (cross-repo)" : ""}...`);
9078
9237
  await checkAndReconnect();
9079
- const analyzer = new ComplexityAnalyzer(vectorDB, config);
9080
- const report = await analyzer.analyze(validatedArgs.files);
9238
+ let allChunks = [];
9239
+ if (crossRepo && vectorDB instanceof QdrantDB4) {
9240
+ allChunks = await vectorDB.scanCrossRepo({
9241
+ limit: 1e5,
9242
+ repoIds
9243
+ });
9244
+ log(`Scanned ${allChunks.length} chunks across repos`);
9245
+ }
9246
+ const analyzer = new ComplexityAnalyzer(vectorDB);
9247
+ const report = await analyzer.analyze(files, crossRepo && vectorDB instanceof QdrantDB4 ? crossRepo : false, repoIds);
9081
9248
  log(`Analyzed ${report.summary.filesAnalyzed} files`);
9082
9249
  const allViolations = (0, import_collect.default)(Object.entries(report.files)).flatMap(
9083
9250
  ([, fileData]) => fileData.violations.map((v) => transformViolation(v, fileData))
9084
9251
  ).sortByDesc("complexity").all();
9085
- const violations = validatedArgs.threshold !== void 0 ? allViolations.filter((v) => v.complexity >= validatedArgs.threshold) : allViolations;
9086
- const topViolations = violations.slice(0, validatedArgs.top);
9252
+ const violations = threshold !== void 0 ? allViolations.filter((v) => v.complexity >= threshold) : allViolations;
9253
+ const topViolations = violations.slice(0, top);
9087
9254
  const bySeverity = (0, import_collect.default)(violations).countBy("severity").all();
9088
- return {
9255
+ const response = {
9089
9256
  indexInfo: getIndexMetadata(),
9090
9257
  summary: {
9091
9258
  filesAnalyzed: report.summary.filesAnalyzed,
@@ -9099,6 +9266,12 @@ async function handleGetComplexity(args, ctx) {
9099
9266
  },
9100
9267
  violations: topViolations
9101
9268
  };
9269
+ if (crossRepo && vectorDB instanceof QdrantDB4 && allChunks.length > 0) {
9270
+ response.groupedByRepo = groupViolationsByRepo(topViolations, allChunks);
9271
+ } else if (crossRepo) {
9272
+ log("Warning: crossRepo=true requires Qdrant backend. Falling back to single-repo analysis.", "warning");
9273
+ }
9274
+ return response;
9102
9275
  }
9103
9276
  )(args);
9104
9277
  }
@@ -9113,168 +9286,91 @@ var toolHandlers = {
9113
9286
  "get_complexity": handleGetComplexity
9114
9287
  };
9115
9288
 
9116
- // src/mcp/server.ts
9117
- import {
9118
- VectorDB,
9119
- LocalEmbeddings,
9120
- GitStateTracker,
9121
- indexMultipleFiles,
9122
- indexSingleFile,
9123
- configService as configService2,
9124
- ManifestManager,
9125
- isGitAvailable,
9126
- isGitRepo as isGitRepo2,
9127
- VERSION_CHECK_INTERVAL_MS,
9128
- LienError as LienError2,
9129
- LienErrorCode as LienErrorCode2
9130
- } from "@liendev/core";
9131
-
9132
- // src/watcher/index.ts
9133
- import chokidar from "chokidar";
9134
- import path3 from "path";
9135
- import { isLegacyConfig, isModernConfig as isModernConfig2 } from "@liendev/core";
9136
- var FileWatcher = class {
9137
- watcher = null;
9138
- debounceTimers = /* @__PURE__ */ new Map();
9139
- config;
9140
- rootDir;
9141
- onChangeHandler = null;
9142
- constructor(rootDir, config) {
9143
- this.rootDir = rootDir;
9144
- this.config = config;
9145
- }
9146
- /**
9147
- * Starts watching files for changes.
9148
- *
9149
- * @param handler - Callback function called when files change
9150
- */
9151
- async start(handler) {
9152
- if (this.watcher) {
9153
- throw new Error("File watcher is already running");
9154
- }
9155
- this.onChangeHandler = handler;
9156
- let includePatterns;
9157
- let excludePatterns;
9158
- if (isLegacyConfig(this.config)) {
9159
- includePatterns = this.config.indexing.include;
9160
- excludePatterns = this.config.indexing.exclude;
9161
- } else if (isModernConfig2(this.config)) {
9162
- includePatterns = this.config.frameworks.flatMap((f) => f.config.include);
9163
- excludePatterns = this.config.frameworks.flatMap((f) => f.config.exclude);
9164
- } else {
9165
- includePatterns = ["**/*"];
9166
- excludePatterns = [];
9167
- }
9168
- this.watcher = chokidar.watch(includePatterns, {
9169
- cwd: this.rootDir,
9170
- ignored: excludePatterns,
9171
- persistent: true,
9172
- ignoreInitial: true,
9173
- // Don't trigger for existing files
9174
- // Handle atomic saves from modern editors (VS Code, Sublime, etc.)
9175
- // Editors write to temp file then rename - without this, we get unlink+add instead of change
9176
- atomic: true,
9177
- awaitWriteFinish: {
9178
- stabilityThreshold: 300,
9179
- // Reduced from 500ms for faster detection
9180
- pollInterval: 100
9181
- },
9182
- // Performance optimizations
9183
- usePolling: false,
9184
- interval: 100,
9185
- binaryInterval: 300
9186
- });
9187
- this.watcher.on("add", (filepath) => this.handleChange("add", filepath)).on("change", (filepath) => this.handleChange("change", filepath)).on("unlink", (filepath) => this.handleChange("unlink", filepath)).on("error", (error) => {
9188
- console.error(`[Lien] File watcher error: ${error}`);
9189
- });
9190
- await new Promise((resolve) => {
9191
- this.watcher.on("ready", () => {
9192
- resolve();
9193
- });
9194
- });
9195
- }
9196
- /**
9197
- * Handles a file change event with debouncing.
9198
- * Debouncing prevents rapid reindexing when files are saved multiple times quickly.
9199
- */
9200
- handleChange(type, filepath) {
9201
- const existingTimer = this.debounceTimers.get(filepath);
9202
- if (existingTimer) {
9203
- clearTimeout(existingTimer);
9204
- }
9205
- const timer = setTimeout(() => {
9206
- this.debounceTimers.delete(filepath);
9207
- if (this.onChangeHandler) {
9208
- const absolutePath = path3.isAbsolute(filepath) ? filepath : path3.join(this.rootDir, filepath);
9209
- try {
9210
- const result = this.onChangeHandler({
9211
- type,
9212
- filepath: absolutePath
9213
- });
9214
- if (result instanceof Promise) {
9215
- result.catch((error) => {
9216
- console.error(`[Lien] Error handling file change: ${error}`);
9217
- });
9218
- }
9219
- } catch (error) {
9220
- console.error(`[Lien] Error handling file change: ${error}`);
9221
- }
9222
- }
9223
- }, this.config.fileWatching.debounceMs);
9224
- this.debounceTimers.set(filepath, timer);
9225
- }
9226
- /**
9227
- * Stops the file watcher and cleans up resources.
9228
- */
9229
- async stop() {
9230
- if (!this.watcher) {
9231
- return;
9232
- }
9233
- for (const timer of this.debounceTimers.values()) {
9234
- clearTimeout(timer);
9289
+ // src/mcp/server-config.ts
9290
+ import { LienError as LienError2, LienErrorCode as LienErrorCode2 } from "@liendev/core";
9291
+ function createMCPServerConfig(name, version) {
9292
+ return {
9293
+ name,
9294
+ version,
9295
+ capabilities: {
9296
+ tools: {},
9297
+ logging: {}
9235
9298
  }
9236
- this.debounceTimers.clear();
9237
- await this.watcher.close();
9238
- this.watcher = null;
9239
- this.onChangeHandler = null;
9240
- }
9241
- /**
9242
- * Gets the list of files currently being watched.
9243
- */
9244
- getWatchedFiles() {
9245
- if (!this.watcher) {
9246
- return [];
9299
+ };
9300
+ }
9301
+ function registerMCPHandlers(server, toolContext, log) {
9302
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
9303
+ return { tools };
9304
+ });
9305
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
9306
+ const { name, arguments: args } = request.params;
9307
+ log(`Handling tool call: ${name}`);
9308
+ const handler = toolHandlers[name];
9309
+ if (!handler) {
9310
+ const error = new LienError2(
9311
+ `Unknown tool: ${name}`,
9312
+ LienErrorCode2.INVALID_INPUT,
9313
+ { requestedTool: name, availableTools: tools.map((t) => t.name) },
9314
+ "medium",
9315
+ false,
9316
+ false
9317
+ );
9318
+ return {
9319
+ isError: true,
9320
+ content: [{ type: "text", text: JSON.stringify(error.toJSON(), null, 2) }]
9321
+ };
9247
9322
  }
9248
- const watched = this.watcher.getWatched();
9249
- const files = [];
9250
- for (const [dir, filenames] of Object.entries(watched)) {
9251
- for (const filename of filenames) {
9252
- files.push(`${dir}/${filename}`);
9323
+ try {
9324
+ return await handler(args, toolContext);
9325
+ } catch (error) {
9326
+ if (error instanceof LienError2) {
9327
+ return {
9328
+ isError: true,
9329
+ content: [{ type: "text", text: JSON.stringify(error.toJSON(), null, 2) }]
9330
+ };
9253
9331
  }
9332
+ console.error(`Unexpected error handling tool call ${name}:`, error);
9333
+ return {
9334
+ isError: true,
9335
+ content: [
9336
+ {
9337
+ type: "text",
9338
+ text: JSON.stringify(
9339
+ {
9340
+ error: error instanceof Error ? error.message : "Unknown error",
9341
+ code: LienErrorCode2.INTERNAL_ERROR,
9342
+ tool: name
9343
+ },
9344
+ null,
9345
+ 2
9346
+ )
9347
+ }
9348
+ ]
9349
+ };
9254
9350
  }
9255
- return files;
9256
- }
9257
- /**
9258
- * Checks if the watcher is currently running.
9259
- */
9260
- isRunning() {
9261
- return this.watcher !== null;
9262
- }
9263
- };
9351
+ });
9352
+ }
9264
9353
 
9265
9354
  // src/mcp/server.ts
9266
9355
  var __filename3 = fileURLToPath3(import.meta.url);
9267
9356
  var __dirname3 = dirname2(__filename3);
9268
- var require4 = createRequire3(import.meta.url);
9357
+ var require3 = createRequire2(import.meta.url);
9269
9358
  var packageJson2;
9270
9359
  try {
9271
- packageJson2 = require4(join2(__dirname3, "../package.json"));
9360
+ packageJson2 = require3(join2(__dirname3, "../package.json"));
9272
9361
  } catch {
9273
- packageJson2 = require4(join2(__dirname3, "../../package.json"));
9362
+ packageJson2 = require3(join2(__dirname3, "../../package.json"));
9274
9363
  }
9275
9364
  async function initializeDatabase(rootDir, log) {
9276
9365
  const embeddings = new LocalEmbeddings();
9277
- const vectorDB = new VectorDB(rootDir);
9366
+ log("Creating vector database...");
9367
+ const vectorDB = await createVectorDB(rootDir);
9368
+ if (!vectorDB) {
9369
+ throw new Error("createVectorDB returned undefined or null");
9370
+ }
9371
+ if (typeof vectorDB.initialize !== "function") {
9372
+ throw new Error(`Invalid vectorDB instance: ${vectorDB.constructor?.name || "unknown"}. Expected VectorDBInterface but got: ${JSON.stringify(Object.keys(vectorDB))}`);
9373
+ }
9278
9374
  log("Loading embedding model...");
9279
9375
  await embeddings.initialize();
9280
9376
  log("Loading vector database...");
@@ -9282,9 +9378,9 @@ async function initializeDatabase(rootDir, log) {
9282
9378
  log("Embeddings and vector DB ready");
9283
9379
  return { embeddings, vectorDB };
9284
9380
  }
9285
- async function handleAutoIndexing(vectorDB, config, rootDir, log) {
9381
+ async function handleAutoIndexing(vectorDB, rootDir, log) {
9286
9382
  const hasIndex = await vectorDB.hasData();
9287
- if (!hasIndex && config.mcp.autoIndexOnFirstRun) {
9383
+ if (!hasIndex) {
9288
9384
  log("\u{1F4E6} No index found - running initial indexing...");
9289
9385
  log("\u23F1\uFE0F This may take 5-20 minutes depending on project size");
9290
9386
  try {
@@ -9295,16 +9391,9 @@ async function handleAutoIndexing(vectorDB, config, rootDir, log) {
9295
9391
  log(`\u26A0\uFE0F Initial indexing failed: ${error}`, "warning");
9296
9392
  log("You can manually run: lien index", "warning");
9297
9393
  }
9298
- } else if (!hasIndex) {
9299
- log("\u26A0\uFE0F No index found. Auto-indexing is disabled in config.", "warning");
9300
- log('Run "lien index" to index your codebase.', "warning");
9301
9394
  }
9302
9395
  }
9303
- async function setupGitDetection(config, rootDir, vectorDB, embeddings, verbose, log) {
9304
- if (!config.gitDetection.enabled) {
9305
- log("Git detection disabled by configuration");
9306
- return { gitTracker: null, gitPollInterval: null };
9307
- }
9396
+ async function setupGitDetection(rootDir, vectorDB, embeddings, verbose, log) {
9308
9397
  const gitAvailable = await isGitAvailable();
9309
9398
  const isRepo = await isGitRepo2(rootDir);
9310
9399
  if (!gitAvailable) {
@@ -9322,7 +9411,7 @@ async function setupGitDetection(config, rootDir, vectorDB, embeddings, verbose,
9322
9411
  const changedFiles = await gitTracker.initialize();
9323
9412
  if (changedFiles && changedFiles.length > 0) {
9324
9413
  log(`\u{1F33F} Git changes detected: ${changedFiles.length} files changed`);
9325
- const count = await indexMultipleFiles(changedFiles, vectorDB, embeddings, config, { verbose });
9414
+ const count = await indexMultipleFiles(changedFiles, vectorDB, embeddings, { verbose });
9326
9415
  log(`\u2713 Reindexed ${count} files`);
9327
9416
  } else {
9328
9417
  log("\u2713 Index is up to date with git state");
@@ -9330,25 +9419,28 @@ async function setupGitDetection(config, rootDir, vectorDB, embeddings, verbose,
9330
9419
  } catch (error) {
9331
9420
  log(`Failed to check git state on startup: ${error}`, "warning");
9332
9421
  }
9333
- log(`\u2713 Git detection enabled (checking every ${config.gitDetection.pollIntervalMs / 1e3}s)`);
9422
+ const pollIntervalSeconds = DEFAULT_GIT_POLL_INTERVAL_MS2 / 1e3;
9423
+ log(`\u2713 Git detection enabled (checking every ${pollIntervalSeconds}s)`);
9334
9424
  const gitPollInterval = setInterval(async () => {
9335
9425
  try {
9336
9426
  const changedFiles = await gitTracker.detectChanges();
9337
9427
  if (changedFiles && changedFiles.length > 0) {
9338
9428
  log(`\u{1F33F} Git change detected: ${changedFiles.length} files changed`);
9339
- indexMultipleFiles(changedFiles, vectorDB, embeddings, config, { verbose }).then((count) => log(`\u2713 Background reindex complete: ${count} files`)).catch((error) => log(`Background reindex failed: ${error}`, "warning"));
9429
+ indexMultipleFiles(changedFiles, vectorDB, embeddings, { verbose }).then((count) => log(`\u2713 Background reindex complete: ${count} files`)).catch((error) => log(`Background reindex failed: ${error}`, "warning"));
9340
9430
  }
9341
9431
  } catch (error) {
9342
9432
  log(`Git detection check failed: ${error}`, "warning");
9343
9433
  }
9344
- }, config.gitDetection.pollIntervalMs);
9434
+ }, DEFAULT_GIT_POLL_INTERVAL_MS2);
9345
9435
  return { gitTracker, gitPollInterval };
9346
9436
  }
9347
- async function setupFileWatching(watch, config, rootDir, vectorDB, embeddings, verbose, log) {
9348
- const fileWatchingEnabled = watch !== void 0 ? watch : config.fileWatching.enabled;
9349
- if (!fileWatchingEnabled) return null;
9437
+ async function setupFileWatching(watch, rootDir, vectorDB, embeddings, verbose, log) {
9438
+ const fileWatchingEnabled = watch !== void 0 ? watch : true;
9439
+ if (!fileWatchingEnabled) {
9440
+ return null;
9441
+ }
9350
9442
  log("\u{1F440} Starting file watcher...");
9351
- const fileWatcher = new FileWatcher(rootDir, config);
9443
+ const fileWatcher = new FileWatcher(rootDir);
9352
9444
  try {
9353
9445
  await fileWatcher.start(async (event) => {
9354
9446
  const { type, filepath } = event;
@@ -9365,7 +9457,7 @@ async function setupFileWatching(watch, config, rootDir, vectorDB, embeddings, v
9365
9457
  } else {
9366
9458
  const action = type === "add" ? "added" : "changed";
9367
9459
  log(`\u{1F4DD} File ${action}: ${filepath}`);
9368
- indexSingleFile(filepath, vectorDB, embeddings, config, { verbose }).catch((error) => log(`Failed to reindex ${filepath}: ${error}`, "warning"));
9460
+ indexSingleFile(filepath, vectorDB, embeddings, { verbose }).catch((error) => log(`Failed to reindex ${filepath}: ${error}`, "warning"));
9369
9461
  }
9370
9462
  });
9371
9463
  log(`\u2713 File watching enabled (watching ${fileWatcher.getWatchedFiles().length} files)`);
@@ -9375,56 +9467,52 @@ async function setupFileWatching(watch, config, rootDir, vectorDB, embeddings, v
9375
9467
  return null;
9376
9468
  }
9377
9469
  }
9378
- function registerToolCallHandler(server, toolContext, log) {
9379
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
9380
- const { name, arguments: args } = request.params;
9381
- log(`Handling tool call: ${name}`);
9382
- const handler = toolHandlers[name];
9383
- if (!handler) {
9384
- const error = new LienError2(
9385
- `Unknown tool: ${name}`,
9386
- LienErrorCode2.INVALID_INPUT,
9387
- { requestedTool: name, availableTools: tools.map((t) => t.name) },
9388
- "medium",
9389
- false,
9390
- false
9391
- );
9392
- return { isError: true, content: [{ type: "text", text: JSON.stringify(error.toJSON(), null, 2) }] };
9393
- }
9470
+ function setupTransport(log) {
9471
+ const transport = new StdioServerTransport();
9472
+ transport.onclose = () => {
9473
+ log("Transport closed");
9474
+ };
9475
+ transport.onerror = (error) => {
9476
+ log(`Transport error: ${error}`);
9477
+ };
9478
+ return transport;
9479
+ }
9480
+ function setupCleanupHandlers(versionCheckInterval, gitPollInterval, fileWatcher, log) {
9481
+ return async () => {
9482
+ log("Shutting down MCP server...");
9483
+ clearInterval(versionCheckInterval);
9484
+ if (gitPollInterval) clearInterval(gitPollInterval);
9485
+ if (fileWatcher) await fileWatcher.stop();
9486
+ process.exit(0);
9487
+ };
9488
+ }
9489
+ function setupVersionChecking(vectorDB, log) {
9490
+ const checkAndReconnect = async () => {
9394
9491
  try {
9395
- return await handler(args, toolContext);
9396
- } catch (error) {
9397
- if (error instanceof LienError2) {
9398
- return { isError: true, content: [{ type: "text", text: JSON.stringify(error.toJSON(), null, 2) }] };
9492
+ if (await vectorDB.checkVersion()) {
9493
+ log("Index version changed, reconnecting...");
9494
+ await vectorDB.reconnect();
9399
9495
  }
9400
- console.error(`Unexpected error handling tool call ${name}:`, error);
9401
- return {
9402
- isError: true,
9403
- content: [{
9404
- type: "text",
9405
- text: JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error", code: LienErrorCode2.INTERNAL_ERROR, tool: name }, null, 2)
9406
- }]
9407
- };
9496
+ } catch (error) {
9497
+ log(`Version check failed: ${error}`, "warning");
9408
9498
  }
9499
+ };
9500
+ const getIndexMetadata = () => ({
9501
+ indexVersion: vectorDB.getCurrentVersion(),
9502
+ indexDate: vectorDB.getVersionDate()
9409
9503
  });
9504
+ const interval = setInterval(checkAndReconnect, VERSION_CHECK_INTERVAL_MS);
9505
+ return { interval, checkAndReconnect, getIndexMetadata };
9410
9506
  }
9411
- async function startMCPServer(options) {
9412
- const { rootDir, verbose, watch } = options;
9413
- const earlyLog = (message, level = "info") => {
9507
+ function createEarlyLog(verbose) {
9508
+ return (message, level = "info") => {
9414
9509
  if (verbose || level === "warning" || level === "error") {
9415
9510
  console.error(`[Lien MCP] [${level}] ${message}`);
9416
9511
  }
9417
9512
  };
9418
- earlyLog("Initializing MCP server...");
9419
- const { embeddings, vectorDB } = await initializeDatabase(rootDir, earlyLog).catch((error) => {
9420
- console.error(`Failed to initialize: ${error}`);
9421
- process.exit(1);
9422
- });
9423
- const server = new Server(
9424
- { name: "lien", version: packageJson2.version },
9425
- { capabilities: { tools: {}, logging: {} } }
9426
- );
9427
- const log = (message, level = "info") => {
9513
+ }
9514
+ function createMCPLog(server, verbose) {
9515
+ return (message, level = "info") => {
9428
9516
  if (verbose || level === "warning" || level === "error") {
9429
9517
  server.sendLoggingMessage({
9430
9518
  level,
@@ -9435,45 +9523,61 @@ async function startMCPServer(options) {
9435
9523
  });
9436
9524
  }
9437
9525
  };
9438
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
9439
- const checkAndReconnect = async () => {
9440
- try {
9441
- if (await vectorDB.checkVersion()) {
9442
- log("Index version changed, reconnecting...");
9443
- await vectorDB.reconnect();
9444
- }
9445
- } catch (error) {
9446
- log(`Version check failed: ${error}`, "warning");
9526
+ }
9527
+ async function initializeComponents(rootDir, earlyLog) {
9528
+ try {
9529
+ const result = await initializeDatabase(rootDir, earlyLog);
9530
+ if (!result.vectorDB || typeof result.vectorDB.initialize !== "function") {
9531
+ throw new Error(`Invalid vectorDB instance: ${result.vectorDB?.constructor?.name || "undefined"}. Missing initialize method.`);
9447
9532
  }
9448
- };
9449
- const getIndexMetadata = () => ({
9450
- indexVersion: vectorDB.getCurrentVersion(),
9451
- indexDate: vectorDB.getVersionDate()
9452
- });
9453
- const versionCheckInterval = setInterval(checkAndReconnect, VERSION_CHECK_INTERVAL_MS);
9454
- const config = await configService2.load(rootDir);
9455
- const toolContext = { vectorDB, embeddings, config, rootDir, log, checkAndReconnect, getIndexMetadata };
9456
- registerToolCallHandler(server, toolContext, log);
9457
- await handleAutoIndexing(vectorDB, config, rootDir, log);
9458
- const { gitPollInterval } = await setupGitDetection(config, rootDir, vectorDB, embeddings, verbose, log);
9459
- const fileWatcher = await setupFileWatching(watch, config, rootDir, vectorDB, embeddings, verbose, log);
9460
- const cleanup = async () => {
9461
- log("Shutting down MCP server...");
9462
- clearInterval(versionCheckInterval);
9463
- if (gitPollInterval) clearInterval(gitPollInterval);
9464
- if (fileWatcher) await fileWatcher.stop();
9465
- process.exit(0);
9466
- };
9533
+ return result;
9534
+ } catch (error) {
9535
+ console.error(`Failed to initialize: ${error}`);
9536
+ if (error instanceof Error && error.stack) {
9537
+ console.error(error.stack);
9538
+ }
9539
+ process.exit(1);
9540
+ }
9541
+ }
9542
+ function createMCPServer() {
9543
+ const serverConfig = createMCPServerConfig("lien", packageJson2.version);
9544
+ return new Server(
9545
+ { name: serverConfig.name, version: serverConfig.version },
9546
+ { capabilities: serverConfig.capabilities }
9547
+ );
9548
+ }
9549
+ async function setupAndConnectServer(server, toolContext, log, versionCheckInterval, options) {
9550
+ const { rootDir, verbose, watch } = options;
9551
+ const { vectorDB, embeddings } = toolContext;
9552
+ registerMCPHandlers(server, toolContext, log);
9553
+ await handleAutoIndexing(vectorDB, rootDir, log);
9554
+ const { gitPollInterval } = await setupGitDetection(rootDir, vectorDB, embeddings, verbose, log);
9555
+ const fileWatcher = await setupFileWatching(watch, rootDir, vectorDB, embeddings, verbose, log);
9556
+ const cleanup = setupCleanupHandlers(versionCheckInterval, gitPollInterval, fileWatcher, log);
9467
9557
  process.on("SIGINT", cleanup);
9468
9558
  process.on("SIGTERM", cleanup);
9469
- const transport = new StdioServerTransport();
9559
+ const transport = setupTransport(log);
9470
9560
  transport.onclose = () => {
9471
- log("Transport closed");
9472
9561
  cleanup().catch(() => process.exit(0));
9473
9562
  };
9474
- transport.onerror = (error) => log(`Transport error: ${error}`);
9475
- await server.connect(transport);
9476
- log("MCP server started and listening on stdio");
9563
+ try {
9564
+ await server.connect(transport);
9565
+ log("MCP server started and listening on stdio");
9566
+ } catch (error) {
9567
+ console.error(`Failed to connect MCP transport: ${error}`);
9568
+ process.exit(1);
9569
+ }
9570
+ }
9571
+ async function startMCPServer(options) {
9572
+ const { rootDir, verbose, watch } = options;
9573
+ const earlyLog = createEarlyLog(verbose);
9574
+ earlyLog("Initializing MCP server...");
9575
+ const { embeddings, vectorDB } = await initializeComponents(rootDir, earlyLog);
9576
+ const server = createMCPServer();
9577
+ const log = createMCPLog(server, verbose);
9578
+ const { interval: versionCheckInterval, checkAndReconnect, getIndexMetadata } = setupVersionChecking(vectorDB, log);
9579
+ const toolContext = { vectorDB, embeddings, rootDir, log, checkAndReconnect, getIndexMetadata };
9580
+ await setupAndConnectServer(server, toolContext, log, versionCheckInterval, { rootDir, verbose, watch });
9477
9581
  }
9478
9582
 
9479
9583
  // src/cli/serve.ts
@@ -9525,8 +9629,7 @@ async function serveCommand(options) {
9525
9629
  import chalk6 from "chalk";
9526
9630
  import fs4 from "fs";
9527
9631
  import path5 from "path";
9528
- import { VectorDB as VectorDB2 } from "@liendev/core";
9529
- import { configService as configService3 } from "@liendev/core";
9632
+ import { VectorDB } from "@liendev/core";
9530
9633
  import { ComplexityAnalyzer as ComplexityAnalyzer2 } from "@liendev/core";
9531
9634
  import { formatReport } from "@liendev/core";
9532
9635
  var VALID_FAIL_ON = ["error", "warning"];
@@ -9555,47 +9658,6 @@ function validateFilesExist(files, rootDir) {
9555
9658
  process.exit(1);
9556
9659
  }
9557
9660
  }
9558
- function parseThresholdValue(value, flagName) {
9559
- if (!value) return null;
9560
- const parsed = parseInt(value, 10);
9561
- if (isNaN(parsed)) {
9562
- console.error(chalk6.red(`Error: Invalid ${flagName} value "${value}". Must be a number`));
9563
- process.exit(1);
9564
- }
9565
- if (parsed <= 0) {
9566
- console.error(chalk6.red(`Error: Invalid ${flagName} value "${value}". Must be a positive number`));
9567
- process.exit(1);
9568
- }
9569
- return parsed;
9570
- }
9571
- function parseThresholdOverrides(options) {
9572
- const baseThreshold = parseThresholdValue(options.threshold, "--threshold");
9573
- const cyclomaticOverride = parseThresholdValue(options.cyclomaticThreshold, "--cyclomatic-threshold");
9574
- const cognitiveOverride = parseThresholdValue(options.cognitiveThreshold, "--cognitive-threshold");
9575
- return {
9576
- // Specific flags take precedence over --threshold
9577
- cyclomatic: cyclomaticOverride ?? baseThreshold,
9578
- cognitive: cognitiveOverride ?? baseThreshold
9579
- };
9580
- }
9581
- function applyThresholdOverrides(config, overrides) {
9582
- if (overrides.cyclomatic === null && overrides.cognitive === null) return;
9583
- const cfg = config;
9584
- if (!cfg.complexity) {
9585
- cfg.complexity = {
9586
- enabled: true,
9587
- thresholds: { testPaths: 15, mentalLoad: 15 }
9588
- };
9589
- } else if (!cfg.complexity.thresholds) {
9590
- cfg.complexity.thresholds = { testPaths: 15, mentalLoad: 15 };
9591
- }
9592
- if (overrides.cyclomatic !== null) {
9593
- cfg.complexity.thresholds.testPaths = overrides.cyclomatic;
9594
- }
9595
- if (overrides.cognitive !== null) {
9596
- cfg.complexity.thresholds.mentalLoad = overrides.cognitive;
9597
- }
9598
- }
9599
9661
  async function ensureIndexExists(vectorDB) {
9600
9662
  try {
9601
9663
  await vectorDB.scanWithFilter({ limit: 1 });
@@ -9611,13 +9673,14 @@ async function complexityCommand(options) {
9611
9673
  validateFailOn(options.failOn);
9612
9674
  validateFormat(options.format);
9613
9675
  validateFilesExist(options.files, rootDir);
9614
- const thresholdOverrides = parseThresholdOverrides(options);
9615
- const config = await configService3.load(rootDir);
9616
- const vectorDB = new VectorDB2(rootDir);
9676
+ if (options.threshold || options.cyclomaticThreshold || options.cognitiveThreshold) {
9677
+ console.warn(chalk6.yellow("Warning: Threshold overrides via CLI flags are not supported."));
9678
+ console.warn(chalk6.yellow("Use the MCP tool with threshold parameter for custom thresholds."));
9679
+ }
9680
+ const vectorDB = new VectorDB(rootDir);
9617
9681
  await vectorDB.initialize();
9618
9682
  await ensureIndexExists(vectorDB);
9619
- applyThresholdOverrides(config, thresholdOverrides);
9620
- const analyzer = new ComplexityAnalyzer2(vectorDB, config);
9683
+ const analyzer = new ComplexityAnalyzer2(vectorDB);
9621
9684
  const report = await analyzer.analyze(options.files);
9622
9685
  console.log(formatReport(report, options.format));
9623
9686
  if (options.failOn) {
@@ -9633,12 +9696,12 @@ async function complexityCommand(options) {
9633
9696
  // src/cli/index.ts
9634
9697
  var __filename4 = fileURLToPath4(import.meta.url);
9635
9698
  var __dirname4 = dirname3(__filename4);
9636
- var require5 = createRequire4(import.meta.url);
9699
+ var require4 = createRequire3(import.meta.url);
9637
9700
  var packageJson3;
9638
9701
  try {
9639
- packageJson3 = require5(join3(__dirname4, "../package.json"));
9702
+ packageJson3 = require4(join3(__dirname4, "../package.json"));
9640
9703
  } catch {
9641
- packageJson3 = require5(join3(__dirname4, "../../package.json"));
9704
+ packageJson3 = require4(join3(__dirname4, "../../package.json"));
9642
9705
  }
9643
9706
  var program = new Command();
9644
9707
  program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);