akm-cli 0.9.0-rc.1 → 0.9.0-rc.13
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 +1190 -52
- package/README.md +62 -37
- package/SECURITY.md +46 -31
- package/dist/akm +162 -38
- package/dist/akm-migrate +44 -0
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/hints/cli-hints-full.md +268 -118
- package/dist/assets/hints/cli-hints-short.md +87 -24
- package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
- package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
- package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
- package/dist/assets/improve-strategies/frequent.json +15 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
- package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
- package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
- package/dist/assets/improve-strategies/reflect-distill.json +30 -0
- package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +5 -5
- package/dist/assets/prompts/extract-session.md +2 -6
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
- package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
- package/dist/assets/prompts/reflect-output-repair.md +3 -0
- package/dist/assets/stash-skeleton/README.md +38 -10
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
- package/dist/assets/tasks/core/extract.yml +3 -2
- package/dist/assets/tasks/core/improve.yml +2 -1
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +2 -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 +5 -4
- package/dist/assets/workflows/workflow-template.md +31 -15
- package/dist/cli/invocation.js +279 -0
- package/dist/cli/parse-args.js +5 -90
- package/dist/cli/retired-commands.js +78 -0
- package/dist/cli/shared.js +158 -48
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +747 -293
- package/dist/commands/agent/agent-dispatch.js +19 -18
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +43 -97
- package/dist/commands/completions.js +80 -23
- package/dist/commands/config-cli.js +44 -281
- package/dist/commands/env/env-binding.js +13 -9
- package/dist/commands/env/env-cli.js +76 -159
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +45 -61
- package/dist/commands/env/secret.js +32 -62
- package/dist/commands/feedback-cli.js +179 -85
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +7 -8
- package/dist/commands/health/checks.js +279 -94
- package/dist/commands/health/html-report.js +197 -578
- package/dist/commands/health/improve-metrics.js +277 -246
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +16 -7
- package/dist/commands/health/metrics.js +67 -32
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +508 -0
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +16 -56
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
- package/dist/commands/health/types-improve.js +29 -0
- package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
- package/dist/commands/health/types-result.js +7 -0
- package/dist/commands/health/types-runs.js +4 -0
- package/dist/commands/health/types-session-log.js +4 -0
- package/dist/commands/health/types-windows.js +4 -0
- package/dist/commands/health/types.js +26 -21
- package/dist/commands/health/windows.js +2 -3
- package/dist/commands/health.js +296 -167
- package/dist/commands/improve/anti-collapse.js +5 -5
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +65 -52
- package/dist/commands/improve/consolidate/chunking.js +9 -7
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +454 -1354
- package/dist/commands/improve/content-hash.js +39 -0
- package/dist/commands/improve/distill/content-repair.js +4 -10
- package/dist/commands/improve/distill/promote-memory.js +89 -64
- package/dist/commands/improve/distill/quality-gate.js +118 -42
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +33 -888
- package/dist/commands/improve/distill.js +607 -363
- package/dist/commands/improve/eligibility.js +165 -79
- package/dist/commands/improve/extract-cli.js +35 -126
- package/dist/commands/improve/extract-prompt.js +6 -35
- package/dist/commands/improve/extract.js +640 -391
- package/dist/commands/improve/feedback-valence.js +2 -12
- package/dist/commands/improve/improve-cli.js +134 -135
- package/dist/commands/improve/improve-result-file.js +30 -50
- package/dist/commands/improve/improve-run-types.js +4 -0
- package/dist/commands/improve/improve-strategies.js +135 -0
- package/dist/commands/improve/improve.js +904 -701
- package/dist/commands/improve/locks.js +64 -111
- package/dist/commands/improve/loop-stages.js +1110 -923
- package/dist/commands/improve/memory/derived-ref.js +124 -0
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
- package/dist/commands/improve/memory/memory-improve.js +25 -37
- package/dist/commands/improve/outcome-loop.js +25 -88
- package/dist/commands/improve/preparation.js +1034 -813
- package/dist/commands/improve/proactive-maintenance.js +34 -9
- package/dist/commands/improve/proposal-envelope.js +31 -0
- package/dist/commands/improve/reflect.js +983 -794
- package/dist/commands/improve/run-context.js +119 -0
- package/dist/commands/improve/salience.js +24 -127
- package/dist/commands/improve/session-asset.js +7 -3
- package/dist/commands/improve/shared.js +14 -34
- package/dist/commands/improve/source-identity.js +28 -0
- package/dist/commands/improve/triage.js +20 -17
- package/dist/commands/lint/base-linter.js +340 -313
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +185 -30
- package/dist/commands/{events.js → log.js} +28 -38
- package/dist/commands/migrate-cli.js +54 -0
- package/dist/commands/migration-tool.js +55 -0
- package/dist/commands/observability-cli.js +70 -208
- package/dist/commands/proposal/diff-format.js +50 -0
- package/dist/commands/proposal/drain-policies.js +0 -6
- package/dist/commands/proposal/drain.js +91 -40
- package/dist/commands/proposal/proposal-cli.js +134 -132
- package/dist/commands/proposal/proposal-types.js +56 -0
- package/dist/commands/proposal/proposal.js +83 -65
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +105 -88
- package/dist/commands/proposal/repository.js +1303 -278
- package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
- package/dist/commands/proposal/validators/proposal-validators.js +61 -12
- package/dist/commands/proposal/validators/proposals.js +6 -8
- package/dist/commands/read/curate.js +78 -73
- package/dist/commands/read/knowledge.js +510 -13
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +84 -15
- package/dist/commands/read/search-cli.js +203 -96
- package/dist/commands/read/search.js +126 -94
- package/dist/commands/read/show.js +226 -250
- package/dist/commands/registry-cli.js +34 -60
- package/dist/commands/remember.js +18 -57
- package/dist/commands/sources/add-cli.js +104 -49
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +63 -0
- package/dist/commands/sources/info.js +27 -15
- package/dist/commands/sources/init.js +30 -40
- package/dist/commands/sources/installed-stashes.js +469 -172
- package/dist/commands/sources/schema-repair.js +10 -9
- package/dist/commands/sources/self-update.js +182 -121
- package/dist/commands/sources/source-add.js +169 -178
- package/dist/commands/sources/source-clone.js +144 -41
- package/dist/commands/sources/source-manage.js +94 -59
- package/dist/commands/sources/sources-cli.js +64 -205
- package/dist/commands/sources/stash-cli.js +91 -54
- package/dist/commands/sources/stash-skeleton.js +1 -1
- package/dist/commands/tasks/tasks-cli.js +106 -104
- package/dist/commands/tasks/tasks.js +445 -262
- package/dist/commands/workflow-cli.js +75 -228
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/activation-policy.js +49 -0
- package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
- package/dist/core/adapter/adapters/akm-adapter.js +528 -0
- package/dist/core/adapter/adapters/akm-lint.js +392 -0
- package/dist/core/adapter/adapters/akm-metadata.js +387 -0
- package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
- package/dist/core/adapter/adapters/claude-adapter.js +61 -0
- package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
- package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
- package/dist/core/adapter/adapters/index.js +80 -0
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
- package/dist/core/adapter/adapters/okf-adapter.js +391 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
- package/dist/core/adapter/adapters/shared.js +286 -0
- package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
- package/dist/core/adapter/bundle-adapter.js +4 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +44 -0
- package/dist/core/adapter/registry.js +56 -0
- package/dist/core/adapter/types.js +4 -0
- package/dist/core/asset/akm-markdown.js +30 -0
- package/dist/core/asset/asset-placement.js +243 -0
- package/dist/core/asset/asset-ref.js +110 -79
- package/dist/core/asset/asset-serialize.js +20 -0
- package/dist/core/asset/frontmatter.js +28 -12
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +274 -0
- package/dist/core/asset/stash-meta.js +2 -2
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +281 -86
- package/dist/core/config/config-io.js +42 -128
- package/dist/core/config/config-schema.js +233 -855
- package/dist/core/config/config-sources.js +162 -39
- package/dist/core/config/config-types.js +16 -11
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +126 -37
- package/dist/core/config/config.js +154 -331
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +28 -0
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +38 -0
- package/dist/core/config/schema/engines.js +116 -0
- package/dist/core/config/schema/experimental.js +47 -0
- package/dist/core/config/schema/feedback.js +31 -0
- package/dist/core/config/schema/improve-processes.js +389 -0
- package/dist/core/config/schema/improve.js +94 -0
- package/dist/core/config/schema/index-config.js +176 -0
- package/dist/core/config/schema/output.js +18 -0
- package/dist/core/config/schema/primitives.js +94 -0
- package/dist/core/config/schema/search.js +30 -0
- package/dist/core/config/schema/setup.js +18 -0
- package/dist/core/config/schema/sources-bundles.js +169 -0
- package/dist/core/config/schema/workflow.js +29 -0
- package/dist/core/env-secret-ref.js +155 -20
- package/dist/core/errors.js +17 -15
- package/dist/core/events-types.js +4 -0
- package/dist/core/events.js +46 -128
- package/dist/core/extra-params.js +62 -0
- package/dist/core/file-change.js +17 -0
- package/dist/core/file-lock.js +202 -57
- package/dist/core/fs-txn.js +392 -0
- package/dist/core/git-message.js +59 -0
- package/dist/core/improve-result.js +167 -0
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +1 -1
- package/dist/core/maintenance-barrier.js +135 -0
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/paths.js +22 -25
- package/dist/core/platform.js +10 -0
- package/dist/core/recognition-util.js +128 -0
- package/dist/core/redaction.js +392 -0
- package/dist/core/standards/resolve-standards-context.js +36 -65
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +5 -5
- package/dist/core/state/migrations.js +242 -11
- package/dist/core/state-db.js +98 -10
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +303 -0
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/time.js +20 -0
- package/dist/core/type-presentation.js +130 -0
- package/dist/core/warn.js +0 -3
- package/dist/core/write-source.js +834 -118
- package/dist/indexer/bundle-identity-guard.js +92 -0
- package/dist/indexer/db/graph-db.js +1 -25
- package/dist/indexer/db/llm-cache.js +1 -1
- package/dist/indexer/ensure-index.js +30 -9
- package/dist/indexer/graph/graph-boost.js +9 -30
- package/dist/indexer/graph/graph-extraction.js +41 -27
- package/dist/indexer/graph/graph-types.js +4 -0
- package/dist/indexer/index-writer-lock.js +93 -49
- package/dist/indexer/index-written-assets.js +100 -53
- package/dist/indexer/indexer.js +746 -329
- package/dist/indexer/init.js +18 -25
- package/dist/indexer/installations.js +142 -0
- package/dist/indexer/passes/dir-staleness.js +18 -10
- package/dist/indexer/passes/memory-inference.js +25 -15
- package/dist/indexer/passes/metadata.js +412 -243
- package/dist/indexer/scan/doc-to-entry.js +160 -0
- package/dist/indexer/scan/drain-dir.js +134 -0
- package/dist/indexer/search/db-search.js +292 -108
- package/dist/indexer/search/fts-query.js +64 -0
- package/dist/indexer/search/ranking-contributors.js +145 -25
- package/dist/indexer/search/ranking-types.js +4 -0
- package/dist/indexer/search/ranking.js +28 -71
- package/dist/indexer/search/search-attribution.js +67 -0
- package/dist/indexer/search/search-fields.js +18 -3
- package/dist/indexer/search/search-hit-enrichers.js +30 -40
- package/dist/indexer/search/search-source.js +157 -111
- package/dist/indexer/search/semantic-status.js +4 -1
- package/dist/indexer/usage/usage-events.js +10 -30
- package/dist/indexer/walk/file-context.js +3 -45
- package/dist/indexer/walk/matchers.js +42 -73
- package/dist/indexer/walk/path-resolver.js +11 -5
- package/dist/indexer/walk/walker.js +42 -14
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -58
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +231 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +8 -3
- package/dist/integrations/agent/profiles.js +6 -99
- package/dist/integrations/agent/prompts.js +46 -18
- package/dist/integrations/agent/runner-dispatch.js +78 -13
- package/dist/integrations/agent/runner.js +76 -208
- package/dist/integrations/agent/spawn.js +48 -279
- package/dist/integrations/harnesses/aider/agent-builder.js +9 -8
- package/dist/integrations/harnesses/aider/index.js +2 -12
- package/dist/integrations/harnesses/amazonq/agent-builder.js +10 -16
- package/dist/integrations/harnesses/amazonq/index.js +3 -17
- package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
- package/dist/integrations/harnesses/claude/config-import.js +1 -3
- package/dist/integrations/harnesses/claude/index.js +1 -14
- package/dist/integrations/harnesses/claude/session-log.js +27 -75
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -7
- package/dist/integrations/harnesses/codex/index.js +2 -13
- package/dist/integrations/harnesses/copilot/agent-builder.js +9 -9
- package/dist/integrations/harnesses/copilot/index.js +1 -13
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -9
- package/dist/integrations/harnesses/gemini/index.js +1 -13
- package/dist/integrations/harnesses/ids.js +24 -0
- package/dist/integrations/harnesses/index.js +31 -33
- package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
- package/dist/integrations/harnesses/opencode/config-import.js +1 -3
- package/dist/integrations/harnesses/opencode/index.js +1 -18
- package/dist/integrations/harnesses/opencode/session-log.js +67 -125
- package/dist/integrations/harnesses/opencode-sdk/harness.js +3 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
- package/dist/integrations/harnesses/openhands/agent-builder.js +12 -10
- package/dist/integrations/harnesses/openhands/index.js +2 -12
- package/dist/integrations/harnesses/pi/agent-builder.js +11 -18
- package/dist/integrations/harnesses/pi/index.js +3 -16
- package/dist/integrations/harnesses/shared.js +17 -0
- package/dist/integrations/harnesses/types.js +38 -34
- package/dist/integrations/lockfile.js +211 -24
- package/dist/integrations/session-logs/index.js +24 -40
- package/dist/integrations/session-logs/provider-base.js +113 -0
- package/dist/llm/client.js +182 -110
- package/dist/llm/embedders/deterministic.js +2 -2
- package/dist/llm/embedders/remote.js +21 -9
- package/dist/llm/feature-gate.js +17 -57
- package/dist/llm/graph-extract.js +12 -13
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +45 -30
- package/dist/llm/structured-call.js +16 -8
- package/dist/llm/usage-persist.js +30 -5
- package/dist/llm/usage-telemetry.js +59 -6
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/command-registry.js +27 -0
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +251 -0
- package/dist/output/html-render.js +11 -16
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +15 -281
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +58 -84
- package/dist/output/shapes/passthrough.js +8 -40
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes/registry.js +12 -6
- package/dist/output/shapes.js +0 -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 +562 -0
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/helpers.js +15 -1375
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +202 -0
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/registry.js +12 -6
- package/dist/output/text/show-directives.js +117 -0
- package/dist/output/text/show-format.js +103 -0
- package/dist/output/text/sync.js +5 -0
- package/dist/output/text/workflow-format.js +332 -0
- package/dist/output/text/workflow.js +1 -2
- package/dist/output/text.js +10 -19
- 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 +15 -25
- package/dist/registry/resolve.js +43 -94
- package/dist/registry/semver.js +43 -0
- package/dist/runtime.js +81 -12
- package/dist/scripts/akm-migrate.js +35529 -0
- package/dist/setup/detect.js +5 -7
- package/dist/setup/detected-engines.js +136 -0
- package/dist/setup/engine-config.js +100 -0
- package/dist/setup/registry-stash-loader.js +3 -3
- package/dist/setup/semantic-assets.js +12 -9
- package/dist/setup/setup.js +444 -208
- package/dist/setup/steps/connection-shared.js +120 -0
- package/dist/setup/steps/connection.js +108 -305
- package/dist/setup/steps/platforms.js +13 -12
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +21 -15
- package/dist/setup/steps/stashdir.js +6 -4
- package/dist/setup/steps/tasks.js +236 -119
- package/dist/setup/steps.js +3 -2
- package/dist/sources/freshness.js +39 -0
- 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 +54 -56
- package/dist/sources/providers/git-stash.js +420 -91
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +47 -22
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +11 -10
- package/dist/sources/snapshot-fetchers/types.js +4 -0
- package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
- package/dist/storage/database.js +60 -4
- package/dist/storage/engines/sqlite-migrations.js +156 -5
- package/dist/storage/locations.js +1 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +51 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -32
- package/dist/storage/repositories/index-connection.js +79 -0
- package/dist/storage/repositories/index-db.js +4 -3
- package/dist/storage/repositories/index-entries-repository.js +863 -0
- package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
- package/dist/storage/repositories/index-entry-types.js +4 -0
- package/dist/storage/repositories/index-fts-repository.js +167 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
- package/dist/storage/repositories/index-meta-repository.js +49 -0
- package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
- package/dist/storage/repositories/index-sql.js +12 -0
- package/dist/storage/repositories/index-utility-repository.js +356 -0
- package/dist/storage/repositories/index-vec-repository.js +250 -0
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +317 -75
- package/dist/storage/repositories/registry-cache.js +1 -1
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +110 -3
- package/dist/storage/repositories/workflow-runs-repository.js +68 -35
- package/dist/tasks/backends/cron.js +169 -46
- package/dist/tasks/backends/exec-utils.js +76 -3
- package/dist/tasks/backends/index.js +6 -9
- package/dist/tasks/backends/launchd.js +292 -55
- package/dist/tasks/backends/schtasks.js +557 -70
- package/dist/tasks/backends/types.js +4 -0
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +56 -38
- package/dist/tasks/parser.js +156 -64
- package/dist/tasks/resolve-akm-bin.js +144 -51
- package/dist/tasks/runner.js +377 -209
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +296 -0
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/task-id.js +35 -0
- package/dist/tasks/validator.js +30 -16
- package/dist/workflows/authoring/authoring.js +96 -148
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/cli.js +0 -20
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/exec/brief.js +25 -59
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +157 -94
- package/dist/workflows/exec/report.js +365 -200
- package/dist/workflows/exec/run-workflow.js +46 -40
- package/dist/workflows/exec/scheduler.js +12 -41
- package/dist/workflows/exec/step-work.js +239 -205
- package/dist/workflows/exec/workflow-engine-gate.js +67 -0
- package/dist/workflows/ir/compile.js +141 -283
- package/dist/workflows/ir/freeze.js +233 -0
- package/dist/workflows/ir/plan-hash.js +40 -5
- package/dist/workflows/ir/schema.js +537 -1
- package/dist/workflows/parser.js +878 -306
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +7 -10
- package/dist/workflows/renderer.js +99 -121
- package/dist/workflows/resource-limits.js +22 -0
- package/dist/workflows/runtime/checkin.js +1 -1
- package/dist/workflows/runtime/plan-classifier.js +131 -0
- package/dist/workflows/runtime/runs.js +200 -113
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +20 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +235 -97
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +4 -26
- package/dist/workflows/validator.js +52 -30
- package/docs/README.md +42 -78
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +9 -8
- package/docs/migration/release-notes/0.9.0.md +158 -14
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +844 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/data-and-telemetry.md +333 -0
- package/package.json +21 -17
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +4636 -0
- package/schemas/akm-task.json +87 -0
- package/{dist/schemas → schemas}/akm-workflow.json +127 -82
- package/dist/akm-migrate-storage +0 -38
- package/dist/assets/help/help-accept.md +0 -12
- package/dist/assets/help/help-improve.md +0 -84
- package/dist/assets/help/help-proposals.md +0 -17
- package/dist/assets/help/help-propose.md +0 -17
- package/dist/assets/help/help-reject.md +0 -11
- package/dist/assets/profiles/frequent.json +0 -13
- package/dist/assets/profiles/recombine-only.json +0 -21
- package/dist/assets/profiles/reflect-distill.json +0 -30
- package/dist/assets/profiles/synthesize.json +0 -15
- package/dist/assets/prompts/procedural-system.md +0 -44
- package/dist/assets/prompts/recombine-system.md +0 -40
- package/dist/assets/prompts/staleness-detect-system.md +0 -6
- package/dist/assets/tasks/core/backup.yml +0 -4
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/assets/templates/html/default.html +0 -78
- package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
- package/dist/assets/wiki/index-template.md +0 -12
- package/dist/assets/wiki/ingest-workflow-template.md +0 -83
- package/dist/assets/wiki/log-template.md +0 -8
- package/dist/assets/wiki/schema-template.md +0 -61
- package/dist/cli/config-migrate.js +0 -150
- package/dist/cli/config-validate.js +0 -39
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -487
- package/dist/commands/improve/calibration.js +0 -161
- package/dist/commands/improve/dedup.js +0 -482
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/improve/hot-probation.js +0 -45
- package/dist/commands/improve/improve-auto-accept.js +0 -276
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/commands/improve/procedural.js +0 -398
- package/dist/commands/improve/recombine.js +0 -818
- package/dist/commands/improve/schema-similarity-gate.js +0 -168
- package/dist/commands/lint/agent-linter.js +0 -44
- package/dist/commands/lint/command-linter.js +0 -44
- package/dist/commands/lint/default-linter.js +0 -16
- package/dist/commands/lint/fact-linter.js +0 -39
- package/dist/commands/lint/knowledge-linter.js +0 -16
- package/dist/commands/lint/memory-linter.js +0 -61
- package/dist/commands/lint/registry.js +0 -41
- package/dist/commands/lint/skill-linter.js +0 -45
- package/dist/commands/lint/task-linter.js +0 -50
- package/dist/commands/lint/workflow-linter.js +0 -81
- package/dist/commands/proposal/legacy-import.js +0 -115
- package/dist/commands/sources/history.js +0 -196
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/commands/wiki-cli.js +0 -292
- package/dist/core/asset/asset-registry.js +0 -76
- package/dist/core/asset/asset-spec.js +0 -316
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/core/eval/rank-metrics.js +0 -113
- package/dist/core/ripgrep/install.js +0 -163
- package/dist/core/ripgrep/resolve.js +0 -81
- package/dist/indexer/db/db.js +0 -1414
- package/dist/indexer/manifest.js +0 -170
- package/dist/indexer/passes/metadata-contributors.js +0 -31
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
- package/dist/integrations/harnesses/opencode-sdk/index.js +0 -25
- package/dist/llm/call-ai.js +0 -62
- package/dist/llm/memory-infer-impl.js +0 -138
- 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/output/text/wiki.js +0 -16
- package/dist/registry/build-index.js +0 -386
- package/dist/schemas/akm-config.json +0 -14225
- package/dist/scripts/migrate-storage.js +0 -13169
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -10169
- package/dist/scripts/migrations/v16-to-v17.js +0 -141
- package/dist/setup/legacy-config.js +0 -106
- package/dist/storage/repositories/consolidation-repository.js +0 -38
- package/dist/storage/repositories/recombine-repository.js +0 -213
- package/dist/wiki/wiki-templates.js +0 -15
- package/dist/wiki/wiki.js +0 -1012
- package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
- package/dist/workflows/db.js +0 -350
- package/dist/workflows/exec/watch.js +0 -116
- package/dist/workflows/program/parser.js +0 -760
- package/dist/workflows/program/project.js +0 -105
- package/docs/data-and-telemetry.md +0 -227
- package/docs/migration/release-notes/0.9.0-beta.60.md +0 -19
- /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
- /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* `index.db` entries repository — CRUD, lookup, re-key, delete-cascade, and the
|
|
6
|
+
* usage-event relink + workflow-document + tag-set reads that key on `entries`.
|
|
7
|
+
*
|
|
8
|
+
* Owns ALL raw SQL against the `entries` table. The shared row/option shapes
|
|
9
|
+
* come from the leaf types + mapper modules.
|
|
10
|
+
*/
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
14
|
+
import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
|
|
15
|
+
import { bestEffort } from "../../core/best-effort.js";
|
|
16
|
+
import { getStateDbPath, withStateDb } from "../../core/state-db.js";
|
|
17
|
+
import { warn } from "../../core/warn.js";
|
|
18
|
+
import { buildSearchText } from "../../indexer/search/search-fields.js";
|
|
19
|
+
import { ENTRY_COLUMNS, rowToIndexedEntry } from "./index-entry-mapper.js";
|
|
20
|
+
import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
|
|
21
|
+
import { deleteEntryVectors, isVecAvailable } from "./index-vec-repository.js";
|
|
22
|
+
// ── Entry operations ────────────────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Insert or update an entry in the `entries` table. Returns the row id.
|
|
25
|
+
*
|
|
26
|
+
* **Important:** This does not update the FTS index. Callers must call
|
|
27
|
+
* `rebuildFts()` after all upserts are complete for full-text search to
|
|
28
|
+
* reflect the changes.
|
|
29
|
+
*/
|
|
30
|
+
export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, searchText, provenance, contentHash) {
|
|
31
|
+
// Hot path during indexing — cache prepared statements per database
|
|
32
|
+
// connection so we don't pay the SQL parse/compile cost on every call.
|
|
33
|
+
const stmts = getUpsertStmts(db);
|
|
34
|
+
const previous = (provenance?.itemRef
|
|
35
|
+
? stmts.findByItemRef.get(provenance.itemRef)
|
|
36
|
+
: undefined) ?? stmts.findByEntryKey.get(entryKey);
|
|
37
|
+
// Phase 5A / Advantage D5: surface derived memory parent ref into the
|
|
38
|
+
// dedicated `derived_from` column so retrieval-time lookup (parent→child)
|
|
39
|
+
// does not have to scan + JSON-decode every memory row.
|
|
40
|
+
const derivedFrom = typeof entry.derivedFrom === "string" && entry.derivedFrom.trim() ? entry.derivedFrom.trim() : null;
|
|
41
|
+
// Chunk-5 Step 2 (spec §14.4): populate the additive identity/provenance
|
|
42
|
+
// columns alongside the legacy ones. `type` mirrors `entry_type` (the open
|
|
43
|
+
// token) unconditionally; `item_ref`/bundle/component/concept/adapter come
|
|
44
|
+
// from the write-boundary derivation when available (NULL otherwise, healed
|
|
45
|
+
// by the next full index). `content_hash` (F4a M-core-2) is `doc.hash` from
|
|
46
|
+
// the diff-persist writer; a NULL passed here PRESERVES any existing hash (the
|
|
47
|
+
// ON CONFLICT COALESCE below) so the LLM-enrichment re-upsert cannot wipe it.
|
|
48
|
+
const apply = () => {
|
|
49
|
+
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);
|
|
50
|
+
if (!result)
|
|
51
|
+
throw new Error("upsertEntry: entry_key not found after upsert");
|
|
52
|
+
if (previous?.id === result.id && previous.search_text !== searchText)
|
|
53
|
+
deleteEntryVectors(db, result.id);
|
|
54
|
+
// Mark this entry as FTS-dirty so `rebuildFts({ incremental: true })`
|
|
55
|
+
// only revisits entries that actually changed. INSERT OR IGNORE is
|
|
56
|
+
// idempotent across multiple upserts of the same row.
|
|
57
|
+
stmts.markDirty.run(result.id);
|
|
58
|
+
return result.id;
|
|
59
|
+
};
|
|
60
|
+
return previous && previous.search_text !== searchText ? db.transaction(apply)() : apply();
|
|
61
|
+
}
|
|
62
|
+
const upsertStmtsByDb = new WeakMap();
|
|
63
|
+
// The ON CONFLICT DO UPDATE column assignments — factored out so the two
|
|
64
|
+
// conflict targets below stay byte-identical. `item_ref` is durable identity;
|
|
65
|
+
// update `entry_key` when adapter ownership changes its
|
|
66
|
+
// spelling so directory pruning retains the upserted row.
|
|
67
|
+
// `content_hash` COALESCEs so a NULL passed by the LLM-enhance re-upsert cannot
|
|
68
|
+
// wipe a previously-persisted hash.
|
|
69
|
+
const UPSERT_SET_CLAUSE = `SET
|
|
70
|
+
entry_key = excluded.entry_key,
|
|
71
|
+
dir_path = excluded.dir_path,
|
|
72
|
+
file_path = excluded.file_path,
|
|
73
|
+
stash_dir = excluded.stash_dir,
|
|
74
|
+
entry_json = excluded.entry_json,
|
|
75
|
+
search_text = excluded.search_text,
|
|
76
|
+
entry_type = excluded.entry_type,
|
|
77
|
+
derived_from = excluded.derived_from,
|
|
78
|
+
item_ref = excluded.item_ref,
|
|
79
|
+
bundle_id = excluded.bundle_id,
|
|
80
|
+
component_id = excluded.component_id,
|
|
81
|
+
concept_id = excluded.concept_id,
|
|
82
|
+
adapter_id = excluded.adapter_id,
|
|
83
|
+
type = excluded.type,
|
|
84
|
+
content_hash = COALESCE(excluded.content_hash, content_hash)`;
|
|
85
|
+
function getUpsertStmts(db) {
|
|
86
|
+
const existing = upsertStmtsByDb.get(db);
|
|
87
|
+
if (existing)
|
|
88
|
+
return existing;
|
|
89
|
+
// Durable identity is the primary conflict target. `entry_key` remains the
|
|
90
|
+
// internal conflict key for low-level entries without provenance.
|
|
91
|
+
const conflictClause = `ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
|
|
92
|
+
ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`;
|
|
93
|
+
const stmts = {
|
|
94
|
+
// RETURNING id handles ON CONFLICT DO UPDATE correctly — no second
|
|
95
|
+
// SELECT round-trip needed (last_insert_rowid() is unreliable for
|
|
96
|
+
// ON CONFLICT). Use `.get()` so a single row comes back.
|
|
97
|
+
upsert: db.prepare(`
|
|
98
|
+
INSERT INTO entries (
|
|
99
|
+
entry_key, dir_path, file_path, stash_dir, entry_json, search_text, entry_type, derived_from,
|
|
100
|
+
item_ref, bundle_id, component_id, concept_id, adapter_id, type, content_hash
|
|
101
|
+
)
|
|
102
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
103
|
+
${conflictClause}
|
|
104
|
+
RETURNING id
|
|
105
|
+
`),
|
|
106
|
+
markDirty: db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)"),
|
|
107
|
+
findByItemRef: db.prepare("SELECT id, search_text FROM entries WHERE item_ref = ? ORDER BY id ASC LIMIT 1"),
|
|
108
|
+
findByEntryKey: db.prepare("SELECT id, search_text FROM entries WHERE entry_key = ? LIMIT 1"),
|
|
109
|
+
};
|
|
110
|
+
upsertStmtsByDb.set(db, stmts);
|
|
111
|
+
return stmts;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Phase 5A / Advantage D5: look up the derived-memory child row whose
|
|
115
|
+
* `derived_from` column matches `parentRef` (the 0.9.0 conceptId, e.g.
|
|
116
|
+
* `"memories/claude-prefs"` — Group-C item 2 flip).
|
|
117
|
+
*
|
|
118
|
+
* Returns the most-recently-updated derived child when multiple exist (one
|
|
119
|
+
* parent should yield exactly one `.derived` child in practice, but the
|
|
120
|
+
* ordering keeps results deterministic). Returns `null` when no derived
|
|
121
|
+
* child has been indexed for this parent.
|
|
122
|
+
*/
|
|
123
|
+
export function getDerivedForParent(db, parentRef, stashDir) {
|
|
124
|
+
if (!parentRef)
|
|
125
|
+
return null;
|
|
126
|
+
const sourceScope = stashDir ? "AND stash_dir = ?" : "";
|
|
127
|
+
const row = db
|
|
128
|
+
.prepare(`SELECT ${ENTRY_COLUMNS}
|
|
129
|
+
FROM entries
|
|
130
|
+
WHERE derived_from = ?
|
|
131
|
+
${sourceScope}
|
|
132
|
+
ORDER BY id DESC
|
|
133
|
+
LIMIT 1`)
|
|
134
|
+
.get(parentRef, ...(stashDir ? [stashDir] : []));
|
|
135
|
+
if (!row)
|
|
136
|
+
return null;
|
|
137
|
+
return rowToIndexedEntry(row, "getDerivedForParent");
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 03-R3: for the given derived-twin row ids, fetch each twin's BASE memory
|
|
141
|
+
* `beliefState`, keyed by twin id.
|
|
142
|
+
*
|
|
143
|
+
* Used by the derived-twin belief inheritance in search ranking: a `.derived`
|
|
144
|
+
* twin has no belief state of its own, so it inherits its base memory's
|
|
145
|
+
* demoting state (contradicted/superseded/…) at search time. A twin's
|
|
146
|
+
* `entry_key` is exactly its base's `entry_key` plus the `.derived` suffix
|
|
147
|
+
* (same stash + type prefix, `<name>` vs `<name>.derived`), so the base is
|
|
148
|
+
* found by stripping that suffix — no ref/prefix reconstruction. Returns a map
|
|
149
|
+
* of twin id → base beliefState for bases that carry a non-empty state.
|
|
150
|
+
* Best-effort: any query error (e.g. legacy DB) yields no inheritance rather
|
|
151
|
+
* than failing the search.
|
|
152
|
+
*/
|
|
153
|
+
export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
|
|
154
|
+
const out = new Map();
|
|
155
|
+
if (twinIds.length === 0)
|
|
156
|
+
return out;
|
|
157
|
+
// Chunk at SQLITE_CHUNK_SIZE like the sibling bulk-by-id helpers, so a large
|
|
158
|
+
// `--limit` candidate set never trips SQLITE_MAX_VARIABLE_NUMBER (which would
|
|
159
|
+
// otherwise fall into the best-effort catch and silently disable the feature).
|
|
160
|
+
for (let i = 0; i < twinIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
161
|
+
const chunk = twinIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
162
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
163
|
+
bestEffort(() => {
|
|
164
|
+
const rows = db
|
|
165
|
+
.prepare(`SELECT twin.id AS twin_id, json_extract(base.entry_json, '$.beliefState') AS belief
|
|
166
|
+
FROM entries twin
|
|
167
|
+
JOIN entries base
|
|
168
|
+
ON base.entry_type = 'memory'
|
|
169
|
+
AND base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
|
|
170
|
+
WHERE twin.id IN (${placeholders})
|
|
171
|
+
AND twin.item_ref LIKE '%.derived'
|
|
172
|
+
AND json_extract(base.entry_json, '$.beliefState') IS NOT NULL`)
|
|
173
|
+
.all(...chunk);
|
|
174
|
+
for (const r of rows) {
|
|
175
|
+
if (typeof r.belief === "string" && r.belief.trim().length > 0)
|
|
176
|
+
out.set(r.twin_id, r.belief.trim());
|
|
177
|
+
}
|
|
178
|
+
}, "belief-state inheritance is best-effort");
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* SPEC-7 (`akm mv`): re-key an entries row IN PLACE after an on-disk rename.
|
|
184
|
+
*
|
|
185
|
+
* The row id is preserved on purpose — `utility_scores`,
|
|
186
|
+
* `utility_scores_scoped`, and `embeddings` are keyed by `entry_id`, so an
|
|
187
|
+
* UPDATE (rather than a delete + insert under the new `entry_key`) is what
|
|
188
|
+
* keeps the asset's accumulated usage-ranking history attached across a
|
|
189
|
+
* rename. (`asset_salience` / `asset_outcome` live in state.db keyed by
|
|
190
|
+
* `asset_ref` TEXT and are re-keyed separately by `akm mv` — see
|
|
191
|
+
* mv-cli.ts `rekeyStateDbForMove`.) `entry_json.name` (and `filename`, when
|
|
192
|
+
* present) is patched and `search_text` rebuilt so search reflects the new
|
|
193
|
+
* name; the row is marked FTS-dirty for the caller's
|
|
194
|
+
* `rebuildFts({incremental: true})`.
|
|
195
|
+
*
|
|
196
|
+
* Bundle-qualified `usage_events.entry_ref` rows for the old conceptId are
|
|
197
|
+
* rewritten to the new item ref. Without this, events keep the old
|
|
198
|
+
* ref, `relinkUsageEvents` finds no matching entry after the next full
|
|
199
|
+
* rebuild, and the utility history the re-key exists to preserve silently
|
|
200
|
+
* resets. DETACHED orphan events already sitting at the new ref (entry_id
|
|
201
|
+
* NULL — a deleted stranger's history) are deleted first, so the moved asset
|
|
202
|
+
* never adopts them (live asset's history wins, matching the stale-row
|
|
203
|
+
* eviction below).
|
|
204
|
+
*
|
|
205
|
+
* A stale row already occupying `newEntryKey` (the caller has verified no
|
|
206
|
+
* FILE exists at the target, so such a row can only be a leftover for a
|
|
207
|
+
* deleted file) is evicted first — through {@link deleteRelatedRows}, so its
|
|
208
|
+
* child rows (embeddings, entries_vec, utility scores, usage events) go with
|
|
209
|
+
* it. A bare `DELETE FROM entries` would trip the non-CASCADE `embeddings`
|
|
210
|
+
* FK under `PRAGMA foreign_keys = ON` and roll back the whole re-key.
|
|
211
|
+
* The moved row keeps its id.
|
|
212
|
+
*
|
|
213
|
+
* Returns the surviving row id, or `null` when no row matches `oldEntryKey`
|
|
214
|
+
* (nothing indexed under the old name — the caller falls open and the next
|
|
215
|
+
* full `akm index` picks the file up as a fresh entry).
|
|
216
|
+
*/
|
|
217
|
+
export function rekeyEntryInPlace(db, opts) {
|
|
218
|
+
const oldItemRef = `${opts.sourceName}//${opts.oldRef}`;
|
|
219
|
+
const row = db
|
|
220
|
+
.prepare("SELECT id, stash_dir, entry_json, search_text, entry_type FROM entries WHERE item_ref = ?")
|
|
221
|
+
.get(oldItemRef);
|
|
222
|
+
if (!row)
|
|
223
|
+
return null;
|
|
224
|
+
if (path.resolve(row.stash_dir) !== path.resolve(opts.sourceRoot)) {
|
|
225
|
+
throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
|
|
226
|
+
}
|
|
227
|
+
// Patch the JSON payload. On corrupt entry_json still re-key key + paths so
|
|
228
|
+
// the utility history survives; the next full index heals the JSON.
|
|
229
|
+
let entryJson = row.entry_json;
|
|
230
|
+
let searchText = row.search_text;
|
|
231
|
+
try {
|
|
232
|
+
const entry = JSON.parse(row.entry_json);
|
|
233
|
+
entry.name = opts.newName;
|
|
234
|
+
if (typeof entry.filename === "string")
|
|
235
|
+
entry.filename = path.basename(opts.newFilePath);
|
|
236
|
+
if (opts.newDerivedFrom !== undefined)
|
|
237
|
+
entry.derivedFrom = opts.newDerivedFrom;
|
|
238
|
+
entryJson = JSON.stringify(entry);
|
|
239
|
+
searchText = buildSearchText(entry);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
/* corrupt entry_json — key/path-only re-key */
|
|
243
|
+
}
|
|
244
|
+
const expectedNewRef = conceptIdFromTypeName(row.entry_type, opts.newName);
|
|
245
|
+
if (opts.newRef !== expectedNewRef) {
|
|
246
|
+
throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: target ref does not match the entry type and name.`);
|
|
247
|
+
}
|
|
248
|
+
const newItemRef = `${opts.sourceName}//${opts.newRef}`;
|
|
249
|
+
db.transaction(() => {
|
|
250
|
+
const stale = db.prepare("SELECT id FROM entries WHERE entry_key = ?").get(opts.newEntryKey);
|
|
251
|
+
if (stale && stale.id !== row.id) {
|
|
252
|
+
// Full child-row cleanup (embeddings, entries_vec, utility scores,
|
|
253
|
+
// usage events, FTS + dirty marks) BEFORE the entries delete: the
|
|
254
|
+
// `embeddings` FK is non-CASCADE and `foreign_keys = ON`, so a bare
|
|
255
|
+
// entries delete would throw and roll back the entire re-key; and
|
|
256
|
+
// without it the FK-less child rows would orphan permanently.
|
|
257
|
+
deleteRelatedRows(db, [{ id: stale.id }]);
|
|
258
|
+
db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
|
|
259
|
+
}
|
|
260
|
+
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);
|
|
261
|
+
if (opts.newDerivedFrom !== undefined) {
|
|
262
|
+
db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
|
|
263
|
+
}
|
|
264
|
+
db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
|
|
265
|
+
})();
|
|
266
|
+
// Re-point usage history at the new ref. Chunk-8 WI-8.3: usage_events lives in
|
|
267
|
+
// state.db now, so this is a SEPARATE cross-DB transaction (best-effort — the
|
|
268
|
+
// rename itself already committed above; on failure the next full index's
|
|
269
|
+
// relinkUsageEvents re-attaches by ref). See the docstring for the spellings
|
|
270
|
+
// and the live-asset-wins collision policy.
|
|
271
|
+
rewriteUsageEventRefForMove(opts);
|
|
272
|
+
return row.id;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Rewrite `usage_events.entry_ref` from `opts.oldRef` to `opts.newRef` in
|
|
276
|
+
* state.db. DETACHED orphan events (entry_id NULL) already sitting AT the new
|
|
277
|
+
* ref are evicted first so the moved asset never adopts a deleted stranger's
|
|
278
|
+
* history (live-asset-wins). Best-effort + guarded on state.db's existence.
|
|
279
|
+
*/
|
|
280
|
+
function rewriteUsageEventRefForMove(opts) {
|
|
281
|
+
if (!fs.existsSync(getStateDbPath()))
|
|
282
|
+
return;
|
|
283
|
+
// `usage_events.entry_ref` is the fully-qualified item_ref
|
|
284
|
+
// (`<bundle>//<conceptId>`).
|
|
285
|
+
const rename = (stateDb, oldR, newR) => {
|
|
286
|
+
stateDb.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newR);
|
|
287
|
+
stateDb.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newR, oldR);
|
|
288
|
+
};
|
|
289
|
+
try {
|
|
290
|
+
withStateDb((stateDb) => {
|
|
291
|
+
stateDb.transaction(() => {
|
|
292
|
+
rename(stateDb, `${opts.sourceName}//${opts.oldRef}`, `${opts.sourceName}//${opts.newRef}`);
|
|
293
|
+
})();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
298
|
+
throw new Error(`Failed to rewrite usage events for move: ${message}`, { cause: error });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Phase 2A / Rec 5: bulk-load positive feedback event counts for the given
|
|
303
|
+
* entry ids. Used by the utility-decay forgetting curve to stabilize
|
|
304
|
+
* (extend the half-life of) memories that have repeatedly proven useful.
|
|
305
|
+
*
|
|
306
|
+
* Returns a `Map<entryId, count>` containing only entries with at least one
|
|
307
|
+
* positive feedback event — missing ids implicitly map to `0`. Chunks at
|
|
308
|
+
* `SQLITE_CHUNK_SIZE` (500) to respect `SQLITE_MAX_VARIABLE_NUMBER`.
|
|
309
|
+
*
|
|
310
|
+
* Cheap when called with zero ids, and silently empty when state.db (or its
|
|
311
|
+
* `usage_events` table) is absent.
|
|
312
|
+
*
|
|
313
|
+
* Chunk-8 WI-8.3: usage_events lives in state.db — this reads it there (no
|
|
314
|
+
* entries join needed; the ids are supplied by the caller). Gated by the caller
|
|
315
|
+
* (`shouldQueryPositiveFeedbackCounts`) so the state.db open is not on the
|
|
316
|
+
* default search hot path.
|
|
317
|
+
*/
|
|
318
|
+
export function getPositiveFeedbackCountsByIds(ids) {
|
|
319
|
+
const result = new Map();
|
|
320
|
+
if (ids.length === 0 || !fs.existsSync(getStateDbPath()))
|
|
321
|
+
return result;
|
|
322
|
+
bestEffort(() => {
|
|
323
|
+
withStateDb((stateDb) => {
|
|
324
|
+
for (let i = 0; i < ids.length; i += SQLITE_CHUNK_SIZE) {
|
|
325
|
+
const chunk = ids.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
326
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
327
|
+
const rows = stateDb
|
|
328
|
+
.prepare(`SELECT entry_id, COUNT(*) AS cnt
|
|
329
|
+
FROM usage_events
|
|
330
|
+
WHERE event_type = 'feedback'
|
|
331
|
+
AND signal = 'positive'
|
|
332
|
+
AND entry_id IN (${placeholders})
|
|
333
|
+
GROUP BY entry_id`)
|
|
334
|
+
.all(...chunk);
|
|
335
|
+
for (const row of rows) {
|
|
336
|
+
if (row.entry_id !== null && row.cnt > 0) {
|
|
337
|
+
result.set(row.entry_id, row.cnt);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}, "positive feedback counts are best-effort");
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
function deleteEntriesWhere(db, column, value) {
|
|
346
|
+
db.transaction(() => {
|
|
347
|
+
const ids = db.prepare(`SELECT id FROM entries WHERE ${column} = ?`).all(value);
|
|
348
|
+
deleteRelatedRows(db, ids);
|
|
349
|
+
db.prepare(`DELETE FROM entries WHERE ${column} = ?`).run(value);
|
|
350
|
+
})();
|
|
351
|
+
}
|
|
352
|
+
export function deleteEntriesByDir(db, dirPath) {
|
|
353
|
+
deleteEntriesWhere(db, "dir_path", dirPath);
|
|
354
|
+
}
|
|
355
|
+
export function deleteEntriesByDirAndStash(db, dirPath, stashDir, options = {}) {
|
|
356
|
+
return db.transaction(() => {
|
|
357
|
+
const ids = db
|
|
358
|
+
.prepare("SELECT id FROM entries WHERE dir_path = ? AND stash_dir = ?")
|
|
359
|
+
.all(dirPath, stashDir);
|
|
360
|
+
deleteRelatedRows(db, ids, options);
|
|
361
|
+
db.prepare("DELETE FROM entries WHERE dir_path = ? AND stash_dir = ?").run(dirPath, stashDir);
|
|
362
|
+
return ids.map((row) => row.id);
|
|
363
|
+
})();
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Delete every entry (and its child rows) under a source's stash root.
|
|
367
|
+
*
|
|
368
|
+
* Chunk-5 flip F1: this keys on `stash_dir` — the physical source-root path,
|
|
369
|
+
* which is orthogonal to the ref grammar and identifies rows regardless of
|
|
370
|
+
* whether their `item_ref` is populated or NULL. No item_ref predicate is
|
|
371
|
+
* needed (or correct) here; both grammars' rows are removed by location.
|
|
372
|
+
*/
|
|
373
|
+
export function deleteEntriesByStashDir(db, stashDir) {
|
|
374
|
+
deleteEntriesWhere(db, "stash_dir", stashDir);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Diff-persist orphan delete (Chunk-5 flip F4a M-core-2): remove every entry
|
|
378
|
+
* (and its child rows, via {@link deleteRelatedRows}) under `dirPath` whose
|
|
379
|
+
* `entry_key` is NOT in `keepKeys` — the rows for files that vanished from a
|
|
380
|
+
* rescanned directory.
|
|
381
|
+
*
|
|
382
|
+
* Replaces the old per-dir `deleteEntriesByDir` + full re-insert: the caller
|
|
383
|
+
* upserts the current file set FIRST (ON CONFLICT preserving `entries.id`, so
|
|
384
|
+
* embeddings / utility / usage stay attached to unchanged rows), then calls this
|
|
385
|
+
* to prune only the DEPARTED rows. The net row-state of `dir_path` is identical
|
|
386
|
+
* to delete-then-reinsert; the win is that unchanged rows keep their id.
|
|
387
|
+
*
|
|
388
|
+
* Keyed on `entry_key` because it mirrors the directory upsert key precisely.
|
|
389
|
+
* Both directory- and source-scoped so overlapping source roots cannot prune
|
|
390
|
+
* one another's rows.
|
|
391
|
+
*/
|
|
392
|
+
export function deleteEntriesByDirExceptKeys(db, dirPath, stashDir, keepKeys, options = {}) {
|
|
393
|
+
return db.transaction(() => {
|
|
394
|
+
const rows = db
|
|
395
|
+
.prepare("SELECT id, entry_key FROM entries WHERE dir_path = ? AND stash_dir = ?")
|
|
396
|
+
.all(dirPath, stashDir);
|
|
397
|
+
const doomed = rows.filter((r) => !keepKeys.has(r.entry_key));
|
|
398
|
+
if (doomed.length === 0)
|
|
399
|
+
return [];
|
|
400
|
+
deleteRelatedRows(db, doomed, options);
|
|
401
|
+
for (let i = 0; i < doomed.length; i += SQLITE_CHUNK_SIZE) {
|
|
402
|
+
const chunk = doomed.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
403
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
404
|
+
db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk.map((r) => r.id));
|
|
405
|
+
}
|
|
406
|
+
return doomed.map((row) => row.id);
|
|
407
|
+
})();
|
|
408
|
+
}
|
|
409
|
+
function deleteRelatedRows(db, ids, options = {}) {
|
|
410
|
+
if (ids.length === 0)
|
|
411
|
+
return;
|
|
412
|
+
const numericIds = ids.map((r) => r.id);
|
|
413
|
+
const vecAvail = isVecAvailable(db);
|
|
414
|
+
// Drop matching FTS rows + dirty markers immediately so an incremental
|
|
415
|
+
// rebuild after a deletion doesn't try to re-index entries that no longer
|
|
416
|
+
// exist (and so a full scan after deletion sees a consistent FTS).
|
|
417
|
+
for (let i = 0; i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
418
|
+
const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
419
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
420
|
+
bestEffort(() => db.prepare(`DELETE FROM entries_fts WHERE entry_id IN (${placeholders})`).run(...chunk), "fts table may not exist on a brand-new db");
|
|
421
|
+
bestEffort(() => db.prepare(`DELETE FROM entries_fts_dirty WHERE entry_id IN (${placeholders})`).run(...chunk), "fts dirty table is created lazily by upsertEntry");
|
|
422
|
+
}
|
|
423
|
+
// Process in chunks to stay within SQLITE_MAX_VARIABLE_NUMBER
|
|
424
|
+
for (let i = 0; i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
425
|
+
const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
426
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
427
|
+
bestEffort(() => db.prepare(`DELETE FROM embeddings WHERE id IN (${placeholders})`).run(...chunk), "delete embeddings for entries");
|
|
428
|
+
if (vecAvail) {
|
|
429
|
+
bestEffort(() => db.prepare(`DELETE FROM entries_vec WHERE id IN (${placeholders})`).run(...chunk), "delete entries_vec for entries");
|
|
430
|
+
}
|
|
431
|
+
// Clean up utility scores before deleting entries
|
|
432
|
+
bestEffort(() => db.prepare(`DELETE FROM utility_scores WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores for entries");
|
|
433
|
+
bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
|
|
434
|
+
}
|
|
435
|
+
// usage_events lives in state.db, outside this transaction. Index persistence
|
|
436
|
+
// disables this cleanup and runs it only after its index.db transaction
|
|
437
|
+
// commits; standalone delete callers retain the immediate behavior.
|
|
438
|
+
if (options.cleanupUsageEvents !== false)
|
|
439
|
+
deleteUsageEventsByEntryIds(numericIds);
|
|
440
|
+
// #624-P1: graph_files is NO LONGER keyed on entries.id, so deleting an
|
|
441
|
+
// entries row must NOT wipe the extracted graph (that is the whole point —
|
|
442
|
+
// the graph survives a reindex when body_hash is unchanged). We therefore do
|
|
443
|
+
// NOT delete graph_files here. We DO, however, recompute graph_meta counts
|
|
444
|
+
// for the stash roots touched by the deleted entries so the summary numbers
|
|
445
|
+
// stay consistent with the live child rows (the counts are derived, and the
|
|
446
|
+
// entries delete may have changed which files are considered/indexed).
|
|
447
|
+
//
|
|
448
|
+
// Resolve the affected stash roots from the entries rows BEFORE deletion.
|
|
449
|
+
const affectedStashRoots = new Set();
|
|
450
|
+
for (let i = 0; i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
451
|
+
const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
452
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
453
|
+
bestEffort(() => {
|
|
454
|
+
const rows = db
|
|
455
|
+
.prepare(`SELECT DISTINCT stash_dir FROM entries WHERE id IN (${placeholders})`)
|
|
456
|
+
.all(...chunk);
|
|
457
|
+
for (const row of rows) {
|
|
458
|
+
if (row.stash_dir)
|
|
459
|
+
affectedStashRoots.add(row.stash_dir);
|
|
460
|
+
}
|
|
461
|
+
}, "resolve stash roots for graph_meta recompute");
|
|
462
|
+
}
|
|
463
|
+
for (const stashRoot of affectedStashRoots) {
|
|
464
|
+
bestEffort(() => db
|
|
465
|
+
.prepare(`UPDATE graph_meta
|
|
466
|
+
SET extracted_files = (SELECT COUNT(*) FROM graph_files WHERE stash_root = ?),
|
|
467
|
+
entity_count = (SELECT COUNT(*) FROM graph_file_entities WHERE stash_root = ?),
|
|
468
|
+
relation_count = (SELECT COUNT(*) FROM graph_file_relations WHERE stash_root = ?)
|
|
469
|
+
WHERE stash_root = ?`)
|
|
470
|
+
.run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
export function deleteUsageEventsByEntryIds(entryIds) {
|
|
474
|
+
if (entryIds.length === 0 || !fs.existsSync(getStateDbPath()))
|
|
475
|
+
return;
|
|
476
|
+
bestEffort(() => {
|
|
477
|
+
withStateDb((stateDb) => {
|
|
478
|
+
for (let i = 0; i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
|
|
479
|
+
const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
480
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
481
|
+
stateDb.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}, "delete usage_events (state.db) for entries");
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Delete entries by their primary key IDs, along with all related rows
|
|
488
|
+
* (embeddings, entries_vec, entries_fts, utility_scores, usage_events).
|
|
489
|
+
*
|
|
490
|
+
* Used by the `--clean` post-pass to remove stale entries whose source files
|
|
491
|
+
* no longer exist on disk.
|
|
492
|
+
*/
|
|
493
|
+
export function deleteEntriesByIds(db, ids) {
|
|
494
|
+
if (ids.length === 0)
|
|
495
|
+
return;
|
|
496
|
+
db.transaction(() => {
|
|
497
|
+
const idObjs = ids.map((id) => ({ id }));
|
|
498
|
+
deleteRelatedRows(db, idObjs);
|
|
499
|
+
for (let i = 0; i < ids.length; i += SQLITE_CHUNK_SIZE) {
|
|
500
|
+
const chunk = ids.slice(i, i + SQLITE_CHUNK_SIZE);
|
|
501
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
502
|
+
db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk);
|
|
503
|
+
}
|
|
504
|
+
})();
|
|
505
|
+
}
|
|
506
|
+
// ── All entries ─────────────────────────────────────────────────────────────
|
|
507
|
+
function parseEntryRows(rows, context) {
|
|
508
|
+
const entries = [];
|
|
509
|
+
for (const row of rows) {
|
|
510
|
+
const mapped = rowToIndexedEntry(row, context);
|
|
511
|
+
if (mapped)
|
|
512
|
+
entries.push(mapped);
|
|
513
|
+
}
|
|
514
|
+
return entries;
|
|
515
|
+
}
|
|
516
|
+
export function getAllEntries(db, entryType, excludeTypes) {
|
|
517
|
+
let sql;
|
|
518
|
+
let params;
|
|
519
|
+
// #627 — exclude-type clause applies only on the untyped ('any') path. Empty
|
|
520
|
+
// list skips the clause (never `NOT IN ()`).
|
|
521
|
+
const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
|
|
522
|
+
if (entryType && entryType !== "any") {
|
|
523
|
+
sql = `SELECT ${ENTRY_COLUMNS} FROM entries WHERE entry_type = ?`;
|
|
524
|
+
params = [entryType];
|
|
525
|
+
}
|
|
526
|
+
else if (excludes.length > 0) {
|
|
527
|
+
sql = `SELECT ${ENTRY_COLUMNS} FROM entries WHERE entry_type NOT IN (${excludes.map(() => "?").join(", ")})`;
|
|
528
|
+
params = [...excludes];
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
sql = `SELECT ${ENTRY_COLUMNS} FROM entries`;
|
|
532
|
+
params = [];
|
|
533
|
+
}
|
|
534
|
+
const rows = db.prepare(sql).all(...params);
|
|
535
|
+
return parseEntryRows(rows, "getAllEntries");
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Resolve a single `entries.id` from a new-grammar `[bundle//]conceptId` ref,
|
|
539
|
+
* keying on the canonical stored `item_ref` (ref-grammar decision D-R1/D-R4).
|
|
540
|
+
* The optional `stashDir` scopes the
|
|
541
|
+
* match to one source root.
|
|
542
|
+
*/
|
|
543
|
+
export function findEntryIdByRef(db, ref, stashDir) {
|
|
544
|
+
return findEntryIdByBundleRef(db, ref, stashDir);
|
|
545
|
+
}
|
|
546
|
+
/** `name` plus its `.md`-toggled sibling — the markdown ext-keep/strip ambiguity. */
|
|
547
|
+
function withMdVariants(name) {
|
|
548
|
+
return name.endsWith(".md") ? [name, name.slice(0, -3)] : [name, `${name}.md`];
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Current (`[bundle//]conceptId`) id lookup: match `item_ref` exactly when
|
|
552
|
+
* bundle-qualified or by `//conceptId` suffix when short.
|
|
553
|
+
*/
|
|
554
|
+
function findEntryIdByBundleRef(db, ref, stashDir) {
|
|
555
|
+
const parsed = parseBundleRef(ref);
|
|
556
|
+
const conceptVariants = withMdVariants(parsed.conceptId);
|
|
557
|
+
// item_ref is the canonical stored spelling post-flip.
|
|
558
|
+
for (const conceptId of conceptVariants) {
|
|
559
|
+
const id = matchIdByItemRef(db, parsed.bundle, conceptId, stashDir);
|
|
560
|
+
if (id !== undefined)
|
|
561
|
+
return id;
|
|
562
|
+
}
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Match a row `id` by `item_ref`: exact `bundle//conceptId` when `bundle` is
|
|
567
|
+
* known, else the `//conceptId` SUFFIX (short ref — any bundle). The suffix
|
|
568
|
+
* uses `substr(...) = ...` (never LIKE) so `_`/`%` in a conceptId are literal,
|
|
569
|
+
* and includes the `//` boundary so a segment split never false-matches.
|
|
570
|
+
*
|
|
571
|
+
* ── Deterministic winner (`ORDER BY id ASC`) ──
|
|
572
|
+
*
|
|
573
|
+
* A SHORT ref can match one concept id across MULTIPLE bundles (e.g. the same
|
|
574
|
+
* `knowledge/guide` in the primary stash and an installed source). The bare
|
|
575
|
+
* `LIMIT 1` this replaced picked whichever row SQLite happened to visit first —
|
|
576
|
+
* a nondeterministic winner when two bundles share a conceptId. We now impose a
|
|
577
|
+
* total order (`ORDER BY id ASC`) so the winner is STABLE across runs.
|
|
578
|
+
*
|
|
579
|
+
* `id ASC` is also the SENSIBLE choice, not just a stable tiebreak: a full index
|
|
580
|
+
* walks its sources in installation-precedence order (`resolveSourceEntries`,
|
|
581
|
+
* primary stash first — the same order `resolveSourcesForOrigin` calls "local"),
|
|
582
|
+
* so the highest-precedence source's rows carry the LOWEST ids. Ascending id
|
|
583
|
+
* therefore prefers the primary/highest-precedence bundle — mirroring what a
|
|
584
|
+
* precedence-ordered scan would pick — while staying a pure config-free leaf
|
|
585
|
+
* (true installation-priority resolution against an injected bundle list is
|
|
586
|
+
* `resolveRef`'s job; this DB helper takes no config handle). The exact-bundle
|
|
587
|
+
* arm is single-row under the UNIQUE `item_ref` index.
|
|
588
|
+
*/
|
|
589
|
+
function matchIdByItemRef(db, bundle, conceptId, stashDir) {
|
|
590
|
+
const scope = stashDir ? "AND stash_dir = ?" : "";
|
|
591
|
+
if (bundle !== undefined) {
|
|
592
|
+
const itemRef = `${bundle}//${conceptId}`;
|
|
593
|
+
const row = db
|
|
594
|
+
.prepare(`SELECT id FROM entries WHERE item_ref = ? ${scope} ORDER BY id ASC LIMIT 1`)
|
|
595
|
+
.get(itemRef, ...(stashDir ? [stashDir] : []));
|
|
596
|
+
return row?.id;
|
|
597
|
+
}
|
|
598
|
+
const suffix = `//${conceptId}`;
|
|
599
|
+
const row = db
|
|
600
|
+
.prepare(`SELECT id FROM entries
|
|
601
|
+
WHERE item_ref IS NOT NULL
|
|
602
|
+
AND substr(item_ref, length(item_ref) - length(?) + 1) = ?
|
|
603
|
+
${scope}
|
|
604
|
+
ORDER BY id ASC
|
|
605
|
+
LIMIT 1`)
|
|
606
|
+
.get(suffix, suffix, ...(stashDir ? [stashDir] : []));
|
|
607
|
+
return row?.id;
|
|
608
|
+
}
|
|
609
|
+
export function getEntryCount(db) {
|
|
610
|
+
const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries").get();
|
|
611
|
+
return row.cnt;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Per-asset-type entry counts (keyed by `entry_type`, e.g. "skill",
|
|
615
|
+
* "knowledge", "memory"). Used by `akm info` to break down the aggregate
|
|
616
|
+
* `indexStats.entryCount` (R-057). Rows with a null/empty `entry_type` are
|
|
617
|
+
* omitted rather than surfaced under a synthetic key.
|
|
618
|
+
*/
|
|
619
|
+
export function getEntryCountByType(db) {
|
|
620
|
+
const rows = db
|
|
621
|
+
.prepare("SELECT entry_type AS type, COUNT(*) AS cnt FROM entries WHERE entry_type IS NOT NULL GROUP BY entry_type")
|
|
622
|
+
.all();
|
|
623
|
+
const out = {};
|
|
624
|
+
for (const row of rows) {
|
|
625
|
+
out[row.type] = row.cnt;
|
|
626
|
+
}
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
629
|
+
export function getEmbeddableEntryCount(db) {
|
|
630
|
+
return getEntryCount(db);
|
|
631
|
+
}
|
|
632
|
+
export function getEntryById(db, id) {
|
|
633
|
+
const row = db
|
|
634
|
+
.prepare("SELECT file_path, stash_dir, entry_json, item_ref, bundle_id, concept_id FROM entries WHERE id = ?")
|
|
635
|
+
.get(id);
|
|
636
|
+
if (!row)
|
|
637
|
+
return undefined;
|
|
638
|
+
// Guard against corrupt JSON
|
|
639
|
+
let entry;
|
|
640
|
+
try {
|
|
641
|
+
entry = JSON.parse(row.entry_json);
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
filePath: row.file_path,
|
|
649
|
+
stashDir: row.stash_dir,
|
|
650
|
+
entry,
|
|
651
|
+
itemRef: row.item_ref,
|
|
652
|
+
bundleId: row.bundle_id,
|
|
653
|
+
conceptId: row.concept_id,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
export function getEntriesByDir(db, dirPath) {
|
|
657
|
+
const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
|
|
658
|
+
return parseEntryRows(rows, "getEntriesByDir");
|
|
659
|
+
}
|
|
660
|
+
/** Return every directory previously indexed for one physical source root. */
|
|
661
|
+
export function getIndexedDirPathsByStashDir(db, stashDir) {
|
|
662
|
+
const rows = db.prepare("SELECT DISTINCT dir_path FROM entries WHERE stash_dir = ?").all(stashDir);
|
|
663
|
+
return rows.map((row) => row.dir_path);
|
|
664
|
+
}
|
|
665
|
+
/** Return every persisted source owner for one physical directory. */
|
|
666
|
+
export function getIndexedStashDirsByDir(db, dirPath) {
|
|
667
|
+
const rows = db.prepare("SELECT DISTINCT stash_dir FROM entries WHERE dir_path = ?").all(dirPath);
|
|
668
|
+
return rows.map((row) => row.stash_dir);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Resolve a single `entries.id` by exact `file_path` (the canonical on-disk
|
|
672
|
+
* path), or `undefined` if no row matches.
|
|
673
|
+
*
|
|
674
|
+
* Lifted verbatim (WS5) from the inline `SELECT id FROM entries WHERE
|
|
675
|
+
* file_path = ? LIMIT 1` in commands/search.ts so all `entries` SQL lives in
|
|
676
|
+
* this module. The result is a plain number materialised before return —
|
|
677
|
+
* nothing lazy crosses a connection boundary.
|
|
678
|
+
*/
|
|
679
|
+
export function getEntryIdByFilePath(db, filePath) {
|
|
680
|
+
const row = db.prepare("SELECT id FROM entries WHERE file_path = ? LIMIT 1").get(filePath);
|
|
681
|
+
return row?.id;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Set of every non-empty `entries.file_path` currently indexed (across all
|
|
685
|
+
* stashes/sources). Used by staleness detection to spot files that exist on
|
|
686
|
+
* disk but were never indexed — a clock-independent signal for newly-added
|
|
687
|
+
* assets that an mtime-vs-builtAt comparison can miss when the two clocks
|
|
688
|
+
* (filesystem vs wall-clock) are skewed within the same millisecond.
|
|
689
|
+
*/
|
|
690
|
+
export function getIndexedFilePaths(db) {
|
|
691
|
+
const rows = db
|
|
692
|
+
.prepare("SELECT DISTINCT file_path FROM entries WHERE file_path IS NOT NULL AND file_path <> ''")
|
|
693
|
+
.all();
|
|
694
|
+
return new Set(rows.map((r) => r.file_path));
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Resolve a single `entries.file_path` by primary key, or `undefined` if no
|
|
698
|
+
* row matches.
|
|
699
|
+
*
|
|
700
|
+
* Lifted verbatim (WS5) from the inline `SELECT file_path FROM entries WHERE
|
|
701
|
+
* id = ?` in commands/feedback-cli.ts. Unlike {@link getEntryById}, this does
|
|
702
|
+
* NOT parse `entry_json`, so a row with corrupt JSON still yields its path —
|
|
703
|
+
* preserving feedback-cli's pre-extraction behaviour byte-for-byte.
|
|
704
|
+
*/
|
|
705
|
+
export function getEntryFilePathById(db, id) {
|
|
706
|
+
const row = db.prepare("SELECT file_path FROM entries WHERE id = ?").get(id);
|
|
707
|
+
return row?.file_path;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Fetch every `(file_path, entry_json)` row whose entry belongs to a given
|
|
711
|
+
* stash root — matched either by exact `stash_dir` OR by `file_path` prefix.
|
|
712
|
+
*
|
|
713
|
+
* Lifted verbatim (WS5) from the inline query in commands/graph.ts'
|
|
714
|
+
* `buildRefByPath`. The full result set is materialised with `.all()` before
|
|
715
|
+
* return so callers can iterate it after the connection closes (WS5
|
|
716
|
+
* connection-lifetime rule). JSON parsing stays with the caller, unchanged.
|
|
717
|
+
*/
|
|
718
|
+
export function getEntryRefRowsForStashRoot(db, stashRoot) {
|
|
719
|
+
return db
|
|
720
|
+
.prepare("SELECT file_path, entry_json FROM entries WHERE stash_dir = ? OR file_path LIKE ?")
|
|
721
|
+
.all(stashRoot, `${stashRoot}%`);
|
|
722
|
+
}
|
|
723
|
+
// ── Indexer-phase helpers (moved from indexer.ts) ────────────────────────────
|
|
724
|
+
/**
|
|
725
|
+
* Upsert a workflow document record for an indexed entry.
|
|
726
|
+
* Persists the parsed workflow AST as JSON alongside a FNV-1a hash of the
|
|
727
|
+
* source content for future incremental fast-paths.
|
|
728
|
+
*/
|
|
729
|
+
export function upsertWorkflowDocument(db, entryId, doc, content) {
|
|
730
|
+
const sourceHash = computeSourceHash(content);
|
|
731
|
+
db.prepare(`INSERT INTO workflow_documents (entry_id, schema_version, document_json, source_path, source_hash, updated_at)
|
|
732
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
733
|
+
ON CONFLICT(entry_id) DO UPDATE SET
|
|
734
|
+
schema_version = excluded.schema_version,
|
|
735
|
+
document_json = excluded.document_json,
|
|
736
|
+
source_path = excluded.source_path,
|
|
737
|
+
source_hash = excluded.source_hash,
|
|
738
|
+
updated_at = excluded.updated_at`).run(entryId, doc.schemaVersion, JSON.stringify(doc), doc.source.path, sourceHash, new Date().toISOString());
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Compute a cheap FNV-1a hash of a buffer for source-identity tracking.
|
|
742
|
+
* Not security-sensitive; used as an incremental fast-path skip key.
|
|
743
|
+
*/
|
|
744
|
+
export function computeSourceHash(content) {
|
|
745
|
+
let hash = 0x811c9dc5;
|
|
746
|
+
for (let i = 0; i < content.length; i++) {
|
|
747
|
+
hash ^= content[i];
|
|
748
|
+
hash = Math.imul(hash, 0x01000193);
|
|
749
|
+
}
|
|
750
|
+
return (hash >>> 0).toString(16);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Return distinct zero-result search queries from the `usage_events` table
|
|
754
|
+
* within the given lookback window.
|
|
755
|
+
*
|
|
756
|
+
* Reads from `usage_events` (event_type = 'search') where the metadata JSON
|
|
757
|
+
* blob contains `resultCount = 0`. The `search_events` table never existed;
|
|
758
|
+
* all errors are caught and an empty array is returned so callers never need
|
|
759
|
+
* to guard against DB schema differences.
|
|
760
|
+
*/
|
|
761
|
+
export function getZeroResultSearches(db, sinceDays = 30) {
|
|
762
|
+
const since = new Date(Date.now() - sinceDays * 24 * 60 * 60 * 1000).toISOString();
|
|
763
|
+
try {
|
|
764
|
+
const rows = db
|
|
765
|
+
.prepare(`SELECT DISTINCT json_extract(metadata, '$.query') AS query
|
|
766
|
+
FROM usage_events
|
|
767
|
+
WHERE event_type = 'search'
|
|
768
|
+
AND created_at >= ?
|
|
769
|
+
AND json_extract(metadata, '$.resultCount') = 0
|
|
770
|
+
ORDER BY created_at DESC LIMIT 20`)
|
|
771
|
+
.all(since);
|
|
772
|
+
return rows.map((r) => r.query).filter((q) => q !== null);
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
return []; // table may not exist in older DBs
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Look up an entry `id` by a new-grammar `[bundle//]conceptId` ref, resolving
|
|
780
|
+
* against the canonical stored `item_ref`. Returns `{ id }` or `null`.
|
|
781
|
+
*/
|
|
782
|
+
export function getEntryByRef(db, ref) {
|
|
783
|
+
const id = findEntryIdByRef(db, ref);
|
|
784
|
+
return id === undefined ? null : { id };
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* The fully-qualified `item_ref` (`<bundle>//<conceptId>`, the durable stored
|
|
788
|
+
* spelling — spec §11.1 D-R3) for an entry `id`, or `null` when the row is gone
|
|
789
|
+
* or its `item_ref` is still NULL (a write-back straggler, healed on the next
|
|
790
|
+
* full index). The usage-event / salience / feedback writers derive the durable
|
|
791
|
+
* key from this so a stored key is always the resolved entry's canonical ref,
|
|
792
|
+
* never raw input (D-R3: durable keys are never derived from input).
|
|
793
|
+
*/
|
|
794
|
+
export function getItemRefById(db, id) {
|
|
795
|
+
const row = db.prepare("SELECT item_ref FROM entries WHERE id = ?").get(id);
|
|
796
|
+
return row?.item_ref ?? null;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Resolve a `usage_events.entry_ref` to its live `entries.id`. `entry_ref` is
|
|
800
|
+
* the fully-qualified `bundle//conceptId` `item_ref` spelling, so this keys
|
|
801
|
+
* directly on the globally-unique `item_ref`. Bare durable refs are invalid and
|
|
802
|
+
* remain detached.
|
|
803
|
+
*/
|
|
804
|
+
function resolveUsageEventEntryId(db, ref) {
|
|
805
|
+
if (parseBundleRef(ref).bundle === undefined)
|
|
806
|
+
return undefined;
|
|
807
|
+
return findEntryIdByRef(db, ref);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Re-link detached usage_events to their current entry_ids via entry_ref.
|
|
811
|
+
*
|
|
812
|
+
* After a full rebuild, entry IDs change. This restores each event's link
|
|
813
|
+
* using the stable `entry_ref` column so usage history survives a reindex.
|
|
814
|
+
*
|
|
815
|
+
* Cross-DB (Chunk-8 WI-8.3): `usage_events` lives in `stateDb` while `entries`
|
|
816
|
+
* lives in `indexDb`. The stale-id null-out (formerly a single-DB
|
|
817
|
+
* `NOT IN (SELECT id FROM entries)`) is done in two bounded passes — the set of
|
|
818
|
+
* distinct linked entry_ids in usage_events is small — and the re-resolution
|
|
819
|
+
* reads `entries` from `indexDb`.
|
|
820
|
+
*/
|
|
821
|
+
export function relinkUsageEvents(indexDb, stateDb, _options = {}) {
|
|
822
|
+
bestEffort(() => {
|
|
823
|
+
// Step 1: null out stale entry_ids (entry was deleted, re-keyed, etc).
|
|
824
|
+
// Leaving them in place would let `recomputeUtilityScores` aggregate by an
|
|
825
|
+
// entry_id that no longer exists in `entries`, then trip the FK constraint
|
|
826
|
+
// on the utility_scores INSERT and roll back the entire finalize
|
|
827
|
+
// transaction. Nulled rows can be re-resolved by step 2 below; events whose
|
|
828
|
+
// entry is permanently gone simply stay null and age out via retention.
|
|
829
|
+
const linkedIds = stateDb.prepare("SELECT DISTINCT entry_id AS id FROM usage_events WHERE entry_id IS NOT NULL").all().map((r) => r.id);
|
|
830
|
+
const entryExists = indexDb.prepare("SELECT 1 FROM entries WHERE id = ?");
|
|
831
|
+
const staleIds = linkedIds.filter((id) => entryExists.get(id) == null);
|
|
832
|
+
if (staleIds.length > 0) {
|
|
833
|
+
const nullOut = stateDb.prepare("UPDATE usage_events SET entry_id = NULL WHERE entry_id = ?");
|
|
834
|
+
const nullTx = stateDb.transaction(() => {
|
|
835
|
+
for (const id of staleIds)
|
|
836
|
+
nullOut.run(id);
|
|
837
|
+
});
|
|
838
|
+
nullTx();
|
|
839
|
+
}
|
|
840
|
+
// Step 2: re-resolve each fully-qualified ref. Bare rows are not current
|
|
841
|
+
// durable identities and remain detached.
|
|
842
|
+
const refs = stateDb
|
|
843
|
+
.prepare("SELECT DISTINCT entry_ref AS ref FROM usage_events WHERE entry_id IS NULL AND entry_ref IS NOT NULL")
|
|
844
|
+
.all();
|
|
845
|
+
const update = stateDb.prepare("UPDATE usage_events SET entry_id = ? WHERE entry_ref = ? AND entry_id IS NULL");
|
|
846
|
+
const relinkTx = stateDb.transaction(() => {
|
|
847
|
+
for (const { ref } of refs) {
|
|
848
|
+
let id;
|
|
849
|
+
try {
|
|
850
|
+
id = resolveUsageEventEntryId(indexDb, ref);
|
|
851
|
+
}
|
|
852
|
+
catch (err) {
|
|
853
|
+
if (err instanceof Error && err.name === "UsageError")
|
|
854
|
+
continue;
|
|
855
|
+
throw err;
|
|
856
|
+
}
|
|
857
|
+
if (id !== undefined)
|
|
858
|
+
update.run(id, ref);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
relinkTx();
|
|
862
|
+
}, "usage_events table may not exist yet during entry_id re-resolution");
|
|
863
|
+
}
|