akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.4
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 +731 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +2 -7
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +0 -17
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +361 -751
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +87 -406
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -21
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/config/retired-experimental-keys-shim.js +0 -62
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -441
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -78
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/** The refs an improve run may consider, read from the index, and the small predicates over them. */
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
7
|
-
import { conceptIdFromTypeName, parseRefInput, resolveRef } from "../../core/asset/resolve-ref.js";
|
|
8
|
+
import { conceptIdFromTypeName, parseRefInput, resolveRef, typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
8
9
|
import { loadConfig } from "../../core/config/config.js";
|
|
9
10
|
import { ConfigError, NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
10
|
-
import { readEvents } from "../../core/events.js";
|
|
11
11
|
import { isPathAbsent } from "../../core/path-access.js";
|
|
12
12
|
import { getDbPath } from "../../core/paths.js";
|
|
13
13
|
import { deriveInstallations, deriveWritableBundleIds } from "../../indexer/installations.js";
|
|
@@ -15,70 +15,71 @@ import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
|
15
15
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
16
16
|
import { closeDatabase, openExistingDatabase, openReadonlyExistingDatabase, } from "../../storage/repositories/index-connection.js";
|
|
17
17
|
import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
|
|
18
|
+
import { hasCurrentEntriesTable } from "../../storage/repositories/index-entry-schema.js";
|
|
18
19
|
import { getUtilityScoresByIds } from "../../storage/repositories/index-utility-repository.js";
|
|
19
20
|
import { SqliteReadSnapshotUnavailableError } from "../../storage/sqlite-read-snapshot.js";
|
|
20
21
|
import { isDistillRefusedInputType } from "./distill.js";
|
|
21
22
|
import { isStrategyFilteredForAllPasses } from "./improve-strategies.js";
|
|
22
23
|
import { parseMemoryRef } from "./memory/derived-ref.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
/** The asset type of a short or bundle-qualified conceptId (`""` when unknown). */
|
|
25
|
+
export function assetTypeOf(ref) {
|
|
26
|
+
return typeNameFromConceptId(ref.includes("//") ? ref.slice(ref.indexOf("//") + 2) : ref)?.type ?? "";
|
|
27
|
+
}
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* index-connection.ts) — for eligibility, "no index" simply means nothing is
|
|
31
|
-
* eligible, exactly like the readOnly arm's `undefined`.
|
|
32
|
-
*
|
|
33
|
-
* `undefined` is reserved for a genuinely ABSENT index. The readOnly arm has
|
|
34
|
-
* refused to conflate that with an unreadable one since #791; this arm used
|
|
35
|
-
* `fs.existsSync`, so the same run reported "nothing eligible to improve" at
|
|
36
|
-
* exit 0 depending only on which branch it took. Both arms now raise.
|
|
29
|
+
* Run `fn` against index.db — the live index, or a non-mutating snapshot for
|
|
30
|
+
* a dry run. `undefined` when there is none or the read fails (best-effort).
|
|
37
31
|
*/
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
export function withIndexDb(readOnly, fn) {
|
|
33
|
+
let db;
|
|
34
|
+
try {
|
|
35
|
+
db = readOnly ? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true }) : openExistingDatabase();
|
|
36
|
+
return db ? fn(db) : undefined;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
rethrowIfTestIsolationError(err);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (db)
|
|
44
|
+
closeDatabase(db);
|
|
49
45
|
}
|
|
50
|
-
return {
|
|
51
|
-
status,
|
|
52
|
-
reason: readOnly
|
|
53
|
-
? "index.db is missing; dry-run uses an empty snapshot and does not create it"
|
|
54
|
-
: "index.db is missing after index preparation; the selector uses an empty snapshot",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
function describeUnavailableSnapshot(error) {
|
|
58
|
-
return {
|
|
59
|
-
status: "incompatible",
|
|
60
|
-
reason: `index.db cannot provide a stable non-mutating snapshot (${error.message}); dry-run uses an empty snapshot`,
|
|
61
|
-
};
|
|
62
46
|
}
|
|
63
47
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* typed boundary mapping: readers otherwise receive no incompatible handle,
|
|
67
|
-
* and we must not turn an arbitrary SQLite error into a successful plan by
|
|
68
|
-
* matching its text.
|
|
48
|
+
* The index for candidate selection, or `undefined` when it is absent. An
|
|
49
|
+
* index without a readable `entries` table needs a rebuild and says so.
|
|
69
50
|
*/
|
|
70
|
-
function
|
|
71
|
-
|
|
51
|
+
function openEligibilityDb(readOnly) {
|
|
52
|
+
const db = readOnly
|
|
53
|
+
? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true })
|
|
54
|
+
: isPathAbsent(getDbPath())
|
|
55
|
+
? undefined
|
|
56
|
+
: openExistingDatabase();
|
|
57
|
+
if (db && !hasCurrentEntriesTable(db)) {
|
|
58
|
+
closeDatabase(db);
|
|
59
|
+
throw new ConfigError("index.db has no entries table this akm can read, so nothing can be selected for improvement.", "INDEX_SCHEMA_INCOMPATIBLE", "Run `akm index` to rebuild the derived index from the currently materialized sources.");
|
|
60
|
+
}
|
|
61
|
+
return db;
|
|
72
62
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
/**
|
|
64
|
+
* How a dry run describes an index it could not read. Only the typed
|
|
65
|
+
* failures map to an empty plan; any other error still throws.
|
|
66
|
+
*/
|
|
67
|
+
function unreadableSnapshot(readOnly, error) {
|
|
68
|
+
if (!readOnly)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (error instanceof SqliteReadSnapshotUnavailableError) {
|
|
71
|
+
return {
|
|
72
|
+
status: "incompatible",
|
|
73
|
+
reason: `index.db cannot provide a stable non-mutating snapshot (${error.message}); dry-run uses an empty snapshot`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (error instanceof ConfigError && error.code === "INDEX_SCHEMA_INCOMPATIBLE") {
|
|
77
|
+
return {
|
|
78
|
+
status: "incompatible",
|
|
79
|
+
reason: `index.db is incompatible; ${error.hint() ?? error.message} Dry-run uses an empty snapshot and does not migrate it.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
82
83
|
}
|
|
83
84
|
export function resolveImproveScope(scope) {
|
|
84
85
|
const trimmed = scope?.trim();
|
|
@@ -89,80 +90,68 @@ export function resolveImproveScope(scope) {
|
|
|
89
90
|
return { mode: "ref", value: trimmed };
|
|
90
91
|
}
|
|
91
92
|
catch (err) {
|
|
92
|
-
// A bare word
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
// not a data-acceptance gate, so no deny-list is needed here: an
|
|
96
|
-
// unrecognized type just matches nothing, since nothing is ever indexed
|
|
97
|
-
// with it). No test pinned the old "Unknown asset type" rejection for
|
|
98
|
-
// this case (chunk-1.5 anchors §A.5 — 0 hits).
|
|
99
|
-
//
|
|
100
|
-
// A colon-shaped value that still fails `parseRefInput` is a genuinely
|
|
101
|
-
// malformed ref attempt (bad name, path traversal, or a deny-listed
|
|
102
|
-
// deliberately-removed type like tool/vault, D1.5-6) — that must still
|
|
103
|
-
// surface as a real error, not be silently absorbed into a type filter
|
|
104
|
-
// that will never match anything.
|
|
105
|
-
if (!/[/:#]/.test(trimmed)) {
|
|
93
|
+
// A bare word is a `--scope <type>` filter (an unknown type matches
|
|
94
|
+
// nothing); a ref-shaped value that fails to parse is a real error.
|
|
95
|
+
if (!/[/:#]/.test(trimmed))
|
|
106
96
|
return { mode: "type", value: trimmed };
|
|
107
|
-
}
|
|
108
97
|
const message = err instanceof Error ? err.message : String(err);
|
|
109
98
|
throw new UsageError(`Invalid --scope "${trimmed}": ${message}`, "INVALID_FLAG_VALUE");
|
|
110
99
|
}
|
|
111
100
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Dedupe eligible refs by their durable item ref when available, preserving
|
|
114
|
-
* the first candidate and its display ref. Used to
|
|
115
|
-
* merge the eligibility sources (feedback-signal, Layer-2 proactive-maintenance,
|
|
116
|
-
* high-salience) without admitting a ref into the loop twice.
|
|
117
|
-
*/
|
|
101
|
+
/** Dedupe by durable identity (item_ref, else ref), keeping the first. */
|
|
118
102
|
export function dedupeRefs(refs) {
|
|
119
103
|
const seen = new Set();
|
|
120
|
-
|
|
121
|
-
for (const r of refs) {
|
|
104
|
+
return refs.filter((r) => {
|
|
122
105
|
const key = r.itemRef ?? r.ref;
|
|
123
106
|
if (seen.has(key))
|
|
124
|
-
|
|
107
|
+
return false;
|
|
125
108
|
seen.add(key);
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
return out;
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
129
111
|
}
|
|
130
112
|
export async function collectEligibleRefs(scope, stashDir, improveProfile, config) {
|
|
131
113
|
return collectEligibleRefsFromIndex(scope, stashDir, improveProfile, false, config);
|
|
132
114
|
}
|
|
133
|
-
/** Dry-run
|
|
115
|
+
/** Dry-run selector: query an existing index without creating or mutating it. */
|
|
134
116
|
export async function collectEligibleRefsReadOnly(scope, stashDir, improveProfile, config) {
|
|
135
117
|
return collectEligibleRefsFromIndex(scope, stashDir, improveProfile, true, config);
|
|
136
118
|
}
|
|
137
119
|
async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, readOnly, configOverride) {
|
|
120
|
+
const empty = (indexSnapshot) => ({
|
|
121
|
+
plannedRefs: [],
|
|
122
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
123
|
+
strategyFilteredRefs: [],
|
|
124
|
+
...(indexSnapshot ? { indexSnapshot } : {}),
|
|
125
|
+
});
|
|
138
126
|
const config = configOverride ?? loadConfig();
|
|
139
127
|
let sources;
|
|
140
128
|
try {
|
|
141
129
|
sources = resolveSourceEntries(stashDir, config);
|
|
142
130
|
}
|
|
143
131
|
catch {
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
if (sources.length === 0) {
|
|
147
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
132
|
+
return empty();
|
|
148
133
|
}
|
|
134
|
+
if (sources.length === 0)
|
|
135
|
+
return empty();
|
|
149
136
|
const installations = deriveInstallations(sources);
|
|
150
137
|
const writableBundleIds = deriveWritableBundleIds(sources);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
138
|
+
const ready = {
|
|
139
|
+
status: "ready",
|
|
140
|
+
reason: readOnly ? "loaded a non-mutating point-in-time copy of the existing index" : "loaded the prepared index",
|
|
141
|
+
};
|
|
142
|
+
const missing = {
|
|
143
|
+
status: "missing",
|
|
144
|
+
reason: readOnly
|
|
145
|
+
? "index.db is missing; dry-run uses an empty snapshot and does not create it"
|
|
146
|
+
: "index.db is missing after index preparation; the selector uses an empty snapshot",
|
|
147
|
+
};
|
|
148
|
+
let db;
|
|
149
|
+
try {
|
|
150
|
+
db = openEligibilityDb(readOnly);
|
|
151
|
+
if (!db)
|
|
152
|
+
return empty(missing);
|
|
153
|
+
if (scope.mode === "ref" && scope.value) {
|
|
154
|
+
const entriesByItemRef = new Map(getAllEntries(db).map((entry) => [entry.itemRef, entry]));
|
|
166
155
|
const resolved = resolveRef(scope.value, {
|
|
167
156
|
defaultBundle: config.defaultBundle,
|
|
168
157
|
bundles: installations.map((installation) => ({
|
|
@@ -172,77 +161,31 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
172
161
|
});
|
|
173
162
|
const indexed = entriesByItemRef.get(`${resolved.bundle}//${resolved.conceptId}`);
|
|
174
163
|
if (!indexed?.bundleId || !indexed.conceptId || !fs.existsSync(indexed.filePath)) {
|
|
175
|
-
if (await findAssetFilePath(scope.value, stashDir))
|
|
176
|
-
return
|
|
177
|
-
plannedRefs: [],
|
|
178
|
-
memorySummary: { eligible: 0, derived: 0 },
|
|
179
|
-
strategyFilteredRefs: [],
|
|
180
|
-
indexSnapshot,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
164
|
+
if (await findAssetFilePath(scope.value, stashDir))
|
|
165
|
+
return empty(ready);
|
|
183
166
|
throw new NotFoundError(`Asset not found in the selected writable source: ${scope.value}`, "ASSET_NOT_FOUND");
|
|
184
167
|
}
|
|
185
|
-
if (!writableBundleIds.has(indexed.bundleId))
|
|
186
|
-
return
|
|
187
|
-
|
|
168
|
+
if (!writableBundleIds.has(indexed.bundleId))
|
|
169
|
+
return empty(ready);
|
|
170
|
+
const isMemory = indexed.entry.type === "memory";
|
|
188
171
|
return {
|
|
189
172
|
plannedRefs: [
|
|
190
173
|
{ ref: indexed.conceptId, itemRef: indexed.itemRef, reason: "scope-ref", filePath: indexed.filePath },
|
|
191
174
|
],
|
|
192
175
|
memorySummary: {
|
|
193
|
-
eligible:
|
|
194
|
-
derived:
|
|
176
|
+
eligible: isMemory ? 1 : 0,
|
|
177
|
+
derived: isMemory && indexed.entry.name.endsWith(".derived") ? 1 : 0,
|
|
195
178
|
},
|
|
196
179
|
strategyFilteredRefs: [],
|
|
197
|
-
indexSnapshot,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
if (readOnly && error instanceof SqliteReadSnapshotUnavailableError) {
|
|
202
|
-
return {
|
|
203
|
-
plannedRefs: [],
|
|
204
|
-
memorySummary: { eligible: 0, derived: 0 },
|
|
205
|
-
strategyFilteredRefs: [],
|
|
206
|
-
indexSnapshot: describeUnavailableSnapshot(error),
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
if (readOnly && isIncompatibleIndexError(error)) {
|
|
210
|
-
return {
|
|
211
|
-
plannedRefs: [],
|
|
212
|
-
memorySummary: { eligible: 0, derived: 0 },
|
|
213
|
-
strategyFilteredRefs: [],
|
|
214
|
-
indexSnapshot: describeIncompatibleIndexSnapshot(error),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
throw error;
|
|
218
|
-
}
|
|
219
|
-
finally {
|
|
220
|
-
if (db)
|
|
221
|
-
closeDatabase(db);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
let db;
|
|
225
|
-
try {
|
|
226
|
-
db = openEligibilityDb(readOnly);
|
|
227
|
-
if (!db) {
|
|
228
|
-
return {
|
|
229
|
-
plannedRefs: [],
|
|
230
|
-
memorySummary: { eligible: 0, derived: 0 },
|
|
231
|
-
strategyFilteredRefs: [],
|
|
232
|
-
indexSnapshot: describeIndexSnapshot(readOnly, "missing"),
|
|
180
|
+
indexSnapshot: ready,
|
|
233
181
|
};
|
|
234
182
|
}
|
|
235
|
-
const indexSnapshot = describeIndexSnapshot(readOnly, "ready");
|
|
236
183
|
const entries = getAllEntries(db, scope.mode === "type" ? scope.value : undefined).filter((indexed) => writableBundleIds.has(indexed.bundleId));
|
|
237
184
|
const planned = new Map();
|
|
238
185
|
const strategyFiltered = new Map();
|
|
239
186
|
let memoryEligible = 0;
|
|
240
187
|
let memoryDerived = 0;
|
|
241
188
|
for (const indexed of entries) {
|
|
242
|
-
// Chunk-8 WI-8.5c: the candidate `ref` is the SHORT conceptId
|
|
243
|
-
// (`<stash-subdir>/<name>`, D-R2) — it now matches the disk lookup,
|
|
244
|
-
// xrefs, and `displayRef` output spelling. `.itemRef` below stays the
|
|
245
|
-
// fully-qualified durable key.
|
|
246
189
|
const ref = conceptIdFromTypeName(indexed.entry.type, indexed.entry.name);
|
|
247
190
|
try {
|
|
248
191
|
parseRefInput(ref);
|
|
@@ -252,40 +195,16 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
252
195
|
continue;
|
|
253
196
|
throw error;
|
|
254
197
|
}
|
|
255
|
-
// Derive the durable item_ref from indexed provenance without another query.
|
|
256
|
-
// A provenance-free row uses the short conceptId ref for improve state.
|
|
257
|
-
const itemRef = indexed.itemRef;
|
|
258
198
|
const isDerived = indexed.entry.name.endsWith(".derived");
|
|
259
|
-
// `.derived` memories
|
|
260
|
-
// (see the synthetic `derived-memory-reflect-skipped` branch in the
|
|
261
|
-
// improve loop). Enqueueing them here just produced one synthetic skip
|
|
262
|
-
// per derived memory per hour with no real work — pure churn observed
|
|
263
|
-
// 2026-05-21: 11 derived refs re-planned every hour during idle periods.
|
|
264
|
-
// The cleanup phase (analyzeMemoryCleanup) inspects derived memories
|
|
265
|
-
// independently of `plannedRefs`, so dropping them here loses nothing.
|
|
199
|
+
// `.derived` memories skip reflect; cleanup inspects them on its own.
|
|
266
200
|
if (!isDerived && !planned.has(ref) && !strategyFiltered.has(ref)) {
|
|
267
|
-
|
|
268
|
-
// refs. If every per-ref pass (reflect + distill) on the active
|
|
269
|
-
// strategy would refuse this ref, drop it from `plannedRefs`. The
|
|
270
|
-
// caller emits `improve_skipped { reason: strategy_filtered_all_passes }`
|
|
271
|
-
// once `eventsCtx` is available so the audit trail is preserved in a
|
|
272
|
-
// single event per ref instead of 2× synthetic actions per run.
|
|
273
|
-
// Background: see /tmp/akm-health-investigations/planner-profile-metrics-deep-analysis.md
|
|
201
|
+
const candidate = { ref, filePath: indexed.filePath, itemRef: indexed.itemRef };
|
|
274
202
|
if (improveProfile && isStrategyFilteredForAllPasses(ref, improveProfile)) {
|
|
275
|
-
strategyFiltered.set(ref, {
|
|
276
|
-
ref,
|
|
277
|
-
reason: "strategy_filtered_all_passes",
|
|
278
|
-
filePath: indexed.filePath,
|
|
279
|
-
itemRef,
|
|
280
|
-
});
|
|
203
|
+
strategyFiltered.set(ref, { ...candidate, reason: "strategy_filtered_all_passes" });
|
|
281
204
|
}
|
|
282
205
|
else {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
reason: scope.mode === "type" ? "scope-type" : indexed.entry.type === "memory" ? "memory-cleanup" : "scope-type",
|
|
286
|
-
filePath: indexed.filePath,
|
|
287
|
-
itemRef,
|
|
288
|
-
});
|
|
206
|
+
const reason = scope.mode !== "type" && indexed.entry.type === "memory" ? "memory-cleanup" : "scope-type";
|
|
207
|
+
planned.set(ref, { ...candidate, reason });
|
|
289
208
|
}
|
|
290
209
|
}
|
|
291
210
|
if (indexed.entry.type === "memory") {
|
|
@@ -298,28 +217,14 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
298
217
|
plannedRefs: [...planned.values()],
|
|
299
218
|
memorySummary: { eligible: memoryEligible, derived: memoryDerived },
|
|
300
219
|
strategyFilteredRefs: [...strategyFiltered.values()],
|
|
301
|
-
indexSnapshot,
|
|
220
|
+
indexSnapshot: ready,
|
|
302
221
|
};
|
|
303
222
|
}
|
|
304
223
|
catch (error) {
|
|
305
|
-
// Empty-stash setup paths can open index.db before its schema exists.
|
|
306
224
|
rethrowIfTestIsolationError(error);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
memorySummary: { eligible: 0, derived: 0 },
|
|
311
|
-
strategyFilteredRefs: [],
|
|
312
|
-
indexSnapshot: describeUnavailableSnapshot(error),
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
if (readOnly && isIncompatibleIndexError(error)) {
|
|
316
|
-
return {
|
|
317
|
-
plannedRefs: [],
|
|
318
|
-
memorySummary: { eligible: 0, derived: 0 },
|
|
319
|
-
strategyFilteredRefs: [],
|
|
320
|
-
indexSnapshot: describeIncompatibleIndexSnapshot(error),
|
|
321
|
-
};
|
|
322
|
-
}
|
|
225
|
+
const snapshot = unreadableSnapshot(readOnly, error);
|
|
226
|
+
if (snapshot)
|
|
227
|
+
return empty(snapshot);
|
|
323
228
|
throw error;
|
|
324
229
|
}
|
|
325
230
|
finally {
|
|
@@ -327,30 +232,20 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
327
232
|
closeDatabase(db);
|
|
328
233
|
}
|
|
329
234
|
}
|
|
235
|
+
/** The parent memory a `--scope <memory ref>` cleanup is restricted to. */
|
|
330
236
|
export function memoryCleanupParentRef(scope, stashDir) {
|
|
331
237
|
if (scope.mode !== "ref" || !scope.value)
|
|
332
238
|
return undefined;
|
|
333
239
|
const parsed = parseRefInput(scope.value);
|
|
334
240
|
if (parsed.type !== "memory")
|
|
335
241
|
return undefined;
|
|
336
|
-
// Non-derived parent scope: emit the canonical `memories/<name>` conceptId so
|
|
337
|
-
// it matches `resolveParentRef`'s output in analyzeMemoryCleanup's parentRef
|
|
338
|
-
// filter (Group-C item 2 — the reader and every comparison site flipped
|
|
339
|
-
// together; emitting the raw scope value would re-open the mismatch the
|
|
340
|
-
// chunk-8 history warns about).
|
|
341
242
|
if (!parsed.name.endsWith(".derived"))
|
|
342
243
|
return conceptIdFromTypeName(parsed.type, parsed.name);
|
|
343
|
-
const
|
|
344
|
-
for (const source of sources) {
|
|
244
|
+
for (const source of resolveSourceEntries(stashDir)) {
|
|
345
245
|
const candidate = path.join(source.path, "memories", `${parsed.name}.md`);
|
|
346
246
|
if (!fs.existsSync(candidate))
|
|
347
247
|
continue;
|
|
348
|
-
const
|
|
349
|
-
const fm = parseFrontmatter(raw).data;
|
|
350
|
-
// The `source:` backref is normalized to the `memories/<name>` conceptId.
|
|
351
|
-
// That is exactly what
|
|
352
|
-
// analyzeMemoryCleanup's parentRef filter compares against — resolveParentRef
|
|
353
|
-
// emits the same conceptId — so the two sites stay in lockstep.
|
|
248
|
+
const fm = parseFrontmatter(fs.readFileSync(candidate, "utf8")).data;
|
|
354
249
|
const parent = parseMemoryRef(typeof fm.source === "string" ? fm.source : undefined);
|
|
355
250
|
if (parent)
|
|
356
251
|
return parent;
|
|
@@ -358,179 +253,29 @@ export function memoryCleanupParentRef(scope, stashDir) {
|
|
|
358
253
|
return conceptIdFromTypeName("memory", parsed.name.slice(0, -".derived".length));
|
|
359
254
|
}
|
|
360
255
|
export function isLessonCandidate(ref) {
|
|
361
|
-
// Only lesson assets need lesson-schema validation (description + when_to_use).
|
|
362
|
-
// Memories have their own distill path via shouldDistillMemoryRef.
|
|
363
|
-
// All other types go through reflect, not distill.
|
|
364
256
|
return parseRefInput(ref).type === "lesson";
|
|
365
257
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Planner-side check: should this ref enter the distill queue?
|
|
368
|
-
*
|
|
369
|
-
* Distill produces lessons from non-lesson sources. Two cases are eligible:
|
|
370
|
-
*
|
|
371
|
-
* 1. Memory refs that pass {@link shouldDistillMemoryRef} (the existing
|
|
372
|
-
* memory→lesson/knowledge promotion path).
|
|
373
|
-
*
|
|
374
|
-
* Refs whose `type` is in {@link DISTILL_REFUSED_INPUT_TYPES} (currently
|
|
375
|
-
* `lesson:*`) are explicitly excluded — distill refuses them at runtime and
|
|
376
|
-
* queuing them just produces a no-op `skipped` outcome per ref per hour. That
|
|
377
|
-
* planner waste was the bug fixed in commit
|
|
378
|
-
* fix(improve): drop distill-refused types from planner.
|
|
379
|
-
*
|
|
380
|
-
* Note: prior to this fix the gate used `isLessonCandidate(ref)` directly,
|
|
381
|
-
* which was true *only* for `lesson:*` refs — exactly the set distill refuses.
|
|
382
|
-
* The result: every hourly run re-queued the same lesson refs, the same skip
|
|
383
|
-
* message returned, and no work was ever done. See
|
|
384
|
-
* `tests/integration/commands/improve-distill-planner-skip-lessons.test.ts`.
|
|
385
|
-
*/
|
|
258
|
+
/** Should this ref enter the distill queue? Types distill refuses never do. */
|
|
386
259
|
export function isDistillCandidateRef(ref, stashDir) {
|
|
387
|
-
|
|
388
|
-
if (isDistillRefusedInputType(parsed.type))
|
|
260
|
+
if (isDistillRefusedInputType(parseRefInput(ref).type))
|
|
389
261
|
return false;
|
|
390
262
|
return shouldDistillMemoryRef(ref, stashDir);
|
|
391
263
|
}
|
|
264
|
+
/** A non-derived memory that is not marked `quality: proposed`. */
|
|
392
265
|
export function shouldDistillMemoryRef(ref, stashDir) {
|
|
393
266
|
const parsed = parseRefInput(ref);
|
|
394
267
|
if (parsed.type !== "memory")
|
|
395
268
|
return false;
|
|
396
|
-
const
|
|
397
|
-
for (const source of sources) {
|
|
269
|
+
for (const source of resolveSourceEntries(stashDir)) {
|
|
398
270
|
const candidate = `${source.path}/memories/${parsed.name}.md`;
|
|
399
271
|
if (!fs.existsSync(candidate))
|
|
400
272
|
continue;
|
|
401
|
-
|
|
402
|
-
const fm = parseFrontmatter(raw).data;
|
|
403
|
-
const quality = typeof fm.quality === "string" ? fm.quality : undefined;
|
|
404
|
-
if (quality === "proposed")
|
|
273
|
+
if (parseFrontmatter(fs.readFileSync(candidate, "utf8")).data.quality === "proposed")
|
|
405
274
|
return false;
|
|
406
|
-
|
|
275
|
+
break;
|
|
407
276
|
}
|
|
408
277
|
return !parsed.name.endsWith(".derived");
|
|
409
278
|
}
|
|
410
|
-
// ── Signal-delta eligibility helpers (0.8.0) ────────────────────────────────
|
|
411
|
-
//
|
|
412
|
-
// The 0.8.0 redesign replaced flat time-based cooldowns for reflect/distill
|
|
413
|
-
// with a *signal-delta* gate: a ref is re-eligible iff new feedback has
|
|
414
|
-
// landed since the last proposal was generated for it. These helpers build
|
|
415
|
-
// the two timestamp maps the gate needs in bulk, so the planner avoids
|
|
416
|
-
// N+1 queries across the full postCleanupRefs set.
|
|
417
|
-
/**
|
|
418
|
-
* Latest feedback event timestamp per ref in the active window. Reads all
|
|
419
|
-
* `feedback` events newer than `sinceIso` in one query and indexes by ref,
|
|
420
|
-
* keeping the maximum `ts` per ref.
|
|
421
|
-
*
|
|
422
|
-
* Only events with a meaningful payload count as "signal" — `metadata.signal`
|
|
423
|
-
* (positive/negative) OR `metadata.note` (a free-form annotation). Empty
|
|
424
|
-
* metadata events are ignored so a stray `akm feedback <ref>` invocation
|
|
425
|
-
* without a flag doesn't trigger downstream re-processing.
|
|
426
|
-
*/
|
|
427
|
-
export function buildLatestFeedbackTsMap(refs, sinceIso, itemRefByRef, eventsCtx) {
|
|
428
|
-
const out = new Map();
|
|
429
|
-
if (refs.length === 0)
|
|
430
|
-
return out;
|
|
431
|
-
// Correlate item_ref-keyed events and conceptId-keyed direct invocations.
|
|
432
|
-
const refByDurableKey = new Map(refs.flatMap((ref) => improveStateReadRefs(ref, itemRefByRef?.get(ref)).map((key) => [key, ref])));
|
|
433
|
-
const { events } = readEvents({ type: "feedback", since: sinceIso }, eventsCtx);
|
|
434
|
-
for (const e of events) {
|
|
435
|
-
const ref = e.ref ? refByDurableKey.get(e.ref) : undefined;
|
|
436
|
-
if (!ref)
|
|
437
|
-
continue;
|
|
438
|
-
const meta = e.metadata;
|
|
439
|
-
const hasSignal = meta !== undefined && (typeof meta.signal === "string" || typeof meta.note === "string");
|
|
440
|
-
if (!hasSignal)
|
|
441
|
-
continue;
|
|
442
|
-
const ts = e.ts ?? "";
|
|
443
|
-
if (ts > (out.get(ref) ?? ""))
|
|
444
|
-
out.set(ref, ts);
|
|
445
|
-
}
|
|
446
|
-
return out;
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Latest proposal timestamp per input-ref, filtered by source ('reflect' or
|
|
450
|
-
* 'distill'). Reads the corresponding `*_invoked` events from state.db —
|
|
451
|
-
* these events are emitted at proposal creation time and carry the *input*
|
|
452
|
-
* asset ref (`memories/foo`, `skills/bar`, etc.) directly. We use them rather than
|
|
453
|
-
* `listProposals` because distill proposals are keyed by the derived
|
|
454
|
-
* lesson/knowledge ref, not the source memory — joining back through the
|
|
455
|
-
* payload would be fragile.
|
|
456
|
-
*/
|
|
457
|
-
export function buildLatestProposalTsMap(refs, source, itemRefByRef, eventsCtx) {
|
|
458
|
-
const out = new Map();
|
|
459
|
-
if (refs.length === 0)
|
|
460
|
-
return out;
|
|
461
|
-
// Correlate item_ref-keyed events and conceptId-keyed direct invocations.
|
|
462
|
-
const refByDurableKey = new Map(refs.flatMap((ref) => improveStateReadRefs(ref, itemRefByRef?.get(ref)).map((key) => [key, ref])));
|
|
463
|
-
const eventType = source === "reflect" ? "reflect_invoked" : "distill_invoked";
|
|
464
|
-
const { events } = readEvents({ type: eventType }, eventsCtx);
|
|
465
|
-
for (const e of events) {
|
|
466
|
-
const ref = e.ref ? refByDurableKey.get(e.ref) : undefined;
|
|
467
|
-
if (!ref)
|
|
468
|
-
continue;
|
|
469
|
-
// For distill_invoked we only count attempts that produced (or attempted
|
|
470
|
-
// to produce) a real proposal — config_disabled outcomes (no LLM work was
|
|
471
|
-
// actually invoked) and llm_failed (transport/timeout — nothing to show
|
|
472
|
-
// for the attempt) should not move the signal-delta cursor forward.
|
|
473
|
-
// R10: quality_rejected and review_needed now also mint a real proposal
|
|
474
|
-
// row (see quality-gate.ts's `writeQualityRejection`), so — like queued —
|
|
475
|
-
// a real attempt was made and the cursor must advance, or the same
|
|
476
|
-
// already-rejected ref is re-selected and re-rejected on every run.
|
|
477
|
-
if (eventType === "distill_invoked") {
|
|
478
|
-
const outcome = e.metadata?.outcome;
|
|
479
|
-
if (outcome !== "queued" &&
|
|
480
|
-
outcome !== "skipped" &&
|
|
481
|
-
outcome !== "validation_failed" &&
|
|
482
|
-
outcome !== "quality_rejected" &&
|
|
483
|
-
outcome !== "review_needed") {
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
const ts = e.ts ?? "";
|
|
488
|
-
if (ts > (out.get(ref) ?? ""))
|
|
489
|
-
out.set(ref, ts);
|
|
490
|
-
}
|
|
491
|
-
return out;
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Signal-delta eligibility predicate.
|
|
495
|
-
*
|
|
496
|
-
* True iff `latestFeedback[ref]` is defined AND either no prior proposal
|
|
497
|
-
* exists for this (ref, source) OR `latestFeedback[ref] > lastProposal[ref]`.
|
|
498
|
-
*
|
|
499
|
-
* Refs with no feedback signal at all are ineligible by definition — the
|
|
500
|
-
* proactive-maintenance and high-salience fallback lanes (see
|
|
501
|
-
* `noFeedbackCandidates` later in the planner) handle never-rated assets
|
|
502
|
-
* separately.
|
|
503
|
-
*/
|
|
504
|
-
export function isSignalDeltaEligible(ref, latestFeedback, lastProposal) {
|
|
505
|
-
const fb = latestFeedback.get(ref);
|
|
506
|
-
if (!fb)
|
|
507
|
-
return false;
|
|
508
|
-
const lp = lastProposal.get(ref);
|
|
509
|
-
if (!lp)
|
|
510
|
-
return true;
|
|
511
|
-
return fb > lp;
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* H7 (#566): cooperative budget watchdog with a captured, RAII-cleared hard-kill.
|
|
515
|
-
*
|
|
516
|
-
* When the wall-clock budget expires, `onExhausted` (normally an
|
|
517
|
-
* `AbortController.abort`) signals cooperative cancellation so the run can drain
|
|
518
|
-
* its in-flight log/`state.db` flush and unwind naturally. A second hard-kill
|
|
519
|
-
* timer is then armed as a watchdog: it only `exit(0)`s if the drain itself
|
|
520
|
-
* overruns `hardKillGraceMs`, preventing the process from outliving the task
|
|
521
|
-
* timeout window (lock-cascade fix).
|
|
522
|
-
*
|
|
523
|
-
* Both timers are captured; the returned dispose() clears whichever is still
|
|
524
|
-
* pending. Callers invoke it from a `finally`, so a *clean* drain reaches the
|
|
525
|
-
* `finally` and cancels the pending hard-kill before it can fire — the previous
|
|
526
|
-
* detached `setTimeout(() => process.exit(0), 5000)` always fired, truncating a
|
|
527
|
-
* clean flush. The hard-kill timer is `unref()`-ed so it never keeps the event
|
|
528
|
-
* loop alive on its own: once the run drains it exits with its own code, not the
|
|
529
|
-
* forced 0.
|
|
530
|
-
*
|
|
531
|
-
* Dependencies are injectable purely so the concurrency-sensitive timing
|
|
532
|
-
* contract can be exercised deterministically in unit tests.
|
|
533
|
-
*/
|
|
534
279
|
export function shouldAnalyzeMemoryCleanup(scope, eligibleMemories, primaryStashDir) {
|
|
535
280
|
if (!primaryStashDir || eligibleMemories === 0)
|
|
536
281
|
return false;
|
|
@@ -538,47 +283,28 @@ export function shouldAnalyzeMemoryCleanup(scope, eligibleMemories, primaryStash
|
|
|
538
283
|
return true;
|
|
539
284
|
if (scope.mode === "type")
|
|
540
285
|
return scope.value === "memory";
|
|
541
|
-
|
|
542
|
-
return false;
|
|
543
|
-
return parseRefInput(scope.value).type === "memory";
|
|
286
|
+
return scope.value ? parseRefInput(scope.value).type === "memory" : false;
|
|
544
287
|
}
|
|
545
288
|
export function buildUtilityMap(refs, readOnly = false) {
|
|
546
289
|
const map = new Map();
|
|
547
290
|
if (refs.length === 0)
|
|
548
291
|
return map;
|
|
549
292
|
const refSet = new Set(refs.map((r) => r.ref));
|
|
550
|
-
|
|
551
|
-
try {
|
|
552
|
-
db = readOnly ? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true }) : openExistingDatabase();
|
|
553
|
-
if (!db)
|
|
554
|
-
return map;
|
|
555
|
-
const allDbEntries = getAllEntries(db);
|
|
293
|
+
withIndexDb(readOnly, (db) => {
|
|
556
294
|
const idToRef = new Map();
|
|
557
|
-
for (const indexed of
|
|
558
|
-
// Chunk-8 WI-8.5c: correlate on the SHORT conceptId to match the
|
|
559
|
-
// `ImproveEligibleRef.ref` set built in collectEligibleRefsFromIndex.
|
|
295
|
+
for (const indexed of getAllEntries(db)) {
|
|
560
296
|
const ref = conceptIdFromTypeName(indexed.entry.type, indexed.entry.name);
|
|
561
297
|
if (refSet.has(ref))
|
|
562
298
|
idToRef.set(indexed.id, ref);
|
|
563
299
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
map.set(ref, score.utility);
|
|
571
|
-
}
|
|
300
|
+
if (idToRef.size === 0)
|
|
301
|
+
return;
|
|
302
|
+
for (const [id, score] of getUtilityScoresByIds(db, [...idToRef.keys()])) {
|
|
303
|
+
const ref = idToRef.get(id);
|
|
304
|
+
if (ref)
|
|
305
|
+
map.set(ref, score.utility);
|
|
572
306
|
}
|
|
573
|
-
}
|
|
574
|
-
catch (err) {
|
|
575
|
-
rethrowIfTestIsolationError(err);
|
|
576
|
-
// best-effort: if DB unavailable, all utilities default to 0
|
|
577
|
-
}
|
|
578
|
-
finally {
|
|
579
|
-
if (db)
|
|
580
|
-
closeDatabase(db);
|
|
581
|
-
}
|
|
307
|
+
});
|
|
582
308
|
return map;
|
|
583
309
|
}
|
|
584
310
|
export async function findAssetFilePath(ref, stashDir, writableBundleIds) {
|