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
|
@@ -1,12 +1,15 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* `akm improve`: under one run lock, bootstrap the index, drain the proposal
|
|
6
|
+
* backlog (triage), select candidates, run preparation → loop → post-loop, then
|
|
7
|
+
* commit what the run wrote (auto-sync). A dry run plans on read-only state.
|
|
8
|
+
*/
|
|
4
9
|
import fs from "node:fs";
|
|
5
10
|
import path from "node:path";
|
|
6
|
-
import { assertNever } from "../../core/assert.js";
|
|
7
11
|
import { parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
8
|
-
import {
|
|
9
|
-
import { bundlesToSourceEntries, loadConfig } from "../../core/config/config.js";
|
|
12
|
+
import { bundlesToSourceEntries, loadConfig, } from "../../core/config/config.js";
|
|
10
13
|
import { ConfigError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
11
14
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
12
15
|
import { classifyImproveAction, foldDistillSkipped } from "../../core/improve-types.js";
|
|
@@ -18,37 +21,33 @@ import { info, warn, warnVerbose } from "../../core/warn.js";
|
|
|
18
21
|
import { beginWriteProvenance, relativeWrittenPath } from "../../core/write-provenance.js";
|
|
19
22
|
import { resolveWritable, resolveWriteTarget } from "../../core/write-source.js";
|
|
20
23
|
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
24
|
+
import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
21
25
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
22
26
|
import { collectPendingMemories } from "../../indexer/passes/memory-inference.js";
|
|
23
27
|
import { resolveEntryContentDir, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
24
28
|
import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
|
|
25
29
|
import { installLlmUsagePersistence, LLM_USAGE_EVENT } from "../../llm/usage-persist.js";
|
|
30
|
+
import { withLlmStage } from "../../llm/usage-telemetry.js";
|
|
26
31
|
import { isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "../../sources/providers/git.js";
|
|
27
32
|
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
28
33
|
import { getEntryCount } from "../../storage/repositories/index-entries-repository.js";
|
|
29
34
|
import { openSqliteReadSnapshot, SqliteReadSnapshotUnavailableError } from "../../storage/sqlite-read-snapshot.js";
|
|
30
35
|
import { summarizeLlmUsageCrossTab } from "../health/llm-usage.js";
|
|
31
36
|
import { drainProposals } from "../proposal/drain.js";
|
|
32
|
-
import { resolveDrainPolicy } from "../proposal/drain-policies.js";
|
|
33
37
|
import { describeGatedLanes, isAutonomyLaneAllowed } from "./autonomy-gate.js";
|
|
34
38
|
import { akmDistill } from "./distill.js";
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
import { countEvalCases } from "./eval-cases.js";
|
|
38
|
-
import { projectResolvedProcessRouting, resolveImprovePlan, resolveImproveStrategy, shouldSkipRef, } from "./improve-strategies.js";
|
|
39
|
+
import { collectEligibleRefs, collectEligibleRefsReadOnly, memoryCleanupParentRef, resolveImproveScope, shouldAnalyzeMemoryCleanup, } from "./eligibility.js";
|
|
40
|
+
import { eligibleRefCount, projectResolvedProcessRouting, resolveImprovePlan, resolveImproveStrategy, } from "./improve-strategies.js";
|
|
39
41
|
import { buildImproveUsageReport } from "./improve-usage-report.js";
|
|
42
|
+
import { lastAttemptByRef, loadLedgerSnapshot } from "./ledger.js";
|
|
40
43
|
import { improveLockPath, releaseImproveLock, tryAcquireImproveLock } from "./locks.js";
|
|
41
|
-
// The cycle loop / post-loop / maintenance stages live in ./loop-stages.
|
|
42
44
|
import { runImproveLoopStage, runImprovePostLoopStage } from "./loop-stages.js";
|
|
43
|
-
import { analyzeMemoryCleanup } from "./memory/memory-improve.js";
|
|
45
|
+
import { analyzeMemoryCleanup, purgeGracedArchive, RETIRE_GRACE_DAYS, } from "./memory/memory-improve.js";
|
|
44
46
|
import { buildImproveExecutionPlan } from "./planner.js";
|
|
45
|
-
|
|
46
|
-
import { runImprovePreparationStage } from "./preparation.js";
|
|
47
|
+
import { CONSOLIDATION_CONFIG_KEYS, pickDefined, recordImproveSkip, runImprovePreparationStage } from "./preparation.js";
|
|
47
48
|
import { DEFAULT_DUE_DAYS, filterProactiveDue } from "./proactive-maintenance.js";
|
|
48
49
|
import { akmReflect } from "./reflect.js";
|
|
49
|
-
import {
|
|
50
|
-
import { errMessage } from "./shared.js";
|
|
51
|
-
// Re-exported from ./loop-stages for test importers (improve-db-locking).
|
|
50
|
+
import { errMessage, noticeSet } from "./stage.js";
|
|
52
51
|
export { runImproveMaintenancePasses } from "./loop-stages.js";
|
|
53
52
|
export function renderSyncCommitMessage(template, result, nowMs) {
|
|
54
53
|
const iso = new Date(nowMs).toISOString();
|
|
@@ -66,18 +65,16 @@ export function renderSyncCommitMessage(template, result, nowMs) {
|
|
|
66
65
|
return template.replace(/\{(\w+)\}/g, (match, key) => tokens[key] ?? match);
|
|
67
66
|
}
|
|
68
67
|
/**
|
|
69
|
-
* How long the
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* finishes and the loop is about to start dispatching engine requests — not
|
|
73
|
-
* at run start — so it measures engine latency, not prepass time. Field
|
|
74
|
-
* re-test (#957): an engine pointed at a dead endpoint produced zero output
|
|
75
|
-
* for minutes, so a genuine hang looked identical to a normal-but-slow run.
|
|
76
|
-
* A few seconds is short enough that an operator watching a scheduled run's
|
|
77
|
-
* live log sees something promptly, long enough that an ordinary fast
|
|
78
|
-
* response never prints it.
|
|
68
|
+
* How long the loop waits for its first engine response before printing one
|
|
69
|
+
* "still waiting" line (#957): a dead endpoint otherwise looks like a slow run.
|
|
70
|
+
* Armed when the loop starts, not at run start.
|
|
79
71
|
*/
|
|
80
72
|
export const FIRST_ENGINE_RESPONSE_HEARTBEAT_MS = 5_000;
|
|
73
|
+
/**
|
|
74
|
+
* Abort the run at the budget; force-exit (0 — budget exhaustion is a normal
|
|
75
|
+
* scheduled-task outcome) only if the drain overruns the grace period.
|
|
76
|
+
* Returns an idempotent disposer.
|
|
77
|
+
*/
|
|
81
78
|
export function armBudgetWatchdog(budgetMs, controller, deps) {
|
|
82
79
|
const setTimeoutFn = deps?.setTimeoutFn ?? setTimeout;
|
|
83
80
|
const clearTimeoutFn = deps?.clearTimeoutFn ?? clearTimeout;
|
|
@@ -85,15 +82,10 @@ export function armBudgetWatchdog(budgetMs, controller, deps) {
|
|
|
85
82
|
const hardKillGraceMs = deps?.hardKillGraceMs ?? 5_000;
|
|
86
83
|
let hardKillTimer;
|
|
87
84
|
const budgetTimer = setTimeoutFn(() => {
|
|
88
|
-
// Cooperative cancellation first: let the run drain.
|
|
89
85
|
controller.abort("improve budget exhausted");
|
|
90
|
-
// Watchdog: only force-exit if the drain itself overruns the grace period.
|
|
91
|
-
// Exit 0: budget exhaustion is a normal scheduled-task condition, not an error.
|
|
92
86
|
hardKillTimer = setTimeoutFn(() => exitFn(0), hardKillGraceMs);
|
|
93
|
-
// Never keep the event loop alive solely for the watchdog.
|
|
94
87
|
hardKillTimer.unref?.();
|
|
95
88
|
}, budgetMs);
|
|
96
|
-
// RAII dispose: clears whichever timer is still pending. Idempotent.
|
|
97
89
|
return () => {
|
|
98
90
|
clearTimeoutFn(budgetTimer);
|
|
99
91
|
if (hardKillTimer !== undefined) {
|
|
@@ -102,61 +94,30 @@ export function armBudgetWatchdog(budgetMs, controller, deps) {
|
|
|
102
94
|
}
|
|
103
95
|
};
|
|
104
96
|
}
|
|
105
|
-
/**
|
|
106
|
-
* The run's write-provenance journal lifecycle as one named unit (#652).
|
|
107
|
-
*
|
|
108
|
-
* The journal is opened once the run owns its lock — so it spans exactly the
|
|
109
|
-
* window in which this run, and only this run, is allowed to write — and closed
|
|
110
|
-
* on every exit path including the crash-safety commit. Keeping the mutable
|
|
111
|
-
* handle behind this factory rather than as a bare `let` in {@link akmImprove}
|
|
112
|
-
* is also what keeps that function under the R31 size gate
|
|
113
|
-
* (`tests/architecture/improve-fn-size-ratchet.test.ts`, absolute 220-line bar
|
|
114
|
-
* with an empty baseline): lifecycle state belongs in a named unit, not in the
|
|
115
|
-
* orchestrator's preamble.
|
|
116
|
-
*/
|
|
117
|
-
function createRunWriteJournal() {
|
|
118
|
-
let journal;
|
|
119
|
-
return {
|
|
120
|
-
open: () => {
|
|
121
|
-
journal = beginWriteProvenance();
|
|
122
|
-
},
|
|
123
|
-
close: () => {
|
|
124
|
-
journal?.end();
|
|
125
|
-
journal = undefined;
|
|
126
|
-
},
|
|
127
|
-
current: () => journal,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
97
|
export async function akmImprove(options = {}) {
|
|
131
98
|
const setup = resolveImproveRunSetup(options);
|
|
132
99
|
options = setup.options;
|
|
133
|
-
const { budgetMs, budgetAbortController, scope, selectedStrategy, syncRepoDir, resolvedStateDbPath
|
|
100
|
+
const { budgetMs, budgetAbortController, scope, selectedStrategy, syncRepoDir, resolvedStateDbPath } = setup;
|
|
134
101
|
let clearBudgetTimer = () => { };
|
|
135
102
|
let clearFirstResponseHeartbeat = () => { };
|
|
136
|
-
//
|
|
137
|
-
// call terminates (success or error), including one issued by the prepass
|
|
138
|
-
// itself — makes arming the heartbeat below a no-op when the run is
|
|
139
|
-
// already known not to be silent.
|
|
103
|
+
// Set by the usage sink when any engine call terminates, prepass included.
|
|
140
104
|
let firstEngineResponseSeen = false;
|
|
141
105
|
let initialGitPaths = new Set();
|
|
142
|
-
|
|
106
|
+
// The write-provenance journal (#652) spans exactly the window this run holds the lock.
|
|
107
|
+
let journal;
|
|
108
|
+
const closeJournal = () => {
|
|
109
|
+
journal?.end();
|
|
110
|
+
journal = undefined;
|
|
111
|
+
};
|
|
143
112
|
const preEnsureCleanupWarnings = [];
|
|
144
|
-
let
|
|
145
|
-
let memorySummary = { eligible: 0, derived: 0 };
|
|
146
|
-
let strategyFilteredRefs = [];
|
|
147
|
-
let indexSnapshot;
|
|
148
|
-
let memoryCleanupPlan;
|
|
149
|
-
let autonomyGatedDirectLanes = [];
|
|
150
|
-
let guidance;
|
|
113
|
+
let collected;
|
|
151
114
|
let triageDrain;
|
|
115
|
+
let ensureIndexDurationMs;
|
|
152
116
|
let improveLockOwnership;
|
|
153
117
|
let exitBackstop;
|
|
154
|
-
// I1: open a single state.db connection for the main improve run so all
|
|
155
|
-
// appendEvent calls reuse one handle instead of open/migrate/close per call.
|
|
156
118
|
let eventsDb;
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// append, so one owner and counter span both lifecycle phases.
|
|
119
|
+
// Boundary-pinned for the prepass; replaced by the long-lived handle after it.
|
|
120
|
+
// The usage sink resolves it per append.
|
|
160
121
|
let eventsCtx = { dbPath: resolvedStateDbPath };
|
|
161
122
|
let disposeLlmUsageSink = () => { };
|
|
162
123
|
const releaseRunLock = () => {
|
|
@@ -168,15 +129,27 @@ export async function akmImprove(options = {}) {
|
|
|
168
129
|
releaseImproveLock(ownership);
|
|
169
130
|
}
|
|
170
131
|
catch {
|
|
171
|
-
// Best-effort
|
|
132
|
+
// Best-effort; exact ownership never deletes a successor's lock.
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const teardown = () => {
|
|
136
|
+
// The usage sink goes before eventsDb closes; the journal closes after the
|
|
137
|
+
// crash-safety commit that still needs it.
|
|
138
|
+
disposeLlmUsageSink();
|
|
139
|
+
clearFirstResponseHeartbeat();
|
|
140
|
+
clearBudgetTimer();
|
|
141
|
+
if (exitBackstop) {
|
|
142
|
+
// Only our own listener: removeAllListeners would drop the host's.
|
|
143
|
+
process.removeListener("exit", exitBackstop);
|
|
144
|
+
exitBackstop = undefined;
|
|
172
145
|
}
|
|
146
|
+
releaseRunLock();
|
|
147
|
+
closeJournal();
|
|
173
148
|
};
|
|
174
149
|
const commitStashBatch = makeCommitStashBatch({
|
|
175
150
|
run: setup,
|
|
176
151
|
getInitialGitPaths: () => initialGitPaths,
|
|
177
|
-
getWriteJournal: () =>
|
|
178
|
-
// Captured via getter: the live `eventsCtx` binding is reassigned after the
|
|
179
|
-
// prepass, and the catch-path sync must write through the current context.
|
|
152
|
+
getWriteJournal: () => journal,
|
|
180
153
|
getEventsCtx: () => eventsCtx,
|
|
181
154
|
});
|
|
182
155
|
if (!options.dryRun) {
|
|
@@ -185,8 +158,7 @@ export async function akmImprove(options = {}) {
|
|
|
185
158
|
}
|
|
186
159
|
try {
|
|
187
160
|
if (!options.dryRun) {
|
|
188
|
-
const acquisition = tryAcquireImproveLock(resolvedLockPath, options.skipIfLocked, {
|
|
189
|
-
// R25: C2 boundary-pinned path — the long-lived handle doesn't exist yet.
|
|
161
|
+
const acquisition = tryAcquireImproveLock(setup.resolvedLockPath, options.skipIfLocked, {
|
|
190
162
|
dbPath: resolvedStateDbPath,
|
|
191
163
|
});
|
|
192
164
|
if (acquisition.state === "skipped") {
|
|
@@ -202,67 +174,53 @@ export async function akmImprove(options = {}) {
|
|
|
202
174
|
process.on("exit", exitBackstop);
|
|
203
175
|
initialGitPaths =
|
|
204
176
|
syncRepoDir && isGitBackedStash(syncRepoDir) ? new Set(listGitChangedPaths(syncRepoDir)) : new Set();
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
//
|
|
177
|
+
journal = beginWriteProvenance();
|
|
178
|
+
// Step 8 (alpha.9 plan §5.4): the purge sweep. Deterministic, no LLM,
|
|
179
|
+
// and first — before anything else this run might touch the archive
|
|
180
|
+
// for. Git-backed bundles only; a bundle with no git history of its
|
|
181
|
+
// own is left untouched (akm health reports its size instead).
|
|
182
|
+
if (setup.primaryStashDir) {
|
|
183
|
+
try {
|
|
184
|
+
const purge = purgeGracedArchive(setup.primaryStashDir);
|
|
185
|
+
if (purge.purgedFiles > 0) {
|
|
186
|
+
info(`[improve] archive purge: ${purge.purgedFiles} file(s) across ${purge.purgedDirs} retirement(s) past the ${RETIRE_GRACE_DAYS}d grace period removed`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
preEnsureCleanupWarnings.push(`archive purge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// The index is made current BEFORE triage (R6): triage promotes into the
|
|
194
|
+
// stash, and a reindex after it would always find fresh work.
|
|
195
|
+
const bootstrap = await runIndexBootstrapPass(setup, budgetAbortController.signal);
|
|
196
|
+
preEnsureCleanupWarnings.push(...bootstrap.warnings);
|
|
197
|
+
ensureIndexDurationMs = bootstrap.ensureIndexDurationMs;
|
|
210
198
|
triageDrain = await runTriagePrePass(setup);
|
|
199
|
+
// Index triage's own writes incrementally so selection sees them.
|
|
200
|
+
const triageWrittenPaths = journal?.writtenPaths() ?? [];
|
|
201
|
+
if (setup.primaryStashDir && triageWrittenPaths.length > 0) {
|
|
202
|
+
await indexWrittenAssets(setup.primaryStashDir, triageWrittenPaths);
|
|
203
|
+
}
|
|
211
204
|
}
|
|
212
|
-
|
|
213
|
-
// indexAndCollect helper).
|
|
214
|
-
const collected = await indexAndCollect({ run: setup, signal: budgetAbortController.signal });
|
|
215
|
-
plannedRefs = collected.plannedRefs;
|
|
216
|
-
memorySummary = collected.memorySummary;
|
|
217
|
-
strategyFilteredRefs = collected.strategyFilteredRefs;
|
|
218
|
-
indexSnapshot = collected.indexSnapshot;
|
|
219
|
-
memoryCleanupPlan = collected.memoryCleanupPlan;
|
|
220
|
-
autonomyGatedDirectLanes = collected.autonomyGatedDirectLanes;
|
|
221
|
-
guidance = collected.guidance;
|
|
222
|
-
preEnsureCleanupWarnings.push(...collected.warnings);
|
|
205
|
+
collected = await indexAndCollect(setup);
|
|
223
206
|
if (options.dryRun) {
|
|
224
|
-
const result = await runDryPlanningStage(
|
|
225
|
-
run: setup,
|
|
226
|
-
collected,
|
|
227
|
-
resolvedStateDbPath,
|
|
228
|
-
budgetMs,
|
|
229
|
-
initialCleanupWarnings: preEnsureCleanupWarnings,
|
|
230
|
-
signal: budgetAbortController.signal,
|
|
231
|
-
});
|
|
207
|
+
const result = await runDryPlanningStage(setup, collected, preEnsureCleanupWarnings);
|
|
232
208
|
clearBudgetTimer();
|
|
233
209
|
return result;
|
|
234
210
|
}
|
|
235
211
|
}
|
|
236
212
|
catch (err) {
|
|
237
|
-
|
|
238
|
-
// before any run teardown. The disposer is idempotent with the main finalizer.
|
|
239
|
-
disposeLlmUsageSink();
|
|
240
|
-
clearFirstResponseHeartbeat();
|
|
241
|
-
clearBudgetTimer();
|
|
242
|
-
if (exitBackstop) {
|
|
243
|
-
process.removeListener("exit", exitBackstop);
|
|
244
|
-
exitBackstop = undefined;
|
|
245
|
-
}
|
|
246
|
-
releaseRunLock();
|
|
247
|
-
runJournal.close();
|
|
213
|
+
teardown();
|
|
248
214
|
throw err;
|
|
249
215
|
}
|
|
250
216
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const ctx = buildImproveRunContext(setup, eventsCtx);
|
|
259
|
-
// #957: arm the heartbeat here, immediately before the improve loop
|
|
260
|
-
// starts dispatching engine requests — not at run start, where its timer
|
|
261
|
-
// would measure the triage/index prepass instead of engine latency. A
|
|
262
|
-
// no-op when the prepass already produced a terminal LLM record (the
|
|
263
|
-
// onRecord callback above already saw it). Cleared the moment any call
|
|
264
|
-
// terminates (success or error) — never rearmed, so this prints at most
|
|
265
|
-
// once per run.
|
|
217
|
+
try {
|
|
218
|
+
eventsDb = openStateDatabase(resolvedStateDbPath);
|
|
219
|
+
eventsCtx = { db: eventsDb };
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
rethrowIfTestIsolationError(err);
|
|
223
|
+
}
|
|
266
224
|
if (!firstEngineResponseSeen) {
|
|
267
225
|
const firstResponseTimer = setTimeout(() => {
|
|
268
226
|
warn("[improve] Still waiting for the first engine response...");
|
|
@@ -270,45 +228,11 @@ export async function akmImprove(options = {}) {
|
|
|
270
228
|
firstResponseTimer.unref?.();
|
|
271
229
|
clearFirstResponseHeartbeat = () => clearTimeout(firstResponseTimer);
|
|
272
230
|
}
|
|
273
|
-
const seq = await runImproveStageSequence(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
autonomyGatedDirectLanes,
|
|
279
|
-
memorySummary,
|
|
280
|
-
preEnsureCleanupWarnings,
|
|
281
|
-
eventsCtx,
|
|
282
|
-
ctx,
|
|
283
|
-
});
|
|
284
|
-
const result = finalizeImproveResult({
|
|
285
|
-
run: setup,
|
|
286
|
-
seq,
|
|
287
|
-
guidance,
|
|
288
|
-
memorySummary,
|
|
289
|
-
memoryCleanupPlan,
|
|
290
|
-
strategyFilteredRefs,
|
|
291
|
-
rawPlannedRefs: plannedRefs,
|
|
292
|
-
indexSnapshot,
|
|
293
|
-
triageDrain,
|
|
294
|
-
eventsCtx,
|
|
295
|
-
});
|
|
296
|
-
// End-of-run BATCH auto-sync — the converged commit. Recognition is
|
|
297
|
-
// decoupled from the per-write path (see write-source.ts case-3): the primary
|
|
298
|
-
// stash writes as a filesystem source during the run, then is committed via
|
|
299
|
-
// the same `saveGitStash` that `akm sync` calls. The gating (git-backed
|
|
300
|
-
// primary stash, sync not disabled) and the NON-FATAL guarantee now live in
|
|
301
|
-
// `commitStashBatch` (#662); the inter-cycle and catch-path calls reuse it.
|
|
302
|
-
// dry-run already returned above, so this always runs on a completed live
|
|
303
|
-
// run. `result.sync` reflects this final commit (for a one-cycle run it is
|
|
304
|
-
// the only commit; for a multi-cycle run the earlier cycles were banked by
|
|
305
|
-
// the inter-cycle calls and this records the last batch). `result` carries
|
|
306
|
-
// the full `{accepted}`/`{refs}`/`{triage_*}` token data for the message.
|
|
307
|
-
//
|
|
308
|
-
// #652: surface the run's write provenance on the envelope BEFORE the sync
|
|
309
|
-
// (the sync itself writes no assets), so `result.writtenPaths` describes
|
|
310
|
-
// exactly the set the commit below was scoped to.
|
|
311
|
-
const writtenPaths = describeRunWrittenPaths(setup, runJournal.current()?.writtenPaths() ?? []);
|
|
231
|
+
const seq = await runImproveStageSequence(setup, collected, preEnsureCleanupWarnings, eventsCtx);
|
|
232
|
+
const result = finalizeImproveResult({ run: setup, seq, collected, triageDrain, ensureIndexDurationMs, eventsCtx });
|
|
233
|
+
// The run's write provenance goes on the envelope before the sync, so
|
|
234
|
+
// `writtenPaths` is exactly the set the commit is scoped to.
|
|
235
|
+
const writtenPaths = describeRunWrittenPaths(setup, journal?.writtenPaths() ?? []);
|
|
312
236
|
if (writtenPaths.length > 0)
|
|
313
237
|
result.writtenPaths = writtenPaths;
|
|
314
238
|
result.sync = commitStashBatch(result);
|
|
@@ -316,64 +240,24 @@ export async function akmImprove(options = {}) {
|
|
|
316
240
|
}
|
|
317
241
|
catch (err) {
|
|
318
242
|
recordImproveFailure(err, setup, eventsCtx);
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
// writes uncommitted until a later clean run sweeps them up. Best-effort —
|
|
323
|
-
// `commitStashBatch` swallows its own errors and no-ops a clean tree, so this
|
|
324
|
-
// never masks or supersedes the original failure being rethrown below.
|
|
325
|
-
commitStashBatch({ scope, plannedRefs, runId: options.runId });
|
|
243
|
+
// Crash/abort safety net (#662): commit what this run already wrote.
|
|
244
|
+
// commitStashBatch never throws and no-ops a clean tree.
|
|
245
|
+
commitStashBatch({ scope, plannedRefs: collected.plannedRefs, runId: options.runId });
|
|
326
246
|
throw err;
|
|
327
247
|
}
|
|
328
248
|
finally {
|
|
329
|
-
|
|
330
|
-
// no late sink invocation can write through a closed handle.
|
|
331
|
-
disposeLlmUsageSink();
|
|
332
|
-
// #957: never leave the first-response heartbeat timer pending past the run.
|
|
333
|
-
clearFirstResponseHeartbeat();
|
|
334
|
-
// O-1 (#364): Clear the budget abort timer so it does not keep the event
|
|
335
|
-
// loop alive after the run completes.
|
|
336
|
-
clearBudgetTimer();
|
|
337
|
-
// Drop ONLY our own process.exit backstop so it does not fire later (or
|
|
338
|
-
// accumulate across repeated in-process calls). Must NOT use
|
|
339
|
-
// removeAllListeners("exit") here: in the in-process model (tests and
|
|
340
|
-
// programmatic callers import cli.ts) that would silently destroy exit
|
|
341
|
-
// handlers owned by the host or other commands.
|
|
342
|
-
if (exitBackstop) {
|
|
343
|
-
process.removeListener("exit", exitBackstop);
|
|
344
|
-
exitBackstop = undefined;
|
|
345
|
-
}
|
|
346
|
-
releaseRunLock();
|
|
347
|
-
// #652: close the write-provenance journal LAST among the write-facing
|
|
348
|
-
// teardown steps — the catch path's crash-safety commit above still needs
|
|
349
|
-
// it open to know what this run wrote.
|
|
350
|
-
runJournal.close();
|
|
351
|
-
// I1: close the long-lived state.db connection opened at the top of the run.
|
|
249
|
+
teardown();
|
|
352
250
|
try {
|
|
353
251
|
eventsDb?.close();
|
|
354
252
|
}
|
|
355
253
|
catch {
|
|
356
|
-
//
|
|
254
|
+
// already closed
|
|
357
255
|
}
|
|
358
256
|
}
|
|
359
257
|
}
|
|
360
|
-
/** Open the run-owned state handle while preserving the boundary-pinned fallback. */
|
|
361
|
-
function openImproveEventsContext(dbPath) {
|
|
362
|
-
try {
|
|
363
|
-
const db = openStateDatabase(dbPath);
|
|
364
|
-
return { db, ctx: { db } };
|
|
365
|
-
}
|
|
366
|
-
catch (err) {
|
|
367
|
-
rethrowIfTestIsolationError(err);
|
|
368
|
-
return { ctx: { dbPath } };
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
258
|
/**
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* stash) when the write landed inside it, absolute otherwise — a run writing to
|
|
375
|
-
* a `--target` bundle outside the stash still reports what it wrote. Deduped and
|
|
376
|
-
* sorted so the field is stable across runs.
|
|
259
|
+
* The run's journaled paths for `result.writtenPaths`: POSIX-relative to the
|
|
260
|
+
* primary stash when inside it, absolute otherwise; deduped and sorted.
|
|
377
261
|
*/
|
|
378
262
|
function describeRunWrittenPaths(setup, writtenPaths) {
|
|
379
263
|
const root = setup.primaryStashDir ?? setup.syncRepoDir;
|
|
@@ -385,19 +269,16 @@ function describeRunWrittenPaths(setup, writtenPaths) {
|
|
|
385
269
|
return [...described].sort();
|
|
386
270
|
}
|
|
387
271
|
/**
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* read-only bundle a plain `--dry-run` would, without duplicating this
|
|
391
|
-
* selector/target/fallback precedence.
|
|
272
|
+
* The source a dry run (or `--show-prompt`) inspects, without adapting it into
|
|
273
|
+
* a write target.
|
|
392
274
|
*/
|
|
393
275
|
export function resolveImproveReadSource(config, scopedRef, explicitTarget, fallbackStashDir) {
|
|
394
276
|
if (scopedRef?.origin && explicitTarget && scopedRef.origin !== explicitTarget) {
|
|
395
277
|
throw new UsageError(`Qualified ref bundle "${scopedRef.origin}" conflicts with --target "${explicitTarget}".`, "INVALID_FLAG_VALUE", `Drop --target or use --target ${scopedRef.origin}.`);
|
|
396
278
|
}
|
|
397
279
|
const selector = scopedRef?.origin ?? explicitTarget ?? config.defaultWriteTarget;
|
|
398
|
-
if (!selector && fallbackStashDir)
|
|
280
|
+
if (!selector && fallbackStashDir)
|
|
399
281
|
return { source: { name: "stash", path: fallbackStashDir } };
|
|
400
|
-
}
|
|
401
282
|
const configuredSelector = selector ?? config.defaultBundle;
|
|
402
283
|
if (configuredSelector) {
|
|
403
284
|
const entry = bundlesToSourceEntries(config)?.find((source) => source.name === configuredSelector);
|
|
@@ -411,17 +292,14 @@ export function resolveImproveReadSource(config, scopedRef, explicitTarget, fall
|
|
|
411
292
|
return { selector: configuredSelector, source: { name: configuredSelector, path: sourcePath } };
|
|
412
293
|
}
|
|
413
294
|
const implicit = resolveSourceEntries(undefined, config)[0];
|
|
414
|
-
if (!implicit)
|
|
295
|
+
if (!implicit)
|
|
415
296
|
throw new ConfigError("no source configured; run `akm bundle create`", "STASH_DIR_NOT_FOUND");
|
|
416
|
-
}
|
|
417
297
|
return { source: { name: implicit.registryId ?? "stash", path: implicit.path } };
|
|
418
298
|
}
|
|
419
299
|
/**
|
|
420
|
-
* Run
|
|
421
|
-
*
|
|
422
|
-
* the
|
|
423
|
-
* snapshot (resolvedStateDbPath) must be taken before the first await of the
|
|
424
|
-
* run.
|
|
300
|
+
* Run setup, fully synchronous: the budget signal, the invocation plan, the
|
|
301
|
+
* write target, the profile-defaulted options, and the state.db and lock paths
|
|
302
|
+
* pinned before the first await (C2: a later env change cannot redirect them).
|
|
425
303
|
*/
|
|
426
304
|
function resolveImproveRunSetup(options) {
|
|
427
305
|
const startMs = Date.now();
|
|
@@ -433,30 +311,15 @@ function resolveImproveRunSetup(options) {
|
|
|
433
311
|
configurable: true,
|
|
434
312
|
});
|
|
435
313
|
const scope = resolveImproveScope(options.scope);
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
const reindexFn = options.reindexFn ?? akmIndex;
|
|
440
|
-
const drainProposalsFn = options.drainProposalsFn ?? drainProposals;
|
|
441
|
-
// #616 multi-cycle test seams. Default to the real module-local fns.
|
|
442
|
-
const collectEligibleRefsImpl = options.collectEligibleRefsFn ?? (options.dryRun ? collectEligibleRefsReadOnly : collectEligibleRefs);
|
|
443
|
-
const runImprovePreparationStageImpl = options.runImprovePreparationStageFn ?? runImprovePreparationStage;
|
|
444
|
-
const runImproveLoopStageImpl = options.runImproveLoopStageFn ?? runImproveLoopStage;
|
|
445
|
-
const runImprovePostLoopStageImpl = options.runImprovePostLoopStageFn ?? runImprovePostLoopStage;
|
|
446
|
-
// Resolve the improve profile for this run. Profile drives type filtering,
|
|
447
|
-
// process gating, and the default limit value.
|
|
448
|
-
const _earlyConfig = options.config ?? loadConfig();
|
|
449
|
-
const configuredImproveProfile = resolveImproveStrategy(options.strategy, _earlyConfig).config;
|
|
314
|
+
const config = options.config ?? loadConfig();
|
|
315
|
+
const configuredImproveProfile = resolveImproveStrategy(options.strategy, config).config;
|
|
316
|
+
// A dry run never dispatches, so an all-disabled strategy must not abort it.
|
|
450
317
|
const resolvedPlan = options.resolvedPlan ??
|
|
451
|
-
|
|
452
|
-
// resolveImprovePlan call: a dry run never dispatches, so a strategy left
|
|
453
|
-
// fully disabled by an unreachable credential must not abort here either.
|
|
454
|
-
resolveImprovePlan(options.strategy, _earlyConfig, {
|
|
318
|
+
resolveImprovePlan(options.strategy, config, {
|
|
455
319
|
repairValidationFailures: options.repairValidationFailures,
|
|
456
320
|
allowAllDisabled: options.dryRun,
|
|
457
321
|
});
|
|
458
322
|
const selectedStrategy = resolvedPlan.strategy;
|
|
459
|
-
const improveSensitiveValues = collectEngineCredentialValues(_earlyConfig);
|
|
460
323
|
const improveProfile = selectedStrategy.config;
|
|
461
324
|
const configuredLimits = {
|
|
462
325
|
...(options.limit !== undefined ? { cli: options.limit } : {}),
|
|
@@ -465,52 +328,36 @@ function resolveImproveRunSetup(options) {
|
|
|
465
328
|
? { reflect: configuredImproveProfile.processes.reflect.limit }
|
|
466
329
|
: {}),
|
|
467
330
|
};
|
|
331
|
+
// --limit, then the reflect process limit, then the profile limit.
|
|
468
332
|
const effectiveLimit = options.limit ?? improveProfile?.processes?.reflect?.limit ?? improveProfile.limit;
|
|
469
333
|
const scopedRef = scope.mode === "ref" && scope.value ? parseRefInput(scope.value) : undefined;
|
|
470
334
|
const readSource = options.dryRun
|
|
471
335
|
? options.writeTarget
|
|
472
336
|
? { selector: options.writeTarget.selector, source: options.writeTarget.source }
|
|
473
|
-
: resolveImproveReadSource(
|
|
337
|
+
: resolveImproveReadSource(config, scopedRef, options.target, options.stashDir)
|
|
474
338
|
: undefined;
|
|
475
339
|
const writeTarget = options.dryRun
|
|
476
340
|
? undefined
|
|
477
341
|
: scopedRef?.origin
|
|
478
|
-
? resolveMutationTarget(
|
|
342
|
+
? resolveMutationTarget(config, scopedRef, options.writeTarget?.source.name ?? options.target).target
|
|
479
343
|
: (options.writeTarget ??
|
|
480
|
-
(options.target ||
|
|
481
|
-
? resolveWriteTarget(
|
|
344
|
+
(options.target || config.defaultWriteTarget || !options.stashDir
|
|
345
|
+
? resolveWriteTarget(config, options.target)
|
|
482
346
|
: {
|
|
483
347
|
source: { kind: "filesystem", name: "stash", path: options.stashDir },
|
|
484
|
-
config: {
|
|
485
|
-
type: "filesystem",
|
|
486
|
-
name: "stash",
|
|
487
|
-
path: options.stashDir,
|
|
488
|
-
writable: true,
|
|
489
|
-
},
|
|
348
|
+
config: { type: "filesystem", name: "stash", path: options.stashDir, writable: true },
|
|
490
349
|
}));
|
|
491
350
|
const selectedSource = writeTarget?.source ?? readSource?.source;
|
|
492
351
|
if (!selectedSource)
|
|
493
352
|
throw new ConfigError("improve could not resolve a source", "STASH_DIR_NOT_FOUND");
|
|
494
|
-
|
|
495
|
-
// Apply profile defaults — CLI flags take precedence over profile defaults.
|
|
496
|
-
// Rebuild options with effective values so all downstream stage functions
|
|
497
|
-
// automatically pick up the profile-driven defaults.
|
|
353
|
+
// Every stage reads this one config snapshot; nothing reloads it mid-run.
|
|
498
354
|
options = {
|
|
499
355
|
...options,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
config: _earlyConfig,
|
|
503
|
-
target: selectedSelector,
|
|
356
|
+
config,
|
|
357
|
+
target: writeTarget?.selector ?? readSource?.selector,
|
|
504
358
|
sourceName: selectedSource.name,
|
|
505
359
|
...(writeTarget ? { writeTarget } : {}),
|
|
506
360
|
stashDir: selectedSource.path,
|
|
507
|
-
consolidateOptions: {
|
|
508
|
-
...options.consolidateOptions,
|
|
509
|
-
target: selectedSelector,
|
|
510
|
-
...(writeTarget ? { writeTarget } : {}),
|
|
511
|
-
},
|
|
512
|
-
// Profile-level limit, then process-level reflect.limit as fallback.
|
|
513
|
-
// CLI --limit takes precedence over both.
|
|
514
361
|
limit: effectiveLimit,
|
|
515
362
|
};
|
|
516
363
|
let primaryStashDir;
|
|
@@ -520,42 +367,24 @@ function resolveImproveRunSetup(options) {
|
|
|
520
367
|
catch {
|
|
521
368
|
primaryStashDir = undefined;
|
|
522
369
|
}
|
|
523
|
-
const syncRepoDir = writeTarget?.source.repoPath ?? primaryStashDir;
|
|
524
|
-
// C2 (#553/#554/#499): resolve the state.db path ONCE, synchronously, at the
|
|
525
|
-
// command boundary — before the first `await` below. Every state.db open in
|
|
526
|
-
// this run (`openStateDatabase`, every default-path `appendEvent`) is pinned
|
|
527
|
-
// to this snapshot via `eventsCtx.dbPath`, so a parallel test file mutating
|
|
528
|
-
// `process.env.XDG_DATA_HOME` across an await boundary can never redirect this
|
|
529
|
-
// run's DB opens to a wrong/just-deleted tmpdir mid-flight (the parallel-load
|
|
530
|
-
// timeout root cause). Because beforeEach runs synchronously, env is still the
|
|
531
|
-
// calling test's own at this point; we capture it before yielding the loop.
|
|
532
|
-
const resolvedStateDbPath = getStateDbPathInDataDir();
|
|
533
|
-
// One conservative run lock protects the complete mutation window, including
|
|
534
|
-
// triage, indexing, proposal work, maintenance, and final stash sync. Moved
|
|
535
|
-
// out of `$STASH/.akm` to `$STATE/locks/<stash>/` (itlackey/akm#890): a lock
|
|
536
|
-
// file is machine-local coordination state, not content that must travel
|
|
537
|
-
// with the bundle.
|
|
538
|
-
const lockBaseDir = getStashLocksDir(primaryStashDir ?? options.stashDir ?? ".");
|
|
539
|
-
const resolvedLockPath = improveLockPath(lockBaseDir);
|
|
540
|
-
const effectiveSync = { ...improveProfile.sync, ...options.sync };
|
|
541
370
|
return {
|
|
542
371
|
startMs,
|
|
543
372
|
budgetMs,
|
|
544
373
|
budgetAbortController,
|
|
545
374
|
scope,
|
|
546
|
-
reflectFn,
|
|
547
|
-
distillFn,
|
|
548
|
-
ensureIndexFn,
|
|
549
|
-
reindexFn,
|
|
550
|
-
drainProposalsFn,
|
|
551
|
-
collectEligibleRefsImpl,
|
|
552
|
-
runImprovePreparationStageImpl,
|
|
553
|
-
runImproveLoopStageImpl,
|
|
554
|
-
runImprovePostLoopStageImpl,
|
|
555
|
-
|
|
375
|
+
reflectFn: options.reflectFn ?? akmReflect,
|
|
376
|
+
distillFn: options.distillFn ?? akmDistill,
|
|
377
|
+
ensureIndexFn: options.ensureIndexFn ?? ensureIndex,
|
|
378
|
+
reindexFn: options.reindexFn ?? akmIndex,
|
|
379
|
+
drainProposalsFn: options.drainProposalsFn ?? drainProposals,
|
|
380
|
+
collectEligibleRefsImpl: options.collectEligibleRefsFn ?? (options.dryRun ? collectEligibleRefsReadOnly : collectEligibleRefs),
|
|
381
|
+
runImprovePreparationStageImpl: options.runImprovePreparationStageFn ?? runImprovePreparationStage,
|
|
382
|
+
runImproveLoopStageImpl: options.runImproveLoopStageFn ?? runImproveLoopStage,
|
|
383
|
+
runImprovePostLoopStageImpl: options.runImprovePostLoopStageFn ?? runImprovePostLoopStage,
|
|
384
|
+
config,
|
|
556
385
|
resolvedPlan,
|
|
557
386
|
selectedStrategy,
|
|
558
|
-
improveSensitiveValues,
|
|
387
|
+
improveSensitiveValues: collectEngineCredentialValues(config),
|
|
559
388
|
improveProfile,
|
|
560
389
|
configuredImproveProfile,
|
|
561
390
|
configuredLimits,
|
|
@@ -563,171 +392,99 @@ function resolveImproveRunSetup(options) {
|
|
|
563
392
|
writeTarget,
|
|
564
393
|
options,
|
|
565
394
|
primaryStashDir,
|
|
566
|
-
syncRepoDir,
|
|
567
|
-
resolvedStateDbPath,
|
|
568
|
-
|
|
569
|
-
|
|
395
|
+
syncRepoDir: writeTarget?.source.repoPath ?? primaryStashDir,
|
|
396
|
+
resolvedStateDbPath: getStateDbPathInDataDir(),
|
|
397
|
+
// The run lock is machine-local state, kept outside the bundle (#890).
|
|
398
|
+
resolvedLockPath: improveLockPath(getStashLocksDir(primaryStashDir ?? options.stashDir ?? ".")),
|
|
399
|
+
effectiveSync: { ...improveProfile.sync, ...options.sync },
|
|
570
400
|
};
|
|
571
401
|
}
|
|
572
|
-
/**
|
|
573
|
-
* WI-9.10: build the run's {@link RunContext} purely from values
|
|
574
|
-
* `resolveImproveRunSetup` and the long-lived state.db open already
|
|
575
|
-
* resolved — no second config load, no new db handle. Called once, right
|
|
576
|
-
* after `eventsCtx` is resolved (the last of {config, stashDir, eventsCtx,
|
|
577
|
-
* proposalsCtx, sourceRun, dryRun} to become available in `akmImprove`).
|
|
578
|
-
*/
|
|
579
|
-
function buildImproveRunContext(run, eventsCtx) {
|
|
580
|
-
return createRunContext({
|
|
581
|
-
config: run._earlyConfig,
|
|
582
|
-
// primaryStashDir can fail to resolve (rare); "." mirrors the existing
|
|
583
|
-
// lockBaseDir fallback in resolveImproveRunSetup for the same case. This
|
|
584
|
-
// is a BEST-EFFORT value for the required RunContext field only — the
|
|
585
|
-
// loop stage reads `ImproveLoopState.primaryStashDir` (the honest
|
|
586
|
-
// optional), so its `if (primaryStashDir)` guards still skip on the
|
|
587
|
-
// unresolvable path; no RunContext consumer reads `ctx.stashDir` there.
|
|
588
|
-
stashDir: run.primaryStashDir ?? run.options.stashDir ?? ".",
|
|
589
|
-
eventsCtx,
|
|
590
|
-
// ProposalsContext threads only a dbPath seam (D14: no db handle is
|
|
591
|
-
// threaded in), mirroring eventsCtx's own dbPath-only fallback shape.
|
|
592
|
-
// Not yet wired into any proposal call site this stage — verb-level
|
|
593
|
-
// RunContext adoption (reflect/distill/extract/consolidate) is later.
|
|
594
|
-
proposalsCtx: { dbPath: run.resolvedStateDbPath },
|
|
595
|
-
// Representative symbolic runner for this run: reflect is the loop's
|
|
596
|
-
// primary model-driving process. Credentials remain unresolved here.
|
|
597
|
-
getLlmRunner: () => run.resolvedPlan.processes.reflect.runner,
|
|
598
|
-
sourceRun: run.options.runId ?? `improve-${run.startMs}`,
|
|
599
|
-
// Always false here: callers only reach this point past the dry-run
|
|
600
|
-
// early return in akmImprove.
|
|
601
|
-
dryRun: run.options.dryRun ?? false,
|
|
602
|
-
// Same AbortSignal instance armBudgetWatchdog stamped a live
|
|
603
|
-
// `remainingBudgetMs` getter onto (#616) — identity preserved so
|
|
604
|
-
// `ctx.signal.remainingBudgetMs` resolves correctly for every consumer.
|
|
605
|
-
signal: run.budgetAbortController.signal,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
/** The catch-path improve_failed audit event (D3), redacted. */
|
|
402
|
+
/** The redacted `improve_failed` event for a crashed run. */
|
|
609
403
|
function recordImproveFailure(err, run, eventsCtx) {
|
|
610
|
-
const { scope, selectedStrategy, improveSensitiveValues, startMs } = run;
|
|
611
|
-
// D3: emit improve_failed on unexpected crash so dashboards can detect failures.
|
|
612
404
|
appendEvent({
|
|
613
405
|
eventType: "improve_failed",
|
|
614
|
-
ref: scope.mode === "ref" ? scope.value : `improve:${scope.mode}:${scope.value ?? "all"}`,
|
|
406
|
+
ref: run.scope.mode === "ref" ? run.scope.value : `improve:${run.scope.mode}:${run.scope.value ?? "all"}`,
|
|
615
407
|
metadata: {
|
|
616
|
-
strategy: selectedStrategy.name,
|
|
617
|
-
error: redactSensitiveText(errMessage(err), improveSensitiveValues),
|
|
618
|
-
durationMs: Date.now() - startMs,
|
|
408
|
+
strategy: run.selectedStrategy.name,
|
|
409
|
+
error: redactSensitiveText(errMessage(err), run.improveSensitiveValues),
|
|
410
|
+
durationMs: Date.now() - run.startMs,
|
|
619
411
|
},
|
|
620
412
|
}, eventsCtx);
|
|
621
413
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
async function indexAndCollect(args) {
|
|
628
|
-
const { signal } = args;
|
|
629
|
-
const { scope, options, primaryStashDir, improveProfile, _earlyConfig, ensureIndexFn, collectEligibleRefsImpl } = args.run;
|
|
630
|
-
const warnings = [];
|
|
631
|
-
let plannedRefs = [];
|
|
632
|
-
let memorySummary = { eligible: 0, derived: 0 };
|
|
633
|
-
let strategyFilteredRefs = [];
|
|
634
|
-
let indexSnapshot;
|
|
635
|
-
// #339 fix: ensureIndex MUST run BEFORE collectEligibleRefs. The eligible-ref
|
|
636
|
-
// query reads the `entries` table; if a DB version upgrade just dropped that
|
|
637
|
-
// table (or the index is otherwise empty), the prior run order silently
|
|
638
|
-
// returned plannedRefs=[] and the improve loop no-op'd. Hoisting the call
|
|
639
|
-
// here repopulates the index first so the subsequent query sees fresh data.
|
|
640
|
-
if (primaryStashDir && !options.dryRun) {
|
|
641
|
-
// Probe pre-ensureIndex entry count to drive the loud-fail warning below.
|
|
642
|
-
// Best-effort: a missing DB / unreadable schema is the fresh-install case
|
|
643
|
-
// and not a bug — we silently skip the probe.
|
|
644
|
-
let preEnsureEntryCount;
|
|
645
|
-
try {
|
|
646
|
-
const dbPath = getDbPath();
|
|
647
|
-
if (fs.existsSync(dbPath)) {
|
|
648
|
-
const probeDb = openExistingDatabase();
|
|
649
|
-
try {
|
|
650
|
-
preEnsureEntryCount = getEntryCount(probeDb);
|
|
651
|
-
}
|
|
652
|
-
finally {
|
|
653
|
-
closeDatabase(probeDb);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
catch (err) {
|
|
658
|
-
rethrowIfTestIsolationError(err);
|
|
659
|
-
// best-effort; leave preEnsureEntryCount undefined
|
|
660
|
-
}
|
|
414
|
+
function probeEntryCount() {
|
|
415
|
+
try {
|
|
416
|
+
if (!fs.existsSync(getDbPath()))
|
|
417
|
+
return undefined;
|
|
418
|
+
const db = openExistingDatabase();
|
|
661
419
|
try {
|
|
662
|
-
|
|
420
|
+
return getEntryCount(db);
|
|
663
421
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
throw err;
|
|
667
|
-
warnings.push(`ensureIndex failed: ${errMessage(err)}`);
|
|
668
|
-
}
|
|
669
|
-
// #339 loud-fail: if the index was empty pre-ensureIndex but is now
|
|
670
|
-
// populated, a version-upgrade-triggered rebuild just happened. Surface
|
|
671
|
-
// that on stderr so the improve run is not silently masked by stale
|
|
672
|
-
// index state. Zero-before AND zero-after is the empty-stash case and
|
|
673
|
-
// is intentionally not warned (not a bug).
|
|
674
|
-
if (preEnsureEntryCount === 0) {
|
|
675
|
-
try {
|
|
676
|
-
const probeDb = openExistingDatabase();
|
|
677
|
-
let postCount = 0;
|
|
678
|
-
try {
|
|
679
|
-
postCount = getEntryCount(probeDb);
|
|
680
|
-
}
|
|
681
|
-
finally {
|
|
682
|
-
closeDatabase(probeDb);
|
|
683
|
-
}
|
|
684
|
-
if (postCount > 0) {
|
|
685
|
-
warn("[improve] index was empty after DB version upgrade — repopulating before continuing");
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
catch (err) {
|
|
689
|
-
rethrowIfTestIsolationError(err);
|
|
690
|
-
// best-effort
|
|
691
|
-
}
|
|
422
|
+
finally {
|
|
423
|
+
closeDatabase(db);
|
|
692
424
|
}
|
|
693
425
|
}
|
|
694
|
-
({
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
426
|
+
catch (err) {
|
|
427
|
+
rethrowIfTestIsolationError(err);
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* ensureIndex before selection (#339): the eligible-ref query reads `entries`,
|
|
433
|
+
* which a DB version upgrade may just have dropped. An index that was empty
|
|
434
|
+
* and is now populated means an upgrade rebuild happened; say so.
|
|
435
|
+
*/
|
|
436
|
+
async function runIndexBootstrapPass(run, signal) {
|
|
437
|
+
const { primaryStashDir, options } = run;
|
|
438
|
+
const warnings = [];
|
|
439
|
+
if (!primaryStashDir || options.dryRun)
|
|
440
|
+
return { warnings };
|
|
441
|
+
const preEnsureEntryCount = probeEntryCount();
|
|
442
|
+
let ensureIndexDurationMs;
|
|
443
|
+
try {
|
|
444
|
+
await run.ensureIndexFn(primaryStashDir, {
|
|
445
|
+
mode: "blocking",
|
|
446
|
+
signal,
|
|
447
|
+
onReindexTiming: ({ durationMs }) => {
|
|
448
|
+
ensureIndexDurationMs = durationMs;
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
if (signal.aborted)
|
|
454
|
+
throw err;
|
|
455
|
+
warnings.push(`ensureIndex failed: ${errMessage(err)}`);
|
|
456
|
+
}
|
|
457
|
+
if (preEnsureEntryCount === 0 && (probeEntryCount() ?? 0) > 0) {
|
|
458
|
+
warn("[improve] index was empty after DB version upgrade — repopulating before continuing");
|
|
459
|
+
}
|
|
460
|
+
return { warnings, ensureIndexDurationMs };
|
|
461
|
+
}
|
|
462
|
+
/** Candidate selection plus the memory-cleanup plan and the autonomy-gated direct lanes. */
|
|
463
|
+
async function indexAndCollect(run) {
|
|
464
|
+
const { scope, options, primaryStashDir, improveProfile, config } = run;
|
|
465
|
+
const { plannedRefs, memorySummary, strategyFilteredRefs, indexSnapshot } = await run.collectEligibleRefsImpl(scope, options.stashDir, improveProfile, config);
|
|
700
466
|
const cleanupParentRef = memoryCleanupParentRef(scope, options.stashDir);
|
|
701
|
-
|
|
702
|
-
// and eligible-memory count and no strategy flag at all. Compute it once: it
|
|
703
|
-
// decides whether each lane would have run, which is also what decides whether
|
|
704
|
-
// a gated lane is worth REPORTING. A lane that was never eligible was not
|
|
705
|
-
// suppressed by the gate, so claiming it was would be noise.
|
|
706
|
-
const cleanupEligible = shouldAnalyzeMemoryCleanup(scope, memorySummary.eligible, primaryStashDir);
|
|
707
|
-
// Cleanup remains independent of the disabled contradiction writer.
|
|
708
|
-
const memoryCleanupPlan = cleanupEligible
|
|
467
|
+
const memoryCleanupPlan = shouldAnalyzeMemoryCleanup(scope, memorySummary.eligible, primaryStashDir)
|
|
709
468
|
? analyzeMemoryCleanup(primaryStashDir, cleanupParentRef ? { parentRef: cleanupParentRef } : undefined)
|
|
710
469
|
: undefined;
|
|
470
|
+
// A lane that would not have run anyway was not suppressed by the gate.
|
|
711
471
|
const cleanupWouldMutate = Boolean(memoryCleanupPlan &&
|
|
712
472
|
(memoryCleanupPlan.pruneCandidates.length > 0 ||
|
|
713
473
|
memoryCleanupPlan.beliefStateTransitions.length > 0 ||
|
|
714
474
|
memoryCleanupPlan.relativeDateCandidates.length > 0));
|
|
715
|
-
const autonomyGatedDirectLanes = cleanupWouldMutate && !isAutonomyLaneAllowed("memoryCleanup", _earlyConfig) ? ["memoryCleanup"] : [];
|
|
716
|
-
const guidance = memorySummary.eligible > 0
|
|
717
|
-
? "Improve folds memory cleanup into the same proposal queue: speculative promotions still go through reflect/distill proposals, while high-confidence redundant derived memories are moved into a recoverable cleanup archive instead of being left active in the stash."
|
|
718
|
-
: undefined;
|
|
719
475
|
return {
|
|
720
476
|
plannedRefs,
|
|
721
477
|
memorySummary,
|
|
722
478
|
strategyFilteredRefs: strategyFilteredRefs ?? [],
|
|
723
479
|
indexSnapshot,
|
|
724
480
|
memoryCleanupPlan,
|
|
725
|
-
autonomyGatedDirectLanes,
|
|
726
|
-
guidance
|
|
727
|
-
|
|
481
|
+
autonomyGatedDirectLanes: cleanupWouldMutate && !isAutonomyLaneAllowed("memoryCleanup", config) ? ["memoryCleanup"] : [],
|
|
482
|
+
guidance: memorySummary.eligible > 0
|
|
483
|
+
? "Improve folds memory cleanup into the same proposal queue: speculative promotions still go through reflect/distill proposals, while high-confidence redundant derived memories are moved into a recoverable cleanup archive instead of being left active in the stash."
|
|
484
|
+
: undefined,
|
|
728
485
|
};
|
|
729
486
|
}
|
|
730
|
-
/** The
|
|
487
|
+
/** The envelope for a run skipped because another run holds the lock. */
|
|
731
488
|
export function buildLockSkippedResult(strategyName, scope, runId) {
|
|
732
489
|
return {
|
|
733
490
|
schemaVersion: 2,
|
|
@@ -742,14 +499,13 @@ export function buildLockSkippedResult(strategyName, scope, runId) {
|
|
|
742
499
|
...(runId !== undefined ? { runId } : {}),
|
|
743
500
|
};
|
|
744
501
|
}
|
|
745
|
-
/**
|
|
746
|
-
async function runDryPlanningStage(
|
|
747
|
-
const { run, collected, resolvedStateDbPath, budgetMs, initialCleanupWarnings, signal } = args;
|
|
502
|
+
/** A dry run: the real selectors on a read-only state.db snapshot, stopping before every writer. */
|
|
503
|
+
async function runDryPlanningStage(run, collected, initialCleanupWarnings) {
|
|
748
504
|
let stateDb;
|
|
749
505
|
let stateSnapshotUnavailable = false;
|
|
750
506
|
try {
|
|
751
507
|
try {
|
|
752
|
-
stateDb = openSqliteReadSnapshot(resolvedStateDbPath);
|
|
508
|
+
stateDb = openSqliteReadSnapshot(run.resolvedStateDbPath);
|
|
753
509
|
stateSnapshotUnavailable = !stateDb;
|
|
754
510
|
}
|
|
755
511
|
catch (error) {
|
|
@@ -757,27 +513,13 @@ async function runDryPlanningStage(args) {
|
|
|
757
513
|
throw error;
|
|
758
514
|
stateSnapshotUnavailable = true;
|
|
759
515
|
}
|
|
760
|
-
const eventsCtx = {
|
|
761
|
-
...(stateDb ? { db: stateDb } : { dbPath: resolvedStateDbPath }),
|
|
762
|
-
readOnly: true,
|
|
763
|
-
...(stateSnapshotUnavailable ? { readOnlySnapshotUnavailable: true } : {}),
|
|
764
|
-
};
|
|
765
516
|
const preparation = await run.runImprovePreparationStageImpl({
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
reindexFn: run.reindexFn,
|
|
773
|
-
startMs: run.startMs,
|
|
774
|
-
budgetMs,
|
|
775
|
-
eventsCtx,
|
|
776
|
-
initialCleanupWarnings,
|
|
777
|
-
improveProfile: run.improveProfile,
|
|
778
|
-
resolvedPlan: run.resolvedPlan,
|
|
779
|
-
strategyName: run.selectedStrategy.name,
|
|
780
|
-
budgetSignal: signal,
|
|
517
|
+
...preparationArgs(run, collected, initialCleanupWarnings),
|
|
518
|
+
eventsCtx: {
|
|
519
|
+
...(stateDb ? { db: stateDb } : { dbPath: run.resolvedStateDbPath }),
|
|
520
|
+
readOnly: true,
|
|
521
|
+
...(stateSnapshotUnavailable ? { readOnlySnapshotUnavailable: true } : {}),
|
|
522
|
+
},
|
|
781
523
|
planOnly: true,
|
|
782
524
|
});
|
|
783
525
|
return buildDryRunResult(run, collected, preparation);
|
|
@@ -786,123 +528,100 @@ async function runDryPlanningStage(args) {
|
|
|
786
528
|
stateDb?.close();
|
|
787
529
|
}
|
|
788
530
|
}
|
|
789
|
-
|
|
531
|
+
function preparationArgs(run, collected, initialCleanupWarnings) {
|
|
532
|
+
return {
|
|
533
|
+
scope: run.scope,
|
|
534
|
+
options: run.options,
|
|
535
|
+
plannedRefs: collected.plannedRefs,
|
|
536
|
+
memoryCleanupPlan: collected.memoryCleanupPlan,
|
|
537
|
+
primaryStashDir: run.primaryStashDir,
|
|
538
|
+
memorySummary: collected.memorySummary,
|
|
539
|
+
reindexFn: run.reindexFn,
|
|
540
|
+
startMs: run.startMs,
|
|
541
|
+
budgetMs: run.budgetMs,
|
|
542
|
+
initialCleanupWarnings,
|
|
543
|
+
improveProfile: run.improveProfile,
|
|
544
|
+
resolvedPlan: run.resolvedPlan,
|
|
545
|
+
strategyName: run.selectedStrategy.name,
|
|
546
|
+
budgetSignal: run.budgetAbortController.signal,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
/** The plan-only envelope of a dry run. */
|
|
790
550
|
export function buildDryRunResult(run, collected, preparation) {
|
|
791
|
-
const { selectedStrategy, scope } = run;
|
|
792
551
|
const { guidance, memorySummary, memoryCleanupPlan, plannedRefs, strategyFilteredRefs } = collected;
|
|
793
|
-
const
|
|
794
|
-
const notices = collectImproveNotices({ resolvedPlan: run.resolvedPlan });
|
|
552
|
+
const notices = collectImproveNotices(run.resolvedPlan, []);
|
|
795
553
|
return {
|
|
796
554
|
schemaVersion: 2,
|
|
797
555
|
ok: true,
|
|
798
|
-
strategy: selectedStrategy.name,
|
|
799
|
-
scope,
|
|
556
|
+
strategy: run.selectedStrategy.name,
|
|
557
|
+
scope: run.scope,
|
|
800
558
|
dryRun: true,
|
|
801
|
-
...
|
|
559
|
+
...notices.fields(),
|
|
802
560
|
...(guidance ? { guidance } : {}),
|
|
803
561
|
memorySummary,
|
|
804
562
|
...(memoryCleanupPlan ? { memoryCleanup: shapeMemoryCleanup(memoryCleanupPlan) } : {}),
|
|
805
|
-
plannedRefs:
|
|
806
|
-
...(preparation?.planning
|
|
807
|
-
? {
|
|
808
|
-
plan: buildResultExecutionPlan(run, preparation, plannedRefs, strategyFilteredRefs, collected.indexSnapshot, true),
|
|
809
|
-
}
|
|
810
|
-
: {}),
|
|
563
|
+
plannedRefs: preparation?.loopRefs ?? plannedRefs,
|
|
564
|
+
...(preparation?.planning ? { plan: buildResultExecutionPlan(run, preparation, collected, true) } : {}),
|
|
811
565
|
...(strategyFilteredRefs.length > 0 ? { strategyFilteredRefs } : {}),
|
|
812
566
|
...(run.resolvedPlan.engineUnavailable.length > 0 ? { skippedProcesses: run.resolvedPlan.engineUnavailable } : {}),
|
|
567
|
+
...(run.options.engineProbe !== undefined ? { engineProbe: run.options.engineProbe } : {}),
|
|
813
568
|
...(preparation?.proactiveMaintenance ? { proactiveMaintenance: preparation.proactiveMaintenance } : {}),
|
|
814
569
|
};
|
|
815
570
|
}
|
|
816
|
-
/**
|
|
817
|
-
function buildResultExecutionPlan(run, preparation,
|
|
818
|
-
const { improveProfile, configuredImproveProfile, resolvedPlan,
|
|
819
|
-
const
|
|
571
|
+
/** The public execution-plan projection shared by the dry and live results. */
|
|
572
|
+
function buildResultExecutionPlan(run, preparation, collected, dryRun) {
|
|
573
|
+
const { improveProfile, configuredImproveProfile, resolvedPlan, scope } = run;
|
|
574
|
+
const { strategyFilteredRefs } = collected;
|
|
820
575
|
const configuredTriage = configuredImproveProfile.processes?.triage;
|
|
821
|
-
const configuredProactive = configuredImproveProfile.processes?.proactiveMaintenance;
|
|
822
|
-
const configuredConsolidation = configuredImproveProfile.processes?.consolidate;
|
|
823
|
-
const triageEnabled = scope.mode !== "ref" && resolvedPlan.processes.triage.enabled;
|
|
824
|
-
const distillOnlySet = new Set(preparation.distillOnlyRefs.map((entry) => entry.ref));
|
|
825
576
|
const inferenceMinPending = improveProfile.processes?.memoryInference?.minPendingCount;
|
|
826
577
|
const pendingMemories = run.primaryStashDir && inferenceMinPending !== undefined && inferenceMinPending > 0
|
|
827
578
|
? collectPendingMemories(run.primaryStashDir).length
|
|
828
579
|
: undefined;
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const profileGate = {
|
|
833
|
-
name: "profile",
|
|
834
|
-
removed: strategyFilteredRefs.length,
|
|
835
|
-
reason: "all enabled per-ref processes refuse the asset type",
|
|
836
|
-
};
|
|
837
|
-
// #947 — per-process resolved engine/model/notices, plus how many of this
|
|
838
|
-
// run's effective refs each ref-scoped process (reflect/distill/consolidate)
|
|
839
|
-
// would act on. Counts only (not a per-ref matrix) to bound result_json size.
|
|
840
|
-
const REF_SCOPED_PROCESSES = new Set(["reflect", "distill", "consolidate"]);
|
|
580
|
+
const belowMinPending = pendingMemories !== undefined && inferenceMinPending !== undefined && pendingMemories < inferenceMinPending;
|
|
581
|
+
const memoryInferenceEnabled = resolvedPlan.processes.memoryInference.enabled && !belowMinPending;
|
|
582
|
+
// Per-process routing, plus how many effective refs each ref-scoped process would act on (#947).
|
|
841
583
|
const processes = projectResolvedProcessRouting(resolvedPlan).map((row) => {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const eligibleRefs = preparation.loopRefs.filter((entry) => !shouldSkipRef(entry.ref, row.process, resolvedPlan.strategy.config)
|
|
845
|
-
.skip).length;
|
|
846
|
-
return { ...row, eligibleRefs };
|
|
584
|
+
const eligibleRefs = eligibleRefCount(preparation.loopRefs, row.process, resolvedPlan.strategy.config);
|
|
585
|
+
return eligibleRefs === undefined ? row : { ...row, eligibleRefs };
|
|
847
586
|
});
|
|
848
587
|
const proactive = preparation.planning.proactive
|
|
849
588
|
? {
|
|
850
589
|
...preparation.planning.proactive,
|
|
851
|
-
configured:
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
590
|
+
configured: pickDefined(configuredImproveProfile.processes?.proactiveMaintenance, [
|
|
591
|
+
"dueDays",
|
|
592
|
+
"maxPerRun",
|
|
593
|
+
"limit",
|
|
594
|
+
]),
|
|
856
595
|
}
|
|
857
596
|
: undefined;
|
|
858
|
-
const consolidation = {
|
|
859
|
-
...preparation.planning.consolidation,
|
|
860
|
-
configured: {
|
|
861
|
-
...(configuredConsolidation?.enabled !== undefined ? { enabled: configuredConsolidation.enabled } : {}),
|
|
862
|
-
...(configuredConsolidation?.minPoolSize !== undefined
|
|
863
|
-
? { minPoolSize: configuredConsolidation.minPoolSize }
|
|
864
|
-
: {}),
|
|
865
|
-
...(configuredConsolidation?.limit !== undefined ? { limit: configuredConsolidation.limit } : {}),
|
|
866
|
-
...(configuredConsolidation?.maxChunkSize !== undefined
|
|
867
|
-
? { maxChunkSize: configuredConsolidation.maxChunkSize }
|
|
868
|
-
: {}),
|
|
869
|
-
...(configuredConsolidation?.incrementalSince !== undefined
|
|
870
|
-
? { incrementalSince: configuredConsolidation.incrementalSince }
|
|
871
|
-
: {}),
|
|
872
|
-
},
|
|
873
|
-
};
|
|
874
597
|
return buildImproveExecutionPlan({
|
|
875
598
|
dryRun,
|
|
876
|
-
snapshot: indexSnapshot ?? {
|
|
599
|
+
snapshot: collected.indexSnapshot ?? {
|
|
877
600
|
status: "unknown",
|
|
878
601
|
reason: "the injected selector did not report an index snapshot status",
|
|
879
602
|
},
|
|
880
|
-
rawInScope:
|
|
603
|
+
rawInScope: collected.plannedRefs.length + strategyFilteredRefs.length,
|
|
881
604
|
selectedRefs: preparation.actionableRefs,
|
|
882
605
|
effectiveRefs: preparation.loopRefs,
|
|
883
|
-
distillOnlyRefs:
|
|
884
|
-
configuredLimits,
|
|
885
|
-
effectiveLimit,
|
|
886
|
-
|
|
887
|
-
|
|
606
|
+
distillOnlyRefs: new Set(preparation.distillOnlyRefs.map((entry) => entry.ref)),
|
|
607
|
+
configuredLimits: run.configuredLimits,
|
|
608
|
+
effectiveLimit: run.effectiveLimit,
|
|
609
|
+
gates: [
|
|
610
|
+
{
|
|
611
|
+
name: "profile",
|
|
612
|
+
removed: strategyFilteredRefs.length,
|
|
613
|
+
reason: "all enabled per-ref processes refuse the asset type",
|
|
614
|
+
},
|
|
615
|
+
...preparation.planning.gates,
|
|
616
|
+
],
|
|
888
617
|
processes,
|
|
889
618
|
...(proactive ? { proactive } : {}),
|
|
890
|
-
consolidation
|
|
619
|
+
consolidation: {
|
|
620
|
+
...preparation.planning.consolidation,
|
|
621
|
+
configured: pickDefined(configuredImproveProfile.processes?.consolidate, CONSOLIDATION_CONFIG_KEYS),
|
|
622
|
+
},
|
|
891
623
|
stageConfig: {
|
|
892
|
-
extract: {
|
|
893
|
-
enabled: preparation.planning.extract.wouldRun,
|
|
894
|
-
reason: preparation.planning.extract.reason,
|
|
895
|
-
},
|
|
896
|
-
graphExtraction: {
|
|
897
|
-
enabled: graphExtractionEnabled,
|
|
898
|
-
reason: !resolvedPlan.processes.graphExtraction.enabled
|
|
899
|
-
? "disabled"
|
|
900
|
-
: graphExtractionEnabled
|
|
901
|
-
? improveProfile.processes?.graphExtraction?.fullScan === true
|
|
902
|
-
? "enabled for a full-corpus scan"
|
|
903
|
-
: "enabled for refs touched by this run"
|
|
904
|
-
: "enabled but no primary source is available",
|
|
905
|
-
},
|
|
624
|
+
extract: { enabled: preparation.planning.extract.wouldRun, reason: preparation.planning.extract.reason },
|
|
906
625
|
memoryInference: {
|
|
907
626
|
enabled: memoryInferenceEnabled,
|
|
908
627
|
reason: !resolvedPlan.processes.memoryInference.enabled
|
|
@@ -913,78 +632,51 @@ function buildResultExecutionPlan(run, preparation, rawProfileEligibleRefs, stra
|
|
|
913
632
|
},
|
|
914
633
|
},
|
|
915
634
|
triage: {
|
|
916
|
-
enabled:
|
|
635
|
+
enabled: scope.mode !== "ref" && resolvedPlan.processes.triage.enabled,
|
|
917
636
|
configuredMode: configuredTriage?.applyMode ?? "queue",
|
|
918
|
-
mode:
|
|
637
|
+
mode: improveProfile.processes?.triage?.applyMode ?? "queue",
|
|
919
638
|
maxAcceptsPerRun: configuredTriage?.maxAcceptsPerRun ?? 25,
|
|
920
|
-
...(configuredTriage?.maxDiffLines !== undefined ? { maxDiffLines: configuredTriage.maxDiffLines } : {}),
|
|
921
639
|
},
|
|
922
640
|
});
|
|
923
641
|
}
|
|
924
|
-
/**
|
|
642
|
+
/** Drain the proposal backlog before generating more (non-fatal; a single-ref scope never drains). */
|
|
925
643
|
async function runTriagePrePass(run) {
|
|
926
|
-
const { primaryStashDir, resolvedPlan, scope, options, improveProfile
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
catch (err) {
|
|
952
|
-
warn(`[improve] triage pre-pass failed (non-fatal): ${errMessage(err)}`);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
644
|
+
const { primaryStashDir, resolvedPlan, scope, options, improveProfile } = run;
|
|
645
|
+
if (!primaryStashDir || !resolvedPlan.processes.triage.enabled)
|
|
646
|
+
return undefined;
|
|
647
|
+
if (scope.mode === "ref") {
|
|
648
|
+
warn("[improve] triage pre-pass skipped (single-ref scope never drains the whole queue)");
|
|
649
|
+
return undefined;
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
const triageConfig = improveProfile.processes?.triage;
|
|
653
|
+
return await withLlmStage("triage", () => run.drainProposalsFn({
|
|
654
|
+
stashDir: primaryStashDir,
|
|
655
|
+
...(options.target ? { target: options.target } : {}),
|
|
656
|
+
config: options.config,
|
|
657
|
+
applyMode: triageConfig?.applyMode ?? "queue",
|
|
658
|
+
maxAccepts: triageConfig?.maxAcceptsPerRun ?? 25,
|
|
659
|
+
dryRun: false,
|
|
660
|
+
excludeIds: new Set(),
|
|
661
|
+
judgment: resolvedPlan.triageJudgment,
|
|
662
|
+
}), { engine: resolvedPlan.triageJudgment?.engine, process: "triage.judgment" });
|
|
663
|
+
}
|
|
664
|
+
catch (err) {
|
|
665
|
+
warn(`[improve] triage pre-pass failed (non-fatal): ${errMessage(err)}`);
|
|
666
|
+
return undefined;
|
|
955
667
|
}
|
|
956
|
-
return triageDrain;
|
|
957
|
-
}
|
|
958
|
-
/** A path is never staged by auto-sync when it is (or looks like) a lock file. */
|
|
959
|
-
function isSyncExcludedPath(relativePath) {
|
|
960
|
-
return path.basename(relativePath).includes(".lock");
|
|
961
668
|
}
|
|
962
669
|
/**
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
* Git currently reports as changed. The intersection is what makes the set
|
|
970
|
-
* correct rather than merely plausible: it drops a journaled path whose
|
|
971
|
-
* final bytes match HEAD (nothing to commit), a journaled path that was
|
|
972
|
-
* created and then removed again (never existed for Git), and any journaled
|
|
973
|
-
* path Git ignores (which `saveGitStash` would otherwise reject outright).
|
|
974
|
-
* A journaled path that was ALREADY dirty when the run started stays IN —
|
|
975
|
-
* this run rewrote it, so this run owns it.
|
|
976
|
-
* 2. **Dirty-path diff** (pre-#652 behaviour) — only when no journal was open,
|
|
977
|
-
* which a live run never hits. Kept as the defensive fallback so a future
|
|
978
|
-
* caller that commits outside the journal window degrades to the previous,
|
|
979
|
-
* battle-tested heuristic instead of silently committing nothing.
|
|
980
|
-
*
|
|
981
|
-
* `unattributed` counts in-scope paths that became dirty DURING the run without
|
|
982
|
-
* this run writing them: concurrent human edits (correctly excluded) and, if the
|
|
983
|
-
* number is ever surprising, the fingerprint of a missing journal call site.
|
|
670
|
+
* The exact repo-relative paths the auto-sync commit stages (#652): the paths
|
|
671
|
+
* this run wrote that Git reports changed (a journaled path already dirty at
|
|
672
|
+
* start stays in — this run rewrote it). Without a journal, the pre-#652
|
|
673
|
+
* dirty-path diff. `unattributed` counts in-scope paths that went dirty during
|
|
674
|
+
* the run without this run writing them (concurrent edits, left alone). Lock
|
|
675
|
+
* files are never staged.
|
|
984
676
|
*/
|
|
985
677
|
export function resolveSyncPathSet(input) {
|
|
986
678
|
const { repoDir, assetPrefix, changedPaths, initialPaths, writtenPaths, provenance } = input;
|
|
987
|
-
const inScope = (relativePath) => !
|
|
679
|
+
const inScope = (relativePath) => !path.basename(relativePath).includes(".lock") &&
|
|
988
680
|
(!assetPrefix || relativePath === assetPrefix || relativePath.startsWith(`${assetPrefix}/`));
|
|
989
681
|
if (!provenance) {
|
|
990
682
|
return { paths: changedPaths.filter((p) => !initialPaths.has(p) && inScope(p)), unattributed: [] };
|
|
@@ -993,9 +685,8 @@ export function resolveSyncPathSet(input) {
|
|
|
993
685
|
const attributed = new Set();
|
|
994
686
|
for (const absolutePath of writtenPaths) {
|
|
995
687
|
const relativePath = relativeWrittenPath(repoDir, absolutePath);
|
|
996
|
-
if (
|
|
997
|
-
|
|
998
|
-
attributed.add(relativePath);
|
|
688
|
+
if (relativePath && changed.has(relativePath) && inScope(relativePath))
|
|
689
|
+
attributed.add(relativePath);
|
|
999
690
|
}
|
|
1000
691
|
return {
|
|
1001
692
|
paths: [...attributed].sort(),
|
|
@@ -1003,60 +694,46 @@ export function resolveSyncPathSet(input) {
|
|
|
1003
694
|
};
|
|
1004
695
|
}
|
|
1005
696
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
* path. Idempotent + NON-FATAL. `getEventsCtx`/`getInitialGitPaths`/
|
|
1010
|
-
* `getWriteJournal` are getters because those outer bindings are (re)assigned
|
|
1011
|
-
* after this factory runs — the returned closure must observe the live values.
|
|
697
|
+
* The auto-sync commit (#662), used at end of run and from the crash path:
|
|
698
|
+
* idempotent and never throws. The getters read bindings reassigned after this
|
|
699
|
+
* factory runs.
|
|
1012
700
|
*/
|
|
1013
701
|
function makeCommitStashBatch(deps) {
|
|
1014
|
-
const { writeTarget, primaryStashDir, effectiveSync, options,
|
|
702
|
+
const { writeTarget, primaryStashDir, effectiveSync, options, config, improveProfile } = deps.run;
|
|
1015
703
|
return (messageContext) => {
|
|
1016
704
|
const eventsCtx = deps.getEventsCtx();
|
|
1017
|
-
const initialGitPaths = deps.getInitialGitPaths();
|
|
1018
705
|
const writeJournal = deps.getWriteJournal();
|
|
1019
706
|
const repoDir = writeTarget?.source.repoPath ?? primaryStashDir;
|
|
1020
707
|
if (!primaryStashDir || !repoDir || effectiveSync.enabled === false || !isGitBackedStash(repoDir)) {
|
|
1021
708
|
return undefined;
|
|
1022
709
|
}
|
|
1023
710
|
const saveGitStashFn = options.saveGitStashFn ?? saveGitStash;
|
|
1024
|
-
const writableOverride = writeTarget ? resolveWritable(writeTarget.config) : resolveWritableOverride(
|
|
711
|
+
const writableOverride = writeTarget ? resolveWritable(writeTarget.config) : resolveWritableOverride(config);
|
|
1025
712
|
const push = options.sync?.push ?? improveProfile.sync?.push ?? true;
|
|
1026
713
|
const message = renderSyncCommitMessage(effectiveSync.message ?? "akm improve auto-sync", messageContext, Date.now());
|
|
714
|
+
const record = (metadata) => appendEvent({ eventType: "stash_synced", metadata }, eventsCtx);
|
|
1027
715
|
try {
|
|
1028
716
|
const assetRoot = writeTarget?.source.path ?? primaryStashDir;
|
|
1029
|
-
const assetPrefix = assetRoot ? path.relative(repoDir, assetRoot).replaceAll(path.sep, "/") : "";
|
|
1030
717
|
const { paths, unattributed } = resolveSyncPathSet({
|
|
1031
718
|
repoDir,
|
|
1032
|
-
assetPrefix,
|
|
719
|
+
assetPrefix: path.relative(repoDir, assetRoot).replaceAll(path.sep, "/"),
|
|
1033
720
|
changedPaths: listGitChangedPaths(repoDir),
|
|
1034
|
-
initialPaths:
|
|
721
|
+
initialPaths: deps.getInitialGitPaths(),
|
|
1035
722
|
writtenPaths: writeJournal?.writtenPaths() ?? [],
|
|
1036
723
|
provenance: writeJournal !== undefined,
|
|
1037
724
|
});
|
|
1038
725
|
if (unattributed.length > 0) {
|
|
1039
726
|
warnVerbose(`[improve] auto-sync left ${unattributed.length} path(s) uncommitted — not written by this run: ${unattributed.join(", ")}`);
|
|
1040
727
|
}
|
|
1041
|
-
const syncResult = saveGitStashFn(undefined, message, writableOverride, {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
728
|
+
const syncResult = saveGitStashFn(undefined, message, writableOverride, { push, repoDir, paths });
|
|
729
|
+
record({
|
|
730
|
+
committed: syncResult.committed,
|
|
731
|
+
pushed: syncResult.pushed,
|
|
732
|
+
skipped: syncResult.skipped,
|
|
733
|
+
reason: syncResult.reason ?? null,
|
|
734
|
+
attributed: paths.length,
|
|
735
|
+
unattributed: unattributed.length,
|
|
1045
736
|
});
|
|
1046
|
-
appendEvent({
|
|
1047
|
-
eventType: "stash_synced",
|
|
1048
|
-
metadata: {
|
|
1049
|
-
committed: syncResult.committed,
|
|
1050
|
-
pushed: syncResult.pushed,
|
|
1051
|
-
skipped: syncResult.skipped,
|
|
1052
|
-
reason: syncResult.reason ?? null,
|
|
1053
|
-
// #652 provenance audit trail: how many paths this run staged, and
|
|
1054
|
-
// how many in-scope paths went dirty during the run that it did NOT
|
|
1055
|
-
// write (concurrent edits — deliberately left for their author).
|
|
1056
|
-
attributed: paths.length,
|
|
1057
|
-
unattributed: unattributed.length,
|
|
1058
|
-
},
|
|
1059
|
-
}, eventsCtx);
|
|
1060
737
|
return {
|
|
1061
738
|
committed: syncResult.committed,
|
|
1062
739
|
pushed: syncResult.pushed,
|
|
@@ -1067,298 +744,131 @@ function makeCommitStashBatch(deps) {
|
|
|
1067
744
|
catch (syncErr) {
|
|
1068
745
|
const reason = errMessage(syncErr);
|
|
1069
746
|
warn(`improve: stash sync failed (non-fatal): ${reason}`);
|
|
1070
|
-
|
|
1071
|
-
eventType: "stash_synced",
|
|
1072
|
-
metadata: { committed: false, pushed: false, skipped: true, reason },
|
|
1073
|
-
}, eventsCtx);
|
|
747
|
+
record({ committed: false, pushed: false, skipped: true, reason });
|
|
1074
748
|
return { committed: false, pushed: false, skipped: true, reason };
|
|
1075
749
|
}
|
|
1076
750
|
};
|
|
1077
751
|
}
|
|
1078
|
-
/** D6: pre-load the last 30 days of proposal_rejected events once per run. */
|
|
1079
|
-
function preloadRejectedProposals() {
|
|
1080
|
-
// D6: pre-load all proposal_rejected events from the last 30 days once,
|
|
1081
|
-
// so the per-asset loop can use a Map lookup instead of N DB round trips.
|
|
1082
|
-
const REJECTED_PROPOSAL_WINDOW_MS = daysToMs(30);
|
|
1083
|
-
const rejectedProposalSince = new Date(Date.now() - REJECTED_PROPOSAL_WINDOW_MS).toISOString();
|
|
1084
|
-
const allRejectedProposalEvents = readEvents({ type: "proposal_rejected", since: rejectedProposalSince }).events;
|
|
1085
|
-
const rejectedProposalsByRef = new Map();
|
|
1086
|
-
for (const e of allRejectedProposalEvents) {
|
|
1087
|
-
if (e.ref && (!rejectedProposalsByRef.has(e.ref) || e.ts > (rejectedProposalsByRef.get(e.ref)?.ts ?? ""))) {
|
|
1088
|
-
rejectedProposalsByRef.set(e.ref, e);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
return rejectedProposalsByRef;
|
|
1092
|
-
}
|
|
1093
752
|
/**
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
* before this run acquired its lock are visible.
|
|
753
|
+
* Re-read the improve ledger under the lock and drop proactive refs another run
|
|
754
|
+
* attempted after this one planned.
|
|
1097
755
|
*/
|
|
1098
|
-
export function refilterProactiveLoopRefs(loopRefs, improveProfile) {
|
|
1099
|
-
// Re-read cooldown timestamps immediately before execution so external
|
|
1100
|
-
// proposal writes that occurred before this run acquired its lock are visible.
|
|
756
|
+
export function refilterProactiveLoopRefs(loopRefs, improveProfile, ledgerAccess) {
|
|
1101
757
|
const proactiveLoopRefs = loopRefs.filter((r) => r.eligibilitySource === "proactive");
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
postLockLoopRefs = loopRefs.filter((r) => r.eligibilitySource !== "proactive" || stillDue.has(r.ref));
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
return postLockLoopRefs;
|
|
758
|
+
if (proactiveLoopRefs.length === 0 || !ledgerAccess.stashDir)
|
|
759
|
+
return loopRefs;
|
|
760
|
+
const ledger = loadLedgerSnapshot({ eventsCtx: ledgerAccess.eventsCtx }, ledgerAccess.stashDir, [
|
|
761
|
+
"reflect",
|
|
762
|
+
"distill",
|
|
763
|
+
]);
|
|
764
|
+
const stillDue = new Set(filterProactiveDue(proactiveLoopRefs, lastAttemptByRef(ledger, "reflect", proactiveLoopRefs), lastAttemptByRef(ledger, "distill", proactiveLoopRefs), improveProfile.processes?.proactiveMaintenance?.dueDays ?? DEFAULT_DUE_DAYS, Date.now()).map((r) => r.ref));
|
|
765
|
+
const dropped = proactiveLoopRefs.filter((r) => !stillDue.has(r.ref));
|
|
766
|
+
if (dropped.length === 0)
|
|
767
|
+
return loopRefs;
|
|
768
|
+
info(`[improve] post-lock cooldown re-filter: dropped ${dropped.length} proactive ref(s) claimed by concurrent run (${dropped.map((r) => r.ref).join(", ")})`);
|
|
769
|
+
return loopRefs.filter((r) => r.eligibilitySource !== "proactive" || stillDue.has(r.ref));
|
|
1118
770
|
}
|
|
1119
771
|
/**
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
* `runImproveStageSequence` — fn-size decomposition, no logic change).
|
|
772
|
+
* The audit events for refs and lanes this run will not touch, then
|
|
773
|
+
* preparation → loop → post-loop. No post-loop work starts past the budget; the
|
|
774
|
+
* result still finalizes, so budget exhaustion exits 0.
|
|
1124
775
|
*/
|
|
1125
|
-
async function
|
|
1126
|
-
const {
|
|
1127
|
-
|
|
1128
|
-
//
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if (budgetAbortController.signal.aborted || (remainingBudget !== undefined && remainingBudget <= 0)) {
|
|
1136
|
-
info("[improve] post-loop maintenance skipped (wall-clock budget exhausted)");
|
|
1137
|
-
return emptyPostLoopResult;
|
|
776
|
+
async function runImproveStageSequence(run, collected, preEnsureCleanupWarnings, eventsCtx) {
|
|
777
|
+
const { scope, options, primaryStashDir, improveProfile, resolvedPlan, budgetAbortController } = run;
|
|
778
|
+
const strategy = run.selectedStrategy.name;
|
|
779
|
+
// One count-only row for planner-filtered refs, never one per ref (#592).
|
|
780
|
+
if (collected.strategyFilteredRefs.length > 0) {
|
|
781
|
+
recordImproveSkip(eventsCtx, undefined, {
|
|
782
|
+
strategy,
|
|
783
|
+
reason: "strategy_filtered_all_passes",
|
|
784
|
+
count: collected.strategyFilteredRefs.length,
|
|
785
|
+
});
|
|
1138
786
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
787
|
+
// A gated lane names the config key that would enable it.
|
|
788
|
+
for (const lane of [...resolvedPlan.autonomyGated, ...describeGatedLanes(collected.autonomyGatedDirectLanes)]) {
|
|
789
|
+
warn(`[improve] ${lane.lane} skipped — it ${lane.reason}. Set \`${lane.configKey}: true\` to enable it.`);
|
|
790
|
+
recordImproveSkip(eventsCtx, undefined, {
|
|
791
|
+
strategy,
|
|
792
|
+
reason: "autonomy_gated",
|
|
793
|
+
lane: lane.lane,
|
|
794
|
+
configKey: lane.configKey,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
for (const item of resolvedPlan.engineUnavailable) {
|
|
798
|
+
warn(`[improve] ${item.process} skipped — it ${item.reason}.`);
|
|
799
|
+
recordImproveSkip(eventsCtx, undefined, {
|
|
800
|
+
strategy,
|
|
801
|
+
reason: "engine_unavailable",
|
|
802
|
+
process: item.process,
|
|
803
|
+
configKey: item.configKey,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
const preparation = await run.runImprovePreparationStageImpl({
|
|
807
|
+
...preparationArgs(run, collected, preEnsureCleanupWarnings),
|
|
808
|
+
eventsCtx,
|
|
809
|
+
});
|
|
810
|
+
const loopResult = await run.runImproveLoopStageImpl({
|
|
1148
811
|
eventsCtx,
|
|
1149
812
|
budgetSignal: budgetAbortController.signal,
|
|
813
|
+
primaryStashDir,
|
|
814
|
+
scope,
|
|
815
|
+
options,
|
|
816
|
+
reflectFn: run.reflectFn,
|
|
817
|
+
distillFn: run.distillFn,
|
|
818
|
+
loopRefs: refilterProactiveLoopRefs(preparation.loopRefs, improveProfile, {
|
|
819
|
+
stashDir: primaryStashDir ?? options.stashDir,
|
|
820
|
+
eventsCtx,
|
|
821
|
+
}),
|
|
822
|
+
actions: preparation.actions,
|
|
823
|
+
signalBearingSet: preparation.signalBearingSet,
|
|
824
|
+
distillCooledRefs: preparation.distillCooledRefs,
|
|
825
|
+
distillOnlyRefs: preparation.distillOnlyRefs,
|
|
826
|
+
recentErrors: preparation.recentErrors,
|
|
827
|
+
startMs: run.startMs,
|
|
828
|
+
budgetMs: run.budgetMs,
|
|
1150
829
|
improveProfile,
|
|
1151
830
|
resolvedPlan,
|
|
1152
|
-
consolidationRan: preparation.consolidationRan,
|
|
1153
|
-
// R5: floor violations from this run's consolidate pass, for the collapse
|
|
1154
|
-
// detector's merge-floor advisory.
|
|
1155
|
-
consolidationMergeFloorViolations: preparation.consolidation.mergeFloorViolations ?? 0,
|
|
1156
831
|
});
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
* accumulator the result assembly reads — the old closure-scoped `let`s.
|
|
1162
|
-
*/
|
|
1163
|
-
async function runImproveStageSequence(args) {
|
|
1164
|
-
const { strategyFilteredRefs, plannedRefs, memoryCleanupPlan, memorySummary, preEnsureCleanupWarnings, eventsCtx, ctx, } = args;
|
|
1165
|
-
const { scope, options, primaryStashDir, startMs, budgetMs, improveProfile, resolvedPlan, budgetAbortController, selectedStrategy, reflectFn, distillFn, reindexFn, runImprovePreparationStageImpl, runImproveLoopStageImpl, runImprovePostLoopStageImpl, } = args.run;
|
|
1166
|
-
// 2026-05-27: one summary `improve_skipped` audit event (count only) for
|
|
1167
|
-
// planner-pre-filtered refs — never per-ref (#592: O(n) sequential state.db
|
|
1168
|
-
// writes cost ~500 s on a 9 000-ref stash; health reads the counters).
|
|
1169
|
-
if (strategyFilteredRefs.length > 0) {
|
|
1170
|
-
appendEvent({
|
|
1171
|
-
eventType: "improve_skipped",
|
|
1172
|
-
ref: undefined,
|
|
1173
|
-
metadata: {
|
|
1174
|
-
strategy: selectedStrategy.name,
|
|
1175
|
-
reason: "strategy_filtered_all_passes",
|
|
1176
|
-
count: strategyFilteredRefs.length,
|
|
1177
|
-
},
|
|
1178
|
-
}, eventsCtx);
|
|
1179
|
-
}
|
|
1180
|
-
// D8 — one event per lane the autonomy gate downgraded, naming the lane AND
|
|
1181
|
-
// the config key that would enable it. This is the difference between a gate
|
|
1182
|
-
// and a silent no-op: whatever the user would have seen happen, they now see
|
|
1183
|
-
// explained. `health` already aggregates `improve_skipped` by reason
|
|
1184
|
-
// (buildImproveSkipSummary), so these surface there without new machinery.
|
|
1185
|
-
for (const lane of [...args.run.resolvedPlan.autonomyGated, ...describeGatedLanes(args.autonomyGatedDirectLanes)]) {
|
|
1186
|
-
warn(`[improve] ${lane.lane} skipped — it ${lane.reason}. Set \`${lane.configKey}: true\` to enable it.`);
|
|
1187
|
-
appendEvent({
|
|
1188
|
-
eventType: "improve_skipped",
|
|
1189
|
-
ref: undefined,
|
|
1190
|
-
metadata: {
|
|
1191
|
-
strategy: selectedStrategy.name,
|
|
1192
|
-
reason: "autonomy_gated",
|
|
1193
|
-
lane: lane.lane,
|
|
1194
|
-
configKey: lane.configKey,
|
|
1195
|
-
},
|
|
1196
|
-
}, eventsCtx);
|
|
1197
|
-
}
|
|
1198
|
-
for (const item of resolvedPlan.engineUnavailable) {
|
|
1199
|
-
warn(`[improve] ${item.process} skipped — it ${item.reason}.`);
|
|
1200
|
-
appendEvent({
|
|
1201
|
-
eventType: "improve_skipped",
|
|
1202
|
-
ref: undefined,
|
|
1203
|
-
metadata: {
|
|
1204
|
-
strategy: selectedStrategy.name,
|
|
1205
|
-
reason: "engine_unavailable",
|
|
1206
|
-
process: item.process,
|
|
1207
|
-
configKey: item.configKey,
|
|
1208
|
-
},
|
|
1209
|
-
}, eventsCtx);
|
|
832
|
+
let postLoop = { allWarnings: [], memoryInferenceDurationMs: 0 };
|
|
833
|
+
const remainingBudget = budgetAbortController.signal.remainingBudgetMs;
|
|
834
|
+
if (budgetAbortController.signal.aborted || (remainingBudget !== undefined && remainingBudget <= 0)) {
|
|
835
|
+
info("[improve] post-loop maintenance skipped (wall-clock budget exhausted)");
|
|
1210
836
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
let preparation;
|
|
1214
|
-
let memoryRefsForInference = new Set();
|
|
1215
|
-
let consolidation;
|
|
1216
|
-
let memoryInference;
|
|
1217
|
-
let graphExtraction;
|
|
1218
|
-
let cycleMetrics;
|
|
1219
|
-
// Summed counters/durations.
|
|
1220
|
-
let reflectsWithErrorContext = 0;
|
|
1221
|
-
let memoryInferenceDurationMs = 0;
|
|
1222
|
-
let graphExtractionDurationMs = 0;
|
|
1223
|
-
let orphansPurged;
|
|
1224
|
-
let proposalsExpired;
|
|
1225
|
-
// Concatenated arrays.
|
|
1226
|
-
const allWarnings = [];
|
|
1227
|
-
let deadUrls;
|
|
1228
|
-
let deadUrlCoverage;
|
|
1229
|
-
const finalActions = [];
|
|
1230
|
-
{
|
|
1231
|
-
const runPreparation = () => runImprovePreparationStageImpl({
|
|
837
|
+
else {
|
|
838
|
+
postLoop = await run.runImprovePostLoopStageImpl({
|
|
1232
839
|
scope,
|
|
1233
840
|
options,
|
|
1234
|
-
plannedRefs,
|
|
1235
|
-
memoryCleanupPlan,
|
|
1236
841
|
primaryStashDir,
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
842
|
+
actionableRefs: preparation.actionableRefs,
|
|
843
|
+
appliedCleanup: preparation.appliedCleanup,
|
|
844
|
+
cleanupWarnings: preparation.cleanupWarnings,
|
|
845
|
+
memoryRefsForInference: loopResult.memoryRefsForInference,
|
|
1241
846
|
eventsCtx,
|
|
1242
|
-
initialCleanupWarnings: preEnsureCleanupWarnings,
|
|
1243
|
-
improveProfile,
|
|
1244
|
-
resolvedPlan,
|
|
1245
|
-
strategyName: selectedStrategy.name,
|
|
1246
847
|
budgetSignal: budgetAbortController.signal,
|
|
1247
|
-
});
|
|
1248
|
-
preparation = await runPreparation();
|
|
1249
|
-
const rejectedProposalsByRef = preloadRejectedProposals();
|
|
1250
|
-
const runLoop = () => {
|
|
1251
|
-
const postLockLoopRefs = refilterProactiveLoopRefs(preparation.loopRefs, improveProfile);
|
|
1252
|
-
return runImproveLoopStageImpl({
|
|
1253
|
-
ctx,
|
|
1254
|
-
primaryStashDir,
|
|
1255
|
-
scope,
|
|
1256
|
-
options,
|
|
1257
|
-
reflectFn,
|
|
1258
|
-
distillFn,
|
|
1259
|
-
loopRefs: postLockLoopRefs,
|
|
1260
|
-
actions: preparation.actions,
|
|
1261
|
-
signalBearingSet: preparation.signalBearingSet,
|
|
1262
|
-
distillCooledRefs: preparation.distillCooledRefs,
|
|
1263
|
-
distillOnlyRefs: preparation.distillOnlyRefs,
|
|
1264
|
-
recentErrors: preparation.recentErrors,
|
|
1265
|
-
rejectedProposalsByRef,
|
|
1266
|
-
utilityMap: preparation.utilityMap,
|
|
1267
|
-
startMs,
|
|
1268
|
-
budgetMs,
|
|
1269
|
-
improveProfile,
|
|
1270
|
-
resolvedPlan,
|
|
1271
|
-
});
|
|
1272
|
-
};
|
|
1273
|
-
const loopResult = await runLoop();
|
|
1274
|
-
reflectsWithErrorContext += loopResult.reflectsWithErrorContext;
|
|
1275
|
-
memoryRefsForInference = loopResult.memoryRefsForInference;
|
|
1276
|
-
// #551: consolidation now runs in the preparation stage (before extract);
|
|
1277
|
-
// its result and run-flag are read from `preparation`, not the post-loop.
|
|
1278
|
-
consolidation = preparation.consolidation;
|
|
1279
|
-
const postLoopResult = await runPostLoopStageOrSkip({
|
|
1280
|
-
budgetAbortController,
|
|
1281
|
-
scope,
|
|
1282
|
-
options,
|
|
1283
|
-
primaryStashDir,
|
|
1284
|
-
preparation,
|
|
1285
|
-
memoryRefsForInference,
|
|
1286
|
-
reindexFn,
|
|
1287
|
-
eventsCtx,
|
|
1288
848
|
improveProfile,
|
|
1289
849
|
resolvedPlan,
|
|
1290
|
-
runImprovePostLoopStageImpl,
|
|
1291
850
|
});
|
|
1292
|
-
// Result objects (single pass — no cycle accumulation).
|
|
1293
|
-
memoryInference = postLoopResult.memoryInference;
|
|
1294
|
-
graphExtraction = postLoopResult.graphExtraction;
|
|
1295
|
-
if (postLoopResult.cycleMetrics)
|
|
1296
|
-
cycleMetrics = postLoopResult.cycleMetrics;
|
|
1297
|
-
// Summed counters/durations.
|
|
1298
|
-
memoryInferenceDurationMs += postLoopResult.memoryInferenceDurationMs;
|
|
1299
|
-
graphExtractionDurationMs += postLoopResult.graphExtractionDurationMs;
|
|
1300
|
-
if (postLoopResult.orphansPurged !== undefined) {
|
|
1301
|
-
orphansPurged = (orphansPurged ?? 0) + postLoopResult.orphansPurged;
|
|
1302
|
-
}
|
|
1303
|
-
if (postLoopResult.proposalsExpired !== undefined) {
|
|
1304
|
-
proposalsExpired = (proposalsExpired ?? 0) + postLoopResult.proposalsExpired;
|
|
1305
|
-
}
|
|
1306
|
-
// Concatenated arrays.
|
|
1307
|
-
allWarnings.push(...postLoopResult.allWarnings);
|
|
1308
|
-
if (postLoopResult.deadUrls !== undefined) {
|
|
1309
|
-
deadUrls = [...(deadUrls ?? []), ...postLoopResult.deadUrls];
|
|
1310
|
-
}
|
|
1311
|
-
if (postLoopResult.deadUrlCoverage !== undefined) {
|
|
1312
|
-
deadUrlCoverage = postLoopResult.deadUrlCoverage;
|
|
1313
|
-
}
|
|
1314
|
-
const maintenanceActions = postLoopResult.maintenanceActions;
|
|
1315
|
-
if (maintenanceActions && maintenanceActions.length > 0) {
|
|
1316
|
-
finalActions.push(...preparation.actions, ...maintenanceActions);
|
|
1317
|
-
}
|
|
1318
|
-
else {
|
|
1319
|
-
finalActions.push(...preparation.actions);
|
|
1320
|
-
}
|
|
1321
851
|
}
|
|
1322
852
|
return {
|
|
1323
853
|
preparation,
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
graphExtraction,
|
|
1328
|
-
cycleMetrics,
|
|
1329
|
-
reflectsWithErrorContext,
|
|
1330
|
-
memoryInferenceDurationMs,
|
|
1331
|
-
graphExtractionDurationMs,
|
|
1332
|
-
orphansPurged,
|
|
1333
|
-
proposalsExpired,
|
|
1334
|
-
allWarnings,
|
|
1335
|
-
deadUrls,
|
|
1336
|
-
deadUrlCoverage,
|
|
1337
|
-
finalActions,
|
|
854
|
+
postLoop,
|
|
855
|
+
reflectsWithErrorContext: loopResult.reflectsWithErrorContext,
|
|
856
|
+
finalActions: [...preparation.actions, ...(postLoop.maintenanceActions ?? [])],
|
|
1338
857
|
};
|
|
1339
858
|
}
|
|
1340
|
-
/**
|
|
1341
|
-
* Run-teardown, result half: assemble the AkmImproveResult envelope and emit
|
|
1342
|
-
* the improve_completed event (same adjacency as the old inline code — no
|
|
1343
|
-
* side effects run between assembly and emit).
|
|
1344
|
-
*/
|
|
859
|
+
/** Assemble the result envelope and emit `improve_completed`. */
|
|
1345
860
|
function finalizeImproveResult(args) {
|
|
1346
|
-
const {
|
|
1347
|
-
const {
|
|
1348
|
-
const {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
//
|
|
861
|
+
const { run, collected, triageDrain, ensureIndexDurationMs, eventsCtx } = args;
|
|
862
|
+
const { preparation, postLoop, reflectsWithErrorContext, finalActions } = args.seq;
|
|
863
|
+
const { options, startMs, resolvedPlan } = run;
|
|
864
|
+
const { memoryCleanupPlan, strategyFilteredRefs } = collected;
|
|
865
|
+
const consolidation = preparation.consolidation;
|
|
866
|
+
const { memoryInference, allWarnings, deadUrls, deadUrlCoverage, orphansPurged, proposalsExpired } = postLoop;
|
|
867
|
+
const { memoryInferenceDurationMs } = postLoop;
|
|
868
|
+
// The per-ref distill-skipped rows fold into a bounded aggregate before persistence (C1).
|
|
1354
869
|
const { actions: persistedActions, aggregate: distillSkippedAggregate } = foldDistillSkipped(finalActions);
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
1357
|
-
// events carry no runId column, so bound the read to this run's own wall
|
|
1358
|
-
// clock — the same per-run event-scoping technique `health/windows.ts`
|
|
1359
|
-
// already uses for wall time. `until` is "now" (assembly happens at
|
|
1360
|
-
// teardown, after every LLM call this run will make has already emitted
|
|
1361
|
-
// its event).
|
|
870
|
+
// This run's LLM accounting (#944): llm_usage rows carry no run id, so the
|
|
871
|
+
// read is bounded by the run's own wall clock.
|
|
1362
872
|
const usageEvents = readEvents({ since: new Date(startMs).toISOString(), type: LLM_USAGE_EVENT }, eventsCtx).events;
|
|
1363
873
|
const usageReport = buildImproveUsageReport({
|
|
1364
874
|
resolvedPlan,
|
|
@@ -1368,37 +878,35 @@ function finalizeImproveResult(args) {
|
|
|
1368
878
|
persistedActions,
|
|
1369
879
|
distillSkippedAggregate,
|
|
1370
880
|
});
|
|
1371
|
-
const notices = collectImproveNotices(
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
schemaRepairs: preparation.schemaRepairs,
|
|
881
|
+
const notices = collectImproveNotices(resolvedPlan, [
|
|
882
|
+
...finalActions.map((action) => action.result),
|
|
883
|
+
...preparation.schemaRepairs,
|
|
1375
884
|
consolidation,
|
|
1376
|
-
extract
|
|
885
|
+
...(preparation.extract ?? []).flatMap((extract) => [extract, ...(extract.sessions ?? [])]),
|
|
1377
886
|
memoryInference,
|
|
1378
|
-
graphExtraction,
|
|
1379
887
|
triageDrain,
|
|
1380
|
-
|
|
888
|
+
]);
|
|
889
|
+
const applied = preparation.appliedCleanup;
|
|
890
|
+
const countMode = (mode) => finalActions.filter((a) => a.mode === mode).length;
|
|
1381
891
|
const result = {
|
|
1382
892
|
schemaVersion: 2,
|
|
1383
893
|
ok: true,
|
|
1384
|
-
strategy: selectedStrategy.name,
|
|
1385
|
-
scope,
|
|
894
|
+
strategy: run.selectedStrategy.name,
|
|
895
|
+
scope: run.scope,
|
|
1386
896
|
dryRun: false,
|
|
1387
|
-
...
|
|
1388
|
-
...(guidance ? { guidance } : {}),
|
|
1389
|
-
memorySummary,
|
|
897
|
+
...notices.fields(),
|
|
898
|
+
...(collected.guidance ? { guidance: collected.guidance } : {}),
|
|
899
|
+
memorySummary: collected.memorySummary,
|
|
1390
900
|
...(memoryCleanupPlan
|
|
1391
901
|
? {
|
|
1392
902
|
memoryCleanup: {
|
|
1393
903
|
...shapeMemoryCleanup(memoryCleanupPlan),
|
|
1394
|
-
...(
|
|
904
|
+
...(applied
|
|
1395
905
|
? {
|
|
1396
|
-
archived:
|
|
1397
|
-
...(
|
|
1398
|
-
|
|
1399
|
-
:
|
|
1400
|
-
...(preparation.appliedCleanup.transitionLogEntries !== undefined
|
|
1401
|
-
? { transitionLogEntries: preparation.appliedCleanup.transitionLogEntries }
|
|
906
|
+
archived: applied.archived,
|
|
907
|
+
...(applied.transitionLogPath ? { transitionLogPath: applied.transitionLogPath } : {}),
|
|
908
|
+
...(applied.transitionLogEntries !== undefined
|
|
909
|
+
? { transitionLogEntries: applied.transitionLogEntries }
|
|
1402
910
|
: {}),
|
|
1403
911
|
...(allWarnings.length > 0 ? { warnings: allWarnings } : {}),
|
|
1404
912
|
}
|
|
@@ -1409,13 +917,10 @@ function finalizeImproveResult(args) {
|
|
|
1409
917
|
}
|
|
1410
918
|
: {}),
|
|
1411
919
|
plannedRefs: preparation.loopRefs,
|
|
1412
|
-
...(preparation.planning
|
|
1413
|
-
? {
|
|
1414
|
-
plan: buildResultExecutionPlan(args.run, preparation, rawPlannedRefs, strategyFilteredRefs, indexSnapshot, false),
|
|
1415
|
-
}
|
|
1416
|
-
: {}),
|
|
920
|
+
...(preparation.planning ? { plan: buildResultExecutionPlan(run, preparation, collected, false) } : {}),
|
|
1417
921
|
...(strategyFilteredRefs.length > 0 ? { strategyFilteredRefs } : {}),
|
|
1418
922
|
...(resolvedPlan.engineUnavailable.length > 0 ? { skippedProcesses: resolvedPlan.engineUnavailable } : {}),
|
|
923
|
+
...(options.engineProbe !== undefined ? { engineProbe: options.engineProbe } : {}),
|
|
1419
924
|
...(usageReport ? { usageReport } : {}),
|
|
1420
925
|
actions: persistedActions,
|
|
1421
926
|
...(distillSkippedAggregate ? { distillSkipped: distillSkippedAggregate } : {}),
|
|
@@ -1426,35 +931,20 @@ function finalizeImproveResult(args) {
|
|
|
1426
931
|
...(preparation.memoryIndexHealth !== undefined ? { memoryIndexHealth: preparation.memoryIndexHealth } : {}),
|
|
1427
932
|
...(preparation.coverageGaps.length > 0 ? { coverageGaps: preparation.coverageGaps } : {}),
|
|
1428
933
|
...(preparation.extract && preparation.extract.length > 0 ? { extract: preparation.extract } : {}),
|
|
1429
|
-
...(primaryStashDir !== undefined ? { evalCasesWritten: countEvalCases(primaryStashDir) } : {}),
|
|
1430
934
|
...(deadUrls !== undefined && deadUrls.length > 0 ? { deadUrls } : {}),
|
|
1431
|
-
// Present whenever the check ran,
|
|
1432
|
-
// (zero dead links) still needs to tell the health report how much of
|
|
1433
|
-
// the bundle it actually covered (#892).
|
|
935
|
+
// Present whenever the check ran, so health knows the coverage of a clean run (#892).
|
|
1434
936
|
...(deadUrlCoverage !== undefined ? { deadUrlCoverage } : {}),
|
|
1435
937
|
...(reflectsWithErrorContext > 0 ? { reflectsWithErrorContext } : {}),
|
|
1436
938
|
...(memoryInference ? { memoryInference } : {}),
|
|
1437
|
-
|
|
1438
|
-
//
|
|
1439
|
-
// envelope (not nested) because `health.ts`'s `wallTime.byPhase`
|
|
1440
|
-
// aggregator and the existing `memoryInference.durationMs` /
|
|
1441
|
-
// `graphExtraction.durationMs` health buckets all read
|
|
1442
|
-
// `result.{memoryInferenceDurationMs,graphExtractionDurationMs}`
|
|
1443
|
-
// directly. Mirrors how `consolidation.durationMs` is surfaced inside
|
|
1444
|
-
// the consolidation sub-object (different convention because the
|
|
1445
|
-
// consolidation result type already owns that field). Phases that did
|
|
1446
|
-
// not run (zero duration) are omitted so the aggregator's
|
|
1447
|
-
// "phase actually ran" filter (`> 0`) excludes them from the median/p95
|
|
1448
|
-
// sample. Plumbed in d1273d0's follow-up — see
|
|
1449
|
-
// `/tmp/akm-health-investigations/metrics-taxonomy-review.md` §1k / §3.
|
|
939
|
+
// Top-level phase durations feed health's wall-time buckets; a phase that
|
|
940
|
+
// did not run is omitted, not zero.
|
|
1450
941
|
...(memoryInferenceDurationMs > 0 ? { memoryInferenceDurationMs } : {}),
|
|
1451
|
-
...(
|
|
1452
|
-
...(cycleMetrics ? { cycleMetrics } : {}),
|
|
942
|
+
...(ensureIndexDurationMs !== undefined ? { ensureIndexDurationMs } : {}),
|
|
1453
943
|
...(orphansPurged !== undefined ? { orphansPurged } : {}),
|
|
1454
944
|
...(proposalsExpired !== undefined && proposalsExpired > 0 ? { proposalsExpired } : {}),
|
|
1455
|
-
reflectCooldownActions:
|
|
1456
|
-
reflectSkippedActions:
|
|
1457
|
-
reflectGuardRejectedActions:
|
|
945
|
+
reflectCooldownActions: countMode("reflect-cooldown"),
|
|
946
|
+
reflectSkippedActions: countMode("reflect-skipped"),
|
|
947
|
+
reflectGuardRejectedActions: countMode("reflect-guard-rejected"),
|
|
1458
948
|
...(triageDrain
|
|
1459
949
|
? {
|
|
1460
950
|
triage: {
|
|
@@ -1469,116 +959,57 @@ function finalizeImproveResult(args) {
|
|
|
1469
959
|
...(preparation.proactiveMaintenance ? { proactiveMaintenance: preparation.proactiveMaintenance } : {}),
|
|
1470
960
|
...(options.runId !== undefined ? { runId: options.runId } : {}),
|
|
1471
961
|
};
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
orphansPurged: orphansPurged ?? 0,
|
|
1479
|
-
}, eventsCtx);
|
|
962
|
+
emitImproveCompletedEvent(result, {
|
|
963
|
+
memoryInferenceDurationMs,
|
|
964
|
+
totalDurationMs: Date.now() - startMs,
|
|
965
|
+
warningCount: allWarnings.length,
|
|
966
|
+
orphansPurged: orphansPurged ?? 0,
|
|
967
|
+
}, eventsCtx);
|
|
1480
968
|
return result;
|
|
1481
969
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
970
|
+
/** Every lowering notice the plan and this run's stage results carry, deduplicated. */
|
|
971
|
+
function collectImproveNotices(resolvedPlan, carriers) {
|
|
972
|
+
const notices = noticeSet();
|
|
1484
973
|
const collect = (carrier) => {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
if (!Array.isArray(notices))
|
|
1489
|
-
return;
|
|
1490
|
-
for (const notice of notices)
|
|
1491
|
-
byKey.set(JSON.stringify(notice), notice);
|
|
974
|
+
const list = carrier?.notices;
|
|
975
|
+
if (typeof carrier === "object" && Array.isArray(list))
|
|
976
|
+
notices.add(list);
|
|
1492
977
|
};
|
|
1493
|
-
for (const process of Object.values(
|
|
978
|
+
for (const process of Object.values(resolvedPlan.processes))
|
|
1494
979
|
collect(process);
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
collect(action.result);
|
|
1500
|
-
for (const repair of args.schemaRepairs ?? [])
|
|
1501
|
-
collect(repair);
|
|
1502
|
-
collect(args.consolidation);
|
|
1503
|
-
for (const extract of args.extract ?? []) {
|
|
1504
|
-
collect(extract);
|
|
1505
|
-
for (const session of extract.sessions ?? [])
|
|
1506
|
-
collect(session);
|
|
1507
|
-
}
|
|
1508
|
-
collect(args.memoryInference);
|
|
1509
|
-
collect(args.graphExtraction);
|
|
1510
|
-
collect(args.triageDrain);
|
|
1511
|
-
return Object.freeze([...byKey.values()]);
|
|
980
|
+
notices.add(resolvedPlan.triageJudgmentNotices ?? []);
|
|
981
|
+
for (const carrier of carriers)
|
|
982
|
+
collect(carrier);
|
|
983
|
+
return notices;
|
|
1512
984
|
}
|
|
985
|
+
/** `improve_completed` per-mode counters; keyed by every mode so a new one cannot be dropped. */
|
|
986
|
+
const ACTION_COUNTER = {
|
|
987
|
+
reflect: "reflectActions",
|
|
988
|
+
distill: "distillActions",
|
|
989
|
+
"distill-skipped": "distillSkippedActions",
|
|
990
|
+
"memory-prune": "memoryPruneActions",
|
|
991
|
+
"memory-inference": "memoryInferenceActions",
|
|
992
|
+
error: "errorActions",
|
|
993
|
+
"reflect-failed": "reflectFailedActions",
|
|
994
|
+
"reflect-cooldown": "reflectCooldownActions",
|
|
995
|
+
"reflect-skipped": "reflectSkippedActions",
|
|
996
|
+
"reflect-guard-rejected": "reflectGuardRejectedActions",
|
|
997
|
+
};
|
|
1513
998
|
function emitImproveCompletedEvent(result, durations, eventsCtx) {
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
reflectFailed: 0,
|
|
1517
|
-
reflectCooldown: 0,
|
|
1518
|
-
reflectSkipped: 0,
|
|
1519
|
-
reflectGuardRejected: 0,
|
|
1520
|
-
distill: 0,
|
|
1521
|
-
distillSkipped: 0,
|
|
1522
|
-
memoryPrune: 0,
|
|
1523
|
-
memoryInference: 0,
|
|
1524
|
-
graphExtraction: 0,
|
|
1525
|
-
error: 0,
|
|
1526
|
-
};
|
|
1527
|
-
// Coarse audit buckets, derived from the SAME classifyImproveAction the
|
|
1528
|
-
// persisted metrics_json uses (state-db.ts#computeImproveRunMetrics) so the
|
|
1529
|
-
// emitted event and the stored row can never disagree.
|
|
999
|
+
const counts = Object.fromEntries(Object.values(ACTION_COUNTER).map((key) => [key, 0]));
|
|
1000
|
+
// The coarse buckets come from the same classifier the persisted metrics use.
|
|
1530
1001
|
const classCounts = { accepted: 0, rejected: 0, skipped: 0, error: 0, noop: 0 };
|
|
1531
1002
|
for (const action of result.actions ?? []) {
|
|
1003
|
+
const key = ACTION_COUNTER[action.mode];
|
|
1004
|
+
if (key)
|
|
1005
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
1532
1006
|
classCounts[classifyImproveAction(action.mode)] += 1;
|
|
1533
|
-
// Per-variant counters for the event metadata. The default arm makes any
|
|
1534
|
-
// new ImproveActionMode variant a compile error so a future variant cannot
|
|
1535
|
-
// be silently dropped from the improve_completed event (the `reflect-guard-
|
|
1536
|
-
// rejected` case below was previously missing here entirely).
|
|
1537
|
-
switch (action.mode) {
|
|
1538
|
-
case "reflect":
|
|
1539
|
-
actionCounts.reflect += 1;
|
|
1540
|
-
break;
|
|
1541
|
-
case "reflect-failed":
|
|
1542
|
-
actionCounts.reflectFailed += 1;
|
|
1543
|
-
break;
|
|
1544
|
-
case "reflect-cooldown":
|
|
1545
|
-
actionCounts.reflectCooldown += 1;
|
|
1546
|
-
break;
|
|
1547
|
-
case "reflect-skipped":
|
|
1548
|
-
actionCounts.reflectSkipped += 1;
|
|
1549
|
-
break;
|
|
1550
|
-
case "reflect-guard-rejected":
|
|
1551
|
-
actionCounts.reflectGuardRejected += 1;
|
|
1552
|
-
break;
|
|
1553
|
-
case "distill":
|
|
1554
|
-
actionCounts.distill += 1;
|
|
1555
|
-
break;
|
|
1556
|
-
case "distill-skipped":
|
|
1557
|
-
actionCounts.distillSkipped += 1;
|
|
1558
|
-
break;
|
|
1559
|
-
case "memory-prune":
|
|
1560
|
-
actionCounts.memoryPrune += 1;
|
|
1561
|
-
break;
|
|
1562
|
-
case "memory-inference":
|
|
1563
|
-
actionCounts.memoryInference += 1;
|
|
1564
|
-
break;
|
|
1565
|
-
case "graph-extraction":
|
|
1566
|
-
actionCounts.graphExtraction += 1;
|
|
1567
|
-
break;
|
|
1568
|
-
case "error":
|
|
1569
|
-
actionCounts.error += 1;
|
|
1570
|
-
break;
|
|
1571
|
-
default:
|
|
1572
|
-
assertNever(action.mode);
|
|
1573
|
-
}
|
|
1574
1007
|
}
|
|
1575
|
-
//
|
|
1576
|
-
// bounded `distillSkipped` aggregate at assembly). Add the aggregate total to
|
|
1577
|
-
// the per-variant counter AND the coarse `skipped` bucket so the emitted event
|
|
1578
|
-
// still reports the true skipped volume.
|
|
1008
|
+
// distill-skipped rows were folded into the aggregate; count them back in.
|
|
1579
1009
|
const distillSkippedTotal = result.distillSkipped?.total ?? 0;
|
|
1580
|
-
|
|
1010
|
+
counts.distillSkippedActions = (counts.distillSkippedActions ?? 0) + distillSkippedTotal;
|
|
1581
1011
|
classCounts.skipped += distillSkippedTotal;
|
|
1012
|
+
const cleanup = result.memoryCleanup;
|
|
1582
1013
|
appendEvent({
|
|
1583
1014
|
eventType: "improve_completed",
|
|
1584
1015
|
ref: result.scope.mode === "ref"
|
|
@@ -1587,60 +1018,35 @@ function emitImproveCompletedEvent(result, durations, eventsCtx) {
|
|
|
1587
1018
|
metadata: {
|
|
1588
1019
|
strategy: result.strategy,
|
|
1589
1020
|
plannedRefs: result.plannedRefs.length,
|
|
1590
|
-
|
|
1591
|
-
distillActions: actionCounts.distill,
|
|
1592
|
-
distillSkippedActions: actionCounts.distillSkipped,
|
|
1593
|
-
memoryPruneActions: actionCounts.memoryPrune,
|
|
1594
|
-
memoryInferenceActions: actionCounts.memoryInference,
|
|
1595
|
-
graphExtractionActions: actionCounts.graphExtraction,
|
|
1596
|
-
errorActions: actionCounts.error,
|
|
1597
|
-
reflectFailedActions: actionCounts.reflectFailed,
|
|
1598
|
-
reflectCooldownActions: actionCounts.reflectCooldown,
|
|
1599
|
-
reflectSkippedActions: actionCounts.reflectSkipped,
|
|
1600
|
-
// Previously dropped from the event entirely; now emitted so the guard
|
|
1601
|
-
// rejections are visible in improve_completed telemetry.
|
|
1602
|
-
reflectGuardRejectedActions: actionCounts.reflectGuardRejected,
|
|
1021
|
+
...counts,
|
|
1603
1022
|
acceptedActions: classCounts.accepted,
|
|
1604
1023
|
rejectedActions: classCounts.rejected,
|
|
1605
1024
|
skippedActions: classCounts.skipped,
|
|
1606
1025
|
noopActions: classCounts.noop,
|
|
1607
1026
|
reflectsWithErrorContext: result.reflectsWithErrorContext ?? 0,
|
|
1608
1027
|
coverageGapCount: result.coverageGaps?.length ?? 0,
|
|
1609
|
-
evalCasesWritten: result.evalCasesWritten ?? 0,
|
|
1610
1028
|
deadUrlCount: result.deadUrls?.length ?? 0,
|
|
1611
1029
|
deadUrlsChecked: result.deadUrlCoverage?.checked ?? 0,
|
|
1612
1030
|
deadUrlsTotal: result.deadUrlCoverage?.total ?? 0,
|
|
1613
1031
|
deadUrlsSkipped: result.deadUrlCoverage?.skipped ?? 0,
|
|
1614
1032
|
memoryEligible: result.memorySummary.eligible,
|
|
1615
1033
|
memoryDerived: result.memorySummary.derived,
|
|
1616
|
-
memoryCleanupPruneCandidates:
|
|
1617
|
-
memoryCleanupContradictionCandidates:
|
|
1618
|
-
memoryCleanupBeliefStateTransitions:
|
|
1619
|
-
memoryCleanupConsolidationCandidates:
|
|
1620
|
-
memoryCleanupArchived:
|
|
1621
|
-
memoryCleanupWarnings:
|
|
1034
|
+
memoryCleanupPruneCandidates: cleanup?.pruneCandidates.length ?? 0,
|
|
1035
|
+
memoryCleanupContradictionCandidates: cleanup?.contradictionCandidates.length ?? 0,
|
|
1036
|
+
memoryCleanupBeliefStateTransitions: cleanup?.beliefStateTransitions.length ?? 0,
|
|
1037
|
+
memoryCleanupConsolidationCandidates: cleanup?.consolidationCandidates.length ?? 0,
|
|
1038
|
+
memoryCleanupArchived: cleanup?.archived?.length ?? 0,
|
|
1039
|
+
memoryCleanupWarnings: cleanup?.warnings?.length ?? 0,
|
|
1622
1040
|
consolidationProcessed: result.consolidation?.processed ?? 0,
|
|
1623
1041
|
consolidationDurationMs: result.consolidation?.durationMs ?? 0,
|
|
1624
1042
|
memoryInferenceWrites: result.memoryInference?.writtenFacts ?? 0,
|
|
1625
1043
|
memoryInferenceDurationMs: durations.memoryInferenceDurationMs,
|
|
1626
|
-
graphExtractionExtractedFiles: result.graphExtraction?.quality.extractedFiles ?? 0,
|
|
1627
|
-
graphExtractionDurationMs: durations.graphExtractionDurationMs,
|
|
1628
|
-
// Layer-2 proactive-maintenance coverage (0 when the process is disabled
|
|
1629
|
-
// or the run was ref-scoped) so a scheduled sweep's reach is trackable.
|
|
1630
1044
|
proactiveSelected: result.proactiveMaintenance?.selected ?? 0,
|
|
1631
1045
|
proactiveDueTotal: result.proactiveMaintenance?.dueTotal ?? 0,
|
|
1632
1046
|
proactiveNeverReflected: result.proactiveMaintenance?.neverReflected ?? 0,
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
...(durations.orphansPurged !== undefined ? { orphansPurged: durations.orphansPurged } : {}),
|
|
1637
|
-
...(result.graphExtraction?.quality
|
|
1638
|
-
? {
|
|
1639
|
-
graphCoverage: result.graphExtraction.quality.extractionCoverage,
|
|
1640
|
-
graphDensity: result.graphExtraction.quality.density,
|
|
1641
|
-
graphEntities: result.graphExtraction.quality.entityCount,
|
|
1642
|
-
}
|
|
1643
|
-
: {}),
|
|
1047
|
+
durationMs: durations.totalDurationMs,
|
|
1048
|
+
warningCount: durations.warningCount,
|
|
1049
|
+
orphansPurged: durations.orphansPurged,
|
|
1644
1050
|
},
|
|
1645
1051
|
}, eventsCtx);
|
|
1646
1052
|
}
|