akm-cli 0.9.1 → 0.9.2-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 +88 -0
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract.js +521 -342
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +10 -0
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +835 -152
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +83 -34
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +21 -20
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +65 -34
- package/dist/indexer/indexer.js +443 -222
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/scan/doc-to-entry.js +7 -9
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +98 -22
- package/dist/indexer/search/search-fields.js +1 -1
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +28 -32
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19122 -52218
- package/dist/scripts/akm-migrate.js +19064 -51567
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +167 -207
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +10 -10
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +71 -342
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +1 -1
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +153 -27
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -216
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -28,28 +28,21 @@ import { deleteEntryVectors, isVecAvailable } from "./index-vec-repository.js";
|
|
|
28
28
|
* `rebuildFts()` after all upserts are complete for full-text search to
|
|
29
29
|
* reflect the changes.
|
|
30
30
|
*/
|
|
31
|
-
export function upsertEntry(db,
|
|
31
|
+
export function upsertEntry(db, filePath, entry, searchText, provenance, contentHash) {
|
|
32
32
|
// Hot path during indexing — cache prepared statements per database
|
|
33
33
|
// connection so we don't pay the SQL parse/compile cost on every call.
|
|
34
34
|
const stmts = getUpsertStmts(db);
|
|
35
|
-
const previous = (provenance
|
|
36
|
-
? stmts.findByItemRef.get(provenance.itemRef)
|
|
37
|
-
: undefined) ?? stmts.findByEntryKey.get(entryKey);
|
|
35
|
+
const previous = stmts.findByItemRef.get(provenance.itemRef);
|
|
38
36
|
// Phase 5A / Advantage D5: surface derived memory parent ref into the
|
|
39
37
|
// dedicated `derived_from` column so retrieval-time lookup (parent→child)
|
|
40
38
|
// does not have to scan + JSON-decode every memory row.
|
|
41
39
|
const derivedFrom = typeof entry.derivedFrom === "string" && entry.derivedFrom.trim() ? entry.derivedFrom.trim() : null;
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// token) unconditionally; `item_ref`/bundle/component/concept/adapter come
|
|
45
|
-
// from the write-boundary derivation when available (NULL otherwise, healed
|
|
46
|
-
// by the next full index). `content_hash` (F4a M-core-2) is `doc.hash` from
|
|
47
|
-
// the diff-persist writer; a NULL passed here PRESERVES any existing hash (the
|
|
48
|
-
// ON CONFLICT COALESCE below) so the LLM-enrichment re-upsert cannot wipe it.
|
|
40
|
+
// `content_hash` is optional on the LLM-enrichment re-upsert; a missing hash
|
|
41
|
+
// preserves the scan writer's current value.
|
|
49
42
|
const apply = () => {
|
|
50
|
-
const result = stmts.upsert.get(
|
|
43
|
+
const result = stmts.upsert.get(provenance.itemRef, provenance.bundleId, provenance.componentId, provenance.conceptId, provenance.adapterId, entry.type, filePath, contentHash ?? null, JSON.stringify(entry), searchText, derivedFrom);
|
|
51
44
|
if (!result)
|
|
52
|
-
throw new Error("upsertEntry:
|
|
45
|
+
throw new Error("upsertEntry: item_ref not found after upsert");
|
|
53
46
|
if (previous?.id === result.id && previous.search_text !== searchText)
|
|
54
47
|
deleteEntryVectors(db, result.id);
|
|
55
48
|
// Mark this entry as FTS-dirty so `rebuildFts({ incremental: true })`
|
|
@@ -61,52 +54,38 @@ export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, se
|
|
|
61
54
|
return previous && previous.search_text !== searchText ? db.transaction(apply)() : apply();
|
|
62
55
|
}
|
|
63
56
|
const upsertStmtsByDb = new WeakMap();
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// update `entry_key` when adapter ownership changes its
|
|
67
|
-
// spelling so directory pruning retains the upserted row.
|
|
68
|
-
// `content_hash` COALESCEs so a NULL passed by the LLM-enhance re-upsert cannot
|
|
69
|
-
// wipe a previously-persisted hash.
|
|
57
|
+
// item_ref is the sole durable conflict target. `content_hash` COALESCEs so a
|
|
58
|
+
// metadata-only enrichment pass cannot wipe a scan hash.
|
|
70
59
|
const UPSERT_SET_CLAUSE = `SET
|
|
71
|
-
entry_key = excluded.entry_key,
|
|
72
|
-
dir_path = excluded.dir_path,
|
|
73
|
-
file_path = excluded.file_path,
|
|
74
|
-
stash_dir = excluded.stash_dir,
|
|
75
|
-
entry_json = excluded.entry_json,
|
|
76
|
-
search_text = excluded.search_text,
|
|
77
|
-
entry_type = excluded.entry_type,
|
|
78
|
-
derived_from = excluded.derived_from,
|
|
79
|
-
item_ref = excluded.item_ref,
|
|
80
60
|
bundle_id = excluded.bundle_id,
|
|
81
61
|
component_id = excluded.component_id,
|
|
82
62
|
concept_id = excluded.concept_id,
|
|
83
63
|
adapter_id = excluded.adapter_id,
|
|
84
64
|
type = excluded.type,
|
|
65
|
+
file_path = excluded.file_path,
|
|
66
|
+
document_json = excluded.document_json,
|
|
67
|
+
search_text = excluded.search_text,
|
|
68
|
+
derived_from = excluded.derived_from,
|
|
85
69
|
content_hash = COALESCE(excluded.content_hash, content_hash)`;
|
|
86
70
|
function getUpsertStmts(db) {
|
|
87
71
|
const existing = upsertStmtsByDb.get(db);
|
|
88
72
|
if (existing)
|
|
89
73
|
return existing;
|
|
90
|
-
// Durable identity is the primary conflict target. `entry_key` remains the
|
|
91
|
-
// internal conflict key for low-level entries without provenance.
|
|
92
|
-
const conflictClause = `ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
|
|
93
|
-
ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`;
|
|
94
74
|
const stmts = {
|
|
95
75
|
// RETURNING id handles ON CONFLICT DO UPDATE correctly — no second
|
|
96
76
|
// SELECT round-trip needed (last_insert_rowid() is unreliable for
|
|
97
77
|
// ON CONFLICT). Use `.get()` so a single row comes back.
|
|
98
78
|
upsert: db.prepare(`
|
|
99
79
|
INSERT INTO entries (
|
|
100
|
-
|
|
101
|
-
|
|
80
|
+
item_ref, bundle_id, component_id, concept_id, adapter_id, type,
|
|
81
|
+
file_path, content_hash, document_json, search_text, derived_from
|
|
102
82
|
)
|
|
103
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
104
|
-
${
|
|
83
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
84
|
+
ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
|
|
105
85
|
RETURNING id
|
|
106
86
|
`),
|
|
107
87
|
markDirty: db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)"),
|
|
108
|
-
findByItemRef: db.prepare("SELECT id, search_text FROM entries WHERE item_ref = ?
|
|
109
|
-
findByEntryKey: db.prepare("SELECT id, search_text FROM entries WHERE entry_key = ? LIMIT 1"),
|
|
88
|
+
findByItemRef: db.prepare("SELECT id, search_text FROM entries WHERE item_ref = ?"),
|
|
110
89
|
};
|
|
111
90
|
upsertStmtsByDb.set(db, stmts);
|
|
112
91
|
return stmts;
|
|
@@ -121,10 +100,10 @@ function getUpsertStmts(db) {
|
|
|
121
100
|
* ordering keeps results deterministic). Returns `null` when no derived
|
|
122
101
|
* child has been indexed for this parent.
|
|
123
102
|
*/
|
|
124
|
-
export function getDerivedForParent(db, parentRef,
|
|
103
|
+
export function getDerivedForParent(db, parentRef, bundleId) {
|
|
125
104
|
if (!parentRef)
|
|
126
105
|
return null;
|
|
127
|
-
const sourceScope =
|
|
106
|
+
const sourceScope = bundleId ? "AND bundle_id = ?" : "";
|
|
128
107
|
const row = db
|
|
129
108
|
.prepare(`SELECT ${ENTRY_COLUMNS}
|
|
130
109
|
FROM entries
|
|
@@ -132,7 +111,7 @@ export function getDerivedForParent(db, parentRef, stashDir) {
|
|
|
132
111
|
${sourceScope}
|
|
133
112
|
ORDER BY id DESC
|
|
134
113
|
LIMIT 1`)
|
|
135
|
-
.get(parentRef, ...(
|
|
114
|
+
.get(parentRef, ...(bundleId ? [bundleId] : []));
|
|
136
115
|
if (!row)
|
|
137
116
|
return null;
|
|
138
117
|
return rowToIndexedEntry(row, "getDerivedForParent");
|
|
@@ -143,10 +122,9 @@ export function getDerivedForParent(db, parentRef, stashDir) {
|
|
|
143
122
|
*
|
|
144
123
|
* Used by the derived-twin belief inheritance in search ranking: a `.derived`
|
|
145
124
|
* twin has no belief state of its own, so it inherits its base memory's
|
|
146
|
-
* demoting state (contradicted/superseded/…) at search time. A twin's
|
|
147
|
-
* `
|
|
148
|
-
*
|
|
149
|
-
* found by stripping that suffix — no ref/prefix reconstruction. Returns a map
|
|
125
|
+
* demoting state (contradicted/superseded/…) at search time. A twin's durable
|
|
126
|
+
* `item_ref` is exactly its base ref plus the `.derived` suffix, so the base is
|
|
127
|
+
* found by stripping that suffix. Returns a map
|
|
150
128
|
* of twin id → base beliefState for bases that carry a non-empty state.
|
|
151
129
|
* Best-effort: any query error (e.g. legacy DB) yields no inheritance rather
|
|
152
130
|
* than failing the search.
|
|
@@ -163,14 +141,14 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
163
141
|
const placeholders = chunk.map(() => "?").join(",");
|
|
164
142
|
bestEffort(() => {
|
|
165
143
|
const rows = db
|
|
166
|
-
.prepare(`SELECT twin.id AS twin_id, json_extract(base.
|
|
144
|
+
.prepare(`SELECT twin.id AS twin_id, json_extract(base.document_json, '$.beliefState') AS belief
|
|
167
145
|
FROM entries twin
|
|
168
146
|
JOIN entries base
|
|
169
|
-
ON base.
|
|
147
|
+
ON base.type = 'memory'
|
|
170
148
|
AND base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
|
|
171
149
|
WHERE twin.id IN (${placeholders})
|
|
172
150
|
AND twin.item_ref LIKE '%.derived'
|
|
173
|
-
AND json_extract(base.
|
|
151
|
+
AND json_extract(base.document_json, '$.beliefState') IS NOT NULL`)
|
|
174
152
|
.all(...chunk);
|
|
175
153
|
for (const r of rows) {
|
|
176
154
|
if (typeof r.belief === "string" && r.belief.trim().length > 0)
|
|
@@ -181,15 +159,15 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
181
159
|
return out;
|
|
182
160
|
}
|
|
183
161
|
/**
|
|
184
|
-
*
|
|
162
|
+
* Re-key an entries row in place for the opt-in source-maintenance script.
|
|
185
163
|
*
|
|
186
164
|
* The row id is preserved on purpose — `utility_scores`,
|
|
187
165
|
* `utility_scores_scoped`, and `embeddings` are keyed by `entry_id`, so an
|
|
188
|
-
* UPDATE (rather than a delete + insert under the new `
|
|
166
|
+
* UPDATE (rather than a delete + insert under the new `item_ref`) is what
|
|
189
167
|
* keeps the asset's accumulated usage-ranking history attached across a
|
|
190
168
|
* rename. (`asset_salience` / `asset_outcome` live in state.db keyed by
|
|
191
169
|
* `asset_ref` TEXT and are re-keyed separately by `akm mv` — see
|
|
192
|
-
*
|
|
170
|
+
* the state rekey helper.) `document_json.name` (and `filename`, when
|
|
193
171
|
* present) is patched and `search_text` rebuilt so search reflects the new
|
|
194
172
|
* name; the row is marked FTS-dirty for the caller's
|
|
195
173
|
* `rebuildFts({incremental: true})`.
|
|
@@ -203,7 +181,7 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
203
181
|
* never adopts them (live asset's history wins, matching the stale-row
|
|
204
182
|
* eviction below).
|
|
205
183
|
*
|
|
206
|
-
* A stale row already occupying
|
|
184
|
+
* A stale row already occupying the new item ref (the caller has verified no
|
|
207
185
|
* FILE exists at the target, so such a row can only be a leftover for a
|
|
208
186
|
* deleted file) is evicted first — through {@link deleteRelatedRows}, so its
|
|
209
187
|
* child rows (embeddings, entries_vec, utility scores, usage events) go with
|
|
@@ -211,44 +189,46 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
211
189
|
* FK under `PRAGMA foreign_keys = ON` and roll back the whole re-key.
|
|
212
190
|
* The moved row keeps its id.
|
|
213
191
|
*
|
|
214
|
-
* Returns the surviving row id, or `null` when no row matches
|
|
192
|
+
* Returns the surviving row id, or `null` when no row matches the old item ref
|
|
215
193
|
* (nothing indexed under the old name — the caller falls open and the next
|
|
216
194
|
* full `akm index` picks the file up as a fresh entry).
|
|
217
195
|
*/
|
|
218
196
|
export function rekeyEntryInPlace(db, opts) {
|
|
219
197
|
const oldItemRef = `${opts.sourceName}//${opts.oldRef}`;
|
|
220
198
|
const row = db
|
|
221
|
-
.prepare("SELECT id,
|
|
199
|
+
.prepare("SELECT id, file_path, document_json, search_text, type FROM entries WHERE item_ref = ?")
|
|
222
200
|
.get(oldItemRef);
|
|
223
201
|
if (!row)
|
|
224
202
|
return null;
|
|
225
|
-
|
|
226
|
-
|
|
203
|
+
const sourceRoot = path.resolve(opts.sourceRoot);
|
|
204
|
+
const currentPath = path.resolve(row.file_path);
|
|
205
|
+
if (currentPath !== sourceRoot && !currentPath.startsWith(`${sourceRoot}${path.sep}`)) {
|
|
206
|
+
throw new Error(`Refusing to re-key entry ${oldItemRef}: source root does not match.`);
|
|
227
207
|
}
|
|
228
|
-
// Patch the JSON payload. On corrupt
|
|
208
|
+
// Patch the JSON payload. On corrupt document_json still re-key identity/path so
|
|
229
209
|
// the utility history survives; the next full index heals the JSON.
|
|
230
|
-
let
|
|
210
|
+
let documentJson = row.document_json;
|
|
231
211
|
let searchText = row.search_text;
|
|
232
212
|
try {
|
|
233
|
-
const entry = JSON.parse(row.
|
|
213
|
+
const entry = JSON.parse(row.document_json);
|
|
234
214
|
entry.name = opts.newName;
|
|
235
215
|
if (typeof entry.filename === "string")
|
|
236
216
|
entry.filename = path.basename(opts.newFilePath);
|
|
237
217
|
if (opts.newDerivedFrom !== undefined)
|
|
238
218
|
entry.derivedFrom = opts.newDerivedFrom;
|
|
239
|
-
|
|
219
|
+
documentJson = JSON.stringify(entry);
|
|
240
220
|
searchText = buildSearchText(entry);
|
|
241
221
|
}
|
|
242
222
|
catch {
|
|
243
|
-
/* corrupt
|
|
223
|
+
/* corrupt document_json — identity/path-only re-key */
|
|
244
224
|
}
|
|
245
|
-
const expectedNewRef = conceptIdFromTypeName(row.
|
|
225
|
+
const expectedNewRef = conceptIdFromTypeName(row.type, opts.newName);
|
|
246
226
|
if (opts.newRef !== expectedNewRef) {
|
|
247
|
-
throw new Error(`Refusing to re-key entry ${
|
|
227
|
+
throw new Error(`Refusing to re-key entry ${oldItemRef}: target ref does not match the entry type and name.`);
|
|
248
228
|
}
|
|
249
229
|
const newItemRef = `${opts.sourceName}//${opts.newRef}`;
|
|
250
230
|
db.transaction(() => {
|
|
251
|
-
const stale = db.prepare("SELECT id FROM entries WHERE
|
|
231
|
+
const stale = db.prepare("SELECT id FROM entries WHERE item_ref = ?").get(newItemRef);
|
|
252
232
|
if (stale && stale.id !== row.id) {
|
|
253
233
|
// Full child-row cleanup (embeddings, entries_vec, utility scores,
|
|
254
234
|
// usage events, FTS + dirty marks) BEFORE the entries delete: the
|
|
@@ -258,7 +238,7 @@ export function rekeyEntryInPlace(db, opts) {
|
|
|
258
238
|
deleteRelatedRows(db, [{ id: stale.id }]);
|
|
259
239
|
db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
|
|
260
240
|
}
|
|
261
|
-
db.prepare("UPDATE entries SET
|
|
241
|
+
db.prepare("UPDATE entries SET file_path = ?, document_json = ?, search_text = ?, item_ref = ?, concept_id = ? WHERE id = ?").run(opts.newFilePath, documentJson, searchText, newItemRef, opts.newRef, row.id);
|
|
262
242
|
if (opts.newDerivedFrom !== undefined) {
|
|
263
243
|
db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
|
|
264
244
|
}
|
|
@@ -347,68 +327,54 @@ export function getPositiveFeedbackCountsByIds(ids) {
|
|
|
347
327
|
}, "positive feedback counts are best-effort");
|
|
348
328
|
return result;
|
|
349
329
|
}
|
|
350
|
-
function
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
330
|
+
function rowsInDirectory(db, dirPath, bundleId) {
|
|
331
|
+
const rows = db
|
|
332
|
+
.prepare(`SELECT id, item_ref, file_path FROM entries${bundleId ? " WHERE bundle_id = ?" : ""}`)
|
|
333
|
+
.all(...(bundleId ? [bundleId] : []));
|
|
334
|
+
const resolvedDir = path.resolve(dirPath);
|
|
335
|
+
return rows.filter((row) => path.dirname(path.resolve(row.file_path)) === resolvedDir);
|
|
336
|
+
}
|
|
337
|
+
function deleteEntryRows(db, rows, options = {}) {
|
|
338
|
+
if (rows.length === 0)
|
|
339
|
+
return [];
|
|
340
|
+
deleteRelatedRows(db, rows, options);
|
|
341
|
+
for (let i = 0; i < rows.length; i += SQLITE_CHUNK_SIZE) {
|
|
342
|
+
const chunk = rows.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
343
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
344
|
+
db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk.map((row) => row.id));
|
|
345
|
+
}
|
|
346
|
+
return rows.map((row) => row.id);
|
|
356
347
|
}
|
|
357
348
|
export function deleteEntriesByDir(db, dirPath) {
|
|
358
|
-
|
|
349
|
+
db.transaction(() => deleteEntryRows(db, rowsInDirectory(db, dirPath)))();
|
|
359
350
|
}
|
|
360
|
-
export function
|
|
361
|
-
return db.transaction(() =>
|
|
362
|
-
const ids = db
|
|
363
|
-
.prepare("SELECT id FROM entries WHERE dir_path = ? AND stash_dir = ?")
|
|
364
|
-
.all(dirPath, stashDir);
|
|
365
|
-
deleteRelatedRows(db, ids, options);
|
|
366
|
-
db.prepare("DELETE FROM entries WHERE dir_path = ? AND stash_dir = ?").run(dirPath, stashDir);
|
|
367
|
-
return ids.map((row) => row.id);
|
|
368
|
-
})();
|
|
351
|
+
export function deleteEntriesByDirAndBundle(db, dirPath, bundleId, options = {}) {
|
|
352
|
+
return db.transaction(() => deleteEntryRows(db, rowsInDirectory(db, dirPath, bundleId), options))();
|
|
369
353
|
}
|
|
370
|
-
/**
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
* needed (or correct) here; both grammars' rows are removed by location.
|
|
377
|
-
*/
|
|
378
|
-
export function deleteEntriesByStashDir(db, stashDir) {
|
|
379
|
-
deleteEntriesWhere(db, "stash_dir", stashDir);
|
|
354
|
+
/** Delete every entry and child row belonging to one canonical bundle. */
|
|
355
|
+
export function deleteEntriesByBundle(db, bundleId) {
|
|
356
|
+
db.transaction(() => {
|
|
357
|
+
const rows = db.prepare("SELECT id FROM entries WHERE bundle_id = ?").all(bundleId);
|
|
358
|
+
deleteEntryRows(db, rows);
|
|
359
|
+
})();
|
|
380
360
|
}
|
|
381
361
|
/**
|
|
382
|
-
* Diff-persist orphan delete
|
|
383
|
-
*
|
|
384
|
-
* `entry_key` is NOT in `keepKeys` — the rows for files that vanished from a
|
|
385
|
-
* rescanned directory.
|
|
362
|
+
* Diff-persist orphan delete: remove every entry under `dirPath` whose durable
|
|
363
|
+
* `item_ref` is not in `keepRefs`.
|
|
386
364
|
*
|
|
387
365
|
* Replaces the old per-dir `deleteEntriesByDir` + full re-insert: the caller
|
|
388
|
-
* upserts the current file set
|
|
366
|
+
* upserts the current file set first (ON CONFLICT preserving `entries.id`, so
|
|
389
367
|
* embeddings / utility / usage stay attached to unchanged rows), then calls this
|
|
390
|
-
* to prune only the
|
|
368
|
+
* to prune only the departed rows. The net row-state for the directory is identical
|
|
391
369
|
* to delete-then-reinsert; the win is that unchanged rows keep their id.
|
|
392
370
|
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* one another's rows.
|
|
371
|
+
* Both directory- and bundle-scoped so overlapping physical roots cannot
|
|
372
|
+
* prune one another's rows.
|
|
396
373
|
*/
|
|
397
|
-
export function
|
|
374
|
+
export function deleteEntriesByDirExceptRefs(db, dirPath, bundleId, keepRefs, options = {}) {
|
|
398
375
|
return db.transaction(() => {
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
.all(dirPath, stashDir);
|
|
402
|
-
const doomed = rows.filter((r) => !keepKeys.has(r.entry_key));
|
|
403
|
-
if (doomed.length === 0)
|
|
404
|
-
return [];
|
|
405
|
-
deleteRelatedRows(db, doomed, options);
|
|
406
|
-
for (let i = 0; i < doomed.length; i += SQLITE_CHUNK_SIZE) {
|
|
407
|
-
const chunk = doomed.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
408
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
409
|
-
db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk.map((r) => r.id));
|
|
410
|
-
}
|
|
411
|
-
return doomed.map((row) => row.id);
|
|
376
|
+
const doomed = rowsInDirectory(db, dirPath, bundleId).filter((row) => !keepRefs.has(row.item_ref));
|
|
377
|
+
return deleteEntryRows(db, doomed, options);
|
|
412
378
|
})();
|
|
413
379
|
}
|
|
414
380
|
function deleteRelatedRows(db, ids, options = {}) {
|
|
@@ -437,43 +403,40 @@ function deleteRelatedRows(db, ids, options = {}) {
|
|
|
437
403
|
bestEffort(() => db.prepare(`DELETE FROM utility_scores WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores for entries");
|
|
438
404
|
bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
|
|
439
405
|
}
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
deleteUsageEventsByEntryIds(numericIds);
|
|
445
|
-
// #624-P1: graph_files is NO LONGER keyed on entries.id, so deleting an
|
|
446
|
-
// entries row must NOT wipe the extracted graph (that is the whole point —
|
|
447
|
-
// the graph survives a reindex when body_hash is unchanged). We therefore do
|
|
448
|
-
// NOT delete graph_files here. We DO, however, recompute graph_meta counts
|
|
449
|
-
// for the stash roots touched by the deleted entries so the summary numbers
|
|
450
|
-
// stay consistent with the live child rows (the counts are derived, and the
|
|
451
|
-
// entries delete may have changed which files are considered/indexed).
|
|
452
|
-
//
|
|
453
|
-
// Resolve the affected stash roots from the entries rows BEFORE deletion.
|
|
454
|
-
const affectedStashRoots = new Set();
|
|
406
|
+
// Graph rows are independently keyed and intentionally survive entry
|
|
407
|
+
// deletion. Resolve their owning roots through the canonical physical path
|
|
408
|
+
// before entries disappear, then refresh the derived summary counts.
|
|
409
|
+
const affectedGraphRoots = new Set();
|
|
455
410
|
for (let i = 0; i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
456
411
|
const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
457
412
|
const placeholders = chunk.map(() => "?").join(",");
|
|
458
413
|
bestEffort(() => {
|
|
459
414
|
const rows = db
|
|
460
|
-
.prepare(`SELECT DISTINCT
|
|
415
|
+
.prepare(`SELECT DISTINCT gf.stash_root
|
|
416
|
+
FROM graph_files gf
|
|
417
|
+
JOIN entries e ON e.file_path = gf.file_path
|
|
418
|
+
WHERE e.id IN (${placeholders})`)
|
|
461
419
|
.all(...chunk);
|
|
462
|
-
for (const row of rows)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
}, "resolve stash roots for graph_meta recompute");
|
|
420
|
+
for (const row of rows)
|
|
421
|
+
affectedGraphRoots.add(row.stash_root);
|
|
422
|
+
}, "resolve graph roots for graph_meta recompute");
|
|
467
423
|
}
|
|
468
|
-
for (const stashRoot of
|
|
424
|
+
for (const stashRoot of affectedGraphRoots) {
|
|
469
425
|
bestEffort(() => db
|
|
470
426
|
.prepare(`UPDATE graph_meta
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
427
|
+
SET extracted_files = (SELECT COUNT(*) FROM graph_files WHERE stash_root = ?),
|
|
428
|
+
entity_count = (SELECT COUNT(*) FROM graph_file_entities WHERE stash_root = ?),
|
|
429
|
+
relation_count = (SELECT COUNT(*) FROM graph_file_relations WHERE stash_root = ?)
|
|
430
|
+
WHERE stash_root = ?`)
|
|
475
431
|
.run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
|
|
476
432
|
}
|
|
433
|
+
// usage_events lives in state.db, outside this transaction. Index persistence
|
|
434
|
+
// disables this cleanup and runs it only after its index.db transaction
|
|
435
|
+
// commits; standalone delete callers retain the immediate behavior.
|
|
436
|
+
if (options.cleanupUsageEvents !== false)
|
|
437
|
+
deleteUsageEventsByEntryIds(numericIds);
|
|
438
|
+
// graph_files is keyed by its own stash_root/file_path/body_hash identity,
|
|
439
|
+
// so deleting an entry row intentionally leaves extracted graph data intact.
|
|
477
440
|
}
|
|
478
441
|
export function deleteUsageEventsByEntryIds(entryIds) {
|
|
479
442
|
if (entryIds.length === 0 || !fs.existsSync(getStateDbPath()))
|
|
@@ -525,11 +488,11 @@ export function getAllEntries(db, entryType, excludeTypes) {
|
|
|
525
488
|
// list skips the clause (never `NOT IN ()`).
|
|
526
489
|
const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
|
|
527
490
|
if (entryType && entryType !== "any") {
|
|
528
|
-
sql = `SELECT ${ENTRY_COLUMNS} FROM entries WHERE
|
|
491
|
+
sql = `SELECT ${ENTRY_COLUMNS} FROM entries WHERE type = ?`;
|
|
529
492
|
params = [entryType];
|
|
530
493
|
}
|
|
531
494
|
else if (excludes.length > 0) {
|
|
532
|
-
sql = `SELECT ${ENTRY_COLUMNS} FROM entries WHERE
|
|
495
|
+
sql = `SELECT ${ENTRY_COLUMNS} FROM entries WHERE type NOT IN (${excludes.map(() => "?").join(", ")})`;
|
|
533
496
|
params = [...excludes];
|
|
534
497
|
}
|
|
535
498
|
else {
|
|
@@ -542,11 +505,10 @@ export function getAllEntries(db, entryType, excludeTypes) {
|
|
|
542
505
|
/**
|
|
543
506
|
* Resolve a single `entries.id` from a new-grammar `[bundle//]conceptId` ref,
|
|
544
507
|
* keying on the canonical stored `item_ref` (ref-grammar decision D-R1/D-R4).
|
|
545
|
-
* The optional `
|
|
546
|
-
* match to one source root.
|
|
508
|
+
* The optional `bundleId` scopes the match to one indexed bundle.
|
|
547
509
|
*/
|
|
548
|
-
export function findEntryIdByRef(db, ref,
|
|
549
|
-
return findEntryIdByBundleRef(db, ref,
|
|
510
|
+
export function findEntryIdByRef(db, ref, bundleId) {
|
|
511
|
+
return findEntryIdByBundleRef(db, ref, bundleId);
|
|
550
512
|
}
|
|
551
513
|
/** `name` plus its `.md`-toggled sibling — the markdown ext-keep/strip ambiguity. */
|
|
552
514
|
function withMdVariants(name) {
|
|
@@ -556,12 +518,12 @@ function withMdVariants(name) {
|
|
|
556
518
|
* Current (`[bundle//]conceptId`) id lookup: match `item_ref` exactly when
|
|
557
519
|
* bundle-qualified or by `//conceptId` suffix when short.
|
|
558
520
|
*/
|
|
559
|
-
function findEntryIdByBundleRef(db, ref,
|
|
521
|
+
function findEntryIdByBundleRef(db, ref, bundleId) {
|
|
560
522
|
const parsed = parseBundleRef(ref);
|
|
561
523
|
const conceptVariants = withMdVariants(parsed.conceptId);
|
|
562
524
|
// item_ref is the canonical stored spelling post-flip.
|
|
563
525
|
for (const conceptId of conceptVariants) {
|
|
564
|
-
const id = matchIdByItemRef(db, parsed.bundle, conceptId,
|
|
526
|
+
const id = matchIdByItemRef(db, parsed.bundle, conceptId, bundleId);
|
|
565
527
|
if (id !== undefined)
|
|
566
528
|
return id;
|
|
567
529
|
}
|
|
@@ -591,24 +553,23 @@ function findEntryIdByBundleRef(db, ref, stashDir) {
|
|
|
591
553
|
* `resolveRef`'s job; this DB helper takes no config handle). The exact-bundle
|
|
592
554
|
* arm is single-row under the UNIQUE `item_ref` index.
|
|
593
555
|
*/
|
|
594
|
-
function matchIdByItemRef(db, bundle, conceptId,
|
|
595
|
-
const scope =
|
|
556
|
+
function matchIdByItemRef(db, bundle, conceptId, bundleId) {
|
|
557
|
+
const scope = bundleId ? "AND bundle_id = ?" : "";
|
|
596
558
|
if (bundle !== undefined) {
|
|
597
559
|
const itemRef = `${bundle}//${conceptId}`;
|
|
598
560
|
const row = db
|
|
599
561
|
.prepare(`SELECT id FROM entries WHERE item_ref = ? ${scope} ORDER BY id ASC LIMIT 1`)
|
|
600
|
-
.get(itemRef, ...(
|
|
562
|
+
.get(itemRef, ...(bundleId ? [bundleId] : []));
|
|
601
563
|
return row?.id;
|
|
602
564
|
}
|
|
603
565
|
const suffix = `//${conceptId}`;
|
|
604
566
|
const row = db
|
|
605
567
|
.prepare(`SELECT id FROM entries
|
|
606
|
-
WHERE item_ref
|
|
607
|
-
AND substr(item_ref, length(item_ref) - length(?) + 1) = ?
|
|
568
|
+
WHERE substr(item_ref, length(item_ref) - length(?) + 1) = ?
|
|
608
569
|
${scope}
|
|
609
570
|
ORDER BY id ASC
|
|
610
571
|
LIMIT 1`)
|
|
611
|
-
.get(suffix, suffix, ...(
|
|
572
|
+
.get(suffix, suffix, ...(bundleId ? [bundleId] : []));
|
|
612
573
|
return row?.id;
|
|
613
574
|
}
|
|
614
575
|
export function getEntryCount(db) {
|
|
@@ -616,15 +577,12 @@ export function getEntryCount(db) {
|
|
|
616
577
|
return row.cnt;
|
|
617
578
|
}
|
|
618
579
|
/**
|
|
619
|
-
* Per-asset-type entry counts (keyed by `
|
|
580
|
+
* Per-asset-type entry counts (keyed by `type`, e.g. "skill",
|
|
620
581
|
* "knowledge", "memory"). Used by `akm info` to break down the aggregate
|
|
621
|
-
* `indexStats.entryCount` (R-057).
|
|
622
|
-
* omitted rather than surfaced under a synthetic key.
|
|
582
|
+
* `indexStats.entryCount` (R-057).
|
|
623
583
|
*/
|
|
624
584
|
export function getEntryCountByType(db) {
|
|
625
|
-
const rows = db
|
|
626
|
-
.prepare("SELECT entry_type AS type, COUNT(*) AS cnt FROM entries WHERE entry_type IS NOT NULL GROUP BY entry_type")
|
|
627
|
-
.all();
|
|
585
|
+
const rows = db.prepare("SELECT type, COUNT(*) AS cnt FROM entries GROUP BY type").all();
|
|
628
586
|
const out = {};
|
|
629
587
|
for (const row of rows) {
|
|
630
588
|
out[row.type] = row.cnt;
|
|
@@ -636,41 +594,44 @@ export function getEmbeddableEntryCount(db) {
|
|
|
636
594
|
}
|
|
637
595
|
export function getEntryById(db, id) {
|
|
638
596
|
const row = db
|
|
639
|
-
.prepare("SELECT file_path,
|
|
597
|
+
.prepare("SELECT file_path, document_json, item_ref, bundle_id, component_id, concept_id, adapter_id FROM entries WHERE id = ?")
|
|
640
598
|
.get(id);
|
|
641
599
|
if (!row)
|
|
642
600
|
return undefined;
|
|
643
601
|
// Guard against corrupt JSON
|
|
644
602
|
let entry;
|
|
645
603
|
try {
|
|
646
|
-
entry = JSON.parse(row.
|
|
604
|
+
entry = JSON.parse(row.document_json);
|
|
647
605
|
}
|
|
648
606
|
catch {
|
|
649
|
-
warn(`[db] getEntryById: skipping entry id=${id} — corrupt
|
|
607
|
+
warn(`[db] getEntryById: skipping entry id=${id} — corrupt document_json`);
|
|
650
608
|
return undefined;
|
|
651
609
|
}
|
|
652
610
|
return {
|
|
653
611
|
filePath: row.file_path,
|
|
654
|
-
stashDir: row.stash_dir,
|
|
655
612
|
entry,
|
|
656
613
|
itemRef: row.item_ref,
|
|
657
614
|
bundleId: row.bundle_id,
|
|
615
|
+
componentId: row.component_id,
|
|
658
616
|
conceptId: row.concept_id,
|
|
617
|
+
adapterId: row.adapter_id,
|
|
659
618
|
};
|
|
660
619
|
}
|
|
661
620
|
export function getEntriesByDir(db, dirPath) {
|
|
662
|
-
const
|
|
621
|
+
const ids = new Set(rowsInDirectory(db, dirPath).map((row) => row.id));
|
|
622
|
+
const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries`).all().filter((row) => ids.has(row.id));
|
|
663
623
|
return parseEntryRows(rows, "getEntriesByDir");
|
|
664
624
|
}
|
|
665
|
-
/** Return every directory previously indexed for one
|
|
666
|
-
export function
|
|
667
|
-
const rows = db.prepare("SELECT
|
|
668
|
-
return rows.map((row) => row.
|
|
625
|
+
/** Return every directory previously indexed for one canonical bundle. */
|
|
626
|
+
export function getIndexedDirPathsByBundleId(db, bundleId) {
|
|
627
|
+
const rows = db.prepare("SELECT file_path FROM entries WHERE bundle_id = ?").all(bundleId);
|
|
628
|
+
return [...new Set(rows.map((row) => path.dirname(row.file_path)))];
|
|
669
629
|
}
|
|
670
|
-
/** Return every persisted
|
|
671
|
-
export function
|
|
672
|
-
const
|
|
673
|
-
|
|
630
|
+
/** Return every persisted bundle owner for one physical directory. */
|
|
631
|
+
export function getIndexedBundleIdsByDir(db, dirPath) {
|
|
632
|
+
const ids = new Set(rowsInDirectory(db, dirPath).map((row) => row.id));
|
|
633
|
+
const rows = db.prepare("SELECT id, bundle_id FROM entries").all();
|
|
634
|
+
return [...new Set(rows.filter((row) => ids.has(row.id)).map((row) => row.bundle_id))];
|
|
674
635
|
}
|
|
675
636
|
/**
|
|
676
637
|
* Resolve a single `entries.id` by exact `file_path` (the canonical on-disk
|
|
@@ -704,7 +665,7 @@ export function getIndexedFilePaths(db) {
|
|
|
704
665
|
*
|
|
705
666
|
* Lifted verbatim (WS5) from the inline `SELECT file_path FROM entries WHERE
|
|
706
667
|
* id = ?` in commands/feedback-cli.ts. Unlike {@link getEntryById}, this does
|
|
707
|
-
* NOT parse `
|
|
668
|
+
* NOT parse `document_json`, so a row with corrupt JSON still yields its path —
|
|
708
669
|
* preserving feedback-cli's pre-extraction behaviour byte-for-byte.
|
|
709
670
|
*/
|
|
710
671
|
export function getEntryFilePathById(db, id) {
|
|
@@ -712,8 +673,9 @@ export function getEntryFilePathById(db, id) {
|
|
|
712
673
|
return row?.file_path;
|
|
713
674
|
}
|
|
714
675
|
/**
|
|
715
|
-
* Fetch every `(file_path,
|
|
716
|
-
*
|
|
676
|
+
* Fetch every `(file_path, document_json)` row whose path is under a source
|
|
677
|
+
* root. The path containment filter is applied in JS so SQLite LIKE wildcard
|
|
678
|
+
* characters in filesystem paths cannot widen ownership.
|
|
717
679
|
*
|
|
718
680
|
* Lifted verbatim (WS5) from the inline query in commands/graph.ts'
|
|
719
681
|
* `buildRefByPath`. The full result set is materialised with `.all()` before
|
|
@@ -721,27 +683,13 @@ export function getEntryFilePathById(db, id) {
|
|
|
721
683
|
* connection-lifetime rule). JSON parsing stays with the caller, unchanged.
|
|
722
684
|
*/
|
|
723
685
|
export function getEntryRefRowsForStashRoot(db, stashRoot) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
.
|
|
686
|
+
const root = path.resolve(stashRoot);
|
|
687
|
+
return db.prepare("SELECT file_path, document_json FROM entries").all().filter((row) => {
|
|
688
|
+
const file = path.resolve(row.file_path);
|
|
689
|
+
return file === root || file.startsWith(`${root}${path.sep}`);
|
|
690
|
+
});
|
|
727
691
|
}
|
|
728
692
|
// ── Indexer-phase helpers (moved from indexer.ts) ────────────────────────────
|
|
729
|
-
/**
|
|
730
|
-
* Upsert a workflow document record for an indexed entry.
|
|
731
|
-
* Persists the parsed workflow AST as JSON alongside a FNV-1a hash of the
|
|
732
|
-
* source content for future incremental fast-paths.
|
|
733
|
-
*/
|
|
734
|
-
export function upsertWorkflowDocument(db, entryId, doc, content) {
|
|
735
|
-
const sourceHash = computeSourceHash(content);
|
|
736
|
-
db.prepare(`INSERT INTO workflow_documents (entry_id, schema_version, document_json, source_path, source_hash, updated_at)
|
|
737
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
738
|
-
ON CONFLICT(entry_id) DO UPDATE SET
|
|
739
|
-
schema_version = excluded.schema_version,
|
|
740
|
-
document_json = excluded.document_json,
|
|
741
|
-
source_path = excluded.source_path,
|
|
742
|
-
source_hash = excluded.source_hash,
|
|
743
|
-
updated_at = excluded.updated_at`).run(entryId, doc.schemaVersion, JSON.stringify(doc), doc.source.path, sourceHash, new Date().toISOString());
|
|
744
|
-
}
|
|
745
693
|
/**
|
|
746
694
|
* Compute a cheap FNV-1a hash of a buffer for source-identity tracking.
|
|
747
695
|
* Not security-sensitive; used as an incremental fast-path skip key.
|
|
@@ -791,8 +739,7 @@ export function getEntryByRef(db, ref) {
|
|
|
791
739
|
/**
|
|
792
740
|
* The fully-qualified `item_ref` (`<bundle>//<conceptId>`, the durable stored
|
|
793
741
|
* spelling — spec §11.1 D-R3) for an entry `id`, or `null` when the row is gone
|
|
794
|
-
*
|
|
795
|
-
* full index). The usage-event / salience / feedback writers derive the durable
|
|
742
|
+
* The usage-event / salience / feedback writers derive the durable
|
|
796
743
|
* key from this so a stored key is always the resolved entry's canonical ref,
|
|
797
744
|
* never raw input (D-R3: durable keys are never derived from input).
|
|
798
745
|
*/
|
|
@@ -823,7 +770,15 @@ function resolveUsageEventEntryId(db, ref) {
|
|
|
823
770
|
* distinct linked entry_ids in usage_events is small — and the re-resolution
|
|
824
771
|
* reads `entries` from `indexDb`.
|
|
825
772
|
*/
|
|
826
|
-
|
|
773
|
+
function qualifiedUsageEventsTable(stateSchema) {
|
|
774
|
+
if (stateSchema === undefined)
|
|
775
|
+
return "usage_events";
|
|
776
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(stateSchema))
|
|
777
|
+
throw new Error("Invalid attached state schema name.");
|
|
778
|
+
return `"${stateSchema}".usage_events`;
|
|
779
|
+
}
|
|
780
|
+
export function relinkUsageEvents(indexDb, stateDb, options = {}) {
|
|
781
|
+
const usageEvents = qualifiedUsageEventsTable(options.stateSchema);
|
|
827
782
|
bestEffort(() => {
|
|
828
783
|
// Step 1: null out stale entry_ids (entry was deleted, re-keyed, etc).
|
|
829
784
|
// Leaving them in place would let `recomputeUtilityScores` aggregate by an
|
|
@@ -831,23 +786,28 @@ export function relinkUsageEvents(indexDb, stateDb, _options = {}) {
|
|
|
831
786
|
// on the utility_scores INSERT and roll back the entire finalize
|
|
832
787
|
// transaction. Nulled rows can be re-resolved by step 2 below; events whose
|
|
833
788
|
// entry is permanently gone simply stay null and age out via retention.
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
789
|
+
const linkedRows = stateDb
|
|
790
|
+
.prepare(`SELECT DISTINCT entry_id AS id, entry_ref AS ref FROM ${usageEvents} WHERE entry_id IS NOT NULL`)
|
|
791
|
+
.all();
|
|
792
|
+
const entryIdentity = indexDb.prepare("SELECT item_ref AS itemRef FROM entries WHERE id = ?");
|
|
793
|
+
const staleLinks = linkedRows.filter(({ id, ref }) => {
|
|
794
|
+
const live = entryIdentity.get(id);
|
|
795
|
+
return live == null || (ref !== null && live.itemRef !== ref);
|
|
796
|
+
});
|
|
797
|
+
if (staleLinks.length > 0) {
|
|
798
|
+
const nullOut = stateDb.prepare(`UPDATE ${usageEvents} SET entry_id = NULL WHERE entry_id = ? AND entry_ref IS ?`);
|
|
839
799
|
const nullTx = stateDb.transaction(() => {
|
|
840
|
-
for (const id of
|
|
841
|
-
nullOut.run(id);
|
|
800
|
+
for (const { id, ref } of staleLinks)
|
|
801
|
+
nullOut.run(id, ref);
|
|
842
802
|
});
|
|
843
803
|
nullTx();
|
|
844
804
|
}
|
|
845
805
|
// Step 2: re-resolve each fully-qualified ref. Bare rows are not current
|
|
846
806
|
// durable identities and remain detached.
|
|
847
807
|
const refs = stateDb
|
|
848
|
-
.prepare(
|
|
808
|
+
.prepare(`SELECT DISTINCT entry_ref AS ref FROM ${usageEvents} WHERE entry_id IS NULL AND entry_ref IS NOT NULL`)
|
|
849
809
|
.all();
|
|
850
|
-
const update = stateDb.prepare(
|
|
810
|
+
const update = stateDb.prepare(`UPDATE ${usageEvents} SET entry_id = ? WHERE entry_ref = ? AND entry_id IS NULL`);
|
|
851
811
|
const relinkTx = stateDb.transaction(() => {
|
|
852
812
|
for (const { ref } of refs) {
|
|
853
813
|
let id;
|