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