akm-cli 0.9.0-rc.0 → 0.9.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1283 -22
- package/README.md +62 -37
- package/SECURITY.md +46 -31
- package/dist/akm +162 -38
- package/dist/akm-migrate +44 -0
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/hints/cli-hints-full.md +268 -118
- package/dist/assets/hints/cli-hints-short.md +87 -24
- package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
- package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
- package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
- package/dist/assets/improve-strategies/frequent.json +15 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
- package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
- package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
- package/dist/assets/improve-strategies/reflect-distill.json +30 -0
- package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +5 -5
- package/dist/assets/prompts/extract-session.md +2 -6
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
- package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
- package/dist/assets/prompts/reflect-output-repair.md +3 -0
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +38 -10
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
- package/dist/assets/tasks/core/extract.yml +3 -2
- package/dist/assets/tasks/core/improve.yml +2 -1
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +2 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
- package/dist/assets/templates/html/health.html +5 -4
- package/dist/assets/workflows/workflow-template.md +31 -15
- package/dist/cli/invocation.js +279 -0
- package/dist/cli/parse-args.js +5 -90
- package/dist/cli/retired-commands.js +78 -0
- package/dist/cli/shared.js +158 -48
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +747 -293
- package/dist/commands/agent/agent-dispatch.js +19 -18
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +43 -97
- package/dist/commands/completions.js +80 -23
- package/dist/commands/config-cli.js +44 -281
- package/dist/commands/env/env-binding.js +99 -0
- package/dist/commands/env/env-cli.js +84 -224
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +45 -61
- package/dist/commands/env/secret.js +32 -62
- package/dist/commands/feedback-cli.js +179 -85
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +7 -8
- package/dist/commands/health/checks.js +279 -94
- package/dist/commands/health/html-report.js +197 -578
- package/dist/commands/health/improve-metrics.js +277 -246
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +16 -7
- package/dist/commands/health/metrics.js +67 -32
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +508 -0
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +16 -56
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
- package/dist/commands/health/types-improve.js +29 -0
- package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
- package/dist/commands/health/types-result.js +7 -0
- package/dist/commands/health/types-runs.js +4 -0
- package/dist/commands/health/types-session-log.js +4 -0
- package/dist/commands/health/types-windows.js +4 -0
- package/dist/commands/health/types.js +26 -21
- package/dist/commands/health/windows.js +2 -3
- package/dist/commands/health.js +296 -167
- package/dist/commands/improve/anti-collapse.js +5 -5
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +65 -52
- package/dist/commands/improve/consolidate/chunking.js +9 -7
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +454 -1354
- package/dist/commands/improve/content-hash.js +39 -0
- package/dist/commands/improve/distill/content-repair.js +4 -10
- package/dist/commands/improve/distill/promote-memory.js +89 -64
- package/dist/commands/improve/distill/quality-gate.js +118 -42
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +33 -888
- package/dist/commands/improve/distill.js +607 -363
- package/dist/commands/improve/eligibility.js +165 -79
- package/dist/commands/improve/extract-cli.js +35 -126
- package/dist/commands/improve/extract-prompt.js +6 -35
- package/dist/commands/improve/extract.js +640 -391
- package/dist/commands/improve/feedback-valence.js +2 -12
- package/dist/commands/improve/improve-cli.js +134 -135
- package/dist/commands/improve/improve-result-file.js +30 -50
- package/dist/commands/improve/improve-run-types.js +4 -0
- package/dist/commands/improve/improve-strategies.js +135 -0
- package/dist/commands/improve/improve.js +904 -701
- package/dist/commands/improve/locks.js +64 -111
- package/dist/commands/improve/loop-stages.js +1110 -923
- package/dist/commands/improve/memory/derived-ref.js +124 -0
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
- package/dist/commands/improve/memory/memory-improve.js +25 -37
- package/dist/commands/improve/outcome-loop.js +25 -88
- package/dist/commands/improve/preparation.js +1034 -813
- package/dist/commands/improve/proactive-maintenance.js +34 -9
- package/dist/commands/improve/proposal-envelope.js +31 -0
- package/dist/commands/improve/reflect.js +983 -794
- package/dist/commands/improve/run-context.js +119 -0
- package/dist/commands/improve/salience.js +24 -127
- package/dist/commands/improve/session-asset.js +7 -3
- package/dist/commands/improve/shared.js +14 -34
- package/dist/commands/improve/source-identity.js +28 -0
- package/dist/commands/improve/triage.js +20 -17
- package/dist/commands/lint/base-linter.js +340 -313
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +185 -30
- package/dist/commands/{events.js → log.js} +28 -38
- package/dist/commands/migrate-cli.js +54 -0
- package/dist/commands/migration-tool.js +55 -0
- package/dist/commands/observability-cli.js +70 -208
- package/dist/commands/proposal/diff-format.js +50 -0
- package/dist/commands/proposal/drain-policies.js +0 -6
- package/dist/commands/proposal/drain.js +91 -40
- package/dist/commands/proposal/proposal-cli.js +134 -132
- package/dist/commands/proposal/proposal-types.js +56 -0
- package/dist/commands/proposal/proposal.js +83 -65
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +105 -88
- package/dist/commands/proposal/repository.js +1303 -278
- package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
- package/dist/commands/proposal/validators/proposal-validators.js +61 -12
- package/dist/commands/proposal/validators/proposals.js +6 -8
- package/dist/commands/read/curate.js +78 -73
- package/dist/commands/read/knowledge.js +510 -13
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +84 -15
- package/dist/commands/read/search-cli.js +203 -96
- package/dist/commands/read/search.js +126 -94
- package/dist/commands/read/show.js +226 -250
- package/dist/commands/registry-cli.js +34 -60
- package/dist/commands/remember.js +18 -57
- package/dist/commands/sources/add-cli.js +104 -49
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +63 -0
- package/dist/commands/sources/info.js +27 -15
- package/dist/commands/sources/init.js +30 -40
- package/dist/commands/sources/installed-stashes.js +469 -172
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +10 -9
- package/dist/commands/sources/self-update.js +182 -121
- package/dist/commands/sources/source-add.js +169 -178
- package/dist/commands/sources/source-clone.js +144 -41
- package/dist/commands/sources/source-manage.js +94 -59
- package/dist/commands/sources/sources-cli.js +64 -205
- package/dist/commands/sources/stash-cli.js +91 -54
- package/dist/commands/sources/stash-skeleton.js +1 -1
- package/dist/commands/tasks/tasks-cli.js +106 -104
- package/dist/commands/tasks/tasks.js +445 -262
- package/dist/commands/workflow-cli.js +232 -121
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/activation-policy.js +49 -0
- package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
- package/dist/core/adapter/adapters/akm-adapter.js +528 -0
- package/dist/core/adapter/adapters/akm-lint.js +392 -0
- package/dist/core/adapter/adapters/akm-metadata.js +387 -0
- package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
- package/dist/core/adapter/adapters/claude-adapter.js +61 -0
- package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
- package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
- package/dist/core/adapter/adapters/index.js +80 -0
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
- package/dist/core/adapter/adapters/okf-adapter.js +391 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
- package/dist/core/adapter/adapters/shared.js +286 -0
- package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
- package/dist/core/adapter/bundle-adapter.js +4 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +44 -0
- package/dist/core/adapter/registry.js +56 -0
- package/dist/core/adapter/types.js +4 -0
- package/dist/core/asset/akm-markdown.js +30 -0
- package/dist/core/asset/asset-placement.js +243 -0
- package/dist/core/asset/asset-ref.js +110 -79
- package/dist/core/asset/asset-serialize.js +20 -0
- package/dist/core/asset/frontmatter.js +28 -12
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +274 -0
- package/dist/core/asset/stash-meta.js +2 -2
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +281 -86
- package/dist/core/config/config-io.js +42 -128
- package/dist/core/config/config-schema.js +233 -834
- package/dist/core/config/config-sources.js +162 -39
- package/dist/core/config/config-types.js +16 -11
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +126 -37
- package/dist/core/config/config.js +154 -331
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +28 -0
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +38 -0
- package/dist/core/config/schema/engines.js +116 -0
- package/dist/core/config/schema/experimental.js +47 -0
- package/dist/core/config/schema/feedback.js +31 -0
- package/dist/core/config/schema/improve-processes.js +389 -0
- package/dist/core/config/schema/improve.js +94 -0
- package/dist/core/config/schema/index-config.js +176 -0
- package/dist/core/config/schema/output.js +18 -0
- package/dist/core/config/schema/primitives.js +94 -0
- package/dist/core/config/schema/search.js +30 -0
- package/dist/core/config/schema/setup.js +18 -0
- package/dist/core/config/schema/sources-bundles.js +169 -0
- package/dist/core/config/schema/workflow.js +29 -0
- package/dist/core/env-secret-ref.js +155 -20
- package/dist/core/errors.js +17 -15
- package/dist/core/events-types.js +4 -0
- package/dist/core/events.js +46 -128
- package/dist/core/extra-params.js +62 -0
- package/dist/core/file-change.js +17 -0
- package/dist/core/file-lock.js +202 -57
- package/dist/core/fs-txn.js +392 -0
- package/dist/core/git-message.js +59 -0
- package/dist/core/improve-result.js +167 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +1 -1
- package/dist/core/maintenance-barrier.js +135 -0
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/paths.js +22 -25
- package/dist/core/platform.js +10 -0
- package/dist/core/recognition-util.js +128 -0
- package/dist/core/redaction.js +392 -0
- package/dist/core/standards/resolve-standards-context.js +36 -65
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +5 -5
- package/dist/core/state/migrations.js +242 -11
- package/dist/core/state-db.js +98 -10
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +303 -0
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/time.js +20 -0
- package/dist/core/type-presentation.js +130 -0
- package/dist/core/warn.js +0 -3
- package/dist/core/write-source.js +834 -118
- package/dist/indexer/bundle-identity-guard.js +92 -0
- package/dist/indexer/db/graph-db.js +1 -25
- package/dist/indexer/db/llm-cache.js +1 -1
- package/dist/indexer/ensure-index.js +30 -9
- package/dist/indexer/graph/graph-boost.js +9 -30
- package/dist/indexer/graph/graph-extraction.js +41 -27
- package/dist/indexer/graph/graph-types.js +4 -0
- package/dist/indexer/index-writer-lock.js +93 -49
- package/dist/indexer/index-written-assets.js +100 -53
- package/dist/indexer/indexer.js +746 -329
- package/dist/indexer/init.js +18 -25
- package/dist/indexer/installations.js +142 -0
- package/dist/indexer/passes/dir-staleness.js +18 -10
- package/dist/indexer/passes/memory-inference.js +25 -15
- package/dist/indexer/passes/metadata.js +412 -243
- package/dist/indexer/scan/doc-to-entry.js +160 -0
- package/dist/indexer/scan/drain-dir.js +134 -0
- package/dist/indexer/search/db-search.js +292 -108
- package/dist/indexer/search/fts-query.js +64 -0
- package/dist/indexer/search/ranking-contributors.js +145 -25
- package/dist/indexer/search/ranking-types.js +4 -0
- package/dist/indexer/search/ranking.js +28 -71
- package/dist/indexer/search/search-attribution.js +67 -0
- package/dist/indexer/search/search-fields.js +18 -3
- package/dist/indexer/search/search-hit-enrichers.js +30 -40
- package/dist/indexer/search/search-source.js +157 -111
- package/dist/indexer/search/semantic-status.js +4 -1
- package/dist/indexer/usage/usage-events.js +10 -30
- package/dist/indexer/walk/file-context.js +3 -45
- package/dist/indexer/walk/matchers.js +42 -34
- package/dist/indexer/walk/path-resolver.js +11 -5
- package/dist/indexer/walk/walker.js +42 -14
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -56
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +231 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/prompts.js +46 -18
- package/dist/integrations/agent/runner-dispatch.js +93 -4
- package/dist/integrations/agent/runner.js +76 -208
- package/dist/integrations/agent/spawn.js +88 -196
- package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
- package/dist/integrations/harnesses/aider/index.js +48 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
- package/dist/integrations/harnesses/amazonq/index.js +45 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/config-import.js +1 -3
- package/dist/integrations/harnesses/claude/index.js +24 -35
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +27 -75
- package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
- package/dist/integrations/harnesses/codex/index.js +52 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +48 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +48 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/ids.js +24 -0
- package/dist/integrations/harnesses/index.js +54 -34
- package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
- package/dist/integrations/harnesses/opencode/config-import.js +1 -3
- package/dist/integrations/harnesses/opencode/index.js +14 -32
- package/dist/integrations/harnesses/opencode/session-log.js +67 -125
- package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
- package/dist/integrations/harnesses/openhands/index.js +48 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
- package/dist/integrations/harnesses/pi/index.js +45 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/shared.js +17 -0
- package/dist/integrations/harnesses/types.js +43 -32
- package/dist/integrations/lockfile.js +211 -24
- package/dist/integrations/session-logs/index.js +36 -39
- package/dist/integrations/session-logs/provider-base.js +113 -0
- package/dist/llm/client.js +182 -110
- package/dist/llm/embedders/deterministic.js +2 -2
- package/dist/llm/embedders/remote.js +21 -9
- package/dist/llm/feature-gate.js +17 -57
- package/dist/llm/graph-extract.js +12 -13
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +45 -30
- package/dist/llm/structured-call.js +16 -8
- package/dist/llm/usage-persist.js +30 -5
- package/dist/llm/usage-telemetry.js +59 -6
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/command-registry.js +27 -0
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +251 -0
- package/dist/output/html-render.js +11 -16
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -279
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +58 -84
- package/dist/output/shapes/passthrough.js +11 -39
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes/registry.js +12 -6
- package/dist/output/shapes.js +0 -9
- package/dist/output/text/{init.js → bundle-create.js} +3 -1
- package/dist/output/text/bundle-show.js +7 -0
- package/dist/output/text/command-format.js +562 -0
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/helpers.js +15 -1164
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +202 -0
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/registry.js +12 -6
- package/dist/output/text/show-directives.js +117 -0
- package/dist/output/text/show-format.js +103 -0
- package/dist/output/text/sync.js +5 -0
- package/dist/output/text/workflow-format.js +332 -0
- package/dist/output/text/workflow.js +3 -2
- package/dist/output/text.js +10 -19
- package/dist/registry/factory.js +4 -6
- package/dist/registry/origin-resolve.js +16 -27
- package/dist/registry/providers/skills-sh.js +3 -3
- package/dist/registry/providers/static-index.js +15 -25
- package/dist/registry/resolve.js +43 -94
- package/dist/registry/semver.js +43 -0
- package/dist/runtime.js +81 -12
- package/dist/scripts/akm-migrate.js +35529 -0
- package/dist/setup/detect.js +5 -7
- package/dist/setup/detected-engines.js +136 -0
- package/dist/setup/engine-config.js +100 -0
- package/dist/setup/registry-stash-loader.js +3 -3
- package/dist/setup/semantic-assets.js +12 -9
- package/dist/setup/setup.js +444 -208
- package/dist/setup/steps/connection-shared.js +120 -0
- package/dist/setup/steps/connection.js +108 -305
- package/dist/setup/steps/platforms.js +13 -12
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +21 -15
- package/dist/setup/steps/stashdir.js +6 -4
- package/dist/setup/steps/tasks.js +236 -119
- package/dist/setup/steps.js +3 -2
- package/dist/sources/freshness.js +39 -0
- package/dist/sources/provider-factory.js +11 -17
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +278 -34
- package/dist/sources/providers/git-provider.js +54 -56
- package/dist/sources/providers/git-stash.js +420 -91
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +47 -22
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +11 -10
- package/dist/sources/snapshot-fetchers/types.js +4 -0
- package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
- package/dist/storage/database.js +60 -4
- package/dist/storage/engines/sqlite-migrations.js +156 -5
- package/dist/storage/locations.js +1 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +51 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -32
- package/dist/storage/repositories/index-connection.js +79 -0
- package/dist/storage/repositories/index-db.js +4 -3
- package/dist/storage/repositories/index-entries-repository.js +863 -0
- package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
- package/dist/storage/repositories/index-entry-types.js +4 -0
- package/dist/storage/repositories/index-fts-repository.js +167 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
- package/dist/storage/repositories/index-meta-repository.js +49 -0
- package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
- package/dist/storage/repositories/index-sql.js +12 -0
- package/dist/storage/repositories/index-utility-repository.js +356 -0
- package/dist/storage/repositories/index-vec-repository.js +250 -0
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +317 -75
- package/dist/storage/repositories/registry-cache.js +1 -1
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +110 -3
- package/dist/storage/repositories/workflow-runs-repository.js +240 -19
- package/dist/tasks/backends/cron.js +169 -46
- package/dist/tasks/backends/exec-utils.js +76 -3
- package/dist/tasks/backends/index.js +6 -9
- package/dist/tasks/backends/launchd.js +292 -55
- package/dist/tasks/backends/schtasks.js +557 -70
- package/dist/tasks/backends/types.js +4 -0
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +56 -38
- package/dist/tasks/parser.js +156 -64
- package/dist/tasks/resolve-akm-bin.js +144 -51
- package/dist/tasks/runner.js +377 -209
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +296 -0
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/task-id.js +35 -0
- package/dist/tasks/validator.js +30 -16
- package/dist/text-import-hook.mjs +1 -1
- package/dist/workflows/authoring/authoring.js +104 -43
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/cli.js +0 -16
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/exec/brief.js +450 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1038 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1460 -0
- package/dist/workflows/exec/run-workflow.js +602 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1190 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/workflow-engine-gate.js +67 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +246 -0
- package/dist/workflows/ir/freeze.js +233 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +540 -0
- package/dist/workflows/parser.js +878 -304
- package/dist/workflows/program/expressions.js +181 -0
- package/dist/workflows/program/schema.js +51 -0
- package/dist/workflows/renderer.js +100 -45
- package/dist/workflows/resource-limits.js +22 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/checkin.js +1 -1
- package/dist/workflows/runtime/plan-classifier.js +131 -0
- package/dist/workflows/runtime/runs.js +376 -119
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/unit-phases.js +20 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +2 -3
- package/dist/workflows/validator.js +52 -30
- package/docs/README.md +42 -78
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +9 -8
- package/docs/migration/release-notes/0.9.0.md +158 -14
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +844 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/data-and-telemetry.md +333 -0
- package/package.json +21 -17
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +4636 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +373 -0
- package/dist/akm-migrate-storage +0 -38
- package/dist/assets/help/help-accept.md +0 -12
- package/dist/assets/help/help-improve.md +0 -84
- package/dist/assets/help/help-proposals.md +0 -17
- package/dist/assets/help/help-propose.md +0 -17
- package/dist/assets/help/help-reject.md +0 -11
- package/dist/assets/profiles/frequent.json +0 -13
- package/dist/assets/profiles/recombine-only.json +0 -21
- package/dist/assets/profiles/reflect-distill.json +0 -30
- package/dist/assets/profiles/synthesize.json +0 -15
- package/dist/assets/prompts/procedural-system.md +0 -44
- package/dist/assets/prompts/recombine-system.md +0 -40
- package/dist/assets/prompts/staleness-detect-system.md +0 -6
- package/dist/assets/tasks/core/backup.yml +0 -4
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/assets/templates/html/default.html +0 -78
- package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
- package/dist/assets/wiki/index-template.md +0 -12
- package/dist/assets/wiki/ingest-workflow-template.md +0 -83
- package/dist/assets/wiki/log-template.md +0 -8
- package/dist/assets/wiki/schema-template.md +0 -61
- package/dist/cli/config-migrate.js +0 -150
- package/dist/cli/config-validate.js +0 -39
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -487
- package/dist/commands/improve/calibration.js +0 -161
- package/dist/commands/improve/dedup.js +0 -482
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/improve/hot-probation.js +0 -45
- package/dist/commands/improve/improve-auto-accept.js +0 -276
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/commands/improve/procedural.js +0 -398
- package/dist/commands/improve/recombine.js +0 -818
- package/dist/commands/improve/schema-similarity-gate.js +0 -168
- package/dist/commands/lint/agent-linter.js +0 -44
- package/dist/commands/lint/command-linter.js +0 -44
- package/dist/commands/lint/default-linter.js +0 -16
- package/dist/commands/lint/fact-linter.js +0 -39
- package/dist/commands/lint/knowledge-linter.js +0 -16
- package/dist/commands/lint/memory-linter.js +0 -61
- package/dist/commands/lint/registry.js +0 -41
- package/dist/commands/lint/skill-linter.js +0 -45
- package/dist/commands/lint/task-linter.js +0 -50
- package/dist/commands/lint/workflow-linter.js +0 -81
- package/dist/commands/proposal/legacy-import.js +0 -115
- package/dist/commands/sources/history.js +0 -196
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/commands/wiki-cli.js +0 -292
- package/dist/core/asset/asset-registry.js +0 -76
- package/dist/core/asset/asset-spec.js +0 -259
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/core/eval/rank-metrics.js +0 -113
- package/dist/core/ripgrep/install.js +0 -163
- package/dist/core/ripgrep/resolve.js +0 -81
- package/dist/indexer/db/db.js +0 -1413
- package/dist/indexer/manifest.js +0 -170
- package/dist/indexer/passes/metadata-contributors.js +0 -31
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
- package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
- package/dist/llm/call-ai.js +0 -62
- package/dist/llm/memory-infer-impl.js +0 -138
- package/dist/output/shapes/distill.js +0 -14
- package/dist/output/shapes/history.js +0 -11
- package/dist/output/text/distill.js +0 -6
- package/dist/output/text/enable-disable.js +0 -8
- package/dist/output/text/history.js +0 -6
- package/dist/output/text/wiki.js +0 -16
- package/dist/registry/build-index.js +0 -386
- package/dist/scripts/migrate-storage.js +0 -19108
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
- package/dist/scripts/migrations/v16-to-v17.js +0 -141
- package/dist/setup/legacy-config.js +0 -106
- package/dist/storage/repositories/consolidation-repository.js +0 -38
- package/dist/storage/repositories/recombine-repository.js +0 -213
- package/dist/wiki/wiki-templates.js +0 -15
- package/dist/wiki/wiki.js +0 -1012
- package/dist/workflows/db.js +0 -215
- package/docs/data-and-telemetry.md +0 -226
- /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
- /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
|
@@ -4,46 +4,47 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { assertNever } from "../../core/assert.js";
|
|
7
|
+
import { parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
7
8
|
import { daysToMs } from "../../core/common.js";
|
|
8
|
-
import { loadConfig } from "../../core/config/config.js";
|
|
9
|
-
import { rethrowIfTestIsolationError } from "../../core/errors.js";
|
|
9
|
+
import { bundlesToSourceEntries, loadConfig } from "../../core/config/config.js";
|
|
10
|
+
import { ConfigError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
10
11
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
11
12
|
import { classifyImproveAction, foldDistillSkipped } from "../../core/improve-types.js";
|
|
13
|
+
import { resolveMutationTarget } from "../../core/mutation-target.js";
|
|
12
14
|
import { getDbPath, getStateDbPathInDataDir } from "../../core/paths.js";
|
|
15
|
+
import { redactSensitiveText } from "../../core/redaction.js";
|
|
13
16
|
import { openStateDatabase } from "../../core/state-db.js";
|
|
14
17
|
import { info, warn } from "../../core/warn.js";
|
|
15
|
-
import {
|
|
18
|
+
import { resolveWritable, resolveWriteTarget } from "../../core/write-source.js";
|
|
16
19
|
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
17
20
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
18
|
-
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
19
|
-
import {
|
|
21
|
+
import { resolveEntryContentDir, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
22
|
+
import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
|
|
23
|
+
import { materializeLlmRunnerConnection } from "../../integrations/agent/runner.js";
|
|
20
24
|
import { installLlmUsagePersistence } from "../../llm/usage-persist.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
25
|
+
import { isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "../../sources/providers/git.js";
|
|
26
|
+
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
27
|
+
import { getEntryCount } from "../../storage/repositories/index-entries-repository.js";
|
|
23
28
|
import { drainProposals } from "../proposal/drain.js";
|
|
24
29
|
import { resolveDrainPolicy } from "../proposal/drain-policies.js";
|
|
30
|
+
import { describeGatedLanes, isAutonomyLaneAllowed } from "./autonomy-gate.js";
|
|
25
31
|
import { akmDistill } from "./distill.js";
|
|
26
32
|
// Eligibility / candidate-selection predicates live in ./eligibility.
|
|
27
|
-
import { buildLatestProposalTsMap, collectEligibleRefs, memoryCleanupParentRef, resolveImproveScope, shouldAnalyzeMemoryCleanup, } from "./eligibility.js";
|
|
33
|
+
import { buildLatestProposalTsMap, collectEligibleRefs, collectEligibleRefsReadOnly, memoryCleanupParentRef, resolveImproveScope, shouldAnalyzeMemoryCleanup, } from "./eligibility.js";
|
|
28
34
|
import { countEvalCases } from "./eval-cases.js";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
// resetHeldProcessLocks is re-exported (the test seam imports it from here).
|
|
32
|
-
import { PROCESS_LOCK_DEFS, processLockPath, releaseAllProcessLocks, releaseHeldLocksIfOwned, releaseProcessLock, tryAcquireProcessLock, withOptionalProcessLock, } from "./locks.js";
|
|
35
|
+
import { resolveImprovePlan } from "./improve-strategies.js";
|
|
36
|
+
import { improveLockPath, MIN_IMPROVE_LOCK_STALE_MS, releaseImproveLock, tryAcquireImproveLock } from "./locks.js";
|
|
33
37
|
// The cycle loop / post-loop / maintenance stages live in ./loop-stages.
|
|
34
38
|
import { runImproveLoopStage, runImprovePostLoopStage } from "./loop-stages.js";
|
|
35
|
-
import { detectAndWriteContradictions } from "./memory/memory-contradiction-detect.js";
|
|
36
39
|
import { analyzeMemoryCleanup } from "./memory/memory-improve.js";
|
|
37
40
|
// The pre-loop preparation pipeline lives in ./preparation.
|
|
38
41
|
import { runImprovePreparationStage } from "./preparation.js";
|
|
39
42
|
import { DEFAULT_DUE_DAYS, filterProactiveDue } from "./proactive-maintenance.js";
|
|
40
43
|
import { akmReflect } from "./reflect.js";
|
|
44
|
+
import { createRunContext } from "./run-context.js";
|
|
41
45
|
import { errMessage } from "./shared.js";
|
|
42
|
-
export { resetHeldProcessLocks } from "./locks.js";
|
|
43
46
|
// Re-exported from ./loop-stages for test importers (improve-db-locking).
|
|
44
47
|
export { runImproveMaintenancePasses } from "./loop-stages.js";
|
|
45
|
-
// Re-exported from ./preparation so existing importers (tests, callers) resolve.
|
|
46
|
-
export { maybeAutoTuneThreshold } from "./preparation.js";
|
|
47
48
|
export function renderSyncCommitMessage(template, result, nowMs) {
|
|
48
49
|
const iso = new Date(nowMs).toISOString();
|
|
49
50
|
const tokens = {
|
|
@@ -57,7 +58,7 @@ export function renderSyncCommitMessage(template, result, nowMs) {
|
|
|
57
58
|
triage_rejected: String(result.triage?.rejected ?? 0),
|
|
58
59
|
runId: result.runId ?? "",
|
|
59
60
|
};
|
|
60
|
-
return template.replace(/\{(\w+)\}/g, (match, key) =>
|
|
61
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => tokens[key] ?? match);
|
|
61
62
|
}
|
|
62
63
|
export function armBudgetWatchdog(budgetMs, controller, deps) {
|
|
63
64
|
const setTimeoutFn = deps?.setTimeoutFn ?? setTimeout;
|
|
@@ -84,6 +85,234 @@ export function armBudgetWatchdog(budgetMs, controller, deps) {
|
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
export async function akmImprove(options = {}) {
|
|
88
|
+
const setup = resolveImproveRunSetup(options);
|
|
89
|
+
options = setup.options;
|
|
90
|
+
const { budgetMs, budgetAbortController, scope, selectedStrategy, syncRepoDir, resolvedStateDbPath, resolvedLockPath, lockStaleAfterMs, } = setup;
|
|
91
|
+
let clearBudgetTimer = () => { };
|
|
92
|
+
let initialGitPaths = new Set();
|
|
93
|
+
const preEnsureCleanupWarnings = [];
|
|
94
|
+
// Assigned by runIndexAndCollect() (closure) so TS cannot prove definite
|
|
95
|
+
// assignment — seed with empty values; the runIndexAndCollect() call below
|
|
96
|
+
// always overwrites them before any read.
|
|
97
|
+
let plannedRefs = [];
|
|
98
|
+
let memorySummary = { eligible: 0, derived: 0 };
|
|
99
|
+
let strategyFilteredRefs = [];
|
|
100
|
+
let memoryCleanupPlan;
|
|
101
|
+
let autonomyGatedDirectLanes = [];
|
|
102
|
+
let guidance;
|
|
103
|
+
let triageDrain;
|
|
104
|
+
let improveLockOwnership;
|
|
105
|
+
let exitBackstop;
|
|
106
|
+
// I1: open a single state.db connection for the main improve run so all
|
|
107
|
+
// appendEvent calls reuse one handle instead of open/migrate/close per call.
|
|
108
|
+
let eventsDb;
|
|
109
|
+
// Start boundary-pinned for prepass telemetry, then replace this binding with
|
|
110
|
+
// the long-lived handle after the prepass. The usage sink resolves it per
|
|
111
|
+
// append, so one owner and counter span both lifecycle phases.
|
|
112
|
+
let eventsCtx = { dbPath: resolvedStateDbPath };
|
|
113
|
+
let disposeLlmUsageSink = () => { };
|
|
114
|
+
const releaseRunLock = () => {
|
|
115
|
+
const ownership = improveLockOwnership;
|
|
116
|
+
if (!ownership)
|
|
117
|
+
return;
|
|
118
|
+
improveLockOwnership = undefined;
|
|
119
|
+
try {
|
|
120
|
+
releaseImproveLock(ownership);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Best-effort cleanup. Exact ownership prevents deleting a successor.
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const commitStashBatch = makeCommitStashBatch({
|
|
127
|
+
run: setup,
|
|
128
|
+
getInitialGitPaths: () => initialGitPaths,
|
|
129
|
+
// Captured via getter: the live `eventsCtx` binding is reassigned after the
|
|
130
|
+
// prepass, and the catch-path sync must write through the current context.
|
|
131
|
+
getEventsCtx: () => eventsCtx,
|
|
132
|
+
});
|
|
133
|
+
if (!options.dryRun) {
|
|
134
|
+
const remainingBudget = budgetAbortController.signal.remainingBudgetMs;
|
|
135
|
+
clearBudgetTimer = armBudgetWatchdog(Math.max(1, remainingBudget ?? budgetMs), budgetAbortController);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
if (!options.dryRun) {
|
|
139
|
+
const acquisition = tryAcquireImproveLock(resolvedLockPath, lockStaleAfterMs, options.skipIfLocked, {
|
|
140
|
+
// R25: C2 boundary-pinned path — the long-lived handle doesn't exist yet.
|
|
141
|
+
dbPath: resolvedStateDbPath,
|
|
142
|
+
});
|
|
143
|
+
if (acquisition.state === "skipped") {
|
|
144
|
+
clearBudgetTimer();
|
|
145
|
+
return buildLockSkippedResult(selectedStrategy.name, scope, options.runId);
|
|
146
|
+
}
|
|
147
|
+
improveLockOwnership = acquisition.ownership;
|
|
148
|
+
disposeLlmUsageSink = installLlmUsagePersistence(() => eventsCtx);
|
|
149
|
+
exitBackstop = releaseRunLock;
|
|
150
|
+
process.on("exit", exitBackstop);
|
|
151
|
+
initialGitPaths =
|
|
152
|
+
syncRepoDir && isGitBackedStash(syncRepoDir) ? new Set(listGitChangedPaths(syncRepoDir)) : new Set();
|
|
153
|
+
// Drain the standing proposal backlog before indexing so fresh proposal
|
|
154
|
+
// generation sees promotions from this same serialized run.
|
|
155
|
+
triageDrain = await runTriagePrePass(setup);
|
|
156
|
+
}
|
|
157
|
+
// #339 fix: ensureIndex MUST run BEFORE collectEligibleRefs (inside the
|
|
158
|
+
// indexAndCollect helper).
|
|
159
|
+
const collected = await indexAndCollect({ run: setup, signal: budgetAbortController.signal });
|
|
160
|
+
plannedRefs = collected.plannedRefs;
|
|
161
|
+
memorySummary = collected.memorySummary;
|
|
162
|
+
strategyFilteredRefs = collected.strategyFilteredRefs;
|
|
163
|
+
memoryCleanupPlan = collected.memoryCleanupPlan;
|
|
164
|
+
autonomyGatedDirectLanes = collected.autonomyGatedDirectLanes;
|
|
165
|
+
guidance = collected.guidance;
|
|
166
|
+
preEnsureCleanupWarnings.push(...collected.warnings);
|
|
167
|
+
if (options.dryRun) {
|
|
168
|
+
const result = buildDryRunResult(setup, collected);
|
|
169
|
+
clearBudgetTimer();
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
// If the live prepass fails, emit its summary and clear the owning sink
|
|
175
|
+
// before any run teardown. The disposer is idempotent with the main finalizer.
|
|
176
|
+
disposeLlmUsageSink();
|
|
177
|
+
clearBudgetTimer();
|
|
178
|
+
if (exitBackstop) {
|
|
179
|
+
process.removeListener("exit", exitBackstop);
|
|
180
|
+
exitBackstop = undefined;
|
|
181
|
+
}
|
|
182
|
+
releaseRunLock();
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
try {
|
|
187
|
+
eventsDb = openStateDatabase(resolvedStateDbPath);
|
|
188
|
+
eventsCtx = { db: eventsDb };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
rethrowIfTestIsolationError(err);
|
|
192
|
+
// If we cannot open state.db up-front, fall back to per-call opens — but
|
|
193
|
+
// still pinned to the boundary-resolved path, never a live env re-read.
|
|
194
|
+
eventsCtx = { dbPath: resolvedStateDbPath };
|
|
195
|
+
}
|
|
196
|
+
// WI-9.10: construct the run's RunContext here — the first point after
|
|
197
|
+
// run-setup where config/stashDir/eventsCtx/proposalsCtx/sourceRun/dryRun
|
|
198
|
+
// are all in hand. See buildImproveRunContext for exactly which
|
|
199
|
+
// already-resolved values back each field.
|
|
200
|
+
const ctx = buildImproveRunContext(setup, eventsCtx);
|
|
201
|
+
const seq = await runImproveStageSequence({
|
|
202
|
+
run: setup,
|
|
203
|
+
strategyFilteredRefs,
|
|
204
|
+
plannedRefs,
|
|
205
|
+
memoryCleanupPlan,
|
|
206
|
+
autonomyGatedDirectLanes,
|
|
207
|
+
memorySummary,
|
|
208
|
+
preEnsureCleanupWarnings,
|
|
209
|
+
eventsCtx,
|
|
210
|
+
ctx,
|
|
211
|
+
});
|
|
212
|
+
const result = finalizeImproveResult({
|
|
213
|
+
run: setup,
|
|
214
|
+
seq,
|
|
215
|
+
guidance,
|
|
216
|
+
memorySummary,
|
|
217
|
+
memoryCleanupPlan,
|
|
218
|
+
strategyFilteredRefs,
|
|
219
|
+
triageDrain,
|
|
220
|
+
eventsCtx,
|
|
221
|
+
});
|
|
222
|
+
// End-of-run BATCH auto-sync — the converged commit. Recognition is
|
|
223
|
+
// decoupled from the per-write path (see write-source.ts case-3): the primary
|
|
224
|
+
// stash writes as a filesystem source during the run, then is committed via
|
|
225
|
+
// the same `saveGitStash` that `akm sync` calls. The gating (git-backed
|
|
226
|
+
// primary stash, sync not disabled) and the NON-FATAL guarantee now live in
|
|
227
|
+
// `commitStashBatch` (#662); the inter-cycle and catch-path calls reuse it.
|
|
228
|
+
// dry-run already returned above, so this always runs on a completed live
|
|
229
|
+
// run. `result.sync` reflects this final commit (for a one-cycle run it is
|
|
230
|
+
// the only commit; for a multi-cycle run the earlier cycles were banked by
|
|
231
|
+
// the inter-cycle calls and this records the last batch). `result` carries
|
|
232
|
+
// the full `{accepted}`/`{refs}`/`{triage_*}` token data for the message.
|
|
233
|
+
result.sync = commitStashBatch(result);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
recordImproveFailure(err, setup, eventsCtx);
|
|
238
|
+
// #662 crash/abort safety net: commit whatever this run already wrote to the
|
|
239
|
+
// primary stash BEFORE rethrowing, so an interrupted run (mid-cycle crash or
|
|
240
|
+
// a cooperative budget abort that surfaces as a throw) does not leave its
|
|
241
|
+
// writes uncommitted until a later clean run sweeps them up. Best-effort —
|
|
242
|
+
// `commitStashBatch` swallows its own errors and no-ops a clean tree, so this
|
|
243
|
+
// never masks or supersedes the original failure being rethrown below.
|
|
244
|
+
commitStashBatch({ scope, plannedRefs, runId: options.runId });
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
// #576: clear the per-run LLM usage sink BEFORE closing `eventsDb` below, so
|
|
249
|
+
// no late sink invocation can write through a closed handle.
|
|
250
|
+
disposeLlmUsageSink();
|
|
251
|
+
// O-1 (#364): Clear the budget abort timer so it does not keep the event
|
|
252
|
+
// loop alive after the run completes.
|
|
253
|
+
clearBudgetTimer();
|
|
254
|
+
// Drop ONLY our own process.exit backstop so it does not fire later (or
|
|
255
|
+
// accumulate across repeated in-process calls). Must NOT use
|
|
256
|
+
// removeAllListeners("exit") here: in the in-process model (tests and
|
|
257
|
+
// programmatic callers import cli.ts) that would silently destroy exit
|
|
258
|
+
// handlers owned by the host or other commands.
|
|
259
|
+
if (exitBackstop) {
|
|
260
|
+
process.removeListener("exit", exitBackstop);
|
|
261
|
+
exitBackstop = undefined;
|
|
262
|
+
}
|
|
263
|
+
releaseRunLock();
|
|
264
|
+
// I1: close the long-lived state.db connection opened at the top of the run.
|
|
265
|
+
try {
|
|
266
|
+
eventsDb?.close();
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// ignore — DB may already be closed
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** Resolve a dry-run inspection source without adapting it into a write target. */
|
|
274
|
+
function resolveImproveReadSource(config, scopedRef, explicitTarget, fallbackStashDir) {
|
|
275
|
+
if (scopedRef?.origin && explicitTarget && scopedRef.origin !== explicitTarget) {
|
|
276
|
+
throw new UsageError(`Qualified ref bundle "${scopedRef.origin}" conflicts with --target "${explicitTarget}".`, "INVALID_FLAG_VALUE", `Drop --target or use --target ${scopedRef.origin}.`);
|
|
277
|
+
}
|
|
278
|
+
const selector = scopedRef?.origin ?? explicitTarget ?? config.defaultWriteTarget;
|
|
279
|
+
if (!selector && fallbackStashDir) {
|
|
280
|
+
return { source: { name: "stash", path: fallbackStashDir } };
|
|
281
|
+
}
|
|
282
|
+
const configuredSelector = selector ?? config.defaultBundle;
|
|
283
|
+
if (configuredSelector) {
|
|
284
|
+
const entry = bundlesToSourceEntries(config)?.find((source) => source.name === configuredSelector);
|
|
285
|
+
if (!entry) {
|
|
286
|
+
throw new UsageError(`No source named "${configuredSelector}" is configured. Run \`akm bundle list\` to see available sources.`, "INVALID_FLAG_VALUE");
|
|
287
|
+
}
|
|
288
|
+
const sourcePath = resolveEntryContentDir(entry);
|
|
289
|
+
if (!sourcePath) {
|
|
290
|
+
throw new ConfigError(`Source "${configuredSelector}" has no resolvable on-disk path; improve cannot inspect this entry.`, "INVALID_CONFIG_FILE");
|
|
291
|
+
}
|
|
292
|
+
return { selector: configuredSelector, source: { name: configuredSelector, path: sourcePath } };
|
|
293
|
+
}
|
|
294
|
+
const implicit = resolveSourceEntries(undefined, config)[0];
|
|
295
|
+
if (!implicit) {
|
|
296
|
+
throw new ConfigError("no source configured; run `akm bundle create`", "STASH_DIR_NOT_FOUND");
|
|
297
|
+
}
|
|
298
|
+
return { source: { name: implicit.registryId ?? "stash", path: implicit.path } };
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Run-setup: budget/watchdog plumbing, scope + seam resolution, the invocation
|
|
302
|
+
* plan, write-target resolution, the profile-defaulted options rebuild, and
|
|
303
|
+
* the boundary-pinned state.db/lock paths. Fully SYNCHRONOUS — the C2 boundary
|
|
304
|
+
* snapshot (resolvedStateDbPath) must be taken before the first await of the
|
|
305
|
+
* run.
|
|
306
|
+
*/
|
|
307
|
+
function resolveImproveRunSetup(options) {
|
|
308
|
+
const startMs = Date.now();
|
|
309
|
+
const budgetMs = options.timeoutMs ?? 2 * 60 * 60 * 1000;
|
|
310
|
+
const budgetAbortController = new AbortController();
|
|
311
|
+
Object.defineProperty(budgetAbortController.signal, "remainingBudgetMs", {
|
|
312
|
+
get: () => Math.max(0, budgetMs - (Date.now() - startMs)),
|
|
313
|
+
enumerable: false,
|
|
314
|
+
configurable: true,
|
|
315
|
+
});
|
|
87
316
|
const scope = resolveImproveScope(options.scope);
|
|
88
317
|
const reflectFn = options.reflectFn ?? akmReflect;
|
|
89
318
|
const distillFn = options.distillFn ?? akmDistill;
|
|
@@ -91,27 +320,67 @@ export async function akmImprove(options = {}) {
|
|
|
91
320
|
const reindexFn = options.reindexFn ?? akmIndex;
|
|
92
321
|
const drainProposalsFn = options.drainProposalsFn ?? drainProposals;
|
|
93
322
|
// #616 multi-cycle test seams. Default to the real module-local fns.
|
|
94
|
-
const collectEligibleRefsImpl = options.collectEligibleRefsFn ?? collectEligibleRefs;
|
|
323
|
+
const collectEligibleRefsImpl = options.collectEligibleRefsFn ?? (options.dryRun ? collectEligibleRefsReadOnly : collectEligibleRefs);
|
|
95
324
|
const runImprovePreparationStageImpl = options.runImprovePreparationStageFn ?? runImprovePreparationStage;
|
|
96
325
|
const runImproveLoopStageImpl = options.runImproveLoopStageFn ?? runImproveLoopStage;
|
|
97
326
|
const runImprovePostLoopStageImpl = options.runImprovePostLoopStageFn ?? runImprovePostLoopStage;
|
|
98
327
|
// Resolve the improve profile for this run. Profile drives type filtering,
|
|
99
|
-
// process gating, and default
|
|
328
|
+
// process gating, and the default limit value.
|
|
100
329
|
const _earlyConfig = options.config ?? loadConfig();
|
|
101
|
-
const
|
|
330
|
+
const resolvedPlan = options.resolvedPlan ??
|
|
331
|
+
resolveImprovePlan(options.strategy, _earlyConfig, {
|
|
332
|
+
repairValidationFailures: options.repairValidationFailures,
|
|
333
|
+
});
|
|
334
|
+
const selectedStrategy = resolvedPlan.strategy;
|
|
335
|
+
const improveSensitiveValues = collectEngineCredentialValues(_earlyConfig);
|
|
336
|
+
const improveProfile = selectedStrategy.config;
|
|
337
|
+
const scopedRef = scope.mode === "ref" && scope.value ? parseRefInput(scope.value) : undefined;
|
|
338
|
+
const readSource = options.dryRun
|
|
339
|
+
? options.writeTarget
|
|
340
|
+
? { selector: options.writeTarget.selector, source: options.writeTarget.source }
|
|
341
|
+
: resolveImproveReadSource(_earlyConfig, scopedRef, options.target, options.stashDir)
|
|
342
|
+
: undefined;
|
|
343
|
+
const writeTarget = options.dryRun
|
|
344
|
+
? undefined
|
|
345
|
+
: scopedRef?.origin
|
|
346
|
+
? resolveMutationTarget(_earlyConfig, scopedRef, options.writeTarget?.source.name ?? options.target).target
|
|
347
|
+
: (options.writeTarget ??
|
|
348
|
+
(options.target || _earlyConfig.defaultWriteTarget || !options.stashDir
|
|
349
|
+
? resolveWriteTarget(_earlyConfig, options.target)
|
|
350
|
+
: {
|
|
351
|
+
source: { kind: "filesystem", name: "stash", path: options.stashDir },
|
|
352
|
+
config: {
|
|
353
|
+
type: "filesystem",
|
|
354
|
+
name: "stash",
|
|
355
|
+
path: options.stashDir,
|
|
356
|
+
writable: true,
|
|
357
|
+
},
|
|
358
|
+
}));
|
|
359
|
+
const selectedSource = writeTarget?.source ?? readSource?.source;
|
|
360
|
+
if (!selectedSource)
|
|
361
|
+
throw new ConfigError("improve could not resolve a source", "STASH_DIR_NOT_FOUND");
|
|
362
|
+
const selectedSelector = writeTarget?.selector ?? readSource?.selector;
|
|
102
363
|
// Apply profile defaults — CLI flags take precedence over profile defaults.
|
|
103
364
|
// Rebuild options with effective values so all downstream stage functions
|
|
104
365
|
// automatically pick up the profile-driven defaults.
|
|
105
366
|
options = {
|
|
106
367
|
...options,
|
|
107
|
-
|
|
368
|
+
// Pin nested calls and quality gates to the same config snapshot as the
|
|
369
|
+
// invocation plan. They must never reload a changed config mid-run.
|
|
370
|
+
config: _earlyConfig,
|
|
371
|
+
target: selectedSelector,
|
|
372
|
+
sourceName: selectedSource.name,
|
|
373
|
+
...(writeTarget ? { writeTarget } : {}),
|
|
374
|
+
stashDir: selectedSource.path,
|
|
375
|
+
consolidateOptions: {
|
|
376
|
+
...options.consolidateOptions,
|
|
377
|
+
target: selectedSelector,
|
|
378
|
+
...(writeTarget ? { writeTarget } : {}),
|
|
379
|
+
},
|
|
108
380
|
// Profile-level limit, then process-level reflect.limit as fallback.
|
|
109
381
|
// CLI --limit takes precedence over both.
|
|
110
382
|
limit: options.limit ?? improveProfile?.processes?.reflect?.limit ?? improveProfile.limit,
|
|
111
383
|
};
|
|
112
|
-
// #616 — bounded multi-cycle phasing. CLI/programmatic override wins over
|
|
113
|
-
// profile.maxCycles; default 1 => single pass (byte-identical to pre-#616).
|
|
114
|
-
const maxCycles = Math.max(1, Math.trunc(options.maxCycles ?? improveProfile.maxCycles ?? 1));
|
|
115
384
|
let primaryStashDir;
|
|
116
385
|
try {
|
|
117
386
|
primaryStashDir = resolveSourceEntries(options.stashDir)[0]?.path;
|
|
@@ -119,6 +388,7 @@ export async function akmImprove(options = {}) {
|
|
|
119
388
|
catch {
|
|
120
389
|
primaryStashDir = undefined;
|
|
121
390
|
}
|
|
391
|
+
const syncRepoDir = writeTarget?.source.repoPath ?? primaryStashDir;
|
|
122
392
|
// C2 (#553/#554/#499): resolve the state.db path ONCE, synchronously, at the
|
|
123
393
|
// command boundary — before the first `await` below. Every state.db open in
|
|
124
394
|
// this run (`openStateDatabase`, every default-path `appendEvent`) is pinned
|
|
@@ -128,281 +398,303 @@ export async function akmImprove(options = {}) {
|
|
|
128
398
|
// timeout root cause). Because beforeEach runs synchronously, env is still the
|
|
129
399
|
// calling test's own at this point; we capture it before yielding the loop.
|
|
130
400
|
const resolvedStateDbPath = getStateDbPathInDataDir();
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// WS-4 change: thresholds are now PER PHASE. The old single global mutation
|
|
135
|
-
// of `options.autoAccept` is retired — it caused every phase to share one
|
|
136
|
-
// calibration signal, so a reflect-dominated run could tighten the consolidate
|
|
137
|
-
// gate (or vice-versa). Instead:
|
|
138
|
-
// - Each `makeGateConfig` call reads the phase's stored threshold from
|
|
139
|
-
// state.db (Migration 012) and uses it as `phaseThreshold`, overriding
|
|
140
|
-
// the `globalThreshold` (= options.autoAccept) for that phase.
|
|
141
|
-
// - Per-phase `maybeAutoTuneThreshold` calls fire AFTER each phase's gate
|
|
142
|
-
// has run and persist the new threshold to state.db for the NEXT run.
|
|
143
|
-
// - `options.autoAccept` stays unchanged (it is the operator-supplied
|
|
144
|
-
// baseline, not a mutable run-time state).
|
|
145
|
-
//
|
|
146
|
-
// The global tune call is intentionally removed here. See per-phase calls
|
|
147
|
-
// below (near each makeGateConfig / runAutoAcceptGate block).
|
|
148
|
-
// #607 Lock decomposition: three per-process locks replace the single
|
|
149
|
-
// `improve.lock`. Each process acquires only the lock(s) it needs, so
|
|
150
|
-
// quick-shredder consolidate can run alongside daily reflect+distill.
|
|
151
|
-
//
|
|
152
|
-
// consolidate.lock — protects consolidate + memoryInference + graphExtraction (index.db writers)
|
|
153
|
-
// reflect-distill.lock — protects reflect + distill (state.db proposal writers)
|
|
154
|
-
// triage.lock — protects triage pre-pass (state.db proposal promotions)
|
|
155
|
-
//
|
|
156
|
-
// Lock base directory — same `.akm/` under the primary stash dir.
|
|
401
|
+
// One conservative run lock protects the complete mutation window, including
|
|
402
|
+
// triage, indexing, proposal work, maintenance, and final stash sync.
|
|
157
403
|
const lockBaseDir = primaryStashDir ? path.join(primaryStashDir, ".akm") : path.join(options.stashDir ?? ".", ".akm");
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
404
|
+
const resolvedLockPath = improveLockPath(lockBaseDir);
|
|
405
|
+
const lockStaleAfterMs = Math.max(MIN_IMPROVE_LOCK_STALE_MS, budgetMs + 10 * 60 * 1000);
|
|
406
|
+
const effectiveSync = { ...improveProfile.sync, ...options.sync };
|
|
407
|
+
return {
|
|
408
|
+
startMs,
|
|
409
|
+
budgetMs,
|
|
410
|
+
budgetAbortController,
|
|
411
|
+
scope,
|
|
412
|
+
reflectFn,
|
|
413
|
+
distillFn,
|
|
414
|
+
ensureIndexFn,
|
|
415
|
+
reindexFn,
|
|
416
|
+
drainProposalsFn,
|
|
417
|
+
collectEligibleRefsImpl,
|
|
418
|
+
runImprovePreparationStageImpl,
|
|
419
|
+
runImproveLoopStageImpl,
|
|
420
|
+
runImprovePostLoopStageImpl,
|
|
421
|
+
_earlyConfig,
|
|
422
|
+
resolvedPlan,
|
|
423
|
+
selectedStrategy,
|
|
424
|
+
improveSensitiveValues,
|
|
425
|
+
improveProfile,
|
|
426
|
+
writeTarget,
|
|
427
|
+
options,
|
|
428
|
+
primaryStashDir,
|
|
429
|
+
syncRepoDir,
|
|
430
|
+
resolvedStateDbPath,
|
|
431
|
+
resolvedLockPath,
|
|
432
|
+
lockStaleAfterMs,
|
|
433
|
+
effectiveSync,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* WI-9.10: build the run's {@link RunContext} purely from values
|
|
438
|
+
* `resolveImproveRunSetup` and the long-lived state.db open already
|
|
439
|
+
* resolved — no second config load, no new db handle. Called once, right
|
|
440
|
+
* after `eventsCtx` is resolved (the last of {config, stashDir, eventsCtx,
|
|
441
|
+
* proposalsCtx, sourceRun, dryRun} to become available in `akmImprove`).
|
|
442
|
+
*/
|
|
443
|
+
function buildImproveRunContext(run, eventsCtx) {
|
|
444
|
+
return createRunContext({
|
|
445
|
+
config: run._earlyConfig,
|
|
446
|
+
// primaryStashDir can fail to resolve (rare); "." mirrors the existing
|
|
447
|
+
// lockBaseDir fallback in resolveImproveRunSetup for the same case. This
|
|
448
|
+
// is a BEST-EFFORT value for the required RunContext field only — the
|
|
449
|
+
// loop stage reads `ImproveLoopState.primaryStashDir` (the honest
|
|
450
|
+
// optional), so its `if (primaryStashDir)` guards still skip on the
|
|
451
|
+
// unresolvable path; no RunContext consumer reads `ctx.stashDir` there.
|
|
452
|
+
stashDir: run.primaryStashDir ?? run.options.stashDir ?? ".",
|
|
453
|
+
eventsCtx,
|
|
454
|
+
// ProposalsContext threads only a dbPath seam (D14: no db handle is
|
|
455
|
+
// threaded in), mirroring eventsCtx's own dbPath-only fallback shape.
|
|
456
|
+
// Not yet wired into any proposal call site this stage — verb-level
|
|
457
|
+
// RunContext adoption (reflect/distill/extract/consolidate) is later.
|
|
458
|
+
proposalsCtx: { dbPath: run.resolvedStateDbPath },
|
|
459
|
+
// Representative connection for this run: reflect is the loop's primary
|
|
460
|
+
// LLM-driving process. Mirrors the existing
|
|
461
|
+
// `runner ? materializeLlmRunnerConnection(runner) : null` pattern
|
|
462
|
+
// already used for the consolidate process elsewhere (contradiction pass).
|
|
463
|
+
getLlmConfig: () => {
|
|
464
|
+
const runner = run.resolvedPlan.processes.reflect.runner;
|
|
465
|
+
return runner ? materializeLlmRunnerConnection(runner) : null;
|
|
466
|
+
},
|
|
467
|
+
sourceRun: run.options.runId ?? `improve-${run.startMs}`,
|
|
468
|
+
// Always false here: callers only reach this point past the dry-run
|
|
469
|
+
// early return in akmImprove.
|
|
470
|
+
dryRun: run.options.dryRun ?? false,
|
|
471
|
+
// Same AbortSignal instance armBudgetWatchdog stamped a live
|
|
472
|
+
// `remainingBudgetMs` getter onto (#616) — identity preserved so
|
|
473
|
+
// `ctx.signal.remainingBudgetMs` resolves correctly for every consumer.
|
|
474
|
+
signal: run.budgetAbortController.signal,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/** The catch-path improve_failed audit event (D3), redacted. */
|
|
478
|
+
function recordImproveFailure(err, run, eventsCtx) {
|
|
479
|
+
const { scope, selectedStrategy, improveSensitiveValues, startMs } = run;
|
|
480
|
+
// D3: emit improve_failed on unexpected crash so dashboards can detect failures.
|
|
481
|
+
appendEvent({
|
|
482
|
+
eventType: "improve_failed",
|
|
483
|
+
ref: scope.mode === "ref" ? scope.value : `improve:${scope.mode}:${scope.value ?? "all"}`,
|
|
484
|
+
metadata: {
|
|
485
|
+
strategy: selectedStrategy.name,
|
|
486
|
+
error: redactSensitiveText(errMessage(err), improveSensitiveValues),
|
|
487
|
+
durationMs: Date.now() - startMs,
|
|
488
|
+
},
|
|
489
|
+
}, eventsCtx);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* ensureIndex + collectEligibleRefs + the contradiction pre-pass + the
|
|
493
|
+
* memory-cleanup recompute (#339 ordering). Formerly the runIndexAndCollect
|
|
494
|
+
* closure mutating outer `let`s; now a pure pass returning its results.
|
|
495
|
+
*/
|
|
496
|
+
async function indexAndCollect(args) {
|
|
497
|
+
const { signal } = args;
|
|
498
|
+
const { scope, options, primaryStashDir, improveProfile, _earlyConfig, ensureIndexFn, collectEligibleRefsImpl } = args.run;
|
|
499
|
+
const warnings = [];
|
|
162
500
|
let plannedRefs = [];
|
|
163
501
|
let memorySummary = { eligible: 0, derived: 0 };
|
|
164
|
-
let
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// returned plannedRefs=[] and the improve loop no-op'd. Hoisting the call
|
|
180
|
-
// here repopulates the index first so the subsequent query sees fresh data.
|
|
181
|
-
if (primaryStashDir) {
|
|
182
|
-
// Probe pre-ensureIndex entry count to drive the loud-fail warning below.
|
|
183
|
-
// Best-effort: a missing DB / unreadable schema is the fresh-install case
|
|
184
|
-
// and not a bug — we silently skip the probe.
|
|
185
|
-
let preEnsureEntryCount;
|
|
186
|
-
try {
|
|
187
|
-
const dbPath = getDbPath();
|
|
188
|
-
if (fs.existsSync(dbPath)) {
|
|
189
|
-
const probeDb = openExistingDatabase();
|
|
190
|
-
try {
|
|
191
|
-
preEnsureEntryCount = getEntryCount(probeDb);
|
|
192
|
-
}
|
|
193
|
-
finally {
|
|
194
|
-
closeDatabase(probeDb);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
rethrowIfTestIsolationError(err);
|
|
200
|
-
// best-effort; leave preEnsureEntryCount undefined
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
await ensureIndexFn(primaryStashDir, { mode: "blocking" });
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
preEnsureCleanupWarnings.push(`ensureIndex failed: ${errMessage(err)}`);
|
|
207
|
-
}
|
|
208
|
-
// #339 loud-fail: if the index was empty pre-ensureIndex but is now
|
|
209
|
-
// populated, a version-upgrade-triggered rebuild just happened. Surface
|
|
210
|
-
// that on stderr so the improve run is not silently masked by stale
|
|
211
|
-
// index state. Zero-before AND zero-after is the empty-stash case and
|
|
212
|
-
// is intentionally not warned (not a bug).
|
|
213
|
-
if (preEnsureEntryCount === 0) {
|
|
502
|
+
let strategyFilteredRefs = [];
|
|
503
|
+
// #339 fix: ensureIndex MUST run BEFORE collectEligibleRefs. The eligible-ref
|
|
504
|
+
// query reads the `entries` table; if a DB version upgrade just dropped that
|
|
505
|
+
// table (or the index is otherwise empty), the prior run order silently
|
|
506
|
+
// returned plannedRefs=[] and the improve loop no-op'd. Hoisting the call
|
|
507
|
+
// here repopulates the index first so the subsequent query sees fresh data.
|
|
508
|
+
if (primaryStashDir && !options.dryRun) {
|
|
509
|
+
// Probe pre-ensureIndex entry count to drive the loud-fail warning below.
|
|
510
|
+
// Best-effort: a missing DB / unreadable schema is the fresh-install case
|
|
511
|
+
// and not a bug — we silently skip the probe.
|
|
512
|
+
let preEnsureEntryCount;
|
|
513
|
+
try {
|
|
514
|
+
const dbPath = getDbPath();
|
|
515
|
+
if (fs.existsSync(dbPath)) {
|
|
516
|
+
const probeDb = openExistingDatabase();
|
|
214
517
|
try {
|
|
215
|
-
|
|
216
|
-
let postCount = 0;
|
|
217
|
-
try {
|
|
218
|
-
postCount = getEntryCount(probeDb);
|
|
219
|
-
}
|
|
220
|
-
finally {
|
|
221
|
-
closeDatabase(probeDb);
|
|
222
|
-
}
|
|
223
|
-
if (postCount > 0) {
|
|
224
|
-
warn("[improve] index was empty after DB version upgrade — repopulating before continuing");
|
|
225
|
-
}
|
|
518
|
+
preEnsureEntryCount = getEntryCount(probeDb);
|
|
226
519
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// best-effort
|
|
520
|
+
finally {
|
|
521
|
+
closeDatabase(probeDb);
|
|
230
522
|
}
|
|
231
523
|
}
|
|
232
524
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
525
|
+
catch (err) {
|
|
526
|
+
rethrowIfTestIsolationError(err);
|
|
527
|
+
// best-effort; leave preEnsureEntryCount undefined
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
await ensureIndexFn(primaryStashDir, { mode: "blocking", signal: signal });
|
|
531
|
+
}
|
|
532
|
+
catch (err) {
|
|
533
|
+
if (signal.aborted)
|
|
534
|
+
throw err;
|
|
535
|
+
warnings.push(`ensureIndex failed: ${errMessage(err)}`);
|
|
536
|
+
}
|
|
537
|
+
// #339 loud-fail: if the index was empty pre-ensureIndex but is now
|
|
538
|
+
// populated, a version-upgrade-triggered rebuild just happened. Surface
|
|
539
|
+
// that on stderr so the improve run is not silently masked by stale
|
|
540
|
+
// index state. Zero-before AND zero-after is the empty-stash case and
|
|
541
|
+
// is intentionally not warned (not a bug).
|
|
542
|
+
if (preEnsureEntryCount === 0) {
|
|
239
543
|
try {
|
|
240
|
-
|
|
241
|
-
|
|
544
|
+
const probeDb = openExistingDatabase();
|
|
545
|
+
let postCount = 0;
|
|
546
|
+
try {
|
|
547
|
+
postCount = getEntryCount(probeDb);
|
|
548
|
+
}
|
|
549
|
+
finally {
|
|
550
|
+
closeDatabase(probeDb);
|
|
551
|
+
}
|
|
552
|
+
if (postCount > 0) {
|
|
553
|
+
warn("[improve] index was empty after DB version upgrade — repopulating before continuing");
|
|
554
|
+
}
|
|
242
555
|
}
|
|
243
556
|
catch (err) {
|
|
244
|
-
|
|
245
|
-
|
|
557
|
+
rethrowIfTestIsolationError(err);
|
|
558
|
+
// best-effort
|
|
246
559
|
}
|
|
247
560
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
561
|
+
}
|
|
562
|
+
({
|
|
563
|
+
plannedRefs,
|
|
564
|
+
memorySummary,
|
|
565
|
+
strategyFilteredRefs = [],
|
|
566
|
+
} = await collectEligibleRefsImpl(scope, options.stashDir, improveProfile, _earlyConfig));
|
|
567
|
+
const cleanupParentRef = memoryCleanupParentRef(scope, options.stashDir);
|
|
568
|
+
// D8 — the two direct lanes share one eligibility predicate, which reads scope
|
|
569
|
+
// and eligible-memory count and no strategy flag at all. Compute it once: it
|
|
570
|
+
// decides whether each lane would have run, which is also what decides whether
|
|
571
|
+
// a gated lane is worth REPORTING. A lane that was never eligible was not
|
|
572
|
+
// suppressed by the gate, so claiming it was would be noise.
|
|
573
|
+
const cleanupEligible = shouldAnalyzeMemoryCleanup(scope, memorySummary.eligible, primaryStashDir);
|
|
574
|
+
// Cleanup remains independent of the disabled contradiction writer.
|
|
575
|
+
const memoryCleanupPlan = cleanupEligible
|
|
576
|
+
? analyzeMemoryCleanup(primaryStashDir, cleanupParentRef ? { parentRef: cleanupParentRef } : undefined)
|
|
577
|
+
: undefined;
|
|
578
|
+
const cleanupWouldMutate = Boolean(memoryCleanupPlan &&
|
|
579
|
+
(memoryCleanupPlan.pruneCandidates.length > 0 ||
|
|
580
|
+
memoryCleanupPlan.beliefStateTransitions.length > 0 ||
|
|
581
|
+
memoryCleanupPlan.relativeDateCandidates.length > 0));
|
|
582
|
+
const autonomyGatedDirectLanes = cleanupWouldMutate && !isAutonomyLaneAllowed("memoryCleanup", _earlyConfig) ? ["memoryCleanup"] : [];
|
|
583
|
+
const guidance = memorySummary.eligible > 0
|
|
584
|
+
? "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."
|
|
585
|
+
: undefined;
|
|
586
|
+
return {
|
|
587
|
+
plannedRefs,
|
|
588
|
+
memorySummary,
|
|
589
|
+
strategyFilteredRefs: strategyFilteredRefs ?? [],
|
|
590
|
+
memoryCleanupPlan,
|
|
591
|
+
autonomyGatedDirectLanes,
|
|
592
|
+
guidance,
|
|
593
|
+
warnings,
|
|
255
594
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
policy,
|
|
297
|
-
applyMode,
|
|
298
|
-
maxAccepts,
|
|
299
|
-
dryRun: false,
|
|
300
|
-
excludeIds: new Set(),
|
|
301
|
-
...(triageConfig?.maxDiffLines !== undefined ? { maxDiffLines: triageConfig.maxDiffLines } : {}),
|
|
302
|
-
judgment,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
catch (err) {
|
|
306
|
-
warn(`[improve] triage pre-pass failed (non-fatal): ${errMessage(err)}`);
|
|
307
|
-
}
|
|
308
|
-
finally {
|
|
309
|
-
releaseProcessLock(triageLPath);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
595
|
+
}
|
|
596
|
+
/** The P2 lock-skipped envelope (graceful `skipIfLocked` early return). Exported for unit tests. */
|
|
597
|
+
export function buildLockSkippedResult(strategyName, scope, runId) {
|
|
598
|
+
return {
|
|
599
|
+
schemaVersion: 2,
|
|
600
|
+
ok: true,
|
|
601
|
+
strategy: strategyName,
|
|
602
|
+
scope,
|
|
603
|
+
dryRun: false,
|
|
604
|
+
skipped: { reason: "lock-held" },
|
|
605
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
606
|
+
plannedRefs: [],
|
|
607
|
+
actions: [],
|
|
608
|
+
...(runId !== undefined ? { runId } : {}),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/** The P3 dry-run envelope (plan-only early return). Exported for unit tests. */
|
|
612
|
+
export function buildDryRunResult(run, collected) {
|
|
613
|
+
const { selectedStrategy, scope } = run;
|
|
614
|
+
const { guidance, memorySummary, memoryCleanupPlan, plannedRefs, strategyFilteredRefs } = collected;
|
|
615
|
+
return {
|
|
616
|
+
schemaVersion: 2,
|
|
617
|
+
ok: true,
|
|
618
|
+
strategy: selectedStrategy.name,
|
|
619
|
+
scope,
|
|
620
|
+
dryRun: true,
|
|
621
|
+
...(guidance ? { guidance } : {}),
|
|
622
|
+
memorySummary,
|
|
623
|
+
...(memoryCleanupPlan ? { memoryCleanup: shapeMemoryCleanup(memoryCleanupPlan) } : {}),
|
|
624
|
+
plannedRefs,
|
|
625
|
+
...(strategyFilteredRefs.length > 0 ? { strategyFilteredRefs } : {}),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
/** The triage drain pre-pass (non-fatal; single-ref scope never drains). */
|
|
629
|
+
async function runTriagePrePass(run) {
|
|
630
|
+
const { primaryStashDir, resolvedPlan, scope, options, improveProfile, drainProposalsFn } = run;
|
|
631
|
+
let triageDrain;
|
|
632
|
+
if (primaryStashDir && resolvedPlan.processes.triage.enabled) {
|
|
633
|
+
if (scope.mode === "ref") {
|
|
634
|
+
warn("[improve] triage pre-pass skipped (single-ref scope never drains the whole queue)");
|
|
314
635
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
636
|
+
else {
|
|
637
|
+
try {
|
|
638
|
+
const triageConfig = improveProfile.processes?.triage;
|
|
639
|
+
const policy = resolveDrainPolicy(triageConfig?.policy);
|
|
640
|
+
const applyMode = triageConfig?.applyMode ?? "queue";
|
|
641
|
+
const maxAccepts = triageConfig?.maxAcceptsPerRun ?? 25;
|
|
642
|
+
triageDrain = await drainProposalsFn({
|
|
643
|
+
stashDir: primaryStashDir,
|
|
644
|
+
...(options.target ? { target: options.target } : {}),
|
|
645
|
+
config: options.config,
|
|
646
|
+
policy,
|
|
647
|
+
applyMode,
|
|
648
|
+
maxAccepts,
|
|
649
|
+
dryRun: false,
|
|
650
|
+
excludeIds: new Set(),
|
|
651
|
+
...(triageConfig?.maxDiffLines !== undefined ? { maxDiffLines: triageConfig.maxDiffLines } : {}),
|
|
652
|
+
judgment: resolvedPlan.triageJudgment,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
catch (err) {
|
|
656
|
+
warn(`[improve] triage pre-pass failed (non-fatal): ${errMessage(err)}`);
|
|
657
|
+
}
|
|
332
658
|
}
|
|
333
659
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// Attach a live `remainingBudgetMs` getter to the signal so sub-callers
|
|
352
|
-
// (e.g. consolidate.ts cold-start budget estimation) can read the remaining
|
|
353
|
-
// wall-clock budget without needing an extra plumbing parameter. The property
|
|
354
|
-
// is computed at access time via a getter so it always reflects the actual
|
|
355
|
-
// elapsed time rather than a stale snapshot taken at arm time.
|
|
356
|
-
Object.defineProperty(budgetAbortController.signal, "remainingBudgetMs", {
|
|
357
|
-
get: () => Math.max(0, budgetMs - (Date.now() - startMs)),
|
|
358
|
-
enumerable: false,
|
|
359
|
-
configurable: true,
|
|
360
|
-
});
|
|
361
|
-
// Declared in the outer scope so the `finally` can clear the timer even if a
|
|
362
|
-
// throw occurs before/after it is armed. Defaults to a no-op until armed.
|
|
363
|
-
let clearBudgetTimer = () => { };
|
|
364
|
-
// I1: open a single state.db connection for the entire improve run so all
|
|
365
|
-
// appendEvent calls reuse one handle instead of open/migrate/close per call.
|
|
366
|
-
let eventsDb;
|
|
367
|
-
// `eventsCtx` is read by the main catch (improve_failed) and finally, so it
|
|
368
|
-
// lives in the outer scope. It is always assigned at the top of the try.
|
|
369
|
-
// Pinned to the boundary snapshot so the fallback per-call `appendEvent`
|
|
370
|
-
// opens (when the long-lived handle below fails to open) never re-read env.
|
|
371
|
-
let eventsCtx = { dbPath: resolvedStateDbPath };
|
|
372
|
-
// #576: clears the per-run LLM usage sink. Defaults to a no-op until the sink
|
|
373
|
-
// is installed inside the try; the `finally` always calls it.
|
|
374
|
-
let disposeLlmUsageSink = () => { };
|
|
375
|
-
// ── Crash-safe / incremental stash sync (#662) ──────────────────────────────
|
|
376
|
-
// The primary stash writes as a filesystem source DURING the run
|
|
377
|
-
// (write-source.ts case-3); those writes become a git commit only when this
|
|
378
|
-
// closure runs. Historically the only call site was a single BATCH commit at
|
|
379
|
-
// the very end of the happy path, so a run interrupted AFTER writing but
|
|
380
|
-
// BEFORE finishing — a mid-cycle crash, a budget abort, or an external
|
|
381
|
-
// SIGTERM/`process.exit` — left every write uncommitted until some LATER run
|
|
382
|
-
// happened to finish cleanly and swept the whole backlog up. We now call this
|
|
383
|
-
// from THREE places: between cycles (bank each completed cycle), at end-of-run
|
|
384
|
-
// (the converged commit), and from the catch path (commit what was written
|
|
385
|
-
// before the crash). That shrinks the worst-case loss from "the entire run" to
|
|
386
|
-
// "the in-flight cycle".
|
|
387
|
-
//
|
|
388
|
-
// Declared in the OUTER scope (not inside the try) so the catch block can reach
|
|
389
|
-
// it. Idempotent + NON-FATAL: `saveGitStash` short-circuits a clean working
|
|
390
|
-
// tree ("nothing to commit") and a thrown sync error is swallowed here, so a
|
|
391
|
-
// repeat call after a no-op cycle is cheap and a failed push never fails the
|
|
392
|
-
// run. Gated identically to the original end-of-run block (git-backed primary
|
|
393
|
-
// stash, sync not disabled). `eventsCtx` is captured by reference, so calls
|
|
394
|
-
// after the db-backed context is installed inside the try use the live handle.
|
|
395
|
-
const effectiveSync = { ...improveProfile.sync, ...options.sync };
|
|
396
|
-
const commitStashBatch = (messageContext) => {
|
|
397
|
-
if (!primaryStashDir || effectiveSync.enabled === false || !isGitBackedStash(primaryStashDir)) {
|
|
660
|
+
return triageDrain;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Crash-safe / incremental stash sync (#662) — see the factory-returned
|
|
664
|
+
* closure's original doc block: the primary stash writes as a filesystem
|
|
665
|
+
* source DURING the run; this commits them at end-of-run AND from the catch
|
|
666
|
+
* path. Idempotent + NON-FATAL. `getEventsCtx`/`getInitialGitPaths` are
|
|
667
|
+
* getters because both outer bindings are (re)assigned after this factory
|
|
668
|
+
* runs — the returned closure must observe the live values.
|
|
669
|
+
*/
|
|
670
|
+
function makeCommitStashBatch(deps) {
|
|
671
|
+
const { writeTarget, primaryStashDir, effectiveSync, options, _earlyConfig, improveProfile } = deps.run;
|
|
672
|
+
return (messageContext) => {
|
|
673
|
+
const eventsCtx = deps.getEventsCtx();
|
|
674
|
+
const initialGitPaths = deps.getInitialGitPaths();
|
|
675
|
+
const repoDir = writeTarget?.source.repoPath ?? primaryStashDir;
|
|
676
|
+
if (!primaryStashDir || !repoDir || effectiveSync.enabled === false || !isGitBackedStash(repoDir)) {
|
|
398
677
|
return undefined;
|
|
399
678
|
}
|
|
400
679
|
const saveGitStashFn = options.saveGitStashFn ?? saveGitStash;
|
|
401
|
-
const writableOverride = resolveWritableOverride(_earlyConfig);
|
|
402
|
-
const push =
|
|
680
|
+
const writableOverride = writeTarget ? resolveWritable(writeTarget.config) : resolveWritableOverride(_earlyConfig);
|
|
681
|
+
const push = options.sync?.push ?? improveProfile.sync?.push ?? true;
|
|
403
682
|
const message = renderSyncCommitMessage(effectiveSync.message ?? "akm improve auto-sync", messageContext, Date.now());
|
|
404
683
|
try {
|
|
405
|
-
const
|
|
684
|
+
const assetRoot = writeTarget?.source.path ?? primaryStashDir;
|
|
685
|
+
const assetPrefix = assetRoot ? path.relative(repoDir, assetRoot).replaceAll(path.sep, "/") : "";
|
|
686
|
+
const paths = listGitChangedPaths(repoDir).filter((changedPath) => {
|
|
687
|
+
if (initialGitPaths.has(changedPath))
|
|
688
|
+
return false;
|
|
689
|
+
if (path.basename(changedPath).includes(".lock"))
|
|
690
|
+
return false;
|
|
691
|
+
return !assetPrefix || changedPath === assetPrefix || changedPath.startsWith(`${assetPrefix}/`);
|
|
692
|
+
});
|
|
693
|
+
const syncResult = saveGitStashFn(undefined, message, writableOverride, {
|
|
694
|
+
push,
|
|
695
|
+
repoDir,
|
|
696
|
+
paths,
|
|
697
|
+
});
|
|
406
698
|
appendEvent({
|
|
407
699
|
eventType: "stash_synced",
|
|
408
700
|
metadata: {
|
|
@@ -429,443 +721,353 @@ export async function akmImprove(options = {}) {
|
|
|
429
721
|
return { committed: false, pushed: false, skipped: true, reason };
|
|
430
722
|
}
|
|
431
723
|
};
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
catch (err) {
|
|
446
|
-
rethrowIfTestIsolationError(err);
|
|
447
|
-
// If we cannot open state.db up-front, fall back to per-call opens — but
|
|
448
|
-
// still pinned to the boundary-resolved path, never a live env re-read.
|
|
449
|
-
eventsCtx = { dbPath: resolvedStateDbPath };
|
|
724
|
+
}
|
|
725
|
+
/** D6: pre-load the last 30 days of proposal_rejected events once per run. */
|
|
726
|
+
function preloadRejectedProposals() {
|
|
727
|
+
// D6: pre-load all proposal_rejected events from the last 30 days once,
|
|
728
|
+
// so the per-asset loop can use a Map lookup instead of N DB round trips.
|
|
729
|
+
const REJECTED_PROPOSAL_WINDOW_MS = daysToMs(30);
|
|
730
|
+
const rejectedProposalSince = new Date(Date.now() - REJECTED_PROPOSAL_WINDOW_MS).toISOString();
|
|
731
|
+
const allRejectedProposalEvents = readEvents({ type: "proposal_rejected", since: rejectedProposalSince }).events;
|
|
732
|
+
const rejectedProposalsByRef = new Map();
|
|
733
|
+
for (const e of allRejectedProposalEvents) {
|
|
734
|
+
if (e.ref && (!rejectedProposalsByRef.has(e.ref) || e.ts > (rejectedProposalsByRef.get(e.ref)?.ts ?? ""))) {
|
|
735
|
+
rejectedProposalsByRef.set(e.ref, e);
|
|
450
736
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
737
|
+
}
|
|
738
|
+
return rejectedProposalsByRef;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Post-lock proactive cooldown re-filter: re-read cooldown timestamps
|
|
742
|
+
* immediately before the loop so external proposal writes that occurred
|
|
743
|
+
* before this run acquired its lock are visible.
|
|
744
|
+
*/
|
|
745
|
+
export function refilterProactiveLoopRefs(loopRefs, improveProfile) {
|
|
746
|
+
// Re-read cooldown timestamps immediately before execution so external
|
|
747
|
+
// proposal writes that occurred before this run acquired its lock are visible.
|
|
748
|
+
const proactiveLoopRefs = loopRefs.filter((r) => r.eligibilitySource === "proactive");
|
|
749
|
+
let postLockLoopRefs = loopRefs;
|
|
750
|
+
if (proactiveLoopRefs.length > 0) {
|
|
751
|
+
const proactiveRefStrs = proactiveLoopRefs.map((r) => r.ref);
|
|
752
|
+
// Correlate proposal timestamps on each candidate's durable key.
|
|
753
|
+
const proactiveItemRefByRef = new Map(proactiveLoopRefs.map((r) => [r.ref, r.itemRef]));
|
|
754
|
+
const freshReflectTs = buildLatestProposalTsMap(proactiveRefStrs, "reflect", proactiveItemRefByRef);
|
|
755
|
+
const freshDistillTs = buildLatestProposalTsMap(proactiveRefStrs, "distill", proactiveItemRefByRef);
|
|
756
|
+
const pmDueDays = improveProfile.processes?.proactiveMaintenance?.dueDays ?? DEFAULT_DUE_DAYS;
|
|
757
|
+
const stillDue = new Set(filterProactiveDue(proactiveLoopRefs, freshReflectTs, freshDistillTs, pmDueDays, Date.now()).map((r) => r.ref));
|
|
758
|
+
const dropped = proactiveLoopRefs.filter((r) => !stillDue.has(r.ref));
|
|
759
|
+
if (dropped.length > 0) {
|
|
760
|
+
info(`[improve] post-lock cooldown re-filter: dropped ${dropped.length} proactive ref(s) claimed by concurrent run (${dropped.map((r) => r.ref).join(", ")})`);
|
|
761
|
+
postLockLoopRefs = loopRefs.filter((r) => r.eligibilitySource !== "proactive" || stillDue.has(r.ref));
|
|
472
762
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
763
|
+
}
|
|
764
|
+
return postLockLoopRefs;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Post-loop stage, or a no-op skip when the shared wall-clock budget is
|
|
768
|
+
* already exhausted. The result still finalizes normally on skip, so
|
|
769
|
+
* scheduled budget exhaustion exits 0 (extracted verbatim from
|
|
770
|
+
* `runImproveStageSequence` — fn-size decomposition, no logic change).
|
|
771
|
+
*/
|
|
772
|
+
async function runPostLoopStageOrSkip(args) {
|
|
773
|
+
const { budgetAbortController, scope, options, primaryStashDir, preparation, memoryRefsForInference, reindexFn, eventsCtx, improveProfile, resolvedPlan, runImprovePostLoopStageImpl, } = args;
|
|
774
|
+
// Do not start new post-loop work after the shared wall-clock budget. The
|
|
775
|
+
// result still finalizes normally, so scheduled budget exhaustion exits 0.
|
|
776
|
+
const emptyPostLoopResult = {
|
|
777
|
+
allWarnings: [],
|
|
778
|
+
memoryInferenceDurationMs: 0,
|
|
779
|
+
graphExtractionDurationMs: 0,
|
|
780
|
+
};
|
|
781
|
+
const remainingBudget = budgetAbortController.signal.remainingBudgetMs;
|
|
782
|
+
if (budgetAbortController.signal.aborted || (remainingBudget !== undefined && remainingBudget <= 0)) {
|
|
783
|
+
info("[improve] post-loop maintenance skipped (wall-clock budget exhausted)");
|
|
784
|
+
return emptyPostLoopResult;
|
|
785
|
+
}
|
|
786
|
+
return runImprovePostLoopStageImpl({
|
|
787
|
+
scope,
|
|
788
|
+
options,
|
|
789
|
+
primaryStashDir,
|
|
790
|
+
actionableRefs: preparation.actionableRefs,
|
|
791
|
+
appliedCleanup: preparation.appliedCleanup,
|
|
792
|
+
cleanupWarnings: preparation.cleanupWarnings,
|
|
793
|
+
memoryRefsForInference,
|
|
794
|
+
reindexFn,
|
|
795
|
+
eventsCtx,
|
|
796
|
+
budgetSignal: budgetAbortController.signal,
|
|
797
|
+
improveProfile,
|
|
798
|
+
resolvedPlan,
|
|
799
|
+
consolidationRan: preparation.consolidationRan,
|
|
800
|
+
// R5: floor violations from this run's consolidate pass, for the collapse
|
|
801
|
+
// detector's merge-floor advisory.
|
|
802
|
+
consolidationMergeFloorViolations: preparation.consolidation.mergeFloorViolations ?? 0,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Stage-sequencing: the strategy-filtered audit event, then the single
|
|
807
|
+
* prep → loop → post-loop pass via the #616 seams (D12). Returns every
|
|
808
|
+
* accumulator the result assembly reads — the old closure-scoped `let`s.
|
|
809
|
+
*/
|
|
810
|
+
async function runImproveStageSequence(args) {
|
|
811
|
+
const { strategyFilteredRefs, plannedRefs, memoryCleanupPlan, memorySummary, preEnsureCleanupWarnings, eventsCtx, ctx, } = args;
|
|
812
|
+
const { scope, options, primaryStashDir, startMs, budgetMs, improveProfile, resolvedPlan, budgetAbortController, selectedStrategy, reflectFn, distillFn, reindexFn, runImprovePreparationStageImpl, runImproveLoopStageImpl, runImprovePostLoopStageImpl, } = args.run;
|
|
813
|
+
// 2026-05-27: one summary `improve_skipped` audit event (count only) for
|
|
814
|
+
// planner-pre-filtered refs — never per-ref (#592: O(n) sequential state.db
|
|
815
|
+
// writes cost ~500 s on a 9 000-ref stash; health reads the counters).
|
|
816
|
+
if (strategyFilteredRefs.length > 0) {
|
|
817
|
+
appendEvent({
|
|
818
|
+
eventType: "improve_skipped",
|
|
819
|
+
ref: undefined,
|
|
820
|
+
metadata: {
|
|
821
|
+
strategy: selectedStrategy.name,
|
|
822
|
+
reason: "strategy_filtered_all_passes",
|
|
823
|
+
count: strategyFilteredRefs.length,
|
|
824
|
+
},
|
|
825
|
+
}, eventsCtx);
|
|
826
|
+
}
|
|
827
|
+
// D8 — one event per lane the autonomy gate downgraded, naming the lane AND
|
|
828
|
+
// the config key that would enable it. This is the difference between a gate
|
|
829
|
+
// and a silent no-op: whatever the user would have seen happen, they now see
|
|
830
|
+
// explained. `health` already aggregates `improve_skipped` by reason
|
|
831
|
+
// (buildImproveSkipSummary), so these surface there without new machinery.
|
|
832
|
+
for (const lane of [...args.run.resolvedPlan.autonomyGated, ...describeGatedLanes(args.autonomyGatedDirectLanes)]) {
|
|
833
|
+
warn(`[improve] ${lane.lane} skipped — it ${lane.reason}. Set \`${lane.configKey}: true\` to enable it.`);
|
|
834
|
+
appendEvent({
|
|
835
|
+
eventType: "improve_skipped",
|
|
836
|
+
ref: undefined,
|
|
837
|
+
metadata: {
|
|
838
|
+
strategy: selectedStrategy.name,
|
|
839
|
+
reason: "autonomy_gated",
|
|
840
|
+
lane: lane.lane,
|
|
841
|
+
configKey: lane.configKey,
|
|
842
|
+
},
|
|
843
|
+
}, eventsCtx);
|
|
844
|
+
}
|
|
845
|
+
// Single prep->loop->post-loop pass, run under the invocation's lock.
|
|
846
|
+
// Accumulators are direct assignments from the single pass's results.
|
|
847
|
+
let preparation;
|
|
848
|
+
let memoryRefsForInference = new Set();
|
|
849
|
+
let consolidation;
|
|
850
|
+
let memoryInference;
|
|
851
|
+
let graphExtraction;
|
|
852
|
+
let cycleMetrics;
|
|
853
|
+
// Summed counters/durations.
|
|
854
|
+
let reflectsWithErrorContext = 0;
|
|
855
|
+
let memoryInferenceDurationMs = 0;
|
|
856
|
+
let graphExtractionDurationMs = 0;
|
|
857
|
+
let orphansPurged;
|
|
858
|
+
let proposalsExpired;
|
|
859
|
+
// Concatenated arrays.
|
|
860
|
+
const allWarnings = [];
|
|
861
|
+
let deadUrls;
|
|
862
|
+
const finalActions = [];
|
|
863
|
+
{
|
|
864
|
+
const runPreparation = () => runImprovePreparationStageImpl({
|
|
865
|
+
scope,
|
|
866
|
+
options,
|
|
867
|
+
plannedRefs,
|
|
868
|
+
memoryCleanupPlan,
|
|
869
|
+
primaryStashDir,
|
|
870
|
+
memorySummary,
|
|
871
|
+
reindexFn,
|
|
872
|
+
startMs,
|
|
873
|
+
budgetMs,
|
|
874
|
+
eventsCtx,
|
|
875
|
+
initialCleanupWarnings: preEnsureCleanupWarnings,
|
|
876
|
+
improveProfile,
|
|
877
|
+
resolvedPlan,
|
|
878
|
+
strategyName: selectedStrategy.name,
|
|
879
|
+
budgetSignal: budgetAbortController.signal,
|
|
880
|
+
});
|
|
881
|
+
preparation = await runPreparation();
|
|
882
|
+
const rejectedProposalsByRef = preloadRejectedProposals();
|
|
883
|
+
const runLoop = () => {
|
|
884
|
+
const postLockLoopRefs = refilterProactiveLoopRefs(preparation.loopRefs, improveProfile);
|
|
885
|
+
return runImproveLoopStageImpl({
|
|
886
|
+
ctx,
|
|
887
|
+
primaryStashDir,
|
|
554
888
|
scope,
|
|
555
889
|
options,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
890
|
+
reflectFn,
|
|
891
|
+
distillFn,
|
|
892
|
+
loopRefs: postLockLoopRefs,
|
|
893
|
+
actions: preparation.actions,
|
|
894
|
+
signalBearingSet: preparation.signalBearingSet,
|
|
895
|
+
distillCooledRefs: preparation.distillCooledRefs,
|
|
896
|
+
distillOnlyRefs: preparation.distillOnlyRefs,
|
|
897
|
+
recentErrors: preparation.recentErrors,
|
|
898
|
+
rejectedProposalsByRef,
|
|
899
|
+
utilityMap: preparation.utilityMap,
|
|
561
900
|
startMs,
|
|
562
901
|
budgetMs,
|
|
563
|
-
eventsCtx,
|
|
564
|
-
initialCleanupWarnings: preEnsureCleanupWarnings,
|
|
565
902
|
improveProfile,
|
|
566
|
-
|
|
567
|
-
}));
|
|
568
|
-
prepGateCount += preparation.gateAutoAcceptedCount;
|
|
569
|
-
prepGateFailedCount += preparation.gateAutoAcceptFailedCount;
|
|
570
|
-
// D6: pre-load all proposal_rejected events from the last 30 days once,
|
|
571
|
-
// so the per-asset loop can use a Map lookup instead of N DB round trips.
|
|
572
|
-
const REJECTED_PROPOSAL_WINDOW_MS = daysToMs(30);
|
|
573
|
-
const rejectedProposalSince = new Date(Date.now() - REJECTED_PROPOSAL_WINDOW_MS).toISOString();
|
|
574
|
-
const allRejectedProposalEvents = readEvents({ type: "proposal_rejected", since: rejectedProposalSince }).events;
|
|
575
|
-
const rejectedProposalsByRef = new Map();
|
|
576
|
-
for (const e of allRejectedProposalEvents) {
|
|
577
|
-
if (e.ref && (!rejectedProposalsByRef.has(e.ref) || e.ts > (rejectedProposalsByRef.get(e.ref)?.ts ?? ""))) {
|
|
578
|
-
rejectedProposalsByRef.set(e.ref, e);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
// #607: acquire reflect-distill.lock for the loop stage (reflect + distill
|
|
582
|
-
// both write proposals to state.db). Released immediately after.
|
|
583
|
-
const reflectDistillLPath = processLockPath(lockBaseDir, "reflectDistill");
|
|
584
|
-
const loopResult = await withOptionalProcessLock({
|
|
585
|
-
lockPath: reflectDistillLPath,
|
|
586
|
-
staleAfterMs: PROCESS_LOCK_DEFS.reflectDistill.staleAfterMs,
|
|
587
|
-
skipIfLocked: options.skipIfLocked,
|
|
588
|
-
label: "reflect-distill",
|
|
589
|
-
}, () => {
|
|
590
|
-
// Post-lock cooldown re-filter for proactive refs (#SELECT-TIME-LEAK).
|
|
591
|
-
// Planning built `lastReflectProposalTs` BEFORE acquiring this lock, so a
|
|
592
|
-
// concurrent run's `reflect_invoked` writes are invisible to it. Now that
|
|
593
|
-
// we hold the lock, re-read fresh timestamp maps for the proactive subset
|
|
594
|
-
// and drop any ref whose cooldown has been consumed by the concurrent run.
|
|
595
|
-
const proactiveLoopRefs = preparation.loopRefs.filter((r) => r.eligibilitySource === "proactive");
|
|
596
|
-
let postLockLoopRefs = preparation.loopRefs;
|
|
597
|
-
if (proactiveLoopRefs.length > 0) {
|
|
598
|
-
const proactiveRefStrs = proactiveLoopRefs.map((r) => r.ref);
|
|
599
|
-
const freshReflectTs = buildLatestProposalTsMap(proactiveRefStrs, "reflect");
|
|
600
|
-
const freshDistillTs = buildLatestProposalTsMap(proactiveRefStrs, "distill");
|
|
601
|
-
const pmDueDays = improveProfile.processes?.proactiveMaintenance?.dueDays ?? DEFAULT_DUE_DAYS;
|
|
602
|
-
const stillDue = new Set(filterProactiveDue(proactiveLoopRefs, freshReflectTs, freshDistillTs, pmDueDays, Date.now()).map((r) => r.ref));
|
|
603
|
-
const dropped = proactiveLoopRefs.filter((r) => !stillDue.has(r.ref));
|
|
604
|
-
if (dropped.length > 0) {
|
|
605
|
-
info(`[improve] post-lock cooldown re-filter: dropped ${dropped.length} proactive ref(s) claimed by concurrent run (${dropped.map((r) => r.ref).join(", ")})`);
|
|
606
|
-
postLockLoopRefs = preparation.loopRefs.filter((r) => r.eligibilitySource !== "proactive" || stillDue.has(r.ref));
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
return runImproveLoopStageImpl({
|
|
610
|
-
scope,
|
|
611
|
-
options,
|
|
612
|
-
primaryStashDir,
|
|
613
|
-
reflectFn,
|
|
614
|
-
distillFn,
|
|
615
|
-
loopRefs: postLockLoopRefs,
|
|
616
|
-
actions: preparation.actions,
|
|
617
|
-
signalBearingSet: preparation.signalBearingSet,
|
|
618
|
-
distillCooledRefs: preparation.distillCooledRefs,
|
|
619
|
-
distillOnlyRefs: preparation.distillOnlyRefs,
|
|
620
|
-
recentErrors: preparation.recentErrors,
|
|
621
|
-
rejectedProposalsByRef,
|
|
622
|
-
utilityMap: preparation.utilityMap,
|
|
623
|
-
startMs,
|
|
624
|
-
budgetMs,
|
|
625
|
-
eventsCtx,
|
|
626
|
-
improveProfile,
|
|
627
|
-
budgetSignal: budgetAbortController.signal,
|
|
628
|
-
});
|
|
903
|
+
resolvedPlan,
|
|
629
904
|
});
|
|
630
|
-
const loopGateCountThisCycle = loopResult.gateAutoAcceptedCount;
|
|
631
|
-
reflectsWithErrorContext += loopResult.reflectsWithErrorContext;
|
|
632
|
-
loopGateCount += loopResult.gateAutoAcceptedCount;
|
|
633
|
-
loopGateFailedCount += loopResult.gateAutoAcceptFailedCount;
|
|
634
|
-
memoryRefsForInference = loopResult.memoryRefsForInference;
|
|
635
|
-
// #551: consolidation now runs in the preparation stage (before extract);
|
|
636
|
-
// its result and run-flag are read from `preparation`, not the post-loop.
|
|
637
|
-
consolidation = preparation.consolidation;
|
|
638
|
-
// #607: acquire consolidate.lock for the post-loop stage (memoryInference +
|
|
639
|
-
// graphExtraction both write index.db). Released immediately after.
|
|
640
|
-
const consolidatePostLPath = processLockPath(lockBaseDir, "consolidate");
|
|
641
|
-
const postLoopResult = await withOptionalProcessLock({
|
|
642
|
-
lockPath: consolidatePostLPath,
|
|
643
|
-
staleAfterMs: PROCESS_LOCK_DEFS.consolidate.staleAfterMs,
|
|
644
|
-
skipIfLocked: options.skipIfLocked,
|
|
645
|
-
label: "consolidate",
|
|
646
|
-
}, () => runImprovePostLoopStageImpl({
|
|
647
|
-
scope,
|
|
648
|
-
options,
|
|
649
|
-
primaryStashDir,
|
|
650
|
-
actionableRefs: preparation.actionableRefs,
|
|
651
|
-
appliedCleanup: preparation.appliedCleanup,
|
|
652
|
-
cleanupWarnings: preparation.cleanupWarnings,
|
|
653
|
-
memoryRefsForInference,
|
|
654
|
-
reindexFn,
|
|
655
|
-
eventsCtx,
|
|
656
|
-
budgetSignal: budgetAbortController.signal,
|
|
657
|
-
improveProfile,
|
|
658
|
-
consolidationRan: preparation.consolidationRan,
|
|
659
|
-
// R5: floor violations from this run's consolidate pass + the
|
|
660
|
-
// auto-accepted volume so far (prep + loop gates) for churn detection.
|
|
661
|
-
consolidationMergeFloorViolations: preparation.consolidation.mergeFloorViolations ?? 0,
|
|
662
|
-
acceptedActions: preparation.gateAutoAcceptedCount + loopGateCountThisCycle,
|
|
663
|
-
}));
|
|
664
|
-
const postLoopGateCountThisCycle = postLoopResult.gateAutoAcceptedCount;
|
|
665
|
-
// Last-wins point-in-time objects.
|
|
666
|
-
memoryInference = postLoopResult.memoryInference;
|
|
667
|
-
graphExtraction = postLoopResult.graphExtraction;
|
|
668
|
-
recombination = postLoopResult.recombination;
|
|
669
|
-
proceduralCompilation = postLoopResult.proceduralCompilation;
|
|
670
|
-
// Keep the last QUALIFYING cycle's snapshot — a later non-qualifying
|
|
671
|
-
// cycle in a maxCycles>1 run must not clobber it with undefined.
|
|
672
|
-
if (postLoopResult.cycleMetrics)
|
|
673
|
-
cycleMetrics = postLoopResult.cycleMetrics;
|
|
674
|
-
// Summed counters/durations.
|
|
675
|
-
postLoopGateCount += postLoopResult.gateAutoAcceptedCount;
|
|
676
|
-
postLoopGateFailedCount += postLoopResult.gateAutoAcceptFailedCount;
|
|
677
|
-
memoryInferenceDurationMs += postLoopResult.memoryInferenceDurationMs;
|
|
678
|
-
graphExtractionDurationMs += postLoopResult.graphExtractionDurationMs;
|
|
679
|
-
if (postLoopResult.orphansPurged !== undefined) {
|
|
680
|
-
orphansPurged = (orphansPurged ?? 0) + postLoopResult.orphansPurged;
|
|
681
|
-
}
|
|
682
|
-
if (postLoopResult.proposalsExpired !== undefined) {
|
|
683
|
-
proposalsExpired = (proposalsExpired ?? 0) + postLoopResult.proposalsExpired;
|
|
684
|
-
}
|
|
685
|
-
// Concatenated arrays.
|
|
686
|
-
allWarnings.push(...postLoopResult.allWarnings);
|
|
687
|
-
if (postLoopResult.deadUrls !== undefined) {
|
|
688
|
-
deadUrls = [...(deadUrls ?? []), ...postLoopResult.deadUrls];
|
|
689
|
-
}
|
|
690
|
-
const maintenanceActions = postLoopResult.maintenanceActions;
|
|
691
|
-
if (maintenanceActions && maintenanceActions.length > 0) {
|
|
692
|
-
finalActions.push(...preparation.actions, ...maintenanceActions);
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
finalActions.push(...preparation.actions);
|
|
696
|
-
}
|
|
697
|
-
cyclesRun++;
|
|
698
|
-
// #616 fixed-point stop: a cycle that produced ZERO gate-accepted proposals
|
|
699
|
-
// (summed across prep + loop + post-loop) would feed cycle N+1 an identical
|
|
700
|
-
// ref set, so end the loop here rather than spin a pointless next cycle.
|
|
701
|
-
const gateAcceptedThisCycle = preparation.gateAutoAcceptedCount + loopGateCountThisCycle + postLoopGateCountThisCycle;
|
|
702
|
-
if (gateAcceptedThisCycle === 0)
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
// C1 (13-bus-factor): fold the per-ref `distill-skipped` rows (~13k/run,
|
|
706
|
-
// ~91% of result_json bytes) into a bounded aggregate BEFORE persistence.
|
|
707
|
-
// The metric total + per-reason breakdown are preserved on `distillSkipped`;
|
|
708
|
-
// the unbounded row list never reaches result_json. Reflect skip counters
|
|
709
|
-
// below still read `finalActions` (reflect skips are not folded).
|
|
710
|
-
const { actions: persistedActions, aggregate: distillSkippedAggregate } = foldDistillSkipped(finalActions);
|
|
711
|
-
const result = {
|
|
712
|
-
schemaVersion: 1,
|
|
713
|
-
ok: true,
|
|
714
|
-
scope,
|
|
715
|
-
dryRun: false,
|
|
716
|
-
...(guidance ? { guidance } : {}),
|
|
717
|
-
memorySummary,
|
|
718
|
-
...(memoryCleanupPlan
|
|
719
|
-
? {
|
|
720
|
-
memoryCleanup: {
|
|
721
|
-
...shapeMemoryCleanup(memoryCleanupPlan),
|
|
722
|
-
...(preparation.appliedCleanup
|
|
723
|
-
? {
|
|
724
|
-
archived: preparation.appliedCleanup.archived,
|
|
725
|
-
...(preparation.appliedCleanup.transitionLogPath
|
|
726
|
-
? { transitionLogPath: preparation.appliedCleanup.transitionLogPath }
|
|
727
|
-
: {}),
|
|
728
|
-
...(preparation.appliedCleanup.transitionLogEntries !== undefined
|
|
729
|
-
? { transitionLogEntries: preparation.appliedCleanup.transitionLogEntries }
|
|
730
|
-
: {}),
|
|
731
|
-
...(allWarnings.length > 0 ? { warnings: allWarnings } : {}),
|
|
732
|
-
}
|
|
733
|
-
: preparation.cleanupWarnings.length > 0
|
|
734
|
-
? { warnings: preparation.cleanupWarnings }
|
|
735
|
-
: {}),
|
|
736
|
-
},
|
|
737
|
-
}
|
|
738
|
-
: {}),
|
|
739
|
-
plannedRefs: preparation.actionableRefs,
|
|
740
|
-
...(profileFilteredRefs.length > 0 ? { profileFilteredRefs } : {}),
|
|
741
|
-
actions: persistedActions,
|
|
742
|
-
...(distillSkippedAggregate ? { distillSkipped: distillSkippedAggregate } : {}),
|
|
743
|
-
...(preparation.validationFailures.length > 0 ? { validationFailures: preparation.validationFailures } : {}),
|
|
744
|
-
...(preparation.schemaRepairs.length > 0 ? { schemaRepairs: preparation.schemaRepairs } : {}),
|
|
745
|
-
...(consolidation.processed > 0 || consolidation.warnings.length > 0 ? { consolidation } : {}),
|
|
746
|
-
...(preparation.lintSummary !== undefined ? { lintSummary: preparation.lintSummary } : {}),
|
|
747
|
-
...(preparation.memoryIndexHealth !== undefined ? { memoryIndexHealth: preparation.memoryIndexHealth } : {}),
|
|
748
|
-
...(preparation.coverageGaps.length > 0 ? { coverageGaps: preparation.coverageGaps } : {}),
|
|
749
|
-
...(preparation.extract && preparation.extract.length > 0 ? { extract: preparation.extract } : {}),
|
|
750
|
-
...(primaryStashDir !== undefined ? { evalCasesWritten: countEvalCases(primaryStashDir) } : {}),
|
|
751
|
-
...(deadUrls !== undefined && deadUrls.length > 0 ? { deadUrls } : {}),
|
|
752
|
-
...(reflectsWithErrorContext > 0 ? { reflectsWithErrorContext } : {}),
|
|
753
|
-
...(memoryInference ? { memoryInference } : {}),
|
|
754
|
-
...(graphExtraction ? { graphExtraction } : {}),
|
|
755
|
-
// Per-phase wall-clock durations. Surfaced at the top level of the
|
|
756
|
-
// envelope (not nested) because `health.ts`'s `wallTime.byPhase`
|
|
757
|
-
// aggregator and the existing `memoryInference.durationMs` /
|
|
758
|
-
// `graphExtraction.durationMs` health buckets all read
|
|
759
|
-
// `result.{memoryInferenceDurationMs,graphExtractionDurationMs}`
|
|
760
|
-
// directly. Mirrors how `consolidation.durationMs` is surfaced inside
|
|
761
|
-
// the consolidation sub-object (different convention because the
|
|
762
|
-
// consolidation result type already owns that field). Phases that did
|
|
763
|
-
// not run (zero duration) are omitted so the aggregator's
|
|
764
|
-
// "phase actually ran" filter (`> 0`) excludes them from the median/p95
|
|
765
|
-
// sample. Plumbed in d1273d0's follow-up — see
|
|
766
|
-
// `/tmp/akm-health-investigations/metrics-taxonomy-review.md` §1k / §3.
|
|
767
|
-
...(memoryInferenceDurationMs > 0 ? { memoryInferenceDurationMs } : {}),
|
|
768
|
-
...(graphExtractionDurationMs > 0 ? { graphExtractionDurationMs } : {}),
|
|
769
|
-
...(recombination ? { recombination } : {}),
|
|
770
|
-
...(proceduralCompilation ? { proceduralCompilation } : {}),
|
|
771
|
-
...(cycleMetrics ? { cycleMetrics } : {}),
|
|
772
|
-
...(orphansPurged !== undefined ? { orphansPurged } : {}),
|
|
773
|
-
...(proposalsExpired !== undefined && proposalsExpired > 0 ? { proposalsExpired } : {}),
|
|
774
|
-
reflectCooldownActions: finalActions.filter((a) => a.mode === "reflect-cooldown").length,
|
|
775
|
-
reflectSkippedActions: finalActions.filter((a) => a.mode === "reflect-skipped").length,
|
|
776
|
-
reflectGuardRejectedActions: finalActions.filter((a) => a.mode === "reflect-guard-rejected").length,
|
|
777
|
-
...(() => {
|
|
778
|
-
const t = prepGateCount + loopGateCount + postLoopGateCount;
|
|
779
|
-
return t > 0 ? { gateAutoAcceptedCount: t } : {};
|
|
780
|
-
})(),
|
|
781
|
-
...(() => {
|
|
782
|
-
const f = prepGateFailedCount + loopGateFailedCount + postLoopGateFailedCount;
|
|
783
|
-
return f > 0 ? { gateAutoAcceptFailedCount: f } : {};
|
|
784
|
-
})(),
|
|
785
|
-
...(triageDrain
|
|
786
|
-
? {
|
|
787
|
-
triage: {
|
|
788
|
-
promoted: triageDrain.promoted.length,
|
|
789
|
-
rejected: triageDrain.rejected.length,
|
|
790
|
-
deferred: triageDrain.deferred.length,
|
|
791
|
-
skippedByCap: triageDrain.skippedByCap.length,
|
|
792
|
-
},
|
|
793
|
-
}
|
|
794
|
-
: {}),
|
|
795
|
-
...(preparation.proactiveMaintenance ? { proactiveMaintenance: preparation.proactiveMaintenance } : {}),
|
|
796
|
-
// #616 — report cycles run only when >1 so the default single-pass
|
|
797
|
-
// serialized envelope stays byte-identical to pre-#616 (AC1).
|
|
798
|
-
...(cyclesRun > 1 ? { cyclesRun } : {}),
|
|
799
|
-
...(options.runId !== undefined ? { runId: options.runId } : {}),
|
|
800
905
|
};
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
durationMs: Date.now() - startMs,
|
|
831
|
-
},
|
|
832
|
-
}, eventsCtx);
|
|
833
|
-
// #662 crash/abort safety net: commit whatever this run already wrote to the
|
|
834
|
-
// primary stash BEFORE rethrowing, so an interrupted run (mid-cycle crash or
|
|
835
|
-
// a cooperative budget abort that surfaces as a throw) does not leave its
|
|
836
|
-
// writes uncommitted until a later clean run sweeps them up. Best-effort —
|
|
837
|
-
// `commitStashBatch` swallows its own errors and no-ops a clean tree, so this
|
|
838
|
-
// never masks or supersedes the original failure being rethrown below.
|
|
839
|
-
commitStashBatch({ scope, plannedRefs, runId: options.runId });
|
|
840
|
-
throw err;
|
|
841
|
-
}
|
|
842
|
-
finally {
|
|
843
|
-
// #576: clear the per-run LLM usage sink BEFORE closing `eventsDb` below, so
|
|
844
|
-
// no late sink invocation can write through a closed handle.
|
|
845
|
-
disposeLlmUsageSink();
|
|
846
|
-
// O-1 (#364): Clear the budget abort timer so it does not keep the event
|
|
847
|
-
// loop alive after the run completes.
|
|
848
|
-
clearBudgetTimer();
|
|
849
|
-
// #607: release any per-process locks still held (backstop for error paths;
|
|
850
|
-
// the normal path already released each lock after its stage completed).
|
|
851
|
-
releaseAllProcessLocks();
|
|
852
|
-
// Drop ONLY our own process.exit backstop so it does not fire later (or
|
|
853
|
-
// accumulate across repeated in-process calls). Must NOT use
|
|
854
|
-
// removeAllListeners("exit") here: in the in-process model (tests and
|
|
855
|
-
// programmatic callers import cli.ts) that would silently destroy exit
|
|
856
|
-
// handlers owned by the host or other commands.
|
|
857
|
-
if (exitBackstop) {
|
|
858
|
-
process.removeListener("exit", exitBackstop);
|
|
859
|
-
exitBackstop = undefined;
|
|
906
|
+
const loopResult = await runLoop();
|
|
907
|
+
reflectsWithErrorContext += loopResult.reflectsWithErrorContext;
|
|
908
|
+
memoryRefsForInference = loopResult.memoryRefsForInference;
|
|
909
|
+
// #551: consolidation now runs in the preparation stage (before extract);
|
|
910
|
+
// its result and run-flag are read from `preparation`, not the post-loop.
|
|
911
|
+
consolidation = preparation.consolidation;
|
|
912
|
+
const postLoopResult = await runPostLoopStageOrSkip({
|
|
913
|
+
budgetAbortController,
|
|
914
|
+
scope,
|
|
915
|
+
options,
|
|
916
|
+
primaryStashDir,
|
|
917
|
+
preparation,
|
|
918
|
+
memoryRefsForInference,
|
|
919
|
+
reindexFn,
|
|
920
|
+
eventsCtx,
|
|
921
|
+
improveProfile,
|
|
922
|
+
resolvedPlan,
|
|
923
|
+
runImprovePostLoopStageImpl,
|
|
924
|
+
});
|
|
925
|
+
// Result objects (single pass — no cycle accumulation).
|
|
926
|
+
memoryInference = postLoopResult.memoryInference;
|
|
927
|
+
graphExtraction = postLoopResult.graphExtraction;
|
|
928
|
+
if (postLoopResult.cycleMetrics)
|
|
929
|
+
cycleMetrics = postLoopResult.cycleMetrics;
|
|
930
|
+
// Summed counters/durations.
|
|
931
|
+
memoryInferenceDurationMs += postLoopResult.memoryInferenceDurationMs;
|
|
932
|
+
graphExtractionDurationMs += postLoopResult.graphExtractionDurationMs;
|
|
933
|
+
if (postLoopResult.orphansPurged !== undefined) {
|
|
934
|
+
orphansPurged = (orphansPurged ?? 0) + postLoopResult.orphansPurged;
|
|
860
935
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
eventsDb?.close();
|
|
936
|
+
if (postLoopResult.proposalsExpired !== undefined) {
|
|
937
|
+
proposalsExpired = (proposalsExpired ?? 0) + postLoopResult.proposalsExpired;
|
|
864
938
|
}
|
|
865
|
-
|
|
866
|
-
|
|
939
|
+
// Concatenated arrays.
|
|
940
|
+
allWarnings.push(...postLoopResult.allWarnings);
|
|
941
|
+
if (postLoopResult.deadUrls !== undefined) {
|
|
942
|
+
deadUrls = [...(deadUrls ?? []), ...postLoopResult.deadUrls];
|
|
943
|
+
}
|
|
944
|
+
const maintenanceActions = postLoopResult.maintenanceActions;
|
|
945
|
+
if (maintenanceActions && maintenanceActions.length > 0) {
|
|
946
|
+
finalActions.push(...preparation.actions, ...maintenanceActions);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
finalActions.push(...preparation.actions);
|
|
867
950
|
}
|
|
868
951
|
}
|
|
952
|
+
return {
|
|
953
|
+
preparation,
|
|
954
|
+
memoryRefsForInference,
|
|
955
|
+
consolidation,
|
|
956
|
+
memoryInference,
|
|
957
|
+
graphExtraction,
|
|
958
|
+
cycleMetrics,
|
|
959
|
+
reflectsWithErrorContext,
|
|
960
|
+
memoryInferenceDurationMs,
|
|
961
|
+
graphExtractionDurationMs,
|
|
962
|
+
orphansPurged,
|
|
963
|
+
proposalsExpired,
|
|
964
|
+
allWarnings,
|
|
965
|
+
deadUrls,
|
|
966
|
+
finalActions,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Run-teardown, result half: assemble the AkmImproveResult envelope and emit
|
|
971
|
+
* the improve_completed event (same adjacency as the old inline code — no
|
|
972
|
+
* side effects run between assembly and emit).
|
|
973
|
+
*/
|
|
974
|
+
function finalizeImproveResult(args) {
|
|
975
|
+
const { guidance, memorySummary, memoryCleanupPlan, strategyFilteredRefs, triageDrain, eventsCtx } = args;
|
|
976
|
+
const { selectedStrategy, scope, options, primaryStashDir, startMs } = args.run;
|
|
977
|
+
const { preparation, consolidation, memoryInference, graphExtraction, cycleMetrics, reflectsWithErrorContext, memoryInferenceDurationMs, graphExtractionDurationMs, orphansPurged, proposalsExpired, allWarnings, deadUrls, finalActions, } = args.seq;
|
|
978
|
+
// C1 (13-bus-factor): fold the per-ref `distill-skipped` rows (~13k/run,
|
|
979
|
+
// ~91% of result_json bytes) into a bounded aggregate BEFORE persistence.
|
|
980
|
+
// The metric total + per-reason breakdown are preserved on `distillSkipped`;
|
|
981
|
+
// the unbounded row list never reaches result_json. Reflect skip counters
|
|
982
|
+
// below still read `finalActions` (reflect skips are not folded).
|
|
983
|
+
const { actions: persistedActions, aggregate: distillSkippedAggregate } = foldDistillSkipped(finalActions);
|
|
984
|
+
const result = {
|
|
985
|
+
schemaVersion: 2,
|
|
986
|
+
ok: true,
|
|
987
|
+
strategy: selectedStrategy.name,
|
|
988
|
+
scope,
|
|
989
|
+
dryRun: false,
|
|
990
|
+
...(guidance ? { guidance } : {}),
|
|
991
|
+
memorySummary,
|
|
992
|
+
...(memoryCleanupPlan
|
|
993
|
+
? {
|
|
994
|
+
memoryCleanup: {
|
|
995
|
+
...shapeMemoryCleanup(memoryCleanupPlan),
|
|
996
|
+
...(preparation.appliedCleanup
|
|
997
|
+
? {
|
|
998
|
+
archived: preparation.appliedCleanup.archived,
|
|
999
|
+
...(preparation.appliedCleanup.transitionLogPath
|
|
1000
|
+
? { transitionLogPath: preparation.appliedCleanup.transitionLogPath }
|
|
1001
|
+
: {}),
|
|
1002
|
+
...(preparation.appliedCleanup.transitionLogEntries !== undefined
|
|
1003
|
+
? { transitionLogEntries: preparation.appliedCleanup.transitionLogEntries }
|
|
1004
|
+
: {}),
|
|
1005
|
+
...(allWarnings.length > 0 ? { warnings: allWarnings } : {}),
|
|
1006
|
+
}
|
|
1007
|
+
: preparation.cleanupWarnings.length > 0
|
|
1008
|
+
? { warnings: preparation.cleanupWarnings }
|
|
1009
|
+
: {}),
|
|
1010
|
+
},
|
|
1011
|
+
}
|
|
1012
|
+
: {}),
|
|
1013
|
+
plannedRefs: preparation.actionableRefs,
|
|
1014
|
+
...(strategyFilteredRefs.length > 0 ? { strategyFilteredRefs } : {}),
|
|
1015
|
+
actions: persistedActions,
|
|
1016
|
+
...(distillSkippedAggregate ? { distillSkipped: distillSkippedAggregate } : {}),
|
|
1017
|
+
...(preparation.validationFailures.length > 0 ? { validationFailures: preparation.validationFailures } : {}),
|
|
1018
|
+
...(preparation.schemaRepairs.length > 0 ? { schemaRepairs: preparation.schemaRepairs } : {}),
|
|
1019
|
+
...(consolidation.processed > 0 || consolidation.warnings.length > 0 ? { consolidation } : {}),
|
|
1020
|
+
...(preparation.lintSummary !== undefined ? { lintSummary: preparation.lintSummary } : {}),
|
|
1021
|
+
...(preparation.memoryIndexHealth !== undefined ? { memoryIndexHealth: preparation.memoryIndexHealth } : {}),
|
|
1022
|
+
...(preparation.coverageGaps.length > 0 ? { coverageGaps: preparation.coverageGaps } : {}),
|
|
1023
|
+
...(preparation.extract && preparation.extract.length > 0 ? { extract: preparation.extract } : {}),
|
|
1024
|
+
...(primaryStashDir !== undefined ? { evalCasesWritten: countEvalCases(primaryStashDir) } : {}),
|
|
1025
|
+
...(deadUrls !== undefined && deadUrls.length > 0 ? { deadUrls } : {}),
|
|
1026
|
+
...(reflectsWithErrorContext > 0 ? { reflectsWithErrorContext } : {}),
|
|
1027
|
+
...(memoryInference ? { memoryInference } : {}),
|
|
1028
|
+
...(graphExtraction ? { graphExtraction } : {}),
|
|
1029
|
+
// Per-phase wall-clock durations. Surfaced at the top level of the
|
|
1030
|
+
// envelope (not nested) because `health.ts`'s `wallTime.byPhase`
|
|
1031
|
+
// aggregator and the existing `memoryInference.durationMs` /
|
|
1032
|
+
// `graphExtraction.durationMs` health buckets all read
|
|
1033
|
+
// `result.{memoryInferenceDurationMs,graphExtractionDurationMs}`
|
|
1034
|
+
// directly. Mirrors how `consolidation.durationMs` is surfaced inside
|
|
1035
|
+
// the consolidation sub-object (different convention because the
|
|
1036
|
+
// consolidation result type already owns that field). Phases that did
|
|
1037
|
+
// not run (zero duration) are omitted so the aggregator's
|
|
1038
|
+
// "phase actually ran" filter (`> 0`) excludes them from the median/p95
|
|
1039
|
+
// sample. Plumbed in d1273d0's follow-up — see
|
|
1040
|
+
// `/tmp/akm-health-investigations/metrics-taxonomy-review.md` §1k / §3.
|
|
1041
|
+
...(memoryInferenceDurationMs > 0 ? { memoryInferenceDurationMs } : {}),
|
|
1042
|
+
...(graphExtractionDurationMs > 0 ? { graphExtractionDurationMs } : {}),
|
|
1043
|
+
...(cycleMetrics ? { cycleMetrics } : {}),
|
|
1044
|
+
...(orphansPurged !== undefined ? { orphansPurged } : {}),
|
|
1045
|
+
...(proposalsExpired !== undefined && proposalsExpired > 0 ? { proposalsExpired } : {}),
|
|
1046
|
+
reflectCooldownActions: finalActions.filter((a) => a.mode === "reflect-cooldown").length,
|
|
1047
|
+
reflectSkippedActions: finalActions.filter((a) => a.mode === "reflect-skipped").length,
|
|
1048
|
+
reflectGuardRejectedActions: finalActions.filter((a) => a.mode === "reflect-guard-rejected").length,
|
|
1049
|
+
...(triageDrain
|
|
1050
|
+
? {
|
|
1051
|
+
triage: {
|
|
1052
|
+
promoted: triageDrain.promoted.length,
|
|
1053
|
+
rejected: triageDrain.rejected.length,
|
|
1054
|
+
deferred: triageDrain.deferred.length,
|
|
1055
|
+
skippedByCap: triageDrain.skippedByCap.length,
|
|
1056
|
+
},
|
|
1057
|
+
}
|
|
1058
|
+
: {}),
|
|
1059
|
+
...(preparation.proactiveMaintenance ? { proactiveMaintenance: preparation.proactiveMaintenance } : {}),
|
|
1060
|
+
...(options.runId !== undefined ? { runId: options.runId } : {}),
|
|
1061
|
+
};
|
|
1062
|
+
if (!result.dryRun)
|
|
1063
|
+
emitImproveCompletedEvent(result, {
|
|
1064
|
+
memoryInferenceDurationMs,
|
|
1065
|
+
graphExtractionDurationMs,
|
|
1066
|
+
totalDurationMs: Date.now() - startMs,
|
|
1067
|
+
warningCount: allWarnings.length,
|
|
1068
|
+
orphansPurged: orphansPurged ?? 0,
|
|
1069
|
+
}, eventsCtx);
|
|
1070
|
+
return result;
|
|
869
1071
|
}
|
|
870
1072
|
function emitImproveCompletedEvent(result, durations, eventsCtx) {
|
|
871
1073
|
const actionCounts = {
|
|
@@ -942,6 +1144,7 @@ function emitImproveCompletedEvent(result, durations, eventsCtx) {
|
|
|
942
1144
|
? result.scope.value
|
|
943
1145
|
: `improve:${result.scope.mode}:${result.scope.value ?? "all"}`,
|
|
944
1146
|
metadata: {
|
|
1147
|
+
strategy: result.strategy,
|
|
945
1148
|
plannedRefs: result.plannedRefs.length,
|
|
946
1149
|
reflectActions: actionCounts.reflect,
|
|
947
1150
|
distillActions: actionCounts.distill,
|