@liendev/lien 0.13.0 → 0.14.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 +112 -7
- package/README.md +9 -1
- package/dist/index.js +1419 -884
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -106,59 +106,9 @@ var init_schema = __esm({
|
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
// src/config/merge.ts
|
|
110
|
-
function deepMergeConfig(defaults, user) {
|
|
111
|
-
return {
|
|
112
|
-
version: user.version ?? defaults.version,
|
|
113
|
-
core: {
|
|
114
|
-
...defaults.core,
|
|
115
|
-
...user.core
|
|
116
|
-
},
|
|
117
|
-
chunking: {
|
|
118
|
-
...defaults.chunking,
|
|
119
|
-
...user.chunking
|
|
120
|
-
},
|
|
121
|
-
mcp: {
|
|
122
|
-
...defaults.mcp,
|
|
123
|
-
...user.mcp
|
|
124
|
-
},
|
|
125
|
-
gitDetection: {
|
|
126
|
-
...defaults.gitDetection,
|
|
127
|
-
...user.gitDetection
|
|
128
|
-
},
|
|
129
|
-
fileWatching: {
|
|
130
|
-
...defaults.fileWatching,
|
|
131
|
-
...user.fileWatching
|
|
132
|
-
},
|
|
133
|
-
frameworks: user.frameworks ?? defaults.frameworks
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function detectNewFields(before, after) {
|
|
137
|
-
const newFields = [];
|
|
138
|
-
for (const key of Object.keys(after)) {
|
|
139
|
-
if (!(key in before)) {
|
|
140
|
-
newFields.push(key);
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (typeof after[key] === "object" && after[key] !== null && !Array.isArray(after[key])) {
|
|
144
|
-
const beforeSection = before[key] || {};
|
|
145
|
-
const afterSection = after[key];
|
|
146
|
-
for (const nestedKey of Object.keys(afterSection)) {
|
|
147
|
-
if (!(nestedKey in beforeSection)) {
|
|
148
|
-
newFields.push(`${key}.${nestedKey}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return newFields;
|
|
154
|
-
}
|
|
155
|
-
var init_merge = __esm({
|
|
156
|
-
"src/config/merge.ts"() {
|
|
157
|
-
"use strict";
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
109
|
// src/config/migration.ts
|
|
110
|
+
import fs from "fs/promises";
|
|
111
|
+
import path from "path";
|
|
162
112
|
function needsMigration(config) {
|
|
163
113
|
if (!config) {
|
|
164
114
|
return false;
|
|
@@ -248,6 +198,36 @@ function migrateConfig(oldConfig) {
|
|
|
248
198
|
}
|
|
249
199
|
return newConfig;
|
|
250
200
|
}
|
|
201
|
+
async function migrateConfigFile(rootDir = process.cwd()) {
|
|
202
|
+
const configPath = path.join(rootDir, ".lien.config.json");
|
|
203
|
+
try {
|
|
204
|
+
const configContent = await fs.readFile(configPath, "utf-8");
|
|
205
|
+
const oldConfig = JSON.parse(configContent);
|
|
206
|
+
if (!needsMigration(oldConfig)) {
|
|
207
|
+
return {
|
|
208
|
+
migrated: false,
|
|
209
|
+
config: oldConfig
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const newConfig = migrateConfig(oldConfig);
|
|
213
|
+
const backupPath = `${configPath}.v0.2.0.backup`;
|
|
214
|
+
await fs.copyFile(configPath, backupPath);
|
|
215
|
+
await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
|
|
216
|
+
return {
|
|
217
|
+
migrated: true,
|
|
218
|
+
backupPath,
|
|
219
|
+
config: newConfig
|
|
220
|
+
};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (error.code === "ENOENT") {
|
|
223
|
+
return {
|
|
224
|
+
migrated: false,
|
|
225
|
+
config: defaultConfig
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
251
231
|
var init_migration = __esm({
|
|
252
232
|
"src/config/migration.ts"() {
|
|
253
233
|
"use strict";
|
|
@@ -256,6 +236,58 @@ var init_migration = __esm({
|
|
|
256
236
|
}
|
|
257
237
|
});
|
|
258
238
|
|
|
239
|
+
// src/config/merge.ts
|
|
240
|
+
function deepMergeConfig(defaults, user) {
|
|
241
|
+
return {
|
|
242
|
+
version: user.version ?? defaults.version,
|
|
243
|
+
core: {
|
|
244
|
+
...defaults.core,
|
|
245
|
+
...user.core
|
|
246
|
+
},
|
|
247
|
+
chunking: {
|
|
248
|
+
...defaults.chunking,
|
|
249
|
+
...user.chunking
|
|
250
|
+
},
|
|
251
|
+
mcp: {
|
|
252
|
+
...defaults.mcp,
|
|
253
|
+
...user.mcp
|
|
254
|
+
},
|
|
255
|
+
gitDetection: {
|
|
256
|
+
...defaults.gitDetection,
|
|
257
|
+
...user.gitDetection
|
|
258
|
+
},
|
|
259
|
+
fileWatching: {
|
|
260
|
+
...defaults.fileWatching,
|
|
261
|
+
...user.fileWatching
|
|
262
|
+
},
|
|
263
|
+
frameworks: user.frameworks ?? defaults.frameworks
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function detectNewFields(before, after) {
|
|
267
|
+
const newFields = [];
|
|
268
|
+
for (const key of Object.keys(after)) {
|
|
269
|
+
if (!(key in before)) {
|
|
270
|
+
newFields.push(key);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (typeof after[key] === "object" && after[key] !== null && !Array.isArray(after[key])) {
|
|
274
|
+
const beforeSection = before[key] || {};
|
|
275
|
+
const afterSection = after[key];
|
|
276
|
+
for (const nestedKey of Object.keys(afterSection)) {
|
|
277
|
+
if (!(nestedKey in beforeSection)) {
|
|
278
|
+
newFields.push(`${key}.${nestedKey}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return newFields;
|
|
284
|
+
}
|
|
285
|
+
var init_merge = __esm({
|
|
286
|
+
"src/config/merge.ts"() {
|
|
287
|
+
"use strict";
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
259
291
|
// src/errors/codes.ts
|
|
260
292
|
var init_codes = __esm({
|
|
261
293
|
"src/errors/codes.ts"() {
|
|
@@ -346,8 +378,8 @@ var init_errors = __esm({
|
|
|
346
378
|
});
|
|
347
379
|
|
|
348
380
|
// src/config/service.ts
|
|
349
|
-
import
|
|
350
|
-
import
|
|
381
|
+
import fs8 from "fs/promises";
|
|
382
|
+
import path8 from "path";
|
|
351
383
|
var ConfigService, configService;
|
|
352
384
|
var init_service = __esm({
|
|
353
385
|
"src/config/service.ts"() {
|
|
@@ -369,13 +401,13 @@ var init_service = __esm({
|
|
|
369
401
|
async load(rootDir = process.cwd()) {
|
|
370
402
|
const configPath = this.getConfigPath(rootDir);
|
|
371
403
|
try {
|
|
372
|
-
const configContent = await
|
|
404
|
+
const configContent = await fs8.readFile(configPath, "utf-8");
|
|
373
405
|
const userConfig = JSON.parse(configContent);
|
|
374
406
|
if (this.needsMigration(userConfig)) {
|
|
375
407
|
console.log("\u{1F504} Migrating config from v0.2.0 to v0.3.0...");
|
|
376
408
|
const result = await this.migrate(rootDir);
|
|
377
409
|
if (result.migrated && result.backupPath) {
|
|
378
|
-
const backupFilename =
|
|
410
|
+
const backupFilename = path8.basename(result.backupPath);
|
|
379
411
|
console.log(`\u2705 Migration complete! Backup saved as ${backupFilename}`);
|
|
380
412
|
console.log("\u{1F4DD} Your config now uses the framework-based structure.");
|
|
381
413
|
}
|
|
@@ -431,7 +463,7 @@ ${validation.errors.join("\n")}`,
|
|
|
431
463
|
}
|
|
432
464
|
try {
|
|
433
465
|
const configJson = JSON.stringify(config, null, 2) + "\n";
|
|
434
|
-
await
|
|
466
|
+
await fs8.writeFile(configPath, configJson, "utf-8");
|
|
435
467
|
} catch (error) {
|
|
436
468
|
throw wrapError(error, "Failed to save configuration", { path: configPath });
|
|
437
469
|
}
|
|
@@ -445,7 +477,7 @@ ${validation.errors.join("\n")}`,
|
|
|
445
477
|
async exists(rootDir = process.cwd()) {
|
|
446
478
|
const configPath = this.getConfigPath(rootDir);
|
|
447
479
|
try {
|
|
448
|
-
await
|
|
480
|
+
await fs8.access(configPath);
|
|
449
481
|
return true;
|
|
450
482
|
} catch {
|
|
451
483
|
return false;
|
|
@@ -462,7 +494,7 @@ ${validation.errors.join("\n")}`,
|
|
|
462
494
|
async migrate(rootDir = process.cwd()) {
|
|
463
495
|
const configPath = this.getConfigPath(rootDir);
|
|
464
496
|
try {
|
|
465
|
-
const configContent = await
|
|
497
|
+
const configContent = await fs8.readFile(configPath, "utf-8");
|
|
466
498
|
const oldConfig = JSON.parse(configContent);
|
|
467
499
|
if (!this.needsMigration(oldConfig)) {
|
|
468
500
|
return {
|
|
@@ -480,7 +512,7 @@ ${validation.errors.join("\n")}`,
|
|
|
480
512
|
);
|
|
481
513
|
}
|
|
482
514
|
const backupPath = `${configPath}.v0.2.0.backup`;
|
|
483
|
-
await
|
|
515
|
+
await fs8.copyFile(configPath, backupPath);
|
|
484
516
|
await this.save(rootDir, newConfig);
|
|
485
517
|
return {
|
|
486
518
|
migrated: true,
|
|
@@ -578,7 +610,7 @@ ${validation.errors.join("\n")}`,
|
|
|
578
610
|
* Get the full path to the config file
|
|
579
611
|
*/
|
|
580
612
|
getConfigPath(rootDir) {
|
|
581
|
-
return
|
|
613
|
+
return path8.join(rootDir, _ConfigService.CONFIG_FILENAME);
|
|
582
614
|
}
|
|
583
615
|
/**
|
|
584
616
|
* Validate modern (v0.3.0+) configuration
|
|
@@ -742,7 +774,7 @@ ${validation.errors.join("\n")}`,
|
|
|
742
774
|
errors.push(`frameworks[${index}] missing required field: path`);
|
|
743
775
|
} else if (typeof fw.path !== "string") {
|
|
744
776
|
errors.push(`frameworks[${index}].path must be a string`);
|
|
745
|
-
} else if (
|
|
777
|
+
} else if (path8.isAbsolute(fw.path)) {
|
|
746
778
|
errors.push(`frameworks[${index}].path must be relative, got: ${fw.path}`);
|
|
747
779
|
}
|
|
748
780
|
if (fw.enabled === void 0) {
|
|
@@ -802,12 +834,12 @@ __export(utils_exports, {
|
|
|
802
834
|
});
|
|
803
835
|
import { exec } from "child_process";
|
|
804
836
|
import { promisify } from "util";
|
|
805
|
-
import
|
|
806
|
-
import
|
|
837
|
+
import fs9 from "fs/promises";
|
|
838
|
+
import path9 from "path";
|
|
807
839
|
async function isGitRepo(rootDir) {
|
|
808
840
|
try {
|
|
809
|
-
const gitDir =
|
|
810
|
-
await
|
|
841
|
+
const gitDir = path9.join(rootDir, ".git");
|
|
842
|
+
await fs9.access(gitDir);
|
|
811
843
|
return true;
|
|
812
844
|
} catch {
|
|
813
845
|
return false;
|
|
@@ -846,7 +878,7 @@ async function getChangedFiles(rootDir, fromRef, toRef) {
|
|
|
846
878
|
// 10 second timeout for diffs
|
|
847
879
|
}
|
|
848
880
|
);
|
|
849
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) =>
|
|
881
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path9.join(rootDir, file));
|
|
850
882
|
return files;
|
|
851
883
|
} catch (error) {
|
|
852
884
|
throw new Error(`Failed to get changed files: ${error}`);
|
|
@@ -861,7 +893,7 @@ async function getChangedFilesInCommit(rootDir, commitSha) {
|
|
|
861
893
|
timeout: 1e4
|
|
862
894
|
}
|
|
863
895
|
);
|
|
864
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) =>
|
|
896
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path9.join(rootDir, file));
|
|
865
897
|
return files;
|
|
866
898
|
} catch (error) {
|
|
867
899
|
throw new Error(`Failed to get changed files in commit: ${error}`);
|
|
@@ -876,7 +908,7 @@ async function getChangedFilesBetweenCommits(rootDir, fromCommit, toCommit) {
|
|
|
876
908
|
timeout: 1e4
|
|
877
909
|
}
|
|
878
910
|
);
|
|
879
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) =>
|
|
911
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path9.join(rootDir, file));
|
|
880
912
|
return files;
|
|
881
913
|
} catch (error) {
|
|
882
914
|
throw new Error(`Failed to get changed files between commits: ${error}`);
|
|
@@ -899,21 +931,21 @@ var init_utils = __esm({
|
|
|
899
931
|
});
|
|
900
932
|
|
|
901
933
|
// src/vectordb/version.ts
|
|
902
|
-
import
|
|
903
|
-
import
|
|
934
|
+
import fs10 from "fs/promises";
|
|
935
|
+
import path10 from "path";
|
|
904
936
|
async function writeVersionFile(indexPath) {
|
|
905
937
|
try {
|
|
906
|
-
const versionFilePath =
|
|
938
|
+
const versionFilePath = path10.join(indexPath, VERSION_FILE);
|
|
907
939
|
const timestamp = Date.now().toString();
|
|
908
|
-
await
|
|
940
|
+
await fs10.writeFile(versionFilePath, timestamp, "utf-8");
|
|
909
941
|
} catch (error) {
|
|
910
942
|
console.error(`Warning: Failed to write version file: ${error}`);
|
|
911
943
|
}
|
|
912
944
|
}
|
|
913
945
|
async function readVersionFile(indexPath) {
|
|
914
946
|
try {
|
|
915
|
-
const versionFilePath =
|
|
916
|
-
const content = await
|
|
947
|
+
const versionFilePath = path10.join(indexPath, VERSION_FILE);
|
|
948
|
+
const content = await fs10.readFile(versionFilePath, "utf-8");
|
|
917
949
|
const timestamp = parseInt(content.trim(), 10);
|
|
918
950
|
return isNaN(timestamp) ? 0 : timestamp;
|
|
919
951
|
} catch (error) {
|
|
@@ -931,8 +963,8 @@ var init_version2 = __esm({
|
|
|
931
963
|
// src/indexer/scanner.ts
|
|
932
964
|
import { glob } from "glob";
|
|
933
965
|
import ignore from "ignore";
|
|
934
|
-
import
|
|
935
|
-
import
|
|
966
|
+
import fs12 from "fs/promises";
|
|
967
|
+
import path12 from "path";
|
|
936
968
|
async function scanCodebaseWithFrameworks(rootDir, config) {
|
|
937
969
|
const allFiles = [];
|
|
938
970
|
for (const framework of config.frameworks) {
|
|
@@ -945,16 +977,16 @@ async function scanCodebaseWithFrameworks(rootDir, config) {
|
|
|
945
977
|
return allFiles;
|
|
946
978
|
}
|
|
947
979
|
async function scanFramework(rootDir, framework) {
|
|
948
|
-
const frameworkPath =
|
|
949
|
-
const gitignorePath =
|
|
980
|
+
const frameworkPath = path12.join(rootDir, framework.path);
|
|
981
|
+
const gitignorePath = path12.join(frameworkPath, ".gitignore");
|
|
950
982
|
let ig = ignore();
|
|
951
983
|
try {
|
|
952
|
-
const gitignoreContent = await
|
|
984
|
+
const gitignoreContent = await fs12.readFile(gitignorePath, "utf-8");
|
|
953
985
|
ig = ignore().add(gitignoreContent);
|
|
954
986
|
} catch (e) {
|
|
955
|
-
const rootGitignorePath =
|
|
987
|
+
const rootGitignorePath = path12.join(rootDir, ".gitignore");
|
|
956
988
|
try {
|
|
957
|
-
const gitignoreContent = await
|
|
989
|
+
const gitignoreContent = await fs12.readFile(rootGitignorePath, "utf-8");
|
|
958
990
|
ig = ignore().add(gitignoreContent);
|
|
959
991
|
} catch (e2) {
|
|
960
992
|
}
|
|
@@ -976,15 +1008,15 @@ async function scanFramework(rootDir, framework) {
|
|
|
976
1008
|
}
|
|
977
1009
|
const uniqueFiles = Array.from(new Set(allFiles));
|
|
978
1010
|
return uniqueFiles.filter((file) => !ig.ignores(file)).map((file) => {
|
|
979
|
-
return framework.path === "." ? file :
|
|
1011
|
+
return framework.path === "." ? file : path12.join(framework.path, file);
|
|
980
1012
|
});
|
|
981
1013
|
}
|
|
982
1014
|
async function scanCodebase(options) {
|
|
983
1015
|
const { rootDir, includePatterns = [], excludePatterns = [] } = options;
|
|
984
|
-
const gitignorePath =
|
|
1016
|
+
const gitignorePath = path12.join(rootDir, ".gitignore");
|
|
985
1017
|
let ig = ignore();
|
|
986
1018
|
try {
|
|
987
|
-
const gitignoreContent = await
|
|
1019
|
+
const gitignoreContent = await fs12.readFile(gitignorePath, "utf-8");
|
|
988
1020
|
ig = ignore().add(gitignoreContent);
|
|
989
1021
|
} catch (e) {
|
|
990
1022
|
}
|
|
@@ -1011,12 +1043,12 @@ async function scanCodebase(options) {
|
|
|
1011
1043
|
}
|
|
1012
1044
|
const uniqueFiles = Array.from(new Set(allFiles));
|
|
1013
1045
|
return uniqueFiles.filter((file) => {
|
|
1014
|
-
const relativePath =
|
|
1046
|
+
const relativePath = path12.relative(rootDir, file);
|
|
1015
1047
|
return !ig.ignores(relativePath);
|
|
1016
1048
|
});
|
|
1017
1049
|
}
|
|
1018
1050
|
function detectLanguage(filepath) {
|
|
1019
|
-
const ext =
|
|
1051
|
+
const ext = path12.extname(filepath).toLowerCase();
|
|
1020
1052
|
const languageMap = {
|
|
1021
1053
|
".ts": "typescript",
|
|
1022
1054
|
".tsx": "typescript",
|
|
@@ -1355,6 +1387,7 @@ var init_symbol_extractor = __esm({
|
|
|
1355
1387
|
import Parser from "tree-sitter";
|
|
1356
1388
|
import TypeScript from "tree-sitter-typescript";
|
|
1357
1389
|
import JavaScript from "tree-sitter-javascript";
|
|
1390
|
+
import PHPParser from "tree-sitter-php";
|
|
1358
1391
|
import { extname } from "path";
|
|
1359
1392
|
function getParser(language) {
|
|
1360
1393
|
if (!parserCache.has(language)) {
|
|
@@ -1379,6 +1412,8 @@ function detectLanguage2(filePath) {
|
|
|
1379
1412
|
case "mjs":
|
|
1380
1413
|
case "cjs":
|
|
1381
1414
|
return "javascript";
|
|
1415
|
+
case "php":
|
|
1416
|
+
return "php";
|
|
1382
1417
|
default:
|
|
1383
1418
|
return null;
|
|
1384
1419
|
}
|
|
@@ -1411,86 +1446,87 @@ var init_parser = __esm({
|
|
|
1411
1446
|
parserCache = /* @__PURE__ */ new Map();
|
|
1412
1447
|
languageConfig = {
|
|
1413
1448
|
typescript: TypeScript.typescript,
|
|
1414
|
-
javascript: JavaScript
|
|
1415
|
-
|
|
1449
|
+
javascript: JavaScript,
|
|
1450
|
+
php: PHPParser.php
|
|
1451
|
+
// Note: tree-sitter-php exports both 'php' (mixed HTML/PHP) and 'php_only'
|
|
1416
1452
|
};
|
|
1417
1453
|
}
|
|
1418
1454
|
});
|
|
1419
1455
|
|
|
1420
1456
|
// src/indexer/ast/symbols.ts
|
|
1421
|
-
function
|
|
1422
|
-
const
|
|
1423
|
-
if (
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
const nameNode = parent.childForFieldName("name");
|
|
1443
|
-
name = nameNode?.text || "anonymous";
|
|
1444
|
-
}
|
|
1445
|
-
return {
|
|
1446
|
-
name,
|
|
1447
|
-
type: parentClass ? "method" : "function",
|
|
1448
|
-
startLine: node.startPosition.row + 1,
|
|
1449
|
-
endLine: node.endPosition.row + 1,
|
|
1450
|
-
parentClass,
|
|
1451
|
-
signature: extractSignature(node, content),
|
|
1452
|
-
parameters: extractParameters(node, content),
|
|
1453
|
-
complexity: calculateComplexity(node)
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
if (type === "method_definition") {
|
|
1457
|
-
const nameNode = node.childForFieldName("name");
|
|
1458
|
-
if (!nameNode) return null;
|
|
1459
|
-
return {
|
|
1460
|
-
name: nameNode.text,
|
|
1461
|
-
type: "method",
|
|
1462
|
-
startLine: node.startPosition.row + 1,
|
|
1463
|
-
endLine: node.endPosition.row + 1,
|
|
1464
|
-
parentClass,
|
|
1465
|
-
signature: extractSignature(node, content),
|
|
1466
|
-
parameters: extractParameters(node, content),
|
|
1467
|
-
returnType: extractReturnType(node, content),
|
|
1468
|
-
complexity: calculateComplexity(node)
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
if (type === "class_declaration") {
|
|
1472
|
-
const nameNode = node.childForFieldName("name");
|
|
1473
|
-
if (!nameNode) return null;
|
|
1474
|
-
return {
|
|
1475
|
-
name: nameNode.text,
|
|
1476
|
-
type: "class",
|
|
1477
|
-
startLine: node.startPosition.row + 1,
|
|
1478
|
-
endLine: node.endPosition.row + 1,
|
|
1479
|
-
signature: `class ${nameNode.text}`
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
if (type === "interface_declaration") {
|
|
1483
|
-
const nameNode = node.childForFieldName("name");
|
|
1484
|
-
if (!nameNode) return null;
|
|
1485
|
-
return {
|
|
1486
|
-
name: nameNode.text,
|
|
1487
|
-
type: "interface",
|
|
1488
|
-
startLine: node.startPosition.row + 1,
|
|
1489
|
-
endLine: node.endPosition.row + 1,
|
|
1490
|
-
signature: `interface ${nameNode.text}`
|
|
1491
|
-
};
|
|
1457
|
+
function extractFunctionInfo(node, content, parentClass) {
|
|
1458
|
+
const nameNode = node.childForFieldName("name");
|
|
1459
|
+
if (!nameNode) return null;
|
|
1460
|
+
return {
|
|
1461
|
+
name: nameNode.text,
|
|
1462
|
+
type: parentClass ? "method" : "function",
|
|
1463
|
+
startLine: node.startPosition.row + 1,
|
|
1464
|
+
endLine: node.endPosition.row + 1,
|
|
1465
|
+
parentClass,
|
|
1466
|
+
signature: extractSignature(node, content),
|
|
1467
|
+
parameters: extractParameters(node, content),
|
|
1468
|
+
returnType: extractReturnType(node, content),
|
|
1469
|
+
complexity: calculateComplexity(node)
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
function extractArrowFunctionInfo(node, content, parentClass) {
|
|
1473
|
+
const parent = node.parent;
|
|
1474
|
+
let name = "anonymous";
|
|
1475
|
+
if (parent?.type === "variable_declarator") {
|
|
1476
|
+
const nameNode = parent.childForFieldName("name");
|
|
1477
|
+
name = nameNode?.text || "anonymous";
|
|
1492
1478
|
}
|
|
1493
|
-
return
|
|
1479
|
+
return {
|
|
1480
|
+
name,
|
|
1481
|
+
type: parentClass ? "method" : "function",
|
|
1482
|
+
startLine: node.startPosition.row + 1,
|
|
1483
|
+
endLine: node.endPosition.row + 1,
|
|
1484
|
+
parentClass,
|
|
1485
|
+
signature: extractSignature(node, content),
|
|
1486
|
+
parameters: extractParameters(node, content),
|
|
1487
|
+
complexity: calculateComplexity(node)
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function extractMethodInfo(node, content, parentClass) {
|
|
1491
|
+
const nameNode = node.childForFieldName("name");
|
|
1492
|
+
if (!nameNode) return null;
|
|
1493
|
+
return {
|
|
1494
|
+
name: nameNode.text,
|
|
1495
|
+
type: "method",
|
|
1496
|
+
startLine: node.startPosition.row + 1,
|
|
1497
|
+
endLine: node.endPosition.row + 1,
|
|
1498
|
+
parentClass,
|
|
1499
|
+
signature: extractSignature(node, content),
|
|
1500
|
+
parameters: extractParameters(node, content),
|
|
1501
|
+
returnType: extractReturnType(node, content),
|
|
1502
|
+
complexity: calculateComplexity(node)
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function extractClassInfo(node, _content, _parentClass) {
|
|
1506
|
+
const nameNode = node.childForFieldName("name");
|
|
1507
|
+
if (!nameNode) return null;
|
|
1508
|
+
return {
|
|
1509
|
+
name: nameNode.text,
|
|
1510
|
+
type: "class",
|
|
1511
|
+
startLine: node.startPosition.row + 1,
|
|
1512
|
+
endLine: node.endPosition.row + 1,
|
|
1513
|
+
signature: `class ${nameNode.text}`
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
function extractInterfaceInfo(node, _content, _parentClass) {
|
|
1517
|
+
const nameNode = node.childForFieldName("name");
|
|
1518
|
+
if (!nameNode) return null;
|
|
1519
|
+
return {
|
|
1520
|
+
name: nameNode.text,
|
|
1521
|
+
type: "interface",
|
|
1522
|
+
startLine: node.startPosition.row + 1,
|
|
1523
|
+
endLine: node.endPosition.row + 1,
|
|
1524
|
+
signature: `interface ${nameNode.text}`
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
function extractSymbolInfo(node, content, parentClass) {
|
|
1528
|
+
const extractor = symbolExtractors[node.type];
|
|
1529
|
+
return extractor ? extractor(node, content, parentClass) : null;
|
|
1494
1530
|
}
|
|
1495
1531
|
function extractSignature(node, content) {
|
|
1496
1532
|
const startLine = node.startPosition.row;
|
|
@@ -1527,6 +1563,7 @@ function extractReturnType(node, _content) {
|
|
|
1527
1563
|
function calculateComplexity(node) {
|
|
1528
1564
|
let complexity = 1;
|
|
1529
1565
|
const decisionPoints = [
|
|
1566
|
+
// TypeScript/JavaScript
|
|
1530
1567
|
"if_statement",
|
|
1531
1568
|
"while_statement",
|
|
1532
1569
|
"do_statement",
|
|
@@ -1538,8 +1575,11 @@ function calculateComplexity(node) {
|
|
|
1538
1575
|
"switch_case",
|
|
1539
1576
|
"catch_clause",
|
|
1540
1577
|
"ternary_expression",
|
|
1541
|
-
"binary_expression"
|
|
1578
|
+
"binary_expression",
|
|
1542
1579
|
// For && and ||
|
|
1580
|
+
// PHP
|
|
1581
|
+
"foreach_statement"
|
|
1582
|
+
// PHP foreach loops
|
|
1543
1583
|
];
|
|
1544
1584
|
function traverse(n) {
|
|
1545
1585
|
if (decisionPoints.includes(n.type)) {
|
|
@@ -1580,9 +1620,200 @@ function extractImports(rootNode) {
|
|
|
1580
1620
|
traverse(rootNode);
|
|
1581
1621
|
return imports;
|
|
1582
1622
|
}
|
|
1623
|
+
var symbolExtractors;
|
|
1583
1624
|
var init_symbols = __esm({
|
|
1584
1625
|
"src/indexer/ast/symbols.ts"() {
|
|
1585
1626
|
"use strict";
|
|
1627
|
+
symbolExtractors = {
|
|
1628
|
+
// TypeScript/JavaScript
|
|
1629
|
+
"function_declaration": extractFunctionInfo,
|
|
1630
|
+
"function": extractFunctionInfo,
|
|
1631
|
+
"arrow_function": extractArrowFunctionInfo,
|
|
1632
|
+
"function_expression": extractArrowFunctionInfo,
|
|
1633
|
+
"method_definition": extractMethodInfo,
|
|
1634
|
+
"class_declaration": extractClassInfo,
|
|
1635
|
+
"interface_declaration": extractInterfaceInfo,
|
|
1636
|
+
// PHP
|
|
1637
|
+
"function_definition": extractFunctionInfo,
|
|
1638
|
+
// PHP functions
|
|
1639
|
+
"method_declaration": extractMethodInfo
|
|
1640
|
+
// PHP methods
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
// src/indexer/ast/traversers/typescript.ts
|
|
1646
|
+
var TypeScriptTraverser, JavaScriptTraverser;
|
|
1647
|
+
var init_typescript = __esm({
|
|
1648
|
+
"src/indexer/ast/traversers/typescript.ts"() {
|
|
1649
|
+
"use strict";
|
|
1650
|
+
TypeScriptTraverser = class {
|
|
1651
|
+
targetNodeTypes = [
|
|
1652
|
+
"function_declaration",
|
|
1653
|
+
"function",
|
|
1654
|
+
"interface_declaration",
|
|
1655
|
+
"method_definition",
|
|
1656
|
+
"lexical_declaration",
|
|
1657
|
+
// For const/let with arrow functions
|
|
1658
|
+
"variable_declaration"
|
|
1659
|
+
// For var with functions
|
|
1660
|
+
];
|
|
1661
|
+
containerTypes = [
|
|
1662
|
+
"class_declaration"
|
|
1663
|
+
// We extract methods, not the class itself
|
|
1664
|
+
];
|
|
1665
|
+
declarationTypes = [
|
|
1666
|
+
"lexical_declaration",
|
|
1667
|
+
// const/let
|
|
1668
|
+
"variable_declaration"
|
|
1669
|
+
// var
|
|
1670
|
+
];
|
|
1671
|
+
functionTypes = [
|
|
1672
|
+
"arrow_function",
|
|
1673
|
+
"function_expression",
|
|
1674
|
+
"function"
|
|
1675
|
+
];
|
|
1676
|
+
shouldExtractChildren(node) {
|
|
1677
|
+
return this.containerTypes.includes(node.type);
|
|
1678
|
+
}
|
|
1679
|
+
isDeclarationWithFunction(node) {
|
|
1680
|
+
return this.declarationTypes.includes(node.type);
|
|
1681
|
+
}
|
|
1682
|
+
getContainerBody(node) {
|
|
1683
|
+
if (node.type === "class_declaration") {
|
|
1684
|
+
return node.childForFieldName("body");
|
|
1685
|
+
}
|
|
1686
|
+
return null;
|
|
1687
|
+
}
|
|
1688
|
+
shouldTraverseChildren(node) {
|
|
1689
|
+
return node.type === "program" || node.type === "export_statement" || node.type === "class_body";
|
|
1690
|
+
}
|
|
1691
|
+
findParentContainerName(node) {
|
|
1692
|
+
let current = node.parent;
|
|
1693
|
+
while (current) {
|
|
1694
|
+
if (current.type === "class_declaration") {
|
|
1695
|
+
const nameNode = current.childForFieldName("name");
|
|
1696
|
+
return nameNode?.text;
|
|
1697
|
+
}
|
|
1698
|
+
current = current.parent;
|
|
1699
|
+
}
|
|
1700
|
+
return void 0;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Check if a declaration node contains a function (arrow, function expression, etc.)
|
|
1704
|
+
*/
|
|
1705
|
+
findFunctionInDeclaration(node) {
|
|
1706
|
+
const search2 = (n, depth) => {
|
|
1707
|
+
if (depth > 3) return null;
|
|
1708
|
+
if (this.functionTypes.includes(n.type)) {
|
|
1709
|
+
return n;
|
|
1710
|
+
}
|
|
1711
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
1712
|
+
const child = n.child(i);
|
|
1713
|
+
if (child) {
|
|
1714
|
+
const result = search2(child, depth + 1);
|
|
1715
|
+
if (result) return result;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return null;
|
|
1719
|
+
};
|
|
1720
|
+
const functionNode = search2(node, 0);
|
|
1721
|
+
return {
|
|
1722
|
+
hasFunction: functionNode !== null,
|
|
1723
|
+
functionNode
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
JavaScriptTraverser = class extends TypeScriptTraverser {
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
// src/indexer/ast/traversers/php.ts
|
|
1733
|
+
var PHPTraverser;
|
|
1734
|
+
var init_php = __esm({
|
|
1735
|
+
"src/indexer/ast/traversers/php.ts"() {
|
|
1736
|
+
"use strict";
|
|
1737
|
+
PHPTraverser = class {
|
|
1738
|
+
targetNodeTypes = [
|
|
1739
|
+
"function_definition",
|
|
1740
|
+
// function foo() {}
|
|
1741
|
+
"method_declaration"
|
|
1742
|
+
// public function bar() {}
|
|
1743
|
+
];
|
|
1744
|
+
containerTypes = [
|
|
1745
|
+
"class_declaration",
|
|
1746
|
+
// We extract methods, not the class itself
|
|
1747
|
+
"trait_declaration",
|
|
1748
|
+
// PHP traits
|
|
1749
|
+
"interface_declaration"
|
|
1750
|
+
// PHP interfaces (for interface methods)
|
|
1751
|
+
];
|
|
1752
|
+
declarationTypes = [
|
|
1753
|
+
// PHP doesn't have arrow functions or const/let like JS
|
|
1754
|
+
// Functions are always defined with 'function' keyword
|
|
1755
|
+
];
|
|
1756
|
+
functionTypes = [
|
|
1757
|
+
"function_definition",
|
|
1758
|
+
"method_declaration"
|
|
1759
|
+
];
|
|
1760
|
+
shouldExtractChildren(node) {
|
|
1761
|
+
return this.containerTypes.includes(node.type);
|
|
1762
|
+
}
|
|
1763
|
+
isDeclarationWithFunction(_node) {
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
getContainerBody(node) {
|
|
1767
|
+
if (node.type === "class_declaration" || node.type === "trait_declaration" || node.type === "interface_declaration") {
|
|
1768
|
+
return node.childForFieldName("body");
|
|
1769
|
+
}
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
shouldTraverseChildren(node) {
|
|
1773
|
+
return node.type === "program" || // Top-level PHP file
|
|
1774
|
+
node.type === "php" || // PHP block
|
|
1775
|
+
node.type === "declaration_list";
|
|
1776
|
+
}
|
|
1777
|
+
findParentContainerName(node) {
|
|
1778
|
+
let current = node.parent;
|
|
1779
|
+
while (current) {
|
|
1780
|
+
if (current.type === "class_declaration" || current.type === "trait_declaration") {
|
|
1781
|
+
const nameNode = current.childForFieldName("name");
|
|
1782
|
+
return nameNode?.text;
|
|
1783
|
+
}
|
|
1784
|
+
current = current.parent;
|
|
1785
|
+
}
|
|
1786
|
+
return void 0;
|
|
1787
|
+
}
|
|
1788
|
+
findFunctionInDeclaration(_node) {
|
|
1789
|
+
return {
|
|
1790
|
+
hasFunction: false,
|
|
1791
|
+
functionNode: null
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
// src/indexer/ast/traversers/index.ts
|
|
1799
|
+
function getTraverser(language) {
|
|
1800
|
+
const traverser = traverserRegistry[language];
|
|
1801
|
+
if (!traverser) {
|
|
1802
|
+
throw new Error(`No traverser available for language: ${language}`);
|
|
1803
|
+
}
|
|
1804
|
+
return traverser;
|
|
1805
|
+
}
|
|
1806
|
+
var traverserRegistry;
|
|
1807
|
+
var init_traversers = __esm({
|
|
1808
|
+
"src/indexer/ast/traversers/index.ts"() {
|
|
1809
|
+
"use strict";
|
|
1810
|
+
init_typescript();
|
|
1811
|
+
init_php();
|
|
1812
|
+
traverserRegistry = {
|
|
1813
|
+
typescript: new TypeScriptTraverser(),
|
|
1814
|
+
javascript: new JavaScriptTraverser(),
|
|
1815
|
+
php: new PHPTraverser()
|
|
1816
|
+
};
|
|
1586
1817
|
}
|
|
1587
1818
|
});
|
|
1588
1819
|
|
|
@@ -1600,20 +1831,18 @@ function chunkByAST(filepath, content, options = {}) {
|
|
|
1600
1831
|
const chunks = [];
|
|
1601
1832
|
const lines = content.split("\n");
|
|
1602
1833
|
const rootNode = parseResult.tree.rootNode;
|
|
1834
|
+
const traverser = getTraverser(language);
|
|
1603
1835
|
const fileImports = extractImports(rootNode);
|
|
1604
|
-
const topLevelNodes = findTopLevelNodes(rootNode);
|
|
1836
|
+
const topLevelNodes = findTopLevelNodes(rootNode, traverser);
|
|
1605
1837
|
for (const node of topLevelNodes) {
|
|
1606
1838
|
let actualNode = node;
|
|
1607
|
-
if (
|
|
1608
|
-
const
|
|
1609
|
-
if (
|
|
1610
|
-
actualNode =
|
|
1839
|
+
if (traverser.isDeclarationWithFunction(node)) {
|
|
1840
|
+
const declInfo = traverser.findFunctionInDeclaration(node);
|
|
1841
|
+
if (declInfo.functionNode) {
|
|
1842
|
+
actualNode = declInfo.functionNode;
|
|
1611
1843
|
}
|
|
1612
1844
|
}
|
|
1613
|
-
|
|
1614
|
-
if (actualNode.type === "method_definition") {
|
|
1615
|
-
parentClassName = findParentClassName(actualNode);
|
|
1616
|
-
}
|
|
1845
|
+
const parentClassName = traverser.findParentContainerName(actualNode);
|
|
1617
1846
|
const symbolInfo = extractSymbolInfo(actualNode, content, parentClassName);
|
|
1618
1847
|
const nodeContent = getNodeContent(node, lines);
|
|
1619
1848
|
chunks.push(createChunk(filepath, node, nodeContent, symbolInfo, fileImports, language));
|
|
@@ -1634,57 +1863,28 @@ function chunkByAST(filepath, content, options = {}) {
|
|
|
1634
1863
|
chunks.sort((a, b) => a.metadata.startLine - b.metadata.startLine);
|
|
1635
1864
|
return chunks;
|
|
1636
1865
|
}
|
|
1637
|
-
function
|
|
1638
|
-
let current = methodNode.parent;
|
|
1639
|
-
while (current) {
|
|
1640
|
-
if (current.type === "class_declaration") {
|
|
1641
|
-
const nameNode = current.childForFieldName("name");
|
|
1642
|
-
return nameNode?.text;
|
|
1643
|
-
}
|
|
1644
|
-
current = current.parent;
|
|
1645
|
-
}
|
|
1646
|
-
return void 0;
|
|
1647
|
-
}
|
|
1648
|
-
function findTopLevelNodes(rootNode) {
|
|
1866
|
+
function findTopLevelNodes(rootNode, traverser) {
|
|
1649
1867
|
const nodes = [];
|
|
1650
|
-
const targetTypes = [
|
|
1651
|
-
"function_declaration",
|
|
1652
|
-
"function",
|
|
1653
|
-
// Note: 'class_declaration' is NOT included here - we extract methods individually
|
|
1654
|
-
"interface_declaration",
|
|
1655
|
-
"method_definition",
|
|
1656
|
-
"lexical_declaration",
|
|
1657
|
-
// For const/let with arrow functions
|
|
1658
|
-
"variable_declaration"
|
|
1659
|
-
// For var with functions
|
|
1660
|
-
];
|
|
1661
1868
|
function traverse(node, depth) {
|
|
1662
|
-
if ((node
|
|
1663
|
-
const
|
|
1664
|
-
if (hasFunction) {
|
|
1869
|
+
if (traverser.isDeclarationWithFunction(node) && depth === 0) {
|
|
1870
|
+
const declInfo = traverser.findFunctionInDeclaration(node);
|
|
1871
|
+
if (declInfo.hasFunction) {
|
|
1665
1872
|
nodes.push(node);
|
|
1666
1873
|
return;
|
|
1667
1874
|
}
|
|
1668
1875
|
}
|
|
1669
|
-
if (depth <= 1 &&
|
|
1876
|
+
if (depth <= 1 && traverser.targetNodeTypes.includes(node.type)) {
|
|
1670
1877
|
nodes.push(node);
|
|
1671
1878
|
return;
|
|
1672
1879
|
}
|
|
1673
|
-
if (node
|
|
1674
|
-
|
|
1675
|
-
const child = node.namedChild(i);
|
|
1676
|
-
if (child) traverse(child, depth);
|
|
1677
|
-
}
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
if (node.type === "class_declaration") {
|
|
1681
|
-
const body = node.childForFieldName("body");
|
|
1880
|
+
if (traverser.shouldExtractChildren(node)) {
|
|
1881
|
+
const body = traverser.getContainerBody(node);
|
|
1682
1882
|
if (body) {
|
|
1683
1883
|
traverse(body, depth + 1);
|
|
1684
1884
|
}
|
|
1685
1885
|
return;
|
|
1686
1886
|
}
|
|
1687
|
-
if (
|
|
1887
|
+
if (traverser.shouldTraverseChildren(node)) {
|
|
1688
1888
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1689
1889
|
const child = node.namedChild(i);
|
|
1690
1890
|
if (child) traverse(child, depth);
|
|
@@ -1694,41 +1894,6 @@ function findTopLevelNodes(rootNode) {
|
|
|
1694
1894
|
traverse(rootNode, 0);
|
|
1695
1895
|
return nodes;
|
|
1696
1896
|
}
|
|
1697
|
-
function findFunctionInDeclaration(node) {
|
|
1698
|
-
const functionTypes = ["arrow_function", "function_expression", "function"];
|
|
1699
|
-
function search(n, depth) {
|
|
1700
|
-
if (depth > 3) return false;
|
|
1701
|
-
if (functionTypes.includes(n.type)) {
|
|
1702
|
-
return true;
|
|
1703
|
-
}
|
|
1704
|
-
for (let i = 0; i < n.childCount; i++) {
|
|
1705
|
-
const child = n.child(i);
|
|
1706
|
-
if (child && search(child, depth + 1)) {
|
|
1707
|
-
return true;
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
return false;
|
|
1711
|
-
}
|
|
1712
|
-
return search(node, 0);
|
|
1713
|
-
}
|
|
1714
|
-
function findActualFunctionNode(node) {
|
|
1715
|
-
const functionTypes = ["arrow_function", "function_expression", "function"];
|
|
1716
|
-
function search(n, depth) {
|
|
1717
|
-
if (depth > 3) return null;
|
|
1718
|
-
if (functionTypes.includes(n.type)) {
|
|
1719
|
-
return n;
|
|
1720
|
-
}
|
|
1721
|
-
for (let i = 0; i < n.childCount; i++) {
|
|
1722
|
-
const child = n.child(i);
|
|
1723
|
-
if (child) {
|
|
1724
|
-
const result = search(child, depth + 1);
|
|
1725
|
-
if (result) return result;
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
return null;
|
|
1729
|
-
}
|
|
1730
|
-
return search(node, 0);
|
|
1731
|
-
}
|
|
1732
1897
|
function getNodeContent(node, lines) {
|
|
1733
1898
|
const startLine = node.startPosition.row;
|
|
1734
1899
|
const endLine = node.endPosition.row;
|
|
@@ -1770,67 +1935,323 @@ function createChunk(filepath, node, content, symbolInfo, imports, language) {
|
|
|
1770
1935
|
}
|
|
1771
1936
|
};
|
|
1772
1937
|
}
|
|
1773
|
-
function
|
|
1774
|
-
const
|
|
1938
|
+
function findUncoveredRanges(coveredRanges, totalLines) {
|
|
1939
|
+
const uncoveredRanges = [];
|
|
1775
1940
|
let currentStart = 0;
|
|
1776
|
-
coveredRanges.sort((a, b) => a.start - b.start);
|
|
1777
|
-
for (const range of
|
|
1941
|
+
const sortedRanges = [...coveredRanges].sort((a, b) => a.start - b.start);
|
|
1942
|
+
for (const range of sortedRanges) {
|
|
1778
1943
|
if (currentStart < range.start) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
content,
|
|
1784
|
-
metadata: {
|
|
1785
|
-
file: filepath,
|
|
1786
|
-
startLine: currentStart + 1,
|
|
1787
|
-
endLine: range.start,
|
|
1788
|
-
type: "block",
|
|
1789
|
-
language,
|
|
1790
|
-
// Empty symbols for uncovered code (imports, exports, etc.)
|
|
1791
|
-
symbols: { functions: [], classes: [], interfaces: [] },
|
|
1792
|
-
imports
|
|
1793
|
-
}
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1944
|
+
uncoveredRanges.push({
|
|
1945
|
+
start: currentStart,
|
|
1946
|
+
end: range.start - 1
|
|
1947
|
+
});
|
|
1796
1948
|
}
|
|
1797
1949
|
currentStart = range.end + 1;
|
|
1798
1950
|
}
|
|
1799
|
-
if (currentStart <
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1951
|
+
if (currentStart < totalLines) {
|
|
1952
|
+
uncoveredRanges.push({
|
|
1953
|
+
start: currentStart,
|
|
1954
|
+
end: totalLines - 1
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
return uncoveredRanges;
|
|
1958
|
+
}
|
|
1959
|
+
function createChunkFromRange(range, lines, filepath, language, imports) {
|
|
1960
|
+
const uncoveredLines = lines.slice(range.start, range.end + 1);
|
|
1961
|
+
const content = uncoveredLines.join("\n").trim();
|
|
1962
|
+
return {
|
|
1963
|
+
content,
|
|
1964
|
+
metadata: {
|
|
1965
|
+
file: filepath,
|
|
1966
|
+
startLine: range.start + 1,
|
|
1967
|
+
endLine: range.end + 1,
|
|
1968
|
+
type: "block",
|
|
1969
|
+
language,
|
|
1970
|
+
// Empty symbols for uncovered code (imports, exports, etc.)
|
|
1971
|
+
symbols: { functions: [], classes: [], interfaces: [] },
|
|
1972
|
+
imports
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function isValidChunk(chunk, minChunkSize) {
|
|
1977
|
+
const lineCount = chunk.metadata.endLine - chunk.metadata.startLine + 1;
|
|
1978
|
+
return chunk.content.length > 0 && lineCount >= minChunkSize;
|
|
1979
|
+
}
|
|
1980
|
+
function extractUncoveredCode(lines, coveredRanges, filepath, minChunkSize, imports, language) {
|
|
1981
|
+
const uncoveredRanges = findUncoveredRanges(coveredRanges, lines.length);
|
|
1982
|
+
return uncoveredRanges.map((range) => createChunkFromRange(range, lines, filepath, language, imports)).filter((chunk) => isValidChunk(chunk, minChunkSize));
|
|
1983
|
+
}
|
|
1984
|
+
function shouldUseAST(filepath) {
|
|
1985
|
+
return isASTSupported(filepath);
|
|
1986
|
+
}
|
|
1987
|
+
var init_chunker = __esm({
|
|
1988
|
+
"src/indexer/ast/chunker.ts"() {
|
|
1989
|
+
"use strict";
|
|
1990
|
+
init_parser();
|
|
1991
|
+
init_symbols();
|
|
1992
|
+
init_traversers();
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
// src/indexer/liquid-chunker.ts
|
|
1997
|
+
function extractSchemaName(schemaContent) {
|
|
1998
|
+
try {
|
|
1999
|
+
let jsonContent = schemaContent.replace(/\{%-?\s*schema\s*-?%\}/g, "").replace(/\{%-?\s*endschema\s*-?%\}/g, "").trim();
|
|
2000
|
+
const schema = JSON.parse(jsonContent);
|
|
2001
|
+
return typeof schema.name === "string" ? schema.name : void 0;
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
}
|
|
2004
|
+
return void 0;
|
|
2005
|
+
}
|
|
2006
|
+
function removeComments(content) {
|
|
2007
|
+
return content.replace(/\{%-?\s*comment\s*-?%\}[\s\S]*?\{%-?\s*endcomment\s*-?%\}/g, "");
|
|
2008
|
+
}
|
|
2009
|
+
function extractRenderTags(contentWithoutComments) {
|
|
2010
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2011
|
+
const renderPattern = /\{%-?\s*render\s+['"]([^'"]+)['"]/g;
|
|
2012
|
+
let match;
|
|
2013
|
+
while ((match = renderPattern.exec(contentWithoutComments)) !== null) {
|
|
2014
|
+
dependencies.add(match[1]);
|
|
2015
|
+
}
|
|
2016
|
+
const includePattern = /\{%-?\s*include\s+['"]([^'"]+)['"]/g;
|
|
2017
|
+
while ((match = includePattern.exec(contentWithoutComments)) !== null) {
|
|
2018
|
+
dependencies.add(match[1]);
|
|
2019
|
+
}
|
|
2020
|
+
const sectionPattern = /\{%-?\s*section\s+['"]([^'"]+)['"]/g;
|
|
2021
|
+
while ((match = sectionPattern.exec(contentWithoutComments)) !== null) {
|
|
2022
|
+
dependencies.add(match[1]);
|
|
2023
|
+
}
|
|
2024
|
+
return Array.from(dependencies);
|
|
2025
|
+
}
|
|
2026
|
+
function findLiquidBlocks(content) {
|
|
2027
|
+
const lines = content.split("\n");
|
|
2028
|
+
const blocks = [];
|
|
2029
|
+
const blockPatterns = [
|
|
2030
|
+
{ type: "schema", start: /\{%-?\s*schema\s*-?%\}/, end: /\{%-?\s*endschema\s*-?%\}/ },
|
|
2031
|
+
{ type: "style", start: /\{%-?\s*style\s*-?%\}/, end: /\{%-?\s*endstyle\s*-?%\}/ },
|
|
2032
|
+
{ type: "javascript", start: /\{%-?\s*javascript\s*-?%\}/, end: /\{%-?\s*endjavascript\s*-?%\}/ }
|
|
2033
|
+
];
|
|
2034
|
+
for (const pattern of blockPatterns) {
|
|
2035
|
+
let searchStart = 0;
|
|
2036
|
+
while (searchStart < lines.length) {
|
|
2037
|
+
const startIdx = lines.findIndex(
|
|
2038
|
+
(line, idx) => idx >= searchStart && pattern.start.test(line)
|
|
2039
|
+
);
|
|
2040
|
+
if (startIdx === -1) break;
|
|
2041
|
+
const endIdx = lines.findIndex(
|
|
2042
|
+
(line, idx) => idx >= startIdx && pattern.end.test(line)
|
|
2043
|
+
);
|
|
2044
|
+
if (endIdx === -1) {
|
|
2045
|
+
break;
|
|
2046
|
+
}
|
|
2047
|
+
const blockContent = lines.slice(startIdx, endIdx + 1).join("\n");
|
|
2048
|
+
blocks.push({
|
|
2049
|
+
type: pattern.type,
|
|
2050
|
+
startLine: startIdx,
|
|
2051
|
+
endLine: endIdx,
|
|
2052
|
+
content: blockContent
|
|
2053
|
+
});
|
|
2054
|
+
searchStart = endIdx + 1;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return blocks.sort((a, b) => a.startLine - b.startLine);
|
|
2058
|
+
}
|
|
2059
|
+
function chunkLiquidFile(filepath, content, chunkSize = 75, chunkOverlap = 10) {
|
|
2060
|
+
const lines = content.split("\n");
|
|
2061
|
+
const blocks = findLiquidBlocks(content);
|
|
2062
|
+
const chunks = [];
|
|
2063
|
+
const contentWithoutComments = removeComments(content);
|
|
2064
|
+
const linesWithoutComments = contentWithoutComments.split("\n");
|
|
2065
|
+
const coveredLines = /* @__PURE__ */ new Set();
|
|
2066
|
+
for (const block of blocks) {
|
|
2067
|
+
for (let i = block.startLine; i <= block.endLine; i++) {
|
|
2068
|
+
coveredLines.add(i);
|
|
2069
|
+
}
|
|
2070
|
+
let symbolName;
|
|
2071
|
+
if (block.type === "schema") {
|
|
2072
|
+
symbolName = extractSchemaName(block.content);
|
|
2073
|
+
}
|
|
2074
|
+
const blockContentWithoutComments = linesWithoutComments.slice(block.startLine, block.endLine + 1).join("\n");
|
|
2075
|
+
const imports = extractRenderTags(blockContentWithoutComments);
|
|
2076
|
+
const blockLineCount = block.endLine - block.startLine + 1;
|
|
2077
|
+
const maxBlockSize = chunkSize * 3;
|
|
2078
|
+
if (blockLineCount <= maxBlockSize) {
|
|
1803
2079
|
chunks.push({
|
|
1804
|
-
content,
|
|
2080
|
+
content: block.content,
|
|
1805
2081
|
metadata: {
|
|
1806
2082
|
file: filepath,
|
|
1807
|
-
startLine:
|
|
1808
|
-
|
|
2083
|
+
startLine: block.startLine + 1,
|
|
2084
|
+
// 1-indexed
|
|
2085
|
+
endLine: block.endLine + 1,
|
|
2086
|
+
language: "liquid",
|
|
1809
2087
|
type: "block",
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
imports
|
|
2088
|
+
symbolName,
|
|
2089
|
+
symbolType: block.type,
|
|
2090
|
+
imports: imports.length > 0 ? imports : void 0
|
|
1814
2091
|
}
|
|
1815
2092
|
});
|
|
2093
|
+
} else {
|
|
2094
|
+
const blockLines = block.content.split("\n");
|
|
2095
|
+
for (let offset = 0; offset < blockLines.length; offset += chunkSize - chunkOverlap) {
|
|
2096
|
+
const endOffset = Math.min(offset + chunkSize, blockLines.length);
|
|
2097
|
+
const chunkContent = blockLines.slice(offset, endOffset).join("\n");
|
|
2098
|
+
if (chunkContent.trim().length > 0) {
|
|
2099
|
+
chunks.push({
|
|
2100
|
+
content: chunkContent,
|
|
2101
|
+
metadata: {
|
|
2102
|
+
file: filepath,
|
|
2103
|
+
startLine: block.startLine + offset + 1,
|
|
2104
|
+
// 1-indexed
|
|
2105
|
+
endLine: block.startLine + endOffset,
|
|
2106
|
+
// 1-indexed (endOffset already accounts for exclusivity)
|
|
2107
|
+
language: "liquid",
|
|
2108
|
+
type: "block",
|
|
2109
|
+
symbolName,
|
|
2110
|
+
// Preserve symbol name for all split chunks
|
|
2111
|
+
symbolType: block.type,
|
|
2112
|
+
imports: imports.length > 0 ? imports : void 0
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
if (endOffset >= blockLines.length) break;
|
|
2117
|
+
}
|
|
1816
2118
|
}
|
|
1817
2119
|
}
|
|
1818
|
-
|
|
2120
|
+
let currentChunk = [];
|
|
2121
|
+
let chunkStartLine = 0;
|
|
2122
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2123
|
+
if (coveredLines.has(i)) {
|
|
2124
|
+
if (currentChunk.length > 0) {
|
|
2125
|
+
const chunkContent = currentChunk.join("\n");
|
|
2126
|
+
if (chunkContent.trim().length > 0) {
|
|
2127
|
+
const cleanedChunk = linesWithoutComments.slice(chunkStartLine, i).join("\n");
|
|
2128
|
+
const imports = extractRenderTags(cleanedChunk);
|
|
2129
|
+
chunks.push({
|
|
2130
|
+
content: chunkContent,
|
|
2131
|
+
metadata: {
|
|
2132
|
+
file: filepath,
|
|
2133
|
+
startLine: chunkStartLine + 1,
|
|
2134
|
+
endLine: i,
|
|
2135
|
+
language: "liquid",
|
|
2136
|
+
type: "template",
|
|
2137
|
+
imports: imports.length > 0 ? imports : void 0
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
currentChunk = [];
|
|
2142
|
+
}
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
if (currentChunk.length === 0) {
|
|
2146
|
+
chunkStartLine = i;
|
|
2147
|
+
}
|
|
2148
|
+
currentChunk.push(lines[i]);
|
|
2149
|
+
if (currentChunk.length >= chunkSize) {
|
|
2150
|
+
const chunkContent = currentChunk.join("\n");
|
|
2151
|
+
if (chunkContent.trim().length > 0) {
|
|
2152
|
+
const cleanedChunk = linesWithoutComments.slice(chunkStartLine, i + 1).join("\n");
|
|
2153
|
+
const imports = extractRenderTags(cleanedChunk);
|
|
2154
|
+
chunks.push({
|
|
2155
|
+
content: chunkContent,
|
|
2156
|
+
metadata: {
|
|
2157
|
+
file: filepath,
|
|
2158
|
+
startLine: chunkStartLine + 1,
|
|
2159
|
+
endLine: i + 1,
|
|
2160
|
+
language: "liquid",
|
|
2161
|
+
type: "template",
|
|
2162
|
+
imports: imports.length > 0 ? imports : void 0
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
currentChunk = currentChunk.slice(-chunkOverlap);
|
|
2167
|
+
chunkStartLine = Math.max(0, i + 1 - chunkOverlap);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
if (currentChunk.length > 0) {
|
|
2171
|
+
const chunkContent = currentChunk.join("\n");
|
|
2172
|
+
if (chunkContent.trim().length === 0) {
|
|
2173
|
+
return chunks.sort((a, b) => a.metadata.startLine - b.metadata.startLine);
|
|
2174
|
+
}
|
|
2175
|
+
const cleanedChunk = linesWithoutComments.slice(chunkStartLine, lines.length).join("\n");
|
|
2176
|
+
const imports = extractRenderTags(cleanedChunk);
|
|
2177
|
+
chunks.push({
|
|
2178
|
+
content: chunkContent,
|
|
2179
|
+
metadata: {
|
|
2180
|
+
file: filepath,
|
|
2181
|
+
startLine: chunkStartLine + 1,
|
|
2182
|
+
endLine: lines.length,
|
|
2183
|
+
language: "liquid",
|
|
2184
|
+
type: "template",
|
|
2185
|
+
imports: imports.length > 0 ? imports : void 0
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
return chunks.sort((a, b) => a.metadata.startLine - b.metadata.startLine);
|
|
1819
2190
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
2191
|
+
var init_liquid_chunker = __esm({
|
|
2192
|
+
"src/indexer/liquid-chunker.ts"() {
|
|
2193
|
+
"use strict";
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
|
|
2197
|
+
// src/indexer/json-template-chunker.ts
|
|
2198
|
+
function extractSectionReferences(jsonContent) {
|
|
2199
|
+
try {
|
|
2200
|
+
const template = JSON.parse(jsonContent);
|
|
2201
|
+
const sectionTypes = /* @__PURE__ */ new Set();
|
|
2202
|
+
if (template.sections && typeof template.sections === "object") {
|
|
2203
|
+
for (const section of Object.values(template.sections)) {
|
|
2204
|
+
if (typeof section === "object" && section !== null && "type" in section && typeof section.type === "string") {
|
|
2205
|
+
sectionTypes.add(section.type);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return Array.from(sectionTypes);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
console.warn(`[Lien] Failed to parse JSON template: ${error instanceof Error ? error.message : String(error)}`);
|
|
2212
|
+
return [];
|
|
2213
|
+
}
|
|
1822
2214
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
2215
|
+
function extractTemplateName(filepath) {
|
|
2216
|
+
const match = filepath.match(/templates\/(.+)\.json$/);
|
|
2217
|
+
return match ? match[1] : void 0;
|
|
2218
|
+
}
|
|
2219
|
+
function chunkJSONTemplate(filepath, content) {
|
|
2220
|
+
if (content.trim().length === 0) {
|
|
2221
|
+
return [];
|
|
2222
|
+
}
|
|
2223
|
+
const lines = content.split("\n");
|
|
2224
|
+
const templateName = extractTemplateName(filepath);
|
|
2225
|
+
const sectionReferences = extractSectionReferences(content);
|
|
2226
|
+
return [{
|
|
2227
|
+
content,
|
|
2228
|
+
metadata: {
|
|
2229
|
+
file: filepath,
|
|
2230
|
+
startLine: 1,
|
|
2231
|
+
endLine: lines.length,
|
|
2232
|
+
language: "json",
|
|
2233
|
+
type: "template",
|
|
2234
|
+
symbolName: templateName,
|
|
2235
|
+
symbolType: "template",
|
|
2236
|
+
imports: sectionReferences.length > 0 ? sectionReferences : void 0
|
|
2237
|
+
}
|
|
2238
|
+
}];
|
|
2239
|
+
}
|
|
2240
|
+
var init_json_template_chunker = __esm({
|
|
2241
|
+
"src/indexer/json-template-chunker.ts"() {
|
|
1825
2242
|
"use strict";
|
|
1826
|
-
init_parser();
|
|
1827
|
-
init_symbols();
|
|
1828
2243
|
}
|
|
1829
2244
|
});
|
|
1830
2245
|
|
|
1831
2246
|
// src/indexer/chunker.ts
|
|
1832
2247
|
function chunkFile(filepath, content, options = {}) {
|
|
1833
2248
|
const { chunkSize = 75, chunkOverlap = 10, useAST = true, astFallback = "line-based" } = options;
|
|
2249
|
+
if (filepath.endsWith(".liquid")) {
|
|
2250
|
+
return chunkLiquidFile(filepath, content, chunkSize, chunkOverlap);
|
|
2251
|
+
}
|
|
2252
|
+
if (filepath.endsWith(".json") && /(?:^|\/)templates\//.test(filepath)) {
|
|
2253
|
+
return chunkJSONTemplate(filepath, content);
|
|
2254
|
+
}
|
|
1834
2255
|
if (useAST && shouldUseAST(filepath)) {
|
|
1835
2256
|
try {
|
|
1836
2257
|
return chunkByAST(filepath, content, {
|
|
@@ -1884,6 +2305,8 @@ var init_chunker2 = __esm({
|
|
|
1884
2305
|
init_scanner();
|
|
1885
2306
|
init_symbol_extractor();
|
|
1886
2307
|
init_chunker();
|
|
2308
|
+
init_liquid_chunker();
|
|
2309
|
+
init_json_template_chunker();
|
|
1887
2310
|
}
|
|
1888
2311
|
});
|
|
1889
2312
|
|
|
@@ -1994,18 +2417,11 @@ var init_intent_classifier = __esm({
|
|
|
1994
2417
|
}
|
|
1995
2418
|
});
|
|
1996
2419
|
|
|
1997
|
-
// src/vectordb/
|
|
1998
|
-
|
|
1999
|
-
__export(lancedb_exports, {
|
|
2000
|
-
VectorDB: () => VectorDB
|
|
2001
|
-
});
|
|
2002
|
-
import * as lancedb from "vectordb";
|
|
2003
|
-
import path11 from "path";
|
|
2004
|
-
import os2 from "os";
|
|
2005
|
-
import crypto2 from "crypto";
|
|
2420
|
+
// src/vectordb/query.ts
|
|
2421
|
+
import path13 from "path";
|
|
2006
2422
|
function isDocumentationFile(filepath) {
|
|
2007
2423
|
const lower = filepath.toLowerCase();
|
|
2008
|
-
const filename =
|
|
2424
|
+
const filename = path13.basename(filepath).toLowerCase();
|
|
2009
2425
|
if (filename.startsWith("readme")) return true;
|
|
2010
2426
|
if (filename.startsWith("changelog")) return true;
|
|
2011
2427
|
if (filename.endsWith(".md") || filename.endsWith(".mdx") || filename.endsWith(".markdown")) {
|
|
@@ -2052,7 +2468,7 @@ function boostPathRelevance(query, filepath, baseScore) {
|
|
|
2052
2468
|
return baseScore * boostFactor;
|
|
2053
2469
|
}
|
|
2054
2470
|
function boostFilenameRelevance(query, filepath, baseScore) {
|
|
2055
|
-
const filename =
|
|
2471
|
+
const filename = path13.basename(filepath, path13.extname(filepath)).toLowerCase();
|
|
2056
2472
|
const queryTokens = query.toLowerCase().split(/\s+/);
|
|
2057
2473
|
let boostFactor = 1;
|
|
2058
2474
|
for (const token of queryTokens) {
|
|
@@ -2067,7 +2483,7 @@ function boostFilenameRelevance(query, filepath, baseScore) {
|
|
|
2067
2483
|
}
|
|
2068
2484
|
function boostForLocationIntent(query, filepath, baseScore) {
|
|
2069
2485
|
let score = baseScore;
|
|
2070
|
-
const filename =
|
|
2486
|
+
const filename = path13.basename(filepath, path13.extname(filepath)).toLowerCase();
|
|
2071
2487
|
const queryTokens = query.toLowerCase().split(/\s+/);
|
|
2072
2488
|
for (const token of queryTokens) {
|
|
2073
2489
|
if (token.length <= 2) continue;
|
|
@@ -2095,51 +2511,408 @@ function boostForConceptualIntent(query, filepath, baseScore) {
|
|
|
2095
2511
|
if (isUtilityFile(filepath)) {
|
|
2096
2512
|
score *= 1.05;
|
|
2097
2513
|
}
|
|
2098
|
-
const filename =
|
|
2099
|
-
const queryTokens = query.toLowerCase().split(/\s+/);
|
|
2100
|
-
for (const token of queryTokens) {
|
|
2101
|
-
if (token.length <= 2) continue;
|
|
2102
|
-
if (filename.includes(token)) {
|
|
2103
|
-
score *= 0.9;
|
|
2104
|
-
}
|
|
2514
|
+
const filename = path13.basename(filepath, path13.extname(filepath)).toLowerCase();
|
|
2515
|
+
const queryTokens = query.toLowerCase().split(/\s+/);
|
|
2516
|
+
for (const token of queryTokens) {
|
|
2517
|
+
if (token.length <= 2) continue;
|
|
2518
|
+
if (filename.includes(token)) {
|
|
2519
|
+
score *= 0.9;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
const pathSegments = filepath.toLowerCase().split(path13.sep);
|
|
2523
|
+
for (const token of queryTokens) {
|
|
2524
|
+
if (token.length <= 2) continue;
|
|
2525
|
+
for (const segment of pathSegments) {
|
|
2526
|
+
if (segment.includes(token)) {
|
|
2527
|
+
score *= 0.95;
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
return score;
|
|
2533
|
+
}
|
|
2534
|
+
function boostForImplementationIntent(query, filepath, baseScore) {
|
|
2535
|
+
let score = baseScore;
|
|
2536
|
+
score = boostFilenameRelevance(query, filepath, score);
|
|
2537
|
+
score = boostPathRelevance(query, filepath, score);
|
|
2538
|
+
if (isTestFile(filepath)) {
|
|
2539
|
+
score *= 0.9;
|
|
2540
|
+
}
|
|
2541
|
+
return score;
|
|
2542
|
+
}
|
|
2543
|
+
function applyRelevanceBoosting(query, filepath, baseScore) {
|
|
2544
|
+
if (!query) {
|
|
2545
|
+
return baseScore;
|
|
2546
|
+
}
|
|
2547
|
+
const intent = classifyQueryIntent(query);
|
|
2548
|
+
switch (intent) {
|
|
2549
|
+
case "location" /* LOCATION */:
|
|
2550
|
+
return boostForLocationIntent(query, filepath, baseScore);
|
|
2551
|
+
case "conceptual" /* CONCEPTUAL */:
|
|
2552
|
+
return boostForConceptualIntent(query, filepath, baseScore);
|
|
2553
|
+
case "implementation" /* IMPLEMENTATION */:
|
|
2554
|
+
return boostForImplementationIntent(query, filepath, baseScore);
|
|
2555
|
+
default:
|
|
2556
|
+
return boostForImplementationIntent(query, filepath, baseScore);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
function dbRecordToSearchResult(r, query) {
|
|
2560
|
+
const baseScore = r._distance ?? 0;
|
|
2561
|
+
const boostedScore = applyRelevanceBoosting(query, r.file, baseScore);
|
|
2562
|
+
return {
|
|
2563
|
+
content: r.content,
|
|
2564
|
+
metadata: {
|
|
2565
|
+
file: r.file,
|
|
2566
|
+
startLine: r.startLine,
|
|
2567
|
+
endLine: r.endLine,
|
|
2568
|
+
type: r.type,
|
|
2569
|
+
language: r.language,
|
|
2570
|
+
// AST-derived metadata (v0.13.0)
|
|
2571
|
+
symbolName: r.symbolName || void 0,
|
|
2572
|
+
symbolType: r.symbolType,
|
|
2573
|
+
parentClass: r.parentClass || void 0,
|
|
2574
|
+
complexity: r.complexity || void 0,
|
|
2575
|
+
parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
|
|
2576
|
+
signature: r.signature || void 0,
|
|
2577
|
+
imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
|
|
2578
|
+
},
|
|
2579
|
+
score: boostedScore,
|
|
2580
|
+
relevance: calculateRelevance(boostedScore)
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
async function search(table, queryVector, limit = 5, query) {
|
|
2584
|
+
if (!table) {
|
|
2585
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2586
|
+
}
|
|
2587
|
+
try {
|
|
2588
|
+
const results = await table.search(Array.from(queryVector)).limit(limit + 20).execute();
|
|
2589
|
+
const filtered = results.filter(
|
|
2590
|
+
(r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
|
|
2591
|
+
).map((r) => dbRecordToSearchResult(r, query)).sort((a, b) => a.score - b.score).slice(0, limit);
|
|
2592
|
+
return filtered;
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
const errorMsg = String(error);
|
|
2595
|
+
if (errorMsg.includes("Not found:") || errorMsg.includes(".lance")) {
|
|
2596
|
+
throw new DatabaseError(
|
|
2597
|
+
`Index appears corrupted or outdated. Please restart the MCP server or run 'lien reindex' in the project directory.`,
|
|
2598
|
+
{ originalError: error }
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
throw wrapError(error, "Failed to search vector database");
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
async function scanWithFilter(table, options) {
|
|
2605
|
+
if (!table) {
|
|
2606
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2607
|
+
}
|
|
2608
|
+
const { language, pattern, limit = 100 } = options;
|
|
2609
|
+
try {
|
|
2610
|
+
const zeroVector = Array(EMBEDDING_DIMENSION).fill(0);
|
|
2611
|
+
const query = table.search(zeroVector).where('file != ""').limit(Math.max(limit * 5, 200));
|
|
2612
|
+
const results = await query.execute();
|
|
2613
|
+
let filtered = results.filter(
|
|
2614
|
+
(r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
|
|
2615
|
+
);
|
|
2616
|
+
if (language) {
|
|
2617
|
+
filtered = filtered.filter(
|
|
2618
|
+
(r) => r.language && r.language.toLowerCase() === language.toLowerCase()
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
if (pattern) {
|
|
2622
|
+
const regex = new RegExp(pattern, "i");
|
|
2623
|
+
filtered = filtered.filter(
|
|
2624
|
+
(r) => regex.test(r.content) || regex.test(r.file)
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
return filtered.slice(0, limit).map((r) => {
|
|
2628
|
+
const score = 0;
|
|
2629
|
+
return {
|
|
2630
|
+
content: r.content,
|
|
2631
|
+
metadata: {
|
|
2632
|
+
file: r.file,
|
|
2633
|
+
startLine: r.startLine,
|
|
2634
|
+
endLine: r.endLine,
|
|
2635
|
+
type: r.type,
|
|
2636
|
+
language: r.language,
|
|
2637
|
+
// AST-derived metadata (v0.13.0)
|
|
2638
|
+
symbolName: r.symbolName || void 0,
|
|
2639
|
+
symbolType: r.symbolType,
|
|
2640
|
+
parentClass: r.parentClass || void 0,
|
|
2641
|
+
complexity: r.complexity || void 0,
|
|
2642
|
+
parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
|
|
2643
|
+
signature: r.signature || void 0,
|
|
2644
|
+
imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
|
|
2645
|
+
},
|
|
2646
|
+
score,
|
|
2647
|
+
relevance: calculateRelevance(score)
|
|
2648
|
+
};
|
|
2649
|
+
});
|
|
2650
|
+
} catch (error) {
|
|
2651
|
+
throw wrapError(error, "Failed to scan with filter");
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function matchesSymbolType(record, symbolType, symbols) {
|
|
2655
|
+
if (record.symbolType) {
|
|
2656
|
+
if (symbolType === "function") {
|
|
2657
|
+
return record.symbolType === "function" || record.symbolType === "method";
|
|
2658
|
+
} else if (symbolType === "class") {
|
|
2659
|
+
return record.symbolType === "class";
|
|
2660
|
+
} else if (symbolType === "interface") {
|
|
2661
|
+
return record.symbolType === "interface";
|
|
2662
|
+
}
|
|
2663
|
+
return false;
|
|
2664
|
+
}
|
|
2665
|
+
return symbols.length > 0 && symbols.some((s) => s.length > 0 && s !== "");
|
|
2666
|
+
}
|
|
2667
|
+
async function querySymbols(table, options) {
|
|
2668
|
+
if (!table) {
|
|
2669
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2670
|
+
}
|
|
2671
|
+
const { language, pattern, symbolType, limit = 50 } = options;
|
|
2672
|
+
try {
|
|
2673
|
+
const zeroVector = Array(EMBEDDING_DIMENSION).fill(0);
|
|
2674
|
+
const query = table.search(zeroVector).where('file != ""').limit(Math.max(limit * 10, 500));
|
|
2675
|
+
const results = await query.execute();
|
|
2676
|
+
let filtered = results.filter((r) => {
|
|
2677
|
+
if (!r.content || r.content.trim().length === 0) {
|
|
2678
|
+
return false;
|
|
2679
|
+
}
|
|
2680
|
+
if (!r.file || r.file.length === 0) {
|
|
2681
|
+
return false;
|
|
2682
|
+
}
|
|
2683
|
+
if (language && (!r.language || r.language.toLowerCase() !== language.toLowerCase())) {
|
|
2684
|
+
return false;
|
|
2685
|
+
}
|
|
2686
|
+
const symbols = symbolType === "function" ? r.functionNames || [] : symbolType === "class" ? r.classNames || [] : symbolType === "interface" ? r.interfaceNames || [] : [...r.functionNames || [], ...r.classNames || [], ...r.interfaceNames || []];
|
|
2687
|
+
const astSymbolName = r.symbolName || "";
|
|
2688
|
+
if (symbols.length === 0 && !astSymbolName) {
|
|
2689
|
+
return false;
|
|
2690
|
+
}
|
|
2691
|
+
if (pattern) {
|
|
2692
|
+
const regex = new RegExp(pattern, "i");
|
|
2693
|
+
const matchesOldSymbols = symbols.some((s) => regex.test(s));
|
|
2694
|
+
const matchesASTSymbol = regex.test(astSymbolName);
|
|
2695
|
+
const nameMatches = matchesOldSymbols || matchesASTSymbol;
|
|
2696
|
+
if (!nameMatches) return false;
|
|
2697
|
+
if (symbolType) {
|
|
2698
|
+
return matchesSymbolType(r, symbolType, symbols);
|
|
2699
|
+
}
|
|
2700
|
+
return nameMatches;
|
|
2701
|
+
}
|
|
2702
|
+
if (symbolType) {
|
|
2703
|
+
return matchesSymbolType(r, symbolType, symbols);
|
|
2704
|
+
}
|
|
2705
|
+
return true;
|
|
2706
|
+
});
|
|
2707
|
+
return filtered.slice(0, limit).map((r) => {
|
|
2708
|
+
const score = 0;
|
|
2709
|
+
return {
|
|
2710
|
+
content: r.content,
|
|
2711
|
+
metadata: {
|
|
2712
|
+
file: r.file,
|
|
2713
|
+
startLine: r.startLine,
|
|
2714
|
+
endLine: r.endLine,
|
|
2715
|
+
type: r.type,
|
|
2716
|
+
language: r.language,
|
|
2717
|
+
symbols: {
|
|
2718
|
+
functions: r.functionNames && r.functionNames.length > 0 && r.functionNames[0] !== "" ? r.functionNames : [],
|
|
2719
|
+
classes: r.classNames && r.classNames.length > 0 && r.classNames[0] !== "" ? r.classNames : [],
|
|
2720
|
+
interfaces: r.interfaceNames && r.interfaceNames.length > 0 && r.interfaceNames[0] !== "" ? r.interfaceNames : []
|
|
2721
|
+
},
|
|
2722
|
+
// AST-derived metadata (v0.13.0)
|
|
2723
|
+
symbolName: r.symbolName || void 0,
|
|
2724
|
+
symbolType: r.symbolType,
|
|
2725
|
+
parentClass: r.parentClass || void 0,
|
|
2726
|
+
complexity: r.complexity || void 0,
|
|
2727
|
+
parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
|
|
2728
|
+
signature: r.signature || void 0,
|
|
2729
|
+
imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
|
|
2730
|
+
},
|
|
2731
|
+
score,
|
|
2732
|
+
relevance: calculateRelevance(score)
|
|
2733
|
+
};
|
|
2734
|
+
});
|
|
2735
|
+
} catch (error) {
|
|
2736
|
+
throw wrapError(error, "Failed to query symbols");
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
var init_query = __esm({
|
|
2740
|
+
"src/vectordb/query.ts"() {
|
|
2741
|
+
"use strict";
|
|
2742
|
+
init_types();
|
|
2743
|
+
init_errors();
|
|
2744
|
+
init_relevance();
|
|
2745
|
+
init_intent_classifier();
|
|
2746
|
+
}
|
|
2747
|
+
});
|
|
2748
|
+
|
|
2749
|
+
// src/vectordb/batch-insert.ts
|
|
2750
|
+
async function insertBatch(db, table, tableName, vectors, metadatas, contents) {
|
|
2751
|
+
if (!db) {
|
|
2752
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2753
|
+
}
|
|
2754
|
+
if (vectors.length !== metadatas.length || vectors.length !== contents.length) {
|
|
2755
|
+
throw new DatabaseError("Vectors, metadatas, and contents arrays must have the same length", {
|
|
2756
|
+
vectorsLength: vectors.length,
|
|
2757
|
+
metadatasLength: metadatas.length,
|
|
2758
|
+
contentsLength: contents.length
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
if (vectors.length === 0) {
|
|
2762
|
+
return table;
|
|
2763
|
+
}
|
|
2764
|
+
if (vectors.length > VECTOR_DB_MAX_BATCH_SIZE) {
|
|
2765
|
+
let currentTable = table;
|
|
2766
|
+
for (let i = 0; i < vectors.length; i += VECTOR_DB_MAX_BATCH_SIZE) {
|
|
2767
|
+
const batchVectors = vectors.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
2768
|
+
const batchMetadata = metadatas.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
2769
|
+
const batchContents = contents.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
2770
|
+
currentTable = await insertBatchInternal(db, currentTable, tableName, batchVectors, batchMetadata, batchContents);
|
|
2771
|
+
}
|
|
2772
|
+
if (!currentTable) {
|
|
2773
|
+
throw new DatabaseError("Failed to create table during batch insert");
|
|
2774
|
+
}
|
|
2775
|
+
return currentTable;
|
|
2776
|
+
} else {
|
|
2777
|
+
return insertBatchInternal(db, table, tableName, vectors, metadatas, contents);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
async function insertBatchInternal(db, table, tableName, vectors, metadatas, contents) {
|
|
2781
|
+
const queue = [{ vectors, metadatas, contents }];
|
|
2782
|
+
const failedRecords = [];
|
|
2783
|
+
let currentTable = table;
|
|
2784
|
+
while (queue.length > 0) {
|
|
2785
|
+
const batch = queue.shift();
|
|
2786
|
+
if (!batch) break;
|
|
2787
|
+
try {
|
|
2788
|
+
const records = batch.vectors.map((vector, i) => ({
|
|
2789
|
+
vector: Array.from(vector),
|
|
2790
|
+
content: batch.contents[i],
|
|
2791
|
+
file: batch.metadatas[i].file,
|
|
2792
|
+
startLine: batch.metadatas[i].startLine,
|
|
2793
|
+
endLine: batch.metadatas[i].endLine,
|
|
2794
|
+
type: batch.metadatas[i].type,
|
|
2795
|
+
language: batch.metadatas[i].language,
|
|
2796
|
+
// Ensure arrays have at least empty string for Arrow type inference
|
|
2797
|
+
functionNames: batch.metadatas[i].symbols?.functions && batch.metadatas[i].symbols.functions.length > 0 ? batch.metadatas[i].symbols.functions : [""],
|
|
2798
|
+
classNames: batch.metadatas[i].symbols?.classes && batch.metadatas[i].symbols.classes.length > 0 ? batch.metadatas[i].symbols.classes : [""],
|
|
2799
|
+
interfaceNames: batch.metadatas[i].symbols?.interfaces && batch.metadatas[i].symbols.interfaces.length > 0 ? batch.metadatas[i].symbols.interfaces : [""],
|
|
2800
|
+
// AST-derived metadata (v0.13.0)
|
|
2801
|
+
symbolName: batch.metadatas[i].symbolName || "",
|
|
2802
|
+
symbolType: batch.metadatas[i].symbolType || "",
|
|
2803
|
+
parentClass: batch.metadatas[i].parentClass || "",
|
|
2804
|
+
complexity: batch.metadatas[i].complexity || 0,
|
|
2805
|
+
parameters: batch.metadatas[i].parameters && batch.metadatas[i].parameters.length > 0 ? batch.metadatas[i].parameters : [""],
|
|
2806
|
+
signature: batch.metadatas[i].signature || "",
|
|
2807
|
+
imports: batch.metadatas[i].imports && batch.metadatas[i].imports.length > 0 ? batch.metadatas[i].imports : [""]
|
|
2808
|
+
}));
|
|
2809
|
+
if (!currentTable) {
|
|
2810
|
+
currentTable = await db.createTable(tableName, records);
|
|
2811
|
+
} else {
|
|
2812
|
+
await currentTable.add(records);
|
|
2813
|
+
}
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
if (batch.vectors.length > VECTOR_DB_MIN_BATCH_SIZE) {
|
|
2816
|
+
const half = Math.floor(batch.vectors.length / 2);
|
|
2817
|
+
queue.push({
|
|
2818
|
+
vectors: batch.vectors.slice(0, half),
|
|
2819
|
+
metadatas: batch.metadatas.slice(0, half),
|
|
2820
|
+
contents: batch.contents.slice(0, half)
|
|
2821
|
+
});
|
|
2822
|
+
queue.push({
|
|
2823
|
+
vectors: batch.vectors.slice(half),
|
|
2824
|
+
metadatas: batch.metadatas.slice(half),
|
|
2825
|
+
contents: batch.contents.slice(half)
|
|
2826
|
+
});
|
|
2827
|
+
} else {
|
|
2828
|
+
failedRecords.push(batch);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
if (failedRecords.length > 0) {
|
|
2833
|
+
const totalFailed = failedRecords.reduce((sum, batch) => sum + batch.vectors.length, 0);
|
|
2834
|
+
throw new DatabaseError(
|
|
2835
|
+
`Failed to insert ${totalFailed} record(s) after retry attempts`,
|
|
2836
|
+
{
|
|
2837
|
+
failedBatches: failedRecords.length,
|
|
2838
|
+
totalRecords: totalFailed,
|
|
2839
|
+
sampleFile: failedRecords[0].metadatas[0].file
|
|
2840
|
+
}
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
if (!currentTable) {
|
|
2844
|
+
throw new DatabaseError("Failed to create table during batch insert");
|
|
2845
|
+
}
|
|
2846
|
+
return currentTable;
|
|
2847
|
+
}
|
|
2848
|
+
var init_batch_insert = __esm({
|
|
2849
|
+
"src/vectordb/batch-insert.ts"() {
|
|
2850
|
+
"use strict";
|
|
2851
|
+
init_errors();
|
|
2852
|
+
init_constants();
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
// src/vectordb/maintenance.ts
|
|
2857
|
+
async function clear(db, table, tableName) {
|
|
2858
|
+
if (!db) {
|
|
2859
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2105
2860
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
for (const segment of pathSegments) {
|
|
2110
|
-
if (segment.includes(token)) {
|
|
2111
|
-
score *= 0.95;
|
|
2112
|
-
break;
|
|
2113
|
-
}
|
|
2861
|
+
try {
|
|
2862
|
+
if (table) {
|
|
2863
|
+
await db.dropTable(tableName);
|
|
2114
2864
|
}
|
|
2865
|
+
} catch (error) {
|
|
2866
|
+
throw wrapError(error, "Failed to clear vector database");
|
|
2115
2867
|
}
|
|
2116
|
-
return score;
|
|
2117
2868
|
}
|
|
2118
|
-
function
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2869
|
+
async function deleteByFile(table, filepath) {
|
|
2870
|
+
if (!table) {
|
|
2871
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2872
|
+
}
|
|
2873
|
+
try {
|
|
2874
|
+
await table.delete(`file = "${filepath}"`);
|
|
2875
|
+
} catch (error) {
|
|
2876
|
+
throw wrapError(error, "Failed to delete file from vector database");
|
|
2124
2877
|
}
|
|
2125
|
-
return score;
|
|
2126
2878
|
}
|
|
2127
|
-
function
|
|
2128
|
-
if (!
|
|
2129
|
-
|
|
2879
|
+
async function updateFile(db, table, tableName, dbPath, filepath, vectors, metadatas, contents) {
|
|
2880
|
+
if (!table) {
|
|
2881
|
+
throw new DatabaseError("Vector database not initialized");
|
|
2130
2882
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2883
|
+
try {
|
|
2884
|
+
await deleteByFile(table, filepath);
|
|
2885
|
+
let updatedTable = table;
|
|
2886
|
+
if (vectors.length > 0) {
|
|
2887
|
+
updatedTable = await insertBatch(db, table, tableName, vectors, metadatas, contents);
|
|
2888
|
+
if (!updatedTable) {
|
|
2889
|
+
throw new DatabaseError("insertBatch unexpectedly returned null");
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
await writeVersionFile(dbPath);
|
|
2893
|
+
return updatedTable;
|
|
2894
|
+
} catch (error) {
|
|
2895
|
+
throw wrapError(error, "Failed to update file in vector database");
|
|
2141
2896
|
}
|
|
2142
2897
|
}
|
|
2898
|
+
var init_maintenance = __esm({
|
|
2899
|
+
"src/vectordb/maintenance.ts"() {
|
|
2900
|
+
"use strict";
|
|
2901
|
+
init_errors();
|
|
2902
|
+
init_version2();
|
|
2903
|
+
init_batch_insert();
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
// src/vectordb/lancedb.ts
|
|
2908
|
+
var lancedb_exports = {};
|
|
2909
|
+
__export(lancedb_exports, {
|
|
2910
|
+
VectorDB: () => VectorDB
|
|
2911
|
+
});
|
|
2912
|
+
import * as lancedb from "vectordb";
|
|
2913
|
+
import path14 from "path";
|
|
2914
|
+
import os2 from "os";
|
|
2915
|
+
import crypto2 from "crypto";
|
|
2143
2916
|
var VectorDB;
|
|
2144
2917
|
var init_lancedb = __esm({
|
|
2145
2918
|
"src/vectordb/lancedb.ts"() {
|
|
@@ -2147,9 +2920,9 @@ var init_lancedb = __esm({
|
|
|
2147
2920
|
init_types();
|
|
2148
2921
|
init_version2();
|
|
2149
2922
|
init_errors();
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2923
|
+
init_query();
|
|
2924
|
+
init_batch_insert();
|
|
2925
|
+
init_maintenance();
|
|
2153
2926
|
VectorDB = class _VectorDB {
|
|
2154
2927
|
db = null;
|
|
2155
2928
|
table = null;
|
|
@@ -2158,9 +2931,9 @@ var init_lancedb = __esm({
|
|
|
2158
2931
|
lastVersionCheck = 0;
|
|
2159
2932
|
currentVersion = 0;
|
|
2160
2933
|
constructor(projectRoot) {
|
|
2161
|
-
const projectName =
|
|
2934
|
+
const projectName = path14.basename(projectRoot);
|
|
2162
2935
|
const pathHash = crypto2.createHash("md5").update(projectRoot).digest("hex").substring(0, 8);
|
|
2163
|
-
this.dbPath =
|
|
2936
|
+
this.dbPath = path14.join(
|
|
2164
2937
|
os2.homedir(),
|
|
2165
2938
|
".lien",
|
|
2166
2939
|
"indices",
|
|
@@ -2188,150 +2961,30 @@ var init_lancedb = __esm({
|
|
|
2188
2961
|
if (!this.db) {
|
|
2189
2962
|
throw new DatabaseError("Vector database not initialized");
|
|
2190
2963
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
return;
|
|
2200
|
-
}
|
|
2201
|
-
if (vectors.length > VECTOR_DB_MAX_BATCH_SIZE) {
|
|
2202
|
-
for (let i = 0; i < vectors.length; i += VECTOR_DB_MAX_BATCH_SIZE) {
|
|
2203
|
-
const batchVectors = vectors.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
2204
|
-
const batchMetadata = metadatas.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
2205
|
-
const batchContents = contents.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
2206
|
-
await this._insertBatchInternal(batchVectors, batchMetadata, batchContents);
|
|
2207
|
-
}
|
|
2208
|
-
} else {
|
|
2209
|
-
await this._insertBatchInternal(vectors, metadatas, contents);
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
/**
|
|
2213
|
-
* Internal method to insert a single batch with iterative retry logic.
|
|
2214
|
-
* Uses a queue-based approach to avoid deep recursion on large batch failures.
|
|
2215
|
-
*/
|
|
2216
|
-
async _insertBatchInternal(vectors, metadatas, contents) {
|
|
2217
|
-
const queue = [{ vectors, metadatas, contents }];
|
|
2218
|
-
const failedRecords = [];
|
|
2219
|
-
while (queue.length > 0) {
|
|
2220
|
-
const batch = queue.shift();
|
|
2221
|
-
try {
|
|
2222
|
-
const records = batch.vectors.map((vector, i) => ({
|
|
2223
|
-
vector: Array.from(vector),
|
|
2224
|
-
content: batch.contents[i],
|
|
2225
|
-
file: batch.metadatas[i].file,
|
|
2226
|
-
startLine: batch.metadatas[i].startLine,
|
|
2227
|
-
endLine: batch.metadatas[i].endLine,
|
|
2228
|
-
type: batch.metadatas[i].type,
|
|
2229
|
-
language: batch.metadatas[i].language,
|
|
2230
|
-
// Ensure arrays have at least empty string for Arrow type inference
|
|
2231
|
-
functionNames: batch.metadatas[i].symbols?.functions && batch.metadatas[i].symbols.functions.length > 0 ? batch.metadatas[i].symbols.functions : [""],
|
|
2232
|
-
classNames: batch.metadatas[i].symbols?.classes && batch.metadatas[i].symbols.classes.length > 0 ? batch.metadatas[i].symbols.classes : [""],
|
|
2233
|
-
interfaceNames: batch.metadatas[i].symbols?.interfaces && batch.metadatas[i].symbols.interfaces.length > 0 ? batch.metadatas[i].symbols.interfaces : [""],
|
|
2234
|
-
// AST-derived metadata (v0.13.0)
|
|
2235
|
-
symbolName: batch.metadatas[i].symbolName || "",
|
|
2236
|
-
symbolType: batch.metadatas[i].symbolType || "",
|
|
2237
|
-
parentClass: batch.metadatas[i].parentClass || "",
|
|
2238
|
-
complexity: batch.metadatas[i].complexity || 0,
|
|
2239
|
-
parameters: batch.metadatas[i].parameters && batch.metadatas[i].parameters.length > 0 ? batch.metadatas[i].parameters : [""],
|
|
2240
|
-
signature: batch.metadatas[i].signature || "",
|
|
2241
|
-
imports: batch.metadatas[i].imports && batch.metadatas[i].imports.length > 0 ? batch.metadatas[i].imports : [""]
|
|
2242
|
-
}));
|
|
2243
|
-
if (!this.table) {
|
|
2244
|
-
this.table = await this.db.createTable(this.tableName, records);
|
|
2245
|
-
} else {
|
|
2246
|
-
await this.table.add(records);
|
|
2247
|
-
}
|
|
2248
|
-
} catch (error) {
|
|
2249
|
-
if (batch.vectors.length > VECTOR_DB_MIN_BATCH_SIZE) {
|
|
2250
|
-
const half = Math.floor(batch.vectors.length / 2);
|
|
2251
|
-
queue.push({
|
|
2252
|
-
vectors: batch.vectors.slice(0, half),
|
|
2253
|
-
metadatas: batch.metadatas.slice(0, half),
|
|
2254
|
-
contents: batch.contents.slice(0, half)
|
|
2255
|
-
});
|
|
2256
|
-
queue.push({
|
|
2257
|
-
vectors: batch.vectors.slice(half),
|
|
2258
|
-
metadatas: batch.metadatas.slice(half),
|
|
2259
|
-
contents: batch.contents.slice(half)
|
|
2260
|
-
});
|
|
2261
|
-
} else {
|
|
2262
|
-
failedRecords.push(batch);
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
if (failedRecords.length > 0) {
|
|
2267
|
-
const totalFailed = failedRecords.reduce((sum, batch) => sum + batch.vectors.length, 0);
|
|
2268
|
-
throw new DatabaseError(
|
|
2269
|
-
`Failed to insert ${totalFailed} record(s) after retry attempts`,
|
|
2270
|
-
{
|
|
2271
|
-
failedBatches: failedRecords.length,
|
|
2272
|
-
totalRecords: totalFailed,
|
|
2273
|
-
sampleFile: failedRecords[0].metadatas[0].file
|
|
2274
|
-
}
|
|
2275
|
-
);
|
|
2276
|
-
}
|
|
2964
|
+
this.table = await insertBatch(
|
|
2965
|
+
this.db,
|
|
2966
|
+
this.table,
|
|
2967
|
+
this.tableName,
|
|
2968
|
+
vectors,
|
|
2969
|
+
metadatas,
|
|
2970
|
+
contents
|
|
2971
|
+
);
|
|
2277
2972
|
}
|
|
2278
2973
|
async search(queryVector, limit = 5, query) {
|
|
2279
2974
|
if (!this.table) {
|
|
2280
2975
|
throw new DatabaseError("Vector database not initialized");
|
|
2281
2976
|
}
|
|
2282
2977
|
try {
|
|
2283
|
-
|
|
2284
|
-
const filtered = results.filter(
|
|
2285
|
-
(r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
|
|
2286
|
-
).map((r) => {
|
|
2287
|
-
const baseScore = r._distance ?? 0;
|
|
2288
|
-
const boostedScore = applyRelevanceBoosting(query, r.file, baseScore);
|
|
2289
|
-
return {
|
|
2290
|
-
content: r.content,
|
|
2291
|
-
metadata: {
|
|
2292
|
-
file: r.file,
|
|
2293
|
-
startLine: r.startLine,
|
|
2294
|
-
endLine: r.endLine,
|
|
2295
|
-
type: r.type,
|
|
2296
|
-
language: r.language,
|
|
2297
|
-
// AST-derived metadata (v0.13.0)
|
|
2298
|
-
symbolName: r.symbolName || void 0,
|
|
2299
|
-
symbolType: r.symbolType,
|
|
2300
|
-
parentClass: r.parentClass || void 0,
|
|
2301
|
-
complexity: r.complexity || void 0,
|
|
2302
|
-
parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
|
|
2303
|
-
signature: r.signature || void 0,
|
|
2304
|
-
imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
|
|
2305
|
-
},
|
|
2306
|
-
score: boostedScore,
|
|
2307
|
-
relevance: calculateRelevance(boostedScore)
|
|
2308
|
-
};
|
|
2309
|
-
}).sort((a, b) => a.score - b.score).slice(0, limit);
|
|
2310
|
-
return filtered;
|
|
2978
|
+
return await search(this.table, queryVector, limit, query);
|
|
2311
2979
|
} catch (error) {
|
|
2312
2980
|
const errorMsg = String(error);
|
|
2313
2981
|
if (errorMsg.includes("Not found:") || errorMsg.includes(".lance")) {
|
|
2314
2982
|
try {
|
|
2315
2983
|
await this.initialize();
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
const baseScore = r._distance ?? 0;
|
|
2321
|
-
const boostedScore = applyRelevanceBoosting(query, r.file, baseScore);
|
|
2322
|
-
return {
|
|
2323
|
-
content: r.content,
|
|
2324
|
-
metadata: {
|
|
2325
|
-
file: r.file,
|
|
2326
|
-
startLine: r.startLine,
|
|
2327
|
-
endLine: r.endLine,
|
|
2328
|
-
type: r.type,
|
|
2329
|
-
language: r.language
|
|
2330
|
-
},
|
|
2331
|
-
score: boostedScore,
|
|
2332
|
-
relevance: calculateRelevance(boostedScore)
|
|
2333
|
-
};
|
|
2334
|
-
}).sort((a, b) => a.score - b.score).slice(0, limit);
|
|
2984
|
+
if (!this.table) {
|
|
2985
|
+
throw new DatabaseError("Vector database not initialized after reconnection");
|
|
2986
|
+
}
|
|
2987
|
+
return await search(this.table, queryVector, limit, query);
|
|
2335
2988
|
} catch (retryError) {
|
|
2336
2989
|
throw new DatabaseError(
|
|
2337
2990
|
`Index appears corrupted or outdated. Please restart the MCP server or run 'lien reindex' in the project directory.`,
|
|
@@ -2339,209 +2992,52 @@ var init_lancedb = __esm({
|
|
|
2339
2992
|
);
|
|
2340
2993
|
}
|
|
2341
2994
|
}
|
|
2342
|
-
throw
|
|
2995
|
+
throw error;
|
|
2343
2996
|
}
|
|
2344
2997
|
}
|
|
2345
2998
|
async scanWithFilter(options) {
|
|
2346
2999
|
if (!this.table) {
|
|
2347
3000
|
throw new DatabaseError("Vector database not initialized");
|
|
2348
3001
|
}
|
|
2349
|
-
|
|
2350
|
-
try {
|
|
2351
|
-
const zeroVector = Array(EMBEDDING_DIMENSION).fill(0);
|
|
2352
|
-
const query = this.table.search(zeroVector).where('file != ""').limit(Math.max(limit * 5, 200));
|
|
2353
|
-
const results = await query.execute();
|
|
2354
|
-
let filtered = results.filter(
|
|
2355
|
-
(r) => r.content && r.content.trim().length > 0 && r.file && r.file.length > 0
|
|
2356
|
-
);
|
|
2357
|
-
if (language) {
|
|
2358
|
-
filtered = filtered.filter(
|
|
2359
|
-
(r) => r.language && r.language.toLowerCase() === language.toLowerCase()
|
|
2360
|
-
);
|
|
2361
|
-
}
|
|
2362
|
-
if (pattern) {
|
|
2363
|
-
const regex = new RegExp(pattern, "i");
|
|
2364
|
-
filtered = filtered.filter(
|
|
2365
|
-
(r) => regex.test(r.content) || regex.test(r.file)
|
|
2366
|
-
);
|
|
2367
|
-
}
|
|
2368
|
-
return filtered.slice(0, limit).map((r) => {
|
|
2369
|
-
const score = 0;
|
|
2370
|
-
return {
|
|
2371
|
-
content: r.content,
|
|
2372
|
-
metadata: {
|
|
2373
|
-
file: r.file,
|
|
2374
|
-
startLine: r.startLine,
|
|
2375
|
-
endLine: r.endLine,
|
|
2376
|
-
type: r.type,
|
|
2377
|
-
language: r.language,
|
|
2378
|
-
// AST-derived metadata (v0.13.0)
|
|
2379
|
-
symbolName: r.symbolName || void 0,
|
|
2380
|
-
symbolType: r.symbolType,
|
|
2381
|
-
parentClass: r.parentClass || void 0,
|
|
2382
|
-
complexity: r.complexity || void 0,
|
|
2383
|
-
parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
|
|
2384
|
-
signature: r.signature || void 0,
|
|
2385
|
-
imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
|
|
2386
|
-
},
|
|
2387
|
-
score,
|
|
2388
|
-
relevance: calculateRelevance(score)
|
|
2389
|
-
};
|
|
2390
|
-
});
|
|
2391
|
-
} catch (error) {
|
|
2392
|
-
throw wrapError(error, "Failed to scan with filter");
|
|
2393
|
-
}
|
|
3002
|
+
return scanWithFilter(this.table, options);
|
|
2394
3003
|
}
|
|
2395
3004
|
async querySymbols(options) {
|
|
2396
3005
|
if (!this.table) {
|
|
2397
3006
|
throw new DatabaseError("Vector database not initialized");
|
|
2398
3007
|
}
|
|
2399
|
-
|
|
2400
|
-
try {
|
|
2401
|
-
const zeroVector = Array(EMBEDDING_DIMENSION).fill(0);
|
|
2402
|
-
const query = this.table.search(zeroVector).where('file != ""').limit(Math.max(limit * 10, 500));
|
|
2403
|
-
const results = await query.execute();
|
|
2404
|
-
let filtered = results.filter((r) => {
|
|
2405
|
-
if (!r.content || r.content.trim().length === 0) {
|
|
2406
|
-
return false;
|
|
2407
|
-
}
|
|
2408
|
-
if (!r.file || r.file.length === 0) {
|
|
2409
|
-
return false;
|
|
2410
|
-
}
|
|
2411
|
-
if (language && (!r.language || r.language.toLowerCase() !== language.toLowerCase())) {
|
|
2412
|
-
return false;
|
|
2413
|
-
}
|
|
2414
|
-
const symbols = symbolType === "function" ? r.functionNames || [] : symbolType === "class" ? r.classNames || [] : symbolType === "interface" ? r.interfaceNames || [] : [...r.functionNames || [], ...r.classNames || [], ...r.interfaceNames || []];
|
|
2415
|
-
const astSymbolName = r.symbolName || "";
|
|
2416
|
-
if (symbols.length === 0 && !astSymbolName) {
|
|
2417
|
-
return false;
|
|
2418
|
-
}
|
|
2419
|
-
if (pattern) {
|
|
2420
|
-
const regex = new RegExp(pattern, "i");
|
|
2421
|
-
const matchesOldSymbols = symbols.some((s) => regex.test(s));
|
|
2422
|
-
const matchesASTSymbol = regex.test(astSymbolName);
|
|
2423
|
-
const nameMatches = matchesOldSymbols || matchesASTSymbol;
|
|
2424
|
-
if (!nameMatches) return false;
|
|
2425
|
-
if (symbolType) {
|
|
2426
|
-
if (r.symbolType) {
|
|
2427
|
-
if (symbolType === "function") {
|
|
2428
|
-
return r.symbolType === "function" || r.symbolType === "method";
|
|
2429
|
-
} else if (symbolType === "class") {
|
|
2430
|
-
return r.symbolType === "class";
|
|
2431
|
-
} else if (symbolType === "interface") {
|
|
2432
|
-
return r.symbolType === "interface";
|
|
2433
|
-
}
|
|
2434
|
-
return false;
|
|
2435
|
-
}
|
|
2436
|
-
return nameMatches;
|
|
2437
|
-
}
|
|
2438
|
-
return nameMatches;
|
|
2439
|
-
}
|
|
2440
|
-
if (symbolType) {
|
|
2441
|
-
if (r.symbolType) {
|
|
2442
|
-
if (symbolType === "function") {
|
|
2443
|
-
return r.symbolType === "function" || r.symbolType === "method";
|
|
2444
|
-
} else if (symbolType === "class") {
|
|
2445
|
-
return r.symbolType === "class";
|
|
2446
|
-
} else if (symbolType === "interface") {
|
|
2447
|
-
return r.symbolType === "interface";
|
|
2448
|
-
}
|
|
2449
|
-
return false;
|
|
2450
|
-
}
|
|
2451
|
-
return symbols.length > 0 && symbols.some((s) => s.length > 0 && s !== "");
|
|
2452
|
-
}
|
|
2453
|
-
return true;
|
|
2454
|
-
});
|
|
2455
|
-
return filtered.slice(0, limit).map((r) => {
|
|
2456
|
-
const score = 0;
|
|
2457
|
-
return {
|
|
2458
|
-
content: r.content,
|
|
2459
|
-
metadata: {
|
|
2460
|
-
file: r.file,
|
|
2461
|
-
startLine: r.startLine,
|
|
2462
|
-
endLine: r.endLine,
|
|
2463
|
-
type: r.type,
|
|
2464
|
-
language: r.language,
|
|
2465
|
-
symbols: {
|
|
2466
|
-
functions: r.functionNames || [],
|
|
2467
|
-
classes: r.classNames || [],
|
|
2468
|
-
interfaces: r.interfaceNames || []
|
|
2469
|
-
},
|
|
2470
|
-
// AST-derived metadata (v0.13.0)
|
|
2471
|
-
symbolName: r.symbolName || void 0,
|
|
2472
|
-
symbolType: r.symbolType,
|
|
2473
|
-
parentClass: r.parentClass || void 0,
|
|
2474
|
-
complexity: r.complexity || void 0,
|
|
2475
|
-
parameters: r.parameters && r.parameters.length > 0 && r.parameters[0] !== "" ? r.parameters : void 0,
|
|
2476
|
-
signature: r.signature || void 0,
|
|
2477
|
-
imports: r.imports && r.imports.length > 0 && r.imports[0] !== "" ? r.imports : void 0
|
|
2478
|
-
},
|
|
2479
|
-
score,
|
|
2480
|
-
relevance: calculateRelevance(score)
|
|
2481
|
-
};
|
|
2482
|
-
});
|
|
2483
|
-
} catch (error) {
|
|
2484
|
-
throw wrapError(error, "Failed to query symbols");
|
|
2485
|
-
}
|
|
3008
|
+
return querySymbols(this.table, options);
|
|
2486
3009
|
}
|
|
2487
3010
|
async clear() {
|
|
2488
3011
|
if (!this.db) {
|
|
2489
3012
|
throw new DatabaseError("Vector database not initialized");
|
|
2490
3013
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
await this.db.dropTable(this.tableName);
|
|
2494
|
-
}
|
|
2495
|
-
this.table = null;
|
|
2496
|
-
} catch (error) {
|
|
2497
|
-
throw wrapError(error, "Failed to clear vector database");
|
|
2498
|
-
}
|
|
3014
|
+
await clear(this.db, this.table, this.tableName);
|
|
3015
|
+
this.table = null;
|
|
2499
3016
|
}
|
|
2500
|
-
/**
|
|
2501
|
-
* Deletes all chunks from a specific file.
|
|
2502
|
-
* Used for incremental reindexing when a file is deleted or needs to be re-indexed.
|
|
2503
|
-
*
|
|
2504
|
-
* @param filepath - Path to the file whose chunks should be deleted
|
|
2505
|
-
*/
|
|
2506
3017
|
async deleteByFile(filepath) {
|
|
2507
3018
|
if (!this.table) {
|
|
2508
3019
|
throw new DatabaseError("Vector database not initialized");
|
|
2509
3020
|
}
|
|
2510
|
-
|
|
2511
|
-
await this.table.delete(`file = "${filepath}"`);
|
|
2512
|
-
} catch (error) {
|
|
2513
|
-
throw wrapError(error, "Failed to delete file from vector database");
|
|
2514
|
-
}
|
|
3021
|
+
await deleteByFile(this.table, filepath);
|
|
2515
3022
|
}
|
|
2516
|
-
/**
|
|
2517
|
-
* Updates a file in the index by atomically deleting old chunks and inserting new ones.
|
|
2518
|
-
* This is the primary method for incremental reindexing.
|
|
2519
|
-
*
|
|
2520
|
-
* @param filepath - Path to the file being updated
|
|
2521
|
-
* @param vectors - New embedding vectors
|
|
2522
|
-
* @param metadatas - New chunk metadata
|
|
2523
|
-
* @param contents - New chunk contents
|
|
2524
|
-
*/
|
|
2525
3023
|
async updateFile(filepath, vectors, metadatas, contents) {
|
|
2526
|
-
if (!this.
|
|
2527
|
-
throw new DatabaseError("Vector database not initialized");
|
|
3024
|
+
if (!this.db) {
|
|
3025
|
+
throw new DatabaseError("Vector database connection not initialized");
|
|
2528
3026
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
if (vectors.length > 0) {
|
|
2532
|
-
await this.insertBatch(vectors, metadatas, contents);
|
|
2533
|
-
}
|
|
2534
|
-
await writeVersionFile(this.dbPath);
|
|
2535
|
-
} catch (error) {
|
|
2536
|
-
throw wrapError(error, "Failed to update file in vector database");
|
|
3027
|
+
if (!this.table) {
|
|
3028
|
+
throw new DatabaseError("Vector database table not initialized");
|
|
2537
3029
|
}
|
|
3030
|
+
this.table = await updateFile(
|
|
3031
|
+
this.db,
|
|
3032
|
+
this.table,
|
|
3033
|
+
this.tableName,
|
|
3034
|
+
this.dbPath,
|
|
3035
|
+
filepath,
|
|
3036
|
+
vectors,
|
|
3037
|
+
metadatas,
|
|
3038
|
+
contents
|
|
3039
|
+
);
|
|
2538
3040
|
}
|
|
2539
|
-
/**
|
|
2540
|
-
* Checks if the index version has changed since last check.
|
|
2541
|
-
* Uses caching to minimize I/O overhead (checks at most once per second).
|
|
2542
|
-
*
|
|
2543
|
-
* @returns true if version has changed, false otherwise
|
|
2544
|
-
*/
|
|
2545
3041
|
async checkVersion() {
|
|
2546
3042
|
const now = Date.now();
|
|
2547
3043
|
if (now - this.lastVersionCheck < 1e3) {
|
|
@@ -2559,11 +3055,6 @@ var init_lancedb = __esm({
|
|
|
2559
3055
|
return false;
|
|
2560
3056
|
}
|
|
2561
3057
|
}
|
|
2562
|
-
/**
|
|
2563
|
-
* Reconnects to the database by reinitializing the connection.
|
|
2564
|
-
* Used when the index has been rebuilt/reindexed.
|
|
2565
|
-
* Forces a complete reload from disk by closing existing connections first.
|
|
2566
|
-
*/
|
|
2567
3058
|
async reconnect() {
|
|
2568
3059
|
try {
|
|
2569
3060
|
this.table = null;
|
|
@@ -2573,31 +3064,15 @@ var init_lancedb = __esm({
|
|
|
2573
3064
|
throw wrapError(error, "Failed to reconnect to vector database");
|
|
2574
3065
|
}
|
|
2575
3066
|
}
|
|
2576
|
-
/**
|
|
2577
|
-
* Gets the current index version (timestamp of last reindex).
|
|
2578
|
-
*
|
|
2579
|
-
* @returns Version timestamp, or 0 if unknown
|
|
2580
|
-
*/
|
|
2581
3067
|
getCurrentVersion() {
|
|
2582
3068
|
return this.currentVersion;
|
|
2583
3069
|
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Gets the current index version as a human-readable date string.
|
|
2586
|
-
*
|
|
2587
|
-
* @returns Formatted date string, or 'Unknown' if no version
|
|
2588
|
-
*/
|
|
2589
3070
|
getVersionDate() {
|
|
2590
3071
|
if (this.currentVersion === 0) {
|
|
2591
3072
|
return "Unknown";
|
|
2592
3073
|
}
|
|
2593
3074
|
return new Date(this.currentVersion).toLocaleString();
|
|
2594
3075
|
}
|
|
2595
|
-
/**
|
|
2596
|
-
* Checks if the database contains real indexed data.
|
|
2597
|
-
* Used to detect first run and trigger auto-indexing.
|
|
2598
|
-
*
|
|
2599
|
-
* @returns true if database has real code chunks, false if empty or only schema rows
|
|
2600
|
-
*/
|
|
2601
3076
|
async hasData() {
|
|
2602
3077
|
if (!this.table) {
|
|
2603
3078
|
return false;
|
|
@@ -2630,8 +3105,8 @@ var manifest_exports = {};
|
|
|
2630
3105
|
__export(manifest_exports, {
|
|
2631
3106
|
ManifestManager: () => ManifestManager
|
|
2632
3107
|
});
|
|
2633
|
-
import
|
|
2634
|
-
import
|
|
3108
|
+
import fs13 from "fs/promises";
|
|
3109
|
+
import path15 from "path";
|
|
2635
3110
|
var MANIFEST_FILE, ManifestManager;
|
|
2636
3111
|
var init_manifest = __esm({
|
|
2637
3112
|
"src/indexer/manifest.ts"() {
|
|
@@ -2653,7 +3128,7 @@ var init_manifest = __esm({
|
|
|
2653
3128
|
*/
|
|
2654
3129
|
constructor(indexPath) {
|
|
2655
3130
|
this.indexPath = indexPath;
|
|
2656
|
-
this.manifestPath =
|
|
3131
|
+
this.manifestPath = path15.join(indexPath, MANIFEST_FILE);
|
|
2657
3132
|
}
|
|
2658
3133
|
/**
|
|
2659
3134
|
* Loads the manifest from disk.
|
|
@@ -2666,7 +3141,7 @@ var init_manifest = __esm({
|
|
|
2666
3141
|
*/
|
|
2667
3142
|
async load() {
|
|
2668
3143
|
try {
|
|
2669
|
-
const content = await
|
|
3144
|
+
const content = await fs13.readFile(this.manifestPath, "utf-8");
|
|
2670
3145
|
const manifest = JSON.parse(content);
|
|
2671
3146
|
if (manifest.formatVersion !== INDEX_FORMAT_VERSION) {
|
|
2672
3147
|
console.error(
|
|
@@ -2693,7 +3168,7 @@ var init_manifest = __esm({
|
|
|
2693
3168
|
*/
|
|
2694
3169
|
async save(manifest) {
|
|
2695
3170
|
try {
|
|
2696
|
-
await
|
|
3171
|
+
await fs13.mkdir(this.indexPath, { recursive: true });
|
|
2697
3172
|
const manifestToSave = {
|
|
2698
3173
|
...manifest,
|
|
2699
3174
|
formatVersion: INDEX_FORMAT_VERSION,
|
|
@@ -2701,7 +3176,7 @@ var init_manifest = __esm({
|
|
|
2701
3176
|
lastIndexed: Date.now()
|
|
2702
3177
|
};
|
|
2703
3178
|
const content = JSON.stringify(manifestToSave, null, 2);
|
|
2704
|
-
await
|
|
3179
|
+
await fs13.writeFile(this.manifestPath, content, "utf-8");
|
|
2705
3180
|
} catch (error) {
|
|
2706
3181
|
console.error(`[Lien] Warning: Failed to save manifest: ${error}`);
|
|
2707
3182
|
}
|
|
@@ -2838,7 +3313,7 @@ var init_manifest = __esm({
|
|
|
2838
3313
|
*/
|
|
2839
3314
|
async clear() {
|
|
2840
3315
|
try {
|
|
2841
|
-
await
|
|
3316
|
+
await fs13.unlink(this.manifestPath);
|
|
2842
3317
|
} catch (error) {
|
|
2843
3318
|
if (error.code !== "ENOENT") {
|
|
2844
3319
|
console.error(`[Lien] Warning: Failed to clear manifest: ${error}`);
|
|
@@ -2867,8 +3342,8 @@ var tracker_exports = {};
|
|
|
2867
3342
|
__export(tracker_exports, {
|
|
2868
3343
|
GitStateTracker: () => GitStateTracker
|
|
2869
3344
|
});
|
|
2870
|
-
import
|
|
2871
|
-
import
|
|
3345
|
+
import fs14 from "fs/promises";
|
|
3346
|
+
import path16 from "path";
|
|
2872
3347
|
var GitStateTracker;
|
|
2873
3348
|
var init_tracker = __esm({
|
|
2874
3349
|
"src/git/tracker.ts"() {
|
|
@@ -2880,7 +3355,7 @@ var init_tracker = __esm({
|
|
|
2880
3355
|
currentState = null;
|
|
2881
3356
|
constructor(rootDir, indexPath) {
|
|
2882
3357
|
this.rootDir = rootDir;
|
|
2883
|
-
this.stateFile =
|
|
3358
|
+
this.stateFile = path16.join(indexPath, ".git-state.json");
|
|
2884
3359
|
}
|
|
2885
3360
|
/**
|
|
2886
3361
|
* Loads the last known git state from disk.
|
|
@@ -2888,7 +3363,7 @@ var init_tracker = __esm({
|
|
|
2888
3363
|
*/
|
|
2889
3364
|
async loadState() {
|
|
2890
3365
|
try {
|
|
2891
|
-
const content = await
|
|
3366
|
+
const content = await fs14.readFile(this.stateFile, "utf-8");
|
|
2892
3367
|
return JSON.parse(content);
|
|
2893
3368
|
} catch {
|
|
2894
3369
|
return null;
|
|
@@ -2900,7 +3375,7 @@ var init_tracker = __esm({
|
|
|
2900
3375
|
async saveState(state) {
|
|
2901
3376
|
try {
|
|
2902
3377
|
const content = JSON.stringify(state, null, 2);
|
|
2903
|
-
await
|
|
3378
|
+
await fs14.writeFile(this.stateFile, content, "utf-8");
|
|
2904
3379
|
} catch (error) {
|
|
2905
3380
|
console.error(`[Lien] Warning: Failed to save git state: ${error}`);
|
|
2906
3381
|
}
|
|
@@ -3051,7 +3526,7 @@ var init_tracker = __esm({
|
|
|
3051
3526
|
});
|
|
3052
3527
|
|
|
3053
3528
|
// src/indexer/change-detector.ts
|
|
3054
|
-
import
|
|
3529
|
+
import fs15 from "fs/promises";
|
|
3055
3530
|
async function detectChanges(rootDir, vectorDB, config) {
|
|
3056
3531
|
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
3057
3532
|
const savedManifest = await manifest.load();
|
|
@@ -3155,7 +3630,7 @@ async function mtimeBasedDetection(rootDir, savedManifest, config) {
|
|
|
3155
3630
|
const fileStats = /* @__PURE__ */ new Map();
|
|
3156
3631
|
for (const filepath of currentFiles) {
|
|
3157
3632
|
try {
|
|
3158
|
-
const stats = await
|
|
3633
|
+
const stats = await fs15.stat(filepath);
|
|
3159
3634
|
fileStats.set(filepath, stats.mtimeMs);
|
|
3160
3635
|
} catch {
|
|
3161
3636
|
continue;
|
|
@@ -3193,7 +3668,7 @@ var init_change_detector = __esm({
|
|
|
3193
3668
|
});
|
|
3194
3669
|
|
|
3195
3670
|
// src/indexer/incremental.ts
|
|
3196
|
-
import
|
|
3671
|
+
import fs16 from "fs/promises";
|
|
3197
3672
|
async function processFileContent(filepath, content, embeddings, config, verbose) {
|
|
3198
3673
|
const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
|
|
3199
3674
|
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
|
|
@@ -3232,7 +3707,7 @@ async function indexSingleFile(filepath, vectorDB, embeddings, config, options =
|
|
|
3232
3707
|
const { verbose } = options;
|
|
3233
3708
|
try {
|
|
3234
3709
|
try {
|
|
3235
|
-
await
|
|
3710
|
+
await fs16.access(filepath);
|
|
3236
3711
|
} catch {
|
|
3237
3712
|
if (verbose) {
|
|
3238
3713
|
console.error(`[Lien] File deleted: ${filepath}`);
|
|
@@ -3242,9 +3717,9 @@ async function indexSingleFile(filepath, vectorDB, embeddings, config, options =
|
|
|
3242
3717
|
await manifest2.removeFile(filepath);
|
|
3243
3718
|
return;
|
|
3244
3719
|
}
|
|
3245
|
-
const content = await
|
|
3720
|
+
const content = await fs16.readFile(filepath, "utf-8");
|
|
3246
3721
|
const result = await processFileContent(filepath, content, embeddings, config, verbose || false);
|
|
3247
|
-
const stats = await
|
|
3722
|
+
const stats = await fs16.stat(filepath);
|
|
3248
3723
|
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
3249
3724
|
if (result === null) {
|
|
3250
3725
|
await vectorDB.deleteByFile(filepath);
|
|
@@ -3281,9 +3756,9 @@ async function indexMultipleFiles(filepaths, vectorDB, embeddings, config, optio
|
|
|
3281
3756
|
let content;
|
|
3282
3757
|
let fileMtime;
|
|
3283
3758
|
try {
|
|
3284
|
-
const stats = await
|
|
3759
|
+
const stats = await fs16.stat(filepath);
|
|
3285
3760
|
fileMtime = stats.mtimeMs;
|
|
3286
|
-
content = await
|
|
3761
|
+
content = await fs16.readFile(filepath, "utf-8");
|
|
3287
3762
|
} catch (error) {
|
|
3288
3763
|
if (verbose) {
|
|
3289
3764
|
console.error(`[Lien] File not readable: ${filepath}`);
|
|
@@ -3448,9 +3923,9 @@ var indexer_exports = {};
|
|
|
3448
3923
|
__export(indexer_exports, {
|
|
3449
3924
|
indexCodebase: () => indexCodebase
|
|
3450
3925
|
});
|
|
3451
|
-
import
|
|
3926
|
+
import fs17 from "fs/promises";
|
|
3452
3927
|
import ora from "ora";
|
|
3453
|
-
import
|
|
3928
|
+
import chalk5 from "chalk";
|
|
3454
3929
|
import pLimit from "p-limit";
|
|
3455
3930
|
async function indexCodebase(options = {}) {
|
|
3456
3931
|
const rootDir = options.rootDir ?? process.cwd();
|
|
@@ -3523,7 +3998,7 @@ async function indexCodebase(options = {}) {
|
|
|
3523
3998
|
await manifest2.updateGitState(gitState);
|
|
3524
3999
|
}
|
|
3525
4000
|
}
|
|
3526
|
-
console.log(
|
|
4001
|
+
console.log(chalk5.dim("\nNext step: Run"), chalk5.bold("lien serve"), chalk5.dim("to start the MCP server"));
|
|
3527
4002
|
return;
|
|
3528
4003
|
}
|
|
3529
4004
|
spinner.text = "Full reindex required...";
|
|
@@ -3612,8 +4087,8 @@ async function indexCodebase(options = {}) {
|
|
|
3612
4087
|
const filePromises = files.map(
|
|
3613
4088
|
(file) => limit(async () => {
|
|
3614
4089
|
try {
|
|
3615
|
-
const stats = await
|
|
3616
|
-
const content = await
|
|
4090
|
+
const stats = await fs17.stat(file);
|
|
4091
|
+
const content = await fs17.readFile(file, "utf-8");
|
|
3617
4092
|
const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
|
|
3618
4093
|
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : 10;
|
|
3619
4094
|
const useAST = isModernConfig(config) ? config.chunking.useAST : true;
|
|
@@ -3647,7 +4122,7 @@ async function indexCodebase(options = {}) {
|
|
|
3647
4122
|
}
|
|
3648
4123
|
} catch (error) {
|
|
3649
4124
|
if (options.verbose) {
|
|
3650
|
-
console.error(
|
|
4125
|
+
console.error(chalk5.yellow(`
|
|
3651
4126
|
\u26A0\uFE0F Skipping ${file}: ${error}`));
|
|
3652
4127
|
}
|
|
3653
4128
|
processedFiles++;
|
|
@@ -3687,7 +4162,7 @@ async function indexCodebase(options = {}) {
|
|
|
3687
4162
|
spinner.succeed(
|
|
3688
4163
|
`Indexed ${processedFiles} files (${processedChunks} chunks) in ${totalTime}s using ${concurrency}x concurrency`
|
|
3689
4164
|
);
|
|
3690
|
-
console.log(
|
|
4165
|
+
console.log(chalk5.dim("\nNext step: Run"), chalk5.bold("lien serve"), chalk5.dim("to start the MCP server"));
|
|
3691
4166
|
} catch (error) {
|
|
3692
4167
|
if (updateInterval) {
|
|
3693
4168
|
clearInterval(updateInterval);
|
|
@@ -3722,11 +4197,10 @@ import { dirname as dirname4, join as join4 } from "path";
|
|
|
3722
4197
|
|
|
3723
4198
|
// src/cli/init.ts
|
|
3724
4199
|
init_schema();
|
|
3725
|
-
|
|
3726
|
-
import
|
|
3727
|
-
import path5 from "path";
|
|
4200
|
+
import fs7 from "fs/promises";
|
|
4201
|
+
import path7 from "path";
|
|
3728
4202
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3729
|
-
import
|
|
4203
|
+
import chalk3 from "chalk";
|
|
3730
4204
|
import inquirer from "inquirer";
|
|
3731
4205
|
|
|
3732
4206
|
// src/utils/banner.ts
|
|
@@ -3788,12 +4262,111 @@ function showCompactBanner() {
|
|
|
3788
4262
|
console.log();
|
|
3789
4263
|
}
|
|
3790
4264
|
|
|
3791
|
-
// src/
|
|
4265
|
+
// src/config/migration-manager.ts
|
|
4266
|
+
init_schema();
|
|
3792
4267
|
init_migration();
|
|
4268
|
+
init_merge();
|
|
4269
|
+
init_constants();
|
|
4270
|
+
import fs2 from "fs/promises";
|
|
4271
|
+
import path2 from "path";
|
|
4272
|
+
import chalk2 from "chalk";
|
|
4273
|
+
var MigrationManager = class {
|
|
4274
|
+
constructor(rootDir = process.cwd()) {
|
|
4275
|
+
this.rootDir = rootDir;
|
|
4276
|
+
}
|
|
4277
|
+
/**
|
|
4278
|
+
* Get the config file path
|
|
4279
|
+
*/
|
|
4280
|
+
getConfigPath() {
|
|
4281
|
+
return path2.join(this.rootDir, ".lien.config.json");
|
|
4282
|
+
}
|
|
4283
|
+
/**
|
|
4284
|
+
* Check if the current config needs migration
|
|
4285
|
+
*/
|
|
4286
|
+
async needsMigration() {
|
|
4287
|
+
try {
|
|
4288
|
+
const configPath = this.getConfigPath();
|
|
4289
|
+
const content = await fs2.readFile(configPath, "utf-8");
|
|
4290
|
+
const config = JSON.parse(content);
|
|
4291
|
+
return needsMigration(config);
|
|
4292
|
+
} catch (error) {
|
|
4293
|
+
return false;
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
/**
|
|
4297
|
+
* Perform silent migration (for auto-migration during load)
|
|
4298
|
+
* Returns the migrated config without user interaction
|
|
4299
|
+
*/
|
|
4300
|
+
async autoMigrate() {
|
|
4301
|
+
const result = await migrateConfigFile(this.rootDir);
|
|
4302
|
+
if (result.migrated && result.backupPath) {
|
|
4303
|
+
const backupFilename = path2.basename(result.backupPath);
|
|
4304
|
+
console.log(`\u2705 Migration complete! Backup saved as ${backupFilename}`);
|
|
4305
|
+
console.log("\u{1F4DD} Your config now uses the framework-based structure.");
|
|
4306
|
+
}
|
|
4307
|
+
return result.config;
|
|
4308
|
+
}
|
|
4309
|
+
/**
|
|
4310
|
+
* Perform interactive upgrade (for CLI upgrade command)
|
|
4311
|
+
* Provides detailed feedback and handles edge cases
|
|
4312
|
+
*/
|
|
4313
|
+
async upgradeInteractive() {
|
|
4314
|
+
const configPath = this.getConfigPath();
|
|
4315
|
+
try {
|
|
4316
|
+
const existingContent = await fs2.readFile(configPath, "utf-8");
|
|
4317
|
+
const existingConfig = JSON.parse(existingContent);
|
|
4318
|
+
const migrationNeeded = needsMigration(existingConfig);
|
|
4319
|
+
const newFields = migrationNeeded ? [] : detectNewFields(existingConfig, defaultConfig);
|
|
4320
|
+
const hasChanges = migrationNeeded || newFields.length > 0;
|
|
4321
|
+
if (!hasChanges) {
|
|
4322
|
+
console.log(chalk2.green("\u2713 Config is already up to date"));
|
|
4323
|
+
console.log(chalk2.dim("No changes needed"));
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
const backupPath = `${configPath}.backup`;
|
|
4327
|
+
await fs2.copyFile(configPath, backupPath);
|
|
4328
|
+
let upgradedConfig;
|
|
4329
|
+
let migrated = false;
|
|
4330
|
+
if (migrationNeeded) {
|
|
4331
|
+
console.log(chalk2.blue(`\u{1F504} Migrating config from v0.2.0 to v${CURRENT_CONFIG_VERSION}...`));
|
|
4332
|
+
upgradedConfig = migrateConfig(existingConfig);
|
|
4333
|
+
migrated = true;
|
|
4334
|
+
} else {
|
|
4335
|
+
upgradedConfig = deepMergeConfig(defaultConfig, existingConfig);
|
|
4336
|
+
console.log(chalk2.dim("\nNew options added:"));
|
|
4337
|
+
newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
|
|
4338
|
+
}
|
|
4339
|
+
await fs2.writeFile(
|
|
4340
|
+
configPath,
|
|
4341
|
+
JSON.stringify(upgradedConfig, null, 2) + "\n",
|
|
4342
|
+
"utf-8"
|
|
4343
|
+
);
|
|
4344
|
+
console.log(chalk2.green("\u2713 Config upgraded successfully"));
|
|
4345
|
+
console.log(chalk2.dim("Backup saved to:"), backupPath);
|
|
4346
|
+
if (migrated) {
|
|
4347
|
+
console.log(chalk2.dim("\n\u{1F4DD} Your config now uses the framework-based structure."));
|
|
4348
|
+
}
|
|
4349
|
+
} catch (error) {
|
|
4350
|
+
if (error.code === "ENOENT") {
|
|
4351
|
+
console.log(chalk2.red("Error: No config file found"));
|
|
4352
|
+
console.log(chalk2.dim("Run"), chalk2.bold("lien init"), chalk2.dim("to create a config file"));
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
4355
|
+
throw error;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
/**
|
|
4359
|
+
* Perform migration and return result
|
|
4360
|
+
* Used when programmatic access to migration result is needed
|
|
4361
|
+
*/
|
|
4362
|
+
async migrate() {
|
|
4363
|
+
return migrateConfigFile(this.rootDir);
|
|
4364
|
+
}
|
|
4365
|
+
};
|
|
3793
4366
|
|
|
3794
4367
|
// src/frameworks/detector-service.ts
|
|
3795
|
-
import
|
|
3796
|
-
import
|
|
4368
|
+
import fs6 from "fs/promises";
|
|
4369
|
+
import path6 from "path";
|
|
3797
4370
|
|
|
3798
4371
|
// src/frameworks/types.ts
|
|
3799
4372
|
var defaultDetectionOptions = {
|
|
@@ -3815,8 +4388,8 @@ var defaultDetectionOptions = {
|
|
|
3815
4388
|
};
|
|
3816
4389
|
|
|
3817
4390
|
// src/frameworks/nodejs/detector.ts
|
|
3818
|
-
import
|
|
3819
|
-
import
|
|
4391
|
+
import fs3 from "fs/promises";
|
|
4392
|
+
import path3 from "path";
|
|
3820
4393
|
|
|
3821
4394
|
// src/frameworks/nodejs/config.ts
|
|
3822
4395
|
async function generateNodeJsConfig(_rootDir, _relativePath) {
|
|
@@ -3867,7 +4440,7 @@ var nodejsDetector = {
|
|
|
3867
4440
|
priority: 50,
|
|
3868
4441
|
// Generic, yields to specific frameworks like Laravel
|
|
3869
4442
|
async detect(rootDir, relativePath) {
|
|
3870
|
-
const fullPath =
|
|
4443
|
+
const fullPath = path3.join(rootDir, relativePath);
|
|
3871
4444
|
const result = {
|
|
3872
4445
|
detected: false,
|
|
3873
4446
|
name: "nodejs",
|
|
@@ -3875,10 +4448,10 @@ var nodejsDetector = {
|
|
|
3875
4448
|
confidence: "low",
|
|
3876
4449
|
evidence: []
|
|
3877
4450
|
};
|
|
3878
|
-
const packageJsonPath =
|
|
4451
|
+
const packageJsonPath = path3.join(fullPath, "package.json");
|
|
3879
4452
|
let packageJson5 = null;
|
|
3880
4453
|
try {
|
|
3881
|
-
const content = await
|
|
4454
|
+
const content = await fs3.readFile(packageJsonPath, "utf-8");
|
|
3882
4455
|
packageJson5 = JSON.parse(content);
|
|
3883
4456
|
result.evidence.push("Found package.json");
|
|
3884
4457
|
} catch {
|
|
@@ -3926,8 +4499,8 @@ var nodejsDetector = {
|
|
|
3926
4499
|
};
|
|
3927
4500
|
|
|
3928
4501
|
// src/frameworks/laravel/detector.ts
|
|
3929
|
-
import
|
|
3930
|
-
import
|
|
4502
|
+
import fs4 from "fs/promises";
|
|
4503
|
+
import path4 from "path";
|
|
3931
4504
|
|
|
3932
4505
|
// src/frameworks/laravel/config.ts
|
|
3933
4506
|
async function generateLaravelConfig(_rootDir, _relativePath) {
|
|
@@ -3984,7 +4557,7 @@ var laravelDetector = {
|
|
|
3984
4557
|
priority: 100,
|
|
3985
4558
|
// Laravel takes precedence over Node.js
|
|
3986
4559
|
async detect(rootDir, relativePath) {
|
|
3987
|
-
const fullPath =
|
|
4560
|
+
const fullPath = path4.join(rootDir, relativePath);
|
|
3988
4561
|
const result = {
|
|
3989
4562
|
detected: false,
|
|
3990
4563
|
name: "laravel",
|
|
@@ -3992,10 +4565,10 @@ var laravelDetector = {
|
|
|
3992
4565
|
confidence: "low",
|
|
3993
4566
|
evidence: []
|
|
3994
4567
|
};
|
|
3995
|
-
const composerJsonPath =
|
|
4568
|
+
const composerJsonPath = path4.join(fullPath, "composer.json");
|
|
3996
4569
|
let composerJson = null;
|
|
3997
4570
|
try {
|
|
3998
|
-
const content = await
|
|
4571
|
+
const content = await fs4.readFile(composerJsonPath, "utf-8");
|
|
3999
4572
|
composerJson = JSON.parse(content);
|
|
4000
4573
|
result.evidence.push("Found composer.json");
|
|
4001
4574
|
} catch {
|
|
@@ -4006,9 +4579,9 @@ var laravelDetector = {
|
|
|
4006
4579
|
return result;
|
|
4007
4580
|
}
|
|
4008
4581
|
result.evidence.push("Laravel framework detected in composer.json");
|
|
4009
|
-
const artisanPath =
|
|
4582
|
+
const artisanPath = path4.join(fullPath, "artisan");
|
|
4010
4583
|
try {
|
|
4011
|
-
await
|
|
4584
|
+
await fs4.access(artisanPath);
|
|
4012
4585
|
result.evidence.push("Found artisan file");
|
|
4013
4586
|
result.confidence = "high";
|
|
4014
4587
|
} catch {
|
|
@@ -4018,8 +4591,8 @@ var laravelDetector = {
|
|
|
4018
4591
|
let foundDirs = 0;
|
|
4019
4592
|
for (const dir of laravelDirs) {
|
|
4020
4593
|
try {
|
|
4021
|
-
const dirPath =
|
|
4022
|
-
const stats = await
|
|
4594
|
+
const dirPath = path4.join(fullPath, dir);
|
|
4595
|
+
const stats = await fs4.stat(dirPath);
|
|
4023
4596
|
if (stats.isDirectory()) {
|
|
4024
4597
|
foundDirs++;
|
|
4025
4598
|
}
|
|
@@ -4031,14 +4604,14 @@ var laravelDetector = {
|
|
|
4031
4604
|
result.confidence = "high";
|
|
4032
4605
|
}
|
|
4033
4606
|
const testDirsToCheck = [
|
|
4034
|
-
|
|
4035
|
-
|
|
4607
|
+
path4.join(fullPath, "tests", "Feature"),
|
|
4608
|
+
path4.join(fullPath, "tests", "Unit")
|
|
4036
4609
|
];
|
|
4037
4610
|
for (const testDir of testDirsToCheck) {
|
|
4038
4611
|
try {
|
|
4039
|
-
const stats = await
|
|
4612
|
+
const stats = await fs4.stat(testDir);
|
|
4040
4613
|
if (stats.isDirectory()) {
|
|
4041
|
-
const dirName =
|
|
4614
|
+
const dirName = path4.basename(path4.dirname(testDir)) + "/" + path4.basename(testDir);
|
|
4042
4615
|
result.evidence.push(`Found ${dirName} test directory`);
|
|
4043
4616
|
}
|
|
4044
4617
|
} catch {
|
|
@@ -4056,8 +4629,8 @@ var laravelDetector = {
|
|
|
4056
4629
|
};
|
|
4057
4630
|
|
|
4058
4631
|
// src/frameworks/shopify/detector.ts
|
|
4059
|
-
import
|
|
4060
|
-
import
|
|
4632
|
+
import fs5 from "fs/promises";
|
|
4633
|
+
import path5 from "path";
|
|
4061
4634
|
|
|
4062
4635
|
// src/frameworks/shopify/config.ts
|
|
4063
4636
|
async function generateShopifyConfig(_rootDir, _relativePath) {
|
|
@@ -4069,6 +4642,8 @@ async function generateShopifyConfig(_rootDir, _relativePath) {
|
|
|
4069
4642
|
"snippets/**/*.liquid",
|
|
4070
4643
|
"templates/**/*.liquid",
|
|
4071
4644
|
// Matches any nesting level (e.g., templates/customers/account.liquid)
|
|
4645
|
+
"templates/**/*.json",
|
|
4646
|
+
// JSON template definitions (Shopify 2.0+)
|
|
4072
4647
|
// Theme editor blocks (Online Store 2.0)
|
|
4073
4648
|
"blocks/**/*.liquid",
|
|
4074
4649
|
// Assets (CSS, JS with optional Liquid templating)
|
|
@@ -4113,7 +4688,7 @@ var shopifyDetector = {
|
|
|
4113
4688
|
priority: 100,
|
|
4114
4689
|
// High priority (same as Laravel)
|
|
4115
4690
|
async detect(rootDir, relativePath) {
|
|
4116
|
-
const fullPath =
|
|
4691
|
+
const fullPath = path5.join(rootDir, relativePath);
|
|
4117
4692
|
const result = {
|
|
4118
4693
|
detected: false,
|
|
4119
4694
|
name: "shopify",
|
|
@@ -4121,18 +4696,18 @@ var shopifyDetector = {
|
|
|
4121
4696
|
confidence: "low",
|
|
4122
4697
|
evidence: []
|
|
4123
4698
|
};
|
|
4124
|
-
const settingsSchemaPath =
|
|
4699
|
+
const settingsSchemaPath = path5.join(fullPath, "config", "settings_schema.json");
|
|
4125
4700
|
let hasSettingsSchema = false;
|
|
4126
4701
|
try {
|
|
4127
|
-
await
|
|
4702
|
+
await fs5.access(settingsSchemaPath);
|
|
4128
4703
|
hasSettingsSchema = true;
|
|
4129
4704
|
result.evidence.push("Found config/settings_schema.json");
|
|
4130
4705
|
} catch {
|
|
4131
4706
|
}
|
|
4132
|
-
const themeLayoutPath =
|
|
4707
|
+
const themeLayoutPath = path5.join(fullPath, "layout", "theme.liquid");
|
|
4133
4708
|
let hasThemeLayout = false;
|
|
4134
4709
|
try {
|
|
4135
|
-
await
|
|
4710
|
+
await fs5.access(themeLayoutPath);
|
|
4136
4711
|
hasThemeLayout = true;
|
|
4137
4712
|
result.evidence.push("Found layout/theme.liquid");
|
|
4138
4713
|
} catch {
|
|
@@ -4141,8 +4716,8 @@ var shopifyDetector = {
|
|
|
4141
4716
|
let foundDirs = 0;
|
|
4142
4717
|
for (const dir of shopifyDirs) {
|
|
4143
4718
|
try {
|
|
4144
|
-
const dirPath =
|
|
4145
|
-
const stats = await
|
|
4719
|
+
const dirPath = path5.join(fullPath, dir);
|
|
4720
|
+
const stats = await fs5.stat(dirPath);
|
|
4146
4721
|
if (stats.isDirectory()) {
|
|
4147
4722
|
foundDirs++;
|
|
4148
4723
|
}
|
|
@@ -4153,14 +4728,14 @@ var shopifyDetector = {
|
|
|
4153
4728
|
result.evidence.push(`Shopify directory structure detected (${foundDirs}/${shopifyDirs.length} dirs)`);
|
|
4154
4729
|
}
|
|
4155
4730
|
try {
|
|
4156
|
-
const tomlPath =
|
|
4157
|
-
await
|
|
4731
|
+
const tomlPath = path5.join(fullPath, "shopify.theme.toml");
|
|
4732
|
+
await fs5.access(tomlPath);
|
|
4158
4733
|
result.evidence.push("Found shopify.theme.toml");
|
|
4159
4734
|
} catch {
|
|
4160
4735
|
}
|
|
4161
4736
|
try {
|
|
4162
|
-
const ignorePath =
|
|
4163
|
-
await
|
|
4737
|
+
const ignorePath = path5.join(fullPath, ".shopifyignore");
|
|
4738
|
+
await fs5.access(ignorePath);
|
|
4164
4739
|
result.evidence.push("Found .shopifyignore");
|
|
4165
4740
|
} catch {
|
|
4166
4741
|
}
|
|
@@ -4206,7 +4781,7 @@ async function detectAllFrameworks(rootDir, options = {}) {
|
|
|
4206
4781
|
return results;
|
|
4207
4782
|
}
|
|
4208
4783
|
async function detectAtPath(rootDir, relativePath, results, visited) {
|
|
4209
|
-
const fullPath =
|
|
4784
|
+
const fullPath = path6.join(rootDir, relativePath);
|
|
4210
4785
|
if (visited.has(fullPath)) {
|
|
4211
4786
|
return;
|
|
4212
4787
|
}
|
|
@@ -4273,9 +4848,9 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
|
|
|
4273
4848
|
if (depth >= options.maxDepth) {
|
|
4274
4849
|
return;
|
|
4275
4850
|
}
|
|
4276
|
-
const fullPath =
|
|
4851
|
+
const fullPath = path6.join(rootDir, relativePath);
|
|
4277
4852
|
try {
|
|
4278
|
-
const entries = await
|
|
4853
|
+
const entries = await fs6.readdir(fullPath, { withFileTypes: true });
|
|
4279
4854
|
const dirs = entries.filter((e) => e.isDirectory());
|
|
4280
4855
|
for (const dir of dirs) {
|
|
4281
4856
|
if (options.skipDirs.includes(dir.name)) {
|
|
@@ -4284,7 +4859,7 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
|
|
|
4284
4859
|
if (dir.name.startsWith(".")) {
|
|
4285
4860
|
continue;
|
|
4286
4861
|
}
|
|
4287
|
-
const subPath = relativePath === "." ? dir.name :
|
|
4862
|
+
const subPath = relativePath === "." ? dir.name : path6.join(relativePath, dir.name);
|
|
4288
4863
|
await detectAtPath(rootDir, subPath, results, visited);
|
|
4289
4864
|
await scanSubdirectories(rootDir, subPath, results, visited, depth + 1, options);
|
|
4290
4865
|
}
|
|
@@ -4294,44 +4869,44 @@ async function scanSubdirectories(rootDir, relativePath, results, visited, depth
|
|
|
4294
4869
|
}
|
|
4295
4870
|
|
|
4296
4871
|
// src/cli/init.ts
|
|
4297
|
-
init_constants();
|
|
4298
4872
|
var __filename3 = fileURLToPath3(import.meta.url);
|
|
4299
|
-
var __dirname3 =
|
|
4873
|
+
var __dirname3 = path7.dirname(__filename3);
|
|
4300
4874
|
async function initCommand(options = {}) {
|
|
4301
4875
|
const rootDir = options.path || process.cwd();
|
|
4302
|
-
const configPath =
|
|
4876
|
+
const configPath = path7.join(rootDir, ".lien.config.json");
|
|
4303
4877
|
try {
|
|
4304
4878
|
let configExists = false;
|
|
4305
4879
|
try {
|
|
4306
|
-
await
|
|
4880
|
+
await fs7.access(configPath);
|
|
4307
4881
|
configExists = true;
|
|
4308
4882
|
} catch {
|
|
4309
4883
|
}
|
|
4310
4884
|
if (configExists && options.upgrade) {
|
|
4311
|
-
|
|
4885
|
+
const migrationManager = new MigrationManager(rootDir);
|
|
4886
|
+
await migrationManager.upgradeInteractive();
|
|
4312
4887
|
return;
|
|
4313
4888
|
}
|
|
4314
4889
|
if (configExists && !options.upgrade) {
|
|
4315
|
-
console.log(
|
|
4316
|
-
console.log(
|
|
4890
|
+
console.log(chalk3.yellow("\u26A0\uFE0F .lien.config.json already exists"));
|
|
4891
|
+
console.log(chalk3.dim("Run"), chalk3.bold("lien init --upgrade"), chalk3.dim("to merge new config options"));
|
|
4317
4892
|
return;
|
|
4318
4893
|
}
|
|
4319
4894
|
if (!configExists) {
|
|
4320
4895
|
await createNewConfig(rootDir, options);
|
|
4321
4896
|
}
|
|
4322
4897
|
} catch (error) {
|
|
4323
|
-
console.error(
|
|
4898
|
+
console.error(chalk3.red("Error creating config file:"), error);
|
|
4324
4899
|
process.exit(1);
|
|
4325
4900
|
}
|
|
4326
4901
|
}
|
|
4327
4902
|
async function createNewConfig(rootDir, options) {
|
|
4328
4903
|
showCompactBanner();
|
|
4329
|
-
console.log(
|
|
4330
|
-
console.log(
|
|
4904
|
+
console.log(chalk3.bold("Initializing Lien...\n"));
|
|
4905
|
+
console.log(chalk3.dim("\u{1F50D} Detecting frameworks in"), chalk3.bold(rootDir));
|
|
4331
4906
|
const detections = await detectAllFrameworks(rootDir);
|
|
4332
4907
|
let frameworks = [];
|
|
4333
4908
|
if (detections.length === 0) {
|
|
4334
|
-
console.log(
|
|
4909
|
+
console.log(chalk3.yellow("\n\u26A0\uFE0F No frameworks detected"));
|
|
4335
4910
|
if (!options.yes) {
|
|
4336
4911
|
const { useGeneric } = await inquirer.prompt([
|
|
4337
4912
|
{
|
|
@@ -4342,7 +4917,7 @@ async function createNewConfig(rootDir, options) {
|
|
|
4342
4917
|
}
|
|
4343
4918
|
]);
|
|
4344
4919
|
if (!useGeneric) {
|
|
4345
|
-
console.log(
|
|
4920
|
+
console.log(chalk3.dim("Aborted."));
|
|
4346
4921
|
return;
|
|
4347
4922
|
}
|
|
4348
4923
|
}
|
|
@@ -4365,16 +4940,16 @@ async function createNewConfig(rootDir, options) {
|
|
|
4365
4940
|
}
|
|
4366
4941
|
});
|
|
4367
4942
|
} else {
|
|
4368
|
-
console.log(
|
|
4943
|
+
console.log(chalk3.green(`
|
|
4369
4944
|
\u2713 Found ${detections.length} framework(s):
|
|
4370
4945
|
`));
|
|
4371
4946
|
for (const det of detections) {
|
|
4372
4947
|
const pathDisplay = det.path === "." ? "root" : det.path;
|
|
4373
|
-
console.log(
|
|
4374
|
-
console.log(
|
|
4948
|
+
console.log(chalk3.bold(` ${det.name}`), chalk3.dim(`(${det.confidence} confidence)`));
|
|
4949
|
+
console.log(chalk3.dim(` Location: ${pathDisplay}`));
|
|
4375
4950
|
if (det.evidence.length > 0) {
|
|
4376
4951
|
det.evidence.forEach((e) => {
|
|
4377
|
-
console.log(
|
|
4952
|
+
console.log(chalk3.dim(` \u2022 ${e}`));
|
|
4378
4953
|
});
|
|
4379
4954
|
}
|
|
4380
4955
|
console.log();
|
|
@@ -4389,14 +4964,14 @@ async function createNewConfig(rootDir, options) {
|
|
|
4389
4964
|
}
|
|
4390
4965
|
]);
|
|
4391
4966
|
if (!confirm) {
|
|
4392
|
-
console.log(
|
|
4967
|
+
console.log(chalk3.dim("Aborted."));
|
|
4393
4968
|
return;
|
|
4394
4969
|
}
|
|
4395
4970
|
}
|
|
4396
4971
|
for (const det of detections) {
|
|
4397
4972
|
const detector = getFrameworkDetector(det.name);
|
|
4398
4973
|
if (!detector) {
|
|
4399
|
-
console.warn(
|
|
4974
|
+
console.warn(chalk3.yellow(`\u26A0\uFE0F No detector found for ${det.name}, skipping`));
|
|
4400
4975
|
continue;
|
|
4401
4976
|
}
|
|
4402
4977
|
const frameworkConfig = await detector.generateConfig(rootDir, det.path);
|
|
@@ -4418,7 +4993,7 @@ async function createNewConfig(rootDir, options) {
|
|
|
4418
4993
|
finalConfig = { ...frameworkConfig, ...customized };
|
|
4419
4994
|
} else {
|
|
4420
4995
|
const pathDisplay = det.path === "." ? "root" : det.path;
|
|
4421
|
-
console.log(
|
|
4996
|
+
console.log(chalk3.dim(` \u2192 Using defaults for ${det.name} at ${pathDisplay}`));
|
|
4422
4997
|
}
|
|
4423
4998
|
frameworks.push({
|
|
4424
4999
|
name: det.name,
|
|
@@ -4439,23 +5014,23 @@ async function createNewConfig(rootDir, options) {
|
|
|
4439
5014
|
]);
|
|
4440
5015
|
if (installCursorRules) {
|
|
4441
5016
|
try {
|
|
4442
|
-
const cursorRulesDir =
|
|
4443
|
-
await
|
|
4444
|
-
const templatePath =
|
|
4445
|
-
const rulesPath =
|
|
5017
|
+
const cursorRulesDir = path7.join(rootDir, ".cursor");
|
|
5018
|
+
await fs7.mkdir(cursorRulesDir, { recursive: true });
|
|
5019
|
+
const templatePath = path7.join(__dirname3, "../CURSOR_RULES_TEMPLATE.md");
|
|
5020
|
+
const rulesPath = path7.join(cursorRulesDir, "rules");
|
|
4446
5021
|
let targetPath;
|
|
4447
5022
|
let isDirectory = false;
|
|
4448
5023
|
let isFile = false;
|
|
4449
5024
|
try {
|
|
4450
|
-
const stats = await
|
|
5025
|
+
const stats = await fs7.stat(rulesPath);
|
|
4451
5026
|
isDirectory = stats.isDirectory();
|
|
4452
5027
|
isFile = stats.isFile();
|
|
4453
5028
|
} catch {
|
|
4454
5029
|
}
|
|
4455
5030
|
if (isDirectory) {
|
|
4456
|
-
targetPath =
|
|
4457
|
-
await
|
|
4458
|
-
console.log(
|
|
5031
|
+
targetPath = path7.join(rulesPath, "lien.mdc");
|
|
5032
|
+
await fs7.copyFile(templatePath, targetPath);
|
|
5033
|
+
console.log(chalk3.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
|
|
4459
5034
|
} else if (isFile) {
|
|
4460
5035
|
const { convertToDir } = await inquirer.prompt([
|
|
4461
5036
|
{
|
|
@@ -4466,27 +5041,27 @@ async function createNewConfig(rootDir, options) {
|
|
|
4466
5041
|
}
|
|
4467
5042
|
]);
|
|
4468
5043
|
if (convertToDir) {
|
|
4469
|
-
const existingRules = await
|
|
4470
|
-
await
|
|
4471
|
-
await
|
|
4472
|
-
await
|
|
4473
|
-
await
|
|
4474
|
-
console.log(
|
|
4475
|
-
console.log(
|
|
4476
|
-
console.log(
|
|
5044
|
+
const existingRules = await fs7.readFile(rulesPath, "utf-8");
|
|
5045
|
+
await fs7.unlink(rulesPath);
|
|
5046
|
+
await fs7.mkdir(rulesPath);
|
|
5047
|
+
await fs7.writeFile(path7.join(rulesPath, "project.mdc"), existingRules);
|
|
5048
|
+
await fs7.copyFile(templatePath, path7.join(rulesPath, "lien.mdc"));
|
|
5049
|
+
console.log(chalk3.green("\u2713 Converted .cursor/rules to directory"));
|
|
5050
|
+
console.log(chalk3.green(" - Your project rules: .cursor/rules/project.mdc"));
|
|
5051
|
+
console.log(chalk3.green(" - Lien rules: .cursor/rules/lien.mdc"));
|
|
4477
5052
|
} else {
|
|
4478
|
-
console.log(
|
|
5053
|
+
console.log(chalk3.dim("Skipped Cursor rules installation (preserving existing file)"));
|
|
4479
5054
|
}
|
|
4480
5055
|
} else {
|
|
4481
|
-
await
|
|
4482
|
-
targetPath =
|
|
4483
|
-
await
|
|
4484
|
-
console.log(
|
|
5056
|
+
await fs7.mkdir(rulesPath, { recursive: true });
|
|
5057
|
+
targetPath = path7.join(rulesPath, "lien.mdc");
|
|
5058
|
+
await fs7.copyFile(templatePath, targetPath);
|
|
5059
|
+
console.log(chalk3.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
|
|
4485
5060
|
}
|
|
4486
5061
|
} catch (error) {
|
|
4487
|
-
console.log(
|
|
4488
|
-
console.log(
|
|
4489
|
-
console.log(
|
|
5062
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Could not install Cursor rules"));
|
|
5063
|
+
console.log(chalk3.dim(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
5064
|
+
console.log(chalk3.dim("You can manually copy CURSOR_RULES_TEMPLATE.md to .cursor/rules/lien.mdc"));
|
|
4490
5065
|
}
|
|
4491
5066
|
}
|
|
4492
5067
|
}
|
|
@@ -4494,17 +5069,17 @@ async function createNewConfig(rootDir, options) {
|
|
|
4494
5069
|
...defaultConfig,
|
|
4495
5070
|
frameworks
|
|
4496
5071
|
};
|
|
4497
|
-
const configPath =
|
|
4498
|
-
await
|
|
4499
|
-
console.log(
|
|
4500
|
-
console.log(
|
|
4501
|
-
console.log(
|
|
4502
|
-
console.log(
|
|
4503
|
-
console.log(
|
|
4504
|
-
console.log(
|
|
5072
|
+
const configPath = path7.join(rootDir, ".lien.config.json");
|
|
5073
|
+
await fs7.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
5074
|
+
console.log(chalk3.green("\n\u2713 Created .lien.config.json"));
|
|
5075
|
+
console.log(chalk3.green(`\u2713 Configured ${frameworks.length} framework(s)`));
|
|
5076
|
+
console.log(chalk3.dim("\nNext steps:"));
|
|
5077
|
+
console.log(chalk3.dim(" 1. Run"), chalk3.bold("lien index"), chalk3.dim("to index your codebase"));
|
|
5078
|
+
console.log(chalk3.dim(" 2. Run"), chalk3.bold("lien serve"), chalk3.dim("to start the MCP server"));
|
|
5079
|
+
console.log(chalk3.dim(" 3. Configure Cursor to use the MCP server (see README.md)"));
|
|
4505
5080
|
}
|
|
4506
5081
|
async function promptForCustomization(frameworkName, config) {
|
|
4507
|
-
console.log(
|
|
5082
|
+
console.log(chalk3.bold(`
|
|
4508
5083
|
Customizing ${frameworkName} settings:`));
|
|
4509
5084
|
const answers = await inquirer.prompt([
|
|
4510
5085
|
{
|
|
@@ -4527,155 +5102,115 @@ Customizing ${frameworkName} settings:`));
|
|
|
4527
5102
|
exclude: answers.exclude
|
|
4528
5103
|
};
|
|
4529
5104
|
}
|
|
4530
|
-
async function upgradeConfig(configPath) {
|
|
4531
|
-
try {
|
|
4532
|
-
const existingContent = await fs5.readFile(configPath, "utf-8");
|
|
4533
|
-
const existingConfig = JSON.parse(existingContent);
|
|
4534
|
-
const migrationNeeded = needsMigration(existingConfig);
|
|
4535
|
-
const newFields = migrationNeeded ? [] : detectNewFields(existingConfig, defaultConfig);
|
|
4536
|
-
const hasChanges = migrationNeeded || newFields.length > 0;
|
|
4537
|
-
if (!hasChanges) {
|
|
4538
|
-
console.log(chalk2.green("\u2713 Config is already up to date"));
|
|
4539
|
-
console.log(chalk2.dim("No changes needed"));
|
|
4540
|
-
return;
|
|
4541
|
-
}
|
|
4542
|
-
const backupPath = `${configPath}.backup`;
|
|
4543
|
-
await fs5.copyFile(configPath, backupPath);
|
|
4544
|
-
let upgradedConfig;
|
|
4545
|
-
let migrated = false;
|
|
4546
|
-
if (migrationNeeded) {
|
|
4547
|
-
console.log(chalk2.blue(`\u{1F504} Migrating config from v0.2.0 to v${CURRENT_CONFIG_VERSION}...`));
|
|
4548
|
-
upgradedConfig = migrateConfig(existingConfig);
|
|
4549
|
-
migrated = true;
|
|
4550
|
-
} else {
|
|
4551
|
-
upgradedConfig = deepMergeConfig(defaultConfig, existingConfig);
|
|
4552
|
-
console.log(chalk2.dim("\nNew options added:"));
|
|
4553
|
-
newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
|
|
4554
|
-
}
|
|
4555
|
-
await fs5.writeFile(
|
|
4556
|
-
configPath,
|
|
4557
|
-
JSON.stringify(upgradedConfig, null, 2) + "\n",
|
|
4558
|
-
"utf-8"
|
|
4559
|
-
);
|
|
4560
|
-
console.log(chalk2.green("\u2713 Config upgraded successfully"));
|
|
4561
|
-
console.log(chalk2.dim("Backup saved to:"), backupPath);
|
|
4562
|
-
if (migrated) {
|
|
4563
|
-
console.log(chalk2.dim("\n\u{1F4DD} Your config now uses the framework-based structure."));
|
|
4564
|
-
}
|
|
4565
|
-
} catch (error) {
|
|
4566
|
-
console.error(chalk2.red("Error upgrading config:"), error);
|
|
4567
|
-
throw error;
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
5105
|
|
|
4571
5106
|
// src/cli/status.ts
|
|
4572
5107
|
init_service();
|
|
4573
5108
|
init_utils();
|
|
4574
5109
|
init_version2();
|
|
4575
|
-
import
|
|
4576
|
-
import
|
|
4577
|
-
import
|
|
5110
|
+
import chalk4 from "chalk";
|
|
5111
|
+
import fs11 from "fs/promises";
|
|
5112
|
+
import path11 from "path";
|
|
4578
5113
|
import os from "os";
|
|
4579
5114
|
import crypto from "crypto";
|
|
4580
5115
|
init_schema();
|
|
4581
5116
|
async function statusCommand() {
|
|
4582
5117
|
const rootDir = process.cwd();
|
|
4583
|
-
const projectName =
|
|
5118
|
+
const projectName = path11.basename(rootDir);
|
|
4584
5119
|
const pathHash = crypto.createHash("md5").update(rootDir).digest("hex").substring(0, 8);
|
|
4585
|
-
const indexPath =
|
|
5120
|
+
const indexPath = path11.join(os.homedir(), ".lien", "indices", `${projectName}-${pathHash}`);
|
|
4586
5121
|
showCompactBanner();
|
|
4587
|
-
console.log(
|
|
5122
|
+
console.log(chalk4.bold("Status\n"));
|
|
4588
5123
|
const hasConfig = await configService.exists(rootDir);
|
|
4589
|
-
console.log(
|
|
5124
|
+
console.log(chalk4.dim("Configuration:"), hasConfig ? chalk4.green("\u2713 Found") : chalk4.red("\u2717 Not initialized"));
|
|
4590
5125
|
if (!hasConfig) {
|
|
4591
|
-
console.log(
|
|
5126
|
+
console.log(chalk4.yellow("\nRun"), chalk4.bold("lien init"), chalk4.yellow("to initialize"));
|
|
4592
5127
|
return;
|
|
4593
5128
|
}
|
|
4594
5129
|
try {
|
|
4595
|
-
const stats = await
|
|
4596
|
-
console.log(
|
|
4597
|
-
console.log(
|
|
5130
|
+
const stats = await fs11.stat(indexPath);
|
|
5131
|
+
console.log(chalk4.dim("Index location:"), indexPath);
|
|
5132
|
+
console.log(chalk4.dim("Index status:"), chalk4.green("\u2713 Exists"));
|
|
4598
5133
|
try {
|
|
4599
|
-
const files = await
|
|
4600
|
-
console.log(
|
|
5134
|
+
const files = await fs11.readdir(indexPath, { recursive: true });
|
|
5135
|
+
console.log(chalk4.dim("Index files:"), files.length);
|
|
4601
5136
|
} catch (e) {
|
|
4602
5137
|
}
|
|
4603
|
-
console.log(
|
|
5138
|
+
console.log(chalk4.dim("Last modified:"), stats.mtime.toLocaleString());
|
|
4604
5139
|
try {
|
|
4605
5140
|
const version = await readVersionFile(indexPath);
|
|
4606
5141
|
if (version > 0) {
|
|
4607
5142
|
const versionDate = new Date(version);
|
|
4608
|
-
console.log(
|
|
5143
|
+
console.log(chalk4.dim("Last reindex:"), versionDate.toLocaleString());
|
|
4609
5144
|
}
|
|
4610
5145
|
} catch {
|
|
4611
5146
|
}
|
|
4612
5147
|
} catch (error) {
|
|
4613
|
-
console.log(
|
|
4614
|
-
console.log(
|
|
5148
|
+
console.log(chalk4.dim("Index status:"), chalk4.yellow("\u2717 Not indexed"));
|
|
5149
|
+
console.log(chalk4.yellow("\nRun"), chalk4.bold("lien index"), chalk4.yellow("to index your codebase"));
|
|
4615
5150
|
}
|
|
4616
5151
|
try {
|
|
4617
5152
|
const config = await configService.load(rootDir);
|
|
4618
|
-
console.log(
|
|
5153
|
+
console.log(chalk4.bold("\nFeatures:"));
|
|
4619
5154
|
const isRepo = await isGitRepo(rootDir);
|
|
4620
5155
|
if (config.gitDetection.enabled && isRepo) {
|
|
4621
|
-
console.log(
|
|
4622
|
-
console.log(
|
|
5156
|
+
console.log(chalk4.dim("Git detection:"), chalk4.green("\u2713 Enabled"));
|
|
5157
|
+
console.log(chalk4.dim(" Poll interval:"), `${config.gitDetection.pollIntervalMs / 1e3}s`);
|
|
4623
5158
|
try {
|
|
4624
5159
|
const branch = await getCurrentBranch(rootDir);
|
|
4625
5160
|
const commit = await getCurrentCommit(rootDir);
|
|
4626
|
-
console.log(
|
|
4627
|
-
console.log(
|
|
4628
|
-
const gitStateFile =
|
|
5161
|
+
console.log(chalk4.dim(" Current branch:"), branch);
|
|
5162
|
+
console.log(chalk4.dim(" Current commit:"), commit.substring(0, 8));
|
|
5163
|
+
const gitStateFile = path11.join(indexPath, ".git-state.json");
|
|
4629
5164
|
try {
|
|
4630
|
-
const gitStateContent = await
|
|
5165
|
+
const gitStateContent = await fs11.readFile(gitStateFile, "utf-8");
|
|
4631
5166
|
const gitState = JSON.parse(gitStateContent);
|
|
4632
5167
|
if (gitState.branch !== branch || gitState.commit !== commit) {
|
|
4633
|
-
console.log(
|
|
5168
|
+
console.log(chalk4.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
|
|
4634
5169
|
}
|
|
4635
5170
|
} catch {
|
|
4636
5171
|
}
|
|
4637
5172
|
} catch {
|
|
4638
5173
|
}
|
|
4639
5174
|
} else if (config.gitDetection.enabled && !isRepo) {
|
|
4640
|
-
console.log(
|
|
5175
|
+
console.log(chalk4.dim("Git detection:"), chalk4.yellow("Enabled (not a git repo)"));
|
|
4641
5176
|
} else {
|
|
4642
|
-
console.log(
|
|
5177
|
+
console.log(chalk4.dim("Git detection:"), chalk4.gray("Disabled"));
|
|
4643
5178
|
}
|
|
4644
5179
|
if (config.fileWatching.enabled) {
|
|
4645
|
-
console.log(
|
|
4646
|
-
console.log(
|
|
5180
|
+
console.log(chalk4.dim("File watching:"), chalk4.green("\u2713 Enabled"));
|
|
5181
|
+
console.log(chalk4.dim(" Debounce:"), `${config.fileWatching.debounceMs}ms`);
|
|
4647
5182
|
} else {
|
|
4648
|
-
console.log(
|
|
4649
|
-
console.log(
|
|
5183
|
+
console.log(chalk4.dim("File watching:"), chalk4.gray("Disabled"));
|
|
5184
|
+
console.log(chalk4.dim(" Enable with:"), chalk4.bold("lien serve --watch"));
|
|
4650
5185
|
}
|
|
4651
|
-
console.log(
|
|
5186
|
+
console.log(chalk4.bold("\nIndexing Settings:"));
|
|
4652
5187
|
if (isModernConfig(config)) {
|
|
4653
|
-
console.log(
|
|
4654
|
-
console.log(
|
|
4655
|
-
console.log(
|
|
4656
|
-
console.log(
|
|
5188
|
+
console.log(chalk4.dim("Concurrency:"), config.core.concurrency);
|
|
5189
|
+
console.log(chalk4.dim("Batch size:"), config.core.embeddingBatchSize);
|
|
5190
|
+
console.log(chalk4.dim("Chunk size:"), config.core.chunkSize);
|
|
5191
|
+
console.log(chalk4.dim("Chunk overlap:"), config.core.chunkOverlap);
|
|
4657
5192
|
}
|
|
4658
5193
|
} catch (error) {
|
|
4659
|
-
console.log(
|
|
5194
|
+
console.log(chalk4.yellow("\nWarning: Could not load configuration"));
|
|
4660
5195
|
}
|
|
4661
5196
|
}
|
|
4662
5197
|
|
|
4663
5198
|
// src/cli/index-cmd.ts
|
|
4664
5199
|
init_indexer();
|
|
4665
|
-
import
|
|
5200
|
+
import chalk6 from "chalk";
|
|
4666
5201
|
async function indexCommand(options) {
|
|
4667
5202
|
showCompactBanner();
|
|
4668
5203
|
try {
|
|
4669
5204
|
if (options.force) {
|
|
4670
5205
|
const { VectorDB: VectorDB2 } = await Promise.resolve().then(() => (init_lancedb(), lancedb_exports));
|
|
4671
5206
|
const { ManifestManager: ManifestManager2 } = await Promise.resolve().then(() => (init_manifest(), manifest_exports));
|
|
4672
|
-
console.log(
|
|
5207
|
+
console.log(chalk6.yellow("Clearing existing index and manifest..."));
|
|
4673
5208
|
const vectorDB = new VectorDB2(process.cwd());
|
|
4674
5209
|
await vectorDB.initialize();
|
|
4675
5210
|
await vectorDB.clear();
|
|
4676
5211
|
const manifest = new ManifestManager2(vectorDB.dbPath);
|
|
4677
5212
|
await manifest.clear();
|
|
4678
|
-
console.log(
|
|
5213
|
+
console.log(chalk6.green("\u2713 Index and manifest cleared\n"));
|
|
4679
5214
|
}
|
|
4680
5215
|
await indexCodebase({
|
|
4681
5216
|
rootDir: process.cwd(),
|
|
@@ -4683,18 +5218,18 @@ async function indexCommand(options) {
|
|
|
4683
5218
|
force: options.force || false
|
|
4684
5219
|
});
|
|
4685
5220
|
if (options.watch) {
|
|
4686
|
-
console.log(
|
|
5221
|
+
console.log(chalk6.yellow("\n\u26A0\uFE0F Watch mode not yet implemented"));
|
|
4687
5222
|
}
|
|
4688
5223
|
} catch (error) {
|
|
4689
|
-
console.error(
|
|
5224
|
+
console.error(chalk6.red("Error during indexing:"), error);
|
|
4690
5225
|
process.exit(1);
|
|
4691
5226
|
}
|
|
4692
5227
|
}
|
|
4693
5228
|
|
|
4694
5229
|
// src/cli/serve.ts
|
|
4695
|
-
import
|
|
4696
|
-
import
|
|
4697
|
-
import
|
|
5230
|
+
import chalk7 from "chalk";
|
|
5231
|
+
import fs18 from "fs/promises";
|
|
5232
|
+
import path17 from "path";
|
|
4698
5233
|
|
|
4699
5234
|
// src/mcp/server.ts
|
|
4700
5235
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -5324,36 +5859,36 @@ async function startMCPServer(options) {
|
|
|
5324
5859
|
|
|
5325
5860
|
// src/cli/serve.ts
|
|
5326
5861
|
async function serveCommand(options) {
|
|
5327
|
-
const rootDir = options.root ?
|
|
5862
|
+
const rootDir = options.root ? path17.resolve(options.root) : process.cwd();
|
|
5328
5863
|
try {
|
|
5329
5864
|
if (options.root) {
|
|
5330
5865
|
try {
|
|
5331
|
-
const stats = await
|
|
5866
|
+
const stats = await fs18.stat(rootDir);
|
|
5332
5867
|
if (!stats.isDirectory()) {
|
|
5333
|
-
console.error(
|
|
5868
|
+
console.error(chalk7.red(`Error: --root path is not a directory: ${rootDir}`));
|
|
5334
5869
|
process.exit(1);
|
|
5335
5870
|
}
|
|
5336
5871
|
} catch (error) {
|
|
5337
5872
|
if (error.code === "ENOENT") {
|
|
5338
|
-
console.error(
|
|
5873
|
+
console.error(chalk7.red(`Error: --root directory does not exist: ${rootDir}`));
|
|
5339
5874
|
} else if (error.code === "EACCES") {
|
|
5340
|
-
console.error(
|
|
5875
|
+
console.error(chalk7.red(`Error: --root directory is not accessible: ${rootDir}`));
|
|
5341
5876
|
} else {
|
|
5342
|
-
console.error(
|
|
5343
|
-
console.error(
|
|
5877
|
+
console.error(chalk7.red(`Error: Failed to access --root directory: ${rootDir}`));
|
|
5878
|
+
console.error(chalk7.dim(error.message));
|
|
5344
5879
|
}
|
|
5345
5880
|
process.exit(1);
|
|
5346
5881
|
}
|
|
5347
5882
|
}
|
|
5348
5883
|
showBanner();
|
|
5349
|
-
console.error(
|
|
5884
|
+
console.error(chalk7.bold("Starting MCP server...\n"));
|
|
5350
5885
|
if (options.root) {
|
|
5351
|
-
console.error(
|
|
5886
|
+
console.error(chalk7.dim(`Serving from: ${rootDir}
|
|
5352
5887
|
`));
|
|
5353
5888
|
}
|
|
5354
5889
|
if (options.watch) {
|
|
5355
|
-
console.error(
|
|
5356
|
-
console.error(
|
|
5890
|
+
console.error(chalk7.yellow("\u26A0\uFE0F --watch flag is deprecated (file watching is now default)"));
|
|
5891
|
+
console.error(chalk7.dim(" Use --no-watch to disable file watching\n"));
|
|
5357
5892
|
}
|
|
5358
5893
|
const watch = options.noWatch ? false : options.watch ? true : void 0;
|
|
5359
5894
|
await startMCPServer({
|
|
@@ -5362,7 +5897,7 @@ async function serveCommand(options) {
|
|
|
5362
5897
|
watch
|
|
5363
5898
|
});
|
|
5364
5899
|
} catch (error) {
|
|
5365
|
-
console.error(
|
|
5900
|
+
console.error(chalk7.red("Failed to start MCP server:"), error);
|
|
5366
5901
|
process.exit(1);
|
|
5367
5902
|
}
|
|
5368
5903
|
}
|