akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4
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 +756 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +2 -7
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +0 -17
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +363 -679
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +87 -406
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -21
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/config/retired-experimental-keys-shim.js +0 -62
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -78
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -4,82 +4,103 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* `index.db` FTS5 search + materialization repository.
|
|
6
6
|
*
|
|
7
|
-
* Owns the `entries_fts` full-text query path, per-entry projections
|
|
8
|
-
*
|
|
7
|
+
* Owns the `entries_fts` full-text query path, per-entry projections (the FTS
|
|
8
|
+
* row and the `entry_fragments` safe Markdown `akm show` resolves fragment
|
|
9
|
+
* selectors from), and the explicit full recovery rebuild.
|
|
10
|
+
*
|
|
11
|
+
* `entries_fts` is contentless (`content=''`, see index-entry-schema.ts): the
|
|
12
|
+
* text lives once in `entries`, and an FTS row is addressed only by its rowid
|
|
13
|
+
* (`entries.id`). The `COALESCE(f.entry_id, f.rowid)` join below also reads
|
|
14
|
+
* the content-bearing table older releases wrote (where the UNINDEXED column
|
|
15
|
+
* is populated), so a read-only open of a not-yet-migrated index still
|
|
16
|
+
* answers correctly.
|
|
9
17
|
*/
|
|
10
18
|
import { splitMarkdownFragments } from "../../core/asset/markdown-fragments.js";
|
|
11
|
-
import { stableFtsScore } from "../../core/lexical-score.js";
|
|
12
19
|
import { warn } from "../../core/warn.js";
|
|
13
|
-
import {
|
|
20
|
+
import { ftsOrMatch, ftsQueryTokens } from "../../indexer/search/fts-query.js";
|
|
14
21
|
import { buildSearchFields } from "../../indexer/search/search-fields.js";
|
|
15
22
|
import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
|
|
16
|
-
|
|
17
|
-
const
|
|
23
|
+
// `entries_fts.rowid = entries.id`, so a per-entry delete is a rowid lookup.
|
|
24
|
+
const INSERT_FTS_SQL = "INSERT INTO entries_fts (rowid, entry_id, name, description, tags, hints, content) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
|
18
25
|
const ftsMutationStatementsByDb = new WeakMap();
|
|
19
26
|
function getFtsMutationStatements(db) {
|
|
20
27
|
const existing = ftsMutationStatementsByDb.get(db);
|
|
21
28
|
if (existing)
|
|
22
29
|
return existing;
|
|
23
30
|
const statements = {
|
|
24
|
-
deleteOne: db.prepare("DELETE FROM entries_fts WHERE
|
|
31
|
+
deleteOne: db.prepare("DELETE FROM entries_fts WHERE rowid = ?"),
|
|
25
32
|
insert: db.prepare(INSERT_FTS_SQL),
|
|
26
|
-
deleteFragments: db.prepare("DELETE FROM entry_fragments_fts WHERE entry_id = ?"),
|
|
27
33
|
upsertFragmentSource: db.prepare("INSERT INTO entry_fragments (entry_id, safe_markdown) VALUES (?, ?) ON CONFLICT(entry_id) DO UPDATE SET safe_markdown = excluded.safe_markdown"),
|
|
28
34
|
deleteFragmentSource: db.prepare("DELETE FROM entry_fragments WHERE entry_id = ?"),
|
|
29
|
-
insertFragment: db.prepare(INSERT_FRAGMENT_SQL),
|
|
30
35
|
};
|
|
31
36
|
ftsMutationStatementsByDb.set(db, statements);
|
|
32
37
|
return statements;
|
|
33
38
|
}
|
|
34
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Replace one entry's derived FTS row, and its fragment source when the scan
|
|
41
|
+
* read Markdown, inside the caller's transaction.
|
|
42
|
+
*/
|
|
35
43
|
export function replaceFtsEntry(db, entryId, entry, fragmentContent) {
|
|
36
44
|
const fields = buildSearchFields(entry);
|
|
37
45
|
const statements = getFtsMutationStatements(db);
|
|
38
46
|
statements.deleteOne.run(entryId);
|
|
39
|
-
statements.insert.run(entryId, fields.name, fields.description, fields.tags, fields.hints, fields.content);
|
|
47
|
+
statements.insert.run(entryId, entryId, fields.name, fields.description, fields.tags, fields.hints, fields.content);
|
|
40
48
|
if (fragmentContent === undefined) {
|
|
41
49
|
// Metadata-only re-upserts and re-keys deserialize the public document
|
|
42
50
|
// without the internal substrate. Leave the persisted source untouched.
|
|
43
51
|
// A scan that did read Markdown always supplies a value below.
|
|
44
52
|
return;
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
statements.upsertFragmentSource.run(entryId, fragmentContent);
|
|
51
|
-
for (const fragment of splitMarkdownFragments(fragmentContent)) {
|
|
52
|
-
statements.insertFragment.run(entryId, fragment.fragmentId, fragment.ordinal, fragment.text.toLowerCase());
|
|
53
|
-
}
|
|
54
|
+
if (fragmentContent)
|
|
55
|
+
statements.upsertFragmentSource.run(entryId, fragmentContent);
|
|
56
|
+
else
|
|
57
|
+
statements.deleteFragmentSource.run(entryId);
|
|
54
58
|
}
|
|
55
|
-
/** Delete
|
|
59
|
+
/** Delete the FTS rows and fragment sources of canonical entries that are being removed. */
|
|
56
60
|
export function deleteFtsEntries(db, entryIds) {
|
|
57
61
|
for (let i = 0; i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
58
62
|
const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
59
63
|
const placeholders = chunk.map(() => "?").join(",");
|
|
60
|
-
db.prepare(`DELETE FROM entries_fts WHERE
|
|
61
|
-
db.prepare(`DELETE FROM entry_fragments_fts WHERE entry_id IN (${placeholders})`).run(...chunk);
|
|
64
|
+
db.prepare(`DELETE FROM entries_fts WHERE rowid IN (${placeholders})`).run(...chunk);
|
|
62
65
|
db.prepare(`DELETE FROM entry_fragments WHERE entry_id IN (${placeholders})`).run(...chunk);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
/**
|
|
69
|
+
* BM25 per-column weights for `entries_fts(entry_id, name, description, tags,
|
|
70
|
+
* hints, content)`. `entry_id` is UNINDEXED and carries no weight.
|
|
71
|
+
*/
|
|
72
|
+
const BM25_COLUMN_WEIGHTS = "0, 1.0, 1.0, 1.0, 1.0, 1.0";
|
|
73
|
+
/**
|
|
74
|
+
* The top `limit` entries matching ANY of the query's tokens
|
|
75
|
+
* (`ftsQueryTokens`), best whole-document BM25 first; equal scores are
|
|
76
|
+
* ordered by `item_ref` so the ranking is deterministic. Fragments play no
|
|
77
|
+
* part: whole-document BM25 beat the fragment population by 0.059 nDCG@10 on
|
|
78
|
+
* the retrieval suite.
|
|
79
|
+
*
|
|
80
|
+
* `entryType` narrows to one type; otherwise `excludeTypes` (#627, the
|
|
81
|
+
* default-hidden types such as `session`) are left out.
|
|
82
|
+
*/
|
|
83
|
+
export function searchFts(db, query, limit, entryType, excludeTypes = []) {
|
|
84
|
+
const tokens = ftsQueryTokens(query);
|
|
85
|
+
if (limit <= 0 || tokens.length === 0)
|
|
68
86
|
return [];
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
const typed = entryType !== undefined && entryType !== "any";
|
|
88
|
+
const filter = typed
|
|
89
|
+
? "AND e.type = ?"
|
|
90
|
+
: excludeTypes.length > 0
|
|
91
|
+
? `AND e.type NOT IN (${excludeTypes.map(() => "?").join(", ")})`
|
|
92
|
+
: "";
|
|
93
|
+
const params = [ftsOrMatch(tokens), ...(typed ? [entryType] : excludeTypes), limit];
|
|
94
|
+
// Contentless FTS rows address their entry by rowid; the COALESCE also reads
|
|
95
|
+
// the content-bearing layout older releases wrote.
|
|
96
|
+
return db
|
|
97
|
+
.prepare(`SELECT e.id AS id, e.item_ref AS itemRef
|
|
98
|
+
FROM entries_fts f
|
|
99
|
+
JOIN entries e ON e.id = COALESCE(f.entry_id, f.rowid)
|
|
100
|
+
WHERE entries_fts MATCH ? ${filter}
|
|
101
|
+
ORDER BY bm25(entries_fts, ${BM25_COLUMN_WEIGHTS}), e.item_ref
|
|
102
|
+
LIMIT ?`)
|
|
103
|
+
.all(...params);
|
|
83
104
|
}
|
|
84
105
|
/**
|
|
85
106
|
* Resolve an opaque fragment selector from the indexed safe projection, not
|
|
@@ -87,40 +108,14 @@ export function searchFts(db, query, limit, entryType, excludeTypes) {
|
|
|
87
108
|
* file edit; the next index refresh atomically publishes the new revision.
|
|
88
109
|
*/
|
|
89
110
|
export function getIndexedMarkdownFragment(db, itemRef, fragmentId) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
export function getIndexedMarkdownFragments(db, selections) {
|
|
99
|
-
if (selections.length === 0)
|
|
100
|
-
return [];
|
|
101
|
-
const itemRefs = [...new Set(selections.map((selection) => selection.itemRef))];
|
|
102
|
-
const sourceByRef = new Map();
|
|
103
|
-
for (let offset = 0; offset < itemRefs.length; offset += SQLITE_CHUNK_SIZE) {
|
|
104
|
-
const chunk = itemRefs.slice(offset, offset + SQLITE_CHUNK_SIZE);
|
|
105
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
106
|
-
const rows = db
|
|
107
|
-
.prepare(`SELECT e.item_ref, s.safe_markdown FROM entry_fragments s JOIN entries e ON e.id = s.entry_id WHERE e.item_ref IN (${placeholders})`)
|
|
108
|
-
.all(...chunk);
|
|
109
|
-
for (const row of rows)
|
|
110
|
-
sourceByRef.set(row.item_ref, row.safe_markdown);
|
|
111
|
-
}
|
|
112
|
-
const fragmentsByRef = new Map();
|
|
113
|
-
for (const [itemRef, safeMarkdown] of sourceByRef) {
|
|
114
|
-
fragmentsByRef.set(itemRef, splitMarkdownFragments(safeMarkdown));
|
|
115
|
-
}
|
|
116
|
-
return selections.map((selection) => {
|
|
117
|
-
const safeMarkdown = sourceByRef.get(selection.itemRef);
|
|
118
|
-
const fragments = fragmentsByRef.get(selection.itemRef);
|
|
119
|
-
if (safeMarkdown === undefined || !fragments)
|
|
120
|
-
return undefined;
|
|
121
|
-
const fragment = fragments.find((candidate) => candidate.fragmentId === selection.fragmentId || candidate.headingSlug === selection.fragmentId);
|
|
122
|
-
return fragment ? materializeIndexedMarkdownFragment(fragment, fragments, safeMarkdown.length) : undefined;
|
|
123
|
-
});
|
|
111
|
+
const row = db
|
|
112
|
+
.prepare("SELECT s.safe_markdown FROM entry_fragments s JOIN entries e ON e.id = s.entry_id WHERE e.item_ref = ?")
|
|
113
|
+
.get(itemRef);
|
|
114
|
+
if (row === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
const fragments = splitMarkdownFragments(row.safe_markdown);
|
|
117
|
+
const fragment = fragments.find((candidate) => candidate.fragmentId === fragmentId || candidate.headingSlug === fragmentId);
|
|
118
|
+
return fragment ? materializeIndexedMarkdownFragment(fragment, fragments, row.safe_markdown.length) : undefined;
|
|
124
119
|
}
|
|
125
120
|
function materializeIndexedMarkdownFragment(fragment, fragments, parentChars) {
|
|
126
121
|
return {
|
|
@@ -136,170 +131,8 @@ function materializeIndexedMarkdownFragment(fragment, fragments, parentChars) {
|
|
|
136
131
|
fragments,
|
|
137
132
|
};
|
|
138
133
|
}
|
|
139
|
-
function runFtsQuery(db, ftsQuery, lexicalMatch, limit, entryType, excludeTypes) {
|
|
140
|
-
// Preserve the repository's ordinary limit contract for direct callers.
|
|
141
|
-
// The boundary-expansion rule applies only to a positive candidate pool.
|
|
142
|
-
if (limit <= 0)
|
|
143
|
-
return [];
|
|
144
|
-
// #627 — exclude-type clause. Only applies on the untyped ('any') path; an
|
|
145
|
-
// explicit include filter (entryType) already narrows to a single type, so
|
|
146
|
-
// exclusion is redundant there. An empty list skips the clause entirely
|
|
147
|
-
// (never emit `NOT IN ()`, which is a SQL error / always-false).
|
|
148
|
-
const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
|
|
149
|
-
const candidateBoundaryOffset = Math.max(0, limit - 1);
|
|
150
|
-
// The typed and untyped paths differ only by one `type` WHERE clause
|
|
151
|
-
// equality vs. an optional NOT IN exclusion) and their parameter order.
|
|
152
|
-
// Join on integer entry_id directly (no CAST; we store integer). bm25()
|
|
153
|
-
// per-column weights:
|
|
154
|
-
// entry_id(0), name(10), description(5), tags(3), hints(2), content(1).
|
|
155
|
-
let filterClause;
|
|
156
|
-
let params;
|
|
157
|
-
if (entryType && entryType !== "any") {
|
|
158
|
-
filterClause = "AND e.type = ?";
|
|
159
|
-
params = [ftsQuery, entryType, candidateBoundaryOffset];
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
filterClause = excludes.length > 0 ? `AND e.type NOT IN (${excludes.map(() => "?").join(", ")})` : "";
|
|
163
|
-
// Param order: MATCH, then the NOT IN values, then the zero-based
|
|
164
|
-
// candidate-boundary offset.
|
|
165
|
-
params = [ftsQuery, ...excludes, candidateBoundaryOffset];
|
|
166
|
-
}
|
|
167
|
-
const sql = `
|
|
168
|
-
-- Do not make a SQL-only relevance decision inside a tied BM25 boundary:
|
|
169
|
-
-- the TypeScript ranker adds exact-name, type, and other contributors
|
|
170
|
-
-- afterwards. Materialize BM25 once, locate the Nth score, and admit
|
|
171
|
-
-- every row tied with it. This deliberately makes the result set
|
|
172
|
-
-- data-bound for a pathological all-tied query; that is the only way to
|
|
173
|
-
-- avoid silently dropping a legitimate later ranking winner.
|
|
174
|
-
WITH scored AS MATERIALIZED (
|
|
175
|
-
-- Keep this materialized set deliberately narrow. document_json can be
|
|
176
|
-
-- large, and only rows admitted through the BM25 boundary need it.
|
|
177
|
-
SELECT e.id, bm25(entries_fts, 0, 10.0, 5.0, 3.0, 2.0, 1.0) AS bm25Score
|
|
178
|
-
FROM entries_fts f
|
|
179
|
-
JOIN entries e ON e.id = f.entry_id
|
|
180
|
-
WHERE entries_fts MATCH ?
|
|
181
|
-
${filterClause}
|
|
182
|
-
), boundary AS (
|
|
183
|
-
SELECT bm25Score
|
|
184
|
-
FROM scored
|
|
185
|
-
ORDER BY bm25Score
|
|
186
|
-
LIMIT 1 OFFSET ?
|
|
187
|
-
)
|
|
188
|
-
SELECT e.id, e.file_path AS filePath, e.document_json AS documentJson, e.search_text AS searchText,
|
|
189
|
-
e.item_ref AS itemRef, e.bundle_id AS bundleId, e.concept_id AS conceptId, e.adapter_id AS adapterId,
|
|
190
|
-
scored.bm25Score
|
|
191
|
-
FROM scored
|
|
192
|
-
JOIN entries e ON e.id = scored.id
|
|
193
|
-
WHERE NOT EXISTS (SELECT 1 FROM boundary)
|
|
194
|
-
OR scored.bm25Score <= (SELECT bm25Score FROM boundary)
|
|
195
|
-
ORDER BY scored.bm25Score, e.id ASC
|
|
196
|
-
`;
|
|
197
|
-
const rows = db.prepare(sql).all(...params);
|
|
198
|
-
const results = materializeRows(rows, lexicalMatch);
|
|
199
|
-
// Fragments are a separate, intentionally calibrated evidence population:
|
|
200
|
-
// parent FTS remains the sole implementation of metadata/body conjunction.
|
|
201
|
-
// A selector is emitted only for one fragment that independently satisfies
|
|
202
|
-
// this query. Raw BM25 values are never claimed comparable across tables;
|
|
203
|
-
// each is passed through #933's stable mapping before merge.
|
|
204
|
-
const fragmentResults = hasFragmentFts(db)
|
|
205
|
-
? runFragmentQuery(db, ftsQuery, lexicalMatch, limit, entryType, excludes)
|
|
206
|
-
: [];
|
|
207
|
-
return mergeParentAndFragmentResults(results, fragmentResults);
|
|
208
|
-
}
|
|
209
|
-
function hasFragmentFts(db) {
|
|
210
|
-
return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'entry_fragments_fts'").get());
|
|
211
|
-
}
|
|
212
|
-
function materializeRows(rows, lexicalMatch) {
|
|
213
|
-
const results = [];
|
|
214
|
-
for (const row of rows) {
|
|
215
|
-
let entry;
|
|
216
|
-
try {
|
|
217
|
-
entry = JSON.parse(row.documentJson);
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
warn(`[db] searchFts: skipping entry id=${row.id} — corrupt document_json`);
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
results.push({
|
|
224
|
-
id: row.id,
|
|
225
|
-
filePath: row.filePath,
|
|
226
|
-
entry,
|
|
227
|
-
searchText: row.searchText,
|
|
228
|
-
bm25Score: row.bm25Score,
|
|
229
|
-
itemRef: row.itemRef,
|
|
230
|
-
bundleId: row.bundleId,
|
|
231
|
-
conceptId: row.conceptId,
|
|
232
|
-
adapterId: row.adapterId,
|
|
233
|
-
lexicalMatch,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
return results;
|
|
237
|
-
}
|
|
238
|
-
function runFragmentQuery(db, ftsQuery, lexicalMatch, limit, entryType, excludes) {
|
|
239
|
-
const filter = entryType && entryType !== "any"
|
|
240
|
-
? "AND e.type = ?"
|
|
241
|
-
: excludes.length
|
|
242
|
-
? `AND e.type NOT IN (${excludes.map(() => "?").join(",")})`
|
|
243
|
-
: "";
|
|
244
|
-
const filterParams = entryType && entryType !== "any" ? [entryType] : excludes;
|
|
245
|
-
const candidateBoundaryOffset = Math.max(0, limit - 1);
|
|
246
|
-
// Select the winning child per parent inside SQLite before finding the
|
|
247
|
-
// candidate boundary. A document with many matching fragments therefore
|
|
248
|
-
// occupies one parent slot, while a boundary tie retains every parent for
|
|
249
|
-
// the TypeScript ranker to decide with its non-BM25 contributors. This has
|
|
250
|
-
// one FTS query and no OFFSET walk; the returned boundary is intentionally
|
|
251
|
-
// data-bound for a pathological all-tied query, just like parent FTS.
|
|
252
|
-
const sql = `
|
|
253
|
-
WITH matches AS MATERIALIZED (
|
|
254
|
-
-- Keep repeated child rows as narrow as parent FTS's scored CTE. The
|
|
255
|
-
-- document projection can be large; hydrate it only after the one-child
|
|
256
|
-
-- per-parent collapse and BM25 boundary filtering below.
|
|
257
|
-
SELECT e.id, f.fragment_id AS fragmentId, f.fragment_ordinal AS fragmentOrdinal,
|
|
258
|
-
bm25(entry_fragments_fts) AS bm25Score
|
|
259
|
-
FROM entry_fragments_fts f JOIN entries e ON e.id = f.entry_id
|
|
260
|
-
WHERE entry_fragments_fts MATCH ? ${filter}
|
|
261
|
-
), ranked AS MATERIALIZED (
|
|
262
|
-
SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY bm25Score ASC, fragmentOrdinal ASC, fragmentId ASC) AS parentRank
|
|
263
|
-
FROM matches
|
|
264
|
-
), parents AS MATERIALIZED (
|
|
265
|
-
SELECT * FROM ranked WHERE parentRank = 1
|
|
266
|
-
), boundary AS (
|
|
267
|
-
SELECT bm25Score FROM parents ORDER BY bm25Score ASC LIMIT 1 OFFSET ?
|
|
268
|
-
)
|
|
269
|
-
SELECT e.id, e.file_path AS filePath, e.document_json AS documentJson, e.search_text AS searchText,
|
|
270
|
-
e.item_ref AS itemRef, e.bundle_id AS bundleId, e.concept_id AS conceptId, e.adapter_id AS adapterId,
|
|
271
|
-
parents.fragmentId, parents.bm25Score
|
|
272
|
-
FROM parents JOIN entries e ON e.id = parents.id
|
|
273
|
-
WHERE NOT EXISTS (SELECT 1 FROM boundary)
|
|
274
|
-
OR parents.bm25Score <= (SELECT bm25Score FROM boundary)
|
|
275
|
-
ORDER BY parents.bm25Score ASC, parents.id ASC`;
|
|
276
|
-
const rows = db.prepare(sql).all(ftsQuery, ...filterParams, candidateBoundaryOffset);
|
|
277
|
-
const results = [];
|
|
278
|
-
for (const row of rows) {
|
|
279
|
-
const [result] = materializeRows([row], lexicalMatch);
|
|
280
|
-
if (result) {
|
|
281
|
-
results.push({
|
|
282
|
-
...result,
|
|
283
|
-
fragmentId: row.fragmentId,
|
|
284
|
-
lexicalScore: stableFtsScore(result.bm25Score, "fragment"),
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return results;
|
|
289
|
-
}
|
|
290
|
-
function mergeParentAndFragmentResults(parents, fragments) {
|
|
291
|
-
const winners = new Map();
|
|
292
|
-
for (const parent of parents)
|
|
293
|
-
winners.set(parent.id, { ...parent, lexicalScore: stableFtsScore(parent.bm25Score) });
|
|
294
|
-
for (const fragment of fragments) {
|
|
295
|
-
const existing = winners.get(fragment.id);
|
|
296
|
-
if (!existing || (fragment.lexicalScore ?? 0) > (existing.lexicalScore ?? 0))
|
|
297
|
-
winners.set(fragment.id, fragment);
|
|
298
|
-
}
|
|
299
|
-
return [...winners.values()].sort((left, right) => (right.lexicalScore ?? 0) - (left.lexicalScore ?? 0) || left.id - right.id);
|
|
300
|
-
}
|
|
301
134
|
/**
|
|
302
|
-
* Explicitly rebuild
|
|
135
|
+
* Explicitly rebuild `entries_fts` from canonical entries.
|
|
303
136
|
* Ordinary entry mutations do not call this: `upsertEntry` and the delete
|
|
304
137
|
* operations publish their FTS state in the same transaction as `entries`.
|
|
305
138
|
* This remains a recovery/schema-verification primitive for regenerable
|
|
@@ -311,29 +144,26 @@ function mergeParentAndFragmentResults(parents, fragments) {
|
|
|
311
144
|
export function rebuildFts(db) {
|
|
312
145
|
db.transaction(() => {
|
|
313
146
|
db.exec("DELETE FROM entries_fts");
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
.prepare("SELECT e.id, e.document_json, f.safe_markdown FROM entries e LEFT JOIN entry_fragments f ON f.entry_id = e.id")
|
|
317
|
-
.all();
|
|
147
|
+
// Keyset pages, so a large index is never held in memory at once.
|
|
148
|
+
const page = db.prepare("SELECT id, document_json FROM entries WHERE id > ? ORDER BY id LIMIT 500");
|
|
318
149
|
const insertStmt = db.prepare(INSERT_FTS_SQL);
|
|
319
|
-
const fragmentStmt = db.prepare(INSERT_FRAGMENT_SQL);
|
|
320
150
|
let skipped = 0;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
fragmentStmt.run(row.id, fragment.fragmentId, fragment.ordinal, fragment.text.toLowerCase());
|
|
151
|
+
let afterId = -1;
|
|
152
|
+
for (;;) {
|
|
153
|
+
const rows = page.all(afterId);
|
|
154
|
+
if (rows.length === 0)
|
|
155
|
+
break;
|
|
156
|
+
afterId = rows[rows.length - 1].id;
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
let fields;
|
|
159
|
+
try {
|
|
160
|
+
fields = buildSearchFields(JSON.parse(row.document_json));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
skipped++;
|
|
164
|
+
continue;
|
|
336
165
|
}
|
|
166
|
+
insertStmt.run(row.id, row.id, fields.name, fields.description, fields.tags, fields.hints, fields.content);
|
|
337
167
|
}
|
|
338
168
|
}
|
|
339
169
|
if (skipped > 0) {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* lets `akm index --enrich` skip the LLM call when a file's body is unchanged.
|
|
9
9
|
*/
|
|
10
10
|
import { sha256Hex } from "../../runtime.js";
|
|
11
|
+
import { escapeLikePattern } from "../like-pattern.js";
|
|
11
12
|
import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
|
|
12
13
|
/**
|
|
13
14
|
* Look up a cached LLM result for the given asset_ref.
|
|
@@ -93,6 +94,22 @@ export function clearStaleCacheEntries(db) {
|
|
|
93
94
|
AND asset_ref NOT IN (SELECT item_ref FROM entries)
|
|
94
95
|
`);
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Rewrite every `llm_enrichment_cache.asset_ref` naming `oldBundleId` (a
|
|
99
|
+
* metadata-enrichment cache key, the canonical `<bundle>//conceptId` form of
|
|
100
|
+
* `entries.item_ref`) to `newBundleId` (`akm bundle rename`, D6). Must run in
|
|
101
|
+
* the same `index.db` write as `renameEntriesBundleId` — otherwise the next
|
|
102
|
+
* `akm index`'s {@link clearStaleCacheEntries} deletes every row still keyed
|
|
103
|
+
* to the old bundle, forcing a full LLM re-enrichment. A graph/memory cache
|
|
104
|
+
* row (keyed by absolute file path, not `item_ref`) never matches the `//`
|
|
105
|
+
* prefix and is left alone. Returns the number of rows rewritten.
|
|
106
|
+
*/
|
|
107
|
+
export function renameLlmCacheAssetRefs(db, oldBundleId, newBundleId) {
|
|
108
|
+
const prefix = `${escapeLikePattern(oldBundleId)}//`;
|
|
109
|
+
return Number(db
|
|
110
|
+
.prepare(`UPDATE llm_enrichment_cache SET asset_ref = ? || substr(asset_ref, ?) WHERE asset_ref LIKE ? ESCAPE '\\'`)
|
|
111
|
+
.run(newBundleId, oldBundleId.length + 1, `${prefix}%`).changes);
|
|
112
|
+
}
|
|
96
113
|
/**
|
|
97
114
|
* Compute a stable SHA-256 hex digest of a UTF-8 string. Used as the body_hash
|
|
98
115
|
* key in `llm_enrichment_cache`. Routed through the runtime boundary so the
|
|
@@ -23,7 +23,7 @@ export function deleteMeta(db, key) {
|
|
|
23
23
|
// ── Per-directory index state ───────────────────────────────────────────────
|
|
24
24
|
export function getIndexDirState(db, dirPath) {
|
|
25
25
|
const row = db
|
|
26
|
-
.prepare("SELECT dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count FROM index_dir_state WHERE dir_path = ?")
|
|
26
|
+
.prepare("SELECT dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count, index_variant FROM index_dir_state WHERE dir_path = ?")
|
|
27
27
|
.get(dirPath);
|
|
28
28
|
if (!row)
|
|
29
29
|
return undefined;
|
|
@@ -34,17 +34,19 @@ export function getIndexDirState(db, dirPath) {
|
|
|
34
34
|
reason: row.reason,
|
|
35
35
|
updatedAt: row.updated_at,
|
|
36
36
|
rowCount: row.row_count ?? undefined,
|
|
37
|
+
indexVariant: row.index_variant ?? undefined,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
export function upsertIndexDirState(db, state) {
|
|
40
|
-
db.prepare(`INSERT INTO index_dir_state (dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count)
|
|
41
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
41
|
+
db.prepare(`INSERT INTO index_dir_state (dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count, index_variant)
|
|
42
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
42
43
|
ON CONFLICT(dir_path) DO UPDATE SET
|
|
43
44
|
file_set_hash = excluded.file_set_hash,
|
|
44
45
|
file_mtime_max_ms = excluded.file_mtime_max_ms,
|
|
45
46
|
reason = excluded.reason,
|
|
46
47
|
updated_at = excluded.updated_at,
|
|
47
|
-
row_count = excluded.row_count
|
|
48
|
+
row_count = excluded.row_count,
|
|
49
|
+
index_variant = excluded.index_variant`).run(state.dirPath, state.fileSetHash, state.fileMtimeMaxMs, state.reason, new Date().toISOString(), state.rowCount ?? null, state.indexVariant ?? null);
|
|
48
50
|
}
|
|
49
51
|
export function deleteIndexDirState(db, dirPath) {
|
|
50
52
|
db.prepare("DELETE FROM index_dir_state WHERE dir_path = ?").run(dirPath);
|