lens-engine 0.1.19 → 0.1.21
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 +747 -521
- package/daemon.js +2329 -1182
- package/dashboard/assets/index-DmLHTAhP.css +1 -0
- package/dashboard/assets/index-ojhLsux_.js +341 -0
- package/dashboard/index.html +2 -2
- package/package.json +17 -3
- package/dashboard/assets/index-B-FwfnUH.css +0 -1
- package/dashboard/assets/index-FwosDwb9.js +0 -341
package/daemon.js
CHANGED
|
@@ -6627,9 +6627,9 @@ var init_handler = __esm({
|
|
|
6627
6627
|
if (this.fsw.closed) {
|
|
6628
6628
|
return;
|
|
6629
6629
|
}
|
|
6630
|
-
const
|
|
6630
|
+
const dirname4 = sysPath.dirname(file);
|
|
6631
6631
|
const basename4 = sysPath.basename(file);
|
|
6632
|
-
const parent = this.fsw._getWatchedDir(
|
|
6632
|
+
const parent = this.fsw._getWatchedDir(dirname4);
|
|
6633
6633
|
let prevStats = stats;
|
|
6634
6634
|
if (parent.has(basename4))
|
|
6635
6635
|
return;
|
|
@@ -6656,7 +6656,7 @@ var init_handler = __esm({
|
|
|
6656
6656
|
prevStats = newStats2;
|
|
6657
6657
|
}
|
|
6658
6658
|
} catch (error3) {
|
|
6659
|
-
this.fsw._remove(
|
|
6659
|
+
this.fsw._remove(dirname4, basename4);
|
|
6660
6660
|
}
|
|
6661
6661
|
} else if (parent.has(basename4)) {
|
|
6662
6662
|
const at = newStats.atimeMs;
|
|
@@ -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
|
+
GOLD_DATASET: () => GOLD_DATASET,
|
|
7608
7609
|
RequestTrace: () => RequestTrace,
|
|
7609
7610
|
agglomerativeCluster: () => agglomerativeCluster,
|
|
7610
7611
|
analyzeGitHistory: () => analyzeGitHistory,
|
|
@@ -7616,10 +7617,12 @@ __export(dist_exports, {
|
|
|
7616
7617
|
closeDb: () => closeDb,
|
|
7617
7618
|
cochangeQueries: () => cochangeQueries,
|
|
7618
7619
|
computeMaxImportDepth: () => computeMaxImportDepth,
|
|
7619
|
-
|
|
7620
|
+
computeMetrics: () => computeMetrics,
|
|
7621
|
+
cosine: () => cosine2,
|
|
7620
7622
|
deriveIdentityKey: () => deriveIdentityKey,
|
|
7621
7623
|
detectLanguage: () => detectLanguage,
|
|
7622
7624
|
diffScan: () => diffScan,
|
|
7625
|
+
discoverTestFiles: () => discoverTestFiles,
|
|
7623
7626
|
enrichPurpose: () => enrichPurpose,
|
|
7624
7627
|
ensureEmbedded: () => ensureEmbedded,
|
|
7625
7628
|
ensureIndexed: () => ensureIndexed,
|
|
@@ -7654,10 +7657,13 @@ __export(dist_exports, {
|
|
|
7654
7657
|
metadataQueries: () => metadataQueries,
|
|
7655
7658
|
openDb: () => openDb,
|
|
7656
7659
|
parseGitLog: () => parseGitLog,
|
|
7660
|
+
parseQuery: () => parseQuery,
|
|
7657
7661
|
registerRepo: () => registerRepo,
|
|
7658
7662
|
removeRepo: () => removeRepo,
|
|
7659
7663
|
repoQueries: () => repoQueries,
|
|
7660
7664
|
resolveImport: () => resolveImport,
|
|
7665
|
+
resolveSnippets: () => resolveSnippets,
|
|
7666
|
+
runEval: () => runEval,
|
|
7661
7667
|
runIndex: () => runIndex,
|
|
7662
7668
|
setTelemetryEnabled: () => setTelemetryEnabled,
|
|
7663
7669
|
settingsQueries: () => settingsQueries,
|
|
@@ -7670,21 +7676,21 @@ __export(dist_exports, {
|
|
|
7670
7676
|
vectorSearch: () => vectorSearch
|
|
7671
7677
|
});
|
|
7672
7678
|
import { randomUUID } from "crypto";
|
|
7673
|
-
import
|
|
7674
|
-
import { existsSync, mkdirSync } from "fs";
|
|
7675
|
-
import { join as join3 } from "path";
|
|
7676
|
-
import { homedir } from "os";
|
|
7679
|
+
import { readFile } from "fs/promises";
|
|
7677
7680
|
import { createHash } from "crypto";
|
|
7678
7681
|
import { execFile } from "child_process";
|
|
7679
7682
|
import { stat as stat4 } from "fs/promises";
|
|
7680
|
-
import {
|
|
7683
|
+
import { extname as extname2, resolve as resolve3 } from "path";
|
|
7681
7684
|
import { promisify } from "util";
|
|
7682
|
-
import { readFile } from "fs/promises";
|
|
7683
|
-
import { createHash as createHash2 } from "crypto";
|
|
7684
7685
|
import { execFile as execFile2 } from "child_process";
|
|
7685
7686
|
import { promisify as promisify2 } from "util";
|
|
7687
|
+
import { existsSync, mkdirSync } from "fs";
|
|
7688
|
+
import { homedir } from "os";
|
|
7689
|
+
import { join as join3 } from "path";
|
|
7690
|
+
import Database from "better-sqlite3";
|
|
7686
7691
|
import { readFile as readFile2, stat as stat22 } from "fs/promises";
|
|
7687
7692
|
import { relative as relative3 } from "path";
|
|
7693
|
+
import { createHash as createHash2 } from "crypto";
|
|
7688
7694
|
function jsonParse(val, fallback) {
|
|
7689
7695
|
if (val == null) return fallback;
|
|
7690
7696
|
if (typeof val === "string") {
|
|
@@ -7704,339 +7710,75 @@ function fromEmbeddingBlob(blob2) {
|
|
|
7704
7710
|
const buf = blob2 instanceof Buffer ? blob2 : Buffer.from(blob2);
|
|
7705
7711
|
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
7706
7712
|
}
|
|
7707
|
-
function
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
sqlite.exec(createTablesSql());
|
|
7722
|
-
const cols = new Set(
|
|
7723
|
-
sqlite.pragma("table_info(file_metadata)").map((c) => c.name)
|
|
7724
|
-
);
|
|
7725
|
-
if (!cols.has("sections")) sqlite.exec("ALTER TABLE file_metadata ADD COLUMN sections TEXT DEFAULT '[]'");
|
|
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");
|
|
7739
|
-
_db = db;
|
|
7740
|
-
_raw = sqlite;
|
|
7741
|
-
return db;
|
|
7742
|
-
}
|
|
7743
|
-
function getDb() {
|
|
7744
|
-
if (!_db) throw new Error("Database not initialized. Call openDb() first.");
|
|
7745
|
-
return _db;
|
|
7746
|
-
}
|
|
7747
|
-
function getRawDb() {
|
|
7748
|
-
if (!_raw) throw new Error("Database not initialized. Call openDb() first.");
|
|
7749
|
-
return _raw;
|
|
7750
|
-
}
|
|
7751
|
-
function closeDb() {
|
|
7752
|
-
if (_raw) _raw.close();
|
|
7753
|
-
_db = null;
|
|
7754
|
-
_raw = null;
|
|
7755
|
-
}
|
|
7756
|
-
function createTablesSql() {
|
|
7757
|
-
return `
|
|
7758
|
-
CREATE TABLE IF NOT EXISTS repos (
|
|
7759
|
-
id TEXT PRIMARY KEY,
|
|
7760
|
-
identity_key TEXT UNIQUE NOT NULL,
|
|
7761
|
-
name TEXT NOT NULL,
|
|
7762
|
-
root_path TEXT NOT NULL,
|
|
7763
|
-
remote_url TEXT,
|
|
7764
|
-
last_indexed_commit TEXT,
|
|
7765
|
-
index_status TEXT NOT NULL DEFAULT 'pending',
|
|
7766
|
-
last_indexed_at TEXT,
|
|
7767
|
-
last_git_analysis_commit TEXT,
|
|
7768
|
-
max_import_depth INTEGER DEFAULT 0,
|
|
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,
|
|
7774
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7775
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7776
|
-
);
|
|
7777
|
-
CREATE INDEX IF NOT EXISTS idx_repos_identity ON repos(identity_key);
|
|
7778
|
-
|
|
7779
|
-
CREATE TABLE IF NOT EXISTS chunks (
|
|
7780
|
-
id TEXT PRIMARY KEY,
|
|
7781
|
-
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
7782
|
-
path TEXT NOT NULL,
|
|
7783
|
-
chunk_index INTEGER NOT NULL,
|
|
7784
|
-
start_line INTEGER NOT NULL,
|
|
7785
|
-
end_line INTEGER NOT NULL,
|
|
7786
|
-
content TEXT NOT NULL,
|
|
7787
|
-
chunk_hash TEXT NOT NULL,
|
|
7788
|
-
last_seen_commit TEXT NOT NULL,
|
|
7789
|
-
language TEXT,
|
|
7790
|
-
embedding BLOB,
|
|
7791
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7792
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7793
|
-
UNIQUE(repo_id, path, chunk_index, chunk_hash)
|
|
7794
|
-
);
|
|
7795
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_repo_path ON chunks(repo_id, path);
|
|
7796
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_repo ON chunks(repo_id);
|
|
7797
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_hash ON chunks(repo_id, chunk_hash);
|
|
7798
|
-
|
|
7799
|
-
CREATE TABLE IF NOT EXISTS file_metadata (
|
|
7800
|
-
id TEXT PRIMARY KEY,
|
|
7801
|
-
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
7802
|
-
path TEXT NOT NULL,
|
|
7803
|
-
language TEXT,
|
|
7804
|
-
exports TEXT DEFAULT '[]',
|
|
7805
|
-
imports TEXT DEFAULT '[]',
|
|
7806
|
-
docstring TEXT DEFAULT '',
|
|
7807
|
-
sections TEXT DEFAULT '[]',
|
|
7808
|
-
internals TEXT DEFAULT '[]',
|
|
7809
|
-
purpose TEXT DEFAULT '',
|
|
7810
|
-
purpose_hash TEXT DEFAULT '',
|
|
7811
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7812
|
-
UNIQUE(repo_id, path)
|
|
7813
|
-
);
|
|
7814
|
-
CREATE INDEX IF NOT EXISTS idx_file_metadata_repo ON file_metadata(repo_id);
|
|
7815
|
-
|
|
7816
|
-
CREATE TABLE IF NOT EXISTS file_imports (
|
|
7817
|
-
id TEXT PRIMARY KEY,
|
|
7818
|
-
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
7819
|
-
source_path TEXT NOT NULL,
|
|
7820
|
-
target_path TEXT NOT NULL,
|
|
7821
|
-
UNIQUE(repo_id, source_path, target_path)
|
|
7822
|
-
);
|
|
7823
|
-
CREATE INDEX IF NOT EXISTS idx_file_imports_target ON file_imports(repo_id, target_path);
|
|
7824
|
-
|
|
7825
|
-
CREATE TABLE IF NOT EXISTS file_stats (
|
|
7826
|
-
id TEXT PRIMARY KEY,
|
|
7827
|
-
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
7828
|
-
path TEXT NOT NULL,
|
|
7829
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
7830
|
-
recent_count INTEGER NOT NULL DEFAULT 0,
|
|
7831
|
-
last_modified TEXT,
|
|
7832
|
-
UNIQUE(repo_id, path)
|
|
7833
|
-
);
|
|
7834
|
-
|
|
7835
|
-
CREATE TABLE IF NOT EXISTS file_cochanges (
|
|
7836
|
-
id TEXT PRIMARY KEY,
|
|
7837
|
-
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
7838
|
-
path_a TEXT NOT NULL,
|
|
7839
|
-
path_b TEXT NOT NULL,
|
|
7840
|
-
cochange_count INTEGER NOT NULL DEFAULT 1,
|
|
7841
|
-
UNIQUE(repo_id, path_a, path_b)
|
|
7842
|
-
);
|
|
7843
|
-
CREATE INDEX IF NOT EXISTS idx_cochanges_lookup ON file_cochanges(repo_id, path_a);
|
|
7844
|
-
|
|
7845
|
-
CREATE TABLE IF NOT EXISTS usage_counters (
|
|
7846
|
-
id TEXT PRIMARY KEY,
|
|
7847
|
-
date TEXT NOT NULL UNIQUE,
|
|
7848
|
-
context_queries INTEGER NOT NULL DEFAULT 0,
|
|
7849
|
-
embedding_requests INTEGER NOT NULL DEFAULT 0,
|
|
7850
|
-
embedding_chunks INTEGER NOT NULL DEFAULT 0,
|
|
7851
|
-
purpose_requests INTEGER NOT NULL DEFAULT 0,
|
|
7852
|
-
repos_indexed INTEGER NOT NULL DEFAULT 0,
|
|
7853
|
-
synced_at TEXT,
|
|
7854
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7855
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7856
|
-
);
|
|
7857
|
-
CREATE INDEX IF NOT EXISTS idx_usage_counters_date ON usage_counters(date);
|
|
7858
|
-
|
|
7859
|
-
CREATE TABLE IF NOT EXISTS request_logs (
|
|
7860
|
-
id TEXT PRIMARY KEY,
|
|
7861
|
-
method TEXT NOT NULL,
|
|
7862
|
-
path TEXT NOT NULL,
|
|
7863
|
-
status INTEGER NOT NULL,
|
|
7864
|
-
duration_ms INTEGER NOT NULL,
|
|
7865
|
-
source TEXT NOT NULL DEFAULT 'api',
|
|
7866
|
-
request_body TEXT,
|
|
7867
|
-
response_size INTEGER,
|
|
7868
|
-
response_body TEXT,
|
|
7869
|
-
trace TEXT,
|
|
7870
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7871
|
-
);
|
|
7872
|
-
CREATE INDEX IF NOT EXISTS idx_request_logs_created ON request_logs(created_at);
|
|
7873
|
-
CREATE INDEX IF NOT EXISTS idx_request_logs_source ON request_logs(source);
|
|
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
|
-
|
|
7881
|
-
CREATE TABLE IF NOT EXISTS telemetry_events (
|
|
7882
|
-
id TEXT PRIMARY KEY,
|
|
7883
|
-
event_type TEXT NOT NULL,
|
|
7884
|
-
event_data TEXT,
|
|
7885
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7886
|
-
synced_at TEXT
|
|
7887
|
-
);
|
|
7888
|
-
CREATE INDEX IF NOT EXISTS idx_telemetry_events_type ON telemetry_events(event_type);
|
|
7889
|
-
CREATE INDEX IF NOT EXISTS idx_telemetry_events_synced ON telemetry_events(synced_at);
|
|
7890
|
-
`;
|
|
7713
|
+
function extractSlice(chunks2, targetLine) {
|
|
7714
|
+
const chunk = chunks2.find((c) => c.start_line <= targetLine && targetLine <= c.end_line);
|
|
7715
|
+
if (!chunk) return null;
|
|
7716
|
+
const lines = chunk.content.split("\n");
|
|
7717
|
+
const offset = targetLine - chunk.start_line;
|
|
7718
|
+
const from = Math.max(0, offset - RADIUS);
|
|
7719
|
+
const to = Math.min(lines.length, offset + RADIUS + 1);
|
|
7720
|
+
const sliced = lines.slice(from, to);
|
|
7721
|
+
if (!sliced.length) return null;
|
|
7722
|
+
return {
|
|
7723
|
+
startLine: chunk.start_line + from,
|
|
7724
|
+
endLine: chunk.start_line + to - 1,
|
|
7725
|
+
code: sliced.join("\n")
|
|
7726
|
+
};
|
|
7891
7727
|
}
|
|
7892
|
-
function
|
|
7893
|
-
|
|
7894
|
-
const
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7728
|
+
function sliceContext(db, repoId, snippets, queryKind) {
|
|
7729
|
+
const result = /* @__PURE__ */ new Map();
|
|
7730
|
+
const limit = MAX_SLICES[queryKind] ?? 3;
|
|
7731
|
+
const candidates = [...snippets.values()].filter((s) => s.line !== null).slice(0, limit);
|
|
7732
|
+
if (!candidates.length) return result;
|
|
7733
|
+
const paths = candidates.map((c) => c.path);
|
|
7734
|
+
const allChunks = chunkQueries.getByRepoPaths(db, repoId, paths);
|
|
7735
|
+
const chunksByPath = /* @__PURE__ */ new Map();
|
|
7736
|
+
for (const c of allChunks) {
|
|
7737
|
+
const arr = chunksByPath.get(c.path) ?? [];
|
|
7738
|
+
arr.push({ start_line: c.start_line, end_line: c.end_line, content: c.content });
|
|
7739
|
+
chunksByPath.set(c.path, arr);
|
|
7740
|
+
}
|
|
7741
|
+
for (const snip of candidates) {
|
|
7742
|
+
const chunks2 = chunksByPath.get(snip.path);
|
|
7743
|
+
if (!chunks2) continue;
|
|
7744
|
+
const slice = extractSlice(chunks2, snip.line);
|
|
7745
|
+
if (!slice) continue;
|
|
7746
|
+
result.set(snip.path, {
|
|
7747
|
+
path: snip.path,
|
|
7748
|
+
startLine: slice.startLine,
|
|
7749
|
+
endLine: slice.endLine,
|
|
7750
|
+
code: slice.code,
|
|
7751
|
+
symbol: snip.symbol
|
|
7752
|
+
});
|
|
7899
7753
|
}
|
|
7900
|
-
|
|
7901
|
-
normalized = normalized.replace(/\/$/, "");
|
|
7902
|
-
return normalized.toLowerCase();
|
|
7903
|
-
}
|
|
7904
|
-
function deriveIdentityKey(rootPath, remoteUrl) {
|
|
7905
|
-
const source = remoteUrl ? normalizeRemoteUrl(remoteUrl) : rootPath;
|
|
7906
|
-
return createHash("sha256").update(source).digest("hex");
|
|
7754
|
+
return result;
|
|
7907
7755
|
}
|
|
7908
|
-
function
|
|
7909
|
-
|
|
7756
|
+
function setTelemetryEnabled(enabled) {
|
|
7757
|
+
_enabled = enabled;
|
|
7910
7758
|
}
|
|
7911
|
-
function
|
|
7912
|
-
|
|
7759
|
+
function track(db, eventType, data) {
|
|
7760
|
+
if (!_enabled) return;
|
|
7761
|
+
try {
|
|
7762
|
+
telemetryQueries.insert(db, eventType, data);
|
|
7763
|
+
} catch {
|
|
7764
|
+
}
|
|
7913
7765
|
}
|
|
7914
|
-
function
|
|
7915
|
-
|
|
7766
|
+
function computeChunkHash(content, params) {
|
|
7767
|
+
const payload = JSON.stringify({ content, params });
|
|
7768
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
7916
7769
|
}
|
|
7917
|
-
|
|
7918
|
-
const
|
|
7919
|
-
const
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
}
|
|
7930
|
-
results.push({ path: p, absolute_path: abs, language: detectLanguage(p), status: "added" });
|
|
7931
|
-
}
|
|
7932
|
-
return results;
|
|
7933
|
-
}
|
|
7934
|
-
async function diffScan(repoRoot, fromCommit, toCommit) {
|
|
7935
|
-
const { stdout } = await execFileAsync(
|
|
7936
|
-
"git",
|
|
7937
|
-
["diff", "--name-status", "--diff-filter=ACMRD", "-z", `${fromCommit}..${toCommit}`],
|
|
7938
|
-
{ cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 }
|
|
7939
|
-
);
|
|
7940
|
-
const parts = stdout.split("\0").filter(Boolean);
|
|
7941
|
-
const results = [];
|
|
7942
|
-
for (let i = 0; i < parts.length; i += 2) {
|
|
7943
|
-
const statusChar = parts[i];
|
|
7944
|
-
const p = parts[i + 1];
|
|
7945
|
-
if (!p) continue;
|
|
7946
|
-
if (isBinaryExt(p)) continue;
|
|
7947
|
-
const abs = resolve3(repoRoot, p);
|
|
7948
|
-
let status;
|
|
7949
|
-
if (statusChar === "D") {
|
|
7950
|
-
status = "deleted";
|
|
7951
|
-
} else if (statusChar === "A") {
|
|
7952
|
-
status = "added";
|
|
7953
|
-
} else {
|
|
7954
|
-
status = "modified";
|
|
7955
|
-
}
|
|
7956
|
-
if (status !== "deleted") {
|
|
7957
|
-
try {
|
|
7958
|
-
const s = await stat4(abs);
|
|
7959
|
-
if (s.size > MAX_FILE_SIZE) continue;
|
|
7960
|
-
} catch {
|
|
7961
|
-
continue;
|
|
7962
|
-
}
|
|
7963
|
-
}
|
|
7964
|
-
results.push({ path: p, absolute_path: abs, language: detectLanguage(p), status });
|
|
7965
|
-
}
|
|
7966
|
-
return results;
|
|
7967
|
-
}
|
|
7968
|
-
async function getHeadCommit(repoRoot) {
|
|
7969
|
-
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: repoRoot });
|
|
7970
|
-
return stdout.trim();
|
|
7971
|
-
}
|
|
7972
|
-
function registerRepo(db, rootPath, name, remoteUrl) {
|
|
7973
|
-
const identityKey = deriveIdentityKey(rootPath, remoteUrl);
|
|
7974
|
-
const repoName = name ?? rootPath.split("/").pop() ?? "unknown";
|
|
7975
|
-
const { id, created } = repoQueries.upsert(db, identityKey, repoName, rootPath, remoteUrl ?? null);
|
|
7976
|
-
return { repo_id: id, identity_key: identityKey, name: repoName, created };
|
|
7977
|
-
}
|
|
7978
|
-
function getRepo(db, id) {
|
|
7979
|
-
const repo = repoQueries.getById(db, id);
|
|
7980
|
-
if (!repo) throw new Error("repo not found");
|
|
7981
|
-
return repo;
|
|
7982
|
-
}
|
|
7983
|
-
function listRepos(db) {
|
|
7984
|
-
return repoQueries.list(db);
|
|
7985
|
-
}
|
|
7986
|
-
function removeRepo(db, id) {
|
|
7987
|
-
const chunkCount = chunkQueries.countByRepo(db, id);
|
|
7988
|
-
const deleted = repoQueries.remove(db, id);
|
|
7989
|
-
if (!deleted) throw new Error("repo not found");
|
|
7990
|
-
return { deleted: true, chunks_removed: chunkCount };
|
|
7991
|
-
}
|
|
7992
|
-
async function getRepoStatus(db, id) {
|
|
7993
|
-
const repo = repoQueries.getById(db, id);
|
|
7994
|
-
if (!repo) throw new Error("repo not found");
|
|
7995
|
-
let currentHead = null;
|
|
7996
|
-
try {
|
|
7997
|
-
currentHead = await getHeadCommit(repo.root_path);
|
|
7998
|
-
} catch {
|
|
7999
|
-
}
|
|
8000
|
-
const isStale = !!(currentHead && repo.last_indexed_commit !== currentHead);
|
|
8001
|
-
const stats = chunkQueries.getStats(db, id);
|
|
8002
|
-
const structural = metadataQueries.getStructuralStats(db, id);
|
|
8003
|
-
const vocabRaw = repo.vocab_clusters;
|
|
8004
|
-
const vocabClusters = vocabRaw ? JSON.parse(vocabRaw) : [];
|
|
8005
|
-
return {
|
|
8006
|
-
index_status: repo.index_status,
|
|
8007
|
-
indexed_commit: repo.last_indexed_commit,
|
|
8008
|
-
current_head: currentHead,
|
|
8009
|
-
is_stale: isStale,
|
|
8010
|
-
chunk_count: stats.chunk_count,
|
|
8011
|
-
files_indexed: stats.files_indexed,
|
|
8012
|
-
embedded_count: stats.embedded_count,
|
|
8013
|
-
embeddable_count: stats.embeddable_count,
|
|
8014
|
-
embedded_pct: stats.embeddable_count > 0 ? Math.round(stats.embedded_count / stats.embeddable_count * 100) : 0,
|
|
8015
|
-
metadata_count: structural.metadata_count,
|
|
8016
|
-
import_edge_count: structural.import_edge_count,
|
|
8017
|
-
git_commits_analyzed: structural.git_file_count,
|
|
8018
|
-
cochange_pairs: structural.cochange_pairs,
|
|
8019
|
-
purpose_count: structural.purpose_count,
|
|
8020
|
-
purpose_total: structural.purpose_total,
|
|
8021
|
-
vocab_cluster_count: Array.isArray(vocabClusters) ? vocabClusters.length : 0
|
|
8022
|
-
};
|
|
8023
|
-
}
|
|
8024
|
-
function computeChunkHash(content, params) {
|
|
8025
|
-
const payload = JSON.stringify({ content, params });
|
|
8026
|
-
return createHash2("sha256").update(payload).digest("hex");
|
|
8027
|
-
}
|
|
8028
|
-
function findBoundary(lines, targetLine, windowSize = 15) {
|
|
8029
|
-
const start = Math.max(0, targetLine - windowSize);
|
|
8030
|
-
const end = Math.min(lines.length, targetLine + windowSize);
|
|
8031
|
-
let bestBlank = -1;
|
|
8032
|
-
let bestBlankDist = Infinity;
|
|
8033
|
-
for (let i = start; i < end; i++) {
|
|
8034
|
-
if (lines[i].trim() === "") {
|
|
8035
|
-
const dist = Math.abs(i - targetLine);
|
|
8036
|
-
if (dist < bestBlankDist) {
|
|
8037
|
-
bestBlank = i;
|
|
8038
|
-
bestBlankDist = dist;
|
|
8039
|
-
}
|
|
7770
|
+
function findBoundary(lines, targetLine, windowSize = 15) {
|
|
7771
|
+
const start = Math.max(0, targetLine - windowSize);
|
|
7772
|
+
const end = Math.min(lines.length, targetLine + windowSize);
|
|
7773
|
+
let bestBlank = -1;
|
|
7774
|
+
let bestBlankDist = Infinity;
|
|
7775
|
+
for (let i = start; i < end; i++) {
|
|
7776
|
+
if (lines[i].trim() === "") {
|
|
7777
|
+
const dist = Math.abs(i - targetLine);
|
|
7778
|
+
if (dist < bestBlankDist) {
|
|
7779
|
+
bestBlank = i;
|
|
7780
|
+
bestBlankDist = dist;
|
|
7781
|
+
}
|
|
8040
7782
|
}
|
|
8041
7783
|
}
|
|
8042
7784
|
if (bestBlank >= 0) return bestBlank + 1;
|
|
@@ -8093,6 +7835,70 @@ function chunkFile(content, params = DEFAULT_CHUNKING_PARAMS) {
|
|
|
8093
7835
|
}
|
|
8094
7836
|
return chunks2;
|
|
8095
7837
|
}
|
|
7838
|
+
function isDocFile(filePath) {
|
|
7839
|
+
return DOCS_EXTENSIONS.has(extname2(filePath).toLowerCase());
|
|
7840
|
+
}
|
|
7841
|
+
function detectLanguage(filePath) {
|
|
7842
|
+
return LANG_MAP[extname2(filePath).toLowerCase()] ?? null;
|
|
7843
|
+
}
|
|
7844
|
+
function isBinaryExt(filePath) {
|
|
7845
|
+
return BINARY_EXTENSIONS.has(extname2(filePath).toLowerCase());
|
|
7846
|
+
}
|
|
7847
|
+
async function fullScan(repoRoot) {
|
|
7848
|
+
const { stdout } = await execFileAsync("git", ["ls-files", "-z"], { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 });
|
|
7849
|
+
const paths = stdout.split("\0").filter(Boolean);
|
|
7850
|
+
const results = [];
|
|
7851
|
+
for (const p of paths) {
|
|
7852
|
+
if (isBinaryExt(p)) continue;
|
|
7853
|
+
const abs = resolve3(repoRoot, p);
|
|
7854
|
+
try {
|
|
7855
|
+
const s = await stat4(abs);
|
|
7856
|
+
if (s.size > MAX_FILE_SIZE) continue;
|
|
7857
|
+
} catch {
|
|
7858
|
+
continue;
|
|
7859
|
+
}
|
|
7860
|
+
results.push({ path: p, absolute_path: abs, language: detectLanguage(p), status: "added" });
|
|
7861
|
+
}
|
|
7862
|
+
return results;
|
|
7863
|
+
}
|
|
7864
|
+
async function diffScan(repoRoot, fromCommit, toCommit) {
|
|
7865
|
+
const { stdout } = await execFileAsync(
|
|
7866
|
+
"git",
|
|
7867
|
+
["diff", "--name-status", "--diff-filter=ACMRD", "-z", `${fromCommit}..${toCommit}`],
|
|
7868
|
+
{ cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 }
|
|
7869
|
+
);
|
|
7870
|
+
const parts = stdout.split("\0").filter(Boolean);
|
|
7871
|
+
const results = [];
|
|
7872
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
7873
|
+
const statusChar = parts[i];
|
|
7874
|
+
const p = parts[i + 1];
|
|
7875
|
+
if (!p) continue;
|
|
7876
|
+
if (isBinaryExt(p)) continue;
|
|
7877
|
+
const abs = resolve3(repoRoot, p);
|
|
7878
|
+
let status;
|
|
7879
|
+
if (statusChar === "D") {
|
|
7880
|
+
status = "deleted";
|
|
7881
|
+
} else if (statusChar === "A") {
|
|
7882
|
+
status = "added";
|
|
7883
|
+
} else {
|
|
7884
|
+
status = "modified";
|
|
7885
|
+
}
|
|
7886
|
+
if (status !== "deleted") {
|
|
7887
|
+
try {
|
|
7888
|
+
const s = await stat4(abs);
|
|
7889
|
+
if (s.size > MAX_FILE_SIZE) continue;
|
|
7890
|
+
} catch {
|
|
7891
|
+
continue;
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7894
|
+
results.push({ path: p, absolute_path: abs, language: detectLanguage(p), status });
|
|
7895
|
+
}
|
|
7896
|
+
return results;
|
|
7897
|
+
}
|
|
7898
|
+
async function getHeadCommit(repoRoot) {
|
|
7899
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: repoRoot });
|
|
7900
|
+
return stdout.trim();
|
|
7901
|
+
}
|
|
8096
7902
|
function extractImportSpecifiers(content, language) {
|
|
8097
7903
|
switch (language) {
|
|
8098
7904
|
case "typescript":
|
|
@@ -8195,13 +8001,13 @@ function resolveRust(dir, spec, known) {
|
|
|
8195
8001
|
base = spec.replace("crate::", "src/").replace(/::/g, "/");
|
|
8196
8002
|
} else if (spec.startsWith("super::")) {
|
|
8197
8003
|
const parent = dir.substring(0, dir.lastIndexOf("/"));
|
|
8198
|
-
base = parent
|
|
8004
|
+
base = `${parent}/${spec.replace("super::", "").replace(/::/g, "/")}`;
|
|
8199
8005
|
} else {
|
|
8200
|
-
base = dir
|
|
8006
|
+
base = `${dir}/${spec.replace("self::", "").replace(/::/g, "/")}`;
|
|
8201
8007
|
}
|
|
8202
|
-
const candidate = normalizePath2(base)
|
|
8008
|
+
const candidate = `${normalizePath2(base)}.rs`;
|
|
8203
8009
|
if (known.has(candidate)) return candidate;
|
|
8204
|
-
const modCandidate = normalizePath2(base)
|
|
8010
|
+
const modCandidate = `${normalizePath2(base)}/mod.rs`;
|
|
8205
8011
|
if (known.has(modCandidate)) return modCandidate;
|
|
8206
8012
|
return null;
|
|
8207
8013
|
}
|
|
@@ -8349,7 +8155,8 @@ function extractAndPersistMetadata(db, repoId) {
|
|
|
8349
8155
|
for (const row of rows) {
|
|
8350
8156
|
const existing = files.get(row.path);
|
|
8351
8157
|
if (existing) {
|
|
8352
|
-
existing.content +=
|
|
8158
|
+
existing.content += `
|
|
8159
|
+
${row.content}`;
|
|
8353
8160
|
} else {
|
|
8354
8161
|
files.set(row.path, {
|
|
8355
8162
|
content: row.content,
|
|
@@ -8375,35 +8182,6 @@ function extractAndPersistMetadata(db, repoId) {
|
|
|
8375
8182
|
}
|
|
8376
8183
|
return count;
|
|
8377
8184
|
}
|
|
8378
|
-
function buildAndPersistImportGraph(db, repoId) {
|
|
8379
|
-
const rows = chunkQueries.getAllByRepo(db, repoId);
|
|
8380
|
-
const files = /* @__PURE__ */ new Map();
|
|
8381
|
-
for (const row of rows) {
|
|
8382
|
-
const existing = files.get(row.path);
|
|
8383
|
-
if (existing) {
|
|
8384
|
-
existing.content += "\n" + row.content;
|
|
8385
|
-
} else {
|
|
8386
|
-
files.set(row.path, {
|
|
8387
|
-
content: row.content,
|
|
8388
|
-
language: row.language ?? detectLanguage(row.path) ?? "text"
|
|
8389
|
-
});
|
|
8390
|
-
}
|
|
8391
|
-
}
|
|
8392
|
-
const knownPaths = new Set(files.keys());
|
|
8393
|
-
importQueries.deleteByRepo(db, repoId);
|
|
8394
|
-
let edgeCount = 0;
|
|
8395
|
-
for (const [sourcePath, { content, language }] of files) {
|
|
8396
|
-
const specs = extractImportSpecifiers(content, language);
|
|
8397
|
-
for (const spec of specs) {
|
|
8398
|
-
const targetPath = resolveImport(sourcePath, spec, language, knownPaths);
|
|
8399
|
-
if (targetPath && targetPath !== sourcePath) {
|
|
8400
|
-
importQueries.insert(db, repoId, sourcePath, targetPath);
|
|
8401
|
-
edgeCount++;
|
|
8402
|
-
}
|
|
8403
|
-
}
|
|
8404
|
-
}
|
|
8405
|
-
return edgeCount;
|
|
8406
|
-
}
|
|
8407
8185
|
async function analyzeGitHistory(db, repoId, rootPath, lastAnalyzedCommit) {
|
|
8408
8186
|
const args = ["log", "--name-only", "--format=%H %aI", "--no-merges", `-n`, `${MAX_COMMITS}`];
|
|
8409
8187
|
if (lastAnalyzedCommit) args.push(`${lastAnalyzedCommit}..HEAD`);
|
|
@@ -8498,15 +8276,35 @@ function parseGitLog(stdout) {
|
|
|
8498
8276
|
if (current) commits.push(current);
|
|
8499
8277
|
return commits;
|
|
8500
8278
|
}
|
|
8501
|
-
function
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8279
|
+
function buildAndPersistImportGraph(db, repoId) {
|
|
8280
|
+
const rows = chunkQueries.getAllByRepo(db, repoId);
|
|
8281
|
+
const files = /* @__PURE__ */ new Map();
|
|
8282
|
+
for (const row of rows) {
|
|
8283
|
+
const existing = files.get(row.path);
|
|
8284
|
+
if (existing) {
|
|
8285
|
+
existing.content += `
|
|
8286
|
+
${row.content}`;
|
|
8287
|
+
} else {
|
|
8288
|
+
files.set(row.path, {
|
|
8289
|
+
content: row.content,
|
|
8290
|
+
language: row.language ?? detectLanguage(row.path) ?? "text"
|
|
8291
|
+
});
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
const knownPaths = new Set(files.keys());
|
|
8295
|
+
importQueries.deleteByRepo(db, repoId);
|
|
8296
|
+
let edgeCount = 0;
|
|
8297
|
+
for (const [sourcePath, { content, language }] of files) {
|
|
8298
|
+
const specs = extractImportSpecifiers(content, language);
|
|
8299
|
+
for (const spec of specs) {
|
|
8300
|
+
const targetPath = resolveImport(sourcePath, spec, language, knownPaths);
|
|
8301
|
+
if (targetPath && targetPath !== sourcePath) {
|
|
8302
|
+
importQueries.insert(db, repoId, sourcePath, targetPath);
|
|
8303
|
+
edgeCount++;
|
|
8304
|
+
}
|
|
8305
|
+
}
|
|
8509
8306
|
}
|
|
8307
|
+
return edgeCount;
|
|
8510
8308
|
}
|
|
8511
8309
|
async function withLock(key, fn) {
|
|
8512
8310
|
while (locks.has(key)) {
|
|
@@ -8524,7 +8322,7 @@ async function withLock(key, fn) {
|
|
|
8524
8322
|
resolve22();
|
|
8525
8323
|
}
|
|
8526
8324
|
}
|
|
8527
|
-
async function runIndex(db, repoId,
|
|
8325
|
+
async function runIndex(db, repoId, _caps, force = false, onProgress, trace) {
|
|
8528
8326
|
return withLock(repoId, async () => {
|
|
8529
8327
|
const start = Date.now();
|
|
8530
8328
|
const repo = repoQueries.getById(db, repoId);
|
|
@@ -8696,458 +8494,372 @@ async function ensureIndexed(db, repoId, caps) {
|
|
|
8696
8494
|
if (repo.last_indexed_commit === head) return null;
|
|
8697
8495
|
return runIndex(db, repoId, caps);
|
|
8698
8496
|
}
|
|
8699
|
-
function estimateTokens(
|
|
8700
|
-
return Math.ceil(
|
|
8497
|
+
function estimateTokens(s) {
|
|
8498
|
+
return Math.ceil(s.length / 4);
|
|
8701
8499
|
}
|
|
8702
|
-
|
|
8703
|
-
const
|
|
8704
|
-
|
|
8705
|
-
|
|
8500
|
+
function basename3(p) {
|
|
8501
|
+
const i = p.lastIndexOf("/");
|
|
8502
|
+
return i >= 0 ? p.substring(i + 1) : p;
|
|
8503
|
+
}
|
|
8504
|
+
function guessLang(path) {
|
|
8505
|
+
const dot = path.lastIndexOf(".");
|
|
8506
|
+
if (dot < 0) return "";
|
|
8507
|
+
return LANG_EXT[path.substring(dot + 1)] ?? "";
|
|
8508
|
+
}
|
|
8509
|
+
function getPurpose(path, data) {
|
|
8510
|
+
for (const m of data.metadata) {
|
|
8511
|
+
if (m.path === path && m.purpose) return m.purpose;
|
|
8706
8512
|
}
|
|
8707
|
-
|
|
8708
|
-
let apiCalls = 0;
|
|
8709
|
-
while (apiCalls < MAX_API_CALLS) {
|
|
8710
|
-
const remaining = chunkQueries.countUnembedded(db, repoId);
|
|
8711
|
-
if (remaining === 0) break;
|
|
8712
|
-
const rows = chunkQueries.getUnembedded(db, repoId, POOL_SIZE);
|
|
8713
|
-
if (rows.length === 0) break;
|
|
8714
|
-
const validRows = rows.filter((r) => r.content.trim().length > 0);
|
|
8715
|
-
if (validRows.length === 0) {
|
|
8716
|
-
apiCalls++;
|
|
8717
|
-
continue;
|
|
8718
|
-
}
|
|
8719
|
-
let offset = 0;
|
|
8720
|
-
let poolFailed = false;
|
|
8721
|
-
while (offset < validRows.length && apiCalls < MAX_API_CALLS) {
|
|
8722
|
-
const batch = [];
|
|
8723
|
-
let batchTokens = 0;
|
|
8724
|
-
while (offset < validRows.length) {
|
|
8725
|
-
const est = estimateTokens(validRows[offset].content);
|
|
8726
|
-
if (batch.length > 0 && batchTokens + est > MAX_BATCH_TOKENS) break;
|
|
8727
|
-
batch.push(validRows[offset]);
|
|
8728
|
-
batchTokens += est;
|
|
8729
|
-
offset++;
|
|
8730
|
-
}
|
|
8731
|
-
try {
|
|
8732
|
-
const texts = batch.map((r) => r.content);
|
|
8733
|
-
const vectors = await caps.embedTexts(texts);
|
|
8734
|
-
for (let i = 0; i < batch.length; i++) {
|
|
8735
|
-
chunkQueries.updateEmbedding(db, batch[i].id, repoId, vectors[i]);
|
|
8736
|
-
}
|
|
8737
|
-
embedded += batch.length;
|
|
8738
|
-
} catch (err) {
|
|
8739
|
-
console.error(`[LENS] Embed batch failed (${batch.length} chunks, ~${batchTokens} tokens):`, err.message);
|
|
8740
|
-
poolFailed = true;
|
|
8741
|
-
break;
|
|
8742
|
-
}
|
|
8743
|
-
apiCalls++;
|
|
8744
|
-
}
|
|
8745
|
-
if (poolFailed) break;
|
|
8746
|
-
}
|
|
8747
|
-
return { embedded_count: embedded, skipped_count: 0, duration_ms: Date.now() - start };
|
|
8513
|
+
return "";
|
|
8748
8514
|
}
|
|
8749
|
-
|
|
8750
|
-
const
|
|
8751
|
-
|
|
8752
|
-
|
|
8515
|
+
function renderImports(path, data) {
|
|
8516
|
+
const fwd = data.forwardImports.get(path)?.slice(0, 3) ?? [];
|
|
8517
|
+
const rev = data.reverseImports.get(path)?.slice(0, 3) ?? [];
|
|
8518
|
+
const parts = [];
|
|
8519
|
+
if (rev.length) parts.push(`\u2190 ${rev.join(", ")}`);
|
|
8520
|
+
if (fwd.length) parts.push(`\u2192 ${fwd.join(", ")}`);
|
|
8521
|
+
return parts.join(" | ");
|
|
8522
|
+
}
|
|
8523
|
+
function renderCochanges(path, cochanges, limit = 2) {
|
|
8524
|
+
const partners = [];
|
|
8525
|
+
for (const cc of cochanges) {
|
|
8526
|
+
if (cc.path === path) partners.push({ name: basename3(cc.partner), count: cc.count });
|
|
8527
|
+
else if (cc.partner === path) partners.push({ name: basename3(cc.path), count: cc.count });
|
|
8753
8528
|
}
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
const batch = candidates.slice(i, i + PURPOSE_CONCURRENCY);
|
|
8761
|
-
const results = await Promise.allSettled(
|
|
8762
|
-
batch.map(async (row) => {
|
|
8763
|
-
const exports = Array.isArray(row.exports) ? row.exports : jsonParse(row.exports, []);
|
|
8764
|
-
const purpose = await caps.generatePurpose(row.path, row.first_chunk, exports, row.docstring ?? "");
|
|
8765
|
-
return { row, purpose };
|
|
8766
|
-
})
|
|
8767
|
-
);
|
|
8768
|
-
for (let j = 0; j < results.length; j++) {
|
|
8769
|
-
const result = results[j];
|
|
8770
|
-
const row = batch[j];
|
|
8771
|
-
if (result.status === "fulfilled" && result.value.purpose) {
|
|
8772
|
-
metadataQueries.updatePurpose(db, repoId, row.path, result.value.purpose, row.chunk_hash);
|
|
8773
|
-
totalEnriched++;
|
|
8774
|
-
} else {
|
|
8775
|
-
metadataQueries.setPurposeHash(db, repoId, row.path, row.chunk_hash);
|
|
8776
|
-
totalSkipped++;
|
|
8777
|
-
}
|
|
8778
|
-
}
|
|
8779
|
-
}
|
|
8780
|
-
if (!fullRun) break;
|
|
8529
|
+
partners.sort((a, b) => b.count - a.count);
|
|
8530
|
+
return partners.slice(0, limit).map((p) => `${p.name} (${p.count}x)`).join(", ");
|
|
8531
|
+
}
|
|
8532
|
+
function getExports(path, data) {
|
|
8533
|
+
for (const m of data.metadata) {
|
|
8534
|
+
if (m.path === path) return m.exports ?? [];
|
|
8781
8535
|
}
|
|
8782
|
-
return
|
|
8536
|
+
return [];
|
|
8783
8537
|
}
|
|
8784
|
-
function
|
|
8785
|
-
|
|
8538
|
+
function fileLabel(path, snippet) {
|
|
8539
|
+
const line = snippet?.line ? `:${snippet.line}` : "";
|
|
8540
|
+
return `${path}${line}`;
|
|
8786
8541
|
}
|
|
8787
|
-
function
|
|
8788
|
-
const
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8542
|
+
function renderSlice(path, data) {
|
|
8543
|
+
const slice = data.slices?.get(path);
|
|
8544
|
+
if (!slice) return null;
|
|
8545
|
+
const lang = guessLang(path);
|
|
8546
|
+
return ` \`\`\`${lang}
|
|
8547
|
+
${slice.code}
|
|
8548
|
+
\`\`\``;
|
|
8549
|
+
}
|
|
8550
|
+
function formatNatural(data) {
|
|
8551
|
+
const L = [];
|
|
8552
|
+
const files = data.files.slice(0, 7);
|
|
8553
|
+
const snippets = data.snippets ?? /* @__PURE__ */ new Map();
|
|
8554
|
+
L.push(`# ${data.goal}`);
|
|
8555
|
+
L.push("");
|
|
8556
|
+
L.push("## Key Files");
|
|
8557
|
+
for (let i = 0; i < files.length; i++) {
|
|
8558
|
+
const f = files[i];
|
|
8559
|
+
const snip = snippets.get(f.path);
|
|
8560
|
+
const purpose = getPurpose(f.path, data);
|
|
8561
|
+
const label = fileLabel(f.path, snip);
|
|
8562
|
+
L.push(`${i + 1}. **${label}**${purpose ? ` \u2014 ${purpose}` : ""}`);
|
|
8563
|
+
const exports = getExports(f.path, data);
|
|
8564
|
+
if (exports.length) L.push(` Exports: ${exports.slice(0, 5).join(", ")}`);
|
|
8565
|
+
const imports = renderImports(f.path, data);
|
|
8566
|
+
if (imports) L.push(` ${imports}`);
|
|
8567
|
+
const cochangeStr = renderCochanges(f.path, data.cochanges);
|
|
8568
|
+
if (cochangeStr) L.push(` Co-changes: ${cochangeStr}`);
|
|
8569
|
+
const sliceStr = renderSlice(f.path, data);
|
|
8570
|
+
if (sliceStr) L.push(sliceStr);
|
|
8571
|
+
}
|
|
8572
|
+
const testLines = [];
|
|
8573
|
+
const testFiles = data.testFiles;
|
|
8574
|
+
if (testFiles) {
|
|
8575
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8576
|
+
for (const f of files.slice(0, 5)) {
|
|
8577
|
+
const tests = testFiles.get(f.path);
|
|
8578
|
+
if (tests) {
|
|
8579
|
+
for (const t of tests) {
|
|
8580
|
+
if (!seen.has(t)) {
|
|
8581
|
+
seen.add(t);
|
|
8582
|
+
testLines.push(`- ${t}`);
|
|
8583
|
+
}
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8586
|
+
}
|
|
8587
|
+
}
|
|
8588
|
+
if (testLines.length) {
|
|
8589
|
+
L.push("");
|
|
8590
|
+
L.push("## Tests");
|
|
8591
|
+
for (const t of testLines) L.push(t);
|
|
8592
|
+
}
|
|
8593
|
+
return L.join("\n");
|
|
8594
|
+
}
|
|
8595
|
+
function formatSymbol(data) {
|
|
8596
|
+
const L = [];
|
|
8597
|
+
const files = data.files;
|
|
8598
|
+
const snippets = data.snippets ?? /* @__PURE__ */ new Map();
|
|
8599
|
+
const top = files[0];
|
|
8600
|
+
if (!top) return `# ${data.goal}
|
|
8601
|
+
|
|
8602
|
+
No files found.`;
|
|
8603
|
+
const snip = snippets.get(top.path);
|
|
8604
|
+
const purpose = getPurpose(top.path, data);
|
|
8605
|
+
L.push(`# Symbol: ${data.goal}`);
|
|
8606
|
+
L.push("");
|
|
8607
|
+
L.push("## Definition");
|
|
8608
|
+
L.push(`**${fileLabel(top.path, snip)}**${purpose ? ` \u2014 ${purpose}` : ""}`);
|
|
8609
|
+
if (snip?.symbol) {
|
|
8610
|
+
L.push(` \`${snip.symbol}\``);
|
|
8611
|
+
} else {
|
|
8612
|
+
const exports = getExports(top.path, data);
|
|
8613
|
+
if (exports.length) L.push(` Exports: ${exports.slice(0, 5).join(", ")}`);
|
|
8614
|
+
}
|
|
8615
|
+
const sliceStr = renderSlice(top.path, data);
|
|
8616
|
+
if (sliceStr) L.push(sliceStr);
|
|
8617
|
+
const rev = data.reverseImports.get(top.path);
|
|
8618
|
+
if (rev?.length) {
|
|
8619
|
+
L.push("");
|
|
8620
|
+
L.push("## Dependents");
|
|
8621
|
+
L.push(`\u2190 ${rev.slice(0, 5).join(", ")}`);
|
|
8622
|
+
}
|
|
8623
|
+
const cochangeStr = renderCochanges(top.path, data.cochanges, 3);
|
|
8624
|
+
if (cochangeStr) {
|
|
8625
|
+
L.push("");
|
|
8626
|
+
L.push("## Co-changes");
|
|
8627
|
+
L.push(cochangeStr);
|
|
8628
|
+
}
|
|
8629
|
+
if (files.length > 1) {
|
|
8630
|
+
L.push("");
|
|
8631
|
+
L.push("## Also relevant");
|
|
8632
|
+
for (let i = 1; i < Math.min(files.length, 5); i++) {
|
|
8633
|
+
const f = files[i];
|
|
8634
|
+
const p = getPurpose(f.path, data);
|
|
8635
|
+
L.push(`${i + 1}. ${f.path}${p ? ` \u2014 ${p}` : ""}`);
|
|
8805
8636
|
}
|
|
8806
8637
|
}
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8638
|
+
return L.join("\n");
|
|
8639
|
+
}
|
|
8640
|
+
function formatError(data) {
|
|
8641
|
+
const L = [];
|
|
8642
|
+
const files = data.files;
|
|
8643
|
+
const snippets = data.snippets ?? /* @__PURE__ */ new Map();
|
|
8644
|
+
L.push(`# Error: ${data.goal}`);
|
|
8645
|
+
L.push("");
|
|
8646
|
+
if (files.length === 0) {
|
|
8647
|
+
L.push("No matching files found.");
|
|
8648
|
+
return L.join("\n");
|
|
8649
|
+
}
|
|
8650
|
+
const top = files[0];
|
|
8651
|
+
const snip = snippets.get(top.path);
|
|
8652
|
+
const purpose = getPurpose(top.path, data);
|
|
8653
|
+
L.push("## Error Source");
|
|
8654
|
+
L.push(`1. **${fileLabel(top.path, snip)}**${purpose ? ` \u2014 ${purpose}` : ""}`);
|
|
8655
|
+
const exports = getExports(top.path, data);
|
|
8656
|
+
if (exports.length) L.push(` Exports: ${exports.slice(0, 5).join(", ")}`);
|
|
8657
|
+
const imports = renderImports(top.path, data);
|
|
8658
|
+
if (imports) L.push(` ${imports}`);
|
|
8659
|
+
const sliceStr = renderSlice(top.path, data);
|
|
8660
|
+
if (sliceStr) L.push(sliceStr);
|
|
8661
|
+
if (files.length > 1) {
|
|
8662
|
+
L.push("");
|
|
8663
|
+
L.push("## Also References");
|
|
8664
|
+
for (let i = 1; i < Math.min(files.length, 5); i++) {
|
|
8665
|
+
const f = files[i];
|
|
8666
|
+
const s = snippets.get(f.path);
|
|
8667
|
+
const p = getPurpose(f.path, data);
|
|
8668
|
+
L.push(`${i + 1}. **${fileLabel(f.path, s)}**${p ? ` \u2014 ${p}` : ""}`);
|
|
8813
8669
|
}
|
|
8814
8670
|
}
|
|
8815
|
-
|
|
8816
|
-
const terms = filtered.slice(0, MAX_TERMS);
|
|
8817
|
-
return { terms, termToFiles };
|
|
8671
|
+
return L.join("\n");
|
|
8818
8672
|
}
|
|
8819
|
-
function
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8673
|
+
function formatStackTrace(data) {
|
|
8674
|
+
const L = [];
|
|
8675
|
+
const files = data.files;
|
|
8676
|
+
const snippets = data.snippets ?? /* @__PURE__ */ new Map();
|
|
8677
|
+
L.push("# Stack Trace");
|
|
8678
|
+
L.push("");
|
|
8679
|
+
if (files.length === 0) {
|
|
8680
|
+
L.push("No matching files found.");
|
|
8681
|
+
return L.join("\n");
|
|
8682
|
+
}
|
|
8683
|
+
const top = files[0];
|
|
8684
|
+
const snip = snippets.get(top.path);
|
|
8685
|
+
const purpose = getPurpose(top.path, data);
|
|
8686
|
+
L.push("## Crash Point");
|
|
8687
|
+
L.push(`**${fileLabel(top.path, snip)}**${purpose ? ` \u2014 ${purpose}` : ""}`);
|
|
8688
|
+
if (snip?.symbol) {
|
|
8689
|
+
L.push(` \`${snip.symbol}\``);
|
|
8690
|
+
}
|
|
8691
|
+
const sliceStr = renderSlice(top.path, data);
|
|
8692
|
+
if (sliceStr) L.push(sliceStr);
|
|
8693
|
+
const chain = [];
|
|
8694
|
+
const fwd = data.forwardImports.get(top.path);
|
|
8695
|
+
const rev = data.reverseImports.get(top.path);
|
|
8696
|
+
if (rev?.length || fwd?.length) {
|
|
8697
|
+
const callers = rev?.slice(0, 2) ?? [];
|
|
8698
|
+
const deps = fwd?.slice(0, 2) ?? [];
|
|
8699
|
+
if (callers.length || deps.length) {
|
|
8700
|
+
const parts = [...callers.map((c) => `${c} \u2192`), top.path, ...deps.map((d) => `\u2192 ${d}`)];
|
|
8701
|
+
chain.push(parts.join(" "));
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
if (chain.length) {
|
|
8705
|
+
L.push("");
|
|
8706
|
+
L.push("## Call Chain");
|
|
8707
|
+
for (const c of chain) L.push(c);
|
|
8825
8708
|
}
|
|
8826
|
-
|
|
8827
|
-
|
|
8709
|
+
if (files.length > 1) {
|
|
8710
|
+
L.push("");
|
|
8711
|
+
L.push("## Related");
|
|
8712
|
+
for (let i = 1; i < Math.min(files.length, 5); i++) {
|
|
8713
|
+
const f = files[i];
|
|
8714
|
+
const p = getPurpose(f.path, data);
|
|
8715
|
+
const cochangeStr = renderCochanges(f.path, data.cochanges, 1);
|
|
8716
|
+
L.push(`- ${f.path}${p ? ` \u2014 ${p}` : ""}${cochangeStr ? ` (${cochangeStr})` : ""}`);
|
|
8717
|
+
const relSlice = renderSlice(f.path, data);
|
|
8718
|
+
if (relSlice) L.push(relSlice);
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
return L.join("\n");
|
|
8828
8722
|
}
|
|
8829
|
-
function
|
|
8830
|
-
const
|
|
8831
|
-
|
|
8832
|
-
for (
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8723
|
+
function stripCodeSlices(lines) {
|
|
8724
|
+
const result = [];
|
|
8725
|
+
let inFence = false;
|
|
8726
|
+
for (const line of lines) {
|
|
8727
|
+
if (line.trimStart().startsWith("```") && !inFence) {
|
|
8728
|
+
inFence = true;
|
|
8729
|
+
continue;
|
|
8730
|
+
}
|
|
8731
|
+
if (inFence && line.trimStart().startsWith("```")) {
|
|
8732
|
+
inFence = false;
|
|
8733
|
+
continue;
|
|
8836
8734
|
}
|
|
8735
|
+
if (!inFence) result.push(line);
|
|
8837
8736
|
}
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8737
|
+
return result;
|
|
8738
|
+
}
|
|
8739
|
+
function progressiveStrip(output, _data) {
|
|
8740
|
+
let lines = output.split("\n");
|
|
8741
|
+
if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
|
|
8742
|
+
lines = stripCodeSlices(lines);
|
|
8844
8743
|
}
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8744
|
+
if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
|
|
8745
|
+
lines = lines.map((l) => {
|
|
8746
|
+
if (l.trimStart().startsWith("Co-changes:")) {
|
|
8747
|
+
const match2 = l.match(/Co-changes:\s*([^,]+)/);
|
|
8748
|
+
if (match2) return `${l.substring(0, l.indexOf("Co-changes:"))}Co-changes: ${match2[1].trim()}`;
|
|
8749
|
+
}
|
|
8750
|
+
return l;
|
|
8751
|
+
});
|
|
8851
8752
|
}
|
|
8852
|
-
|
|
8853
|
-
const
|
|
8854
|
-
if (
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
size[ri] += size[rj];
|
|
8859
|
-
} else {
|
|
8860
|
-
parent[ri] = rj;
|
|
8861
|
-
size[rj] += size[ri];
|
|
8753
|
+
if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
|
|
8754
|
+
const testIdx = lines.findIndex((l) => l.startsWith("## Tests"));
|
|
8755
|
+
if (testIdx >= 0) {
|
|
8756
|
+
let endIdx = lines.findIndex((l, i) => i > testIdx && l.startsWith("## "));
|
|
8757
|
+
if (endIdx < 0) endIdx = lines.length;
|
|
8758
|
+
lines.splice(testIdx, endIdx - testIdx);
|
|
8862
8759
|
}
|
|
8863
8760
|
}
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
clusterMap.set(root, arr);
|
|
8761
|
+
if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
|
|
8762
|
+
lines = lines.filter((l) => {
|
|
8763
|
+
const trimmed = l.trimStart();
|
|
8764
|
+
return !(trimmed.startsWith("\u2190") || trimmed.startsWith("\u2192")) || !trimmed.includes("|");
|
|
8765
|
+
});
|
|
8870
8766
|
}
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8767
|
+
if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
|
|
8768
|
+
const reduced = [];
|
|
8769
|
+
let fileCount = 0;
|
|
8770
|
+
for (const line of lines) {
|
|
8771
|
+
if (/^\d+\./.test(line.trimStart())) {
|
|
8772
|
+
fileCount++;
|
|
8773
|
+
if (fileCount > 5) continue;
|
|
8774
|
+
}
|
|
8775
|
+
reduced.push(line);
|
|
8776
|
+
}
|
|
8777
|
+
lines = reduced;
|
|
8877
8778
|
}
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8779
|
+
if (estimateTokens(lines.join("\n")) > TOKEN_CAP) {
|
|
8780
|
+
lines = lines.map((l) => {
|
|
8781
|
+
const dashIdx = l.indexOf(" \u2014 ");
|
|
8782
|
+
if (dashIdx > 0 && (l.includes("**") || /^\d+\./.test(l.trimStart()))) {
|
|
8783
|
+
return l.substring(0, dashIdx);
|
|
8784
|
+
}
|
|
8785
|
+
return l;
|
|
8786
|
+
});
|
|
8883
8787
|
}
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8788
|
+
return lines.join("\n");
|
|
8789
|
+
}
|
|
8790
|
+
function filterByScoreRelevance(data) {
|
|
8791
|
+
if (!data.scores || data.scores.size === 0) return data;
|
|
8792
|
+
const topScore = Math.max(...data.scores.values());
|
|
8793
|
+
if (topScore <= 0) return data;
|
|
8794
|
+
const threshold = topScore * 0.15;
|
|
8795
|
+
const filtered = data.files.filter((f) => {
|
|
8796
|
+
const score = data.scores?.get(f.path);
|
|
8797
|
+
if (score === void 0) return data.files.indexOf(f) < 5;
|
|
8798
|
+
return score >= threshold;
|
|
8799
|
+
});
|
|
8800
|
+
return { ...data, files: filtered };
|
|
8801
|
+
}
|
|
8802
|
+
function formatContextPack(data) {
|
|
8803
|
+
const filtered = filterByScoreRelevance(data);
|
|
8804
|
+
let output;
|
|
8805
|
+
switch (filtered.queryKind) {
|
|
8806
|
+
case "symbol":
|
|
8807
|
+
output = formatSymbol(filtered);
|
|
8808
|
+
break;
|
|
8809
|
+
case "error_message":
|
|
8810
|
+
output = formatError(filtered);
|
|
8811
|
+
break;
|
|
8812
|
+
case "stack_trace":
|
|
8813
|
+
output = formatStackTrace(filtered);
|
|
8814
|
+
break;
|
|
8815
|
+
default:
|
|
8816
|
+
output = formatNatural(filtered);
|
|
8817
|
+
break;
|
|
8891
8818
|
}
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
console.error(`[LENS] Vocab clusters: skipped (only ${terms.length} terms, need \u22654)`);
|
|
8895
|
-
return;
|
|
8819
|
+
if (estimateTokens(output) > TOKEN_CAP) {
|
|
8820
|
+
output = progressiveStrip(output, filtered);
|
|
8896
8821
|
}
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8822
|
+
return output;
|
|
8823
|
+
}
|
|
8824
|
+
function extractFrames(query) {
|
|
8825
|
+
const frames = [];
|
|
8826
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8827
|
+
for (const pattern of FRAME_PATTERNS) {
|
|
8828
|
+
pattern.lastIndex = 0;
|
|
8829
|
+
let m;
|
|
8830
|
+
while (m = pattern.exec(query)) {
|
|
8831
|
+
const raw2 = m[1].replace(/^\.\//, "").replace(/^\/+/, "");
|
|
8832
|
+
const line = Number.parseInt(m[2], 10);
|
|
8833
|
+
const key = `${raw2}:${line}`;
|
|
8834
|
+
if (!seen.has(key)) {
|
|
8835
|
+
seen.add(key);
|
|
8836
|
+
frames.push({ path: raw2, line });
|
|
8837
|
+
}
|
|
8905
8838
|
}
|
|
8906
|
-
embeddings = results;
|
|
8907
|
-
} catch (err) {
|
|
8908
|
-
console.error(`[LENS] Vocab clusters: embed failed (${terms.length} terms):`, err.message);
|
|
8909
|
-
return;
|
|
8910
8839
|
}
|
|
8911
|
-
|
|
8912
|
-
console.error(`[LENS] Vocab clusters: ${rawClusters.length} clusters from ${terms.length} terms`);
|
|
8913
|
-
const clusters = rawClusters.map((indices) => {
|
|
8914
|
-
const clusterTerms = indices.map((i) => terms[i]);
|
|
8915
|
-
const fileSet = /* @__PURE__ */ new Set();
|
|
8916
|
-
for (const t of clusterTerms) {
|
|
8917
|
-
const files = termToFiles.get(t);
|
|
8918
|
-
if (files) for (const f of files) fileSet.add(f);
|
|
8919
|
-
}
|
|
8920
|
-
return { terms: clusterTerms, files: [...fileSet].sort() };
|
|
8921
|
-
}).filter((c) => c.files.length > 0).sort((a, b) => b.files.length - a.files.length).slice(0, MAX_CLUSTERS);
|
|
8922
|
-
repoQueries.updateVocabClusters(db, repoId, clusters, repo.last_indexed_commit ?? void 0);
|
|
8840
|
+
return frames;
|
|
8923
8841
|
}
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
return;
|
|
8842
|
+
function tokenize(query) {
|
|
8843
|
+
return query.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
|
|
8844
|
+
}
|
|
8845
|
+
function parseQuery(query) {
|
|
8846
|
+
const naturalTokens = tokenize(query);
|
|
8847
|
+
const frames = extractFrames(query);
|
|
8848
|
+
if (frames.length >= 2) {
|
|
8849
|
+
return { kind: "stack_trace", raw: query, frames, symbol: null, errorToken: null, naturalTokens };
|
|
8932
8850
|
}
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
} catch {
|
|
8937
|
-
return;
|
|
8851
|
+
const trimmed = query.trim();
|
|
8852
|
+
if (SYMBOL_RE.test(trimmed) && trimmed.length >= 4 && CASING_BOUNDARY.test(trimmed)) {
|
|
8853
|
+
return { kind: "symbol", raw: query, frames: [], symbol: trimmed, errorToken: null, naturalTokens };
|
|
8938
8854
|
}
|
|
8939
|
-
const
|
|
8940
|
-
const
|
|
8941
|
-
const
|
|
8942
|
-
|
|
8943
|
-
|
|
8855
|
+
const errorCode = query.match(ERROR_CODE_RE)?.[0];
|
|
8856
|
+
const errorSuffix = query.match(ERROR_SUFFIX_RE)?.[0];
|
|
8857
|
+
const errorPrefix = query.match(ERROR_PREFIX_RE)?.[1];
|
|
8858
|
+
const errorToken = errorCode || errorSuffix || errorPrefix || null;
|
|
8859
|
+
if (errorToken) {
|
|
8860
|
+
return { kind: "error_message", raw: query, frames, symbol: null, errorToken, naturalTokens };
|
|
8944
8861
|
}
|
|
8945
|
-
|
|
8946
|
-
for (const chunk of newChunks) {
|
|
8947
|
-
const key = `${chunk.chunk_index}:${chunk.chunk_hash}`;
|
|
8948
|
-
newKeys.add(key);
|
|
8949
|
-
if (existing.has(key)) {
|
|
8950
|
-
chunkQueries.updateLastSeen(db, existing.get(key), repoId, "watcher");
|
|
8951
|
-
} else {
|
|
8952
|
-
chunkQueries.upsert(
|
|
8953
|
-
db,
|
|
8954
|
-
repoId,
|
|
8955
|
-
relPath,
|
|
8956
|
-
chunk.chunk_index,
|
|
8957
|
-
chunk.start_line,
|
|
8958
|
-
chunk.end_line,
|
|
8959
|
-
chunk.content,
|
|
8960
|
-
chunk.chunk_hash,
|
|
8961
|
-
"watcher",
|
|
8962
|
-
language
|
|
8963
|
-
);
|
|
8964
|
-
}
|
|
8965
|
-
}
|
|
8966
|
-
for (const [key, id] of existing) {
|
|
8967
|
-
if (!newKeys.has(key)) chunkQueries.deleteById(db, id, repoId);
|
|
8968
|
-
}
|
|
8969
|
-
const entry = watchers.get(repoId);
|
|
8970
|
-
if (entry) entry.changedFiles++;
|
|
8971
|
-
}
|
|
8972
|
-
async function handleFileDelete(db, repoId, repoRoot, absPath) {
|
|
8973
|
-
const relPath = relative3(repoRoot, absPath);
|
|
8974
|
-
if (isBinaryExt(relPath)) return;
|
|
8975
|
-
chunkQueries.deleteByRepoPath(db, repoId, relPath);
|
|
8976
|
-
const entry = watchers.get(repoId);
|
|
8977
|
-
if (entry) entry.deletedFiles++;
|
|
8978
|
-
}
|
|
8979
|
-
function debouncedHandler(key, fn) {
|
|
8980
|
-
const existing = debounceTimers.get(key);
|
|
8981
|
-
if (existing) clearTimeout(existing);
|
|
8982
|
-
debounceTimers.set(
|
|
8983
|
-
key,
|
|
8984
|
-
setTimeout(() => {
|
|
8985
|
-
debounceTimers.delete(key);
|
|
8986
|
-
fn().catch(() => {
|
|
8987
|
-
});
|
|
8988
|
-
}, DEBOUNCE_MS)
|
|
8989
|
-
);
|
|
8990
|
-
}
|
|
8991
|
-
function startWatcher(db, repoId, rootPath) {
|
|
8992
|
-
if (watchers.has(repoId)) return { started: false, already_watching: true };
|
|
8993
|
-
try {
|
|
8994
|
-
const fsWatcher = watch(rootPath, {
|
|
8995
|
-
ignored: IGNORED,
|
|
8996
|
-
ignoreInitial: true,
|
|
8997
|
-
awaitWriteFinish: { stabilityThreshold: 200 },
|
|
8998
|
-
persistent: false,
|
|
8999
|
-
usePolling: false,
|
|
9000
|
-
depth: 5
|
|
9001
|
-
});
|
|
9002
|
-
const entry = {
|
|
9003
|
-
watcher: fsWatcher,
|
|
9004
|
-
repoRoot: rootPath,
|
|
9005
|
-
repoId,
|
|
9006
|
-
changedFiles: 0,
|
|
9007
|
-
deletedFiles: 0,
|
|
9008
|
-
startedAt: /* @__PURE__ */ new Date()
|
|
9009
|
-
};
|
|
9010
|
-
watchers.set(repoId, entry);
|
|
9011
|
-
fsWatcher.on(
|
|
9012
|
-
"add",
|
|
9013
|
-
(path) => debouncedHandler(`${repoId}:${path}`, () => handleFileChange(db, repoId, rootPath, path))
|
|
9014
|
-
);
|
|
9015
|
-
fsWatcher.on(
|
|
9016
|
-
"change",
|
|
9017
|
-
(path) => debouncedHandler(`${repoId}:${path}`, () => handleFileChange(db, repoId, rootPath, path))
|
|
9018
|
-
);
|
|
9019
|
-
fsWatcher.on(
|
|
9020
|
-
"unlink",
|
|
9021
|
-
(path) => debouncedHandler(`${repoId}:${path}`, () => handleFileDelete(db, repoId, rootPath, path))
|
|
9022
|
-
);
|
|
9023
|
-
fsWatcher.on("error", () => {
|
|
9024
|
-
stopWatcher(repoId).catch(() => {
|
|
9025
|
-
});
|
|
9026
|
-
});
|
|
9027
|
-
return { started: true, already_watching: false };
|
|
9028
|
-
} catch {
|
|
9029
|
-
return { started: false, already_watching: false };
|
|
9030
|
-
}
|
|
9031
|
-
}
|
|
9032
|
-
async function stopWatcher(repoId) {
|
|
9033
|
-
const entry = watchers.get(repoId);
|
|
9034
|
-
if (!entry) return { stopped: false };
|
|
9035
|
-
await entry.watcher.close();
|
|
9036
|
-
watchers.delete(repoId);
|
|
9037
|
-
for (const [key, timer] of debounceTimers) {
|
|
9038
|
-
if (key.startsWith(`${repoId}:`)) {
|
|
9039
|
-
clearTimeout(timer);
|
|
9040
|
-
debounceTimers.delete(key);
|
|
9041
|
-
}
|
|
9042
|
-
}
|
|
9043
|
-
return { stopped: true };
|
|
9044
|
-
}
|
|
9045
|
-
function getWatcherStatus(repoId) {
|
|
9046
|
-
const entry = watchers.get(repoId);
|
|
9047
|
-
if (!entry) return { watching: false, repo_root: null, changed_files: 0, deleted_files: 0, started_at: null };
|
|
9048
|
-
return {
|
|
9049
|
-
watching: true,
|
|
9050
|
-
repo_root: entry.repoRoot,
|
|
9051
|
-
changed_files: entry.changedFiles,
|
|
9052
|
-
deleted_files: entry.deletedFiles,
|
|
9053
|
-
started_at: entry.startedAt.toISOString()
|
|
9054
|
-
};
|
|
9055
|
-
}
|
|
9056
|
-
function basename3(p) {
|
|
9057
|
-
const i = p.lastIndexOf("/");
|
|
9058
|
-
return i >= 0 ? p.substring(i + 1) : p;
|
|
9059
|
-
}
|
|
9060
|
-
function daysAgo(d) {
|
|
9061
|
-
if (!d) return "unknown";
|
|
9062
|
-
const days = Math.round((Date.now() - d.getTime()) / 864e5);
|
|
9063
|
-
if (days === 0) return "today";
|
|
9064
|
-
if (days === 1) return "1d ago";
|
|
9065
|
-
return `${days}d ago`;
|
|
9066
|
-
}
|
|
9067
|
-
function formatContextPack(data) {
|
|
9068
|
-
const L = [];
|
|
9069
|
-
const files = data.files.slice(0, 15);
|
|
9070
|
-
const exportsMap = /* @__PURE__ */ new Map();
|
|
9071
|
-
for (const m of data.metadata) {
|
|
9072
|
-
if (m.exports?.length) exportsMap.set(m.path, m.exports);
|
|
9073
|
-
}
|
|
9074
|
-
L.push(`# ${data.goal}`);
|
|
9075
|
-
L.push("");
|
|
9076
|
-
if (files.length > 0) {
|
|
9077
|
-
L.push("## Files");
|
|
9078
|
-
for (let i = 0; i < files.length; i++) {
|
|
9079
|
-
const f = files[i];
|
|
9080
|
-
const exports = exportsMap.get(f.path);
|
|
9081
|
-
const exportStr = exports?.length ? ` \u2014 exports: ${exports.slice(0, 5).join(", ")}` : "";
|
|
9082
|
-
L.push(`${i + 1}. ${f.path}${exportStr}`);
|
|
9083
|
-
}
|
|
9084
|
-
L.push("");
|
|
9085
|
-
}
|
|
9086
|
-
const depLines = [];
|
|
9087
|
-
for (const f of files.slice(0, 5)) {
|
|
9088
|
-
const fwd = data.forwardImports.get(f.path);
|
|
9089
|
-
const rev = data.reverseImports.get(f.path);
|
|
9090
|
-
const hop2 = data.hop2Deps.get(f.path);
|
|
9091
|
-
if (!fwd?.length && !rev?.length) continue;
|
|
9092
|
-
depLines.push(f.path);
|
|
9093
|
-
if (fwd?.length) {
|
|
9094
|
-
depLines.push(` imports: ${fwd.slice(0, 5).map(basename3).join(", ")}`);
|
|
9095
|
-
}
|
|
9096
|
-
if (rev?.length) {
|
|
9097
|
-
let revStr = rev.slice(0, 4).map(basename3).join(", ");
|
|
9098
|
-
if (hop2?.length) {
|
|
9099
|
-
revStr += ` \u2192 ${hop2.slice(0, 3).map(basename3).join(", ")}`;
|
|
9100
|
-
}
|
|
9101
|
-
depLines.push(` imported by: ${revStr}`);
|
|
9102
|
-
}
|
|
9103
|
-
}
|
|
9104
|
-
if (depLines.length > 0) {
|
|
9105
|
-
L.push("## Dependency Graph");
|
|
9106
|
-
for (const line of depLines) L.push(line);
|
|
9107
|
-
L.push("");
|
|
9108
|
-
}
|
|
9109
|
-
if (data.cochanges.length > 0) {
|
|
9110
|
-
const clusters = buildClusters(data.cochanges);
|
|
9111
|
-
if (clusters.length > 0) {
|
|
9112
|
-
L.push("## Co-change Clusters");
|
|
9113
|
-
for (const c of clusters) {
|
|
9114
|
-
L.push(`[${c.members.map(basename3).join(", ")}] \u2014 ${c.count} co-commits`);
|
|
9115
|
-
}
|
|
9116
|
-
L.push("");
|
|
9117
|
-
}
|
|
9118
|
-
}
|
|
9119
|
-
const activityLines = [];
|
|
9120
|
-
for (const f of files.slice(0, 5)) {
|
|
9121
|
-
const stat32 = data.fileStats.get(f.path);
|
|
9122
|
-
if (!stat32 || stat32.commit_count === 0) continue;
|
|
9123
|
-
activityLines.push(
|
|
9124
|
-
`${basename3(f.path)}: ${stat32.commit_count} commits, ${stat32.recent_count}/90d, last: ${daysAgo(stat32.last_modified)}`
|
|
9125
|
-
);
|
|
9126
|
-
}
|
|
9127
|
-
if (activityLines.length > 0) {
|
|
9128
|
-
L.push("## Activity");
|
|
9129
|
-
for (const line of activityLines) L.push(line);
|
|
9130
|
-
}
|
|
9131
|
-
return L.join("\n");
|
|
9132
|
-
}
|
|
9133
|
-
function buildClusters(cochanges) {
|
|
9134
|
-
const clusters = [];
|
|
9135
|
-
for (const cc of cochanges) {
|
|
9136
|
-
let merged = false;
|
|
9137
|
-
for (const c of clusters) {
|
|
9138
|
-
if (c.members.includes(cc.path) || c.members.includes(cc.partner)) {
|
|
9139
|
-
if (!c.members.includes(cc.path)) c.members.push(cc.path);
|
|
9140
|
-
if (!c.members.includes(cc.partner)) c.members.push(cc.partner);
|
|
9141
|
-
c.count = Math.max(c.count, cc.count);
|
|
9142
|
-
merged = true;
|
|
9143
|
-
break;
|
|
9144
|
-
}
|
|
9145
|
-
}
|
|
9146
|
-
if (!merged) {
|
|
9147
|
-
clusters.push({ members: [cc.path, cc.partner], count: cc.count });
|
|
9148
|
-
}
|
|
9149
|
-
}
|
|
9150
|
-
return clusters.filter((c) => c.count >= 2).sort((a, b) => b.count - a.count).slice(0, 5);
|
|
8862
|
+
return { kind: "natural", raw: query, frames, symbol: null, errorToken: null, naturalTokens };
|
|
9151
8863
|
}
|
|
9152
8864
|
function stem(word) {
|
|
9153
8865
|
if (word.length <= 4) return word;
|
|
@@ -9155,6 +8867,7 @@ function stem(word) {
|
|
|
9155
8867
|
}
|
|
9156
8868
|
function expandKeywords(words, clusters) {
|
|
9157
8869
|
const exact = /* @__PURE__ */ new Set();
|
|
8870
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
9158
8871
|
const stemmed = /* @__PURE__ */ new Set();
|
|
9159
8872
|
const clusterFiles = /* @__PURE__ */ new Set();
|
|
9160
8873
|
for (const w of words) {
|
|
@@ -9174,21 +8887,26 @@ function expandKeywords(words, clusters) {
|
|
|
9174
8887
|
for (const w of words) {
|
|
9175
8888
|
const synonyms = CONCEPT_SYNONYMS[w];
|
|
9176
8889
|
if (synonyms) {
|
|
9177
|
-
for (const syn of synonyms)
|
|
8890
|
+
for (const syn of synonyms) {
|
|
8891
|
+
if (!exact.has(syn)) expanded.add(syn);
|
|
8892
|
+
}
|
|
9178
8893
|
}
|
|
9179
8894
|
}
|
|
9180
8895
|
if (clusters) {
|
|
9181
8896
|
for (const w of words) {
|
|
9182
8897
|
for (const cluster of clusters) {
|
|
9183
8898
|
if (cluster.terms.includes(w) || cluster.terms.some((t) => stem(t) === stem(w))) {
|
|
9184
|
-
for (const t of cluster.terms)
|
|
8899
|
+
for (const t of cluster.terms) {
|
|
8900
|
+
if (!exact.has(t)) expanded.add(t);
|
|
8901
|
+
}
|
|
9185
8902
|
for (const f of cluster.files) clusterFiles.add(f);
|
|
9186
8903
|
}
|
|
9187
8904
|
}
|
|
9188
8905
|
}
|
|
9189
8906
|
}
|
|
9190
8907
|
for (const e of exact) stemmed.delete(e);
|
|
9191
|
-
|
|
8908
|
+
for (const e of expanded) stemmed.delete(e);
|
|
8909
|
+
return { exact: [...exact], expanded, stemmed: [...stemmed], clusterFiles };
|
|
9192
8910
|
}
|
|
9193
8911
|
function isNoisePath(path) {
|
|
9194
8912
|
const lower = path.toLowerCase();
|
|
@@ -9212,10 +8930,10 @@ function buildTermWeights(words, metadata) {
|
|
|
9212
8930
|
}
|
|
9213
8931
|
return weights;
|
|
9214
8932
|
}
|
|
9215
|
-
function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, maxImportDepth) {
|
|
8933
|
+
function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, maxImportDepth, parsed) {
|
|
9216
8934
|
const rawWords = query.toLowerCase().replace(/[^a-z0-9\s_-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS2.has(w));
|
|
9217
|
-
const { exact, stemmed, clusterFiles } = expandKeywords(rawWords, vocabClusters ?? null);
|
|
9218
|
-
const allTerms = [...exact, ...stemmed];
|
|
8935
|
+
const { exact, expanded, stemmed, clusterFiles } = expandKeywords(rawWords, vocabClusters ?? null);
|
|
8936
|
+
const allTerms = [...exact, ...expanded, ...stemmed];
|
|
9219
8937
|
const termWeights = buildTermWeights(allTerms, metadata);
|
|
9220
8938
|
const exportTokensMap = /* @__PURE__ */ new Map();
|
|
9221
8939
|
for (const f of metadata) {
|
|
@@ -9242,6 +8960,32 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9242
8960
|
const dirTokens = pathSegments.slice(-4, -1).flatMap((s) => s.replace(/\./g, " ").split(/\s+/)).filter((t) => t.length >= 2);
|
|
9243
8961
|
const pathTokenSet = /* @__PURE__ */ new Set([...fileTokens, ...dirTokens]);
|
|
9244
8962
|
const expTokens = exportTokensMap.get(f.path);
|
|
8963
|
+
if (parsed) {
|
|
8964
|
+
if (parsed.kind === "stack_trace") {
|
|
8965
|
+
for (const frame of parsed.frames) {
|
|
8966
|
+
if (f.path.endsWith(frame.path) || frame.path.endsWith(f.path)) {
|
|
8967
|
+
score += 50;
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
} else if (parsed.kind === "symbol" && parsed.symbol) {
|
|
8971
|
+
const sym = parsed.symbol;
|
|
8972
|
+
if ((f.exports ?? []).includes(sym)) score += 100;
|
|
8973
|
+
else if ((f.internals ?? []).includes(sym)) score += 80;
|
|
8974
|
+
} else if (parsed.kind === "error_message") {
|
|
8975
|
+
if (parsed.errorToken) {
|
|
8976
|
+
const token = parsed.errorToken.toLowerCase();
|
|
8977
|
+
if (exportsLower.includes(token) || internalsLower.includes(token) || pathLower.includes(token)) {
|
|
8978
|
+
score += 30;
|
|
8979
|
+
}
|
|
8980
|
+
}
|
|
8981
|
+
const rawError = parsed.raw.replace(/^(Error|TypeError|ReferenceError|SyntaxError|RangeError|FATAL|WARN|ERR):\s*/i, "").toLowerCase().trim();
|
|
8982
|
+
if (rawError.length >= 5) {
|
|
8983
|
+
if (docLower.includes(rawError) || sectionsLower.includes(rawError) || internalsLower.includes(rawError) || purposeLower.includes(rawError)) {
|
|
8984
|
+
score += 40;
|
|
8985
|
+
}
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
9245
8989
|
for (const w of exact) {
|
|
9246
8990
|
const weight = termWeights.get(w) ?? 1;
|
|
9247
8991
|
let termScore = 0;
|
|
@@ -9255,6 +8999,16 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9255
8999
|
if (termScore > 0) matchedTerms++;
|
|
9256
9000
|
score += termScore;
|
|
9257
9001
|
}
|
|
9002
|
+
for (const w of expanded) {
|
|
9003
|
+
const weight = (termWeights.get(w) ?? 1) * 0.5;
|
|
9004
|
+
let termScore = 0;
|
|
9005
|
+
if (expTokens?.has(w)) termScore += 2 * weight;
|
|
9006
|
+
else if (exportsLower.includes(w)) termScore += 1.5 * weight;
|
|
9007
|
+
if (docLower.includes(w) || purposeLower.includes(w)) termScore += 1 * weight;
|
|
9008
|
+
if (internalsLower.includes(w)) termScore += 1 * weight;
|
|
9009
|
+
if (termScore > 0) matchedTerms++;
|
|
9010
|
+
score += termScore;
|
|
9011
|
+
}
|
|
9258
9012
|
for (const w of stemmed) {
|
|
9259
9013
|
const weight = termWeights.get(w) ?? 1;
|
|
9260
9014
|
let termScore = 0;
|
|
@@ -9270,7 +9024,7 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9270
9024
|
score *= 1 + coverage * coverage;
|
|
9271
9025
|
}
|
|
9272
9026
|
if (score > 0 && isNoisePath(f.path)) {
|
|
9273
|
-
score
|
|
9027
|
+
score = 0;
|
|
9274
9028
|
}
|
|
9275
9029
|
const exportCount = (f.exports ?? []).length;
|
|
9276
9030
|
if (exportCount > 5 && score > 0) {
|
|
@@ -9304,14 +9058,27 @@ function interpretQuery(query, metadata, fileStats2, vocabClusters, indegrees, m
|
|
|
9304
9058
|
deduped.push(f);
|
|
9305
9059
|
if (deduped.length >= fileCap) break;
|
|
9306
9060
|
}
|
|
9061
|
+
const scores = /* @__PURE__ */ new Map();
|
|
9062
|
+
for (const f of deduped) scores.set(f.path, f.score);
|
|
9307
9063
|
return {
|
|
9308
9064
|
fileCap,
|
|
9065
|
+
scores,
|
|
9309
9066
|
files: deduped.map((f) => {
|
|
9310
|
-
const reason = f.docstring
|
|
9067
|
+
const reason = truncateAtWord(f.docstring, 80) || formatExports(f.exports) || f.path.split("/").pop() || "";
|
|
9311
9068
|
return { path: f.path, reason };
|
|
9312
9069
|
})
|
|
9313
9070
|
};
|
|
9314
9071
|
}
|
|
9072
|
+
function truncateAtWord(s, max) {
|
|
9073
|
+
if (!s) return "";
|
|
9074
|
+
if (s.length <= max) return s;
|
|
9075
|
+
const cut = s.lastIndexOf(" ", max);
|
|
9076
|
+
return cut > max * 0.5 ? `${s.slice(0, cut)}...` : `${s.slice(0, max)}...`;
|
|
9077
|
+
}
|
|
9078
|
+
function formatExports(exports) {
|
|
9079
|
+
if (!exports?.length) return "";
|
|
9080
|
+
return `exports: ${exports.slice(0, 4).join(", ")}`;
|
|
9081
|
+
}
|
|
9315
9082
|
function siblingKey(path) {
|
|
9316
9083
|
const lastSlash = path.lastIndexOf("/");
|
|
9317
9084
|
const dir = lastSlash >= 0 ? path.substring(0, lastSlash) : "";
|
|
@@ -9320,6 +9087,99 @@ function siblingKey(path) {
|
|
|
9320
9087
|
const tokens = stem2.split(/[-.]/).slice(0, 3).join("-");
|
|
9321
9088
|
return `${dir}/${tokens}`;
|
|
9322
9089
|
}
|
|
9090
|
+
function findSymbolLine(symbol, chunkRows) {
|
|
9091
|
+
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9092
|
+
const re = new RegExp(`\\b${escaped}\\b`);
|
|
9093
|
+
for (const chunk of chunkRows) {
|
|
9094
|
+
const lines = chunk.content.split("\n");
|
|
9095
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9096
|
+
if (re.test(lines[i]) && SYMBOL_DEF_RE.test(lines[i])) {
|
|
9097
|
+
return chunk.start_line + i;
|
|
9098
|
+
}
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
9101
|
+
for (const chunk of chunkRows) {
|
|
9102
|
+
const lines = chunk.content.split("\n");
|
|
9103
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9104
|
+
if (re.test(lines[i])) {
|
|
9105
|
+
return chunk.start_line + i;
|
|
9106
|
+
}
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
return null;
|
|
9110
|
+
}
|
|
9111
|
+
function pickBestExport(exports, queryTokens) {
|
|
9112
|
+
if (exports.length <= 1 || !queryTokens.length) return exports[0];
|
|
9113
|
+
let best = exports[0];
|
|
9114
|
+
let bestScore = 0;
|
|
9115
|
+
for (const exp of exports) {
|
|
9116
|
+
const lower = exp.toLowerCase();
|
|
9117
|
+
const expTokens = lower.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[\s_-]+/);
|
|
9118
|
+
let score = 0;
|
|
9119
|
+
for (const qt of queryTokens) {
|
|
9120
|
+
if (lower.includes(qt)) score += 2;
|
|
9121
|
+
else if (expTokens.some((t) => t.includes(qt) || qt.includes(t))) score++;
|
|
9122
|
+
}
|
|
9123
|
+
if (score > bestScore) {
|
|
9124
|
+
bestScore = score;
|
|
9125
|
+
best = exp;
|
|
9126
|
+
}
|
|
9127
|
+
}
|
|
9128
|
+
return best;
|
|
9129
|
+
}
|
|
9130
|
+
function resolveSnippets(db, repoId, files, metadata, parsed, limit = 5) {
|
|
9131
|
+
const result = /* @__PURE__ */ new Map();
|
|
9132
|
+
const topFiles = files.slice(0, limit);
|
|
9133
|
+
const topPaths = topFiles.map((f) => f.path);
|
|
9134
|
+
const allChunks = chunkQueries.getByRepoPaths(db, repoId, topPaths);
|
|
9135
|
+
const chunksByPath = /* @__PURE__ */ new Map();
|
|
9136
|
+
for (const c of allChunks) {
|
|
9137
|
+
const arr = chunksByPath.get(c.path) ?? [];
|
|
9138
|
+
arr.push({ start_line: c.start_line, content: c.content });
|
|
9139
|
+
chunksByPath.set(c.path, arr);
|
|
9140
|
+
}
|
|
9141
|
+
const metaMap = /* @__PURE__ */ new Map();
|
|
9142
|
+
for (const m of metadata) metaMap.set(m.path, m);
|
|
9143
|
+
const frameByPath = /* @__PURE__ */ new Map();
|
|
9144
|
+
for (const frame of parsed.frames) {
|
|
9145
|
+
for (const p of topPaths) {
|
|
9146
|
+
if (p.endsWith(frame.path) || frame.path.endsWith(p)) {
|
|
9147
|
+
frameByPath.set(p, frame.line);
|
|
9148
|
+
}
|
|
9149
|
+
}
|
|
9150
|
+
}
|
|
9151
|
+
for (const f of topFiles) {
|
|
9152
|
+
const chunks2 = chunksByPath.get(f.path) ?? [];
|
|
9153
|
+
const meta = metaMap.get(f.path);
|
|
9154
|
+
const frameLine = frameByPath.get(f.path);
|
|
9155
|
+
if (frameLine !== void 0) {
|
|
9156
|
+
result.set(f.path, { path: f.path, symbol: null, line: frameLine, matchKind: "frame" });
|
|
9157
|
+
continue;
|
|
9158
|
+
}
|
|
9159
|
+
if (parsed.symbol) {
|
|
9160
|
+
const exports = meta?.exports ?? [];
|
|
9161
|
+
const internals = meta?.internals ?? [];
|
|
9162
|
+
if (exports.includes(parsed.symbol)) {
|
|
9163
|
+
const line = findSymbolLine(parsed.symbol, chunks2);
|
|
9164
|
+
result.set(f.path, { path: f.path, symbol: parsed.symbol, line, matchKind: "export" });
|
|
9165
|
+
continue;
|
|
9166
|
+
}
|
|
9167
|
+
if (internals.includes(parsed.symbol)) {
|
|
9168
|
+
const line = findSymbolLine(parsed.symbol, chunks2);
|
|
9169
|
+
result.set(f.path, { path: f.path, symbol: parsed.symbol, line, matchKind: "internal" });
|
|
9170
|
+
continue;
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
if (meta?.exports?.length) {
|
|
9174
|
+
const bestExport = pickBestExport(meta.exports, parsed.naturalTokens);
|
|
9175
|
+
const line = findSymbolLine(bestExport, chunks2);
|
|
9176
|
+
result.set(f.path, { path: f.path, symbol: bestExport, line, matchKind: "export" });
|
|
9177
|
+
continue;
|
|
9178
|
+
}
|
|
9179
|
+
result.set(f.path, { path: f.path, symbol: null, line: null, matchKind: null });
|
|
9180
|
+
}
|
|
9181
|
+
return result;
|
|
9182
|
+
}
|
|
9323
9183
|
function loadFileMetadata(db, repoId) {
|
|
9324
9184
|
return metadataQueries.getByRepo(db, repoId);
|
|
9325
9185
|
}
|
|
@@ -9382,12 +9242,43 @@ function getIndegrees(db, repoId) {
|
|
|
9382
9242
|
function getCochangePartners(db, repoId, paths, minCount = 5, limit = 10) {
|
|
9383
9243
|
return cochangeQueries.getPartners(db, repoId, paths, minCount, limit);
|
|
9384
9244
|
}
|
|
9245
|
+
function discoverTestFiles(reverseImports, cochanges, metadataPaths, sourcePaths) {
|
|
9246
|
+
const result = /* @__PURE__ */ new Map();
|
|
9247
|
+
for (const src of sourcePaths) {
|
|
9248
|
+
const tests = /* @__PURE__ */ new Set();
|
|
9249
|
+
const importers = reverseImports.get(src) ?? [];
|
|
9250
|
+
for (const imp of importers) {
|
|
9251
|
+
if (TEST_PATTERN.test(imp)) tests.add(imp);
|
|
9252
|
+
}
|
|
9253
|
+
for (const cc of cochanges) {
|
|
9254
|
+
const partner = cc.path === src ? cc.partner : cc.partner === src ? cc.path : null;
|
|
9255
|
+
if (partner && TEST_PATTERN.test(partner)) tests.add(partner);
|
|
9256
|
+
}
|
|
9257
|
+
const ext = src.slice(src.lastIndexOf("."));
|
|
9258
|
+
const base = src.slice(0, src.lastIndexOf("."));
|
|
9259
|
+
const dir = src.slice(0, src.lastIndexOf("/"));
|
|
9260
|
+
const name = src.slice(src.lastIndexOf("/") + 1, src.lastIndexOf("."));
|
|
9261
|
+
const candidates = [
|
|
9262
|
+
`${base}.test${ext}`,
|
|
9263
|
+
`${base}.spec${ext}`,
|
|
9264
|
+
`${dir}/__tests__/${name}${ext}`,
|
|
9265
|
+
`${dir}/__tests__/${name}.test${ext}`
|
|
9266
|
+
];
|
|
9267
|
+
for (const c of candidates) {
|
|
9268
|
+
if (metadataPaths.has(c)) tests.add(c);
|
|
9269
|
+
}
|
|
9270
|
+
if (tests.size > 0) {
|
|
9271
|
+
result.set(src, [...tests].slice(0, 3));
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
return result;
|
|
9275
|
+
}
|
|
9385
9276
|
function loadVocabClusters(db, repoId) {
|
|
9386
9277
|
const repo = repoQueries.getById(db, repoId);
|
|
9387
9278
|
if (!repo?.vocab_clusters) return null;
|
|
9388
9279
|
return jsonParse(repo.vocab_clusters, null);
|
|
9389
9280
|
}
|
|
9390
|
-
function
|
|
9281
|
+
function cosine(a, b) {
|
|
9391
9282
|
let dot = 0, na = 0, nb = 0;
|
|
9392
9283
|
for (let i = 0; i < a.length; i++) {
|
|
9393
9284
|
dot += a[i] * b[i];
|
|
@@ -9410,7 +9301,7 @@ async function vectorSearch(db, repoId, query, limit, caps, codeOnly = true) {
|
|
|
9410
9301
|
end_line: c.end_line,
|
|
9411
9302
|
content: c.content,
|
|
9412
9303
|
language: c.language,
|
|
9413
|
-
score:
|
|
9304
|
+
score: cosine(queryArr, c.embedding)
|
|
9414
9305
|
})).filter((c) => !codeOnly || !isDocFile(c.path)).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
9415
9306
|
return scored;
|
|
9416
9307
|
}
|
|
@@ -9442,14 +9333,21 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
|
|
|
9442
9333
|
const repo = repoQueries.getById(db, repoId);
|
|
9443
9334
|
const commit = repo?.last_indexed_commit ?? null;
|
|
9444
9335
|
const useEmb = options?.useEmbeddings !== false;
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9336
|
+
if (!options?.skipCache) {
|
|
9337
|
+
const key = cacheKey(repoId, goal, commit, useEmb);
|
|
9338
|
+
const cached2 = cacheGet(key);
|
|
9339
|
+
if (cached2) {
|
|
9340
|
+
trace?.add("cache", 0, "HIT");
|
|
9341
|
+
track(db, "context", {
|
|
9342
|
+
duration_ms: Date.now() - start,
|
|
9343
|
+
result_count: cached2.stats.files_in_context,
|
|
9344
|
+
cache_hit: true
|
|
9345
|
+
});
|
|
9346
|
+
return { ...cached2, stats: { ...cached2.stats, cached: true, duration_ms: Date.now() - start } };
|
|
9347
|
+
}
|
|
9451
9348
|
}
|
|
9452
9349
|
const embAvailable = useEmb && (await Promise.resolve().then(() => (init_queries(), queries_exports))).chunkQueries.hasEmbeddings(db, repoId);
|
|
9350
|
+
const parsed = parseQuery(goal);
|
|
9453
9351
|
trace?.step("loadStructural");
|
|
9454
9352
|
const metadata = loadFileMetadata(db, repoId);
|
|
9455
9353
|
const allStats = getAllFileStats(db, repoId);
|
|
@@ -9465,11 +9363,44 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
|
|
|
9465
9363
|
statsForInterpreter.set(path, { commit_count: stat32.commit_count, recent_count: stat32.recent_count });
|
|
9466
9364
|
}
|
|
9467
9365
|
trace?.step("interpretQuery");
|
|
9468
|
-
const interpreted = interpretQuery(
|
|
9469
|
-
|
|
9366
|
+
const interpreted = interpretQuery(
|
|
9367
|
+
goal,
|
|
9368
|
+
metadata,
|
|
9369
|
+
statsForInterpreter,
|
|
9370
|
+
vocabClusters,
|
|
9371
|
+
indegreeMap,
|
|
9372
|
+
maxImportDepth,
|
|
9373
|
+
parsed
|
|
9374
|
+
);
|
|
9375
|
+
trace?.end("interpretQuery", `${interpreted.files.length} files (${parsed.kind})`);
|
|
9376
|
+
if (parsed.kind === "error_message") {
|
|
9377
|
+
trace?.step("errorContentSearch");
|
|
9378
|
+
const rawError = parsed.raw.replace(/^(Error|TypeError|ReferenceError|SyntaxError|RangeError|FATAL|WARN|ERR):\s*/i, "").trim();
|
|
9379
|
+
if (rawError.length >= 5) {
|
|
9380
|
+
const contentHits = chunkQueries.searchContent(db, repoId, rawError).filter((p) => !isNoisePath(p));
|
|
9381
|
+
const scored = contentHits.filter((p) => (interpreted.scores.get(p) ?? 0) > 0);
|
|
9382
|
+
const unscored = contentHits.filter((p) => (interpreted.scores.get(p) ?? 0) === 0);
|
|
9383
|
+
const capped = [...scored.slice(0, 3), ...unscored.slice(0, Math.max(0, 3 - scored.length))];
|
|
9384
|
+
const existingSet = new Set(interpreted.files.map((f) => f.path));
|
|
9385
|
+
for (const p of capped) {
|
|
9386
|
+
const currentScore = interpreted.scores.get(p) ?? 0;
|
|
9387
|
+
interpreted.scores.set(p, currentScore + 40);
|
|
9388
|
+
if (!existingSet.has(p)) {
|
|
9389
|
+
const meta = metadata.find((m) => m.path === p);
|
|
9390
|
+
interpreted.files.push({
|
|
9391
|
+
path: p,
|
|
9392
|
+
reason: meta?.docstring?.slice(0, 80) || `contains "${rawError.slice(0, 30)}"`
|
|
9393
|
+
});
|
|
9394
|
+
existingSet.add(p);
|
|
9395
|
+
}
|
|
9396
|
+
}
|
|
9397
|
+
interpreted.files.sort((a, b) => (interpreted.scores.get(b.path) ?? 0) - (interpreted.scores.get(a.path) ?? 0));
|
|
9398
|
+
}
|
|
9399
|
+
trace?.end("errorContentSearch");
|
|
9400
|
+
}
|
|
9470
9401
|
trace?.step("cochangePromotion");
|
|
9471
9402
|
const topForCochange = interpreted.files.slice(0, 5).map((f) => f.path);
|
|
9472
|
-
const cochangePartners = getCochangePartners(db, repoId, topForCochange,
|
|
9403
|
+
const cochangePartners = getCochangePartners(db, repoId, topForCochange, 5, 15);
|
|
9473
9404
|
const existingPathSet = new Set(interpreted.files.map((f) => f.path));
|
|
9474
9405
|
let promoted = 0;
|
|
9475
9406
|
for (const cp of cochangePartners) {
|
|
@@ -9549,6 +9480,20 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
|
|
|
9549
9480
|
}
|
|
9550
9481
|
}
|
|
9551
9482
|
trace?.end("structuralEnrichment");
|
|
9483
|
+
trace?.step("resolveSnippets");
|
|
9484
|
+
const snippets = resolveSnippets(db, repoId, interpreted.files, metadata, parsed, 5);
|
|
9485
|
+
const metadataPaths = new Set(metadata.map((m) => m.path));
|
|
9486
|
+
const testFiles = discoverTestFiles(
|
|
9487
|
+
reverseImports,
|
|
9488
|
+
cochanges,
|
|
9489
|
+
metadataPaths,
|
|
9490
|
+
interpreted.files.slice(0, 5).map((f) => f.path)
|
|
9491
|
+
);
|
|
9492
|
+
trace?.end("resolveSnippets", `${snippets.size} snippets, ${testFiles.size} test maps`);
|
|
9493
|
+
trace?.step("sliceContext");
|
|
9494
|
+
const { sliceContext: sliceCtx } = await Promise.resolve().then(() => (init_slicer(), slicer_exports));
|
|
9495
|
+
const slices = sliceCtx(db, repoId, snippets, parsed.kind);
|
|
9496
|
+
trace?.end("sliceContext", `${slices.size} slices`);
|
|
9552
9497
|
trace?.step("formatContextPack");
|
|
9553
9498
|
const data = {
|
|
9554
9499
|
goal,
|
|
@@ -9558,7 +9503,12 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
|
|
|
9558
9503
|
forwardImports,
|
|
9559
9504
|
hop2Deps,
|
|
9560
9505
|
cochanges,
|
|
9561
|
-
fileStats: allStats
|
|
9506
|
+
fileStats: allStats,
|
|
9507
|
+
scores: interpreted.scores,
|
|
9508
|
+
snippets,
|
|
9509
|
+
slices,
|
|
9510
|
+
testFiles,
|
|
9511
|
+
queryKind: parsed.kind
|
|
9562
9512
|
};
|
|
9563
9513
|
const contextPack = formatContextPack(data);
|
|
9564
9514
|
trace?.end("formatContextPack");
|
|
@@ -9571,19 +9521,742 @@ async function buildContext(db, repoId, goal, caps, trace, options) {
|
|
|
9571
9521
|
cached: false
|
|
9572
9522
|
}
|
|
9573
9523
|
};
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9524
|
+
if (options?.includeRankedFiles) {
|
|
9525
|
+
response.ranked_files = interpreted.files.map((f) => ({
|
|
9526
|
+
path: f.path,
|
|
9527
|
+
reason: f.reason,
|
|
9528
|
+
score: interpreted.scores.get(f.path) ?? 0
|
|
9529
|
+
}));
|
|
9530
|
+
response.query_kind = parsed.kind;
|
|
9531
|
+
}
|
|
9532
|
+
if (!options?.skipCache) {
|
|
9533
|
+
const key = cacheKey(repoId, goal, commit, useEmb);
|
|
9534
|
+
cacheSet(key, response);
|
|
9535
|
+
}
|
|
9536
|
+
track(db, "context", {
|
|
9537
|
+
duration_ms: response.stats.duration_ms,
|
|
9538
|
+
result_count: response.stats.files_in_context,
|
|
9539
|
+
cache_hit: false
|
|
9540
|
+
});
|
|
9541
|
+
return response;
|
|
9542
|
+
} catch {
|
|
9543
|
+
return {
|
|
9544
|
+
context_pack: `# ${goal}
|
|
9545
|
+
|
|
9546
|
+
Context generation failed.`,
|
|
9547
|
+
stats: { files_in_context: 0, index_fresh: false, duration_ms: Date.now() - start, cached: false }
|
|
9548
|
+
};
|
|
9549
|
+
}
|
|
9550
|
+
}
|
|
9551
|
+
function resolveDbPath(customPath) {
|
|
9552
|
+
if (customPath) return customPath;
|
|
9553
|
+
const dir = join3(homedir(), ".lens");
|
|
9554
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
9555
|
+
return join3(dir, "data.db");
|
|
9556
|
+
}
|
|
9557
|
+
function openDb(customPath) {
|
|
9558
|
+
if (_db) return _db;
|
|
9559
|
+
const dbPath = resolveDbPath(customPath);
|
|
9560
|
+
const sqlite = new Database(dbPath);
|
|
9561
|
+
sqlite.pragma("journal_mode = WAL");
|
|
9562
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
9563
|
+
sqlite.pragma("foreign_keys = ON");
|
|
9564
|
+
const db = drizzle(sqlite, { schema: schema_exports });
|
|
9565
|
+
sqlite.exec(createTablesSql());
|
|
9566
|
+
const cols = new Set(sqlite.pragma("table_info(file_metadata)").map((c) => c.name));
|
|
9567
|
+
if (!cols.has("sections")) sqlite.exec("ALTER TABLE file_metadata ADD COLUMN sections TEXT DEFAULT '[]'");
|
|
9568
|
+
if (!cols.has("internals")) sqlite.exec("ALTER TABLE file_metadata ADD COLUMN internals TEXT DEFAULT '[]'");
|
|
9569
|
+
const repoCols = new Set(sqlite.pragma("table_info(repos)").map((c) => c.name));
|
|
9570
|
+
if (!repoCols.has("enable_embeddings"))
|
|
9571
|
+
sqlite.exec("ALTER TABLE repos ADD COLUMN enable_embeddings INTEGER NOT NULL DEFAULT 1");
|
|
9572
|
+
if (!repoCols.has("enable_summaries"))
|
|
9573
|
+
sqlite.exec("ALTER TABLE repos ADD COLUMN enable_summaries INTEGER NOT NULL DEFAULT 1");
|
|
9574
|
+
if (!repoCols.has("enable_vocab_clusters"))
|
|
9575
|
+
sqlite.exec("ALTER TABLE repos ADD COLUMN enable_vocab_clusters INTEGER NOT NULL DEFAULT 1");
|
|
9576
|
+
if (!repoCols.has("last_vocab_cluster_commit"))
|
|
9577
|
+
sqlite.exec("ALTER TABLE repos ADD COLUMN last_vocab_cluster_commit TEXT");
|
|
9578
|
+
const logCols = new Set(sqlite.pragma("table_info(request_logs)").map((c) => c.name));
|
|
9579
|
+
if (!logCols.has("response_body")) sqlite.exec("ALTER TABLE request_logs ADD COLUMN response_body TEXT");
|
|
9580
|
+
if (!logCols.has("trace")) sqlite.exec("ALTER TABLE request_logs ADD COLUMN trace TEXT");
|
|
9581
|
+
_db = db;
|
|
9582
|
+
_raw = sqlite;
|
|
9583
|
+
return db;
|
|
9584
|
+
}
|
|
9585
|
+
function getDb() {
|
|
9586
|
+
if (!_db) throw new Error("Database not initialized. Call openDb() first.");
|
|
9587
|
+
return _db;
|
|
9588
|
+
}
|
|
9589
|
+
function getRawDb() {
|
|
9590
|
+
if (!_raw) throw new Error("Database not initialized. Call openDb() first.");
|
|
9591
|
+
return _raw;
|
|
9592
|
+
}
|
|
9593
|
+
function closeDb() {
|
|
9594
|
+
if (_raw) _raw.close();
|
|
9595
|
+
_db = null;
|
|
9596
|
+
_raw = null;
|
|
9597
|
+
}
|
|
9598
|
+
function createTablesSql() {
|
|
9599
|
+
return `
|
|
9600
|
+
CREATE TABLE IF NOT EXISTS repos (
|
|
9601
|
+
id TEXT PRIMARY KEY,
|
|
9602
|
+
identity_key TEXT UNIQUE NOT NULL,
|
|
9603
|
+
name TEXT NOT NULL,
|
|
9604
|
+
root_path TEXT NOT NULL,
|
|
9605
|
+
remote_url TEXT,
|
|
9606
|
+
last_indexed_commit TEXT,
|
|
9607
|
+
index_status TEXT NOT NULL DEFAULT 'pending',
|
|
9608
|
+
last_indexed_at TEXT,
|
|
9609
|
+
last_git_analysis_commit TEXT,
|
|
9610
|
+
max_import_depth INTEGER DEFAULT 0,
|
|
9611
|
+
vocab_clusters TEXT,
|
|
9612
|
+
last_vocab_cluster_commit TEXT,
|
|
9613
|
+
enable_embeddings INTEGER NOT NULL DEFAULT 1,
|
|
9614
|
+
enable_summaries INTEGER NOT NULL DEFAULT 1,
|
|
9615
|
+
enable_vocab_clusters INTEGER NOT NULL DEFAULT 1,
|
|
9616
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9617
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9618
|
+
);
|
|
9619
|
+
CREATE INDEX IF NOT EXISTS idx_repos_identity ON repos(identity_key);
|
|
9620
|
+
|
|
9621
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
9622
|
+
id TEXT PRIMARY KEY,
|
|
9623
|
+
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
9624
|
+
path TEXT NOT NULL,
|
|
9625
|
+
chunk_index INTEGER NOT NULL,
|
|
9626
|
+
start_line INTEGER NOT NULL,
|
|
9627
|
+
end_line INTEGER NOT NULL,
|
|
9628
|
+
content TEXT NOT NULL,
|
|
9629
|
+
chunk_hash TEXT NOT NULL,
|
|
9630
|
+
last_seen_commit TEXT NOT NULL,
|
|
9631
|
+
language TEXT,
|
|
9632
|
+
embedding BLOB,
|
|
9633
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9634
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9635
|
+
UNIQUE(repo_id, path, chunk_index, chunk_hash)
|
|
9636
|
+
);
|
|
9637
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_repo_path ON chunks(repo_id, path);
|
|
9638
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_repo ON chunks(repo_id);
|
|
9639
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_hash ON chunks(repo_id, chunk_hash);
|
|
9640
|
+
|
|
9641
|
+
CREATE TABLE IF NOT EXISTS file_metadata (
|
|
9642
|
+
id TEXT PRIMARY KEY,
|
|
9643
|
+
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
9644
|
+
path TEXT NOT NULL,
|
|
9645
|
+
language TEXT,
|
|
9646
|
+
exports TEXT DEFAULT '[]',
|
|
9647
|
+
imports TEXT DEFAULT '[]',
|
|
9648
|
+
docstring TEXT DEFAULT '',
|
|
9649
|
+
sections TEXT DEFAULT '[]',
|
|
9650
|
+
internals TEXT DEFAULT '[]',
|
|
9651
|
+
purpose TEXT DEFAULT '',
|
|
9652
|
+
purpose_hash TEXT DEFAULT '',
|
|
9653
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9654
|
+
UNIQUE(repo_id, path)
|
|
9655
|
+
);
|
|
9656
|
+
CREATE INDEX IF NOT EXISTS idx_file_metadata_repo ON file_metadata(repo_id);
|
|
9657
|
+
|
|
9658
|
+
CREATE TABLE IF NOT EXISTS file_imports (
|
|
9659
|
+
id TEXT PRIMARY KEY,
|
|
9660
|
+
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
9661
|
+
source_path TEXT NOT NULL,
|
|
9662
|
+
target_path TEXT NOT NULL,
|
|
9663
|
+
UNIQUE(repo_id, source_path, target_path)
|
|
9664
|
+
);
|
|
9665
|
+
CREATE INDEX IF NOT EXISTS idx_file_imports_target ON file_imports(repo_id, target_path);
|
|
9666
|
+
|
|
9667
|
+
CREATE TABLE IF NOT EXISTS file_stats (
|
|
9668
|
+
id TEXT PRIMARY KEY,
|
|
9669
|
+
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
9670
|
+
path TEXT NOT NULL,
|
|
9671
|
+
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
9672
|
+
recent_count INTEGER NOT NULL DEFAULT 0,
|
|
9673
|
+
last_modified TEXT,
|
|
9674
|
+
UNIQUE(repo_id, path)
|
|
9675
|
+
);
|
|
9676
|
+
|
|
9677
|
+
CREATE TABLE IF NOT EXISTS file_cochanges (
|
|
9678
|
+
id TEXT PRIMARY KEY,
|
|
9679
|
+
repo_id TEXT NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
9680
|
+
path_a TEXT NOT NULL,
|
|
9681
|
+
path_b TEXT NOT NULL,
|
|
9682
|
+
cochange_count INTEGER NOT NULL DEFAULT 1,
|
|
9683
|
+
UNIQUE(repo_id, path_a, path_b)
|
|
9684
|
+
);
|
|
9685
|
+
CREATE INDEX IF NOT EXISTS idx_cochanges_lookup ON file_cochanges(repo_id, path_a);
|
|
9686
|
+
|
|
9687
|
+
CREATE TABLE IF NOT EXISTS usage_counters (
|
|
9688
|
+
id TEXT PRIMARY KEY,
|
|
9689
|
+
date TEXT NOT NULL UNIQUE,
|
|
9690
|
+
context_queries INTEGER NOT NULL DEFAULT 0,
|
|
9691
|
+
embedding_requests INTEGER NOT NULL DEFAULT 0,
|
|
9692
|
+
embedding_chunks INTEGER NOT NULL DEFAULT 0,
|
|
9693
|
+
purpose_requests INTEGER NOT NULL DEFAULT 0,
|
|
9694
|
+
repos_indexed INTEGER NOT NULL DEFAULT 0,
|
|
9695
|
+
synced_at TEXT,
|
|
9696
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9697
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9698
|
+
);
|
|
9699
|
+
CREATE INDEX IF NOT EXISTS idx_usage_counters_date ON usage_counters(date);
|
|
9700
|
+
|
|
9701
|
+
CREATE TABLE IF NOT EXISTS request_logs (
|
|
9702
|
+
id TEXT PRIMARY KEY,
|
|
9703
|
+
method TEXT NOT NULL,
|
|
9704
|
+
path TEXT NOT NULL,
|
|
9705
|
+
status INTEGER NOT NULL,
|
|
9706
|
+
duration_ms INTEGER NOT NULL,
|
|
9707
|
+
source TEXT NOT NULL DEFAULT 'api',
|
|
9708
|
+
request_body TEXT,
|
|
9709
|
+
response_size INTEGER,
|
|
9710
|
+
response_body TEXT,
|
|
9711
|
+
trace TEXT,
|
|
9712
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9713
|
+
);
|
|
9714
|
+
CREATE INDEX IF NOT EXISTS idx_request_logs_created ON request_logs(created_at);
|
|
9715
|
+
CREATE INDEX IF NOT EXISTS idx_request_logs_source ON request_logs(source);
|
|
9716
|
+
|
|
9717
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
9718
|
+
key TEXT PRIMARY KEY,
|
|
9719
|
+
value TEXT NOT NULL,
|
|
9720
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9721
|
+
);
|
|
9722
|
+
|
|
9723
|
+
CREATE TABLE IF NOT EXISTS telemetry_events (
|
|
9724
|
+
id TEXT PRIMARY KEY,
|
|
9725
|
+
event_type TEXT NOT NULL,
|
|
9726
|
+
event_data TEXT,
|
|
9727
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
9728
|
+
synced_at TEXT
|
|
9729
|
+
);
|
|
9730
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_events_type ON telemetry_events(event_type);
|
|
9731
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_events_synced ON telemetry_events(synced_at);
|
|
9732
|
+
`;
|
|
9733
|
+
}
|
|
9734
|
+
function hitAtN(expected, returned, n) {
|
|
9735
|
+
const top = returned.slice(0, n);
|
|
9736
|
+
return expected.some((e) => top.includes(e));
|
|
9737
|
+
}
|
|
9738
|
+
function entryHitAtN(entry, returned, n) {
|
|
9739
|
+
if (!entry) return false;
|
|
9740
|
+
return returned.slice(0, n).includes(entry);
|
|
9741
|
+
}
|
|
9742
|
+
function recallAtK(expected, returned, k) {
|
|
9743
|
+
if (expected.length === 0) return 1;
|
|
9744
|
+
const top = new Set(returned.slice(0, k));
|
|
9745
|
+
const hits = expected.filter((e) => top.has(e)).length;
|
|
9746
|
+
return hits / expected.length;
|
|
9747
|
+
}
|
|
9748
|
+
function computeMetrics(results) {
|
|
9749
|
+
const total = results.length;
|
|
9750
|
+
if (total === 0) {
|
|
9751
|
+
return {
|
|
9752
|
+
total: 0,
|
|
9753
|
+
hit_at_1: 0,
|
|
9754
|
+
hit_at_3: 0,
|
|
9755
|
+
entry_hit_at_1: 0,
|
|
9756
|
+
entry_hit_at_3: 0,
|
|
9757
|
+
avg_recall_at_5: 0,
|
|
9758
|
+
avg_duration_ms: 0,
|
|
9759
|
+
by_kind: [],
|
|
9760
|
+
results
|
|
9761
|
+
};
|
|
9762
|
+
}
|
|
9763
|
+
const hit1 = results.filter((r) => r.hit_at_1).length;
|
|
9764
|
+
const hit3 = results.filter((r) => r.hit_at_3).length;
|
|
9765
|
+
const entryHit1 = results.filter((r) => r.entry_hit_at_1).length;
|
|
9766
|
+
const entryHit3 = results.filter((r) => r.entry_hit_at_3).length;
|
|
9767
|
+
const sumRecall = results.reduce((s, r) => s + r.recall_at_5, 0);
|
|
9768
|
+
const sumDuration = results.reduce((s, r) => s + r.duration_ms, 0);
|
|
9769
|
+
const kinds = [...new Set(results.map((r) => r.kind))];
|
|
9770
|
+
const by_kind = kinds.map((kind) => {
|
|
9771
|
+
const group = results.filter((r) => r.kind === kind);
|
|
9772
|
+
const n = group.length;
|
|
9773
|
+
return {
|
|
9774
|
+
kind,
|
|
9775
|
+
count: n,
|
|
9776
|
+
hit_at_1: group.filter((r) => r.hit_at_1).length / n,
|
|
9777
|
+
hit_at_3: group.filter((r) => r.hit_at_3).length / n,
|
|
9778
|
+
entry_hit_at_1: group.filter((r) => r.entry_hit_at_1).length / n,
|
|
9779
|
+
entry_hit_at_3: group.filter((r) => r.entry_hit_at_3).length / n,
|
|
9780
|
+
avg_recall_at_5: group.reduce((s, r) => s + r.recall_at_5, 0) / n,
|
|
9781
|
+
avg_duration_ms: Math.round(group.reduce((s, r) => s + r.duration_ms, 0) / n)
|
|
9782
|
+
};
|
|
9783
|
+
});
|
|
9784
|
+
return {
|
|
9785
|
+
total,
|
|
9786
|
+
hit_at_1: hit1 / total,
|
|
9787
|
+
hit_at_3: hit3 / total,
|
|
9788
|
+
entry_hit_at_1: entryHit1 / total,
|
|
9789
|
+
entry_hit_at_3: entryHit3 / total,
|
|
9790
|
+
avg_recall_at_5: sumRecall / total,
|
|
9791
|
+
avg_duration_ms: Math.round(sumDuration / total),
|
|
9792
|
+
by_kind,
|
|
9793
|
+
results
|
|
9794
|
+
};
|
|
9795
|
+
}
|
|
9796
|
+
async function runEval(db, repoId, options) {
|
|
9797
|
+
let queries = GOLD_DATASET;
|
|
9798
|
+
if (options?.filterKind) {
|
|
9799
|
+
queries = queries.filter((q) => q.kind === options.filterKind);
|
|
9800
|
+
}
|
|
9801
|
+
const results = [];
|
|
9802
|
+
for (const gold of queries) {
|
|
9803
|
+
const start = Date.now();
|
|
9804
|
+
const response = await buildContext(db, repoId, gold.query, void 0, void 0, {
|
|
9805
|
+
useEmbeddings: false,
|
|
9806
|
+
includeRankedFiles: true,
|
|
9807
|
+
skipCache: true
|
|
9808
|
+
});
|
|
9809
|
+
const duration3 = Date.now() - start;
|
|
9810
|
+
const returned = (response.ranked_files ?? []).map((f) => f.path);
|
|
9811
|
+
results.push({
|
|
9812
|
+
id: gold.id,
|
|
9813
|
+
query: gold.query,
|
|
9814
|
+
kind: gold.kind,
|
|
9815
|
+
expected_files: gold.expected_files,
|
|
9816
|
+
expected_entry: gold.expected_entry,
|
|
9817
|
+
returned_files: returned,
|
|
9818
|
+
hit_at_1: hitAtN(gold.expected_files, returned, 1),
|
|
9819
|
+
hit_at_3: hitAtN(gold.expected_files, returned, 3),
|
|
9820
|
+
entry_hit_at_1: entryHitAtN(gold.expected_entry, returned, 1),
|
|
9821
|
+
entry_hit_at_3: entryHitAtN(gold.expected_entry, returned, 3),
|
|
9822
|
+
recall_at_5: recallAtK(gold.expected_files, returned, 5),
|
|
9823
|
+
duration_ms: duration3,
|
|
9824
|
+
file_count: returned.length
|
|
9825
|
+
});
|
|
9826
|
+
}
|
|
9827
|
+
return computeMetrics(results);
|
|
9828
|
+
}
|
|
9829
|
+
function estimateTokens2(text22) {
|
|
9830
|
+
return Math.ceil(text22.length / CHARS_PER_TOKEN);
|
|
9831
|
+
}
|
|
9832
|
+
async function ensureEmbedded(db, repoId, caps) {
|
|
9833
|
+
const start = Date.now();
|
|
9834
|
+
if (!caps?.embedTexts) {
|
|
9835
|
+
return { embedded_count: 0, skipped_count: 0, duration_ms: 0 };
|
|
9836
|
+
}
|
|
9837
|
+
let embedded = 0;
|
|
9838
|
+
let apiCalls = 0;
|
|
9839
|
+
while (apiCalls < MAX_API_CALLS) {
|
|
9840
|
+
const remaining = chunkQueries.countUnembedded(db, repoId);
|
|
9841
|
+
if (remaining === 0) break;
|
|
9842
|
+
const rows = chunkQueries.getUnembedded(db, repoId, POOL_SIZE);
|
|
9843
|
+
if (rows.length === 0) break;
|
|
9844
|
+
const validRows = rows.filter((r) => r.content.trim().length > 0);
|
|
9845
|
+
if (validRows.length === 0) {
|
|
9846
|
+
apiCalls++;
|
|
9847
|
+
continue;
|
|
9848
|
+
}
|
|
9849
|
+
let offset = 0;
|
|
9850
|
+
let poolFailed = false;
|
|
9851
|
+
while (offset < validRows.length && apiCalls < MAX_API_CALLS) {
|
|
9852
|
+
const batch = [];
|
|
9853
|
+
let batchTokens = 0;
|
|
9854
|
+
while (offset < validRows.length) {
|
|
9855
|
+
const est = estimateTokens2(validRows[offset].content);
|
|
9856
|
+
if (batch.length > 0 && batchTokens + est > MAX_BATCH_TOKENS) break;
|
|
9857
|
+
batch.push(validRows[offset]);
|
|
9858
|
+
batchTokens += est;
|
|
9859
|
+
offset++;
|
|
9860
|
+
}
|
|
9861
|
+
try {
|
|
9862
|
+
const texts = batch.map((r) => r.content);
|
|
9863
|
+
const vectors = await caps.embedTexts(texts);
|
|
9864
|
+
for (let i = 0; i < batch.length; i++) {
|
|
9865
|
+
chunkQueries.updateEmbedding(db, batch[i].id, repoId, vectors[i]);
|
|
9866
|
+
}
|
|
9867
|
+
embedded += batch.length;
|
|
9868
|
+
} catch (err) {
|
|
9869
|
+
console.error(
|
|
9870
|
+
`[LENS] Embed batch failed (${batch.length} chunks, ~${batchTokens} tokens):`,
|
|
9871
|
+
err.message
|
|
9872
|
+
);
|
|
9873
|
+
poolFailed = true;
|
|
9874
|
+
break;
|
|
9875
|
+
}
|
|
9876
|
+
apiCalls++;
|
|
9877
|
+
}
|
|
9878
|
+
if (poolFailed) break;
|
|
9879
|
+
}
|
|
9880
|
+
return { embedded_count: embedded, skipped_count: 0, duration_ms: Date.now() - start };
|
|
9881
|
+
}
|
|
9882
|
+
async function enrichPurpose(db, repoId, caps, fullRun = false) {
|
|
9883
|
+
const start = Date.now();
|
|
9884
|
+
if (!caps?.generatePurpose) {
|
|
9885
|
+
return { enriched: 0, skipped: 0, duration_ms: 0 };
|
|
9886
|
+
}
|
|
9887
|
+
let totalEnriched = 0;
|
|
9888
|
+
let totalSkipped = 0;
|
|
9889
|
+
while (true) {
|
|
9890
|
+
const candidates = metadataQueries.getCandidatesForPurpose(db, repoId, PURPOSE_BATCH_LIMIT);
|
|
9891
|
+
if (candidates.length === 0) break;
|
|
9892
|
+
for (let i = 0; i < candidates.length; i += PURPOSE_CONCURRENCY) {
|
|
9893
|
+
const batch = candidates.slice(i, i + PURPOSE_CONCURRENCY);
|
|
9894
|
+
const results = await Promise.allSettled(
|
|
9895
|
+
batch.map(async (row) => {
|
|
9896
|
+
const exports = Array.isArray(row.exports) ? row.exports : jsonParse(row.exports, []);
|
|
9897
|
+
const purpose = await caps.generatePurpose(row.path, row.first_chunk, exports, row.docstring ?? "");
|
|
9898
|
+
return { row, purpose };
|
|
9899
|
+
})
|
|
9900
|
+
);
|
|
9901
|
+
for (let j = 0; j < results.length; j++) {
|
|
9902
|
+
const result = results[j];
|
|
9903
|
+
const row = batch[j];
|
|
9904
|
+
if (result.status === "fulfilled" && result.value.purpose) {
|
|
9905
|
+
metadataQueries.updatePurpose(db, repoId, row.path, result.value.purpose, row.chunk_hash);
|
|
9906
|
+
totalEnriched++;
|
|
9907
|
+
} else {
|
|
9908
|
+
metadataQueries.setPurposeHash(db, repoId, row.path, row.chunk_hash);
|
|
9909
|
+
totalSkipped++;
|
|
9910
|
+
}
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9913
|
+
if (!fullRun) break;
|
|
9914
|
+
}
|
|
9915
|
+
return { enriched: totalEnriched, skipped: totalSkipped, duration_ms: Date.now() - start };
|
|
9916
|
+
}
|
|
9917
|
+
function splitIdentifier(name) {
|
|
9918
|
+
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 && !STOPWORDS3.has(w));
|
|
9919
|
+
}
|
|
9920
|
+
function extractVocab(files) {
|
|
9921
|
+
const termToFiles = /* @__PURE__ */ new Map();
|
|
9922
|
+
for (const f of files) {
|
|
9923
|
+
const pathParts = f.path.split("/");
|
|
9924
|
+
for (const seg of pathParts) {
|
|
9925
|
+
const base = seg.replace(/\.[^.]+$/, "");
|
|
9926
|
+
for (const word of splitIdentifier(base)) {
|
|
9927
|
+
const s = termToFiles.get(word) ?? /* @__PURE__ */ new Set();
|
|
9928
|
+
s.add(f.path);
|
|
9929
|
+
termToFiles.set(word, s);
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
for (const exp of f.exports) {
|
|
9933
|
+
for (const word of splitIdentifier(exp)) {
|
|
9934
|
+
const s = termToFiles.get(word) ?? /* @__PURE__ */ new Set();
|
|
9935
|
+
s.add(f.path);
|
|
9936
|
+
termToFiles.set(word, s);
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
const totalFiles = files.length;
|
|
9941
|
+
const maxDf = Math.max(10, Math.floor(totalFiles * 0.3));
|
|
9942
|
+
const filtered = [];
|
|
9943
|
+
for (const [term, fileSet] of termToFiles) {
|
|
9944
|
+
if (fileSet.size >= 2 && fileSet.size <= maxDf) {
|
|
9945
|
+
filtered.push(term);
|
|
9946
|
+
}
|
|
9947
|
+
}
|
|
9948
|
+
filtered.sort((a, b) => (termToFiles.get(b)?.size ?? 0) - (termToFiles.get(a)?.size ?? 0));
|
|
9949
|
+
const terms = filtered.slice(0, MAX_TERMS);
|
|
9950
|
+
return { terms, termToFiles };
|
|
9951
|
+
}
|
|
9952
|
+
function cosine2(a, b) {
|
|
9953
|
+
let dot = 0, na = 0, nb = 0;
|
|
9954
|
+
for (let i = 0; i < a.length; i++) {
|
|
9955
|
+
dot += a[i] * b[i];
|
|
9956
|
+
na += a[i] * a[i];
|
|
9957
|
+
nb += b[i] * b[i];
|
|
9958
|
+
}
|
|
9959
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
9960
|
+
return denom > 0 ? dot / denom : 0;
|
|
9961
|
+
}
|
|
9962
|
+
function agglomerativeCluster(terms, embeddings) {
|
|
9963
|
+
const n = terms.length;
|
|
9964
|
+
const pairs = [];
|
|
9965
|
+
for (let i = 0; i < n; i++) {
|
|
9966
|
+
for (let j = i + 1; j < n; j++) {
|
|
9967
|
+
const sim = cosine2(embeddings[i], embeddings[j]);
|
|
9968
|
+
if (sim >= SIMILARITY_THRESHOLD) pairs.push({ i, j, sim });
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
pairs.sort((a, b) => b.sim - a.sim);
|
|
9972
|
+
const parent = new Int32Array(n);
|
|
9973
|
+
const size = new Uint16Array(n);
|
|
9974
|
+
for (let i = 0; i < n; i++) {
|
|
9975
|
+
parent[i] = i;
|
|
9976
|
+
size[i] = 1;
|
|
9977
|
+
}
|
|
9978
|
+
function find(x) {
|
|
9979
|
+
while (parent[x] !== x) {
|
|
9980
|
+
parent[x] = parent[parent[x]];
|
|
9981
|
+
x = parent[x];
|
|
9982
|
+
}
|
|
9983
|
+
return x;
|
|
9984
|
+
}
|
|
9985
|
+
for (const { i, j } of pairs) {
|
|
9986
|
+
const ri = find(i), rj = find(j);
|
|
9987
|
+
if (ri === rj) continue;
|
|
9988
|
+
if (size[ri] + size[rj] > MAX_CLUSTER_SIZE) continue;
|
|
9989
|
+
if (size[ri] >= size[rj]) {
|
|
9990
|
+
parent[rj] = ri;
|
|
9991
|
+
size[ri] += size[rj];
|
|
9992
|
+
} else {
|
|
9993
|
+
parent[ri] = rj;
|
|
9994
|
+
size[rj] += size[ri];
|
|
9995
|
+
}
|
|
9996
|
+
}
|
|
9997
|
+
const clusterMap = /* @__PURE__ */ new Map();
|
|
9998
|
+
for (let i = 0; i < n; i++) {
|
|
9999
|
+
const root = find(i);
|
|
10000
|
+
const arr = clusterMap.get(root) ?? [];
|
|
10001
|
+
arr.push(i);
|
|
10002
|
+
clusterMap.set(root, arr);
|
|
10003
|
+
}
|
|
10004
|
+
return [...clusterMap.values()].filter((c) => c.length >= 2);
|
|
10005
|
+
}
|
|
10006
|
+
async function buildVocabClusters(db, repoId, caps, force = false) {
|
|
10007
|
+
if (!caps?.embedTexts) {
|
|
10008
|
+
console.error("[LENS] Vocab clusters: skipped (no embedTexts capability)");
|
|
10009
|
+
return;
|
|
10010
|
+
}
|
|
10011
|
+
const repo = repoQueries.getById(db, repoId);
|
|
10012
|
+
if (!repo) return;
|
|
10013
|
+
if (!force && repo.last_vocab_cluster_commit && repo.last_vocab_cluster_commit === repo.last_indexed_commit) {
|
|
10014
|
+
console.error("[LENS] Vocab clusters: skipped (unchanged since last build)");
|
|
10015
|
+
return;
|
|
10016
|
+
}
|
|
10017
|
+
const rows = metadataQueries.getByRepo(db, repoId).map((r) => ({
|
|
10018
|
+
path: r.path,
|
|
10019
|
+
exports: Array.isArray(r.exports) ? r.exports : jsonParse(r.exports, [])
|
|
10020
|
+
}));
|
|
10021
|
+
if (rows.length === 0) {
|
|
10022
|
+
console.error("[LENS] Vocab clusters: skipped (no metadata rows)");
|
|
10023
|
+
return;
|
|
10024
|
+
}
|
|
10025
|
+
const { terms, termToFiles } = extractVocab(rows);
|
|
10026
|
+
if (terms.length < 4) {
|
|
10027
|
+
console.error(`[LENS] Vocab clusters: skipped (only ${terms.length} terms, need \u22654)`);
|
|
10028
|
+
return;
|
|
10029
|
+
}
|
|
10030
|
+
console.error(
|
|
10031
|
+
`[LENS] Vocab clusters: embedding ${terms.length} terms in ${Math.ceil(terms.length / TERM_BATCH_SIZE)} batches...`
|
|
10032
|
+
);
|
|
10033
|
+
let embeddings;
|
|
10034
|
+
try {
|
|
10035
|
+
const results = [];
|
|
10036
|
+
for (let i = 0; i < terms.length; i += TERM_BATCH_SIZE) {
|
|
10037
|
+
const batch = terms.slice(i, i + TERM_BATCH_SIZE);
|
|
10038
|
+
const vecs = await caps.embedTexts(batch, false);
|
|
10039
|
+
results.push(...vecs);
|
|
10040
|
+
}
|
|
10041
|
+
embeddings = results;
|
|
10042
|
+
} catch (err) {
|
|
10043
|
+
console.error(`[LENS] Vocab clusters: embed failed (${terms.length} terms):`, err.message);
|
|
10044
|
+
return;
|
|
10045
|
+
}
|
|
10046
|
+
const rawClusters = agglomerativeCluster(terms, embeddings);
|
|
10047
|
+
console.error(`[LENS] Vocab clusters: ${rawClusters.length} clusters from ${terms.length} terms`);
|
|
10048
|
+
const clusters = rawClusters.map((indices) => {
|
|
10049
|
+
const clusterTerms = indices.map((i) => terms[i]);
|
|
10050
|
+
const fileSet = /* @__PURE__ */ new Set();
|
|
10051
|
+
for (const t of clusterTerms) {
|
|
10052
|
+
const files = termToFiles.get(t);
|
|
10053
|
+
if (files) for (const f of files) fileSet.add(f);
|
|
10054
|
+
}
|
|
10055
|
+
return { terms: clusterTerms, files: [...fileSet].sort() };
|
|
10056
|
+
}).filter((c) => c.files.length > 0).sort((a, b) => b.files.length - a.files.length).slice(0, MAX_CLUSTERS);
|
|
10057
|
+
repoQueries.updateVocabClusters(db, repoId, clusters, repo.last_indexed_commit ?? void 0);
|
|
10058
|
+
}
|
|
10059
|
+
async function handleFileChange(db, repoId, repoRoot, absPath) {
|
|
10060
|
+
const relPath = relative3(repoRoot, absPath);
|
|
10061
|
+
if (isBinaryExt(relPath)) return;
|
|
10062
|
+
try {
|
|
10063
|
+
const s = await stat22(absPath);
|
|
10064
|
+
if (s.size > MAX_FILE_SIZE) return;
|
|
10065
|
+
} catch {
|
|
10066
|
+
return;
|
|
10067
|
+
}
|
|
10068
|
+
let content;
|
|
10069
|
+
try {
|
|
10070
|
+
content = await readFile2(absPath, "utf-8");
|
|
10071
|
+
} catch {
|
|
10072
|
+
return;
|
|
10073
|
+
}
|
|
10074
|
+
const language = detectLanguage(relPath);
|
|
10075
|
+
const newChunks = chunkFile(content, DEFAULT_CHUNKING_PARAMS);
|
|
10076
|
+
const existing = /* @__PURE__ */ new Map();
|
|
10077
|
+
for (const row of chunkQueries.getByRepoPath(db, repoId, relPath)) {
|
|
10078
|
+
existing.set(`${row.chunk_index}:${row.chunk_hash}`, row.id);
|
|
10079
|
+
}
|
|
10080
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
10081
|
+
for (const chunk of newChunks) {
|
|
10082
|
+
const key = `${chunk.chunk_index}:${chunk.chunk_hash}`;
|
|
10083
|
+
newKeys.add(key);
|
|
10084
|
+
if (existing.has(key)) {
|
|
10085
|
+
chunkQueries.updateLastSeen(db, existing.get(key), repoId, "watcher");
|
|
10086
|
+
} else {
|
|
10087
|
+
chunkQueries.upsert(
|
|
10088
|
+
db,
|
|
10089
|
+
repoId,
|
|
10090
|
+
relPath,
|
|
10091
|
+
chunk.chunk_index,
|
|
10092
|
+
chunk.start_line,
|
|
10093
|
+
chunk.end_line,
|
|
10094
|
+
chunk.content,
|
|
10095
|
+
chunk.chunk_hash,
|
|
10096
|
+
"watcher",
|
|
10097
|
+
language
|
|
10098
|
+
);
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
for (const [key, id] of existing) {
|
|
10102
|
+
if (!newKeys.has(key)) chunkQueries.deleteById(db, id, repoId);
|
|
10103
|
+
}
|
|
10104
|
+
const entry = watchers.get(repoId);
|
|
10105
|
+
if (entry) entry.changedFiles++;
|
|
10106
|
+
}
|
|
10107
|
+
async function handleFileDelete(db, repoId, repoRoot, absPath) {
|
|
10108
|
+
const relPath = relative3(repoRoot, absPath);
|
|
10109
|
+
if (isBinaryExt(relPath)) return;
|
|
10110
|
+
chunkQueries.deleteByRepoPath(db, repoId, relPath);
|
|
10111
|
+
const entry = watchers.get(repoId);
|
|
10112
|
+
if (entry) entry.deletedFiles++;
|
|
10113
|
+
}
|
|
10114
|
+
function debouncedHandler(key, fn) {
|
|
10115
|
+
const existing = debounceTimers.get(key);
|
|
10116
|
+
if (existing) clearTimeout(existing);
|
|
10117
|
+
debounceTimers.set(
|
|
10118
|
+
key,
|
|
10119
|
+
setTimeout(() => {
|
|
10120
|
+
debounceTimers.delete(key);
|
|
10121
|
+
fn().catch(() => {
|
|
10122
|
+
});
|
|
10123
|
+
}, DEBOUNCE_MS)
|
|
10124
|
+
);
|
|
10125
|
+
}
|
|
10126
|
+
function startWatcher(db, repoId, rootPath) {
|
|
10127
|
+
if (watchers.has(repoId)) return { started: false, already_watching: true };
|
|
10128
|
+
try {
|
|
10129
|
+
const fsWatcher = watch(rootPath, {
|
|
10130
|
+
ignored: IGNORED,
|
|
10131
|
+
ignoreInitial: true,
|
|
10132
|
+
awaitWriteFinish: { stabilityThreshold: 200 },
|
|
10133
|
+
persistent: false,
|
|
10134
|
+
usePolling: false,
|
|
10135
|
+
depth: 5
|
|
10136
|
+
});
|
|
10137
|
+
const entry = {
|
|
10138
|
+
watcher: fsWatcher,
|
|
10139
|
+
repoRoot: rootPath,
|
|
10140
|
+
repoId,
|
|
10141
|
+
changedFiles: 0,
|
|
10142
|
+
deletedFiles: 0,
|
|
10143
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
10144
|
+
};
|
|
10145
|
+
watchers.set(repoId, entry);
|
|
10146
|
+
fsWatcher.on(
|
|
10147
|
+
"add",
|
|
10148
|
+
(path) => debouncedHandler(`${repoId}:${path}`, () => handleFileChange(db, repoId, rootPath, path))
|
|
10149
|
+
);
|
|
10150
|
+
fsWatcher.on(
|
|
10151
|
+
"change",
|
|
10152
|
+
(path) => debouncedHandler(`${repoId}:${path}`, () => handleFileChange(db, repoId, rootPath, path))
|
|
10153
|
+
);
|
|
10154
|
+
fsWatcher.on(
|
|
10155
|
+
"unlink",
|
|
10156
|
+
(path) => debouncedHandler(`${repoId}:${path}`, () => handleFileDelete(db, repoId, rootPath, path))
|
|
10157
|
+
);
|
|
10158
|
+
fsWatcher.on("error", () => {
|
|
10159
|
+
stopWatcher(repoId).catch(() => {
|
|
10160
|
+
});
|
|
10161
|
+
});
|
|
10162
|
+
return { started: true, already_watching: false };
|
|
10163
|
+
} catch {
|
|
10164
|
+
return { started: false, already_watching: false };
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
async function stopWatcher(repoId) {
|
|
10168
|
+
const entry = watchers.get(repoId);
|
|
10169
|
+
if (!entry) return { stopped: false };
|
|
10170
|
+
await entry.watcher.close();
|
|
10171
|
+
watchers.delete(repoId);
|
|
10172
|
+
for (const [key, timer] of debounceTimers) {
|
|
10173
|
+
if (key.startsWith(`${repoId}:`)) {
|
|
10174
|
+
clearTimeout(timer);
|
|
10175
|
+
debounceTimers.delete(key);
|
|
10176
|
+
}
|
|
10177
|
+
}
|
|
10178
|
+
return { stopped: true };
|
|
10179
|
+
}
|
|
10180
|
+
function getWatcherStatus(repoId) {
|
|
10181
|
+
const entry = watchers.get(repoId);
|
|
10182
|
+
if (!entry) return { watching: false, repo_root: null, changed_files: 0, deleted_files: 0, started_at: null };
|
|
10183
|
+
return {
|
|
10184
|
+
watching: true,
|
|
10185
|
+
repo_root: entry.repoRoot,
|
|
10186
|
+
changed_files: entry.changedFiles,
|
|
10187
|
+
deleted_files: entry.deletedFiles,
|
|
10188
|
+
started_at: entry.startedAt.toISOString()
|
|
10189
|
+
};
|
|
10190
|
+
}
|
|
10191
|
+
function normalizeRemoteUrl(url) {
|
|
10192
|
+
let normalized = url.trim();
|
|
10193
|
+
const sshMatch = normalized.match(/^[\w-]+@([\w.-]+):(.+)$/);
|
|
10194
|
+
if (sshMatch) {
|
|
10195
|
+
normalized = `${sshMatch[1]}/${sshMatch[2]}`;
|
|
10196
|
+
} else {
|
|
10197
|
+
normalized = normalized.replace(/^https?:\/\//, "");
|
|
10198
|
+
}
|
|
10199
|
+
normalized = normalized.replace(/\.git$/, "");
|
|
10200
|
+
normalized = normalized.replace(/\/$/, "");
|
|
10201
|
+
return normalized.toLowerCase();
|
|
10202
|
+
}
|
|
10203
|
+
function deriveIdentityKey(rootPath, remoteUrl) {
|
|
10204
|
+
const source = remoteUrl ? normalizeRemoteUrl(remoteUrl) : rootPath;
|
|
10205
|
+
return createHash2("sha256").update(source).digest("hex");
|
|
10206
|
+
}
|
|
10207
|
+
function registerRepo(db, rootPath, name, remoteUrl) {
|
|
10208
|
+
const identityKey = deriveIdentityKey(rootPath, remoteUrl);
|
|
10209
|
+
const repoName = name ?? rootPath.split("/").pop() ?? "unknown";
|
|
10210
|
+
const { id, created } = repoQueries.upsert(db, identityKey, repoName, rootPath, remoteUrl ?? null);
|
|
10211
|
+
return { repo_id: id, identity_key: identityKey, name: repoName, created };
|
|
10212
|
+
}
|
|
10213
|
+
function getRepo(db, id) {
|
|
10214
|
+
const repo = repoQueries.getById(db, id);
|
|
10215
|
+
if (!repo) throw new Error("repo not found");
|
|
10216
|
+
return repo;
|
|
10217
|
+
}
|
|
10218
|
+
function listRepos(db) {
|
|
10219
|
+
return repoQueries.list(db);
|
|
10220
|
+
}
|
|
10221
|
+
function removeRepo(db, id) {
|
|
10222
|
+
const chunkCount = chunkQueries.countByRepo(db, id);
|
|
10223
|
+
const deleted = repoQueries.remove(db, id);
|
|
10224
|
+
if (!deleted) throw new Error("repo not found");
|
|
10225
|
+
return { deleted: true, chunks_removed: chunkCount };
|
|
10226
|
+
}
|
|
10227
|
+
async function getRepoStatus(db, id) {
|
|
10228
|
+
const repo = repoQueries.getById(db, id);
|
|
10229
|
+
if (!repo) throw new Error("repo not found");
|
|
10230
|
+
let currentHead = null;
|
|
10231
|
+
try {
|
|
10232
|
+
currentHead = await getHeadCommit(repo.root_path);
|
|
9577
10233
|
} catch {
|
|
9578
|
-
return {
|
|
9579
|
-
context_pack: `# ${goal}
|
|
9580
|
-
|
|
9581
|
-
Context generation failed.`,
|
|
9582
|
-
stats: { files_in_context: 0, index_fresh: false, duration_ms: Date.now() - start, cached: false }
|
|
9583
|
-
};
|
|
9584
10234
|
}
|
|
10235
|
+
const isStale = !!(currentHead && repo.last_indexed_commit !== currentHead);
|
|
10236
|
+
const stats = chunkQueries.getStats(db, id);
|
|
10237
|
+
const structural = metadataQueries.getStructuralStats(db, id);
|
|
10238
|
+
const vocabRaw = repo.vocab_clusters;
|
|
10239
|
+
const vocabClusters = vocabRaw ? JSON.parse(vocabRaw) : [];
|
|
10240
|
+
return {
|
|
10241
|
+
index_status: repo.index_status,
|
|
10242
|
+
indexed_commit: repo.last_indexed_commit,
|
|
10243
|
+
current_head: currentHead,
|
|
10244
|
+
is_stale: isStale,
|
|
10245
|
+
chunk_count: stats.chunk_count,
|
|
10246
|
+
files_indexed: stats.files_indexed,
|
|
10247
|
+
embedded_count: stats.embedded_count,
|
|
10248
|
+
embeddable_count: stats.embeddable_count,
|
|
10249
|
+
embedded_pct: stats.embeddable_count > 0 ? Math.round(stats.embedded_count / stats.embeddable_count * 100) : 0,
|
|
10250
|
+
metadata_count: structural.metadata_count,
|
|
10251
|
+
import_edge_count: structural.import_edge_count,
|
|
10252
|
+
git_commits_analyzed: structural.git_file_count,
|
|
10253
|
+
cochange_pairs: structural.cochange_pairs,
|
|
10254
|
+
purpose_count: structural.purpose_count,
|
|
10255
|
+
purpose_total: structural.purpose_total,
|
|
10256
|
+
vocab_cluster_count: Array.isArray(vocabClusters) ? vocabClusters.length : 0
|
|
10257
|
+
};
|
|
9585
10258
|
}
|
|
9586
|
-
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,
|
|
10259
|
+
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, slicer_exports, RADIUS, MAX_SLICES, init_slicer, _enabled, DEFAULT_CHUNKING_PARAMS, execFileAsync, MAX_FILE_SIZE, BINARY_EXTENSIONS, DOCS_EXTENSIONS, LANG_MAP, 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, UNIVERSAL_DECL_RES, EXPORT_LINE_RE, SKIP_NAMES, execFileAsync2, MAX_COMMITS, MAX_FILES_PER_COMMIT, RECENT_DAYS, MAX_CHUNKS_PER_REPO, locks, TOKEN_CAP, LANG_EXT, FRAME_PATTERNS, CASING_BOUNDARY, SYMBOL_RE, ERROR_CODE_RE, ERROR_SUFFIX_RE, ERROR_PREFIX_RE, STOPWORDS, STOPWORDS2, NOISE_EXTENSIONS, NOISE_PATHS, CONCEPT_SYNONYMS, SYMBOL_DEF_RE, TEST_PATTERN, CACHE_TTL, CACHE_MAX, cache, _db, _raw, GOLD_DATASET, 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, STOPWORDS3, watchers, debounceTimers, IGNORED, DEBOUNCE_MS, RequestTrace;
|
|
9587
10260
|
var init_dist = __esm({
|
|
9588
10261
|
"packages/engine/dist/index.js"() {
|
|
9589
10262
|
"use strict";
|
|
@@ -9771,10 +10444,7 @@ var init_dist = __esm({
|
|
|
9771
10444
|
created_at: now(),
|
|
9772
10445
|
synced_at: text("synced_at")
|
|
9773
10446
|
},
|
|
9774
|
-
(t) => [
|
|
9775
|
-
index("idx_telemetry_events_type").on(t.event_type),
|
|
9776
|
-
index("idx_telemetry_events_synced").on(t.synced_at)
|
|
9777
|
-
]
|
|
10447
|
+
(t) => [index("idx_telemetry_events_type").on(t.event_type), index("idx_telemetry_events_synced").on(t.synced_at)]
|
|
9778
10448
|
);
|
|
9779
10449
|
}
|
|
9780
10450
|
});
|
|
@@ -9972,6 +10642,21 @@ var init_dist = __esm({
|
|
|
9972
10642
|
const row = db.select({ id: chunks.id }).from(chunks).where(and(eq(chunks.repo_id, repoId), sql`embedding IS NOT NULL`)).limit(1).get();
|
|
9973
10643
|
return !!row;
|
|
9974
10644
|
},
|
|
10645
|
+
getByRepoPaths(db, repoId, paths) {
|
|
10646
|
+
if (!paths.length) return [];
|
|
10647
|
+
return db.select({
|
|
10648
|
+
path: chunks.path,
|
|
10649
|
+
chunk_index: chunks.chunk_index,
|
|
10650
|
+
start_line: chunks.start_line,
|
|
10651
|
+
end_line: chunks.end_line,
|
|
10652
|
+
content: chunks.content
|
|
10653
|
+
}).from(chunks).where(and(eq(chunks.repo_id, repoId), inArray(chunks.path, paths))).orderBy(chunks.path, chunks.chunk_index).all();
|
|
10654
|
+
},
|
|
10655
|
+
searchContent(db, repoId, searchString) {
|
|
10656
|
+
if (!searchString || searchString.length < 4) return [];
|
|
10657
|
+
const rows = db.select({ path: chunks.path }).from(chunks).where(and(eq(chunks.repo_id, repoId), sql`INSTR(LOWER(${chunks.content}), ${searchString.toLowerCase()}) > 0`)).groupBy(chunks.path).all();
|
|
10658
|
+
return rows.map((r) => r.path);
|
|
10659
|
+
},
|
|
9975
10660
|
getStats(db, repoId) {
|
|
9976
10661
|
const row = db.select({
|
|
9977
10662
|
chunk_count: sql`count(*)`,
|
|
@@ -10222,7 +10907,7 @@ var init_dist = __esm({
|
|
|
10222
10907
|
const { limit = 50, offset = 0, method, path, status, source } = opts;
|
|
10223
10908
|
const conditions = [];
|
|
10224
10909
|
if (method) conditions.push(eq(requestLogs.method, method));
|
|
10225
|
-
if (path) conditions.push(sql`${requestLogs.path} LIKE ${
|
|
10910
|
+
if (path) conditions.push(sql`${requestLogs.path} LIKE ${`%${path}%`}`);
|
|
10226
10911
|
if (status) conditions.push(eq(requestLogs.status, status));
|
|
10227
10912
|
if (source) conditions.push(eq(requestLogs.source, source));
|
|
10228
10913
|
const where = conditions.length ? and(...conditions) : void 0;
|
|
@@ -10264,7 +10949,9 @@ var init_dist = __esm({
|
|
|
10264
10949
|
return db.select().from(usageCounters).where(eq(usageCounters.date, date3)).get() ?? null;
|
|
10265
10950
|
},
|
|
10266
10951
|
getUnsynced(db) {
|
|
10267
|
-
return db.select().from(usageCounters).where(
|
|
10952
|
+
return db.select().from(usageCounters).where(
|
|
10953
|
+
sql`synced_at IS NULL AND (context_queries > 0 OR embedding_requests > 0 OR purpose_requests > 0 OR repos_indexed > 0)`
|
|
10954
|
+
).all();
|
|
10268
10955
|
},
|
|
10269
10956
|
markSynced(db, date3) {
|
|
10270
10957
|
db.update(usageCounters).set({ synced_at: sql`datetime('now')` }).where(eq(usageCounters.date, date3)).run();
|
|
@@ -10316,10 +11003,32 @@ var init_dist = __esm({
|
|
|
10316
11003
|
};
|
|
10317
11004
|
}
|
|
10318
11005
|
});
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
11006
|
+
slicer_exports = {};
|
|
11007
|
+
__export2(slicer_exports, {
|
|
11008
|
+
sliceContext: () => sliceContext
|
|
11009
|
+
});
|
|
11010
|
+
init_slicer = __esm2({
|
|
11011
|
+
"src/context/slicer.ts"() {
|
|
11012
|
+
"use strict";
|
|
11013
|
+
init_queries();
|
|
11014
|
+
RADIUS = 10;
|
|
11015
|
+
MAX_SLICES = {
|
|
11016
|
+
symbol: 1,
|
|
11017
|
+
error_message: 1,
|
|
11018
|
+
stack_trace: 2,
|
|
11019
|
+
natural: 3
|
|
11020
|
+
};
|
|
11021
|
+
}
|
|
11022
|
+
});
|
|
11023
|
+
init_queries();
|
|
10322
11024
|
init_queries();
|
|
11025
|
+
init_queries();
|
|
11026
|
+
_enabled = true;
|
|
11027
|
+
DEFAULT_CHUNKING_PARAMS = {
|
|
11028
|
+
target_lines: 150,
|
|
11029
|
+
overlap_lines: 10,
|
|
11030
|
+
version: 1
|
|
11031
|
+
};
|
|
10323
11032
|
execFileAsync = promisify(execFile);
|
|
10324
11033
|
MAX_FILE_SIZE = 2 * 1024 * 1024;
|
|
10325
11034
|
BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -10397,12 +11106,6 @@ var init_dist = __esm({
|
|
|
10397
11106
|
".scss": "scss"
|
|
10398
11107
|
};
|
|
10399
11108
|
init_queries();
|
|
10400
|
-
DEFAULT_CHUNKING_PARAMS = {
|
|
10401
|
-
target_lines: 150,
|
|
10402
|
-
overlap_lines: 10,
|
|
10403
|
-
version: 1
|
|
10404
|
-
};
|
|
10405
|
-
init_queries();
|
|
10406
11109
|
TS_IMPORT_RE = /(?:import\s+.*?from\s+|import\s+|export\s+.*?from\s+|require\s*\()['"]([^'"]+)['"]/g;
|
|
10407
11110
|
PY_IMPORT_RE = /^(?:from\s+(\.[\w.]*)\s+import|import\s+([\w.]+))/gm;
|
|
10408
11111
|
GO_IMPORT_RE = /import\s+(?:\(\s*)?(?:[\w.]*\s+)?"([^"]+)"/g;
|
|
@@ -10414,7 +11117,7 @@ var init_dist = __esm({
|
|
|
10414
11117
|
GO_EXPORT_RE = /^func\s+([A-Z]\w*)/gm;
|
|
10415
11118
|
RUST_EXPORT_RE = /^pub\s+(?:fn|struct|enum|trait|type|mod)\s+(\w+)/gm;
|
|
10416
11119
|
CSHARP_EXPORT_RE = /^\s*(?:public|internal)\s+(?:static\s+)?(?:abstract\s+|sealed\s+|partial\s+)?(?:class|interface|enum|struct|record|delegate)\s+(\w+)/gm;
|
|
10417
|
-
CSHARP_EXPORT_METHOD_RE = /^\s*public\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?[\w
|
|
11120
|
+
CSHARP_EXPORT_METHOD_RE = /^\s*public\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?[\w<>[\]?.]+\s+(\w+)\s*\(/gm;
|
|
10418
11121
|
JAVA_EXPORT_RE = /^\s*(?:public)\s+(?:static\s+)?(?:abstract\s+|final\s+)?(?:class|interface|enum|record)\s+(\w+)/gm;
|
|
10419
11122
|
JSDOC_RE = /^\/\*\*\s*([\s\S]*?)\*\//m;
|
|
10420
11123
|
PY_DOCSTRING_RE = /^(?:["']{3})([\s\S]*?)(?:["']{3})/m;
|
|
@@ -10428,60 +11131,179 @@ var init_dist = __esm({
|
|
|
10428
11131
|
/^\s*(?:const|let|var|val)\s+(\w+)\s*[=:]/gm,
|
|
10429
11132
|
/^\s*(?:abstract\s+|sealed\s+|partial\s+|data\s+)?(?:class|struct|enum|trait|interface|record|object|mod)\s+(\w+)/gm,
|
|
10430
11133
|
/^\s*type\s+(\w+)\s*[=<{]/gm,
|
|
10431
|
-
/^\s*(?:private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:suspend\s+)?(?:override\s+)?[\w
|
|
11134
|
+
/^\s*(?:private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:suspend\s+)?(?:override\s+)?[\w<>[\]?.]+\s+(\w+)\s*\(/gm
|
|
10432
11135
|
];
|
|
10433
11136
|
EXPORT_LINE_RE = /^(?:export|pub\s|public\s)/;
|
|
10434
11137
|
SKIP_NAMES = /* @__PURE__ */ new Set([
|
|
10435
11138
|
"if",
|
|
10436
|
-
"for",
|
|
10437
|
-
"foreach",
|
|
10438
|
-
"while",
|
|
10439
|
-
"switch",
|
|
10440
|
-
"using",
|
|
10441
|
-
"catch",
|
|
10442
|
-
"lock",
|
|
10443
|
-
"return",
|
|
10444
|
-
"throw",
|
|
10445
|
-
"yield",
|
|
10446
|
-
"try",
|
|
10447
|
-
"do",
|
|
10448
|
-
"else",
|
|
10449
|
-
"new",
|
|
10450
|
-
"await",
|
|
10451
|
-
"base",
|
|
11139
|
+
"for",
|
|
11140
|
+
"foreach",
|
|
11141
|
+
"while",
|
|
11142
|
+
"switch",
|
|
11143
|
+
"using",
|
|
11144
|
+
"catch",
|
|
11145
|
+
"lock",
|
|
11146
|
+
"return",
|
|
11147
|
+
"throw",
|
|
11148
|
+
"yield",
|
|
11149
|
+
"try",
|
|
11150
|
+
"do",
|
|
11151
|
+
"else",
|
|
11152
|
+
"new",
|
|
11153
|
+
"await",
|
|
11154
|
+
"base",
|
|
11155
|
+
"this",
|
|
11156
|
+
"super",
|
|
11157
|
+
"self",
|
|
11158
|
+
"import",
|
|
11159
|
+
"require",
|
|
11160
|
+
"from",
|
|
11161
|
+
"package"
|
|
11162
|
+
]);
|
|
11163
|
+
init_queries();
|
|
11164
|
+
execFileAsync2 = promisify2(execFile2);
|
|
11165
|
+
MAX_COMMITS = 2e3;
|
|
11166
|
+
MAX_FILES_PER_COMMIT = 20;
|
|
11167
|
+
RECENT_DAYS = 90;
|
|
11168
|
+
init_queries();
|
|
11169
|
+
MAX_CHUNKS_PER_REPO = 1e5;
|
|
11170
|
+
locks = /* @__PURE__ */ new Map();
|
|
11171
|
+
TOKEN_CAP = 2e3;
|
|
11172
|
+
LANG_EXT = {
|
|
11173
|
+
ts: "typescript",
|
|
11174
|
+
tsx: "typescript",
|
|
11175
|
+
js: "javascript",
|
|
11176
|
+
jsx: "javascript",
|
|
11177
|
+
py: "python",
|
|
11178
|
+
rb: "ruby",
|
|
11179
|
+
go: "go",
|
|
11180
|
+
rs: "rust",
|
|
11181
|
+
java: "java",
|
|
11182
|
+
kt: "kotlin",
|
|
11183
|
+
cs: "csharp",
|
|
11184
|
+
cpp: "cpp",
|
|
11185
|
+
c: "c",
|
|
11186
|
+
h: "c",
|
|
11187
|
+
swift: "swift",
|
|
11188
|
+
php: "php",
|
|
11189
|
+
sql: "sql",
|
|
11190
|
+
sh: "shell"
|
|
11191
|
+
};
|
|
11192
|
+
FRAME_PATTERNS = [
|
|
11193
|
+
// JS/TS: at fn (path:line:col) or at path:line:col
|
|
11194
|
+
/at\s+(?:\S+\s+\()?([^():]+):(\d+):\d+\)?/g,
|
|
11195
|
+
// Python: File "path", line N
|
|
11196
|
+
/File\s+"([^"]+)",\s+line\s+(\d+)/g,
|
|
11197
|
+
// C#/Java: at namespace(path:line)
|
|
11198
|
+
/at\s+\S+\(([^():]+):(\d+)\)/g
|
|
11199
|
+
];
|
|
11200
|
+
CASING_BOUNDARY = /[a-z][A-Z]|_/;
|
|
11201
|
+
SYMBOL_RE = /^[a-zA-Z_]\w+$/;
|
|
11202
|
+
ERROR_CODE_RE = /\b[A-Z][A-Z0-9_]{2,}\b/;
|
|
11203
|
+
ERROR_SUFFIX_RE = /\w+(Exception|Error)\b/;
|
|
11204
|
+
ERROR_PREFIX_RE = /\b(TypeError|ReferenceError|SyntaxError|RangeError|Error|panic|FATAL|WARN|ERR):/;
|
|
11205
|
+
STOPWORDS = /* @__PURE__ */ new Set([
|
|
11206
|
+
"the",
|
|
11207
|
+
"a",
|
|
11208
|
+
"an",
|
|
11209
|
+
"is",
|
|
11210
|
+
"are",
|
|
11211
|
+
"was",
|
|
11212
|
+
"were",
|
|
11213
|
+
"be",
|
|
11214
|
+
"been",
|
|
11215
|
+
"being",
|
|
11216
|
+
"have",
|
|
11217
|
+
"has",
|
|
11218
|
+
"had",
|
|
11219
|
+
"do",
|
|
11220
|
+
"does",
|
|
11221
|
+
"did",
|
|
11222
|
+
"will",
|
|
11223
|
+
"would",
|
|
11224
|
+
"could",
|
|
11225
|
+
"should",
|
|
11226
|
+
"may",
|
|
11227
|
+
"might",
|
|
11228
|
+
"can",
|
|
11229
|
+
"need",
|
|
11230
|
+
"must",
|
|
11231
|
+
"to",
|
|
11232
|
+
"of",
|
|
11233
|
+
"in",
|
|
11234
|
+
"for",
|
|
11235
|
+
"on",
|
|
11236
|
+
"with",
|
|
11237
|
+
"at",
|
|
11238
|
+
"by",
|
|
11239
|
+
"from",
|
|
11240
|
+
"as",
|
|
11241
|
+
"into",
|
|
11242
|
+
"through",
|
|
11243
|
+
"and",
|
|
11244
|
+
"but",
|
|
11245
|
+
"or",
|
|
11246
|
+
"not",
|
|
11247
|
+
"no",
|
|
11248
|
+
"so",
|
|
11249
|
+
"if",
|
|
11250
|
+
"then",
|
|
11251
|
+
"than",
|
|
11252
|
+
"that",
|
|
10452
11253
|
"this",
|
|
10453
|
-
"
|
|
10454
|
-
"
|
|
11254
|
+
"it",
|
|
11255
|
+
"its",
|
|
11256
|
+
"all",
|
|
11257
|
+
"each",
|
|
11258
|
+
"every",
|
|
11259
|
+
"any",
|
|
11260
|
+
"some",
|
|
11261
|
+
"how",
|
|
11262
|
+
"what",
|
|
11263
|
+
"which",
|
|
11264
|
+
"who",
|
|
11265
|
+
"when",
|
|
11266
|
+
"where",
|
|
11267
|
+
"why",
|
|
11268
|
+
"get",
|
|
11269
|
+
"set",
|
|
11270
|
+
"new",
|
|
11271
|
+
"null",
|
|
11272
|
+
"true",
|
|
11273
|
+
"false",
|
|
11274
|
+
"void",
|
|
11275
|
+
"type",
|
|
11276
|
+
"var",
|
|
11277
|
+
"let",
|
|
11278
|
+
"const",
|
|
11279
|
+
"return",
|
|
10455
11280
|
"import",
|
|
10456
|
-
"
|
|
10457
|
-
"
|
|
10458
|
-
"
|
|
11281
|
+
"export",
|
|
11282
|
+
"default",
|
|
11283
|
+
"class",
|
|
11284
|
+
"function",
|
|
11285
|
+
"string",
|
|
11286
|
+
"number",
|
|
11287
|
+
"boolean",
|
|
11288
|
+
"object",
|
|
11289
|
+
"array",
|
|
11290
|
+
"index",
|
|
11291
|
+
"data",
|
|
11292
|
+
"value",
|
|
11293
|
+
"result",
|
|
11294
|
+
"item",
|
|
11295
|
+
"list",
|
|
11296
|
+
"name",
|
|
11297
|
+
"id",
|
|
11298
|
+
"key",
|
|
11299
|
+
"src",
|
|
11300
|
+
"lib",
|
|
11301
|
+
"app",
|
|
11302
|
+
"spec",
|
|
11303
|
+
"mock",
|
|
11304
|
+
"module"
|
|
10459
11305
|
]);
|
|
10460
|
-
|
|
10461
|
-
init_queries();
|
|
10462
|
-
execFileAsync2 = promisify2(execFile2);
|
|
10463
|
-
MAX_COMMITS = 2e3;
|
|
10464
|
-
MAX_FILES_PER_COMMIT = 20;
|
|
10465
|
-
RECENT_DAYS = 90;
|
|
10466
|
-
init_queries();
|
|
10467
|
-
_enabled = true;
|
|
10468
|
-
MAX_CHUNKS_PER_REPO = 1e5;
|
|
10469
|
-
locks = /* @__PURE__ */ new Map();
|
|
10470
|
-
init_queries();
|
|
10471
|
-
MAX_API_CALLS = 2e3;
|
|
10472
|
-
POOL_SIZE = 32;
|
|
10473
|
-
MAX_BATCH_TOKENS = 1e5;
|
|
10474
|
-
CHARS_PER_TOKEN = 3;
|
|
10475
|
-
init_queries();
|
|
10476
|
-
PURPOSE_BATCH_LIMIT = 200;
|
|
10477
|
-
PURPOSE_CONCURRENCY = 10;
|
|
10478
|
-
init_queries();
|
|
10479
|
-
TERM_BATCH_SIZE = 32;
|
|
10480
|
-
SIMILARITY_THRESHOLD = 0.75;
|
|
10481
|
-
MAX_TERMS = 1500;
|
|
10482
|
-
MAX_CLUSTERS = 200;
|
|
10483
|
-
MAX_CLUSTER_SIZE = 12;
|
|
10484
|
-
STOPWORDS = /* @__PURE__ */ new Set([
|
|
11306
|
+
STOPWORDS2 = /* @__PURE__ */ new Set([
|
|
10485
11307
|
"the",
|
|
10486
11308
|
"a",
|
|
10487
11309
|
"an",
|
|
@@ -10578,39 +11400,294 @@ var init_dist = __esm({
|
|
|
10578
11400
|
"src",
|
|
10579
11401
|
"lib",
|
|
10580
11402
|
"app",
|
|
10581
|
-
"test",
|
|
10582
11403
|
"spec",
|
|
10583
11404
|
"mock",
|
|
10584
|
-
"model",
|
|
10585
11405
|
"module",
|
|
10586
|
-
"
|
|
11406
|
+
"work",
|
|
11407
|
+
"works",
|
|
11408
|
+
"working",
|
|
11409
|
+
"make",
|
|
11410
|
+
"makes",
|
|
11411
|
+
"use",
|
|
11412
|
+
"uses",
|
|
11413
|
+
"used",
|
|
11414
|
+
"using",
|
|
11415
|
+
"find",
|
|
11416
|
+
"run",
|
|
11417
|
+
"runs",
|
|
11418
|
+
"call",
|
|
11419
|
+
"calls",
|
|
11420
|
+
"look",
|
|
11421
|
+
"like",
|
|
11422
|
+
"just",
|
|
11423
|
+
"also",
|
|
11424
|
+
"about",
|
|
11425
|
+
"there",
|
|
11426
|
+
"here",
|
|
11427
|
+
"define",
|
|
11428
|
+
"defined",
|
|
11429
|
+
"handle",
|
|
11430
|
+
"handles",
|
|
11431
|
+
"add",
|
|
11432
|
+
"adds",
|
|
11433
|
+
"create",
|
|
11434
|
+
"serve",
|
|
11435
|
+
"does",
|
|
11436
|
+
"file",
|
|
11437
|
+
"files"
|
|
11438
|
+
]);
|
|
11439
|
+
NOISE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
11440
|
+
".md",
|
|
11441
|
+
".json",
|
|
11442
|
+
".yaml",
|
|
11443
|
+
".yml",
|
|
11444
|
+
".toml",
|
|
11445
|
+
".lock",
|
|
11446
|
+
".sql",
|
|
11447
|
+
".txt",
|
|
11448
|
+
".csv",
|
|
11449
|
+
".psd",
|
|
11450
|
+
".png",
|
|
11451
|
+
".jpg",
|
|
11452
|
+
".jpeg",
|
|
11453
|
+
".gif",
|
|
11454
|
+
".svg",
|
|
11455
|
+
".ico",
|
|
11456
|
+
".woff",
|
|
11457
|
+
".woff2",
|
|
11458
|
+
".ttf",
|
|
11459
|
+
".eot",
|
|
11460
|
+
".xml",
|
|
11461
|
+
".axml",
|
|
11462
|
+
".resx",
|
|
11463
|
+
".config"
|
|
10587
11464
|
]);
|
|
11465
|
+
NOISE_PATHS = [
|
|
11466
|
+
".gitignore",
|
|
11467
|
+
".github/",
|
|
11468
|
+
".vscode/",
|
|
11469
|
+
".idea/",
|
|
11470
|
+
"node_modules/",
|
|
11471
|
+
"dist/",
|
|
11472
|
+
"build/",
|
|
11473
|
+
"publish/",
|
|
11474
|
+
"vendor/",
|
|
11475
|
+
"vendors/",
|
|
11476
|
+
"/scripts/",
|
|
11477
|
+
"areas/helppage/",
|
|
11478
|
+
"packages.config",
|
|
11479
|
+
"resources/drawable/",
|
|
11480
|
+
"resources/layout/",
|
|
11481
|
+
"resources/values/",
|
|
11482
|
+
"wwwroot/lib/",
|
|
11483
|
+
"wwwroot/css/",
|
|
11484
|
+
"wwwroot/js/lib/",
|
|
11485
|
+
".min.js",
|
|
11486
|
+
".min.css",
|
|
11487
|
+
".designer.cs",
|
|
11488
|
+
"assemblyinfo.cs"
|
|
11489
|
+
];
|
|
11490
|
+
CONCEPT_SYNONYMS = {
|
|
11491
|
+
error: ["interceptor", "exception", "catch", "middleware", "handler"],
|
|
11492
|
+
errors: ["interceptor", "exception", "catch", "middleware", "handler"],
|
|
11493
|
+
retry: ["interceptor", "backoff", "retry", "queue"],
|
|
11494
|
+
retries: ["interceptor", "backoff", "retry", "queue"],
|
|
11495
|
+
auth: ["interceptor", "token", "login", "guard", "middleware", "session"],
|
|
11496
|
+
authentication: ["interceptor", "token", "login", "guard", "middleware"],
|
|
11497
|
+
authorization: ["guard", "role", "permission", "policy"],
|
|
11498
|
+
cache: ["invalidat", "ttl", "expire", "store", "memo"],
|
|
11499
|
+
caching: ["invalidat", "ttl", "expire", "store", "memo"],
|
|
11500
|
+
logging: ["logger", "trace", "monitor", "telemetry"],
|
|
11501
|
+
validation: ["validator", "schema", "sanitiz", "constraint"],
|
|
11502
|
+
notification: ["email", "alert", "signal", "push", "webhook"],
|
|
11503
|
+
upload: ["multipart", "stream", "blob", "attachment", "photo"],
|
|
11504
|
+
performance: ["optimize", "cache", "lazy", "batch", "throttle"],
|
|
11505
|
+
security: ["auth", "token", "encrypt", "cors", "csrf", "sanitiz"],
|
|
11506
|
+
testing: ["spec", "mock", "fixture", "assert", "stub"],
|
|
11507
|
+
deploy: ["pipeline", "docker", "build", "release", "ci-cd"],
|
|
11508
|
+
database: ["repository", "migration", "query", "schema", "orm"],
|
|
11509
|
+
api: ["controller", "endpoint", "route", "middleware", "interceptor"]
|
|
11510
|
+
};
|
|
10588
11511
|
init_queries();
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
11512
|
+
SYMBOL_DEF_RE = /^.*?\b(?:function|def|fn|class|const|let|var|type|interface|struct|enum|export\s+(?:function|class|const|type|interface|default))\s+/;
|
|
11513
|
+
init_queries();
|
|
11514
|
+
TEST_PATTERN = /\.(?:test|spec)\.|__tests__\/|_test\./;
|
|
11515
|
+
init_queries();
|
|
11516
|
+
CACHE_TTL = 12e4;
|
|
11517
|
+
CACHE_MAX = 20;
|
|
11518
|
+
cache = /* @__PURE__ */ new Map();
|
|
11519
|
+
init_schema();
|
|
11520
|
+
_db = null;
|
|
11521
|
+
_raw = null;
|
|
11522
|
+
init_queries();
|
|
11523
|
+
GOLD_DATASET = [
|
|
11524
|
+
// --- natural (12) ---
|
|
11525
|
+
{
|
|
11526
|
+
id: "nat-01",
|
|
11527
|
+
query: "How does the context pack pipeline work?",
|
|
11528
|
+
kind: "natural",
|
|
11529
|
+
expected_files: [
|
|
11530
|
+
"packages/engine/src/context/context.ts",
|
|
11531
|
+
"packages/engine/src/context/formatter.ts",
|
|
11532
|
+
"packages/engine/src/context/query-interpreter.ts"
|
|
11533
|
+
],
|
|
11534
|
+
expected_entry: "packages/engine/src/context/context.ts"
|
|
11535
|
+
},
|
|
11536
|
+
{
|
|
11537
|
+
id: "nat-02",
|
|
11538
|
+
query: "How does file indexing work?",
|
|
11539
|
+
kind: "natural",
|
|
11540
|
+
expected_files: [
|
|
11541
|
+
"packages/engine/src/index/engine.ts",
|
|
11542
|
+
"packages/engine/src/index/chunker.ts",
|
|
11543
|
+
"packages/engine/src/index/discovery.ts"
|
|
11544
|
+
],
|
|
11545
|
+
expected_entry: "packages/engine/src/index/engine.ts"
|
|
11546
|
+
},
|
|
11547
|
+
{
|
|
11548
|
+
id: "nat-03",
|
|
11549
|
+
query: "How are imports resolved and the import graph built?",
|
|
11550
|
+
kind: "natural",
|
|
11551
|
+
expected_files: ["packages/engine/src/index/import-graph.ts", "packages/engine/src/index/imports.ts"],
|
|
11552
|
+
expected_entry: "packages/engine/src/index/import-graph.ts"
|
|
11553
|
+
},
|
|
11554
|
+
{
|
|
11555
|
+
id: "nat-04",
|
|
11556
|
+
query: "How does the daemon HTTP server handle requests?",
|
|
11557
|
+
kind: "natural",
|
|
11558
|
+
expected_files: ["apps/daemon/src/server.ts", "apps/daemon/src/index.ts"],
|
|
11559
|
+
expected_entry: "apps/daemon/src/server.ts"
|
|
11560
|
+
},
|
|
11561
|
+
{
|
|
11562
|
+
id: "nat-05",
|
|
11563
|
+
query: "How does the CLI register a repo?",
|
|
11564
|
+
kind: "natural",
|
|
11565
|
+
expected_files: ["packages/cli/src/commands/register.ts", "packages/engine/src/repo/repo.ts"],
|
|
11566
|
+
expected_entry: "packages/cli/src/commands/register.ts"
|
|
11567
|
+
},
|
|
11568
|
+
{
|
|
11569
|
+
id: "nat-06",
|
|
11570
|
+
query: "How does git history analysis and co-change detection work?",
|
|
11571
|
+
kind: "natural",
|
|
11572
|
+
expected_files: ["packages/engine/src/index/git-analysis.ts"],
|
|
11573
|
+
expected_entry: "packages/engine/src/index/git-analysis.ts"
|
|
11574
|
+
},
|
|
11575
|
+
{
|
|
11576
|
+
id: "nat-07",
|
|
11577
|
+
query: "How does the MCP stdio server work?",
|
|
11578
|
+
kind: "natural",
|
|
11579
|
+
expected_files: ["apps/daemon/src/mcp.ts"],
|
|
11580
|
+
expected_entry: "apps/daemon/src/mcp.ts"
|
|
11581
|
+
},
|
|
11582
|
+
{
|
|
11583
|
+
id: "nat-08",
|
|
11584
|
+
query: "How does TF-IDF scoring work in the query interpreter?",
|
|
11585
|
+
kind: "natural",
|
|
11586
|
+
expected_files: ["packages/engine/src/context/query-interpreter.ts"],
|
|
11587
|
+
expected_entry: "packages/engine/src/context/query-interpreter.ts"
|
|
11588
|
+
},
|
|
11589
|
+
{
|
|
11590
|
+
id: "nat-09",
|
|
11591
|
+
query: "How are file metadata (exports, docstrings, sections) extracted?",
|
|
11592
|
+
kind: "natural",
|
|
11593
|
+
expected_files: ["packages/engine/src/index/extract-metadata.ts"],
|
|
11594
|
+
expected_entry: "packages/engine/src/index/extract-metadata.ts"
|
|
11595
|
+
},
|
|
11596
|
+
{
|
|
11597
|
+
id: "nat-10",
|
|
11598
|
+
query: "How does vector/semantic search work?",
|
|
11599
|
+
kind: "natural",
|
|
11600
|
+
expected_files: ["packages/engine/src/context/vector.ts", "packages/engine/src/index/embed.ts"],
|
|
11601
|
+
expected_entry: "packages/engine/src/context/vector.ts"
|
|
11602
|
+
},
|
|
11603
|
+
{
|
|
11604
|
+
id: "nat-11",
|
|
11605
|
+
query: "How does the file watcher detect changes?",
|
|
11606
|
+
kind: "natural",
|
|
11607
|
+
expected_files: ["packages/engine/src/index/watcher.ts"],
|
|
11608
|
+
expected_entry: "packages/engine/src/index/watcher.ts"
|
|
11609
|
+
},
|
|
11610
|
+
{
|
|
11611
|
+
id: "nat-12",
|
|
11612
|
+
query: "How is the database schema defined?",
|
|
11613
|
+
kind: "natural",
|
|
11614
|
+
expected_files: ["packages/engine/src/db/schema.ts", "packages/engine/src/db/connection.ts"],
|
|
11615
|
+
expected_entry: "packages/engine/src/db/schema.ts"
|
|
11616
|
+
},
|
|
11617
|
+
// --- symbol (5) ---
|
|
11618
|
+
{
|
|
11619
|
+
id: "sym-01",
|
|
11620
|
+
query: "buildContext",
|
|
11621
|
+
kind: "symbol",
|
|
11622
|
+
expected_files: ["packages/engine/src/context/context.ts"],
|
|
11623
|
+
expected_entry: "packages/engine/src/context/context.ts"
|
|
11624
|
+
},
|
|
11625
|
+
{
|
|
11626
|
+
id: "sym-02",
|
|
11627
|
+
query: "interpretQuery",
|
|
11628
|
+
kind: "symbol",
|
|
11629
|
+
expected_files: ["packages/engine/src/context/query-interpreter.ts"],
|
|
11630
|
+
expected_entry: "packages/engine/src/context/query-interpreter.ts"
|
|
11631
|
+
},
|
|
11632
|
+
{
|
|
11633
|
+
id: "sym-03",
|
|
11634
|
+
query: "runIndex",
|
|
11635
|
+
kind: "symbol",
|
|
11636
|
+
expected_files: ["packages/engine/src/index/engine.ts"],
|
|
11637
|
+
expected_entry: "packages/engine/src/index/engine.ts"
|
|
11638
|
+
},
|
|
11639
|
+
{
|
|
11640
|
+
id: "sym-04",
|
|
11641
|
+
query: "extractFileMetadata",
|
|
11642
|
+
kind: "symbol",
|
|
11643
|
+
expected_files: ["packages/engine/src/index/extract-metadata.ts"],
|
|
11644
|
+
expected_entry: "packages/engine/src/index/extract-metadata.ts"
|
|
11645
|
+
},
|
|
11646
|
+
{
|
|
11647
|
+
id: "sym-05",
|
|
11648
|
+
query: "formatContextPack",
|
|
11649
|
+
kind: "symbol",
|
|
11650
|
+
expected_files: ["packages/engine/src/context/formatter.ts"],
|
|
11651
|
+
expected_entry: "packages/engine/src/context/formatter.ts"
|
|
11652
|
+
},
|
|
11653
|
+
// --- error_message (3) ---
|
|
11654
|
+
{
|
|
11655
|
+
id: "err-01",
|
|
11656
|
+
query: "Error: repo not found",
|
|
11657
|
+
kind: "error_message",
|
|
11658
|
+
expected_files: ["packages/engine/src/repo/repo.ts", "apps/daemon/src/server.ts"],
|
|
11659
|
+
expected_entry: "packages/engine/src/repo/repo.ts"
|
|
11660
|
+
},
|
|
11661
|
+
{
|
|
11662
|
+
id: "err-02",
|
|
11663
|
+
query: "LENS daemon is not running",
|
|
11664
|
+
kind: "error_message",
|
|
11665
|
+
expected_files: ["packages/cli/src/util/client.ts"],
|
|
11666
|
+
expected_entry: "packages/cli/src/util/client.ts"
|
|
11667
|
+
},
|
|
11668
|
+
{
|
|
11669
|
+
id: "err-03",
|
|
11670
|
+
query: "Context generation failed",
|
|
11671
|
+
kind: "error_message",
|
|
11672
|
+
expected_files: ["packages/engine/src/context/context.ts"],
|
|
11673
|
+
expected_entry: "packages/engine/src/context/context.ts"
|
|
11674
|
+
}
|
|
10610
11675
|
];
|
|
10611
|
-
DEBOUNCE_MS = 500;
|
|
10612
11676
|
init_queries();
|
|
10613
|
-
|
|
11677
|
+
MAX_API_CALLS = 2e3;
|
|
11678
|
+
POOL_SIZE = 32;
|
|
11679
|
+
MAX_BATCH_TOKENS = 1e5;
|
|
11680
|
+
CHARS_PER_TOKEN = 3;
|
|
11681
|
+
init_queries();
|
|
11682
|
+
PURPOSE_BATCH_LIMIT = 200;
|
|
11683
|
+
PURPOSE_CONCURRENCY = 10;
|
|
11684
|
+
init_queries();
|
|
11685
|
+
TERM_BATCH_SIZE = 32;
|
|
11686
|
+
SIMILARITY_THRESHOLD = 0.75;
|
|
11687
|
+
MAX_TERMS = 1500;
|
|
11688
|
+
MAX_CLUSTERS = 200;
|
|
11689
|
+
MAX_CLUSTER_SIZE = 12;
|
|
11690
|
+
STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
10614
11691
|
"the",
|
|
10615
11692
|
"a",
|
|
10616
11693
|
"an",
|
|
@@ -10707,86 +11784,37 @@ var init_dist = __esm({
|
|
|
10707
11784
|
"src",
|
|
10708
11785
|
"lib",
|
|
10709
11786
|
"app",
|
|
11787
|
+
"test",
|
|
10710
11788
|
"spec",
|
|
10711
11789
|
"mock",
|
|
10712
|
-
"
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
".md",
|
|
10716
|
-
".json",
|
|
10717
|
-
".yaml",
|
|
10718
|
-
".yml",
|
|
10719
|
-
".toml",
|
|
10720
|
-
".lock",
|
|
10721
|
-
".sql",
|
|
10722
|
-
".txt",
|
|
10723
|
-
".csv",
|
|
10724
|
-
".psd",
|
|
10725
|
-
".png",
|
|
10726
|
-
".jpg",
|
|
10727
|
-
".jpeg",
|
|
10728
|
-
".gif",
|
|
10729
|
-
".svg",
|
|
10730
|
-
".ico",
|
|
10731
|
-
".woff",
|
|
10732
|
-
".woff2",
|
|
10733
|
-
".ttf",
|
|
10734
|
-
".eot",
|
|
10735
|
-
".xml",
|
|
10736
|
-
".axml",
|
|
10737
|
-
".resx",
|
|
10738
|
-
".config"
|
|
11790
|
+
"model",
|
|
11791
|
+
"module",
|
|
11792
|
+
"component"
|
|
10739
11793
|
]);
|
|
10740
|
-
NOISE_PATHS = [
|
|
10741
|
-
".gitignore",
|
|
10742
|
-
".github/",
|
|
10743
|
-
".vscode/",
|
|
10744
|
-
".idea/",
|
|
10745
|
-
"node_modules/",
|
|
10746
|
-
"dist/",
|
|
10747
|
-
"build/",
|
|
10748
|
-
"vendor/",
|
|
10749
|
-
"vendors/",
|
|
10750
|
-
"/scripts/",
|
|
10751
|
-
"areas/helppage/",
|
|
10752
|
-
"packages.config",
|
|
10753
|
-
"resources/drawable/",
|
|
10754
|
-
"resources/layout/",
|
|
10755
|
-
"resources/values/",
|
|
10756
|
-
"wwwroot/lib/",
|
|
10757
|
-
"wwwroot/css/",
|
|
10758
|
-
"wwwroot/js/lib/",
|
|
10759
|
-
".min.js",
|
|
10760
|
-
".min.css",
|
|
10761
|
-
".designer.cs",
|
|
10762
|
-
"assemblyinfo.cs"
|
|
10763
|
-
];
|
|
10764
|
-
CONCEPT_SYNONYMS = {
|
|
10765
|
-
error: ["interceptor", "exception", "catch", "middleware", "handler"],
|
|
10766
|
-
errors: ["interceptor", "exception", "catch", "middleware", "handler"],
|
|
10767
|
-
retry: ["interceptor", "backoff", "retry", "queue"],
|
|
10768
|
-
retries: ["interceptor", "backoff", "retry", "queue"],
|
|
10769
|
-
auth: ["interceptor", "token", "login", "guard", "middleware", "session"],
|
|
10770
|
-
authentication: ["interceptor", "token", "login", "guard", "middleware"],
|
|
10771
|
-
authorization: ["guard", "role", "permission", "policy"],
|
|
10772
|
-
cache: ["invalidat", "ttl", "expire", "store", "memo"],
|
|
10773
|
-
caching: ["invalidat", "ttl", "expire", "store", "memo"],
|
|
10774
|
-
logging: ["logger", "trace", "monitor", "telemetry"],
|
|
10775
|
-
validation: ["validator", "schema", "sanitiz", "constraint"],
|
|
10776
|
-
notification: ["email", "alert", "signal", "push", "webhook"],
|
|
10777
|
-
upload: ["multipart", "stream", "blob", "attachment", "photo"],
|
|
10778
|
-
performance: ["optimize", "cache", "lazy", "batch", "throttle"],
|
|
10779
|
-
security: ["auth", "token", "encrypt", "cors", "csrf", "sanitiz"],
|
|
10780
|
-
testing: ["spec", "mock", "fixture", "assert", "stub"],
|
|
10781
|
-
deploy: ["pipeline", "docker", "build", "release", "ci-cd"],
|
|
10782
|
-
database: ["repository", "migration", "query", "schema", "orm"],
|
|
10783
|
-
api: ["controller", "endpoint", "route", "middleware", "interceptor"]
|
|
10784
|
-
};
|
|
10785
|
-
init_queries();
|
|
10786
11794
|
init_queries();
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
11795
|
+
watchers = /* @__PURE__ */ new Map();
|
|
11796
|
+
debounceTimers = /* @__PURE__ */ new Map();
|
|
11797
|
+
IGNORED = [
|
|
11798
|
+
/(^|[/\\])\../,
|
|
11799
|
+
"**/node_modules/**",
|
|
11800
|
+
"**/.git/**",
|
|
11801
|
+
"**/dist/**",
|
|
11802
|
+
"**/build/**",
|
|
11803
|
+
"**/__pycache__/**",
|
|
11804
|
+
"**/target/**",
|
|
11805
|
+
"**/.next/**",
|
|
11806
|
+
"**/.nuxt/**",
|
|
11807
|
+
"**/coverage/**",
|
|
11808
|
+
"**/.turbo/**",
|
|
11809
|
+
"**/out/**",
|
|
11810
|
+
"**/.output/**",
|
|
11811
|
+
"**/*.log",
|
|
11812
|
+
"**/*.lock",
|
|
11813
|
+
"**/pnpm-lock.yaml",
|
|
11814
|
+
"**/package-lock.json",
|
|
11815
|
+
"**/yarn.lock"
|
|
11816
|
+
];
|
|
11817
|
+
DEBOUNCE_MS = 500;
|
|
10790
11818
|
init_queries();
|
|
10791
11819
|
RequestTrace = class {
|
|
10792
11820
|
steps = [];
|
|
@@ -10822,10 +11850,10 @@ var init_dist = __esm({
|
|
|
10822
11850
|
});
|
|
10823
11851
|
|
|
10824
11852
|
// apps/daemon/src/config.ts
|
|
10825
|
-
import { readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
10826
|
-
import { join as join4, dirname as dirname3 } from "path";
|
|
10827
|
-
import { homedir as homedir2 } from "os";
|
|
10828
11853
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11854
|
+
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
11855
|
+
import { homedir as homedir2 } from "os";
|
|
11856
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
10829
11857
|
function readConfig() {
|
|
10830
11858
|
try {
|
|
10831
11859
|
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
@@ -10876,31 +11904,45 @@ var cloud_capabilities_exports = {};
|
|
|
10876
11904
|
__export(cloud_capabilities_exports, {
|
|
10877
11905
|
createCloudCapabilities: () => createCloudCapabilities
|
|
10878
11906
|
});
|
|
10879
|
-
function createCloudCapabilities(
|
|
11907
|
+
function createCloudCapabilities(getKey, refreshKey, trackUsage, logRequest) {
|
|
10880
11908
|
const CLOUD_API_URL = getCloudUrl();
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
"
|
|
10884
|
-
|
|
11909
|
+
async function cloudFetch(path, reqBody) {
|
|
11910
|
+
const apiKey = await getKey();
|
|
11911
|
+
if (!apiKey) throw new Error("No API key available");
|
|
11912
|
+
const start = performance.now();
|
|
11913
|
+
let res = await fetch(`${CLOUD_API_URL}${path}`, {
|
|
11914
|
+
method: "POST",
|
|
11915
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
11916
|
+
body: reqBody
|
|
11917
|
+
});
|
|
11918
|
+
let resText = await res.text();
|
|
11919
|
+
let duration3 = Math.round(performance.now() - start);
|
|
11920
|
+
logRequest?.("POST", path, res.status, duration3, "cloud", reqBody, resText);
|
|
11921
|
+
if (res.status === 401) {
|
|
11922
|
+
const newKey = await refreshKey();
|
|
11923
|
+
if (newKey) {
|
|
11924
|
+
const retryStart = performance.now();
|
|
11925
|
+
res = await fetch(`${CLOUD_API_URL}${path}`, {
|
|
11926
|
+
method: "POST",
|
|
11927
|
+
headers: { Authorization: `Bearer ${newKey}`, "Content-Type": "application/json" },
|
|
11928
|
+
body: reqBody
|
|
11929
|
+
});
|
|
11930
|
+
resText = await res.text();
|
|
11931
|
+
duration3 = Math.round(performance.now() - retryStart);
|
|
11932
|
+
logRequest?.("POST", path, res.status, duration3, "cloud-retry", reqBody, resText);
|
|
11933
|
+
}
|
|
11934
|
+
}
|
|
11935
|
+
return { res, resText };
|
|
11936
|
+
}
|
|
10885
11937
|
return {
|
|
10886
11938
|
async embedTexts(texts, isQuery) {
|
|
10887
|
-
const start = performance.now();
|
|
10888
11939
|
const reqBody = JSON.stringify({
|
|
10889
11940
|
input: texts,
|
|
10890
11941
|
model: "voyage-code-3",
|
|
10891
11942
|
input_type: isQuery ? "query" : "document"
|
|
10892
11943
|
});
|
|
10893
|
-
const res = await
|
|
10894
|
-
|
|
10895
|
-
headers,
|
|
10896
|
-
body: reqBody
|
|
10897
|
-
});
|
|
10898
|
-
const resText = await res.text();
|
|
10899
|
-
const duration3 = Math.round(performance.now() - start);
|
|
10900
|
-
logRequest?.("POST", "/api/proxy/embed", res.status, duration3, "cloud", reqBody, resText);
|
|
10901
|
-
if (!res.ok) {
|
|
10902
|
-
throw new Error(`Cloud embed failed (${res.status}): ${resText}`);
|
|
10903
|
-
}
|
|
11944
|
+
const { res, resText } = await cloudFetch("/api/proxy/embed", reqBody);
|
|
11945
|
+
if (!res.ok) throw new Error(`Cloud embed failed (${res.status}): ${resText}`);
|
|
10904
11946
|
const data = JSON.parse(resText);
|
|
10905
11947
|
trackUsage?.("embedding_requests");
|
|
10906
11948
|
trackUsage?.("embedding_chunks", texts.length);
|
|
@@ -10914,25 +11956,18 @@ function createCloudCapabilities(apiKey, trackUsage, logRequest) {
|
|
|
10914
11956
|
"",
|
|
10915
11957
|
content.slice(0, 2e3)
|
|
10916
11958
|
].filter(Boolean).join("\n");
|
|
10917
|
-
const start = performance.now();
|
|
10918
11959
|
const reqBody = JSON.stringify({
|
|
10919
11960
|
messages: [
|
|
10920
|
-
{
|
|
11961
|
+
{
|
|
11962
|
+
role: "system",
|
|
11963
|
+
content: "You are a code analyst. Output ONLY a 1-sentence purpose summary for the given file. No preamble."
|
|
11964
|
+
},
|
|
10921
11965
|
{ role: "user", content: prompt }
|
|
10922
11966
|
],
|
|
10923
11967
|
max_tokens: 128
|
|
10924
11968
|
});
|
|
10925
|
-
const res = await
|
|
10926
|
-
|
|
10927
|
-
headers,
|
|
10928
|
-
body: reqBody
|
|
10929
|
-
});
|
|
10930
|
-
const resText = await res.text();
|
|
10931
|
-
const duration3 = Math.round(performance.now() - start);
|
|
10932
|
-
logRequest?.("POST", "/api/proxy/chat", res.status, duration3, "cloud", reqBody, resText);
|
|
10933
|
-
if (!res.ok) {
|
|
10934
|
-
throw new Error(`Cloud purpose failed (${res.status}): ${resText}`);
|
|
10935
|
-
}
|
|
11969
|
+
const { res, resText } = await cloudFetch("/api/proxy/chat", reqBody);
|
|
11970
|
+
if (!res.ok) throw new Error(`Cloud purpose failed (${res.status}): ${resText}`);
|
|
10936
11971
|
const data = JSON.parse(resText);
|
|
10937
11972
|
trackUsage?.("purpose_requests");
|
|
10938
11973
|
return data.choices?.[0]?.message?.content?.trim() ?? "";
|
|
@@ -15655,7 +16690,7 @@ function flattenError(error3, mapper = (issue2) => issue2.message) {
|
|
|
15655
16690
|
}
|
|
15656
16691
|
return { formErrors, fieldErrors };
|
|
15657
16692
|
}
|
|
15658
|
-
function
|
|
16693
|
+
function formatError2(error3, _mapper) {
|
|
15659
16694
|
const mapper = _mapper || function(issue2) {
|
|
15660
16695
|
return issue2.message;
|
|
15661
16696
|
};
|
|
@@ -19301,7 +20336,7 @@ var init_errors4 = __esm({
|
|
|
19301
20336
|
inst.name = "ZodError";
|
|
19302
20337
|
Object.defineProperties(inst, {
|
|
19303
20338
|
format: {
|
|
19304
|
-
value: (mapper) =>
|
|
20339
|
+
value: (mapper) => formatError2(inst, mapper)
|
|
19305
20340
|
// enumerable: false,
|
|
19306
20341
|
},
|
|
19307
20342
|
flatten: {
|
|
@@ -32382,7 +33417,17 @@ __export(mcp_exports, {
|
|
|
32382
33417
|
});
|
|
32383
33418
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
32384
33419
|
function logMcp(method, path, status, duration3, reqBody, resSize, resBody, trace) {
|
|
32385
|
-
getRawDb().prepare(LOG_SQL).run(
|
|
33420
|
+
getRawDb().prepare(LOG_SQL).run(
|
|
33421
|
+
randomUUID3(),
|
|
33422
|
+
method,
|
|
33423
|
+
path,
|
|
33424
|
+
status,
|
|
33425
|
+
duration3,
|
|
33426
|
+
reqBody ?? null,
|
|
33427
|
+
resSize ?? null,
|
|
33428
|
+
resBody ?? null,
|
|
33429
|
+
trace ?? null
|
|
33430
|
+
);
|
|
32386
33431
|
}
|
|
32387
33432
|
function text2(s) {
|
|
32388
33433
|
return { content: [{ type: "text", text: s }] };
|
|
@@ -32401,7 +33446,7 @@ function createMcpServer(db, caps) {
|
|
|
32401
33446
|
server.registerTool(
|
|
32402
33447
|
"get_context",
|
|
32403
33448
|
{
|
|
32404
|
-
description: "
|
|
33449
|
+
description: "Returns ranked files with import dependency chains and git co-change clusters for a development goal. Surfaces structural relationships (imports, co-changes, exports) that keyword search alone won't find. Use when exploring architecture, tracing data flow, assessing change impact, or navigating an unfamiliar codebase. Skip for simple lookups where you already know the file path.",
|
|
32405
33450
|
inputSchema: { repo_path: external_exports.string(), goal: external_exports.string() }
|
|
32406
33451
|
},
|
|
32407
33452
|
async ({ repo_path, goal }) => {
|
|
@@ -32416,11 +33461,29 @@ function createMcpServer(db, caps) {
|
|
|
32416
33461
|
const result = await buildContext(db, repoId, goal, caps, trace, { useEmbeddings });
|
|
32417
33462
|
const duration3 = Math.round(performance.now() - start);
|
|
32418
33463
|
const reqBody = JSON.stringify({ repo_path, goal });
|
|
32419
|
-
logMcp(
|
|
33464
|
+
logMcp(
|
|
33465
|
+
"MCP",
|
|
33466
|
+
"/tool/get_context",
|
|
33467
|
+
200,
|
|
33468
|
+
duration3,
|
|
33469
|
+
reqBody,
|
|
33470
|
+
result.context_pack.length,
|
|
33471
|
+
result.context_pack,
|
|
33472
|
+
trace.serialize()
|
|
33473
|
+
);
|
|
32420
33474
|
return text2(result.context_pack);
|
|
32421
33475
|
} catch (e) {
|
|
32422
33476
|
const duration3 = Math.round(performance.now() - start);
|
|
32423
|
-
logMcp(
|
|
33477
|
+
logMcp(
|
|
33478
|
+
"MCP",
|
|
33479
|
+
"/tool/get_context",
|
|
33480
|
+
500,
|
|
33481
|
+
duration3,
|
|
33482
|
+
JSON.stringify({ repo_path, goal }),
|
|
33483
|
+
void 0,
|
|
33484
|
+
e.message,
|
|
33485
|
+
trace.serialize()
|
|
33486
|
+
);
|
|
32424
33487
|
return error2(e.message);
|
|
32425
33488
|
}
|
|
32426
33489
|
}
|
|
@@ -32458,7 +33521,7 @@ function createMcpServer(db, caps) {
|
|
|
32458
33521
|
const repo = repoQueries.getByPath(db, repo_path);
|
|
32459
33522
|
if (!repo) {
|
|
32460
33523
|
logMcp("MCP", "/tool/get_status", 404, Math.round(performance.now() - start), JSON.stringify({ repo_path }));
|
|
32461
|
-
return error2(
|
|
33524
|
+
return error2(`repo not found at ${repo_path}`);
|
|
32462
33525
|
}
|
|
32463
33526
|
const status = await getRepoStatus(db, repo.id);
|
|
32464
33527
|
const body = JSON.stringify(status, null, 2);
|
|
@@ -32488,11 +33551,29 @@ function createMcpServer(db, caps) {
|
|
|
32488
33551
|
const result = await runIndex(db, repoId, caps, force ?? false, void 0, trace);
|
|
32489
33552
|
const body = JSON.stringify(result, null, 2);
|
|
32490
33553
|
const duration3 = Math.round(performance.now() - start);
|
|
32491
|
-
logMcp(
|
|
33554
|
+
logMcp(
|
|
33555
|
+
"MCP",
|
|
33556
|
+
"/tool/index_repo",
|
|
33557
|
+
200,
|
|
33558
|
+
duration3,
|
|
33559
|
+
JSON.stringify({ repo_path, force }),
|
|
33560
|
+
body.length,
|
|
33561
|
+
body,
|
|
33562
|
+
trace.serialize()
|
|
33563
|
+
);
|
|
32492
33564
|
return text2(body);
|
|
32493
33565
|
} catch (e) {
|
|
32494
33566
|
const duration3 = Math.round(performance.now() - start);
|
|
32495
|
-
logMcp(
|
|
33567
|
+
logMcp(
|
|
33568
|
+
"MCP",
|
|
33569
|
+
"/tool/index_repo",
|
|
33570
|
+
500,
|
|
33571
|
+
duration3,
|
|
33572
|
+
JSON.stringify({ repo_path, force }),
|
|
33573
|
+
void 0,
|
|
33574
|
+
e.message,
|
|
33575
|
+
trace.serialize()
|
|
33576
|
+
);
|
|
32496
33577
|
return error2(e.message);
|
|
32497
33578
|
}
|
|
32498
33579
|
}
|
|
@@ -34816,9 +35897,9 @@ var server_exports = {};
|
|
|
34816
35897
|
__export(server_exports, {
|
|
34817
35898
|
createApp: () => createApp
|
|
34818
35899
|
});
|
|
34819
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2,
|
|
34820
|
-
import { join as join5, extname as extname3 } from "path";
|
|
35900
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync, watch as watch2, writeFileSync as writeFileSync2 } from "fs";
|
|
34821
35901
|
import { homedir as homedir3 } from "os";
|
|
35902
|
+
import { extname as extname3, join as join5 } from "path";
|
|
34822
35903
|
function getDbSizeMb() {
|
|
34823
35904
|
const dbPath = join5(homedir3(), ".lens", "data.db");
|
|
34824
35905
|
try {
|
|
@@ -34849,7 +35930,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34849
35930
|
const app = new Hono2();
|
|
34850
35931
|
const telemetryEnabled = isTelemetryEnabled();
|
|
34851
35932
|
setTelemetryEnabled(telemetryEnabled);
|
|
34852
|
-
const {
|
|
35933
|
+
const { first_run } = ensureTelemetryId();
|
|
34853
35934
|
if (first_run) {
|
|
34854
35935
|
console.log(
|
|
34855
35936
|
"[LENS] Anonymous telemetry is enabled. No PII, no repo paths, no code.\n[LENS] Opt out: lens config set telemetry false"
|
|
@@ -34925,18 +36006,6 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34925
36006
|
const trace = c.get("trace");
|
|
34926
36007
|
const { root_path, name, remote_url } = await c.req.json();
|
|
34927
36008
|
if (!root_path) return c.json({ error: "root_path required" }, 400);
|
|
34928
|
-
trace.step("quotaCheck");
|
|
34929
|
-
const currentRepos = listRepos(db).length;
|
|
34930
|
-
const maxRepos = quotaCache?.quota?.maxRepos ?? 50;
|
|
34931
|
-
trace.end("quotaCheck", `${currentRepos}/${maxRepos}`);
|
|
34932
|
-
if (currentRepos >= maxRepos) {
|
|
34933
|
-
return c.json({
|
|
34934
|
-
error: "Repo limit reached",
|
|
34935
|
-
current: currentRepos,
|
|
34936
|
-
limit: maxRepos,
|
|
34937
|
-
plan: quotaCache?.plan ?? "unknown"
|
|
34938
|
-
}, 429);
|
|
34939
|
-
}
|
|
34940
36009
|
trace.step("registerRepo");
|
|
34941
36010
|
const result = registerRepo(db, root_path, name, remote_url);
|
|
34942
36011
|
trace.end("registerRepo", result.created ? "created" : "existing");
|
|
@@ -34952,37 +36021,64 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
34952
36021
|
const tasks = [];
|
|
34953
36022
|
if (repo?.enable_vocab_clusters && quotaRemaining("embeddingChunks") > 0) {
|
|
34954
36023
|
bg.step("vocabClusters");
|
|
34955
|
-
tasks.push(
|
|
34956
|
-
|
|
34957
|
-
|
|
34958
|
-
|
|
36024
|
+
tasks.push(
|
|
36025
|
+
buildVocabClusters(db, result.repo_id, caps).then(() => {
|
|
36026
|
+
bg.end("vocabClusters");
|
|
36027
|
+
results.vocabClusters = "done";
|
|
36028
|
+
})
|
|
36029
|
+
);
|
|
34959
36030
|
} else {
|
|
34960
36031
|
bg.add("vocabClusters", 0, !repo?.enable_vocab_clusters ? "disabled" : "quota exceeded");
|
|
34961
36032
|
}
|
|
34962
36033
|
if (repo?.enable_embeddings && quotaRemaining("embeddingChunks") > 0) {
|
|
34963
36034
|
bg.step("embeddings");
|
|
34964
|
-
tasks.push(
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
36035
|
+
tasks.push(
|
|
36036
|
+
ensureEmbedded(db, result.repo_id, caps).then((er) => {
|
|
36037
|
+
bg.end("embeddings", `${er.embedded_count} embedded`);
|
|
36038
|
+
results.embeddings = er;
|
|
36039
|
+
})
|
|
36040
|
+
);
|
|
34968
36041
|
} else {
|
|
34969
36042
|
bg.add("embeddings", 0, !repo?.enable_embeddings ? "disabled" : "quota exceeded");
|
|
34970
36043
|
}
|
|
34971
36044
|
if (repo?.enable_summaries && quotaRemaining("purposeRequests") > 0) {
|
|
34972
36045
|
bg.step("purpose");
|
|
34973
|
-
tasks.push(
|
|
34974
|
-
|
|
34975
|
-
|
|
34976
|
-
|
|
36046
|
+
tasks.push(
|
|
36047
|
+
enrichPurpose(db, result.repo_id, caps).then((pr) => {
|
|
36048
|
+
bg.end("purpose", `${pr.enriched} enriched`);
|
|
36049
|
+
results.purpose = pr;
|
|
36050
|
+
})
|
|
36051
|
+
);
|
|
34977
36052
|
} else {
|
|
34978
36053
|
bg.add("purpose", 0, !repo?.enable_summaries ? "disabled" : "quota exceeded");
|
|
34979
36054
|
}
|
|
34980
36055
|
await Promise.all(tasks);
|
|
34981
36056
|
const resBody = JSON.stringify(results);
|
|
34982
36057
|
const bgDuration = Math.round(performance.now() - bgStart);
|
|
34983
|
-
logQueries.insert(
|
|
36058
|
+
logQueries.insert(
|
|
36059
|
+
db,
|
|
36060
|
+
"BG",
|
|
36061
|
+
`/enrichment/${result.repo_id}`,
|
|
36062
|
+
200,
|
|
36063
|
+
bgDuration,
|
|
36064
|
+
"system",
|
|
36065
|
+
reqBody,
|
|
36066
|
+
resBody.length,
|
|
36067
|
+
resBody,
|
|
36068
|
+
bg.serialize()
|
|
36069
|
+
);
|
|
34984
36070
|
}).catch((e) => {
|
|
34985
|
-
logQueries.insert(
|
|
36071
|
+
logQueries.insert(
|
|
36072
|
+
db,
|
|
36073
|
+
"BG",
|
|
36074
|
+
`/enrichment/${result.repo_id}`,
|
|
36075
|
+
500,
|
|
36076
|
+
0,
|
|
36077
|
+
"system",
|
|
36078
|
+
JSON.stringify({ repo_id: result.repo_id }),
|
|
36079
|
+
void 0,
|
|
36080
|
+
String(e)
|
|
36081
|
+
);
|
|
34986
36082
|
});
|
|
34987
36083
|
}
|
|
34988
36084
|
trace.step("startWatcher");
|
|
@@ -35099,6 +36195,20 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35099
36195
|
return c.json({ error: e.message }, 500);
|
|
35100
36196
|
}
|
|
35101
36197
|
});
|
|
36198
|
+
trackRoute("POST", "/eval/run");
|
|
36199
|
+
app.post("/eval/run", async (c) => {
|
|
36200
|
+
try {
|
|
36201
|
+
const trace = c.get("trace");
|
|
36202
|
+
const { repo_id, filter_kind } = await c.req.json();
|
|
36203
|
+
if (!repo_id) return c.json({ error: "repo_id required" }, 400);
|
|
36204
|
+
trace.step("runEval");
|
|
36205
|
+
const summary = await runEval(db, repo_id, { filterKind: filter_kind });
|
|
36206
|
+
trace.end("runEval", `${summary.total} queries`);
|
|
36207
|
+
return c.json(summary);
|
|
36208
|
+
} catch (e) {
|
|
36209
|
+
return c.json({ error: e.message }, 500);
|
|
36210
|
+
}
|
|
36211
|
+
});
|
|
35102
36212
|
trackRoute("POST", "/index/run");
|
|
35103
36213
|
app.post("/index/run", async (c) => {
|
|
35104
36214
|
try {
|
|
@@ -35114,35 +36224,52 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35114
36224
|
const tasks = [];
|
|
35115
36225
|
if (repo?.enable_vocab_clusters && quotaRemaining("embeddingChunks") > 0) {
|
|
35116
36226
|
bg.step("vocabClusters");
|
|
35117
|
-
tasks.push(
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
|
|
36227
|
+
tasks.push(
|
|
36228
|
+
buildVocabClusters(db, repo_id, caps).then(() => {
|
|
36229
|
+
bg.end("vocabClusters");
|
|
36230
|
+
results.vocabClusters = "done";
|
|
36231
|
+
})
|
|
36232
|
+
);
|
|
35121
36233
|
} else {
|
|
35122
36234
|
bg.add("vocabClusters", 0, !repo?.enable_vocab_clusters ? "disabled" : "quota exceeded");
|
|
35123
36235
|
}
|
|
35124
36236
|
if (repo?.enable_embeddings && quotaRemaining("embeddingChunks") > 0) {
|
|
35125
36237
|
bg.step("embeddings");
|
|
35126
|
-
tasks.push(
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
36238
|
+
tasks.push(
|
|
36239
|
+
ensureEmbedded(db, repo_id, caps).then((er) => {
|
|
36240
|
+
bg.end("embeddings", `${er.embedded_count} embedded`);
|
|
36241
|
+
results.embeddings = er;
|
|
36242
|
+
})
|
|
36243
|
+
);
|
|
35130
36244
|
} else {
|
|
35131
36245
|
bg.add("embeddings", 0, !repo?.enable_embeddings ? "disabled" : "quota exceeded");
|
|
35132
36246
|
}
|
|
35133
36247
|
if (repo?.enable_summaries && quotaRemaining("purposeRequests") > 0) {
|
|
35134
36248
|
bg.step("purpose");
|
|
35135
|
-
tasks.push(
|
|
35136
|
-
|
|
35137
|
-
|
|
35138
|
-
|
|
36249
|
+
tasks.push(
|
|
36250
|
+
enrichPurpose(db, repo_id, caps).then((pr) => {
|
|
36251
|
+
bg.end("purpose", `${pr.enriched} enriched`);
|
|
36252
|
+
results.purpose = pr;
|
|
36253
|
+
})
|
|
36254
|
+
);
|
|
35139
36255
|
} else {
|
|
35140
36256
|
bg.add("purpose", 0, !repo?.enable_summaries ? "disabled" : "quota exceeded");
|
|
35141
36257
|
}
|
|
35142
36258
|
Promise.all(tasks).then(() => {
|
|
35143
36259
|
const resBody = JSON.stringify(results);
|
|
35144
36260
|
const bgDuration = Math.round(performance.now() - bgStart);
|
|
35145
|
-
logQueries.insert(
|
|
36261
|
+
logQueries.insert(
|
|
36262
|
+
db,
|
|
36263
|
+
"BG",
|
|
36264
|
+
`/enrichment/${repo_id}`,
|
|
36265
|
+
200,
|
|
36266
|
+
bgDuration,
|
|
36267
|
+
"system",
|
|
36268
|
+
reqBody,
|
|
36269
|
+
resBody.length,
|
|
36270
|
+
resBody,
|
|
36271
|
+
bg.serialize()
|
|
36272
|
+
);
|
|
35146
36273
|
}).catch((e) => {
|
|
35147
36274
|
logQueries.insert(db, "BG", `/enrichment/${repo_id}`, 500, 0, "system", reqBody, void 0, String(e));
|
|
35148
36275
|
});
|
|
@@ -35288,24 +36415,34 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35288
36415
|
expires_at: Math.floor(Date.now() / 1e3) + (data.expires_in ?? 3600)
|
|
35289
36416
|
};
|
|
35290
36417
|
}
|
|
35291
|
-
const
|
|
36418
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
35292
36419
|
const encoder = new TextEncoder();
|
|
35293
|
-
function
|
|
35294
|
-
|
|
36420
|
+
function emitSSE(type) {
|
|
36421
|
+
const payload = encoder.encode(`data: ${JSON.stringify({ type })}
|
|
36422
|
+
|
|
36423
|
+
`);
|
|
36424
|
+
for (const ctrl of sseClients) {
|
|
35295
36425
|
try {
|
|
35296
|
-
ctrl.enqueue(
|
|
36426
|
+
ctrl.enqueue(payload);
|
|
35297
36427
|
} catch {
|
|
35298
|
-
|
|
36428
|
+
sseClients.delete(ctrl);
|
|
35299
36429
|
}
|
|
35300
36430
|
}
|
|
35301
36431
|
}
|
|
35302
|
-
|
|
35303
|
-
|
|
36432
|
+
function emitRepoEvent() {
|
|
36433
|
+
emitSSE("repo");
|
|
36434
|
+
}
|
|
36435
|
+
function emitAuthEvent() {
|
|
36436
|
+
emitSSE("auth");
|
|
36437
|
+
}
|
|
36438
|
+
trackRoute("GET", "/api/events");
|
|
36439
|
+
app.get("/api/events", (_c) => {
|
|
35304
36440
|
const stream = new ReadableStream({
|
|
35305
36441
|
start(ctrl) {
|
|
35306
|
-
|
|
36442
|
+
sseClients.add(ctrl);
|
|
35307
36443
|
},
|
|
35308
|
-
cancel() {
|
|
36444
|
+
cancel(ctrl) {
|
|
36445
|
+
sseClients.delete(ctrl);
|
|
35309
36446
|
}
|
|
35310
36447
|
});
|
|
35311
36448
|
return new Response(stream, {
|
|
@@ -35316,17 +36453,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35316
36453
|
}
|
|
35317
36454
|
});
|
|
35318
36455
|
});
|
|
35319
|
-
const authClients = /* @__PURE__ */ new Set();
|
|
35320
36456
|
const authDir = join5(homedir3(), ".lens");
|
|
35321
|
-
function emitAuthEvent() {
|
|
35322
|
-
for (const ctrl of authClients) {
|
|
35323
|
-
try {
|
|
35324
|
-
ctrl.enqueue(encoder.encode("data: auth-changed\n\n"));
|
|
35325
|
-
} catch {
|
|
35326
|
-
authClients.delete(ctrl);
|
|
35327
|
-
}
|
|
35328
|
-
}
|
|
35329
|
-
}
|
|
35330
36457
|
try {
|
|
35331
36458
|
watch2(authDir, (_, filename) => {
|
|
35332
36459
|
if (filename && filename !== "auth.json") return;
|
|
@@ -35334,23 +36461,6 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35334
36461
|
});
|
|
35335
36462
|
} catch {
|
|
35336
36463
|
}
|
|
35337
|
-
trackRoute("GET", "/api/auth/events");
|
|
35338
|
-
app.get("/api/auth/events", (c) => {
|
|
35339
|
-
const stream = new ReadableStream({
|
|
35340
|
-
start(ctrl) {
|
|
35341
|
-
authClients.add(ctrl);
|
|
35342
|
-
},
|
|
35343
|
-
cancel() {
|
|
35344
|
-
}
|
|
35345
|
-
});
|
|
35346
|
-
return new Response(stream, {
|
|
35347
|
-
headers: {
|
|
35348
|
-
"Content-Type": "text/event-stream",
|
|
35349
|
-
"Cache-Control": "no-cache",
|
|
35350
|
-
Connection: "keep-alive"
|
|
35351
|
-
}
|
|
35352
|
-
});
|
|
35353
|
-
});
|
|
35354
36464
|
trackRoute("POST", "/api/auth/notify");
|
|
35355
36465
|
app.post("/api/auth/notify", async (c) => {
|
|
35356
36466
|
await refreshQuotaCache();
|
|
@@ -35602,7 +36712,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35602
36712
|
const search = c.req.query("search") || void 0;
|
|
35603
36713
|
const meta = metadataQueries.getByRepo(db, id);
|
|
35604
36714
|
const raw2 = getRawDb();
|
|
35605
|
-
const chunkCounts = raw2.prepare(
|
|
36715
|
+
const chunkCounts = raw2.prepare(
|
|
36716
|
+
"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"
|
|
36717
|
+
).all(id);
|
|
35606
36718
|
const countMap = new Map(chunkCounts.map((r) => [r.path, r]));
|
|
35607
36719
|
let all = meta.map((m) => {
|
|
35608
36720
|
const counts = countMap.get(m.path);
|
|
@@ -35637,12 +36749,16 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35637
36749
|
const meta = allMeta.find((m) => m.path === filePath);
|
|
35638
36750
|
if (!meta) return c.json({ error: "file not found" }, 404);
|
|
35639
36751
|
const raw2 = getRawDb();
|
|
35640
|
-
const chunkRow = raw2.prepare(
|
|
36752
|
+
const chunkRow = raw2.prepare(
|
|
36753
|
+
"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 = ?"
|
|
36754
|
+
).get(id, filePath);
|
|
35641
36755
|
const allImports = importQueries.getBySources(db, id, [filePath]);
|
|
35642
36756
|
const allImporters = importQueries.getByTargets(db, id, [filePath]);
|
|
35643
36757
|
const gitStats = statsQueries.getByRepo(db, id);
|
|
35644
36758
|
const fileStat = gitStats.get(filePath);
|
|
35645
|
-
const cochangeRows = raw2.prepare(
|
|
36759
|
+
const cochangeRows = raw2.prepare(
|
|
36760
|
+
"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"
|
|
36761
|
+
).all(id, filePath, filePath);
|
|
35646
36762
|
const cochanges = cochangeRows.map((r) => ({
|
|
35647
36763
|
path: r.path_a === filePath ? r.path_b : r.path_a,
|
|
35648
36764
|
count: r.cochange_count
|
|
@@ -35682,7 +36798,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35682
36798
|
const raw2 = getRawDb();
|
|
35683
36799
|
const where = pathFilter ? "WHERE repo_id = ? AND path = ?" : "WHERE repo_id = ?";
|
|
35684
36800
|
const params = pathFilter ? [id, pathFilter] : [id];
|
|
35685
|
-
const rows = raw2.prepare(
|
|
36801
|
+
const rows = raw2.prepare(
|
|
36802
|
+
`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 ?`
|
|
36803
|
+
).all(...params, limit, offset);
|
|
35686
36804
|
const totalRow = raw2.prepare(`SELECT count(*) as count FROM chunks ${where}`).get(...params);
|
|
35687
36805
|
return c.json({
|
|
35688
36806
|
chunks: rows.map((r) => ({ ...r, has_embedding: r.has_embedding === 1 })),
|
|
@@ -35698,7 +36816,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35698
36816
|
const id = c.req.param("id");
|
|
35699
36817
|
const chunkId = c.req.param("chunkId");
|
|
35700
36818
|
const raw2 = getRawDb();
|
|
35701
|
-
const row = raw2.prepare(
|
|
36819
|
+
const row = raw2.prepare(
|
|
36820
|
+
"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 = ?"
|
|
36821
|
+
).get(chunkId, id);
|
|
35702
36822
|
if (!row) return c.json({ error: "chunk not found" }, 404);
|
|
35703
36823
|
return c.json({ ...row, has_embedding: row.has_embedding === 1 });
|
|
35704
36824
|
} catch (e) {
|
|
@@ -35739,7 +36859,9 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35739
36859
|
app.get("/api/dashboard/tables", (c) => {
|
|
35740
36860
|
try {
|
|
35741
36861
|
const raw2 = getRawDb();
|
|
35742
|
-
const tables = raw2.prepare(
|
|
36862
|
+
const tables = raw2.prepare(
|
|
36863
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle%' ORDER BY name"
|
|
36864
|
+
).all();
|
|
35743
36865
|
const result = tables.map((t) => {
|
|
35744
36866
|
const row = raw2.prepare(`SELECT count(*) as count FROM "${t.name}"`).get();
|
|
35745
36867
|
return { name: t.name, count: row.count };
|
|
@@ -35875,7 +36997,8 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35875
36997
|
if (apiKey) {
|
|
35876
36998
|
const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
|
|
35877
36999
|
caps = createCloudCapabilities2(
|
|
35878
|
-
|
|
37000
|
+
() => readApiKey(),
|
|
37001
|
+
provisionApiKey,
|
|
35879
37002
|
(counter, amount) => {
|
|
35880
37003
|
try {
|
|
35881
37004
|
usageQueries.increment(db, counter, amount);
|
|
@@ -35905,7 +37028,7 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35905
37028
|
caps = void 0;
|
|
35906
37029
|
}
|
|
35907
37030
|
} catch {
|
|
35908
|
-
quotaCache = { plan: "free", usage: {}, quota: {}, fetchedAt: Date.now() };
|
|
37031
|
+
quotaCache = { plan: "free", usage: {}, quota: {}, subscription: null, fetchedAt: Date.now() };
|
|
35909
37032
|
caps = void 0;
|
|
35910
37033
|
}
|
|
35911
37034
|
}
|
|
@@ -35934,7 +37057,10 @@ function createApp(db, dashboardDist, initialCaps, initialPlanData) {
|
|
|
35934
37057
|
signal: AbortSignal.timeout(1e4)
|
|
35935
37058
|
});
|
|
35936
37059
|
if (res.ok) {
|
|
35937
|
-
telemetryQueries.markSynced(
|
|
37060
|
+
telemetryQueries.markSynced(
|
|
37061
|
+
db,
|
|
37062
|
+
unsynced.map((e) => e.id)
|
|
37063
|
+
);
|
|
35938
37064
|
}
|
|
35939
37065
|
} catch {
|
|
35940
37066
|
}
|
|
@@ -35953,16 +37079,15 @@ var TEMPLATE, startedAt, MIME_TYPES, QUOTA_TTL, quotaCache;
|
|
|
35953
37079
|
var init_server3 = __esm({
|
|
35954
37080
|
"apps/daemon/src/server.ts"() {
|
|
35955
37081
|
"use strict";
|
|
37082
|
+
init_dist();
|
|
35956
37083
|
init_dist2();
|
|
35957
37084
|
init_config();
|
|
35958
|
-
init_dist();
|
|
35959
37085
|
TEMPLATE = `<!-- LENS \u2014 Repo Context Daemon -->
|
|
35960
37086
|
## LENS Context
|
|
35961
37087
|
|
|
35962
|
-
This repo is indexed by LENS.
|
|
35963
|
-
|
|
35964
|
-
lens context "<your goal>"
|
|
35965
|
-
\`\`\``;
|
|
37088
|
+
This repo is indexed by LENS. When exploring architecture, tracing data flow, or assessing change impact, call \`mcp__lens__get_context\` \u2014 it returns ranked files with import chains and co-change clusters that keyword search alone won't surface.
|
|
37089
|
+
|
|
37090
|
+
CLI alternative: \`lens context "<your goal>"\``;
|
|
35966
37091
|
startedAt = Date.now();
|
|
35967
37092
|
MIME_TYPES = {
|
|
35968
37093
|
".html": "text/html",
|
|
@@ -36548,13 +37673,14 @@ var init_dist3 = __esm({
|
|
|
36548
37673
|
// apps/daemon/src/index.ts
|
|
36549
37674
|
init_dist();
|
|
36550
37675
|
init_config();
|
|
36551
|
-
import { writeFileSync as writeFileSync3, unlinkSync, readFileSync as readFileSync3, statSync as statSync2, watchFile as watchFile2, openSync } from "fs";
|
|
36552
|
-
import { join as join6 } from "path";
|
|
36553
|
-
import { homedir as homedir4 } from "os";
|
|
36554
37676
|
import { spawn } from "child_process";
|
|
37677
|
+
import { openSync, readFileSync as readFileSync3, statSync as statSync2, unlinkSync, watchFile as watchFile2, writeFileSync as writeFileSync3 } from "fs";
|
|
37678
|
+
import { homedir as homedir4 } from "os";
|
|
37679
|
+
import { join as join6 } from "path";
|
|
36555
37680
|
var LENS_DIR = join6(homedir4(), ".lens");
|
|
36556
37681
|
var PID_FILE = join6(LENS_DIR, "daemon.pid");
|
|
36557
37682
|
var LOG_FILE = join6(LENS_DIR, "daemon.log");
|
|
37683
|
+
var AUTH_FILE = join6(LENS_DIR, "auth.json");
|
|
36558
37684
|
function writePid() {
|
|
36559
37685
|
writeFileSync3(PID_FILE, String(process.pid));
|
|
36560
37686
|
}
|
|
@@ -36564,76 +37690,97 @@ function removePid() {
|
|
|
36564
37690
|
} catch {
|
|
36565
37691
|
}
|
|
36566
37692
|
}
|
|
36567
|
-
|
|
36568
|
-
|
|
36569
|
-
|
|
36570
|
-
|
|
37693
|
+
function readApiKeyFromDisk() {
|
|
37694
|
+
try {
|
|
37695
|
+
const data = JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
|
|
37696
|
+
return data.api_key || null;
|
|
37697
|
+
} catch {
|
|
37698
|
+
return null;
|
|
37699
|
+
}
|
|
37700
|
+
}
|
|
37701
|
+
async function provisionNewKey() {
|
|
36571
37702
|
try {
|
|
37703
|
+
const data = JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
|
|
37704
|
+
if (!data.access_token) return null;
|
|
37705
|
+
const cloudUrl = getCloudUrl();
|
|
36572
37706
|
const res = await fetch(`${cloudUrl}/auth/key`, {
|
|
36573
37707
|
headers: { Authorization: `Bearer ${data.access_token}` }
|
|
36574
37708
|
});
|
|
36575
|
-
if (!res.ok)
|
|
36576
|
-
console.error(`[LENS] API key provisioning failed (${res.status})`);
|
|
36577
|
-
return void 0;
|
|
36578
|
-
}
|
|
37709
|
+
if (!res.ok) return null;
|
|
36579
37710
|
const { api_key } = await res.json();
|
|
36580
37711
|
data.api_key = api_key;
|
|
36581
|
-
|
|
36582
|
-
|
|
36583
|
-
console.error("[LENS] API key auto-provisioned");
|
|
37712
|
+
writeFileSync3(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
37713
|
+
console.error("[LENS] API key re-provisioned");
|
|
36584
37714
|
return api_key;
|
|
36585
|
-
} catch
|
|
36586
|
-
|
|
36587
|
-
return void 0;
|
|
37715
|
+
} catch {
|
|
37716
|
+
return null;
|
|
36588
37717
|
}
|
|
36589
37718
|
}
|
|
36590
37719
|
async function loadCapabilities(db) {
|
|
36591
37720
|
try {
|
|
36592
|
-
|
|
36593
|
-
|
|
36594
|
-
|
|
36595
|
-
|
|
37721
|
+
let apiKey = readApiKeyFromDisk();
|
|
37722
|
+
if (!apiKey) {
|
|
37723
|
+
apiKey = await provisionNewKey();
|
|
37724
|
+
if (!apiKey) return {};
|
|
37725
|
+
}
|
|
36596
37726
|
const cloudUrl = getCloudUrl();
|
|
36597
37727
|
const res = await fetch(`${cloudUrl}/api/usage/current`, {
|
|
36598
37728
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
36599
37729
|
});
|
|
37730
|
+
if (res.status === 401) {
|
|
37731
|
+
apiKey = await provisionNewKey();
|
|
37732
|
+
if (!apiKey) return {};
|
|
37733
|
+
const retry = await fetch(`${cloudUrl}/api/usage/current`, {
|
|
37734
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
37735
|
+
});
|
|
37736
|
+
if (!retry.ok) {
|
|
37737
|
+
console.error(`[LENS] Plan check failed after re-provision (${retry.status})`);
|
|
37738
|
+
return {};
|
|
37739
|
+
}
|
|
37740
|
+
const usageData2 = await retry.json();
|
|
37741
|
+
return buildCapsResult(db, usageData2);
|
|
37742
|
+
}
|
|
36600
37743
|
if (!res.ok) {
|
|
36601
37744
|
console.error(`[LENS] Plan check failed (${res.status}), capabilities disabled`);
|
|
36602
37745
|
return {};
|
|
36603
37746
|
}
|
|
36604
37747
|
const usageData = await res.json();
|
|
36605
|
-
|
|
36606
|
-
plan: usageData.plan ?? "free",
|
|
36607
|
-
usage: usageData.usage ?? {},
|
|
36608
|
-
quota: usageData.quota ?? {}
|
|
36609
|
-
};
|
|
36610
|
-
if (usageData.plan !== "pro") {
|
|
36611
|
-
console.error(`[LENS] Plan: ${usageData.plan ?? "free"} \u2014 Pro features disabled`);
|
|
36612
|
-
return { planData };
|
|
36613
|
-
}
|
|
36614
|
-
const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
|
|
36615
|
-
const { usageQueries: usageQueries2, logQueries: logQueries2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
36616
|
-
const caps = createCloudCapabilities2(
|
|
36617
|
-
apiKey,
|
|
36618
|
-
(counter, amount) => {
|
|
36619
|
-
try {
|
|
36620
|
-
usageQueries2.increment(db, counter, amount);
|
|
36621
|
-
} catch {
|
|
36622
|
-
}
|
|
36623
|
-
},
|
|
36624
|
-
(method, path, status, duration3, source, reqBody, resBody) => {
|
|
36625
|
-
try {
|
|
36626
|
-
logQueries2.insert(db, method, path, status, duration3, source, reqBody, resBody?.length, resBody);
|
|
36627
|
-
} catch {
|
|
36628
|
-
}
|
|
36629
|
-
}
|
|
36630
|
-
);
|
|
36631
|
-
console.error("[LENS] Cloud capabilities enabled (Pro plan)");
|
|
36632
|
-
return { caps, planData };
|
|
37748
|
+
return buildCapsResult(db, usageData);
|
|
36633
37749
|
} catch {
|
|
36634
37750
|
return {};
|
|
36635
37751
|
}
|
|
36636
37752
|
}
|
|
37753
|
+
async function buildCapsResult(db, usageData) {
|
|
37754
|
+
const planData = {
|
|
37755
|
+
plan: usageData.plan ?? "free",
|
|
37756
|
+
usage: usageData.usage ?? {},
|
|
37757
|
+
quota: usageData.quota ?? {}
|
|
37758
|
+
};
|
|
37759
|
+
if (usageData.plan !== "pro") {
|
|
37760
|
+
console.error(`[LENS] Plan: ${usageData.plan ?? "free"} \u2014 Pro features disabled`);
|
|
37761
|
+
return { planData };
|
|
37762
|
+
}
|
|
37763
|
+
const { createCloudCapabilities: createCloudCapabilities2 } = await Promise.resolve().then(() => (init_cloud_capabilities(), cloud_capabilities_exports));
|
|
37764
|
+
const { usageQueries: usageQueries2, logQueries: logQueries2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
37765
|
+
const caps = createCloudCapabilities2(
|
|
37766
|
+
() => Promise.resolve(readApiKeyFromDisk()),
|
|
37767
|
+
provisionNewKey,
|
|
37768
|
+
(counter, amount) => {
|
|
37769
|
+
try {
|
|
37770
|
+
usageQueries2.increment(db, counter, amount);
|
|
37771
|
+
} catch {
|
|
37772
|
+
}
|
|
37773
|
+
},
|
|
37774
|
+
(method, path, status, duration3, source, reqBody, resBody) => {
|
|
37775
|
+
try {
|
|
37776
|
+
logQueries2.insert(db, method, path, status, duration3, source, reqBody, resBody?.length, resBody);
|
|
37777
|
+
} catch {
|
|
37778
|
+
}
|
|
37779
|
+
}
|
|
37780
|
+
);
|
|
37781
|
+
console.error("[LENS] Cloud capabilities enabled (Pro plan)");
|
|
37782
|
+
return { caps, planData };
|
|
37783
|
+
}
|
|
36637
37784
|
async function main() {
|
|
36638
37785
|
const db = openDb();
|
|
36639
37786
|
const { caps, planData } = await loadCapabilities(db);
|
|
@@ -36665,11 +37812,11 @@ async function main() {
|
|
|
36665
37812
|
var shutdown = shutdown2;
|
|
36666
37813
|
const { createApp: createApp2 } = await Promise.resolve().then(() => (init_server3(), server_exports));
|
|
36667
37814
|
const { serve: serve2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports2));
|
|
36668
|
-
const { resolve: resolve4, dirname:
|
|
37815
|
+
const { resolve: resolve4, dirname: dirname4 } = await import("path");
|
|
36669
37816
|
const { existsSync: existsSync3 } = await import("fs");
|
|
36670
37817
|
const { fileURLToPath } = await import("url");
|
|
36671
37818
|
const port = Number(process.env.LENS_PORT) || 4111;
|
|
36672
|
-
const selfDir =
|
|
37819
|
+
const selfDir = dirname4(fileURLToPath(import.meta.url));
|
|
36673
37820
|
const candidates = [
|
|
36674
37821
|
resolve4(selfDir, "../../dashboard/dist"),
|
|
36675
37822
|
// monorepo dev
|