lens-engine 0.1.16 → 0.1.18
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/cli.js +284 -24
- package/daemon.js +914 -343
- package/dashboard/assets/index-B-FwfnUH.css +1 -0
- package/dashboard/assets/index-Dpgp0uUn.js +341 -0
- package/dashboard/index.html +2 -2
- package/package.json +1 -1
- package/dashboard/assets/index-DDXq5eat.js +0 -232
- package/dashboard/assets/index-UUQ9jgzS.css +0 -1
package/daemon.js
CHANGED
|
@@ -7605,6 +7605,7 @@ var init_esm2 = __esm({
|
|
|
7605
7605
|
var dist_exports = {};
|
|
7606
7606
|
__export(dist_exports, {
|
|
7607
7607
|
DEFAULT_CHUNKING_PARAMS: () => DEFAULT_CHUNKING_PARAMS,
|
|
7608
|
+
RequestTrace: () => RequestTrace,
|
|
7608
7609
|
agglomerativeCluster: () => agglomerativeCluster,
|
|
7609
7610
|
analyzeGitHistory: () => analyzeGitHistory,
|
|
7610
7611
|
buildAndPersistImportGraph: () => buildAndPersistImportGraph,
|
|
@@ -7659,6 +7660,7 @@ __export(dist_exports, {
|
|
|
7659
7660
|
resolveImport: () => resolveImport,
|
|
7660
7661
|
runIndex: () => runIndex,
|
|
7661
7662
|
setTelemetryEnabled: () => setTelemetryEnabled,
|
|
7663
|
+
settingsQueries: () => settingsQueries,
|
|
7662
7664
|
startWatcher: () => startWatcher,
|
|
7663
7665
|
statsQueries: () => statsQueries,
|
|
7664
7666
|
stopWatcher: () => stopWatcher,
|
|
@@ -7722,6 +7724,18 @@ function openDb(customPath) {
|
|
|
7722
7724
|
);
|
|
7723
7725
|
if (!cols.has("sections")) sqlite.exec("ALTER TABLE file_metadata ADD COLUMN sections TEXT DEFAULT '[]'");
|
|
7724
7726
|
if (!cols.has("internals")) sqlite.exec("ALTER TABLE file_metadata ADD COLUMN internals TEXT DEFAULT '[]'");
|
|
7727
|
+
const repoCols = new Set(
|
|
7728
|
+
sqlite.pragma("table_info(repos)").map((c) => c.name)
|
|
7729
|
+
);
|
|
7730
|
+
if (!repoCols.has("enable_embeddings")) sqlite.exec("ALTER TABLE repos ADD COLUMN enable_embeddings INTEGER NOT NULL DEFAULT 1");
|
|
7731
|
+
if (!repoCols.has("enable_summaries")) sqlite.exec("ALTER TABLE repos ADD COLUMN enable_summaries INTEGER NOT NULL DEFAULT 1");
|
|
7732
|
+
if (!repoCols.has("enable_vocab_clusters")) sqlite.exec("ALTER TABLE repos ADD COLUMN enable_vocab_clusters INTEGER NOT NULL DEFAULT 1");
|
|
7733
|
+
if (!repoCols.has("last_vocab_cluster_commit")) sqlite.exec("ALTER TABLE repos ADD COLUMN last_vocab_cluster_commit TEXT");
|
|
7734
|
+
const logCols = new Set(
|
|
7735
|
+
sqlite.pragma("table_info(request_logs)").map((c) => c.name)
|
|
7736
|
+
);
|
|
7737
|
+
if (!logCols.has("response_body")) sqlite.exec("ALTER TABLE request_logs ADD COLUMN response_body TEXT");
|
|
7738
|
+
if (!logCols.has("trace")) sqlite.exec("ALTER TABLE request_logs ADD COLUMN trace TEXT");
|
|
7725
7739
|
_db = db;
|
|
7726
7740
|
_raw = sqlite;
|
|
7727
7741
|
return db;
|
|
@@ -7753,6 +7767,10 @@ function createTablesSql() {
|
|
|
7753
7767
|
last_git_analysis_commit TEXT,
|
|
7754
7768
|
max_import_depth INTEGER DEFAULT 0,
|
|
7755
7769
|
vocab_clusters TEXT,
|
|
7770
|
+
last_vocab_cluster_commit TEXT,
|
|
7771
|
+
enable_embeddings INTEGER NOT NULL DEFAULT 1,
|
|
7772
|
+
enable_summaries INTEGER NOT NULL DEFAULT 1,
|
|
7773
|
+
enable_vocab_clusters INTEGER NOT NULL DEFAULT 1,
|
|
7756
7774
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7757
7775
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7758
7776
|
);
|
|
@@ -7847,11 +7865,19 @@ function createTablesSql() {
|
|
|
7847
7865
|
source TEXT NOT NULL DEFAULT 'api',
|
|
7848
7866
|
request_body TEXT,
|
|
7849
7867
|
response_size INTEGER,
|
|
7868
|
+
response_body TEXT,
|
|
7869
|
+
trace TEXT,
|
|
7850
7870
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7851
7871
|
);
|
|
7852
7872
|
CREATE INDEX IF NOT EXISTS idx_request_logs_created ON request_logs(created_at);
|
|
7853
7873
|
CREATE INDEX IF NOT EXISTS idx_request_logs_source ON request_logs(source);
|
|
7854
7874
|
|
|
7875
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
7876
|
+
key TEXT PRIMARY KEY,
|
|
7877
|
+
value TEXT NOT NULL,
|
|
7878
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7879
|
+
);
|
|
7880
|
+
|
|
7855
7881
|
CREATE TABLE IF NOT EXISTS telemetry_events (
|
|
7856
7882
|
id TEXT PRIMARY KEY,
|
|
7857
7883
|
event_type TEXT NOT NULL,
|
|
@@ -8206,9 +8232,15 @@ function extractExports(content, language) {
|
|
|
8206
8232
|
case "rust":
|
|
8207
8233
|
re = new RegExp(RUST_EXPORT_RE.source, "gm");
|
|
8208
8234
|
break;
|
|
8209
|
-
case "csharp":
|
|
8210
|
-
|
|
8211
|
-
|
|
8235
|
+
case "csharp": {
|
|
8236
|
+
const exports2 = [];
|
|
8237
|
+
for (const pattern of [CSHARP_EXPORT_RE, CSHARP_EXPORT_METHOD_RE]) {
|
|
8238
|
+
for (const m of content.matchAll(new RegExp(pattern.source, "gm"))) {
|
|
8239
|
+
if (m[1] && !exports2.includes(m[1])) exports2.push(m[1]);
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
return exports2.slice(0, 30);
|
|
8243
|
+
}
|
|
8212
8244
|
case "java":
|
|
8213
8245
|
case "kotlin":
|
|
8214
8246
|
re = new RegExp(JAVA_EXPORT_RE.source, "gm");
|
|
@@ -8239,6 +8271,15 @@ function extractDocstring(content, language) {
|
|
|
8239
8271
|
case "go":
|
|
8240
8272
|
m = content.match(GO_PKG_RE);
|
|
8241
8273
|
break;
|
|
8274
|
+
case "rust": {
|
|
8275
|
+
const rustMatch = content.match(RUST_DOC_RE);
|
|
8276
|
+
if (!rustMatch) return "";
|
|
8277
|
+
return rustMatch[0].replace(/^\s*\/\/!\s?/gm, "").replace(/\n/g, " ").trim().slice(0, 200);
|
|
8278
|
+
}
|
|
8279
|
+
case "java":
|
|
8280
|
+
case "kotlin":
|
|
8281
|
+
m = content.match(JSDOC_RE);
|
|
8282
|
+
break;
|
|
8242
8283
|
default:
|
|
8243
8284
|
return "";
|
|
8244
8285
|
}
|
|
@@ -8260,33 +8301,69 @@ function extractSections(content) {
|
|
|
8260
8301
|
}
|
|
8261
8302
|
return sections.slice(0, 15);
|
|
8262
8303
|
}
|
|
8263
|
-
function
|
|
8264
|
-
switch (language) {
|
|
8265
|
-
case "typescript":
|
|
8266
|
-
case "javascript":
|
|
8267
|
-
case "tsx":
|
|
8268
|
-
case "jsx":
|
|
8269
|
-
break;
|
|
8270
|
-
default:
|
|
8271
|
-
return [];
|
|
8272
|
-
}
|
|
8273
|
-
const exportSet = new Set(exports);
|
|
8304
|
+
function collectMatches(content, regexes, exportSet, skipLine, skipName) {
|
|
8274
8305
|
const seen = /* @__PURE__ */ new Set();
|
|
8275
|
-
const
|
|
8306
|
+
const results = [];
|
|
8276
8307
|
for (const line of content.split("\n")) {
|
|
8277
|
-
if (line
|
|
8278
|
-
for (const re of
|
|
8308
|
+
if (skipLine?.(line)) continue;
|
|
8309
|
+
for (const re of regexes) {
|
|
8279
8310
|
const pattern = new RegExp(re.source, re.flags);
|
|
8280
8311
|
for (const m of line.matchAll(pattern)) {
|
|
8281
8312
|
const name = m[1];
|
|
8282
|
-
if (name && name.length >= 6 && !exportSet.has(name) && !seen.has(name)) {
|
|
8313
|
+
if (name && name.length >= 6 && !exportSet.has(name) && !seen.has(name) && !skipName?.(name)) {
|
|
8283
8314
|
seen.add(name);
|
|
8284
|
-
|
|
8315
|
+
results.push(name);
|
|
8285
8316
|
}
|
|
8286
8317
|
}
|
|
8287
8318
|
}
|
|
8288
8319
|
}
|
|
8289
|
-
return
|
|
8320
|
+
return results.slice(0, 20);
|
|
8321
|
+
}
|
|
8322
|
+
function extractInternals(content, language, exports) {
|
|
8323
|
+
const exportSet = new Set(exports);
|
|
8324
|
+
switch (language) {
|
|
8325
|
+
case "typescript":
|
|
8326
|
+
case "javascript":
|
|
8327
|
+
case "tsx":
|
|
8328
|
+
case "jsx":
|
|
8329
|
+
return collectMatches(
|
|
8330
|
+
content,
|
|
8331
|
+
[TS_INTERNAL_FN_RE, TS_INTERNAL_CONST_RE],
|
|
8332
|
+
exportSet,
|
|
8333
|
+
(line) => line.trimStart().startsWith("export")
|
|
8334
|
+
);
|
|
8335
|
+
case "go":
|
|
8336
|
+
return collectMatches(content, GO_INTERNAL_RES, exportSet);
|
|
8337
|
+
case "python":
|
|
8338
|
+
return collectMatches(
|
|
8339
|
+
content,
|
|
8340
|
+
PY_INTERNAL_RES,
|
|
8341
|
+
exportSet,
|
|
8342
|
+
void 0,
|
|
8343
|
+
(name) => name.startsWith("_")
|
|
8344
|
+
);
|
|
8345
|
+
case "rust":
|
|
8346
|
+
return collectMatches(
|
|
8347
|
+
content,
|
|
8348
|
+
RUST_INTERNAL_RES,
|
|
8349
|
+
exportSet,
|
|
8350
|
+
(line) => /^\s*pub\s/.test(line)
|
|
8351
|
+
);
|
|
8352
|
+
case "csharp":
|
|
8353
|
+
return collectMatches(
|
|
8354
|
+
content,
|
|
8355
|
+
CSHARP_INTERNAL_RES,
|
|
8356
|
+
exportSet,
|
|
8357
|
+
(line) => /^\s*public\s/.test(line),
|
|
8358
|
+
(name) => /^(?:if|for|foreach|while|switch|using|catch|lock|return|throw|yield|try|do|else|new|await|base|this)$/.test(name)
|
|
8359
|
+
);
|
|
8360
|
+
case "java":
|
|
8361
|
+
return collectMatches(content, JAVA_INTERNAL_RES, exportSet);
|
|
8362
|
+
case "kotlin":
|
|
8363
|
+
return collectMatches(content, KOTLIN_INTERNAL_RES, exportSet);
|
|
8364
|
+
default:
|
|
8365
|
+
return [];
|
|
8366
|
+
}
|
|
8290
8367
|
}
|
|
8291
8368
|
function extractFileMetadata(path, content, language) {
|
|
8292
8369
|
const exports = extractExports(content, language);
|
|
@@ -8317,7 +8394,17 @@ function extractAndPersistMetadata(db, repoId) {
|
|
|
8317
8394
|
let count = 0;
|
|
8318
8395
|
for (const [path, { content, language }] of files) {
|
|
8319
8396
|
const meta = extractFileMetadata(path, content, language);
|
|
8320
|
-
metadataQueries.upsert(
|
|
8397
|
+
metadataQueries.upsert(
|
|
8398
|
+
db,
|
|
8399
|
+
repoId,
|
|
8400
|
+
path,
|
|
8401
|
+
language,
|
|
8402
|
+
meta.exports,
|
|
8403
|
+
meta.imports,
|
|
8404
|
+
meta.docstring,
|
|
8405
|
+
meta.sections,
|
|
8406
|
+
meta.internals
|
|
8407
|
+
);
|
|
8321
8408
|
count++;
|
|
8322
8409
|
}
|
|
8323
8410
|
return count;
|
|
@@ -8351,140 +8438,6 @@ function buildAndPersistImportGraph(db, repoId) {
|
|
|
8351
8438
|
}
|
|
8352
8439
|
return edgeCount;
|
|
8353
8440
|
}
|
|
8354
|
-
function splitIdentifier(name) {
|
|
8355
|
-
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_\-./\\]/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length >= 3 && !STOPWORDS.has(w));
|
|
8356
|
-
}
|
|
8357
|
-
function extractVocab(files) {
|
|
8358
|
-
const termToFiles = /* @__PURE__ */ new Map();
|
|
8359
|
-
for (const f of files) {
|
|
8360
|
-
const pathParts = f.path.split("/");
|
|
8361
|
-
for (const seg of pathParts) {
|
|
8362
|
-
const base = seg.replace(/\.[^.]+$/, "");
|
|
8363
|
-
for (const word of splitIdentifier(base)) {
|
|
8364
|
-
const s = termToFiles.get(word) ?? /* @__PURE__ */ new Set();
|
|
8365
|
-
s.add(f.path);
|
|
8366
|
-
termToFiles.set(word, s);
|
|
8367
|
-
}
|
|
8368
|
-
}
|
|
8369
|
-
for (const exp of f.exports) {
|
|
8370
|
-
for (const word of splitIdentifier(exp)) {
|
|
8371
|
-
const s = termToFiles.get(word) ?? /* @__PURE__ */ new Set();
|
|
8372
|
-
s.add(f.path);
|
|
8373
|
-
termToFiles.set(word, s);
|
|
8374
|
-
}
|
|
8375
|
-
}
|
|
8376
|
-
}
|
|
8377
|
-
const totalFiles = files.length;
|
|
8378
|
-
const maxDf = Math.max(10, Math.floor(totalFiles * 0.3));
|
|
8379
|
-
const filtered = [];
|
|
8380
|
-
for (const [term, fileSet] of termToFiles) {
|
|
8381
|
-
if (fileSet.size >= 2 && fileSet.size <= maxDf) {
|
|
8382
|
-
filtered.push(term);
|
|
8383
|
-
}
|
|
8384
|
-
}
|
|
8385
|
-
filtered.sort((a, b) => (termToFiles.get(b)?.size ?? 0) - (termToFiles.get(a)?.size ?? 0));
|
|
8386
|
-
const terms = filtered.slice(0, MAX_TERMS);
|
|
8387
|
-
return { terms, termToFiles };
|
|
8388
|
-
}
|
|
8389
|
-
function cosine(a, b) {
|
|
8390
|
-
let dot = 0, na = 0, nb = 0;
|
|
8391
|
-
for (let i = 0; i < a.length; i++) {
|
|
8392
|
-
dot += a[i] * b[i];
|
|
8393
|
-
na += a[i] * a[i];
|
|
8394
|
-
nb += b[i] * b[i];
|
|
8395
|
-
}
|
|
8396
|
-
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
8397
|
-
return denom > 0 ? dot / denom : 0;
|
|
8398
|
-
}
|
|
8399
|
-
function agglomerativeCluster(terms, embeddings) {
|
|
8400
|
-
const n = terms.length;
|
|
8401
|
-
const pairs = [];
|
|
8402
|
-
for (let i = 0; i < n; i++) {
|
|
8403
|
-
for (let j = i + 1; j < n; j++) {
|
|
8404
|
-
const sim = cosine(embeddings[i], embeddings[j]);
|
|
8405
|
-
if (sim >= SIMILARITY_THRESHOLD) pairs.push({ i, j, sim });
|
|
8406
|
-
}
|
|
8407
|
-
}
|
|
8408
|
-
pairs.sort((a, b) => b.sim - a.sim);
|
|
8409
|
-
const parent = new Int32Array(n);
|
|
8410
|
-
const size = new Uint16Array(n);
|
|
8411
|
-
for (let i = 0; i < n; i++) {
|
|
8412
|
-
parent[i] = i;
|
|
8413
|
-
size[i] = 1;
|
|
8414
|
-
}
|
|
8415
|
-
function find(x) {
|
|
8416
|
-
while (parent[x] !== x) {
|
|
8417
|
-
parent[x] = parent[parent[x]];
|
|
8418
|
-
x = parent[x];
|
|
8419
|
-
}
|
|
8420
|
-
return x;
|
|
8421
|
-
}
|
|
8422
|
-
for (const { i, j } of pairs) {
|
|
8423
|
-
const ri = find(i), rj = find(j);
|
|
8424
|
-
if (ri === rj) continue;
|
|
8425
|
-
if (size[ri] + size[rj] > MAX_CLUSTER_SIZE) continue;
|
|
8426
|
-
if (size[ri] >= size[rj]) {
|
|
8427
|
-
parent[rj] = ri;
|
|
8428
|
-
size[ri] += size[rj];
|
|
8429
|
-
} else {
|
|
8430
|
-
parent[ri] = rj;
|
|
8431
|
-
size[rj] += size[ri];
|
|
8432
|
-
}
|
|
8433
|
-
}
|
|
8434
|
-
const clusterMap = /* @__PURE__ */ new Map();
|
|
8435
|
-
for (let i = 0; i < n; i++) {
|
|
8436
|
-
const root = find(i);
|
|
8437
|
-
const arr = clusterMap.get(root) ?? [];
|
|
8438
|
-
arr.push(i);
|
|
8439
|
-
clusterMap.set(root, arr);
|
|
8440
|
-
}
|
|
8441
|
-
return [...clusterMap.values()].filter((c) => c.length >= 2);
|
|
8442
|
-
}
|
|
8443
|
-
async function buildVocabClusters(db, repoId, caps) {
|
|
8444
|
-
if (!caps?.embedTexts) {
|
|
8445
|
-
console.error("[LENS] Vocab clusters: skipped (no embedTexts capability)");
|
|
8446
|
-
return;
|
|
8447
|
-
}
|
|
8448
|
-
const rows = metadataQueries.getByRepo(db, repoId).map((r) => ({
|
|
8449
|
-
path: r.path,
|
|
8450
|
-
exports: Array.isArray(r.exports) ? r.exports : jsonParse(r.exports, [])
|
|
8451
|
-
}));
|
|
8452
|
-
if (rows.length === 0) {
|
|
8453
|
-
console.error("[LENS] Vocab clusters: skipped (no metadata rows)");
|
|
8454
|
-
return;
|
|
8455
|
-
}
|
|
8456
|
-
const { terms, termToFiles } = extractVocab(rows);
|
|
8457
|
-
if (terms.length < 4) {
|
|
8458
|
-
console.error(`[LENS] Vocab clusters: skipped (only ${terms.length} terms, need \u22654)`);
|
|
8459
|
-
return;
|
|
8460
|
-
}
|
|
8461
|
-
console.error(`[LENS] Vocab clusters: embedding ${terms.length} terms in ${Math.ceil(terms.length / TERM_BATCH_SIZE)} batches...`);
|
|
8462
|
-
let embeddings;
|
|
8463
|
-
try {
|
|
8464
|
-
const results = [];
|
|
8465
|
-
for (let i = 0; i < terms.length; i += TERM_BATCH_SIZE) {
|
|
8466
|
-
const batch = terms.slice(i, i + TERM_BATCH_SIZE);
|
|
8467
|
-
const vecs = await caps.embedTexts(batch, false);
|
|
8468
|
-
results.push(...vecs);
|
|
8469
|
-
}
|
|
8470
|
-
embeddings = results;
|
|
8471
|
-
} catch (err) {
|
|
8472
|
-
console.error(`[LENS] Vocab clusters: embed failed (${terms.length} terms):`, err.message);
|
|
8473
|
-
return;
|
|
8474
|
-
}
|
|
8475
|
-
const rawClusters = agglomerativeCluster(terms, embeddings);
|
|
8476
|
-
console.error(`[LENS] Vocab clusters: ${rawClusters.length} clusters from ${terms.length} terms`);
|
|
8477
|
-
const clusters = rawClusters.map((indices) => {
|
|
8478
|
-
const clusterTerms = indices.map((i) => terms[i]);
|
|
8479
|
-
const fileSet = /* @__PURE__ */ new Set();
|
|
8480
|
-
for (const t of clusterTerms) {
|
|
8481
|
-
const files = termToFiles.get(t);
|
|
8482
|
-
if (files) for (const f of files) fileSet.add(f);
|
|
8483
|
-
}
|
|
8484
|
-
return { terms: clusterTerms, files: [...fileSet].sort() };
|
|
8485
|
-
}).filter((c) => c.files.length > 0).sort((a, b) => b.files.length - a.files.length).slice(0, MAX_CLUSTERS);
|
|
8486
|
-
repoQueries.updateVocabClusters(db, repoId, clusters);
|
|
8487
|
-
}
|
|
8488
8441
|
async function analyzeGitHistory(db, repoId, rootPath, lastAnalyzedCommit) {
|
|
8489
8442
|
const args = ["log", "--name-only", "--format=%H %aI", "--no-merges", `-n`, `${MAX_COMMITS}`];
|
|
8490
8443
|
if (lastAnalyzedCommit) args.push(`${lastAnalyzedCommit}..HEAD`);
|
|
@@ -8605,7 +8558,7 @@ async function withLock(key, fn) {
|
|
|
8605
8558
|
resolve22();
|
|
8606
8559
|
}
|
|
8607
8560
|
}
|
|
8608
|
-
async function runIndex(db, repoId, caps, force = false, onProgress) {
|
|
8561
|
+
async function runIndex(db, repoId, caps, force = false, onProgress, trace) {
|
|
8609
8562
|
return withLock(repoId, async () => {
|
|
8610
8563
|
const start = Date.now();
|
|
8611
8564
|
const repo = repoQueries.getById(db, repoId);
|
|
@@ -8621,17 +8574,21 @@ async function runIndex(db, repoId, caps, force = false, onProgress) {
|
|
|
8621
8574
|
};
|
|
8622
8575
|
}
|
|
8623
8576
|
repoQueries.setIndexing(db, repoId);
|
|
8577
|
+
trace?.step("discovery");
|
|
8624
8578
|
let files;
|
|
8625
8579
|
if (!force && repo.last_indexed_commit) {
|
|
8626
8580
|
files = await diffScan(repo.root_path, repo.last_indexed_commit, headCommit);
|
|
8627
8581
|
} else {
|
|
8628
8582
|
files = await fullScan(repo.root_path);
|
|
8629
8583
|
}
|
|
8584
|
+
trace?.end("discovery", `${files.length} files`);
|
|
8585
|
+
trace?.step("chunking");
|
|
8630
8586
|
let currentChunks = chunkQueries.countByRepo(db, repoId);
|
|
8631
8587
|
let chunksCreated = 0;
|
|
8632
8588
|
let chunksUnchanged = 0;
|
|
8633
8589
|
let chunksDeleted = 0;
|
|
8634
|
-
for (
|
|
8590
|
+
for (let fi = 0; fi < files.length; fi++) {
|
|
8591
|
+
const file = files[fi];
|
|
8635
8592
|
if (file.status === "deleted") {
|
|
8636
8593
|
chunksDeleted += chunkQueries.deleteByRepoPath(db, repoId, file.path);
|
|
8637
8594
|
continue;
|
|
@@ -8680,14 +8637,39 @@ async function runIndex(db, repoId, caps, force = false, onProgress) {
|
|
|
8680
8637
|
}
|
|
8681
8638
|
}
|
|
8682
8639
|
}
|
|
8640
|
+
const isFullScan = force || !repo.last_indexed_commit;
|
|
8641
|
+
if (isFullScan) {
|
|
8642
|
+
const scannedPaths = new Set(files.map((f) => f.path));
|
|
8643
|
+
const allChunkPaths = new Set(chunkQueries.getAllByRepo(db, repoId).map((c) => c.path));
|
|
8644
|
+
for (const path of allChunkPaths) {
|
|
8645
|
+
if (!scannedPaths.has(path)) {
|
|
8646
|
+
chunksDeleted += chunkQueries.deleteByRepoPath(db, repoId, path);
|
|
8647
|
+
}
|
|
8648
|
+
}
|
|
8649
|
+
}
|
|
8650
|
+
trace?.end("chunking", `+${chunksCreated} -${chunksDeleted}`);
|
|
8683
8651
|
onProgress?.();
|
|
8652
|
+
trace?.step("extractMetadata");
|
|
8684
8653
|
extractAndPersistMetadata(db, repoId);
|
|
8654
|
+
if (isFullScan) {
|
|
8655
|
+
const scannedPaths = new Set(files.map((f) => f.path));
|
|
8656
|
+
const allMeta = metadataQueries.getByRepo(db, repoId);
|
|
8657
|
+
for (const m of allMeta) {
|
|
8658
|
+
if (!scannedPaths.has(m.path)) {
|
|
8659
|
+
metadataQueries.deleteByPath(db, repoId, m.path);
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
}
|
|
8663
|
+
trace?.end("extractMetadata");
|
|
8685
8664
|
onProgress?.();
|
|
8686
|
-
|
|
8665
|
+
trace?.step("importGraph");
|
|
8687
8666
|
buildAndPersistImportGraph(db, repoId);
|
|
8688
8667
|
computeMaxImportDepth(db, repoId);
|
|
8668
|
+
trace?.end("importGraph");
|
|
8689
8669
|
onProgress?.();
|
|
8670
|
+
trace?.step("gitAnalysis");
|
|
8690
8671
|
await analyzeGitHistory(db, repoId, repo.root_path, repo.last_git_analysis_commit);
|
|
8672
|
+
trace?.end("gitAnalysis");
|
|
8691
8673
|
onProgress?.();
|
|
8692
8674
|
repoQueries.updateIndexState(db, repoId, headCommit, "ready");
|
|
8693
8675
|
onProgress?.();
|
|
@@ -8796,42 +8778,182 @@ async function ensureEmbedded(db, repoId, caps) {
|
|
|
8796
8778
|
}
|
|
8797
8779
|
if (poolFailed) break;
|
|
8798
8780
|
}
|
|
8799
|
-
return { embedded_count: embedded, skipped_count: 0, duration_ms: Date.now() - start };
|
|
8800
|
-
}
|
|
8801
|
-
async function enrichPurpose(db, repoId, caps, fullRun = false) {
|
|
8802
|
-
const start = Date.now();
|
|
8803
|
-
if (!caps?.generatePurpose) {
|
|
8804
|
-
return { enriched: 0, skipped: 0, duration_ms: 0 };
|
|
8781
|
+
return { embedded_count: embedded, skipped_count: 0, duration_ms: Date.now() - start };
|
|
8782
|
+
}
|
|
8783
|
+
async function enrichPurpose(db, repoId, caps, fullRun = false) {
|
|
8784
|
+
const start = Date.now();
|
|
8785
|
+
if (!caps?.generatePurpose) {
|
|
8786
|
+
return { enriched: 0, skipped: 0, duration_ms: 0 };
|
|
8787
|
+
}
|
|
8788
|
+
let totalEnriched = 0;
|
|
8789
|
+
let totalSkipped = 0;
|
|
8790
|
+
while (true) {
|
|
8791
|
+
const candidates = metadataQueries.getCandidatesForPurpose(db, repoId, PURPOSE_BATCH_LIMIT);
|
|
8792
|
+
if (candidates.length === 0) break;
|
|
8793
|
+
for (let i = 0; i < candidates.length; i += PURPOSE_CONCURRENCY) {
|
|
8794
|
+
const batch = candidates.slice(i, i + PURPOSE_CONCURRENCY);
|
|
8795
|
+
const results = await Promise.allSettled(
|
|
8796
|
+
batch.map(async (row) => {
|
|
8797
|
+
const exports = Array.isArray(row.exports) ? row.exports : jsonParse(row.exports, []);
|
|
8798
|
+
const purpose = await caps.generatePurpose(row.path, row.first_chunk, exports, row.docstring ?? "");
|
|
8799
|
+
return { row, purpose };
|
|
8800
|
+
})
|
|
8801
|
+
);
|
|
8802
|
+
for (let j = 0; j < results.length; j++) {
|
|
8803
|
+
const result = results[j];
|
|
8804
|
+
const row = batch[j];
|
|
8805
|
+
if (result.status === "fulfilled" && result.value.purpose) {
|
|
8806
|
+
metadataQueries.updatePurpose(db, repoId, row.path, result.value.purpose, row.chunk_hash);
|
|
8807
|
+
totalEnriched++;
|
|
8808
|
+
} else {
|
|
8809
|
+
metadataQueries.setPurposeHash(db, repoId, row.path, row.chunk_hash);
|
|
8810
|
+
totalSkipped++;
|
|
8811
|
+
}
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
if (!fullRun) break;
|
|
8815
|
+
}
|
|
8816
|
+
return { enriched: totalEnriched, skipped: totalSkipped, duration_ms: Date.now() - start };
|
|
8817
|
+
}
|
|
8818
|
+
function splitIdentifier(name) {
|
|
8819
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_\-./\\]/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length >= 3 && !STOPWORDS.has(w));
|
|
8820
|
+
}
|
|
8821
|
+
function extractVocab(files) {
|
|
8822
|
+
const termToFiles = /* @__PURE__ */ new Map();
|
|
8823
|
+
for (const f of files) {
|
|
8824
|
+
const pathParts = f.path.split("/");
|
|
8825
|
+
for (const seg of pathParts) {
|
|
8826
|
+
const base = seg.replace(/\.[^.]+$/, "");
|
|
8827
|
+
for (const word of splitIdentifier(base)) {
|
|
8828
|
+
const s = termToFiles.get(word) ?? /* @__PURE__ */ new Set();
|
|
8829
|
+
s.add(f.path);
|
|
8830
|
+
termToFiles.set(word, s);
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8833
|
+
for (const exp of f.exports) {
|
|
8834
|
+
for (const word of splitIdentifier(exp)) {
|
|
8835
|
+
const s = termToFiles.get(word) ?? /* @__PURE__ */ new Set();
|
|
8836
|
+
s.add(f.path);
|
|
8837
|
+
termToFiles.set(word, s);
|
|
8838
|
+
}
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
const totalFiles = files.length;
|
|
8842
|
+
const maxDf = Math.max(10, Math.floor(totalFiles * 0.3));
|
|
8843
|
+
const filtered = [];
|
|
8844
|
+
for (const [term, fileSet] of termToFiles) {
|
|
8845
|
+
if (fileSet.size >= 2 && fileSet.size <= maxDf) {
|
|
8846
|
+
filtered.push(term);
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
filtered.sort((a, b) => (termToFiles.get(b)?.size ?? 0) - (termToFiles.get(a)?.size ?? 0));
|
|
8850
|
+
const terms = filtered.slice(0, MAX_TERMS);
|
|
8851
|
+
return { terms, termToFiles };
|
|
8852
|
+
}
|
|
8853
|
+
function cosine(a, b) {
|
|
8854
|
+
let dot = 0, na = 0, nb = 0;
|
|
8855
|
+
for (let i = 0; i < a.length; i++) {
|
|
8856
|
+
dot += a[i] * b[i];
|
|
8857
|
+
na += a[i] * a[i];
|
|
8858
|
+
nb += b[i] * b[i];
|
|
8859
|
+
}
|
|
8860
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
8861
|
+
return denom > 0 ? dot / denom : 0;
|
|
8862
|
+
}
|
|
8863
|
+
function agglomerativeCluster(terms, embeddings) {
|
|
8864
|
+
const n = terms.length;
|
|
8865
|
+
const pairs = [];
|
|
8866
|
+
for (let i = 0; i < n; i++) {
|
|
8867
|
+
for (let j = i + 1; j < n; j++) {
|
|
8868
|
+
const sim = cosine(embeddings[i], embeddings[j]);
|
|
8869
|
+
if (sim >= SIMILARITY_THRESHOLD) pairs.push({ i, j, sim });
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
pairs.sort((a, b) => b.sim - a.sim);
|
|
8873
|
+
const parent = new Int32Array(n);
|
|
8874
|
+
const size = new Uint16Array(n);
|
|
8875
|
+
for (let i = 0; i < n; i++) {
|
|
8876
|
+
parent[i] = i;
|
|
8877
|
+
size[i] = 1;
|
|
8878
|
+
}
|
|
8879
|
+
function find(x) {
|
|
8880
|
+
while (parent[x] !== x) {
|
|
8881
|
+
parent[x] = parent[parent[x]];
|
|
8882
|
+
x = parent[x];
|
|
8883
|
+
}
|
|
8884
|
+
return x;
|
|
8885
|
+
}
|
|
8886
|
+
for (const { i, j } of pairs) {
|
|
8887
|
+
const ri = find(i), rj = find(j);
|
|
8888
|
+
if (ri === rj) continue;
|
|
8889
|
+
if (size[ri] + size[rj] > MAX_CLUSTER_SIZE) continue;
|
|
8890
|
+
if (size[ri] >= size[rj]) {
|
|
8891
|
+
parent[rj] = ri;
|
|
8892
|
+
size[ri] += size[rj];
|
|
8893
|
+
} else {
|
|
8894
|
+
parent[ri] = rj;
|
|
8895
|
+
size[rj] += size[ri];
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
const clusterMap = /* @__PURE__ */ new Map();
|
|
8899
|
+
for (let i = 0; i < n; i++) {
|
|
8900
|
+
const root = find(i);
|
|
8901
|
+
const arr = clusterMap.get(root) ?? [];
|
|
8902
|
+
arr.push(i);
|
|
8903
|
+
clusterMap.set(root, arr);
|
|
8904
|
+
}
|
|
8905
|
+
return [...clusterMap.values()].filter((c) => c.length >= 2);
|
|
8906
|
+
}
|
|
8907
|
+
async function buildVocabClusters(db, repoId, caps, force = false) {
|
|
8908
|
+
if (!caps?.embedTexts) {
|
|
8909
|
+
console.error("[LENS] Vocab clusters: skipped (no embedTexts capability)");
|
|
8910
|
+
return;
|
|
8911
|
+
}
|
|
8912
|
+
const repo = repoQueries.getById(db, repoId);
|
|
8913
|
+
if (!repo) return;
|
|
8914
|
+
if (!force && repo.last_vocab_cluster_commit && repo.last_vocab_cluster_commit === repo.last_indexed_commit) {
|
|
8915
|
+
console.error("[LENS] Vocab clusters: skipped (unchanged since last build)");
|
|
8916
|
+
return;
|
|
8917
|
+
}
|
|
8918
|
+
const rows = metadataQueries.getByRepo(db, repoId).map((r) => ({
|
|
8919
|
+
path: r.path,
|
|
8920
|
+
exports: Array.isArray(r.exports) ? r.exports : jsonParse(r.exports, [])
|
|
8921
|
+
}));
|
|
8922
|
+
if (rows.length === 0) {
|
|
8923
|
+
console.error("[LENS] Vocab clusters: skipped (no metadata rows)");
|
|
8924
|
+
return;
|
|
8925
|
+
}
|
|
8926
|
+
const { terms, termToFiles } = extractVocab(rows);
|
|
8927
|
+
if (terms.length < 4) {
|
|
8928
|
+
console.error(`[LENS] Vocab clusters: skipped (only ${terms.length} terms, need \u22654)`);
|
|
8929
|
+
return;
|
|
8805
8930
|
}
|
|
8806
|
-
|
|
8807
|
-
let
|
|
8808
|
-
|
|
8809
|
-
const
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
const
|
|
8813
|
-
|
|
8814
|
-
batch.map(async (row) => {
|
|
8815
|
-
const exports = Array.isArray(row.exports) ? row.exports : jsonParse(row.exports, []);
|
|
8816
|
-
const purpose = await caps.generatePurpose(row.path, row.first_chunk, exports, row.docstring ?? "");
|
|
8817
|
-
return { row, purpose };
|
|
8818
|
-
})
|
|
8819
|
-
);
|
|
8820
|
-
for (let j = 0; j < results.length; j++) {
|
|
8821
|
-
const result = results[j];
|
|
8822
|
-
const row = batch[j];
|
|
8823
|
-
if (result.status === "fulfilled" && result.value.purpose) {
|
|
8824
|
-
metadataQueries.updatePurpose(db, repoId, row.path, result.value.purpose, row.chunk_hash);
|
|
8825
|
-
totalEnriched++;
|
|
8826
|
-
} else {
|
|
8827
|
-
metadataQueries.setPurposeHash(db, repoId, row.path, row.chunk_hash);
|
|
8828
|
-
totalSkipped++;
|
|
8829
|
-
}
|
|
8830
|
-
}
|
|
8931
|
+
console.error(`[LENS] Vocab clusters: embedding ${terms.length} terms in ${Math.ceil(terms.length / TERM_BATCH_SIZE)} batches...`);
|
|
8932
|
+
let embeddings;
|
|
8933
|
+
try {
|
|
8934
|
+
const results = [];
|
|
8935
|
+
for (let i = 0; i < terms.length; i += TERM_BATCH_SIZE) {
|
|
8936
|
+
const batch = terms.slice(i, i + TERM_BATCH_SIZE);
|
|
8937
|
+
const vecs = await caps.embedTexts(batch, false);
|
|
8938
|
+
results.push(...vecs);
|
|
8831
8939
|
}
|
|
8832
|
-
|
|
8940
|
+
embeddings = results;
|
|
8941
|
+
} catch (err) {
|
|
8942
|
+
console.error(`[LENS] Vocab clusters: embed failed (${terms.length} terms):`, err.message);
|
|
8943
|
+
return;
|
|
8833
8944
|
}
|
|
8834
|
-
|
|
8945
|
+
const rawClusters = agglomerativeCluster(terms, embeddings);
|
|
8946
|
+
console.error(`[LENS] Vocab clusters: ${rawClusters.length} clusters from ${terms.length} terms`);
|
|
8947
|
+
const clusters = rawClusters.map((indices) => {
|
|
8948
|
+
const clusterTerms = indices.map((i) => terms[i]);
|
|
8949
|
+
const fileSet = /* @__PURE__ */ new Set();
|
|
8950
|
+
for (const t of clusterTerms) {
|
|
8951
|
+
const files = termToFiles.get(t);
|
|
8952
|
+
if (files) for (const f of files) fileSet.add(f);
|
|
8953
|
+
}
|
|
8954
|
+
return { terms: clusterTerms, files: [...fileSet].sort() };
|
|
8955
|
+
}).filter((c) => c.files.length > 0).sort((a, b) => b.files.length - a.files.length).slice(0, MAX_CLUSTERS);
|
|
8956
|
+
repoQueries.updateVocabClusters(db, repoId, clusters, repo.last_indexed_commit ?? void 0);
|
|
8835
8957
|
}
|
|
8836
8958
|
async function handleFileChange(db, repoId, repoRoot, absPath) {
|
|
8837
8959
|
const relPath = relative3(repoRoot, absPath);
|
|
@@ -9129,6 +9251,16 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9129
9251
|
const { exact, stemmed, clusterFiles } = expandKeywords(rawWords, vocabClusters ?? null);
|
|
9130
9252
|
const allTerms = [...exact, ...stemmed];
|
|
9131
9253
|
const termWeights = buildTermWeights(allTerms, metadata);
|
|
9254
|
+
const exportTokensMap = /* @__PURE__ */ new Map();
|
|
9255
|
+
for (const f of metadata) {
|
|
9256
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
9257
|
+
for (const exp of f.exports ?? []) {
|
|
9258
|
+
for (const part of exp.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[\s_-]+/)) {
|
|
9259
|
+
if (part.length >= 3 && !STOPWORDS2.has(part)) tokens.add(part);
|
|
9260
|
+
}
|
|
9261
|
+
}
|
|
9262
|
+
exportTokensMap.set(f.path, tokens);
|
|
9263
|
+
}
|
|
9132
9264
|
const scored = metadata.map((f) => {
|
|
9133
9265
|
let score = 0;
|
|
9134
9266
|
let matchedTerms = 0;
|
|
@@ -9143,12 +9275,14 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9143
9275
|
const fileTokens = fileName.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[._-]/g, " ").toLowerCase().split(/\s+/).filter((t) => t.length >= 2);
|
|
9144
9276
|
const dirTokens = pathSegments.slice(-4, -1).flatMap((s) => s.replace(/\./g, " ").split(/\s+/)).filter((t) => t.length >= 2);
|
|
9145
9277
|
const pathTokenSet = /* @__PURE__ */ new Set([...fileTokens, ...dirTokens]);
|
|
9278
|
+
const expTokens = exportTokensMap.get(f.path);
|
|
9146
9279
|
for (const w of exact) {
|
|
9147
9280
|
const weight = termWeights.get(w) ?? 1;
|
|
9148
9281
|
let termScore = 0;
|
|
9149
9282
|
if (fileTokens.some((t) => t === w || t.includes(w))) termScore += 4 * weight;
|
|
9150
9283
|
else if (pathTokenSet.has(w)) termScore += 2 * weight;
|
|
9151
|
-
if (
|
|
9284
|
+
if (expTokens?.has(w)) termScore += 2.5 * weight;
|
|
9285
|
+
else if (exportsLower.includes(w)) termScore += 2 * weight;
|
|
9152
9286
|
if (docLower.includes(w) || purposeLower.includes(w)) termScore += 1 * weight;
|
|
9153
9287
|
if (sectionsLower.includes(w)) termScore += 1 * weight;
|
|
9154
9288
|
if (internalsLower.includes(w)) termScore += 1.5 * weight;
|
|
@@ -9172,6 +9306,10 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9172
9306
|
if (score > 0 && isNoisePath(f.path)) {
|
|
9173
9307
|
score *= 0.3;
|
|
9174
9308
|
}
|
|
9309
|
+
const exportCount = (f.exports ?? []).length;
|
|
9310
|
+
if (exportCount > 5 && score > 0) {
|
|
9311
|
+
score *= 1 / (1 + Math.log2(exportCount / 5) * 0.3);
|
|
9312
|
+
}
|
|
9175
9313
|
const stats = fileStats2?.get(f.path);
|
|
9176
9314
|
if (stats && stats.recent_count > 0 && score > 0) {
|
|
9177
9315
|
score += Math.min(stats.recent_count, 5) * 0.5;
|
|
@@ -9310,8 +9448,8 @@ async function vectorSearch(db, repoId, query, limit, caps, codeOnly = true) {
|
|
|
9310
9448
|
})).filter((c) => !codeOnly || !isDocFile(c.path)).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
9311
9449
|
return scored;
|
|
9312
9450
|
}
|
|
9313
|
-
function cacheKey(repoId, goal, commit) {
|
|
9314
|
-
return `${repoId}:${commit ?? ""}:${goal}`;
|
|
9451
|
+
function cacheKey(repoId, goal, commit, emb = true) {
|
|
9452
|
+
return `${repoId}:${commit ?? ""}:${emb ? "e" : "n"}:${goal}`;
|
|
9315
9453
|
}
|
|
9316
9454
|
function cacheGet(key) {
|
|
9317
9455
|
const entry = cache.get(key);
|
|
@@ -9329,30 +9467,41 @@ function cacheSet(key, response) {
|
|
|
9329
9467
|
}
|
|
9330
9468
|
cache.set(key, { response, expires: Date.now() + CACHE_TTL });
|
|
9331
9469
|
}
|
|
9332
|
-
async function buildContext(db, repoId, goal, caps) {
|
|
9470
|
+
async function buildContext(db, repoId, goal, caps, trace, options) {
|
|
9333
9471
|
const start = Date.now();
|
|
9334
9472
|
try {
|
|
9473
|
+
trace?.step("ensureIndexed");
|
|
9335
9474
|
await ensureIndexed(db, repoId, caps);
|
|
9475
|
+
trace?.end("ensureIndexed");
|
|
9336
9476
|
const repo = repoQueries.getById(db, repoId);
|
|
9337
9477
|
const commit = repo?.last_indexed_commit ?? null;
|
|
9338
|
-
const
|
|
9478
|
+
const useEmb = options?.useEmbeddings !== false;
|
|
9479
|
+
const key = cacheKey(repoId, goal, commit, useEmb);
|
|
9339
9480
|
const cached2 = cacheGet(key);
|
|
9340
9481
|
if (cached2) {
|
|
9482
|
+
trace?.add("cache", 0, "HIT");
|
|
9341
9483
|
track(db, "context", { duration_ms: Date.now() - start, result_count: cached2.stats.files_in_context, cache_hit: true });
|
|
9342
9484
|
return { ...cached2, stats: { ...cached2.stats, cached: true, duration_ms: Date.now() - start } };
|
|
9343
9485
|
}
|
|
9344
|
-
const embAvailable = (await Promise.resolve().then(() => (init_queries(), queries_exports))).chunkQueries.hasEmbeddings(db, repoId);
|
|
9486
|
+
const embAvailable = useEmb && (await Promise.resolve().then(() => (init_queries(), queries_exports))).chunkQueries.hasEmbeddings(db, repoId);
|
|
9487
|
+
trace?.step("loadStructural");
|
|
9345
9488
|
const metadata = loadFileMetadata(db, repoId);
|
|
9346
9489
|
const allStats = getAllFileStats(db, repoId);
|
|
9347
9490
|
const vocabClusters = loadVocabClusters(db, repoId);
|
|
9348
9491
|
const indegreeMap = getIndegrees(db, repoId);
|
|
9349
9492
|
const maxImportDepth = repo?.max_import_depth ?? 0;
|
|
9493
|
+
trace?.end("loadStructural");
|
|
9494
|
+
trace?.step("vectorSearch");
|
|
9350
9495
|
const vecResults = embAvailable ? await vectorSearch(db, repoId, goal, 10, caps, true).catch(() => []) : [];
|
|
9496
|
+
trace?.end("vectorSearch", `${vecResults.length} results`);
|
|
9351
9497
|
const statsForInterpreter = /* @__PURE__ */ new Map();
|
|
9352
9498
|
for (const [path, stat32] of allStats) {
|
|
9353
9499
|
statsForInterpreter.set(path, { commit_count: stat32.commit_count, recent_count: stat32.recent_count });
|
|
9354
9500
|
}
|
|
9501
|
+
trace?.step("interpretQuery");
|
|
9355
9502
|
const interpreted = interpretQuery(goal, metadata, statsForInterpreter, vocabClusters, indegreeMap, maxImportDepth);
|
|
9503
|
+
trace?.end("interpretQuery", `${interpreted.files.length} files`);
|
|
9504
|
+
trace?.step("cochangePromotion");
|
|
9356
9505
|
const topForCochange = interpreted.files.slice(0, 5).map((f) => f.path);
|
|
9357
9506
|
const cochangePartners = getCochangePartners(db, repoId, topForCochange, 3, 20);
|
|
9358
9507
|
const existingPathSet = new Set(interpreted.files.map((f) => f.path));
|
|
@@ -9367,6 +9516,7 @@ async function buildContext(db, repoId, goal, caps) {
|
|
|
9367
9516
|
existingPathSet.add(cp.partner);
|
|
9368
9517
|
promoted++;
|
|
9369
9518
|
}
|
|
9519
|
+
trace?.end("cochangePromotion", `${promoted} promoted`);
|
|
9370
9520
|
if (vecResults.length > 0) {
|
|
9371
9521
|
const vecByFile = /* @__PURE__ */ new Map();
|
|
9372
9522
|
for (const r of vecResults) {
|
|
@@ -9396,6 +9546,7 @@ async function buildContext(db, repoId, goal, caps) {
|
|
|
9396
9546
|
}
|
|
9397
9547
|
const hitPaths = interpreted.files.map((f) => f.path);
|
|
9398
9548
|
const topPaths = hitPaths.slice(0, 3);
|
|
9549
|
+
trace?.step("structuralEnrichment");
|
|
9399
9550
|
const reverseImports = getReverseImports(db, repoId, hitPaths);
|
|
9400
9551
|
const forwardImports = getForwardImports(db, repoId, hitPaths);
|
|
9401
9552
|
const hop2Deps = get2HopReverseDeps(db, repoId, topPaths);
|
|
@@ -9431,6 +9582,8 @@ async function buildContext(db, repoId, goal, caps) {
|
|
|
9431
9582
|
}
|
|
9432
9583
|
}
|
|
9433
9584
|
}
|
|
9585
|
+
trace?.end("structuralEnrichment");
|
|
9586
|
+
trace?.step("formatContextPack");
|
|
9434
9587
|
const data = {
|
|
9435
9588
|
goal,
|
|
9436
9589
|
files: interpreted.files,
|
|
@@ -9442,6 +9595,7 @@ async function buildContext(db, repoId, goal, caps) {
|
|
|
9442
9595
|
fileStats: allStats
|
|
9443
9596
|
};
|
|
9444
9597
|
const contextPack = formatContextPack(data);
|
|
9598
|
+
trace?.end("formatContextPack");
|
|
9445
9599
|
const response = {
|
|
9446
9600
|
context_pack: contextPack,
|
|
9447
9601
|
stats: {
|
|
@@ -9463,7 +9617,7 @@ Context generation failed.`,
|
|
|
9463
9617
|
};
|
|
9464
9618
|
}
|
|
9465
9619
|
}
|
|
9466
|
-
var __defProp2, __getOwnPropNames2, __esm2, __export2, schema_exports, uuid, now, updatedAt, repos, chunks, fileMetadata, fileImports, fileStats, fileCochanges, usageCounters, requestLogs, telemetryEvents, init_schema, queries_exports, repoQueries, chunkQueries, metadataQueries, importQueries, statsQueries, cochangeQueries, logQueries, usageQueries, telemetryQueries, init_queries, _db, _raw, execFileAsync, MAX_FILE_SIZE, BINARY_EXTENSIONS, DOCS_EXTENSIONS, LANG_MAP, DEFAULT_CHUNKING_PARAMS, TS_IMPORT_RE, PY_IMPORT_RE, GO_IMPORT_RE, RUST_USE_RE, TS_EXTENSIONS, PY_EXTENSIONS, TS_EXPORT_RE, PY_EXPORT_RE, GO_EXPORT_RE, RUST_EXPORT_RE, CSHARP_EXPORT_RE, JAVA_EXPORT_RE, JSDOC_RE, PY_DOCSTRING_RE, CSHARP_DOC_RE, GO_PKG_RE, SECTION_SINGLE_RE, SECTION_BLOCK_RE, TS_INTERNAL_FN_RE, TS_INTERNAL_CONST_RE,
|
|
9620
|
+
var __defProp2, __getOwnPropNames2, __esm2, __export2, schema_exports, uuid, now, updatedAt, repos, chunks, fileMetadata, fileImports, fileStats, fileCochanges, usageCounters, requestLogs, settings, telemetryEvents, init_schema, queries_exports, repoQueries, chunkQueries, metadataQueries, importQueries, statsQueries, cochangeQueries, logQueries, usageQueries, telemetryQueries, settingsQueries, init_queries, _db, _raw, execFileAsync, MAX_FILE_SIZE, BINARY_EXTENSIONS, DOCS_EXTENSIONS, LANG_MAP, DEFAULT_CHUNKING_PARAMS, TS_IMPORT_RE, PY_IMPORT_RE, GO_IMPORT_RE, RUST_USE_RE, TS_EXTENSIONS, PY_EXTENSIONS, TS_EXPORT_RE, PY_EXPORT_RE, GO_EXPORT_RE, RUST_EXPORT_RE, CSHARP_EXPORT_RE, CSHARP_EXPORT_METHOD_RE, JAVA_EXPORT_RE, JSDOC_RE, PY_DOCSTRING_RE, CSHARP_DOC_RE, GO_PKG_RE, RUST_DOC_RE, SECTION_SINGLE_RE, SECTION_BLOCK_RE, TS_INTERNAL_FN_RE, TS_INTERNAL_CONST_RE, GO_INTERNAL_RES, PY_INTERNAL_RES, RUST_INTERNAL_RES, CSHARP_INTERNAL_RES, JAVA_INTERNAL_RES, KOTLIN_INTERNAL_RES, execFileAsync2, MAX_COMMITS, MAX_FILES_PER_COMMIT, RECENT_DAYS, _enabled, MAX_CHUNKS_PER_REPO, locks, MAX_API_CALLS, POOL_SIZE, MAX_BATCH_TOKENS, CHARS_PER_TOKEN, PURPOSE_BATCH_LIMIT, PURPOSE_CONCURRENCY, TERM_BATCH_SIZE, SIMILARITY_THRESHOLD, MAX_TERMS, MAX_CLUSTERS, MAX_CLUSTER_SIZE, STOPWORDS, watchers, debounceTimers, IGNORED, DEBOUNCE_MS, STOPWORDS2, NOISE_EXTENSIONS, NOISE_PATHS, CONCEPT_SYNONYMS, CACHE_TTL, CACHE_MAX, cache, RequestTrace;
|
|
9467
9621
|
var init_dist = __esm({
|
|
9468
9622
|
"packages/engine/dist/index.js"() {
|
|
9469
9623
|
"use strict";
|
|
@@ -9490,6 +9644,7 @@ var init_dist = __esm({
|
|
|
9490
9644
|
fileStats: () => fileStats,
|
|
9491
9645
|
repos: () => repos,
|
|
9492
9646
|
requestLogs: () => requestLogs,
|
|
9647
|
+
settings: () => settings,
|
|
9493
9648
|
telemetryEvents: () => telemetryEvents,
|
|
9494
9649
|
usageCounters: () => usageCounters
|
|
9495
9650
|
});
|
|
@@ -9513,6 +9668,10 @@ var init_dist = __esm({
|
|
|
9513
9668
|
last_git_analysis_commit: text("last_git_analysis_commit"),
|
|
9514
9669
|
max_import_depth: integer("max_import_depth").default(0),
|
|
9515
9670
|
vocab_clusters: text("vocab_clusters"),
|
|
9671
|
+
last_vocab_cluster_commit: text("last_vocab_cluster_commit"),
|
|
9672
|
+
enable_embeddings: integer("enable_embeddings").notNull().default(1),
|
|
9673
|
+
enable_summaries: integer("enable_summaries").notNull().default(1),
|
|
9674
|
+
enable_vocab_clusters: integer("enable_vocab_clusters").notNull().default(1),
|
|
9516
9675
|
created_at: now(),
|
|
9517
9676
|
updated_at: updatedAt()
|
|
9518
9677
|
},
|
|
@@ -9626,10 +9785,17 @@ var init_dist = __esm({
|
|
|
9626
9785
|
source: text("source").notNull().default("api"),
|
|
9627
9786
|
request_body: text("request_body"),
|
|
9628
9787
|
response_size: integer("response_size"),
|
|
9788
|
+
response_body: text("response_body"),
|
|
9789
|
+
trace: text("trace"),
|
|
9629
9790
|
created_at: now()
|
|
9630
9791
|
},
|
|
9631
9792
|
(t) => [index("idx_request_logs_created").on(t.created_at), index("idx_request_logs_source").on(t.source)]
|
|
9632
9793
|
);
|
|
9794
|
+
settings = sqliteTable("settings", {
|
|
9795
|
+
key: text("key").primaryKey(),
|
|
9796
|
+
value: text("value").notNull(),
|
|
9797
|
+
updated_at: updatedAt()
|
|
9798
|
+
});
|
|
9633
9799
|
telemetryEvents = sqliteTable(
|
|
9634
9800
|
"telemetry_events",
|
|
9635
9801
|
{
|
|
@@ -9656,6 +9822,7 @@ var init_dist = __esm({
|
|
|
9656
9822
|
logQueries: () => logQueries,
|
|
9657
9823
|
metadataQueries: () => metadataQueries,
|
|
9658
9824
|
repoQueries: () => repoQueries,
|
|
9825
|
+
settingsQueries: () => settingsQueries,
|
|
9659
9826
|
statsQueries: () => statsQueries,
|
|
9660
9827
|
telemetryQueries: () => telemetryQueries,
|
|
9661
9828
|
toEmbeddingBlob: () => toEmbeddingBlob,
|
|
@@ -9716,8 +9883,10 @@ var init_dist = __esm({
|
|
|
9716
9883
|
updateMaxDepth(db, id, depth) {
|
|
9717
9884
|
db.update(repos).set({ max_import_depth: depth }).where(eq(repos.id, id)).run();
|
|
9718
9885
|
},
|
|
9719
|
-
updateVocabClusters(db, id, clusters) {
|
|
9720
|
-
|
|
9886
|
+
updateVocabClusters(db, id, clusters, commit) {
|
|
9887
|
+
const set = { vocab_clusters: JSON.stringify(clusters) };
|
|
9888
|
+
if (commit) set.last_vocab_cluster_commit = commit;
|
|
9889
|
+
db.update(repos).set(set).where(eq(repos.id, id)).run();
|
|
9721
9890
|
},
|
|
9722
9891
|
updateGitAnalysisCommit(db, id, commit) {
|
|
9723
9892
|
db.update(repos).set({ last_git_analysis_commit: commit }).where(eq(repos.id, id)).run();
|
|
@@ -9725,6 +9894,13 @@ var init_dist = __esm({
|
|
|
9725
9894
|
setIndexing(db, id) {
|
|
9726
9895
|
db.update(repos).set({ index_status: "indexing" }).where(eq(repos.id, id)).run();
|
|
9727
9896
|
},
|
|
9897
|
+
updateProFeatures(db, id, flags) {
|
|
9898
|
+
const set = { updated_at: sql`datetime('now')` };
|
|
9899
|
+
if (flags.enable_embeddings !== void 0) set.enable_embeddings = flags.enable_embeddings;
|
|
9900
|
+
if (flags.enable_summaries !== void 0) set.enable_summaries = flags.enable_summaries;
|
|
9901
|
+
if (flags.enable_vocab_clusters !== void 0) set.enable_vocab_clusters = flags.enable_vocab_clusters;
|
|
9902
|
+
db.update(repos).set(set).where(eq(repos.id, id)).run();
|
|
9903
|
+
},
|
|
9728
9904
|
remove(db, id) {
|
|
9729
9905
|
db.delete(fileCochanges).where(eq(fileCochanges.repo_id, id)).run();
|
|
9730
9906
|
db.delete(fileStats).where(eq(fileStats.repo_id, id)).run();
|
|
@@ -9936,6 +10112,9 @@ var init_dist = __esm({
|
|
|
9936
10112
|
purpose_count,
|
|
9937
10113
|
purpose_total
|
|
9938
10114
|
};
|
|
10115
|
+
},
|
|
10116
|
+
deleteByPath(db, repoId, path) {
|
|
10117
|
+
db.delete(fileMetadata).where(and(eq(fileMetadata.repo_id, repoId), eq(fileMetadata.path, path))).run();
|
|
9939
10118
|
}
|
|
9940
10119
|
};
|
|
9941
10120
|
importQueries = {
|
|
@@ -10059,7 +10238,7 @@ var init_dist = __esm({
|
|
|
10059
10238
|
}
|
|
10060
10239
|
};
|
|
10061
10240
|
logQueries = {
|
|
10062
|
-
insert(db, method, path, status, durationMs, source, requestBody, responseSize) {
|
|
10241
|
+
insert(db, method, path, status, durationMs, source, requestBody, responseSize, responseBody, trace) {
|
|
10063
10242
|
db.insert(requestLogs).values({
|
|
10064
10243
|
id: randomUUID(),
|
|
10065
10244
|
method,
|
|
@@ -10068,7 +10247,9 @@ var init_dist = __esm({
|
|
|
10068
10247
|
duration_ms: durationMs,
|
|
10069
10248
|
source,
|
|
10070
10249
|
request_body: requestBody ?? null,
|
|
10071
|
-
response_size: responseSize ?? null
|
|
10250
|
+
response_size: responseSize ?? null,
|
|
10251
|
+
response_body: responseBody ?? null,
|
|
10252
|
+
trace: trace ?? null
|
|
10072
10253
|
}).run();
|
|
10073
10254
|
},
|
|
10074
10255
|
list(db, opts = {}) {
|
|
@@ -10148,6 +10329,25 @@ var init_dist = __esm({
|
|
|
10148
10329
|
return result.changes;
|
|
10149
10330
|
}
|
|
10150
10331
|
};
|
|
10332
|
+
settingsQueries = {
|
|
10333
|
+
get(db, key) {
|
|
10334
|
+
const row = db.select({ value: settings.value }).from(settings).where(eq(settings.key, key)).get();
|
|
10335
|
+
return row?.value ?? null;
|
|
10336
|
+
},
|
|
10337
|
+
set(db, key, value) {
|
|
10338
|
+
db.insert(settings).values({ key, value }).onConflictDoUpdate({
|
|
10339
|
+
target: settings.key,
|
|
10340
|
+
set: { value, updated_at: sql`datetime('now')` }
|
|
10341
|
+
}).run();
|
|
10342
|
+
},
|
|
10343
|
+
getAll(db) {
|
|
10344
|
+
const rows = db.select().from(settings).all();
|
|
10345
|
+
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
|
10346
|
+
},
|
|
10347
|
+
delete(db, key) {
|
|
10348
|
+
db.delete(settings).where(eq(settings.key, key)).run();
|
|
10349
|
+
}
|
|
10350
|
+
};
|
|
10151
10351
|
}
|
|
10152
10352
|
});
|
|
10153
10353
|
init_schema();
|
|
@@ -10155,7 +10355,7 @@ var init_dist = __esm({
|
|
|
10155
10355
|
_raw = null;
|
|
10156
10356
|
init_queries();
|
|
10157
10357
|
execFileAsync = promisify(execFile);
|
|
10158
|
-
MAX_FILE_SIZE =
|
|
10358
|
+
MAX_FILE_SIZE = 2 * 1024 * 1024;
|
|
10159
10359
|
BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10160
10360
|
".png",
|
|
10161
10361
|
".jpg",
|
|
@@ -10247,17 +10447,66 @@ var init_dist = __esm({
|
|
|
10247
10447
|
PY_EXPORT_RE = /^(?:def|class)\s+(\w+)/gm;
|
|
10248
10448
|
GO_EXPORT_RE = /^func\s+([A-Z]\w*)/gm;
|
|
10249
10449
|
RUST_EXPORT_RE = /^pub\s+(?:fn|struct|enum|trait|type|mod)\s+(\w+)/gm;
|
|
10250
|
-
CSHARP_EXPORT_RE =
|
|
10251
|
-
|
|
10450
|
+
CSHARP_EXPORT_RE = /^\s*(?:public|internal)\s+(?:static\s+)?(?:abstract\s+|sealed\s+|partial\s+)?(?:class|interface|enum|struct|record|delegate)\s+(\w+)/gm;
|
|
10451
|
+
CSHARP_EXPORT_METHOD_RE = /^\s*public\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?[\w<>\[\]?.]+\s+(\w+)\s*\(/gm;
|
|
10452
|
+
JAVA_EXPORT_RE = /^\s*(?:public)\s+(?:static\s+)?(?:abstract\s+|final\s+)?(?:class|interface|enum|record)\s+(\w+)/gm;
|
|
10252
10453
|
JSDOC_RE = /^\/\*\*\s*([\s\S]*?)\*\//m;
|
|
10253
10454
|
PY_DOCSTRING_RE = /^(?:["']{3})([\s\S]*?)(?:["']{3})/m;
|
|
10254
10455
|
CSHARP_DOC_RE = /^(?:\s*\/\/\/\s*(.*))+/m;
|
|
10255
10456
|
GO_PKG_RE = /^\/\/\s*Package\s+\w+\s+(.*)/m;
|
|
10457
|
+
RUST_DOC_RE = /^(?:\s*\/\/!\s*(.*)(?:\n\s*\/\/!\s*(.*))*)/m;
|
|
10256
10458
|
SECTION_SINGLE_RE = /^(?:\/\/|#)\s*[-=]{3,}\s*(.+?)\s*[-=]{3,}\s*$/gm;
|
|
10257
10459
|
SECTION_BLOCK_RE = /^\/\*\s*[-=]{3,}\s*(.+?)\s*[-=]{3,}\s*\*\/$/gm;
|
|
10258
10460
|
TS_INTERNAL_FN_RE = /^(?:async\s+)?function\s+(\w+)/gm;
|
|
10259
10461
|
TS_INTERNAL_CONST_RE = /^(?:const|let)\s+(\w+)\s*=/gm;
|
|
10462
|
+
GO_INTERNAL_RES = [
|
|
10463
|
+
/^func\s+([a-z]\w*)/gm,
|
|
10464
|
+
/^var\s+([a-z]\w*)/gm,
|
|
10465
|
+
/^const\s+([a-z]\w*)/gm
|
|
10466
|
+
];
|
|
10467
|
+
PY_INTERNAL_RES = [/^def\s+(\w+)/gm, /^class\s+(\w+)/gm];
|
|
10468
|
+
RUST_INTERNAL_RES = [
|
|
10469
|
+
/^(?:async\s+)?fn\s+(\w+)/gm,
|
|
10470
|
+
/^struct\s+(\w+)/gm,
|
|
10471
|
+
/^enum\s+(\w+)/gm,
|
|
10472
|
+
/^trait\s+(\w+)/gm,
|
|
10473
|
+
/^type\s+(\w+)/gm,
|
|
10474
|
+
/^const\s+(\w+)/gm
|
|
10475
|
+
];
|
|
10476
|
+
CSHARP_INTERNAL_RES = [
|
|
10477
|
+
// Types with explicit access modifier
|
|
10478
|
+
/^\s*(?:private|protected|internal)\s+(?:static\s+)?(?:abstract\s+|sealed\s+|partial\s+)?(?:class|interface|enum|struct|record)\s+(\w+)/gm,
|
|
10479
|
+
// Methods with explicit access modifier
|
|
10480
|
+
/^\s*(?:private|protected|internal)\s+(?:static\s+)?(?:async\s+)?[\w<>\[\]?.]+\s+(\w+)\s*\(/gm,
|
|
10481
|
+
// Methods with no access modifier (implicitly private in C#): returnType Name(
|
|
10482
|
+
/^\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?[\w<>\[\]?.]+\s+(\w+)\s*\(/gm
|
|
10483
|
+
];
|
|
10484
|
+
JAVA_INTERNAL_RES = [
|
|
10485
|
+
/^\s*(?:private|protected)\s+(?:static\s+)?(?:final\s+|abstract\s+)?(?:class|interface|enum|record)\s+(\w+)/gm,
|
|
10486
|
+
/^\s*(?:private|protected)\s+(?:static\s+)?(?:final\s+|abstract\s+)?[\w<>\[\].]+\s+(\w+)\s*\(/gm
|
|
10487
|
+
];
|
|
10488
|
+
KOTLIN_INTERNAL_RES = [
|
|
10489
|
+
/^\s*private\s+(?:suspend\s+)?fun\s+(\w+)/gm,
|
|
10490
|
+
/^\s*private\s+(?:data\s+)?(?:class|interface|object|enum)\s+(\w+)/gm
|
|
10491
|
+
];
|
|
10492
|
+
init_queries();
|
|
10493
|
+
init_queries();
|
|
10494
|
+
execFileAsync2 = promisify2(execFile2);
|
|
10495
|
+
MAX_COMMITS = 2e3;
|
|
10496
|
+
MAX_FILES_PER_COMMIT = 20;
|
|
10497
|
+
RECENT_DAYS = 90;
|
|
10498
|
+
init_queries();
|
|
10499
|
+
_enabled = true;
|
|
10500
|
+
MAX_CHUNKS_PER_REPO = 1e5;
|
|
10501
|
+
locks = /* @__PURE__ */ new Map();
|
|
10502
|
+
init_queries();
|
|
10503
|
+
MAX_API_CALLS = 2e3;
|
|
10504
|
+
POOL_SIZE = 32;
|
|
10505
|
+
MAX_BATCH_TOKENS = 1e5;
|
|
10506
|
+
CHARS_PER_TOKEN = 3;
|
|
10260
10507
|
init_queries();
|
|
10508
|
+
PURPOSE_BATCH_LIMIT = 200;
|
|
10509
|
+
PURPOSE_CONCURRENCY = 10;
|
|
10261
10510
|
init_queries();
|
|
10262
10511
|
TERM_BATCH_SIZE = 32;
|
|
10263
10512
|
SIMILARITY_THRESHOLD = 0.75;
|
|
@@ -10369,23 +10618,6 @@ var init_dist = __esm({
|
|
|
10369
10618
|
"component"
|
|
10370
10619
|
]);
|
|
10371
10620
|
init_queries();
|
|
10372
|
-
execFileAsync2 = promisify2(execFile2);
|
|
10373
|
-
MAX_COMMITS = 2e3;
|
|
10374
|
-
MAX_FILES_PER_COMMIT = 20;
|
|
10375
|
-
RECENT_DAYS = 90;
|
|
10376
|
-
init_queries();
|
|
10377
|
-
_enabled = true;
|
|
10378
|
-
MAX_CHUNKS_PER_REPO = 1e5;
|
|
10379
|
-
locks = /* @__PURE__ */ new Map();
|
|
10380
|
-
init_queries();
|
|
10381
|
-
MAX_API_CALLS = 2e3;
|
|
10382
|
-
POOL_SIZE = 32;
|
|
10383
|
-
MAX_BATCH_TOKENS = 1e5;
|
|
10384
|
-
CHARS_PER_TOKEN = 3;
|
|
10385
|
-
init_queries();
|
|
10386
|
-
PURPOSE_BATCH_LIMIT = 200;
|
|
10387
|
-
PURPOSE_CONCURRENCY = 10;
|
|
10388
|
-
init_queries();
|
|
10389
10621
|
watchers = /* @__PURE__ */ new Map();
|
|
10390
10622
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
10391
10623
|
IGNORED = [
|
|
@@ -10588,6 +10820,36 @@ var init_dist = __esm({
|
|
|
10588
10820
|
CACHE_MAX = 20;
|
|
10589
10821
|
cache = /* @__PURE__ */ new Map();
|
|
10590
10822
|
init_queries();
|
|
10823
|
+
RequestTrace = class {
|
|
10824
|
+
steps = [];
|
|
10825
|
+
pending = /* @__PURE__ */ new Map();
|
|
10826
|
+
step(label) {
|
|
10827
|
+
this.pending.set(label, performance.now());
|
|
10828
|
+
}
|
|
10829
|
+
end(label, detail) {
|
|
10830
|
+
const start = this.pending.get(label);
|
|
10831
|
+
if (start === void 0) return;
|
|
10832
|
+
this.pending.delete(label);
|
|
10833
|
+
this.steps.push({
|
|
10834
|
+
step: label,
|
|
10835
|
+
duration_ms: Math.round(performance.now() - start),
|
|
10836
|
+
...detail ? { detail } : {}
|
|
10837
|
+
});
|
|
10838
|
+
}
|
|
10839
|
+
add(label, duration_ms, detail) {
|
|
10840
|
+
this.steps.push({
|
|
10841
|
+
step: label,
|
|
10842
|
+
duration_ms,
|
|
10843
|
+
...detail ? { detail } : {}
|
|
10844
|
+
});
|
|
10845
|
+
}
|
|
10846
|
+
toJSON() {
|
|
10847
|
+
return this.steps;
|
|
10848
|
+
}
|
|
10849
|
+
serialize() {
|
|
10850
|
+
return JSON.stringify(this.steps);
|
|
10851
|
+
}
|
|
10852
|
+
};
|
|
10591
10853
|
}
|
|
10592
10854
|
});
|
|
10593
10855
|
|
|
@@ -10655,22 +10917,23 @@ function createCloudCapabilities(apiKey, trackUsage, logRequest) {
|
|
|
10655
10917
|
return {
|
|
10656
10918
|
async embedTexts(texts, isQuery) {
|
|
10657
10919
|
const start = performance.now();
|
|
10920
|
+
const reqBody = JSON.stringify({
|
|
10921
|
+
input: texts,
|
|
10922
|
+
model: "voyage-code-3",
|
|
10923
|
+
input_type: isQuery ? "query" : "document"
|
|
10924
|
+
});
|
|
10658
10925
|
const res = await fetch(`${CLOUD_API_URL}/api/proxy/embed`, {
|
|
10659
10926
|
method: "POST",
|
|
10660
10927
|
headers,
|
|
10661
|
-
body:
|
|
10662
|
-
input: texts,
|
|
10663
|
-
model: "voyage-code-3",
|
|
10664
|
-
input_type: isQuery ? "query" : "document"
|
|
10665
|
-
})
|
|
10928
|
+
body: reqBody
|
|
10666
10929
|
});
|
|
10930
|
+
const resText = await res.text();
|
|
10667
10931
|
const duration3 = Math.round(performance.now() - start);
|
|
10668
|
-
logRequest?.("POST", "/api/proxy/embed", res.status, duration3, "cloud");
|
|
10932
|
+
logRequest?.("POST", "/api/proxy/embed", res.status, duration3, "cloud", reqBody, resText);
|
|
10669
10933
|
if (!res.ok) {
|
|
10670
|
-
|
|
10671
|
-
throw new Error(`Cloud embed failed (${res.status}): ${err}`);
|
|
10934
|
+
throw new Error(`Cloud embed failed (${res.status}): ${resText}`);
|
|
10672
10935
|
}
|
|
10673
|
-
const data =
|
|
10936
|
+
const data = JSON.parse(resText);
|
|
10674
10937
|
trackUsage?.("embedding_requests");
|
|
10675
10938
|
trackUsage?.("embedding_chunks", texts.length);
|
|
10676
10939
|
return (data.data ?? []).map((d) => d.embedding);
|
|
@@ -10684,24 +10947,25 @@ function createCloudCapabilities(apiKey, trackUsage, logRequest) {
|
|
|
10684
10947
|
content.slice(0, 2e3)
|
|
10685
10948
|
].filter(Boolean).join("\n");
|
|
10686
10949
|
const start = performance.now();
|
|
10950
|
+
const reqBody = JSON.stringify({
|
|
10951
|
+
messages: [
|
|
10952
|
+
{ role: "system", content: "You are a code analyst. Output ONLY a 1-sentence purpose summary for the given file. No preamble." },
|
|
10953
|
+
{ role: "user", content: prompt }
|
|
10954
|
+
],
|
|
10955
|
+
max_tokens: 128
|
|
10956
|
+
});
|
|
10687
10957
|
const res = await fetch(`${CLOUD_API_URL}/api/proxy/chat`, {
|
|
10688
10958
|
method: "POST",
|
|
10689
10959
|
headers,
|
|
10690
|
-
body:
|
|
10691
|
-
messages: [
|
|
10692
|
-
{ role: "system", content: "You are a code analyst. Output ONLY a 1-sentence purpose summary for the given file. No preamble." },
|
|
10693
|
-
{ role: "user", content: prompt }
|
|
10694
|
-
],
|
|
10695
|
-
max_tokens: 128
|
|
10696
|
-
})
|
|
10960
|
+
body: reqBody
|
|
10697
10961
|
});
|
|
10962
|
+
const resText = await res.text();
|
|
10698
10963
|
const duration3 = Math.round(performance.now() - start);
|
|
10699
|
-
logRequest?.("POST", "/api/proxy/chat", res.status, duration3, "cloud");
|
|
10964
|
+
logRequest?.("POST", "/api/proxy/chat", res.status, duration3, "cloud", reqBody, resText);
|
|
10700
10965
|
if (!res.ok) {
|
|
10701
|
-
|
|
10702
|
-
throw new Error(`Cloud purpose failed (${res.status}): ${err}`);
|
|
10966
|
+
throw new Error(`Cloud purpose failed (${res.status}): ${resText}`);
|
|
10703
10967
|
}
|
|
10704
|
-
const data =
|
|
10968
|
+
const data = JSON.parse(resText);
|
|
10705
10969
|
trackUsage?.("purpose_requests");
|
|
10706
10970
|
return data.choices?.[0]?.message?.content?.trim() ?? "";
|
|
10707
10971
|
}
|
|
@@ -32148,6 +32412,10 @@ var mcp_exports = {};
|
|
|
32148
32412
|
__export(mcp_exports, {
|
|
32149
32413
|
createMcpServer: () => createMcpServer
|
|
32150
32414
|
});
|
|
32415
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
32416
|
+
function logMcp(method, path, status, duration3, reqBody, resSize, resBody, trace) {
|
|
32417
|
+
getRawDb().prepare(LOG_SQL).run(randomUUID3(), method, path, status, duration3, reqBody ?? null, resSize ?? null, resBody ?? null, trace ?? null);
|
|
32418
|
+
}
|
|
32151
32419
|
function text2(s) {
|
|
32152
32420
|
return { content: [{ type: "text", text: s }] };
|
|
32153
32421
|
}
|
|
@@ -32169,11 +32437,22 @@ function createMcpServer(db, caps) {
|
|
|
32169
32437
|
inputSchema: { repo_path: external_exports.string(), goal: external_exports.string() }
|
|
32170
32438
|
},
|
|
32171
32439
|
async ({ repo_path, goal }) => {
|
|
32440
|
+
const trace = new RequestTrace();
|
|
32441
|
+
const start = performance.now();
|
|
32172
32442
|
try {
|
|
32443
|
+
trace.step("findRepo");
|
|
32173
32444
|
const repoId = findOrRegisterRepo(db, repo_path);
|
|
32174
|
-
|
|
32445
|
+
trace.end("findRepo");
|
|
32446
|
+
const repo = repoQueries.getById(db, repoId);
|
|
32447
|
+
const useEmbeddings = repo?.enable_embeddings === 1;
|
|
32448
|
+
const result = await buildContext(db, repoId, goal, caps, trace, { useEmbeddings });
|
|
32449
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32450
|
+
const reqBody = JSON.stringify({ repo_path, goal });
|
|
32451
|
+
logMcp("MCP", "/tool/get_context", 200, duration3, reqBody, result.context_pack.length, result.context_pack, trace.serialize());
|
|
32175
32452
|
return text2(result.context_pack);
|
|
32176
32453
|
} catch (e) {
|
|
32454
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32455
|
+
logMcp("MCP", "/tool/get_context", 500, duration3, JSON.stringify({ repo_path, goal }), void 0, e.message, trace.serialize());
|
|
32177
32456
|
return error2(e.message);
|
|
32178
32457
|
}
|
|
32179
32458
|
}
|
|
@@ -32185,10 +32464,16 @@ function createMcpServer(db, caps) {
|
|
|
32185
32464
|
inputSchema: {}
|
|
32186
32465
|
},
|
|
32187
32466
|
async () => {
|
|
32467
|
+
const start = performance.now();
|
|
32188
32468
|
try {
|
|
32189
32469
|
const repos2 = listRepos(db);
|
|
32190
|
-
|
|
32470
|
+
const body = JSON.stringify(repos2, null, 2);
|
|
32471
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32472
|
+
logMcp("MCP", "/tool/list_repos", 200, duration3, void 0, body.length, body);
|
|
32473
|
+
return text2(body);
|
|
32191
32474
|
} catch (e) {
|
|
32475
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32476
|
+
logMcp("MCP", "/tool/list_repos", 500, duration3, void 0, void 0, e.message);
|
|
32192
32477
|
return error2(e.message);
|
|
32193
32478
|
}
|
|
32194
32479
|
}
|
|
@@ -32200,12 +32485,21 @@ function createMcpServer(db, caps) {
|
|
|
32200
32485
|
inputSchema: { repo_path: external_exports.string() }
|
|
32201
32486
|
},
|
|
32202
32487
|
async ({ repo_path }) => {
|
|
32488
|
+
const start = performance.now();
|
|
32203
32489
|
try {
|
|
32204
32490
|
const repo = repoQueries.getByPath(db, repo_path);
|
|
32205
|
-
if (!repo)
|
|
32491
|
+
if (!repo) {
|
|
32492
|
+
logMcp("MCP", "/tool/get_status", 404, Math.round(performance.now() - start), JSON.stringify({ repo_path }));
|
|
32493
|
+
return error2("repo not found at " + repo_path);
|
|
32494
|
+
}
|
|
32206
32495
|
const status = await getRepoStatus(db, repo.id);
|
|
32207
|
-
|
|
32496
|
+
const body = JSON.stringify(status, null, 2);
|
|
32497
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32498
|
+
logMcp("MCP", "/tool/get_status", 200, duration3, JSON.stringify({ repo_path }), body.length, body);
|
|
32499
|
+
return text2(body);
|
|
32208
32500
|
} catch (e) {
|
|
32501
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32502
|
+
logMcp("MCP", "/tool/get_status", 500, duration3, JSON.stringify({ repo_path }), void 0, e.message);
|
|
32209
32503
|
return error2(e.message);
|
|
32210
32504
|
}
|
|
32211
32505
|
}
|
|
@@ -32217,23 +32511,35 @@ function createMcpServer(db, caps) {
|
|
|
32217
32511
|
inputSchema: { repo_path: external_exports.string(), force: external_exports.boolean().optional() }
|
|
32218
32512
|
},
|
|
32219
32513
|
async ({ repo_path, force }) => {
|
|
32514
|
+
const trace = new RequestTrace();
|
|
32515
|
+
const start = performance.now();
|
|
32220
32516
|
try {
|
|
32517
|
+
trace.step("findRepo");
|
|
32221
32518
|
const repoId = findOrRegisterRepo(db, repo_path);
|
|
32222
|
-
|
|
32223
|
-
|
|
32519
|
+
trace.end("findRepo");
|
|
32520
|
+
const result = await runIndex(db, repoId, caps, force ?? false, void 0, trace);
|
|
32521
|
+
const body = JSON.stringify(result, null, 2);
|
|
32522
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32523
|
+
logMcp("MCP", "/tool/index_repo", 200, duration3, JSON.stringify({ repo_path, force }), body.length, body, trace.serialize());
|
|
32524
|
+
return text2(body);
|
|
32224
32525
|
} catch (e) {
|
|
32526
|
+
const duration3 = Math.round(performance.now() - start);
|
|
32527
|
+
logMcp("MCP", "/tool/index_repo", 500, duration3, JSON.stringify({ repo_path, force }), void 0, e.message, trace.serialize());
|
|
32225
32528
|
return error2(e.message);
|
|
32226
32529
|
}
|
|
32227
32530
|
}
|
|
32228
32531
|
);
|
|
32229
32532
|
return server;
|
|
32230
32533
|
}
|
|
32534
|
+
var LOG_SQL;
|
|
32231
32535
|
var init_mcp2 = __esm({
|
|
32232
32536
|
"apps/daemon/src/mcp.ts"() {
|
|
32233
32537
|
"use strict";
|
|
32234
32538
|
init_dist();
|
|
32235
32539
|
init_mcp();
|
|
32236
32540
|
init_zod();
|
|
32541
|
+
LOG_SQL = `INSERT INTO request_logs (id, method, path, status, duration_ms, source, request_body, response_size, response_body, trace, created_at)
|
|
32542
|
+
VALUES (?, ?, ?, ?, ?, 'mcp', ?, ?, ?, ?, datetime('now'))`;
|
|
32237
32543
|
}
|
|
32238
32544
|
});
|
|
32239
32545
|
|
|
@@ -34594,14 +34900,32 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34594
34900
|
let lastPrune = 0;
|
|
34595
34901
|
app.use("*", async (c, next) => {
|
|
34596
34902
|
const start = performance.now();
|
|
34903
|
+
const path = new URL(c.req.url).pathname;
|
|
34904
|
+
if (path.startsWith("/dashboard/") || path.startsWith("/api/dashboard/") || path === "/health" || path.endsWith("/events")) {
|
|
34905
|
+
return next();
|
|
34906
|
+
}
|
|
34907
|
+
const trace = new RequestTrace();
|
|
34908
|
+
c.set("trace", trace);
|
|
34909
|
+
let reqBody;
|
|
34910
|
+
if (c.req.method !== "GET") {
|
|
34911
|
+
try {
|
|
34912
|
+
reqBody = await c.req.raw.clone().text();
|
|
34913
|
+
} catch {
|
|
34914
|
+
}
|
|
34915
|
+
}
|
|
34597
34916
|
await next();
|
|
34598
34917
|
const duration3 = Math.round(performance.now() - start);
|
|
34599
|
-
const path = new URL(c.req.url).pathname;
|
|
34600
34918
|
const source = deriveSource(c.req.raw, path);
|
|
34601
|
-
|
|
34602
|
-
|
|
34919
|
+
const traceData = trace.toJSON().length > 0 ? trace.serialize() : void 0;
|
|
34920
|
+
let resBody;
|
|
34921
|
+
let resSize;
|
|
34922
|
+
try {
|
|
34923
|
+
resBody = await c.res.clone().text();
|
|
34924
|
+
resSize = resBody.length;
|
|
34925
|
+
} catch {
|
|
34926
|
+
}
|
|
34603
34927
|
try {
|
|
34604
|
-
logQueries.insert(db, c.req.method, path, c.res.status, duration3, source);
|
|
34928
|
+
logQueries.insert(db, c.req.method, path, c.res.status, duration3, source, reqBody, resSize, resBody, traceData);
|
|
34605
34929
|
const now2 = Date.now();
|
|
34606
34930
|
if (now2 - lastPrune > 36e5) {
|
|
34607
34931
|
lastPrune = now2;
|
|
@@ -34611,14 +34935,17 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34611
34935
|
}
|
|
34612
34936
|
});
|
|
34613
34937
|
trackRoute("GET", "/health");
|
|
34614
|
-
app.get("/health", (c) => c.json({ status: "ok", version: "0.1.0" }));
|
|
34938
|
+
app.get("/health", (c) => c.json({ status: "ok", version: "0.1.0", cloud_url: CLOUD_API_URL }));
|
|
34615
34939
|
trackRoute("POST", "/telemetry/track");
|
|
34616
34940
|
app.post("/telemetry/track", async (c) => {
|
|
34617
34941
|
try {
|
|
34618
34942
|
if (!isTelemetryEnabled()) return c.json({ ok: true, skipped: true });
|
|
34943
|
+
const trace = c.get("trace");
|
|
34619
34944
|
const { event_type, event_data } = await c.req.json();
|
|
34620
34945
|
if (!event_type || typeof event_type !== "string") return c.json({ error: "event_type required" }, 400);
|
|
34946
|
+
trace.step("track");
|
|
34621
34947
|
track(db, event_type, event_data);
|
|
34948
|
+
trace.end("track", event_type);
|
|
34622
34949
|
return c.json({ ok: true });
|
|
34623
34950
|
} catch {
|
|
34624
34951
|
return c.json({ ok: true });
|
|
@@ -34627,10 +34954,13 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34627
34954
|
trackRoute("POST", "/repo/register");
|
|
34628
34955
|
app.post("/repo/register", async (c) => {
|
|
34629
34956
|
try {
|
|
34957
|
+
const trace = c.get("trace");
|
|
34630
34958
|
const { root_path, name, remote_url } = await c.req.json();
|
|
34631
34959
|
if (!root_path) return c.json({ error: "root_path required" }, 400);
|
|
34960
|
+
trace.step("quotaCheck");
|
|
34632
34961
|
const currentRepos = listRepos(db).length;
|
|
34633
34962
|
const maxRepos = quotaCache?.quota?.maxRepos ?? 50;
|
|
34963
|
+
trace.end("quotaCheck", `${currentRepos}/${maxRepos}`);
|
|
34634
34964
|
if (currentRepos >= maxRepos) {
|
|
34635
34965
|
return c.json({
|
|
34636
34966
|
error: "Repo limit reached",
|
|
@@ -34639,25 +34969,57 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34639
34969
|
plan: quotaCache?.plan ?? "unknown"
|
|
34640
34970
|
}, 429);
|
|
34641
34971
|
}
|
|
34972
|
+
trace.step("registerRepo");
|
|
34642
34973
|
const result = registerRepo(db, root_path, name, remote_url);
|
|
34974
|
+
trace.end("registerRepo", result.created ? "created" : "existing");
|
|
34643
34975
|
if (result.created) {
|
|
34644
|
-
runIndex(db, result.repo_id, caps, false, emitRepoEvent).then((r) => {
|
|
34976
|
+
runIndex(db, result.repo_id, caps, false, emitRepoEvent).then(async (r) => {
|
|
34977
|
+
const bg = new RequestTrace();
|
|
34978
|
+
const bgStart = performance.now();
|
|
34979
|
+
const results = { index: r };
|
|
34980
|
+
bg.add("index", r.duration_ms, `${r.files_scanned} files, +${r.chunks_created} chunks`);
|
|
34645
34981
|
if (r.files_scanned > 0) usageQueries.increment(db, "repos_indexed");
|
|
34982
|
+
const repo = repoQueries.getById(db, result.repo_id);
|
|
34983
|
+
const reqBody = JSON.stringify({ repo_id: result.repo_id, repo_name: repo?.name });
|
|
34646
34984
|
const tasks = [];
|
|
34647
|
-
if (quotaRemaining("embeddingChunks") > 0) {
|
|
34648
|
-
|
|
34985
|
+
if (repo?.enable_vocab_clusters && quotaRemaining("embeddingChunks") > 0) {
|
|
34986
|
+
bg.step("vocabClusters");
|
|
34987
|
+
tasks.push(buildVocabClusters(db, result.repo_id, caps).then(() => {
|
|
34988
|
+
bg.end("vocabClusters");
|
|
34989
|
+
results.vocabClusters = "done";
|
|
34990
|
+
}));
|
|
34649
34991
|
} else {
|
|
34650
|
-
|
|
34992
|
+
bg.add("vocabClusters", 0, !repo?.enable_vocab_clusters ? "disabled" : "quota exceeded");
|
|
34651
34993
|
}
|
|
34652
|
-
if (quotaRemaining("
|
|
34653
|
-
|
|
34994
|
+
if (repo?.enable_embeddings && quotaRemaining("embeddingChunks") > 0) {
|
|
34995
|
+
bg.step("embeddings");
|
|
34996
|
+
tasks.push(ensureEmbedded(db, result.repo_id, caps).then((er) => {
|
|
34997
|
+
bg.end("embeddings", `${er.embedded_count} embedded`);
|
|
34998
|
+
results.embeddings = er;
|
|
34999
|
+
}));
|
|
34654
35000
|
} else {
|
|
34655
|
-
|
|
35001
|
+
bg.add("embeddings", 0, !repo?.enable_embeddings ? "disabled" : "quota exceeded");
|
|
34656
35002
|
}
|
|
34657
|
-
|
|
34658
|
-
|
|
35003
|
+
if (repo?.enable_summaries && quotaRemaining("purposeRequests") > 0) {
|
|
35004
|
+
bg.step("purpose");
|
|
35005
|
+
tasks.push(enrichPurpose(db, result.repo_id, caps).then((pr) => {
|
|
35006
|
+
bg.end("purpose", `${pr.enriched} enriched`);
|
|
35007
|
+
results.purpose = pr;
|
|
35008
|
+
}));
|
|
35009
|
+
} else {
|
|
35010
|
+
bg.add("purpose", 0, !repo?.enable_summaries ? "disabled" : "quota exceeded");
|
|
35011
|
+
}
|
|
35012
|
+
await Promise.all(tasks);
|
|
35013
|
+
const resBody = JSON.stringify(results);
|
|
35014
|
+
const bgDuration = Math.round(performance.now() - bgStart);
|
|
35015
|
+
logQueries.insert(db, "BG", `/enrichment/${result.repo_id}`, 200, bgDuration, "system", reqBody, resBody.length, resBody, bg.serialize());
|
|
35016
|
+
}).catch((e) => {
|
|
35017
|
+
logQueries.insert(db, "BG", `/enrichment/${result.repo_id}`, 500, 0, "system", JSON.stringify({ repo_id: result.repo_id }), void 0, String(e));
|
|
35018
|
+
});
|
|
34659
35019
|
}
|
|
35020
|
+
trace.step("startWatcher");
|
|
34660
35021
|
startWatcher(db, result.repo_id, root_path);
|
|
35022
|
+
trace.end("startWatcher");
|
|
34661
35023
|
emitRepoEvent();
|
|
34662
35024
|
return c.json(result);
|
|
34663
35025
|
} catch (e) {
|
|
@@ -34667,7 +35029,11 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34667
35029
|
trackRoute("GET", "/repo/list");
|
|
34668
35030
|
app.get("/repo/list", (c) => {
|
|
34669
35031
|
try {
|
|
34670
|
-
|
|
35032
|
+
const trace = c.get("trace");
|
|
35033
|
+
trace.step("listRepos");
|
|
35034
|
+
const repos2 = listRepos(db);
|
|
35035
|
+
trace.end("listRepos", `${repos2.length} repos`);
|
|
35036
|
+
return c.json({ repos: repos2 });
|
|
34671
35037
|
} catch (e) {
|
|
34672
35038
|
return c.json({ error: e.message }, 500);
|
|
34673
35039
|
}
|
|
@@ -34675,7 +35041,11 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34675
35041
|
trackRoute("GET", "/repo/list/detailed");
|
|
34676
35042
|
app.get("/repo/list/detailed", (c) => {
|
|
34677
35043
|
try {
|
|
35044
|
+
const trace = c.get("trace");
|
|
35045
|
+
trace.step("listRepos");
|
|
34678
35046
|
const repos2 = listRepos(db);
|
|
35047
|
+
trace.end("listRepos", `${repos2.length} repos`);
|
|
35048
|
+
trace.step("loadStats");
|
|
34679
35049
|
const detailed = repos2.map((r) => {
|
|
34680
35050
|
const stats = chunkQueries.getStats(db, r.id);
|
|
34681
35051
|
return {
|
|
@@ -34689,6 +35059,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34689
35059
|
last_indexed_at: r.last_indexed_at
|
|
34690
35060
|
};
|
|
34691
35061
|
});
|
|
35062
|
+
trace.end("loadStats");
|
|
34692
35063
|
return c.json({ repos: detailed });
|
|
34693
35064
|
} catch (e) {
|
|
34694
35065
|
return c.json({ error: e.message }, 500);
|
|
@@ -34699,7 +35070,10 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34699
35070
|
trackRoute("GET", "/repo/:id");
|
|
34700
35071
|
app.get("/repo/:id", (c) => {
|
|
34701
35072
|
try {
|
|
35073
|
+
const trace = c.get("trace");
|
|
35074
|
+
trace.step("getRepo");
|
|
34702
35075
|
const repo = getRepo(db, c.req.param("id"));
|
|
35076
|
+
trace.end("getRepo");
|
|
34703
35077
|
return c.json(repo);
|
|
34704
35078
|
} catch (e) {
|
|
34705
35079
|
if (e.message === "repo not found") return c.json({ error: "repo not found" }, 404);
|
|
@@ -34709,10 +35083,15 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34709
35083
|
trackRoute("DELETE", "/repo/:id");
|
|
34710
35084
|
app.delete("/repo/:id", async (c) => {
|
|
34711
35085
|
try {
|
|
35086
|
+
const trace = c.get("trace");
|
|
34712
35087
|
const id = c.req.param("id");
|
|
35088
|
+
trace.step("stopWatcher");
|
|
34713
35089
|
await stopWatcher(id).catch(() => {
|
|
34714
35090
|
});
|
|
35091
|
+
trace.end("stopWatcher");
|
|
35092
|
+
trace.step("removeRepo");
|
|
34715
35093
|
const result = removeRepo(db, id);
|
|
35094
|
+
trace.end("removeRepo");
|
|
34716
35095
|
emitRepoEvent();
|
|
34717
35096
|
return c.json(result);
|
|
34718
35097
|
} catch (e) {
|
|
@@ -34723,7 +35102,10 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34723
35102
|
trackRoute("GET", "/repo/:id/status");
|
|
34724
35103
|
app.get("/repo/:id/status", async (c) => {
|
|
34725
35104
|
try {
|
|
35105
|
+
const trace = c.get("trace");
|
|
35106
|
+
trace.step("getRepoStatus");
|
|
34726
35107
|
const status = await getRepoStatus(db, c.req.param("id"));
|
|
35108
|
+
trace.end("getRepoStatus");
|
|
34727
35109
|
return c.json({
|
|
34728
35110
|
...status,
|
|
34729
35111
|
has_capabilities: !!caps,
|
|
@@ -34740,7 +35122,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34740
35122
|
try {
|
|
34741
35123
|
const { repo_id, goal } = await c.req.json();
|
|
34742
35124
|
if (!repo_id || !goal) return c.json({ error: "repo_id and goal required" }, 400);
|
|
34743
|
-
const
|
|
35125
|
+
const repo = repoQueries.getById(db, repo_id);
|
|
35126
|
+
const useEmbeddings = repo?.enable_embeddings === 1;
|
|
35127
|
+
const result = await buildContext(db, repo_id, goal, caps, c.get("trace"), { useEmbeddings });
|
|
34744
35128
|
usageQueries.increment(db, "context_queries");
|
|
34745
35129
|
return c.json(result);
|
|
34746
35130
|
} catch (e) {
|
|
@@ -34752,20 +35136,48 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34752
35136
|
try {
|
|
34753
35137
|
const { repo_id, force } = await c.req.json();
|
|
34754
35138
|
if (!repo_id) return c.json({ error: "repo_id required" }, 400);
|
|
34755
|
-
const result = await runIndex(db, repo_id, caps, force ?? false, emitRepoEvent);
|
|
35139
|
+
const result = await runIndex(db, repo_id, caps, force ?? false, emitRepoEvent, c.get("trace"));
|
|
34756
35140
|
if (result.files_scanned > 0) usageQueries.increment(db, "repos_indexed");
|
|
35141
|
+
const repo = repoQueries.getById(db, repo_id);
|
|
35142
|
+
const bg = new RequestTrace();
|
|
35143
|
+
const bgStart = performance.now();
|
|
35144
|
+
const results = { index: result };
|
|
35145
|
+
const reqBody = JSON.stringify({ repo_id, repo_name: repo?.name, force });
|
|
34757
35146
|
const tasks = [];
|
|
34758
|
-
if (quotaRemaining("embeddingChunks") > 0) {
|
|
34759
|
-
|
|
35147
|
+
if (repo?.enable_vocab_clusters && quotaRemaining("embeddingChunks") > 0) {
|
|
35148
|
+
bg.step("vocabClusters");
|
|
35149
|
+
tasks.push(buildVocabClusters(db, repo_id, caps).then(() => {
|
|
35150
|
+
bg.end("vocabClusters");
|
|
35151
|
+
results.vocabClusters = "done";
|
|
35152
|
+
}));
|
|
34760
35153
|
} else {
|
|
34761
|
-
|
|
35154
|
+
bg.add("vocabClusters", 0, !repo?.enable_vocab_clusters ? "disabled" : "quota exceeded");
|
|
34762
35155
|
}
|
|
34763
|
-
if (quotaRemaining("
|
|
34764
|
-
|
|
35156
|
+
if (repo?.enable_embeddings && quotaRemaining("embeddingChunks") > 0) {
|
|
35157
|
+
bg.step("embeddings");
|
|
35158
|
+
tasks.push(ensureEmbedded(db, repo_id, caps).then((er) => {
|
|
35159
|
+
bg.end("embeddings", `${er.embedded_count} embedded`);
|
|
35160
|
+
results.embeddings = er;
|
|
35161
|
+
}));
|
|
34765
35162
|
} else {
|
|
34766
|
-
|
|
35163
|
+
bg.add("embeddings", 0, !repo?.enable_embeddings ? "disabled" : "quota exceeded");
|
|
34767
35164
|
}
|
|
34768
|
-
|
|
35165
|
+
if (repo?.enable_summaries && quotaRemaining("purposeRequests") > 0) {
|
|
35166
|
+
bg.step("purpose");
|
|
35167
|
+
tasks.push(enrichPurpose(db, repo_id, caps).then((pr) => {
|
|
35168
|
+
bg.end("purpose", `${pr.enriched} enriched`);
|
|
35169
|
+
results.purpose = pr;
|
|
35170
|
+
}));
|
|
35171
|
+
} else {
|
|
35172
|
+
bg.add("purpose", 0, !repo?.enable_summaries ? "disabled" : "quota exceeded");
|
|
35173
|
+
}
|
|
35174
|
+
Promise.all(tasks).then(() => {
|
|
35175
|
+
const resBody = JSON.stringify(results);
|
|
35176
|
+
const bgDuration = Math.round(performance.now() - bgStart);
|
|
35177
|
+
logQueries.insert(db, "BG", `/enrichment/${repo_id}`, 200, bgDuration, "system", reqBody, resBody.length, resBody, bg.serialize());
|
|
35178
|
+
}).catch((e) => {
|
|
35179
|
+
logQueries.insert(db, "BG", `/enrichment/${repo_id}`, 500, 0, "system", reqBody, void 0, String(e));
|
|
35180
|
+
});
|
|
34769
35181
|
return c.json(result);
|
|
34770
35182
|
} catch (e) {
|
|
34771
35183
|
return c.json({ error: e.message }, 500);
|
|
@@ -34774,16 +35186,23 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34774
35186
|
trackRoute("GET", "/index/status/:repo_id");
|
|
34775
35187
|
app.get("/index/status/:repo_id", async (c) => {
|
|
34776
35188
|
try {
|
|
35189
|
+
const trace = c.get("trace");
|
|
34777
35190
|
const repoId = c.req.param("repo_id");
|
|
35191
|
+
trace.step("getRepo");
|
|
34778
35192
|
const repo = repoQueries.getById(db, repoId);
|
|
35193
|
+
trace.end("getRepo");
|
|
34779
35194
|
if (!repo) return c.json({ error: "repo not found" }, 404);
|
|
35195
|
+
trace.step("getHeadCommit");
|
|
34780
35196
|
let currentHead = null;
|
|
34781
35197
|
try {
|
|
34782
35198
|
currentHead = await getHeadCommit(repo.root_path);
|
|
34783
35199
|
} catch {
|
|
34784
35200
|
}
|
|
35201
|
+
trace.end("getHeadCommit");
|
|
34785
35202
|
const isStale = !!(currentHead && repo.last_indexed_commit !== currentHead);
|
|
35203
|
+
trace.step("getStats");
|
|
34786
35204
|
const stats = chunkQueries.getStats(db, repoId);
|
|
35205
|
+
trace.end("getStats");
|
|
34787
35206
|
return c.json({
|
|
34788
35207
|
index_status: repo.index_status,
|
|
34789
35208
|
last_indexed_commit: repo.last_indexed_commit,
|
|
@@ -34802,10 +35221,15 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34802
35221
|
trackRoute("POST", "/index/watch");
|
|
34803
35222
|
app.post("/index/watch", async (c) => {
|
|
34804
35223
|
try {
|
|
35224
|
+
const trace = c.get("trace");
|
|
34805
35225
|
const { repo_id } = await c.req.json();
|
|
34806
35226
|
if (!repo_id) return c.json({ error: "repo_id required" }, 400);
|
|
35227
|
+
trace.step("getRepo");
|
|
34807
35228
|
const repo = getRepo(db, repo_id);
|
|
35229
|
+
trace.end("getRepo");
|
|
35230
|
+
trace.step("startWatcher");
|
|
34808
35231
|
const result = startWatcher(db, repo_id, repo.root_path);
|
|
35232
|
+
trace.end("startWatcher");
|
|
34809
35233
|
emitRepoEvent();
|
|
34810
35234
|
return c.json(result);
|
|
34811
35235
|
} catch (e) {
|
|
@@ -34816,9 +35240,12 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34816
35240
|
trackRoute("POST", "/index/unwatch");
|
|
34817
35241
|
app.post("/index/unwatch", async (c) => {
|
|
34818
35242
|
try {
|
|
35243
|
+
const trace = c.get("trace");
|
|
34819
35244
|
const { repo_id } = await c.req.json();
|
|
34820
35245
|
if (!repo_id) return c.json({ error: "repo_id required" }, 400);
|
|
35246
|
+
trace.step("stopWatcher");
|
|
34821
35247
|
const result = await stopWatcher(repo_id);
|
|
35248
|
+
trace.end("stopWatcher");
|
|
34822
35249
|
emitRepoEvent();
|
|
34823
35250
|
return c.json(result);
|
|
34824
35251
|
} catch (e) {
|
|
@@ -34828,7 +35255,11 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34828
35255
|
trackRoute("GET", "/index/watch-status/:repo_id");
|
|
34829
35256
|
app.get("/index/watch-status/:repo_id", (c) => {
|
|
34830
35257
|
try {
|
|
34831
|
-
|
|
35258
|
+
const trace = c.get("trace");
|
|
35259
|
+
trace.step("getWatcherStatus");
|
|
35260
|
+
const status = getWatcherStatus(c.req.param("repo_id"));
|
|
35261
|
+
trace.end("getWatcherStatus");
|
|
35262
|
+
return c.json(status);
|
|
34832
35263
|
} catch (e) {
|
|
34833
35264
|
return c.json({ error: e.message }, 500);
|
|
34834
35265
|
}
|
|
@@ -34836,14 +35267,19 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34836
35267
|
trackRoute("GET", "/daemon/stats");
|
|
34837
35268
|
app.get("/daemon/stats", (c) => {
|
|
34838
35269
|
try {
|
|
35270
|
+
const trace = c.get("trace");
|
|
35271
|
+
trace.step("listRepos");
|
|
34839
35272
|
const repos2 = listRepos(db);
|
|
35273
|
+
trace.end("listRepos", `${repos2.length} repos`);
|
|
34840
35274
|
let totalChunks = 0;
|
|
34841
35275
|
let totalEmbeddings = 0;
|
|
35276
|
+
trace.step("aggregateStats");
|
|
34842
35277
|
for (const r of repos2) {
|
|
34843
35278
|
const s = chunkQueries.getStats(db, r.id);
|
|
34844
35279
|
totalChunks += s.chunk_count;
|
|
34845
35280
|
totalEmbeddings += s.embedded_count;
|
|
34846
35281
|
}
|
|
35282
|
+
trace.end("aggregateStats");
|
|
34847
35283
|
return c.json({
|
|
34848
35284
|
repos_count: repos2.length,
|
|
34849
35285
|
total_chunks: totalChunks,
|
|
@@ -34901,8 +35337,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34901
35337
|
start(ctrl) {
|
|
34902
35338
|
repoClients.add(ctrl);
|
|
34903
35339
|
},
|
|
34904
|
-
cancel(
|
|
34905
|
-
repoClients.delete(ctrl);
|
|
35340
|
+
cancel() {
|
|
34906
35341
|
}
|
|
34907
35342
|
});
|
|
34908
35343
|
return new Response(stream, {
|
|
@@ -34915,18 +35350,19 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34915
35350
|
});
|
|
34916
35351
|
const authClients = /* @__PURE__ */ new Set();
|
|
34917
35352
|
const authDir = join5(homedir3(), ".lens");
|
|
35353
|
+
function emitAuthEvent() {
|
|
35354
|
+
for (const ctrl of authClients) {
|
|
35355
|
+
try {
|
|
35356
|
+
ctrl.enqueue(encoder.encode("data: auth-changed\n\n"));
|
|
35357
|
+
} catch {
|
|
35358
|
+
authClients.delete(ctrl);
|
|
35359
|
+
}
|
|
35360
|
+
}
|
|
35361
|
+
}
|
|
34918
35362
|
try {
|
|
34919
35363
|
watch2(authDir, (_, filename) => {
|
|
34920
|
-
if (filename !== "auth.json") return;
|
|
34921
|
-
|
|
34922
|
-
try {
|
|
34923
|
-
ctrl.enqueue(new TextEncoder().encode("data: auth-changed\n\n"));
|
|
34924
|
-
} catch {
|
|
34925
|
-
authClients.delete(ctrl);
|
|
34926
|
-
}
|
|
34927
|
-
}
|
|
34928
|
-
refreshQuotaCache().catch(() => {
|
|
34929
|
-
});
|
|
35364
|
+
if (filename && filename !== "auth.json") return;
|
|
35365
|
+
refreshQuotaCache().then(() => emitAuthEvent()).catch(() => emitAuthEvent());
|
|
34930
35366
|
});
|
|
34931
35367
|
} catch {
|
|
34932
35368
|
}
|
|
@@ -34936,8 +35372,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34936
35372
|
start(ctrl) {
|
|
34937
35373
|
authClients.add(ctrl);
|
|
34938
35374
|
},
|
|
34939
|
-
cancel(
|
|
34940
|
-
authClients.delete(ctrl);
|
|
35375
|
+
cancel() {
|
|
34941
35376
|
}
|
|
34942
35377
|
});
|
|
34943
35378
|
return new Response(stream, {
|
|
@@ -34948,6 +35383,12 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34948
35383
|
}
|
|
34949
35384
|
});
|
|
34950
35385
|
});
|
|
35386
|
+
trackRoute("POST", "/api/auth/notify");
|
|
35387
|
+
app.post("/api/auth/notify", async (c) => {
|
|
35388
|
+
await refreshQuotaCache();
|
|
35389
|
+
emitAuthEvent();
|
|
35390
|
+
return c.json({ ok: true });
|
|
35391
|
+
});
|
|
34951
35392
|
trackRoute("GET", "/api/auth/status");
|
|
34952
35393
|
app.get("/api/auth/status", async (c) => {
|
|
34953
35394
|
try {
|
|
@@ -35022,15 +35463,22 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35022
35463
|
}
|
|
35023
35464
|
trackRoute("GET", "/api/cloud/usage");
|
|
35024
35465
|
app.get("/api/cloud/usage", async (c) => {
|
|
35466
|
+
const trace = c.get("trace");
|
|
35025
35467
|
const start = c.req.query("start");
|
|
35026
35468
|
const end = c.req.query("end");
|
|
35027
|
-
|
|
35469
|
+
trace.step("cloudProxy");
|
|
35470
|
+
const res = await cloudProxy("GET", `/api/usage?start=${start}&end=${end}`);
|
|
35471
|
+
trace.end("cloudProxy", `${res.status}`);
|
|
35472
|
+
return res;
|
|
35028
35473
|
});
|
|
35029
35474
|
trackRoute("GET", "/api/cloud/usage/current");
|
|
35030
|
-
app.get(
|
|
35031
|
-
"
|
|
35032
|
-
|
|
35033
|
-
|
|
35475
|
+
app.get("/api/cloud/usage/current", async (c) => {
|
|
35476
|
+
const trace = c.get("trace");
|
|
35477
|
+
trace.step("cloudProxy");
|
|
35478
|
+
const res = await cloudProxy("GET", "/api/usage/current");
|
|
35479
|
+
trace.end("cloudProxy", `${res.status}`);
|
|
35480
|
+
return res;
|
|
35481
|
+
});
|
|
35034
35482
|
trackRoute("GET", "/api/cloud/subscription");
|
|
35035
35483
|
app.get("/api/cloud/subscription", (c) => {
|
|
35036
35484
|
const sub = quotaCache?.subscription ?? { plan: "free", status: "active" };
|
|
@@ -35038,14 +35486,22 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35038
35486
|
});
|
|
35039
35487
|
trackRoute("POST", "/api/cloud/billing/checkout");
|
|
35040
35488
|
app.post("/api/cloud/billing/checkout", async (c) => {
|
|
35489
|
+
const trace = c.get("trace");
|
|
35041
35490
|
const body = await c.req.json().catch(() => ({}));
|
|
35042
|
-
|
|
35491
|
+
trace.step("cloudProxy");
|
|
35492
|
+
const res = await cloudProxy("POST", "/api/billing/checkout", body);
|
|
35493
|
+
trace.end("cloudProxy", `${res.status}`);
|
|
35494
|
+
return res;
|
|
35043
35495
|
});
|
|
35044
35496
|
trackRoute("GET", "/api/cloud/billing/portal");
|
|
35045
35497
|
app.get("/api/cloud/billing/portal", async (c) => {
|
|
35498
|
+
const trace = c.get("trace");
|
|
35046
35499
|
const returnUrl = c.req.query("return_url") || "";
|
|
35047
35500
|
const qs = returnUrl ? `?return_url=${encodeURIComponent(returnUrl)}` : "";
|
|
35048
|
-
|
|
35501
|
+
trace.step("cloudProxy");
|
|
35502
|
+
const res = await cloudProxy("GET", `/api/billing/portal${qs}`);
|
|
35503
|
+
trace.end("cloudProxy", `${res.status}`);
|
|
35504
|
+
return res;
|
|
35049
35505
|
});
|
|
35050
35506
|
trackRoute("GET", "/api/dashboard/stats");
|
|
35051
35507
|
app.get("/api/dashboard/stats", (c) => {
|
|
@@ -35100,6 +35556,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35100
35556
|
last_indexed_at: r.last_indexed_at,
|
|
35101
35557
|
last_indexed_commit: r.last_indexed_commit,
|
|
35102
35558
|
max_import_depth: r.max_import_depth,
|
|
35559
|
+
enable_embeddings: r.enable_embeddings,
|
|
35560
|
+
enable_summaries: r.enable_summaries,
|
|
35561
|
+
enable_vocab_clusters: r.enable_vocab_clusters,
|
|
35103
35562
|
has_capabilities: !!caps,
|
|
35104
35563
|
watcher: {
|
|
35105
35564
|
active: watcher.watching,
|
|
@@ -35146,6 +35605,150 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35146
35605
|
return c.json({ error: e.message }, 500);
|
|
35147
35606
|
}
|
|
35148
35607
|
});
|
|
35608
|
+
trackRoute("PATCH", "/api/dashboard/repos/:id/settings");
|
|
35609
|
+
app.patch("/api/dashboard/repos/:id/settings", async (c) => {
|
|
35610
|
+
try {
|
|
35611
|
+
const id = c.req.param("id");
|
|
35612
|
+
const repo = repoQueries.getById(db, id);
|
|
35613
|
+
if (!repo) return c.json({ error: "repo not found" }, 404);
|
|
35614
|
+
const body = await c.req.json();
|
|
35615
|
+
const flags = {};
|
|
35616
|
+
if (body.enable_embeddings !== void 0) flags.enable_embeddings = body.enable_embeddings ? 1 : 0;
|
|
35617
|
+
if (body.enable_summaries !== void 0) flags.enable_summaries = body.enable_summaries ? 1 : 0;
|
|
35618
|
+
if (body.enable_vocab_clusters !== void 0) flags.enable_vocab_clusters = body.enable_vocab_clusters ? 1 : 0;
|
|
35619
|
+
repoQueries.updateProFeatures(db, id, flags);
|
|
35620
|
+
emitRepoEvent();
|
|
35621
|
+
return c.json({ ok: true });
|
|
35622
|
+
} catch (e) {
|
|
35623
|
+
return c.json({ error: e.message }, 500);
|
|
35624
|
+
}
|
|
35625
|
+
});
|
|
35626
|
+
trackRoute("GET", "/api/dashboard/repos/:id/files");
|
|
35627
|
+
app.get("/api/dashboard/repos/:id/files", (c) => {
|
|
35628
|
+
try {
|
|
35629
|
+
const id = c.req.param("id");
|
|
35630
|
+
const repo = repoQueries.getById(db, id);
|
|
35631
|
+
if (!repo) return c.json({ error: "repo not found" }, 404);
|
|
35632
|
+
const limit = Number(c.req.query("limit") || 100);
|
|
35633
|
+
const offset = Number(c.req.query("offset") || 0);
|
|
35634
|
+
const search = c.req.query("search") || void 0;
|
|
35635
|
+
const meta = metadataQueries.getByRepo(db, id);
|
|
35636
|
+
const raw2 = getRawDb();
|
|
35637
|
+
const chunkCounts = raw2.prepare("SELECT path, count(*) as chunk_count, SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) as embedded_count FROM chunks WHERE repo_id = ? GROUP BY path").all(id);
|
|
35638
|
+
const countMap = new Map(chunkCounts.map((r) => [r.path, r]));
|
|
35639
|
+
let all = meta.map((m) => {
|
|
35640
|
+
const counts = countMap.get(m.path);
|
|
35641
|
+
return {
|
|
35642
|
+
path: m.path,
|
|
35643
|
+
language: m.language,
|
|
35644
|
+
exports: m.exports,
|
|
35645
|
+
purpose: m.purpose,
|
|
35646
|
+
chunk_count: counts?.chunk_count ?? 0,
|
|
35647
|
+
has_embedding: (counts?.embedded_count ?? 0) > 0
|
|
35648
|
+
};
|
|
35649
|
+
});
|
|
35650
|
+
if (search) {
|
|
35651
|
+
const q = search.toLowerCase();
|
|
35652
|
+
all = all.filter((f) => f.path.toLowerCase().includes(q));
|
|
35653
|
+
}
|
|
35654
|
+
const total = all.length;
|
|
35655
|
+
const files = all.slice(offset, offset + limit);
|
|
35656
|
+
return c.json({ files, total });
|
|
35657
|
+
} catch (e) {
|
|
35658
|
+
return c.json({ error: e.message }, 500);
|
|
35659
|
+
}
|
|
35660
|
+
});
|
|
35661
|
+
trackRoute("GET", "/api/dashboard/repos/:id/files/:path");
|
|
35662
|
+
app.get("/api/dashboard/repos/:id/files/:path", (c) => {
|
|
35663
|
+
try {
|
|
35664
|
+
const id = c.req.param("id");
|
|
35665
|
+
const filePath = decodeURIComponent(c.req.param("path"));
|
|
35666
|
+
const repo = repoQueries.getById(db, id);
|
|
35667
|
+
if (!repo) return c.json({ error: "repo not found" }, 404);
|
|
35668
|
+
const allMeta = metadataQueries.getByRepo(db, id);
|
|
35669
|
+
const meta = allMeta.find((m) => m.path === filePath);
|
|
35670
|
+
if (!meta) return c.json({ error: "file not found" }, 404);
|
|
35671
|
+
const raw2 = getRawDb();
|
|
35672
|
+
const chunkRow = raw2.prepare("SELECT count(*) as chunk_count, SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) as embedded_count FROM chunks WHERE repo_id = ? AND path = ?").get(id, filePath);
|
|
35673
|
+
const allImports = importQueries.getBySources(db, id, [filePath]);
|
|
35674
|
+
const allImporters = importQueries.getByTargets(db, id, [filePath]);
|
|
35675
|
+
const gitStats = statsQueries.getByRepo(db, id);
|
|
35676
|
+
const fileStat = gitStats.get(filePath);
|
|
35677
|
+
const cochangeRows = raw2.prepare("SELECT path_a, path_b, cochange_count FROM file_cochanges WHERE repo_id = ? AND (path_a = ? OR path_b = ?) ORDER BY cochange_count DESC LIMIT 10").all(id, filePath, filePath);
|
|
35678
|
+
const cochanges = cochangeRows.map((r) => ({
|
|
35679
|
+
path: r.path_a === filePath ? r.path_b : r.path_a,
|
|
35680
|
+
count: r.cochange_count
|
|
35681
|
+
}));
|
|
35682
|
+
return c.json({
|
|
35683
|
+
path: meta.path,
|
|
35684
|
+
language: meta.language,
|
|
35685
|
+
exports: meta.exports,
|
|
35686
|
+
docstring: meta.docstring,
|
|
35687
|
+
sections: meta.sections,
|
|
35688
|
+
internals: meta.internals,
|
|
35689
|
+
purpose: meta.purpose,
|
|
35690
|
+
chunk_count: chunkRow.chunk_count,
|
|
35691
|
+
embedded_count: chunkRow.embedded_count,
|
|
35692
|
+
imports: allImports.map((i) => i.target_path),
|
|
35693
|
+
imported_by: allImporters.map((i) => i.source_path),
|
|
35694
|
+
git: fileStat ? {
|
|
35695
|
+
commit_count: fileStat.commit_count,
|
|
35696
|
+
recent_count: fileStat.recent_count,
|
|
35697
|
+
last_modified: fileStat.last_modified?.toISOString() ?? null
|
|
35698
|
+
} : null,
|
|
35699
|
+
cochanges
|
|
35700
|
+
});
|
|
35701
|
+
} catch (e) {
|
|
35702
|
+
return c.json({ error: e.message }, 500);
|
|
35703
|
+
}
|
|
35704
|
+
});
|
|
35705
|
+
trackRoute("GET", "/api/dashboard/repos/:id/chunks");
|
|
35706
|
+
app.get("/api/dashboard/repos/:id/chunks", (c) => {
|
|
35707
|
+
try {
|
|
35708
|
+
const id = c.req.param("id");
|
|
35709
|
+
const repo = repoQueries.getById(db, id);
|
|
35710
|
+
if (!repo) return c.json({ error: "repo not found" }, 404);
|
|
35711
|
+
const limit = Number(c.req.query("limit") || 100);
|
|
35712
|
+
const offset = Number(c.req.query("offset") || 0);
|
|
35713
|
+
const pathFilter = c.req.query("path") || void 0;
|
|
35714
|
+
const raw2 = getRawDb();
|
|
35715
|
+
const where = pathFilter ? "WHERE repo_id = ? AND path = ?" : "WHERE repo_id = ?";
|
|
35716
|
+
const params = pathFilter ? [id, pathFilter] : [id];
|
|
35717
|
+
const rows = raw2.prepare(`SELECT id, path, chunk_index, start_line, end_line, language, CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END as has_embedding FROM chunks ${where} ORDER BY path, chunk_index LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
35718
|
+
const totalRow = raw2.prepare(`SELECT count(*) as count FROM chunks ${where}`).get(...params);
|
|
35719
|
+
return c.json({
|
|
35720
|
+
chunks: rows.map((r) => ({ ...r, has_embedding: r.has_embedding === 1 })),
|
|
35721
|
+
total: totalRow.count
|
|
35722
|
+
});
|
|
35723
|
+
} catch (e) {
|
|
35724
|
+
return c.json({ error: e.message }, 500);
|
|
35725
|
+
}
|
|
35726
|
+
});
|
|
35727
|
+
trackRoute("GET", "/api/dashboard/repos/:id/chunks/:chunkId");
|
|
35728
|
+
app.get("/api/dashboard/repos/:id/chunks/:chunkId", (c) => {
|
|
35729
|
+
try {
|
|
35730
|
+
const id = c.req.param("id");
|
|
35731
|
+
const chunkId = c.req.param("chunkId");
|
|
35732
|
+
const raw2 = getRawDb();
|
|
35733
|
+
const row = raw2.prepare("SELECT id, path, chunk_index, start_line, end_line, content, language, chunk_hash, CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END as has_embedding FROM chunks WHERE id = ? AND repo_id = ?").get(chunkId, id);
|
|
35734
|
+
if (!row) return c.json({ error: "chunk not found" }, 404);
|
|
35735
|
+
return c.json({ ...row, has_embedding: row.has_embedding === 1 });
|
|
35736
|
+
} catch (e) {
|
|
35737
|
+
return c.json({ error: e.message }, 500);
|
|
35738
|
+
}
|
|
35739
|
+
});
|
|
35740
|
+
trackRoute("GET", "/api/dashboard/repos/:id/vocab-clusters");
|
|
35741
|
+
app.get("/api/dashboard/repos/:id/vocab-clusters", (c) => {
|
|
35742
|
+
try {
|
|
35743
|
+
const id = c.req.param("id");
|
|
35744
|
+
const repo = repoQueries.getById(db, id);
|
|
35745
|
+
if (!repo) return c.json({ error: "repo not found" }, 404);
|
|
35746
|
+
const clusters = repo.vocab_clusters ? JSON.parse(repo.vocab_clusters) : [];
|
|
35747
|
+
return c.json({ clusters: Array.isArray(clusters) ? clusters : [] });
|
|
35748
|
+
} catch (e) {
|
|
35749
|
+
return c.json({ error: e.message }, 500);
|
|
35750
|
+
}
|
|
35751
|
+
});
|
|
35149
35752
|
trackRoute("GET", "/api/dashboard/logs");
|
|
35150
35753
|
app.get("/api/dashboard/logs", (c) => {
|
|
35151
35754
|
try {
|
|
@@ -35204,46 +35807,6 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35204
35807
|
return c.json({ error: e.message }, 500);
|
|
35205
35808
|
}
|
|
35206
35809
|
});
|
|
35207
|
-
trackRoute("GET", "/api/dashboard/jobs");
|
|
35208
|
-
app.get("/api/dashboard/jobs", async (c) => {
|
|
35209
|
-
try {
|
|
35210
|
-
const repos2 = listRepos(db);
|
|
35211
|
-
const result = await Promise.all(
|
|
35212
|
-
repos2.map(async (r) => {
|
|
35213
|
-
const stats = chunkQueries.getStats(db, r.id);
|
|
35214
|
-
const structural = metadataQueries.getStructuralStats(db, r.id);
|
|
35215
|
-
const watcher = getWatcherStatus(r.id);
|
|
35216
|
-
let currentHead = null;
|
|
35217
|
-
try {
|
|
35218
|
-
currentHead = await getHeadCommit(r.root_path);
|
|
35219
|
-
} catch {
|
|
35220
|
-
}
|
|
35221
|
-
return {
|
|
35222
|
-
id: r.id,
|
|
35223
|
-
name: r.name,
|
|
35224
|
-
index_status: r.index_status,
|
|
35225
|
-
last_indexed_commit: r.last_indexed_commit,
|
|
35226
|
-
last_indexed_at: r.last_indexed_at,
|
|
35227
|
-
is_stale: !!(currentHead && r.last_indexed_commit !== currentHead),
|
|
35228
|
-
current_head: currentHead,
|
|
35229
|
-
chunk_count: stats.chunk_count,
|
|
35230
|
-
embedded_count: stats.embedded_count,
|
|
35231
|
-
embeddable_count: stats.embeddable_count,
|
|
35232
|
-
purpose_count: structural.purpose_count,
|
|
35233
|
-
purpose_total: structural.purpose_total,
|
|
35234
|
-
watcher: {
|
|
35235
|
-
active: watcher.watching,
|
|
35236
|
-
changed_files: watcher.changed_files ?? 0,
|
|
35237
|
-
started_at: watcher.started_at ?? null
|
|
35238
|
-
}
|
|
35239
|
-
};
|
|
35240
|
-
})
|
|
35241
|
-
);
|
|
35242
|
-
return c.json({ repos: result });
|
|
35243
|
-
} catch (e) {
|
|
35244
|
-
return c.json({ error: e.message }, 500);
|
|
35245
|
-
}
|
|
35246
|
-
});
|
|
35247
35810
|
trackRoute("GET", "/api/dashboard/usage");
|
|
35248
35811
|
app.get("/api/dashboard/usage", (c) => {
|
|
35249
35812
|
try {
|
|
@@ -35299,12 +35862,19 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35299
35862
|
if (existsSync2(fullPath) && statSync(fullPath).isFile()) {
|
|
35300
35863
|
const content = readFileSync2(fullPath);
|
|
35301
35864
|
const mime = MIME_TYPES[extname3(fullPath)] ?? "application/octet-stream";
|
|
35302
|
-
|
|
35865
|
+
const isHtml = mime === "text/html";
|
|
35866
|
+
return new Response(content, {
|
|
35867
|
+
headers: {
|
|
35868
|
+
"Content-Type": mime,
|
|
35869
|
+
"Cache-Control": isHtml ? "no-cache" : "public, max-age=86400",
|
|
35870
|
+
"Access-Control-Allow-Origin": "*"
|
|
35871
|
+
}
|
|
35872
|
+
});
|
|
35303
35873
|
}
|
|
35304
35874
|
const indexPath = join5(dashboardDist, "index.html");
|
|
35305
35875
|
if (existsSync2(indexPath)) {
|
|
35306
35876
|
return new Response(readFileSync2(indexPath), {
|
|
35307
|
-
headers: { "Content-Type": "text/html" }
|
|
35877
|
+
headers: { "Content-Type": "text/html", "Cache-Control": "no-cache", "Access-Control-Allow-Origin": "*" }
|
|
35308
35878
|
});
|
|
35309
35879
|
}
|
|
35310
35880
|
return c.json({ error: "not found" }, 404);
|
|
@@ -35319,19 +35889,20 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35319
35889
|
]);
|
|
35320
35890
|
if (res.ok) {
|
|
35321
35891
|
const data = await res.json();
|
|
35892
|
+
const plan = (data.plan ?? "free").trim();
|
|
35322
35893
|
const subData = subRes.ok ? (await subRes.json()).subscription ?? null : null;
|
|
35323
35894
|
quotaCache = {
|
|
35324
|
-
plan
|
|
35895
|
+
plan,
|
|
35325
35896
|
usage: data.usage ?? {},
|
|
35326
35897
|
quota: data.quota ?? {},
|
|
35327
35898
|
subscription: subData,
|
|
35328
35899
|
fetchedAt: Date.now()
|
|
35329
35900
|
};
|
|
35330
|
-
if (caps &&
|
|
35901
|
+
if (caps && plan !== "pro") {
|
|
35331
35902
|
caps = void 0;
|
|
35332
35903
|
console.error("[LENS] Cloud capabilities disabled (plan changed to free)");
|
|
35333
35904
|
}
|
|
35334
|
-
if (!caps &&
|
|
35905
|
+
if (!caps && plan === "pro") {
|
|
35335
35906
|
const apiKey = await readApiKey();
|
|
35336
35907
|
if (apiKey) {
|
|
35337
35908
|
const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
|
|
@@ -35343,9 +35914,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35343
35914
|
} catch {
|
|
35344
35915
|
}
|
|
35345
35916
|
},
|
|
35346
|
-
(method, path, status, duration3, source) => {
|
|
35917
|
+
(method, path, status, duration3, source, reqBody, resBody) => {
|
|
35347
35918
|
try {
|
|
35348
|
-
logQueries.insert(db, method, path, status, duration3, source);
|
|
35919
|
+
logQueries.insert(db, method, path, status, duration3, source, reqBody, resBody?.length, resBody);
|
|
35349
35920
|
} catch {
|
|
35350
35921
|
}
|
|
35351
35922
|
}
|
|
@@ -36082,9 +36653,9 @@ async function loadCapabilities(db) {
|
|
|
36082
36653
|
} catch {
|
|
36083
36654
|
}
|
|
36084
36655
|
},
|
|
36085
|
-
(method, path, status, duration3, source) => {
|
|
36656
|
+
(method, path, status, duration3, source, reqBody, resBody) => {
|
|
36086
36657
|
try {
|
|
36087
|
-
logQueries2.insert(db, method, path, status, duration3, source);
|
|
36658
|
+
logQueries2.insert(db, method, path, status, duration3, source, reqBody, resBody?.length, resBody);
|
|
36088
36659
|
} catch {
|
|
36089
36660
|
}
|
|
36090
36661
|
}
|