akm-cli 0.9.17-alpha.8 → 0.9.17
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/CHANGELOG.md +277 -1694
- package/STABILITY.md +9 -8
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +17 -19
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +46 -1
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/html-report.js +1 -4
- package/dist/commands/health/improve-metrics.js +25 -37
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/report-view-model.js +4 -14
- package/dist/commands/health/windows.js +0 -1
- package/dist/commands/health.js +13 -0
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate.js +38 -63
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/improve-cli.js +1 -1
- package/dist/commands/improve/improve-strategies.js +23 -5
- package/dist/commands/improve/improve.js +19 -30
- package/dist/commands/improve/ledger.js +3 -2
- package/dist/commands/improve/loop-stages.js +5 -85
- package/dist/commands/improve/memory/memory-belief.js +3 -1
- package/dist/commands/improve/memory/memory-improve.js +262 -11
- package/dist/commands/improve/planner.js +0 -5
- package/dist/commands/improve/preparation.js +20 -135
- package/dist/commands/improve/retrieval-scope.js +19 -4
- package/dist/commands/improve/salience.js +1 -14
- package/dist/commands/improve/stage.js +0 -1
- package/dist/commands/lint/base-linter.js +19 -11
- package/dist/commands/proposal/drain.js +8 -1
- package/dist/commands/proposal/proposal-cli.js +16 -2
- package/dist/commands/proposal/proposal-types.js +7 -0
- package/dist/commands/proposal/proposal.js +37 -6
- package/dist/commands/proposal/repository.js +613 -4
- package/dist/commands/proposal/validators/proposals.js +9 -0
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/show.js +0 -14
- package/dist/commands/sources/info.js +122 -18
- package/dist/commands/sources/stash-cli.js +23 -3
- package/dist/core/bundle-rename.js +1 -7
- package/dist/core/config/config-schema.js +8 -1
- package/dist/core/config/config.js +23 -48
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/schema/improve-processes.js +17 -42
- package/dist/core/config/schema/index-config.js +5 -25
- package/dist/core/file-change.js +13 -5
- package/dist/core/improve-result.js +22 -6
- package/dist/core/improve-types.js +0 -1
- package/dist/core/loopback.js +7 -12
- package/dist/core/parse.js +13 -16
- package/dist/core/state/migrations.js +15 -0
- package/dist/core/time.js +0 -20
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +2 -2
- package/dist/indexer/index-written-assets.js +2 -3
- package/dist/indexer/indexer.js +18 -418
- package/dist/indexer/passes/metadata.js +0 -19
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/llm/client.js +8 -10
- package/dist/llm/embedders/remote.js +1 -2
- package/dist/llm/feature-gate.js +0 -5
- package/dist/output/shapes/helpers.js +20 -4
- package/dist/output/text/command-format.js +9 -8
- package/dist/output/text/proposal-format.js +47 -1
- package/dist/output/text/show-format.js +0 -20
- package/dist/scripts/akm-migrate-node.js +923 -950
- package/dist/scripts/akm-migrate.js +923 -950
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/sources/providers/git-stash.js +83 -4
- package/dist/storage/repositories/improve-ledger-repository.js +48 -7
- package/dist/storage/repositories/index-connection.js +5 -2
- package/dist/storage/repositories/index-entries-repository.js +4 -7
- package/dist/storage/repositories/index-entry-schema.js +4 -2
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -26
- package/dist/storage/repositories/index-schema.js +55 -104
- package/dist/storage/repositories/proposals-repository.js +61 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/docs/migration/README.md +1 -1
- package/docs/migration/release-notes/0.9.17.md +130 -41
- package/docs/migration/release-notes/README.md +7 -0
- package/docs/reference/cli.md +27 -21
- package/docs/reference/configuration.md +21 -12
- package/docs/reference/data-and-telemetry.md +0 -1
- package/package.json +1 -1
- package/schemas/akm-config.json +0 -342
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/indexer/db/graph-db.js +0 -399
- package/dist/indexer/graph/graph-extraction.js +0 -809
- package/dist/indexer/graph/graph-related.js +0 -131
- package/dist/indexer/graph/graph-types.js +0 -4
- package/dist/llm/graph-extract.js +0 -892
- package/dist/llm/metadata-enhance.js +0 -95
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import { rethrowIfDataDirUnreadable, rethrowIfTestIsolationError } from "../../core/errors.js";
|
|
5
|
-
import { isPathAbsent } from "../../core/path-access.js";
|
|
6
|
-
import { getDbPath } from "../../core/paths.js";
|
|
7
|
-
import { normalizeEntityKey } from "../../llm/graph-extract.js";
|
|
8
|
-
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
9
|
-
import { GRAPH_SCHEMA_VERSION } from "../../storage/repositories/index-schema.js";
|
|
10
|
-
function withReadableGraphDb(db, fn) {
|
|
11
|
-
if (db)
|
|
12
|
-
return fn(db);
|
|
13
|
-
const dbPath = getDbPath();
|
|
14
|
-
// `GRAPH_DB_MISSING` is the loaders' "nothing extracted yet" sentinel — every
|
|
15
|
-
// caller below turns it into `null`/`[]`. Reserve it for a genuinely ABSENT
|
|
16
|
-
// index: an index that exists and cannot be read must reach the caller as the
|
|
17
|
-
// ConfigError `openExistingDatabase` raises, not as "no graph data" (#791).
|
|
18
|
-
if (isPathAbsent(dbPath))
|
|
19
|
-
throw new Error("GRAPH_DB_MISSING");
|
|
20
|
-
const opened = openExistingDatabase(dbPath);
|
|
21
|
-
try {
|
|
22
|
-
return fn(opened);
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
closeDatabase(opened);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function uniqueSorted(values) {
|
|
29
|
-
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
30
|
-
}
|
|
31
|
-
/** Child rows joined to their file row, the same join the loaders read through. */
|
|
32
|
-
const STORED_ENTITIES = `graph_file_entities e
|
|
33
|
-
JOIN graph_files gf ON gf.stash_root = e.stash_root AND gf.file_path = e.file_path AND gf.body_hash = e.body_hash
|
|
34
|
-
WHERE gf.stash_root = ?`;
|
|
35
|
-
const STORED_RELATIONS = `graph_file_relations r
|
|
36
|
-
JOIN graph_files gf ON gf.stash_root = r.stash_root AND gf.file_path = r.file_path AND gf.body_hash = r.body_hash
|
|
37
|
-
WHERE gf.stash_root = ?`;
|
|
38
|
-
/** One comparable key for a file's extraction: its entities and relations, in order. */
|
|
39
|
-
function extractionKey(entities, relations) {
|
|
40
|
-
return JSON.stringify([entities, relations.map((r) => [r.from, r.to, r.type ?? null, r.confidence ?? null])]);
|
|
41
|
-
}
|
|
42
|
-
const EMPTY_EXTRACTION_KEY = extractionKey([], []);
|
|
43
|
-
/** The extraction key of every stored file under a root that has child rows. */
|
|
44
|
-
function readStoredExtractionKeys(db, stashRoot) {
|
|
45
|
-
const entityRows = db
|
|
46
|
-
.prepare(`SELECT e.file_path AS file_path, e.entity AS entity FROM ${STORED_ENTITIES} ORDER BY e.file_path, e.entity_order`)
|
|
47
|
-
.all(stashRoot);
|
|
48
|
-
const relationRows = db
|
|
49
|
-
.prepare(`SELECT r.file_path AS file_path, r.from_entity AS from_entity, r.to_entity AS to_entity,
|
|
50
|
-
r.relation_type AS relation_type, r.confidence AS confidence
|
|
51
|
-
FROM ${STORED_RELATIONS} ORDER BY r.file_path, r.relation_order`)
|
|
52
|
-
.all(stashRoot);
|
|
53
|
-
const byPath = new Map();
|
|
54
|
-
const bucket = (filePath) => {
|
|
55
|
-
let entry = byPath.get(filePath);
|
|
56
|
-
if (!entry) {
|
|
57
|
-
entry = { entities: [], relations: [] };
|
|
58
|
-
byPath.set(filePath, entry);
|
|
59
|
-
}
|
|
60
|
-
return entry;
|
|
61
|
-
};
|
|
62
|
-
for (const row of entityRows)
|
|
63
|
-
bucket(row.file_path).entities.push(row.entity);
|
|
64
|
-
for (const row of relationRows) {
|
|
65
|
-
bucket(row.file_path).relations.push({
|
|
66
|
-
from: row.from_entity,
|
|
67
|
-
to: row.to_entity,
|
|
68
|
-
...(row.relation_type !== null ? { type: row.relation_type } : {}),
|
|
69
|
-
...(row.confidence !== null ? { confidence: row.confidence } : {}),
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
return new Map([...byPath].map(([filePath, stored]) => [filePath, extractionKey(stored.entities, stored.relations)]));
|
|
73
|
-
}
|
|
74
|
-
function roundMetric(value) {
|
|
75
|
-
return Number(value.toFixed(4));
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* The graph_meta counts, derived from the stored rows of one root. Each field
|
|
79
|
-
* has one meaning (see {@link GraphQualityTelemetry}): files are graph_files
|
|
80
|
-
* rows, entities are distinct case-folded names, relations are distinct
|
|
81
|
-
* case-folded (from, to, type) triples.
|
|
82
|
-
*/
|
|
83
|
-
function readStoredGraphQuality(db, stashRoot) {
|
|
84
|
-
const count = (sql) => db.prepare(sql).get(stashRoot).n;
|
|
85
|
-
const storedFiles = count("SELECT COUNT(*) AS n FROM graph_files WHERE stash_root = ?");
|
|
86
|
-
const filesWithEntities = count(`SELECT COUNT(DISTINCT gf.file_path) AS n FROM ${STORED_ENTITIES}`);
|
|
87
|
-
const entityCount = count(`SELECT COUNT(DISTINCT e.entity_norm) AS n FROM ${STORED_ENTITIES}`);
|
|
88
|
-
const relationCount = count(`SELECT COUNT(*) AS n FROM (
|
|
89
|
-
SELECT DISTINCT r.from_entity_norm, r.to_entity_norm, lower(coalesce(r.relation_type, '')) FROM ${STORED_RELATIONS}
|
|
90
|
-
)`);
|
|
91
|
-
const maxEdges = entityCount > 1 ? (entityCount * (entityCount - 1)) / 2 : 0;
|
|
92
|
-
return {
|
|
93
|
-
consideredFiles: storedFiles,
|
|
94
|
-
extractedFiles: filesWithEntities,
|
|
95
|
-
entityCount,
|
|
96
|
-
relationCount,
|
|
97
|
-
extractionCoverage: storedFiles > 0 ? roundMetric(filesWithEntities / storedFiles) : 0,
|
|
98
|
-
density: maxEdges > 0 ? roundMetric(relationCount / maxEdges) : 0,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Persist (or update) a graph snapshot for a stash root.
|
|
103
|
-
*
|
|
104
|
-
* #624-P1: keyed on (stash_root, file_path, body_hash) — NOT entries.id. Graph
|
|
105
|
-
* rows are self-keyed by path, so they survive an entries delete + reinsert
|
|
106
|
-
* (a reindex) when body_hash is unchanged. A file whose body_hash is unchanged
|
|
107
|
-
* keeps its row; its entity and relation rows are rewritten only when they
|
|
108
|
-
* differ from the snapshot's (a re-extraction of the same body, e.g. after a
|
|
109
|
-
* model or prompt change or a failed first attempt, must land). Files whose
|
|
110
|
-
* body_hash changed have their old row + child rows deleted and the new content
|
|
111
|
-
* inserted; files in DB but absent from the new snapshot are deleted. There is
|
|
112
|
-
* no entry_id resolution and no orphan-skip — a graph file no longer needs a
|
|
113
|
-
* matching entries row.
|
|
114
|
-
*
|
|
115
|
-
* graph_meta records the snapshot's time and run telemetry; its counts are
|
|
116
|
-
* derived from the rows as stored after the write, never from the caller's
|
|
117
|
-
* in-memory graph.
|
|
118
|
-
*/
|
|
119
|
-
export function replaceStoredGraph(db, graph) {
|
|
120
|
-
const upsertMeta = db.prepare(`INSERT INTO graph_meta (
|
|
121
|
-
stash_root,
|
|
122
|
-
schema_version,
|
|
123
|
-
generated_at,
|
|
124
|
-
considered_files,
|
|
125
|
-
extracted_files,
|
|
126
|
-
entity_count,
|
|
127
|
-
relation_count,
|
|
128
|
-
extraction_coverage,
|
|
129
|
-
density,
|
|
130
|
-
extractor_id,
|
|
131
|
-
extraction_run_id,
|
|
132
|
-
model,
|
|
133
|
-
prompt_version,
|
|
134
|
-
batch_size,
|
|
135
|
-
cache_hits,
|
|
136
|
-
cache_misses,
|
|
137
|
-
truncation_count,
|
|
138
|
-
failure_count
|
|
139
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
140
|
-
ON CONFLICT(stash_root) DO UPDATE SET
|
|
141
|
-
schema_version = excluded.schema_version,
|
|
142
|
-
generated_at = excluded.generated_at,
|
|
143
|
-
considered_files = excluded.considered_files,
|
|
144
|
-
extracted_files = excluded.extracted_files,
|
|
145
|
-
entity_count = excluded.entity_count,
|
|
146
|
-
relation_count = excluded.relation_count,
|
|
147
|
-
extraction_coverage = excluded.extraction_coverage,
|
|
148
|
-
density = excluded.density,
|
|
149
|
-
extractor_id = excluded.extractor_id,
|
|
150
|
-
extraction_run_id = excluded.extraction_run_id,
|
|
151
|
-
model = excluded.model,
|
|
152
|
-
prompt_version = excluded.prompt_version,
|
|
153
|
-
batch_size = excluded.batch_size,
|
|
154
|
-
cache_hits = excluded.cache_hits,
|
|
155
|
-
cache_misses = excluded.cache_misses,
|
|
156
|
-
truncation_count = excluded.truncation_count,
|
|
157
|
-
failure_count = excluded.failure_count`);
|
|
158
|
-
const selectExisting = db.prepare("SELECT file_path, body_hash, file_order FROM graph_files WHERE stash_root = ?");
|
|
159
|
-
const deleteFile = db.prepare("DELETE FROM graph_files WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
160
|
-
const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
161
|
-
const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
162
|
-
const insertFile = db.prepare(`INSERT INTO graph_files (
|
|
163
|
-
stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
|
|
164
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
165
|
-
const updateFileMeta = db.prepare(`UPDATE graph_files
|
|
166
|
-
SET file_order = ?, file_type = ?, confidence = ?, status = ?, reason = ?, extraction_run_id = ?
|
|
167
|
-
WHERE stash_root = ? AND file_path = ? AND body_hash = ?`);
|
|
168
|
-
const insertEntity = db.prepare(`INSERT INTO graph_file_entities (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
|
|
169
|
-
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
170
|
-
const insertRelation = db.prepare(`INSERT INTO graph_file_relations (
|
|
171
|
-
stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
|
|
172
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
173
|
-
const telemetry = graph.telemetry;
|
|
174
|
-
db.transaction(() => {
|
|
175
|
-
// Build a snapshot of existing rows for incremental compare. The unique
|
|
176
|
-
// index idx_graph_files_path guarantees at most one row per file_path.
|
|
177
|
-
const existingRows = selectExisting.all(graph.stashRoot);
|
|
178
|
-
const existingByPath = new Map();
|
|
179
|
-
for (const row of existingRows)
|
|
180
|
-
existingByPath.set(row.file_path, row);
|
|
181
|
-
const storedKeys = readStoredExtractionKeys(db, graph.stashRoot);
|
|
182
|
-
const presentPaths = new Set();
|
|
183
|
-
for (const [fileOrder, node] of graph.files.entries()) {
|
|
184
|
-
// body_hash is part of the PK; default to a sentinel for inputs (test
|
|
185
|
-
// fixtures, legacy imports) that don't supply one. The sentinel never
|
|
186
|
-
// equals a real hash so subsequent staleness checks always re-extract —
|
|
187
|
-
// correct behaviour for "unknown" bodies. Distinct files in one stash
|
|
188
|
-
// are still keyed apart by file_path, so the empty sentinel is safe.
|
|
189
|
-
const bodyHash = node.bodyHash && node.bodyHash.length > 0 ? node.bodyHash : "";
|
|
190
|
-
const status = node.status ?? (node.entities.length > 0 ? "extracted" : "empty");
|
|
191
|
-
const reason = node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content");
|
|
192
|
-
const runId = node.extractionRunId ?? telemetry?.extractionRunId ?? null;
|
|
193
|
-
presentPaths.add(node.path);
|
|
194
|
-
const existing = existingByPath.get(node.path);
|
|
195
|
-
if (existing && existing.body_hash === bodyHash) {
|
|
196
|
-
// Body unchanged — refresh the file meta, and rewrite the child rows
|
|
197
|
-
// only when this snapshot's extraction differs from the stored one.
|
|
198
|
-
updateFileMeta.run(fileOrder, node.type, node.confidence ?? null, status, reason, runId, graph.stashRoot, node.path, bodyHash);
|
|
199
|
-
const storedKey = storedKeys.get(node.path) ?? EMPTY_EXTRACTION_KEY;
|
|
200
|
-
if (storedKey === extractionKey(node.entities, node.relations))
|
|
201
|
-
continue;
|
|
202
|
-
deleteEntities.run(graph.stashRoot, node.path, bodyHash);
|
|
203
|
-
deleteRelations.run(graph.stashRoot, node.path, bodyHash);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
if (existing) {
|
|
207
|
-
// Stale row (different body_hash for this path). Delete the old row by
|
|
208
|
-
// its OLD body_hash; child rows cascade, but explicit DELETE keeps the
|
|
209
|
-
// order deterministic and is safe regardless of the FK pragma.
|
|
210
|
-
deleteEntities.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
211
|
-
deleteRelations.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
212
|
-
deleteFile.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
213
|
-
}
|
|
214
|
-
insertFile.run(graph.stashRoot, node.path, fileOrder, node.type, bodyHash, node.confidence ?? null, status, reason, runId);
|
|
215
|
-
}
|
|
216
|
-
for (const [entityOrder, entity] of node.entities.entries()) {
|
|
217
|
-
insertEntity.run(graph.stashRoot, node.path, bodyHash, entityOrder, normalizeEntityKey(entity), entity);
|
|
218
|
-
}
|
|
219
|
-
for (const [relationOrder, relation] of node.relations.entries()) {
|
|
220
|
-
insertRelation.run(graph.stashRoot, node.path, bodyHash, relationOrder, normalizeEntityKey(relation.from), relation.from, normalizeEntityKey(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Delete files present in DB but absent from the new snapshot. Child
|
|
224
|
-
// tables CASCADE on the composite key; explicit DELETE keeps it determinstic.
|
|
225
|
-
for (const row of existingRows) {
|
|
226
|
-
if (!presentPaths.has(row.file_path)) {
|
|
227
|
-
deleteEntities.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
228
|
-
deleteRelations.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
229
|
-
deleteFile.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const quality = readStoredGraphQuality(db, graph.stashRoot);
|
|
233
|
-
upsertMeta.run(graph.stashRoot, GRAPH_SCHEMA_VERSION, graph.generatedAt, quality.consideredFiles, quality.extractedFiles, quality.entityCount, quality.relationCount, quality.extractionCoverage, quality.density, telemetry?.extractorId ?? null, telemetry?.extractionRunId ?? null, telemetry?.model ?? null, telemetry?.promptVersion ?? null, telemetry?.batchSize ?? null, telemetry?.cacheHits ?? 0, telemetry?.cacheMisses ?? 0, telemetry?.truncationCount ?? 0, telemetry?.failureCount ?? 0);
|
|
234
|
-
})();
|
|
235
|
-
}
|
|
236
|
-
export function deleteStoredGraph(db, stashPath) {
|
|
237
|
-
db.transaction(() => {
|
|
238
|
-
// Child rows cascade via the composite (stash_root, file_path, body_hash)
|
|
239
|
-
// FK; deleting graph_files clears them. This is the explicit full-clear
|
|
240
|
-
// path for a stash (entries-delete no longer wipes graph data — see #624-P1).
|
|
241
|
-
db.prepare("DELETE FROM graph_files WHERE stash_root = ?").run(stashPath);
|
|
242
|
-
db.prepare("DELETE FROM graph_meta WHERE stash_root = ?").run(stashPath);
|
|
243
|
-
})();
|
|
244
|
-
}
|
|
245
|
-
export function loadStoredGraphMeta(stashPath, db) {
|
|
246
|
-
try {
|
|
247
|
-
return withReadableGraphDb(db, (readDb) => {
|
|
248
|
-
const row = readDb
|
|
249
|
-
.prepare(`SELECT
|
|
250
|
-
stash_root,
|
|
251
|
-
generated_at,
|
|
252
|
-
considered_files,
|
|
253
|
-
extracted_files,
|
|
254
|
-
entity_count,
|
|
255
|
-
relation_count,
|
|
256
|
-
extraction_coverage,
|
|
257
|
-
density,
|
|
258
|
-
extractor_id,
|
|
259
|
-
extraction_run_id,
|
|
260
|
-
model,
|
|
261
|
-
prompt_version,
|
|
262
|
-
batch_size,
|
|
263
|
-
cache_hits,
|
|
264
|
-
cache_misses,
|
|
265
|
-
truncation_count,
|
|
266
|
-
failure_count
|
|
267
|
-
FROM graph_meta
|
|
268
|
-
WHERE stash_root = ?`)
|
|
269
|
-
.get(stashPath);
|
|
270
|
-
if (!row)
|
|
271
|
-
return null;
|
|
272
|
-
return {
|
|
273
|
-
stashPath: row.stash_root,
|
|
274
|
-
graphPath: getDbPath(),
|
|
275
|
-
generatedAt: row.generated_at,
|
|
276
|
-
quality: {
|
|
277
|
-
consideredFiles: row.considered_files,
|
|
278
|
-
extractedFiles: row.extracted_files,
|
|
279
|
-
entityCount: row.entity_count,
|
|
280
|
-
relationCount: row.relation_count,
|
|
281
|
-
extractionCoverage: row.extraction_coverage,
|
|
282
|
-
density: row.density,
|
|
283
|
-
},
|
|
284
|
-
telemetry: {
|
|
285
|
-
...(row.extractor_id ? { extractorId: row.extractor_id } : {}),
|
|
286
|
-
...(row.extraction_run_id ? { extractionRunId: row.extraction_run_id } : {}),
|
|
287
|
-
...(row.model ? { model: row.model } : {}),
|
|
288
|
-
...(row.prompt_version ? { promptVersion: row.prompt_version } : {}),
|
|
289
|
-
...(typeof row.batch_size === "number" ? { batchSize: row.batch_size } : {}),
|
|
290
|
-
cacheHits: row.cache_hits,
|
|
291
|
-
cacheMisses: row.cache_misses,
|
|
292
|
-
truncationCount: row.truncation_count,
|
|
293
|
-
failureCount: row.failure_count,
|
|
294
|
-
// `retry_attempts` is not persisted to the graph-meta table (it is
|
|
295
|
-
// surfaced from the run's emitted telemetry into `akm health`, not
|
|
296
|
-
// from the reuse cache). Default to 0 so the loaded shape satisfies
|
|
297
|
-
// GraphExtractionTelemetry.
|
|
298
|
-
retryAttempts: 0,
|
|
299
|
-
},
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
catch (err) {
|
|
304
|
-
// Never mask the bun-test isolation guard as "no stored graph meta",
|
|
305
|
-
// nor an unreadable index as one that simply has no graph (#791).
|
|
306
|
-
rethrowIfTestIsolationError(err);
|
|
307
|
-
rethrowIfDataDirUnreadable(err);
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
export function loadStoredGraphSnapshot(stashPath, db) {
|
|
312
|
-
try {
|
|
313
|
-
return withReadableGraphDb(db, (readDb) => {
|
|
314
|
-
const meta = loadStoredGraphMeta(stashPath, readDb);
|
|
315
|
-
if (!meta)
|
|
316
|
-
return null;
|
|
317
|
-
const fileRows = readDb
|
|
318
|
-
.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
|
|
319
|
-
FROM graph_files
|
|
320
|
-
WHERE stash_root = ?
|
|
321
|
-
ORDER BY file_order`)
|
|
322
|
-
.all(stashPath);
|
|
323
|
-
const entityRows = readDb
|
|
324
|
-
.prepare(`SELECT gf.file_path AS file_path, gfe.entity AS entity
|
|
325
|
-
FROM graph_file_entities gfe
|
|
326
|
-
JOIN graph_files gf
|
|
327
|
-
ON gf.stash_root = gfe.stash_root
|
|
328
|
-
AND gf.file_path = gfe.file_path
|
|
329
|
-
AND gf.body_hash = gfe.body_hash
|
|
330
|
-
WHERE gf.stash_root = ?
|
|
331
|
-
ORDER BY gf.file_order, gfe.entity_order`)
|
|
332
|
-
.all(stashPath);
|
|
333
|
-
const relationRows = readDb
|
|
334
|
-
.prepare(`SELECT gf.file_path AS file_path,
|
|
335
|
-
gfr.from_entity AS from_entity,
|
|
336
|
-
gfr.to_entity AS to_entity,
|
|
337
|
-
gfr.relation_type AS relation_type,
|
|
338
|
-
gfr.confidence AS confidence
|
|
339
|
-
FROM graph_file_relations gfr
|
|
340
|
-
JOIN graph_files gf
|
|
341
|
-
ON gf.stash_root = gfr.stash_root
|
|
342
|
-
AND gf.file_path = gfr.file_path
|
|
343
|
-
AND gf.body_hash = gfr.body_hash
|
|
344
|
-
WHERE gf.stash_root = ?
|
|
345
|
-
ORDER BY gf.file_order, gfr.relation_order`)
|
|
346
|
-
.all(stashPath);
|
|
347
|
-
const entitiesByPath = new Map();
|
|
348
|
-
for (const row of entityRows) {
|
|
349
|
-
const bucket = entitiesByPath.get(row.file_path);
|
|
350
|
-
if (bucket)
|
|
351
|
-
bucket.push(row.entity);
|
|
352
|
-
else
|
|
353
|
-
entitiesByPath.set(row.file_path, [row.entity]);
|
|
354
|
-
}
|
|
355
|
-
const relationsByPath = new Map();
|
|
356
|
-
for (const row of relationRows) {
|
|
357
|
-
const relation = {
|
|
358
|
-
from: row.from_entity,
|
|
359
|
-
to: row.to_entity,
|
|
360
|
-
...(row.relation_type ? { type: row.relation_type } : {}),
|
|
361
|
-
...(typeof row.confidence === "number" ? { confidence: row.confidence } : {}),
|
|
362
|
-
};
|
|
363
|
-
const bucket = relationsByPath.get(row.file_path);
|
|
364
|
-
if (bucket)
|
|
365
|
-
bucket.push(relation);
|
|
366
|
-
else
|
|
367
|
-
relationsByPath.set(row.file_path, [relation]);
|
|
368
|
-
}
|
|
369
|
-
const files = fileRows.map((row) => ({
|
|
370
|
-
path: row.file_path,
|
|
371
|
-
type: row.file_type,
|
|
372
|
-
...(row.body_hash ? { bodyHash: row.body_hash } : {}),
|
|
373
|
-
entities: entitiesByPath.get(row.file_path) ?? [],
|
|
374
|
-
relations: relationsByPath.get(row.file_path) ?? [],
|
|
375
|
-
...(typeof row.confidence === "number" ? { confidence: row.confidence } : {}),
|
|
376
|
-
...(row.status ? { status: row.status } : {}),
|
|
377
|
-
...(row.reason ? { reason: row.reason } : {}),
|
|
378
|
-
...(row.extraction_run_id ? { extractionRunId: row.extraction_run_id } : {}),
|
|
379
|
-
}));
|
|
380
|
-
return {
|
|
381
|
-
stashPath: meta.stashPath,
|
|
382
|
-
graphPath: meta.graphPath,
|
|
383
|
-
generatedAt: meta.generatedAt,
|
|
384
|
-
...(meta.quality ? { quality: meta.quality } : {}),
|
|
385
|
-
...(meta.telemetry ? { telemetry: meta.telemetry } : {}),
|
|
386
|
-
files,
|
|
387
|
-
entities: uniqueSorted(files.flatMap((file) => file.entities)),
|
|
388
|
-
relations: files.flatMap((file) => file.relations),
|
|
389
|
-
};
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
catch (err) {
|
|
393
|
-
// Never mask the bun-test isolation guard as "no stored graph snapshot",
|
|
394
|
-
// nor an unreadable index as one that simply has no graph (#791).
|
|
395
|
-
rethrowIfTestIsolationError(err);
|
|
396
|
-
rethrowIfDataDirUnreadable(err);
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
}
|