@fodx/codelens 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -0
- package/adapters/pi/codelens.extension.ts +280 -0
- package/adapters/pi/extension.json +10 -0
- package/build/src/cli.js +265 -0
- package/build/src/cli.js.map +1 -0
- package/build/src/context/schema.sql +23 -0
- package/build/src/context/store.js +91 -0
- package/build/src/context/store.js.map +1 -0
- package/build/src/db/db.js +65 -0
- package/build/src/db/db.js.map +1 -0
- package/build/src/db/migrations.js +88 -0
- package/build/src/db/migrations.js.map +1 -0
- package/build/src/db/schema.js +11 -0
- package/build/src/db/schema.js.map +1 -0
- package/build/src/db/schema.sql +111 -0
- package/build/src/git/scope.js +68 -0
- package/build/src/git/scope.js.map +1 -0
- package/build/src/graph/edges.js +76 -0
- package/build/src/graph/edges.js.map +1 -0
- package/build/src/graph/grammars.js +57 -0
- package/build/src/graph/grammars.js.map +1 -0
- package/build/src/graph/query.js +52 -0
- package/build/src/graph/query.js.map +1 -0
- package/build/src/graph/resolve.js +68 -0
- package/build/src/graph/resolve.js.map +1 -0
- package/build/src/graph/symbols.js +84 -0
- package/build/src/graph/symbols.js.map +1 -0
- package/build/src/graph/tests.js +73 -0
- package/build/src/graph/tests.js.map +1 -0
- package/build/src/index/autoprune.js +29 -0
- package/build/src/index/autoprune.js.map +1 -0
- package/build/src/index/deny.js +40 -0
- package/build/src/index/deny.js.map +1 -0
- package/build/src/index/freshness.js +60 -0
- package/build/src/index/freshness.js.map +1 -0
- package/build/src/index/fts.js +125 -0
- package/build/src/index/fts.js.map +1 -0
- package/build/src/index/identity.js +21 -0
- package/build/src/index/identity.js.map +1 -0
- package/build/src/index/indexer.js +32 -0
- package/build/src/index/indexer.js.map +1 -0
- package/build/src/index/manager.js +48 -0
- package/build/src/index/manager.js.map +1 -0
- package/build/src/index/queue.js +47 -0
- package/build/src/index/queue.js.map +1 -0
- package/build/src/index/recovery.js +51 -0
- package/build/src/index/recovery.js.map +1 -0
- package/build/src/index/reindex.js +70 -0
- package/build/src/index/reindex.js.map +1 -0
- package/build/src/index/scanner.js +147 -0
- package/build/src/index/scanner.js.map +1 -0
- package/build/src/index/ttl.js +87 -0
- package/build/src/index/ttl.js.map +1 -0
- package/build/src/index/watcher.js +74 -0
- package/build/src/index/watcher.js.map +1 -0
- package/build/src/installer/agents.js +440 -0
- package/build/src/installer/agents.js.map +1 -0
- package/build/src/obs/doctor.js +53 -0
- package/build/src/obs/doctor.js.map +1 -0
- package/build/src/obs/stats.js +28 -0
- package/build/src/obs/stats.js.map +1 -0
- package/build/src/obs/usage.js +136 -0
- package/build/src/obs/usage.js.map +1 -0
- package/build/src/search/rank.js +70 -0
- package/build/src/search/rank.js.map +1 -0
- package/build/src/search/snippet.js +66 -0
- package/build/src/search/snippet.js.map +1 -0
- package/build/src/server.js +126 -0
- package/build/src/server.js.map +1 -0
- package/build/src/tools/current.js +32 -0
- package/build/src/tools/current.js.map +1 -0
- package/build/src/tools/expand.js +54 -0
- package/build/src/tools/expand.js.map +1 -0
- package/build/src/tools/prune.js +46 -0
- package/build/src/tools/prune.js.map +1 -0
- package/build/src/tools/refresh.js +14 -0
- package/build/src/tools/refresh.js.map +1 -0
- package/build/src/tools/registry.js +176 -0
- package/build/src/tools/registry.js.map +1 -0
- package/build/src/tools/related.js +28 -0
- package/build/src/tools/related.js.map +1 -0
- package/build/src/tools/save.js +15 -0
- package/build/src/tools/save.js.map +1 -0
- package/build/src/tools/search.js +124 -0
- package/build/src/tools/search.js.map +1 -0
- package/build/src/upgrade.js +74 -0
- package/build/src/upgrade.js.map +1 -0
- package/build/src/util/hash.js +15 -0
- package/build/src/util/hash.js.map +1 -0
- package/build/src/util/paths.js +67 -0
- package/build/src/util/paths.js.map +1 -0
- package/build/src/version.js +27 -0
- package/build/src/version.js.map +1 -0
- package/docs/agent-guide.md +47 -0
- package/docs/codelens-preview.png +0 -0
- package/docs/routing.md +59 -0
- package/docs/tools.md +53 -0
- package/package.json +103 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { pruneIndexes } from "./ttl.js";
|
|
2
|
+
import { enqueue } from "./queue.js";
|
|
3
|
+
/**
|
|
4
|
+
* Automatic pruning scheduler (Step 23).
|
|
5
|
+
*
|
|
6
|
+
* Runs pruneIndexes on server startup, after index creation, and on a periodic
|
|
7
|
+
* idle timer. All pruning goes through the single-writer queue so it never
|
|
8
|
+
* blocks queries (WAL lets readers see the prior snapshot).
|
|
9
|
+
*/
|
|
10
|
+
const IDLE_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
11
|
+
let timer = null;
|
|
12
|
+
/** Run an immediate prune (queued, non-blocking). */
|
|
13
|
+
export function pruneNow(db) {
|
|
14
|
+
void enqueue(() => pruneIndexes(db));
|
|
15
|
+
}
|
|
16
|
+
/** Schedule startup + periodic idle pruning. Returns a stop() function. */
|
|
17
|
+
export function scheduleAutoPrune(db) {
|
|
18
|
+
pruneNow(db);
|
|
19
|
+
if (timer)
|
|
20
|
+
clearInterval(timer);
|
|
21
|
+
timer = setInterval(() => pruneNow(db), IDLE_INTERVAL_MS);
|
|
22
|
+
return () => {
|
|
23
|
+
if (timer) {
|
|
24
|
+
clearInterval(timer);
|
|
25
|
+
timer = null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=autoprune.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoprune.js","sourceRoot":"","sources":["../../../src/index/autoprune.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC;;;;;;GAMG;AAEH,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAEtD,IAAI,KAAK,GAA0C,IAAI,CAAC;AAExD,qDAAqD;AACrD,MAAM,UAAU,QAAQ,CAAC,EAAqB;IAC5C,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,iBAAiB,CAAC,EAAqB;IACrD,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,IAAI,KAAK;QAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAChC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC1D,OAAO,GAAG,EAAE;QACV,IAAI,KAAK,EAAE,CAAC;YAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC;QAAC,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic deny list (Design Decisions #3 + Section 16 risk 5).
|
|
3
|
+
*
|
|
4
|
+
* Even when a path is untracked (not gitignored), deny generated/build output
|
|
5
|
+
* so it never pollutes the index. Configurable later via .contextignore.
|
|
6
|
+
*/
|
|
7
|
+
const DENY_DIRS = [
|
|
8
|
+
"node_modules/",
|
|
9
|
+
"build/",
|
|
10
|
+
"dist/",
|
|
11
|
+
"out/",
|
|
12
|
+
"coverage/",
|
|
13
|
+
".cache/",
|
|
14
|
+
".next/",
|
|
15
|
+
".nuxt/",
|
|
16
|
+
".turbo/",
|
|
17
|
+
".svelte-kit/",
|
|
18
|
+
".gradle/",
|
|
19
|
+
"target/", // Rust
|
|
20
|
+
"__pycache__/",
|
|
21
|
+
".venv/",
|
|
22
|
+
"venv/",
|
|
23
|
+
".mypy_cache/",
|
|
24
|
+
".pytest_cache/",
|
|
25
|
+
];
|
|
26
|
+
const DENY_GLOBS = [
|
|
27
|
+
/\.min\.js$/,
|
|
28
|
+
/\.min\.css$/,
|
|
29
|
+
/\.map$/,
|
|
30
|
+
/(^|\/)vendor\//,
|
|
31
|
+
/\.bundle\.[a-z]+$/,
|
|
32
|
+
];
|
|
33
|
+
/** True if a POSIX repo-relative path should be denied (excluded from indexing). */
|
|
34
|
+
export function shouldDeny(posixPath) {
|
|
35
|
+
if (DENY_DIRS.some((d) => posixPath.includes("/" + d)) || DENY_DIRS.some((d) => posixPath.startsWith(d))) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return DENY_GLOBS.some((re) => re.test(posixPath));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=deny.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deny.js","sourceRoot":"","sources":["../../../src/index/deny.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,SAAS,GAAG;IAChB,eAAe;IACf,QAAQ;IACR,OAAO;IACP,MAAM;IACN,WAAW;IACX,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,cAAc;IACd,UAAU;IACV,SAAS,EAAE,OAAO;IAClB,cAAc;IACd,QAAQ;IACR,OAAO;IACP,cAAc;IACd,gBAAgB;CACjB,CAAC;AAEF,MAAM,UAAU,GAAG;IACjB,YAAY;IACZ,aAAa;IACb,QAAQ;IACR,gBAAgB;IAChB,mBAAmB;CACpB,CAAC;AAEF,oFAAoF;AACpF,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzG,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveReal } from "../util/paths.js";
|
|
4
|
+
import { contentHash } from "../util/hash.js";
|
|
5
|
+
/** Load indexed file rows for an index, keyed by POSIX path. */
|
|
6
|
+
function loadStored(db, indexId) {
|
|
7
|
+
const rows = db
|
|
8
|
+
.prepare("SELECT id, path, size, mtime_ms, content_hash FROM files WHERE index_id = ? AND deleted = 0")
|
|
9
|
+
.all(indexId);
|
|
10
|
+
return new Map(rows.map((r) => [r.path, r]));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Diff scanned files vs stored rows. Fast path: skip hashing when size AND
|
|
14
|
+
* mtime match. Changed/new files are flagged for reindex (hashing happens
|
|
15
|
+
* inside indexFile in Step 10).
|
|
16
|
+
*/
|
|
17
|
+
export function diffFiles(db, indexId, scanned, repoRoot) {
|
|
18
|
+
const stored = loadStored(db, indexId);
|
|
19
|
+
const scannedByPath = new Map(scanned.map((f) => [f.path, f]));
|
|
20
|
+
const unchanged = [];
|
|
21
|
+
const changed = [];
|
|
22
|
+
const newFiles = [];
|
|
23
|
+
const deleted = [];
|
|
24
|
+
for (const f of scanned) {
|
|
25
|
+
const s = stored.get(f.path);
|
|
26
|
+
if (!s) {
|
|
27
|
+
newFiles.push(f);
|
|
28
|
+
}
|
|
29
|
+
else if (s.size === f.size && s.mtime_ms === f.mtimeMs) {
|
|
30
|
+
unchanged.push(f);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
changed.push(f);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const [path, s] of stored) {
|
|
37
|
+
if (!scannedByPath.has(path))
|
|
38
|
+
deleted.push({ path, id: s.id });
|
|
39
|
+
}
|
|
40
|
+
void repoRoot;
|
|
41
|
+
return { unchanged, changed, newFiles, deleted };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Strong check: recompute content hash and compare to stored. Used when mtime
|
|
45
|
+
* is ambiguous (e.g. after a checkout that resets mtime). Returns true if the
|
|
46
|
+
* file's content actually differs from what's indexed.
|
|
47
|
+
*/
|
|
48
|
+
export function contentChanged(repoRoot, file, storedHash) {
|
|
49
|
+
if (!storedHash)
|
|
50
|
+
return true;
|
|
51
|
+
try {
|
|
52
|
+
const root = resolveReal(repoRoot);
|
|
53
|
+
const text = readFileSync(join(root, file.path), "utf-8");
|
|
54
|
+
return contentHash(text) !== storedHash;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return true; // unreadable → treat as changed (will be re-skipped by indexer)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=freshness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../../src/index/freshness.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0B9C,gEAAgE;AAChE,SAAS,UAAU,CAAC,EAAqB,EAAE,OAAe;IACxD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,6FAA6F,CAAC;SACtG,GAAG,CAAC,OAAO,CAAiB,CAAC;IAChC,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,EAAqB,EAAE,OAAe,EAAE,OAAsB,EAAE,QAAgB;IACxG,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAkB,EAAE,CAAC;IACpC,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAmC,EAAE,CAAC;IAEnD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;YACzD,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;QAC/B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,KAAK,QAAQ,CAAC;IACd,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,IAAiB,EAAE,UAAyB;IAC3F,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1D,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,gEAAgE;IAC/E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveReal } from "../util/paths.js";
|
|
5
|
+
import { contentHash } from "../util/hash.js";
|
|
6
|
+
import { extractSymbols } from "../graph/symbols.js";
|
|
7
|
+
import { extractEdges, insertEdges } from "../graph/edges.js";
|
|
8
|
+
import { isTestFile, resolveTestTargets } from "../graph/tests.js";
|
|
9
|
+
/**
|
|
10
|
+
* FTS5 chunk indexer (Step 7).
|
|
11
|
+
*
|
|
12
|
+
* Chunks a file into line-bounded slices (~500 "tokens" approximated as chars/4),
|
|
13
|
+
* stores a files row + chunks rows + FTS5 rows. Per-file transactional. Symbols
|
|
14
|
+
* and edges come in Steps 13-15.
|
|
15
|
+
*/
|
|
16
|
+
const CHUNK_CHARS = 2000; // ~500 tokens at ~4 chars/token
|
|
17
|
+
/** Read a file's text (UTF-8). Throws on read failure — caller handles. */
|
|
18
|
+
function readFileText(absPath) {
|
|
19
|
+
return readFileSync(absPath, "utf-8");
|
|
20
|
+
}
|
|
21
|
+
/** Split text into line-bounded chunks of roughly CHUNK_CHARS characters. */
|
|
22
|
+
export function chunkText(text) {
|
|
23
|
+
const lines = text.split("\n");
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let buf = [];
|
|
26
|
+
let bufChars = 0;
|
|
27
|
+
let startLine = 1;
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
buf.push(line);
|
|
31
|
+
bufChars += line.length + 1;
|
|
32
|
+
if (bufChars >= CHUNK_CHARS) {
|
|
33
|
+
chunks.push({ startLine, endLine: i + 1, content: buf.join("\n") });
|
|
34
|
+
buf = [];
|
|
35
|
+
bufChars = 0;
|
|
36
|
+
startLine = i + 2;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (buf.length > 0) {
|
|
40
|
+
chunks.push({ startLine, endLine: lines.length, content: buf.join("\n") });
|
|
41
|
+
}
|
|
42
|
+
return chunks;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Index a single file into files + chunks + chunks_fts. Transactional.
|
|
46
|
+
* Caller must pass the active indexId and the scanned file metadata.
|
|
47
|
+
*/
|
|
48
|
+
export function indexFile(db, indexId, repoRoot, file, knownFiles = new Set()) {
|
|
49
|
+
const root = resolveReal(repoRoot);
|
|
50
|
+
const abs = join(root, file.path);
|
|
51
|
+
const text = readFileText(abs);
|
|
52
|
+
const hash = contentHash(text);
|
|
53
|
+
const fileId = "file_" + randomUUID();
|
|
54
|
+
const chunks = chunkText(text);
|
|
55
|
+
const symbols = extractSymbols(file.path, file.language ?? "", text);
|
|
56
|
+
const edges = extractEdges(file.path, file.language ?? "", text, root, knownFiles);
|
|
57
|
+
const tx = db.transaction(() => {
|
|
58
|
+
// Remove ALL prior rows for this path+index so reindexing a changed file
|
|
59
|
+
// does not leave stale symbols/edges. Without this, incremental reindex
|
|
60
|
+
// would accumulate duplicate symbols + stale graph edges.
|
|
61
|
+
db.prepare("DELETE FROM chunks_fts WHERE index_id = ? AND path = ?").run(indexId, file.path);
|
|
62
|
+
db.prepare("DELETE FROM chunks WHERE index_id = ? AND path = ?").run(indexId, file.path);
|
|
63
|
+
db.prepare("DELETE FROM symbols WHERE index_id = ? AND path = ?").run(indexId, file.path);
|
|
64
|
+
// Only clear THIS file's outbound edges (from_path = file.path). Inbound edges
|
|
65
|
+
// from other files (e.g. Y imports X) stay valid since X still exists; they
|
|
66
|
+
// are only removed when X is deleted (deleteFileFromIndex uses from OR to).
|
|
67
|
+
db.prepare("DELETE FROM edges WHERE index_id = ? AND from_path = ?").run(indexId, file.path);
|
|
68
|
+
db.prepare("DELETE FROM files WHERE index_id = ? AND path = ?").run(indexId, file.path);
|
|
69
|
+
db.prepare(`INSERT INTO files (id, index_id, path, language, size, mtime_ms, content_hash, git_blob_sha, deleted, last_indexed_at)
|
|
70
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 0, ?)`).run(fileId, indexId, file.path, file.language, file.size, file.mtimeMs, hash, Date.now());
|
|
71
|
+
const insertChunk = db.prepare(`INSERT INTO chunks (id, index_id, file_id, symbol_id, path, start_line, end_line, content, content_hash, content_type)
|
|
72
|
+
VALUES (?, ?, ?, NULL, ?, ?, ?, ?, ?, ?)`);
|
|
73
|
+
const insertFts = db.prepare(`INSERT INTO chunks_fts (content, path, index_id, chunk_id) VALUES (?, ?, ?, ?)`);
|
|
74
|
+
for (const c of chunks) {
|
|
75
|
+
const chunkId = "chk_" + randomUUID();
|
|
76
|
+
const cHash = contentHash(c.content);
|
|
77
|
+
const ctype = file.language && ["typescript", "javascript", "python", "go", "rust", "java", "c", "cpp"].includes(file.language)
|
|
78
|
+
? "code"
|
|
79
|
+
: "prose";
|
|
80
|
+
insertChunk.run(chunkId, indexId, fileId, file.path, c.startLine, c.endLine, c.content, cHash, ctype);
|
|
81
|
+
insertFts.run(c.content, file.path, indexId, chunkId);
|
|
82
|
+
}
|
|
83
|
+
// Symbols (Step 13)
|
|
84
|
+
const insertSymbol = db.prepare(`INSERT INTO symbols (id, index_id, file_id, path, name, kind, signature, start_line, end_line, exported, doc)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`);
|
|
86
|
+
for (const sym of symbols) {
|
|
87
|
+
insertSymbol.run("sym_" + randomUUID(), indexId, fileId, file.path, sym.name, sym.kind, sym.signature ?? null, sym.startLine, sym.endLine, sym.exported ? 1 : 0);
|
|
88
|
+
}
|
|
89
|
+
// Edges: imports (file→file) from extractEdges; defines/belongs_to
|
|
90
|
+
// (file↔symbol); exports (file→exported symbol).
|
|
91
|
+
insertEdges(db, indexId, edges);
|
|
92
|
+
const insertEdge = db.prepare(`INSERT INTO edges (id, index_id, from_id, to_id, from_path, to_path, type, confidence) VALUES (?, ?, NULL, NULL, ?, ?, ?, ?)`);
|
|
93
|
+
for (const sym of symbols) {
|
|
94
|
+
// file defines symbol (file→symbol via path; symbol_id kept NULL for path-based edges)
|
|
95
|
+
insertEdge.run("edge_" + randomUUID(), indexId, file.path, file.path, "defines", 1.0);
|
|
96
|
+
// symbol belongs_to file
|
|
97
|
+
insertEdge.run("edge_" + randomUUID(), indexId, file.path, file.path, "belongs_to", 1.0);
|
|
98
|
+
if (sym.exported) {
|
|
99
|
+
insertEdge.run("edge_" + randomUUID(), indexId, file.path, file.path, "exports", 1.0);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Test edges (Step 15): if this file is a test, emit `tests` edges to
|
|
103
|
+
// resolved source files present in the index.
|
|
104
|
+
if (isTestFile(file.path)) {
|
|
105
|
+
const targets = resolveTestTargets(file.path, knownFiles);
|
|
106
|
+
for (const t of targets) {
|
|
107
|
+
insertEdge.run("edge_" + randomUUID(), indexId, file.path, t, "tests", 0.8);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
tx();
|
|
112
|
+
return { fileId, chunkCount: chunks.length, contentHash: hash, symbols, edges };
|
|
113
|
+
}
|
|
114
|
+
/** Mark a file as deleted (remove its rows) for an index. Transactional. */
|
|
115
|
+
export function deleteFileFromIndex(db, indexId, path) {
|
|
116
|
+
const tx = db.transaction(() => {
|
|
117
|
+
db.prepare("DELETE FROM chunks_fts WHERE index_id = ? AND path = ?").run(indexId, path);
|
|
118
|
+
db.prepare("DELETE FROM chunks WHERE index_id = ? AND path = ?").run(indexId, path);
|
|
119
|
+
db.prepare("DELETE FROM symbols WHERE index_id = ? AND path = ?").run(indexId, path);
|
|
120
|
+
db.prepare("DELETE FROM edges WHERE index_id = ? AND (from_path = ? OR to_path = ?)").run(indexId, path, path);
|
|
121
|
+
db.prepare("DELETE FROM files WHERE index_id = ? AND path = ?").run(indexId, path);
|
|
122
|
+
});
|
|
123
|
+
tx();
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=fts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fts.js","sourceRoot":"","sources":["../../../src/index/fts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Index identity (Design Decisions: branch isolation).
|
|
4
|
+
*
|
|
5
|
+
* index_id = sha256(repoRoot | worktreePath | branch | headSha)
|
|
6
|
+
*
|
|
7
|
+
* Every stored row carries index_id; every query filters by the active id so
|
|
8
|
+
* branch A never returns branch B results by default.
|
|
9
|
+
*/
|
|
10
|
+
export function computeIndexId(scope) {
|
|
11
|
+
if (!scope.repoRoot)
|
|
12
|
+
throw new Error("computeIndexId: missing repoRoot");
|
|
13
|
+
if (!scope.headSha && !scope.detached) {
|
|
14
|
+
// Allow empty head only for detached/fresh repos. A normal branch with no
|
|
15
|
+
// head is a caller bug.
|
|
16
|
+
throw new Error("computeIndexId: missing headSha on non-detached scope");
|
|
17
|
+
}
|
|
18
|
+
const key = [scope.repoRoot, scope.worktreePath, scope.branch, scope.headSha].join("|");
|
|
19
|
+
return "idx_" + createHash("sha256").update(key).digest("hex").slice(0, 32);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/index/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;GAOG;AAEH,MAAM,UAAU,cAAc,CAAC,KAAe;IAC5C,IAAI,CAAC,KAAK,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACzE,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACtC,0EAA0E;QAC1E,wBAAwB;QACxB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxF,OAAO,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getOrCreateIndex, getActiveIndexId } from "./manager.js";
|
|
2
|
+
import { scanFiles } from "./scanner.js";
|
|
3
|
+
import { indexFile } from "./fts.js";
|
|
4
|
+
export function buildIndex(db, scope) {
|
|
5
|
+
const row = getOrCreateIndex(db, scope);
|
|
6
|
+
const indexId = row.id;
|
|
7
|
+
const files = scanFiles(scope.repoRoot);
|
|
8
|
+
const knownFiles = new Set(files.map((f) => f.path));
|
|
9
|
+
let indexedFiles = 0;
|
|
10
|
+
let totalChunks = 0;
|
|
11
|
+
let skipped = 0;
|
|
12
|
+
for (const f of files) {
|
|
13
|
+
try {
|
|
14
|
+
const r = indexFile(db, indexId, scope.repoRoot, f, knownFiles);
|
|
15
|
+
indexedFiles++;
|
|
16
|
+
totalChunks += r.chunkCount;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
skipped++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { indexId, indexedFiles, totalChunks, skipped };
|
|
23
|
+
}
|
|
24
|
+
/** Convenience: ensure active index id is set after build. */
|
|
25
|
+
export function activeIndex(db) {
|
|
26
|
+
const id = getActiveIndexId();
|
|
27
|
+
if (!id)
|
|
28
|
+
throw new Error("no active index — call buildIndex or getOrCreateIndex first");
|
|
29
|
+
void db;
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=indexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../../src/index/indexer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,SAAS,EAAoB,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAiBrC,MAAM,UAAU,UAAU,CAAC,EAAqB,EAAE,KAAe;IAC/D,MAAM,GAAG,GAAG,gBAAgB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;YAChE,YAAY,EAAE,CAAC;YACf,WAAW,IAAI,CAAC,CAAC,UAAU,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AACzD,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,EAAqB;IAC/C,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAAC;IAC9B,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACxF,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { computeIndexId } from "./identity.js";
|
|
2
|
+
let activeIndexId = null;
|
|
3
|
+
/** Get or create the index row for the current git scope; touch access time. */
|
|
4
|
+
export function getOrCreateIndex(db, scope) {
|
|
5
|
+
const id = computeIndexId(scope);
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const existing = db
|
|
8
|
+
.prepare("SELECT * FROM indexes WHERE id = ?")
|
|
9
|
+
.get(id);
|
|
10
|
+
if (existing) {
|
|
11
|
+
db.prepare("UPDATE indexes SET last_accessed_at = ?, status = 'active', expires_at = NULL WHERE id = ?")
|
|
12
|
+
.run(now, id);
|
|
13
|
+
activeIndexId = id;
|
|
14
|
+
return { ...existing, last_accessed_at: now, status: "active", expires_at: null };
|
|
15
|
+
}
|
|
16
|
+
db.prepare(`INSERT INTO indexes (id, repo_root, worktree_path, branch_name, head_sha, created_at, last_accessed_at, expires_at, pinned, status)
|
|
17
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 0, 'active')`).run(id, scope.repoRoot, scope.worktreePath, scope.branch, scope.headSha, now, now);
|
|
18
|
+
activeIndexId = id;
|
|
19
|
+
return {
|
|
20
|
+
id,
|
|
21
|
+
repo_root: scope.repoRoot,
|
|
22
|
+
worktree_path: scope.worktreePath,
|
|
23
|
+
branch_name: scope.branch,
|
|
24
|
+
head_sha: scope.headSha,
|
|
25
|
+
created_at: now,
|
|
26
|
+
last_accessed_at: now,
|
|
27
|
+
expires_at: null,
|
|
28
|
+
pinned: 0,
|
|
29
|
+
status: "active",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Bump last_accessed_at for an index (used on every query). */
|
|
33
|
+
export function touchIndex(db, indexId) {
|
|
34
|
+
db.prepare("UPDATE indexes SET last_accessed_at = ? WHERE id = ?").run(Date.now(), indexId);
|
|
35
|
+
}
|
|
36
|
+
/** Set the active index for the current process. */
|
|
37
|
+
export function setActiveIndex(indexId) {
|
|
38
|
+
activeIndexId = indexId;
|
|
39
|
+
}
|
|
40
|
+
/** Current active index id (null if none activated yet). */
|
|
41
|
+
export function getActiveIndexId() {
|
|
42
|
+
return activeIndexId;
|
|
43
|
+
}
|
|
44
|
+
/** Fetch an index row by id. */
|
|
45
|
+
export function getIndex(db, indexId) {
|
|
46
|
+
return db.prepare("SELECT * FROM indexes WHERE id = ?").get(indexId);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/index/manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAoB/C,IAAI,aAAa,GAAkB,IAAI,CAAC;AAExC,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAAC,EAAqB,EAAE,KAAe;IACrE,MAAM,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,EAAE;SAChB,OAAO,CAAC,oCAAoC,CAAC;SAC7C,GAAG,CAAC,EAAE,CAAyB,CAAC;IACnC,IAAI,QAAQ,EAAE,CAAC;QACb,EAAE,CAAC,OAAO,CAAC,4FAA4F,CAAC;aACrG,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAChB,aAAa,GAAG,EAAE,CAAC;QACnB,OAAO,EAAE,GAAG,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACpF,CAAC;IACD,EAAE,CAAC,OAAO,CACR;qDACiD,CAClD,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACrF,aAAa,GAAG,EAAE,CAAC;IACnB,OAAO;QACL,EAAE;QACF,SAAS,EAAE,KAAK,CAAC,QAAQ;QACzB,aAAa,EAAE,KAAK,CAAC,YAAY;QACjC,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,QAAQ,EAAE,KAAK,CAAC,OAAO;QACvB,UAAU,EAAE,GAAG;QACf,gBAAgB,EAAE,GAAG;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,QAAQ;KACjB,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU,CAAC,EAAqB,EAAE,OAAe;IAC/D,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;AAC9F,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,aAAa,GAAG,OAAO,CAAC;AAC1B,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB;IAC9B,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,QAAQ,CAAC,EAAqB,EAAE,OAAe;IAC7D,OAAO,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAyB,CAAC;AAC/F,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-writer queue (Design Decisions #4 + Section 16 risk 2).
|
|
3
|
+
*
|
|
4
|
+
* Serializes all index writes through one promise chain per process so two
|
|
5
|
+
* agent processes never corrupt rows. Readers (search/expand) bypass the queue
|
|
6
|
+
* and read the prior committed snapshot (WAL allows this).
|
|
7
|
+
*
|
|
8
|
+
* Cross-process safety: index_locks table holds an advisory lease. If another
|
|
9
|
+
* process holds a non-expired lease, writes skip (return a "locked" result) and
|
|
10
|
+
* the caller surfaces partial freshness rather than blocking.
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
const state = { tail: Promise.resolve(), active: false };
|
|
14
|
+
/** Enqueue a write task; runs after all prior writes complete. */
|
|
15
|
+
export function enqueue(task) {
|
|
16
|
+
const run = state.tail.then(task, task);
|
|
17
|
+
state.tail = run.catch(() => { });
|
|
18
|
+
return run;
|
|
19
|
+
}
|
|
20
|
+
/** Whether a write is currently executing. */
|
|
21
|
+
export function isWriteActive() {
|
|
22
|
+
return state.active;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Acquire an advisory cross-process write lease for an index. Returns true if
|
|
26
|
+
* acquired (or already held by this process), false if another process holds a
|
|
27
|
+
* non-expired lease. Releases on the next ensureFreshIndex completion.
|
|
28
|
+
*/
|
|
29
|
+
export function acquireWriteLease(db, indexId, owner = process.pid.toString(), leaseMs = 30000) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const existing = db.prepare("SELECT owner, expires_at FROM index_locks WHERE index_id = ?").get(indexId);
|
|
32
|
+
if (existing && existing.expires_at > now && existing.owner !== owner) {
|
|
33
|
+
return false; // another process holds it
|
|
34
|
+
}
|
|
35
|
+
db.prepare("INSERT INTO index_locks (index_id, owner, expires_at) VALUES (?, ?, ?) " +
|
|
36
|
+
"ON CONFLICT(index_id) DO UPDATE SET owner = excluded.owner, expires_at = excluded.expires_at").run(indexId, owner, now + leaseMs);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/** Release the write lease if owned by this process. */
|
|
40
|
+
export function releaseWriteLease(db, indexId, owner = process.pid.toString()) {
|
|
41
|
+
db.prepare("DELETE FROM index_locks WHERE index_id = ? AND owner = ?").run(indexId, owner);
|
|
42
|
+
}
|
|
43
|
+
/** Generate a unique owner id (for tests that want isolation). */
|
|
44
|
+
export function newOwnerId() {
|
|
45
|
+
return "own_" + randomUUID();
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../../src/index/queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AASzC,MAAM,KAAK,GAAe,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAErE,kEAAkE;AAClE,MAAM,UAAU,OAAO,CAAI,IAAa;IACtC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAe,CAAC;IACtD,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAqC,CAAC,CAAC,CAAC;IACpE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,aAAa;IAC3B,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAqB,EAAE,OAAe,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,GAAG,KAAK;IACvH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC,GAAG,CAAC,OAAO,CAClD,CAAC;IACtD,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,GAAG,GAAG,IAAI,QAAQ,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACtE,OAAO,KAAK,CAAC,CAAC,2BAA2B;IAC3C,CAAC;IACD,EAAE,CAAC,OAAO,CACR,yEAAyE;QACvE,8FAA8F,CACjG,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iBAAiB,CAAC,EAAqB,EAAE,OAAe,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE;IACtG,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC7F,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,GAAG,UAAU,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { copyFileSync, existsSync } from "node:fs";
|
|
2
|
+
/** Fast integrity check via PRAGMA quick_check. */
|
|
3
|
+
export function checkIntegrity(db) {
|
|
4
|
+
try {
|
|
5
|
+
const rows = db.prepare("PRAGMA quick_check").all();
|
|
6
|
+
const msg = rows.map((r) => r.quick_check).join(" ");
|
|
7
|
+
return { ok: msg === "ok", message: msg };
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Copy the DB file to `<path>.backup-v<version>` for restore-on-migration-failure. */
|
|
14
|
+
export function backupBeforeMigration(dbPath, version) {
|
|
15
|
+
const backup = `${dbPath}.backup-v${version}`;
|
|
16
|
+
copyFileSync(dbPath, backup);
|
|
17
|
+
return backup;
|
|
18
|
+
}
|
|
19
|
+
/** Restore a backup over the DB file (used if migration fails). Caller closes DB first. */
|
|
20
|
+
export function restoreBackup(dbPath, version) {
|
|
21
|
+
const backup = `${dbPath}.backup-v${version}`;
|
|
22
|
+
if (!existsSync(backup))
|
|
23
|
+
return false;
|
|
24
|
+
copyFileSync(backup, dbPath);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Drop core index tables (keep indexes rows + schema_version) so the caller can
|
|
29
|
+
* re-scan + reindex from scratch. saved_contexts are in a separate DB (Step 21)
|
|
30
|
+
* and are untouched. This is the recovery path on corruption.
|
|
31
|
+
*/
|
|
32
|
+
export function dropCoreTables(db) {
|
|
33
|
+
// Order matters for FK: drop dependents first.
|
|
34
|
+
db.exec(`
|
|
35
|
+
DROP TABLE IF EXISTS embeddings;
|
|
36
|
+
DROP TABLE IF EXISTS edges;
|
|
37
|
+
DROP TABLE IF EXISTS chunks_fts;
|
|
38
|
+
DROP TABLE IF EXISTS chunks;
|
|
39
|
+
DROP TABLE IF EXISTS symbols;
|
|
40
|
+
DROP TABLE IF EXISTS files;
|
|
41
|
+
DROP TABLE IF EXISTS index_locks;
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
/** True if a thrown error looks like SQLite corruption. */
|
|
45
|
+
export function isCorruptionError(err) {
|
|
46
|
+
if (!(err instanceof Error))
|
|
47
|
+
return false;
|
|
48
|
+
const msg = err.message.toLowerCase();
|
|
49
|
+
return msg.includes("corrupt") || msg.includes("database disk image is malformed") || msg.includes("not a database");
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../../src/index/recovery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAmBnD,mDAAmD;AACnD,MAAM,UAAU,cAAc,CAAC,EAAqB;IAClD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,GAAG,EAA+B,CAAC;QACjF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAClF,CAAC;AACH,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,OAAe;IACnE,MAAM,MAAM,GAAG,GAAG,MAAM,YAAY,OAAO,EAAE,CAAC;IAC9C,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAe;IAC3D,MAAM,MAAM,GAAG,GAAG,MAAM,YAAY,OAAO,EAAE,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAqB;IAClD,+CAA+C;IAC/C,EAAE,CAAC,IAAI,CAAC;;;;;;;;GAQP,CAAC,CAAC;AACL,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,kCAAkC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AACvH,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { scanFiles } from "./scanner.js";
|
|
2
|
+
import { diffFiles } from "./freshness.js";
|
|
3
|
+
import { indexFile, deleteFileFromIndex } from "./fts.js";
|
|
4
|
+
import { getOrCreateIndex } from "./manager.js";
|
|
5
|
+
import { acquireWriteLease, releaseWriteLease, newOwnerId } from "./queue.js";
|
|
6
|
+
const DEFAULT_BUDGET_MS = 500;
|
|
7
|
+
const FULL_SCAN_INTERVAL_MS = 5000;
|
|
8
|
+
let lastFullScanAt = 0;
|
|
9
|
+
let activeWatcher = null;
|
|
10
|
+
/** Register the process file watcher (called on server startup). */
|
|
11
|
+
export function registerWatcher(w) { activeWatcher = w; }
|
|
12
|
+
export function ensureFreshIndex(db, scope, opts) {
|
|
13
|
+
const row = getOrCreateIndex(db, scope);
|
|
14
|
+
const indexId = row.id;
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
const budget = opts?.budgetMs ?? DEFAULT_BUDGET_MS;
|
|
17
|
+
const watcher = opts?.watcher ?? activeWatcher;
|
|
18
|
+
// Watcher short-circuit: if a watcher is active, it has reported no changes
|
|
19
|
+
// since the last full scan, and we scanned recently, skip the re-scan
|
|
20
|
+
// entirely (the quiet-period optimization). A periodic full scan
|
|
21
|
+
// (FULL_SCAN_INTERVAL_MS) catches anything the watcher missed.
|
|
22
|
+
if (watcher?.active && watcher.dirty.size === 0 && (Date.now() - lastFullScanAt) < FULL_SCAN_INTERVAL_MS) {
|
|
23
|
+
return { refreshed: 0, deleted: 0, pending: 0, durationMs: 0 };
|
|
24
|
+
}
|
|
25
|
+
// Cross-process write lease: skip if another process is mid-reindex.
|
|
26
|
+
const owner = newOwnerId();
|
|
27
|
+
// Lease duration must cover the full operation so a long-budget reindex isn't
|
|
28
|
+
// stolen mid-flight by another process. >= 3x budget, minimum 30s.
|
|
29
|
+
const leaseMs = Math.max(30000, budget * 3);
|
|
30
|
+
if (!acquireWriteLease(db, indexId, owner, leaseMs)) {
|
|
31
|
+
return { refreshed: 0, deleted: 0, pending: 0, durationMs: 0, locked: true };
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
lastFullScanAt = Date.now();
|
|
35
|
+
// Always full-scan for correct changed/new/deleted detection. The watcher's
|
|
36
|
+
// value is the quiet-period short-circuit above (skip the scan entirely when
|
|
37
|
+
// nothing changed); a narrow "only dirty paths" path was removed because it
|
|
38
|
+
// mis-classified every non-dirty stored file as deleted.
|
|
39
|
+
if (watcher?.active)
|
|
40
|
+
watcher.consume(); // drain any dirty set
|
|
41
|
+
const scanned = scanFiles(scope.repoRoot);
|
|
42
|
+
const diff = diffFiles(db, indexId, scanned, scope.repoRoot);
|
|
43
|
+
const toProcess = [...diff.changed, ...diff.newFiles];
|
|
44
|
+
let refreshed = 0;
|
|
45
|
+
let pending = 0;
|
|
46
|
+
for (const f of toProcess) {
|
|
47
|
+
if (Date.now() - start > budget) {
|
|
48
|
+
pending = toProcess.length - refreshed;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
indexFile(db, indexId, scope.repoRoot, f);
|
|
53
|
+
refreshed++;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// skip unreadable
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
let deleted = 0;
|
|
60
|
+
for (const d of diff.deleted) {
|
|
61
|
+
deleteFileFromIndex(db, indexId, d.path);
|
|
62
|
+
deleted++;
|
|
63
|
+
}
|
|
64
|
+
return { refreshed, deleted, pending, durationMs: Date.now() - start };
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
releaseWriteLease(db, indexId, owner);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=reindex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reindex.js","sourceRoot":"","sources":["../../../src/index/reindex.ts"],"names":[],"mappings":"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"}
|