@liendev/lien 0.10.0 → 0.12.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 +67 -0
- package/dist/index.js +1672 -773
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/constants.ts
|
|
13
|
-
var DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_OVERLAP, DEFAULT_CONCURRENCY, DEFAULT_EMBEDDING_BATCH_SIZE, EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, DEFAULT_PORT, VERSION_CHECK_INTERVAL_MS, DEFAULT_GIT_POLL_INTERVAL_MS, DEFAULT_DEBOUNCE_MS, CURRENT_CONFIG_VERSION;
|
|
13
|
+
var DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_OVERLAP, DEFAULT_CONCURRENCY, DEFAULT_EMBEDDING_BATCH_SIZE, EMBEDDING_MICRO_BATCH_SIZE, VECTOR_DB_MAX_BATCH_SIZE, VECTOR_DB_MIN_BATCH_SIZE, EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, DEFAULT_PORT, VERSION_CHECK_INTERVAL_MS, DEFAULT_GIT_POLL_INTERVAL_MS, DEFAULT_DEBOUNCE_MS, CURRENT_CONFIG_VERSION, INDEX_FORMAT_VERSION;
|
|
14
14
|
var init_constants = __esm({
|
|
15
15
|
"src/constants.ts"() {
|
|
16
16
|
"use strict";
|
|
@@ -18,6 +18,9 @@ var init_constants = __esm({
|
|
|
18
18
|
DEFAULT_CHUNK_OVERLAP = 10;
|
|
19
19
|
DEFAULT_CONCURRENCY = 4;
|
|
20
20
|
DEFAULT_EMBEDDING_BATCH_SIZE = 50;
|
|
21
|
+
EMBEDDING_MICRO_BATCH_SIZE = 10;
|
|
22
|
+
VECTOR_DB_MAX_BATCH_SIZE = 1e3;
|
|
23
|
+
VECTOR_DB_MIN_BATCH_SIZE = 10;
|
|
21
24
|
EMBEDDING_DIMENSIONS = 384;
|
|
22
25
|
DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
23
26
|
DEFAULT_PORT = 7133;
|
|
@@ -25,6 +28,7 @@ var init_constants = __esm({
|
|
|
25
28
|
DEFAULT_GIT_POLL_INTERVAL_MS = 1e4;
|
|
26
29
|
DEFAULT_DEBOUNCE_MS = 1e3;
|
|
27
30
|
CURRENT_CONFIG_VERSION = "0.3.0";
|
|
31
|
+
INDEX_FORMAT_VERSION = 1;
|
|
28
32
|
}
|
|
29
33
|
});
|
|
30
34
|
|
|
@@ -58,8 +62,8 @@ var init_schema = __esm({
|
|
|
58
62
|
pollIntervalMs: DEFAULT_GIT_POLL_INTERVAL_MS
|
|
59
63
|
},
|
|
60
64
|
fileWatching: {
|
|
61
|
-
enabled:
|
|
62
|
-
//
|
|
65
|
+
enabled: true,
|
|
66
|
+
// Enabled by default (fast with incremental indexing!)
|
|
63
67
|
debounceMs: DEFAULT_DEBOUNCE_MS
|
|
64
68
|
},
|
|
65
69
|
frameworks: []
|
|
@@ -116,75 +120,6 @@ var init_merge = __esm({
|
|
|
116
120
|
}
|
|
117
121
|
});
|
|
118
122
|
|
|
119
|
-
// src/utils/banner.ts
|
|
120
|
-
var banner_exports = {};
|
|
121
|
-
__export(banner_exports, {
|
|
122
|
-
showBanner: () => showBanner,
|
|
123
|
-
showCompactBanner: () => showCompactBanner
|
|
124
|
-
});
|
|
125
|
-
import figlet from "figlet";
|
|
126
|
-
import chalk from "chalk";
|
|
127
|
-
import { createRequire } from "module";
|
|
128
|
-
import { fileURLToPath } from "url";
|
|
129
|
-
import { dirname, join } from "path";
|
|
130
|
-
function wrapInBox(text, footer, padding = 1) {
|
|
131
|
-
const lines = text.split("\n").filter((line) => line.trim().length > 0);
|
|
132
|
-
const maxLength = Math.max(...lines.map((line) => line.length));
|
|
133
|
-
const horizontalBorder = "\u2500".repeat(maxLength + padding * 2);
|
|
134
|
-
const top = `\u250C${horizontalBorder}\u2510`;
|
|
135
|
-
const bottom = `\u2514${horizontalBorder}\u2518`;
|
|
136
|
-
const separator = `\u251C${horizontalBorder}\u2524`;
|
|
137
|
-
const paddedLines = lines.map((line) => {
|
|
138
|
-
const padRight = " ".repeat(maxLength - line.length + padding);
|
|
139
|
-
const padLeft = " ".repeat(padding);
|
|
140
|
-
return `\u2502${padLeft}${line}${padRight}\u2502`;
|
|
141
|
-
});
|
|
142
|
-
const totalPad = maxLength - footer.length;
|
|
143
|
-
const leftPad = Math.floor(totalPad / 2);
|
|
144
|
-
const rightPad = totalPad - leftPad;
|
|
145
|
-
const centeredFooter = " ".repeat(leftPad) + footer + " ".repeat(rightPad);
|
|
146
|
-
const paddedFooter = `\u2502${" ".repeat(padding)}${centeredFooter}${" ".repeat(padding)}\u2502`;
|
|
147
|
-
return [top, ...paddedLines, separator, paddedFooter, bottom].join("\n");
|
|
148
|
-
}
|
|
149
|
-
function showBanner() {
|
|
150
|
-
const banner = figlet.textSync("LIEN", {
|
|
151
|
-
font: "ANSI Shadow",
|
|
152
|
-
horizontalLayout: "fitted",
|
|
153
|
-
verticalLayout: "fitted"
|
|
154
|
-
});
|
|
155
|
-
const footer = `${PACKAGE_NAME} - v${VERSION}`;
|
|
156
|
-
const boxedBanner = wrapInBox(banner.trim(), footer);
|
|
157
|
-
console.error(chalk.cyan(boxedBanner));
|
|
158
|
-
console.error();
|
|
159
|
-
}
|
|
160
|
-
function showCompactBanner() {
|
|
161
|
-
const banner = figlet.textSync("LIEN", {
|
|
162
|
-
font: "ANSI Shadow",
|
|
163
|
-
horizontalLayout: "fitted",
|
|
164
|
-
verticalLayout: "fitted"
|
|
165
|
-
});
|
|
166
|
-
const footer = `${PACKAGE_NAME} - v${VERSION}`;
|
|
167
|
-
const boxedBanner = wrapInBox(banner.trim(), footer);
|
|
168
|
-
console.log(chalk.cyan(boxedBanner));
|
|
169
|
-
console.log();
|
|
170
|
-
}
|
|
171
|
-
var __filename, __dirname, require2, packageJson, PACKAGE_NAME, VERSION;
|
|
172
|
-
var init_banner = __esm({
|
|
173
|
-
"src/utils/banner.ts"() {
|
|
174
|
-
"use strict";
|
|
175
|
-
__filename = fileURLToPath(import.meta.url);
|
|
176
|
-
__dirname = dirname(__filename);
|
|
177
|
-
require2 = createRequire(import.meta.url);
|
|
178
|
-
try {
|
|
179
|
-
packageJson = require2(join(__dirname, "../package.json"));
|
|
180
|
-
} catch {
|
|
181
|
-
packageJson = require2(join(__dirname, "../../package.json"));
|
|
182
|
-
}
|
|
183
|
-
PACKAGE_NAME = packageJson.name;
|
|
184
|
-
VERSION = packageJson.version;
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
123
|
// src/config/migration.ts
|
|
189
124
|
function needsMigration(config) {
|
|
190
125
|
if (!config) {
|
|
@@ -275,13 +210,20 @@ var init_migration = __esm({
|
|
|
275
210
|
}
|
|
276
211
|
});
|
|
277
212
|
|
|
213
|
+
// src/errors/codes.ts
|
|
214
|
+
var init_codes = __esm({
|
|
215
|
+
"src/errors/codes.ts"() {
|
|
216
|
+
"use strict";
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
278
220
|
// src/errors/index.ts
|
|
279
221
|
function wrapError(error, context, additionalContext) {
|
|
280
222
|
const message = error instanceof Error ? error.message : String(error);
|
|
281
223
|
const stack = error instanceof Error ? error.stack : void 0;
|
|
282
224
|
const wrappedError = new LienError(
|
|
283
225
|
`${context}: ${message}`,
|
|
284
|
-
"
|
|
226
|
+
"INTERNAL_ERROR" /* INTERNAL_ERROR */,
|
|
285
227
|
additionalContext
|
|
286
228
|
);
|
|
287
229
|
if (stack) {
|
|
@@ -296,32 +238,61 @@ var LienError, ConfigError, EmbeddingError, DatabaseError;
|
|
|
296
238
|
var init_errors = __esm({
|
|
297
239
|
"src/errors/index.ts"() {
|
|
298
240
|
"use strict";
|
|
241
|
+
init_codes();
|
|
242
|
+
init_codes();
|
|
299
243
|
LienError = class extends Error {
|
|
300
|
-
constructor(message, code, context) {
|
|
244
|
+
constructor(message, code, context, severity = "medium", recoverable = true, retryable = false) {
|
|
301
245
|
super(message);
|
|
302
246
|
this.code = code;
|
|
303
247
|
this.context = context;
|
|
248
|
+
this.severity = severity;
|
|
249
|
+
this.recoverable = recoverable;
|
|
250
|
+
this.retryable = retryable;
|
|
304
251
|
this.name = "LienError";
|
|
305
252
|
if (Error.captureStackTrace) {
|
|
306
253
|
Error.captureStackTrace(this, this.constructor);
|
|
307
254
|
}
|
|
308
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Serialize error to JSON for MCP responses
|
|
258
|
+
*/
|
|
259
|
+
toJSON() {
|
|
260
|
+
return {
|
|
261
|
+
error: this.message,
|
|
262
|
+
code: this.code,
|
|
263
|
+
severity: this.severity,
|
|
264
|
+
recoverable: this.recoverable,
|
|
265
|
+
context: this.context
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if this error is retryable
|
|
270
|
+
*/
|
|
271
|
+
isRetryable() {
|
|
272
|
+
return this.retryable;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if this error is recoverable
|
|
276
|
+
*/
|
|
277
|
+
isRecoverable() {
|
|
278
|
+
return this.recoverable;
|
|
279
|
+
}
|
|
309
280
|
};
|
|
310
281
|
ConfigError = class extends LienError {
|
|
311
282
|
constructor(message, context) {
|
|
312
|
-
super(message, "
|
|
283
|
+
super(message, "CONFIG_INVALID" /* CONFIG_INVALID */, context, "medium", true, false);
|
|
313
284
|
this.name = "ConfigError";
|
|
314
285
|
}
|
|
315
286
|
};
|
|
316
287
|
EmbeddingError = class extends LienError {
|
|
317
288
|
constructor(message, context) {
|
|
318
|
-
super(message, "
|
|
289
|
+
super(message, "EMBEDDING_GENERATION_FAILED" /* EMBEDDING_GENERATION_FAILED */, context, "high", true, true);
|
|
319
290
|
this.name = "EmbeddingError";
|
|
320
291
|
}
|
|
321
292
|
};
|
|
322
293
|
DatabaseError = class extends LienError {
|
|
323
294
|
constructor(message, context) {
|
|
324
|
-
super(message, "
|
|
295
|
+
super(message, "INTERNAL_ERROR" /* INTERNAL_ERROR */, context, "high", true, true);
|
|
325
296
|
this.name = "DatabaseError";
|
|
326
297
|
}
|
|
327
298
|
};
|
|
@@ -772,6 +743,115 @@ ${validation.errors.join("\n")}`,
|
|
|
772
743
|
}
|
|
773
744
|
});
|
|
774
745
|
|
|
746
|
+
// src/git/utils.ts
|
|
747
|
+
var utils_exports = {};
|
|
748
|
+
__export(utils_exports, {
|
|
749
|
+
getChangedFiles: () => getChangedFiles,
|
|
750
|
+
getChangedFilesBetweenCommits: () => getChangedFilesBetweenCommits,
|
|
751
|
+
getChangedFilesInCommit: () => getChangedFilesInCommit,
|
|
752
|
+
getCurrentBranch: () => getCurrentBranch,
|
|
753
|
+
getCurrentCommit: () => getCurrentCommit,
|
|
754
|
+
isGitAvailable: () => isGitAvailable,
|
|
755
|
+
isGitRepo: () => isGitRepo
|
|
756
|
+
});
|
|
757
|
+
import { exec } from "child_process";
|
|
758
|
+
import { promisify } from "util";
|
|
759
|
+
import fs7 from "fs/promises";
|
|
760
|
+
import path7 from "path";
|
|
761
|
+
async function isGitRepo(rootDir) {
|
|
762
|
+
try {
|
|
763
|
+
const gitDir = path7.join(rootDir, ".git");
|
|
764
|
+
await fs7.access(gitDir);
|
|
765
|
+
return true;
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async function getCurrentBranch(rootDir) {
|
|
771
|
+
try {
|
|
772
|
+
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
|
|
773
|
+
cwd: rootDir,
|
|
774
|
+
timeout: 5e3
|
|
775
|
+
// 5 second timeout
|
|
776
|
+
});
|
|
777
|
+
return stdout.trim();
|
|
778
|
+
} catch (error) {
|
|
779
|
+
throw new Error(`Failed to get current branch: ${error}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async function getCurrentCommit(rootDir) {
|
|
783
|
+
try {
|
|
784
|
+
const { stdout } = await execAsync("git rev-parse HEAD", {
|
|
785
|
+
cwd: rootDir,
|
|
786
|
+
timeout: 5e3
|
|
787
|
+
});
|
|
788
|
+
return stdout.trim();
|
|
789
|
+
} catch (error) {
|
|
790
|
+
throw new Error(`Failed to get current commit: ${error}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async function getChangedFiles(rootDir, fromRef, toRef) {
|
|
794
|
+
try {
|
|
795
|
+
const { stdout } = await execAsync(
|
|
796
|
+
`git diff --name-only ${fromRef}...${toRef}`,
|
|
797
|
+
{
|
|
798
|
+
cwd: rootDir,
|
|
799
|
+
timeout: 1e4
|
|
800
|
+
// 10 second timeout for diffs
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path7.join(rootDir, file));
|
|
804
|
+
return files;
|
|
805
|
+
} catch (error) {
|
|
806
|
+
throw new Error(`Failed to get changed files: ${error}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async function getChangedFilesInCommit(rootDir, commitSha) {
|
|
810
|
+
try {
|
|
811
|
+
const { stdout } = await execAsync(
|
|
812
|
+
`git diff-tree --no-commit-id --name-only -r ${commitSha}`,
|
|
813
|
+
{
|
|
814
|
+
cwd: rootDir,
|
|
815
|
+
timeout: 1e4
|
|
816
|
+
}
|
|
817
|
+
);
|
|
818
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path7.join(rootDir, file));
|
|
819
|
+
return files;
|
|
820
|
+
} catch (error) {
|
|
821
|
+
throw new Error(`Failed to get changed files in commit: ${error}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
async function getChangedFilesBetweenCommits(rootDir, fromCommit, toCommit) {
|
|
825
|
+
try {
|
|
826
|
+
const { stdout } = await execAsync(
|
|
827
|
+
`git diff --name-only ${fromCommit} ${toCommit}`,
|
|
828
|
+
{
|
|
829
|
+
cwd: rootDir,
|
|
830
|
+
timeout: 1e4
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path7.join(rootDir, file));
|
|
834
|
+
return files;
|
|
835
|
+
} catch (error) {
|
|
836
|
+
throw new Error(`Failed to get changed files between commits: ${error}`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
async function isGitAvailable() {
|
|
840
|
+
try {
|
|
841
|
+
await execAsync("git --version", { timeout: 3e3 });
|
|
842
|
+
return true;
|
|
843
|
+
} catch {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
var execAsync;
|
|
848
|
+
var init_utils = __esm({
|
|
849
|
+
"src/git/utils.ts"() {
|
|
850
|
+
"use strict";
|
|
851
|
+
execAsync = promisify(exec);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
|
|
775
855
|
// src/vectordb/version.ts
|
|
776
856
|
import fs8 from "fs/promises";
|
|
777
857
|
import path8 from "path";
|
|
@@ -1530,6 +1610,7 @@ var init_lancedb = __esm({
|
|
|
1530
1610
|
init_errors();
|
|
1531
1611
|
init_relevance();
|
|
1532
1612
|
init_intent_classifier();
|
|
1613
|
+
init_constants();
|
|
1533
1614
|
VectorDB = class _VectorDB {
|
|
1534
1615
|
db = null;
|
|
1535
1616
|
table = null;
|
|
@@ -1575,27 +1656,76 @@ var init_lancedb = __esm({
|
|
|
1575
1656
|
contentsLength: contents.length
|
|
1576
1657
|
});
|
|
1577
1658
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
// Ensure arrays have at least empty string for Arrow type inference
|
|
1588
|
-
functionNames: metadatas[i].symbols?.functions && metadatas[i].symbols.functions.length > 0 ? metadatas[i].symbols.functions : [""],
|
|
1589
|
-
classNames: metadatas[i].symbols?.classes && metadatas[i].symbols.classes.length > 0 ? metadatas[i].symbols.classes : [""],
|
|
1590
|
-
interfaceNames: metadatas[i].symbols?.interfaces && metadatas[i].symbols.interfaces.length > 0 ? metadatas[i].symbols.interfaces : [""]
|
|
1591
|
-
}));
|
|
1592
|
-
if (!this.table) {
|
|
1593
|
-
this.table = await this.db.createTable(this.tableName, records);
|
|
1594
|
-
} else {
|
|
1595
|
-
await this.table.add(records);
|
|
1659
|
+
if (vectors.length === 0) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (vectors.length > VECTOR_DB_MAX_BATCH_SIZE) {
|
|
1663
|
+
for (let i = 0; i < vectors.length; i += VECTOR_DB_MAX_BATCH_SIZE) {
|
|
1664
|
+
const batchVectors = vectors.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
1665
|
+
const batchMetadata = metadatas.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
1666
|
+
const batchContents = contents.slice(i, Math.min(i + VECTOR_DB_MAX_BATCH_SIZE, vectors.length));
|
|
1667
|
+
await this._insertBatchInternal(batchVectors, batchMetadata, batchContents);
|
|
1596
1668
|
}
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1669
|
+
} else {
|
|
1670
|
+
await this._insertBatchInternal(vectors, metadatas, contents);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Internal method to insert a single batch with iterative retry logic.
|
|
1675
|
+
* Uses a queue-based approach to avoid deep recursion on large batch failures.
|
|
1676
|
+
*/
|
|
1677
|
+
async _insertBatchInternal(vectors, metadatas, contents) {
|
|
1678
|
+
const queue = [{ vectors, metadatas, contents }];
|
|
1679
|
+
const failedRecords = [];
|
|
1680
|
+
while (queue.length > 0) {
|
|
1681
|
+
const batch = queue.shift();
|
|
1682
|
+
try {
|
|
1683
|
+
const records = batch.vectors.map((vector, i) => ({
|
|
1684
|
+
vector: Array.from(vector),
|
|
1685
|
+
content: batch.contents[i],
|
|
1686
|
+
file: batch.metadatas[i].file,
|
|
1687
|
+
startLine: batch.metadatas[i].startLine,
|
|
1688
|
+
endLine: batch.metadatas[i].endLine,
|
|
1689
|
+
type: batch.metadatas[i].type,
|
|
1690
|
+
language: batch.metadatas[i].language,
|
|
1691
|
+
// Ensure arrays have at least empty string for Arrow type inference
|
|
1692
|
+
functionNames: batch.metadatas[i].symbols?.functions && batch.metadatas[i].symbols.functions.length > 0 ? batch.metadatas[i].symbols.functions : [""],
|
|
1693
|
+
classNames: batch.metadatas[i].symbols?.classes && batch.metadatas[i].symbols.classes.length > 0 ? batch.metadatas[i].symbols.classes : [""],
|
|
1694
|
+
interfaceNames: batch.metadatas[i].symbols?.interfaces && batch.metadatas[i].symbols.interfaces.length > 0 ? batch.metadatas[i].symbols.interfaces : [""]
|
|
1695
|
+
}));
|
|
1696
|
+
if (!this.table) {
|
|
1697
|
+
this.table = await this.db.createTable(this.tableName, records);
|
|
1698
|
+
} else {
|
|
1699
|
+
await this.table.add(records);
|
|
1700
|
+
}
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
if (batch.vectors.length > VECTOR_DB_MIN_BATCH_SIZE) {
|
|
1703
|
+
const half = Math.floor(batch.vectors.length / 2);
|
|
1704
|
+
queue.push({
|
|
1705
|
+
vectors: batch.vectors.slice(0, half),
|
|
1706
|
+
metadatas: batch.metadatas.slice(0, half),
|
|
1707
|
+
contents: batch.contents.slice(0, half)
|
|
1708
|
+
});
|
|
1709
|
+
queue.push({
|
|
1710
|
+
vectors: batch.vectors.slice(half),
|
|
1711
|
+
metadatas: batch.metadatas.slice(half),
|
|
1712
|
+
contents: batch.contents.slice(half)
|
|
1713
|
+
});
|
|
1714
|
+
} else {
|
|
1715
|
+
failedRecords.push(batch);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (failedRecords.length > 0) {
|
|
1720
|
+
const totalFailed = failedRecords.reduce((sum, batch) => sum + batch.vectors.length, 0);
|
|
1721
|
+
throw new DatabaseError(
|
|
1722
|
+
`Failed to insert ${totalFailed} record(s) after retry attempts`,
|
|
1723
|
+
{
|
|
1724
|
+
failedBatches: failedRecords.length,
|
|
1725
|
+
totalRecords: totalFailed,
|
|
1726
|
+
sampleFile: failedRecords[0].metadatas[0].file
|
|
1727
|
+
}
|
|
1728
|
+
);
|
|
1599
1729
|
}
|
|
1600
1730
|
}
|
|
1601
1731
|
async search(queryVector, limit = 5, query) {
|
|
@@ -1893,78 +2023,1018 @@ var init_lancedb = __esm({
|
|
|
1893
2023
|
}
|
|
1894
2024
|
});
|
|
1895
2025
|
|
|
1896
|
-
// src/
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2026
|
+
// src/utils/version.ts
|
|
2027
|
+
import { createRequire as createRequire2 } from "module";
|
|
2028
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2029
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
2030
|
+
function getPackageVersion() {
|
|
2031
|
+
return packageJson2.version;
|
|
2032
|
+
}
|
|
2033
|
+
var __filename3, __dirname3, require3, packageJson2;
|
|
2034
|
+
var init_version2 = __esm({
|
|
2035
|
+
"src/utils/version.ts"() {
|
|
2036
|
+
"use strict";
|
|
2037
|
+
__filename3 = fileURLToPath3(import.meta.url);
|
|
2038
|
+
__dirname3 = dirname2(__filename3);
|
|
2039
|
+
require3 = createRequire2(import.meta.url);
|
|
2040
|
+
try {
|
|
2041
|
+
packageJson2 = require3(join2(__dirname3, "../package.json"));
|
|
2042
|
+
} catch {
|
|
2043
|
+
try {
|
|
2044
|
+
packageJson2 = require3(join2(__dirname3, "../../package.json"));
|
|
2045
|
+
} catch {
|
|
2046
|
+
console.warn("[Lien] Warning: Could not load package.json, using fallback version");
|
|
2047
|
+
packageJson2 = { version: "0.0.0-unknown" };
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
// src/indexer/manifest.ts
|
|
2054
|
+
var manifest_exports = {};
|
|
2055
|
+
__export(manifest_exports, {
|
|
2056
|
+
ManifestManager: () => ManifestManager
|
|
1900
2057
|
});
|
|
1901
2058
|
import fs11 from "fs/promises";
|
|
1902
|
-
import
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2059
|
+
import path12 from "path";
|
|
2060
|
+
var MANIFEST_FILE, ManifestManager;
|
|
2061
|
+
var init_manifest = __esm({
|
|
2062
|
+
"src/indexer/manifest.ts"() {
|
|
2063
|
+
"use strict";
|
|
2064
|
+
init_constants();
|
|
2065
|
+
init_version2();
|
|
2066
|
+
MANIFEST_FILE = "manifest.json";
|
|
2067
|
+
ManifestManager = class {
|
|
2068
|
+
manifestPath;
|
|
2069
|
+
indexPath;
|
|
2070
|
+
/**
|
|
2071
|
+
* Promise-based lock to prevent race conditions during concurrent updates.
|
|
2072
|
+
* Ensures read-modify-write operations are atomic.
|
|
2073
|
+
*/
|
|
2074
|
+
updateLock = Promise.resolve();
|
|
2075
|
+
/**
|
|
2076
|
+
* Creates a new ManifestManager
|
|
2077
|
+
* @param indexPath - Path to the index directory (same as VectorDB path)
|
|
2078
|
+
*/
|
|
2079
|
+
constructor(indexPath) {
|
|
2080
|
+
this.indexPath = indexPath;
|
|
2081
|
+
this.manifestPath = path12.join(indexPath, MANIFEST_FILE);
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Loads the manifest from disk.
|
|
2085
|
+
* Returns null if:
|
|
2086
|
+
* - Manifest doesn't exist (first run)
|
|
2087
|
+
* - Manifest is corrupt
|
|
2088
|
+
* - Format version is incompatible (triggers full reindex)
|
|
2089
|
+
*
|
|
2090
|
+
* @returns Loaded manifest or null
|
|
2091
|
+
*/
|
|
2092
|
+
async load() {
|
|
2093
|
+
try {
|
|
2094
|
+
const content = await fs11.readFile(this.manifestPath, "utf-8");
|
|
2095
|
+
const manifest = JSON.parse(content);
|
|
2096
|
+
if (manifest.formatVersion !== INDEX_FORMAT_VERSION) {
|
|
2097
|
+
console.error(
|
|
2098
|
+
`[Lien] Index format v${manifest.formatVersion} is incompatible with current v${INDEX_FORMAT_VERSION}`
|
|
2099
|
+
);
|
|
2100
|
+
console.error(`[Lien] Full reindex required after Lien upgrade`);
|
|
2101
|
+
await this.clear();
|
|
2102
|
+
return null;
|
|
2103
|
+
}
|
|
2104
|
+
return manifest;
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
if (error.code === "ENOENT") {
|
|
2107
|
+
return null;
|
|
2108
|
+
}
|
|
2109
|
+
console.error(`[Lien] Warning: Failed to load manifest: ${error}`);
|
|
2110
|
+
return null;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Saves the manifest to disk.
|
|
2115
|
+
* Always saves with current format and package versions.
|
|
2116
|
+
*
|
|
2117
|
+
* @param manifest - Manifest to save
|
|
2118
|
+
*/
|
|
2119
|
+
async save(manifest) {
|
|
2120
|
+
try {
|
|
2121
|
+
await fs11.mkdir(this.indexPath, { recursive: true });
|
|
2122
|
+
const manifestToSave = {
|
|
2123
|
+
...manifest,
|
|
2124
|
+
formatVersion: INDEX_FORMAT_VERSION,
|
|
2125
|
+
lienVersion: getPackageVersion(),
|
|
2126
|
+
lastIndexed: Date.now()
|
|
2127
|
+
};
|
|
2128
|
+
const content = JSON.stringify(manifestToSave, null, 2);
|
|
2129
|
+
await fs11.writeFile(this.manifestPath, content, "utf-8");
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
console.error(`[Lien] Warning: Failed to save manifest: ${error}`);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Adds or updates a file entry in the manifest.
|
|
2136
|
+
* Protected by lock to prevent race conditions during concurrent updates.
|
|
2137
|
+
*
|
|
2138
|
+
* @param filepath - Path to the file
|
|
2139
|
+
* @param entry - File entry metadata
|
|
2140
|
+
*/
|
|
2141
|
+
async updateFile(filepath, entry) {
|
|
2142
|
+
this.updateLock = this.updateLock.then(async () => {
|
|
2143
|
+
const manifest = await this.load() || this.createEmpty();
|
|
2144
|
+
manifest.files[filepath] = entry;
|
|
2145
|
+
await this.save(manifest);
|
|
2146
|
+
}).catch((error) => {
|
|
2147
|
+
console.error(`[Lien] Failed to update manifest for ${filepath}: ${error}`);
|
|
2148
|
+
return void 0;
|
|
2149
|
+
});
|
|
2150
|
+
await this.updateLock;
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Removes a file entry from the manifest.
|
|
2154
|
+
* Protected by lock to prevent race conditions during concurrent updates.
|
|
2155
|
+
*
|
|
2156
|
+
* Note: If the manifest doesn't exist, this is a no-op (not an error).
|
|
2157
|
+
* This can happen legitimately after clearing the index or on fresh installs.
|
|
2158
|
+
*
|
|
2159
|
+
* @param filepath - Path to the file to remove
|
|
2160
|
+
*/
|
|
2161
|
+
async removeFile(filepath) {
|
|
2162
|
+
this.updateLock = this.updateLock.then(async () => {
|
|
2163
|
+
const manifest = await this.load();
|
|
2164
|
+
if (!manifest) {
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
delete manifest.files[filepath];
|
|
2168
|
+
await this.save(manifest);
|
|
2169
|
+
}).catch((error) => {
|
|
2170
|
+
console.error(`[Lien] Failed to remove manifest entry for ${filepath}: ${error}`);
|
|
2171
|
+
return void 0;
|
|
2172
|
+
});
|
|
2173
|
+
await this.updateLock;
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Updates multiple files at once (more efficient than individual updates).
|
|
2177
|
+
* Protected by lock to prevent race conditions during concurrent updates.
|
|
2178
|
+
*
|
|
2179
|
+
* @param entries - Array of file entries to update
|
|
2180
|
+
*/
|
|
2181
|
+
async updateFiles(entries) {
|
|
2182
|
+
this.updateLock = this.updateLock.then(async () => {
|
|
2183
|
+
const manifest = await this.load() || this.createEmpty();
|
|
2184
|
+
for (const entry of entries) {
|
|
2185
|
+
manifest.files[entry.filepath] = entry;
|
|
2186
|
+
}
|
|
2187
|
+
await this.save(manifest);
|
|
2188
|
+
}).catch((error) => {
|
|
2189
|
+
console.error(`[Lien] Failed to update manifest for ${entries.length} files: ${error}`);
|
|
2190
|
+
return void 0;
|
|
2191
|
+
});
|
|
2192
|
+
await this.updateLock;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Updates the git state in the manifest.
|
|
2196
|
+
* Protected by lock to prevent race conditions during concurrent updates.
|
|
2197
|
+
*
|
|
2198
|
+
* @param gitState - Current git state
|
|
2199
|
+
*/
|
|
2200
|
+
async updateGitState(gitState) {
|
|
2201
|
+
this.updateLock = this.updateLock.then(async () => {
|
|
2202
|
+
const manifest = await this.load() || this.createEmpty();
|
|
2203
|
+
manifest.gitState = gitState;
|
|
2204
|
+
await this.save(manifest);
|
|
2205
|
+
}).catch((error) => {
|
|
2206
|
+
console.error(`[Lien] Failed to update git state in manifest: ${error}`);
|
|
2207
|
+
return void 0;
|
|
2208
|
+
});
|
|
2209
|
+
await this.updateLock;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Gets the list of files currently in the manifest
|
|
2213
|
+
*
|
|
2214
|
+
* @returns Array of filepaths
|
|
2215
|
+
*/
|
|
2216
|
+
async getIndexedFiles() {
|
|
2217
|
+
const manifest = await this.load();
|
|
2218
|
+
if (!manifest) return [];
|
|
2219
|
+
return Object.keys(manifest.files);
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* Detects which files have changed based on mtime comparison
|
|
2223
|
+
*
|
|
2224
|
+
* @param currentFiles - Map of current files with their mtimes
|
|
2225
|
+
* @returns Array of filepaths that have changed
|
|
2226
|
+
*/
|
|
2227
|
+
async getChangedFiles(currentFiles) {
|
|
2228
|
+
const manifest = await this.load();
|
|
2229
|
+
if (!manifest) {
|
|
2230
|
+
return Array.from(currentFiles.keys());
|
|
2231
|
+
}
|
|
2232
|
+
const changedFiles = [];
|
|
2233
|
+
for (const [filepath, mtime] of currentFiles) {
|
|
2234
|
+
const entry = manifest.files[filepath];
|
|
2235
|
+
if (!entry) {
|
|
2236
|
+
changedFiles.push(filepath);
|
|
2237
|
+
} else if (entry.lastModified < mtime) {
|
|
2238
|
+
changedFiles.push(filepath);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
return changedFiles;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Gets files that are in the manifest but not in the current file list
|
|
2245
|
+
* (i.e., deleted files)
|
|
2246
|
+
*
|
|
2247
|
+
* @param currentFiles - Set of current file paths
|
|
2248
|
+
* @returns Array of deleted file paths
|
|
2249
|
+
*/
|
|
2250
|
+
async getDeletedFiles(currentFiles) {
|
|
2251
|
+
const manifest = await this.load();
|
|
2252
|
+
if (!manifest) return [];
|
|
2253
|
+
const deletedFiles = [];
|
|
2254
|
+
for (const filepath of Object.keys(manifest.files)) {
|
|
2255
|
+
if (!currentFiles.has(filepath)) {
|
|
2256
|
+
deletedFiles.push(filepath);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
return deletedFiles;
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Clears the manifest file
|
|
2263
|
+
*/
|
|
2264
|
+
async clear() {
|
|
2265
|
+
try {
|
|
2266
|
+
await fs11.unlink(this.manifestPath);
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
if (error.code !== "ENOENT") {
|
|
2269
|
+
console.error(`[Lien] Warning: Failed to clear manifest: ${error}`);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Creates an empty manifest with current version information
|
|
2275
|
+
*
|
|
2276
|
+
* @returns Empty manifest
|
|
2277
|
+
*/
|
|
2278
|
+
createEmpty() {
|
|
2279
|
+
return {
|
|
2280
|
+
formatVersion: INDEX_FORMAT_VERSION,
|
|
2281
|
+
lienVersion: getPackageVersion(),
|
|
2282
|
+
lastIndexed: Date.now(),
|
|
2283
|
+
files: {}
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
// src/git/tracker.ts
|
|
2291
|
+
var tracker_exports = {};
|
|
2292
|
+
__export(tracker_exports, {
|
|
2293
|
+
GitStateTracker: () => GitStateTracker
|
|
2294
|
+
});
|
|
2295
|
+
import fs12 from "fs/promises";
|
|
2296
|
+
import path13 from "path";
|
|
2297
|
+
var GitStateTracker;
|
|
2298
|
+
var init_tracker = __esm({
|
|
2299
|
+
"src/git/tracker.ts"() {
|
|
2300
|
+
"use strict";
|
|
2301
|
+
init_utils();
|
|
2302
|
+
GitStateTracker = class {
|
|
2303
|
+
stateFile;
|
|
2304
|
+
rootDir;
|
|
2305
|
+
currentState = null;
|
|
2306
|
+
constructor(rootDir, indexPath) {
|
|
2307
|
+
this.rootDir = rootDir;
|
|
2308
|
+
this.stateFile = path13.join(indexPath, ".git-state.json");
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Loads the last known git state from disk.
|
|
2312
|
+
* Returns null if no state file exists (first run).
|
|
2313
|
+
*/
|
|
2314
|
+
async loadState() {
|
|
2315
|
+
try {
|
|
2316
|
+
const content = await fs12.readFile(this.stateFile, "utf-8");
|
|
2317
|
+
return JSON.parse(content);
|
|
2318
|
+
} catch {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Saves the current git state to disk.
|
|
2324
|
+
*/
|
|
2325
|
+
async saveState(state) {
|
|
2326
|
+
try {
|
|
2327
|
+
const content = JSON.stringify(state, null, 2);
|
|
2328
|
+
await fs12.writeFile(this.stateFile, content, "utf-8");
|
|
2329
|
+
} catch (error) {
|
|
2330
|
+
console.error(`[Lien] Warning: Failed to save git state: ${error}`);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Gets the current git state from the repository.
|
|
2335
|
+
*
|
|
2336
|
+
* @returns Current git state
|
|
2337
|
+
* @throws Error if git commands fail
|
|
2338
|
+
*/
|
|
2339
|
+
async getCurrentGitState() {
|
|
2340
|
+
const branch = await getCurrentBranch(this.rootDir);
|
|
2341
|
+
const commit = await getCurrentCommit(this.rootDir);
|
|
2342
|
+
return {
|
|
2343
|
+
branch,
|
|
2344
|
+
commit,
|
|
2345
|
+
timestamp: Date.now()
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* Initializes the tracker by loading saved state and checking current state.
|
|
2350
|
+
* Should be called once when MCP server starts.
|
|
2351
|
+
*
|
|
2352
|
+
* @returns Array of changed files if state changed, null if no changes or first run
|
|
2353
|
+
*/
|
|
2354
|
+
async initialize() {
|
|
2355
|
+
const isRepo = await isGitRepo(this.rootDir);
|
|
2356
|
+
if (!isRepo) {
|
|
2357
|
+
return null;
|
|
2358
|
+
}
|
|
2359
|
+
try {
|
|
2360
|
+
this.currentState = await this.getCurrentGitState();
|
|
2361
|
+
const previousState = await this.loadState();
|
|
2362
|
+
if (!previousState) {
|
|
2363
|
+
await this.saveState(this.currentState);
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
const branchChanged = previousState.branch !== this.currentState.branch;
|
|
2367
|
+
const commitChanged = previousState.commit !== this.currentState.commit;
|
|
2368
|
+
if (!branchChanged && !commitChanged) {
|
|
2369
|
+
return null;
|
|
2370
|
+
}
|
|
2371
|
+
let changedFiles = [];
|
|
2372
|
+
if (branchChanged) {
|
|
2373
|
+
try {
|
|
2374
|
+
changedFiles = await getChangedFiles(
|
|
2375
|
+
this.rootDir,
|
|
2376
|
+
previousState.branch,
|
|
2377
|
+
this.currentState.branch
|
|
2378
|
+
);
|
|
2379
|
+
} catch (error) {
|
|
2380
|
+
console.error(`[Lien] Branch diff failed, using commit diff: ${error}`);
|
|
2381
|
+
changedFiles = await getChangedFilesBetweenCommits(
|
|
2382
|
+
this.rootDir,
|
|
2383
|
+
previousState.commit,
|
|
2384
|
+
this.currentState.commit
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
} else if (commitChanged) {
|
|
2388
|
+
changedFiles = await getChangedFilesBetweenCommits(
|
|
2389
|
+
this.rootDir,
|
|
2390
|
+
previousState.commit,
|
|
2391
|
+
this.currentState.commit
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
await this.saveState(this.currentState);
|
|
2395
|
+
return changedFiles;
|
|
2396
|
+
} catch (error) {
|
|
2397
|
+
console.error(`[Lien] Failed to initialize git tracker: ${error}`);
|
|
2398
|
+
return null;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Checks for git state changes since last check.
|
|
2403
|
+
* This is called periodically by the MCP server.
|
|
2404
|
+
*
|
|
2405
|
+
* @returns Array of changed files if state changed, null if no changes
|
|
2406
|
+
*/
|
|
2407
|
+
async detectChanges() {
|
|
2408
|
+
const isRepo = await isGitRepo(this.rootDir);
|
|
2409
|
+
if (!isRepo) {
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
try {
|
|
2413
|
+
const newState = await this.getCurrentGitState();
|
|
2414
|
+
if (!this.currentState) {
|
|
2415
|
+
this.currentState = newState;
|
|
2416
|
+
await this.saveState(newState);
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
const branchChanged = this.currentState.branch !== newState.branch;
|
|
2420
|
+
const commitChanged = this.currentState.commit !== newState.commit;
|
|
2421
|
+
if (!branchChanged && !commitChanged) {
|
|
2422
|
+
return null;
|
|
2423
|
+
}
|
|
2424
|
+
let changedFiles = [];
|
|
2425
|
+
if (branchChanged) {
|
|
2426
|
+
try {
|
|
2427
|
+
changedFiles = await getChangedFiles(
|
|
2428
|
+
this.rootDir,
|
|
2429
|
+
this.currentState.branch,
|
|
2430
|
+
newState.branch
|
|
2431
|
+
);
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
console.error(`[Lien] Branch diff failed, using commit diff: ${error}`);
|
|
2434
|
+
changedFiles = await getChangedFilesBetweenCommits(
|
|
2435
|
+
this.rootDir,
|
|
2436
|
+
this.currentState.commit,
|
|
2437
|
+
newState.commit
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
} else if (commitChanged) {
|
|
2441
|
+
changedFiles = await getChangedFilesBetweenCommits(
|
|
2442
|
+
this.rootDir,
|
|
2443
|
+
this.currentState.commit,
|
|
2444
|
+
newState.commit
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
this.currentState = newState;
|
|
2448
|
+
await this.saveState(newState);
|
|
2449
|
+
return changedFiles;
|
|
2450
|
+
} catch (error) {
|
|
2451
|
+
console.error(`[Lien] Failed to detect git changes: ${error}`);
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Gets the current git state.
|
|
2457
|
+
* Useful for status display.
|
|
2458
|
+
*/
|
|
2459
|
+
getState() {
|
|
2460
|
+
return this.currentState;
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Manually updates the saved state.
|
|
2464
|
+
* Useful after manual reindexing to sync state.
|
|
2465
|
+
*/
|
|
2466
|
+
async updateState() {
|
|
2467
|
+
try {
|
|
2468
|
+
this.currentState = await this.getCurrentGitState();
|
|
2469
|
+
await this.saveState(this.currentState);
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
console.error(`[Lien] Failed to update git state: ${error}`);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
});
|
|
2477
|
+
|
|
2478
|
+
// src/indexer/change-detector.ts
|
|
2479
|
+
import fs13 from "fs/promises";
|
|
2480
|
+
async function detectChanges(rootDir, vectorDB, config) {
|
|
2481
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
2482
|
+
const savedManifest = await manifest.load();
|
|
2483
|
+
if (!savedManifest) {
|
|
2484
|
+
const allFiles = await getAllFiles(rootDir, config);
|
|
2485
|
+
return {
|
|
2486
|
+
added: allFiles,
|
|
2487
|
+
modified: [],
|
|
2488
|
+
deleted: [],
|
|
2489
|
+
reason: "full"
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
const gitAvailable = await isGitAvailable();
|
|
2493
|
+
const isRepo = await isGitRepo(rootDir);
|
|
2494
|
+
if (gitAvailable && isRepo && savedManifest.gitState) {
|
|
2495
|
+
const gitTracker = new GitStateTracker(rootDir, vectorDB.dbPath);
|
|
2496
|
+
await gitTracker.initialize();
|
|
2497
|
+
const currentState = gitTracker.getState();
|
|
2498
|
+
if (currentState && (currentState.branch !== savedManifest.gitState.branch || currentState.commit !== savedManifest.gitState.commit)) {
|
|
2499
|
+
try {
|
|
2500
|
+
const changedFilesPaths = await getChangedFiles(
|
|
2501
|
+
rootDir,
|
|
2502
|
+
savedManifest.gitState.commit,
|
|
2503
|
+
currentState.commit
|
|
2504
|
+
);
|
|
2505
|
+
const changedFilesSet = new Set(changedFilesPaths);
|
|
2506
|
+
const allFiles = await getAllFiles(rootDir, config);
|
|
2507
|
+
const currentFileSet = new Set(allFiles);
|
|
2508
|
+
const added = [];
|
|
2509
|
+
const modified = [];
|
|
2510
|
+
const deleted = [];
|
|
2511
|
+
for (const filepath of changedFilesPaths) {
|
|
2512
|
+
if (currentFileSet.has(filepath)) {
|
|
2513
|
+
if (savedManifest.files[filepath]) {
|
|
2514
|
+
modified.push(filepath);
|
|
2515
|
+
} else {
|
|
2516
|
+
added.push(filepath);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
for (const filepath of allFiles) {
|
|
2521
|
+
if (!savedManifest.files[filepath] && !changedFilesSet.has(filepath)) {
|
|
2522
|
+
added.push(filepath);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
for (const filepath of Object.keys(savedManifest.files)) {
|
|
2526
|
+
if (!currentFileSet.has(filepath)) {
|
|
2527
|
+
deleted.push(filepath);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
return {
|
|
2531
|
+
added,
|
|
2532
|
+
modified,
|
|
2533
|
+
deleted,
|
|
2534
|
+
reason: "git-state-changed"
|
|
2535
|
+
};
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
console.warn(`[Lien] Git diff failed, falling back to full reindex: ${error}`);
|
|
2538
|
+
const allFiles = await getAllFiles(rootDir, config);
|
|
2539
|
+
const currentFileSet = new Set(allFiles);
|
|
2540
|
+
const deleted = [];
|
|
2541
|
+
for (const filepath of Object.keys(savedManifest.files)) {
|
|
2542
|
+
if (!currentFileSet.has(filepath)) {
|
|
2543
|
+
deleted.push(filepath);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return {
|
|
2547
|
+
added: allFiles,
|
|
2548
|
+
modified: [],
|
|
2549
|
+
deleted,
|
|
2550
|
+
reason: "git-state-changed"
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
return await mtimeBasedDetection(rootDir, savedManifest, config);
|
|
2556
|
+
}
|
|
2557
|
+
async function getAllFiles(rootDir, config) {
|
|
2558
|
+
if (isModernConfig(config) && config.frameworks.length > 0) {
|
|
2559
|
+
return await scanCodebaseWithFrameworks(rootDir, config);
|
|
2560
|
+
} else if (isLegacyConfig(config)) {
|
|
2561
|
+
return await scanCodebase({
|
|
2562
|
+
rootDir,
|
|
2563
|
+
includePatterns: config.indexing.include,
|
|
2564
|
+
excludePatterns: config.indexing.exclude
|
|
2565
|
+
});
|
|
2566
|
+
} else {
|
|
2567
|
+
return await scanCodebase({
|
|
2568
|
+
rootDir,
|
|
2569
|
+
includePatterns: [],
|
|
2570
|
+
excludePatterns: []
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
async function mtimeBasedDetection(rootDir, savedManifest, config) {
|
|
2575
|
+
const added = [];
|
|
2576
|
+
const modified = [];
|
|
2577
|
+
const deleted = [];
|
|
2578
|
+
const currentFiles = await getAllFiles(rootDir, config);
|
|
2579
|
+
const currentFileSet = new Set(currentFiles);
|
|
2580
|
+
const fileStats = /* @__PURE__ */ new Map();
|
|
2581
|
+
for (const filepath of currentFiles) {
|
|
2582
|
+
try {
|
|
2583
|
+
const stats = await fs13.stat(filepath);
|
|
2584
|
+
fileStats.set(filepath, stats.mtimeMs);
|
|
2585
|
+
} catch {
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
for (const [filepath, mtime] of fileStats) {
|
|
2590
|
+
const entry = savedManifest.files[filepath];
|
|
2591
|
+
if (!entry) {
|
|
2592
|
+
added.push(filepath);
|
|
2593
|
+
} else if (entry.lastModified < mtime) {
|
|
2594
|
+
modified.push(filepath);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
for (const filepath of Object.keys(savedManifest.files)) {
|
|
2598
|
+
if (!currentFileSet.has(filepath)) {
|
|
2599
|
+
deleted.push(filepath);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
return {
|
|
2603
|
+
added,
|
|
2604
|
+
modified,
|
|
2605
|
+
deleted,
|
|
2606
|
+
reason: "mtime"
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
var init_change_detector = __esm({
|
|
2610
|
+
"src/indexer/change-detector.ts"() {
|
|
2611
|
+
"use strict";
|
|
2612
|
+
init_manifest();
|
|
2613
|
+
init_scanner();
|
|
2614
|
+
init_schema();
|
|
2615
|
+
init_tracker();
|
|
2616
|
+
init_utils();
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
// src/indexer/incremental.ts
|
|
2621
|
+
import fs14 from "fs/promises";
|
|
2622
|
+
async function processFileContent(filepath, content, embeddings, config, verbose) {
|
|
2623
|
+
const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
|
|
2624
|
+
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
|
|
2625
|
+
const chunks = chunkFile(filepath, content, {
|
|
2626
|
+
chunkSize,
|
|
2627
|
+
chunkOverlap
|
|
2628
|
+
});
|
|
2629
|
+
if (chunks.length === 0) {
|
|
2630
|
+
if (verbose) {
|
|
2631
|
+
console.error(`[Lien] Empty file: ${filepath}`);
|
|
2632
|
+
}
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
const texts = chunks.map((c) => c.content);
|
|
2636
|
+
const vectors = [];
|
|
2637
|
+
for (let j = 0; j < texts.length; j += EMBEDDING_MICRO_BATCH_SIZE) {
|
|
2638
|
+
const microBatch = texts.slice(j, Math.min(j + EMBEDDING_MICRO_BATCH_SIZE, texts.length));
|
|
2639
|
+
const microResults = await embeddings.embedBatch(microBatch);
|
|
2640
|
+
vectors.push(...microResults);
|
|
2641
|
+
if (texts.length > EMBEDDING_MICRO_BATCH_SIZE) {
|
|
2642
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
return {
|
|
2646
|
+
chunkCount: chunks.length,
|
|
2647
|
+
vectors,
|
|
2648
|
+
chunks,
|
|
2649
|
+
texts
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
async function indexSingleFile(filepath, vectorDB, embeddings, config, options = {}) {
|
|
2653
|
+
const { verbose } = options;
|
|
2654
|
+
try {
|
|
2655
|
+
try {
|
|
2656
|
+
await fs14.access(filepath);
|
|
2657
|
+
} catch {
|
|
2658
|
+
if (verbose) {
|
|
2659
|
+
console.error(`[Lien] File deleted: ${filepath}`);
|
|
2660
|
+
}
|
|
2661
|
+
await vectorDB.deleteByFile(filepath);
|
|
2662
|
+
const manifest2 = new ManifestManager(vectorDB.dbPath);
|
|
2663
|
+
await manifest2.removeFile(filepath);
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
const content = await fs14.readFile(filepath, "utf-8");
|
|
2667
|
+
const result = await processFileContent(filepath, content, embeddings, config, verbose || false);
|
|
2668
|
+
const stats = await fs14.stat(filepath);
|
|
2669
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
2670
|
+
if (result === null) {
|
|
2671
|
+
await vectorDB.deleteByFile(filepath);
|
|
2672
|
+
await manifest.updateFile(filepath, {
|
|
2673
|
+
filepath,
|
|
2674
|
+
lastModified: stats.mtimeMs,
|
|
2675
|
+
chunkCount: 0
|
|
2676
|
+
});
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
await vectorDB.updateFile(
|
|
2680
|
+
filepath,
|
|
2681
|
+
result.vectors,
|
|
2682
|
+
result.chunks.map((c) => c.metadata),
|
|
2683
|
+
result.texts
|
|
2684
|
+
);
|
|
2685
|
+
await manifest.updateFile(filepath, {
|
|
2686
|
+
filepath,
|
|
2687
|
+
lastModified: stats.mtimeMs,
|
|
2688
|
+
chunkCount: result.chunkCount
|
|
2689
|
+
});
|
|
2690
|
+
if (verbose) {
|
|
2691
|
+
console.error(`[Lien] \u2713 Updated ${filepath} (${result.chunkCount} chunks)`);
|
|
2692
|
+
}
|
|
2693
|
+
} catch (error) {
|
|
2694
|
+
console.error(`[Lien] \u26A0\uFE0F Failed to index ${filepath}: ${error}`);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
async function indexMultipleFiles(filepaths, vectorDB, embeddings, config, options = {}) {
|
|
2698
|
+
const { verbose } = options;
|
|
2699
|
+
let processedCount = 0;
|
|
2700
|
+
const manifestEntries = [];
|
|
2701
|
+
for (const filepath of filepaths) {
|
|
2702
|
+
let content;
|
|
2703
|
+
let fileMtime;
|
|
2704
|
+
try {
|
|
2705
|
+
const stats = await fs14.stat(filepath);
|
|
2706
|
+
fileMtime = stats.mtimeMs;
|
|
2707
|
+
content = await fs14.readFile(filepath, "utf-8");
|
|
2708
|
+
} catch (error) {
|
|
2709
|
+
if (verbose) {
|
|
2710
|
+
console.error(`[Lien] File not readable: ${filepath}`);
|
|
2711
|
+
}
|
|
2712
|
+
try {
|
|
2713
|
+
await vectorDB.deleteByFile(filepath);
|
|
2714
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
2715
|
+
await manifest.removeFile(filepath);
|
|
2716
|
+
} catch (error2) {
|
|
2717
|
+
if (verbose) {
|
|
2718
|
+
console.error(`[Lien] Note: ${filepath} not in index`);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
processedCount++;
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
try {
|
|
2725
|
+
const result = await processFileContent(filepath, content, embeddings, config, verbose || false);
|
|
2726
|
+
if (result === null) {
|
|
2727
|
+
try {
|
|
2728
|
+
await vectorDB.deleteByFile(filepath);
|
|
2729
|
+
} catch (error) {
|
|
2730
|
+
}
|
|
2731
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
2732
|
+
await manifest.updateFile(filepath, {
|
|
2733
|
+
filepath,
|
|
2734
|
+
lastModified: fileMtime,
|
|
2735
|
+
chunkCount: 0
|
|
2736
|
+
});
|
|
2737
|
+
processedCount++;
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
try {
|
|
2741
|
+
await vectorDB.deleteByFile(filepath);
|
|
2742
|
+
} catch (error) {
|
|
2743
|
+
}
|
|
2744
|
+
await vectorDB.insertBatch(
|
|
2745
|
+
result.vectors,
|
|
2746
|
+
result.chunks.map((c) => c.metadata),
|
|
2747
|
+
result.texts
|
|
2748
|
+
);
|
|
2749
|
+
manifestEntries.push({
|
|
2750
|
+
filepath,
|
|
2751
|
+
chunkCount: result.chunkCount,
|
|
2752
|
+
mtime: fileMtime
|
|
2753
|
+
});
|
|
2754
|
+
if (verbose) {
|
|
2755
|
+
console.error(`[Lien] \u2713 Updated ${filepath} (${result.chunkCount} chunks)`);
|
|
2756
|
+
}
|
|
2757
|
+
processedCount++;
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
console.error(`[Lien] \u26A0\uFE0F Failed to index ${filepath}: ${error}`);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
if (manifestEntries.length > 0) {
|
|
2763
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
2764
|
+
await manifest.updateFiles(
|
|
2765
|
+
manifestEntries.map((entry) => ({
|
|
2766
|
+
filepath: entry.filepath,
|
|
2767
|
+
lastModified: entry.mtime,
|
|
2768
|
+
// Use actual file mtime for accurate change detection
|
|
2769
|
+
chunkCount: entry.chunkCount
|
|
2770
|
+
}))
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
return processedCount;
|
|
2774
|
+
}
|
|
2775
|
+
var init_incremental = __esm({
|
|
2776
|
+
"src/indexer/incremental.ts"() {
|
|
2777
|
+
"use strict";
|
|
2778
|
+
init_chunker();
|
|
2779
|
+
init_schema();
|
|
2780
|
+
init_manifest();
|
|
2781
|
+
init_constants();
|
|
2782
|
+
}
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
// src/utils/loading-messages.ts
|
|
2786
|
+
function getIndexingMessage() {
|
|
2787
|
+
const message = INDEXING_MESSAGES[currentIndexingIndex % INDEXING_MESSAGES.length];
|
|
2788
|
+
currentIndexingIndex++;
|
|
2789
|
+
return message;
|
|
2790
|
+
}
|
|
2791
|
+
function getEmbeddingMessage() {
|
|
2792
|
+
const message = EMBEDDING_MESSAGES[currentEmbeddingIndex % EMBEDDING_MESSAGES.length];
|
|
2793
|
+
currentEmbeddingIndex++;
|
|
2794
|
+
return message;
|
|
2795
|
+
}
|
|
2796
|
+
function getModelLoadingMessage() {
|
|
2797
|
+
const message = MODEL_LOADING_MESSAGES[currentModelIndex % MODEL_LOADING_MESSAGES.length];
|
|
2798
|
+
currentModelIndex++;
|
|
2799
|
+
return message;
|
|
2800
|
+
}
|
|
2801
|
+
var INDEXING_MESSAGES, EMBEDDING_MESSAGES, MODEL_LOADING_MESSAGES, currentIndexingIndex, currentEmbeddingIndex, currentModelIndex;
|
|
2802
|
+
var init_loading_messages = __esm({
|
|
2803
|
+
"src/utils/loading-messages.ts"() {
|
|
2804
|
+
"use strict";
|
|
2805
|
+
INDEXING_MESSAGES = [
|
|
2806
|
+
"Teaching AI to read your spaghetti code...",
|
|
2807
|
+
"Convincing the LLM that your variable names make sense...",
|
|
2808
|
+
"Indexing your TODO comments (so many TODOs)...",
|
|
2809
|
+
'Building semantic links faster than you can say "grep"...',
|
|
2810
|
+
"Making your codebase searchable (the good, the bad, and the ugly)...",
|
|
2811
|
+
"Chunking code like a boss...",
|
|
2812
|
+
"Feeding your code to the neural network (it's hungry)...",
|
|
2813
|
+
"Creating embeddings (it's like compression, but fancier)...",
|
|
2814
|
+
"Teaching machines to understand your midnight commits...",
|
|
2815
|
+
"Vectorizing your technical debt...",
|
|
2816
|
+
"Indexing... because Ctrl+F wasn't cutting it anymore...",
|
|
2817
|
+
"Making semantic connections (unlike your last refactor)...",
|
|
2818
|
+
"Processing files faster than your CI pipeline...",
|
|
2819
|
+
"Embedding wisdom from your comments (all 3 of them)...",
|
|
2820
|
+
"Analyzing code semantics (yes, even that one function)...",
|
|
2821
|
+
"Building search index (now with 100% more AI)...",
|
|
2822
|
+
"Crunching vectors like it's nobody's business...",
|
|
2823
|
+
"Linking code fragments across the spacetime continuum...",
|
|
2824
|
+
"Teaching transformers about your coding style...",
|
|
2825
|
+
"Preparing for semantic search domination...",
|
|
2826
|
+
"Indexing your genius (and that hacky workaround from 2019)...",
|
|
2827
|
+
"Making your codebase AI-readable (you're welcome, future you)...",
|
|
2828
|
+
"Converting code to math (engineers love this trick)...",
|
|
2829
|
+
"Building the neural net's mental model of your app...",
|
|
2830
|
+
"Chunking files like a lumberjack, but for code..."
|
|
2831
|
+
];
|
|
2832
|
+
EMBEDDING_MESSAGES = [
|
|
2833
|
+
"Generating embeddings (math is happening)...",
|
|
2834
|
+
"Teaching transformers about your forEach loops...",
|
|
2835
|
+
"Converting code to 384-dimensional space (wild, right?)...",
|
|
2836
|
+
"Running the neural network (the Matrix, but for code)...",
|
|
2837
|
+
"Creating semantic vectors (fancy word for AI magic)...",
|
|
2838
|
+
"Embedding your code into hyperspace...",
|
|
2839
|
+
'Teaching the model what "clean code" means in your codebase...',
|
|
2840
|
+
'Generating vectors faster than you can say "AI"...',
|
|
2841
|
+
"Making math from your methods...",
|
|
2842
|
+
"Transforming code into numbers (the AI way)...",
|
|
2843
|
+
"Processing with transformers.js (yes, it runs locally!)...",
|
|
2844
|
+
"Embedding semantics (your code's hidden meaning)...",
|
|
2845
|
+
"Vectorizing variables (alliteration achieved)...",
|
|
2846
|
+
"Teaching AI the difference between foo and bar...",
|
|
2847
|
+
"Creating embeddings (384 dimensions of awesome)..."
|
|
2848
|
+
];
|
|
2849
|
+
MODEL_LOADING_MESSAGES = [
|
|
2850
|
+
"Waking up the neural network...",
|
|
2851
|
+
"Loading transformer model (patience, young padawan)...",
|
|
2852
|
+
"Downloading AI brain (first run only, promise!)...",
|
|
2853
|
+
"Initializing the semantic search engine...",
|
|
2854
|
+
"Booting up the language model (coffee break recommended)...",
|
|
2855
|
+
"Loading 100MB of pure AI goodness...",
|
|
2856
|
+
"Preparing the transformer for action...",
|
|
2857
|
+
"Model loading (this is why we run locally)...",
|
|
2858
|
+
"Spinning up the embedding generator...",
|
|
2859
|
+
"Getting the AI ready for your codebase..."
|
|
2860
|
+
];
|
|
2861
|
+
currentIndexingIndex = 0;
|
|
2862
|
+
currentEmbeddingIndex = 0;
|
|
2863
|
+
currentModelIndex = 0;
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
|
|
2867
|
+
// src/indexer/index.ts
|
|
2868
|
+
var indexer_exports = {};
|
|
2869
|
+
__export(indexer_exports, {
|
|
2870
|
+
indexCodebase: () => indexCodebase
|
|
2871
|
+
});
|
|
2872
|
+
import fs15 from "fs/promises";
|
|
2873
|
+
import ora from "ora";
|
|
2874
|
+
import chalk4 from "chalk";
|
|
2875
|
+
import pLimit from "p-limit";
|
|
2876
|
+
async function indexCodebase(options = {}) {
|
|
2877
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
2878
|
+
const spinner = ora("Starting indexing process...").start();
|
|
2879
|
+
let updateInterval;
|
|
2880
|
+
try {
|
|
2881
|
+
spinner.text = "Loading configuration...";
|
|
2882
|
+
const config = await configService.load(rootDir);
|
|
2883
|
+
spinner.text = "Initializing vector database...";
|
|
2884
|
+
const vectorDB = new VectorDB(rootDir);
|
|
2885
|
+
await vectorDB.initialize();
|
|
2886
|
+
if (!options.force) {
|
|
2887
|
+
spinner.text = "Checking for changes...";
|
|
2888
|
+
const manifest2 = new ManifestManager(vectorDB.dbPath);
|
|
2889
|
+
const savedManifest = await manifest2.load();
|
|
2890
|
+
if (savedManifest) {
|
|
2891
|
+
const changes = await detectChanges(rootDir, vectorDB, config);
|
|
2892
|
+
if (changes.reason !== "full") {
|
|
2893
|
+
const totalChanges = changes.added.length + changes.modified.length;
|
|
2894
|
+
const totalDeleted = changes.deleted.length;
|
|
2895
|
+
if (totalChanges === 0 && totalDeleted === 0) {
|
|
2896
|
+
spinner.succeed("No changes detected - index is up to date!");
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
spinner.succeed(
|
|
2900
|
+
`Detected changes: ${totalChanges} files to index, ${totalDeleted} to remove (${changes.reason} detection)`
|
|
2901
|
+
);
|
|
2902
|
+
spinner.start(getModelLoadingMessage());
|
|
2903
|
+
const embeddings2 = new LocalEmbeddings();
|
|
2904
|
+
await embeddings2.initialize();
|
|
2905
|
+
spinner.succeed("Embedding model loaded");
|
|
2906
|
+
if (totalDeleted > 0) {
|
|
2907
|
+
spinner.start(`Removing ${totalDeleted} deleted files...`);
|
|
2908
|
+
let removedCount = 0;
|
|
2909
|
+
for (const filepath of changes.deleted) {
|
|
2910
|
+
try {
|
|
2911
|
+
await vectorDB.deleteByFile(filepath);
|
|
2912
|
+
await manifest2.removeFile(filepath);
|
|
2913
|
+
removedCount++;
|
|
2914
|
+
} catch (err) {
|
|
2915
|
+
spinner.warn(`Failed to remove file "${filepath}": ${err instanceof Error ? err.message : String(err)}`);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
spinner.succeed(`Removed ${removedCount}/${totalDeleted} deleted files`);
|
|
2919
|
+
}
|
|
2920
|
+
if (totalChanges > 0) {
|
|
2921
|
+
spinner.start(`Reindexing ${totalChanges} changed files...`);
|
|
2922
|
+
const filesToIndex = [...changes.added, ...changes.modified];
|
|
2923
|
+
const count = await indexMultipleFiles(
|
|
2924
|
+
filesToIndex,
|
|
2925
|
+
vectorDB,
|
|
2926
|
+
embeddings2,
|
|
2927
|
+
config,
|
|
2928
|
+
{ verbose: options.verbose }
|
|
2929
|
+
);
|
|
2930
|
+
await writeVersionFile(vectorDB.dbPath);
|
|
2931
|
+
spinner.succeed(
|
|
2932
|
+
`Incremental reindex complete: ${count}/${totalChanges} files indexed successfully`
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2935
|
+
const { isGitAvailable: isGitAvailable3, isGitRepo: isGitRepo3 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
|
|
2936
|
+
const { GitStateTracker: GitStateTracker3 } = await Promise.resolve().then(() => (init_tracker(), tracker_exports));
|
|
2937
|
+
const gitAvailable2 = await isGitAvailable3();
|
|
2938
|
+
const isRepo2 = await isGitRepo3(rootDir);
|
|
2939
|
+
if (gitAvailable2 && isRepo2) {
|
|
2940
|
+
const gitTracker = new GitStateTracker3(rootDir, vectorDB.dbPath);
|
|
2941
|
+
await gitTracker.initialize();
|
|
2942
|
+
const gitState = gitTracker.getState();
|
|
2943
|
+
if (gitState) {
|
|
2944
|
+
await manifest2.updateGitState(gitState);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
console.log(chalk4.dim("\nNext step: Run"), chalk4.bold("lien serve"), chalk4.dim("to start the MCP server"));
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
spinner.text = "Full reindex required...";
|
|
2951
|
+
}
|
|
2952
|
+
} else {
|
|
2953
|
+
spinner.text = "Force flag enabled, performing full reindex...";
|
|
2954
|
+
}
|
|
2955
|
+
spinner.text = "Scanning codebase...";
|
|
2956
|
+
let files;
|
|
2957
|
+
if (isModernConfig(config) && config.frameworks.length > 0) {
|
|
2958
|
+
files = await scanCodebaseWithFrameworks(rootDir, config);
|
|
2959
|
+
} else if (isLegacyConfig(config)) {
|
|
2960
|
+
files = await scanCodebase({
|
|
2961
|
+
rootDir,
|
|
2962
|
+
includePatterns: config.indexing.include,
|
|
2963
|
+
excludePatterns: config.indexing.exclude
|
|
2964
|
+
});
|
|
2965
|
+
} else {
|
|
2966
|
+
files = await scanCodebase({
|
|
2967
|
+
rootDir,
|
|
2968
|
+
includePatterns: [],
|
|
2969
|
+
excludePatterns: []
|
|
2970
|
+
});
|
|
2971
|
+
}
|
|
2972
|
+
if (files.length === 0) {
|
|
2973
|
+
spinner.fail("No files found to index");
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
spinner.text = `Found ${files.length} files`;
|
|
2977
|
+
spinner.text = getModelLoadingMessage();
|
|
2978
|
+
const embeddings = new LocalEmbeddings();
|
|
2979
|
+
await embeddings.initialize();
|
|
2980
|
+
spinner.succeed("Embedding model loaded");
|
|
2981
|
+
const concurrency = isModernConfig(config) ? config.core.concurrency : 4;
|
|
2982
|
+
const embeddingBatchSize = isModernConfig(config) ? config.core.embeddingBatchSize : 50;
|
|
2983
|
+
const vectorDBBatchSize = 100;
|
|
2984
|
+
spinner.start(`Processing files with ${concurrency}x concurrency...`);
|
|
2985
|
+
const startTime = Date.now();
|
|
2986
|
+
let processedFiles = 0;
|
|
2987
|
+
let processedChunks = 0;
|
|
2988
|
+
const chunkAccumulator = [];
|
|
2989
|
+
const limit = pLimit(concurrency);
|
|
2990
|
+
const indexedFileEntries = [];
|
|
2991
|
+
const progressState = {
|
|
2992
|
+
processedFiles: 0,
|
|
2993
|
+
totalFiles: files.length,
|
|
2994
|
+
wittyMessage: getIndexingMessage()
|
|
2995
|
+
};
|
|
2996
|
+
const SPINNER_UPDATE_INTERVAL_MS = 200;
|
|
2997
|
+
const MESSAGE_ROTATION_INTERVAL_MS = 8e3;
|
|
2998
|
+
const MESSAGE_ROTATION_TICKS = Math.floor(MESSAGE_ROTATION_INTERVAL_MS / SPINNER_UPDATE_INTERVAL_MS);
|
|
2999
|
+
let spinnerTick = 0;
|
|
3000
|
+
updateInterval = setInterval(() => {
|
|
3001
|
+
spinnerTick++;
|
|
3002
|
+
if (spinnerTick >= MESSAGE_ROTATION_TICKS) {
|
|
3003
|
+
progressState.wittyMessage = getIndexingMessage();
|
|
3004
|
+
spinnerTick = 0;
|
|
3005
|
+
}
|
|
3006
|
+
spinner.text = `${progressState.processedFiles}/${progressState.totalFiles} files | ${progressState.wittyMessage}`;
|
|
3007
|
+
}, SPINNER_UPDATE_INTERVAL_MS);
|
|
3008
|
+
const processAccumulatedChunks = async () => {
|
|
3009
|
+
if (chunkAccumulator.length === 0) return;
|
|
3010
|
+
const toProcess = chunkAccumulator.splice(0, chunkAccumulator.length);
|
|
3011
|
+
for (let i = 0; i < toProcess.length; i += embeddingBatchSize) {
|
|
3012
|
+
const batch = toProcess.slice(i, Math.min(i + embeddingBatchSize, toProcess.length));
|
|
3013
|
+
progressState.wittyMessage = getEmbeddingMessage();
|
|
3014
|
+
const texts = batch.map((item) => item.content);
|
|
3015
|
+
const embeddingVectors = [];
|
|
3016
|
+
for (let j = 0; j < texts.length; j += EMBEDDING_MICRO_BATCH_SIZE) {
|
|
3017
|
+
const microBatch = texts.slice(j, Math.min(j + EMBEDDING_MICRO_BATCH_SIZE, texts.length));
|
|
3018
|
+
const microResults = await embeddings.embedBatch(microBatch);
|
|
3019
|
+
embeddingVectors.push(...microResults);
|
|
3020
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
3021
|
+
}
|
|
1961
3022
|
processedChunks += batch.length;
|
|
3023
|
+
progressState.wittyMessage = `Inserting ${batch.length} chunks into vector space...`;
|
|
3024
|
+
await vectorDB.insertBatch(
|
|
3025
|
+
embeddingVectors,
|
|
3026
|
+
batch.map((item) => item.chunk.metadata),
|
|
3027
|
+
texts
|
|
3028
|
+
);
|
|
3029
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1962
3030
|
}
|
|
3031
|
+
progressState.wittyMessage = getIndexingMessage();
|
|
1963
3032
|
};
|
|
1964
3033
|
const filePromises = files.map(
|
|
1965
3034
|
(file) => limit(async () => {
|
|
1966
3035
|
try {
|
|
1967
|
-
const
|
|
3036
|
+
const stats = await fs15.stat(file);
|
|
3037
|
+
const content = await fs15.readFile(file, "utf-8");
|
|
1968
3038
|
const chunkSize = isModernConfig(config) ? config.core.chunkSize : 75;
|
|
1969
3039
|
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : 10;
|
|
1970
3040
|
const chunks = chunkFile(file, content, {
|
|
@@ -1973,6 +3043,7 @@ async function indexCodebase(options = {}) {
|
|
|
1973
3043
|
});
|
|
1974
3044
|
if (chunks.length === 0) {
|
|
1975
3045
|
processedFiles++;
|
|
3046
|
+
progressState.processedFiles = processedFiles;
|
|
1976
3047
|
return;
|
|
1977
3048
|
}
|
|
1978
3049
|
for (const chunk of chunks) {
|
|
@@ -1981,25 +3052,53 @@ async function indexCodebase(options = {}) {
|
|
|
1981
3052
|
content: chunk.content
|
|
1982
3053
|
});
|
|
1983
3054
|
}
|
|
1984
|
-
|
|
3055
|
+
indexedFileEntries.push({
|
|
3056
|
+
filepath: file,
|
|
3057
|
+
chunkCount: chunks.length,
|
|
3058
|
+
mtime: stats.mtimeMs
|
|
3059
|
+
});
|
|
3060
|
+
processedFiles++;
|
|
3061
|
+
progressState.processedFiles = processedFiles;
|
|
3062
|
+
if (chunkAccumulator.length >= vectorDBBatchSize) {
|
|
1985
3063
|
await processAccumulatedChunks();
|
|
1986
3064
|
}
|
|
1987
|
-
processedFiles++;
|
|
1988
|
-
const elapsed = (Date.now() - startTime) / 1e3;
|
|
1989
|
-
const rate = processedFiles / elapsed;
|
|
1990
|
-
const eta = rate > 0 ? Math.round((files.length - processedFiles) / rate) : 0;
|
|
1991
|
-
spinner.text = `Indexed ${processedFiles}/${files.length} files (${processedChunks} chunks) | ${concurrency}x concurrency | ETA: ${eta}s`;
|
|
1992
3065
|
} catch (error) {
|
|
1993
3066
|
if (options.verbose) {
|
|
1994
3067
|
console.error(chalk4.yellow(`
|
|
1995
3068
|
\u26A0\uFE0F Skipping ${file}: ${error}`));
|
|
1996
3069
|
}
|
|
1997
3070
|
processedFiles++;
|
|
3071
|
+
progressState.processedFiles = processedFiles;
|
|
1998
3072
|
}
|
|
1999
3073
|
})
|
|
2000
3074
|
);
|
|
2001
3075
|
await Promise.all(filePromises);
|
|
3076
|
+
progressState.wittyMessage = "Processing final chunks...";
|
|
2002
3077
|
await processAccumulatedChunks();
|
|
3078
|
+
clearInterval(updateInterval);
|
|
3079
|
+
spinner.start("Saving index manifest...");
|
|
3080
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
3081
|
+
await manifest.updateFiles(
|
|
3082
|
+
indexedFileEntries.map((entry) => ({
|
|
3083
|
+
filepath: entry.filepath,
|
|
3084
|
+
lastModified: entry.mtime,
|
|
3085
|
+
// Use actual file mtime for accurate change detection
|
|
3086
|
+
chunkCount: entry.chunkCount
|
|
3087
|
+
}))
|
|
3088
|
+
);
|
|
3089
|
+
const { isGitAvailable: isGitAvailable2, isGitRepo: isGitRepo2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
|
|
3090
|
+
const { GitStateTracker: GitStateTracker2 } = await Promise.resolve().then(() => (init_tracker(), tracker_exports));
|
|
3091
|
+
const gitAvailable = await isGitAvailable2();
|
|
3092
|
+
const isRepo = await isGitRepo2(rootDir);
|
|
3093
|
+
if (gitAvailable && isRepo) {
|
|
3094
|
+
const gitTracker = new GitStateTracker2(rootDir, vectorDB.dbPath);
|
|
3095
|
+
await gitTracker.initialize();
|
|
3096
|
+
const gitState = gitTracker.getState();
|
|
3097
|
+
if (gitState) {
|
|
3098
|
+
await manifest.updateGitState(gitState);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
spinner.succeed("Manifest saved");
|
|
2003
3102
|
await writeVersionFile(vectorDB.dbPath);
|
|
2004
3103
|
const totalTime = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2005
3104
|
spinner.succeed(
|
|
@@ -2007,6 +3106,9 @@ async function indexCodebase(options = {}) {
|
|
|
2007
3106
|
);
|
|
2008
3107
|
console.log(chalk4.dim("\nNext step: Run"), chalk4.bold("lien serve"), chalk4.dim("to start the MCP server"));
|
|
2009
3108
|
} catch (error) {
|
|
3109
|
+
if (updateInterval) {
|
|
3110
|
+
clearInterval(updateInterval);
|
|
3111
|
+
}
|
|
2010
3112
|
spinner.fail(`Indexing failed: ${error}`);
|
|
2011
3113
|
throw error;
|
|
2012
3114
|
}
|
|
@@ -2021,26 +3123,91 @@ var init_indexer = __esm({
|
|
|
2021
3123
|
init_service();
|
|
2022
3124
|
init_version();
|
|
2023
3125
|
init_schema();
|
|
3126
|
+
init_manifest();
|
|
3127
|
+
init_change_detector();
|
|
3128
|
+
init_incremental();
|
|
3129
|
+
init_loading_messages();
|
|
3130
|
+
init_constants();
|
|
2024
3131
|
}
|
|
2025
3132
|
});
|
|
2026
3133
|
|
|
2027
3134
|
// src/cli/index.ts
|
|
2028
3135
|
import { Command } from "commander";
|
|
2029
|
-
import { createRequire as
|
|
2030
|
-
import { fileURLToPath as
|
|
2031
|
-
import { dirname as
|
|
3136
|
+
import { createRequire as createRequire4 } from "module";
|
|
3137
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
3138
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
2032
3139
|
|
|
2033
3140
|
// src/cli/init.ts
|
|
2034
3141
|
init_schema();
|
|
2035
3142
|
init_merge();
|
|
2036
|
-
init_banner();
|
|
2037
|
-
init_migration();
|
|
2038
3143
|
import fs5 from "fs/promises";
|
|
2039
3144
|
import path5 from "path";
|
|
2040
3145
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2041
3146
|
import chalk2 from "chalk";
|
|
2042
3147
|
import inquirer from "inquirer";
|
|
2043
3148
|
|
|
3149
|
+
// src/utils/banner.ts
|
|
3150
|
+
import figlet from "figlet";
|
|
3151
|
+
import chalk from "chalk";
|
|
3152
|
+
import { createRequire } from "module";
|
|
3153
|
+
import { fileURLToPath } from "url";
|
|
3154
|
+
import { dirname, join } from "path";
|
|
3155
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
3156
|
+
var __dirname = dirname(__filename);
|
|
3157
|
+
var require2 = createRequire(import.meta.url);
|
|
3158
|
+
var packageJson;
|
|
3159
|
+
try {
|
|
3160
|
+
packageJson = require2(join(__dirname, "../package.json"));
|
|
3161
|
+
} catch {
|
|
3162
|
+
packageJson = require2(join(__dirname, "../../package.json"));
|
|
3163
|
+
}
|
|
3164
|
+
var PACKAGE_NAME = packageJson.name;
|
|
3165
|
+
var VERSION = packageJson.version;
|
|
3166
|
+
function wrapInBox(text, footer, padding = 1) {
|
|
3167
|
+
const lines = text.split("\n").filter((line) => line.trim().length > 0);
|
|
3168
|
+
const maxLength = Math.max(...lines.map((line) => line.length));
|
|
3169
|
+
const horizontalBorder = "\u2500".repeat(maxLength + padding * 2);
|
|
3170
|
+
const top = `\u250C${horizontalBorder}\u2510`;
|
|
3171
|
+
const bottom = `\u2514${horizontalBorder}\u2518`;
|
|
3172
|
+
const separator = `\u251C${horizontalBorder}\u2524`;
|
|
3173
|
+
const paddedLines = lines.map((line) => {
|
|
3174
|
+
const padRight = " ".repeat(maxLength - line.length + padding);
|
|
3175
|
+
const padLeft = " ".repeat(padding);
|
|
3176
|
+
return `\u2502${padLeft}${line}${padRight}\u2502`;
|
|
3177
|
+
});
|
|
3178
|
+
const totalPad = maxLength - footer.length;
|
|
3179
|
+
const leftPad = Math.floor(totalPad / 2);
|
|
3180
|
+
const rightPad = totalPad - leftPad;
|
|
3181
|
+
const centeredFooter = " ".repeat(leftPad) + footer + " ".repeat(rightPad);
|
|
3182
|
+
const paddedFooter = `\u2502${" ".repeat(padding)}${centeredFooter}${" ".repeat(padding)}\u2502`;
|
|
3183
|
+
return [top, ...paddedLines, separator, paddedFooter, bottom].join("\n");
|
|
3184
|
+
}
|
|
3185
|
+
function showBanner() {
|
|
3186
|
+
const banner = figlet.textSync("LIEN", {
|
|
3187
|
+
font: "ANSI Shadow",
|
|
3188
|
+
horizontalLayout: "fitted",
|
|
3189
|
+
verticalLayout: "fitted"
|
|
3190
|
+
});
|
|
3191
|
+
const footer = `${PACKAGE_NAME} - v${VERSION}`;
|
|
3192
|
+
const boxedBanner = wrapInBox(banner.trim(), footer);
|
|
3193
|
+
console.error(chalk.cyan(boxedBanner));
|
|
3194
|
+
console.error();
|
|
3195
|
+
}
|
|
3196
|
+
function showCompactBanner() {
|
|
3197
|
+
const banner = figlet.textSync("LIEN", {
|
|
3198
|
+
font: "ANSI Shadow",
|
|
3199
|
+
horizontalLayout: "fitted",
|
|
3200
|
+
verticalLayout: "fitted"
|
|
3201
|
+
});
|
|
3202
|
+
const footer = `${PACKAGE_NAME} - v${VERSION}`;
|
|
3203
|
+
const boxedBanner = wrapInBox(banner.trim(), footer);
|
|
3204
|
+
console.log(chalk.cyan(boxedBanner));
|
|
3205
|
+
console.log();
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
// src/cli/init.ts
|
|
3209
|
+
init_migration();
|
|
3210
|
+
|
|
2044
3211
|
// src/frameworks/detector-service.ts
|
|
2045
3212
|
import fs4 from "fs/promises";
|
|
2046
3213
|
import path4 from "path";
|
|
@@ -2126,17 +3293,17 @@ var nodejsDetector = {
|
|
|
2126
3293
|
evidence: []
|
|
2127
3294
|
};
|
|
2128
3295
|
const packageJsonPath = path.join(fullPath, "package.json");
|
|
2129
|
-
let
|
|
3296
|
+
let packageJson5 = null;
|
|
2130
3297
|
try {
|
|
2131
3298
|
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
2132
|
-
|
|
3299
|
+
packageJson5 = JSON.parse(content);
|
|
2133
3300
|
result.evidence.push("Found package.json");
|
|
2134
3301
|
} catch {
|
|
2135
3302
|
return result;
|
|
2136
3303
|
}
|
|
2137
3304
|
result.detected = true;
|
|
2138
3305
|
result.confidence = "high";
|
|
2139
|
-
if (
|
|
3306
|
+
if (packageJson5.devDependencies?.typescript || packageJson5.dependencies?.typescript) {
|
|
2140
3307
|
result.evidence.push("TypeScript detected");
|
|
2141
3308
|
}
|
|
2142
3309
|
const testFrameworks = [
|
|
@@ -2147,7 +3314,7 @@ var nodejsDetector = {
|
|
|
2147
3314
|
{ name: "@playwright/test", display: "Playwright" }
|
|
2148
3315
|
];
|
|
2149
3316
|
for (const framework of testFrameworks) {
|
|
2150
|
-
if (
|
|
3317
|
+
if (packageJson5.devDependencies?.[framework.name] || packageJson5.dependencies?.[framework.name]) {
|
|
2151
3318
|
result.evidence.push(`${framework.display} test framework detected`);
|
|
2152
3319
|
break;
|
|
2153
3320
|
}
|
|
@@ -2160,13 +3327,13 @@ var nodejsDetector = {
|
|
|
2160
3327
|
{ name: "@nestjs/core", display: "NestJS" }
|
|
2161
3328
|
];
|
|
2162
3329
|
for (const fw of frameworks) {
|
|
2163
|
-
if (
|
|
3330
|
+
if (packageJson5.dependencies?.[fw.name]) {
|
|
2164
3331
|
result.evidence.push(`${fw.display} detected`);
|
|
2165
3332
|
break;
|
|
2166
3333
|
}
|
|
2167
3334
|
}
|
|
2168
|
-
if (
|
|
2169
|
-
result.version =
|
|
3335
|
+
if (packageJson5.engines?.node) {
|
|
3336
|
+
result.version = packageJson5.engines.node;
|
|
2170
3337
|
}
|
|
2171
3338
|
return result;
|
|
2172
3339
|
},
|
|
@@ -2796,111 +3963,31 @@ async function upgradeConfig(configPath) {
|
|
|
2796
3963
|
newFields.forEach((field) => console.log(chalk2.dim(" \u2022"), chalk2.bold(field)));
|
|
2797
3964
|
}
|
|
2798
3965
|
}
|
|
2799
|
-
await fs5.writeFile(
|
|
2800
|
-
configPath,
|
|
2801
|
-
JSON.stringify(upgradedConfig, null, 2) + "\n",
|
|
2802
|
-
"utf-8"
|
|
2803
|
-
);
|
|
2804
|
-
console.log(chalk2.green("\u2713 Config upgraded successfully"));
|
|
2805
|
-
console.log(chalk2.dim("Backup saved to:"), backupPath);
|
|
2806
|
-
if (migrated) {
|
|
2807
|
-
console.log(chalk2.dim("\n\u{1F4DD} Your config now uses the framework-based structure."));
|
|
2808
|
-
}
|
|
2809
|
-
} catch (error) {
|
|
2810
|
-
console.error(chalk2.red("Error upgrading config:"), error);
|
|
2811
|
-
throw error;
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
|
|
2815
|
-
// src/cli/status.ts
|
|
2816
|
-
init_service();
|
|
2817
|
-
import chalk3 from "chalk";
|
|
2818
|
-
import fs9 from "fs/promises";
|
|
2819
|
-
import path9 from "path";
|
|
2820
|
-
import os from "os";
|
|
2821
|
-
import crypto from "crypto";
|
|
2822
|
-
|
|
2823
|
-
// src/git/utils.ts
|
|
2824
|
-
import { exec } from "child_process";
|
|
2825
|
-
import { promisify } from "util";
|
|
2826
|
-
import fs7 from "fs/promises";
|
|
2827
|
-
import path7 from "path";
|
|
2828
|
-
var execAsync = promisify(exec);
|
|
2829
|
-
async function isGitRepo(rootDir) {
|
|
2830
|
-
try {
|
|
2831
|
-
const gitDir = path7.join(rootDir, ".git");
|
|
2832
|
-
await fs7.access(gitDir);
|
|
2833
|
-
return true;
|
|
2834
|
-
} catch {
|
|
2835
|
-
return false;
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
async function getCurrentBranch(rootDir) {
|
|
2839
|
-
try {
|
|
2840
|
-
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
|
|
2841
|
-
cwd: rootDir,
|
|
2842
|
-
timeout: 5e3
|
|
2843
|
-
// 5 second timeout
|
|
2844
|
-
});
|
|
2845
|
-
return stdout.trim();
|
|
2846
|
-
} catch (error) {
|
|
2847
|
-
throw new Error(`Failed to get current branch: ${error}`);
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
async function getCurrentCommit(rootDir) {
|
|
2851
|
-
try {
|
|
2852
|
-
const { stdout } = await execAsync("git rev-parse HEAD", {
|
|
2853
|
-
cwd: rootDir,
|
|
2854
|
-
timeout: 5e3
|
|
2855
|
-
});
|
|
2856
|
-
return stdout.trim();
|
|
2857
|
-
} catch (error) {
|
|
2858
|
-
throw new Error(`Failed to get current commit: ${error}`);
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
async function getChangedFiles(rootDir, fromRef, toRef) {
|
|
2862
|
-
try {
|
|
2863
|
-
const { stdout } = await execAsync(
|
|
2864
|
-
`git diff --name-only ${fromRef}...${toRef}`,
|
|
2865
|
-
{
|
|
2866
|
-
cwd: rootDir,
|
|
2867
|
-
timeout: 1e4
|
|
2868
|
-
// 10 second timeout for diffs
|
|
2869
|
-
}
|
|
2870
|
-
);
|
|
2871
|
-
const files = stdout.trim().split("\n").filter(Boolean).map((file) => path7.join(rootDir, file));
|
|
2872
|
-
return files;
|
|
2873
|
-
} catch (error) {
|
|
2874
|
-
throw new Error(`Failed to get changed files: ${error}`);
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
async function getChangedFilesBetweenCommits(rootDir, fromCommit, toCommit) {
|
|
2878
|
-
try {
|
|
2879
|
-
const { stdout } = await execAsync(
|
|
2880
|
-
`git diff --name-only ${fromCommit} ${toCommit}`,
|
|
2881
|
-
{
|
|
2882
|
-
cwd: rootDir,
|
|
2883
|
-
timeout: 1e4
|
|
2884
|
-
}
|
|
3966
|
+
await fs5.writeFile(
|
|
3967
|
+
configPath,
|
|
3968
|
+
JSON.stringify(upgradedConfig, null, 2) + "\n",
|
|
3969
|
+
"utf-8"
|
|
2885
3970
|
);
|
|
2886
|
-
|
|
2887
|
-
|
|
3971
|
+
console.log(chalk2.green("\u2713 Config upgraded successfully"));
|
|
3972
|
+
console.log(chalk2.dim("Backup saved to:"), backupPath);
|
|
3973
|
+
if (migrated) {
|
|
3974
|
+
console.log(chalk2.dim("\n\u{1F4DD} Your config now uses the framework-based structure."));
|
|
3975
|
+
}
|
|
2888
3976
|
} catch (error) {
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
}
|
|
2892
|
-
async function isGitAvailable() {
|
|
2893
|
-
try {
|
|
2894
|
-
await execAsync("git --version", { timeout: 3e3 });
|
|
2895
|
-
return true;
|
|
2896
|
-
} catch {
|
|
2897
|
-
return false;
|
|
3977
|
+
console.error(chalk2.red("Error upgrading config:"), error);
|
|
3978
|
+
throw error;
|
|
2898
3979
|
}
|
|
2899
3980
|
}
|
|
2900
3981
|
|
|
2901
3982
|
// src/cli/status.ts
|
|
3983
|
+
init_service();
|
|
3984
|
+
init_utils();
|
|
2902
3985
|
init_version();
|
|
2903
|
-
|
|
3986
|
+
import chalk3 from "chalk";
|
|
3987
|
+
import fs9 from "fs/promises";
|
|
3988
|
+
import path9 from "path";
|
|
3989
|
+
import os from "os";
|
|
3990
|
+
import crypto from "crypto";
|
|
2904
3991
|
init_schema();
|
|
2905
3992
|
async function statusCommand() {
|
|
2906
3993
|
const rootDir = process.cwd();
|
|
@@ -2986,14 +4073,25 @@ async function statusCommand() {
|
|
|
2986
4073
|
|
|
2987
4074
|
// src/cli/index-cmd.ts
|
|
2988
4075
|
init_indexer();
|
|
2989
|
-
init_banner();
|
|
2990
4076
|
import chalk5 from "chalk";
|
|
2991
4077
|
async function indexCommand(options) {
|
|
2992
4078
|
showCompactBanner();
|
|
2993
4079
|
try {
|
|
4080
|
+
if (options.force) {
|
|
4081
|
+
const { VectorDB: VectorDB2 } = await Promise.resolve().then(() => (init_lancedb(), lancedb_exports));
|
|
4082
|
+
const { ManifestManager: ManifestManager2 } = await Promise.resolve().then(() => (init_manifest(), manifest_exports));
|
|
4083
|
+
console.log(chalk5.yellow("Clearing existing index and manifest..."));
|
|
4084
|
+
const vectorDB = new VectorDB2(process.cwd());
|
|
4085
|
+
await vectorDB.initialize();
|
|
4086
|
+
await vectorDB.clear();
|
|
4087
|
+
const manifest = new ManifestManager2(vectorDB.dbPath);
|
|
4088
|
+
await manifest.clear();
|
|
4089
|
+
console.log(chalk5.green("\u2713 Index and manifest cleared\n"));
|
|
4090
|
+
}
|
|
2994
4091
|
await indexCodebase({
|
|
2995
4092
|
rootDir: process.cwd(),
|
|
2996
|
-
verbose: options.verbose || false
|
|
4093
|
+
verbose: options.verbose || false,
|
|
4094
|
+
force: options.force || false
|
|
2997
4095
|
});
|
|
2998
4096
|
if (options.watch) {
|
|
2999
4097
|
console.log(chalk5.yellow("\n\u26A0\uFE0F Watch mode not yet implemented"));
|
|
@@ -3006,8 +4104,8 @@ async function indexCommand(options) {
|
|
|
3006
4104
|
|
|
3007
4105
|
// src/cli/serve.ts
|
|
3008
4106
|
import chalk6 from "chalk";
|
|
3009
|
-
import
|
|
3010
|
-
import
|
|
4107
|
+
import fs16 from "fs/promises";
|
|
4108
|
+
import path14 from "path";
|
|
3011
4109
|
|
|
3012
4110
|
// src/mcp/server.ts
|
|
3013
4111
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -3016,332 +4114,99 @@ import {
|
|
|
3016
4114
|
CallToolRequestSchema,
|
|
3017
4115
|
ListToolsRequestSchema
|
|
3018
4116
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
3019
|
-
import { createRequire as
|
|
3020
|
-
import { fileURLToPath as
|
|
3021
|
-
import { dirname as
|
|
4117
|
+
import { createRequire as createRequire3 } from "module";
|
|
4118
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4119
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
4120
|
+
|
|
4121
|
+
// src/mcp/utils/zod-to-json-schema.ts
|
|
4122
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
4123
|
+
function toMCPToolSchema(zodSchema, name, description) {
|
|
4124
|
+
return {
|
|
4125
|
+
name,
|
|
4126
|
+
description,
|
|
4127
|
+
inputSchema: zodToJsonSchema(zodSchema, {
|
|
4128
|
+
target: "jsonSchema7",
|
|
4129
|
+
$refStrategy: "none"
|
|
4130
|
+
})
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
// src/mcp/schemas/search.schema.ts
|
|
4135
|
+
import { z } from "zod";
|
|
4136
|
+
var SemanticSearchSchema = z.object({
|
|
4137
|
+
query: z.string().min(3, "Query must be at least 3 characters").max(500, "Query too long (max 500 characters)").describe(
|
|
4138
|
+
"Natural language description of what you're looking for.\n\nUse full sentences describing functionality, not exact names.\n\nGood examples:\n - 'handles user authentication'\n - 'validates email format'\n - 'processes payment transactions'\n\nBad examples:\n - 'auth' (too vague)\n - 'validateEmail' (use grep for exact names)"
|
|
4139
|
+
),
|
|
4140
|
+
limit: z.number().int().min(1, "Limit must be at least 1").max(50, "Limit cannot exceed 50").default(5).describe(
|
|
4141
|
+
"Number of results to return.\n\nDefault: 5\nIncrease to 10-15 for broad exploration."
|
|
4142
|
+
)
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
// src/mcp/schemas/similarity.schema.ts
|
|
4146
|
+
import { z as z2 } from "zod";
|
|
4147
|
+
var FindSimilarSchema = z2.object({
|
|
4148
|
+
code: z2.string().min(10, "Code snippet must be at least 10 characters").describe(
|
|
4149
|
+
"Code snippet to find similar implementations for.\n\nProvide a representative code sample that demonstrates the pattern you want to find similar examples of in the codebase."
|
|
4150
|
+
),
|
|
4151
|
+
limit: z2.number().int().min(1, "Limit must be at least 1").max(20, "Limit cannot exceed 20").default(5).describe(
|
|
4152
|
+
"Number of similar code blocks to return.\n\nDefault: 5"
|
|
4153
|
+
)
|
|
4154
|
+
});
|
|
4155
|
+
|
|
4156
|
+
// src/mcp/schemas/file.schema.ts
|
|
4157
|
+
import { z as z3 } from "zod";
|
|
4158
|
+
var GetFileContextSchema = z3.object({
|
|
4159
|
+
filepath: z3.string().min(1, "Filepath cannot be empty").describe(
|
|
4160
|
+
"Relative path to file from workspace root.\n\nExample: 'src/components/Button.tsx'"
|
|
4161
|
+
),
|
|
4162
|
+
includeRelated: z3.boolean().default(true).describe(
|
|
4163
|
+
"Include semantically related chunks from nearby code.\n\nDefault: true\n\nWhen enabled, also returns related code from other files that are semantically similar to the target file's contents."
|
|
4164
|
+
)
|
|
4165
|
+
});
|
|
4166
|
+
|
|
4167
|
+
// src/mcp/schemas/symbols.schema.ts
|
|
4168
|
+
import { z as z4 } from "zod";
|
|
4169
|
+
var ListFunctionsSchema = z4.object({
|
|
4170
|
+
pattern: z4.string().optional().describe(
|
|
4171
|
+
"Regex pattern to match symbol names.\n\nExamples:\n - '.*Controller.*' to find all Controllers\n - 'handle.*' to find handlers\n - '.*Service$' to find Services\n\nIf omitted, returns all symbols."
|
|
4172
|
+
),
|
|
4173
|
+
language: z4.string().optional().describe(
|
|
4174
|
+
"Filter by programming language.\n\nExamples: 'typescript', 'python', 'javascript', 'php'\n\nIf omitted, searches all languages."
|
|
4175
|
+
)
|
|
4176
|
+
});
|
|
3022
4177
|
|
|
3023
4178
|
// src/mcp/tools.ts
|
|
3024
4179
|
var tools = [
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
name: "find_similar",
|
|
3046
|
-
description: "Find code similar to a given code snippet. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
|
|
3047
|
-
inputSchema: {
|
|
3048
|
-
type: "object",
|
|
3049
|
-
properties: {
|
|
3050
|
-
code: {
|
|
3051
|
-
type: "string",
|
|
3052
|
-
description: "Code snippet to find similar implementations"
|
|
3053
|
-
},
|
|
3054
|
-
limit: {
|
|
3055
|
-
type: "number",
|
|
3056
|
-
description: "Maximum number of results to return",
|
|
3057
|
-
default: 5
|
|
3058
|
-
}
|
|
3059
|
-
},
|
|
3060
|
-
required: ["code"]
|
|
3061
|
-
}
|
|
3062
|
-
},
|
|
3063
|
-
{
|
|
3064
|
-
name: "get_file_context",
|
|
3065
|
-
description: "Get all chunks and related context for a specific file. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity.",
|
|
3066
|
-
inputSchema: {
|
|
3067
|
-
type: "object",
|
|
3068
|
-
properties: {
|
|
3069
|
-
filepath: {
|
|
3070
|
-
type: "string",
|
|
3071
|
-
description: "Path to the file (relative to project root)"
|
|
3072
|
-
},
|
|
3073
|
-
includeRelated: {
|
|
3074
|
-
type: "boolean",
|
|
3075
|
-
description: "Include semantically related chunks from other files",
|
|
3076
|
-
default: true
|
|
3077
|
-
}
|
|
3078
|
-
},
|
|
3079
|
-
required: ["filepath"]
|
|
3080
|
-
}
|
|
3081
|
-
},
|
|
3082
|
-
{
|
|
3083
|
-
name: "list_functions",
|
|
3084
|
-
description: "List functions, classes, and interfaces by name pattern and language",
|
|
3085
|
-
inputSchema: {
|
|
3086
|
-
type: "object",
|
|
3087
|
-
properties: {
|
|
3088
|
-
pattern: {
|
|
3089
|
-
type: "string",
|
|
3090
|
-
description: 'Regex pattern to match symbol names (e.g., ".*Service$", "handle.*")'
|
|
3091
|
-
},
|
|
3092
|
-
language: {
|
|
3093
|
-
type: "string",
|
|
3094
|
-
description: 'Language filter (e.g., "typescript", "python", "php")'
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
4180
|
+
toMCPToolSchema(
|
|
4181
|
+
SemanticSearchSchema,
|
|
4182
|
+
"semantic_search",
|
|
4183
|
+
"Search the codebase semantically for relevant code using natural language. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
|
|
4184
|
+
),
|
|
4185
|
+
toMCPToolSchema(
|
|
4186
|
+
FindSimilarSchema,
|
|
4187
|
+
"find_similar",
|
|
4188
|
+
"Find code similar to a given code snippet. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
|
|
4189
|
+
),
|
|
4190
|
+
toMCPToolSchema(
|
|
4191
|
+
GetFileContextSchema,
|
|
4192
|
+
"get_file_context",
|
|
4193
|
+
"Get all chunks and related context for a specific file. Results include a relevance category (highly_relevant, relevant, loosely_related, not_relevant) based on semantic similarity."
|
|
4194
|
+
),
|
|
4195
|
+
toMCPToolSchema(
|
|
4196
|
+
ListFunctionsSchema,
|
|
4197
|
+
"list_functions",
|
|
4198
|
+
"List functions, classes, and interfaces by name pattern and language"
|
|
4199
|
+
)
|
|
3099
4200
|
];
|
|
3100
4201
|
|
|
3101
4202
|
// src/mcp/server.ts
|
|
3102
4203
|
init_lancedb();
|
|
3103
4204
|
init_local();
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
import fs12 from "fs/promises";
|
|
3107
|
-
import path12 from "path";
|
|
3108
|
-
var GitStateTracker = class {
|
|
3109
|
-
stateFile;
|
|
3110
|
-
rootDir;
|
|
3111
|
-
currentState = null;
|
|
3112
|
-
constructor(rootDir, indexPath) {
|
|
3113
|
-
this.rootDir = rootDir;
|
|
3114
|
-
this.stateFile = path12.join(indexPath, ".git-state.json");
|
|
3115
|
-
}
|
|
3116
|
-
/**
|
|
3117
|
-
* Loads the last known git state from disk.
|
|
3118
|
-
* Returns null if no state file exists (first run).
|
|
3119
|
-
*/
|
|
3120
|
-
async loadState() {
|
|
3121
|
-
try {
|
|
3122
|
-
const content = await fs12.readFile(this.stateFile, "utf-8");
|
|
3123
|
-
return JSON.parse(content);
|
|
3124
|
-
} catch {
|
|
3125
|
-
return null;
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
/**
|
|
3129
|
-
* Saves the current git state to disk.
|
|
3130
|
-
*/
|
|
3131
|
-
async saveState(state) {
|
|
3132
|
-
try {
|
|
3133
|
-
const content = JSON.stringify(state, null, 2);
|
|
3134
|
-
await fs12.writeFile(this.stateFile, content, "utf-8");
|
|
3135
|
-
} catch (error) {
|
|
3136
|
-
console.error(`[Lien] Warning: Failed to save git state: ${error}`);
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
/**
|
|
3140
|
-
* Gets the current git state from the repository.
|
|
3141
|
-
*
|
|
3142
|
-
* @returns Current git state
|
|
3143
|
-
* @throws Error if git commands fail
|
|
3144
|
-
*/
|
|
3145
|
-
async getCurrentGitState() {
|
|
3146
|
-
const branch = await getCurrentBranch(this.rootDir);
|
|
3147
|
-
const commit = await getCurrentCommit(this.rootDir);
|
|
3148
|
-
return {
|
|
3149
|
-
branch,
|
|
3150
|
-
commit,
|
|
3151
|
-
timestamp: Date.now()
|
|
3152
|
-
};
|
|
3153
|
-
}
|
|
3154
|
-
/**
|
|
3155
|
-
* Initializes the tracker by loading saved state and checking current state.
|
|
3156
|
-
* Should be called once when MCP server starts.
|
|
3157
|
-
*
|
|
3158
|
-
* @returns Array of changed files if state changed, null if no changes or first run
|
|
3159
|
-
*/
|
|
3160
|
-
async initialize() {
|
|
3161
|
-
const isRepo = await isGitRepo(this.rootDir);
|
|
3162
|
-
if (!isRepo) {
|
|
3163
|
-
return null;
|
|
3164
|
-
}
|
|
3165
|
-
try {
|
|
3166
|
-
this.currentState = await this.getCurrentGitState();
|
|
3167
|
-
const previousState = await this.loadState();
|
|
3168
|
-
if (!previousState) {
|
|
3169
|
-
await this.saveState(this.currentState);
|
|
3170
|
-
return null;
|
|
3171
|
-
}
|
|
3172
|
-
const branchChanged = previousState.branch !== this.currentState.branch;
|
|
3173
|
-
const commitChanged = previousState.commit !== this.currentState.commit;
|
|
3174
|
-
if (!branchChanged && !commitChanged) {
|
|
3175
|
-
return null;
|
|
3176
|
-
}
|
|
3177
|
-
let changedFiles = [];
|
|
3178
|
-
if (branchChanged) {
|
|
3179
|
-
try {
|
|
3180
|
-
changedFiles = await getChangedFiles(
|
|
3181
|
-
this.rootDir,
|
|
3182
|
-
previousState.branch,
|
|
3183
|
-
this.currentState.branch
|
|
3184
|
-
);
|
|
3185
|
-
} catch (error) {
|
|
3186
|
-
console.error(`[Lien] Branch diff failed, using commit diff: ${error}`);
|
|
3187
|
-
changedFiles = await getChangedFilesBetweenCommits(
|
|
3188
|
-
this.rootDir,
|
|
3189
|
-
previousState.commit,
|
|
3190
|
-
this.currentState.commit
|
|
3191
|
-
);
|
|
3192
|
-
}
|
|
3193
|
-
} else if (commitChanged) {
|
|
3194
|
-
changedFiles = await getChangedFilesBetweenCommits(
|
|
3195
|
-
this.rootDir,
|
|
3196
|
-
previousState.commit,
|
|
3197
|
-
this.currentState.commit
|
|
3198
|
-
);
|
|
3199
|
-
}
|
|
3200
|
-
await this.saveState(this.currentState);
|
|
3201
|
-
return changedFiles;
|
|
3202
|
-
} catch (error) {
|
|
3203
|
-
console.error(`[Lien] Failed to initialize git tracker: ${error}`);
|
|
3204
|
-
return null;
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
/**
|
|
3208
|
-
* Checks for git state changes since last check.
|
|
3209
|
-
* This is called periodically by the MCP server.
|
|
3210
|
-
*
|
|
3211
|
-
* @returns Array of changed files if state changed, null if no changes
|
|
3212
|
-
*/
|
|
3213
|
-
async detectChanges() {
|
|
3214
|
-
const isRepo = await isGitRepo(this.rootDir);
|
|
3215
|
-
if (!isRepo) {
|
|
3216
|
-
return null;
|
|
3217
|
-
}
|
|
3218
|
-
try {
|
|
3219
|
-
const newState = await this.getCurrentGitState();
|
|
3220
|
-
if (!this.currentState) {
|
|
3221
|
-
this.currentState = newState;
|
|
3222
|
-
await this.saveState(newState);
|
|
3223
|
-
return null;
|
|
3224
|
-
}
|
|
3225
|
-
const branchChanged = this.currentState.branch !== newState.branch;
|
|
3226
|
-
const commitChanged = this.currentState.commit !== newState.commit;
|
|
3227
|
-
if (!branchChanged && !commitChanged) {
|
|
3228
|
-
return null;
|
|
3229
|
-
}
|
|
3230
|
-
let changedFiles = [];
|
|
3231
|
-
if (branchChanged) {
|
|
3232
|
-
try {
|
|
3233
|
-
changedFiles = await getChangedFiles(
|
|
3234
|
-
this.rootDir,
|
|
3235
|
-
this.currentState.branch,
|
|
3236
|
-
newState.branch
|
|
3237
|
-
);
|
|
3238
|
-
} catch (error) {
|
|
3239
|
-
console.error(`[Lien] Branch diff failed, using commit diff: ${error}`);
|
|
3240
|
-
changedFiles = await getChangedFilesBetweenCommits(
|
|
3241
|
-
this.rootDir,
|
|
3242
|
-
this.currentState.commit,
|
|
3243
|
-
newState.commit
|
|
3244
|
-
);
|
|
3245
|
-
}
|
|
3246
|
-
} else if (commitChanged) {
|
|
3247
|
-
changedFiles = await getChangedFilesBetweenCommits(
|
|
3248
|
-
this.rootDir,
|
|
3249
|
-
this.currentState.commit,
|
|
3250
|
-
newState.commit
|
|
3251
|
-
);
|
|
3252
|
-
}
|
|
3253
|
-
this.currentState = newState;
|
|
3254
|
-
await this.saveState(newState);
|
|
3255
|
-
return changedFiles;
|
|
3256
|
-
} catch (error) {
|
|
3257
|
-
console.error(`[Lien] Failed to detect git changes: ${error}`);
|
|
3258
|
-
return null;
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
/**
|
|
3262
|
-
* Gets the current git state.
|
|
3263
|
-
* Useful for status display.
|
|
3264
|
-
*/
|
|
3265
|
-
getState() {
|
|
3266
|
-
return this.currentState;
|
|
3267
|
-
}
|
|
3268
|
-
/**
|
|
3269
|
-
* Manually updates the saved state.
|
|
3270
|
-
* Useful after manual reindexing to sync state.
|
|
3271
|
-
*/
|
|
3272
|
-
async updateState() {
|
|
3273
|
-
try {
|
|
3274
|
-
this.currentState = await this.getCurrentGitState();
|
|
3275
|
-
await this.saveState(this.currentState);
|
|
3276
|
-
} catch (error) {
|
|
3277
|
-
console.error(`[Lien] Failed to update git state: ${error}`);
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
};
|
|
3281
|
-
|
|
3282
|
-
// src/indexer/incremental.ts
|
|
3283
|
-
init_chunker();
|
|
3284
|
-
init_schema();
|
|
3285
|
-
import fs13 from "fs/promises";
|
|
3286
|
-
async function indexSingleFile(filepath, vectorDB, embeddings, config, options = {}) {
|
|
3287
|
-
const { verbose } = options;
|
|
3288
|
-
try {
|
|
3289
|
-
try {
|
|
3290
|
-
await fs13.access(filepath);
|
|
3291
|
-
} catch {
|
|
3292
|
-
if (verbose) {
|
|
3293
|
-
console.error(`[Lien] File deleted: ${filepath}`);
|
|
3294
|
-
}
|
|
3295
|
-
await vectorDB.deleteByFile(filepath);
|
|
3296
|
-
return;
|
|
3297
|
-
}
|
|
3298
|
-
const content = await fs13.readFile(filepath, "utf-8");
|
|
3299
|
-
const chunkSize = isModernConfig(config) ? config.core.chunkSize : isLegacyConfig(config) ? config.indexing.chunkSize : 75;
|
|
3300
|
-
const chunkOverlap = isModernConfig(config) ? config.core.chunkOverlap : isLegacyConfig(config) ? config.indexing.chunkOverlap : 10;
|
|
3301
|
-
const chunks = chunkFile(filepath, content, {
|
|
3302
|
-
chunkSize,
|
|
3303
|
-
chunkOverlap
|
|
3304
|
-
});
|
|
3305
|
-
if (chunks.length === 0) {
|
|
3306
|
-
if (verbose) {
|
|
3307
|
-
console.error(`[Lien] Empty file: ${filepath}`);
|
|
3308
|
-
}
|
|
3309
|
-
await vectorDB.deleteByFile(filepath);
|
|
3310
|
-
return;
|
|
3311
|
-
}
|
|
3312
|
-
const texts = chunks.map((c) => c.content);
|
|
3313
|
-
const vectors = await embeddings.embedBatch(texts);
|
|
3314
|
-
await vectorDB.updateFile(
|
|
3315
|
-
filepath,
|
|
3316
|
-
vectors,
|
|
3317
|
-
chunks.map((c) => c.metadata),
|
|
3318
|
-
texts
|
|
3319
|
-
);
|
|
3320
|
-
if (verbose) {
|
|
3321
|
-
console.error(`[Lien] \u2713 Updated ${filepath} (${chunks.length} chunks)`);
|
|
3322
|
-
}
|
|
3323
|
-
} catch (error) {
|
|
3324
|
-
console.error(`[Lien] \u26A0\uFE0F Failed to index ${filepath}: ${error}`);
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
async function indexMultipleFiles(filepaths, vectorDB, embeddings, config, options = {}) {
|
|
3328
|
-
const { verbose } = options;
|
|
3329
|
-
let successCount = 0;
|
|
3330
|
-
for (const filepath of filepaths) {
|
|
3331
|
-
try {
|
|
3332
|
-
await indexSingleFile(filepath, vectorDB, embeddings, config, options);
|
|
3333
|
-
successCount++;
|
|
3334
|
-
} catch (error) {
|
|
3335
|
-
if (verbose) {
|
|
3336
|
-
console.error(`[Lien] Failed to process ${filepath}`);
|
|
3337
|
-
}
|
|
3338
|
-
}
|
|
3339
|
-
}
|
|
3340
|
-
return successCount;
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
// src/mcp/server.ts
|
|
4205
|
+
init_tracker();
|
|
4206
|
+
init_incremental();
|
|
3344
4207
|
init_service();
|
|
4208
|
+
init_manifest();
|
|
4209
|
+
init_utils();
|
|
3345
4210
|
|
|
3346
4211
|
// src/watcher/index.ts
|
|
3347
4212
|
init_schema();
|
|
@@ -3474,14 +4339,72 @@ var FileWatcher = class {
|
|
|
3474
4339
|
|
|
3475
4340
|
// src/mcp/server.ts
|
|
3476
4341
|
init_constants();
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
4342
|
+
|
|
4343
|
+
// src/mcp/utils/tool-wrapper.ts
|
|
4344
|
+
init_errors();
|
|
4345
|
+
import { ZodError } from "zod";
|
|
4346
|
+
function wrapToolHandler(schema, handler) {
|
|
4347
|
+
return async (args) => {
|
|
4348
|
+
try {
|
|
4349
|
+
const validated = schema.parse(args);
|
|
4350
|
+
const result = await handler(validated);
|
|
4351
|
+
return {
|
|
4352
|
+
content: [{
|
|
4353
|
+
type: "text",
|
|
4354
|
+
text: JSON.stringify(result, null, 2)
|
|
4355
|
+
}]
|
|
4356
|
+
};
|
|
4357
|
+
} catch (error) {
|
|
4358
|
+
if (error instanceof ZodError) {
|
|
4359
|
+
return {
|
|
4360
|
+
isError: true,
|
|
4361
|
+
content: [{
|
|
4362
|
+
type: "text",
|
|
4363
|
+
text: JSON.stringify({
|
|
4364
|
+
error: "Invalid parameters",
|
|
4365
|
+
code: "INVALID_INPUT" /* INVALID_INPUT */,
|
|
4366
|
+
details: error.errors.map((e) => ({
|
|
4367
|
+
field: e.path.join("."),
|
|
4368
|
+
message: e.message
|
|
4369
|
+
}))
|
|
4370
|
+
}, null, 2)
|
|
4371
|
+
}]
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
if (error instanceof LienError) {
|
|
4375
|
+
return {
|
|
4376
|
+
isError: true,
|
|
4377
|
+
content: [{
|
|
4378
|
+
type: "text",
|
|
4379
|
+
text: JSON.stringify(error.toJSON(), null, 2)
|
|
4380
|
+
}]
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
console.error("Unexpected error in tool handler:", error);
|
|
4384
|
+
return {
|
|
4385
|
+
isError: true,
|
|
4386
|
+
content: [{
|
|
4387
|
+
type: "text",
|
|
4388
|
+
text: JSON.stringify({
|
|
4389
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
4390
|
+
code: "INTERNAL_ERROR" /* INTERNAL_ERROR */
|
|
4391
|
+
}, null, 2)
|
|
4392
|
+
}]
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
// src/mcp/server.ts
|
|
4399
|
+
init_errors();
|
|
4400
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
4401
|
+
var __dirname4 = dirname3(__filename4);
|
|
4402
|
+
var require4 = createRequire3(import.meta.url);
|
|
4403
|
+
var packageJson3;
|
|
3481
4404
|
try {
|
|
3482
|
-
|
|
4405
|
+
packageJson3 = require4(join3(__dirname4, "../package.json"));
|
|
3483
4406
|
} catch {
|
|
3484
|
-
|
|
4407
|
+
packageJson3 = require4(join3(__dirname4, "../../package.json"));
|
|
3485
4408
|
}
|
|
3486
4409
|
async function startMCPServer(options) {
|
|
3487
4410
|
const { rootDir, verbose, watch } = options;
|
|
@@ -3506,7 +4429,7 @@ async function startMCPServer(options) {
|
|
|
3506
4429
|
const server = new Server(
|
|
3507
4430
|
{
|
|
3508
4431
|
name: "lien",
|
|
3509
|
-
version:
|
|
4432
|
+
version: packageJson3.version
|
|
3510
4433
|
},
|
|
3511
4434
|
{
|
|
3512
4435
|
capabilities: {
|
|
@@ -3538,148 +4461,139 @@ async function startMCPServer(options) {
|
|
|
3538
4461
|
}, VERSION_CHECK_INTERVAL_MS);
|
|
3539
4462
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
3540
4463
|
const { name, arguments: args } = request.params;
|
|
4464
|
+
log(`Handling tool call: ${name}`);
|
|
3541
4465
|
try {
|
|
3542
|
-
log(`Handling tool call: ${name}`);
|
|
3543
4466
|
switch (name) {
|
|
3544
|
-
case "semantic_search":
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
const fileChunks = allResults.filter(
|
|
3594
|
-
(r) => r.metadata.file.includes(filepath) || filepath.includes(r.metadata.file)
|
|
3595
|
-
);
|
|
3596
|
-
let results = fileChunks;
|
|
3597
|
-
if (includeRelated && fileChunks.length > 0) {
|
|
3598
|
-
const relatedEmbedding = await embeddings.embed(fileChunks[0].content);
|
|
3599
|
-
const related = await vectorDB.search(relatedEmbedding, 5, fileChunks[0].content);
|
|
3600
|
-
const relatedOtherFiles = related.filter(
|
|
3601
|
-
(r) => !r.metadata.file.includes(filepath) && !filepath.includes(r.metadata.file)
|
|
3602
|
-
);
|
|
3603
|
-
results = [...fileChunks, ...relatedOtherFiles];
|
|
3604
|
-
}
|
|
3605
|
-
log(`Found ${results.length} chunks`);
|
|
3606
|
-
const response = {
|
|
3607
|
-
indexInfo: getIndexMetadata(),
|
|
3608
|
-
file: filepath,
|
|
3609
|
-
chunks: results
|
|
3610
|
-
};
|
|
3611
|
-
return {
|
|
3612
|
-
content: [
|
|
3613
|
-
{
|
|
3614
|
-
type: "text",
|
|
3615
|
-
text: JSON.stringify(response, null, 2)
|
|
4467
|
+
case "semantic_search":
|
|
4468
|
+
return await wrapToolHandler(
|
|
4469
|
+
SemanticSearchSchema,
|
|
4470
|
+
async (validatedArgs) => {
|
|
4471
|
+
log(`Searching for: "${validatedArgs.query}"`);
|
|
4472
|
+
await checkAndReconnect();
|
|
4473
|
+
const queryEmbedding = await embeddings.embed(validatedArgs.query);
|
|
4474
|
+
const results = await vectorDB.search(queryEmbedding, validatedArgs.limit, validatedArgs.query);
|
|
4475
|
+
log(`Found ${results.length} results`);
|
|
4476
|
+
return {
|
|
4477
|
+
indexInfo: getIndexMetadata(),
|
|
4478
|
+
results
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
)(args);
|
|
4482
|
+
case "find_similar":
|
|
4483
|
+
return await wrapToolHandler(
|
|
4484
|
+
FindSimilarSchema,
|
|
4485
|
+
async (validatedArgs) => {
|
|
4486
|
+
log(`Finding similar code...`);
|
|
4487
|
+
await checkAndReconnect();
|
|
4488
|
+
const codeEmbedding = await embeddings.embed(validatedArgs.code);
|
|
4489
|
+
const results = await vectorDB.search(codeEmbedding, validatedArgs.limit, validatedArgs.code);
|
|
4490
|
+
log(`Found ${results.length} similar chunks`);
|
|
4491
|
+
return {
|
|
4492
|
+
indexInfo: getIndexMetadata(),
|
|
4493
|
+
results
|
|
4494
|
+
};
|
|
4495
|
+
}
|
|
4496
|
+
)(args);
|
|
4497
|
+
case "get_file_context":
|
|
4498
|
+
return await wrapToolHandler(
|
|
4499
|
+
GetFileContextSchema,
|
|
4500
|
+
async (validatedArgs) => {
|
|
4501
|
+
log(`Getting context for: ${validatedArgs.filepath}`);
|
|
4502
|
+
await checkAndReconnect();
|
|
4503
|
+
const fileEmbedding = await embeddings.embed(validatedArgs.filepath);
|
|
4504
|
+
const allResults = await vectorDB.search(fileEmbedding, 50, validatedArgs.filepath);
|
|
4505
|
+
const fileChunks = allResults.filter(
|
|
4506
|
+
(r) => r.metadata.file.includes(validatedArgs.filepath) || validatedArgs.filepath.includes(r.metadata.file)
|
|
4507
|
+
);
|
|
4508
|
+
let results = fileChunks;
|
|
4509
|
+
if (validatedArgs.includeRelated && fileChunks.length > 0) {
|
|
4510
|
+
const relatedEmbedding = await embeddings.embed(fileChunks[0].content);
|
|
4511
|
+
const related = await vectorDB.search(relatedEmbedding, 5, fileChunks[0].content);
|
|
4512
|
+
const relatedOtherFiles = related.filter(
|
|
4513
|
+
(r) => !r.metadata.file.includes(validatedArgs.filepath) && !validatedArgs.filepath.includes(r.metadata.file)
|
|
4514
|
+
);
|
|
4515
|
+
results = [...fileChunks, ...relatedOtherFiles];
|
|
3616
4516
|
}
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
log("Listing functions with symbol metadata...");
|
|
3624
|
-
await checkAndReconnect();
|
|
3625
|
-
let results;
|
|
3626
|
-
let usedMethod = "symbols";
|
|
3627
|
-
try {
|
|
3628
|
-
results = await vectorDB.querySymbols({
|
|
3629
|
-
language,
|
|
3630
|
-
pattern,
|
|
3631
|
-
limit: 50
|
|
3632
|
-
});
|
|
3633
|
-
if (results.length === 0 && (language || pattern)) {
|
|
3634
|
-
log("No symbol results, falling back to content scan...");
|
|
3635
|
-
results = await vectorDB.scanWithFilter({
|
|
3636
|
-
language,
|
|
3637
|
-
pattern,
|
|
3638
|
-
limit: 50
|
|
3639
|
-
});
|
|
3640
|
-
usedMethod = "content";
|
|
4517
|
+
log(`Found ${results.length} chunks`);
|
|
4518
|
+
return {
|
|
4519
|
+
indexInfo: getIndexMetadata(),
|
|
4520
|
+
file: validatedArgs.filepath,
|
|
4521
|
+
chunks: results
|
|
4522
|
+
};
|
|
3641
4523
|
}
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
4524
|
+
)(args);
|
|
4525
|
+
case "list_functions":
|
|
4526
|
+
return await wrapToolHandler(
|
|
4527
|
+
ListFunctionsSchema,
|
|
4528
|
+
async (validatedArgs) => {
|
|
4529
|
+
log("Listing functions with symbol metadata...");
|
|
4530
|
+
await checkAndReconnect();
|
|
4531
|
+
let results;
|
|
4532
|
+
let usedMethod = "symbols";
|
|
4533
|
+
try {
|
|
4534
|
+
results = await vectorDB.querySymbols({
|
|
4535
|
+
language: validatedArgs.language,
|
|
4536
|
+
pattern: validatedArgs.pattern,
|
|
4537
|
+
limit: 50
|
|
4538
|
+
});
|
|
4539
|
+
if (results.length === 0 && (validatedArgs.language || validatedArgs.pattern)) {
|
|
4540
|
+
log("No symbol results, falling back to content scan...");
|
|
4541
|
+
results = await vectorDB.scanWithFilter({
|
|
4542
|
+
language: validatedArgs.language,
|
|
4543
|
+
pattern: validatedArgs.pattern,
|
|
4544
|
+
limit: 50
|
|
4545
|
+
});
|
|
4546
|
+
usedMethod = "content";
|
|
4547
|
+
}
|
|
4548
|
+
} catch (error) {
|
|
4549
|
+
log(`Symbol query failed, falling back to content scan: ${error}`);
|
|
4550
|
+
results = await vectorDB.scanWithFilter({
|
|
4551
|
+
language: validatedArgs.language,
|
|
4552
|
+
pattern: validatedArgs.pattern,
|
|
4553
|
+
limit: 50
|
|
4554
|
+
});
|
|
4555
|
+
usedMethod = "content";
|
|
3663
4556
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
4557
|
+
log(`Found ${results.length} matches using ${usedMethod} method`);
|
|
4558
|
+
return {
|
|
4559
|
+
indexInfo: getIndexMetadata(),
|
|
4560
|
+
method: usedMethod,
|
|
4561
|
+
results,
|
|
4562
|
+
note: usedMethod === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
|
|
4563
|
+
};
|
|
4564
|
+
}
|
|
4565
|
+
)(args);
|
|
3667
4566
|
default:
|
|
3668
|
-
throw new
|
|
4567
|
+
throw new LienError(
|
|
4568
|
+
`Unknown tool: ${name}`,
|
|
4569
|
+
"INVALID_INPUT" /* INVALID_INPUT */,
|
|
4570
|
+
{ requestedTool: name, availableTools: tools.map((t) => t.name) },
|
|
4571
|
+
"medium",
|
|
4572
|
+
false,
|
|
4573
|
+
false
|
|
4574
|
+
);
|
|
3669
4575
|
}
|
|
3670
4576
|
} catch (error) {
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
{
|
|
4577
|
+
if (error instanceof LienError) {
|
|
4578
|
+
return {
|
|
4579
|
+
isError: true,
|
|
4580
|
+
content: [{
|
|
3675
4581
|
type: "text",
|
|
3676
|
-
text: JSON.stringify(
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
isError: true
|
|
4582
|
+
text: JSON.stringify(error.toJSON(), null, 2)
|
|
4583
|
+
}]
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
console.error(`Unexpected error handling tool call ${name}:`, error);
|
|
4587
|
+
return {
|
|
4588
|
+
isError: true,
|
|
4589
|
+
content: [{
|
|
4590
|
+
type: "text",
|
|
4591
|
+
text: JSON.stringify({
|
|
4592
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
4593
|
+
code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
|
|
4594
|
+
tool: name
|
|
4595
|
+
}, null, 2)
|
|
4596
|
+
}]
|
|
3683
4597
|
};
|
|
3684
4598
|
}
|
|
3685
4599
|
});
|
|
@@ -3762,7 +4676,7 @@ async function startMCPServer(options) {
|
|
|
3762
4676
|
} else {
|
|
3763
4677
|
log("Git detection disabled by configuration");
|
|
3764
4678
|
}
|
|
3765
|
-
const fileWatchingEnabled = watch
|
|
4679
|
+
const fileWatchingEnabled = watch !== void 0 ? watch : config.fileWatching.enabled;
|
|
3766
4680
|
if (fileWatchingEnabled) {
|
|
3767
4681
|
log("\u{1F440} Starting file watcher...");
|
|
3768
4682
|
fileWatcher = new FileWatcher(rootDir, config);
|
|
@@ -3773,6 +4687,8 @@ async function startMCPServer(options) {
|
|
|
3773
4687
|
log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
|
|
3774
4688
|
try {
|
|
3775
4689
|
await vectorDB.deleteByFile(filepath);
|
|
4690
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
4691
|
+
await manifest.removeFile(filepath);
|
|
3776
4692
|
log(`\u2713 Removed ${filepath} from index`);
|
|
3777
4693
|
} catch (error) {
|
|
3778
4694
|
log(`Warning: Failed to remove ${filepath}: ${error}`);
|
|
@@ -3818,13 +4734,12 @@ async function startMCPServer(options) {
|
|
|
3818
4734
|
}
|
|
3819
4735
|
|
|
3820
4736
|
// src/cli/serve.ts
|
|
3821
|
-
init_banner();
|
|
3822
4737
|
async function serveCommand(options) {
|
|
3823
|
-
const rootDir = options.root ?
|
|
4738
|
+
const rootDir = options.root ? path14.resolve(options.root) : process.cwd();
|
|
3824
4739
|
try {
|
|
3825
4740
|
if (options.root) {
|
|
3826
4741
|
try {
|
|
3827
|
-
const stats = await
|
|
4742
|
+
const stats = await fs16.stat(rootDir);
|
|
3828
4743
|
if (!stats.isDirectory()) {
|
|
3829
4744
|
console.error(chalk6.red(`Error: --root path is not a directory: ${rootDir}`));
|
|
3830
4745
|
process.exit(1);
|
|
@@ -3847,10 +4762,15 @@ async function serveCommand(options) {
|
|
|
3847
4762
|
console.error(chalk6.dim(`Serving from: ${rootDir}
|
|
3848
4763
|
`));
|
|
3849
4764
|
}
|
|
4765
|
+
if (options.watch) {
|
|
4766
|
+
console.error(chalk6.yellow("\u26A0\uFE0F --watch flag is deprecated (file watching is now default)"));
|
|
4767
|
+
console.error(chalk6.dim(" Use --no-watch to disable file watching\n"));
|
|
4768
|
+
}
|
|
4769
|
+
const watch = options.noWatch ? false : options.watch ? true : void 0;
|
|
3850
4770
|
await startMCPServer({
|
|
3851
4771
|
rootDir,
|
|
3852
4772
|
verbose: true,
|
|
3853
|
-
watch
|
|
4773
|
+
watch
|
|
3854
4774
|
});
|
|
3855
4775
|
} catch (error) {
|
|
3856
4776
|
console.error(chalk6.red("Failed to start MCP server:"), error);
|
|
@@ -3859,42 +4779,21 @@ async function serveCommand(options) {
|
|
|
3859
4779
|
}
|
|
3860
4780
|
|
|
3861
4781
|
// src/cli/index.ts
|
|
3862
|
-
var
|
|
3863
|
-
var
|
|
3864
|
-
var
|
|
3865
|
-
var
|
|
4782
|
+
var __filename5 = fileURLToPath5(import.meta.url);
|
|
4783
|
+
var __dirname5 = dirname4(__filename5);
|
|
4784
|
+
var require5 = createRequire4(import.meta.url);
|
|
4785
|
+
var packageJson4;
|
|
3866
4786
|
try {
|
|
3867
|
-
|
|
4787
|
+
packageJson4 = require5(join4(__dirname5, "../package.json"));
|
|
3868
4788
|
} catch {
|
|
3869
|
-
|
|
4789
|
+
packageJson4 = require5(join4(__dirname5, "../../package.json"));
|
|
3870
4790
|
}
|
|
3871
4791
|
var program = new Command();
|
|
3872
|
-
program.name("lien").description("Local semantic code search for AI assistants via MCP").version(
|
|
4792
|
+
program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson4.version);
|
|
3873
4793
|
program.command("init").description("Initialize Lien in the current directory").option("-u, --upgrade", "Upgrade existing config with new options").option("-y, --yes", "Skip interactive prompts and use defaults").option("-p, --path <path>", "Path to initialize (defaults to current directory)").action(initCommand);
|
|
3874
|
-
program.command("index").description("Index the codebase for semantic search").option("-w, --watch", "Watch for changes and re-index automatically").option("-v, --verbose", "Show detailed logging during indexing").action(indexCommand);
|
|
3875
|
-
program.command("serve").description("Start the MCP server for Cursor integration").option("-p, --port <port>", "Port number (for future use)", "7133").option("-w, --watch", "
|
|
4794
|
+
program.command("index").description("Index the codebase for semantic search").option("-f, --force", "Force full reindex (skip incremental)").option("-w, --watch", "Watch for changes and re-index automatically").option("-v, --verbose", "Show detailed logging during indexing").action(indexCommand);
|
|
4795
|
+
program.command("serve").description("Start the MCP server for Cursor integration").option("-p, --port <port>", "Port number (for future use)", "7133").option("--no-watch", "Disable file watching for this session").option("-w, --watch", "[DEPRECATED] File watching is now enabled by default").option("-r, --root <path>", "Root directory to serve (defaults to current directory)").action(serveCommand);
|
|
3876
4796
|
program.command("status").description("Show indexing status and statistics").action(statusCommand);
|
|
3877
|
-
program.command("reindex").description("Clear index and re-index the entire codebase").option("-v, --verbose", "Show detailed logging during indexing").action(async (options) => {
|
|
3878
|
-
const { showCompactBanner: showCompactBanner2 } = await Promise.resolve().then(() => (init_banner(), banner_exports));
|
|
3879
|
-
const chalk7 = (await import("chalk")).default;
|
|
3880
|
-
const { VectorDB: VectorDB2 } = await Promise.resolve().then(() => (init_lancedb(), lancedb_exports));
|
|
3881
|
-
const { indexCodebase: indexCodebase2 } = await Promise.resolve().then(() => (init_indexer(), indexer_exports));
|
|
3882
|
-
showCompactBanner2();
|
|
3883
|
-
try {
|
|
3884
|
-
console.log(chalk7.yellow("Clearing existing index..."));
|
|
3885
|
-
const vectorDB = new VectorDB2(process.cwd());
|
|
3886
|
-
await vectorDB.initialize();
|
|
3887
|
-
await vectorDB.clear();
|
|
3888
|
-
console.log(chalk7.green("\u2713 Index cleared\n"));
|
|
3889
|
-
await indexCodebase2({
|
|
3890
|
-
rootDir: process.cwd(),
|
|
3891
|
-
verbose: options.verbose || false
|
|
3892
|
-
});
|
|
3893
|
-
} catch (error) {
|
|
3894
|
-
console.error(chalk7.red("Error during re-indexing:"), error);
|
|
3895
|
-
process.exit(1);
|
|
3896
|
-
}
|
|
3897
|
-
});
|
|
3898
4797
|
|
|
3899
4798
|
// src/index.ts
|
|
3900
4799
|
program.parse();
|