akm-cli 0.9.0-rc.9 → 0.9.1-beta.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 +1770 -44
- package/README.md +38 -47
- package/SECURITY.md +14 -1
- package/STABILITY.md +499 -0
- package/dist/akm +148 -35
- package/dist/{akm-migrate-storage → akm-migrate} +6 -9
- package/dist/assets/hints/cli-hints-full.md +223 -95
- package/dist/assets/hints/cli-hints-short.md +85 -22
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/stash-skeleton/README.md +6 -5
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
- package/dist/assets/tasks/core/extract.yml +1 -1
- package/dist/assets/tasks/core/version-check.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
- package/dist/assets/templates/html/health.html +1 -3
- package/dist/assets/workflows/workflow-template.md +32 -15
- package/dist/cli/invocation.js +40 -15
- package/dist/cli/parse-args.js +0 -22
- package/dist/cli/retired-commands.js +121 -0
- package/dist/cli/shared.js +154 -22
- package/dist/cli/unknown-flags.js +236 -0
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +696 -258
- package/dist/commands/agent/agent-dispatch.js +14 -3
- package/dist/commands/agent/contribute-cli.js +74 -89
- package/dist/commands/completions.js +79 -22
- package/dist/commands/config-cli.js +17 -150
- package/dist/commands/env/env-cli.js +59 -143
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +36 -66
- package/dist/commands/env/secret.js +24 -57
- package/dist/commands/feedback-cli.js +148 -88
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +3 -4
- package/dist/commands/health/checks.js +85 -23
- package/dist/commands/health/html-report.js +7 -10
- package/dist/commands/health/improve-metrics.js +25 -83
- package/dist/commands/health/llm-usage.js +2 -1
- package/dist/commands/health/md-report.js +5 -9
- package/dist/commands/health/metrics.js +62 -20
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +4 -5
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +4 -122
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +160 -35
- package/dist/commands/improve/anti-collapse.js +2 -2
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +41 -40
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +140 -1000
- package/dist/commands/improve/distill/promote-memory.js +12 -12
- package/dist/commands/improve/distill/quality-gate.js +11 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +101 -55
- package/dist/commands/improve/extract-cli.js +14 -133
- package/dist/commands/improve/improve-cli.js +98 -114
- package/dist/commands/improve/improve-result-file.js +1 -28
- package/dist/commands/improve/improve-strategies.js +8 -5
- package/dist/commands/improve/improve.js +254 -101
- package/dist/commands/improve/locks.js +7 -0
- package/dist/commands/improve/loop-stages.js +182 -20
- package/dist/commands/improve/memory/derived-ref.js +45 -43
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
- package/dist/commands/improve/memory/memory-improve.js +14 -5
- package/dist/commands/improve/outcome-loop.js +22 -65
- package/dist/commands/improve/preparation.js +114 -123
- package/dist/commands/improve/proactive-maintenance.js +2 -5
- package/dist/commands/improve/reflect.js +56 -160
- package/dist/commands/improve/run-context.js +5 -0
- package/dist/commands/improve/salience.js +11 -122
- package/dist/commands/improve/session-asset.js +4 -0
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +51 -131
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +444 -88
- package/dist/commands/lint/types.js +22 -1
- package/dist/commands/{events.js → log.js} +33 -38
- package/dist/commands/migrate-cli.js +92 -12
- package/dist/commands/migration-tool.js +46 -0
- package/dist/commands/observability-cli.js +70 -209
- package/dist/commands/proposal/drain.js +101 -29
- package/dist/commands/proposal/proposal-cli.js +76 -48
- package/dist/commands/proposal/proposal.js +54 -18
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +23 -15
- package/dist/commands/proposal/repository.js +718 -279
- package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
- package/dist/commands/proposal/validators/proposal-validators.js +55 -7
- package/dist/commands/proposal/validators/proposals.js +4 -7
- package/dist/commands/read/curate.js +34 -53
- package/dist/commands/read/knowledge.js +150 -95
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +42 -15
- package/dist/commands/read/search-cli.js +180 -78
- package/dist/commands/read/search.js +58 -43
- package/dist/commands/read/show.js +197 -141
- package/dist/commands/registry-cli.js +12 -51
- package/dist/commands/remember.js +14 -57
- package/dist/commands/sources/add-cli.js +100 -31
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +7 -2
- package/dist/commands/sources/info.js +30 -7
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +387 -98
- package/dist/commands/sources/schema-repair.js +3 -2
- package/dist/commands/sources/self-update.js +131 -38
- package/dist/commands/sources/source-add.js +72 -17
- package/dist/commands/sources/source-clone.js +129 -45
- package/dist/commands/sources/source-manage.js +43 -23
- package/dist/commands/sources/sources-cli.js +57 -208
- package/dist/commands/sources/stash-cli.js +46 -53
- package/dist/commands/tasks/tasks-cli.js +91 -97
- package/dist/commands/tasks/tasks.js +284 -423
- package/dist/commands/workflow-cli.js +181 -450
- package/dist/core/abort-deadline.js +28 -0
- package/dist/core/adapter/adapters/agent-skills-adapter.js +67 -5
- package/dist/core/adapter/adapters/akm-adapter.js +60 -38
- package/dist/core/adapter/adapters/akm-lint.js +109 -42
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +54 -31
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
- package/dist/core/adapter/adapters/index.js +6 -6
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
- package/dist/core/adapter/adapters/okf-adapter.js +187 -19
- package/dist/core/adapter/adapters/shared.js +3 -19
- package/dist/core/adapter/adapters/tool-dir-shared.js +13 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +6 -4
- package/dist/core/adapter/validate-context.js +214 -0
- package/dist/core/asset/akm-markdown.js +63 -0
- package/dist/core/asset/asset-placement.js +20 -6
- package/dist/core/asset/asset-ref.js +11 -9
- package/dist/core/asset/frontmatter-lint.js +30 -0
- package/dist/core/asset/frontmatter.js +29 -9
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +89 -18
- package/dist/core/asset/stash-meta.js +1 -1
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +218 -44
- package/dist/core/concurrent.js +32 -0
- package/dist/core/config/config-io.js +12 -1
- package/dist/core/config/config-schema.js +35 -8
- package/dist/core/config/config-sources.js +55 -11
- package/dist/core/config/config-walker.js +25 -9
- package/dist/core/config/config.js +9 -48
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +5 -1
- package/dist/core/config/schema/experimental.js +30 -0
- package/dist/core/config/schema/improve-processes.js +0 -6
- package/dist/core/config/schema/improve.js +21 -3
- package/dist/core/config/schema/index-config.js +8 -15
- package/dist/core/config/schema/output.js +4 -1
- package/dist/core/config/schema/setup.js +9 -18
- package/dist/core/config/schema/sources-bundles.js +49 -33
- package/dist/core/config/schema/workflow.js +14 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +43 -12
- package/dist/core/events.js +76 -152
- package/dist/core/file-change.js +6 -5
- package/dist/core/file-lock.js +7 -1
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +6 -100
- package/dist/core/json-schema.js +327 -9
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/loopback.js +89 -0
- package/dist/core/migration-operation.js +33 -2
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/path-access.js +107 -0
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +98 -17
- package/dist/core/spawn-env.js +234 -0
- package/dist/core/standards/resolve-standards-context.js +2 -14
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +2 -2
- package/dist/core/state/migrations.js +41 -18
- package/dist/core/state-db-scope.js +134 -0
- package/dist/core/state-db.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +182 -36
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/type-presentation.js +3 -3
- package/dist/core/warn.js +0 -3
- package/dist/core/write-provenance.js +85 -0
- package/dist/core/write-source.js +778 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +17 -30
- package/dist/indexer/ensure-index.js +11 -3
- package/dist/indexer/graph/graph-boost.js +9 -34
- package/dist/indexer/graph/graph-extraction.js +8 -5
- package/dist/indexer/index-writer-lock.js +53 -17
- package/dist/indexer/index-written-assets.js +33 -24
- package/dist/indexer/indexer.js +551 -253
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +15 -9
- package/dist/indexer/passes/metadata.js +113 -47
- package/dist/indexer/scan/doc-to-entry.js +38 -1
- package/dist/indexer/scan/drain-dir.js +13 -23
- package/dist/indexer/search/db-search.js +107 -69
- package/dist/indexer/search/fts-query.js +47 -24
- package/dist/indexer/search/ranking-contributors.js +42 -20
- package/dist/indexer/search/ranking.js +18 -99
- package/dist/indexer/search/search-fields.js +7 -2
- package/dist/indexer/search/search-source.js +82 -93
- package/dist/indexer/usage/usage-events.js +0 -89
- package/dist/indexer/walk/file-context.js +2 -1
- package/dist/indexer/walk/matchers.js +30 -43
- package/dist/indexer/walk/path-resolver.js +7 -2
- package/dist/indexer/walk/walker.js +38 -12
- package/dist/integrations/agent/builders.js +0 -6
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/engine-resolution.js +24 -11
- package/dist/integrations/agent/model-aliases.js +1 -1
- package/dist/integrations/agent/profiles.js +23 -1
- package/dist/integrations/agent/prompts.js +12 -8
- package/dist/integrations/agent/runner-dispatch.js +4 -2
- package/dist/integrations/agent/runner.js +0 -1
- package/dist/integrations/agent/spawn.js +20 -93
- package/dist/integrations/github.js +1 -1
- package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
- package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
- package/dist/integrations/harnesses/claude/session-log.js +0 -10
- package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
- package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
- package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
- package/dist/integrations/harnesses/index.js +3 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
- package/dist/integrations/harnesses/opencode/session-log.js +0 -15
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
- package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
- package/dist/integrations/lockfile.js +132 -8
- package/dist/integrations/session-logs/index.js +3 -28
- package/dist/llm/client.js +136 -100
- package/dist/llm/embedders/remote.js +13 -5
- package/dist/llm/feature-gate.js +4 -12
- package/dist/llm/graph-extract.js +5 -11
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +5 -7
- package/dist/llm/structured-call.js +1 -1
- package/dist/llm/usage-persist.js +26 -5
- package/dist/llm/usage-telemetry.js +25 -2
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +259 -0
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -36
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +56 -83
- package/dist/output/shapes/migrate.js +8 -0
- package/dist/output/shapes/passthrough.js +7 -41
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes.js +2 -9
- package/dist/output/text/{init.js → bundle-create.js} +3 -1
- package/dist/output/text/bundle-show.js +7 -0
- package/dist/output/text/command-format.js +164 -96
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/health-format.js +103 -0
- package/dist/output/text/health.js +7 -0
- package/dist/output/text/helpers.js +10 -8
- package/dist/output/text/lint-format.js +56 -0
- package/dist/{migrate-storage-node.mjs → output/text/lint.js} +2 -5
- package/dist/output/text/migrate.js +88 -0
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +44 -72
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +15 -7
- package/dist/output/text/status-list.js +32 -0
- package/dist/output/text/{save.js → sync.js} +2 -2
- package/dist/output/text/workflow-format.js +24 -203
- package/dist/output/text/workflow.js +1 -7
- package/dist/output/text.js +16 -17
- package/dist/registry/factory.js +4 -6
- package/dist/registry/origin-resolve.js +16 -27
- package/dist/registry/providers/skills-sh.js +3 -3
- package/dist/registry/providers/static-index.js +13 -23
- package/dist/registry/resolve.js +42 -7
- package/dist/registry/semver.js +34 -84
- package/dist/runtime.js +2 -23
- package/dist/scripts/akm-migrate-node.js +60972 -0
- package/dist/scripts/akm-migrate.js +60310 -0
- package/dist/setup/detect.js +42 -15
- package/dist/setup/registry-stash-loader.js +2 -2
- package/dist/setup/setup.js +236 -136
- package/dist/setup/steps/connection.js +7 -9
- package/dist/setup/steps/platforms.js +9 -9
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +12 -13
- package/dist/setup/steps/stashdir.js +2 -3
- package/dist/setup/steps/tasks.js +237 -120
- package/dist/sources/freshness.js +1 -1
- package/dist/sources/provider-factory.js +11 -17
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +278 -34
- package/dist/sources/providers/git-provider.js +25 -23
- package/dist/sources/providers/git-stash.js +414 -106
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +7 -4
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +6 -1
- package/dist/sources/resolve.js +6 -5
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
- package/dist/sources/snapshot-fetchers/github.js +100 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
- package/dist/sources/snapshot-fetchers/registry.js +17 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +282 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +686 -282
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +21 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +27 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -12
- package/dist/storage/repositories/index-connection.js +44 -8
- package/dist/storage/repositories/index-entries-repository.js +157 -241
- package/dist/storage/repositories/index-entry-mapper.js +15 -11
- package/dist/storage/repositories/index-fts-repository.js +5 -2
- package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
- package/dist/storage/repositories/index-meta-repository.js +2 -3
- package/dist/storage/repositories/index-schema.js +10 -25
- package/dist/storage/repositories/index-utility-repository.js +15 -28
- package/dist/storage/repositories/index-vec-repository.js +6 -1
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +296 -59
- package/dist/storage/repositories/registry-cache.js +19 -0
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +15 -13
- package/dist/storage/repositories/workflow-runs-repository.js +118 -53
- package/dist/tasks/backends/cron.js +105 -15
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +85 -38
- package/dist/tasks/backends/schtasks.js +135 -15
- package/dist/tasks/embedded.js +56 -40
- package/dist/tasks/log-redaction.js +156 -0
- package/dist/tasks/parser.js +87 -160
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +209 -44
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +89 -1
- package/dist/tasks/task-id.js +1 -3
- package/dist/tasks/validator.js +20 -6
- package/dist/workflows/authoring/authoring.js +94 -143
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/concurrency-policy.js +95 -1
- package/dist/workflows/exec/dispatch-redaction.js +114 -0
- package/dist/workflows/exec/exec-unit.js +542 -0
- package/dist/workflows/exec/frozen-judge.js +130 -32
- package/dist/workflows/exec/native-executor.js +513 -274
- package/dist/workflows/exec/param-secrets.js +12 -11
- package/dist/workflows/exec/run-workflow.js +496 -237
- package/dist/workflows/exec/step-work.js +616 -412
- package/dist/workflows/exec/unit-dispatch.js +34 -0
- package/dist/workflows/exec/unit-writer.js +53 -13
- package/dist/workflows/exec/worktree.js +454 -41
- package/dist/workflows/ir/compile.js +166 -271
- package/dist/workflows/ir/freeze.js +119 -42
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +129 -45
- package/dist/workflows/parser.js +1070 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +31 -10
- package/dist/workflows/renderer.js +123 -68
- package/dist/workflows/resource-limits.js +184 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +331 -132
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +47 -38
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +110 -0
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +11 -11
- package/docs/migration/release-notes/0.9.0.md +209 -27
- package/docs/migration/v0.7-to-v0.8.md +48 -48
- package/docs/migration/v0.8-to-v0.9.md +567 -209
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +19 -0
- package/docs/reference/bundle-types.md +19 -0
- package/docs/reference/cli.md +2342 -0
- package/docs/reference/configuration.md +371 -0
- package/docs/reference/data-and-telemetry.md +129 -46
- package/docs/reference/supported-formats.md +50 -0
- package/docs/reference/workflow-schema.md +1014 -0
- package/docs/reference/workflows.md +51 -0
- package/package.json +31 -13
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +89 -128
- package/schemas/akm-task.json +27 -5
- package/schemas/akm-workflow.json +157 -81
- package/dist/assets/tasks/core/backup.yml +0 -5
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/cli/config-migrate.js +0 -1878
- package/dist/cli/config-validate.js +0 -41
- package/dist/commands/backup-cli.js +0 -56
- package/dist/commands/bundle/bundle-cli.js +0 -68
- package/dist/commands/bundle/bundle.js +0 -219
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -489
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/mv-cli.js +0 -1221
- package/dist/commands/sources/history.js +0 -201
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/core/migration-backup.js +0 -1231
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
- package/dist/llm/memory-infer-impl.js +0 -138
- package/dist/migrate/legacy/config-source-migration.js +0 -257
- package/dist/migrate/legacy/content-migration.js +0 -350
- package/dist/migrate/legacy/legacy-layout.js +0 -779
- package/dist/migrate/legacy/legacy-paths.js +0 -25
- package/dist/migrate/legacy/legacy-stash-json.js +0 -80
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -278
- package/dist/migrate/legacy/three-db-cutover.js +0 -845
- package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
- package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
- package/dist/migrate/legacy-ref-grammar.js +0 -214
- package/dist/output/shapes/distill.js +0 -14
- package/dist/output/shapes/history.js +0 -11
- package/dist/output/text/distill.js +0 -6
- package/dist/output/text/enable-disable.js +0 -8
- package/dist/output/text/history.js +0 -6
- package/dist/registry/build-index.js +0 -382
- package/dist/schemas/akm-config.json +0 -4704
- package/dist/schemas/akm-task.json +0 -87
- package/dist/schemas/akm-workflow.json +0 -372
- package/dist/scripts/migrate-storage.js +0 -3816
- package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
- package/dist/workflows/cli.js +0 -53
- package/dist/workflows/exec/brief.js +0 -481
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/watch.js +0 -116
- package/dist/workflows/program/parser.js +0 -813
- package/dist/workflows/program/project.js +0 -104
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
* `index.db` entries repository — CRUD, lookup, re-key, delete-cascade, and the
|
|
6
6
|
* usage-event relink + workflow-document + tag-set reads that key on `entries`.
|
|
7
7
|
*
|
|
8
|
-
* Owns ALL raw SQL against the `entries` table
|
|
9
|
-
*
|
|
10
|
-
* leaf types + mapper modules rather than from the old `db.ts` hub.
|
|
8
|
+
* Owns ALL raw SQL against the `entries` table. The shared row/option shapes
|
|
9
|
+
* come from the leaf types + mapper modules.
|
|
11
10
|
*/
|
|
12
11
|
import fs from "node:fs";
|
|
13
12
|
import path from "node:path";
|
|
14
13
|
import { parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
15
14
|
import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
|
|
16
15
|
import { bestEffort } from "../../core/best-effort.js";
|
|
16
|
+
import { isPathAbsent } from "../../core/path-access.js";
|
|
17
17
|
import { getStateDbPath, withStateDb } from "../../core/state-db.js";
|
|
18
18
|
import { warn } from "../../core/warn.js";
|
|
19
19
|
import { buildSearchText } from "../../indexer/search/search-fields.js";
|
|
20
20
|
import { ENTRY_COLUMNS, rowToIndexedEntry } from "./index-entry-mapper.js";
|
|
21
21
|
import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
|
|
22
|
-
import { isVecAvailable } from "./index-vec-repository.js";
|
|
22
|
+
import { deleteEntryVectors, isVecAvailable } from "./index-vec-repository.js";
|
|
23
23
|
// ── Entry operations ────────────────────────────────────────────────────────
|
|
24
24
|
/**
|
|
25
25
|
* Insert or update an entry in the `entries` table. Returns the row id.
|
|
@@ -29,11 +29,12 @@ import { isVecAvailable } from "./index-vec-repository.js";
|
|
|
29
29
|
* reflect the changes.
|
|
30
30
|
*/
|
|
31
31
|
export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, searchText, provenance, contentHash) {
|
|
32
|
-
// Hot path during indexing — cache
|
|
33
|
-
//
|
|
34
|
-
// every call. The dirty-mark INSERT and the upsert-with-RETURNING
|
|
35
|
-
// share the same WeakMap so they live and die with the connection.
|
|
32
|
+
// Hot path during indexing — cache prepared statements per database
|
|
33
|
+
// connection so we don't pay the SQL parse/compile cost on every call.
|
|
36
34
|
const stmts = getUpsertStmts(db);
|
|
35
|
+
const previous = (provenance?.itemRef
|
|
36
|
+
? stmts.findByItemRef.get(provenance.itemRef)
|
|
37
|
+
: undefined) ?? stmts.findByEntryKey.get(entryKey);
|
|
37
38
|
// Phase 5A / Advantage D5: surface derived memory parent ref into the
|
|
38
39
|
// dedicated `derived_from` column so retrieval-time lookup (parent→child)
|
|
39
40
|
// does not have to scan + JSON-decode every memory row.
|
|
@@ -45,22 +46,29 @@ export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, se
|
|
|
45
46
|
// by the next full index). `content_hash` (F4a M-core-2) is `doc.hash` from
|
|
46
47
|
// the diff-persist writer; a NULL passed here PRESERVES any existing hash (the
|
|
47
48
|
// ON CONFLICT COALESCE below) so the LLM-enrichment re-upsert cannot wipe it.
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
const apply = () => {
|
|
50
|
+
const result = stmts.upsert.get(entryKey, dirPath, filePath, stashDir, JSON.stringify(entry), searchText, entry.type, derivedFrom, provenance?.itemRef ?? null, provenance?.bundleId ?? null, provenance?.componentId ?? null, provenance?.conceptId ?? null, provenance?.adapterId ?? null, entry.type, contentHash ?? null);
|
|
51
|
+
if (!result)
|
|
52
|
+
throw new Error("upsertEntry: entry_key not found after upsert");
|
|
53
|
+
if (previous?.id === result.id && previous.search_text !== searchText)
|
|
54
|
+
deleteEntryVectors(db, result.id);
|
|
55
|
+
// Mark this entry as FTS-dirty so `rebuildFts({ incremental: true })`
|
|
56
|
+
// only revisits entries that actually changed. INSERT OR IGNORE is
|
|
57
|
+
// idempotent across multiple upserts of the same row.
|
|
58
|
+
stmts.markDirty.run(result.id);
|
|
59
|
+
return result.id;
|
|
60
|
+
};
|
|
61
|
+
return previous && previous.search_text !== searchText ? db.transaction(apply)() : apply();
|
|
56
62
|
}
|
|
57
63
|
const upsertStmtsByDb = new WeakMap();
|
|
58
64
|
// The ON CONFLICT DO UPDATE column assignments — factored out so the two
|
|
59
|
-
// conflict targets below stay byte-identical. `
|
|
60
|
-
//
|
|
65
|
+
// conflict targets below stay byte-identical. `item_ref` is durable identity;
|
|
66
|
+
// update `entry_key` when adapter ownership changes its
|
|
67
|
+
// spelling so directory pruning retains the upserted row.
|
|
61
68
|
// `content_hash` COALESCEs so a NULL passed by the LLM-enhance re-upsert cannot
|
|
62
69
|
// wipe a previously-persisted hash.
|
|
63
70
|
const UPSERT_SET_CLAUSE = `SET
|
|
71
|
+
entry_key = excluded.entry_key,
|
|
64
72
|
dir_path = excluded.dir_path,
|
|
65
73
|
file_path = excluded.file_path,
|
|
66
74
|
stash_dir = excluded.stash_dir,
|
|
@@ -75,53 +83,14 @@ const UPSERT_SET_CLAUSE = `SET
|
|
|
75
83
|
adapter_id = excluded.adapter_id,
|
|
76
84
|
type = excluded.type,
|
|
77
85
|
content_hash = COALESCE(excluded.content_hash, content_hash)`;
|
|
78
|
-
/**
|
|
79
|
-
* Whether `entries.item_ref` currently carries its UNIQUE index (the v19
|
|
80
|
-
* `idx_entries_item_ref`, built by `ensureUniqueItemRefIndex`). On a
|
|
81
|
-
* partially-migrated DB with a duplicate non-NULL item_ref, that build falls
|
|
82
|
-
* back to a NON-unique index — and `ON CONFLICT(item_ref)` is only a legal
|
|
83
|
-
* conflict target when a UNIQUE index/constraint backs the column (otherwise
|
|
84
|
-
* the upsert throws AT PREPARE TIME). This gate lets {@link getUpsertStmts}
|
|
85
|
-
* degrade to the always-present `entry_key` target until a rebuild restores
|
|
86
|
-
* uniqueness. Best-effort: any probe failure treats item_ref as non-unique.
|
|
87
|
-
*/
|
|
88
|
-
function itemRefHasUniqueIndex(db) {
|
|
89
|
-
try {
|
|
90
|
-
const indexes = db.prepare("PRAGMA index_list(entries)").all();
|
|
91
|
-
return indexes.some((idx) => idx.name === "idx_entries_item_ref" && Number(idx.unique) === 1);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
86
|
function getUpsertStmts(db) {
|
|
98
87
|
const existing = upsertStmtsByDb.get(db);
|
|
99
88
|
if (existing)
|
|
100
89
|
return existing;
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// EXISTING row with a NULL `item_ref` (SQLite treats NULLs as distinct in a
|
|
106
|
-
// UNIQUE index, so an item_ref-only target would miss the row, fall through
|
|
107
|
-
// to a plain INSERT, and ABORT on the `entry_key NOT NULL UNIQUE` constraint).
|
|
108
|
-
// Concretely, the out-of-scope LLM metadata-enhance re-upsert (indexer.ts
|
|
109
|
-
// `enhanceDirsWithLlm`) re-writes already-indexed rows without provenance →
|
|
110
|
-
// NULL item_ref; the `entry_key` arm keeps that re-upsert an UPDATE, not a
|
|
111
|
-
// crash. Both arms run the IDENTICAL assignment block, so the outcome is the
|
|
112
|
-
// same regardless of which constraint matches. The `entry_key` fallback is
|
|
113
|
-
// deletable once every write path sets `item_ref`.
|
|
114
|
-
//
|
|
115
|
-
// When item_ref lacks its UNIQUE index (the ensureUniqueItemRefIndex fallback
|
|
116
|
-
// on a partially-migrated DB), `ON CONFLICT(item_ref)` is not a legal target
|
|
117
|
-
// and would fail at prepare time — so we degrade to the entry_key-only upsert
|
|
118
|
-
// (behaviour-identical to the pre-repoint key) until the next rebuild restores
|
|
119
|
-
// uniqueness; without this the very rebuild meant to heal the duplicate could
|
|
120
|
-
// not run.
|
|
121
|
-
const conflictClause = itemRefHasUniqueIndex(db)
|
|
122
|
-
? `ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
|
|
123
|
-
ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`
|
|
124
|
-
: `ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`;
|
|
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}`;
|
|
125
94
|
const stmts = {
|
|
126
95
|
// RETURNING id handles ON CONFLICT DO UPDATE correctly — no second
|
|
127
96
|
// SELECT round-trip needed (last_insert_rowid() is unreliable for
|
|
@@ -136,6 +105,8 @@ function getUpsertStmts(db) {
|
|
|
136
105
|
RETURNING id
|
|
137
106
|
`),
|
|
138
107
|
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 = ? ORDER BY id ASC LIMIT 1"),
|
|
109
|
+
findByEntryKey: db.prepare("SELECT id, search_text FROM entries WHERE entry_key = ? LIMIT 1"),
|
|
139
110
|
};
|
|
140
111
|
upsertStmtsByDb.set(db, stmts);
|
|
141
112
|
return stmts;
|
|
@@ -153,25 +124,18 @@ function getUpsertStmts(db) {
|
|
|
153
124
|
export function getDerivedForParent(db, parentRef, stashDir) {
|
|
154
125
|
if (!parentRef)
|
|
155
126
|
return null;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.prepare(`SELECT ${ENTRY_COLUMNS}
|
|
127
|
+
const sourceScope = stashDir ? "AND stash_dir = ?" : "";
|
|
128
|
+
const row = db
|
|
129
|
+
.prepare(`SELECT ${ENTRY_COLUMNS}
|
|
160
130
|
FROM entries
|
|
161
131
|
WHERE derived_from = ?
|
|
162
132
|
${sourceScope}
|
|
163
133
|
ORDER BY id DESC
|
|
164
134
|
LIMIT 1`)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return null;
|
|
168
|
-
return rowToIndexedEntry(row, "getDerivedForParent");
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
/* `derived_from` column may not exist on legacy DBs that haven't been
|
|
172
|
-
rebuilt; treat as "no derived child". */
|
|
135
|
+
.get(parentRef, ...(stashDir ? [stashDir] : []));
|
|
136
|
+
if (!row)
|
|
173
137
|
return null;
|
|
174
|
-
|
|
138
|
+
return rowToIndexedEntry(row, "getDerivedForParent");
|
|
175
139
|
}
|
|
176
140
|
/**
|
|
177
141
|
* 03-R3: for the given derived-twin row ids, fetch each twin's BASE memory
|
|
@@ -198,33 +162,21 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
198
162
|
const chunk = twinIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
199
163
|
const placeholders = chunk.map(() => "?").join(",");
|
|
200
164
|
bestEffort(() => {
|
|
201
|
-
// Chunk-5 flip F1: a twin's identity suffix is `.derived` on BOTH the
|
|
202
|
-
// legacy `entry_key` (`<stash>:memory:<name>.derived`) and the new
|
|
203
|
-
// `item_ref` (`<bundle>//memories/<name>.derived`). Join the base by
|
|
204
|
-
// stripping that suffix on EITHER column so both fully-migrated rows
|
|
205
|
-
// (item_ref path) and NULL-`item_ref` rows (legacy path) inherit. On a
|
|
206
|
-
// consistent index both predicates select the SAME base row, so the OR
|
|
207
|
-
// never double-counts; `substr(NULL, …)` is NULL and matches nothing, so
|
|
208
|
-
// a NULL `item_ref` simply falls through to the entry_key predicate.
|
|
209
|
-
// F5: the `entry_key` arm is the deletable legacy shim.
|
|
210
165
|
const rows = db
|
|
211
166
|
.prepare(`SELECT twin.id AS twin_id, json_extract(base.entry_json, '$.beliefState') AS belief
|
|
212
167
|
FROM entries twin
|
|
213
168
|
JOIN entries base
|
|
214
169
|
ON base.entry_type = 'memory'
|
|
215
|
-
|
|
216
|
-
base.entry_key = substr(twin.entry_key, 1, length(twin.entry_key) - length('.derived'))
|
|
217
|
-
OR base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
|
|
218
|
-
)
|
|
170
|
+
AND base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
|
|
219
171
|
WHERE twin.id IN (${placeholders})
|
|
220
|
-
AND
|
|
172
|
+
AND twin.item_ref LIKE '%.derived'
|
|
221
173
|
AND json_extract(base.entry_json, '$.beliefState') IS NOT NULL`)
|
|
222
174
|
.all(...chunk);
|
|
223
175
|
for (const r of rows) {
|
|
224
176
|
if (typeof r.belief === "string" && r.belief.trim().length > 0)
|
|
225
177
|
out.set(r.twin_id, r.belief.trim());
|
|
226
178
|
}
|
|
227
|
-
}, "
|
|
179
|
+
}, "belief-state inheritance is best-effort");
|
|
228
180
|
}
|
|
229
181
|
return out;
|
|
230
182
|
}
|
|
@@ -242,10 +194,8 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
242
194
|
* name; the row is marked FTS-dirty for the caller's
|
|
243
195
|
* `rebuildFts({incremental: true})`.
|
|
244
196
|
*
|
|
245
|
-
* `usage_events.entry_ref` rows for the old
|
|
246
|
-
*
|
|
247
|
-
* origin-qualified `origin//type:name` spelling (search/show writers persist
|
|
248
|
-
* either, see {@link getRetrievalCounts}). Without this, events keep the old
|
|
197
|
+
* Bundle-qualified `usage_events.entry_ref` rows for the old conceptId are
|
|
198
|
+
* rewritten to the new item ref. Without this, events keep the old
|
|
249
199
|
* ref, `relinkUsageEvents` finds no matching entry after the next full
|
|
250
200
|
* rebuild, and the utility history the re-key exists to preserve silently
|
|
251
201
|
* resets. DETACHED orphan events already sitting at the new ref (entry_id
|
|
@@ -266,14 +216,13 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
|
266
216
|
* full `akm index` picks the file up as a fresh entry).
|
|
267
217
|
*/
|
|
268
218
|
export function rekeyEntryInPlace(db, opts) {
|
|
269
|
-
|
|
270
|
-
// always has it and it is the UNIQUE identity column until the re-key.
|
|
219
|
+
const oldItemRef = `${opts.sourceName}//${opts.oldRef}`;
|
|
271
220
|
const row = db
|
|
272
|
-
.prepare("SELECT id, stash_dir, entry_json, search_text, entry_type
|
|
273
|
-
.get(
|
|
221
|
+
.prepare("SELECT id, stash_dir, entry_json, search_text, entry_type FROM entries WHERE item_ref = ?")
|
|
222
|
+
.get(oldItemRef);
|
|
274
223
|
if (!row)
|
|
275
224
|
return null;
|
|
276
|
-
if (
|
|
225
|
+
if (path.resolve(row.stash_dir) !== path.resolve(opts.sourceRoot)) {
|
|
277
226
|
throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
|
|
278
227
|
}
|
|
279
228
|
// Patch the JSON payload. On corrupt entry_json still re-key key + paths so
|
|
@@ -293,19 +242,11 @@ export function rekeyEntryInPlace(db, opts) {
|
|
|
293
242
|
catch {
|
|
294
243
|
/* corrupt entry_json — key/path-only re-key */
|
|
295
244
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
// crosses bundles); a NULL item_ref (write-back row) stays NULL and is healed
|
|
300
|
-
// on the next full index, exactly like the legacy columns before the flip.
|
|
301
|
-
let newItemRef = null;
|
|
302
|
-
let newConceptId = null;
|
|
303
|
-
if (row.item_ref) {
|
|
304
|
-
const boundary = row.item_ref.indexOf("//");
|
|
305
|
-
const bundle = boundary >= 0 ? row.item_ref.slice(0, boundary) : undefined;
|
|
306
|
-
newConceptId = conceptIdFromTypeName(row.entry_type, opts.newName);
|
|
307
|
-
newItemRef = bundle !== undefined ? `${bundle}//${newConceptId}` : newConceptId;
|
|
245
|
+
const expectedNewRef = conceptIdFromTypeName(row.entry_type, opts.newName);
|
|
246
|
+
if (opts.newRef !== expectedNewRef) {
|
|
247
|
+
throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: target ref does not match the entry type and name.`);
|
|
308
248
|
}
|
|
249
|
+
const newItemRef = `${opts.sourceName}//${opts.newRef}`;
|
|
309
250
|
db.transaction(() => {
|
|
310
251
|
const stale = db.prepare("SELECT id FROM entries WHERE entry_key = ?").get(opts.newEntryKey);
|
|
311
252
|
if (stale && stale.id !== row.id) {
|
|
@@ -317,13 +258,10 @@ export function rekeyEntryInPlace(db, opts) {
|
|
|
317
258
|
deleteRelatedRows(db, [{ id: stale.id }]);
|
|
318
259
|
db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
|
|
319
260
|
}
|
|
320
|
-
db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ? WHERE id = ?").run(opts.newEntryKey, path.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, row.id);
|
|
261
|
+
db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ?, item_ref = ?, concept_id = ? WHERE id = ?").run(opts.newEntryKey, path.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, newItemRef, opts.newRef, row.id);
|
|
321
262
|
if (opts.newDerivedFrom !== undefined) {
|
|
322
263
|
db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
|
|
323
264
|
}
|
|
324
|
-
if (newItemRef !== null) {
|
|
325
|
-
db.prepare("UPDATE entries SET item_ref = ?, concept_id = ? WHERE id = ?").run(newItemRef, newConceptId, row.id);
|
|
326
|
-
}
|
|
327
265
|
db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
|
|
328
266
|
})();
|
|
329
267
|
// Re-point usage history at the new ref. Chunk-8 WI-8.3: usage_events lives in
|
|
@@ -336,28 +274,19 @@ export function rekeyEntryInPlace(db, opts) {
|
|
|
336
274
|
}
|
|
337
275
|
/**
|
|
338
276
|
* Rewrite `usage_events.entry_ref` from `opts.oldRef` to `opts.newRef` in
|
|
339
|
-
* state.db (
|
|
340
|
-
* spellings). DETACHED orphan events (entry_id NULL) already sitting AT the new
|
|
277
|
+
* state.db. DETACHED orphan events (entry_id NULL) already sitting AT the new
|
|
341
278
|
* ref are evicted first so the moved asset never adopts a deleted stranger's
|
|
342
|
-
* history (live-asset-wins). Best-effort + guarded on state.db's existence
|
|
343
|
-
* legacy state.db predating usage_events is tolerated.
|
|
279
|
+
* history (live-asset-wins). Best-effort + guarded on state.db's existence.
|
|
344
280
|
*/
|
|
345
|
-
/** The conceptId for a legacy `type:name` ref (`memory:foo` → `memories/foo`), or undefined. */
|
|
346
|
-
function conceptIdFromTypeNameRef(ref) {
|
|
347
|
-
const colon = ref.indexOf(":");
|
|
348
|
-
if (colon <= 0)
|
|
349
|
-
return undefined;
|
|
350
|
-
return conceptIdFromTypeName(ref.slice(0, colon), ref.slice(colon + 1));
|
|
351
|
-
}
|
|
352
281
|
function rewriteUsageEventRefForMove(opts) {
|
|
353
|
-
|
|
282
|
+
// Every other failure in here throws (see the catch below) precisely because
|
|
283
|
+
// a move that quietly drops its usage history is a wrong answer wearing a
|
|
284
|
+
// success. An unreadable state.db must not be the one silent exception —
|
|
285
|
+
// only a state.db that was never created skips (#791).
|
|
286
|
+
if (isPathAbsent(getStateDbPath()))
|
|
354
287
|
return;
|
|
355
|
-
//
|
|
356
|
-
// (`<bundle>//<conceptId>`)
|
|
357
|
-
// only on rows written before the cutover. Rewrite BOTH: `opts.oldRef`/`newRef`
|
|
358
|
-
// are the legacy `type:name` forms, so the conceptId siblings are derived here.
|
|
359
|
-
const oldConcept = conceptIdFromTypeNameRef(opts.oldRef);
|
|
360
|
-
const newConcept = conceptIdFromTypeNameRef(opts.newRef);
|
|
288
|
+
// `usage_events.entry_ref` is the fully-qualified item_ref
|
|
289
|
+
// (`<bundle>//<conceptId>`).
|
|
361
290
|
const rename = (stateDb, oldR, newR) => {
|
|
362
291
|
stateDb.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newR);
|
|
363
292
|
stateDb.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newR, oldR);
|
|
@@ -365,33 +294,13 @@ function rewriteUsageEventRefForMove(opts) {
|
|
|
365
294
|
try {
|
|
366
295
|
withStateDb((stateDb) => {
|
|
367
296
|
stateDb.transaction(() => {
|
|
368
|
-
|
|
369
|
-
const origins = new Set([opts.sourceName]);
|
|
370
|
-
if (opts.sourceName === "stash" && opts.includeLegacyBare)
|
|
371
|
-
origins.add("local");
|
|
372
|
-
for (const origin of origins) {
|
|
373
|
-
rename(stateDb, `${origin}//${opts.oldRef}`, `${origin}//${opts.newRef}`);
|
|
374
|
-
// The item_ref spelling (`<bundle>//<conceptId>`) — the durable
|
|
375
|
-
// post-cutover key. Origins double as the bundle id for the stash.
|
|
376
|
-
if (oldConcept !== undefined && newConcept !== undefined)
|
|
377
|
-
rename(stateDb, `${origin}//${oldConcept}`, `${origin}//${newConcept}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (opts.includeLegacyBare || !opts.sourceName) {
|
|
381
|
-
rename(stateDb, opts.oldRef, opts.newRef);
|
|
382
|
-
if (oldConcept !== undefined && newConcept !== undefined)
|
|
383
|
-
rename(stateDb, oldConcept, newConcept);
|
|
384
|
-
}
|
|
297
|
+
rename(stateDb, `${opts.sourceName}//${opts.oldRef}`, `${opts.sourceName}//${opts.newRef}`);
|
|
385
298
|
})();
|
|
386
299
|
});
|
|
387
300
|
}
|
|
388
301
|
catch (error) {
|
|
389
302
|
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
-
|
|
391
|
-
/no such column:\s*(?:usage_events\.)?(?:entry_id|entry_ref)\b/i.test(message) ||
|
|
392
|
-
/table\s+usage_events\s+has no column named\s+(?:entry_id|entry_ref)\b/i.test(message);
|
|
393
|
-
if (!missingLegacyUsageSchema)
|
|
394
|
-
throw error;
|
|
303
|
+
throw new Error(`Failed to rewrite usage events for move: ${message}`, { cause: error });
|
|
395
304
|
}
|
|
396
305
|
}
|
|
397
306
|
/**
|
|
@@ -435,7 +344,7 @@ export function getPositiveFeedbackCountsByIds(ids) {
|
|
|
435
344
|
}
|
|
436
345
|
}
|
|
437
346
|
});
|
|
438
|
-
}, "
|
|
347
|
+
}, "positive feedback counts are best-effort");
|
|
439
348
|
return result;
|
|
440
349
|
}
|
|
441
350
|
function deleteEntriesWhere(db, column, value) {
|
|
@@ -448,6 +357,16 @@ function deleteEntriesWhere(db, column, value) {
|
|
|
448
357
|
export function deleteEntriesByDir(db, dirPath) {
|
|
449
358
|
deleteEntriesWhere(db, "dir_path", dirPath);
|
|
450
359
|
}
|
|
360
|
+
export function deleteEntriesByDirAndStash(db, dirPath, stashDir, options = {}) {
|
|
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
|
+
})();
|
|
369
|
+
}
|
|
451
370
|
/**
|
|
452
371
|
* Delete every entry (and its child rows) under a source's stash root.
|
|
453
372
|
*
|
|
@@ -471,28 +390,28 @@ export function deleteEntriesByStashDir(db, stashDir) {
|
|
|
471
390
|
* to prune only the DEPARTED rows. The net row-state of `dir_path` is identical
|
|
472
391
|
* to delete-then-reinsert; the win is that unchanged rows keep their id.
|
|
473
392
|
*
|
|
474
|
-
* Keyed on `entry_key`
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
* conflict target precisely. Dir-scoped (not stash-dir-scoped) so it is safe
|
|
478
|
-
* under the per-dir incremental freshness gate — untouched (skipped) sibling
|
|
479
|
-
* directories are never in scope.
|
|
393
|
+
* Keyed on `entry_key` because it mirrors the directory upsert key precisely.
|
|
394
|
+
* Both directory- and source-scoped so overlapping source roots cannot prune
|
|
395
|
+
* one another's rows.
|
|
480
396
|
*/
|
|
481
|
-
export function deleteEntriesByDirExceptKeys(db, dirPath, keepKeys) {
|
|
482
|
-
db.transaction(() => {
|
|
483
|
-
const rows = db
|
|
397
|
+
export function deleteEntriesByDirExceptKeys(db, dirPath, stashDir, keepKeys, options = {}) {
|
|
398
|
+
return db.transaction(() => {
|
|
399
|
+
const rows = db
|
|
400
|
+
.prepare("SELECT id, entry_key FROM entries WHERE dir_path = ? AND stash_dir = ?")
|
|
401
|
+
.all(dirPath, stashDir);
|
|
484
402
|
const doomed = rows.filter((r) => !keepKeys.has(r.entry_key));
|
|
485
403
|
if (doomed.length === 0)
|
|
486
|
-
return;
|
|
487
|
-
deleteRelatedRows(db, doomed);
|
|
404
|
+
return [];
|
|
405
|
+
deleteRelatedRows(db, doomed, options);
|
|
488
406
|
for (let i = 0; i < doomed.length; i += SQLITE_CHUNK_SIZE) {
|
|
489
407
|
const chunk = doomed.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
490
408
|
const placeholders = chunk.map(() => "?").join(",");
|
|
491
409
|
db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk.map((r) => r.id));
|
|
492
410
|
}
|
|
411
|
+
return doomed.map((row) => row.id);
|
|
493
412
|
})();
|
|
494
413
|
}
|
|
495
|
-
function deleteRelatedRows(db, ids) {
|
|
414
|
+
function deleteRelatedRows(db, ids, options = {}) {
|
|
496
415
|
if (ids.length === 0)
|
|
497
416
|
return;
|
|
498
417
|
const numericIds = ids.map((r) => r.id);
|
|
@@ -518,22 +437,11 @@ function deleteRelatedRows(db, ids) {
|
|
|
518
437
|
bestEffort(() => db.prepare(`DELETE FROM utility_scores WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores for entries");
|
|
519
438
|
bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
|
|
520
439
|
}
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (fs.existsSync(getStateDbPath())) {
|
|
527
|
-
bestEffort(() => {
|
|
528
|
-
withStateDb((stateDb) => {
|
|
529
|
-
for (let i = 0; i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
530
|
-
const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
531
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
532
|
-
stateDb.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk);
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
}, "delete usage_events (state.db) for entries");
|
|
536
|
-
}
|
|
440
|
+
// usage_events lives in state.db, outside this transaction. Index persistence
|
|
441
|
+
// disables this cleanup and runs it only after its index.db transaction
|
|
442
|
+
// commits; standalone delete callers retain the immediate behavior.
|
|
443
|
+
if (options.cleanupUsageEvents !== false)
|
|
444
|
+
deleteUsageEventsByEntryIds(numericIds);
|
|
537
445
|
// #624-P1: graph_files is NO LONGER keyed on entries.id, so deleting an
|
|
538
446
|
// entries row must NOT wipe the extracted graph (that is the whole point —
|
|
539
447
|
// the graph survives a reindex when body_hash is unchanged). We therefore do
|
|
@@ -567,6 +475,19 @@ function deleteRelatedRows(db, ids) {
|
|
|
567
475
|
.run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
|
|
568
476
|
}
|
|
569
477
|
}
|
|
478
|
+
export function deleteUsageEventsByEntryIds(entryIds) {
|
|
479
|
+
if (entryIds.length === 0 || !fs.existsSync(getStateDbPath()))
|
|
480
|
+
return;
|
|
481
|
+
bestEffort(() => {
|
|
482
|
+
withStateDb((stateDb) => {
|
|
483
|
+
for (let i = 0; i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
484
|
+
const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
485
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
486
|
+
stateDb.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}, "delete usage_events (state.db) for entries");
|
|
490
|
+
}
|
|
570
491
|
/**
|
|
571
492
|
* Delete entries by their primary key IDs, along with all related rows
|
|
572
493
|
* (embeddings, entries_vec, entries_fts, utility_scores, usage_events).
|
|
@@ -621,7 +542,7 @@ export function getAllEntries(db, entryType, excludeTypes) {
|
|
|
621
542
|
/**
|
|
622
543
|
* Resolve a single `entries.id` from a new-grammar `[bundle//]conceptId` ref,
|
|
623
544
|
* keying on the canonical stored `item_ref` (ref-grammar decision D-R1/D-R4).
|
|
624
|
-
*
|
|
545
|
+
* The optional `stashDir` scopes the
|
|
625
546
|
* match to one source root.
|
|
626
547
|
*/
|
|
627
548
|
export function findEntryIdByRef(db, ref, stashDir) {
|
|
@@ -632,9 +553,8 @@ function withMdVariants(name) {
|
|
|
632
553
|
return name.endsWith(".md") ? [name, name.slice(0, -3)] : [name, `${name}.md`];
|
|
633
554
|
}
|
|
634
555
|
/**
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
* legacy `entry_key` predicate for NULL-`item_ref` rows.
|
|
556
|
+
* Current (`[bundle//]conceptId`) id lookup: match `item_ref` exactly when
|
|
557
|
+
* bundle-qualified or by `//conceptId` suffix when short.
|
|
638
558
|
*/
|
|
639
559
|
function findEntryIdByBundleRef(db, ref, stashDir) {
|
|
640
560
|
const parsed = parseBundleRef(ref);
|
|
@@ -669,9 +589,7 @@ function findEntryIdByBundleRef(db, ref, stashDir) {
|
|
|
669
589
|
* precedence-ordered scan would pick — while staying a pure config-free leaf
|
|
670
590
|
* (true installation-priority resolution against an injected bundle list is
|
|
671
591
|
* `resolveRef`'s job; this DB helper takes no config handle). The exact-bundle
|
|
672
|
-
* arm is single-row under the UNIQUE `item_ref` index
|
|
673
|
-
* keeps it deterministic on a partially-migrated DB whose fallback index is
|
|
674
|
-
* non-unique.
|
|
592
|
+
* arm is single-row under the UNIQUE `item_ref` index.
|
|
675
593
|
*/
|
|
676
594
|
function matchIdByItemRef(db, bundle, conceptId, stashDir) {
|
|
677
595
|
const scope = stashDir ? "AND stash_dir = ?" : "";
|
|
@@ -697,11 +615,29 @@ export function getEntryCount(db) {
|
|
|
697
615
|
const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries").get();
|
|
698
616
|
return row.cnt;
|
|
699
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Per-asset-type entry counts (keyed by `entry_type`, e.g. "skill",
|
|
620
|
+
* "knowledge", "memory"). Used by `akm info` to break down the aggregate
|
|
621
|
+
* `indexStats.entryCount` (R-057). Rows with a null/empty `entry_type` are
|
|
622
|
+
* omitted rather than surfaced under a synthetic key.
|
|
623
|
+
*/
|
|
624
|
+
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();
|
|
628
|
+
const out = {};
|
|
629
|
+
for (const row of rows) {
|
|
630
|
+
out[row.type] = row.cnt;
|
|
631
|
+
}
|
|
632
|
+
return out;
|
|
633
|
+
}
|
|
700
634
|
export function getEmbeddableEntryCount(db) {
|
|
701
635
|
return getEntryCount(db);
|
|
702
636
|
}
|
|
703
637
|
export function getEntryById(db, id) {
|
|
704
|
-
const row = db
|
|
638
|
+
const row = db
|
|
639
|
+
.prepare("SELECT file_path, stash_dir, entry_json, item_ref, bundle_id, concept_id FROM entries WHERE id = ?")
|
|
640
|
+
.get(id);
|
|
705
641
|
if (!row)
|
|
706
642
|
return undefined;
|
|
707
643
|
// Guard against corrupt JSON
|
|
@@ -713,12 +649,29 @@ export function getEntryById(db, id) {
|
|
|
713
649
|
warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
|
|
714
650
|
return undefined;
|
|
715
651
|
}
|
|
716
|
-
return {
|
|
652
|
+
return {
|
|
653
|
+
filePath: row.file_path,
|
|
654
|
+
stashDir: row.stash_dir,
|
|
655
|
+
entry,
|
|
656
|
+
itemRef: row.item_ref,
|
|
657
|
+
bundleId: row.bundle_id,
|
|
658
|
+
conceptId: row.concept_id,
|
|
659
|
+
};
|
|
717
660
|
}
|
|
718
661
|
export function getEntriesByDir(db, dirPath) {
|
|
719
662
|
const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
|
|
720
663
|
return parseEntryRows(rows, "getEntriesByDir");
|
|
721
664
|
}
|
|
665
|
+
/** Return every directory previously indexed for one physical source root. */
|
|
666
|
+
export function getIndexedDirPathsByStashDir(db, stashDir) {
|
|
667
|
+
const rows = db.prepare("SELECT DISTINCT dir_path FROM entries WHERE stash_dir = ?").all(stashDir);
|
|
668
|
+
return rows.map((row) => row.dir_path);
|
|
669
|
+
}
|
|
670
|
+
/** Return every persisted source owner for one physical directory. */
|
|
671
|
+
export function getIndexedStashDirsByDir(db, dirPath) {
|
|
672
|
+
const rows = db.prepare("SELECT DISTINCT stash_dir FROM entries WHERE dir_path = ?").all(dirPath);
|
|
673
|
+
return rows.map((row) => row.stash_dir);
|
|
674
|
+
}
|
|
722
675
|
/**
|
|
723
676
|
* Resolve a single `entries.id` by exact `file_path` (the canonical on-disk
|
|
724
677
|
* path), or `undefined` if no row matches.
|
|
@@ -848,14 +801,15 @@ export function getItemRefById(db, id) {
|
|
|
848
801
|
return row?.item_ref ?? null;
|
|
849
802
|
}
|
|
850
803
|
/**
|
|
851
|
-
* Resolve a `usage_events.entry_ref` to its live `entries.id`.
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* short conceptId (no bundle) falls back to the default-stash scope.
|
|
804
|
+
* Resolve a `usage_events.entry_ref` to its live `entries.id`. `entry_ref` is
|
|
805
|
+
* the fully-qualified `bundle//conceptId` `item_ref` spelling, so this keys
|
|
806
|
+
* directly on the globally-unique `item_ref`. Bare durable refs are invalid and
|
|
807
|
+
* remain detached.
|
|
856
808
|
*/
|
|
857
|
-
function resolveUsageEventEntryId(db, ref
|
|
858
|
-
|
|
809
|
+
function resolveUsageEventEntryId(db, ref) {
|
|
810
|
+
if (parseBundleRef(ref).bundle === undefined)
|
|
811
|
+
return undefined;
|
|
812
|
+
return findEntryIdByRef(db, ref);
|
|
859
813
|
}
|
|
860
814
|
/**
|
|
861
815
|
* Re-link detached usage_events to their current entry_ids via entry_ref.
|
|
@@ -869,7 +823,7 @@ function resolveUsageEventEntryId(db, ref, options) {
|
|
|
869
823
|
* distinct linked entry_ids in usage_events is small — and the re-resolution
|
|
870
824
|
* reads `entries` from `indexDb`.
|
|
871
825
|
*/
|
|
872
|
-
export function relinkUsageEvents(indexDb, stateDb,
|
|
826
|
+
export function relinkUsageEvents(indexDb, stateDb, _options = {}) {
|
|
873
827
|
bestEffort(() => {
|
|
874
828
|
// Step 1: null out stale entry_ids (entry was deleted, re-keyed, etc).
|
|
875
829
|
// Leaving them in place would let `recomputeUtilityScores` aggregate by an
|
|
@@ -888,10 +842,8 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
|
|
|
888
842
|
});
|
|
889
843
|
nullTx();
|
|
890
844
|
}
|
|
891
|
-
// Step 2: re-resolve each
|
|
892
|
-
//
|
|
893
|
-
// historical/default root. This keeps duplicate refs from adopting whichever
|
|
894
|
-
// entries row SQLite happens to return first while retaining indexed lookups.
|
|
845
|
+
// Step 2: re-resolve each fully-qualified ref. Bare rows are not current
|
|
846
|
+
// durable identities and remain detached.
|
|
895
847
|
const refs = stateDb
|
|
896
848
|
.prepare("SELECT DISTINCT entry_ref AS ref FROM usage_events WHERE entry_id IS NULL AND entry_ref IS NOT NULL")
|
|
897
849
|
.all();
|
|
@@ -900,7 +852,7 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
|
|
|
900
852
|
for (const { ref } of refs) {
|
|
901
853
|
let id;
|
|
902
854
|
try {
|
|
903
|
-
id = resolveUsageEventEntryId(indexDb, ref
|
|
855
|
+
id = resolveUsageEventEntryId(indexDb, ref);
|
|
904
856
|
}
|
|
905
857
|
catch (err) {
|
|
906
858
|
if (err instanceof Error && err.name === "UsageError")
|
|
@@ -914,39 +866,3 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
|
|
|
914
866
|
relinkTx();
|
|
915
867
|
}, "usage_events table may not exist yet during entry_id re-resolution");
|
|
916
868
|
}
|
|
917
|
-
/**
|
|
918
|
-
* Walk indexed entries and collect a deduplicated set of tags. When
|
|
919
|
-
* `entryType` is provided, only entries of that type contribute tags.
|
|
920
|
-
*
|
|
921
|
-
* Pure read; never mutates the DB. Used by `akm lessons coverage` (Phase 7A)
|
|
922
|
-
* to compute the diff between all-asset tags and lesson tags. Tags are
|
|
923
|
-
* normalised by trimming and lower-casing, and blank tags are dropped.
|
|
924
|
-
*
|
|
925
|
-
* SQL owner: this module owns ALL raw SQL against the `entries` table (WS5),
|
|
926
|
-
* so the `lessons coverage` read lives here rather than leaking into cli.ts.
|
|
927
|
-
* The result set is fully materialised (`.all()` then iterate) before return.
|
|
928
|
-
*/
|
|
929
|
-
export function collectTagSetFromEntries(db, entryType) {
|
|
930
|
-
const tags = new Set();
|
|
931
|
-
const stmt = entryType
|
|
932
|
-
? db.prepare("SELECT entry_json FROM entries WHERE entry_type = ?")
|
|
933
|
-
: db.prepare("SELECT entry_json FROM entries");
|
|
934
|
-
const rows = (entryType ? stmt.all(entryType) : stmt.all());
|
|
935
|
-
for (const row of rows) {
|
|
936
|
-
let parsed;
|
|
937
|
-
try {
|
|
938
|
-
parsed = JSON.parse(row.entry_json);
|
|
939
|
-
}
|
|
940
|
-
catch {
|
|
941
|
-
continue;
|
|
942
|
-
}
|
|
943
|
-
if (!Array.isArray(parsed.tags))
|
|
944
|
-
continue;
|
|
945
|
-
for (const tag of parsed.tags) {
|
|
946
|
-
if (typeof tag === "string" && tag.trim().length > 0) {
|
|
947
|
-
tags.add(tag.trim().toLowerCase());
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
return tags;
|
|
952
|
-
}
|