akm-cli 0.9.15 → 0.9.16-alpha.1
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 +144 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/cli/retired-commands.js +2 -0
- package/dist/cli/unknown-flags.js +36 -3
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +6 -4
- package/dist/commands/improve/improve-cli.js +1 -1
- package/dist/commands/proposal/repository.js +12 -3
- package/dist/commands/read/curate.js +34 -44
- package/dist/commands/read/search.js +50 -2
- package/dist/commands/sources/index-status.js +99 -0
- package/dist/commands/sources/info.js +8 -8
- package/dist/commands/sources/installed-stashes.js +33 -12
- package/dist/commands/sources/source-add.js +21 -6
- package/dist/commands/sources/stash-cli.js +119 -111
- package/dist/core/adapter/adapters/akm-adapter.js +35 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -1
- package/dist/core/asset/asset-placement.js +35 -0
- package/dist/core/config/schema/embedding.js +7 -30
- package/dist/core/config/schema/search.js +11 -9
- package/dist/core/errors.js +5 -2
- package/dist/core/hash.js +18 -0
- package/dist/core/maintenance-barrier.js +8 -6
- package/dist/core/paths.js +0 -11
- package/dist/core/run-lock.js +5 -2
- package/dist/core/state/migrations.js +26 -1
- package/dist/core/state-db.js +63 -27
- package/dist/indexer/drain.js +306 -0
- package/dist/indexer/embedding-identity.js +20 -0
- package/dist/indexer/enrich.js +260 -0
- package/dist/indexer/ensure-index.js +5 -0
- package/dist/indexer/index-written-assets.js +133 -171
- package/dist/indexer/indexer.js +458 -1621
- package/dist/indexer/lookup/adapter-concept-owner.js +19 -5
- package/dist/indexer/passes/metadata.js +18 -1
- package/dist/indexer/reconcile.js +890 -0
- package/dist/indexer/scan/drain-dir.js +27 -70
- package/dist/indexer/scan/parse-file.js +66 -0
- package/dist/indexer/search/db-search.js +373 -89
- package/dist/indexer/search/ranking-contributors.js +21 -16
- package/dist/indexer/search/ranking.js +135 -57
- package/dist/indexer/units/unit.js +159 -0
- package/dist/llm/client.js +10 -1
- package/dist/llm/embedder.js +10 -3
- package/dist/llm/embedders/provider-limits.js +288 -0
- package/dist/llm/embedders/remote.js +133 -104
- package/dist/llm/feature-gate.js +4 -2
- package/dist/llm/rerank-client.js +3 -3
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/output/text/command-format.js +19 -13
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/index.js +5 -2
- package/dist/scripts/akm-migrate-node.js +1141 -1237
- package/dist/scripts/akm-migrate.js +1141 -1237
- package/dist/setup/semantic-assets.js +2 -2
- package/dist/setup/steps/connection.js +3 -2
- package/dist/storage/repositories/files-repository.js +181 -0
- package/dist/storage/repositories/index-connection.js +1 -3
- package/dist/storage/repositories/index-entries-repository.js +77 -68
- package/dist/storage/repositories/index-entry-schema.js +16 -25
- package/dist/storage/repositories/index-fts-repository.js +29 -263
- package/dist/storage/repositories/index-meta-repository.js +0 -29
- package/dist/storage/repositories/index-schema.js +115 -122
- package/dist/storage/repositories/index-utility-repository.js +1 -1
- package/dist/storage/repositories/index-vec-repository.js +21 -334
- package/dist/storage/repositories/units-repository.js +510 -0
- package/docs/migration/release-notes/0.9.15.md +34 -36
- package/docs/migration/release-notes/0.9.16.md +110 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/reference/cli.md +93 -87
- package/docs/reference/configuration.md +128 -89
- package/docs/reference/data-and-telemetry.md +2 -1
- package/package.json +1 -1
- package/schemas/akm-config.json +2 -58
- package/dist/indexer/index-db-contention.js +0 -56
- package/dist/indexer/index-rebuild-lock.js +0 -73
- package/dist/indexer/materialize-embeddings.js +0 -771
- package/dist/indexer/passes/dir-staleness.js +0 -161
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
|
@@ -6,16 +6,18 @@
|
|
|
6
6
|
* storage layer. This isolates the one genuinely risky area (schema
|
|
7
7
|
* evolution) from the CRUD/FTS/vector queries.
|
|
8
8
|
*
|
|
9
|
-
* The meta accessors
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The meta accessors and vec-availability probe that `ensureSchema` leans on
|
|
10
|
+
* live in the sibling `index-meta-repository` / `index-vec-repository`
|
|
11
|
+
* modules.
|
|
12
12
|
*/
|
|
13
13
|
import { ConfigError } from "../../core/errors.js";
|
|
14
|
+
import { withImmediateTransaction } from "../../core/state-db.js";
|
|
14
15
|
import { warn } from "../../core/warn.js";
|
|
15
|
-
import {
|
|
16
|
+
import { ensureFileAndUnitTextTables } from "./files-repository.js";
|
|
16
17
|
import { CANONICAL_ENTRY_SCHEMA_SQL, CANONICAL_INDEX_DB_VERSION, classifyIndexGeneration, isCanonicalIndexGeneration, } from "./index-entry-schema.js";
|
|
17
18
|
import { getMeta, setMeta } from "./index-meta-repository.js";
|
|
18
|
-
import { isVecAvailable
|
|
19
|
+
import { isVecAvailable } from "./index-vec-repository.js";
|
|
20
|
+
import { ensureUnitTables } from "./units-repository.js";
|
|
19
21
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
20
22
|
// index.db is a regenerable cache. Incompatible entry-schema changes advance
|
|
21
23
|
// this generation and discard only derived index tables; durable state remains
|
|
@@ -167,8 +169,10 @@ function ensureGraphTables(db) {
|
|
|
167
169
|
/**
|
|
168
170
|
* Cross the incompatible entry-schema boundary by discarding the derived index
|
|
169
171
|
* generation. No row conversion or dual-schema compatibility is attempted:
|
|
170
|
-
* the next index run rebuilds entries, FTS,
|
|
171
|
-
*
|
|
172
|
+
* the next index run rebuilds entries, FTS, utility aggregates, graph
|
|
173
|
+
* extraction, and enrichment caches from current sources/state. Vectors are
|
|
174
|
+
* content-addressed (`units`/`units_vec`) and are never part of this
|
|
175
|
+
* discard — see `ensureUnitTables`.
|
|
172
176
|
*/
|
|
173
177
|
function rebuildIncompatibleIndexGeneration(db) {
|
|
174
178
|
const version = getMeta(db, "version");
|
|
@@ -184,26 +188,22 @@ function rebuildIncompatibleIndexGeneration(db) {
|
|
|
184
188
|
"to use this index.", "INDEX_SCHEMA_INCOMPATIBLE", "Upgrade akm to a version that understands this index generation.");
|
|
185
189
|
}
|
|
186
190
|
warn(`Index database generation ${classification.storedVersion ?? "unknown"} is older than this akm's generation ` +
|
|
187
|
-
`${CANONICAL_INDEX_DB_VERSION} — rebuilding the derived index (entries, FTS,
|
|
188
|
-
"
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// as the drop, so the copy and the discard commit or roll back together.
|
|
204
|
-
// The next embedding pass hands salvaged vectors back to unchanged
|
|
205
|
-
// content instead of re-embedding the whole corpus after this bump.
|
|
206
|
-
salvageEmbeddingsBeforeDiscard(db);
|
|
191
|
+
`${CANONICAL_INDEX_DB_VERSION} — rebuilding the derived index (entries, FTS, graph tables, utility scores, ` +
|
|
192
|
+
"and the LLM enrichment cache). This re-walks and re-indexes every source on the next run.");
|
|
193
|
+
// A pre-redesign (v23 or older) index can still carry the legacy
|
|
194
|
+
// `embeddings` table, declared `FOREIGN KEY (id) REFERENCES entries(id)`
|
|
195
|
+
// with no cascade. Under `foreign_keys=ON`, `DROP TABLE entries` below
|
|
196
|
+
// fails with "FOREIGN KEY constraint failed" unless `embeddings` (and its
|
|
197
|
+
// vec0 mirror `entries_vec`, dropped alongside it for symmetry) is gone
|
|
198
|
+
// first — so both must be dropped here, inside this transaction, BEFORE
|
|
199
|
+
// `entries`. `ensureSchema` also drops them unconditionally on every open
|
|
200
|
+
// (it is what cleans up a v24 index built before the tables were retired,
|
|
201
|
+
// and clears `entries_vec` when sqlite-vec was unavailable here), but it
|
|
202
|
+
// runs AFTER this rebuild, so it cannot be the only place the drop
|
|
203
|
+
// happens. Vectors themselves live only in the content-addressed
|
|
204
|
+
// `units`/`units_vec` store, which a generation rebuild never drops
|
|
205
|
+
// (ensureUnitTables' contract).
|
|
206
|
+
withImmediateTransaction(db, () => {
|
|
207
207
|
db.exec("DROP TABLE IF EXISTS graph_file_relations");
|
|
208
208
|
db.exec("DROP TABLE IF EXISTS graph_file_entities");
|
|
209
209
|
db.exec("DROP TABLE IF EXISTS graph_files");
|
|
@@ -214,32 +214,53 @@ function rebuildIncompatibleIndexGeneration(db) {
|
|
|
214
214
|
db.exec("DROP TABLE IF EXISTS entry_fragments");
|
|
215
215
|
db.exec("DROP TABLE IF EXISTS entries_fts");
|
|
216
216
|
db.exec("DROP TABLE IF EXISTS embeddings");
|
|
217
|
+
if (isVecAvailable(db))
|
|
218
|
+
db.exec("DROP TABLE IF EXISTS entries_vec");
|
|
217
219
|
db.exec("DROP TABLE IF EXISTS utility_scores_scoped");
|
|
218
220
|
db.exec("DROP TABLE IF EXISTS utility_scores");
|
|
219
221
|
db.exec("DROP TABLE IF EXISTS llm_enrichment_cache");
|
|
220
222
|
db.exec("DROP TABLE IF EXISTS index_dir_state");
|
|
221
223
|
db.exec("DROP TABLE IF EXISTS entries");
|
|
222
224
|
db.exec("DELETE FROM index_meta");
|
|
223
|
-
|
|
224
|
-
// it is the ONE piece of derived state a generation rebuild must not
|
|
225
|
-
// discard.
|
|
226
|
-
})();
|
|
227
|
-
if (vecResetPending)
|
|
228
|
-
setMeta(db, "vecResetPending", "1");
|
|
225
|
+
}, "index");
|
|
229
226
|
}
|
|
230
227
|
export function ensureSchema(db, embeddingDim) {
|
|
231
|
-
// Create meta table first
|
|
228
|
+
// Create meta table first (outside the transaction below): it must
|
|
229
|
+
// survive even a rolled-back schema attempt, since
|
|
230
|
+
// "does not stamp a generation until every required DDL surface succeeds"
|
|
231
|
+
// (canonical-entry-schema.test.ts) opens index_meta on a failed partial
|
|
232
|
+
// schema and expects the table to exist with no version row, not to be
|
|
233
|
+
// missing entirely.
|
|
232
234
|
db.exec(`
|
|
233
235
|
CREATE TABLE IF NOT EXISTS index_meta (
|
|
234
236
|
key TEXT PRIMARY KEY,
|
|
235
237
|
value TEXT NOT NULL
|
|
236
238
|
);
|
|
237
239
|
`);
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
240
|
+
// First open of a brand-new index.db used to be racy: a concurrent second
|
|
241
|
+
// `ensureSchema` could see `entries` created but `index_meta.version` not
|
|
242
|
+
// yet stamped, which `rebuildIncompatibleIndexGeneration` reads as an old
|
|
243
|
+
// generation and drops `entries` from under the first process mid-run
|
|
244
|
+
// (exit 70, "no such table: entries"). Only THAT case needs a lock — an
|
|
245
|
+
// already-canonical database (the common case) takes the plain path below,
|
|
246
|
+
// unchanged from before: wrapping every open unconditionally was tried
|
|
247
|
+
// first and reintroduced a lock on ordinary index runs, turning a single
|
|
248
|
+
// `busy_timeout` wait under a held writer into one per retry attempt
|
|
249
|
+
// (timed out `index-db-contention.test.ts`). This read can never race a
|
|
250
|
+
// genuine in-progress rebuild — WAL readers only see committed state, and a
|
|
251
|
+
// rebuild only runs when the last-committed state was itself non-canonical.
|
|
252
|
+
if (isCanonicalIndexGeneration(db)) {
|
|
253
|
+
applySchemaBody(db, embeddingDim);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Not (yet) canonical — a fresh database or a genuine stale generation.
|
|
257
|
+
// `withImmediateTransaction(db, ..., "index")` makes a concurrent opener
|
|
258
|
+
// block on `BEGIN IMMEDIATE` until this commits, so it only ever observes
|
|
259
|
+
// fully-fresh or fully-canonical. A real generation mismatch still
|
|
260
|
+
// rebuilds, via the nested call joining this same transaction.
|
|
261
|
+
withImmediateTransaction(db, () => applySchemaBody(db, embeddingDim), "index");
|
|
262
|
+
}
|
|
263
|
+
function applySchemaBody(db, embeddingDim) {
|
|
243
264
|
rebuildIncompatibleIndexGeneration(db);
|
|
244
265
|
db.exec(CANONICAL_ENTRY_SCHEMA_SQL);
|
|
245
266
|
// Workflow source is compiled directly into source IR at each command
|
|
@@ -247,14 +268,32 @@ export function ensureSchema(db, embeddingDim) {
|
|
|
247
268
|
// second persisted representation and was never used by current execution.
|
|
248
269
|
// index.db is derived state, so remove the obsolete table on every open.
|
|
249
270
|
db.exec("DROP TABLE IF EXISTS workflow_documents");
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
);
|
|
257
|
-
|
|
271
|
+
// #955's embedding-salvage cache (`embedding-salvage-repository.ts`) is
|
|
272
|
+
// retired (index redesign, B5) — units are content-addressed, so a
|
|
273
|
+
// generation bump keeps whatever vectors are still keyed by an
|
|
274
|
+
// unchanged unit hash instead of needing a copy-aside step. Drop the
|
|
275
|
+
// table on every open so an install upgrading past this release does not
|
|
276
|
+
// carry the now-unreferenced rows forever.
|
|
277
|
+
db.exec("DROP TABLE IF EXISTS embedding_salvage");
|
|
278
|
+
// The legacy per-entry vector tables (`embeddings`, a plain BLOB table, and
|
|
279
|
+
// `entries_vec`, its vec0 mirror) are retired (index redesign, B5h) —
|
|
280
|
+
// vectors live only in the content-addressed `units`/`units_vec` store
|
|
281
|
+
// now. Drop both unconditionally on every open, not gated on a generation
|
|
282
|
+
// bump: a v24 index built before this change may still carry them, and v24
|
|
283
|
+
// tolerates either shape. `embeddings` is a plain table, always safe to
|
|
284
|
+
// drop; `entries_vec` is a vec0 virtual table that can only be dropped
|
|
285
|
+
// while the extension is loaded, so a drop that cannot run yet is deferred
|
|
286
|
+
// the same way a generation rebuild used to defer it — a `vecResetPending`
|
|
287
|
+
// marker, finished the first later open where sqlite-vec is available.
|
|
288
|
+
db.exec("DROP TABLE IF EXISTS embeddings");
|
|
289
|
+
if (isVecAvailable(db)) {
|
|
290
|
+
db.exec("DROP TABLE IF EXISTS entries_vec");
|
|
291
|
+
if (getMeta(db, "vecResetPending") === "1")
|
|
292
|
+
setMeta(db, "vecResetPending", "0");
|
|
293
|
+
}
|
|
294
|
+
else if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'entries_vec'").get()) {
|
|
295
|
+
setMeta(db, "vecResetPending", "1");
|
|
296
|
+
}
|
|
258
297
|
// usage_events lives in state.db. utility_scores remains a regenerable
|
|
259
298
|
// index.db cache.
|
|
260
299
|
// Utility scores table (aggregated per-entry utility metrics)
|
|
@@ -284,17 +323,6 @@ export function ensureSchema(db, embeddingDim) {
|
|
|
284
323
|
CREATE INDEX IF NOT EXISTS idx_utility_scores_scoped_entry_id
|
|
285
324
|
ON utility_scores_scoped(entry_id);
|
|
286
325
|
`);
|
|
287
|
-
db.exec(`
|
|
288
|
-
CREATE TABLE IF NOT EXISTS index_dir_state (
|
|
289
|
-
dir_path TEXT PRIMARY KEY,
|
|
290
|
-
file_set_hash TEXT NOT NULL,
|
|
291
|
-
file_mtime_max_ms REAL NOT NULL,
|
|
292
|
-
reason TEXT NOT NULL,
|
|
293
|
-
updated_at TEXT NOT NULL,
|
|
294
|
-
row_count INTEGER
|
|
295
|
-
);
|
|
296
|
-
`);
|
|
297
|
-
ensureIndexDirStateRowCountColumn(db);
|
|
298
326
|
// LLM enrichment result cache. Stores a SHA-256 body hash and the JSON
|
|
299
327
|
// result for each asset so that subsequent `akm index --enrich` runs can
|
|
300
328
|
// skip the LLM call when the body hasn't changed. The cache is keyed by
|
|
@@ -329,68 +357,46 @@ export function ensureSchema(db, embeddingDim) {
|
|
|
329
357
|
// child rows are removed when a graph_files row is replaced.
|
|
330
358
|
//
|
|
331
359
|
ensureGraphTables(db);
|
|
332
|
-
//
|
|
333
|
-
// was unavailable, finish that reset as soon as vec0 can be loaded again.
|
|
334
|
-
if (isVecAvailable(db) && getMeta(db, "vecResetPending") === "1") {
|
|
335
|
-
db.exec("DROP TABLE IF EXISTS entries_vec");
|
|
336
|
-
setMeta(db, "vecResetPending", "0");
|
|
337
|
-
}
|
|
338
|
-
// sqlite-vec table
|
|
360
|
+
// Effective embedding vector width for `units_vec` (docs/plans/index-fragment-vectors.md).
|
|
339
361
|
//
|
|
340
362
|
// Dimension contract:
|
|
341
363
|
// - When `embeddingDim` is `undefined`, the caller did NOT request a
|
|
342
|
-
// specific dim. Do not touch `index_meta.embeddingDim`
|
|
343
|
-
// the
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
// improve/index value and oscillate the stored dim.
|
|
364
|
+
// specific dim. Do not touch `index_meta.embeddingDim` — fall back to
|
|
365
|
+
// the stored dim (or the static default). Without this guard,
|
|
366
|
+
// registry-side and other dim-unaware `openDatabase()` callers would
|
|
367
|
+
// silently overwrite the dim-aware improve/index value and oscillate
|
|
368
|
+
// the stored dim.
|
|
348
369
|
// - When `embeddingDim` is a number, the caller explicitly asked for
|
|
349
|
-
// that dim
|
|
370
|
+
// that dim; it is stamped into `index_meta.embeddingDim`.
|
|
371
|
+
//
|
|
372
|
+
// A genuine dimension change (a real model swap) is NOT handled here: it
|
|
373
|
+
// surfaces as a different observed embedding identity
|
|
374
|
+
// (`deriveObservedEmbeddingIdentity` folds the observed vector width into
|
|
375
|
+
// the identity string), and `dropOtherIdentities` — called from the
|
|
376
|
+
// embedding loop when the active identity changes — recreates `units_vec`
|
|
377
|
+
// at the new width then. `units_vec` itself is never dropped or purged
|
|
378
|
+
// here; ensureUnitTables only creates it if missing, at whatever width is
|
|
379
|
+
// effective the first time that happens.
|
|
350
380
|
const dimExplicit = embeddingDim !== undefined;
|
|
351
381
|
const requestedDim = embeddingDim ?? (Number(getMeta(db, "embeddingDim")) || EMBEDDING_DIM);
|
|
352
382
|
const effectiveDim = Number.isInteger(requestedDim) && requestedDim > 0 ? requestedDim : EMBEDDING_DIM;
|
|
353
383
|
if (effectiveDim !== requestedDim) {
|
|
354
384
|
warn(`Invalid embedding dimension ${requestedDim} — falling back to the default (${EMBEDDING_DIM}).`);
|
|
355
385
|
}
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
if (dimExplicit) {
|
|
359
|
-
const storedDim = getMeta(db, "embeddingDim");
|
|
360
|
-
if (storedDim && storedDim !== String(effectiveDim)) {
|
|
361
|
-
// Stored vectors are incompatible with the new dimension. Drop the vec
|
|
362
|
-
// table so the block below recreates it at the new width; the BLOB rows
|
|
363
|
-
// go too. Regenerable from markdown — re-embedded by the next index.
|
|
364
|
-
purgeEmbeddings(db, { dropVecTable: true });
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
const vecExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'").get();
|
|
368
|
-
if (!vecExists) {
|
|
369
|
-
db.exec(`
|
|
370
|
-
CREATE VIRTUAL TABLE entries_vec USING vec0(
|
|
371
|
-
id INTEGER PRIMARY KEY,
|
|
372
|
-
embedding FLOAT[${effectiveDim}]
|
|
373
|
-
);
|
|
374
|
-
`);
|
|
375
|
-
}
|
|
376
|
-
if (dimExplicit) {
|
|
377
|
-
setMeta(db, "embeddingDim", String(effectiveDim));
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
// Also purge BLOB embeddings on dimension change (JS fallback path).
|
|
382
|
-
// When sqlite-vec is unavailable, entries_vec doesn't exist but the BLOB
|
|
383
|
-
// embeddings table still stores vectors. If the configured dimension
|
|
384
|
-
// changes, those stored BLOBs become silently incompatible.
|
|
385
|
-
if (dimExplicit) {
|
|
386
|
-
const storedDim = getMeta(db, "embeddingDim");
|
|
387
|
-
if (storedDim && storedDim !== String(effectiveDim)) {
|
|
388
|
-
// JS-fallback path: no vec table, just clear the stale BLOB vectors.
|
|
389
|
-
purgeEmbeddings(db);
|
|
390
|
-
}
|
|
391
|
-
setMeta(db, "embeddingDim", String(effectiveDim));
|
|
392
|
-
}
|
|
386
|
+
if (dimExplicit) {
|
|
387
|
+
setMeta(db, "embeddingDim", String(effectiveDim));
|
|
393
388
|
}
|
|
389
|
+
// units / units_vec / entry_units (docs/plans/index-fragment-vectors.md):
|
|
390
|
+
// created if missing, and NEVER dropped by the generation rebuild above or
|
|
391
|
+
// by any purge — only dropOtherIdentities (called from the embedding loop
|
|
392
|
+
// on a real identity change) removes rows. ensureUnitTables is idempotent,
|
|
393
|
+
// so this runs on every ensureSchema call, not just the first.
|
|
394
|
+
ensureUnitTables(db, effectiveDim);
|
|
395
|
+
// files / unit_texts / units_fts (docs/plans/index-redesign-contract.md, B1):
|
|
396
|
+
// the reconcile engine's stat cache and content-addressed unit text store.
|
|
397
|
+
// Created if missing; reconcile.ts and pruneOrphanUnitTexts own all row-level
|
|
398
|
+
// writes and deletes, never this ensure path.
|
|
399
|
+
ensureFileAndUnitTextTables(db);
|
|
394
400
|
// Usage telemetry (usage_events) lives in state.db since Chunk-8 WI-8.3 —
|
|
395
401
|
// no longer created here.
|
|
396
402
|
// Registry index cache table — caches remote registry index documents so
|
|
@@ -408,16 +414,3 @@ function tableExists(db, name) {
|
|
|
408
414
|
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1").get(name);
|
|
409
415
|
return row !== undefined && row !== null;
|
|
410
416
|
}
|
|
411
|
-
/**
|
|
412
|
-
* #900: `row_count` was added after the table's first release, so a database
|
|
413
|
-
* created before it needs an `ALTER TABLE` (`CREATE TABLE IF NOT EXISTS` only
|
|
414
|
-
* shapes a fresh table). Idempotent. Pre-existing rows keep NULL until their
|
|
415
|
-
* directory is next drained; index.db is a regenerable cache, so nothing is
|
|
416
|
-
* backfilled.
|
|
417
|
-
*/
|
|
418
|
-
function ensureIndexDirStateRowCountColumn(db) {
|
|
419
|
-
const columns = db.prepare("PRAGMA table_info(index_dir_state)").all();
|
|
420
|
-
if (!columns.some((column) => column.name === "row_count")) {
|
|
421
|
-
db.exec("ALTER TABLE index_dir_state ADD COLUMN row_count INTEGER");
|
|
422
|
-
}
|
|
423
|
-
}
|
|
@@ -81,7 +81,7 @@ export function getUtilityScoresByIds(db, ids, scopeKey) {
|
|
|
81
81
|
* Insert or update a utility score for an entry.
|
|
82
82
|
*/
|
|
83
83
|
export function upsertUtilityScore(db, entryId, data) {
|
|
84
|
-
// Pre-flight FK guard
|
|
84
|
+
// Pre-flight FK guard: when an entry is
|
|
85
85
|
// deleted between when its id is aggregated from usage_events and when
|
|
86
86
|
// this INSERT runs, the FK constraint fails and rolls back the entire
|
|
87
87
|
// finalize transaction. A cheap SELECT here turns the race into a
|