akm-cli 0.9.16 → 0.9.17-alpha.10
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 +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
package/dist/execution/source.js
CHANGED
|
@@ -1,102 +1,38 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import {
|
|
5
|
-
import { cloneExecutionJson, cloneExecutionJsonObject, } from "./json.js";
|
|
6
|
-
import { assertSnapshotKeys, snapshotStrictRecord } from "./record.js";
|
|
4
|
+
import { cloneExecutionJson, cloneExecutionJsonObject } from "./json.js";
|
|
7
5
|
export const EXECUTION_SOURCE_SCHEMA_VERSION = 1;
|
|
8
6
|
/** Current internal adapter identifiers are lowercase kebab-case registry keys. */
|
|
9
7
|
export const EXECUTION_ADAPTER_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
10
|
-
const CONTROL_OR_LINE_SEPARATOR_PATTERN = /[\p{Cc}\p{Zl}\p{Zp}]/u;
|
|
11
|
-
const FORMAT_CHARACTER_PATTERN = /\p{Cf}/u;
|
|
12
|
-
const WINDOWS_DRIVE_PREFIX_PATTERN = /^[A-Za-z]:/;
|
|
13
|
-
const RESERVED_EXTENSION_OWNERS = new Set(["__proto__", "constructor", "prototype", "tostring"]);
|
|
14
|
-
const renderedSourceBrand = Symbol("akm.adapter-rendered-execution-source");
|
|
15
|
-
const renderedSourceInstances = new WeakSet();
|
|
16
8
|
function requireRecord(value, path) {
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
function assertOnlyKeys(value, allowed, path) {
|
|
20
|
-
assertSnapshotKeys(value, allowed, path);
|
|
21
|
-
}
|
|
22
|
-
function validateExtensionOwner(owner) {
|
|
23
|
-
const normalized = owner.toLowerCase();
|
|
24
|
-
if (!EXECUTION_ADAPTER_ID_PATTERN.test(owner) || RESERVED_EXTENSION_OWNERS.has(normalized)) {
|
|
25
|
-
throw new TypeError(`extension owner must be a canonical, non-reserved adapter identifier: ${owner}`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function frozenNullPrototypeMap(entries) {
|
|
29
|
-
const out = Object.create(null);
|
|
30
|
-
for (const [key, value] of entries) {
|
|
31
|
-
Object.defineProperty(out, key, { value, enumerable: true, configurable: false, writable: false });
|
|
9
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
10
|
+
throw new TypeError(`${path} must be an object`);
|
|
32
11
|
}
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
function cloneExtensionEntry(value, index) {
|
|
36
|
-
const path = `extension entry ${index}`;
|
|
37
|
-
const cloned = cloneExecutionJson(value, path);
|
|
38
|
-
if (!Array.isArray(cloned) || cloned.length !== 2) {
|
|
39
|
-
throw new TypeError(`${path} must be a two-element [owner, values] array`);
|
|
40
|
-
}
|
|
41
|
-
const [owner, values] = cloned;
|
|
42
|
-
if (typeof owner !== "string")
|
|
43
|
-
throw new TypeError(`${path} owner must be a string`);
|
|
44
|
-
if (values === null || Array.isArray(values) || typeof values !== "object") {
|
|
45
|
-
throw new TypeError(`${path} values must be a JSON object`);
|
|
46
|
-
}
|
|
47
|
-
return [owner, values];
|
|
12
|
+
return value;
|
|
48
13
|
}
|
|
49
14
|
export function createAdapterExtensions(first, second, ...rest) {
|
|
50
|
-
const
|
|
15
|
+
const entries = typeof first === "string"
|
|
51
16
|
? [[first, second]]
|
|
52
17
|
: [first, ...(second === undefined ? [] : [second]), ...rest];
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
validateExtensionOwner(owner);
|
|
58
|
-
if (seen.has(owner))
|
|
59
|
-
throw new TypeError(`duplicate extension owner: ${owner}`);
|
|
60
|
-
seen.add(owner);
|
|
61
|
-
cloned.push([owner, cloneExecutionJsonObject(values, `extensions.${owner}`)]);
|
|
62
|
-
}
|
|
63
|
-
return frozenNullPrototypeMap(cloned);
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const [owner, values] of entries)
|
|
20
|
+
out[owner] = cloneExecutionJsonObject(values, `extensions.${owner}`);
|
|
21
|
+
return Object.freeze(out);
|
|
64
22
|
}
|
|
65
23
|
export function cloneAdapterExtensions(value, path) {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
return first ? createAdapterExtensions(first, ...rest) : frozenNullPrototypeMap([]);
|
|
70
|
-
}
|
|
71
|
-
function requireCanonicalString(value, path) {
|
|
72
|
-
if (typeof value !== "string" ||
|
|
73
|
-
value.length === 0 ||
|
|
74
|
-
!isWellFormedUnicode(value) ||
|
|
75
|
-
value.normalize("NFC") !== value) {
|
|
76
|
-
throw new TypeError(`${path} must be a non-empty NFC string`);
|
|
77
|
-
}
|
|
78
|
-
if (hasUnsafeIdentityCharacter(value)) {
|
|
79
|
-
throw new TypeError(`${path} must not contain Unicode control or dangerous format characters`);
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [owner, fields] of Object.entries(requireRecord(value, path))) {
|
|
26
|
+
out[owner] = cloneExecutionJsonObject(fields, `${path}.${owner}`);
|
|
80
27
|
}
|
|
81
|
-
return
|
|
28
|
+
return Object.freeze(out);
|
|
82
29
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Validate a selector/layer identifier that may cross the durable execution
|
|
85
|
-
* boundary. Keep this shared with the cascade planner so resume cannot admit a
|
|
86
|
-
* selector the live planner would reject.
|
|
87
|
-
*/
|
|
30
|
+
/** A selector or layer identifier that may cross the durable execution boundary. */
|
|
88
31
|
export function requireStableExecutionSelector(value, path) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
selector = requireCanonicalString(value, path);
|
|
32
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
|
|
33
|
+
throw new TypeError(`${path} must be a non-empty stable identifier`);
|
|
92
34
|
}
|
|
93
|
-
|
|
94
|
-
throw new TypeError(`${path} must be a non-empty stable NFC identifier`, { cause });
|
|
95
|
-
}
|
|
96
|
-
if (selector.length > 512 || selector.trim() !== selector) {
|
|
97
|
-
throw new TypeError(`${path} must be a non-empty stable NFC identifier`);
|
|
98
|
-
}
|
|
99
|
-
return selector;
|
|
35
|
+
return value;
|
|
100
36
|
}
|
|
101
37
|
export function isPortableExecutionAgentSelector(value) {
|
|
102
38
|
return /^(?:[^/]+\/\/)?agents\/[^/]/.test(value);
|
|
@@ -104,118 +40,61 @@ export function isPortableExecutionAgentSelector(value) {
|
|
|
104
40
|
export function executionPersonaMatchesSelector(selector, personaRef) {
|
|
105
41
|
return selector.includes("//") ? selector === personaRef : personaRef.endsWith(`//${selector}`);
|
|
106
42
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
function isWellFormedUnicode(value) {
|
|
119
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
120
|
-
const code = value.charCodeAt(index);
|
|
121
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
122
|
-
const next = value.charCodeAt(index + 1);
|
|
123
|
-
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
124
|
-
return false;
|
|
125
|
-
index += 1;
|
|
126
|
-
}
|
|
127
|
-
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
function validateCanonicalIdentity(input, path) {
|
|
134
|
-
assertOnlyKeys(input, ["ref", "bundle", "adapter", "file", "hash"], path);
|
|
135
|
-
const ref = requireCanonicalString(input.ref, `${path}.ref`);
|
|
136
|
-
const bundle = requireCanonicalString(input.bundle, `${path}.bundle`);
|
|
137
|
-
const adapter = requireCanonicalString(input.adapter, `${path}.adapter`);
|
|
138
|
-
const file = requireCanonicalString(input.file, `${path}.file`);
|
|
139
|
-
const hash = requireCanonicalString(input.hash, `${path}.hash`);
|
|
140
|
-
let parsed;
|
|
141
|
-
try {
|
|
142
|
-
parsed = parseBundleRef(ref);
|
|
143
|
-
}
|
|
144
|
-
catch (cause) {
|
|
145
|
-
throw new TypeError(`${path}.ref is not a canonical bundle ref`, { cause });
|
|
146
|
-
}
|
|
147
|
-
if (parsed.bundle === undefined ||
|
|
148
|
-
parsed.bundle !== bundle ||
|
|
149
|
-
parsed.fragment !== undefined ||
|
|
150
|
-
!isBundleSlug(bundle) ||
|
|
151
|
-
bundleRefToString(parsed) !== ref) {
|
|
152
|
-
throw new TypeError(`${path}.ref must round-trip as the same fully-qualified bundle ref without a fragment`);
|
|
153
|
-
}
|
|
154
|
-
if (!EXECUTION_ADAPTER_ID_PATTERN.test(adapter)) {
|
|
155
|
-
throw new TypeError(`${path}.adapter must use the current lowercase kebab-case adapter identifier grammar`);
|
|
156
|
-
}
|
|
157
|
-
const segments = file.split("/");
|
|
158
|
-
if (file.startsWith("/") ||
|
|
159
|
-
WINDOWS_DRIVE_PREFIX_PATTERN.test(file) ||
|
|
160
|
-
file.includes("\\") ||
|
|
161
|
-
segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
162
|
-
throw new TypeError(`${path}.file must be a normalized relative POSIX path`);
|
|
43
|
+
const IDENTITY_KEYS = ["ref", "bundle", "adapter", "file", "hash"];
|
|
44
|
+
function identityFrom(value, path) {
|
|
45
|
+
const record = requireRecord(value, path);
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const key of IDENTITY_KEYS) {
|
|
48
|
+
const field = record[key];
|
|
49
|
+
if (typeof field !== "string" || field.length === 0)
|
|
50
|
+
throw new TypeError(`${path}.${key} must be a non-empty string`);
|
|
51
|
+
out[key] = field;
|
|
163
52
|
}
|
|
164
|
-
|
|
165
|
-
throw new TypeError(`${path}.hash must be a SHA-256 hex digest`);
|
|
166
|
-
return { ref, bundle, adapter, file, hash };
|
|
53
|
+
return Object.freeze(out);
|
|
167
54
|
}
|
|
168
|
-
/**
|
|
55
|
+
/** Copy and freeze an identity produced by an adapter renderer. */
|
|
169
56
|
export function createExecutionSourceIdentity(input) {
|
|
170
|
-
return
|
|
57
|
+
return identityFrom(input, "execution source identity");
|
|
171
58
|
}
|
|
172
|
-
/**
|
|
59
|
+
/** Read a source identity back from a frozen request. */
|
|
173
60
|
export function decodeExecutionSourceIdentity(value, path = "execution source identity") {
|
|
174
|
-
return
|
|
61
|
+
return identityFrom(value, path);
|
|
175
62
|
}
|
|
63
|
+
/** Type-check the common frontmatter defaults; unknown keys are ignored. */
|
|
176
64
|
export function cloneUnresolvedExecutionDefaults(input, path = "execution source defaults") {
|
|
177
65
|
const record = requireRecord(input, path);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
"engine",
|
|
181
|
-
"model",
|
|
182
|
-
"inference",
|
|
183
|
-
"outputSchema",
|
|
184
|
-
"tools",
|
|
185
|
-
"timeout",
|
|
186
|
-
"workspace",
|
|
187
|
-
"environment",
|
|
188
|
-
"runtime",
|
|
189
|
-
], path);
|
|
190
|
-
const json = { ...cloneExecutionJsonObject(record, path) };
|
|
66
|
+
const present = (key) => Object.hasOwn(record, key) && record[key] !== undefined;
|
|
67
|
+
const out = {};
|
|
191
68
|
for (const key of ["agent", "engine", "model", "workspace"]) {
|
|
192
|
-
|
|
193
|
-
|
|
69
|
+
if (!present(key))
|
|
70
|
+
continue;
|
|
71
|
+
const value = record[key];
|
|
72
|
+
if (value !== null && typeof value !== "string")
|
|
194
73
|
throw new TypeError(`${path}.${key} must be a string or null`);
|
|
195
|
-
|
|
74
|
+
out[key] = value;
|
|
196
75
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
76
|
+
if (present("timeout")) {
|
|
77
|
+
const timeout = record.timeout;
|
|
78
|
+
if (timeout !== null && typeof timeout !== "string" && typeof timeout !== "number") {
|
|
79
|
+
throw new TypeError(`${path}.timeout must be a string, number, or null`);
|
|
80
|
+
}
|
|
81
|
+
out.timeout = timeout;
|
|
200
82
|
}
|
|
201
83
|
for (const key of ["inference", "outputSchema", "runtime"]) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
84
|
+
if (!present(key))
|
|
85
|
+
continue;
|
|
86
|
+
out[key] = record[key] === null ? null : cloneExecutionJsonObject(record[key], `${path}.${key}`);
|
|
206
87
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
throw new TypeError(`${path}.environment must be an object or null`);
|
|
211
|
-
}
|
|
212
|
-
if (Object.values(environment).some((value) => typeof value !== "string")) {
|
|
88
|
+
if (present("environment")) {
|
|
89
|
+
const environment = record.environment === null ? null : cloneExecutionJsonObject(record.environment, `${path}.environment`);
|
|
90
|
+
if (environment !== null && Object.values(environment).some((value) => typeof value !== "string")) {
|
|
213
91
|
throw new TypeError(`${path}.environment values must be strings`);
|
|
214
92
|
}
|
|
93
|
+
out.environment = environment;
|
|
215
94
|
}
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
return Object.freeze(
|
|
95
|
+
if (present("tools"))
|
|
96
|
+
out.tools = cloneToolSelection(record.tools, `${path}.tools`);
|
|
97
|
+
return Object.freeze(out);
|
|
219
98
|
}
|
|
220
99
|
export function cloneToolSelection(value, path = "tools") {
|
|
221
100
|
if (value === null || typeof value === "string")
|
|
@@ -229,92 +108,19 @@ export function cloneToolSelection(value, path = "tools") {
|
|
|
229
108
|
return cloneExecutionJsonObject(cloned, path);
|
|
230
109
|
}
|
|
231
110
|
export function createAdapterRenderedExecutionSource(input) {
|
|
232
|
-
const
|
|
233
|
-
assertOnlyKeys(record, ["kind", "content", "identity", "defaults", "extensions"], "adapter-rendered execution source");
|
|
234
|
-
const kind = record.kind;
|
|
235
|
-
const content = record.content;
|
|
111
|
+
const { kind, content } = input;
|
|
236
112
|
if (kind !== "command" && kind !== "persona")
|
|
237
113
|
throw new TypeError("execution source kind is invalid");
|
|
238
114
|
if (typeof content !== "string")
|
|
239
115
|
throw new TypeError("execution source content must be a string");
|
|
240
|
-
|
|
241
|
-
if (defaults === undefined) {
|
|
242
|
-
throw new TypeError("execution source defaults must be omitted or be an object");
|
|
243
|
-
}
|
|
244
|
-
const source = {
|
|
116
|
+
return Object.freeze({
|
|
245
117
|
schemaVersion: EXECUTION_SOURCE_SCHEMA_VERSION,
|
|
246
118
|
kind,
|
|
247
119
|
content,
|
|
248
|
-
defaults: cloneUnresolvedExecutionDefaults(defaults),
|
|
249
|
-
identity: createExecutionSourceIdentity(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
Object.defineProperty(source, renderedSourceBrand, { value: true, enumerable: false });
|
|
255
|
-
const frozen = Object.freeze(source);
|
|
256
|
-
renderedSourceInstances.add(frozen);
|
|
257
|
-
return frozen;
|
|
258
|
-
}
|
|
259
|
-
function isAdapterRenderedSource(value, kind) {
|
|
260
|
-
if (typeof value !== "object" || value === null)
|
|
261
|
-
return false;
|
|
262
|
-
if (!renderedSourceInstances.has(value))
|
|
263
|
-
return false;
|
|
264
|
-
if (Object.getPrototypeOf(value) !== Object.prototype || !Object.isFrozen(value))
|
|
265
|
-
return false;
|
|
266
|
-
if (!Object.hasOwn(value, renderedSourceBrand))
|
|
267
|
-
return false;
|
|
268
|
-
const required = new Set(["schemaVersion", "kind", "content", "defaults", "identity"]);
|
|
269
|
-
const optional = new Set(["extensions"]);
|
|
270
|
-
for (const key of Reflect.ownKeys(value)) {
|
|
271
|
-
if (key === renderedSourceBrand)
|
|
272
|
-
continue;
|
|
273
|
-
if (typeof key !== "string" || (!required.delete(key) && !optional.has(key)))
|
|
274
|
-
return false;
|
|
275
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
276
|
-
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
if (required.size > 0)
|
|
280
|
-
return false;
|
|
281
|
-
const brand = Object.getOwnPropertyDescriptor(value, renderedSourceBrand);
|
|
282
|
-
const source = value;
|
|
283
|
-
if (brand?.value !== true ||
|
|
284
|
-
brand.enumerable !== false ||
|
|
285
|
-
brand.configurable !== false ||
|
|
286
|
-
brand.writable !== false ||
|
|
287
|
-
source.schemaVersion !== EXECUTION_SOURCE_SCHEMA_VERSION ||
|
|
288
|
-
source.kind !== kind ||
|
|
289
|
-
typeof source.content !== "string" ||
|
|
290
|
-
typeof source.defaults !== "object" ||
|
|
291
|
-
source.defaults === null ||
|
|
292
|
-
!Object.isFrozen(source.defaults) ||
|
|
293
|
-
typeof source.identity !== "object" ||
|
|
294
|
-
source.identity === null ||
|
|
295
|
-
Object.getPrototypeOf(source.identity) !== Object.prototype ||
|
|
296
|
-
!Object.isFrozen(source.identity)) {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
if (Object.hasOwn(source, "extensions")) {
|
|
300
|
-
if (typeof source.extensions !== "object" ||
|
|
301
|
-
source.extensions === null ||
|
|
302
|
-
Object.getPrototypeOf(source.extensions) !== null ||
|
|
303
|
-
!Object.isFrozen(source.extensions)) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
try {
|
|
308
|
-
decodeExecutionSourceIdentity(source.identity);
|
|
309
|
-
}
|
|
310
|
-
catch {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
return true;
|
|
314
|
-
}
|
|
315
|
-
export function isAdapterRenderedCommandSource(value) {
|
|
316
|
-
return isAdapterRenderedSource(value, "command");
|
|
317
|
-
}
|
|
318
|
-
export function isAdapterRenderedPersonaSource(value) {
|
|
319
|
-
return isAdapterRenderedSource(value, "persona");
|
|
120
|
+
defaults: cloneUnresolvedExecutionDefaults(input.defaults ?? {}),
|
|
121
|
+
identity: createExecutionSourceIdentity(input.identity),
|
|
122
|
+
...(input.extensions
|
|
123
|
+
? { extensions: cloneAdapterExtensions(input.extensions, "execution source extensions") }
|
|
124
|
+
: {}),
|
|
125
|
+
});
|
|
320
126
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* Explicit non-goals (binding, spec §4.1): no GitHub locator grammar, no
|
|
24
24
|
* `akm/command` builtin special case, no resolution, no filesystem access, no
|
|
25
25
|
* guessing. Callers layer builtin detection on top (see
|
|
26
|
-
* `
|
|
26
|
+
* `classifyWorkflowStepUses` in src/workflows/source-semantics.ts).
|
|
27
27
|
*/
|
|
28
28
|
import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
29
29
|
import { UsageError } from "../core/errors.js";
|
|
@@ -34,7 +34,7 @@ import { rethrowIfTestIsolationError } from "../core/errors.js";
|
|
|
34
34
|
import { getDbPath } from "../core/paths.js";
|
|
35
35
|
import { warn } from "../core/warn.js";
|
|
36
36
|
import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
37
|
-
import {
|
|
37
|
+
import { hasCurrentEntriesTable } from "../storage/repositories/index-entry-schema.js";
|
|
38
38
|
let guardSettled = false;
|
|
39
39
|
/** TEST-ONLY: re-arm the once-per-process guard between cases. */
|
|
40
40
|
export function resetBundleIdentityGuardForTests() {
|
|
@@ -49,7 +49,7 @@ function indexedBundlePaths(dbPath) {
|
|
|
49
49
|
db = openReadonlyExistingDatabase(dbPath);
|
|
50
50
|
if (!db)
|
|
51
51
|
return undefined;
|
|
52
|
-
if (!
|
|
52
|
+
if (!hasCurrentEntriesTable(db))
|
|
53
53
|
return undefined;
|
|
54
54
|
return db
|
|
55
55
|
.prepare("SELECT DISTINCT bundle_id AS bundleId, file_path AS filePath FROM entries " +
|
|
@@ -27,10 +27,10 @@ import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry, } from "../../s
|
|
|
27
27
|
* already hashed the body (e.g. to reuse it elsewhere)
|
|
28
28
|
* should pass this to avoid the redundant work.
|
|
29
29
|
* @param cacheVariant - Namespace token for the cache row so different passes'
|
|
30
|
-
* rows do not collide (e.g. memory-inference vs
|
|
30
|
+
* rows do not collide (e.g. memory-inference vs metadata-enhance).
|
|
31
31
|
* @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
|
|
32
32
|
*/
|
|
33
|
-
export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant
|
|
33
|
+
export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant, hooks) {
|
|
34
34
|
const bodyHash = precomputedHash ?? computeBodyHash(body);
|
|
35
35
|
if (!reEnrich) {
|
|
36
36
|
const cacheHit = bestEffort(() => {
|
|
@@ -21,12 +21,15 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import fs from "node:fs";
|
|
23
23
|
import path from "node:path";
|
|
24
|
+
import { hashContent } from "../core/adapter/adapters/shared.js";
|
|
24
25
|
import { placementSpecList } from "../core/asset/asset-placement.js";
|
|
26
|
+
import { ConfigError } from "../core/errors.js";
|
|
25
27
|
import { classifyPathAccess } from "../core/path-access.js";
|
|
26
28
|
import { getDbPath } from "../core/paths.js";
|
|
29
|
+
import { warnVerbose } from "../core/warn.js";
|
|
27
30
|
import { assertIndexPathReadable, closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
28
|
-
import { getEntryCount, getIndexedFilePaths } from "../storage/repositories/index-entries-repository.js";
|
|
29
|
-
import {
|
|
31
|
+
import { getEntryCount, getIndexedFileHashes, getIndexedFilePaths, } from "../storage/repositories/index-entries-repository.js";
|
|
32
|
+
import { hasCurrentEntriesTable } from "../storage/repositories/index-entry-schema.js";
|
|
30
33
|
import { getMeta } from "../storage/repositories/index-meta-repository.js";
|
|
31
34
|
import { warnOnBundleRenameDrift } from "./bundle-identity-guard.js";
|
|
32
35
|
function getIndexableFiles(root, spec) {
|
|
@@ -77,11 +80,20 @@ function getIndexableFiles(root, spec) {
|
|
|
77
80
|
* millisecond-truncated), so the mtime test alone silently misses
|
|
78
81
|
* additions made within ~a millisecond of the previous build.
|
|
79
82
|
*
|
|
83
|
+
* A file whose mtime is newer than `builtAt` is NOT automatically stale
|
|
84
|
+
* (R6): `indexWrittenAssets` upserts a fresh `content_hash` without bumping
|
|
85
|
+
* `builtAt`, so a file `ensureIndex` itself just incrementally re-indexed
|
|
86
|
+
* (for example a proposal triage just promoted into `knowledge/`) would
|
|
87
|
+
* otherwise keep tripping this check on every subsequent call, forcing the
|
|
88
|
+
* full rescan the write-path fast path exists to avoid. Only files newer
|
|
89
|
+
* than `builtAt` are hashed here, so this stays cheap — the common case is
|
|
90
|
+
* zero or a handful of such files.
|
|
91
|
+
*
|
|
80
92
|
* `getIndexableFiles` applies each asset type's own relevance filter, so
|
|
81
93
|
* non-indexed companion files (e.g. `package.json` next to a knowledge doc) are
|
|
82
94
|
* never considered and do not produce false "new file" positives.
|
|
83
95
|
*/
|
|
84
|
-
function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths) {
|
|
96
|
+
function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths, indexedHashes) {
|
|
85
97
|
const builtAtMs = builtAt ? new Date(builtAt).getTime() : Number.NaN;
|
|
86
98
|
const builtAtUsable = Number.isFinite(builtAtMs);
|
|
87
99
|
for (const spec of placementSpecList()) {
|
|
@@ -92,62 +104,42 @@ function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths) {
|
|
|
92
104
|
return true;
|
|
93
105
|
if (!builtAtUsable)
|
|
94
106
|
return true;
|
|
107
|
+
let mtimeMs;
|
|
95
108
|
try {
|
|
96
|
-
|
|
97
|
-
return true;
|
|
109
|
+
mtimeMs = fs.statSync(file).mtimeMs;
|
|
98
110
|
}
|
|
99
111
|
catch {
|
|
100
112
|
return true;
|
|
101
113
|
}
|
|
114
|
+
if (mtimeMs <= builtAtMs)
|
|
115
|
+
continue;
|
|
116
|
+
// Newer than the last full build — only stale if its current content
|
|
117
|
+
// actually differs from what is indexed. No stored hash means the row
|
|
118
|
+
// predates content-hash tracking, so fall back to the conservative
|
|
119
|
+
// mtime-stale answer.
|
|
120
|
+
const indexedHash = indexedHashes.get(file);
|
|
121
|
+
if (indexedHash === undefined)
|
|
122
|
+
return true;
|
|
123
|
+
let currentHash;
|
|
124
|
+
try {
|
|
125
|
+
currentHash = hashContent(fs.readFileSync(file, "utf8"));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (currentHash !== indexedHash)
|
|
131
|
+
return true;
|
|
102
132
|
}
|
|
103
133
|
}
|
|
104
134
|
return false;
|
|
105
135
|
}
|
|
106
136
|
/**
|
|
107
137
|
* Check whether the local index is stale relative to the given stash directory.
|
|
108
|
-
* Returns `true` when the index is missing, empty,
|
|
109
|
-
* different primary stash dir.
|
|
138
|
+
* Returns `true` when the index is missing, empty, was built against a
|
|
139
|
+
* different primary stash dir, or an indexable file is newer than it.
|
|
110
140
|
*/
|
|
111
141
|
export function isIndexStale(stashDir) {
|
|
112
|
-
|
|
113
|
-
// Raises on an index we cannot READ rather than calling it stale — "stale"
|
|
114
|
-
// sends us into an inline reindex that will fail anyway, and whose failure is
|
|
115
|
-
// reported as the misleading "proceeding with existing index" (#791).
|
|
116
|
-
assertIndexPathReadable(dbPath);
|
|
117
|
-
if (classifyPathAccess(dbPath).access === "absent")
|
|
118
|
-
return true;
|
|
119
|
-
let db;
|
|
120
|
-
try {
|
|
121
|
-
db = openExistingDatabase(dbPath);
|
|
122
|
-
if (!isCanonicalIndexGeneration(db))
|
|
123
|
-
return true;
|
|
124
|
-
const entryCount = getEntryCount(db);
|
|
125
|
-
if (entryCount === 0)
|
|
126
|
-
return true;
|
|
127
|
-
const builtAt = getMeta(db, "builtAt");
|
|
128
|
-
if (hasNewerIndexableFiles(stashDir, builtAt, getIndexedFilePaths(db)))
|
|
129
|
-
return true;
|
|
130
|
-
const storedStashDir = getMeta(db, "stashDir");
|
|
131
|
-
if (storedStashDir !== stashDir) {
|
|
132
|
-
// Check if the incoming stashDir appears in the stored stashDirs array
|
|
133
|
-
try {
|
|
134
|
-
const storedDirs = JSON.parse(getMeta(db, "stashDirs") ?? "[]");
|
|
135
|
-
if (!storedDirs.includes(stashDir))
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
finally {
|
|
148
|
-
if (db)
|
|
149
|
-
closeDatabase(db);
|
|
150
|
-
}
|
|
142
|
+
return !indexCanServeStash(stashDir, { requireFresh: true });
|
|
151
143
|
}
|
|
152
144
|
/**
|
|
153
145
|
* Whether the existing index can serve queries for `stashDir` *right now* —
|
|
@@ -157,20 +149,27 @@ export function isIndexStale(stashDir) {
|
|
|
157
149
|
* content-stale, so read paths serve it as-is. When it is false the existing
|
|
158
150
|
* index has nothing relevant to return (no DB, no `entries` table, zero rows,
|
|
159
151
|
* or built for a different stash), so those cases must rebuild inline.
|
|
152
|
+
* `requireFresh` also demands that no indexable file is newer than the index.
|
|
160
153
|
*/
|
|
161
|
-
function indexCanServeStash(stashDir) {
|
|
162
|
-
// Same rule as isIndexStale: unreadable is an error, not "cannot serve" (#791).
|
|
154
|
+
function indexCanServeStash(stashDir, options = {}) {
|
|
163
155
|
const dbPath = getDbPath();
|
|
156
|
+
// Raises on an index we cannot READ rather than calling it unusable — that
|
|
157
|
+
// sends us into an inline reindex that will fail anyway, and whose failure
|
|
158
|
+
// is reported as the misleading "proceeding with existing index" (#791).
|
|
164
159
|
assertIndexPathReadable(dbPath);
|
|
165
160
|
if (classifyPathAccess(dbPath).access === "absent")
|
|
166
161
|
return false;
|
|
167
162
|
let db;
|
|
168
163
|
try {
|
|
169
164
|
db = openExistingDatabase(dbPath);
|
|
170
|
-
if (!
|
|
165
|
+
if (!hasCurrentEntriesTable(db))
|
|
171
166
|
return false;
|
|
172
167
|
if (getEntryCount(db) === 0)
|
|
173
168
|
return false;
|
|
169
|
+
if (options.requireFresh &&
|
|
170
|
+
hasNewerIndexableFiles(stashDir, getMeta(db, "builtAt"), getIndexedFilePaths(db), getIndexedFileHashes(db))) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
174
173
|
const storedStashDir = getMeta(db, "stashDir");
|
|
175
174
|
if (storedStashDir === stashDir)
|
|
176
175
|
return true;
|
|
@@ -182,7 +181,10 @@ function indexCanServeStash(stashDir) {
|
|
|
182
181
|
return false;
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
|
-
catch {
|
|
184
|
+
catch (error) {
|
|
185
|
+
// A newer layout's refusal names the remedy; a rebuild would refuse too.
|
|
186
|
+
if (error instanceof ConfigError)
|
|
187
|
+
throw error;
|
|
186
188
|
// No `entries` table (or otherwise unreadable) — cannot serve.
|
|
187
189
|
return false;
|
|
188
190
|
}
|
|
@@ -191,16 +193,6 @@ function indexCanServeStash(stashDir) {
|
|
|
191
193
|
closeDatabase(db);
|
|
192
194
|
}
|
|
193
195
|
}
|
|
194
|
-
async function runInlineReindex(stashDir, options = {}) {
|
|
195
|
-
const { akmIndex } = await import("./indexer.js");
|
|
196
|
-
await akmIndex({
|
|
197
|
-
stashDir,
|
|
198
|
-
implicit: true,
|
|
199
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
200
|
-
...(options.hydrateSources === false ? { hydrateSources: false } : {}),
|
|
201
|
-
});
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
196
|
/**
|
|
205
197
|
* Ensure the local index exists and can serve the caller.
|
|
206
198
|
*
|
|
@@ -221,20 +213,32 @@ export async function ensureIndex(stashDir, options = {}) {
|
|
|
221
213
|
// §11.5: warn (once) if the configured bundle ids drifted from the persisted
|
|
222
214
|
// index prefixes (hand-renamed bundle key) BEFORE any rebuild could re-mint.
|
|
223
215
|
warnOnBundleRenameDrift();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// materialization point — hydrate cache-backed sources as usual.
|
|
227
|
-
if (!isIndexStale(stashDir))
|
|
228
|
-
return false;
|
|
229
|
-
return runInlineReindex(stashDir, { ...(options.signal ? { signal: options.signal } : {}) });
|
|
230
|
-
}
|
|
231
|
-
// Background = the READ path (`show` auto-index): query time must never clone/
|
|
232
|
-
// pull/fetch (spec §14.3 / D11). Build from already-materialized content only;
|
|
233
|
-
// absent source caches are skipped with a warning, not fetched.
|
|
234
|
-
if (indexCanServeStash(stashDir))
|
|
216
|
+
const blocking = options.mode === "blocking";
|
|
217
|
+
if (indexCanServeStash(stashDir, { requireFresh: blocking }))
|
|
235
218
|
return false;
|
|
236
|
-
|
|
219
|
+
const { akmIndex } = await import("./indexer.js");
|
|
220
|
+
const startedMs = Date.now();
|
|
221
|
+
const response = await akmIndex({
|
|
222
|
+
stashDir,
|
|
223
|
+
implicit: true,
|
|
237
224
|
...(options.signal ? { signal: options.signal } : {}),
|
|
238
|
-
|
|
225
|
+
// Blocking callers (improve's planning preflight) are a sanctioned
|
|
226
|
+
// materialization point and hydrate cache-backed sources as usual. The
|
|
227
|
+
// background READ path (`show` auto-index) must never clone/pull/fetch at
|
|
228
|
+
// query time (spec §14.3 / D11): it builds from already-materialized
|
|
229
|
+
// content, and absent source caches are skipped with a warning.
|
|
230
|
+
...(blocking ? {} : { hydrateSources: false }),
|
|
239
231
|
});
|
|
232
|
+
// R6: the implicit reindex's cost was previously discarded entirely
|
|
233
|
+
// (`await akmIndex(...)` and nothing else), making a 27-minute blocking
|
|
234
|
+
// rebuild invisible to both the operator and the improve result. Fall back
|
|
235
|
+
// to a wall-clock measurement when the response carries no `timing` block.
|
|
236
|
+
const durationMs = response.timing?.totalMs ?? Date.now() - startedMs;
|
|
237
|
+
const timing = response.timing;
|
|
238
|
+
warnVerbose(`[ensure-index] implicit reindex completed in ${durationMs}ms` +
|
|
239
|
+
(timing
|
|
240
|
+
? ` (walk=${timing.walkMs}ms llm=${timing.llmMs}ms embed=${timing.embedMs}ms finalize=${timing.finalizeMs}ms)`
|
|
241
|
+
: ""));
|
|
242
|
+
options.onReindexTiming?.({ durationMs, timing });
|
|
243
|
+
return true;
|
|
240
244
|
}
|