@liendev/lien 0.16.0 → 0.18.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/CURSOR_RULES_TEMPLATE.md +35 -4
- package/dist/index.js +560 -138
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -378,8 +378,8 @@ var init_errors = __esm({
|
|
|
378
378
|
});
|
|
379
379
|
|
|
380
380
|
// src/config/service.ts
|
|
381
|
-
import
|
|
382
|
-
import
|
|
381
|
+
import fs9 from "fs/promises";
|
|
382
|
+
import path9 from "path";
|
|
383
383
|
var ConfigService, configService;
|
|
384
384
|
var init_service = __esm({
|
|
385
385
|
"src/config/service.ts"() {
|
|
@@ -401,13 +401,13 @@ var init_service = __esm({
|
|
|
401
401
|
async load(rootDir = process.cwd()) {
|
|
402
402
|
const configPath = this.getConfigPath(rootDir);
|
|
403
403
|
try {
|
|
404
|
-
const configContent = await
|
|
404
|
+
const configContent = await fs9.readFile(configPath, "utf-8");
|
|
405
405
|
const userConfig = JSON.parse(configContent);
|
|
406
406
|
if (this.needsMigration(userConfig)) {
|
|
407
407
|
console.log("\u{1F504} Migrating config from v0.2.0 to v0.3.0...");
|
|
408
408
|
const result = await this.migrate(rootDir);
|
|
409
409
|
if (result.migrated && result.backupPath) {
|
|
410
|
-
const backupFilename =
|
|
410
|
+
const backupFilename = path9.basename(result.backupPath);
|
|
411
411
|
console.log(`\u2705 Migration complete! Backup saved as ${backupFilename}`);
|
|
412
412
|
console.log("\u{1F4DD} Your config now uses the framework-based structure.");
|
|
413
413
|
}
|
|
@@ -463,7 +463,7 @@ ${validation.errors.join("\n")}`,
|
|
|
463
463
|
}
|
|
464
464
|
try {
|
|
465
465
|
const configJson = JSON.stringify(config, null, 2) + "\n";
|
|
466
|
-
await
|
|
466
|
+
await fs9.writeFile(configPath, configJson, "utf-8");
|
|
467
467
|
} catch (error) {
|
|
468
468
|
throw wrapError(error, "Failed to save configuration", { path: configPath });
|
|
469
469
|
}
|
|
@@ -477,7 +477,7 @@ ${validation.errors.join("\n")}`,
|
|
|
477
477
|
async exists(rootDir = process.cwd()) {
|
|
478
478
|
const configPath = this.getConfigPath(rootDir);
|
|
479
479
|
try {
|
|
480
|
-
await
|
|
480
|
+
await fs9.access(configPath);
|
|
481
481
|
return true;
|
|
482
482
|
} catch {
|
|
483
483
|
return false;
|
|
@@ -494,7 +494,7 @@ ${validation.errors.join("\n")}`,
|
|
|
494
494
|
async migrate(rootDir = process.cwd()) {
|
|
495
495
|
const configPath = this.getConfigPath(rootDir);
|
|
496
496
|
try {
|
|
497
|
-
const configContent = await
|
|
497
|
+
const configContent = await fs9.readFile(configPath, "utf-8");
|
|
498
498
|
const oldConfig = JSON.parse(configContent);
|
|
499
499
|
if (!this.needsMigration(oldConfig)) {
|
|
500
500
|
return {
|
|
@@ -512,7 +512,7 @@ ${validation.errors.join("\n")}`,
|
|
|
512
512
|
);
|
|
513
513
|
}
|
|
514
514
|
const backupPath = `${configPath}.v0.2.0.backup`;
|
|
515
|
-
await
|
|
515
|
+
await fs9.copyFile(configPath, backupPath);
|
|
516
516
|
await this.save(rootDir, newConfig);
|
|
517
517
|
return {
|
|
518
518
|
migrated: true,
|
|
@@ -610,7 +610,7 @@ ${validation.errors.join("\n")}`,
|
|
|
610
610
|
* Get the full path to the config file
|
|
611
611
|
*/
|
|
612
612
|
getConfigPath(rootDir) {
|
|
613
|
-
return
|
|
613
|
+
return path9.join(rootDir, _ConfigService.CONFIG_FILENAME);
|
|
614
614
|
}
|
|
615
615
|
/**
|
|
616
616
|
* Validate modern (v0.3.0+) configuration
|
|
@@ -774,7 +774,7 @@ ${validation.errors.join("\n")}`,
|
|
|
774
774
|
errors.push(`frameworks[${index}] missing required field: path`);
|
|
775
775
|
} else if (typeof fw.path !== "string") {
|
|
776
776
|
errors.push(`frameworks[${index}].path must be a string`);
|
|
777
|
-
} else if (
|
|
777
|
+
} else if (path9.isAbsolute(fw.path)) {
|
|
778
778
|
errors.push(`frameworks[${index}].path must be relative, got: ${fw.path}`);
|
|
779
779
|
}
|
|
780
780
|
if (fw.enabled === void 0) {
|
|
@@ -834,12 +834,12 @@ __export(utils_exports, {
|
|
|
834
834
|
});
|
|
835
835
|
import { exec } from "child_process";
|
|
836
836
|
import { promisify } from "util";
|
|
837
|
-
import
|
|
838
|
-
import
|
|
837
|
+
import fs10 from "fs/promises";
|
|
838
|
+
import path10 from "path";
|
|
839
839
|
async function isGitRepo(rootDir) {
|
|
840
840
|
try {
|
|
841
|
-
const gitDir =
|
|
842
|
-
await
|
|
841
|
+
const gitDir = path10.join(rootDir, ".git");
|
|
842
|
+
await fs10.access(gitDir);
|
|
843
843
|
return true;
|
|
844
844
|
} catch {
|
|
845
845
|
return false;
|
|
@@ -878,7 +878,7 @@ async function getChangedFiles(rootDir, fromRef, toRef) {
|
|
|
878
878
|
// 10 second timeout for diffs
|
|
879
879
|
}
|
|
880
880
|
);
|
|
881
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) =>
|
|
881
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path10.join(rootDir, file));
|
|
882
882
|
return files;
|
|
883
883
|
} catch (error) {
|
|
884
884
|
throw new Error(`Failed to get changed files: ${error}`);
|
|
@@ -893,7 +893,7 @@ async function getChangedFilesInCommit(rootDir, commitSha) {
|
|
|
893
893
|
timeout: 1e4
|
|
894
894
|
}
|
|
895
895
|
);
|
|
896
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) =>
|
|
896
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path10.join(rootDir, file));
|
|
897
897
|
return files;
|
|
898
898
|
} catch (error) {
|
|
899
899
|
throw new Error(`Failed to get changed files in commit: ${error}`);
|
|
@@ -908,7 +908,7 @@ async function getChangedFilesBetweenCommits(rootDir, fromCommit, toCommit) {
|
|
|
908
908
|
timeout: 1e4
|
|
909
909
|
}
|
|
910
910
|
);
|
|
911
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) =>
|
|
911
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path10.join(rootDir, file));
|
|
912
912
|
return files;
|
|
913
913
|
} catch (error) {
|
|
914
914
|
throw new Error(`Failed to get changed files between commits: ${error}`);
|
|
@@ -931,21 +931,21 @@ var init_utils = __esm({
|
|
|
931
931
|
});
|
|
932
932
|
|
|
933
933
|
// src/vectordb/version.ts
|
|
934
|
-
import
|
|
935
|
-
import
|
|
934
|
+
import fs11 from "fs/promises";
|
|
935
|
+
import path11 from "path";
|
|
936
936
|
async function writeVersionFile(indexPath) {
|
|
937
937
|
try {
|
|
938
|
-
const versionFilePath =
|
|
938
|
+
const versionFilePath = path11.join(indexPath, VERSION_FILE);
|
|
939
939
|
const timestamp = Date.now().toString();
|
|
940
|
-
await
|
|
940
|
+
await fs11.writeFile(versionFilePath, timestamp, "utf-8");
|
|
941
941
|
} catch (error) {
|
|
942
942
|
console.error(`Warning: Failed to write version file: ${error}`);
|
|
943
943
|
}
|
|
944
944
|
}
|
|
945
945
|
async function readVersionFile(indexPath) {
|
|
946
946
|
try {
|
|
947
|
-
const versionFilePath =
|
|
948
|
-
const content = await
|
|
947
|
+
const versionFilePath = path11.join(indexPath, VERSION_FILE);
|
|
948
|
+
const content = await fs11.readFile(versionFilePath, "utf-8");
|
|
949
949
|
const timestamp = parseInt(content.trim(), 10);
|
|
950
950
|
return isNaN(timestamp) ? 0 : timestamp;
|
|
951
951
|
} catch (error) {
|
|
@@ -963,8 +963,8 @@ var init_version2 = __esm({
|
|
|
963
963
|
// src/indexer/scanner.ts
|
|
964
964
|
import { glob } from "glob";
|
|
965
965
|
import ignore from "ignore";
|
|
966
|
-
import
|
|
967
|
-
import
|
|
966
|
+
import fs13 from "fs/promises";
|
|
967
|
+
import path13 from "path";
|
|
968
968
|
async function scanCodebaseWithFrameworks(rootDir, config) {
|
|
969
969
|
const allFiles = [];
|
|
970
970
|
for (const framework of config.frameworks) {
|
|
@@ -977,16 +977,16 @@ async function scanCodebaseWithFrameworks(rootDir, config) {
|
|
|
977
977
|
return allFiles;
|
|
978
978
|
}
|
|
979
979
|
async function scanFramework(rootDir, framework) {
|
|
980
|
-
const frameworkPath =
|
|
981
|
-
const gitignorePath =
|
|
980
|
+
const frameworkPath = path13.join(rootDir, framework.path);
|
|
981
|
+
const gitignorePath = path13.join(frameworkPath, ".gitignore");
|
|
982
982
|
let ig = ignore();
|
|
983
983
|
try {
|
|
984
|
-
const gitignoreContent = await
|
|
984
|
+
const gitignoreContent = await fs13.readFile(gitignorePath, "utf-8");
|
|
985
985
|
ig = ignore().add(gitignoreContent);
|
|
986
986
|
} catch (e) {
|
|
987
|
-
const rootGitignorePath =
|
|
987
|
+
const rootGitignorePath = path13.join(rootDir, ".gitignore");
|
|
988
988
|
try {
|
|
989
|
-
const gitignoreContent = await
|
|
989
|
+
const gitignoreContent = await fs13.readFile(rootGitignorePath, "utf-8");
|
|
990
990
|
ig = ignore().add(gitignoreContent);
|
|
991
991
|
} catch (e2) {
|
|
992
992
|
}
|
|
@@ -1008,15 +1008,15 @@ async function scanFramework(rootDir, framework) {
|
|
|
1008
1008
|
}
|
|
1009
1009
|
const uniqueFiles = Array.from(new Set(allFiles));
|
|
1010
1010
|
return uniqueFiles.filter((file) => !ig.ignores(file)).map((file) => {
|
|
1011
|
-
return framework.path === "." ? file :
|
|
1011
|
+
return framework.path === "." ? file : path13.join(framework.path, file);
|
|
1012
1012
|
});
|
|
1013
1013
|
}
|
|
1014
1014
|
async function scanCodebase(options) {
|
|
1015
1015
|
const { rootDir, includePatterns = [], excludePatterns = [] } = options;
|
|
1016
|
-
const gitignorePath =
|
|
1016
|
+
const gitignorePath = path13.join(rootDir, ".gitignore");
|
|
1017
1017
|
let ig = ignore();
|
|
1018
1018
|
try {
|
|
1019
|
-
const gitignoreContent = await
|
|
1019
|
+
const gitignoreContent = await fs13.readFile(gitignorePath, "utf-8");
|
|
1020
1020
|
ig = ignore().add(gitignoreContent);
|
|
1021
1021
|
} catch (e) {
|
|
1022
1022
|
}
|
|
@@ -1043,12 +1043,12 @@ async function scanCodebase(options) {
|
|
|
1043
1043
|
}
|
|
1044
1044
|
const uniqueFiles = Array.from(new Set(allFiles));
|
|
1045
1045
|
return uniqueFiles.filter((file) => {
|
|
1046
|
-
const relativePath =
|
|
1046
|
+
const relativePath = path13.relative(rootDir, file);
|
|
1047
1047
|
return !ig.ignores(relativePath);
|
|
1048
1048
|
});
|
|
1049
1049
|
}
|
|
1050
1050
|
function detectLanguage(filepath) {
|
|
1051
|
-
const ext =
|
|
1051
|
+
const ext = path13.extname(filepath).toLowerCase();
|
|
1052
1052
|
const languageMap = {
|
|
1053
1053
|
".ts": "typescript",
|
|
1054
1054
|
".tsx": "typescript",
|
|
@@ -2591,10 +2591,10 @@ var init_types2 = __esm({
|
|
|
2591
2591
|
});
|
|
2592
2592
|
|
|
2593
2593
|
// src/vectordb/boosting/strategies.ts
|
|
2594
|
-
import
|
|
2594
|
+
import path14 from "path";
|
|
2595
2595
|
function isDocumentationFile(filepath) {
|
|
2596
2596
|
const lower = filepath.toLowerCase();
|
|
2597
|
-
const filename =
|
|
2597
|
+
const filename = path14.basename(filepath).toLowerCase();
|
|
2598
2598
|
if (filename.startsWith("readme")) return true;
|
|
2599
2599
|
if (filename.startsWith("changelog")) return true;
|
|
2600
2600
|
if (filename.endsWith(".md") || filename.endsWith(".mdx") || filename.endsWith(".markdown")) {
|
|
@@ -2651,7 +2651,7 @@ var init_strategies = __esm({
|
|
|
2651
2651
|
FilenameBoostingStrategy = class {
|
|
2652
2652
|
name = "filename-matching";
|
|
2653
2653
|
apply(query, filepath, baseScore) {
|
|
2654
|
-
const filename =
|
|
2654
|
+
const filename = path14.basename(filepath, path14.extname(filepath)).toLowerCase();
|
|
2655
2655
|
const queryTokens = query.toLowerCase().split(/\s+/);
|
|
2656
2656
|
let boostFactor = 1;
|
|
2657
2657
|
for (const token of queryTokens) {
|
|
@@ -3153,7 +3153,7 @@ __export(lancedb_exports, {
|
|
|
3153
3153
|
VectorDB: () => VectorDB
|
|
3154
3154
|
});
|
|
3155
3155
|
import * as lancedb from "vectordb";
|
|
3156
|
-
import
|
|
3156
|
+
import path15 from "path";
|
|
3157
3157
|
import os2 from "os";
|
|
3158
3158
|
import crypto2 from "crypto";
|
|
3159
3159
|
var VectorDB;
|
|
@@ -3174,9 +3174,9 @@ var init_lancedb = __esm({
|
|
|
3174
3174
|
lastVersionCheck = 0;
|
|
3175
3175
|
currentVersion = 0;
|
|
3176
3176
|
constructor(projectRoot) {
|
|
3177
|
-
const projectName =
|
|
3177
|
+
const projectName = path15.basename(projectRoot);
|
|
3178
3178
|
const pathHash = crypto2.createHash("md5").update(projectRoot).digest("hex").substring(0, 8);
|
|
3179
|
-
this.dbPath =
|
|
3179
|
+
this.dbPath = path15.join(
|
|
3180
3180
|
os2.homedir(),
|
|
3181
3181
|
".lien",
|
|
3182
3182
|
"indices",
|
|
@@ -3348,8 +3348,8 @@ var manifest_exports = {};
|
|
|
3348
3348
|
__export(manifest_exports, {
|
|
3349
3349
|
ManifestManager: () => ManifestManager
|
|
3350
3350
|
});
|
|
3351
|
-
import
|
|
3352
|
-
import
|
|
3351
|
+
import fs14 from "fs/promises";
|
|
3352
|
+
import path16 from "path";
|
|
3353
3353
|
var MANIFEST_FILE, ManifestManager;
|
|
3354
3354
|
var init_manifest = __esm({
|
|
3355
3355
|
"src/indexer/manifest.ts"() {
|
|
@@ -3371,7 +3371,7 @@ var init_manifest = __esm({
|
|
|
3371
3371
|
*/
|
|
3372
3372
|
constructor(indexPath) {
|
|
3373
3373
|
this.indexPath = indexPath;
|
|
3374
|
-
this.manifestPath =
|
|
3374
|
+
this.manifestPath = path16.join(indexPath, MANIFEST_FILE);
|
|
3375
3375
|
}
|
|
3376
3376
|
/**
|
|
3377
3377
|
* Loads the manifest from disk.
|
|
@@ -3384,7 +3384,7 @@ var init_manifest = __esm({
|
|
|
3384
3384
|
*/
|
|
3385
3385
|
async load() {
|
|
3386
3386
|
try {
|
|
3387
|
-
const content = await
|
|
3387
|
+
const content = await fs14.readFile(this.manifestPath, "utf-8");
|
|
3388
3388
|
const manifest = JSON.parse(content);
|
|
3389
3389
|
if (manifest.formatVersion !== INDEX_FORMAT_VERSION) {
|
|
3390
3390
|
console.error(
|
|
@@ -3411,7 +3411,7 @@ var init_manifest = __esm({
|
|
|
3411
3411
|
*/
|
|
3412
3412
|
async save(manifest) {
|
|
3413
3413
|
try {
|
|
3414
|
-
await
|
|
3414
|
+
await fs14.mkdir(this.indexPath, { recursive: true });
|
|
3415
3415
|
const manifestToSave = {
|
|
3416
3416
|
...manifest,
|
|
3417
3417
|
formatVersion: INDEX_FORMAT_VERSION,
|
|
@@ -3419,7 +3419,7 @@ var init_manifest = __esm({
|
|
|
3419
3419
|
lastIndexed: Date.now()
|
|
3420
3420
|
};
|
|
3421
3421
|
const content = JSON.stringify(manifestToSave, null, 2);
|
|
3422
|
-
await
|
|
3422
|
+
await fs14.writeFile(this.manifestPath, content, "utf-8");
|
|
3423
3423
|
} catch (error) {
|
|
3424
3424
|
console.error(`[Lien] Warning: Failed to save manifest: ${error}`);
|
|
3425
3425
|
}
|
|
@@ -3556,7 +3556,7 @@ var init_manifest = __esm({
|
|
|
3556
3556
|
*/
|
|
3557
3557
|
async clear() {
|
|
3558
3558
|
try {
|
|
3559
|
-
await
|
|
3559
|
+
await fs14.unlink(this.manifestPath);
|
|
3560
3560
|
} catch (error) {
|
|
3561
3561
|
if (error.code !== "ENOENT") {
|
|
3562
3562
|
console.error(`[Lien] Warning: Failed to clear manifest: ${error}`);
|
|
@@ -3585,8 +3585,8 @@ var tracker_exports = {};
|
|
|
3585
3585
|
__export(tracker_exports, {
|
|
3586
3586
|
GitStateTracker: () => GitStateTracker
|
|
3587
3587
|
});
|
|
3588
|
-
import
|
|
3589
|
-
import
|
|
3588
|
+
import fs15 from "fs/promises";
|
|
3589
|
+
import path17 from "path";
|
|
3590
3590
|
var GitStateTracker;
|
|
3591
3591
|
var init_tracker = __esm({
|
|
3592
3592
|
"src/git/tracker.ts"() {
|
|
@@ -3598,7 +3598,7 @@ var init_tracker = __esm({
|
|
|
3598
3598
|
currentState = null;
|
|
3599
3599
|
constructor(rootDir, indexPath) {
|
|
3600
3600
|
this.rootDir = rootDir;
|
|
3601
|
-
this.stateFile =
|
|
3601
|
+
this.stateFile = path17.join(indexPath, ".git-state.json");
|
|
3602
3602
|
}
|
|
3603
3603
|
/**
|
|
3604
3604
|
* Loads the last known git state from disk.
|
|
@@ -3606,7 +3606,7 @@ var init_tracker = __esm({
|
|
|
3606
3606
|
*/
|
|
3607
3607
|
async loadState() {
|
|
3608
3608
|
try {
|
|
3609
|
-
const content = await
|
|
3609
|
+
const content = await fs15.readFile(this.stateFile, "utf-8");
|
|
3610
3610
|
return JSON.parse(content);
|
|
3611
3611
|
} catch {
|
|
3612
3612
|
return null;
|
|
@@ -3618,7 +3618,7 @@ var init_tracker = __esm({
|
|
|
3618
3618
|
async saveState(state) {
|
|
3619
3619
|
try {
|
|
3620
3620
|
const content = JSON.stringify(state, null, 2);
|
|
3621
|
-
await
|
|
3621
|
+
await fs15.writeFile(this.stateFile, content, "utf-8");
|
|
3622
3622
|
} catch (error) {
|
|
3623
3623
|
console.error(`[Lien] Warning: Failed to save git state: ${error}`);
|
|
3624
3624
|
}
|
|
@@ -3769,7 +3769,7 @@ var init_tracker = __esm({
|
|
|
3769
3769
|
});
|
|
3770
3770
|
|
|
3771
3771
|
// src/indexer/change-detector.ts
|
|
3772
|
-
import
|
|
3772
|
+
import fs16 from "fs/promises";
|
|
3773
3773
|
async function detectChanges(rootDir, vectorDB, config) {
|
|
3774
3774
|
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
3775
3775
|
const savedManifest = await manifest.load();
|
|
@@ -3873,7 +3873,7 @@ async function mtimeBasedDetection(rootDir, savedManifest, config) {
|
|
|
3873
3873
|
const fileStats = /* @__PURE__ */ new Map();
|
|
3874
3874
|
for (const filepath of currentFiles) {
|
|
3875
3875
|
try {
|
|
3876
|
-
const stats = await
|
|
3876
|
+
const stats = await fs16.stat(filepath);
|
|
3877
3877
|
fileStats.set(filepath, stats.mtimeMs);
|
|
3878
3878
|
} catch {
|
|
3879
3879
|
continue;
|
|
@@ -3927,7 +3927,7 @@ var init_result = __esm({
|
|
|
3927
3927
|
});
|
|
3928
3928
|
|
|
3929
3929
|
// src/indexer/incremental.ts
|
|
3930
|
-
import
|
|
3930
|
+
import fs17 from "fs/promises";
|
|
3931
3931
|
async function processFileContent(filepath, content, embeddings, config, verbose) {
|
|
3932
3932
|
const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
|
|
3933
3933
|
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
|
|
@@ -3966,7 +3966,7 @@ async function indexSingleFile(filepath, vectorDB, embeddings, config, options =
|
|
|
3966
3966
|
const { verbose } = options;
|
|
3967
3967
|
try {
|
|
3968
3968
|
try {
|
|
3969
|
-
await
|
|
3969
|
+
await fs17.access(filepath);
|
|
3970
3970
|
} catch {
|
|
3971
3971
|
if (verbose) {
|
|
3972
3972
|
console.error(`[Lien] File deleted: ${filepath}`);
|
|
@@ -3976,9 +3976,9 @@ async function indexSingleFile(filepath, vectorDB, embeddings, config, options =
|
|
|
3976
3976
|
await manifest2.removeFile(filepath);
|
|
3977
3977
|
return;
|
|
3978
3978
|
}
|
|
3979
|
-
const content = await
|
|
3979
|
+
const content = await fs17.readFile(filepath, "utf-8");
|
|
3980
3980
|
const result = await processFileContent(filepath, content, embeddings, config, verbose || false);
|
|
3981
|
-
const stats = await
|
|
3981
|
+
const stats = await fs17.stat(filepath);
|
|
3982
3982
|
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
3983
3983
|
if (result === null) {
|
|
3984
3984
|
await vectorDB.deleteByFile(filepath);
|
|
@@ -4009,8 +4009,8 @@ async function indexSingleFile(filepath, vectorDB, embeddings, config, options =
|
|
|
4009
4009
|
}
|
|
4010
4010
|
async function processSingleFileForIndexing(filepath, embeddings, config, verbose) {
|
|
4011
4011
|
try {
|
|
4012
|
-
const stats = await
|
|
4013
|
-
const content = await
|
|
4012
|
+
const stats = await fs17.stat(filepath);
|
|
4013
|
+
const content = await fs17.readFile(filepath, "utf-8");
|
|
4014
4014
|
const result = await processFileContent(filepath, content, embeddings, config, verbose);
|
|
4015
4015
|
return Ok({
|
|
4016
4016
|
filepath,
|
|
@@ -4281,7 +4281,7 @@ var indexer_exports = {};
|
|
|
4281
4281
|
__export(indexer_exports, {
|
|
4282
4282
|
indexCodebase: () => indexCodebase
|
|
4283
4283
|
});
|
|
4284
|
-
import
|
|
4284
|
+
import fs18 from "fs/promises";
|
|
4285
4285
|
import ora from "ora";
|
|
4286
4286
|
import chalk5 from "chalk";
|
|
4287
4287
|
import pLimit from "p-limit";
|
|
@@ -4454,8 +4454,8 @@ async function performFullIndex(rootDir, vectorDB, config, options, spinner) {
|
|
|
4454
4454
|
const filePromises = files.map(
|
|
4455
4455
|
(file) => limit(async () => {
|
|
4456
4456
|
try {
|
|
4457
|
-
const stats = await
|
|
4458
|
-
const content = await
|
|
4457
|
+
const stats = await fs18.stat(file);
|
|
4458
|
+
const content = await fs18.readFile(file, "utf-8");
|
|
4459
4459
|
const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
|
|
4460
4460
|
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : 10;
|
|
4461
4461
|
const useAST = isModernConfig(config) ? config.chunking.useAST : true;
|
|
@@ -4583,8 +4583,8 @@ import { dirname as dirname4, join as join4 } from "path";
|
|
|
4583
4583
|
|
|
4584
4584
|
// src/cli/init.ts
|
|
4585
4585
|
init_schema();
|
|
4586
|
-
import
|
|
4587
|
-
import
|
|
4586
|
+
import fs8 from "fs/promises";
|
|
4587
|
+
import path8 from "path";
|
|
4588
4588
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4589
4589
|
import chalk3 from "chalk";
|
|
4590
4590
|
import inquirer from "inquirer";
|
|
@@ -4751,8 +4751,8 @@ var MigrationManager = class {
|
|
|
4751
4751
|
};
|
|
4752
4752
|
|
|
4753
4753
|
// src/frameworks/detector-service.ts
|
|
4754
|
-
import
|
|
4755
|
-
import
|
|
4754
|
+
import fs7 from "fs/promises";
|
|
4755
|
+
import path7 from "path";
|
|
4756
4756
|
|
|
4757
4757
|
// src/frameworks/types.ts
|
|
4758
4758
|
var defaultDetectionOptions = {
|
|
@@ -4884,10 +4884,132 @@ var nodejsDetector = {
|
|
|
4884
4884
|
}
|
|
4885
4885
|
};
|
|
4886
4886
|
|
|
4887
|
-
// src/frameworks/
|
|
4887
|
+
// src/frameworks/php/detector.ts
|
|
4888
4888
|
import fs4 from "fs/promises";
|
|
4889
4889
|
import path4 from "path";
|
|
4890
4890
|
|
|
4891
|
+
// src/frameworks/php/config.ts
|
|
4892
|
+
async function generatePhpConfig(_rootDir, _relativePath) {
|
|
4893
|
+
return {
|
|
4894
|
+
include: [
|
|
4895
|
+
// PHP source code
|
|
4896
|
+
"src/**/*.php",
|
|
4897
|
+
"lib/**/*.php",
|
|
4898
|
+
"app/**/*.php",
|
|
4899
|
+
"tests/**/*.php",
|
|
4900
|
+
"*.php",
|
|
4901
|
+
// Common PHP project files
|
|
4902
|
+
"config/**/*.php",
|
|
4903
|
+
"public/**/*.php",
|
|
4904
|
+
// Documentation
|
|
4905
|
+
"**/*.md",
|
|
4906
|
+
"**/*.mdx",
|
|
4907
|
+
"docs/**/*.md",
|
|
4908
|
+
"README.md",
|
|
4909
|
+
"CHANGELOG.md"
|
|
4910
|
+
],
|
|
4911
|
+
exclude: [
|
|
4912
|
+
"vendor/**",
|
|
4913
|
+
"node_modules/**",
|
|
4914
|
+
"dist/**",
|
|
4915
|
+
"build/**",
|
|
4916
|
+
"storage/**",
|
|
4917
|
+
"cache/**",
|
|
4918
|
+
// Test artifacts
|
|
4919
|
+
"coverage/**",
|
|
4920
|
+
"test-results/**",
|
|
4921
|
+
".phpunit.cache/**",
|
|
4922
|
+
// Build outputs
|
|
4923
|
+
"__generated__/**"
|
|
4924
|
+
]
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
// src/frameworks/php/detector.ts
|
|
4929
|
+
var phpDetector = {
|
|
4930
|
+
name: "php",
|
|
4931
|
+
priority: 50,
|
|
4932
|
+
// Generic, yields to specific frameworks like Laravel
|
|
4933
|
+
async detect(rootDir, relativePath) {
|
|
4934
|
+
const fullPath = path4.join(rootDir, relativePath);
|
|
4935
|
+
const result = {
|
|
4936
|
+
detected: false,
|
|
4937
|
+
name: "php",
|
|
4938
|
+
path: relativePath,
|
|
4939
|
+
confidence: "low",
|
|
4940
|
+
evidence: []
|
|
4941
|
+
};
|
|
4942
|
+
const composerJsonPath = path4.join(fullPath, "composer.json");
|
|
4943
|
+
let composerJson = null;
|
|
4944
|
+
try {
|
|
4945
|
+
const content = await fs4.readFile(composerJsonPath, "utf-8");
|
|
4946
|
+
composerJson = JSON.parse(content);
|
|
4947
|
+
result.evidence.push("Found composer.json");
|
|
4948
|
+
} catch {
|
|
4949
|
+
return result;
|
|
4950
|
+
}
|
|
4951
|
+
const hasLaravel = composerJson.require?.["laravel/framework"] || composerJson["require-dev"]?.["laravel/framework"];
|
|
4952
|
+
if (hasLaravel) {
|
|
4953
|
+
return result;
|
|
4954
|
+
}
|
|
4955
|
+
result.detected = true;
|
|
4956
|
+
result.confidence = "high";
|
|
4957
|
+
const phpDirs = ["src", "lib", "app", "tests"];
|
|
4958
|
+
let foundDirs = 0;
|
|
4959
|
+
for (const dir of phpDirs) {
|
|
4960
|
+
try {
|
|
4961
|
+
const dirPath = path4.join(fullPath, dir);
|
|
4962
|
+
const stats = await fs4.stat(dirPath);
|
|
4963
|
+
if (stats.isDirectory()) {
|
|
4964
|
+
foundDirs++;
|
|
4965
|
+
}
|
|
4966
|
+
} catch {
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
if (foundDirs > 0) {
|
|
4970
|
+
result.evidence.push(`Found PHP project structure (${foundDirs} directories)`);
|
|
4971
|
+
}
|
|
4972
|
+
if (composerJson.require?.php) {
|
|
4973
|
+
result.version = composerJson.require.php;
|
|
4974
|
+
result.evidence.push(`PHP ${composerJson.require.php}`);
|
|
4975
|
+
}
|
|
4976
|
+
const testFrameworks = [
|
|
4977
|
+
{ name: "phpunit/phpunit", display: "PHPUnit" },
|
|
4978
|
+
{ name: "pestphp/pest", display: "Pest" },
|
|
4979
|
+
{ name: "codeception/codeception", display: "Codeception" },
|
|
4980
|
+
{ name: "behat/behat", display: "Behat" }
|
|
4981
|
+
];
|
|
4982
|
+
for (const framework of testFrameworks) {
|
|
4983
|
+
if (composerJson.require?.[framework.name] || composerJson["require-dev"]?.[framework.name]) {
|
|
4984
|
+
result.evidence.push(`${framework.display} test framework detected`);
|
|
4985
|
+
break;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
const tools2 = [
|
|
4989
|
+
{ name: "symfony/framework-bundle", display: "Symfony" },
|
|
4990
|
+
{ name: "symfony/http-kernel", display: "Symfony" },
|
|
4991
|
+
{ name: "symfony/symfony", display: "Symfony (monolithic)" },
|
|
4992
|
+
{ name: "doctrine/orm", display: "Doctrine ORM" },
|
|
4993
|
+
{ name: "guzzlehttp/guzzle", display: "Guzzle HTTP" },
|
|
4994
|
+
{ name: "monolog/monolog", display: "Monolog" }
|
|
4995
|
+
];
|
|
4996
|
+
for (const tool of tools2) {
|
|
4997
|
+
if (composerJson.require?.[tool.name]) {
|
|
4998
|
+
result.evidence.push(`${tool.display} detected`);
|
|
4999
|
+
break;
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
return result;
|
|
5003
|
+
},
|
|
5004
|
+
async generateConfig(rootDir, relativePath) {
|
|
5005
|
+
return generatePhpConfig(rootDir, relativePath);
|
|
5006
|
+
}
|
|
5007
|
+
};
|
|
5008
|
+
|
|
5009
|
+
// src/frameworks/laravel/detector.ts
|
|
5010
|
+
import fs5 from "fs/promises";
|
|
5011
|
+
import path5 from "path";
|
|
5012
|
+
|
|
4891
5013
|
// src/frameworks/laravel/config.ts
|
|
4892
5014
|
async function generateLaravelConfig(_rootDir, _relativePath) {
|
|
4893
5015
|
return {
|
|
@@ -4943,7 +5065,7 @@ var laravelDetector = {
|
|
|
4943
5065
|
priority: 100,
|
|
4944
5066
|
// Laravel takes precedence over Node.js
|
|
4945
5067
|
async detect(rootDir, relativePath) {
|
|
4946
|
-
const fullPath =
|
|
5068
|
+
const fullPath = path5.join(rootDir, relativePath);
|
|
4947
5069
|
const result = {
|
|
4948
5070
|
detected: false,
|
|
4949
5071
|
name: "laravel",
|
|
@@ -4951,10 +5073,10 @@ var laravelDetector = {
|
|
|
4951
5073
|
confidence: "low",
|
|
4952
5074
|
evidence: []
|
|
4953
5075
|
};
|
|
4954
|
-
const composerJsonPath =
|
|
5076
|
+
const composerJsonPath = path5.join(fullPath, "composer.json");
|
|
4955
5077
|
let composerJson = null;
|
|
4956
5078
|
try {
|
|
4957
|
-
const content = await
|
|
5079
|
+
const content = await fs5.readFile(composerJsonPath, "utf-8");
|
|
4958
5080
|
composerJson = JSON.parse(content);
|
|
4959
5081
|
result.evidence.push("Found composer.json");
|
|
4960
5082
|
} catch {
|
|
@@ -4965,9 +5087,9 @@ var laravelDetector = {
|
|
|
4965
5087
|
return result;
|
|
4966
5088
|
}
|
|
4967
5089
|
result.evidence.push("Laravel framework detected in composer.json");
|
|
4968
|
-
const artisanPath =
|
|
5090
|
+
const artisanPath = path5.join(fullPath, "artisan");
|
|
4969
5091
|
try {
|
|
4970
|
-
await
|
|
5092
|
+
await fs5.access(artisanPath);
|
|
4971
5093
|
result.evidence.push("Found artisan file");
|
|
4972
5094
|
result.confidence = "high";
|
|
4973
5095
|
} catch {
|
|
@@ -4977,8 +5099,8 @@ var laravelDetector = {
|
|
|
4977
5099
|
let foundDirs = 0;
|
|
4978
5100
|
for (const dir of laravelDirs) {
|
|
4979
5101
|
try {
|
|
4980
|
-
const dirPath =
|
|
4981
|
-
const stats = await
|
|
5102
|
+
const dirPath = path5.join(fullPath, dir);
|
|
5103
|
+
const stats = await fs5.stat(dirPath);
|
|
4982
5104
|
if (stats.isDirectory()) {
|
|
4983
5105
|
foundDirs++;
|
|
4984
5106
|
}
|
|
@@ -4990,14 +5112,14 @@ var laravelDetector = {
|
|
|
4990
5112
|
result.confidence = "high";
|
|
4991
5113
|
}
|
|
4992
5114
|
const testDirsToCheck = [
|
|
4993
|
-
|
|
4994
|
-
|
|
5115
|
+
path5.join(fullPath, "tests", "Feature"),
|
|
5116
|
+
path5.join(fullPath, "tests", "Unit")
|
|
4995
5117
|
];
|
|
4996
5118
|
for (const testDir of testDirsToCheck) {
|
|
4997
5119
|
try {
|
|
4998
|
-
const stats = await
|
|
5120
|
+
const stats = await fs5.stat(testDir);
|
|
4999
5121
|
if (stats.isDirectory()) {
|
|
5000
|
-
const dirName =
|
|
5122
|
+
const dirName = path5.basename(path5.dirname(testDir)) + "/" + path5.basename(testDir);
|
|
5001
5123
|
result.evidence.push(`Found ${dirName} test directory`);
|
|
5002
5124
|
}
|
|
5003
5125
|
} catch {
|
|
@@ -5015,8 +5137,8 @@ var laravelDetector = {
|
|
|
5015
5137
|
};
|
|
5016
5138
|
|
|
5017
5139
|
// src/frameworks/shopify/detector.ts
|
|
5018
|
-
import
|
|
5019
|
-
import
|
|
5140
|
+
import fs6 from "fs/promises";
|
|
5141
|
+
import path6 from "path";
|
|
5020
5142
|
|
|
5021
5143
|
// src/frameworks/shopify/config.ts
|
|
5022
5144
|
async function generateShopifyConfig(_rootDir, _relativePath) {
|
|
@@ -5074,7 +5196,7 @@ var shopifyDetector = {
|
|
|
5074
5196
|
priority: 100,
|
|
5075
5197
|
// High priority (same as Laravel)
|
|
5076
5198
|
async detect(rootDir, relativePath) {
|
|
5077
|
-
const fullPath =
|
|
5199
|
+
const fullPath = path6.join(rootDir, relativePath);
|
|
5078
5200
|
const result = {
|
|
5079
5201
|
detected: false,
|
|
5080
5202
|
name: "shopify",
|
|
@@ -5082,18 +5204,18 @@ var shopifyDetector = {
|
|
|
5082
5204
|
confidence: "low",
|
|
5083
5205
|
evidence: []
|
|
5084
5206
|
};
|
|
5085
|
-
const settingsSchemaPath =
|
|
5207
|
+
const settingsSchemaPath = path6.join(fullPath, "config", "settings_schema.json");
|
|
5086
5208
|
let hasSettingsSchema = false;
|
|
5087
5209
|
try {
|
|
5088
|
-
await
|
|
5210
|
+
await fs6.access(settingsSchemaPath);
|
|
5089
5211
|
hasSettingsSchema = true;
|
|
5090
5212
|
result.evidence.push("Found config/settings_schema.json");
|
|
5091
5213
|
} catch {
|
|
5092
5214
|
}
|
|
5093
|
-
const themeLayoutPath =
|
|
5215
|
+
const themeLayoutPath = path6.join(fullPath, "layout", "theme.liquid");
|
|
5094
5216
|
let hasThemeLayout = false;
|
|
5095
5217
|
try {
|
|
5096
|
-
await
|
|
5218
|
+
await fs6.access(themeLayoutPath);
|
|
5097
5219
|
hasThemeLayout = true;
|
|
5098
5220
|
result.evidence.push("Found layout/theme.liquid");
|
|
5099
5221
|
} catch {
|
|
@@ -5102,8 +5224,8 @@ var shopifyDetector = {
|
|
|
5102
5224
|
let foundDirs = 0;
|
|
5103
5225
|
for (const dir of shopifyDirs) {
|
|
5104
5226
|
try {
|
|
5105
|
-
const dirPath =
|
|
5106
|
-
const stats = await
|
|
5227
|
+
const dirPath = path6.join(fullPath, dir);
|
|
5228
|
+
const stats = await fs6.stat(dirPath);
|
|
5107
5229
|
if (stats.isDirectory()) {
|
|
5108
5230
|
foundDirs++;
|
|
5109
5231
|
}
|
|
@@ -5114,14 +5236,14 @@ var shopifyDetector = {
|
|
|
5114
5236
|
result.evidence.push(`Shopify directory structure detected (${foundDirs}/${shopifyDirs.length} dirs)`);
|
|
5115
5237
|
}
|
|
5116
5238
|
try {
|
|
5117
|
-
const tomlPath =
|
|
5118
|
-
await
|
|
5239
|
+
const tomlPath = path6.join(fullPath, "shopify.theme.toml");
|
|
5240
|
+
await fs6.access(tomlPath);
|
|
5119
5241
|
result.evidence.push("Found shopify.theme.toml");
|
|
5120
5242
|
} catch {
|
|
5121
5243
|
}
|
|
5122
5244
|
try {
|
|
5123
|
-
const ignorePath =
|
|
5124
|
-
await
|
|
5245
|
+
const ignorePath = path6.join(fullPath, ".shopifyignore");
|
|
5246
|
+
await fs6.access(ignorePath);
|
|
5125
5247
|
result.evidence.push("Found .shopifyignore");
|
|
5126
5248
|
} catch {
|
|
5127
5249
|
}
|
|
@@ -5150,6 +5272,7 @@ var shopifyDetector = {
|
|
|
5150
5272
|
// src/frameworks/registry.ts
|
|
5151
5273
|
var frameworkDetectors = [
|
|
5152
5274
|
nodejsDetector,
|
|
5275
|
+
phpDetector,
|
|
5153
5276
|
laravelDetector,
|
|
5154
5277
|
shopifyDetector
|
|
5155
5278
|
];
|
|
@@ -5167,7 +5290,7 @@ async function detectAllFrameworks(rootDir, options = {}) {
|
|
|
5167
5290
|
return results;
|
|
5168
5291
|
}
|
|
5169
5292
|
async function detectAtPath(rootDir, relativePath, results, visited) {
|
|
5170
|
-
const fullPath =
|
|
5293
|
+
const fullPath = path7.join(rootDir, relativePath);
|
|
5171
5294
|
if (visited.has(fullPath)) {
|
|
5172
5295
|
return;
|
|
5173
5296
|
}
|
|
@@ -5234,9 +5357,9 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
|
|
|
5234
5357
|
if (depth >= options.maxDepth) {
|
|
5235
5358
|
return;
|
|
5236
5359
|
}
|
|
5237
|
-
const fullPath =
|
|
5360
|
+
const fullPath = path7.join(rootDir, relativePath);
|
|
5238
5361
|
try {
|
|
5239
|
-
const entries = await
|
|
5362
|
+
const entries = await fs7.readdir(fullPath, { withFileTypes: true });
|
|
5240
5363
|
const dirs = entries.filter((e) => e.isDirectory());
|
|
5241
5364
|
for (const dir of dirs) {
|
|
5242
5365
|
if (options.skipDirs.includes(dir.name)) {
|
|
@@ -5245,7 +5368,7 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
|
|
|
5245
5368
|
if (dir.name.startsWith(".")) {
|
|
5246
5369
|
continue;
|
|
5247
5370
|
}
|
|
5248
|
-
const subPath = relativePath === "." ? dir.name :
|
|
5371
|
+
const subPath = relativePath === "." ? dir.name : path7.join(relativePath, dir.name);
|
|
5249
5372
|
await detectAtPath(rootDir, subPath, results, visited);
|
|
5250
5373
|
await scanSubdirectories(rootDir, subPath, results, visited, depth + 1, options);
|
|
5251
5374
|
}
|
|
@@ -5256,14 +5379,14 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
|
|
|
5256
5379
|
|
|
5257
5380
|
// src/cli/init.ts
|
|
5258
5381
|
var __filename3 = fileURLToPath3(import.meta.url);
|
|
5259
|
-
var __dirname3 =
|
|
5382
|
+
var __dirname3 = path8.dirname(__filename3);
|
|
5260
5383
|
async function initCommand(options = {}) {
|
|
5261
5384
|
const rootDir = options.path || process.cwd();
|
|
5262
|
-
const configPath =
|
|
5385
|
+
const configPath = path8.join(rootDir, ".lien.config.json");
|
|
5263
5386
|
try {
|
|
5264
5387
|
let configExists = false;
|
|
5265
5388
|
try {
|
|
5266
|
-
await
|
|
5389
|
+
await fs8.access(configPath);
|
|
5267
5390
|
configExists = true;
|
|
5268
5391
|
} catch {
|
|
5269
5392
|
}
|
|
@@ -5400,22 +5523,22 @@ async function createNewConfig(rootDir, options) {
|
|
|
5400
5523
|
]);
|
|
5401
5524
|
if (installCursorRules) {
|
|
5402
5525
|
try {
|
|
5403
|
-
const cursorRulesDir =
|
|
5404
|
-
await
|
|
5405
|
-
const templatePath =
|
|
5406
|
-
const rulesPath =
|
|
5526
|
+
const cursorRulesDir = path8.join(rootDir, ".cursor");
|
|
5527
|
+
await fs8.mkdir(cursorRulesDir, { recursive: true });
|
|
5528
|
+
const templatePath = path8.join(__dirname3, "../CURSOR_RULES_TEMPLATE.md");
|
|
5529
|
+
const rulesPath = path8.join(cursorRulesDir, "rules");
|
|
5407
5530
|
let targetPath;
|
|
5408
5531
|
let isDirectory = false;
|
|
5409
5532
|
let isFile = false;
|
|
5410
5533
|
try {
|
|
5411
|
-
const stats = await
|
|
5534
|
+
const stats = await fs8.stat(rulesPath);
|
|
5412
5535
|
isDirectory = stats.isDirectory();
|
|
5413
5536
|
isFile = stats.isFile();
|
|
5414
5537
|
} catch {
|
|
5415
5538
|
}
|
|
5416
5539
|
if (isDirectory) {
|
|
5417
|
-
targetPath =
|
|
5418
|
-
await
|
|
5540
|
+
targetPath = path8.join(rulesPath, "lien.mdc");
|
|
5541
|
+
await fs8.copyFile(templatePath, targetPath);
|
|
5419
5542
|
console.log(chalk3.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
|
|
5420
5543
|
} else if (isFile) {
|
|
5421
5544
|
const { convertToDir } = await inquirer.prompt([
|
|
@@ -5427,11 +5550,11 @@ async function createNewConfig(rootDir, options) {
|
|
|
5427
5550
|
}
|
|
5428
5551
|
]);
|
|
5429
5552
|
if (convertToDir) {
|
|
5430
|
-
const existingRules = await
|
|
5431
|
-
await
|
|
5432
|
-
await
|
|
5433
|
-
await
|
|
5434
|
-
await
|
|
5553
|
+
const existingRules = await fs8.readFile(rulesPath, "utf-8");
|
|
5554
|
+
await fs8.unlink(rulesPath);
|
|
5555
|
+
await fs8.mkdir(rulesPath);
|
|
5556
|
+
await fs8.writeFile(path8.join(rulesPath, "project.mdc"), existingRules);
|
|
5557
|
+
await fs8.copyFile(templatePath, path8.join(rulesPath, "lien.mdc"));
|
|
5435
5558
|
console.log(chalk3.green("\u2713 Converted .cursor/rules to directory"));
|
|
5436
5559
|
console.log(chalk3.green(" - Your project rules: .cursor/rules/project.mdc"));
|
|
5437
5560
|
console.log(chalk3.green(" - Lien rules: .cursor/rules/lien.mdc"));
|
|
@@ -5439,9 +5562,9 @@ async function createNewConfig(rootDir, options) {
|
|
|
5439
5562
|
console.log(chalk3.dim("Skipped Cursor rules installation (preserving existing file)"));
|
|
5440
5563
|
}
|
|
5441
5564
|
} else {
|
|
5442
|
-
await
|
|
5443
|
-
targetPath =
|
|
5444
|
-
await
|
|
5565
|
+
await fs8.mkdir(rulesPath, { recursive: true });
|
|
5566
|
+
targetPath = path8.join(rulesPath, "lien.mdc");
|
|
5567
|
+
await fs8.copyFile(templatePath, targetPath);
|
|
5445
5568
|
console.log(chalk3.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
|
|
5446
5569
|
}
|
|
5447
5570
|
} catch (error) {
|
|
@@ -5455,8 +5578,8 @@ async function createNewConfig(rootDir, options) {
|
|
|
5455
5578
|
...defaultConfig,
|
|
5456
5579
|
frameworks
|
|
5457
5580
|
};
|
|
5458
|
-
const configPath =
|
|
5459
|
-
await
|
|
5581
|
+
const configPath = path8.join(rootDir, ".lien.config.json");
|
|
5582
|
+
await fs8.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5460
5583
|
console.log(chalk3.green("\n\u2713 Created .lien.config.json"));
|
|
5461
5584
|
console.log(chalk3.green(`\u2713 Configured ${frameworks.length} framework(s)`));
|
|
5462
5585
|
console.log(chalk3.dim("\nNext steps:"));
|
|
@@ -5494,16 +5617,16 @@ init_service();
|
|
|
5494
5617
|
init_utils();
|
|
5495
5618
|
init_version2();
|
|
5496
5619
|
import chalk4 from "chalk";
|
|
5497
|
-
import
|
|
5498
|
-
import
|
|
5620
|
+
import fs12 from "fs/promises";
|
|
5621
|
+
import path12 from "path";
|
|
5499
5622
|
import os from "os";
|
|
5500
5623
|
import crypto from "crypto";
|
|
5501
5624
|
init_schema();
|
|
5502
5625
|
async function statusCommand() {
|
|
5503
5626
|
const rootDir = process.cwd();
|
|
5504
|
-
const projectName =
|
|
5627
|
+
const projectName = path12.basename(rootDir);
|
|
5505
5628
|
const pathHash = crypto.createHash("md5").update(rootDir).digest("hex").substring(0, 8);
|
|
5506
|
-
const indexPath =
|
|
5629
|
+
const indexPath = path12.join(os.homedir(), ".lien", "indices", `${projectName}-${pathHash}`);
|
|
5507
5630
|
showCompactBanner();
|
|
5508
5631
|
console.log(chalk4.bold("Status\n"));
|
|
5509
5632
|
const hasConfig = await configService.exists(rootDir);
|
|
@@ -5513,11 +5636,11 @@ async function statusCommand() {
|
|
|
5513
5636
|
return;
|
|
5514
5637
|
}
|
|
5515
5638
|
try {
|
|
5516
|
-
const stats = await
|
|
5639
|
+
const stats = await fs12.stat(indexPath);
|
|
5517
5640
|
console.log(chalk4.dim("Index location:"), indexPath);
|
|
5518
5641
|
console.log(chalk4.dim("Index status:"), chalk4.green("\u2713 Exists"));
|
|
5519
5642
|
try {
|
|
5520
|
-
const files = await
|
|
5643
|
+
const files = await fs12.readdir(indexPath, { recursive: true });
|
|
5521
5644
|
console.log(chalk4.dim("Index files:"), files.length);
|
|
5522
5645
|
} catch (e) {
|
|
5523
5646
|
}
|
|
@@ -5546,9 +5669,9 @@ async function statusCommand() {
|
|
|
5546
5669
|
const commit = await getCurrentCommit(rootDir);
|
|
5547
5670
|
console.log(chalk4.dim(" Current branch:"), branch);
|
|
5548
5671
|
console.log(chalk4.dim(" Current commit:"), commit.substring(0, 8));
|
|
5549
|
-
const gitStateFile =
|
|
5672
|
+
const gitStateFile = path12.join(indexPath, ".git-state.json");
|
|
5550
5673
|
try {
|
|
5551
|
-
const gitStateContent = await
|
|
5674
|
+
const gitStateContent = await fs12.readFile(gitStateFile, "utf-8");
|
|
5552
5675
|
const gitState = JSON.parse(gitStateContent);
|
|
5553
5676
|
if (gitState.branch !== branch || gitState.commit !== commit) {
|
|
5554
5677
|
console.log(chalk4.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
|
|
@@ -5614,8 +5737,8 @@ async function indexCommand(options) {
|
|
|
5614
5737
|
|
|
5615
5738
|
// src/cli/serve.ts
|
|
5616
5739
|
import chalk7 from "chalk";
|
|
5617
|
-
import
|
|
5618
|
-
import
|
|
5740
|
+
import fs19 from "fs/promises";
|
|
5741
|
+
import path18 from "path";
|
|
5619
5742
|
|
|
5620
5743
|
// src/mcp/server.ts
|
|
5621
5744
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -5688,6 +5811,17 @@ var ListFunctionsSchema = z4.object({
|
|
|
5688
5811
|
)
|
|
5689
5812
|
});
|
|
5690
5813
|
|
|
5814
|
+
// src/mcp/schemas/dependents.schema.ts
|
|
5815
|
+
import { z as z5 } from "zod";
|
|
5816
|
+
var GetDependentsSchema = z5.object({
|
|
5817
|
+
filepath: z5.string().min(1, "Filepath cannot be empty").describe(
|
|
5818
|
+
"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)."
|
|
5819
|
+
),
|
|
5820
|
+
depth: z5.number().int().min(1).max(1).default(1).describe(
|
|
5821
|
+
"Depth of transitive dependencies. Only depth=1 (direct dependents) is currently supported.\n\n1 = Direct dependents only"
|
|
5822
|
+
)
|
|
5823
|
+
});
|
|
5824
|
+
|
|
5691
5825
|
// src/mcp/tools.ts
|
|
5692
5826
|
var tools = [
|
|
5693
5827
|
toMCPToolSchema(
|
|
@@ -5722,15 +5856,39 @@ MANDATORY: Call this BEFORE editing any file. Accepts single path or array of pa
|
|
|
5722
5856
|
|
|
5723
5857
|
Single file:
|
|
5724
5858
|
get_files_context({ filepaths: "src/auth.ts" })
|
|
5859
|
+
|
|
5860
|
+
Returns:
|
|
5861
|
+
{
|
|
5862
|
+
file: "src/auth.ts",
|
|
5863
|
+
chunks: [...],
|
|
5864
|
+
testAssociations: ["src/__tests__/auth.test.ts"]
|
|
5865
|
+
}
|
|
5725
5866
|
|
|
5726
5867
|
Multiple files (batch):
|
|
5727
5868
|
get_files_context({ filepaths: ["src/auth.ts", "src/user.ts"] })
|
|
5869
|
+
|
|
5870
|
+
Returns:
|
|
5871
|
+
{
|
|
5872
|
+
files: {
|
|
5873
|
+
"src/auth.ts": {
|
|
5874
|
+
chunks: [...],
|
|
5875
|
+
testAssociations: ["src/__tests__/auth.test.ts"]
|
|
5876
|
+
},
|
|
5877
|
+
"src/user.ts": {
|
|
5878
|
+
chunks: [...],
|
|
5879
|
+
testAssociations: ["src/__tests__/user.test.ts"]
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5728
5883
|
|
|
5729
5884
|
Returns for each file:
|
|
5730
5885
|
- All chunks and related code
|
|
5731
|
-
- testAssociations
|
|
5886
|
+
- testAssociations: Array of test files that import this file (reverse dependency lookup)
|
|
5732
5887
|
- Relevance scoring
|
|
5733
5888
|
|
|
5889
|
+
ALWAYS check testAssociations before modifying source code.
|
|
5890
|
+
After changes, remind the user to run the associated tests.
|
|
5891
|
+
|
|
5734
5892
|
Batch calls are more efficient than multiple single-file calls.`
|
|
5735
5893
|
),
|
|
5736
5894
|
toMCPToolSchema(
|
|
@@ -5743,6 +5901,20 @@ Examples:
|
|
|
5743
5901
|
- "Find service classes" \u2192 list_functions({ pattern: ".*Service$" })
|
|
5744
5902
|
|
|
5745
5903
|
10x faster than semantic_search for structural/architectural queries. Use semantic_search instead when searching by what code DOES.`
|
|
5904
|
+
),
|
|
5905
|
+
toMCPToolSchema(
|
|
5906
|
+
GetDependentsSchema,
|
|
5907
|
+
"get_dependents",
|
|
5908
|
+
`Find all code that depends on a file (reverse dependency lookup). Use for impact analysis:
|
|
5909
|
+
- "What breaks if I change this?"
|
|
5910
|
+
- "Is this safe to delete?"
|
|
5911
|
+
- "What imports this module?"
|
|
5912
|
+
|
|
5913
|
+
Returns:
|
|
5914
|
+
- List of files that import the target
|
|
5915
|
+
- Risk level (low/medium/high/critical) based on dependent count and complexity
|
|
5916
|
+
|
|
5917
|
+
Example: get_dependents({ filepath: "src/utils/validate.ts" })`
|
|
5746
5918
|
)
|
|
5747
5919
|
];
|
|
5748
5920
|
|
|
@@ -5942,6 +6114,50 @@ function wrapToolHandler(schema, handler) {
|
|
|
5942
6114
|
};
|
|
5943
6115
|
}
|
|
5944
6116
|
|
|
6117
|
+
// src/mcp/utils/path-matching.ts
|
|
6118
|
+
function normalizePath(path19, workspaceRoot) {
|
|
6119
|
+
let normalized = path19.replace(/['"]/g, "").trim().replace(/\\/g, "/");
|
|
6120
|
+
normalized = normalized.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
6121
|
+
if (normalized.startsWith(workspaceRoot + "/")) {
|
|
6122
|
+
normalized = normalized.substring(workspaceRoot.length + 1);
|
|
6123
|
+
}
|
|
6124
|
+
return normalized;
|
|
6125
|
+
}
|
|
6126
|
+
function matchesAtBoundary(str, pattern) {
|
|
6127
|
+
const index = str.indexOf(pattern);
|
|
6128
|
+
if (index === -1) return false;
|
|
6129
|
+
const charBefore = index > 0 ? str[index - 1] : "/";
|
|
6130
|
+
if (charBefore !== "/" && index !== 0) return false;
|
|
6131
|
+
const endIndex = index + pattern.length;
|
|
6132
|
+
if (endIndex === str.length) return true;
|
|
6133
|
+
const charAfter = str[endIndex];
|
|
6134
|
+
return charAfter === "/";
|
|
6135
|
+
}
|
|
6136
|
+
function matchesFile(normalizedImport, normalizedTarget) {
|
|
6137
|
+
if (normalizedImport === normalizedTarget) return true;
|
|
6138
|
+
if (matchesAtBoundary(normalizedImport, normalizedTarget)) {
|
|
6139
|
+
return true;
|
|
6140
|
+
}
|
|
6141
|
+
if (matchesAtBoundary(normalizedTarget, normalizedImport)) {
|
|
6142
|
+
return true;
|
|
6143
|
+
}
|
|
6144
|
+
const cleanedImport = normalizedImport.replace(/^(\.\.?\/)+/, "");
|
|
6145
|
+
if (matchesAtBoundary(cleanedImport, normalizedTarget) || matchesAtBoundary(normalizedTarget, cleanedImport)) {
|
|
6146
|
+
return true;
|
|
6147
|
+
}
|
|
6148
|
+
return false;
|
|
6149
|
+
}
|
|
6150
|
+
function getCanonicalPath(filepath, workspaceRoot) {
|
|
6151
|
+
let canonical = filepath.replace(/\\/g, "/");
|
|
6152
|
+
if (canonical.startsWith(workspaceRoot + "/")) {
|
|
6153
|
+
canonical = canonical.substring(workspaceRoot.length + 1);
|
|
6154
|
+
}
|
|
6155
|
+
return canonical;
|
|
6156
|
+
}
|
|
6157
|
+
function isTestFile2(filepath) {
|
|
6158
|
+
return /\.(test|spec)\.[^/]+$/.test(filepath) || /(^|[/\\])(test|tests|__tests__)[/\\]/.test(filepath);
|
|
6159
|
+
}
|
|
6160
|
+
|
|
5945
6161
|
// src/mcp/server.ts
|
|
5946
6162
|
init_errors();
|
|
5947
6163
|
var __filename4 = fileURLToPath4(import.meta.url);
|
|
@@ -5953,6 +6169,31 @@ try {
|
|
|
5953
6169
|
} catch {
|
|
5954
6170
|
packageJson3 = require4(join3(__dirname4, "../../package.json"));
|
|
5955
6171
|
}
|
|
6172
|
+
var DEPENDENT_COUNT_THRESHOLDS = {
|
|
6173
|
+
LOW: 5,
|
|
6174
|
+
// Few dependents, safe to change
|
|
6175
|
+
MEDIUM: 15,
|
|
6176
|
+
// Moderate impact, review dependents
|
|
6177
|
+
HIGH: 30
|
|
6178
|
+
// High impact, careful planning needed
|
|
6179
|
+
};
|
|
6180
|
+
var COMPLEXITY_THRESHOLDS = {
|
|
6181
|
+
HIGH_COMPLEXITY_DEPENDENT: 10,
|
|
6182
|
+
// Individual file is complex
|
|
6183
|
+
CRITICAL_AVG: 15,
|
|
6184
|
+
// Average complexity indicates systemic complexity
|
|
6185
|
+
CRITICAL_MAX: 25,
|
|
6186
|
+
// Peak complexity indicates hotspot
|
|
6187
|
+
HIGH_AVG: 10,
|
|
6188
|
+
// Moderately complex on average
|
|
6189
|
+
HIGH_MAX: 20,
|
|
6190
|
+
// Some complex functions exist
|
|
6191
|
+
MEDIUM_AVG: 6,
|
|
6192
|
+
// Slightly above simple code
|
|
6193
|
+
MEDIUM_MAX: 15
|
|
6194
|
+
// Occasional branching
|
|
6195
|
+
};
|
|
6196
|
+
var SCAN_LIMIT = 1e4;
|
|
5956
6197
|
async function startMCPServer(options) {
|
|
5957
6198
|
const { rootDir, verbose, watch } = options;
|
|
5958
6199
|
const log = (message) => {
|
|
@@ -6049,6 +6290,7 @@ async function startMCPServer(options) {
|
|
|
6049
6290
|
const isSingleFile = !Array.isArray(validatedArgs.filepaths);
|
|
6050
6291
|
log(`Getting context for: ${filepaths.join(", ")}`);
|
|
6051
6292
|
await checkAndReconnect();
|
|
6293
|
+
const workspaceRoot = process.cwd().replace(/\\/g, "/");
|
|
6052
6294
|
const fileEmbeddings = await Promise.all(filepaths.map((fp) => embeddings.embed(fp)));
|
|
6053
6295
|
const allFileSearches = await Promise.all(
|
|
6054
6296
|
fileEmbeddings.map(
|
|
@@ -6057,9 +6299,11 @@ async function startMCPServer(options) {
|
|
|
6057
6299
|
);
|
|
6058
6300
|
const fileChunksMap = filepaths.map((filepath, i) => {
|
|
6059
6301
|
const allResults = allFileSearches[i];
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6302
|
+
const targetCanonical = getCanonicalPath(filepath, workspaceRoot);
|
|
6303
|
+
return allResults.filter((r) => {
|
|
6304
|
+
const chunkCanonical = getCanonicalPath(r.metadata.file, workspaceRoot);
|
|
6305
|
+
return chunkCanonical === targetCanonical;
|
|
6306
|
+
});
|
|
6063
6307
|
});
|
|
6064
6308
|
let relatedChunksMap = [];
|
|
6065
6309
|
if (validatedArgs.includeRelated) {
|
|
@@ -6076,18 +6320,57 @@ async function startMCPServer(options) {
|
|
|
6076
6320
|
relatedChunksMap = Array.from({ length: filepaths.length }, () => []);
|
|
6077
6321
|
filesWithChunks.forEach(({ filepath, index }, i) => {
|
|
6078
6322
|
const related = relatedSearches[i];
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6323
|
+
const targetCanonical = getCanonicalPath(filepath, workspaceRoot);
|
|
6324
|
+
relatedChunksMap[index] = related.filter((r) => {
|
|
6325
|
+
const chunkCanonical = getCanonicalPath(r.metadata.file, workspaceRoot);
|
|
6326
|
+
return chunkCanonical !== targetCanonical;
|
|
6327
|
+
});
|
|
6082
6328
|
});
|
|
6083
6329
|
}
|
|
6084
6330
|
}
|
|
6331
|
+
const allChunks = await vectorDB.scanWithFilter({ limit: SCAN_LIMIT });
|
|
6332
|
+
if (allChunks.length === SCAN_LIMIT) {
|
|
6333
|
+
log(`WARNING: Scanned ${SCAN_LIMIT} chunks (limit reached). Test associations may be incomplete for large codebases.`);
|
|
6334
|
+
}
|
|
6335
|
+
const pathCache = /* @__PURE__ */ new Map();
|
|
6336
|
+
const normalizePathCached = (path19) => {
|
|
6337
|
+
if (pathCache.has(path19)) return pathCache.get(path19);
|
|
6338
|
+
const normalized = normalizePath(path19, workspaceRoot);
|
|
6339
|
+
pathCache.set(path19, normalized);
|
|
6340
|
+
return normalized;
|
|
6341
|
+
};
|
|
6342
|
+
const testAssociationsMap = filepaths.map((filepath) => {
|
|
6343
|
+
const normalizedTarget = normalizePathCached(filepath);
|
|
6344
|
+
const testFiles = /* @__PURE__ */ new Set();
|
|
6345
|
+
for (const chunk of allChunks) {
|
|
6346
|
+
const chunkFile2 = getCanonicalPath(chunk.metadata.file, workspaceRoot);
|
|
6347
|
+
if (!isTestFile2(chunkFile2)) continue;
|
|
6348
|
+
const imports = chunk.metadata.imports || [];
|
|
6349
|
+
for (const imp of imports) {
|
|
6350
|
+
const normalizedImport = normalizePathCached(imp);
|
|
6351
|
+
if (matchesFile(normalizedImport, normalizedTarget)) {
|
|
6352
|
+
testFiles.add(chunkFile2);
|
|
6353
|
+
break;
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
return Array.from(testFiles);
|
|
6358
|
+
});
|
|
6085
6359
|
const filesData = {};
|
|
6086
6360
|
filepaths.forEach((filepath, i) => {
|
|
6087
6361
|
const fileChunks = fileChunksMap[i];
|
|
6088
6362
|
const relatedChunks = relatedChunksMap[i] || [];
|
|
6363
|
+
const seenChunks = /* @__PURE__ */ new Set();
|
|
6364
|
+
const dedupedChunks = [...fileChunks, ...relatedChunks].filter((chunk) => {
|
|
6365
|
+
const canonicalFile = getCanonicalPath(chunk.metadata.file, workspaceRoot);
|
|
6366
|
+
const chunkId = `${canonicalFile}:${chunk.metadata.startLine}-${chunk.metadata.endLine}`;
|
|
6367
|
+
if (seenChunks.has(chunkId)) return false;
|
|
6368
|
+
seenChunks.add(chunkId);
|
|
6369
|
+
return true;
|
|
6370
|
+
});
|
|
6089
6371
|
filesData[filepath] = {
|
|
6090
|
-
chunks:
|
|
6372
|
+
chunks: dedupedChunks,
|
|
6373
|
+
testAssociations: testAssociationsMap[i]
|
|
6091
6374
|
};
|
|
6092
6375
|
});
|
|
6093
6376
|
log(`Found ${Object.values(filesData).reduce((sum, f) => sum + f.chunks.length, 0)} total chunks`);
|
|
@@ -6096,7 +6379,8 @@ async function startMCPServer(options) {
|
|
|
6096
6379
|
return {
|
|
6097
6380
|
indexInfo: getIndexMetadata(),
|
|
6098
6381
|
file: filepath,
|
|
6099
|
-
chunks: filesData[filepath].chunks
|
|
6382
|
+
chunks: filesData[filepath].chunks,
|
|
6383
|
+
testAssociations: filesData[filepath].testAssociations
|
|
6100
6384
|
};
|
|
6101
6385
|
} else {
|
|
6102
6386
|
return {
|
|
@@ -6147,6 +6431,144 @@ async function startMCPServer(options) {
|
|
|
6147
6431
|
};
|
|
6148
6432
|
}
|
|
6149
6433
|
)(args);
|
|
6434
|
+
case "get_dependents":
|
|
6435
|
+
return await wrapToolHandler(
|
|
6436
|
+
GetDependentsSchema,
|
|
6437
|
+
async (validatedArgs) => {
|
|
6438
|
+
log(`Finding dependents of: ${validatedArgs.filepath}`);
|
|
6439
|
+
await checkAndReconnect();
|
|
6440
|
+
const allChunks = await vectorDB.scanWithFilter({ limit: SCAN_LIMIT });
|
|
6441
|
+
if (allChunks.length === SCAN_LIMIT) {
|
|
6442
|
+
log(`WARNING: Scanned ${SCAN_LIMIT} chunks (limit reached). Results may be incomplete for large codebases.`);
|
|
6443
|
+
}
|
|
6444
|
+
log(`Scanning ${allChunks.length} chunks for imports...`);
|
|
6445
|
+
const workspaceRoot = process.cwd().replace(/\\/g, "/");
|
|
6446
|
+
const pathCache = /* @__PURE__ */ new Map();
|
|
6447
|
+
const normalizePathCached = (path19) => {
|
|
6448
|
+
if (pathCache.has(path19)) return pathCache.get(path19);
|
|
6449
|
+
const normalized = normalizePath(path19, workspaceRoot);
|
|
6450
|
+
pathCache.set(path19, normalized);
|
|
6451
|
+
return normalized;
|
|
6452
|
+
};
|
|
6453
|
+
const importIndex = /* @__PURE__ */ new Map();
|
|
6454
|
+
for (const chunk of allChunks) {
|
|
6455
|
+
const imports = chunk.metadata.imports || [];
|
|
6456
|
+
for (const imp of imports) {
|
|
6457
|
+
const normalizedImport = normalizePathCached(imp);
|
|
6458
|
+
if (!importIndex.has(normalizedImport)) {
|
|
6459
|
+
importIndex.set(normalizedImport, []);
|
|
6460
|
+
}
|
|
6461
|
+
importIndex.get(normalizedImport).push(chunk);
|
|
6462
|
+
}
|
|
6463
|
+
}
|
|
6464
|
+
const normalizedTarget = normalizePathCached(validatedArgs.filepath);
|
|
6465
|
+
const dependentChunks = [];
|
|
6466
|
+
const seenChunkIds = /* @__PURE__ */ new Set();
|
|
6467
|
+
if (importIndex.has(normalizedTarget)) {
|
|
6468
|
+
for (const chunk of importIndex.get(normalizedTarget)) {
|
|
6469
|
+
const chunkId = `${chunk.metadata.file}:${chunk.metadata.startLine}-${chunk.metadata.endLine}`;
|
|
6470
|
+
if (!seenChunkIds.has(chunkId)) {
|
|
6471
|
+
dependentChunks.push(chunk);
|
|
6472
|
+
seenChunkIds.add(chunkId);
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
for (const [normalizedImport, chunks] of importIndex.entries()) {
|
|
6477
|
+
if (normalizedImport !== normalizedTarget && matchesFile(normalizedImport, normalizedTarget)) {
|
|
6478
|
+
for (const chunk of chunks) {
|
|
6479
|
+
const chunkId = `${chunk.metadata.file}:${chunk.metadata.startLine}-${chunk.metadata.endLine}`;
|
|
6480
|
+
if (!seenChunkIds.has(chunkId)) {
|
|
6481
|
+
dependentChunks.push(chunk);
|
|
6482
|
+
seenChunkIds.add(chunkId);
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
const chunksByFile = /* @__PURE__ */ new Map();
|
|
6488
|
+
for (const chunk of dependentChunks) {
|
|
6489
|
+
const canonical = getCanonicalPath(chunk.metadata.file, workspaceRoot);
|
|
6490
|
+
const existing = chunksByFile.get(canonical) || [];
|
|
6491
|
+
existing.push(chunk);
|
|
6492
|
+
chunksByFile.set(canonical, existing);
|
|
6493
|
+
}
|
|
6494
|
+
const fileComplexities = [];
|
|
6495
|
+
for (const [filepath, chunks] of chunksByFile.entries()) {
|
|
6496
|
+
const complexities = chunks.map((c) => c.metadata.complexity).filter((c) => typeof c === "number" && c > 0);
|
|
6497
|
+
if (complexities.length > 0) {
|
|
6498
|
+
const sum = complexities.reduce((a, b) => a + b, 0);
|
|
6499
|
+
const avg = sum / complexities.length;
|
|
6500
|
+
const max = Math.max(...complexities);
|
|
6501
|
+
fileComplexities.push({
|
|
6502
|
+
filepath,
|
|
6503
|
+
avgComplexity: Math.round(avg * 10) / 10,
|
|
6504
|
+
// Round to 1 decimal
|
|
6505
|
+
maxComplexity: max,
|
|
6506
|
+
complexityScore: sum,
|
|
6507
|
+
chunksWithComplexity: complexities.length
|
|
6508
|
+
});
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
let complexityMetrics;
|
|
6512
|
+
if (fileComplexities.length > 0) {
|
|
6513
|
+
const allAvgs = fileComplexities.map((f) => f.avgComplexity);
|
|
6514
|
+
const allMaxes = fileComplexities.map((f) => f.maxComplexity);
|
|
6515
|
+
const totalAvg = allAvgs.reduce((a, b) => a + b, 0) / allAvgs.length;
|
|
6516
|
+
const globalMax = Math.max(...allMaxes);
|
|
6517
|
+
const highComplexityDependents = fileComplexities.filter((f) => f.maxComplexity > COMPLEXITY_THRESHOLDS.HIGH_COMPLEXITY_DEPENDENT).sort((a, b) => b.maxComplexity - a.maxComplexity).slice(0, 5).map((f) => ({
|
|
6518
|
+
filepath: f.filepath,
|
|
6519
|
+
maxComplexity: f.maxComplexity,
|
|
6520
|
+
avgComplexity: f.avgComplexity
|
|
6521
|
+
}));
|
|
6522
|
+
let complexityRiskBoost = "low";
|
|
6523
|
+
if (totalAvg > COMPLEXITY_THRESHOLDS.CRITICAL_AVG || globalMax > COMPLEXITY_THRESHOLDS.CRITICAL_MAX) {
|
|
6524
|
+
complexityRiskBoost = "critical";
|
|
6525
|
+
} else if (totalAvg > COMPLEXITY_THRESHOLDS.HIGH_AVG || globalMax > COMPLEXITY_THRESHOLDS.HIGH_MAX) {
|
|
6526
|
+
complexityRiskBoost = "high";
|
|
6527
|
+
} else if (totalAvg > COMPLEXITY_THRESHOLDS.MEDIUM_AVG || globalMax > COMPLEXITY_THRESHOLDS.MEDIUM_MAX) {
|
|
6528
|
+
complexityRiskBoost = "medium";
|
|
6529
|
+
}
|
|
6530
|
+
complexityMetrics = {
|
|
6531
|
+
averageComplexity: Math.round(totalAvg * 10) / 10,
|
|
6532
|
+
maxComplexity: globalMax,
|
|
6533
|
+
filesWithComplexityData: fileComplexities.length,
|
|
6534
|
+
highComplexityDependents,
|
|
6535
|
+
complexityRiskBoost
|
|
6536
|
+
};
|
|
6537
|
+
} else {
|
|
6538
|
+
complexityMetrics = {
|
|
6539
|
+
averageComplexity: 0,
|
|
6540
|
+
maxComplexity: 0,
|
|
6541
|
+
filesWithComplexityData: 0,
|
|
6542
|
+
highComplexityDependents: [],
|
|
6543
|
+
complexityRiskBoost: "low"
|
|
6544
|
+
};
|
|
6545
|
+
}
|
|
6546
|
+
const uniqueFiles = Array.from(chunksByFile.keys()).map((filepath) => ({
|
|
6547
|
+
filepath,
|
|
6548
|
+
isTestFile: isTestFile2(filepath)
|
|
6549
|
+
}));
|
|
6550
|
+
const count = uniqueFiles.length;
|
|
6551
|
+
let riskLevel = count === 0 ? "low" : count <= DEPENDENT_COUNT_THRESHOLDS.LOW ? "low" : count <= DEPENDENT_COUNT_THRESHOLDS.MEDIUM ? "medium" : count <= DEPENDENT_COUNT_THRESHOLDS.HIGH ? "high" : "critical";
|
|
6552
|
+
const RISK_ORDER = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
6553
|
+
if (RISK_ORDER[complexityMetrics.complexityRiskBoost] > RISK_ORDER[riskLevel]) {
|
|
6554
|
+
riskLevel = complexityMetrics.complexityRiskBoost;
|
|
6555
|
+
}
|
|
6556
|
+
log(`Found ${count} dependent files (risk: ${riskLevel}${complexityMetrics.filesWithComplexityData > 0 ? ", complexity-boosted" : ""})`);
|
|
6557
|
+
let note;
|
|
6558
|
+
if (allChunks.length === SCAN_LIMIT) {
|
|
6559
|
+
note = `Warning: Scanned ${SCAN_LIMIT} chunks (limit reached). Results may be incomplete for large codebases. Some dependents might not be listed.`;
|
|
6560
|
+
}
|
|
6561
|
+
return {
|
|
6562
|
+
indexInfo: getIndexMetadata(),
|
|
6563
|
+
filepath: validatedArgs.filepath,
|
|
6564
|
+
dependentCount: count,
|
|
6565
|
+
riskLevel,
|
|
6566
|
+
dependents: uniqueFiles,
|
|
6567
|
+
complexityMetrics,
|
|
6568
|
+
note
|
|
6569
|
+
};
|
|
6570
|
+
}
|
|
6571
|
+
)(args);
|
|
6150
6572
|
default:
|
|
6151
6573
|
throw new LienError(
|
|
6152
6574
|
`Unknown tool: ${name}`,
|
|
@@ -6319,11 +6741,11 @@ async function startMCPServer(options) {
|
|
|
6319
6741
|
|
|
6320
6742
|
// src/cli/serve.ts
|
|
6321
6743
|
async function serveCommand(options) {
|
|
6322
|
-
const rootDir = options.root ?
|
|
6744
|
+
const rootDir = options.root ? path18.resolve(options.root) : process.cwd();
|
|
6323
6745
|
try {
|
|
6324
6746
|
if (options.root) {
|
|
6325
6747
|
try {
|
|
6326
|
-
const stats = await
|
|
6748
|
+
const stats = await fs19.stat(rootDir);
|
|
6327
6749
|
if (!stats.isDirectory()) {
|
|
6328
6750
|
console.error(chalk7.red(`Error: --root path is not a directory: ${rootDir}`));
|
|
6329
6751
|
process.exit(1);
|