@descryy/core 0.7.0 → 0.9.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/dist/contracts/index.d.ts +5 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/paths.d.ts +7 -0
- package/dist/contracts/paths.d.ts.map +1 -1
- package/dist/contracts/paths.js +7 -4
- package/dist/contracts/paths.js.map +1 -1
- package/dist/contracts/shapes.d.ts +12 -1
- package/dist/contracts/shapes.d.ts.map +1 -1
- package/dist/contracts/shapes.js +68 -0
- package/dist/contracts/shapes.js.map +1 -1
- package/dist/contracts/workspace-join.d.ts +104 -0
- package/dist/contracts/workspace-join.d.ts.map +1 -0
- package/dist/contracts/workspace-join.js +208 -0
- package/dist/contracts/workspace-join.js.map +1 -0
- package/dist/contracts/workspace-table-join.d.ts +204 -0
- package/dist/contracts/workspace-table-join.d.ts.map +1 -0
- package/dist/contracts/workspace-table-join.js +316 -0
- package/dist/contracts/workspace-table-join.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/query/index.d.ts +4 -2
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +2 -1
- package/dist/query/index.js.map +1 -1
- package/dist/query/queries.d.ts +5 -0
- package/dist/query/queries.d.ts.map +1 -1
- package/dist/query/queries.js +58 -0
- package/dist/query/queries.js.map +1 -1
- package/dist/query/user-chain.d.ts +89 -0
- package/dist/query/user-chain.d.ts.map +1 -0
- package/dist/query/user-chain.js +203 -0
- package/dist/query/user-chain.js.map +1 -0
- package/dist/sources/git/history.d.ts +12 -0
- package/dist/sources/git/history.d.ts.map +1 -1
- package/dist/sources/git/history.js +27 -0
- package/dist/sources/git/history.js.map +1 -1
- package/dist/sources/git/index.d.ts +1 -1
- package/dist/sources/git/index.d.ts.map +1 -1
- package/dist/sources/git/index.js +1 -1
- package/dist/sources/git/index.js.map +1 -1
- package/dist/store/incremental-cache.d.ts +120 -0
- package/dist/store/incremental-cache.d.ts.map +1 -0
- package/dist/store/incremental-cache.js +161 -0
- package/dist/store/incremental-cache.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/schema.d.ts +4 -4
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +45 -1
- package/dist/store/schema.js.map +1 -1
- package/dist/workspace/graph-layout.d.ts +85 -0
- package/dist/workspace/graph-layout.d.ts.map +1 -0
- package/dist/workspace/graph-layout.js +90 -0
- package/dist/workspace/graph-layout.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/index.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gap 6 (steps 1-2): a real per-file IR cache, not a hash-gate.
|
|
3
|
+
*
|
|
4
|
+
* `file_hashes` (writer.ts) only ever stored a hash — no cached IR — so there was
|
|
5
|
+
* no way to "still supply" a skipped file's nodes/edges to `writeBatch` without
|
|
6
|
+
* re-parsing it. `writeBatch`'s hard invariant is that the batch a producer
|
|
7
|
+
* supplies is the *complete truth* for `producedBy`+`repo` (DEC-015): anything a
|
|
8
|
+
* prior run owned that a batch doesn't re-supply is evicted. Naive hash-gating
|
|
9
|
+
* that simply omitted an unchanged file from a batch would therefore have the
|
|
10
|
+
* writer silently evict that file's rows as stale.
|
|
11
|
+
*
|
|
12
|
+
* This module stores each file's actual contribution — its nodes, the edges
|
|
13
|
+
* whose `from` node lives in it, and its unresolved refs — so a caller (see
|
|
14
|
+
* `analyze.ts`) can reconstruct a producer's *entire* batch from cache when
|
|
15
|
+
* nothing changed, and re-supply it to `writeBatch` intact. The invariant is
|
|
16
|
+
* preserved by construction: the reconstructed batch is byte-for-byte what the
|
|
17
|
+
* last real run produced, so nothing the writer would evict is ever missing.
|
|
18
|
+
*
|
|
19
|
+
* Cross-file invalidation is explicitly out of scope for this pass (a renamed
|
|
20
|
+
* export in file A affecting file B's call-resolution edges, say). That is safe
|
|
21
|
+
* here only because the decision this cache feeds (`planIncrementalReuse` in
|
|
22
|
+
* `analyze.ts`) is whole-producer, not per-file: a batch is only ever
|
|
23
|
+
* reconstructed from cache when *every* file the producer last read is
|
|
24
|
+
* confirmed byte-identical, so whatever cross-file synthesis the producer would
|
|
25
|
+
* have computed from its complete input is unchanged too (assuming the producer
|
|
26
|
+
* is a deterministic function of its input, which `writeBatch`'s own same-batch
|
|
27
|
+
* idempotence already relies on). What this module does NOT support — and must
|
|
28
|
+
* not be used for — is reusing one file's cached contribution while a *sibling*
|
|
29
|
+
* file changed; that would risk exactly the cross-file staleness this comment
|
|
30
|
+
* warns about, which is why nothing here offers a partial/per-file reconstruction
|
|
31
|
+
* path.
|
|
32
|
+
*/
|
|
33
|
+
import type { IREdge, IRNode, SkippedFile, UnresolvedRef } from "@descryy/ir";
|
|
34
|
+
import type { SqlDriver } from "./driver/driver.ts";
|
|
35
|
+
export interface CachedFileEntry {
|
|
36
|
+
readonly filePath: string;
|
|
37
|
+
readonly contentHash: string;
|
|
38
|
+
readonly nodes: readonly IRNode[];
|
|
39
|
+
readonly edges: readonly IREdge[];
|
|
40
|
+
readonly unresolved: readonly UnresolvedRef[];
|
|
41
|
+
}
|
|
42
|
+
export interface CachedProducerFiles {
|
|
43
|
+
readonly files: ReadonlyMap<string, CachedFileEntry>;
|
|
44
|
+
/** `undefined` only when `files` is empty — a cold cache. */
|
|
45
|
+
readonly reachedResolution: number | undefined;
|
|
46
|
+
readonly skippedFiles: readonly SkippedFile[];
|
|
47
|
+
}
|
|
48
|
+
/** Sentinel `file_path` for a batch's fileless/cross-file contribution — nodes
|
|
49
|
+
* with no `file`, plus edges whose `from` node isn't attributable to any one
|
|
50
|
+
* cached file. Never compared for change on its own (see the module doc); only
|
|
51
|
+
* ever reused as part of a whole-producer reconstruction. */
|
|
52
|
+
export declare const FILELESS_CACHE_KEY = "";
|
|
53
|
+
export declare function getCachedFiles(driver: SqlDriver, producedBy: string, repo: string): CachedProducerFiles;
|
|
54
|
+
/** Replaces the entire per-file cache for one producer — always a full
|
|
55
|
+
* delete+reinsert, never a partial update: the caller only calls this right
|
|
56
|
+
* after a real, complete `emit()`, so partial-update bookkeeping (which rows
|
|
57
|
+
* survive, which don't) is not a distinction this module needs to make. */
|
|
58
|
+
export declare function replaceCachedFiles(driver: SqlDriver, producedBy: string, repo: string, entries: readonly CachedFileEntry[], reachedResolution: number, skippedFiles: readonly SkippedFile[], now: number): void;
|
|
59
|
+
/** Partitions one producer's batch by file, so each file's contribution can be
|
|
60
|
+
* cached and later reconstructed without the others. Every entry in
|
|
61
|
+
* `sourceFiles` gets a bucket even when it contributed nothing (an empty file,
|
|
62
|
+
* or one that only produced unresolved refs) — omitting it would silently drop
|
|
63
|
+
* it from a reconstructed batch's `sourceFiles`, which is exactly the eviction
|
|
64
|
+
* bug this module exists to prevent. An edge is attributed to the file of its
|
|
65
|
+
* `from` node when that node is in this same batch; otherwise (and for every
|
|
66
|
+
* fileless node) it lands in `FILELESS_CACHE_KEY`. `contentHash` is left for
|
|
67
|
+
* the caller to fill in (it needs to read the file off disk, which this pure
|
|
68
|
+
* function deliberately does not do) — this returns nodes/edges/unresolved
|
|
69
|
+
* only, keyed by file path. */
|
|
70
|
+
export declare function partitionBatchByFile(batch: {
|
|
71
|
+
readonly sourceFiles: readonly string[];
|
|
72
|
+
readonly nodes: readonly IRNode[];
|
|
73
|
+
readonly edges: readonly IREdge[];
|
|
74
|
+
readonly unresolved: readonly UnresolvedRef[];
|
|
75
|
+
}): ReadonlyMap<string, {
|
|
76
|
+
nodes: IRNode[];
|
|
77
|
+
edges: IREdge[];
|
|
78
|
+
unresolved: UnresolvedRef[];
|
|
79
|
+
}>;
|
|
80
|
+
export interface IncrementalReuseInput {
|
|
81
|
+
/** The real (non-sentinel) file paths a producer's cache currently holds —
|
|
82
|
+
* i.e. `[...getCachedFiles(...).files.keys()].filter((k) => k !== FILELESS_CACHE_KEY)`. */
|
|
83
|
+
readonly cachedPaths: readonly string[];
|
|
84
|
+
/** Every path the working tree currently has, or `null` when it could not be
|
|
85
|
+
* determined (git unavailable, not a repository, ...). `null` always forces a
|
|
86
|
+
* full reparse — "cannot tell whether a new file appeared" is never treated
|
|
87
|
+
* as "no new file appeared". */
|
|
88
|
+
readonly repoPaths: readonly string[] | null;
|
|
89
|
+
/** Current on-disk content hash, for `cachedPaths` only — the caller need not
|
|
90
|
+
* hash anything else, since only `cachedPaths` participate in the isChanged
|
|
91
|
+
* check below. */
|
|
92
|
+
readonly currentHashes: ReadonlyMap<string, string>;
|
|
93
|
+
readonly cachedHashes: ReadonlyMap<string, string>;
|
|
94
|
+
}
|
|
95
|
+
export interface IncrementalReuseDecision {
|
|
96
|
+
readonly canReuse: boolean;
|
|
97
|
+
readonly reason: string;
|
|
98
|
+
}
|
|
99
|
+
/** Whole-producer reuse decision (Gap 6 step 3). Deliberately conservative and
|
|
100
|
+
* deliberately NOT per-file: partial (some-files-cached, some-fresh) reuse
|
|
101
|
+
* would require re-invoking a `LanguageAdapter` on a file subset, which this
|
|
102
|
+
* codebase has no safe, verified way to do (adapters live in the separate
|
|
103
|
+
* `descry-adapters` repo and are a black box behind `IRSource.emit()` — see
|
|
104
|
+
* the module doc). This function instead answers one coarser, safe question:
|
|
105
|
+
* "is EVERY file this producer read last time still byte-identical, with no
|
|
106
|
+
* new candidate file added?" When yes, the whole cached batch can be reused
|
|
107
|
+
* verbatim; when no (or when it cannot be determined), the caller must fall
|
|
108
|
+
* back to a full `emit()` — a decision that is always safe, just sometimes
|
|
109
|
+
* more conservative than strictly necessary (rule 7: a skip that never
|
|
110
|
+
* triggers is still correct, only slower).
|
|
111
|
+
*
|
|
112
|
+
* The "new candidate file" check is itself a heuristic, disclosed here rather
|
|
113
|
+
* than silently assumed sound: it flags any current repo path whose extension
|
|
114
|
+
* matches an extension already seen among `cachedPaths`. A file whose extension
|
|
115
|
+
* this producer has genuinely never emitted before (the very first `.mts` file
|
|
116
|
+
* in a repo the adapter had previously only seen as `.ts`) would not be flagged
|
|
117
|
+
* and could be silently missed. This is a narrow, accepted gap — analogous in
|
|
118
|
+
* kind to this codebase's other disclosed heuristics — not a hidden one. */
|
|
119
|
+
export declare function planIncrementalReuse(input: IncrementalReuseInput): IncrementalReuseDecision;
|
|
120
|
+
//# sourceMappingURL=incremental-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental-cache.d.ts","sourceRoot":"","sources":["../../src/store/incremental-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACrD,6DAA6D;IAC7D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,CAAC;CAC/C;AAYD;;;8DAG8D;AAC9D,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CA2BvG;AAED;;;4EAG4E;AAC5E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,GAAG,EAAE,MAAM,GACV,IAAI,CAyBN;AAED;;;;;;;;;;gCAUgC;AAChC,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC/C,GAAG,WAAW,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CA4BzF;AAED,MAAM,WAAW,qBAAqB;IACpC;gGAC4F;IAC5F,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC;;;qCAGiC;IACjC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7C;;uBAEmB;IACnB,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpD,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;4EAmB4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,qBAAqB,GAAG,wBAAwB,CA6B3F"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gap 6 (steps 1-2): a real per-file IR cache, not a hash-gate.
|
|
3
|
+
*
|
|
4
|
+
* `file_hashes` (writer.ts) only ever stored a hash — no cached IR — so there was
|
|
5
|
+
* no way to "still supply" a skipped file's nodes/edges to `writeBatch` without
|
|
6
|
+
* re-parsing it. `writeBatch`'s hard invariant is that the batch a producer
|
|
7
|
+
* supplies is the *complete truth* for `producedBy`+`repo` (DEC-015): anything a
|
|
8
|
+
* prior run owned that a batch doesn't re-supply is evicted. Naive hash-gating
|
|
9
|
+
* that simply omitted an unchanged file from a batch would therefore have the
|
|
10
|
+
* writer silently evict that file's rows as stale.
|
|
11
|
+
*
|
|
12
|
+
* This module stores each file's actual contribution — its nodes, the edges
|
|
13
|
+
* whose `from` node lives in it, and its unresolved refs — so a caller (see
|
|
14
|
+
* `analyze.ts`) can reconstruct a producer's *entire* batch from cache when
|
|
15
|
+
* nothing changed, and re-supply it to `writeBatch` intact. The invariant is
|
|
16
|
+
* preserved by construction: the reconstructed batch is byte-for-byte what the
|
|
17
|
+
* last real run produced, so nothing the writer would evict is ever missing.
|
|
18
|
+
*
|
|
19
|
+
* Cross-file invalidation is explicitly out of scope for this pass (a renamed
|
|
20
|
+
* export in file A affecting file B's call-resolution edges, say). That is safe
|
|
21
|
+
* here only because the decision this cache feeds (`planIncrementalReuse` in
|
|
22
|
+
* `analyze.ts`) is whole-producer, not per-file: a batch is only ever
|
|
23
|
+
* reconstructed from cache when *every* file the producer last read is
|
|
24
|
+
* confirmed byte-identical, so whatever cross-file synthesis the producer would
|
|
25
|
+
* have computed from its complete input is unchanged too (assuming the producer
|
|
26
|
+
* is a deterministic function of its input, which `writeBatch`'s own same-batch
|
|
27
|
+
* idempotence already relies on). What this module does NOT support — and must
|
|
28
|
+
* not be used for — is reusing one file's cached contribution while a *sibling*
|
|
29
|
+
* file changed; that would risk exactly the cross-file staleness this comment
|
|
30
|
+
* warns about, which is why nothing here offers a partial/per-file reconstruction
|
|
31
|
+
* path.
|
|
32
|
+
*/
|
|
33
|
+
import { extname } from "node:path";
|
|
34
|
+
/** Sentinel `file_path` for a batch's fileless/cross-file contribution — nodes
|
|
35
|
+
* with no `file`, plus edges whose `from` node isn't attributable to any one
|
|
36
|
+
* cached file. Never compared for change on its own (see the module doc); only
|
|
37
|
+
* ever reused as part of a whole-producer reconstruction. */
|
|
38
|
+
export const FILELESS_CACHE_KEY = "";
|
|
39
|
+
export function getCachedFiles(driver, producedBy, repo) {
|
|
40
|
+
const rows = driver
|
|
41
|
+
.prepare(`SELECT file_path, content_hash, nodes_json, edges_json, unresolved_json, reached_resolution, skipped_files_json
|
|
42
|
+
FROM file_ir_cache WHERE produced_by = ? AND repo = ?`)
|
|
43
|
+
.all(producedBy, repo);
|
|
44
|
+
const files = new Map();
|
|
45
|
+
let reachedResolution;
|
|
46
|
+
let skippedFiles = [];
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
files.set(row.file_path, {
|
|
49
|
+
filePath: row.file_path,
|
|
50
|
+
contentHash: row.content_hash,
|
|
51
|
+
nodes: JSON.parse(row.nodes_json),
|
|
52
|
+
edges: JSON.parse(row.edges_json),
|
|
53
|
+
unresolved: JSON.parse(row.unresolved_json),
|
|
54
|
+
});
|
|
55
|
+
// Denormalised onto every row for this producer (schema.ts's comment on
|
|
56
|
+
// file_ir_cache explains why) — any row's copy is as good as any other's.
|
|
57
|
+
reachedResolution = row.reached_resolution;
|
|
58
|
+
skippedFiles = JSON.parse(row.skipped_files_json);
|
|
59
|
+
}
|
|
60
|
+
return { files, reachedResolution, skippedFiles };
|
|
61
|
+
}
|
|
62
|
+
/** Replaces the entire per-file cache for one producer — always a full
|
|
63
|
+
* delete+reinsert, never a partial update: the caller only calls this right
|
|
64
|
+
* after a real, complete `emit()`, so partial-update bookkeeping (which rows
|
|
65
|
+
* survive, which don't) is not a distinction this module needs to make. */
|
|
66
|
+
export function replaceCachedFiles(driver, producedBy, repo, entries, reachedResolution, skippedFiles, now) {
|
|
67
|
+
driver.transaction(() => {
|
|
68
|
+
driver.prepare("DELETE FROM file_ir_cache WHERE produced_by = ? AND repo = ?").run(producedBy, repo);
|
|
69
|
+
const insert = driver.prepare(`INSERT INTO file_ir_cache
|
|
70
|
+
(produced_by, repo, file_path, content_hash, nodes_json, edges_json, unresolved_json,
|
|
71
|
+
reached_resolution, skipped_files_json, cached_at)
|
|
72
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
73
|
+
const skippedJson = JSON.stringify(skippedFiles);
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
insert.run(producedBy, repo, entry.filePath, entry.contentHash, JSON.stringify(entry.nodes), JSON.stringify(entry.edges), JSON.stringify(entry.unresolved), reachedResolution, skippedJson, now);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Partitions one producer's batch by file, so each file's contribution can be
|
|
80
|
+
* cached and later reconstructed without the others. Every entry in
|
|
81
|
+
* `sourceFiles` gets a bucket even when it contributed nothing (an empty file,
|
|
82
|
+
* or one that only produced unresolved refs) — omitting it would silently drop
|
|
83
|
+
* it from a reconstructed batch's `sourceFiles`, which is exactly the eviction
|
|
84
|
+
* bug this module exists to prevent. An edge is attributed to the file of its
|
|
85
|
+
* `from` node when that node is in this same batch; otherwise (and for every
|
|
86
|
+
* fileless node) it lands in `FILELESS_CACHE_KEY`. `contentHash` is left for
|
|
87
|
+
* the caller to fill in (it needs to read the file off disk, which this pure
|
|
88
|
+
* function deliberately does not do) — this returns nodes/edges/unresolved
|
|
89
|
+
* only, keyed by file path. */
|
|
90
|
+
export function partitionBatchByFile(batch) {
|
|
91
|
+
const buckets = new Map();
|
|
92
|
+
const ensure = (key) => {
|
|
93
|
+
let bucket = buckets.get(key);
|
|
94
|
+
if (bucket === undefined) {
|
|
95
|
+
bucket = { nodes: [], edges: [], unresolved: [] };
|
|
96
|
+
buckets.set(key, bucket);
|
|
97
|
+
}
|
|
98
|
+
return bucket;
|
|
99
|
+
};
|
|
100
|
+
for (const file of batch.sourceFiles)
|
|
101
|
+
ensure(file);
|
|
102
|
+
ensure(FILELESS_CACHE_KEY);
|
|
103
|
+
const nodeFile = new Map();
|
|
104
|
+
for (const node of batch.nodes) {
|
|
105
|
+
const key = node.file ?? FILELESS_CACHE_KEY;
|
|
106
|
+
nodeFile.set(node.id, key);
|
|
107
|
+
ensure(key).nodes.push(node);
|
|
108
|
+
}
|
|
109
|
+
for (const edge of batch.edges) {
|
|
110
|
+
ensure(nodeFile.get(edge.from) ?? FILELESS_CACHE_KEY).edges.push(edge);
|
|
111
|
+
}
|
|
112
|
+
for (const ref of batch.unresolved) {
|
|
113
|
+
ensure(ref.file ?? FILELESS_CACHE_KEY).unresolved.push(ref);
|
|
114
|
+
}
|
|
115
|
+
return buckets;
|
|
116
|
+
}
|
|
117
|
+
/** Whole-producer reuse decision (Gap 6 step 3). Deliberately conservative and
|
|
118
|
+
* deliberately NOT per-file: partial (some-files-cached, some-fresh) reuse
|
|
119
|
+
* would require re-invoking a `LanguageAdapter` on a file subset, which this
|
|
120
|
+
* codebase has no safe, verified way to do (adapters live in the separate
|
|
121
|
+
* `descry-adapters` repo and are a black box behind `IRSource.emit()` — see
|
|
122
|
+
* the module doc). This function instead answers one coarser, safe question:
|
|
123
|
+
* "is EVERY file this producer read last time still byte-identical, with no
|
|
124
|
+
* new candidate file added?" When yes, the whole cached batch can be reused
|
|
125
|
+
* verbatim; when no (or when it cannot be determined), the caller must fall
|
|
126
|
+
* back to a full `emit()` — a decision that is always safe, just sometimes
|
|
127
|
+
* more conservative than strictly necessary (rule 7: a skip that never
|
|
128
|
+
* triggers is still correct, only slower).
|
|
129
|
+
*
|
|
130
|
+
* The "new candidate file" check is itself a heuristic, disclosed here rather
|
|
131
|
+
* than silently assumed sound: it flags any current repo path whose extension
|
|
132
|
+
* matches an extension already seen among `cachedPaths`. A file whose extension
|
|
133
|
+
* this producer has genuinely never emitted before (the very first `.mts` file
|
|
134
|
+
* in a repo the adapter had previously only seen as `.ts`) would not be flagged
|
|
135
|
+
* and could be silently missed. This is a narrow, accepted gap — analogous in
|
|
136
|
+
* kind to this codebase's other disclosed heuristics — not a hidden one. */
|
|
137
|
+
export function planIncrementalReuse(input) {
|
|
138
|
+
if (input.cachedPaths.length === 0) {
|
|
139
|
+
return { canReuse: false, reason: "no cached files for this producer yet" };
|
|
140
|
+
}
|
|
141
|
+
if (input.repoPaths === null) {
|
|
142
|
+
return { canReuse: false, reason: "could not determine the current file listing" };
|
|
143
|
+
}
|
|
144
|
+
const extensions = new Set(input.cachedPaths.map((path) => extname(path)));
|
|
145
|
+
const candidatePaths = [...new Set(input.repoPaths.filter((path) => extensions.has(extname(path))))].sort();
|
|
146
|
+
const cachedSorted = [...input.cachedPaths].sort();
|
|
147
|
+
if (candidatePaths.length !== cachedSorted.length ||
|
|
148
|
+
candidatePaths.some((path, index) => path !== cachedSorted[index])) {
|
|
149
|
+
return {
|
|
150
|
+
canReuse: false,
|
|
151
|
+
reason: "the set of candidate files changed since the last run (added, removed, or renamed)",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
for (const path of cachedSorted) {
|
|
155
|
+
if (input.currentHashes.get(path) !== input.cachedHashes.get(path)) {
|
|
156
|
+
return { canReuse: false, reason: `${path} changed since the last run` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { canReuse: true, reason: `all ${String(cachedSorted.length)} cached file(s) unchanged` };
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=incremental-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental-cache.js","sourceRoot":"","sources":["../../src/store/incremental-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/store/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export { DERIVED_TABLES, DERIVED_DROP_ORDER, EARNED_TABLES, IR_SCHEMA_VERSION, N
|
|
|
23
23
|
export type { DerivedTable, EarnedTable } from "./schema.ts";
|
|
24
24
|
export { writeBatch, invalidateAdapter, runIdFor, putFileHash, getFileHash, evictStaleConfirmedIncidentNodes, } from "./writer.ts";
|
|
25
25
|
export type { WriteResult, WriteOptions } from "./writer.ts";
|
|
26
|
+
export { FILELESS_CACHE_KEY, getCachedFiles, replaceCachedFiles, partitionBatchByFile, planIncrementalReuse, } from "./incremental-cache.ts";
|
|
27
|
+
export type { CachedFileEntry, CachedProducerFiles, IncrementalReuseDecision, IncrementalReuseInput, } from "./incremental-cache.ts";
|
|
26
28
|
export { getNode, findNodes, nodesOfType, nodesInFiles, outgoing, incoming, counts, contentHash } from "./reader.ts";
|
|
27
29
|
export type { StoreCounts } from "./reader.ts";
|
|
28
30
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGjE,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,KAAK,IAAI,IAAI,CAAC;CACf;AAED;iGACiG;AACjG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,KAAK,CAwB7E;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3G,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gCAAgC,GACjC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGjE,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,KAAK,IAAI,IAAI,CAAC;CACf;AAED;iGACiG;AACjG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,KAAK,CAwB7E;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3G,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gCAAgC,GACjC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/store/index.js
CHANGED
|
@@ -36,5 +36,6 @@ export { NodeSqliteDriver } from "./driver/node-sqlite.js";
|
|
|
36
36
|
export { migrate, verifyReadOnly, dropDerived, rebuildStaleGraph, SchemaVersionError } from "./migrate.js";
|
|
37
37
|
export { DERIVED_TABLES, DERIVED_DROP_ORDER, EARNED_TABLES, IR_SCHEMA_VERSION, NODE_ID_SCHEME, SCHEMA_SQL, } from "./schema.js";
|
|
38
38
|
export { writeBatch, invalidateAdapter, runIdFor, putFileHash, getFileHash, evictStaleConfirmedIncidentNodes, } from "./writer.js";
|
|
39
|
+
export { FILELESS_CACHE_KEY, getCachedFiles, replaceCachedFiles, partitionBatchByFile, planIncrementalReuse, } from "./incremental-cache.js";
|
|
39
40
|
export { getNode, findNodes, nodesOfType, nodesInFiles, outgoing, incoming, counts, contentHash } from "./reader.js";
|
|
40
41
|
//# sourceMappingURL=index.js.map
|
package/dist/store/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAcvD;iGACiG;AACjG,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,UAA4B,EAAE;IACpE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;IACxD,MAAM,WAAW,GAAgB;QAC/B,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1D,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;KAC1E,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,0FAA0F;QAC1F,wFAAwF;QACxF,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC9B,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;QACjE,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO;YACL,MAAM;YACN,OAAO;YACP,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;SAC5B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AASjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3G,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gCAAgC,GACjC,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAcvD;iGACiG;AACjG,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,UAA4B,EAAE;IACpE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;IACxD,MAAM,WAAW,GAAgB;QAC/B,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1D,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;KAC1E,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,0FAA0F;QAC1F,wFAAwF;QACxF,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC9B,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;QACjE,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO;YACL,MAAM;YACN,OAAO;YACP,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;SAC5B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AASjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3G,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,WAAW,EACX,gCAAgC,GACjC,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAQhC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* `(source/target, type)` indices (max in-degree 1,036). Derived/earned: DEC-017. */
|
|
6
6
|
/** Bumped when the IR itself changes shape, not when an adapter changes.
|
|
7
7
|
* DEC-017: on mismatch, refuse to read, drop derived, rebuild, migrate earned. */
|
|
8
|
-
export declare const IR_SCHEMA_VERSION =
|
|
8
|
+
export declare const IR_SCHEMA_VERSION = 6;
|
|
9
9
|
/** Tag on every node id, derived from the constant, not restated — a
|
|
10
10
|
* literal here would keep validating the old scheme after an id change. */
|
|
11
11
|
export declare const NODE_ID_SCHEME: string;
|
|
12
12
|
/**
|
|
13
13
|
* Regenerable from source. Dropping these loses time, never information.
|
|
14
14
|
*/
|
|
15
|
-
export declare const DERIVED_TABLES: readonly ["nodes", "edges", "unresolved_refs", "file_hashes", "embeddings", "adapter_runs"];
|
|
15
|
+
export declare const DERIVED_TABLES: readonly ["nodes", "edges", "unresolved_refs", "file_hashes", "file_ir_cache", "embeddings", "adapter_runs"];
|
|
16
16
|
/** Cannot be regenerated — a witnessed edge, an executed test, or a human's
|
|
17
17
|
* correction exists nowhere in source. These survive every rebuild. */
|
|
18
18
|
export declare const EARNED_TABLES: readonly ["observed_edges", "observed_tests", "edge_corrections", "incident_links", "pr_scopes"];
|
|
@@ -21,6 +21,6 @@ export type EarnedTable = (typeof EARNED_TABLES)[number];
|
|
|
21
21
|
/** Children before parents, stated explicitly (not derived by reversing
|
|
22
22
|
* `DERIVED_TABLES`) — `PRAGMA foreign_keys` can't change mid-transaction,
|
|
23
23
|
* so wrong order fails or leans on cascade to rescue it. */
|
|
24
|
-
export declare const DERIVED_DROP_ORDER: readonly ["embeddings", "unresolved_refs", "edges", "nodes", "file_hashes", "adapter_runs"];
|
|
25
|
-
export declare const SCHEMA_SQL = "\n-- ---------------------------------------------------------------------------\n-- graph_meta \u2014 identity of the store itself. Read before anything else.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS graph_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n) STRICT;\n\n-- ---------------------------------------------------------------------------\n-- adapter_runs \u2014 one row per accepted IRBatch. \u00A724.1's adapter registry state\n-- and DEC-015's invalidation key are the same thing, so they are one table.\n--\n-- id is derived (hash of producer + repo + commit + source files), never an\n-- autoincrement rowid: re-ingesting an identical batch must reuse the same row,\n-- or idempotence is impossible to state.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS adapter_runs (\n id TEXT PRIMARY KEY,\n repo TEXT NOT NULL,\n -- The identity scope workspace-scoped ids in this batch were computed under\n -- (DEC-054). NULL means \"this repository alone\". Recorded because the scope is\n -- configuration: once it changes, no node id in this run can be re-derived from\n -- the row itself, and a workspace typo is otherwise invisible after the fact.\n workspace TEXT,\n commit_sha TEXT NOT NULL,\n adapter_id TEXT NOT NULL, -- the part before '@'\n adapter_version TEXT NOT NULL, -- the part after '@'\n produced_by TEXT NOT NULL, -- adapter_id@adapter_version, as written on every row\n reached_resolution INTEGER NOT NULL,\n source_files TEXT NOT NULL, -- JSON array; the invalidation key (DEC-015)\n file_count INTEGER NOT NULL,\n written_at INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_runs_adapter ON adapter_runs(adapter_id);\nCREATE INDEX IF NOT EXISTS idx_runs_producer ON adapter_runs(produced_by);\n\n-- ---------------------------------------------------------------------------\n-- nodes\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n file_path TEXT, -- null for fileless types (DEC-014)\n start_line INTEGER,\n end_line INTEGER,\n language TEXT, -- correction 4: nullable\n produced_by TEXT NOT NULL,\n resolution INTEGER NOT NULL,\n attrs TEXT NOT NULL, -- JSON; correction 1\n external TEXT, -- JSON ExternalProvenance, null for every in-repo node\n run_id TEXT NOT NULL REFERENCES adapter_runs(id) ON DELETE CASCADE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);\nCREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);\nCREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file_path);\nCREATE INDEX IF NOT EXISTS idx_nodes_resolution ON nodes(resolution);\nCREATE INDEX IF NOT EXISTS idx_nodes_run ON nodes(run_id);\n\n-- ---------------------------------------------------------------------------\n-- edges. Primary key is hash(from, to, type) per DEC-012, so two adapters\n-- finding the same relationship collide by construction \u2014 which is what makes\n-- a merge policy statable at all. 30% of prior-art edges were exact\n-- duplicates, so this is load-bearing rather than tidy.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n source TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n target TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n type TEXT NOT NULL,\n resolution INTEGER NOT NULL,\n confidence REAL NOT NULL,\n produced_by TEXT NOT NULL,\n observed_by_run TEXT, -- present exactly when resolution = 4\n attrs TEXT, -- JSON or null; corrections 1 and 2\n run_id TEXT NOT NULL REFERENCES adapter_runs(id) ON DELETE CASCADE,\n CHECK (resolution BETWEEN 0 AND 4),\n CHECK (confidence BETWEEN 0.0 AND 1.0),\n CHECK ((resolution = 4) = (observed_by_run IS NOT NULL))\n) STRICT;\n\n-- Correction 5: the real access paths. Every hop filters by type.\nCREATE INDEX IF NOT EXISTS idx_edges_source_type ON edges(source, type);\nCREATE INDEX IF NOT EXISTS idx_edges_target_type ON edges(target, type);\nCREATE INDEX IF NOT EXISTS idx_edges_resolution ON edges(resolution);\nCREATE INDEX IF NOT EXISTS idx_edges_run ON edges(run_id);\n\n-- ---------------------------------------------------------------------------\n-- unresolved_refs \u2014 DEC-016. What an adapter saw but could not resolve.\n--\n-- Not edges, because a name-matched guess is a false claim under \u00A711B.1. Not\n-- discarded either, because then \u00A711B.6 recall has no denominator and \u00A720.2's\n-- \"not analysable\" category has nothing to count. On the prior-art graphs this\n-- is 52-60% of everything emitted.\n-- ---------------------------------------------------------------------------\n-- id is a content hash, never an autoincrement rowid: an insertion-order key\n-- would make the permuted-ingest determinism proof impossible to state, and two\n-- byte-identical references are the same reference.\nCREATE TABLE IF NOT EXISTS unresolved_refs (\n id TEXT PRIMARY KEY,\n from_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n edge_type TEXT NOT NULL,\n raw_target TEXT NOT NULL,\n file_path TEXT,\n line INTEGER,\n produced_by TEXT NOT NULL,\n reason TEXT NOT NULL,\n -- DEC-242's attrs.blockedBy/attrs.refusalClass live here, JSON or null --\n -- same optional-attrs treatment as edges (correction 2), and deliberately\n -- excluded from id's content hash below: this is classification of a\n -- refusal, not part of what makes two refusals the same refusal. Added in\n -- schema version 3 -- DEC-017 governs what that bump costs an existing\n -- store (drop derived, rebuild; earned tables untouched).\n attrs TEXT,\n run_id TEXT NOT NULL REFERENCES adapter_runs(id) ON DELETE CASCADE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_unresolved_from ON unresolved_refs(from_node_id);\nCREATE INDEX IF NOT EXISTS idx_unresolved_target ON unresolved_refs(raw_target);\nCREATE INDEX IF NOT EXISTS idx_unresolved_run ON unresolved_refs(run_id);\n\n-- ---------------------------------------------------------------------------\n-- file_hashes \u2014 \u00A711.14 rebuild acceleration. Layer 1 change detection is\n-- defined against this table and v2.1 never declared it.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS file_hashes (\n file_path TEXT PRIMARY KEY,\n hash TEXT NOT NULL,\n last_parsed_at INTEGER NOT NULL\n) STRICT;\n\n-- ---------------------------------------------------------------------------\n-- embeddings \u2014 pointer table only. No vector extension is loaded in this build;\n-- \u00A78.2 keeps vectors in pgvector hosted or a local extension, and a column of\n-- serialised floats with no index would be a scan, not a search.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS embeddings (\n node_id TEXT PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,\n vector_reference TEXT NOT NULL,\n model_version TEXT NOT NULL\n) STRICT;\n\n-- ===========================================================================\n-- EARNED \u2014 DEC-017. Never dropped. No REFERENCES into the derived tables:\n-- a foreign key would cascade these away with the graph they describe, which\n-- is precisely the outcome the partition exists to prevent.\n-- ===========================================================================\n\n-- R4 evidence. An edge witnessed firing during a live run (\u00A711B.3). This is the\n-- product's core moat and cannot be recomputed from source at any price.\nCREATE TABLE IF NOT EXISTS observed_edges (\n edge_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n from_node_id TEXT NOT NULL,\n to_node_id TEXT NOT NULL,\n edge_type TEXT NOT NULL,\n commit_sha TEXT NOT NULL,\n observed_at INTEGER NOT NULL,\n PRIMARY KEY (edge_id, run_id)\n) STRICT;\n\n-- Which tests actually ran, and what happened. The coverage axis \u2014 AI layer \u00A720's\n-- \"Verification status\" \u2014 a different question from confidence, needing a\n-- different carrier.\n--\n-- Earned for the same reason as observed_edges, one step over: nothing in the\n-- source says which tests executed, so a rebuild that dropped this would turn\n-- \"what I actually verified\" silently back into \"what might be affected\".\n--\n-- Not an edge, deliberately. A test-runner's evidence carries no stack, so an\n-- observed test names exactly one node \u2014 its own TEST_CASE \u2014 and pairing it with\n-- a function it merely co-occurred with would mint an edge from temporal\n-- coincidence. That is why this is a table and not a promotion of a TESTS edge\n-- to R4; see DEC-NEXT-test-run-coverage-has-no-carrier.md.\n--\n-- (node_id, run_id) is the identity: two runs of one test are two facts, while a\n-- retry inside one run is the same fact re-observed, so the latter updates in\n-- place. Within-run retries are therefore not distinguishable here \u2014 a disclosed\n-- limit, not an oversight; this table is a coverage carrier, not a flake detector.\nCREATE TABLE IF NOT EXISTS observed_tests (\n node_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n qualified_name TEXT NOT NULL,\n outcome TEXT NOT NULL, -- 'passed' | 'failed' | 'skipped'\n -- REAL, not INTEGER, and this was an INTEGER until a real run proved it could\n -- not be. Runners report fractional milliseconds \u2014 node:test emits 2.316958 \u2014\n -- and a STRICT table refuses that value outright, so every write failed on\n -- first contact with an actual suite while the unit tests, which all passed\n -- integer literals, stayed green. Rounding was the other option and is worse:\n -- sub-millisecond tests are ordinary here, and 0.36 rounded to 0 reads as\n -- \"instant\", the same falsehood the null-rather-than-zero rule below refuses.\n duration_ms REAL,\n commit_sha TEXT NOT NULL,\n observed_at INTEGER NOT NULL,\n PRIMARY KEY (node_id, run_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_observed_tests_run ON observed_tests(run_id);\n\n-- \u00A711B.5. A human said an edge was wrong, or runtime contradicted it. Feeding\n-- this back is how precision improves; losing it repeats the same false claim.\nCREATE TABLE IF NOT EXISTS edge_corrections (\n edge_id TEXT PRIMARY KEY,\n verdict TEXT NOT NULL, -- 'wrong' | 'right'\n reason TEXT NOT NULL,\n corrected_at INTEGER NOT NULL\n) STRICT;\n\n-- \u00A714.2. One pull request's computed scope, and it is EARNED rather than derived\n-- for a reason that is easy to get backwards.\n--\n-- Everything else on the derived side can be regenerated from source because the\n-- source is what produced it. A PR scope cannot: it was computed against a graph\n-- at one commit, and that graph moves. Recomputing it later answers a DIFFERENT\n-- question and returns a plausible answer to it \u2014 so dropping this table does\n-- not lose time, it loses the COMPARISON BASIS. Overlap scoring between two PRs\n-- is only meaningful when both scopes were computed against the same graph, and\n-- nothing in a recomputed row would say that it was not.\n--\n-- graph_commit_sha is therefore not decoration: it is the field that makes two\n-- rows comparable or not, and a reader that ignores it will happily overlap a\n-- scope from last week with one from today and report a number.\n--\n-- scope_json holds the node id set. Kept opaque here on purpose: the shape of a\n-- scope is the scoping layer's business, and a column per dimension would need\n-- a migration every time that layer learns something. What the store guarantees\n-- is that the bytes come back unchanged with the commit they were computed at.\nCREATE TABLE IF NOT EXISTS pr_scopes (\n pr_id TEXT NOT NULL,\n repo TEXT NOT NULL,\n -- The head commit of the PR itself.\n head_sha TEXT NOT NULL,\n -- The commit the GRAPH was at when this scope was computed. Two scopes are\n -- comparable only when these agree.\n graph_commit_sha TEXT NOT NULL,\n scope_json TEXT NOT NULL,\n node_count INTEGER NOT NULL,\n computed_at INTEGER NOT NULL,\n PRIMARY KEY (repo, pr_id, graph_commit_sha)\n) STRICT;\n\n-- The access path for overlap scoring: every scope computed against one graph.\n-- Without it, comparing N PRs scans the whole table N times.\nCREATE INDEX IF NOT EXISTS idx_pr_scopes_graph ON pr_scopes(repo, graph_commit_sha);\n\n-- Incident and fix-pattern links. Accumulated from real failures over time.\nCREATE TABLE IF NOT EXISTS incident_links (\n incident_node_id TEXT NOT NULL,\n target_node_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (incident_node_id, target_node_id, kind)\n) STRICT;\n";
|
|
24
|
+
export declare const DERIVED_DROP_ORDER: readonly ["embeddings", "unresolved_refs", "edges", "nodes", "file_hashes", "file_ir_cache", "adapter_runs"];
|
|
25
|
+
export declare const SCHEMA_SQL = "\n-- ---------------------------------------------------------------------------\n-- graph_meta \u2014 identity of the store itself. Read before anything else.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS graph_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n) STRICT;\n\n-- ---------------------------------------------------------------------------\n-- adapter_runs \u2014 one row per accepted IRBatch. \u00A724.1's adapter registry state\n-- and DEC-015's invalidation key are the same thing, so they are one table.\n--\n-- id is derived (hash of producer + repo + commit + source files), never an\n-- autoincrement rowid: re-ingesting an identical batch must reuse the same row,\n-- or idempotence is impossible to state.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS adapter_runs (\n id TEXT PRIMARY KEY,\n repo TEXT NOT NULL,\n -- The identity scope workspace-scoped ids in this batch were computed under\n -- (DEC-054). NULL means \"this repository alone\". Recorded because the scope is\n -- configuration: once it changes, no node id in this run can be re-derived from\n -- the row itself, and a workspace typo is otherwise invisible after the fact.\n workspace TEXT,\n commit_sha TEXT NOT NULL,\n adapter_id TEXT NOT NULL, -- the part before '@'\n adapter_version TEXT NOT NULL, -- the part after '@'\n produced_by TEXT NOT NULL, -- adapter_id@adapter_version, as written on every row\n reached_resolution INTEGER NOT NULL,\n source_files TEXT NOT NULL, -- JSON array; the invalidation key (DEC-015)\n file_count INTEGER NOT NULL,\n written_at INTEGER NOT NULL\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_runs_adapter ON adapter_runs(adapter_id);\nCREATE INDEX IF NOT EXISTS idx_runs_producer ON adapter_runs(produced_by);\n\n-- ---------------------------------------------------------------------------\n-- nodes\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n file_path TEXT, -- null for fileless types (DEC-014)\n start_line INTEGER,\n end_line INTEGER,\n language TEXT, -- correction 4: nullable\n produced_by TEXT NOT NULL,\n resolution INTEGER NOT NULL,\n attrs TEXT NOT NULL, -- JSON; correction 1\n external TEXT, -- JSON ExternalProvenance, null for every in-repo node\n run_id TEXT NOT NULL REFERENCES adapter_runs(id) ON DELETE CASCADE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);\nCREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);\nCREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file_path);\nCREATE INDEX IF NOT EXISTS idx_nodes_resolution ON nodes(resolution);\nCREATE INDEX IF NOT EXISTS idx_nodes_run ON nodes(run_id);\n\n-- ---------------------------------------------------------------------------\n-- edges. Primary key is hash(from, to, type) per DEC-012, so two adapters\n-- finding the same relationship collide by construction \u2014 which is what makes\n-- a merge policy statable at all. 30% of prior-art edges were exact\n-- duplicates, so this is load-bearing rather than tidy.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n source TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n target TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n type TEXT NOT NULL,\n resolution INTEGER NOT NULL,\n confidence REAL NOT NULL,\n produced_by TEXT NOT NULL,\n observed_by_run TEXT, -- present exactly when resolution = 4\n attrs TEXT, -- JSON or null; corrections 1 and 2\n run_id TEXT NOT NULL REFERENCES adapter_runs(id) ON DELETE CASCADE,\n CHECK (resolution BETWEEN 0 AND 4),\n CHECK (confidence BETWEEN 0.0 AND 1.0),\n CHECK ((resolution = 4) = (observed_by_run IS NOT NULL))\n) STRICT;\n\n-- Correction 5: the real access paths. Every hop filters by type.\nCREATE INDEX IF NOT EXISTS idx_edges_source_type ON edges(source, type);\nCREATE INDEX IF NOT EXISTS idx_edges_target_type ON edges(target, type);\nCREATE INDEX IF NOT EXISTS idx_edges_resolution ON edges(resolution);\nCREATE INDEX IF NOT EXISTS idx_edges_run ON edges(run_id);\n\n-- ---------------------------------------------------------------------------\n-- unresolved_refs \u2014 DEC-016. What an adapter saw but could not resolve.\n--\n-- Not edges, because a name-matched guess is a false claim under \u00A711B.1. Not\n-- discarded either, because then \u00A711B.6 recall has no denominator and \u00A720.2's\n-- \"not analysable\" category has nothing to count. On the prior-art graphs this\n-- is 52-60% of everything emitted.\n-- ---------------------------------------------------------------------------\n-- id is a content hash, never an autoincrement rowid: an insertion-order key\n-- would make the permuted-ingest determinism proof impossible to state, and two\n-- byte-identical references are the same reference.\nCREATE TABLE IF NOT EXISTS unresolved_refs (\n id TEXT PRIMARY KEY,\n from_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\n edge_type TEXT NOT NULL,\n raw_target TEXT NOT NULL,\n file_path TEXT,\n line INTEGER,\n produced_by TEXT NOT NULL,\n reason TEXT NOT NULL,\n -- DEC-242's attrs.blockedBy/attrs.refusalClass live here, JSON or null --\n -- same optional-attrs treatment as edges (correction 2), and deliberately\n -- excluded from id's content hash below: this is classification of a\n -- refusal, not part of what makes two refusals the same refusal. Added in\n -- schema version 3 -- DEC-017 governs what that bump costs an existing\n -- store (drop derived, rebuild; earned tables untouched).\n attrs TEXT,\n run_id TEXT NOT NULL REFERENCES adapter_runs(id) ON DELETE CASCADE\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_unresolved_from ON unresolved_refs(from_node_id);\nCREATE INDEX IF NOT EXISTS idx_unresolved_target ON unresolved_refs(raw_target);\nCREATE INDEX IF NOT EXISTS idx_unresolved_run ON unresolved_refs(run_id);\n\n-- ---------------------------------------------------------------------------\n-- file_hashes \u2014 \u00A711.14 rebuild acceleration. Layer 1 change detection is\n-- defined against this table and v2.1 never declared it.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS file_hashes (\n file_path TEXT PRIMARY KEY,\n hash TEXT NOT NULL,\n last_parsed_at INTEGER NOT NULL\n) STRICT;\n\n-- ---------------------------------------------------------------------------\n-- file_ir_cache \u2014 Gap 6 incremental indexing. Unlike file_hashes above (which\n-- only ever stores a hash, with no cached IR to reconstruct from \u2014 the reason\n-- naive hash-gating in analyze.ts would silently evict unchanged files' rows),\n-- this holds each file's actual contribution to its producer's last successful\n-- batch, so an unchanged file's nodes/edges/unresolved-refs can be re-supplied\n-- to the writer without re-parsing anything.\n--\n-- Keyed by (produced_by, repo, file_path): two different producers reading the\n-- same path (a polyglot repo) must not collide, and DEC-228 already established\n-- the same for file_hashes' sibling problem.\n--\n-- One row uses file_path = '' as a sentinel for the batch's fileless/cross-file\n-- contribution (nodes with no file, plus edges whose \"from\" node isn't itself\n-- attributable to a single cached file) \u2014 content_hash is meaningless for that\n-- row and never compared; it is only ever reused as part of a whole-producer\n-- reconstruction where every real file already matched (see incremental-cache.ts).\n--\n-- reached_resolution/skipped_files_json are batch-level, not per-file, and are\n-- denormalised onto every row for one producer rather than kept in a second\n-- table \u2014 the same trade this schema already makes for adapter_runs vs a\n-- one-true-row-per-batch design.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS file_ir_cache (\n produced_by TEXT NOT NULL,\n repo TEXT NOT NULL,\n file_path TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n nodes_json TEXT NOT NULL,\n edges_json TEXT NOT NULL,\n unresolved_json TEXT NOT NULL,\n reached_resolution INTEGER NOT NULL,\n skipped_files_json TEXT NOT NULL,\n cached_at INTEGER NOT NULL,\n PRIMARY KEY (produced_by, repo, file_path)\n) STRICT;\n\n-- ---------------------------------------------------------------------------\n-- embeddings \u2014 pointer table only. No vector extension is loaded in this build;\n-- \u00A78.2 keeps vectors in pgvector hosted or a local extension, and a column of\n-- serialised floats with no index would be a scan, not a search.\n-- ---------------------------------------------------------------------------\nCREATE TABLE IF NOT EXISTS embeddings (\n node_id TEXT PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,\n vector_reference TEXT NOT NULL,\n model_version TEXT NOT NULL\n) STRICT;\n\n-- ===========================================================================\n-- EARNED \u2014 DEC-017. Never dropped. No REFERENCES into the derived tables:\n-- a foreign key would cascade these away with the graph they describe, which\n-- is precisely the outcome the partition exists to prevent.\n-- ===========================================================================\n\n-- R4 evidence. An edge witnessed firing during a live run (\u00A711B.3). This is the\n-- product's core moat and cannot be recomputed from source at any price.\nCREATE TABLE IF NOT EXISTS observed_edges (\n edge_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n from_node_id TEXT NOT NULL,\n to_node_id TEXT NOT NULL,\n edge_type TEXT NOT NULL,\n commit_sha TEXT NOT NULL,\n observed_at INTEGER NOT NULL,\n PRIMARY KEY (edge_id, run_id)\n) STRICT;\n\n-- Which tests actually ran, and what happened. The coverage axis \u2014 AI layer \u00A720's\n-- \"Verification status\" \u2014 a different question from confidence, needing a\n-- different carrier.\n--\n-- Earned for the same reason as observed_edges, one step over: nothing in the\n-- source says which tests executed, so a rebuild that dropped this would turn\n-- \"what I actually verified\" silently back into \"what might be affected\".\n--\n-- Not an edge, deliberately. A test-runner's evidence carries no stack, so an\n-- observed test names exactly one node \u2014 its own TEST_CASE \u2014 and pairing it with\n-- a function it merely co-occurred with would mint an edge from temporal\n-- coincidence. That is why this is a table and not a promotion of a TESTS edge\n-- to R4; see DEC-NEXT-test-run-coverage-has-no-carrier.md.\n--\n-- (node_id, run_id) is the identity: two runs of one test are two facts, while a\n-- retry inside one run is the same fact re-observed, so the latter updates in\n-- place. Within-run retries are therefore not distinguishable here \u2014 a disclosed\n-- limit, not an oversight; this table is a coverage carrier, not a flake detector.\nCREATE TABLE IF NOT EXISTS observed_tests (\n node_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n qualified_name TEXT NOT NULL,\n outcome TEXT NOT NULL, -- 'passed' | 'failed' | 'skipped'\n -- REAL, not INTEGER, and this was an INTEGER until a real run proved it could\n -- not be. Runners report fractional milliseconds \u2014 node:test emits 2.316958 \u2014\n -- and a STRICT table refuses that value outright, so every write failed on\n -- first contact with an actual suite while the unit tests, which all passed\n -- integer literals, stayed green. Rounding was the other option and is worse:\n -- sub-millisecond tests are ordinary here, and 0.36 rounded to 0 reads as\n -- \"instant\", the same falsehood the null-rather-than-zero rule below refuses.\n duration_ms REAL,\n commit_sha TEXT NOT NULL,\n observed_at INTEGER NOT NULL,\n PRIMARY KEY (node_id, run_id)\n) STRICT;\n\nCREATE INDEX IF NOT EXISTS idx_observed_tests_run ON observed_tests(run_id);\n\n-- \u00A711B.5. A human said an edge was wrong, or runtime contradicted it. Feeding\n-- this back is how precision improves; losing it repeats the same false claim.\nCREATE TABLE IF NOT EXISTS edge_corrections (\n edge_id TEXT PRIMARY KEY,\n verdict TEXT NOT NULL, -- 'wrong' | 'right'\n reason TEXT NOT NULL,\n corrected_at INTEGER NOT NULL\n) STRICT;\n\n-- \u00A714.2. One pull request's computed scope, and it is EARNED rather than derived\n-- for a reason that is easy to get backwards.\n--\n-- Everything else on the derived side can be regenerated from source because the\n-- source is what produced it. A PR scope cannot: it was computed against a graph\n-- at one commit, and that graph moves. Recomputing it later answers a DIFFERENT\n-- question and returns a plausible answer to it \u2014 so dropping this table does\n-- not lose time, it loses the COMPARISON BASIS. Overlap scoring between two PRs\n-- is only meaningful when both scopes were computed against the same graph, and\n-- nothing in a recomputed row would say that it was not.\n--\n-- graph_commit_sha is therefore not decoration: it is the field that makes two\n-- rows comparable or not, and a reader that ignores it will happily overlap a\n-- scope from last week with one from today and report a number.\n--\n-- scope_json holds the node id set. Kept opaque here on purpose: the shape of a\n-- scope is the scoping layer's business, and a column per dimension would need\n-- a migration every time that layer learns something. What the store guarantees\n-- is that the bytes come back unchanged with the commit they were computed at.\nCREATE TABLE IF NOT EXISTS pr_scopes (\n pr_id TEXT NOT NULL,\n repo TEXT NOT NULL,\n -- The head commit of the PR itself.\n head_sha TEXT NOT NULL,\n -- The commit the GRAPH was at when this scope was computed. Two scopes are\n -- comparable only when these agree.\n graph_commit_sha TEXT NOT NULL,\n scope_json TEXT NOT NULL,\n node_count INTEGER NOT NULL,\n computed_at INTEGER NOT NULL,\n PRIMARY KEY (repo, pr_id, graph_commit_sha)\n) STRICT;\n\n-- The access path for overlap scoring: every scope computed against one graph.\n-- Without it, comparing N PRs scans the whole table N times.\nCREATE INDEX IF NOT EXISTS idx_pr_scopes_graph ON pr_scopes(repo, graph_commit_sha);\n\n-- Incident and fix-pattern links. Accumulated from real failures over time.\nCREATE TABLE IF NOT EXISTS incident_links (\n incident_node_id TEXT NOT NULL,\n target_node_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (incident_node_id, target_node_id, kind)\n) STRICT;\n";
|
|
26
26
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/store/schema.ts"],"names":[],"mappings":"AAAA;;;;sFAIsF;AAItF;mFACmF;AACnF,eAAO,MAAM,iBAAiB,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/store/schema.ts"],"names":[],"mappings":"AAAA;;;;sFAIsF;AAItF;mFACmF;AACnF,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAOnC;4EAC4E;AAC5E,eAAO,MAAM,cAAc,QAAkC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,cAAc,8GAQjB,CAAC;AAEX;wEACwE;AACxE,eAAO,MAAM,aAAa,kGAMhB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;6DAE6D;AAC7D,eAAO,MAAM,kBAAkB,8GAQa,CAAC;AAE7C,eAAO,MAAM,UAAU,2meAqStB,CAAC"}
|
package/dist/store/schema.js
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
import { NODE_ID_PREFIX } from "@descryy/ir";
|
|
7
7
|
/** Bumped when the IR itself changes shape, not when an adapter changes.
|
|
8
8
|
* DEC-017: on mismatch, refuse to read, drop derived, rebuild, migrate earned. */
|
|
9
|
-
export const IR_SCHEMA_VERSION =
|
|
9
|
+
export const IR_SCHEMA_VERSION = 6; // 5 -> 6: API_ENDPOINT/DATABASE_TABLE/DATABASE_COLUMN fold
|
|
10
|
+
// `repo` into their id unconditionally instead of letting a configured workspace strip it
|
|
11
|
+
// (DEC-NEXT-endpoint-identity-repo-scoping) — every id of these three types computed under
|
|
12
|
+
// version 5 is wrong for this build. DEC-017 handles it exactly as designed: refuse to read,
|
|
13
|
+
// drop derived (nodes/edges/...), rebuild from source, keep earned data (R4 observations,
|
|
14
|
+
// edge corrections, incident links) untouched.
|
|
10
15
|
/** Tag on every node id, derived from the constant, not restated — a
|
|
11
16
|
* literal here would keep validating the old scheme after an id change. */
|
|
12
17
|
export const NODE_ID_SCHEME = NODE_ID_PREFIX.replace(":", "");
|
|
@@ -18,6 +23,7 @@ export const DERIVED_TABLES = [
|
|
|
18
23
|
"edges",
|
|
19
24
|
"unresolved_refs",
|
|
20
25
|
"file_hashes",
|
|
26
|
+
"file_ir_cache",
|
|
21
27
|
"embeddings",
|
|
22
28
|
"adapter_runs",
|
|
23
29
|
];
|
|
@@ -39,6 +45,7 @@ export const DERIVED_DROP_ORDER = [
|
|
|
39
45
|
"edges",
|
|
40
46
|
"nodes",
|
|
41
47
|
"file_hashes",
|
|
48
|
+
"file_ir_cache",
|
|
42
49
|
"adapter_runs",
|
|
43
50
|
];
|
|
44
51
|
export const SCHEMA_SQL = `
|
|
@@ -175,6 +182,43 @@ CREATE TABLE IF NOT EXISTS file_hashes (
|
|
|
175
182
|
last_parsed_at INTEGER NOT NULL
|
|
176
183
|
) STRICT;
|
|
177
184
|
|
|
185
|
+
-- ---------------------------------------------------------------------------
|
|
186
|
+
-- file_ir_cache — Gap 6 incremental indexing. Unlike file_hashes above (which
|
|
187
|
+
-- only ever stores a hash, with no cached IR to reconstruct from — the reason
|
|
188
|
+
-- naive hash-gating in analyze.ts would silently evict unchanged files' rows),
|
|
189
|
+
-- this holds each file's actual contribution to its producer's last successful
|
|
190
|
+
-- batch, so an unchanged file's nodes/edges/unresolved-refs can be re-supplied
|
|
191
|
+
-- to the writer without re-parsing anything.
|
|
192
|
+
--
|
|
193
|
+
-- Keyed by (produced_by, repo, file_path): two different producers reading the
|
|
194
|
+
-- same path (a polyglot repo) must not collide, and DEC-228 already established
|
|
195
|
+
-- the same for file_hashes' sibling problem.
|
|
196
|
+
--
|
|
197
|
+
-- One row uses file_path = '' as a sentinel for the batch's fileless/cross-file
|
|
198
|
+
-- contribution (nodes with no file, plus edges whose "from" node isn't itself
|
|
199
|
+
-- attributable to a single cached file) — content_hash is meaningless for that
|
|
200
|
+
-- row and never compared; it is only ever reused as part of a whole-producer
|
|
201
|
+
-- reconstruction where every real file already matched (see incremental-cache.ts).
|
|
202
|
+
--
|
|
203
|
+
-- reached_resolution/skipped_files_json are batch-level, not per-file, and are
|
|
204
|
+
-- denormalised onto every row for one producer rather than kept in a second
|
|
205
|
+
-- table — the same trade this schema already makes for adapter_runs vs a
|
|
206
|
+
-- one-true-row-per-batch design.
|
|
207
|
+
-- ---------------------------------------------------------------------------
|
|
208
|
+
CREATE TABLE IF NOT EXISTS file_ir_cache (
|
|
209
|
+
produced_by TEXT NOT NULL,
|
|
210
|
+
repo TEXT NOT NULL,
|
|
211
|
+
file_path TEXT NOT NULL,
|
|
212
|
+
content_hash TEXT NOT NULL,
|
|
213
|
+
nodes_json TEXT NOT NULL,
|
|
214
|
+
edges_json TEXT NOT NULL,
|
|
215
|
+
unresolved_json TEXT NOT NULL,
|
|
216
|
+
reached_resolution INTEGER NOT NULL,
|
|
217
|
+
skipped_files_json TEXT NOT NULL,
|
|
218
|
+
cached_at INTEGER NOT NULL,
|
|
219
|
+
PRIMARY KEY (produced_by, repo, file_path)
|
|
220
|
+
) STRICT;
|
|
221
|
+
|
|
178
222
|
-- ---------------------------------------------------------------------------
|
|
179
223
|
-- embeddings — pointer table only. No vector extension is loaded in this build;
|
|
180
224
|
-- §8.2 keeps vectors in pgvector hosted or a local extension, and a column of
|
package/dist/store/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/store/schema.ts"],"names":[],"mappings":"AAAA;;;;sFAIsF;AAEtF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;mFACmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/store/schema.ts"],"names":[],"mappings":"AAAA;;;;sFAIsF;AAEtF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;mFACmF;AACnF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,CAAC,2DAA2D;AAC/F,0FAA0F;AAC1F,2FAA2F;AAC3F,6FAA6F;AAC7F,0FAA0F;AAC1F,+CAA+C;AAE/C;4EAC4E;AAC5E,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,OAAO;IACP,OAAO;IACP,iBAAiB;IACjB,aAAa;IACb,eAAe;IACf,YAAY;IACZ,cAAc;CACN,CAAC;AAEX;wEACwE;AACxE,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB;IAClB,gBAAgB;IAChB,WAAW;CACH,CAAC;AAKX;;6DAE6D;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,YAAY;IACZ,iBAAiB;IACjB,OAAO;IACP,OAAO;IACP,aAAa;IACb,eAAe;IACf,cAAc;CAC4B,CAAC;AAE7C,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqSzB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a workspace's shared graph lives, and the migration off the old
|
|
3
|
+
* one-graph-per-repository layout — Gap 1 of
|
|
4
|
+
* `documents/plans/site-claims-closure-production-plan.md`.
|
|
5
|
+
*
|
|
6
|
+
* Ported from `descry-desktop`'s `packages/session/src/indexing/graph-layout.ts`
|
|
7
|
+
* (H-2, "one graph per workspace, not per repository"), which solved the same
|
|
8
|
+
* problem for the desktop app's single, central engine root. The layout differs
|
|
9
|
+
* here because `descry-core` has no such root: every repository owns its own
|
|
10
|
+
* `.descry/` directory and its own `graph` path in `.descry/config.json`
|
|
11
|
+
* (`session.ts`'s `DescryConfig.graph`), so there is no `graph/${repositoryId}.db`
|
|
12
|
+
* convention to reuse. Two adaptations follow from that:
|
|
13
|
+
*
|
|
14
|
+
* 1. `workspaceGraphPath` keeps the desktop version's shape —
|
|
15
|
+
* `join(rootDir, "graph", "workspace-<id>.db")` — but `rootDir` here is a
|
|
16
|
+
* directory the caller chooses per invocation (the MCP `analyze_workspace`
|
|
17
|
+
* tool uses the initiating repository's `.descry/` directory), not a
|
|
18
|
+
* single app-wide engine root. That is a disclosed design choice, not an
|
|
19
|
+
* inferred one: the shared graph physically lives inside whichever
|
|
20
|
+
* repository's session first asked for it.
|
|
21
|
+
* 2. `migrateToWorkspaceGraph` takes each repository's *actual* existing graph
|
|
22
|
+
* path directly (`RepoGraphLocation.graphPath`, read from that repo's own
|
|
23
|
+
* config) rather than deriving one from a `repositoryId` the way the
|
|
24
|
+
* desktop version does — `descry-core` already knows the real path per
|
|
25
|
+
* repo without having to reconstruct it.
|
|
26
|
+
*
|
|
27
|
+
* The reasoning for *why* a migration is a move-and-reindex rather than a
|
|
28
|
+
* merge is unchanged from the desktop original: every node id in a
|
|
29
|
+
* pre-workspace graph was computed with `workspace` absent
|
|
30
|
+
* (`IdentityScope.workspace`, `packages/ir/src/identity.ts`), so
|
|
31
|
+
* workspace-scoped node types (`API_ENDPOINT`, `DATABASE_TABLE`,
|
|
32
|
+
* `DATABASE_COLUMN`, `INCIDENT`, `FIX_PATTERN`) in it carry repo-scoped ids —
|
|
33
|
+
* copying those rows into the shared store would populate it with ids nothing
|
|
34
|
+
* will ever produce again, which is worse than an empty graph because it looks
|
|
35
|
+
* full. So the honest handling is: move the stale database aside (never
|
|
36
|
+
* delete it), name the repository whose graph status must be treated as
|
|
37
|
+
* needing a fresh index, and let `indexWorkspace` (`../../ ../mcp/src
|
|
38
|
+
* /workspace-index.ts`) produce correct ids by reading the repository again.
|
|
39
|
+
*/
|
|
40
|
+
/** The one graph every repository in a workspace is indexed into. */
|
|
41
|
+
export declare function workspaceGraphPath(rootDir: string, workspaceId: string): string;
|
|
42
|
+
/** One repository's pre-workspace graph, as it actually sits on disk today. */
|
|
43
|
+
export interface RepoGraphLocation {
|
|
44
|
+
readonly repo: string;
|
|
45
|
+
/** Absolute path to that repository's own (pre-workspace) graph file. */
|
|
46
|
+
readonly graphPath: string;
|
|
47
|
+
}
|
|
48
|
+
export interface MovedGraphDatabase {
|
|
49
|
+
readonly repo: string;
|
|
50
|
+
readonly from: string;
|
|
51
|
+
readonly to: string;
|
|
52
|
+
}
|
|
53
|
+
export interface GraphLayoutMigration {
|
|
54
|
+
readonly migrated: boolean;
|
|
55
|
+
readonly movedDatabases: readonly MovedGraphDatabase[];
|
|
56
|
+
/**
|
|
57
|
+
* Repositories whose old graph must be treated as gone, so the caller
|
|
58
|
+
* re-indexes them into the workspace graph. Leaving one out would mean a
|
|
59
|
+
* repository's rows silently missing from the shared store — nothing errors,
|
|
60
|
+
* a query about it just finds nothing, indistinguishable from "there is
|
|
61
|
+
* nothing there".
|
|
62
|
+
*/
|
|
63
|
+
readonly reposToReindex: readonly string[];
|
|
64
|
+
/** Non-null whenever anything moved. Rule 7: a fallback is never silent. */
|
|
65
|
+
readonly disclosure: string | null;
|
|
66
|
+
}
|
|
67
|
+
export interface MigrateToWorkspaceGraphOptions {
|
|
68
|
+
/** Where the shared graph will live — `graph/` sits directly under it, same as `workspaceGraphPath`. */
|
|
69
|
+
readonly rootDir: string;
|
|
70
|
+
readonly workspaceId: string;
|
|
71
|
+
readonly repositories: readonly RepoGraphLocation[];
|
|
72
|
+
/** Injectable so the backup filename is not at the mercy of the wall clock. */
|
|
73
|
+
readonly now?: () => number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Move every pre-workspace graph aside and report what has to be re-indexed.
|
|
77
|
+
*
|
|
78
|
+
* Idempotent: a second call finds nothing at the recorded paths (because the
|
|
79
|
+
* first call already moved them) and reports `migrated: false`. A path that
|
|
80
|
+
* does not exist — the common case for a repository that has simply never
|
|
81
|
+
* been analysed yet — is skipped without comment; there is nothing stale to
|
|
82
|
+
* move.
|
|
83
|
+
*/
|
|
84
|
+
export declare function migrateToWorkspaceGraph(options: MigrateToWorkspaceGraphOptions): GraphLayoutMigration;
|
|
85
|
+
//# sourceMappingURL=graph-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-layout.d.ts","sourceRoot":"","sources":["../../src/workspace/graph-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAUH,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACvD;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,8BAA8B;IAC7C,wGAAwG;IACxG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACpD,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,GAAG,oBAAoB,CAkCrG"}
|