akm-cli 0.9.0-rc.8 → 0.9.0
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 +1063 -44
- package/README.md +51 -25
- package/SECURITY.md +14 -1
- package/STABILITY.md +497 -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 +73 -88
- 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 +141 -87
- 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/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 +3 -48
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +99 -28
- 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 +6 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +105 -57
- 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 +128 -91
- 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 +6 -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/salience.js +11 -122
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +20 -124
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +249 -43
- 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 +701 -278
- 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 +18 -5
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +382 -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 +276 -421
- package/dist/commands/workflow-cli.js +175 -450
- package/dist/core/adapter/adapters/akm-adapter.js +47 -28
- package/dist/core/adapter/adapters/akm-lint.js +42 -27
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
- 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 +8 -3
- 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 +37 -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 +152 -38
- 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 +3 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +18 -12
- package/dist/core/events.js +46 -128
- package/dist/core/file-change.js +6 -5
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +1 -100
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/migration-operation.js +16 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +34 -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.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +6 -4
- 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-source.js +771 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +0 -24
- package/dist/indexer/ensure-index.js +1 -0
- 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 +16 -22
- package/dist/indexer/indexer.js +497 -239
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +11 -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 +99 -54
- 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/profiles.js +14 -0
- 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 +5 -6
- 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 +101 -6
- 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 +43 -0
- package/dist/output/text/{save.js → lint.js} +2 -2
- 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/sync.js +5 -0
- 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 +60290 -0
- package/dist/scripts/akm-migrate.js +59628 -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 +395 -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 +566 -268
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +7 -0
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +2 -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 +17 -6
- package/dist/storage/repositories/index-entries-repository.js +151 -240
- 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 +52 -40
- 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/parser.js +7 -157
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +79 -42
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +24 -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/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +77 -57
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +133 -79
- package/dist/workflows/exec/step-work.js +219 -346
- package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
- package/dist/workflows/ir/compile.js +141 -270
- package/dist/workflows/ir/freeze.js +40 -30
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +25 -26
- package/dist/workflows/parser.js +872 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +7 -10
- package/dist/workflows/renderer.js +95 -68
- package/dist/workflows/resource-limits.js +2 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +186 -127
- 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 +30 -36
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +68 -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 +208 -27
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +564 -208
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/cli.md +2253 -0
- package/docs/reference/configuration.md +358 -0
- package/docs/reference/data-and-telemetry.md +105 -42
- package/docs/reference/workflows.md +647 -0
- package/package.json +22 -11
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +81 -128
- package/schemas/akm-workflow.json +74 -73
- 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 -1806
- 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 -1234
- 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 -223
- package/dist/migrate/legacy/content-migration.js +0 -305
- 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 -72
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
- package/dist/migrate/legacy/three-db-cutover.js +0 -841
- 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
|
@@ -1,3816 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// @bun
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __returnValue = (v) => v;
|
|
6
|
-
function __exportSetter(name, newValue) {
|
|
7
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
8
|
-
}
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, {
|
|
12
|
-
get: all[name],
|
|
13
|
-
enumerable: true,
|
|
14
|
-
configurable: true,
|
|
15
|
-
set: __exportSetter.bind(all, name)
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
19
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
|
-
|
|
21
|
-
// src/core/errors.ts
|
|
22
|
-
function isTestIsolationError(err) {
|
|
23
|
-
return err instanceof ConfigError && err.code === "TEST_ISOLATION_MISSING";
|
|
24
|
-
}
|
|
25
|
-
function rethrowIfTestIsolationError(err) {
|
|
26
|
-
if (isTestIsolationError(err)) {
|
|
27
|
-
throw err;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
var CONFIG_HINTS, AkmError, ConfigError;
|
|
31
|
-
var init_errors = __esm(() => {
|
|
32
|
-
CONFIG_HINTS = {
|
|
33
|
-
STASH_DIR_NOT_FOUND: "Run `akm setup` to create and configure your stash, or set stashDir in your config.",
|
|
34
|
-
STASH_DIR_NOT_A_DIRECTORY: "The configured stashDir exists but isn't a directory. Update stashDir to point at a folder.",
|
|
35
|
-
STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update stashDir.",
|
|
36
|
-
EMBEDDING_NOT_CONFIGURED: 'Run `akm config set embedding \'{"endpoint":"...","model":"..."}\'` to enable embeddings.',
|
|
37
|
-
LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
|
|
38
|
-
TEST_ISOLATION_MISSING: "Under bun test, when AKM_STASH_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
|
|
39
|
-
SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_STASH_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
|
|
40
|
-
UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The stash directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
|
|
41
|
-
UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive."
|
|
42
|
-
};
|
|
43
|
-
AkmError = class AkmError extends Error {
|
|
44
|
-
};
|
|
45
|
-
ConfigError = class ConfigError extends AkmError {
|
|
46
|
-
kind = "config";
|
|
47
|
-
code;
|
|
48
|
-
_hint;
|
|
49
|
-
constructor(msg, code = "INVALID_CONFIG_FILE", hint) {
|
|
50
|
-
super(msg);
|
|
51
|
-
this.name = "ConfigError";
|
|
52
|
-
this.code = code;
|
|
53
|
-
this._hint = hint;
|
|
54
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
55
|
-
}
|
|
56
|
-
hint() {
|
|
57
|
-
return this._hint ?? CONFIG_HINTS[this.code];
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// src/core/platform.ts
|
|
63
|
-
var IS_WINDOWS;
|
|
64
|
-
var init_platform = __esm(() => {
|
|
65
|
-
IS_WINDOWS = process.platform === "win32";
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// src/core/warn.ts
|
|
69
|
-
import fs from "node:fs";
|
|
70
|
-
function isVerbose() {
|
|
71
|
-
const env = process.env.AKM_VERBOSE?.trim().toLowerCase();
|
|
72
|
-
if (env === "1" || env === "true" || env === "yes" || env === "on")
|
|
73
|
-
return true;
|
|
74
|
-
if (env === "0" || env === "false" || env === "no" || env === "off")
|
|
75
|
-
return false;
|
|
76
|
-
return verbose;
|
|
77
|
-
}
|
|
78
|
-
function appendToLogFile(level, args) {
|
|
79
|
-
if (!logFilePath)
|
|
80
|
-
return;
|
|
81
|
-
const ts = new Date().toISOString();
|
|
82
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
83
|
-
try {
|
|
84
|
-
fs.appendFileSync(logFilePath, `[${ts}] [${level}] ${msg}
|
|
85
|
-
`);
|
|
86
|
-
} catch (e) {
|
|
87
|
-
process.stderr.write(`[akm:warn] log-file write failed (${logFilePath}): ${e}
|
|
88
|
-
`);
|
|
89
|
-
process.stderr.write(`[${ts}] [${level}] ${msg}
|
|
90
|
-
`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function warn(...args) {
|
|
94
|
-
if (sinkOverride) {
|
|
95
|
-
sinkOverride("warn", args);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
appendToLogFile("WARN", args);
|
|
99
|
-
if (!quiet) {
|
|
100
|
-
console.warn(...args);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function error(...args) {
|
|
104
|
-
if (sinkOverride) {
|
|
105
|
-
sinkOverride("error", args);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
appendToLogFile("ERROR", args);
|
|
109
|
-
if (!quiet) {
|
|
110
|
-
console.error(...args);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
function warnVerbose(...args) {
|
|
114
|
-
if (sinkOverride) {
|
|
115
|
-
sinkOverride("warnVerbose", args);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
if (isVerbose()) {
|
|
119
|
-
warn(...args);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
var quiet = false, verbose = false, logFilePath, sinkOverride;
|
|
123
|
-
var init_warn = () => {};
|
|
124
|
-
|
|
125
|
-
// src/storage/database.ts
|
|
126
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
127
|
-
function selectProvider() {
|
|
128
|
-
const provider = PROVIDERS.find((p) => p.supported());
|
|
129
|
-
if (!provider) {
|
|
130
|
-
throw new Error(`No storage provider supports the current runtime (${isBun ? "Bun" : "Node"}).`);
|
|
131
|
-
}
|
|
132
|
-
return provider;
|
|
133
|
-
}
|
|
134
|
-
function openDatabase(path2, opts) {
|
|
135
|
-
return selectProvider().open(path2, opts);
|
|
136
|
-
}
|
|
137
|
-
function openBunDatabase(path2, opts) {
|
|
138
|
-
const { Database: BunDatabase } = loadBunSqlite();
|
|
139
|
-
const db = opts ? new BunDatabase(path2, bunOptions(opts)) : new BunDatabase(path2);
|
|
140
|
-
return db;
|
|
141
|
-
}
|
|
142
|
-
function bunOptions(opts) {
|
|
143
|
-
const out = {};
|
|
144
|
-
if (opts.readonly !== undefined)
|
|
145
|
-
out.readonly = opts.readonly;
|
|
146
|
-
if (opts.create !== undefined)
|
|
147
|
-
out.create = opts.create;
|
|
148
|
-
return out;
|
|
149
|
-
}
|
|
150
|
-
function loadBunSqlite() {
|
|
151
|
-
if (!bunSqliteModule) {
|
|
152
|
-
bunSqliteModule = nodeRequire("bun:sqlite");
|
|
153
|
-
}
|
|
154
|
-
return bunSqliteModule;
|
|
155
|
-
}
|
|
156
|
-
function loadBetterSqlite3() {
|
|
157
|
-
if (!betterSqlite3Ctor) {
|
|
158
|
-
let mod;
|
|
159
|
-
try {
|
|
160
|
-
mod = nodeRequire("better-sqlite3");
|
|
161
|
-
} catch (err) {
|
|
162
|
-
throw new Error(`akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.
|
|
163
|
-
` + ` • Reinstall akm with a working C/C++ build toolchain so its optional
|
|
164
|
-
` + " 'better-sqlite3' native binding rebuilds (a global `npm i -g better-sqlite3`\n" + ` will NOT be resolved — Node loads it from akm's own node_modules).
|
|
165
|
-
` + ` • Or run akm under Bun, which has a built-in SQLite driver and needs no native build.
|
|
166
|
-
` + ` Underlying load error: ${err instanceof Error ? err.message : String(err)}`);
|
|
167
|
-
}
|
|
168
|
-
betterSqlite3Ctor = mod.default ?? mod;
|
|
169
|
-
}
|
|
170
|
-
return betterSqlite3Ctor;
|
|
171
|
-
}
|
|
172
|
-
function openNodeDatabase(path2, opts) {
|
|
173
|
-
const BetterSqlite3 = loadBetterSqlite3();
|
|
174
|
-
const options = {};
|
|
175
|
-
if (opts?.readonly !== undefined)
|
|
176
|
-
options.readonly = opts.readonly;
|
|
177
|
-
if (opts?.create === false)
|
|
178
|
-
options.fileMustExist = true;
|
|
179
|
-
const db = opts ? new BetterSqlite3(path2, options) : new BetterSqlite3(path2);
|
|
180
|
-
return {
|
|
181
|
-
prepare: db.prepare.bind(db),
|
|
182
|
-
exec: db.exec.bind(db),
|
|
183
|
-
run: (sql, ...params) => db.prepare(sql).run(...params),
|
|
184
|
-
transaction: db.transaction.bind(db),
|
|
185
|
-
get inTransaction() {
|
|
186
|
-
return db.inTransaction;
|
|
187
|
-
},
|
|
188
|
-
close: db.close.bind(db)
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
var isBun, nodeRequire, bunSqliteProvider, nodeSqliteProvider, PROVIDERS, bunSqliteModule, betterSqlite3Ctor;
|
|
192
|
-
var init_database = __esm(() => {
|
|
193
|
-
isBun = !!process.versions?.bun;
|
|
194
|
-
nodeRequire = createRequire2(import.meta.url);
|
|
195
|
-
bunSqliteProvider = {
|
|
196
|
-
name: "bun:sqlite",
|
|
197
|
-
supported: () => isBun,
|
|
198
|
-
open: openBunDatabase
|
|
199
|
-
};
|
|
200
|
-
nodeSqliteProvider = {
|
|
201
|
-
name: "better-sqlite3",
|
|
202
|
-
supported: () => !isBun,
|
|
203
|
-
open: openNodeDatabase
|
|
204
|
-
};
|
|
205
|
-
PROVIDERS = [bunSqliteProvider, nodeSqliteProvider];
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// src/storage/engines/sqlite-migrations.ts
|
|
209
|
-
import crypto from "node:crypto";
|
|
210
|
-
function migrationChecksum(migration) {
|
|
211
|
-
return crypto.createHash("sha256").update(migration.id).update("\x00").update(migration.up).digest("hex");
|
|
212
|
-
}
|
|
213
|
-
function migrationsTableExists(db) {
|
|
214
|
-
return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
|
|
215
|
-
}
|
|
216
|
-
function ledgerHasChecksum(db) {
|
|
217
|
-
return db.prepare("PRAGMA table_info(schema_migrations)").all().some((column) => column.name === "checksum");
|
|
218
|
-
}
|
|
219
|
-
function inspectLedgerAgainst(db, expected) {
|
|
220
|
-
const registryIds = expected.map((entry) => entry.id);
|
|
221
|
-
if (new Set(registryIds).size !== registryIds.length) {
|
|
222
|
-
return {
|
|
223
|
-
status: "inconsistent",
|
|
224
|
-
migrationIds: [],
|
|
225
|
-
checksums: [],
|
|
226
|
-
detail: "migration registry contains duplicate IDs"
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
if (!migrationsTableExists(db))
|
|
230
|
-
return { status: registryIds.length === 0 ? "current" : "old", migrationIds: [], checksums: [] };
|
|
231
|
-
const hasChecksum = ledgerHasChecksum(db);
|
|
232
|
-
const rows = db.prepare(`SELECT id${hasChecksum ? ", checksum" : ""} FROM schema_migrations ORDER BY rowid`).all();
|
|
233
|
-
const migrationIds = rows.map((row) => row.id);
|
|
234
|
-
const checksums = rows.map((row) => row.checksum ?? null);
|
|
235
|
-
for (let index = 0;index < rows.length; index += 1) {
|
|
236
|
-
const entry = expected[index];
|
|
237
|
-
const row = rows[index];
|
|
238
|
-
if (!entry) {
|
|
239
|
-
return { status: "newer", migrationIds, checksums, detail: `unknown migration ID ${row.id}` };
|
|
240
|
-
}
|
|
241
|
-
if (row.id !== entry.id) {
|
|
242
|
-
const knownLater = registryIds.includes(row.id);
|
|
243
|
-
return {
|
|
244
|
-
status: knownLater ? "inconsistent" : "newer",
|
|
245
|
-
migrationIds,
|
|
246
|
-
checksums,
|
|
247
|
-
detail: knownLater ? `migration ledger is not an exact ordered prefix at position ${index + 1}` : `unknown migration ID ${row.id}`
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
if (row.checksum && row.checksum !== entry.checksum) {
|
|
251
|
-
return {
|
|
252
|
-
status: "inconsistent",
|
|
253
|
-
migrationIds,
|
|
254
|
-
checksums,
|
|
255
|
-
detail: `migration ${row.id} checksum does not match the released migration body`
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const missingChecksum = checksums.indexOf(null);
|
|
260
|
-
if (missingChecksum >= 0) {
|
|
261
|
-
return {
|
|
262
|
-
status: "old",
|
|
263
|
-
migrationIds,
|
|
264
|
-
checksums,
|
|
265
|
-
detail: `migration ${migrationIds[missingChecksum]} has not been sealed with a checksum`
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
status: rows.length === expected.length ? "current" : "old",
|
|
270
|
-
migrationIds,
|
|
271
|
-
checksums
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
function inspectMigrationLedger(db, migrations) {
|
|
275
|
-
return inspectLedgerAgainst(db, migrations.map((migration) => ({ id: migration.id, checksum: migrationChecksum(migration) })));
|
|
276
|
-
}
|
|
277
|
-
function assertMigrationLedger(db, migrations) {
|
|
278
|
-
const state = inspectMigrationLedger(db, migrations);
|
|
279
|
-
if (state.status === "newer") {
|
|
280
|
-
throw new Error(`Refusing to open a database with a newer migration ledger: ${state.detail}.`);
|
|
281
|
-
}
|
|
282
|
-
if (state.status === "inconsistent") {
|
|
283
|
-
throw new Error(`Refusing a database whose migrations are not an exact ordered prefix: ${state.detail}.`);
|
|
284
|
-
}
|
|
285
|
-
return state;
|
|
286
|
-
}
|
|
287
|
-
function assertCurrentMigrationLedger(db, migrations) {
|
|
288
|
-
const state = assertMigrationLedger(db, migrations);
|
|
289
|
-
if (state.status !== "current") {
|
|
290
|
-
throw new Error(`Refusing to open an obsolete writable schema; run \`akm migrate apply\`: ${state.detail ?? "pending migrations"}.`);
|
|
291
|
-
}
|
|
292
|
-
return state;
|
|
293
|
-
}
|
|
294
|
-
function ensureMigrationsTable(db) {
|
|
295
|
-
db.exec(`
|
|
296
|
-
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
297
|
-
id TEXT PRIMARY KEY,
|
|
298
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
299
|
-
checksum TEXT
|
|
300
|
-
);
|
|
301
|
-
`);
|
|
302
|
-
if (!ledgerHasChecksum(db))
|
|
303
|
-
db.exec("ALTER TABLE schema_migrations ADD COLUMN checksum TEXT");
|
|
304
|
-
}
|
|
305
|
-
function runMigrations(db, migrations, opts) {
|
|
306
|
-
if (opts?.applyPending === false) {
|
|
307
|
-
assertMigrationLedger(db, migrations);
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (migrationsTableExists(db))
|
|
311
|
-
assertMigrationLedger(db, migrations);
|
|
312
|
-
ensureMigrationsTable(db);
|
|
313
|
-
opts?.bootstrap?.(db);
|
|
314
|
-
const ledger = assertMigrationLedger(db, migrations);
|
|
315
|
-
db.transaction(() => {
|
|
316
|
-
const update = db.prepare("UPDATE schema_migrations SET checksum = ? WHERE id = ? AND checksum IS NULL");
|
|
317
|
-
for (let index = 0;index < ledger.migrationIds.length; index += 1) {
|
|
318
|
-
update.run(migrationChecksum(migrations[index]), ledger.migrationIds[index]);
|
|
319
|
-
}
|
|
320
|
-
if (opts?.generationMarker) {
|
|
321
|
-
db.exec(`
|
|
322
|
-
CREATE TABLE IF NOT EXISTS akm_migration_generation (
|
|
323
|
-
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
324
|
-
operation_id TEXT NOT NULL,
|
|
325
|
-
phase TEXT NOT NULL
|
|
326
|
-
)
|
|
327
|
-
`);
|
|
328
|
-
db.prepare("INSERT INTO akm_migration_generation(singleton, operation_id, phase) VALUES (1, ?, ?) ON CONFLICT(singleton) DO UPDATE SET operation_id=excluded.operation_id, phase=excluded.phase").run(opts.generationMarker.operationId, opts.generationMarker.phase);
|
|
329
|
-
}
|
|
330
|
-
})();
|
|
331
|
-
const appliedRows = db.prepare("SELECT id FROM schema_migrations ORDER BY rowid").all();
|
|
332
|
-
const applied = new Set(appliedRows.map((r) => r.id));
|
|
333
|
-
for (const migration of migrations) {
|
|
334
|
-
if (applied.has(migration.id))
|
|
335
|
-
continue;
|
|
336
|
-
opts?.beforeMigration?.(migration);
|
|
337
|
-
db.transaction(() => {
|
|
338
|
-
db.exec(migration.up);
|
|
339
|
-
db.prepare("INSERT INTO schema_migrations (id, checksum) VALUES (?, ?)").run(migration.id, migrationChecksum(migration));
|
|
340
|
-
})();
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
var init_sqlite_migrations = () => {};
|
|
344
|
-
|
|
345
|
-
// src/core/paths.ts
|
|
346
|
-
import path2 from "node:path";
|
|
347
|
-
function isUnderBunTest(env) {
|
|
348
|
-
return env.BUN_TEST === "1" || env.NODE_ENV === "test";
|
|
349
|
-
}
|
|
350
|
-
function isTransientStashPath2(p) {
|
|
351
|
-
return p.startsWith("/tmp/") || p === "/tmp" || p.startsWith("/var/tmp/") || p === "/var/tmp" || p.startsWith("/private/tmp/") || p.startsWith("/private/var/folders/") || p.startsWith("/var/folders/");
|
|
352
|
-
}
|
|
353
|
-
function testIsolationError() {
|
|
354
|
-
return new ConfigError("Refusing to resolve data directory under bun test: neither XDG_DATA_HOME nor AKM_DATA_DIR is set. " + "This guards against tests writing into the developer's real ~/.local/share/akm. " + "Set XDG_DATA_HOME (or AKM_DATA_DIR) to a mktemp-d directory in this test's env block.", "TEST_ISOLATION_MISSING");
|
|
355
|
-
}
|
|
356
|
-
function getConfigDir2(env = process.env, platform = process.platform) {
|
|
357
|
-
const override = env.AKM_CONFIG_DIR?.trim();
|
|
358
|
-
if (override)
|
|
359
|
-
return override;
|
|
360
|
-
if (platform === "win32") {
|
|
361
|
-
const appData = env.APPDATA?.trim();
|
|
362
|
-
if (appData)
|
|
363
|
-
return path2.join(appData, "akm");
|
|
364
|
-
} else {
|
|
365
|
-
const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
|
|
366
|
-
if (xdgConfigHome2)
|
|
367
|
-
return path2.join(xdgConfigHome2, "akm");
|
|
368
|
-
}
|
|
369
|
-
const stashOverride = env.AKM_STASH_DIR?.trim();
|
|
370
|
-
if (stashOverride && isTransientStashPath2(stashOverride)) {
|
|
371
|
-
return path2.join(stashOverride, ".akm");
|
|
372
|
-
}
|
|
373
|
-
if (platform === "win32") {
|
|
374
|
-
const appData = env.APPDATA?.trim();
|
|
375
|
-
if (appData)
|
|
376
|
-
return path2.join(appData, "akm");
|
|
377
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
378
|
-
if (!userProfile) {
|
|
379
|
-
throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
|
|
380
|
-
}
|
|
381
|
-
return path2.join(userProfile, "AppData", "Roaming", "akm");
|
|
382
|
-
}
|
|
383
|
-
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
384
|
-
if (xdgConfigHome)
|
|
385
|
-
return path2.join(xdgConfigHome, "akm");
|
|
386
|
-
const home = env.HOME?.trim();
|
|
387
|
-
if (!home) {
|
|
388
|
-
throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
|
|
389
|
-
}
|
|
390
|
-
return path2.join(home, ".config", "akm");
|
|
391
|
-
}
|
|
392
|
-
function getConfigPath(env = process.env) {
|
|
393
|
-
return path2.join(getConfigDir2(env), "config.json");
|
|
394
|
-
}
|
|
395
|
-
function getDataDir(env = process.env, platform = process.platform) {
|
|
396
|
-
const override = env.AKM_DATA_DIR?.trim();
|
|
397
|
-
if (override)
|
|
398
|
-
return override;
|
|
399
|
-
if (isUnderBunTest(env) && !env.XDG_DATA_HOME?.trim()) {
|
|
400
|
-
throw testIsolationError();
|
|
401
|
-
}
|
|
402
|
-
if (platform === "win32") {
|
|
403
|
-
const localAppData = env.LOCALAPPDATA?.trim();
|
|
404
|
-
if (localAppData)
|
|
405
|
-
return path2.join(localAppData, "akm", "data");
|
|
406
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
407
|
-
if (userProfile)
|
|
408
|
-
return path2.join(userProfile, "AppData", "Local", "akm", "data");
|
|
409
|
-
const appData = env.APPDATA?.trim();
|
|
410
|
-
if (!appData) {
|
|
411
|
-
throw new ConfigError("Unable to determine data directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
|
|
412
|
-
}
|
|
413
|
-
return path2.join(appData, "..", "Local", "akm", "data");
|
|
414
|
-
}
|
|
415
|
-
const xdgDataHome = env.XDG_DATA_HOME?.trim();
|
|
416
|
-
if (xdgDataHome)
|
|
417
|
-
return path2.join(xdgDataHome, "akm");
|
|
418
|
-
const home = env.HOME?.trim();
|
|
419
|
-
if (!home)
|
|
420
|
-
return path2.join("/tmp", "akm-data");
|
|
421
|
-
return path2.join(home, ".local", "share", "akm");
|
|
422
|
-
}
|
|
423
|
-
function getDbPath(env = process.env) {
|
|
424
|
-
return path2.join(getDataDir(env), "index.db");
|
|
425
|
-
}
|
|
426
|
-
function getMaintenanceBarrierPath() {
|
|
427
|
-
return path2.join(getDataDir(), "maintenance.barrier.lock");
|
|
428
|
-
}
|
|
429
|
-
var init_paths = __esm(() => {
|
|
430
|
-
init_errors();
|
|
431
|
-
init_platform();
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
// src/core/common.ts
|
|
435
|
-
import fs2 from "node:fs";
|
|
436
|
-
function readTextFileDescriptorWithLimit(fd, maxBytes, label = "File", displayPath = "(open file)") {
|
|
437
|
-
const stat = fs2.fstatSync(fd);
|
|
438
|
-
if (!stat.isFile())
|
|
439
|
-
throw new ConfigError(`${label} is not a regular file: ${displayPath}.`, "INVALID_CONFIG_FILE");
|
|
440
|
-
if (stat.size > maxBytes) {
|
|
441
|
-
throw new ConfigError(`${label} exceeds the ${maxBytes}-byte limit: ${displayPath}.`, "INVALID_CONFIG_FILE");
|
|
442
|
-
}
|
|
443
|
-
const buffer = Buffer.allocUnsafe(maxBytes + 1);
|
|
444
|
-
let total = 0;
|
|
445
|
-
while (total <= maxBytes) {
|
|
446
|
-
const bytesRead = fs2.readSync(fd, buffer, total, maxBytes + 1 - total, null);
|
|
447
|
-
if (bytesRead === 0)
|
|
448
|
-
break;
|
|
449
|
-
total += bytesRead;
|
|
450
|
-
}
|
|
451
|
-
if (total > maxBytes) {
|
|
452
|
-
throw new ConfigError(`${label} exceeds the ${maxBytes}-byte limit: ${displayPath}.`, "INVALID_CONFIG_FILE");
|
|
453
|
-
}
|
|
454
|
-
return buffer.subarray(0, total).toString("utf8");
|
|
455
|
-
}
|
|
456
|
-
function isProcessAlive(pid) {
|
|
457
|
-
try {
|
|
458
|
-
process.kill(pid, 0);
|
|
459
|
-
return true;
|
|
460
|
-
} catch {
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
var MAX_CONFIG_FILE_BYTES, MAX_LOCAL_METADATA_BYTES, MAX_LOCK_METADATA_BYTES, DEFAULT_RESPONSE_BYTE_CAP;
|
|
465
|
-
var init_common = __esm(() => {
|
|
466
|
-
init_errors();
|
|
467
|
-
init_paths();
|
|
468
|
-
init_platform();
|
|
469
|
-
MAX_CONFIG_FILE_BYTES = 1024 * 1024;
|
|
470
|
-
MAX_LOCAL_METADATA_BYTES = 1024 * 1024;
|
|
471
|
-
MAX_LOCK_METADATA_BYTES = 64 * 1024;
|
|
472
|
-
DEFAULT_RESPONSE_BYTE_CAP = 10 * 1024 * 1024;
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// src/runtime.ts
|
|
476
|
-
import { createWriteStream, statfsSync } from "node:fs";
|
|
477
|
-
import { createRequire as createRequire3 } from "node:module";
|
|
478
|
-
function statfsType(path3) {
|
|
479
|
-
try {
|
|
480
|
-
return statfsSync(path3).type;
|
|
481
|
-
} catch {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
function bunGlobal() {
|
|
486
|
-
return globalThis.Bun;
|
|
487
|
-
}
|
|
488
|
-
var isBun2, nodeRequire2, MAX_STREAMED_DOWNLOAD_BYTES, STREAMED_DOWNLOAD_BODY_TIMEOUT_MS, mainPath;
|
|
489
|
-
var init_runtime = __esm(() => {
|
|
490
|
-
init_common();
|
|
491
|
-
isBun2 = !!process.versions?.bun;
|
|
492
|
-
nodeRequire2 = createRequire3(import.meta.url);
|
|
493
|
-
MAX_STREAMED_DOWNLOAD_BYTES = 1024 * 1024 * 1024;
|
|
494
|
-
STREAMED_DOWNLOAD_BODY_TIMEOUT_MS = 5 * 60000;
|
|
495
|
-
mainPath = isBun2 ? bunGlobal().main : process.argv[1];
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
// src/storage/sqlite-pragmas.ts
|
|
499
|
-
function resolveJournalMode(raw) {
|
|
500
|
-
if (raw === undefined)
|
|
501
|
-
return "WAL";
|
|
502
|
-
const normalized = raw.trim().toUpperCase();
|
|
503
|
-
if (normalized === "")
|
|
504
|
-
return "WAL";
|
|
505
|
-
if (VALID_MODES.has(normalized)) {
|
|
506
|
-
return normalized;
|
|
507
|
-
}
|
|
508
|
-
warnInvalidJournalModeOnce(raw);
|
|
509
|
-
return "WAL";
|
|
510
|
-
}
|
|
511
|
-
function resolveConfiguredJournalMode(env = process.env) {
|
|
512
|
-
return resolveJournalMode(env.AKM_SQLITE_JOURNAL_MODE);
|
|
513
|
-
}
|
|
514
|
-
function warnInvalidJournalModeOnce(raw) {
|
|
515
|
-
if (warnedInvalid)
|
|
516
|
-
return;
|
|
517
|
-
warnedInvalid = true;
|
|
518
|
-
warn(`[akm] invalid AKM_SQLITE_JOURNAL_MODE=${JSON.stringify(raw)} — using WAL (valid: WAL, DELETE, TRUNCATE)`);
|
|
519
|
-
}
|
|
520
|
-
function isNetworkFilesystem(fsType) {
|
|
521
|
-
if (fsType === undefined)
|
|
522
|
-
return false;
|
|
523
|
-
return NETWORK_FS_MAGICS.has(fsType);
|
|
524
|
-
}
|
|
525
|
-
function applyStandardPragmas(db, opts = {}) {
|
|
526
|
-
let mode = resolveConfiguredJournalMode(opts.env);
|
|
527
|
-
if (mode === "WAL" && opts.dataDir) {
|
|
528
|
-
const probe = opts.fsTypeProbe ?? statfsType;
|
|
529
|
-
if (isNetworkFilesystem(probe(opts.dataDir))) {
|
|
530
|
-
mode = "DELETE";
|
|
531
|
-
warnNetworkFallbackOnce(opts.dataDir);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
db.exec("PRAGMA busy_timeout = 30000");
|
|
535
|
-
db.exec(`PRAGMA journal_mode = ${mode}`);
|
|
536
|
-
if (opts.foreignKeys !== false) {
|
|
537
|
-
db.exec("PRAGMA foreign_keys = ON");
|
|
538
|
-
}
|
|
539
|
-
if (mode !== "WAL") {
|
|
540
|
-
db.exec("PRAGMA synchronous = FULL");
|
|
541
|
-
}
|
|
542
|
-
return mode;
|
|
543
|
-
}
|
|
544
|
-
function warnNetworkFallbackOnce(dataDir) {
|
|
545
|
-
if (warnedNetworkFallback)
|
|
546
|
-
return;
|
|
547
|
-
warnedNetworkFallback = true;
|
|
548
|
-
warn(`[akm] network filesystem detected at ${dataDir} — WAL unsupported, using DELETE journal mode`);
|
|
549
|
-
}
|
|
550
|
-
var VALID_MODES, warnedInvalid = false, warnedNetworkFallback = false, FS_MAGIC_NFS = 26985, FS_MAGIC_SMB = 20859, FS_MAGIC_CIFS = 4283649346, FS_MAGIC_SMB2 = 4266872130, FS_MAGIC_FUSE = 1702057286, NETWORK_FS_MAGICS;
|
|
551
|
-
var init_sqlite_pragmas = __esm(() => {
|
|
552
|
-
init_warn();
|
|
553
|
-
init_runtime();
|
|
554
|
-
VALID_MODES = new Set(["WAL", "DELETE", "TRUNCATE"]);
|
|
555
|
-
NETWORK_FS_MAGICS = new Set([
|
|
556
|
-
FS_MAGIC_NFS,
|
|
557
|
-
FS_MAGIC_SMB,
|
|
558
|
-
FS_MAGIC_CIFS,
|
|
559
|
-
FS_MAGIC_SMB2,
|
|
560
|
-
FS_MAGIC_FUSE
|
|
561
|
-
]);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
// src/storage/managed-db.ts
|
|
565
|
-
import fs3 from "node:fs";
|
|
566
|
-
import path3 from "node:path";
|
|
567
|
-
function openManagedDatabase(spec) {
|
|
568
|
-
const dir = path3.dirname(spec.path);
|
|
569
|
-
if (!fs3.existsSync(dir)) {
|
|
570
|
-
fs3.mkdirSync(dir, { recursive: true });
|
|
571
|
-
}
|
|
572
|
-
const db = openDatabase(spec.path);
|
|
573
|
-
applyStandardPragmas(db, spec.pragmas ?? { dataDir: dir });
|
|
574
|
-
spec.init?.(db);
|
|
575
|
-
return db;
|
|
576
|
-
}
|
|
577
|
-
function withManagedDb(open, fn, opts) {
|
|
578
|
-
if (opts?.borrowed) {
|
|
579
|
-
return fn(opts.borrowed);
|
|
580
|
-
}
|
|
581
|
-
const db = open();
|
|
582
|
-
try {
|
|
583
|
-
return fn(db);
|
|
584
|
-
} finally {
|
|
585
|
-
db.close();
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
async function withManagedDbAsync(open, fn, opts) {
|
|
589
|
-
if (opts?.borrowed) {
|
|
590
|
-
return fn(opts.borrowed);
|
|
591
|
-
}
|
|
592
|
-
const db = open();
|
|
593
|
-
try {
|
|
594
|
-
return await fn(db);
|
|
595
|
-
} finally {
|
|
596
|
-
db.close();
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
var init_managed_db = __esm(() => {
|
|
600
|
-
init_database();
|
|
601
|
-
init_sqlite_pragmas();
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
// src/core/file-lock.ts
|
|
605
|
-
import { randomUUID } from "node:crypto";
|
|
606
|
-
import fs4 from "node:fs";
|
|
607
|
-
import path4 from "node:path";
|
|
608
|
-
function readLockSnapshot(lockPath) {
|
|
609
|
-
let fd;
|
|
610
|
-
try {
|
|
611
|
-
fd = fs4.openSync(lockPath, "r");
|
|
612
|
-
} catch (err) {
|
|
613
|
-
if (err.code === "ENOENT")
|
|
614
|
-
return;
|
|
615
|
-
throw err;
|
|
616
|
-
}
|
|
617
|
-
try {
|
|
618
|
-
const rawContent = readTextFileDescriptorWithLimit(fd, MAX_LOCK_METADATA_BYTES, "Lock metadata", lockPath);
|
|
619
|
-
const stat = fs4.fstatSync(fd);
|
|
620
|
-
return {
|
|
621
|
-
rawContent,
|
|
622
|
-
identity: { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs }
|
|
623
|
-
};
|
|
624
|
-
} finally {
|
|
625
|
-
fs4.closeSync(fd);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
function sameIdentity(left, right) {
|
|
629
|
-
return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
|
|
630
|
-
}
|
|
631
|
-
function operationMutexPath(lockPath) {
|
|
632
|
-
return path4.join(path4.dirname(lockPath), `.${path4.basename(lockPath)}.operations.sensitive`);
|
|
633
|
-
}
|
|
634
|
-
function withLockOperationMutex(lockPath, run) {
|
|
635
|
-
const db = openDatabase(operationMutexPath(lockPath));
|
|
636
|
-
let began = false;
|
|
637
|
-
try {
|
|
638
|
-
db.exec("PRAGMA busy_timeout = 30000");
|
|
639
|
-
for (let attempt = 0;attempt < 5 && !began; attempt += 1) {
|
|
640
|
-
db.exec("BEGIN IMMEDIATE");
|
|
641
|
-
began = db.inTransaction;
|
|
642
|
-
if (!began)
|
|
643
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2 ** attempt);
|
|
644
|
-
}
|
|
645
|
-
if (!began)
|
|
646
|
-
throw new Error(`Could not acquire lock operation mutex for ${lockPath}.`);
|
|
647
|
-
const result = run();
|
|
648
|
-
db.exec("COMMIT");
|
|
649
|
-
began = false;
|
|
650
|
-
return result;
|
|
651
|
-
} catch (error2) {
|
|
652
|
-
if (began && db.inTransaction) {
|
|
653
|
-
try {
|
|
654
|
-
db.exec("ROLLBACK");
|
|
655
|
-
} catch {}
|
|
656
|
-
}
|
|
657
|
-
throw error2;
|
|
658
|
-
} finally {
|
|
659
|
-
db.close();
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
function tryAcquireLockRaw(lockPath, payload) {
|
|
663
|
-
try {
|
|
664
|
-
fs4.writeFileSync(lockPath, payload, { flag: "wx" });
|
|
665
|
-
} catch (err) {
|
|
666
|
-
if (err.code === "EEXIST")
|
|
667
|
-
return;
|
|
668
|
-
throw err;
|
|
669
|
-
}
|
|
670
|
-
let snapshot;
|
|
671
|
-
try {
|
|
672
|
-
snapshot = readLockSnapshot(lockPath);
|
|
673
|
-
} catch (error2) {
|
|
674
|
-
releaseLockRaw(lockPath);
|
|
675
|
-
throw error2;
|
|
676
|
-
}
|
|
677
|
-
if (!snapshot) {
|
|
678
|
-
releaseLockRaw(lockPath);
|
|
679
|
-
throw new Error(`Could not read newly acquired lock at ${lockPath}.`);
|
|
680
|
-
}
|
|
681
|
-
return { lockPath, ...snapshot };
|
|
682
|
-
}
|
|
683
|
-
function releaseLockRaw(lockPath) {
|
|
684
|
-
try {
|
|
685
|
-
fs4.unlinkSync(lockPath);
|
|
686
|
-
} catch {}
|
|
687
|
-
}
|
|
688
|
-
function tryAcquireLockSync(lockPath, payload) {
|
|
689
|
-
return withLockOperationMutex(lockPath, () => tryAcquireLockRaw(lockPath, payload));
|
|
690
|
-
}
|
|
691
|
-
function createLockPayload(metadata = {}) {
|
|
692
|
-
return JSON.stringify({ ...metadata, pid: process.pid, lockId: randomUUID() });
|
|
693
|
-
}
|
|
694
|
-
function probeLock(lockPath, opts) {
|
|
695
|
-
let snapshot;
|
|
696
|
-
try {
|
|
697
|
-
snapshot = readLockSnapshot(lockPath);
|
|
698
|
-
} catch {
|
|
699
|
-
return { state: "stale", reason: "unreadable" };
|
|
700
|
-
}
|
|
701
|
-
if (!snapshot)
|
|
702
|
-
return { state: "absent" };
|
|
703
|
-
const { rawContent, identity } = snapshot;
|
|
704
|
-
const ageMs = Date.now() - identity.mtimeMs;
|
|
705
|
-
const holderPid = extractHolderPid(rawContent);
|
|
706
|
-
if (holderPid === undefined) {
|
|
707
|
-
return { state: "stale", reason: "invalid_pid", ageMs, rawContent, identity };
|
|
708
|
-
}
|
|
709
|
-
if (!isProcessAlive(holderPid)) {
|
|
710
|
-
return { state: "stale", reason: "pid_dead", holderPid, ageMs, rawContent, identity };
|
|
711
|
-
}
|
|
712
|
-
if (opts?.staleAfterMs !== undefined && ageMs > opts.staleAfterMs) {
|
|
713
|
-
return { state: "stale", reason: "age_exceeded", holderPid, ageMs, rawContent, identity };
|
|
714
|
-
}
|
|
715
|
-
return { state: "held", holderPid, ageMs, rawContent, identity };
|
|
716
|
-
}
|
|
717
|
-
function reclaimStaleLock(lockPath, probe, options) {
|
|
718
|
-
if (probe.rawContent === undefined || probe.identity === undefined)
|
|
719
|
-
return false;
|
|
720
|
-
const expectedContent = probe.rawContent;
|
|
721
|
-
const expectedIdentity = probe.identity;
|
|
722
|
-
return withLockOperationMutex(lockPath, () => {
|
|
723
|
-
let current;
|
|
724
|
-
try {
|
|
725
|
-
current = readLockSnapshot(lockPath);
|
|
726
|
-
} catch {
|
|
727
|
-
return false;
|
|
728
|
-
}
|
|
729
|
-
if (!current || current.rawContent !== expectedContent || !sameIdentity(current.identity, expectedIdentity)) {
|
|
730
|
-
return false;
|
|
731
|
-
}
|
|
732
|
-
const quarantinePath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
|
|
733
|
-
try {
|
|
734
|
-
fs4.renameSync(lockPath, quarantinePath);
|
|
735
|
-
} catch (err) {
|
|
736
|
-
if (err.code === "ENOENT")
|
|
737
|
-
return false;
|
|
738
|
-
throw err;
|
|
739
|
-
}
|
|
740
|
-
let quarantined;
|
|
741
|
-
try {
|
|
742
|
-
quarantined = readLockSnapshot(quarantinePath);
|
|
743
|
-
} catch {
|
|
744
|
-
quarantined = undefined;
|
|
745
|
-
}
|
|
746
|
-
if (!quarantined || quarantined.rawContent !== expectedContent || !sameIdentity(quarantined.identity, expectedIdentity)) {
|
|
747
|
-
try {
|
|
748
|
-
fs4.linkSync(quarantinePath, lockPath);
|
|
749
|
-
} catch (err) {
|
|
750
|
-
if (err.code !== "EEXIST")
|
|
751
|
-
throw err;
|
|
752
|
-
}
|
|
753
|
-
releaseLockRaw(quarantinePath);
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
options?.afterQuarantineVerified?.();
|
|
757
|
-
try {
|
|
758
|
-
fs4.unlinkSync(quarantinePath);
|
|
759
|
-
return true;
|
|
760
|
-
} catch (err) {
|
|
761
|
-
if (err.code === "ENOENT")
|
|
762
|
-
return false;
|
|
763
|
-
throw err;
|
|
764
|
-
}
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
function releaseLock(ownership) {
|
|
768
|
-
const { lockPath } = ownership;
|
|
769
|
-
if (!fs4.existsSync(lockPath) && !fs4.existsSync(operationMutexPath(lockPath)))
|
|
770
|
-
return;
|
|
771
|
-
withLockOperationMutex(lockPath, () => {
|
|
772
|
-
let current;
|
|
773
|
-
try {
|
|
774
|
-
current = readLockSnapshot(lockPath);
|
|
775
|
-
} catch {
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
if (current && current.rawContent === ownership.rawContent && sameIdentity(current.identity, ownership.identity)) {
|
|
779
|
-
releaseLockRaw(lockPath);
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
function extractHolderPid(content) {
|
|
784
|
-
const trimmed = content.trim();
|
|
785
|
-
if (!trimmed)
|
|
786
|
-
return;
|
|
787
|
-
if (trimmed.startsWith("{")) {
|
|
788
|
-
try {
|
|
789
|
-
const parsed = JSON.parse(trimmed);
|
|
790
|
-
const pid2 = typeof parsed.pid === "number" ? parsed.pid : Number.NaN;
|
|
791
|
-
return Number.isInteger(pid2) && pid2 > 0 ? pid2 : undefined;
|
|
792
|
-
} catch {
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
const pid = Number.parseInt(trimmed, 10);
|
|
797
|
-
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
798
|
-
}
|
|
799
|
-
var init_file_lock = __esm(() => {
|
|
800
|
-
init_database();
|
|
801
|
-
init_common();
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
// src/core/maintenance-barrier.ts
|
|
805
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
806
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
807
|
-
import fs5 from "node:fs";
|
|
808
|
-
import path5 from "node:path";
|
|
809
|
-
function tryAcquireMaintenanceBarrier() {
|
|
810
|
-
const lockPath = getMaintenanceBarrierPath();
|
|
811
|
-
fs5.mkdirSync(path5.dirname(lockPath), { recursive: true });
|
|
812
|
-
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
813
|
-
const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
|
|
814
|
-
if (ownership) {
|
|
815
|
-
return () => releaseLock(ownership);
|
|
816
|
-
}
|
|
817
|
-
const probe = probeLock(lockPath);
|
|
818
|
-
if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
function acquireMaintenanceBarrier() {
|
|
824
|
-
const release = tryAcquireMaintenanceBarrier();
|
|
825
|
-
if (release)
|
|
826
|
-
return release;
|
|
827
|
-
throw new ConfigError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry after it completes.`, "INVALID_CONFIG_FILE");
|
|
828
|
-
}
|
|
829
|
-
function withMaintenanceStartBarrierSyncWait(run) {
|
|
830
|
-
if (heldBarrierContext.getStore()?.active)
|
|
831
|
-
return run();
|
|
832
|
-
const deadline = Date.now() + 5000;
|
|
833
|
-
let release = tryAcquireMaintenanceBarrier();
|
|
834
|
-
while (!release && Date.now() < deadline) {
|
|
835
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
|
|
836
|
-
release = tryAcquireMaintenanceBarrier();
|
|
837
|
-
}
|
|
838
|
-
if (!release)
|
|
839
|
-
release = acquireMaintenanceBarrier();
|
|
840
|
-
const ownership = { active: true };
|
|
841
|
-
try {
|
|
842
|
-
return heldBarrierContext.run(ownership, run);
|
|
843
|
-
} finally {
|
|
844
|
-
ownership.active = false;
|
|
845
|
-
release();
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function acquireMaintenanceActivitySync(name) {
|
|
849
|
-
return withMaintenanceStartBarrierSyncWait(() => {
|
|
850
|
-
const directory = path5.join(path5.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
|
|
851
|
-
fs5.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
852
|
-
const lockPath = path5.join(directory, `${name}-${process.pid}-${randomUUID2()}.lock`);
|
|
853
|
-
const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
|
|
854
|
-
if (!ownership) {
|
|
855
|
-
throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
|
|
856
|
-
}
|
|
857
|
-
return () => releaseLock(ownership);
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
var heldBarrierContext;
|
|
861
|
-
var init_maintenance_barrier = __esm(() => {
|
|
862
|
-
init_errors();
|
|
863
|
-
init_file_lock();
|
|
864
|
-
init_paths();
|
|
865
|
-
heldBarrierContext = new AsyncLocalStorage;
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
// src/core/migration-operation.ts
|
|
869
|
-
import crypto2 from "node:crypto";
|
|
870
|
-
import fs6 from "node:fs";
|
|
871
|
-
import path6 from "node:path";
|
|
872
|
-
function installationId() {
|
|
873
|
-
return crypto2.createHash("sha256").update(path6.resolve(path6.dirname(getConfigPath()))).update("\x00").update(path6.resolve(getDataDir())).digest("hex").slice(0, 24);
|
|
874
|
-
}
|
|
875
|
-
function getMigrationOperationRoot() {
|
|
876
|
-
return path6.join(getDataDir(), "backups", "migrations", installationId());
|
|
877
|
-
}
|
|
878
|
-
function getMigrationRestoreJournalPath() {
|
|
879
|
-
return path6.join(getMigrationOperationRoot(), "restore-active.json");
|
|
880
|
-
}
|
|
881
|
-
function getMigrationApplyJournalPath() {
|
|
882
|
-
return path6.join(getMigrationOperationRoot(), "apply-active.json");
|
|
883
|
-
}
|
|
884
|
-
function assertNoPendingMigrationOperation() {
|
|
885
|
-
for (const [kind, journalPath] of [
|
|
886
|
-
["restore", getMigrationRestoreJournalPath()],
|
|
887
|
-
["migration apply", getMigrationApplyJournalPath()]
|
|
888
|
-
]) {
|
|
889
|
-
if (fs6.existsSync(journalPath)) {
|
|
890
|
-
throw new ConfigError(`AKM ${kind} recovery is pending at ${journalPath}; refusing canonical config/database access until recovery completes.`, "INVALID_CONFIG_FILE");
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
const hook = afterPendingCheckHook;
|
|
894
|
-
afterPendingCheckHook = undefined;
|
|
895
|
-
hook?.();
|
|
896
|
-
}
|
|
897
|
-
var afterPendingCheckHook;
|
|
898
|
-
var init_migration_operation = __esm(() => {
|
|
899
|
-
init_errors();
|
|
900
|
-
init_paths();
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
// src/core/state/migrations.ts
|
|
904
|
-
function runMigrations2(db, options) {
|
|
905
|
-
runMigrations(db, STATE_MIGRATIONS, {
|
|
906
|
-
applyPending: options?.applyPending,
|
|
907
|
-
generationMarker: options?.generationMarker
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
var STATE_MIGRATIONS;
|
|
911
|
-
var init_migrations = __esm(() => {
|
|
912
|
-
init_sqlite_migrations();
|
|
913
|
-
STATE_MIGRATIONS = [
|
|
914
|
-
{
|
|
915
|
-
id: "001-initial-schema",
|
|
916
|
-
up: `
|
|
917
|
-
-- ── events ──────────────────────────────────────────────────────────────
|
|
918
|
-
--
|
|
919
|
-
-- Replaces events.jsonl. Indexed (query) columns:
|
|
920
|
-
-- id INTEGER PK — monotonic rowid; replaces byte-offset cursor.
|
|
921
|
-
-- Callers store this as "sinceId" for resume.
|
|
922
|
-
-- event_type TEXT — indexed; replaces the type filter in readEvents().
|
|
923
|
-
-- ts TEXT — ISO-8601 UTC ms; indexed for range queries.
|
|
924
|
-
-- ref TEXT — nullable asset ref; indexed for ref-scoped queries.
|
|
925
|
-
--
|
|
926
|
-
-- Extensible (metadata_json) columns:
|
|
927
|
-
-- metadata_json TEXT — JSON object storing all non-indexed payload
|
|
928
|
-
-- fields (tags, any future structured fields).
|
|
929
|
-
-- Maps directly to EventEnvelope.metadata.
|
|
930
|
-
--
|
|
931
|
-
-- schema_version mirrors EventEnvelope.schemaVersion — always 1 for v1
|
|
932
|
-
-- rows. Stored as a column (not in the JSON blob) so future schema
|
|
933
|
-
-- changes can be detected and migrated row-by-row if ever needed.
|
|
934
|
-
--
|
|
935
|
-
-- TTL: rows where ts < NOW() - 90 days can be deleted by a maintenance job.
|
|
936
|
-
-- No automatic deletion occurs here — callers call purgeOldEvents().
|
|
937
|
-
--
|
|
938
|
-
-- ADD COLUMN extension points (future migrations):
|
|
939
|
-
-- ALTER TABLE events ADD COLUMN stash_dir TEXT DEFAULT NULL;
|
|
940
|
-
-- ALTER TABLE events ADD COLUMN correlation_id TEXT DEFAULT NULL;
|
|
941
|
-
-- ALTER TABLE events ADD COLUMN schema_version INTEGER NOT NULL DEFAULT 1;
|
|
942
|
-
--
|
|
943
|
-
CREATE TABLE IF NOT EXISTS events (
|
|
944
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
945
|
-
event_type TEXT NOT NULL,
|
|
946
|
-
ts TEXT NOT NULL,
|
|
947
|
-
ref TEXT,
|
|
948
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
949
|
-
);
|
|
950
|
-
|
|
951
|
-
-- Query patterns supported by these indexes:
|
|
952
|
-
-- SELECT … WHERE event_type = ? → idx_events_type
|
|
953
|
-
-- SELECT … WHERE ref = ? → idx_events_ref
|
|
954
|
-
-- SELECT … WHERE ts >= ? AND ts <= ? → idx_events_ts
|
|
955
|
-
-- SELECT … WHERE event_type = ? AND ref = ? → idx_events_type (prefix scan) + filter
|
|
956
|
-
-- SELECT … WHERE id > ? → PK (rowid) — no extra index needed
|
|
957
|
-
CREATE INDEX IF NOT EXISTS idx_events_type ON events(event_type);
|
|
958
|
-
CREATE INDEX IF NOT EXISTS idx_events_ref ON events(ref);
|
|
959
|
-
CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
|
|
960
|
-
|
|
961
|
-
-- ── proposals ────────────────────────────────────────────────────────────
|
|
962
|
-
--
|
|
963
|
-
-- Replaces per-uuid JSON directories under <stashDir>/.akm/proposals/.
|
|
964
|
-
--
|
|
965
|
-
-- Indexed (query) columns:
|
|
966
|
-
-- id TEXT PK — UUID (crypto.randomUUID()); stable directory name.
|
|
967
|
-
-- stash_dir TEXT — absolute stash root; multi-stash installs need
|
|
968
|
-
-- this to partition proposal lists per stash.
|
|
969
|
-
-- ref TEXT — target asset ref (e.g. "lesson:alpha");
|
|
970
|
-
-- indexed for ref-scoped queue views.
|
|
971
|
-
-- status TEXT — "pending" | "accepted" | "rejected"; indexed
|
|
972
|
-
-- so pending-queue queries are fast.
|
|
973
|
-
-- source TEXT — human-readable origin tag (e.g. "reflect").
|
|
974
|
-
-- created_at TEXT — ISO-8601; used for ORDER BY created_at ASC.
|
|
975
|
-
-- updated_at TEXT — ISO-8601; updated on accept/reject.
|
|
976
|
-
--
|
|
977
|
-
-- Large payload columns (NOT indexed):
|
|
978
|
-
-- content TEXT — full markdown text; the proposal payload body.
|
|
979
|
-
-- frontmatter_json TEXT — JSON of parsed frontmatter (may be NULL when
|
|
980
|
-
-- the content has no frontmatter block).
|
|
981
|
-
--
|
|
982
|
-
-- Extensible (metadata_json) columns:
|
|
983
|
-
-- metadata_json TEXT — JSON object for future proposal fields.
|
|
984
|
-
-- Current fields stored here: sourceRun,
|
|
985
|
-
-- review, confidence, gateDecision (#577),
|
|
986
|
-
-- backupContent, eligibilitySource.
|
|
987
|
-
--
|
|
988
|
-
-- ADD COLUMN extension points (future migrations):
|
|
989
|
-
-- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
|
|
990
|
-
-- ALTER TABLE proposals ADD COLUMN review_outcome TEXT DEFAULT NULL;
|
|
991
|
-
-- ALTER TABLE proposals ADD COLUMN review_reason TEXT DEFAULT NULL;
|
|
992
|
-
-- ALTER TABLE proposals ADD COLUMN review_decided_at TEXT DEFAULT NULL;
|
|
993
|
-
-- ALTER TABLE proposals ADD COLUMN archived INTEGER NOT NULL DEFAULT 0;
|
|
994
|
-
--
|
|
995
|
-
CREATE TABLE IF NOT EXISTS proposals (
|
|
996
|
-
id TEXT PRIMARY KEY,
|
|
997
|
-
stash_dir TEXT NOT NULL,
|
|
998
|
-
ref TEXT NOT NULL,
|
|
999
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
1000
|
-
source TEXT NOT NULL,
|
|
1001
|
-
created_at TEXT NOT NULL,
|
|
1002
|
-
updated_at TEXT NOT NULL,
|
|
1003
|
-
content TEXT NOT NULL DEFAULT '',
|
|
1004
|
-
frontmatter_json TEXT,
|
|
1005
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1006
|
-
);
|
|
1007
|
-
|
|
1008
|
-
-- Query patterns:
|
|
1009
|
-
-- SELECT … WHERE stash_dir = ? AND status = ? → idx_proposals_stash_status
|
|
1010
|
-
-- SELECT … WHERE ref = ? AND status = ? → idx_proposals_ref_status
|
|
1011
|
-
-- SELECT … WHERE id = ? → PK
|
|
1012
|
-
CREATE INDEX IF NOT EXISTS idx_proposals_stash_status
|
|
1013
|
-
ON proposals(stash_dir, status);
|
|
1014
|
-
CREATE INDEX IF NOT EXISTS idx_proposals_ref_status
|
|
1015
|
-
ON proposals(ref, status);
|
|
1016
|
-
|
|
1017
|
-
-- ── task_history ─────────────────────────────────────────────────────────
|
|
1018
|
-
--
|
|
1019
|
-
-- Replaces per-task JSONL files under <cacheDir>/tasks/history/.
|
|
1020
|
-
--
|
|
1021
|
-
-- Indexed (query) columns:
|
|
1022
|
-
-- task_id TEXT PK — stable task identifier string.
|
|
1023
|
-
-- status TEXT — terminal status (e.g. "completed", "failed",
|
|
1024
|
-
-- "cancelled"); indexed for status-scoped queries.
|
|
1025
|
-
-- started_at TEXT — ISO-8601; indexed for time-range queries.
|
|
1026
|
-
-- target_kind TEXT — kind of the target entity (e.g. "issue",
|
|
1027
|
-
-- "workflow", "agent"); indexed for kind-scoped queries.
|
|
1028
|
-
-- target_ref TEXT — stable ref of the target entity; indexed for
|
|
1029
|
-
-- per-target history lookups.
|
|
1030
|
-
--
|
|
1031
|
-
-- Non-indexed time columns:
|
|
1032
|
-
-- completed_at TEXT — ISO-8601 or NULL if still running.
|
|
1033
|
-
-- failed_at TEXT — ISO-8601 or NULL.
|
|
1034
|
-
--
|
|
1035
|
-
-- Non-indexed diagnostic columns:
|
|
1036
|
-
-- log_path TEXT — absolute path to the task log file, if any.
|
|
1037
|
-
--
|
|
1038
|
-
-- Extensible (metadata_json) columns:
|
|
1039
|
-
-- metadata_json TEXT — JSON object for future task fields (exit_code,
|
|
1040
|
-
-- runner, priority, parent_task_id, …).
|
|
1041
|
-
--
|
|
1042
|
-
-- ADD COLUMN extension points (future migrations):
|
|
1043
|
-
-- ALTER TABLE task_history ADD COLUMN exit_code INTEGER DEFAULT NULL;
|
|
1044
|
-
-- ALTER TABLE task_history ADD COLUMN runner TEXT DEFAULT NULL;
|
|
1045
|
-
-- ALTER TABLE task_history ADD COLUMN parent_task_id TEXT DEFAULT NULL;
|
|
1046
|
-
-- ALTER TABLE task_history ADD COLUMN priority INTEGER NOT NULL DEFAULT 0;
|
|
1047
|
-
--
|
|
1048
|
-
CREATE TABLE IF NOT EXISTS task_history (
|
|
1049
|
-
task_id TEXT PRIMARY KEY,
|
|
1050
|
-
status TEXT NOT NULL,
|
|
1051
|
-
started_at TEXT NOT NULL,
|
|
1052
|
-
completed_at TEXT,
|
|
1053
|
-
failed_at TEXT,
|
|
1054
|
-
log_path TEXT,
|
|
1055
|
-
target_kind TEXT,
|
|
1056
|
-
target_ref TEXT,
|
|
1057
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1058
|
-
);
|
|
1059
|
-
|
|
1060
|
-
-- Query patterns:
|
|
1061
|
-
-- SELECT … WHERE task_id = ? → PK
|
|
1062
|
-
-- SELECT … WHERE started_at >= ? AND started_at <= ? → idx_task_history_started
|
|
1063
|
-
-- SELECT … WHERE target_kind = ? AND target_ref = ? → idx_task_history_target
|
|
1064
|
-
-- SELECT … WHERE status = ? → idx_task_history_status
|
|
1065
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_started
|
|
1066
|
-
ON task_history(started_at);
|
|
1067
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_target
|
|
1068
|
-
ON task_history(target_kind, target_ref);
|
|
1069
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_status
|
|
1070
|
-
ON task_history(status);
|
|
1071
|
-
`
|
|
1072
|
-
},
|
|
1073
|
-
{
|
|
1074
|
-
id: "002-task-history-per-run",
|
|
1075
|
-
up: `
|
|
1076
|
-
ALTER TABLE task_history RENAME TO task_history_v1;
|
|
1077
|
-
|
|
1078
|
-
CREATE TABLE task_history (
|
|
1079
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1080
|
-
task_id TEXT NOT NULL,
|
|
1081
|
-
status TEXT NOT NULL,
|
|
1082
|
-
started_at TEXT NOT NULL,
|
|
1083
|
-
completed_at TEXT,
|
|
1084
|
-
failed_at TEXT,
|
|
1085
|
-
log_path TEXT,
|
|
1086
|
-
target_kind TEXT,
|
|
1087
|
-
target_ref TEXT,
|
|
1088
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1089
|
-
);
|
|
1090
|
-
|
|
1091
|
-
INSERT INTO task_history
|
|
1092
|
-
(task_id, status, started_at, completed_at, failed_at,
|
|
1093
|
-
log_path, target_kind, target_ref, metadata_json)
|
|
1094
|
-
SELECT task_id, status, started_at, completed_at, failed_at,
|
|
1095
|
-
log_path, target_kind, target_ref, metadata_json
|
|
1096
|
-
FROM task_history_v1;
|
|
1097
|
-
|
|
1098
|
-
DROP TABLE task_history_v1;
|
|
1099
|
-
|
|
1100
|
-
-- Unique constraint: same task cannot have two runs with the same start time.
|
|
1101
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_history_run
|
|
1102
|
-
ON task_history(task_id, started_at);
|
|
1103
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_task_id
|
|
1104
|
-
ON task_history(task_id);
|
|
1105
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_started
|
|
1106
|
-
ON task_history(started_at);
|
|
1107
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_target
|
|
1108
|
-
ON task_history(target_kind, target_ref);
|
|
1109
|
-
CREATE INDEX IF NOT EXISTS idx_task_history_status
|
|
1110
|
-
ON task_history(status);
|
|
1111
|
-
`
|
|
1112
|
-
},
|
|
1113
|
-
{
|
|
1114
|
-
id: "003-improve-runs",
|
|
1115
|
-
up: `
|
|
1116
|
-
CREATE TABLE IF NOT EXISTS improve_runs (
|
|
1117
|
-
id TEXT PRIMARY KEY,
|
|
1118
|
-
started_at TEXT NOT NULL,
|
|
1119
|
-
completed_at TEXT,
|
|
1120
|
-
stash_dir TEXT NOT NULL,
|
|
1121
|
-
dry_run INTEGER NOT NULL DEFAULT 0,
|
|
1122
|
-
profile TEXT,
|
|
1123
|
-
scope_mode TEXT NOT NULL,
|
|
1124
|
-
scope_value TEXT,
|
|
1125
|
-
guidance TEXT,
|
|
1126
|
-
ok INTEGER NOT NULL,
|
|
1127
|
-
result_json TEXT NOT NULL,
|
|
1128
|
-
metrics_json TEXT,
|
|
1129
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1130
|
-
);
|
|
1131
|
-
|
|
1132
|
-
-- Query patterns supported:
|
|
1133
|
-
-- SELECT … WHERE started_at >= ? AND started_at <= ?
|
|
1134
|
-
-- → idx_improve_runs_started
|
|
1135
|
-
-- SELECT … WHERE dry_run = 0
|
|
1136
|
-
-- → idx_improve_runs_dry_run (productivity audits filter trap)
|
|
1137
|
-
-- SELECT … WHERE stash_dir = ? AND scope_mode = ?
|
|
1138
|
-
-- → idx_improve_runs_stash_scope
|
|
1139
|
-
CREATE INDEX IF NOT EXISTS idx_improve_runs_started
|
|
1140
|
-
ON improve_runs(started_at);
|
|
1141
|
-
CREATE INDEX IF NOT EXISTS idx_improve_runs_dry_run
|
|
1142
|
-
ON improve_runs(dry_run);
|
|
1143
|
-
CREATE INDEX IF NOT EXISTS idx_improve_runs_stash_scope
|
|
1144
|
-
ON improve_runs(stash_dir, scope_mode);
|
|
1145
|
-
`
|
|
1146
|
-
},
|
|
1147
|
-
{
|
|
1148
|
-
id: "004-extract-sessions-seen",
|
|
1149
|
-
up: `
|
|
1150
|
-
CREATE TABLE IF NOT EXISTS extract_sessions_seen (
|
|
1151
|
-
harness TEXT NOT NULL,
|
|
1152
|
-
session_id TEXT NOT NULL,
|
|
1153
|
-
processed_at TEXT NOT NULL,
|
|
1154
|
-
session_ended_at TEXT,
|
|
1155
|
-
outcome TEXT NOT NULL,
|
|
1156
|
-
candidate_count INTEGER NOT NULL DEFAULT 0,
|
|
1157
|
-
proposal_count INTEGER NOT NULL DEFAULT 0,
|
|
1158
|
-
rationale TEXT,
|
|
1159
|
-
source_run TEXT,
|
|
1160
|
-
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
1161
|
-
PRIMARY KEY (harness, session_id)
|
|
1162
|
-
);
|
|
1163
|
-
|
|
1164
|
-
-- Query patterns:
|
|
1165
|
-
-- SELECT … WHERE harness = ? → idx_extract_sessions_harness
|
|
1166
|
-
-- SELECT … WHERE processed_at >= ? → idx_extract_sessions_processed
|
|
1167
|
-
-- SELECT … WHERE harness = ? AND session_id = ? → PK
|
|
1168
|
-
CREATE INDEX IF NOT EXISTS idx_extract_sessions_harness
|
|
1169
|
-
ON extract_sessions_seen(harness);
|
|
1170
|
-
CREATE INDEX IF NOT EXISTS idx_extract_sessions_processed
|
|
1171
|
-
ON extract_sessions_seen(processed_at);
|
|
1172
|
-
`
|
|
1173
|
-
},
|
|
1174
|
-
{
|
|
1175
|
-
id: "005-proposal-fs-imports",
|
|
1176
|
-
up: `
|
|
1177
|
-
CREATE TABLE IF NOT EXISTS proposal_fs_imports (
|
|
1178
|
-
stash_dir TEXT PRIMARY KEY,
|
|
1179
|
-
imported_at TEXT NOT NULL,
|
|
1180
|
-
imported_count INTEGER NOT NULL DEFAULT 0
|
|
1181
|
-
);
|
|
1182
|
-
`
|
|
1183
|
-
},
|
|
1184
|
-
{
|
|
1185
|
-
id: "006-proposals-pending-ref-source",
|
|
1186
|
-
up: `
|
|
1187
|
-
CREATE INDEX IF NOT EXISTS idx_proposals_stash_status_ref_source
|
|
1188
|
-
ON proposals(stash_dir, status, ref, source);
|
|
1189
|
-
`
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
id: "007-consolidation-judged",
|
|
1193
|
-
up: `
|
|
1194
|
-
CREATE TABLE IF NOT EXISTS consolidation_judged (
|
|
1195
|
-
entry_key TEXT PRIMARY KEY,
|
|
1196
|
-
content_hash TEXT NOT NULL,
|
|
1197
|
-
judged_at TEXT NOT NULL,
|
|
1198
|
-
outcome TEXT NOT NULL
|
|
1199
|
-
);
|
|
1200
|
-
`
|
|
1201
|
-
},
|
|
1202
|
-
{
|
|
1203
|
-
id: "008-body-embeddings",
|
|
1204
|
-
up: `
|
|
1205
|
-
CREATE TABLE IF NOT EXISTS body_embeddings (
|
|
1206
|
-
content_hash TEXT PRIMARY KEY,
|
|
1207
|
-
embedding BLOB NOT NULL,
|
|
1208
|
-
model_id TEXT NOT NULL,
|
|
1209
|
-
created_at INTEGER NOT NULL
|
|
1210
|
-
);
|
|
1211
|
-
`
|
|
1212
|
-
},
|
|
1213
|
-
{
|
|
1214
|
-
id: "009-asset-salience",
|
|
1215
|
-
up: `
|
|
1216
|
-
CREATE TABLE IF NOT EXISTS asset_salience (
|
|
1217
|
-
asset_ref TEXT PRIMARY KEY,
|
|
1218
|
-
encoding_salience REAL NOT NULL DEFAULT 0.5,
|
|
1219
|
-
outcome_salience REAL NOT NULL DEFAULT 0.0,
|
|
1220
|
-
retrieval_salience REAL NOT NULL DEFAULT 0.0,
|
|
1221
|
-
rank_score REAL NOT NULL DEFAULT 0.0,
|
|
1222
|
-
consecutive_no_ops INTEGER NOT NULL DEFAULT 0,
|
|
1223
|
-
updated_at INTEGER NOT NULL DEFAULT 0
|
|
1224
|
-
);
|
|
1225
|
-
|
|
1226
|
-
-- Hot path: sort / filter by rank_score for selector queries.
|
|
1227
|
-
CREATE INDEX IF NOT EXISTS idx_asset_salience_rank
|
|
1228
|
-
ON asset_salience(rank_score DESC);
|
|
1229
|
-
`
|
|
1230
|
-
},
|
|
1231
|
-
{
|
|
1232
|
-
id: "010-asset-outcome",
|
|
1233
|
-
up: `
|
|
1234
|
-
CREATE TABLE IF NOT EXISTS asset_outcome (
|
|
1235
|
-
asset_ref TEXT PRIMARY KEY,
|
|
1236
|
-
last_retrieved_at INTEGER NOT NULL DEFAULT 0,
|
|
1237
|
-
retrieval_count INTEGER NOT NULL DEFAULT 0,
|
|
1238
|
-
expected_retrieval_rate REAL NOT NULL DEFAULT 0.0,
|
|
1239
|
-
negative_feedback_count INTEGER NOT NULL DEFAULT 0,
|
|
1240
|
-
accepted_change_count INTEGER NOT NULL DEFAULT 0,
|
|
1241
|
-
review_pressure INTEGER NOT NULL DEFAULT 0,
|
|
1242
|
-
outcome_score REAL NOT NULL DEFAULT 0.0,
|
|
1243
|
-
updated_at INTEGER NOT NULL DEFAULT 0
|
|
1244
|
-
);
|
|
1245
|
-
|
|
1246
|
-
-- Hot path: sort assets by review_pressure DESC for #613 admission.
|
|
1247
|
-
CREATE INDEX IF NOT EXISTS idx_asset_outcome_review_pressure
|
|
1248
|
-
ON asset_outcome(review_pressure DESC);
|
|
1249
|
-
|
|
1250
|
-
-- Secondary: sort by outcome_score DESC for outcomeSalience reads.
|
|
1251
|
-
CREATE INDEX IF NOT EXISTS idx_asset_outcome_score
|
|
1252
|
-
ON asset_outcome(outcome_score DESC);
|
|
1253
|
-
`
|
|
1254
|
-
},
|
|
1255
|
-
{
|
|
1256
|
-
id: "011-asset-salience-homeostatic-demoted-at",
|
|
1257
|
-
up: `
|
|
1258
|
-
ALTER TABLE asset_salience ADD COLUMN homeostatic_demoted_at INTEGER DEFAULT NULL;
|
|
1259
|
-
`
|
|
1260
|
-
},
|
|
1261
|
-
{
|
|
1262
|
-
id: "012-improve-gate-thresholds",
|
|
1263
|
-
up: `
|
|
1264
|
-
CREATE TABLE IF NOT EXISTS improve_gate_thresholds (
|
|
1265
|
-
phase TEXT NOT NULL PRIMARY KEY,
|
|
1266
|
-
threshold INTEGER NOT NULL,
|
|
1267
|
-
updated_at INTEGER NOT NULL
|
|
1268
|
-
);
|
|
1269
|
-
`
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
id: "013-extract-sessions-content-hash",
|
|
1273
|
-
up: `
|
|
1274
|
-
ALTER TABLE extract_sessions_seen ADD COLUMN content_hash TEXT DEFAULT NULL;
|
|
1275
|
-
`
|
|
1276
|
-
},
|
|
1277
|
-
{
|
|
1278
|
-
id: "014-recombine-hypotheses",
|
|
1279
|
-
up: `
|
|
1280
|
-
CREATE TABLE IF NOT EXISTS recombine_hypotheses (
|
|
1281
|
-
hypothesis_ref TEXT PRIMARY KEY,
|
|
1282
|
-
signature TEXT NOT NULL,
|
|
1283
|
-
member_key TEXT NOT NULL,
|
|
1284
|
-
consecutive_count INTEGER NOT NULL DEFAULT 0,
|
|
1285
|
-
first_seen_at TEXT NOT NULL,
|
|
1286
|
-
last_seen_at TEXT NOT NULL,
|
|
1287
|
-
last_run TEXT,
|
|
1288
|
-
promoted_at TEXT,
|
|
1289
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1290
|
-
);
|
|
1291
|
-
CREATE INDEX IF NOT EXISTS idx_recombine_hypotheses_last_seen
|
|
1292
|
-
ON recombine_hypotheses(last_seen_at);
|
|
1293
|
-
`
|
|
1294
|
-
},
|
|
1295
|
-
{
|
|
1296
|
-
id: "015-asset-salience-encoding-source",
|
|
1297
|
-
up: `
|
|
1298
|
-
ALTER TABLE asset_salience ADD COLUMN encoding_source TEXT DEFAULT NULL;
|
|
1299
|
-
`
|
|
1300
|
-
},
|
|
1301
|
-
{
|
|
1302
|
-
id: "016-collapse-churn-detector",
|
|
1303
|
-
up: `
|
|
1304
|
-
CREATE TABLE IF NOT EXISTS canary_queries (
|
|
1305
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1306
|
-
canary_set_id TEXT NOT NULL,
|
|
1307
|
-
anchor_ref TEXT NOT NULL,
|
|
1308
|
-
query TEXT NOT NULL,
|
|
1309
|
-
source TEXT NOT NULL DEFAULT 'auto',
|
|
1310
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
1311
|
-
created_at TEXT NOT NULL
|
|
1312
|
-
);
|
|
1313
|
-
CREATE INDEX IF NOT EXISTS idx_canary_queries_active
|
|
1314
|
-
ON canary_queries(active, canary_set_id);
|
|
1315
|
-
|
|
1316
|
-
CREATE TABLE IF NOT EXISTS improve_cycle_metrics (
|
|
1317
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1318
|
-
run_id TEXT NOT NULL,
|
|
1319
|
-
ts TEXT NOT NULL,
|
|
1320
|
-
pass TEXT NOT NULL,
|
|
1321
|
-
canary_set_id TEXT NOT NULL,
|
|
1322
|
-
mean_recall REAL NOT NULL,
|
|
1323
|
-
mean_ndcg REAL NOT NULL,
|
|
1324
|
-
mean_mrr REAL NOT NULL,
|
|
1325
|
-
canary_ranks_json TEXT NOT NULL,
|
|
1326
|
-
store_total INTEGER NOT NULL,
|
|
1327
|
-
store_by_type_json TEXT NOT NULL,
|
|
1328
|
-
distinct_content_ratio REAL NOT NULL,
|
|
1329
|
-
mean_bigram_diversity REAL NOT NULL,
|
|
1330
|
-
over_generation_count INTEGER NOT NULL,
|
|
1331
|
-
accepted_actions INTEGER NOT NULL,
|
|
1332
|
-
merge_floor_violations INTEGER NOT NULL DEFAULT 0,
|
|
1333
|
-
alerts_json TEXT NOT NULL DEFAULT '[]'
|
|
1334
|
-
);
|
|
1335
|
-
CREATE INDEX IF NOT EXISTS idx_improve_cycle_metrics_ts
|
|
1336
|
-
ON improve_cycle_metrics(ts);
|
|
1337
|
-
`
|
|
1338
|
-
},
|
|
1339
|
-
{
|
|
1340
|
-
id: "017-improve-run-strategy",
|
|
1341
|
-
up: `
|
|
1342
|
-
ALTER TABLE improve_runs ADD COLUMN strategy TEXT;
|
|
1343
|
-
CREATE INDEX IF NOT EXISTS idx_improve_runs_strategy_started ON improve_runs(strategy, started_at);
|
|
1344
|
-
`
|
|
1345
|
-
},
|
|
1346
|
-
{
|
|
1347
|
-
id: "018-drop-dead-lane-schema",
|
|
1348
|
-
up: `
|
|
1349
|
-
DROP INDEX IF EXISTS idx_recombine_hypotheses_last_seen;
|
|
1350
|
-
DROP TABLE IF EXISTS recombine_hypotheses;
|
|
1351
|
-
|
|
1352
|
-
DROP TABLE IF EXISTS consolidation_judged;
|
|
1353
|
-
|
|
1354
|
-
DROP INDEX IF EXISTS idx_asset_outcome_review_pressure;
|
|
1355
|
-
ALTER TABLE asset_outcome DROP COLUMN review_pressure;
|
|
1356
|
-
`
|
|
1357
|
-
},
|
|
1358
|
-
{
|
|
1359
|
-
id: "019-proposal-fingerprints",
|
|
1360
|
-
up: `
|
|
1361
|
-
CREATE TABLE IF NOT EXISTS proposal_fingerprints (
|
|
1362
|
-
stash_dir TEXT NOT NULL,
|
|
1363
|
-
fingerprint TEXT NOT NULL,
|
|
1364
|
-
ref TEXT NOT NULL,
|
|
1365
|
-
source TEXT NOT NULL,
|
|
1366
|
-
model_id TEXT NOT NULL DEFAULT '',
|
|
1367
|
-
proposal_id TEXT,
|
|
1368
|
-
created_at TEXT NOT NULL,
|
|
1369
|
-
PRIMARY KEY (stash_dir, fingerprint)
|
|
1370
|
-
);
|
|
1371
|
-
CREATE INDEX IF NOT EXISTS idx_proposal_fingerprints_ref
|
|
1372
|
-
ON proposal_fingerprints(stash_dir, ref);
|
|
1373
|
-
`
|
|
1374
|
-
},
|
|
1375
|
-
{
|
|
1376
|
-
id: "020-three-db-cutover",
|
|
1377
|
-
up: `
|
|
1378
|
-
-- ── workflow_runs (workflows/db.ts base + migrations 001/002/003/006/010) ──
|
|
1379
|
-
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
1380
|
-
id TEXT PRIMARY KEY,
|
|
1381
|
-
workflow_ref TEXT NOT NULL,
|
|
1382
|
-
workflow_entry_id INTEGER,
|
|
1383
|
-
workflow_title TEXT NOT NULL,
|
|
1384
|
-
status TEXT NOT NULL CHECK (status IN ('active', 'completed', 'blocked', 'failed')),
|
|
1385
|
-
params_json TEXT NOT NULL DEFAULT '{}',
|
|
1386
|
-
current_step_id TEXT,
|
|
1387
|
-
created_at TEXT NOT NULL,
|
|
1388
|
-
updated_at TEXT NOT NULL,
|
|
1389
|
-
completed_at TEXT,
|
|
1390
|
-
scope_key TEXT,
|
|
1391
|
-
agent_harness TEXT,
|
|
1392
|
-
agent_session_id TEXT,
|
|
1393
|
-
checkin_armed_at TEXT,
|
|
1394
|
-
plan_json TEXT,
|
|
1395
|
-
plan_hash TEXT,
|
|
1396
|
-
engine_lease_until TEXT,
|
|
1397
|
-
engine_lease_holder TEXT,
|
|
1398
|
-
plan_ir_version INTEGER
|
|
1399
|
-
);
|
|
1400
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_ref ON workflow_runs(workflow_ref);
|
|
1401
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_status ON workflow_runs(status);
|
|
1402
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_scope_ref_status
|
|
1403
|
-
ON workflow_runs(scope_key, workflow_ref, status);
|
|
1404
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_agent_session
|
|
1405
|
-
ON workflow_runs(agent_harness, agent_session_id);
|
|
1406
|
-
|
|
1407
|
-
-- ── workflow_run_steps (base + migration 003 summary) ─────────────────────
|
|
1408
|
-
CREATE TABLE IF NOT EXISTS workflow_run_steps (
|
|
1409
|
-
run_id TEXT NOT NULL,
|
|
1410
|
-
step_id TEXT NOT NULL,
|
|
1411
|
-
step_title TEXT NOT NULL,
|
|
1412
|
-
instructions TEXT NOT NULL,
|
|
1413
|
-
completion_json TEXT,
|
|
1414
|
-
sequence_index INTEGER NOT NULL,
|
|
1415
|
-
status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'blocked', 'failed', 'skipped')),
|
|
1416
|
-
notes TEXT,
|
|
1417
|
-
evidence_json TEXT,
|
|
1418
|
-
completed_at TEXT,
|
|
1419
|
-
summary TEXT,
|
|
1420
|
-
PRIMARY KEY (run_id, step_id),
|
|
1421
|
-
FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
|
|
1422
|
-
);
|
|
1423
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run_sequence
|
|
1424
|
-
ON workflow_run_steps(run_id, sequence_index);
|
|
1425
|
-
|
|
1426
|
-
-- ── workflow_run_units (migration 004 + 005/007/008/009/010 columns) ──────
|
|
1427
|
-
CREATE TABLE IF NOT EXISTS workflow_run_units (
|
|
1428
|
-
run_id TEXT NOT NULL,
|
|
1429
|
-
unit_id TEXT NOT NULL,
|
|
1430
|
-
step_id TEXT,
|
|
1431
|
-
node_id TEXT NOT NULL,
|
|
1432
|
-
parent_unit_id TEXT,
|
|
1433
|
-
phase TEXT,
|
|
1434
|
-
runner TEXT,
|
|
1435
|
-
model TEXT,
|
|
1436
|
-
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'failed', 'skipped')),
|
|
1437
|
-
input_hash TEXT,
|
|
1438
|
-
result_json TEXT,
|
|
1439
|
-
tokens INTEGER,
|
|
1440
|
-
failure_reason TEXT,
|
|
1441
|
-
worktree_path TEXT,
|
|
1442
|
-
started_at TEXT,
|
|
1443
|
-
finished_at TEXT,
|
|
1444
|
-
session_id TEXT,
|
|
1445
|
-
last_checkin_at TEXT,
|
|
1446
|
-
attempts INTEGER NOT NULL DEFAULT 1,
|
|
1447
|
-
claim_holder TEXT,
|
|
1448
|
-
claim_expires_at TEXT,
|
|
1449
|
-
engine TEXT,
|
|
1450
|
-
PRIMARY KEY (run_id, unit_id),
|
|
1451
|
-
FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
|
|
1452
|
-
);
|
|
1453
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_run_units_run_step
|
|
1454
|
-
ON workflow_run_units(run_id, step_id);
|
|
1455
|
-
|
|
1456
|
-
-- ── usage_events (index.db ensureUsageEventsSchema — the durable new home) ─
|
|
1457
|
-
CREATE TABLE IF NOT EXISTS usage_events (
|
|
1458
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1459
|
-
event_type TEXT NOT NULL,
|
|
1460
|
-
query TEXT,
|
|
1461
|
-
entry_id INTEGER,
|
|
1462
|
-
entry_ref TEXT,
|
|
1463
|
-
signal TEXT,
|
|
1464
|
-
metadata TEXT,
|
|
1465
|
-
source TEXT NOT NULL DEFAULT 'user',
|
|
1466
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1467
|
-
);
|
|
1468
|
-
CREATE INDEX IF NOT EXISTS idx_usage_events_entry ON usage_events(entry_id);
|
|
1469
|
-
CREATE INDEX IF NOT EXISTS idx_usage_events_type ON usage_events(event_type);
|
|
1470
|
-
CREATE INDEX IF NOT EXISTS idx_usage_events_ref ON usage_events(entry_ref);
|
|
1471
|
-
CREATE INDEX IF NOT EXISTS idx_usage_events_source ON usage_events(source);
|
|
1472
|
-
|
|
1473
|
-
-- ── legacy_state (ensureLegacyStateTable — the orphan quarantine archive) ──
|
|
1474
|
-
CREATE TABLE IF NOT EXISTS legacy_state (
|
|
1475
|
-
surface TEXT NOT NULL,
|
|
1476
|
-
old_ref TEXT NOT NULL,
|
|
1477
|
-
row_count INTEGER NOT NULL DEFAULT 0,
|
|
1478
|
-
reason TEXT NOT NULL,
|
|
1479
|
-
quarantined_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1480
|
-
PRIMARY KEY (surface, old_ref)
|
|
1481
|
-
);
|
|
1482
|
-
`
|
|
1483
|
-
}
|
|
1484
|
-
];
|
|
1485
|
-
});
|
|
1486
|
-
|
|
1487
|
-
// src/core/state-db.ts
|
|
1488
|
-
var exports_state_db = {};
|
|
1489
|
-
__export(exports_state_db, {
|
|
1490
|
-
withStateDbTelemetry: () => withStateDbTelemetry,
|
|
1491
|
-
withStateDbAsync: () => withStateDbAsync,
|
|
1492
|
-
withStateDb: () => withStateDb,
|
|
1493
|
-
withImmediateTransaction: () => withImmediateTransaction,
|
|
1494
|
-
openStateDatabase: () => openStateDatabase,
|
|
1495
|
-
listExistingTableNames: () => listExistingTableNames,
|
|
1496
|
-
getStateDbPath: () => getStateDbPath
|
|
1497
|
-
});
|
|
1498
|
-
import fs7 from "node:fs";
|
|
1499
|
-
import path7 from "node:path";
|
|
1500
|
-
function getStateDbPath() {
|
|
1501
|
-
return path7.join(getDataDir(), "state.db");
|
|
1502
|
-
}
|
|
1503
|
-
function openStateDatabase(dbPath) {
|
|
1504
|
-
const canonicalPath = getStateDbPath();
|
|
1505
|
-
const resolvedPath = dbPath ?? canonicalPath;
|
|
1506
|
-
const isCanonical = path7.resolve(resolvedPath) === path7.resolve(canonicalPath);
|
|
1507
|
-
if (isCanonical)
|
|
1508
|
-
assertNoPendingMigrationOperation();
|
|
1509
|
-
const releaseActivity = isCanonical ? acquireMaintenanceActivitySync("state-db") : undefined;
|
|
1510
|
-
try {
|
|
1511
|
-
if (isCanonical)
|
|
1512
|
-
assertNoPendingMigrationOperation();
|
|
1513
|
-
const existed = fs7.existsSync(resolvedPath);
|
|
1514
|
-
if (existed) {
|
|
1515
|
-
const preflight = openDatabase(resolvedPath, { readonly: true });
|
|
1516
|
-
try {
|
|
1517
|
-
preflight.exec("PRAGMA busy_timeout = 30000");
|
|
1518
|
-
if (isCanonical)
|
|
1519
|
-
assertCurrentMigrationLedger(preflight, STATE_MIGRATIONS);
|
|
1520
|
-
else
|
|
1521
|
-
assertMigrationLedger(preflight, STATE_MIGRATIONS);
|
|
1522
|
-
} finally {
|
|
1523
|
-
preflight.close();
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
const db = openManagedDatabase({
|
|
1527
|
-
path: resolvedPath,
|
|
1528
|
-
init: (db2) => runMigrations2(db2, { applyPending: !(isCanonical && existed) })
|
|
1529
|
-
});
|
|
1530
|
-
if (!releaseActivity)
|
|
1531
|
-
return db;
|
|
1532
|
-
let closed = false;
|
|
1533
|
-
return {
|
|
1534
|
-
prepare: db.prepare.bind(db),
|
|
1535
|
-
exec: db.exec.bind(db),
|
|
1536
|
-
run: db.run.bind(db),
|
|
1537
|
-
transaction: db.transaction.bind(db),
|
|
1538
|
-
get inTransaction() {
|
|
1539
|
-
return db.inTransaction;
|
|
1540
|
-
},
|
|
1541
|
-
close() {
|
|
1542
|
-
if (closed)
|
|
1543
|
-
return;
|
|
1544
|
-
closed = true;
|
|
1545
|
-
try {
|
|
1546
|
-
db.close();
|
|
1547
|
-
} finally {
|
|
1548
|
-
releaseActivity();
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
};
|
|
1552
|
-
} catch (error2) {
|
|
1553
|
-
releaseActivity?.();
|
|
1554
|
-
throw error2;
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
function withStateDb(fn, opts) {
|
|
1558
|
-
return withManagedDb(() => openStateDatabase(opts?.path), fn, opts);
|
|
1559
|
-
}
|
|
1560
|
-
function withStateDbAsync(fn, opts) {
|
|
1561
|
-
return withManagedDbAsync(() => openStateDatabase(opts?.path), fn, opts);
|
|
1562
|
-
}
|
|
1563
|
-
function withStateDbTelemetry(fn, busyTimeoutMs = 250) {
|
|
1564
|
-
if (!fs7.existsSync(getStateDbPath()))
|
|
1565
|
-
return;
|
|
1566
|
-
const db = openStateDatabase();
|
|
1567
|
-
try {
|
|
1568
|
-
db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);
|
|
1569
|
-
fn(db);
|
|
1570
|
-
} finally {
|
|
1571
|
-
db.close();
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
function isRetryableBeginError(err) {
|
|
1575
|
-
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
1576
|
-
return msg.includes("database is locked") || msg.includes("database table is locked") || msg.includes("did not open a transaction");
|
|
1577
|
-
}
|
|
1578
|
-
function sleepSyncMs(ms) {
|
|
1579
|
-
if (ms <= 0)
|
|
1580
|
-
return;
|
|
1581
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1582
|
-
}
|
|
1583
|
-
function withImmediateTransaction(db, fn) {
|
|
1584
|
-
if (db.inTransaction) {
|
|
1585
|
-
return fn();
|
|
1586
|
-
}
|
|
1587
|
-
let lastBeginErr;
|
|
1588
|
-
for (let attempt = 1;attempt <= WITH_IMMEDIATE_TX_MAX_ATTEMPTS; attempt++) {
|
|
1589
|
-
try {
|
|
1590
|
-
db.exec("BEGIN IMMEDIATE");
|
|
1591
|
-
if (!db.inTransaction) {
|
|
1592
|
-
throw new Error("BEGIN IMMEDIATE did not open a transaction (phantom contention state)");
|
|
1593
|
-
}
|
|
1594
|
-
} catch (err) {
|
|
1595
|
-
lastBeginErr = err;
|
|
1596
|
-
if (isRetryableBeginError(err) && attempt < WITH_IMMEDIATE_TX_MAX_ATTEMPTS) {
|
|
1597
|
-
if (db.inTransaction) {
|
|
1598
|
-
try {
|
|
1599
|
-
db.exec("ROLLBACK");
|
|
1600
|
-
} catch {}
|
|
1601
|
-
}
|
|
1602
|
-
sleepSyncMs(2 ** (attempt - 1));
|
|
1603
|
-
continue;
|
|
1604
|
-
}
|
|
1605
|
-
throw err;
|
|
1606
|
-
}
|
|
1607
|
-
try {
|
|
1608
|
-
const result = fn();
|
|
1609
|
-
if (!db.inTransaction) {
|
|
1610
|
-
throw new Error("withImmediateTransaction invariant violated: transaction opened by BEGIN IMMEDIATE was no longer active after the transaction body ran; refusing to COMMIT (writes may have escaped serialization)");
|
|
1611
|
-
}
|
|
1612
|
-
db.exec("COMMIT");
|
|
1613
|
-
return result;
|
|
1614
|
-
} catch (err) {
|
|
1615
|
-
if (db.inTransaction) {
|
|
1616
|
-
try {
|
|
1617
|
-
db.exec("ROLLBACK");
|
|
1618
|
-
} catch {}
|
|
1619
|
-
}
|
|
1620
|
-
throw err;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
throw lastBeginErr;
|
|
1624
|
-
}
|
|
1625
|
-
function listExistingTableNames(db, names) {
|
|
1626
|
-
if (names.length === 0)
|
|
1627
|
-
return [];
|
|
1628
|
-
const placeholders = names.map(() => "?").join(", ");
|
|
1629
|
-
return db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders}) ORDER BY name`).all(...names);
|
|
1630
|
-
}
|
|
1631
|
-
var WITH_IMMEDIATE_TX_MAX_ATTEMPTS = 5;
|
|
1632
|
-
var init_state_db = __esm(() => {
|
|
1633
|
-
init_database();
|
|
1634
|
-
init_sqlite_migrations();
|
|
1635
|
-
init_managed_db();
|
|
1636
|
-
init_maintenance_barrier();
|
|
1637
|
-
init_migration_operation();
|
|
1638
|
-
init_paths();
|
|
1639
|
-
init_migrations();
|
|
1640
|
-
});
|
|
1641
|
-
|
|
1642
|
-
// src/storage/repositories/events-repository.ts
|
|
1643
|
-
var exports_events_repository = {};
|
|
1644
|
-
__export(exports_events_repository, {
|
|
1645
|
-
readStateEvents: () => readStateEvents,
|
|
1646
|
-
purgeOldEvents: () => purgeOldEvents,
|
|
1647
|
-
insertEventOnce: () => insertEventOnce,
|
|
1648
|
-
insertEvent: () => insertEvent,
|
|
1649
|
-
importEventsJsonl: () => importEventsJsonl,
|
|
1650
|
-
eventRowToEnvelope: () => eventRowToEnvelope
|
|
1651
|
-
});
|
|
1652
|
-
function eventRowToEnvelope(row) {
|
|
1653
|
-
let metadata;
|
|
1654
|
-
try {
|
|
1655
|
-
const parsed = JSON.parse(row.metadata_json);
|
|
1656
|
-
if (Object.keys(parsed).length > 0) {
|
|
1657
|
-
metadata = parsed;
|
|
1658
|
-
}
|
|
1659
|
-
} catch {}
|
|
1660
|
-
return {
|
|
1661
|
-
schemaVersion: 1,
|
|
1662
|
-
id: row.id,
|
|
1663
|
-
ts: row.ts,
|
|
1664
|
-
eventType: row.event_type,
|
|
1665
|
-
...row.ref !== null ? { ref: row.ref } : {},
|
|
1666
|
-
...metadata !== undefined ? { metadata } : {}
|
|
1667
|
-
};
|
|
1668
|
-
}
|
|
1669
|
-
function insertEvent(db, input) {
|
|
1670
|
-
try {
|
|
1671
|
-
const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
1672
|
-
VALUES (?, ?, ?, ?)
|
|
1673
|
-
RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata ?? {}));
|
|
1674
|
-
return result?.id;
|
|
1675
|
-
} catch (err) {
|
|
1676
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1677
|
-
error(`akm: state.db event insert failed (${message})`);
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
function insertEventOnce(db, input) {
|
|
1682
|
-
const rows = db.prepare("SELECT id, metadata_json FROM events WHERE event_type = ? AND ref IS ? ORDER BY id").all(input.eventType, input.ref ?? null);
|
|
1683
|
-
for (const row of rows) {
|
|
1684
|
-
try {
|
|
1685
|
-
const metadata = JSON.parse(row.metadata_json);
|
|
1686
|
-
if (metadata[input.idempotencyMetadataKey ?? "proposalTransactionId"] === input.idempotencyKey)
|
|
1687
|
-
return row.id;
|
|
1688
|
-
} catch {}
|
|
1689
|
-
}
|
|
1690
|
-
const result = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
1691
|
-
VALUES (?, ?, ?, ?)
|
|
1692
|
-
RETURNING id`).get(input.eventType, input.ts, input.ref ?? null, JSON.stringify(input.metadata));
|
|
1693
|
-
if (!result)
|
|
1694
|
-
throw new Error(`Failed to persist ${input.eventType} event.`);
|
|
1695
|
-
return result.id;
|
|
1696
|
-
}
|
|
1697
|
-
function readStateEvents(db, options = {}) {
|
|
1698
|
-
const conditions = [];
|
|
1699
|
-
const params = [];
|
|
1700
|
-
if (options.sinceId !== undefined && options.sinceId > 0) {
|
|
1701
|
-
conditions.push("id > ?");
|
|
1702
|
-
params.push(options.sinceId);
|
|
1703
|
-
}
|
|
1704
|
-
if (options.since) {
|
|
1705
|
-
conditions.push("ts >= ?");
|
|
1706
|
-
params.push(options.since);
|
|
1707
|
-
}
|
|
1708
|
-
if (options.type) {
|
|
1709
|
-
conditions.push("event_type = ?");
|
|
1710
|
-
params.push(options.type);
|
|
1711
|
-
}
|
|
1712
|
-
if (options.ref) {
|
|
1713
|
-
conditions.push("ref = ?");
|
|
1714
|
-
params.push(options.ref);
|
|
1715
|
-
}
|
|
1716
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1717
|
-
const rows = db.prepare(`SELECT id, event_type, ts, ref, metadata_json FROM events ${where} ORDER BY id ASC`).all(...params);
|
|
1718
|
-
const events = rows.map(eventRowToEnvelope);
|
|
1719
|
-
const nextId = events.length > 0 ? events[events.length - 1].id : options.sinceId ?? 0;
|
|
1720
|
-
return { events, nextId };
|
|
1721
|
-
}
|
|
1722
|
-
function purgeOldEvents(db, retentionDays = 90) {
|
|
1723
|
-
if (!Number.isFinite(retentionDays) || retentionDays <= 0)
|
|
1724
|
-
return 0;
|
|
1725
|
-
const cutoff = new Date(Date.now() - retentionDays * 86400000).toISOString();
|
|
1726
|
-
const result = db.prepare("DELETE FROM events WHERE ts < ?").run(cutoff);
|
|
1727
|
-
const changes = result.changes ?? 0;
|
|
1728
|
-
return typeof changes === "bigint" ? Number(changes) : changes;
|
|
1729
|
-
}
|
|
1730
|
-
async function importEventsJsonl(db, jsonlPath) {
|
|
1731
|
-
const { readFileSync, existsSync } = await import("node:fs");
|
|
1732
|
-
if (!existsSync(jsonlPath)) {
|
|
1733
|
-
return { imported: 0, maxId: 0, skipped: 0 };
|
|
1734
|
-
}
|
|
1735
|
-
const text = readFileSync(jsonlPath, "utf8");
|
|
1736
|
-
const lines = text.split(`
|
|
1737
|
-
`).filter((l) => l.trim().length > 0);
|
|
1738
|
-
let imported = 0;
|
|
1739
|
-
let maxId = 0;
|
|
1740
|
-
let skipped = 0;
|
|
1741
|
-
const insertStmt = db.prepare(`INSERT INTO events (event_type, ts, ref, metadata_json)
|
|
1742
|
-
VALUES (?, ?, ?, ?)
|
|
1743
|
-
RETURNING id`);
|
|
1744
|
-
const existsStmt = db.prepare(`SELECT 1 FROM events
|
|
1745
|
-
WHERE event_type = ?
|
|
1746
|
-
AND ts = ?
|
|
1747
|
-
AND ref IS ?
|
|
1748
|
-
AND metadata_json = ?
|
|
1749
|
-
LIMIT 1`);
|
|
1750
|
-
db.transaction(() => {
|
|
1751
|
-
for (const line of lines) {
|
|
1752
|
-
let parsed;
|
|
1753
|
-
try {
|
|
1754
|
-
parsed = JSON.parse(line);
|
|
1755
|
-
} catch {
|
|
1756
|
-
continue;
|
|
1757
|
-
}
|
|
1758
|
-
const eventType = typeof parsed.eventType === "string" ? parsed.eventType : "unknown";
|
|
1759
|
-
const ts = typeof parsed.ts === "string" ? parsed.ts : new Date().toISOString();
|
|
1760
|
-
const ref = typeof parsed.ref === "string" ? parsed.ref : null;
|
|
1761
|
-
const metadata = parsed.metadata !== undefined && typeof parsed.metadata === "object" ? JSON.stringify(parsed.metadata) : "{}";
|
|
1762
|
-
const duplicate = existsStmt.get(eventType, ts, ref, metadata);
|
|
1763
|
-
if (duplicate) {
|
|
1764
|
-
skipped++;
|
|
1765
|
-
continue;
|
|
1766
|
-
}
|
|
1767
|
-
const result = insertStmt.get(eventType, ts, ref, metadata);
|
|
1768
|
-
if (result) {
|
|
1769
|
-
imported++;
|
|
1770
|
-
if (result.id > maxId)
|
|
1771
|
-
maxId = result.id;
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
})();
|
|
1775
|
-
return { imported, maxId, skipped };
|
|
1776
|
-
}
|
|
1777
|
-
var init_events_repository = __esm(() => {
|
|
1778
|
-
init_warn();
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
// src/storage/repositories/task-history-repository.ts
|
|
1782
|
-
var exports_task_history_repository = {};
|
|
1783
|
-
__export(exports_task_history_repository, {
|
|
1784
|
-
upsertTaskHistory: () => upsertTaskHistory,
|
|
1785
|
-
reserveTaskHistoryAttempt: () => reserveTaskHistoryAttempt,
|
|
1786
|
-
queryTaskHistory: () => queryTaskHistory,
|
|
1787
|
-
queryCompletedTaskIntervals: () => queryCompletedTaskIntervals,
|
|
1788
|
-
getTaskHistoryRuns: () => getTaskHistoryRuns,
|
|
1789
|
-
getTaskHistory: () => getTaskHistory,
|
|
1790
|
-
finalizeTaskHistoryAttempt: () => finalizeTaskHistoryAttempt,
|
|
1791
|
-
decodeTaskHistoryMetadata: () => decodeTaskHistoryMetadata
|
|
1792
|
-
});
|
|
1793
|
-
function metadataError(message) {
|
|
1794
|
-
throw new Error(`invalid task_history metadata_json: ${message}`);
|
|
1795
|
-
}
|
|
1796
|
-
function isRecord(value) {
|
|
1797
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1798
|
-
}
|
|
1799
|
-
function validateDetail(value) {
|
|
1800
|
-
if (value === undefined || value === null)
|
|
1801
|
-
return;
|
|
1802
|
-
if (!isRecord(value))
|
|
1803
|
-
metadataError("detail must be an object or null");
|
|
1804
|
-
const allowed = new Set(["runId", "reason", "error", "exitCode"]);
|
|
1805
|
-
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
1806
|
-
if (unknown.length > 0)
|
|
1807
|
-
metadataError(`unknown detail fields: ${unknown.sort().join(", ")}`);
|
|
1808
|
-
for (const field of ["runId", "reason", "error"]) {
|
|
1809
|
-
if (value[field] !== undefined && typeof value[field] !== "string")
|
|
1810
|
-
metadataError(`detail.${field} must be a string`);
|
|
1811
|
-
}
|
|
1812
|
-
if (value.exitCode !== undefined && value.exitCode !== null && typeof value.exitCode !== "number") {
|
|
1813
|
-
metadataError("detail.exitCode must be a number or null");
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
function decodeTaskHistoryMetadata(input) {
|
|
1817
|
-
let parsed = input;
|
|
1818
|
-
if (typeof input === "string") {
|
|
1819
|
-
try {
|
|
1820
|
-
parsed = JSON.parse(input);
|
|
1821
|
-
} catch {
|
|
1822
|
-
metadataError("not valid JSON");
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
if (!isRecord(parsed))
|
|
1826
|
-
metadataError("root must be an object");
|
|
1827
|
-
const version = parsed.metadataVersion ?? 1;
|
|
1828
|
-
if (version === 1) {
|
|
1829
|
-
const allowed = new Set(["metadataVersion", "durationMs", "detail", "profile"]);
|
|
1830
|
-
const unknown = Object.keys(parsed).filter((key) => !allowed.has(key));
|
|
1831
|
-
if (unknown.length > 0)
|
|
1832
|
-
metadataError(`unknown v1 fields: ${unknown.sort().join(", ")}`);
|
|
1833
|
-
if (parsed.metadataVersion !== undefined && parsed.metadataVersion !== 1)
|
|
1834
|
-
metadataError("invalid v1 metadataVersion");
|
|
1835
|
-
if (parsed.durationMs !== undefined && typeof parsed.durationMs !== "number")
|
|
1836
|
-
metadataError("durationMs must be a number");
|
|
1837
|
-
if (parsed.profile !== undefined && typeof parsed.profile !== "string")
|
|
1838
|
-
metadataError("profile must be a string");
|
|
1839
|
-
validateDetail(parsed.detail);
|
|
1840
|
-
return {
|
|
1841
|
-
metadataVersion: 1,
|
|
1842
|
-
...typeof parsed.durationMs === "number" ? { durationMs: parsed.durationMs } : {},
|
|
1843
|
-
...parsed.detail !== undefined && parsed.detail !== null ? { detail: parsed.detail } : {},
|
|
1844
|
-
...typeof parsed.profile === "string" ? { legacyProfile: parsed.profile } : {}
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
if (version === 2) {
|
|
1848
|
-
const allowed = new Set(["metadataVersion", "durationMs", "detail", "engine"]);
|
|
1849
|
-
const unknown = Object.keys(parsed).filter((key) => !allowed.has(key));
|
|
1850
|
-
if (unknown.length > 0)
|
|
1851
|
-
metadataError(`unknown v2 fields: ${unknown.sort().join(", ")}`);
|
|
1852
|
-
if (typeof parsed.durationMs !== "number")
|
|
1853
|
-
metadataError("durationMs must be a number");
|
|
1854
|
-
if (!("detail" in parsed))
|
|
1855
|
-
metadataError("detail is required in v2");
|
|
1856
|
-
if (parsed.engine !== undefined && parsed.engine !== null && typeof parsed.engine !== "string") {
|
|
1857
|
-
metadataError("engine must be a string or null");
|
|
1858
|
-
}
|
|
1859
|
-
validateDetail(parsed.detail);
|
|
1860
|
-
return {
|
|
1861
|
-
metadataVersion: 2,
|
|
1862
|
-
durationMs: parsed.durationMs,
|
|
1863
|
-
detail: parsed.detail ?? null,
|
|
1864
|
-
...parsed.engine !== undefined ? { engine: parsed.engine } : {}
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
metadataError(`unsupported metadataVersion: ${String(version)}`);
|
|
1868
|
-
}
|
|
1869
|
-
function reserveTaskHistoryAttempt(db, row) {
|
|
1870
|
-
const result = db.prepare(`INSERT OR IGNORE INTO task_history
|
|
1871
|
-
(task_id, status, started_at, completed_at, failed_at, log_path,
|
|
1872
|
-
target_kind, target_ref, metadata_json)
|
|
1873
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.task_id, row.status, row.started_at, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json);
|
|
1874
|
-
return Number(result.changes) === 1;
|
|
1875
|
-
}
|
|
1876
|
-
function finalizeTaskHistoryAttempt(db, row) {
|
|
1877
|
-
const result = db.prepare(`UPDATE task_history
|
|
1878
|
-
SET status = ?, completed_at = ?, failed_at = ?, log_path = ?,
|
|
1879
|
-
target_kind = ?, target_ref = ?, metadata_json = ?
|
|
1880
|
-
WHERE task_id = ? AND started_at = ?
|
|
1881
|
-
AND status = 'active' AND completed_at IS NULL`).run(row.status, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json, row.task_id, row.started_at);
|
|
1882
|
-
return Number(result.changes) === 1;
|
|
1883
|
-
}
|
|
1884
|
-
function upsertTaskHistory(db, row) {
|
|
1885
|
-
db.prepare(`
|
|
1886
|
-
INSERT OR IGNORE INTO task_history
|
|
1887
|
-
(task_id, status, started_at, completed_at, failed_at, log_path,
|
|
1888
|
-
target_kind, target_ref, metadata_json)
|
|
1889
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1890
|
-
`).run(row.task_id, row.status, row.started_at, row.completed_at ?? null, row.failed_at ?? null, row.log_path ?? null, row.target_kind ?? null, row.target_ref ?? null, row.metadata_json);
|
|
1891
|
-
}
|
|
1892
|
-
function getTaskHistory(db, taskId) {
|
|
1893
|
-
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
1894
|
-
target_kind, target_ref, metadata_json
|
|
1895
|
-
FROM task_history WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT 1`).get(taskId);
|
|
1896
|
-
}
|
|
1897
|
-
function getTaskHistoryRuns(db, taskId, limit = 50) {
|
|
1898
|
-
return db.prepare(`SELECT id, task_id, status, started_at, completed_at, failed_at, log_path,
|
|
1899
|
-
target_kind, target_ref, metadata_json
|
|
1900
|
-
FROM task_history WHERE task_id = ? ORDER BY started_at DESC, id DESC LIMIT ?`).all(taskId, limit);
|
|
1901
|
-
}
|
|
1902
|
-
function queryTaskHistory(db, options = {}) {
|
|
1903
|
-
const conditions = [];
|
|
1904
|
-
const params = [];
|
|
1905
|
-
if (options.since) {
|
|
1906
|
-
conditions.push("started_at >= ?");
|
|
1907
|
-
params.push(options.since);
|
|
1908
|
-
}
|
|
1909
|
-
if (options.until) {
|
|
1910
|
-
conditions.push("started_at <= ?");
|
|
1911
|
-
params.push(options.until);
|
|
1912
|
-
}
|
|
1913
|
-
if (options.status) {
|
|
1914
|
-
conditions.push("status = ?");
|
|
1915
|
-
params.push(options.status);
|
|
1916
|
-
}
|
|
1917
|
-
if (options.targetKind) {
|
|
1918
|
-
conditions.push("target_kind = ?");
|
|
1919
|
-
params.push(options.targetKind);
|
|
1920
|
-
}
|
|
1921
|
-
if (options.targetRef) {
|
|
1922
|
-
conditions.push("target_ref = ?");
|
|
1923
|
-
params.push(options.targetRef);
|
|
1924
|
-
}
|
|
1925
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1926
|
-
return db.prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
|
|
1927
|
-
target_kind, target_ref, metadata_json
|
|
1928
|
-
FROM task_history ${where} ORDER BY started_at DESC, id DESC`).all(...params);
|
|
1929
|
-
}
|
|
1930
|
-
function queryCompletedTaskIntervals(db, since, until) {
|
|
1931
|
-
const sql = until ? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at" : "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
|
|
1932
|
-
return until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since);
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
|
-
// src/core/best-effort.ts
|
|
1936
|
-
function bestEffort(fn, context) {
|
|
1937
|
-
try {
|
|
1938
|
-
return fn();
|
|
1939
|
-
} catch (err) {
|
|
1940
|
-
if (isVerbose()) {
|
|
1941
|
-
warnVerbose(`[akm:best-effort] ${context ? `${context}: ` : ""}swallowed error`, err);
|
|
1942
|
-
}
|
|
1943
|
-
return;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
var init_best_effort = __esm(() => {
|
|
1947
|
-
init_warn();
|
|
1948
|
-
});
|
|
1949
|
-
|
|
1950
|
-
// src/storage/repositories/index-meta-repository.ts
|
|
1951
|
-
function getMeta(db, key) {
|
|
1952
|
-
const row = db.prepare("SELECT value FROM index_meta WHERE key = ?").get(key);
|
|
1953
|
-
return row?.value;
|
|
1954
|
-
}
|
|
1955
|
-
function setMeta(db, key, value) {
|
|
1956
|
-
db.prepare("INSERT OR REPLACE INTO index_meta (key, value) VALUES (?, ?)").run(key, value);
|
|
1957
|
-
}
|
|
1958
|
-
var init_index_meta_repository = () => {};
|
|
1959
|
-
|
|
1960
|
-
// src/llm/embedders/types.ts
|
|
1961
|
-
var init_types = __esm(() => {
|
|
1962
|
-
init_warn();
|
|
1963
|
-
});
|
|
1964
|
-
|
|
1965
|
-
// src/storage/repositories/index-vec-repository.ts
|
|
1966
|
-
import { createRequire as createRequire4 } from "node:module";
|
|
1967
|
-
function loadVecExtension(db) {
|
|
1968
|
-
try {
|
|
1969
|
-
const esmRequire = createRequire4(import.meta.url);
|
|
1970
|
-
const sqliteVec = esmRequire("sqlite-vec");
|
|
1971
|
-
sqliteVec.load(db);
|
|
1972
|
-
vecStatus.set(db, true);
|
|
1973
|
-
} catch {
|
|
1974
|
-
vecStatus.set(db, false);
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
function isVecAvailable(db) {
|
|
1978
|
-
return vecStatus.get(db) ?? false;
|
|
1979
|
-
}
|
|
1980
|
-
function warnIfVecMissing(db, { once } = { once: false }) {
|
|
1981
|
-
if (isVecAvailable(db))
|
|
1982
|
-
return;
|
|
1983
|
-
if (once && vecInitWarnedDbs.has(db))
|
|
1984
|
-
return;
|
|
1985
|
-
bestEffort(() => {
|
|
1986
|
-
const row = db.prepare("SELECT COUNT(*) AS cnt FROM embeddings").get();
|
|
1987
|
-
const count = row?.cnt ?? 0;
|
|
1988
|
-
if (count >= VEC_FALLBACK_THRESHOLD) {
|
|
1989
|
-
warn(`Semantic search is using JS fallback for %d entries. Install sqlite-vec for faster performance.
|
|
1990
|
-
See: %s`, count, VEC_DOCS_URL);
|
|
1991
|
-
if (once)
|
|
1992
|
-
vecInitWarnedDbs.add(db);
|
|
1993
|
-
}
|
|
1994
|
-
}, "embeddings table may not exist yet during init");
|
|
1995
|
-
}
|
|
1996
|
-
function purgeEmbeddings(db, opts) {
|
|
1997
|
-
bestEffort(() => db.exec("DELETE FROM embeddings"), "purge embeddings");
|
|
1998
|
-
if (isVecAvailable(db)) {
|
|
1999
|
-
bestEffort(() => db.exec(opts?.dropVecTable ? "DROP TABLE IF EXISTS entries_vec" : "DELETE FROM entries_vec"), "purge entries_vec");
|
|
2000
|
-
}
|
|
2001
|
-
setMeta(db, "hasEmbeddings", "0");
|
|
2002
|
-
}
|
|
2003
|
-
var vecStatus, VEC_DOCS_URL = "https://github.com/itlackey/akm/blob/main/docs/reference/configuration.md#sqlite-vec-extension", VEC_FALLBACK_THRESHOLD = 1e4, vecInitWarnedDbs;
|
|
2004
|
-
var init_index_vec_repository = __esm(() => {
|
|
2005
|
-
init_best_effort();
|
|
2006
|
-
init_warn();
|
|
2007
|
-
init_types();
|
|
2008
|
-
init_index_meta_repository();
|
|
2009
|
-
vecStatus = new WeakMap;
|
|
2010
|
-
vecInitWarnedDbs = new WeakSet;
|
|
2011
|
-
});
|
|
2012
|
-
|
|
2013
|
-
// src/storage/repositories/index-schema.ts
|
|
2014
|
-
function ensureGraphTables(db) {
|
|
2015
|
-
db.exec(`
|
|
2016
|
-
CREATE TABLE IF NOT EXISTS graph_meta (
|
|
2017
|
-
stash_root TEXT PRIMARY KEY,
|
|
2018
|
-
schema_version INTEGER NOT NULL,
|
|
2019
|
-
generated_at TEXT NOT NULL,
|
|
2020
|
-
considered_files INTEGER NOT NULL DEFAULT 0,
|
|
2021
|
-
extracted_files INTEGER NOT NULL DEFAULT 0,
|
|
2022
|
-
entity_count INTEGER NOT NULL DEFAULT 0,
|
|
2023
|
-
relation_count INTEGER NOT NULL DEFAULT 0,
|
|
2024
|
-
extraction_coverage REAL NOT NULL DEFAULT 0,
|
|
2025
|
-
density REAL NOT NULL DEFAULT 0,
|
|
2026
|
-
extractor_id TEXT,
|
|
2027
|
-
extraction_run_id TEXT,
|
|
2028
|
-
model TEXT,
|
|
2029
|
-
prompt_version TEXT,
|
|
2030
|
-
batch_size INTEGER,
|
|
2031
|
-
cache_hits INTEGER NOT NULL DEFAULT 0,
|
|
2032
|
-
cache_misses INTEGER NOT NULL DEFAULT 0,
|
|
2033
|
-
truncation_count INTEGER NOT NULL DEFAULT 0,
|
|
2034
|
-
failure_count INTEGER NOT NULL DEFAULT 0
|
|
2035
|
-
);
|
|
2036
|
-
|
|
2037
|
-
CREATE TABLE IF NOT EXISTS graph_files (
|
|
2038
|
-
stash_root TEXT NOT NULL,
|
|
2039
|
-
file_path TEXT NOT NULL,
|
|
2040
|
-
file_order INTEGER NOT NULL,
|
|
2041
|
-
file_type TEXT NOT NULL,
|
|
2042
|
-
body_hash TEXT NOT NULL,
|
|
2043
|
-
confidence REAL,
|
|
2044
|
-
status TEXT NOT NULL DEFAULT 'extracted',
|
|
2045
|
-
reason TEXT,
|
|
2046
|
-
extraction_run_id TEXT,
|
|
2047
|
-
PRIMARY KEY (stash_root, file_path, body_hash)
|
|
2048
|
-
);
|
|
2049
|
-
|
|
2050
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_files_path
|
|
2051
|
-
ON graph_files(stash_root, file_path);
|
|
2052
|
-
|
|
2053
|
-
CREATE INDEX IF NOT EXISTS idx_graph_files_stash_order
|
|
2054
|
-
ON graph_files(stash_root, file_order);
|
|
2055
|
-
|
|
2056
|
-
CREATE TABLE IF NOT EXISTS graph_file_entities (
|
|
2057
|
-
stash_root TEXT NOT NULL,
|
|
2058
|
-
file_path TEXT NOT NULL,
|
|
2059
|
-
body_hash TEXT NOT NULL,
|
|
2060
|
-
entity_order INTEGER NOT NULL,
|
|
2061
|
-
entity_norm TEXT NOT NULL,
|
|
2062
|
-
entity TEXT NOT NULL,
|
|
2063
|
-
PRIMARY KEY (stash_root, file_path, body_hash, entity_order),
|
|
2064
|
-
FOREIGN KEY (stash_root, file_path, body_hash)
|
|
2065
|
-
REFERENCES graph_files(stash_root, file_path, body_hash) ON DELETE CASCADE
|
|
2066
|
-
);
|
|
2067
|
-
|
|
2068
|
-
CREATE INDEX IF NOT EXISTS idx_graph_file_entities_entity_norm
|
|
2069
|
-
ON graph_file_entities(stash_root, entity_norm);
|
|
2070
|
-
|
|
2071
|
-
CREATE TABLE IF NOT EXISTS graph_file_relations (
|
|
2072
|
-
stash_root TEXT NOT NULL,
|
|
2073
|
-
file_path TEXT NOT NULL,
|
|
2074
|
-
body_hash TEXT NOT NULL,
|
|
2075
|
-
relation_order INTEGER NOT NULL,
|
|
2076
|
-
from_entity_norm TEXT NOT NULL,
|
|
2077
|
-
from_entity TEXT NOT NULL,
|
|
2078
|
-
to_entity_norm TEXT NOT NULL,
|
|
2079
|
-
to_entity TEXT NOT NULL,
|
|
2080
|
-
relation_type TEXT,
|
|
2081
|
-
confidence REAL,
|
|
2082
|
-
PRIMARY KEY (stash_root, file_path, body_hash, relation_order),
|
|
2083
|
-
FOREIGN KEY (stash_root, file_path, body_hash)
|
|
2084
|
-
REFERENCES graph_files(stash_root, file_path, body_hash) ON DELETE CASCADE
|
|
2085
|
-
);
|
|
2086
|
-
|
|
2087
|
-
-- #624-P3: lazy graph-extraction queue. Standalone table (NO FK to
|
|
2088
|
-
-- graph_files — a queued file by definition has no graph row yet).
|
|
2089
|
-
-- Idempotent on (stash_root, file_path); drained highest-priority-first.
|
|
2090
|
-
-- CREATE TABLE IF NOT EXISTS is the forward migration (no DB_VERSION bump).
|
|
2091
|
-
CREATE TABLE IF NOT EXISTS graph_extraction_queue (
|
|
2092
|
-
stash_root TEXT NOT NULL,
|
|
2093
|
-
file_path TEXT NOT NULL,
|
|
2094
|
-
body_hash TEXT NOT NULL,
|
|
2095
|
-
queued_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2096
|
-
priority INTEGER NOT NULL DEFAULT 0,
|
|
2097
|
-
PRIMARY KEY (stash_root, file_path)
|
|
2098
|
-
);
|
|
2099
|
-
|
|
2100
|
-
CREATE INDEX IF NOT EXISTS idx_graph_extraction_queue_drain
|
|
2101
|
-
ON graph_extraction_queue(stash_root, priority DESC, queued_at);
|
|
2102
|
-
`);
|
|
2103
|
-
}
|
|
2104
|
-
function ensureSchema(db, embeddingDim) {
|
|
2105
|
-
db.exec(`
|
|
2106
|
-
CREATE TABLE IF NOT EXISTS index_meta (
|
|
2107
|
-
key TEXT PRIMARY KEY,
|
|
2108
|
-
value TEXT NOT NULL
|
|
2109
|
-
);
|
|
2110
|
-
`);
|
|
2111
|
-
db.exec(`
|
|
2112
|
-
CREATE TABLE IF NOT EXISTS entries (
|
|
2113
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2114
|
-
entry_key TEXT NOT NULL UNIQUE,
|
|
2115
|
-
dir_path TEXT NOT NULL,
|
|
2116
|
-
file_path TEXT NOT NULL,
|
|
2117
|
-
stash_dir TEXT NOT NULL,
|
|
2118
|
-
entry_json TEXT NOT NULL,
|
|
2119
|
-
search_text TEXT NOT NULL,
|
|
2120
|
-
entry_type TEXT NOT NULL,
|
|
2121
|
-
derived_from TEXT,
|
|
2122
|
-
-- Chunk-5 Step 2 / DB v18 (spec 14.4): bundle-adapter identity + provenance,
|
|
2123
|
-
-- ADDITIVE alongside the legacy columns above. item_ref is the durable
|
|
2124
|
-
-- <bundle>//<concept-id> spelling; nullable during the transition so a
|
|
2125
|
-
-- pre-repoint reader path never trips a NOT NULL on a partially-migrated row.
|
|
2126
|
-
item_ref TEXT,
|
|
2127
|
-
bundle_id TEXT,
|
|
2128
|
-
component_id TEXT,
|
|
2129
|
-
concept_id TEXT,
|
|
2130
|
-
adapter_id TEXT,
|
|
2131
|
-
type TEXT,
|
|
2132
|
-
content_hash TEXT,
|
|
2133
|
-
document_json TEXT
|
|
2134
|
-
);
|
|
2135
|
-
|
|
2136
|
-
CREATE INDEX IF NOT EXISTS idx_entries_dir ON entries(dir_path);
|
|
2137
|
-
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
2138
|
-
CREATE INDEX IF NOT EXISTS idx_entries_file_path ON entries(file_path);
|
|
2139
|
-
`);
|
|
2140
|
-
ensureBundleRefColumns(db);
|
|
2141
|
-
ensureUniqueItemRefIndex(db);
|
|
2142
|
-
ensureDerivedFromColumn(db);
|
|
2143
|
-
db.exec(`
|
|
2144
|
-
CREATE TABLE IF NOT EXISTS workflow_documents (
|
|
2145
|
-
entry_id INTEGER PRIMARY KEY REFERENCES entries(id) ON DELETE CASCADE,
|
|
2146
|
-
schema_version INTEGER NOT NULL,
|
|
2147
|
-
document_json TEXT NOT NULL,
|
|
2148
|
-
source_path TEXT NOT NULL,
|
|
2149
|
-
source_hash TEXT NOT NULL,
|
|
2150
|
-
updated_at TEXT NOT NULL
|
|
2151
|
-
);
|
|
2152
|
-
|
|
2153
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_documents_source_path
|
|
2154
|
-
ON workflow_documents(source_path);
|
|
2155
|
-
`);
|
|
2156
|
-
const versionAfterCreate = getMeta(db, "version");
|
|
2157
|
-
if (!versionAfterCreate) {
|
|
2158
|
-
setMeta(db, "version", String(DB_VERSION));
|
|
2159
|
-
}
|
|
2160
|
-
db.exec(`
|
|
2161
|
-
CREATE TABLE IF NOT EXISTS embeddings (
|
|
2162
|
-
id INTEGER PRIMARY KEY,
|
|
2163
|
-
embedding BLOB NOT NULL,
|
|
2164
|
-
FOREIGN KEY (id) REFERENCES entries(id)
|
|
2165
|
-
);
|
|
2166
|
-
`);
|
|
2167
|
-
const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_fts'").get();
|
|
2168
|
-
if (!ftsExists) {
|
|
2169
|
-
db.exec(`
|
|
2170
|
-
CREATE VIRTUAL TABLE entries_fts USING fts5(
|
|
2171
|
-
entry_id UNINDEXED,
|
|
2172
|
-
name,
|
|
2173
|
-
description,
|
|
2174
|
-
tags,
|
|
2175
|
-
hints,
|
|
2176
|
-
content,
|
|
2177
|
-
tokenize='porter unicode61'
|
|
2178
|
-
);
|
|
2179
|
-
`);
|
|
2180
|
-
}
|
|
2181
|
-
db.exec(`
|
|
2182
|
-
CREATE TABLE IF NOT EXISTS utility_scores (
|
|
2183
|
-
entry_id INTEGER PRIMARY KEY,
|
|
2184
|
-
utility REAL NOT NULL DEFAULT 0,
|
|
2185
|
-
show_count INTEGER NOT NULL DEFAULT 0,
|
|
2186
|
-
search_count INTEGER NOT NULL DEFAULT 0,
|
|
2187
|
-
select_rate REAL NOT NULL DEFAULT 0,
|
|
2188
|
-
last_used_at TEXT,
|
|
2189
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2190
|
-
FOREIGN KEY (entry_id) REFERENCES entries(id) ON DELETE CASCADE
|
|
2191
|
-
);
|
|
2192
|
-
`);
|
|
2193
|
-
db.exec(`
|
|
2194
|
-
CREATE TABLE IF NOT EXISTS utility_scores_scoped (
|
|
2195
|
-
entry_id INTEGER NOT NULL,
|
|
2196
|
-
scope_key TEXT NOT NULL,
|
|
2197
|
-
utility REAL NOT NULL DEFAULT 0,
|
|
2198
|
-
last_used_at INTEGER NOT NULL,
|
|
2199
|
-
PRIMARY KEY (entry_id, scope_key)
|
|
2200
|
-
);
|
|
2201
|
-
CREATE INDEX IF NOT EXISTS idx_utility_scores_scoped_entry_id
|
|
2202
|
-
ON utility_scores_scoped(entry_id);
|
|
2203
|
-
`);
|
|
2204
|
-
db.exec(`
|
|
2205
|
-
CREATE TABLE IF NOT EXISTS index_dir_state (
|
|
2206
|
-
dir_path TEXT PRIMARY KEY,
|
|
2207
|
-
file_set_hash TEXT NOT NULL,
|
|
2208
|
-
file_mtime_max_ms REAL NOT NULL,
|
|
2209
|
-
reason TEXT NOT NULL,
|
|
2210
|
-
updated_at TEXT NOT NULL
|
|
2211
|
-
);
|
|
2212
|
-
`);
|
|
2213
|
-
db.exec(`
|
|
2214
|
-
CREATE TABLE IF NOT EXISTS llm_enrichment_cache (
|
|
2215
|
-
asset_ref TEXT NOT NULL,
|
|
2216
|
-
cache_variant TEXT NOT NULL,
|
|
2217
|
-
body_hash TEXT NOT NULL,
|
|
2218
|
-
result_json TEXT NOT NULL,
|
|
2219
|
-
updated_at INTEGER NOT NULL,
|
|
2220
|
-
PRIMARY KEY (asset_ref, cache_variant)
|
|
2221
|
-
);
|
|
2222
|
-
|
|
2223
|
-
CREATE INDEX IF NOT EXISTS idx_llm_cache_updated
|
|
2224
|
-
ON llm_enrichment_cache(updated_at);
|
|
2225
|
-
`);
|
|
2226
|
-
migrateGraphFilesSchema(db);
|
|
2227
|
-
ensureGraphTables(db);
|
|
2228
|
-
migrateGraphDataFromLegacy(db);
|
|
2229
|
-
db.exec(`
|
|
2230
|
-
CREATE TABLE IF NOT EXISTS entries_fts_dirty (
|
|
2231
|
-
entry_id INTEGER PRIMARY KEY
|
|
2232
|
-
);
|
|
2233
|
-
`);
|
|
2234
|
-
const dimExplicit = embeddingDim !== undefined;
|
|
2235
|
-
const effectiveDim = embeddingDim ?? (Number(getMeta(db, "embeddingDim")) || EMBEDDING_DIM);
|
|
2236
|
-
if (isVecAvailable(db)) {
|
|
2237
|
-
if (dimExplicit) {
|
|
2238
|
-
const storedDim = getMeta(db, "embeddingDim");
|
|
2239
|
-
if (storedDim && storedDim !== String(embeddingDim)) {
|
|
2240
|
-
purgeEmbeddings(db, { dropVecTable: true });
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
const vecExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'").get();
|
|
2244
|
-
if (!vecExists) {
|
|
2245
|
-
if (!Number.isInteger(effectiveDim) || effectiveDim <= 0 || effectiveDim > 4096) {
|
|
2246
|
-
throw new Error(`Invalid embedding dimension: ${effectiveDim}`);
|
|
2247
|
-
}
|
|
2248
|
-
db.exec(`
|
|
2249
|
-
CREATE VIRTUAL TABLE entries_vec USING vec0(
|
|
2250
|
-
id INTEGER PRIMARY KEY,
|
|
2251
|
-
embedding FLOAT[${effectiveDim}]
|
|
2252
|
-
);
|
|
2253
|
-
`);
|
|
2254
|
-
}
|
|
2255
|
-
if (dimExplicit) {
|
|
2256
|
-
setMeta(db, "embeddingDim", String(embeddingDim));
|
|
2257
|
-
}
|
|
2258
|
-
} else {
|
|
2259
|
-
if (dimExplicit) {
|
|
2260
|
-
const storedDim = getMeta(db, "embeddingDim");
|
|
2261
|
-
if (storedDim && storedDim !== String(embeddingDim)) {
|
|
2262
|
-
purgeEmbeddings(db);
|
|
2263
|
-
}
|
|
2264
|
-
setMeta(db, "embeddingDim", String(embeddingDim));
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
db.exec(REGISTRY_INDEX_CACHE_DDL);
|
|
2268
|
-
}
|
|
2269
|
-
function ensureDerivedFromColumn(db) {
|
|
2270
|
-
bestEffort(() => {
|
|
2271
|
-
const cols = db.prepare("PRAGMA table_info(entries)").all();
|
|
2272
|
-
const hasColumn = cols.some((c) => c.name === "derived_from");
|
|
2273
|
-
if (!hasColumn) {
|
|
2274
|
-
db.exec("ALTER TABLE entries ADD COLUMN derived_from TEXT");
|
|
2275
|
-
}
|
|
2276
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_entries_derived_from ON entries(derived_from)");
|
|
2277
|
-
}, "entries table may not exist on a brand-new DB before CREATE — caller is responsible");
|
|
2278
|
-
}
|
|
2279
|
-
function ensureBundleRefColumns(db) {
|
|
2280
|
-
bestEffort(() => {
|
|
2281
|
-
const cols = db.prepare("PRAGMA table_info(entries)").all();
|
|
2282
|
-
const have = new Set(cols.map((c) => c.name));
|
|
2283
|
-
const additive = [
|
|
2284
|
-
["item_ref", "TEXT"],
|
|
2285
|
-
["bundle_id", "TEXT"],
|
|
2286
|
-
["component_id", "TEXT"],
|
|
2287
|
-
["concept_id", "TEXT"],
|
|
2288
|
-
["adapter_id", "TEXT"],
|
|
2289
|
-
["type", "TEXT"],
|
|
2290
|
-
["content_hash", "TEXT"],
|
|
2291
|
-
["document_json", "TEXT"]
|
|
2292
|
-
];
|
|
2293
|
-
for (const [name, sqlType] of additive) {
|
|
2294
|
-
if (!have.has(name))
|
|
2295
|
-
db.exec(`ALTER TABLE entries ADD COLUMN ${name} ${sqlType}`);
|
|
2296
|
-
}
|
|
2297
|
-
}, "entries table may not exist on a brand-new DB before CREATE — caller is responsible");
|
|
2298
|
-
}
|
|
2299
|
-
function ensureUniqueItemRefIndex(db) {
|
|
2300
|
-
let uniqueOk = false;
|
|
2301
|
-
bestEffort(() => {
|
|
2302
|
-
db.exec("DROP INDEX IF EXISTS idx_entries_item_ref");
|
|
2303
|
-
db.exec("CREATE UNIQUE INDEX idx_entries_item_ref ON entries(item_ref)");
|
|
2304
|
-
uniqueOk = true;
|
|
2305
|
-
}, "entries.item_ref UNIQUE index — column added by ensureBundleRefColumns above");
|
|
2306
|
-
if (!uniqueOk) {
|
|
2307
|
-
warn("[akm] entries.item_ref UNIQUE index could not be built (duplicate item_ref on a partially-migrated index) — " + "keeping the plain lookup index; a full `akm index` rebuild restores uniqueness.");
|
|
2308
|
-
bestEffort(() => db.exec("CREATE INDEX IF NOT EXISTS idx_entries_item_ref ON entries(item_ref)"), "entries.item_ref lookup index fallback");
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
function tableExists(db, name) {
|
|
2312
|
-
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1").get(name);
|
|
2313
|
-
return row !== undefined && row !== null;
|
|
2314
|
-
}
|
|
2315
|
-
function migrateGraphFilesSchema(db) {
|
|
2316
|
-
bestEffort(() => {
|
|
2317
|
-
const cols = db.prepare("PRAGMA table_info(graph_files)").all();
|
|
2318
|
-
const isLegacyShape = cols.some((c) => c.name === "entry_id");
|
|
2319
|
-
if (!isLegacyShape)
|
|
2320
|
-
return;
|
|
2321
|
-
db.exec("DROP TABLE IF EXISTS graph_file_relations_legacy");
|
|
2322
|
-
db.exec("DROP TABLE IF EXISTS graph_file_entities_legacy");
|
|
2323
|
-
db.exec("DROP TABLE IF EXISTS graph_files_legacy");
|
|
2324
|
-
db.exec("ALTER TABLE graph_files RENAME TO graph_files_legacy");
|
|
2325
|
-
if (tableExists(db, "graph_file_entities")) {
|
|
2326
|
-
db.exec("ALTER TABLE graph_file_entities RENAME TO graph_file_entities_legacy");
|
|
2327
|
-
}
|
|
2328
|
-
if (tableExists(db, "graph_file_relations")) {
|
|
2329
|
-
db.exec("ALTER TABLE graph_file_relations RENAME TO graph_file_relations_legacy");
|
|
2330
|
-
}
|
|
2331
|
-
}, "graph_files may not exist on a brand-new DB before CREATE — caller is responsible");
|
|
2332
|
-
}
|
|
2333
|
-
function migrateGraphDataFromLegacy(db) {
|
|
2334
|
-
if (!tableExists(db, "graph_files_legacy"))
|
|
2335
|
-
return;
|
|
2336
|
-
let migratedFiles = 0;
|
|
2337
|
-
bestEffort(() => {
|
|
2338
|
-
db.transaction(() => {
|
|
2339
|
-
const res = db.prepare(`INSERT OR IGNORE INTO graph_files
|
|
2340
|
-
(stash_root, file_path, body_hash, file_order, file_type, confidence, status, reason, extraction_run_id)
|
|
2341
|
-
SELECT stash_root, file_path, body_hash, file_order, file_type, confidence, status, reason, extraction_run_id
|
|
2342
|
-
FROM graph_files_legacy
|
|
2343
|
-
WHERE body_hash IS NOT NULL AND body_hash != ''`).run();
|
|
2344
|
-
migratedFiles = Number(res.changes);
|
|
2345
|
-
if (tableExists(db, "graph_file_entities_legacy")) {
|
|
2346
|
-
db.exec(`INSERT OR IGNORE INTO graph_file_entities
|
|
2347
|
-
(stash_root, file_path, body_hash, entity_order, entity_norm, entity)
|
|
2348
|
-
SELECT gf.stash_root, gf.file_path, gf.body_hash, e.entity_order, e.entity_norm, e.entity
|
|
2349
|
-
FROM graph_file_entities_legacy e
|
|
2350
|
-
JOIN graph_files_legacy gf ON gf.entry_id = e.entry_id
|
|
2351
|
-
WHERE gf.body_hash IS NOT NULL AND gf.body_hash != ''`);
|
|
2352
|
-
}
|
|
2353
|
-
if (tableExists(db, "graph_file_relations_legacy")) {
|
|
2354
|
-
db.exec(`INSERT OR IGNORE INTO graph_file_relations
|
|
2355
|
-
(stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence)
|
|
2356
|
-
SELECT gf.stash_root, gf.file_path, gf.body_hash, r.relation_order, r.from_entity_norm, r.from_entity, r.to_entity_norm, r.to_entity, r.relation_type, r.confidence
|
|
2357
|
-
FROM graph_file_relations_legacy r
|
|
2358
|
-
JOIN graph_files_legacy gf ON gf.entry_id = r.entry_id
|
|
2359
|
-
WHERE gf.body_hash IS NOT NULL AND gf.body_hash != ''`);
|
|
2360
|
-
}
|
|
2361
|
-
})();
|
|
2362
|
-
}, "graph data migration is best-effort; legacy tables are dropped regardless below");
|
|
2363
|
-
bestEffort(() => {
|
|
2364
|
-
db.exec("DROP TABLE IF EXISTS graph_file_relations_legacy");
|
|
2365
|
-
db.exec("DROP TABLE IF EXISTS graph_file_entities_legacy");
|
|
2366
|
-
db.exec("DROP TABLE IF EXISTS graph_files_legacy");
|
|
2367
|
-
}, "drop legacy graph tables after migration");
|
|
2368
|
-
if (migratedFiles > 0) {
|
|
2369
|
-
warn(`[akm] graph index re-keyed (#624): migrated ${migratedFiles} extracted file(s) to the new schema — no re-extraction needed. Index + embeddings untouched.`);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
var DB_VERSION = 20, EMBEDDING_DIM = 384, REGISTRY_INDEX_CACHE_DDL = `
|
|
2373
|
-
CREATE TABLE IF NOT EXISTS registry_index_cache (
|
|
2374
|
-
registry_url TEXT PRIMARY KEY,
|
|
2375
|
-
fetched_at TEXT NOT NULL,
|
|
2376
|
-
etag TEXT,
|
|
2377
|
-
last_modified TEXT,
|
|
2378
|
-
index_json TEXT NOT NULL DEFAULT '{}'
|
|
2379
|
-
);
|
|
2380
|
-
|
|
2381
|
-
CREATE INDEX IF NOT EXISTS idx_registry_cache_fetched
|
|
2382
|
-
ON registry_index_cache(fetched_at);
|
|
2383
|
-
`;
|
|
2384
|
-
var init_index_schema = __esm(() => {
|
|
2385
|
-
init_best_effort();
|
|
2386
|
-
init_warn();
|
|
2387
|
-
init_index_meta_repository();
|
|
2388
|
-
init_index_vec_repository();
|
|
2389
|
-
});
|
|
2390
|
-
|
|
2391
|
-
// src/storage/repositories/index-connection.ts
|
|
2392
|
-
var exports_index_connection = {};
|
|
2393
|
-
__export(exports_index_connection, {
|
|
2394
|
-
openReadonlyExistingDatabase: () => openReadonlyExistingDatabase,
|
|
2395
|
-
openIndexDatabase: () => openIndexDatabase,
|
|
2396
|
-
openExistingDatabase: () => openExistingDatabase,
|
|
2397
|
-
closeDatabase: () => closeDatabase
|
|
2398
|
-
});
|
|
2399
|
-
import fs8 from "node:fs";
|
|
2400
|
-
import { createRequire as createRequire5 } from "node:module";
|
|
2401
|
-
function openIndexDatabase(dbPath, options) {
|
|
2402
|
-
return openManagedDatabase({
|
|
2403
|
-
path: dbPath ?? getDbPath(),
|
|
2404
|
-
init: (db) => {
|
|
2405
|
-
loadVecExtension(db);
|
|
2406
|
-
const resolvedDim = options?.embeddingDim ?? resolveConfiguredEmbeddingDim();
|
|
2407
|
-
ensureSchema(db, resolvedDim);
|
|
2408
|
-
warnIfVecMissing(db, { once: true });
|
|
2409
|
-
}
|
|
2410
|
-
});
|
|
2411
|
-
}
|
|
2412
|
-
function resolveConfiguredEmbeddingDim() {
|
|
2413
|
-
try {
|
|
2414
|
-
const esmRequire = createRequire5(import.meta.url);
|
|
2415
|
-
const { loadConfig } = esmRequire("../../core/config/config");
|
|
2416
|
-
const dim = loadConfig().embedding?.dimension;
|
|
2417
|
-
if (typeof dim === "number" && Number.isInteger(dim) && dim > 0 && dim <= 4096) {
|
|
2418
|
-
return dim;
|
|
2419
|
-
}
|
|
2420
|
-
return;
|
|
2421
|
-
} catch {
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
function openExistingDatabase(dbPath) {
|
|
2426
|
-
return openManagedDatabase({ path: dbPath ?? getDbPath(), init: loadVecExtension });
|
|
2427
|
-
}
|
|
2428
|
-
function openReadonlyExistingDatabase(dbPath) {
|
|
2429
|
-
const resolvedPath = dbPath ?? getDbPath();
|
|
2430
|
-
if (!fs8.existsSync(resolvedPath))
|
|
2431
|
-
return;
|
|
2432
|
-
return openDatabase(resolvedPath, { readonly: true, create: false });
|
|
2433
|
-
}
|
|
2434
|
-
function closeDatabase(db) {
|
|
2435
|
-
db.close();
|
|
2436
|
-
}
|
|
2437
|
-
var init_index_connection = __esm(() => {
|
|
2438
|
-
init_paths();
|
|
2439
|
-
init_database();
|
|
2440
|
-
init_managed_db();
|
|
2441
|
-
init_index_schema();
|
|
2442
|
-
init_index_vec_repository();
|
|
2443
|
-
});
|
|
2444
|
-
|
|
2445
|
-
// src/storage/repositories/index-connection.ts
|
|
2446
|
-
function openExistingDatabase2(dbPath) {
|
|
2447
|
-
return openManagedDatabase({ path: dbPath ?? getDbPath(), init: loadVecExtension });
|
|
2448
|
-
}
|
|
2449
|
-
function closeDatabase2(db) {
|
|
2450
|
-
db.close();
|
|
2451
|
-
}
|
|
2452
|
-
var init_index_connection2 = __esm(() => {
|
|
2453
|
-
init_paths();
|
|
2454
|
-
init_database();
|
|
2455
|
-
init_managed_db();
|
|
2456
|
-
init_index_schema();
|
|
2457
|
-
init_index_vec_repository();
|
|
2458
|
-
});
|
|
2459
|
-
|
|
2460
|
-
// src/indexer/db/graph-db.ts
|
|
2461
|
-
var exports_graph_db = {};
|
|
2462
|
-
__export(exports_graph_db, {
|
|
2463
|
-
replaceStoredGraph: () => replaceStoredGraph,
|
|
2464
|
-
loadStoredGraphSnapshot: () => loadStoredGraphSnapshot,
|
|
2465
|
-
loadStoredGraphMeta: () => loadStoredGraphMeta,
|
|
2466
|
-
loadGraphMetaOnly: () => loadGraphMetaOnly,
|
|
2467
|
-
loadGraphFilesOnly: () => loadGraphFilesOnly,
|
|
2468
|
-
loadGraphEntitiesByPath: () => loadGraphEntitiesByPath,
|
|
2469
|
-
hasGraphData: () => hasGraphData,
|
|
2470
|
-
enqueueGraphExtraction: () => enqueueGraphExtraction,
|
|
2471
|
-
drainExtractionQueue: () => drainExtractionQueue,
|
|
2472
|
-
deleteStoredGraph: () => deleteStoredGraph
|
|
2473
|
-
});
|
|
2474
|
-
import fs9 from "node:fs";
|
|
2475
|
-
function withReadableGraphDb(db, fn) {
|
|
2476
|
-
if (db)
|
|
2477
|
-
return fn(db);
|
|
2478
|
-
const dbPath = getDbPath();
|
|
2479
|
-
if (!fs9.existsSync(dbPath))
|
|
2480
|
-
throw new Error("GRAPH_DB_MISSING");
|
|
2481
|
-
const opened = openExistingDatabase2(dbPath);
|
|
2482
|
-
try {
|
|
2483
|
-
return fn(opened);
|
|
2484
|
-
} finally {
|
|
2485
|
-
closeDatabase2(opened);
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
function uniqueSorted(values) {
|
|
2489
|
-
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
2490
|
-
}
|
|
2491
|
-
function normalizeEntity(value) {
|
|
2492
|
-
return value.trim().toLowerCase();
|
|
2493
|
-
}
|
|
2494
|
-
function replaceStoredGraph(db, graph) {
|
|
2495
|
-
const upsertMeta = db.prepare(`INSERT INTO graph_meta (
|
|
2496
|
-
stash_root,
|
|
2497
|
-
schema_version,
|
|
2498
|
-
generated_at,
|
|
2499
|
-
considered_files,
|
|
2500
|
-
extracted_files,
|
|
2501
|
-
entity_count,
|
|
2502
|
-
relation_count,
|
|
2503
|
-
extraction_coverage,
|
|
2504
|
-
density,
|
|
2505
|
-
extractor_id,
|
|
2506
|
-
extraction_run_id,
|
|
2507
|
-
model,
|
|
2508
|
-
prompt_version,
|
|
2509
|
-
batch_size,
|
|
2510
|
-
cache_hits,
|
|
2511
|
-
cache_misses,
|
|
2512
|
-
truncation_count,
|
|
2513
|
-
failure_count
|
|
2514
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2515
|
-
ON CONFLICT(stash_root) DO UPDATE SET
|
|
2516
|
-
schema_version = excluded.schema_version,
|
|
2517
|
-
generated_at = excluded.generated_at,
|
|
2518
|
-
considered_files = excluded.considered_files,
|
|
2519
|
-
extracted_files = excluded.extracted_files,
|
|
2520
|
-
entity_count = excluded.entity_count,
|
|
2521
|
-
relation_count = excluded.relation_count,
|
|
2522
|
-
extraction_coverage = excluded.extraction_coverage,
|
|
2523
|
-
density = excluded.density,
|
|
2524
|
-
extractor_id = excluded.extractor_id,
|
|
2525
|
-
extraction_run_id = excluded.extraction_run_id,
|
|
2526
|
-
model = excluded.model,
|
|
2527
|
-
prompt_version = excluded.prompt_version,
|
|
2528
|
-
batch_size = excluded.batch_size,
|
|
2529
|
-
cache_hits = excluded.cache_hits,
|
|
2530
|
-
cache_misses = excluded.cache_misses,
|
|
2531
|
-
truncation_count = excluded.truncation_count,
|
|
2532
|
-
failure_count = excluded.failure_count`);
|
|
2533
|
-
const selectExisting = db.prepare("SELECT file_path, body_hash, file_order FROM graph_files WHERE stash_root = ?");
|
|
2534
|
-
const deleteFile = db.prepare("DELETE FROM graph_files WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
2535
|
-
const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
2536
|
-
const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
2537
|
-
const insertFile = db.prepare(`INSERT INTO graph_files (
|
|
2538
|
-
stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
|
|
2539
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2540
|
-
const updateFileMeta = db.prepare(`UPDATE graph_files
|
|
2541
|
-
SET file_order = ?, file_type = ?, confidence = ?, status = ?, reason = ?, extraction_run_id = ?
|
|
2542
|
-
WHERE stash_root = ? AND file_path = ? AND body_hash = ?`);
|
|
2543
|
-
const insertEntity = db.prepare(`INSERT INTO graph_file_entities (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
|
|
2544
|
-
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
2545
|
-
const insertRelation = db.prepare(`INSERT INTO graph_file_relations (
|
|
2546
|
-
stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
|
|
2547
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2548
|
-
const quality = graph.quality;
|
|
2549
|
-
const telemetry = graph.telemetry;
|
|
2550
|
-
db.transaction(() => {
|
|
2551
|
-
upsertMeta.run(graph.stashRoot, graph.schemaVersion, graph.generatedAt, quality?.consideredFiles ?? graph.files.length, quality?.extractedFiles ?? graph.files.length, quality?.entityCount ?? graph.entities?.length ?? 0, quality?.relationCount ?? graph.relations?.length ?? 0, quality?.extractionCoverage ?? 0, quality?.density ?? 0, telemetry?.extractorId ?? null, telemetry?.extractionRunId ?? null, telemetry?.model ?? null, telemetry?.promptVersion ?? null, telemetry?.batchSize ?? null, telemetry?.cacheHits ?? 0, telemetry?.cacheMisses ?? 0, telemetry?.truncationCount ?? 0, telemetry?.failureCount ?? 0);
|
|
2552
|
-
const existingRows = selectExisting.all(graph.stashRoot);
|
|
2553
|
-
const existingByPath = new Map;
|
|
2554
|
-
for (const row of existingRows)
|
|
2555
|
-
existingByPath.set(row.file_path, row);
|
|
2556
|
-
const presentPaths = new Set;
|
|
2557
|
-
for (const [fileOrder, node] of graph.files.entries()) {
|
|
2558
|
-
const bodyHash = node.bodyHash && node.bodyHash.length > 0 ? node.bodyHash : "";
|
|
2559
|
-
presentPaths.add(node.path);
|
|
2560
|
-
const existing = existingByPath.get(node.path);
|
|
2561
|
-
if (existing && existing.body_hash === bodyHash) {
|
|
2562
|
-
updateFileMeta.run(fileOrder, node.type, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null, graph.stashRoot, node.path, bodyHash);
|
|
2563
|
-
continue;
|
|
2564
|
-
}
|
|
2565
|
-
if (existing) {
|
|
2566
|
-
deleteEntities.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
2567
|
-
deleteRelations.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
2568
|
-
deleteFile.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
2569
|
-
}
|
|
2570
|
-
insertFile.run(graph.stashRoot, node.path, fileOrder, node.type, bodyHash, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null);
|
|
2571
|
-
for (const [entityOrder, entity] of node.entities.entries()) {
|
|
2572
|
-
insertEntity.run(graph.stashRoot, node.path, bodyHash, entityOrder, normalizeEntity(entity), entity);
|
|
2573
|
-
}
|
|
2574
|
-
for (const [relationOrder, relation] of node.relations.entries()) {
|
|
2575
|
-
insertRelation.run(graph.stashRoot, node.path, bodyHash, relationOrder, normalizeEntity(relation.from), relation.from, normalizeEntity(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
for (const row of existingRows) {
|
|
2579
|
-
if (!presentPaths.has(row.file_path)) {
|
|
2580
|
-
deleteEntities.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
2581
|
-
deleteRelations.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
2582
|
-
deleteFile.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
})();
|
|
2586
|
-
}
|
|
2587
|
-
function deleteStoredGraph(db, stashPath) {
|
|
2588
|
-
db.transaction(() => {
|
|
2589
|
-
db.prepare("DELETE FROM graph_files WHERE stash_root = ?").run(stashPath);
|
|
2590
|
-
db.prepare("DELETE FROM graph_meta WHERE stash_root = ?").run(stashPath);
|
|
2591
|
-
})();
|
|
2592
|
-
}
|
|
2593
|
-
function hasGraphData(db, stashRoot, filePath) {
|
|
2594
|
-
try {
|
|
2595
|
-
const row = db.prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1").get(stashRoot, filePath);
|
|
2596
|
-
return row !== undefined;
|
|
2597
|
-
} catch {
|
|
2598
|
-
return false;
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
function enqueueGraphExtraction(db, stashRoot, filePath, bodyHash, priority = 0) {
|
|
2602
|
-
try {
|
|
2603
|
-
db.prepare(`INSERT INTO graph_extraction_queue (stash_root, file_path, body_hash, priority)
|
|
2604
|
-
VALUES (?, ?, ?, ?)
|
|
2605
|
-
ON CONFLICT(stash_root, file_path) DO UPDATE SET
|
|
2606
|
-
body_hash = excluded.body_hash,
|
|
2607
|
-
priority = MAX(graph_extraction_queue.priority, excluded.priority),
|
|
2608
|
-
queued_at = datetime('now')`).run(stashRoot, filePath, bodyHash, priority);
|
|
2609
|
-
} catch (err) {
|
|
2610
|
-
rethrowIfTestIsolationError(err);
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
function drainExtractionQueue(db, stashRoot, limit) {
|
|
2614
|
-
try {
|
|
2615
|
-
return db.transaction(() => {
|
|
2616
|
-
const rows = db.prepare(`SELECT file_path, body_hash, priority
|
|
2617
|
-
FROM graph_extraction_queue
|
|
2618
|
-
WHERE stash_root = ?
|
|
2619
|
-
ORDER BY priority DESC, queued_at ASC
|
|
2620
|
-
LIMIT ?`).all(stashRoot, limit);
|
|
2621
|
-
const del = db.prepare("DELETE FROM graph_extraction_queue WHERE stash_root = ? AND file_path = ?");
|
|
2622
|
-
for (const row of rows)
|
|
2623
|
-
del.run(stashRoot, row.file_path);
|
|
2624
|
-
return rows.map((row) => ({ filePath: row.file_path, bodyHash: row.body_hash, priority: row.priority }));
|
|
2625
|
-
})();
|
|
2626
|
-
} catch (err) {
|
|
2627
|
-
rethrowIfTestIsolationError(err);
|
|
2628
|
-
return [];
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
function loadGraphMetaOnly(stashPath, db) {
|
|
2632
|
-
return loadStoredGraphMeta(stashPath, db);
|
|
2633
|
-
}
|
|
2634
|
-
function loadGraphFilesOnly(stashPath, db) {
|
|
2635
|
-
try {
|
|
2636
|
-
return withReadableGraphDb(db, (readDb) => {
|
|
2637
|
-
try {
|
|
2638
|
-
const rows = readDb.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason
|
|
2639
|
-
FROM graph_files
|
|
2640
|
-
WHERE stash_root = ?
|
|
2641
|
-
ORDER BY file_order`).all(stashPath);
|
|
2642
|
-
return rows.map((row) => ({
|
|
2643
|
-
path: row.file_path,
|
|
2644
|
-
type: row.file_type,
|
|
2645
|
-
bodyHash: row.body_hash,
|
|
2646
|
-
...typeof row.confidence === "number" ? { confidence: row.confidence } : {},
|
|
2647
|
-
...row.status ? { status: row.status } : {},
|
|
2648
|
-
...row.reason ? { reason: row.reason } : {}
|
|
2649
|
-
}));
|
|
2650
|
-
} catch {
|
|
2651
|
-
return [];
|
|
2652
|
-
}
|
|
2653
|
-
});
|
|
2654
|
-
} catch (err) {
|
|
2655
|
-
rethrowIfTestIsolationError(err);
|
|
2656
|
-
return [];
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
function loadGraphEntitiesByPath(db, stashRoot, filePath, bodyHash) {
|
|
2660
|
-
try {
|
|
2661
|
-
const rows = db.prepare(`SELECT entity FROM graph_file_entities
|
|
2662
|
-
WHERE stash_root = ? AND file_path = ? AND body_hash = ?
|
|
2663
|
-
ORDER BY entity_order`).all(stashRoot, filePath, bodyHash);
|
|
2664
|
-
return rows.map((r) => r.entity);
|
|
2665
|
-
} catch {
|
|
2666
|
-
return [];
|
|
2667
|
-
}
|
|
2668
|
-
}
|
|
2669
|
-
function loadStoredGraphMeta(stashPath, db) {
|
|
2670
|
-
try {
|
|
2671
|
-
return withReadableGraphDb(db, (readDb) => {
|
|
2672
|
-
try {
|
|
2673
|
-
const row = readDb.prepare(`SELECT
|
|
2674
|
-
stash_root,
|
|
2675
|
-
schema_version,
|
|
2676
|
-
generated_at,
|
|
2677
|
-
considered_files,
|
|
2678
|
-
extracted_files,
|
|
2679
|
-
entity_count,
|
|
2680
|
-
relation_count,
|
|
2681
|
-
extraction_coverage,
|
|
2682
|
-
density,
|
|
2683
|
-
extractor_id,
|
|
2684
|
-
extraction_run_id,
|
|
2685
|
-
model,
|
|
2686
|
-
prompt_version,
|
|
2687
|
-
batch_size,
|
|
2688
|
-
cache_hits,
|
|
2689
|
-
cache_misses,
|
|
2690
|
-
truncation_count,
|
|
2691
|
-
failure_count
|
|
2692
|
-
FROM graph_meta
|
|
2693
|
-
WHERE stash_root = ?`).get(stashPath);
|
|
2694
|
-
if (!row)
|
|
2695
|
-
return null;
|
|
2696
|
-
return {
|
|
2697
|
-
stashPath: row.stash_root,
|
|
2698
|
-
graphPath: getDbPath(),
|
|
2699
|
-
schemaVersion: row.schema_version,
|
|
2700
|
-
generatedAt: row.generated_at,
|
|
2701
|
-
quality: {
|
|
2702
|
-
consideredFiles: row.considered_files,
|
|
2703
|
-
extractedFiles: row.extracted_files,
|
|
2704
|
-
entityCount: row.entity_count,
|
|
2705
|
-
relationCount: row.relation_count,
|
|
2706
|
-
extractionCoverage: row.extraction_coverage,
|
|
2707
|
-
density: row.density
|
|
2708
|
-
},
|
|
2709
|
-
telemetry: {
|
|
2710
|
-
...row.extractor_id ? { extractorId: row.extractor_id } : {},
|
|
2711
|
-
...row.extraction_run_id ? { extractionRunId: row.extraction_run_id } : {},
|
|
2712
|
-
...row.model ? { model: row.model } : {},
|
|
2713
|
-
...row.prompt_version ? { promptVersion: row.prompt_version } : {},
|
|
2714
|
-
...typeof row.batch_size === "number" ? { batchSize: row.batch_size } : {},
|
|
2715
|
-
cacheHits: row.cache_hits,
|
|
2716
|
-
cacheMisses: row.cache_misses,
|
|
2717
|
-
truncationCount: row.truncation_count,
|
|
2718
|
-
failureCount: row.failure_count,
|
|
2719
|
-
retryAttempts: 0
|
|
2720
|
-
}
|
|
2721
|
-
};
|
|
2722
|
-
} catch {
|
|
2723
|
-
return null;
|
|
2724
|
-
}
|
|
2725
|
-
});
|
|
2726
|
-
} catch (err) {
|
|
2727
|
-
rethrowIfTestIsolationError(err);
|
|
2728
|
-
return null;
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
function loadStoredGraphSnapshot(stashPath, db) {
|
|
2732
|
-
try {
|
|
2733
|
-
return withReadableGraphDb(db, (readDb) => {
|
|
2734
|
-
const meta = loadStoredGraphMeta(stashPath, readDb);
|
|
2735
|
-
if (!meta)
|
|
2736
|
-
return null;
|
|
2737
|
-
try {
|
|
2738
|
-
const fileRows = readDb.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
|
|
2739
|
-
FROM graph_files
|
|
2740
|
-
WHERE stash_root = ?
|
|
2741
|
-
ORDER BY file_order`).all(stashPath);
|
|
2742
|
-
const entityRows = readDb.prepare(`SELECT gf.file_path AS file_path, gfe.entity AS entity
|
|
2743
|
-
FROM graph_file_entities gfe
|
|
2744
|
-
JOIN graph_files gf
|
|
2745
|
-
ON gf.stash_root = gfe.stash_root
|
|
2746
|
-
AND gf.file_path = gfe.file_path
|
|
2747
|
-
AND gf.body_hash = gfe.body_hash
|
|
2748
|
-
WHERE gf.stash_root = ?
|
|
2749
|
-
ORDER BY gf.file_order, gfe.entity_order`).all(stashPath);
|
|
2750
|
-
const relationRows = readDb.prepare(`SELECT gf.file_path AS file_path,
|
|
2751
|
-
gfr.from_entity AS from_entity,
|
|
2752
|
-
gfr.to_entity AS to_entity,
|
|
2753
|
-
gfr.relation_type AS relation_type,
|
|
2754
|
-
gfr.confidence AS confidence
|
|
2755
|
-
FROM graph_file_relations gfr
|
|
2756
|
-
JOIN graph_files gf
|
|
2757
|
-
ON gf.stash_root = gfr.stash_root
|
|
2758
|
-
AND gf.file_path = gfr.file_path
|
|
2759
|
-
AND gf.body_hash = gfr.body_hash
|
|
2760
|
-
WHERE gf.stash_root = ?
|
|
2761
|
-
ORDER BY gf.file_order, gfr.relation_order`).all(stashPath);
|
|
2762
|
-
const entitiesByPath = new Map;
|
|
2763
|
-
for (const row of entityRows) {
|
|
2764
|
-
const bucket = entitiesByPath.get(row.file_path);
|
|
2765
|
-
if (bucket)
|
|
2766
|
-
bucket.push(row.entity);
|
|
2767
|
-
else
|
|
2768
|
-
entitiesByPath.set(row.file_path, [row.entity]);
|
|
2769
|
-
}
|
|
2770
|
-
const relationsByPath = new Map;
|
|
2771
|
-
for (const row of relationRows) {
|
|
2772
|
-
const relation = {
|
|
2773
|
-
from: row.from_entity,
|
|
2774
|
-
to: row.to_entity,
|
|
2775
|
-
...row.relation_type ? { type: row.relation_type } : {},
|
|
2776
|
-
...typeof row.confidence === "number" ? { confidence: row.confidence } : {}
|
|
2777
|
-
};
|
|
2778
|
-
const bucket = relationsByPath.get(row.file_path);
|
|
2779
|
-
if (bucket)
|
|
2780
|
-
bucket.push(relation);
|
|
2781
|
-
else
|
|
2782
|
-
relationsByPath.set(row.file_path, [relation]);
|
|
2783
|
-
}
|
|
2784
|
-
const files = fileRows.map((row) => ({
|
|
2785
|
-
path: row.file_path,
|
|
2786
|
-
type: row.file_type,
|
|
2787
|
-
...row.body_hash ? { bodyHash: row.body_hash } : {},
|
|
2788
|
-
entities: entitiesByPath.get(row.file_path) ?? [],
|
|
2789
|
-
relations: relationsByPath.get(row.file_path) ?? [],
|
|
2790
|
-
...typeof row.confidence === "number" ? { confidence: row.confidence } : {},
|
|
2791
|
-
...row.status ? { status: row.status } : {},
|
|
2792
|
-
...row.reason ? { reason: row.reason } : {},
|
|
2793
|
-
...row.extraction_run_id ? { extractionRunId: row.extraction_run_id } : {}
|
|
2794
|
-
}));
|
|
2795
|
-
return {
|
|
2796
|
-
stashPath: meta.stashPath,
|
|
2797
|
-
graphPath: meta.graphPath,
|
|
2798
|
-
schemaVersion: meta.schemaVersion,
|
|
2799
|
-
generatedAt: meta.generatedAt,
|
|
2800
|
-
...meta.quality ? { quality: meta.quality } : {},
|
|
2801
|
-
...meta.telemetry ? { telemetry: meta.telemetry } : {},
|
|
2802
|
-
files,
|
|
2803
|
-
entities: uniqueSorted(files.flatMap((file) => file.entities)),
|
|
2804
|
-
relations: files.flatMap((file) => file.relations)
|
|
2805
|
-
};
|
|
2806
|
-
} catch {
|
|
2807
|
-
return null;
|
|
2808
|
-
}
|
|
2809
|
-
});
|
|
2810
|
-
} catch (err) {
|
|
2811
|
-
rethrowIfTestIsolationError(err);
|
|
2812
|
-
return null;
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
var init_graph_db = __esm(() => {
|
|
2816
|
-
init_errors();
|
|
2817
|
-
init_paths();
|
|
2818
|
-
init_index_connection2();
|
|
2819
|
-
});
|
|
2820
|
-
|
|
2821
|
-
// scripts/migrate-storage.ts
|
|
2822
|
-
import fs10 from "fs";
|
|
2823
|
-
import os from "os";
|
|
2824
|
-
import path8 from "path";
|
|
2825
|
-
import readline from "readline";
|
|
2826
|
-
|
|
2827
|
-
// src/core/paths.ts
|
|
2828
|
-
init_errors();
|
|
2829
|
-
init_platform();
|
|
2830
|
-
import path from "node:path";
|
|
2831
|
-
function isTransientStashPath(p) {
|
|
2832
|
-
return p.startsWith("/tmp/") || p === "/tmp" || p.startsWith("/var/tmp/") || p === "/var/tmp" || p.startsWith("/private/tmp/") || p.startsWith("/private/var/folders/") || p.startsWith("/var/folders/");
|
|
2833
|
-
}
|
|
2834
|
-
function getConfigDir(env = process.env, platform = process.platform) {
|
|
2835
|
-
const override = env.AKM_CONFIG_DIR?.trim();
|
|
2836
|
-
if (override)
|
|
2837
|
-
return override;
|
|
2838
|
-
if (platform === "win32") {
|
|
2839
|
-
const appData = env.APPDATA?.trim();
|
|
2840
|
-
if (appData)
|
|
2841
|
-
return path.join(appData, "akm");
|
|
2842
|
-
} else {
|
|
2843
|
-
const xdgConfigHome2 = env.XDG_CONFIG_HOME?.trim();
|
|
2844
|
-
if (xdgConfigHome2)
|
|
2845
|
-
return path.join(xdgConfigHome2, "akm");
|
|
2846
|
-
}
|
|
2847
|
-
const stashOverride = env.AKM_STASH_DIR?.trim();
|
|
2848
|
-
if (stashOverride && isTransientStashPath(stashOverride)) {
|
|
2849
|
-
return path.join(stashOverride, ".akm");
|
|
2850
|
-
}
|
|
2851
|
-
if (platform === "win32") {
|
|
2852
|
-
const appData = env.APPDATA?.trim();
|
|
2853
|
-
if (appData)
|
|
2854
|
-
return path.join(appData, "akm");
|
|
2855
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
2856
|
-
if (!userProfile) {
|
|
2857
|
-
throw new ConfigError("Unable to determine config directory. Set APPDATA or USERPROFILE.", "CONFIG_DIR_UNRESOLVABLE");
|
|
2858
|
-
}
|
|
2859
|
-
return path.join(userProfile, "AppData", "Roaming", "akm");
|
|
2860
|
-
}
|
|
2861
|
-
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
2862
|
-
if (xdgConfigHome)
|
|
2863
|
-
return path.join(xdgConfigHome, "akm");
|
|
2864
|
-
const home = env.HOME?.trim();
|
|
2865
|
-
if (!home) {
|
|
2866
|
-
throw new ConfigError("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.", "CONFIG_DIR_UNRESOLVABLE");
|
|
2867
|
-
}
|
|
2868
|
-
return path.join(home, ".config", "akm");
|
|
2869
|
-
}
|
|
2870
|
-
function getCacheDir(env = process.env) {
|
|
2871
|
-
const override = env.AKM_CACHE_DIR?.trim();
|
|
2872
|
-
if (override)
|
|
2873
|
-
return override;
|
|
2874
|
-
if (IS_WINDOWS) {
|
|
2875
|
-
const localAppData = env.LOCALAPPDATA?.trim();
|
|
2876
|
-
if (localAppData)
|
|
2877
|
-
return path.join(localAppData, "akm");
|
|
2878
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
2879
|
-
if (userProfile)
|
|
2880
|
-
return path.join(userProfile, "AppData", "Local", "akm");
|
|
2881
|
-
const appData = env.APPDATA?.trim();
|
|
2882
|
-
if (appData) {
|
|
2883
|
-
return path.join(appData, "..", "Local", "akm");
|
|
2884
|
-
}
|
|
2885
|
-
} else {
|
|
2886
|
-
const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
|
|
2887
|
-
if (xdgCacheHome)
|
|
2888
|
-
return path.join(xdgCacheHome, "akm");
|
|
2889
|
-
}
|
|
2890
|
-
const stashOverride = env.AKM_STASH_DIR?.trim();
|
|
2891
|
-
if (stashOverride && isTransientStashPath(stashOverride)) {
|
|
2892
|
-
return path.join(stashOverride, ".akm", "cache");
|
|
2893
|
-
}
|
|
2894
|
-
if (IS_WINDOWS) {
|
|
2895
|
-
throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
|
|
2896
|
-
}
|
|
2897
|
-
const home = env.HOME?.trim();
|
|
2898
|
-
if (!home)
|
|
2899
|
-
return path.join("/tmp", "akm-cache");
|
|
2900
|
-
return path.join(home, ".cache", "akm");
|
|
2901
|
-
}
|
|
2902
|
-
function getDefaultStashDir(env = process.env) {
|
|
2903
|
-
const override = env.AKM_STASH_DIR?.trim();
|
|
2904
|
-
if (override)
|
|
2905
|
-
return override;
|
|
2906
|
-
if (IS_WINDOWS) {
|
|
2907
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
2908
|
-
if (userProfile)
|
|
2909
|
-
return path.join(userProfile, "Documents", "akm");
|
|
2910
|
-
return path.join("C:\\", "akm");
|
|
2911
|
-
}
|
|
2912
|
-
const home = env.HOME?.trim();
|
|
2913
|
-
if (!home) {
|
|
2914
|
-
throw new ConfigError("Unable to determine default stash directory. Set HOME.", "STASH_DIR_NOT_FOUND");
|
|
2915
|
-
}
|
|
2916
|
-
return path.join(home, "akm");
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
// src/indexer/usage/unmigrated-vaults-guard.ts
|
|
2920
|
-
init_warn();
|
|
2921
|
-
var MIGRATED_MARKER = ".migrated";
|
|
2922
|
-
var warnedStashDirs = new Set;
|
|
2923
|
-
|
|
2924
|
-
// scripts/migrate-storage.ts
|
|
2925
|
-
var args = process.argv.slice(2);
|
|
2926
|
-
var DRY_RUN = args.includes("--dry-run");
|
|
2927
|
-
var YES = args.includes("--yes");
|
|
2928
|
-
var LIST_ONLY = args.includes("--list");
|
|
2929
|
-
function parseFromArg() {
|
|
2930
|
-
const idx = args.indexOf("--from");
|
|
2931
|
-
if (idx === -1)
|
|
2932
|
-
return null;
|
|
2933
|
-
const val = args[idx + 1];
|
|
2934
|
-
if (!val || val.startsWith("--")) {
|
|
2935
|
-
console.error("Error: --from requires a version argument (e.g. --from 0.8).");
|
|
2936
|
-
process.exit(2);
|
|
2937
|
-
}
|
|
2938
|
-
return val;
|
|
2939
|
-
}
|
|
2940
|
-
var FROM_VERSION = parseFromArg();
|
|
2941
|
-
var dataDir = process.env.AKM_DATA_DIR?.trim() ?? (process.env.XDG_DATA_HOME?.trim() ? path8.join(process.env.XDG_DATA_HOME.trim(), "akm") : path8.join(os.homedir(), ".local", "share", "akm"));
|
|
2942
|
-
var stateDir = process.env.AKM_STATE_DIR?.trim() ?? (process.env.XDG_STATE_HOME?.trim() ? path8.join(process.env.XDG_STATE_HOME.trim(), "akm") : path8.join(os.homedir(), ".local", "state", "akm"));
|
|
2943
|
-
var cacheDir = getCacheDir();
|
|
2944
|
-
var configDir = getConfigDir();
|
|
2945
|
-
var stateDbPath = path8.join(dataDir, "state.db");
|
|
2946
|
-
var indexDbPath = path8.join(dataDir, "index.db");
|
|
2947
|
-
var PATHS = {
|
|
2948
|
-
cacheDir,
|
|
2949
|
-
configDir,
|
|
2950
|
-
dataDir,
|
|
2951
|
-
stateDir,
|
|
2952
|
-
stateDbPath,
|
|
2953
|
-
indexDbPath
|
|
2954
|
-
};
|
|
2955
|
-
var versionReports = [];
|
|
2956
|
-
function ensureDir(dir) {
|
|
2957
|
-
if (!fs10.existsSync(dir)) {
|
|
2958
|
-
fs10.mkdirSync(dir, { recursive: true });
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
function copyAndVerify(src, dest) {
|
|
2962
|
-
fs10.copyFileSync(src, dest);
|
|
2963
|
-
const srcStat = fs10.statSync(src);
|
|
2964
|
-
const destStat = fs10.statSync(dest);
|
|
2965
|
-
return destStat.size === srcStat.size;
|
|
2966
|
-
}
|
|
2967
|
-
function copyTree(src, dest, opts) {
|
|
2968
|
-
let copied = 0;
|
|
2969
|
-
let skipped = 0;
|
|
2970
|
-
let failed = 0;
|
|
2971
|
-
ensureDir(dest);
|
|
2972
|
-
for (const entry of fs10.readdirSync(src, { withFileTypes: true })) {
|
|
2973
|
-
const srcEntry = path8.join(src, entry.name);
|
|
2974
|
-
const destEntry = path8.join(dest, entry.name);
|
|
2975
|
-
if (entry.isDirectory()) {
|
|
2976
|
-
const sub = copyTree(srcEntry, destEntry, opts);
|
|
2977
|
-
copied += sub.copied;
|
|
2978
|
-
skipped += sub.skipped;
|
|
2979
|
-
failed += sub.failed;
|
|
2980
|
-
continue;
|
|
2981
|
-
}
|
|
2982
|
-
if (!opts.clobber && fs10.existsSync(destEntry)) {
|
|
2983
|
-
skipped++;
|
|
2984
|
-
continue;
|
|
2985
|
-
}
|
|
2986
|
-
try {
|
|
2987
|
-
if (copyAndVerify(srcEntry, destEntry)) {
|
|
2988
|
-
copied++;
|
|
2989
|
-
} else {
|
|
2990
|
-
failed++;
|
|
2991
|
-
}
|
|
2992
|
-
} catch {
|
|
2993
|
-
failed++;
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
return { copied, skipped, failed };
|
|
2997
|
-
}
|
|
2998
|
-
function countFilesRecursive(dir) {
|
|
2999
|
-
let count = 0;
|
|
3000
|
-
for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
|
|
3001
|
-
if (entry.isDirectory()) {
|
|
3002
|
-
count += countFilesRecursive(path8.join(dir, entry.name));
|
|
3003
|
-
} else {
|
|
3004
|
-
count++;
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
return count;
|
|
3008
|
-
}
|
|
3009
|
-
async function runSteps(steps, ctx) {
|
|
3010
|
-
for (const step of steps) {
|
|
3011
|
-
ctx.recordStep(await step.run(ctx));
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
function migrateDb(ctx, filename) {
|
|
3015
|
-
const src = path8.join(ctx.paths.cacheDir, filename);
|
|
3016
|
-
const dest = path8.join(ctx.paths.dataDir, filename);
|
|
3017
|
-
if (!fs10.existsSync(src)) {
|
|
3018
|
-
return { name: filename, status: "skipped", detail: "source not found" };
|
|
3019
|
-
}
|
|
3020
|
-
if (fs10.existsSync(dest)) {
|
|
3021
|
-
return { name: filename, status: "skipped", detail: "destination already exists" };
|
|
3022
|
-
}
|
|
3023
|
-
if (ctx.dryRun) {
|
|
3024
|
-
return { name: filename, status: "success", detail: `[dry-run] would copy ${src} \u2192 ${dest}` };
|
|
3025
|
-
}
|
|
3026
|
-
ensureDir(ctx.paths.dataDir);
|
|
3027
|
-
const ok = copyAndVerify(src, dest);
|
|
3028
|
-
if (ok) {
|
|
3029
|
-
return {
|
|
3030
|
-
name: filename,
|
|
3031
|
-
status: "success",
|
|
3032
|
-
detail: `copied to ${dest} \u2014 source left at ${src} (delete manually when ready)`
|
|
3033
|
-
};
|
|
3034
|
-
}
|
|
3035
|
-
return { name: filename, status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
|
|
3036
|
-
}
|
|
3037
|
-
async function migrateEventsJsonl(ctx) {
|
|
3038
|
-
const src = path8.join(ctx.paths.cacheDir, "events.jsonl");
|
|
3039
|
-
if (!fs10.existsSync(src)) {
|
|
3040
|
-
return { name: "events.jsonl \u2192 state.db", status: "skipped", detail: "source not found" };
|
|
3041
|
-
}
|
|
3042
|
-
if (ctx.dryRun) {
|
|
3043
|
-
return {
|
|
3044
|
-
name: "events.jsonl \u2192 state.db",
|
|
3045
|
-
status: "success",
|
|
3046
|
-
detail: `[dry-run] would import ${src} into ${ctx.paths.stateDbPath}`
|
|
3047
|
-
};
|
|
3048
|
-
}
|
|
3049
|
-
try {
|
|
3050
|
-
ensureDir(ctx.paths.dataDir);
|
|
3051
|
-
const { openStateDatabase: openStateDatabase2 } = await Promise.resolve().then(() => (init_state_db(), exports_state_db));
|
|
3052
|
-
const { importEventsJsonl: importEventsJsonl2 } = await Promise.resolve().then(() => (init_events_repository(), exports_events_repository));
|
|
3053
|
-
const db = openStateDatabase2(ctx.paths.stateDbPath);
|
|
3054
|
-
try {
|
|
3055
|
-
db.exec("BEGIN");
|
|
3056
|
-
let imported = 0;
|
|
3057
|
-
let maxId = 0;
|
|
3058
|
-
let skipped = 0;
|
|
3059
|
-
try {
|
|
3060
|
-
const result = await importEventsJsonl2(db, src);
|
|
3061
|
-
imported = result.imported;
|
|
3062
|
-
maxId = result.maxId;
|
|
3063
|
-
skipped = result.skipped;
|
|
3064
|
-
db.exec("COMMIT");
|
|
3065
|
-
} catch (err) {
|
|
3066
|
-
db.exec("ROLLBACK");
|
|
3067
|
-
throw err;
|
|
3068
|
-
}
|
|
3069
|
-
const dedupNote = skipped > 0 ? ` (${skipped} duplicate(s) skipped \u2014 re-run idempotency)` : "";
|
|
3070
|
-
return {
|
|
3071
|
-
name: "events.jsonl \u2192 state.db",
|
|
3072
|
-
status: "success",
|
|
3073
|
-
detail: `imported ${imported} events (max id: ${maxId})${dedupNote} \u2014 source left at ${src} (delete manually when ready)`
|
|
3074
|
-
};
|
|
3075
|
-
} finally {
|
|
3076
|
-
db.close();
|
|
3077
|
-
}
|
|
3078
|
-
} catch (err) {
|
|
3079
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3080
|
-
return { name: "events.jsonl \u2192 state.db", status: "failed", detail: msg };
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
function migrateTaskHistory(ctx) {
|
|
3084
|
-
const src = path8.join(ctx.paths.cacheDir, "tasks", "history");
|
|
3085
|
-
const dest = path8.join(ctx.paths.stateDir, "tasks", "history");
|
|
3086
|
-
if (!fs10.existsSync(src)) {
|
|
3087
|
-
return { name: "tasks/history/", status: "skipped", detail: "source directory not found" };
|
|
3088
|
-
}
|
|
3089
|
-
try {
|
|
3090
|
-
const files = fs10.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
|
|
3091
|
-
if (files.length === 0) {
|
|
3092
|
-
return { name: "tasks/history/", status: "skipped", detail: "no *.jsonl files found in source directory" };
|
|
3093
|
-
}
|
|
3094
|
-
if (ctx.dryRun) {
|
|
3095
|
-
return {
|
|
3096
|
-
name: "tasks/history/",
|
|
3097
|
-
status: "success",
|
|
3098
|
-
detail: `[dry-run] would copy ${files.length} *.jsonl file(s) from ${src} \u2192 ${dest}`
|
|
3099
|
-
};
|
|
3100
|
-
}
|
|
3101
|
-
ensureDir(dest);
|
|
3102
|
-
let copied = 0;
|
|
3103
|
-
let failed = 0;
|
|
3104
|
-
for (const file of files) {
|
|
3105
|
-
const srcFile = path8.join(src, file);
|
|
3106
|
-
const destFile = path8.join(dest, file);
|
|
3107
|
-
try {
|
|
3108
|
-
if (copyAndVerify(srcFile, destFile)) {
|
|
3109
|
-
copied++;
|
|
3110
|
-
} else {
|
|
3111
|
-
failed++;
|
|
3112
|
-
}
|
|
3113
|
-
} catch {
|
|
3114
|
-
failed++;
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
if (failed === 0) {
|
|
3118
|
-
return {
|
|
3119
|
-
name: "tasks/history/",
|
|
3120
|
-
status: "success",
|
|
3121
|
-
detail: `copied ${copied} files to ${dest} \u2014 sources left in place (delete manually when ready)`
|
|
3122
|
-
};
|
|
3123
|
-
}
|
|
3124
|
-
return {
|
|
3125
|
-
name: "tasks/history/",
|
|
3126
|
-
status: "failed",
|
|
3127
|
-
detail: `copied ${copied}/${files.length} files; ${failed} failed`
|
|
3128
|
-
};
|
|
3129
|
-
} catch (err) {
|
|
3130
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3131
|
-
return { name: "tasks/history/", status: "failed", detail: msg };
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
function migrateLockfile(ctx) {
|
|
3135
|
-
const src = path8.join(ctx.paths.configDir, "akm.lock");
|
|
3136
|
-
const dest = path8.join(ctx.paths.dataDir, "akm.lock");
|
|
3137
|
-
if (!fs10.existsSync(src)) {
|
|
3138
|
-
return { name: "akm.lock", status: "skipped", detail: "source not found" };
|
|
3139
|
-
}
|
|
3140
|
-
if (fs10.existsSync(dest)) {
|
|
3141
|
-
return { name: "akm.lock", status: "skipped", detail: "destination already exists" };
|
|
3142
|
-
}
|
|
3143
|
-
if (ctx.dryRun) {
|
|
3144
|
-
return { name: "akm.lock", status: "success", detail: `[dry-run] would copy ${src} \u2192 ${dest}` };
|
|
3145
|
-
}
|
|
3146
|
-
ensureDir(ctx.paths.dataDir);
|
|
3147
|
-
const ok = copyAndVerify(src, dest);
|
|
3148
|
-
if (ok) {
|
|
3149
|
-
return {
|
|
3150
|
-
name: "akm.lock",
|
|
3151
|
-
status: "success",
|
|
3152
|
-
detail: `copied to ${dest} \u2014 source left at ${src}.
|
|
3153
|
-
IMPORTANT: akm now reads ONLY from $DATA/akm.lock. If this step is skipped,
|
|
3154
|
-
akm will start with an empty lockfile and 'akm add' will rebuild it from scratch.`
|
|
3155
|
-
};
|
|
3156
|
-
}
|
|
3157
|
-
return { name: "akm.lock", status: "failed", detail: `size mismatch after copy: ${src} \u2192 ${dest}` };
|
|
3158
|
-
}
|
|
3159
|
-
function migrateConfigBackups(ctx) {
|
|
3160
|
-
const src = path8.join(ctx.paths.cacheDir, "config-backups");
|
|
3161
|
-
const dest = path8.join(ctx.paths.dataDir, "config-backups");
|
|
3162
|
-
if (!fs10.existsSync(src)) {
|
|
3163
|
-
return { name: "config-backups/", status: "skipped", detail: "source directory not found" };
|
|
3164
|
-
}
|
|
3165
|
-
if (fs10.existsSync(dest)) {
|
|
3166
|
-
return { name: "config-backups/", status: "skipped", detail: "destination already exists" };
|
|
3167
|
-
}
|
|
3168
|
-
if (ctx.dryRun) {
|
|
3169
|
-
let srcCount = 0;
|
|
3170
|
-
try {
|
|
3171
|
-
srcCount = countFilesRecursive(src);
|
|
3172
|
-
} catch {}
|
|
3173
|
-
return {
|
|
3174
|
-
name: "config-backups/",
|
|
3175
|
-
status: "success",
|
|
3176
|
-
detail: `[dry-run] would recursively copy ${srcCount} file(s) from ${src} \u2192 ${dest}`
|
|
3177
|
-
};
|
|
3178
|
-
}
|
|
3179
|
-
try {
|
|
3180
|
-
const srcCount = countFilesRecursive(src);
|
|
3181
|
-
const { copied, failed } = copyTree(src, dest, { clobber: true });
|
|
3182
|
-
const destCount = fs10.existsSync(dest) ? countFilesRecursive(dest) : 0;
|
|
3183
|
-
if (failed === 0 && destCount === srcCount) {
|
|
3184
|
-
return {
|
|
3185
|
-
name: "config-backups/",
|
|
3186
|
-
status: "success",
|
|
3187
|
-
detail: `copied ${copied} files to ${dest} \u2014 sources left in place (delete manually when ready)`
|
|
3188
|
-
};
|
|
3189
|
-
}
|
|
3190
|
-
return {
|
|
3191
|
-
name: "config-backups/",
|
|
3192
|
-
status: "failed",
|
|
3193
|
-
detail: `file count mismatch: source ${srcCount}, destination ${destCount}; ${failed} copy errors`
|
|
3194
|
-
};
|
|
3195
|
-
} catch (err) {
|
|
3196
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3197
|
-
return { name: "config-backups/", status: "failed", detail: msg };
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
async function migrateTaskHistoryToDb(ctx) {
|
|
3201
|
-
const src = path8.join(ctx.paths.cacheDir, "tasks", "history");
|
|
3202
|
-
if (!fs10.existsSync(src)) {
|
|
3203
|
-
return { name: "tasks/history/ \u2192 state.db", status: "skipped", detail: "source directory not found" };
|
|
3204
|
-
}
|
|
3205
|
-
const files = fs10.readdirSync(src).filter((f) => f.endsWith(".jsonl"));
|
|
3206
|
-
if (files.length === 0) {
|
|
3207
|
-
return {
|
|
3208
|
-
name: "tasks/history/ \u2192 state.db",
|
|
3209
|
-
status: "skipped",
|
|
3210
|
-
detail: "no *.jsonl files found in source directory"
|
|
3211
|
-
};
|
|
3212
|
-
}
|
|
3213
|
-
if (ctx.dryRun) {
|
|
3214
|
-
return {
|
|
3215
|
-
name: "tasks/history/ \u2192 state.db",
|
|
3216
|
-
status: "success",
|
|
3217
|
-
detail: `[dry-run] would parse ${files.length} *.jsonl file(s) and import rows into ${ctx.paths.stateDbPath}`
|
|
3218
|
-
};
|
|
3219
|
-
}
|
|
3220
|
-
try {
|
|
3221
|
-
ensureDir(ctx.paths.dataDir);
|
|
3222
|
-
const { openStateDatabase: openStateDatabase2 } = await Promise.resolve().then(() => (init_state_db(), exports_state_db));
|
|
3223
|
-
const { upsertTaskHistory: upsertTaskHistory2 } = await Promise.resolve().then(() => exports_task_history_repository);
|
|
3224
|
-
const db = openStateDatabase2(ctx.paths.stateDbPath);
|
|
3225
|
-
let imported = 0;
|
|
3226
|
-
let failed = 0;
|
|
3227
|
-
try {
|
|
3228
|
-
for (const file of files) {
|
|
3229
|
-
const filePath = path8.join(src, file);
|
|
3230
|
-
const text = fs10.readFileSync(filePath, "utf8");
|
|
3231
|
-
const lines = text.split(`
|
|
3232
|
-
`).filter((l) => l.trim().length > 0);
|
|
3233
|
-
db.exec("BEGIN");
|
|
3234
|
-
let fileImported = 0;
|
|
3235
|
-
try {
|
|
3236
|
-
for (const line of lines) {
|
|
3237
|
-
try {
|
|
3238
|
-
const row = JSON.parse(line);
|
|
3239
|
-
const meta = { durationMs: row.durationMs };
|
|
3240
|
-
if (row.detail !== undefined)
|
|
3241
|
-
meta.detail = row.detail;
|
|
3242
|
-
if (row.target?.kind === "prompt" && row.target.profile !== undefined) {
|
|
3243
|
-
meta.profile = row.target.profile;
|
|
3244
|
-
}
|
|
3245
|
-
upsertTaskHistory2(db, {
|
|
3246
|
-
task_id: row.id,
|
|
3247
|
-
status: row.status,
|
|
3248
|
-
started_at: row.startedAt,
|
|
3249
|
-
completed_at: row.status !== "failed" ? row.finishedAt : null,
|
|
3250
|
-
failed_at: row.status === "failed" ? row.finishedAt : null,
|
|
3251
|
-
log_path: row.log || null,
|
|
3252
|
-
target_kind: row.target?.kind ?? null,
|
|
3253
|
-
target_ref: row.target?.kind === "workflow" ? row.target.ref ?? null : null,
|
|
3254
|
-
metadata_json: JSON.stringify(meta)
|
|
3255
|
-
});
|
|
3256
|
-
fileImported++;
|
|
3257
|
-
} catch {
|
|
3258
|
-
failed++;
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
db.exec("COMMIT");
|
|
3262
|
-
imported += fileImported;
|
|
3263
|
-
} catch (err) {
|
|
3264
|
-
db.exec("ROLLBACK");
|
|
3265
|
-
throw err;
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
} finally {
|
|
3269
|
-
db.close();
|
|
3270
|
-
}
|
|
3271
|
-
if (failed === 0) {
|
|
3272
|
-
return {
|
|
3273
|
-
name: "tasks/history/ \u2192 state.db",
|
|
3274
|
-
status: "success",
|
|
3275
|
-
detail: `imported ${imported} task history row(s) from ${files.length} JSONL file(s) into state.db \u2014 sources left in place (delete manually when ready)`
|
|
3276
|
-
};
|
|
3277
|
-
}
|
|
3278
|
-
return {
|
|
3279
|
-
name: "tasks/history/ \u2192 state.db",
|
|
3280
|
-
status: "failed",
|
|
3281
|
-
detail: `imported ${imported} row(s); ${failed} line(s) could not be parsed`
|
|
3282
|
-
};
|
|
3283
|
-
} catch (err) {
|
|
3284
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3285
|
-
return { name: "tasks/history/ \u2192 state.db", status: "failed", detail: msg };
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
function noteRegistryIndexCache(ctx) {
|
|
3289
|
-
const src = path8.join(ctx.paths.cacheDir, "registry-index");
|
|
3290
|
-
if (!fs10.existsSync(src)) {
|
|
3291
|
-
return {
|
|
3292
|
-
name: "registry-index/ (note)",
|
|
3293
|
-
status: "skipped",
|
|
3294
|
-
detail: "no old $CACHE/registry-index/ directory found"
|
|
3295
|
-
};
|
|
3296
|
-
}
|
|
3297
|
-
const legacyFiles = fs10.readdirSync(src).filter((f) => f.endsWith(".json") && !f.startsWith("website-"));
|
|
3298
|
-
if (legacyFiles.length === 0) {
|
|
3299
|
-
return { name: "registry-index/ (note)", status: "skipped", detail: "no old *.json cache files found" };
|
|
3300
|
-
}
|
|
3301
|
-
if (!ctx.dryRun) {
|
|
3302
|
-
console.log(`
|
|
3303
|
-
Note: found ${legacyFiles.length} old registry-index JSON file(s) in ${src}.` + `
|
|
3304
|
-
These are ignored in v0.9 \u2014 data is now stored in the registry_index_cache` + `
|
|
3305
|
-
table in $DATA/index.db and will be rebuilt on next 'akm registry search'.
|
|
3306
|
-
You may safely delete these files after migration:
|
|
3307
|
-
` + legacyFiles.map((f) => ` ${path8.join(src, f)}`).join(`
|
|
3308
|
-
`));
|
|
3309
|
-
}
|
|
3310
|
-
return {
|
|
3311
|
-
name: "registry-index/ (note)",
|
|
3312
|
-
status: "success",
|
|
3313
|
-
detail: `${legacyFiles.length} old file(s) noted at ${src} \u2014 registry index cache will be rebuilt on next ` + `'akm registry search'. Safe to delete: ${src}/*.json`
|
|
3314
|
-
};
|
|
3315
|
-
}
|
|
3316
|
-
var v07To08Migration = {
|
|
3317
|
-
label: "0.7 \u2192 0.8",
|
|
3318
|
-
sourceVersion: "0.7",
|
|
3319
|
-
isNeeded: (paths) => {
|
|
3320
|
-
const candidates = [
|
|
3321
|
-
path8.join(paths.cacheDir, "index.db"),
|
|
3322
|
-
path8.join(paths.cacheDir, "workflow.db"),
|
|
3323
|
-
path8.join(paths.cacheDir, "events.jsonl"),
|
|
3324
|
-
path8.join(paths.cacheDir, "tasks", "history"),
|
|
3325
|
-
path8.join(paths.configDir, "akm.lock"),
|
|
3326
|
-
path8.join(paths.cacheDir, "config-backups"),
|
|
3327
|
-
path8.join(paths.cacheDir, "registry-index")
|
|
3328
|
-
];
|
|
3329
|
-
return candidates.some((p) => fs10.existsSync(p));
|
|
3330
|
-
},
|
|
3331
|
-
steps: [
|
|
3332
|
-
{ id: "index-db", title: "index.db", run: (ctx) => migrateDb(ctx, "index.db") },
|
|
3333
|
-
{ id: "workflow-db", title: "workflow.db", run: (ctx) => migrateDb(ctx, "workflow.db") },
|
|
3334
|
-
{ id: "events-jsonl", title: "events.jsonl \u2192 state.db", run: migrateEventsJsonl },
|
|
3335
|
-
{ id: "tasks-history-files", title: "tasks/history/", run: migrateTaskHistory },
|
|
3336
|
-
{ id: "lockfile", title: "akm.lock", run: migrateLockfile },
|
|
3337
|
-
{ id: "config-backups", title: "config-backups/", run: migrateConfigBackups },
|
|
3338
|
-
{ id: "tasks-history-db", title: "tasks/history/ \u2192 state.db", run: migrateTaskHistoryToDb },
|
|
3339
|
-
{ id: "registry-index-note", title: "registry-index/ (note)", run: noteRegistryIndexCache }
|
|
3340
|
-
]
|
|
3341
|
-
};
|
|
3342
|
-
function legacyGraphCandidatePaths(ctx) {
|
|
3343
|
-
return [
|
|
3344
|
-
path8.join(ctx.paths.cacheDir, "graph-snapshot.json"),
|
|
3345
|
-
path8.join(ctx.paths.dataDir, "graph-snapshot.json"),
|
|
3346
|
-
path8.join(ctx.paths.dataDir, "graph-export.json")
|
|
3347
|
-
];
|
|
3348
|
-
}
|
|
3349
|
-
function findLegacyGraphFile(ctx) {
|
|
3350
|
-
for (const candidate of legacyGraphCandidatePaths(ctx)) {
|
|
3351
|
-
if (fs10.existsSync(candidate) && fs10.statSync(candidate).isFile())
|
|
3352
|
-
return candidate;
|
|
3353
|
-
}
|
|
3354
|
-
const graphDir = path8.join(ctx.paths.cacheDir, "graph");
|
|
3355
|
-
if (fs10.existsSync(graphDir) && fs10.statSync(graphDir).isDirectory()) {
|
|
3356
|
-
try {
|
|
3357
|
-
const json = fs10.readdirSync(graphDir).filter((f) => f.endsWith(".json")).map((f) => path8.join(graphDir, f));
|
|
3358
|
-
const [first] = json;
|
|
3359
|
-
if (first)
|
|
3360
|
-
return first;
|
|
3361
|
-
} catch {}
|
|
3362
|
-
}
|
|
3363
|
-
return null;
|
|
3364
|
-
}
|
|
3365
|
-
function validateGraphSnapshot(parsed) {
|
|
3366
|
-
if (parsed == null || typeof parsed !== "object")
|
|
3367
|
-
return { ok: false, reason: "root is not an object" };
|
|
3368
|
-
const obj = parsed;
|
|
3369
|
-
if (typeof obj.stashRoot !== "string" || obj.stashRoot.length === 0) {
|
|
3370
|
-
return { ok: false, reason: "missing string field 'stashRoot'" };
|
|
3371
|
-
}
|
|
3372
|
-
if (!Array.isArray(obj.files))
|
|
3373
|
-
return { ok: false, reason: "missing array field 'files'" };
|
|
3374
|
-
for (const [i, f] of obj.files.entries()) {
|
|
3375
|
-
if (!f || typeof f !== "object")
|
|
3376
|
-
return { ok: false, reason: `files[${i}] is not an object` };
|
|
3377
|
-
if (typeof f.path !== "string")
|
|
3378
|
-
return { ok: false, reason: `files[${i}].path must be a string` };
|
|
3379
|
-
if (typeof f.type !== "string")
|
|
3380
|
-
return { ok: false, reason: `files[${i}].type must be a string` };
|
|
3381
|
-
if (!Array.isArray(f.entities))
|
|
3382
|
-
return { ok: false, reason: `files[${i}].entities must be an array` };
|
|
3383
|
-
if (!Array.isArray(f.relations))
|
|
3384
|
-
return { ok: false, reason: `files[${i}].relations must be an array` };
|
|
3385
|
-
}
|
|
3386
|
-
return { ok: true, data: obj };
|
|
3387
|
-
}
|
|
3388
|
-
async function migrateGraphFileToDb(ctx) {
|
|
3389
|
-
const name = "Graph snapshot import";
|
|
3390
|
-
const legacyFile = findLegacyGraphFile(ctx);
|
|
3391
|
-
if (!legacyFile) {
|
|
3392
|
-
return { name, status: "skipped", detail: "no legacy graph file found" };
|
|
3393
|
-
}
|
|
3394
|
-
if (!fs10.existsSync(ctx.paths.indexDbPath)) {
|
|
3395
|
-
return {
|
|
3396
|
-
name,
|
|
3397
|
-
status: "skipped",
|
|
3398
|
-
detail: `index.db not found at ${ctx.paths.indexDbPath}; run akm to initialize, then re-run this migration`
|
|
3399
|
-
};
|
|
3400
|
-
}
|
|
3401
|
-
if (ctx.dryRun) {
|
|
3402
|
-
return {
|
|
3403
|
-
name,
|
|
3404
|
-
status: "success",
|
|
3405
|
-
detail: `[dry-run] would parse ${legacyFile} and import into ${ctx.paths.indexDbPath} graph tables`
|
|
3406
|
-
};
|
|
3407
|
-
}
|
|
3408
|
-
try {
|
|
3409
|
-
const raw = fs10.readFileSync(legacyFile, "utf8");
|
|
3410
|
-
let parsed;
|
|
3411
|
-
try {
|
|
3412
|
-
parsed = JSON.parse(raw);
|
|
3413
|
-
} catch (err) {
|
|
3414
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3415
|
-
return { name, status: "failed", detail: `${legacyFile} is not valid JSON: ${msg}` };
|
|
3416
|
-
}
|
|
3417
|
-
const validation = validateGraphSnapshot(parsed);
|
|
3418
|
-
if (!validation.ok) {
|
|
3419
|
-
return {
|
|
3420
|
-
name,
|
|
3421
|
-
status: "failed",
|
|
3422
|
-
detail: `${legacyFile} does not match expected GraphSnapshot shape: ${validation.reason}`
|
|
3423
|
-
};
|
|
3424
|
-
}
|
|
3425
|
-
const snapshot = validation.data;
|
|
3426
|
-
const stashRoot = snapshot.stashRoot;
|
|
3427
|
-
const { openExistingDatabase: openExistingDatabase3, closeDatabase: closeDatabase3 } = await Promise.resolve().then(() => (init_index_connection(), exports_index_connection));
|
|
3428
|
-
const { replaceStoredGraph: replaceStoredGraph2, loadStoredGraphMeta: loadStoredGraphMeta2 } = await Promise.resolve().then(() => (init_graph_db(), exports_graph_db));
|
|
3429
|
-
const db = openExistingDatabase3(ctx.paths.indexDbPath);
|
|
3430
|
-
try {
|
|
3431
|
-
const graph = {
|
|
3432
|
-
schemaVersion: snapshot.schemaVersion ?? 2,
|
|
3433
|
-
generatedAt: snapshot.generatedAt ?? new Date().toISOString(),
|
|
3434
|
-
stashRoot,
|
|
3435
|
-
files: (snapshot.files ?? []).map((f) => ({
|
|
3436
|
-
path: f.path,
|
|
3437
|
-
type: f.type,
|
|
3438
|
-
...f.bodyHash ? { bodyHash: f.bodyHash } : {},
|
|
3439
|
-
entities: f.entities ?? [],
|
|
3440
|
-
relations: (f.relations ?? []).map((r) => ({
|
|
3441
|
-
from: r.from,
|
|
3442
|
-
to: r.to,
|
|
3443
|
-
...r.type ? { type: r.type } : {},
|
|
3444
|
-
...typeof r.confidence === "number" ? { confidence: r.confidence } : {}
|
|
3445
|
-
})),
|
|
3446
|
-
...typeof f.confidence === "number" ? { confidence: f.confidence } : {}
|
|
3447
|
-
})),
|
|
3448
|
-
...snapshot.entities ? { entities: snapshot.entities } : {},
|
|
3449
|
-
...snapshot.relations ? {
|
|
3450
|
-
relations: snapshot.relations.map((r) => ({
|
|
3451
|
-
from: r.from,
|
|
3452
|
-
to: r.to,
|
|
3453
|
-
...r.type ? { type: r.type } : {},
|
|
3454
|
-
...typeof r.confidence === "number" ? { confidence: r.confidence } : {}
|
|
3455
|
-
}))
|
|
3456
|
-
} : {},
|
|
3457
|
-
...snapshot.quality ? {
|
|
3458
|
-
quality: {
|
|
3459
|
-
consideredFiles: snapshot.quality.consideredFiles ?? 0,
|
|
3460
|
-
extractedFiles: snapshot.quality.extractedFiles ?? 0,
|
|
3461
|
-
entityCount: snapshot.quality.entityCount ?? 0,
|
|
3462
|
-
relationCount: snapshot.quality.relationCount ?? 0,
|
|
3463
|
-
extractionCoverage: snapshot.quality.extractionCoverage ?? 0,
|
|
3464
|
-
density: snapshot.quality.density ?? 0
|
|
3465
|
-
}
|
|
3466
|
-
} : {}
|
|
3467
|
-
};
|
|
3468
|
-
replaceStoredGraph2(db, graph);
|
|
3469
|
-
const importedCount = db.prepare("SELECT COUNT(*) AS cnt FROM graph_files WHERE stash_root = ?").get(stashRoot).cnt;
|
|
3470
|
-
const meta = loadStoredGraphMeta2(stashRoot, db);
|
|
3471
|
-
if (!meta) {
|
|
3472
|
-
return {
|
|
3473
|
-
name,
|
|
3474
|
-
status: "failed",
|
|
3475
|
-
detail: `import did not produce a graph_meta row for stash ${snapshot.stashRoot}`
|
|
3476
|
-
};
|
|
3477
|
-
}
|
|
3478
|
-
if (importedCount === 0) {
|
|
3479
|
-
return {
|
|
3480
|
-
name,
|
|
3481
|
-
status: "failed",
|
|
3482
|
-
detail: `import produced zero graph_files rows for stash ${snapshot.stashRoot} \u2014 ` + `the entries table has no matching paths. Run "akm index" first, then retry the migration. Source file ${legacyFile} was NOT renamed.`
|
|
3483
|
-
};
|
|
3484
|
-
}
|
|
3485
|
-
const renamed = `${legacyFile}.migrated`;
|
|
3486
|
-
try {
|
|
3487
|
-
fs10.renameSync(legacyFile, renamed);
|
|
3488
|
-
} catch (err) {
|
|
3489
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3490
|
-
return {
|
|
3491
|
-
name,
|
|
3492
|
-
status: "success",
|
|
3493
|
-
detail: `imported graph for ${snapshot.stashRoot} into ${ctx.paths.indexDbPath} but could not rename ${legacyFile}: ${msg}`
|
|
3494
|
-
};
|
|
3495
|
-
}
|
|
3496
|
-
return {
|
|
3497
|
-
name,
|
|
3498
|
-
status: "success",
|
|
3499
|
-
detail: `imported graph snapshot from ${legacyFile} into ${ctx.paths.indexDbPath} (stash ${snapshot.stashRoot}; ${importedCount} file(s) imported of ${graph.files.length} in source). Source renamed to ${renamed} \u2014 delete manually when ready.`
|
|
3500
|
-
};
|
|
3501
|
-
} finally {
|
|
3502
|
-
closeDatabase3(db);
|
|
3503
|
-
}
|
|
3504
|
-
} catch (err) {
|
|
3505
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3506
|
-
return { name, status: "failed", detail: msg };
|
|
3507
|
-
}
|
|
3508
|
-
}
|
|
3509
|
-
function resolvePrimaryStashDir(configDirPath) {
|
|
3510
|
-
try {
|
|
3511
|
-
const cfgPath = path8.join(configDirPath, "config.json");
|
|
3512
|
-
if (fs10.existsSync(cfgPath)) {
|
|
3513
|
-
const cfg = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
3514
|
-
if (typeof cfg.stashDir === "string" && cfg.stashDir.length > 0)
|
|
3515
|
-
return cfg.stashDir;
|
|
3516
|
-
}
|
|
3517
|
-
} catch {}
|
|
3518
|
-
return getDefaultStashDir();
|
|
3519
|
-
}
|
|
3520
|
-
function countEnvFilesRecursive(dir) {
|
|
3521
|
-
let count = 0;
|
|
3522
|
-
for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
|
|
3523
|
-
const full = path8.join(dir, entry.name);
|
|
3524
|
-
if (entry.isDirectory()) {
|
|
3525
|
-
count += countEnvFilesRecursive(full);
|
|
3526
|
-
} else if (entry.name === ".env" || entry.name.endsWith(".env")) {
|
|
3527
|
-
count++;
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
return count;
|
|
3531
|
-
}
|
|
3532
|
-
function chmodTreeSecure(dir) {
|
|
3533
|
-
const isWin = process.platform === "win32";
|
|
3534
|
-
try {
|
|
3535
|
-
fs10.chmodSync(dir, 448);
|
|
3536
|
-
} catch (err) {
|
|
3537
|
-
if (!isWin)
|
|
3538
|
-
return { ok: false, detail: `chmod 0700 ${dir} failed: ${err instanceof Error ? err.message : err}` };
|
|
3539
|
-
}
|
|
3540
|
-
for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
|
|
3541
|
-
const full = path8.join(dir, entry.name);
|
|
3542
|
-
if (entry.isDirectory()) {
|
|
3543
|
-
const sub = chmodTreeSecure(full);
|
|
3544
|
-
if (!sub.ok)
|
|
3545
|
-
return sub;
|
|
3546
|
-
continue;
|
|
3547
|
-
}
|
|
3548
|
-
try {
|
|
3549
|
-
fs10.chmodSync(full, 384);
|
|
3550
|
-
if (!isWin && (fs10.statSync(full).mode & 511) !== 384) {
|
|
3551
|
-
return { ok: false, detail: `mode verification failed for ${full}` };
|
|
3552
|
-
}
|
|
3553
|
-
} catch (err) {
|
|
3554
|
-
if (!isWin)
|
|
3555
|
-
return { ok: false, detail: `chmod 0600 ${full} failed: ${err instanceof Error ? err.message : err}` };
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
return { ok: true };
|
|
3559
|
-
}
|
|
3560
|
-
async function migrateVaultsToEnv(ctx) {
|
|
3561
|
-
const name = "vaults/ \u2192 env/";
|
|
3562
|
-
const stashDir = resolvePrimaryStashDir(ctx.paths.configDir);
|
|
3563
|
-
const vaultsDir = path8.join(stashDir, "vaults");
|
|
3564
|
-
const envDir = path8.join(stashDir, "env");
|
|
3565
|
-
const marker = path8.join(vaultsDir, MIGRATED_MARKER);
|
|
3566
|
-
if (!fs10.existsSync(vaultsDir)) {
|
|
3567
|
-
return { name, status: "skipped", detail: `no vaults/ directory under ${stashDir}` };
|
|
3568
|
-
}
|
|
3569
|
-
if (fs10.existsSync(marker)) {
|
|
3570
|
-
return { name, status: "skipped", detail: "already migrated (.migrated marker present)" };
|
|
3571
|
-
}
|
|
3572
|
-
const vaultEnvCount = countEnvFilesRecursive(vaultsDir);
|
|
3573
|
-
if (vaultEnvCount === 0) {
|
|
3574
|
-
return { name, status: "skipped", detail: "vaults/ has no .env files to migrate" };
|
|
3575
|
-
}
|
|
3576
|
-
if (ctx.dryRun) {
|
|
3577
|
-
return {
|
|
3578
|
-
name,
|
|
3579
|
-
status: "success",
|
|
3580
|
-
detail: `[dry-run] would copy ${vaultEnvCount} .env file(s) ${vaultsDir} \u2192 ${envDir}, chmod 0600/0700, and write ${marker}`
|
|
3581
|
-
};
|
|
3582
|
-
}
|
|
3583
|
-
try {
|
|
3584
|
-
const { copied, skipped, failed } = copyTree(vaultsDir, envDir, { clobber: false });
|
|
3585
|
-
if (failed > 0) {
|
|
3586
|
-
return { name, status: "failed", detail: `${failed} file(s) failed to copy; env/ left as-is, no marker written` };
|
|
3587
|
-
}
|
|
3588
|
-
const chmodRes = chmodTreeSecure(envDir);
|
|
3589
|
-
if (!chmodRes.ok) {
|
|
3590
|
-
return { name, status: "failed", detail: chmodRes.detail ?? "permission tightening failed" };
|
|
3591
|
-
}
|
|
3592
|
-
const envEnvCount = countEnvFilesRecursive(envDir);
|
|
3593
|
-
if (envEnvCount < vaultEnvCount) {
|
|
3594
|
-
return {
|
|
3595
|
-
name,
|
|
3596
|
-
status: "failed",
|
|
3597
|
-
detail: `post-copy count ${envEnvCount} < source ${vaultEnvCount}; no marker written`
|
|
3598
|
-
};
|
|
3599
|
-
}
|
|
3600
|
-
fs10.writeFileSync(marker, `migrated vaults/ -> env/ at ${new Date().toISOString()}
|
|
3601
|
-
`, { mode: 384 });
|
|
3602
|
-
return {
|
|
3603
|
-
name,
|
|
3604
|
-
status: "success",
|
|
3605
|
-
detail: `copied ${copied} file(s) (${skipped} already present in env/, preserved) ${vaultsDir} \u2192 ${envDir}; vaults/ left intact as a frozen copy. Run \`akm index\` to refresh search.`
|
|
3606
|
-
};
|
|
3607
|
-
} catch (err) {
|
|
3608
|
-
return { name, status: "failed", detail: err instanceof Error ? err.message : String(err) };
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
var v08To09Migration = {
|
|
3612
|
-
label: "0.8 \u2192 0.9",
|
|
3613
|
-
sourceVersion: "0.8",
|
|
3614
|
-
isNeeded: (_paths) => {
|
|
3615
|
-
return true;
|
|
3616
|
-
},
|
|
3617
|
-
steps: [
|
|
3618
|
-
{ id: "graph-snapshot-import", title: "Graph snapshot import", run: migrateGraphFileToDb },
|
|
3619
|
-
{ id: "vaults-to-env", title: "vaults/ \u2192 env/", run: migrateVaultsToEnv }
|
|
3620
|
-
]
|
|
3621
|
-
};
|
|
3622
|
-
var MIGRATIONS = [v07To08Migration, v08To09Migration];
|
|
3623
|
-
async function confirm() {
|
|
3624
|
-
if (YES)
|
|
3625
|
-
return true;
|
|
3626
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3627
|
-
return new Promise((resolve) => {
|
|
3628
|
-
rl.question("Proceed with migration? [y/N] ", (answer) => {
|
|
3629
|
-
rl.close();
|
|
3630
|
-
resolve(answer.trim().toLowerCase() === "y");
|
|
3631
|
-
});
|
|
3632
|
-
});
|
|
3633
|
-
}
|
|
3634
|
-
function printList() {
|
|
3635
|
-
console.log("akm storage migrations (in execution order):");
|
|
3636
|
-
for (const m of MIGRATIONS) {
|
|
3637
|
-
console.log(` \u2022 ${m.label} [sourceVersion=${m.sourceVersion}]`);
|
|
3638
|
-
}
|
|
3639
|
-
console.log();
|
|
3640
|
-
console.log("Resolved paths:");
|
|
3641
|
-
console.log(` $CACHE = ${cacheDir}`);
|
|
3642
|
-
console.log(` $CONFIG = ${configDir}`);
|
|
3643
|
-
console.log(` $DATA = ${dataDir}`);
|
|
3644
|
-
console.log(` $STATE = ${stateDir}`);
|
|
3645
|
-
}
|
|
3646
|
-
function compareVersion(a, b) {
|
|
3647
|
-
const parse = (v) => v.replace(/^v/, "").split(".").map((p) => Number.parseInt(p, 10)).map((n) => Number.isFinite(n) ? n : 0);
|
|
3648
|
-
const aa = parse(a);
|
|
3649
|
-
const bb = parse(b);
|
|
3650
|
-
const len = Math.max(aa.length, bb.length);
|
|
3651
|
-
for (let i = 0;i < len; i++) {
|
|
3652
|
-
const diff = (aa[i] ?? 0) - (bb[i] ?? 0);
|
|
3653
|
-
if (diff !== 0)
|
|
3654
|
-
return diff;
|
|
3655
|
-
}
|
|
3656
|
-
return 0;
|
|
3657
|
-
}
|
|
3658
|
-
function filteredMigrations() {
|
|
3659
|
-
if (!FROM_VERSION)
|
|
3660
|
-
return MIGRATIONS;
|
|
3661
|
-
return MIGRATIONS.filter((m) => compareVersion(m.sourceVersion, FROM_VERSION) >= 0);
|
|
3662
|
-
}
|
|
3663
|
-
function printGroupedSummary() {
|
|
3664
|
-
console.log(`
|
|
3665
|
-
akm storage migration \u2014 RESULTS`);
|
|
3666
|
-
console.log("================================");
|
|
3667
|
-
let totalFailures = 0;
|
|
3668
|
-
for (const report of versionReports) {
|
|
3669
|
-
console.log(`
|
|
3670
|
-
=== Migration: ${report.label} ===`);
|
|
3671
|
-
if (!report.ran) {
|
|
3672
|
-
console.log(` (not run \u2014 ${report.skipReason ?? "isNeeded returned false"})`);
|
|
3673
|
-
continue;
|
|
3674
|
-
}
|
|
3675
|
-
if (report.results.length === 0) {
|
|
3676
|
-
console.log(" (no steps recorded)");
|
|
3677
|
-
continue;
|
|
3678
|
-
}
|
|
3679
|
-
for (const r of report.results) {
|
|
3680
|
-
const glyph = r.status === "success" ? "\u2713" : r.status === "skipped" ? "\u2298" : "\u2717";
|
|
3681
|
-
console.log(` ${glyph} ${r.name}${r.status === "skipped" ? ` \u2014 ${r.detail}` : ""}`);
|
|
3682
|
-
if (r.status !== "skipped") {
|
|
3683
|
-
console.log(` ${r.detail}`);
|
|
3684
|
-
}
|
|
3685
|
-
if (r.status === "failed")
|
|
3686
|
-
totalFailures += 1;
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
if (totalFailures > 0) {
|
|
3690
|
-
console.log(`
|
|
3691
|
-
${totalFailures} step(s) failed.`);
|
|
3692
|
-
process.exitCode = 1;
|
|
3693
|
-
console.log(`
|
|
3694
|
-
Migration did NOT fully complete. DO NOT delete any legacy files yet.
|
|
3695
|
-
Re-run \`akm-migrate-storage --yes\` after addressing the failure(s) above.
|
|
3696
|
-
The migration is idempotent \u2014 successful steps are skipped on re-run via
|
|
3697
|
-
their dedup keys (events: (event_type, ts, ref, metadata_json);
|
|
3698
|
-
task_history: task_id PK).
|
|
3699
|
-
`);
|
|
3700
|
-
return;
|
|
3701
|
-
}
|
|
3702
|
-
console.log(`
|
|
3703
|
-
Migration complete. No errors.`);
|
|
3704
|
-
console.log(`
|
|
3705
|
-
Old files at the original locations are safe to delete manually after verifying akm works:
|
|
3706
|
-
${path8.join(cacheDir, "index.db")}
|
|
3707
|
-
${path8.join(cacheDir, "workflow.db")}
|
|
3708
|
-
${path8.join(cacheDir, "events.jsonl")}
|
|
3709
|
-
${path8.join(cacheDir, "tasks", "history")}
|
|
3710
|
-
${path8.join(cacheDir, "config-backups")}
|
|
3711
|
-
${path8.join(configDir, "akm.lock")}
|
|
3712
|
-
|
|
3713
|
-
Next step \u2014 repopulate graph data (if migrating from 0.7):
|
|
3714
|
-
The 0.8.0 graph schema redesign (DB_VERSION 12 \u2192 13) rebuilds the graph
|
|
3715
|
-
tables in index.db. Non-graph tables regenerate automatically on first
|
|
3716
|
-
open, but graph extraction requires LLM calls. Run:
|
|
3717
|
-
akm improve
|
|
3718
|
-
once after migration so graph-backed features (akm graph related/entity,
|
|
3719
|
-
graph-boosted search ranking) have data to work with. See
|
|
3720
|
-
docs/migration/v0.7-to-v0.8.md#graph-extraction-will-re-run-after-upgrade.
|
|
3721
|
-
`);
|
|
3722
|
-
}
|
|
3723
|
-
async function runMigrations3(opts = { dryRun: DRY_RUN }) {
|
|
3724
|
-
const paths = opts.paths ?? PATHS;
|
|
3725
|
-
const reports = [];
|
|
3726
|
-
const migrations = filteredMigrations();
|
|
3727
|
-
for (const migration of migrations) {
|
|
3728
|
-
const stepResults = [];
|
|
3729
|
-
const ctx = {
|
|
3730
|
-
dryRun: opts.dryRun,
|
|
3731
|
-
recordStep: (r) => stepResults.push(r),
|
|
3732
|
-
paths
|
|
3733
|
-
};
|
|
3734
|
-
let needed = false;
|
|
3735
|
-
try {
|
|
3736
|
-
needed = await migration.isNeeded(paths);
|
|
3737
|
-
} catch (err) {
|
|
3738
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3739
|
-
reports.push({
|
|
3740
|
-
label: migration.label,
|
|
3741
|
-
ran: false,
|
|
3742
|
-
skipReason: `isNeeded() threw: ${msg}`,
|
|
3743
|
-
results: []
|
|
3744
|
-
});
|
|
3745
|
-
continue;
|
|
3746
|
-
}
|
|
3747
|
-
if (!needed) {
|
|
3748
|
-
reports.push({ label: migration.label, ran: false, skipReason: "isNeeded() returned false", results: [] });
|
|
3749
|
-
continue;
|
|
3750
|
-
}
|
|
3751
|
-
try {
|
|
3752
|
-
await runSteps(migration.steps, ctx);
|
|
3753
|
-
reports.push({ label: migration.label, ran: true, results: stepResults });
|
|
3754
|
-
} catch (err) {
|
|
3755
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3756
|
-
stepResults.push({ name: "(migration aborted)", status: "failed", detail: msg });
|
|
3757
|
-
reports.push({ label: migration.label, ran: true, results: stepResults });
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
return reports;
|
|
3761
|
-
}
|
|
3762
|
-
async function main() {
|
|
3763
|
-
if (LIST_ONLY) {
|
|
3764
|
-
printList();
|
|
3765
|
-
return;
|
|
3766
|
-
}
|
|
3767
|
-
console.log("akm storage migration");
|
|
3768
|
-
console.log("=====================");
|
|
3769
|
-
console.log(` $CACHE = ${cacheDir}`);
|
|
3770
|
-
console.log(` $CONFIG = ${configDir}`);
|
|
3771
|
-
console.log(` $DATA = ${dataDir}`);
|
|
3772
|
-
console.log(` $STATE = ${stateDir}`);
|
|
3773
|
-
console.log();
|
|
3774
|
-
console.log("Planned migrations (after --from filter):");
|
|
3775
|
-
for (const m of filteredMigrations()) {
|
|
3776
|
-
console.log(` \u2022 ${m.label}`);
|
|
3777
|
-
}
|
|
3778
|
-
if (FROM_VERSION)
|
|
3779
|
-
console.log(` (filtered: --from ${FROM_VERSION})`);
|
|
3780
|
-
console.log();
|
|
3781
|
-
if (!DRY_RUN) {
|
|
3782
|
-
const proceed = await confirm();
|
|
3783
|
-
if (!proceed) {
|
|
3784
|
-
console.log("Aborted. No changes made.");
|
|
3785
|
-
return;
|
|
3786
|
-
}
|
|
3787
|
-
} else {
|
|
3788
|
-
console.log(`[dry-run] No changes will be written.
|
|
3789
|
-
`);
|
|
3790
|
-
}
|
|
3791
|
-
const reports = await runMigrations3({ dryRun: DRY_RUN, paths: PATHS });
|
|
3792
|
-
versionReports.push(...reports);
|
|
3793
|
-
printGroupedSummary();
|
|
3794
|
-
if (DRY_RUN) {
|
|
3795
|
-
console.log("Dry run complete. No changes made.");
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
var invokedDirectly = (() => {
|
|
3799
|
-
try {
|
|
3800
|
-
const entry = process.argv[1];
|
|
3801
|
-
if (!entry)
|
|
3802
|
-
return false;
|
|
3803
|
-
const here = new URL(import.meta.url).pathname;
|
|
3804
|
-
return path8.resolve(entry) === here;
|
|
3805
|
-
} catch {
|
|
3806
|
-
return false;
|
|
3807
|
-
}
|
|
3808
|
-
})();
|
|
3809
|
-
if (invokedDirectly) {
|
|
3810
|
-
await main();
|
|
3811
|
-
}
|
|
3812
|
-
export {
|
|
3813
|
-
runMigrations3 as runMigrations,
|
|
3814
|
-
main,
|
|
3815
|
-
MIGRATIONS
|
|
3816
|
-
};
|