akm-cli 0.9.6 → 0.9.8-beta.1

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +408 -0
  2. package/dist/assets/hints/cli-hints-full.md +3 -3
  3. package/dist/assets/improve-strategies/catchup.json +40 -11
  4. package/dist/assets/improve-strategies/thorough.json +45 -7
  5. package/dist/assets/tasks/improve/akm-improve-frequent.yml +2 -2
  6. package/dist/commands/agent/contribute-cli.js +11 -0
  7. package/dist/commands/env/env.js +2 -21
  8. package/dist/commands/health/checks.js +0 -25
  9. package/dist/commands/health/improve-metrics.js +8 -34
  10. package/dist/commands/health/windows.js +0 -4
  11. package/dist/commands/health.js +1 -35
  12. package/dist/commands/improve/consolidate/eligibility.js +11 -5
  13. package/dist/commands/improve/extract.js +36 -32
  14. package/dist/commands/improve/improve-cli.js +1 -1
  15. package/dist/commands/improve/improve-strategies.js +0 -4
  16. package/dist/commands/improve/memory/memory-belief.js +15 -5
  17. package/dist/commands/improve/memory/memory-contradiction-detect.js +16 -25
  18. package/dist/commands/improve/memory/memory-improve.js +9 -20
  19. package/dist/commands/improve/preparation.js +53 -37
  20. package/dist/commands/improve/reflect.js +14 -18
  21. package/dist/commands/lint/base-linter.js +182 -19
  22. package/dist/commands/lint/index.js +21 -9
  23. package/dist/commands/migrate/config-extra-params.js +61 -0
  24. package/dist/commands/migrate/dead-residue.js +113 -0
  25. package/dist/commands/migrate/stale-txn.js +49 -0
  26. package/dist/commands/migrate-cli.js +42 -1
  27. package/dist/commands/proposal/proposal.js +1 -21
  28. package/dist/commands/proposal/repository.js +0 -4
  29. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -6
  30. package/dist/commands/read/curate.js +51 -18
  31. package/dist/commands/read/search-cli.js +24 -1
  32. package/dist/commands/read/show.js +2 -1
  33. package/dist/commands/sources/installed-stashes.js +5 -1
  34. package/dist/commands/sources/self-update.js +38 -1
  35. package/dist/commands/sources/sources-cli.js +17 -1
  36. package/dist/commands/tasks/tasks.js +0 -8
  37. package/dist/commands/url-checker.js +20 -28
  38. package/dist/core/adapter/adapters/agent-skills-adapter.js +2 -6
  39. package/dist/core/adapter/adapters/akm-adapter.js +2 -3
  40. package/dist/core/adapter/adapters/akm-lint.js +2 -20
  41. package/dist/core/adapter/adapters/akm-task-adapter.js +2 -6
  42. package/dist/core/adapter/adapters/akm-workflow-adapter.js +2 -6
  43. package/dist/core/adapter/adapters/dotenv-adapter.js +2 -22
  44. package/dist/core/adapter/adapters/generic-files-adapter.js +3 -9
  45. package/dist/core/adapter/adapters/llm-wiki-adapter.js +2 -6
  46. package/dist/core/adapter/adapters/okf-adapter.js +7 -18
  47. package/dist/core/adapter/adapters/shared.js +2 -0
  48. package/dist/core/adapter/adapters/tool-dir-shared.js +3 -9
  49. package/dist/core/adapter/adapters/website-snapshot-adapter.js +2 -6
  50. package/dist/core/adapter/validate-context.js +1 -3
  51. package/dist/core/asset/asset-placement.js +14 -5
  52. package/dist/core/asset/frontmatter.js +212 -0
  53. package/dist/core/asset/memory-archive.js +97 -0
  54. package/dist/core/common.js +66 -2
  55. package/dist/core/config/config-walker.js +6 -10
  56. package/dist/core/config/config.js +11 -8
  57. package/dist/core/config/engine-semantics.js +0 -2
  58. package/dist/core/extra-params.js +17 -13
  59. package/dist/core/improve-result.js +1 -3
  60. package/dist/core/json-schema.js +9 -11
  61. package/dist/core/state/migrations.js +52 -2
  62. package/dist/core/state-db.js +2 -1
  63. package/dist/execution/executable-identity.js +1 -3
  64. package/dist/execution/guarded-source.js +1 -6
  65. package/dist/indexer/bundle-identity-guard.js +6 -1
  66. package/dist/indexer/db/graph-db.js +139 -154
  67. package/dist/indexer/ensure-index.js +11 -19
  68. package/dist/indexer/graph/graph-boost.js +23 -34
  69. package/dist/indexer/graph/graph-extraction.js +12 -2
  70. package/dist/indexer/indexer.js +1 -1
  71. package/dist/indexer/lookup/adapter-concept-owner.js +12 -9
  72. package/dist/indexer/passes/memory-inference.js +7 -2
  73. package/dist/indexer/scan/drain-dir.js +2 -4
  74. package/dist/indexer/search/db-search.js +3 -3
  75. package/dist/indexer/search/fts-query.js +10 -15
  76. package/dist/indexer/search/search-source.js +0 -13
  77. package/dist/indexer/usage/usage-events.js +9 -1
  78. package/dist/indexer/walk/walker.js +11 -6
  79. package/dist/integrations/harnesses/claude/result-extractor.js +30 -15
  80. package/dist/integrations/harnesses/codex/result-extractor.js +43 -6
  81. package/dist/integrations/harnesses/copilot/result-extractor.js +39 -12
  82. package/dist/integrations/harnesses/gemini/result-extractor.js +40 -12
  83. package/dist/integrations/harnesses/index.js +0 -4
  84. package/dist/integrations/harnesses/openhands/result-extractor.js +51 -19
  85. package/dist/integrations/harnesses/pi/result-extractor.js +45 -12
  86. package/dist/integrations/lockfile.js +0 -14
  87. package/dist/integrations/session-logs/index.js +0 -81
  88. package/dist/llm/client.js +0 -12
  89. package/dist/llm/memory-infer.js +1 -3
  90. package/dist/llm/usage-telemetry.js +1 -3
  91. package/dist/registry/create-provider-registry.js +4 -0
  92. package/dist/registry/factory.js +4 -0
  93. package/dist/registry/resolve.js +23 -8
  94. package/dist/runtime.js +0 -12
  95. package/dist/scripts/akm-migrate-node.js +155 -251
  96. package/dist/scripts/akm-migrate.js +155 -251
  97. package/dist/setup/setup.js +6 -12
  98. package/dist/sources/providers/git-install.js +7 -2
  99. package/dist/sources/providers/tar-utils.js +1 -7
  100. package/dist/sources/snapshot-fetchers/content-extract.js +0 -17
  101. package/dist/sources/snapshot-fetchers/website-ingest.js +126 -0
  102. package/dist/storage/managed-db.js +20 -7
  103. package/dist/storage/repositories/events-repository.js +0 -81
  104. package/dist/storage/repositories/index-connection.js +45 -3
  105. package/dist/storage/repositories/index-entries-repository.js +0 -17
  106. package/dist/storage/repositories/index-fts-repository.js +24 -30
  107. package/dist/storage/repositories/index-utility-repository.js +0 -57
  108. package/dist/storage/repositories/index-vec-repository.js +25 -27
  109. package/dist/storage/repositories/task-history-repository.js +9 -3
  110. package/dist/tasks/backends/cron.js +49 -9
  111. package/dist/tasks/backends/launchd.js +8 -18
  112. package/dist/tasks/resolve-akm-bin.js +17 -2
  113. package/dist/tasks/run/task-history.js +21 -31
  114. package/dist/tasks/scheduler-invocation.js +8 -1
  115. package/dist/tasks/scheduler-sync.js +1 -6
  116. package/dist/tasks/source/bounded-document.js +1 -14
  117. package/dist/tasks/source/parse-task-source.js +23 -9
  118. package/dist/workflows/exec/child-workflow.js +1 -1
  119. package/dist/workflows/exec/native-executor.js +2 -2
  120. package/dist/workflows/exec/step-work.js +5 -17
  121. package/dist/workflows/exec/worktree.js +40 -6
  122. package/dist/workflows/freeze/task-bindings.js +2 -4
  123. package/dist/workflows/ir/compile.js +3 -14
  124. package/dist/workflows/ir/schema-v4.js +4 -6
  125. package/dist/workflows/ir/schema.js +2 -5
  126. package/dist/workflows/parser.js +23 -26
  127. package/dist/workflows/source-files.js +8 -13
  128. package/dist/workflows/source-ir/schema.js +1 -19
  129. package/docs/migration/v0.9.0-troubleshooting.md +12 -2
  130. package/docs/reference/cli.md +7 -1
  131. package/docs/reference/workflow-schema.md +24 -0
  132. package/package.json +2 -5
  133. package/dist/assets/improve-strategies/frequent.json +0 -15
  134. package/dist/assets/improve-strategies/memory-focus.json +0 -15
  135. package/dist/workflows/source-ir/compare.js +0 -17
package/CHANGELOG.md CHANGED
@@ -4,6 +4,414 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.9.8-beta.1] - 2026-09-01
8
+
9
+ A cleanup and stabilization release: deletion of machinery that policed the
10
+ codebase's shape rather than its behaviour, and — because auditing for that
11
+ machinery meant reading the code closely — a run of real defects it had been
12
+ sitting on top of.
13
+
14
+ Two security holes, two search-correctness bugs, a locale-dependent hash, a
15
+ deletion shield that failed open, and sixteen places that answered a failure
16
+ with a confident wrong answer instead of an error.
17
+
18
+ ### Fixed
19
+
20
+ - **Historical state migrations are reachable where akm cannot reinstall
21
+ itself** (#895). A migration flagged `historical-destructive` is refused
22
+ during an ordinary open — it needs a verified safety copy, taken under the
23
+ migration writer lock, so an unattended `akm index` can never quietly drop
24
+ operator state. That guard is correct and unchanged. Its *remedy* was not:
25
+ the only code path that admitted the migration ran as a post-install step of
26
+ `akm upgrade`, behind an npm install. Where akm is installed globally by a
27
+ container image and the runtime user is unprivileged, that install fails
28
+ `EACCES` and throws long before the migration is reached, so `akm index
29
+ --full` was blocked with no supported way out — and the two obvious
30
+ workarounds are both wrong (upgrading as root installs a *newer* akm than the
31
+ image ships, which `config-version-shim` then fails closed against; deleting
32
+ `state.db` destroys task history, proposals, and lessons metadata, none of
33
+ which is derived). New `akm upgrade --state-only` applies pending state
34
+ migrations and installs nothing. Nothing about the migration needed the
35
+ network, root, or a new binary; coupling it to one was the bug. The safety
36
+ copy is still taken — this changes who may request the migration, not what it
37
+ does.
38
+
39
+ - **Two security holes closed.** A failed `git ls-remote` made
40
+ `verifyClonedRevision` a no-op, silently skipping the R-011 post-clone
41
+ revision-integrity check — the guard against a compromised mirror — so any
42
+ network blip disabled it without a word. And `scanExtractedFiles`, the
43
+ post-extraction TOCTOU path-traversal rescan, returned silently when a
44
+ directory could not be read: it passed clean in exactly the race it exists
45
+ to catch. Both now fail loudly.
46
+
47
+ - **`akm curate --type` ignored the type filter.** A set `--type` bypassed
48
+ `selectCuratedStashHits` entirely and did a raw slice of the hits as
49
+ received, which neither filtered by type nor ranked by score — so
50
+ `--type command` could return a skill, in arbitrary order. It now narrows
51
+ the candidate pool and runs the full curation pipeline over it. The test
52
+ that should have caught this passed by accident, because the off-type hit
53
+ happened to sit past the result limit.
54
+
55
+ - **Curate silently dropped relevant results.** An undocumented floor excluded
56
+ any hit scoring below `max(0.35, leader * 0.7)`. Measured against a real
57
+ 3,265-memory bundle, one query went from one result to four once it was
58
+ removed — the suppressed hits were the ones actually matching. Ordering is
59
+ unchanged: results were already sorted before the floor ran, so it could
60
+ only ever hide the tail.
61
+
62
+ - **A bundle-audit hash depended on the machine's locale.** `canonicalJson`
63
+ in `installed-stashes.ts` sorted keys with `localeCompare` — ICU-dependent —
64
+ and fed the result to `sha256Hex`, so the same object could hash differently
65
+ on two machines. Now codepoint order, matching the other implementations.
66
+
67
+ - **A deletion shield failed open.** `isHotCapturedMemory` returned false when
68
+ it could not read or parse a memory, marking exactly the memories it failed
69
+ to inspect as fair game for consolidate to merge or delete. It now fails
70
+ closed.
71
+
72
+ - **Sixteen places returned a confident wrong answer instead of an error.**
73
+ The pattern (#791) recurred across search, graph, indexing, and scheduling:
74
+ a corrupt or locked index reported "no matches"; a failed inline rebuild
75
+ surfaced as "Index is empty. Run 'akm index'" at exit 0; `akm show` reported
76
+ "0 related files" for an unreadable index; a DB failure in
77
+ `listProposalsReadOnly` returned `[]`, so dedup could not tell "no prior
78
+ proposal" from "database unreadable" and could re-mint an already-rejected
79
+ one; a permission fault on the bundle directory told the user to run
80
+ `akm bundle create`; and `launchd`'s `list()` returned `[]` when the
81
+ LaunchAgents directory could not be read, so task reconciliation concluded
82
+ there was nothing to manage. In each case the failure now surfaces.
83
+
84
+ - **Drifted copies of shared helpers.** `asNonEmptyString` trimmed in two
85
+ places and not in four others, so a whitespace-only session id decoded to
86
+ `" "` in some subsystems and `undefined` in others. `isPlainObject` had two
87
+ incompatible definitions under one name; the loose form would accept a
88
+ `Date` or class instance as a plain record. Both consolidated, along with
89
+ `toPosix` (15 copies, whose "avoid an import cycle" justification was false
90
+ in every case), `isRecord` (11), `compareCodePoints` (8), and five more
91
+ families.
92
+
93
+ - **Pruning a memory no longer leaves dangling belief edges (#885).**
94
+ `writeContradictEdge` — the hardened, test-covered `contradictedBy` writer —
95
+ had no production caller; its docstring named three that did not exist. The
96
+ live pass used a private near-copy that had drifted on exactly the two
97
+ behaviours the original was hardened for: it read the key with
98
+ `Array.isArray` only, so a SCALAR edge (live data the indexer accepts and
99
+ lint never flags) read as "no edges" and was overwritten out of existence;
100
+ and it set `beliefState: "contradicted"` unconditionally, promoting an
101
+ `archived` memory back up the ranking. The tests for both behaviours were
102
+ guarding dead code. The copy is gone.
103
+
104
+ `persistBeliefStateTransition` is deliberately NOT routed through the shared
105
+ primitive: it is a state-transition writer that replaces the edge list
106
+ wholesale and can clear it, which an append-only, never-weaken primitive
107
+ cannot express.
108
+
109
+ - **`git worktree` operations no longer fail on a busy machine (#891).** The
110
+ module's internal `GIT_TIMEOUT_MS` was a flat 30s with no margin, so a
111
+ healthy `git worktree remove` on a loaded host returned
112
+ `{ removed: false, error: "timed out after 30000ms" }`. Anyone running a
113
+ workflow with `isolation: worktree` alongside other git activity could hit
114
+ spurious create failures, or have a clean worktree wrongly retained as
115
+ unremovable. Raised to 120s, matching the existing `GIT_PUSH_TIMEOUT_MS`
116
+ precedent. Note `scripts/test-integration.sh` had already raised *bun's*
117
+ per-test timeout for this reason, but that never touched the production
118
+ subprocess timeout, which fires first.
119
+
120
+ - **`akm health`'s dead-link check no longer reports an unearned all-clear
121
+ (#892).** `checkDeadUrls` capped at 20 URLs across the whole bundle, plus an
122
+ undocumented `slice(0, 3)` per entry, then the caller logged "URL check
123
+ complete (0 dead)". A bundle with thousands of links got a clean bill of
124
+ health after twenty were examined. Both caps removed; every URL is checked
125
+ and a failed request surfaces instead of being swallowed.
126
+
127
+ - **`akm lint --fix` no longer breaks on gitignored drafts (#887).** The doc
128
+ linter walked `docs/` with no gitignore awareness, so unpublished drafts
129
+ under `docs/.pending/` were held to the published-docs contract —
130
+ `bun run lint` failed locally while CI, which never has those files, stayed
131
+ green.
132
+
133
+ ### Changed
134
+
135
+ - **Search no longer truncates long queries (#892).** `MAX_LEXICAL_QUERY_TOKENS
136
+ = 16` silently dropped every token past the sixteenth, and tokens are
137
+ collected in order, so the discarded half was the tail — for
138
+ natural-language input, usually where the discriminating words are. It also
139
+ fed ranking, so token-overlap scoring ran on the truncated set too. It was
140
+ unexplained in the code and in the commit that introduced it, and unreachable
141
+ from any flag, config key, or environment variable. Removed: the planner
142
+ handles 10,000 tokens in 9ms, so no performance cliff was being protected.
143
+
144
+ - **Content and memory bodies are no longer silently truncated.**
145
+ `MAX_CONTENT_CHARS` (100k, duplicated across 8 adapters) cut indexed content
146
+ so the tail of a long document was unsearchable; `MAX_BODY_CHARS` (4000) cut
147
+ the text sent for memory inference, so on a large-context engine the model
148
+ saw a fraction of the input while the derived memory looked complete. Both
149
+ removed.
150
+
151
+ - **GitHub Actions are pinned to commit SHAs (#768).** All 29 `uses:` steps
152
+ across every workflow, with the tag preserved in a trailing comment.
153
+
154
+ - **Gated CI runs on schedule, dispatch, and candidate tags only.** The
155
+ `detect-changes` job that selected suites by regex-matching a PR diff is
156
+ gone — its path patterns had gone stale and still named test files this
157
+ release moved or deleted, so it was silently under-selecting suites. Release
158
+ evidence is unchanged; the checklist always required an exact-SHA dispatch.
159
+
160
+ - **`akm-eval` in CI is now a determinism check only.** Its score gates are
161
+ removed. Measured before cutting: the baseline scored a perfect 1.0 against
162
+ a 0.75 gate, and seven of nine case types never ran — CI has no LLM and no
163
+ run history, so everything the eval exists to measure was skipped while the
164
+ job reported green. The harness itself is unchanged and remains a genuine
165
+ quality signal when run against a real bundle.
166
+
167
+ ### Removed
168
+
169
+ - **`akm health --clean-dead-residue` and every other compensating shim.**
170
+ A special-purpose flag on `health` that deleted files existed because
171
+ migrations had not finished their own job. Removing a superseded layout IS
172
+ migration, so `akm migrate status` now reports it and `akm migrate apply`
173
+ removes it. Four more of the same class went the same way: the D8
174
+ `task_history` vocabulary is rewritten once by a state migration instead of
175
+ re-decided at three read sites; the legacy `extraParams` config lift happens
176
+ once in `migrate apply` instead of silently on every load forever (an
177
+ unmigrated config now fails closed, naming the command to run); retired
178
+ `type:slug` xrefs are rewritten by `lint --fix`; and stale transaction
179
+ journals are recovered by `migrate apply` rather than reported by an
180
+ advisory that pointed at documentation while the recovery function it needed
181
+ sat with zero callers.
182
+
183
+ - **Dead code with no consumers.** An empty `{}` type threaded through two
184
+ functions as a parameter neither read, with a `{}` placeholder at the call
185
+ site; unused helpers, imports, and parameters throughout.
186
+
187
+ - **14 architecture ratchets, 15 golden/snapshot suites, 12 characterization
188
+ suites.** Function-size and import-cycle ratchets that failed on refactors
189
+ harming nothing; byte-for-byte snapshots "fixed" by regenerating; tests that
190
+ pinned "what the code does today" by definition.
191
+ - **11 of 14 lint scripts (-3,988 lines).** `lint-tests-isolation` alone was
192
+ 717 lines standing in front of `src/core/paths.ts`, which already throws
193
+ `TEST_ISOLATION_MISSING` at runtime. Also removed: the `gen-config-schema
194
+ --check` gate (`build` regenerates the schema anyway, so what ships is always
195
+ current), and `lint-devto-posts` (322 lines that were wired to nothing and
196
+ duplicated checks `devto-cli` performs itself). The three kept catch
197
+ user-visible problems: secrets must route through the resolver, no dead refs
198
+ in shipped assets, no docs teaching commands that do not exist.
199
+ - **Both `MIN_TESTS` floors** — arbitrary numbers whose only job was to fail
200
+ the suite when test count dropped.
201
+ - **`release-workflow-syntax.yml`** — a workflow that ran actionlint over the
202
+ other workflows. A broken workflow file already fails at GitHub.
203
+ - **Dead `$STASH/.akm` residue is now reportable (#889).** `akm health` gains a
204
+ read-only `stash-dead-residue` advisory naming each stale path and its size;
205
+ deletion is gated behind an explicit `akm health --clean-dead-residue`. On a
206
+ real bundle 82% of `.akm` (135 MB) was pre-0.9.0 leftovers no code reads.
207
+
208
+ ### Testing
209
+
210
+ - Test isolation grandfather list drained 58 -> 2 (#785); the two retained
211
+ entries are the helper-definitions file and a meta-test of the guard itself.
212
+ - The test tree now has a stated unit/integration rule and ~250 files were
213
+ reclassified by evidence — real DB, network, or process spawn — rather than
214
+ by filename (#786).
215
+ - 31 status-only `not.toBe(0)` assertions replaced with exact exit codes and
216
+ machine-readable error codes, every value observed rather than inferred
217
+ (#787).
218
+ - Near-duplicate clusters drained (#788); real-server timing flakes removed or
219
+ made deterministic (#789).
220
+ - Kept deliberately: `registry-network-boundary.test.ts`, which enforces that
221
+ only `pinned-transport.ts` may import `node:http`/`node:https` — the control
222
+ that stops a raw request bypassing address-pinned fetching. No lint script
223
+ covers that boundary.
224
+
225
+ ## [0.9.7] - 2026-08-31
226
+
227
+ ### Added
228
+
229
+ - **`akm curate --pack <tokens>` (#746).** Packs the ranked stash hits' full
230
+ content into a single token-budgeted blob instead of returning refs the
231
+ caller has to follow up on with N separate `akm show` calls. Content is
232
+ resolved through the same path `akm show` uses, so a `ref#fragment` hit
233
+ packs just that section. Drops whole assets from the tail of the ranked
234
+ list first; only a single highest-rank hit that alone exceeds the budget
235
+ is truncated. Registry hits are never packed — that separation stays
236
+ opt-in and there is no flag to override it. Named `--pack` rather than
237
+ `--budget` because the workflow asset schema already uses `budget` for
238
+ run-cost caps.
239
+
240
+ - **`llms.txt`-aware website ingestion (#749).** `akm bundle add` against an
241
+ origin root now probes `<origin>/llms.txt` first and, when present, uses
242
+ that author-curated link list as the crawl frontier instead of discovering
243
+ links by parsing HTML. Each linked page still flows through the same
244
+ robots-compliant, host-guarded, content-extracting fetch as before, so
245
+ ingested pages stay individually addressable. Off-origin manifest entries
246
+ are dropped. The probe is restricted to origin-root URLs, so adding a
247
+ specific page still fetches that page. Sites without the file fall through
248
+ to the existing crawler unchanged. `llms-full.txt` is deliberately not
249
+ read: its `## <path>` separators are ambiguous against real page content.
250
+
251
+ ### Changed
252
+
253
+ - **The improve-strategy presets were reviewed as a set (#878).** The
254
+ investigation first believed `thorough` silently ran without validation;
255
+ that was wrong — every strategy deep-merges onto `default` before
256
+ resolving, so the old asset behaved correctly. What was real: `thorough`
257
+ was the only preset of ten relying on that invisible inheritance instead
258
+ of an explicit process matrix, and its triage used `applyMode: "queue"`
259
+ while its description promised to "drain the backlog" — queue mode never
260
+ promotes, so nothing drained. `catchup` had the same inertness:
261
+ `maxAcceptsPerRun: 100` under queue mode, a key the promote loop never
262
+ reads.
263
+
264
+ Both now use the same judged-promotion pattern as `reflect-distill` and
265
+ `proactive-maintenance`: `applyMode: "promote"`, `judgment: true`, a
266
+ per-run accept cap (`thorough` 25, `catchup` 100), and `maxDiffLines:
267
+ 200`. The autonomy gate demotes promote back to queue unless
268
+ `experimental.improveAutonomy` is enabled, so by default both still
269
+ review-only; with autonomy on, they actually drain. `thorough` now
270
+ carries default's full explicit matrix, and a regression test pins
271
+ "everything default enables, thorough enables identically, plus triage."
272
+
273
+ ### Fixed
274
+
275
+ - **Pruning a memory no longer leaves dangling belief edges (#884).**
276
+ `analyzeMemoryCleanup` archives a pruned memory rather than deleting it —
277
+ the file moves under `.akm/memory-cleanup/archive/` beside a `cleanup.md`
278
+ audit record naming its `ref` and `originalPath`. Ref resolution only ever
279
+ looked at the memory's original location, so once #882 made
280
+ `contradictedBy`/`supersededBy` validatable, every memory pointing at a
281
+ pruned one began reporting `missing-ref`. `refExistsInAnyStash` now resolves
282
+ the archive tombstone, so a belief edge to an archived memory is satisfied
283
+ instead of dangling. Existence only: `resolveRefPathInStash` still ignores
284
+ the archive, because it returns a path callers mutate (`--supersedes`
285
+ demotion) and an archived file must never be written to. The contradiction
286
+ an edge records is preserved rather than erased, which a bare edge-scrub
287
+ would have destroyed.
288
+
289
+ A ref whose target has neither a file nor a tombstone was removed by
290
+ something other than prune — a hand `git rm`, or a pre-fix release — and is
291
+ genuinely dangling. Clearing those deletes user assertions, so it is opt-in
292
+ behind `akm lint --prune-dangling-edges` and is deliberately NOT folded into
293
+ `--fix`: every other auto-fix repairs a malformed file, whereas this one
294
+ edits well-formed files to drop a claim about the belief graph. The repair
295
+ is scoped to the `supersededBy`/`contradictedBy` channels — a stale `xrefs`
296
+ entry is an ordinary broken link that the author may want to re-target, not
297
+ delete — and preserves comments, key order, and surviving list entries. It
298
+ clears the same `writable: false` gate `--fix` does.
299
+
300
+ Known limitation: if a memory's only edge on a channel is dropped, a
301
+ `beliefState:` naming that channel is left without a supporting edge.
302
+ Choosing a demotion target is a belief-semantics decision this fix does not
303
+ make. No occurrences existed in the stash that surfaced #884.
304
+
305
+ - **A corrupt `index.db` now rebuilds instead of failing the command
306
+ (#865).** `src/core/state-db.ts` documented that "a corrupt index is
307
+ recovered by deleting it and re-running `akm index`" — that recovery was
308
+ never implemented. Real on-disk corruption surfaced a raw `SQLITE_CORRUPT`
309
+ and exited 70, and the existing inline-reindex fallback could not help
310
+ because it reopened the same corrupt file. `openIndexDatabase` now detects
311
+ corruption, removes the file and its `-wal`/`-shm` sidecars, and retries
312
+ the open once. Scoped to `index.db`, which is fully regenerable from the
313
+ stash; `state.db` is never touched.
314
+
315
+ - **`akm task sync` accepts an immutable package-local install (#868).** An
316
+ image-baked akm pinned at a path outside the npm global root was refused
317
+ outright, and `--rebind` then warned on every sync that the pinned binary
318
+ was "mutable, unproven" — the opposite of true, training operators to
319
+ ignore a recurring warning. The npm-global check was only ever a proxy for
320
+ "this binary won't change out from under the scheduler"; that property is
321
+ now tested directly, so a launcher this process cannot write to (a
322
+ read-only mount) is eligible without `--rebind` and without the warning.
323
+ Writable `npx`/project-`node_modules` installs remain ineligible, which is
324
+ the case the original check existed for. No new config knob.
325
+
326
+ - **`TASK_SCHEMA_VERSION_UNSUPPORTED` names the actual blocker (#869).** For
327
+ a v2/v3 file the message is only reached after the read-time shim has
328
+ already tried and failed to convert it — meaning a human decision is
329
+ required — yet it pointed at `akm migrate apply`, which would report the
330
+ identical block for that same file. The blocked `reason`/`detail` were
331
+ already computed and then discarded; they are now surfaced, so the error
332
+ names the specific reason (e.g.
333
+ `shell-command-resolution-changes-v2-literal-argv-semantics`) and says a
334
+ decision is needed rather than sending the operator to a command that
335
+ cannot help.
336
+
337
+ Also verified as already fixed and left alone: cross-bundle scoping (a
338
+ blocked file in one bundle does not prevent conversion in another —
339
+ resolved by #866) and `env`-prefixed command conversion (resolved by
340
+ #867).
341
+
342
+ - **`akm task sync` no longer dead-ends on pre-0.9.2 crontab rows (#881).**
343
+ Rows written by v0.9.0/v0.9.1 carry no `--scheduler-context` marker, so
344
+ `extractCronInvocation` could not parse them. Sync therefore saw the task
345
+ as absent, tried to install it, and collided with the row that was still
346
+ physically present — permanently blocking sync for every task on any
347
+ install upgrading past v0.9.2. Such a row is now recognized and
348
+ reconciled. The fallback fires only when the marker is entirely absent
349
+ and only inside akm's own `# akm:task … BEGIN/END` sentinels, so a row
350
+ carrying the marker that fails to parse for any other reason is never
351
+ reinterpreted, and no ownership or cardinality assertion changed.
352
+
353
+ - **`akm lint` validates `type:slug` xrefs instead of silently skipping
354
+ them (#882).** `typeNameFromConceptId` returns `undefined` for the colon
355
+ grammar, so `classifyConceptRef` treated those refs as "not a local ref"
356
+ and dropped them before validation — roughly half the xref channel on a
357
+ real corpus. Zero `missing-ref` findings read as "every reference is
358
+ healthy" when half had never been examined. akm still writes this
359
+ grammar today (`memory-contradiction-detect.ts` emits `contradictedBy:`
360
+ as `memory:<name>`), so it is a live channel, not legacy residue; it is
361
+ now accepted as a fallback in lint only, leaving the parser and write
362
+ paths on the single conceptId grammar.
363
+
364
+ - **Refs to derived memories resolve (#882).** `assetPathCandidatesForName`
365
+ only ever offered `<name>.md`, so any ref to a memory stored as
366
+ `<name>.derived.md` failed to resolve — affecting the conceptId grammar
367
+ equally, and invisible until the above fix made these refs validatable.
368
+ `.derived` is a provenance marker on the same identity, not part of the
369
+ name, so `<name>.derived.md` is now a secondary candidate with plain
370
+ `<name>.md` still taking precedence when both exist. Fixed at the
371
+ resolution primitive, so write-time `--xref`/`--supersedes` validation
372
+ benefits as well.
373
+
374
+ ### Removed
375
+
376
+ - **The `frequent` and `memory-focus` improve strategies.** Zero recorded
377
+ invocations, and both were mid-points of other presets (`frequent` ≈
378
+ `reflect-distill` without distill or triage; `memory-focus` a subset of
379
+ `frequent`). The shipped hourly task template now uses `reflect-distill`
380
+ — matching what real deployments had already switched to by hand. Any
381
+ removed combination remains expressible via `improve.strategies` in
382
+ config. One caveat: a user-config strategy *named* `frequent` or
383
+ `memory-focus` previously merged over the built-in of the same name; it
384
+ now merges over `default` only, so a partial override that relied on the
385
+ built-in's values will resolve differently and should be made explicit.
386
+
387
+ ### Testing
388
+
389
+ - **The previous-release corpus covers more than task sources (#880).**
390
+ Added fixtures for the synthesized `AKM_BUNDLE_DIR` duplicate-`stash`
391
+ bundle shape that caused #870, the retired 0.8 `stashDir`/`sources[]`/
392
+ `installed[]` config keys, and a downstream consumer's `config.json` plus
393
+ four task-source-v4 files. The 0.8 fixture deliberately asserts the
394
+ opposite of the others: those keys are hard-rejected by design, so it
395
+ guards that the rejection stays loud and actionable rather than silently
396
+ loading. Every fixture was proven to catch a regression by breaking the
397
+ guarantee, observing the failure, and reverting.
398
+
399
+ - Pinned current handling of truncated LLM responses (`finishReason:
400
+ "length"`) as a regression test (#865). No distinct classification or
401
+ retry was added: the underlying truncation was already resolved by
402
+ `reasoningEffort`, and threading the finish reason through four layers to
403
+ earn one diagnostic label was not justified.
404
+
405
+ ### Not shipped
406
+
407
+ - **Orphan-asset detection for `akm health` (#750)** was implemented and
408
+ then backed out after measurement. Against a live 23,859-entry index it
409
+ flagged 16,609 of 16,615 in-scope assets (99.96%) and took 135 s. The
410
+ corpus is not densely cross-linked — 1,591 distinct xref tokens exist in
411
+ total — so the check has no signal to give. The broken-ref half of #750
412
+ had already shipped previously. Moved to Backlog; see #882 for the
413
+ resolver defect the investigation exposed.
414
+
7
415
  ## [0.9.6] - 2026-08-31
8
416
 
9
417
  The deletion release: **net −2,100 lines**, almost all of it machinery that
@@ -245,8 +245,8 @@ akm improve --no-push # commit but skip push for this ru
245
245
  akm improve --sync # force sync even on strategies that disable it
246
246
  ```
247
247
 
248
- Strategy sync defaults: `catchup`, `consolidate`, `default`, `frequent`,
249
- `graph-refresh`, `memory-focus`, `quick`, and `thorough` auto-commit + push;
248
+ Strategy sync defaults: `catchup`, `consolidate`, `default`,
249
+ `graph-refresh`, `quick`, and `thorough` auto-commit + push;
250
250
  `proactive-maintenance` and `reflect-distill` skip sync entirely. Override
251
251
  with `--sync` / `--no-sync` flags.
252
252
 
@@ -355,7 +355,7 @@ scheduler (cron / launchd / schtasks). The file is the source of truth:
355
355
  file plus one `sync` is a complete workflow.
356
356
 
357
357
  ```sh
358
- akm task add nightly-improve --schedule "@daily" --command "akm improve --strategy frequent"
358
+ akm task add nightly-improve --schedule "@daily" --command "akm improve --strategy default"
359
359
  akm task add briefing --schedule "0 9 * * *" --prompt "Draft the morning briefing" # Inline command task
360
360
  akm task sync # Reconcile task files with the OS scheduler
361
361
  akm task sync --rebind # Also re-pin the scheduler's akm binary/spelling
@@ -1,15 +1,44 @@
1
1
  {
2
- "description": "Manual catch-up consolidation + triage drain with no interval minimum.",
2
+ "description": "Manual catch-up \u2014 consolidation plus a judged triage drain (up to 100 accepts) with no interval minimum. Auto-accepts only when improve autonomy is enabled; otherwise proposals queue for review.",
3
3
  "processes": {
4
- "reflect": { "enabled": false },
5
- "distill": { "enabled": false },
6
- "consolidate": { "enabled": true, "allowedTypes": ["memory"], "maxChunkSize": 50, "minPoolSize": 0 },
7
- "memoryInference": { "enabled": false },
8
- "graphExtraction": { "enabled": false },
9
- "extract": { "enabled": false },
10
- "triage": { "enabled": true, "applyMode": "queue", "policy": "personal-stash", "maxAcceptsPerRun": 100 },
11
- "validation": { "enabled": false },
12
- "proactiveMaintenance": { "enabled": false }
4
+ "reflect": {
5
+ "enabled": false
6
+ },
7
+ "distill": {
8
+ "enabled": false
9
+ },
10
+ "consolidate": {
11
+ "enabled": true,
12
+ "allowedTypes": ["memory"],
13
+ "maxChunkSize": 50,
14
+ "minPoolSize": 0
15
+ },
16
+ "memoryInference": {
17
+ "enabled": false
18
+ },
19
+ "graphExtraction": {
20
+ "enabled": false
21
+ },
22
+ "extract": {
23
+ "enabled": false
24
+ },
25
+ "triage": {
26
+ "enabled": true,
27
+ "applyMode": "promote",
28
+ "policy": "personal-stash",
29
+ "maxAcceptsPerRun": 100,
30
+ "judgment": true,
31
+ "maxDiffLines": 200
32
+ },
33
+ "validation": {
34
+ "enabled": false
35
+ },
36
+ "proactiveMaintenance": {
37
+ "enabled": false
38
+ }
13
39
  },
14
- "sync": { "enabled": true, "push": true }
40
+ "sync": {
41
+ "enabled": true,
42
+ "push": true
43
+ }
15
44
  }
@@ -1,15 +1,53 @@
1
1
  {
2
- "description": "Like default, plus the triage process (drains the pending-proposal backlog).",
2
+ "description": "Everything default runs, plus a judged triage drain of the pending-proposal backlog (up to 25 accepts per run). Auto-accepts only when improve autonomy is enabled; otherwise proposals queue for review. Identical tuning to default; improve-stage extract and proactive maintenance stay off.",
3
3
  "processes": {
4
4
  "reflect": {
5
5
  "enabled": true,
6
+ "limit": 25,
6
7
  "allowedTypes": ["agent", "command", "knowledge", "lesson", "memory", "skill", "workflow"]
7
8
  },
8
- "distill": { "enabled": true, "allowedTypes": ["memory"] },
9
- "consolidate": { "enabled": true, "allowedTypes": ["memory"] },
10
- "memoryInference": { "enabled": true },
11
- "graphExtraction": { "enabled": true },
12
- "triage": { "enabled": true, "applyMode": "queue" }
9
+ "distill": {
10
+ "enabled": true,
11
+ "allowedTypes": ["memory"],
12
+ "requirePlannedRefs": true
13
+ },
14
+ "consolidate": {
15
+ "enabled": true,
16
+ "allowedTypes": ["memory"],
17
+ "minPoolSize": 500
18
+ },
19
+ "memoryInference": {
20
+ "enabled": true
21
+ },
22
+ "graphExtraction": {
23
+ "enabled": true
24
+ },
25
+ "extract": {
26
+ "enabled": false,
27
+ "triage": {
28
+ "enabled": true,
29
+ "minScore": 2
30
+ }
31
+ },
32
+ "validation": {
33
+ "enabled": true
34
+ },
35
+ "proactiveMaintenance": {
36
+ "enabled": false,
37
+ "dueDays": 30,
38
+ "maxPerRun": 15
39
+ },
40
+ "triage": {
41
+ "enabled": true,
42
+ "applyMode": "promote",
43
+ "policy": "personal-stash",
44
+ "maxAcceptsPerRun": 25,
45
+ "maxDiffLines": 200,
46
+ "judgment": true
47
+ }
13
48
  },
14
- "sync": { "enabled": true, "push": true }
49
+ "sync": {
50
+ "enabled": true,
51
+ "push": true
52
+ }
15
53
  }
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm improve --strategy frequent --skip-if-locked
3
- description: Frequent inference pass (hourly at :40; improve-stage extract off)
2
+ run: akm improve --strategy reflect-distill --skip-if-locked
3
+ description: Frequent learning pass (hourly at :40) reflect, distill, memory inference, judged triage
4
4
  schedule: "40 * * * *"
@@ -139,6 +139,16 @@ export const lintCommand = defineCommand({
139
139
  description: "Exit non-zero when summary.flagged > 0 (CI-friendly). Default: exit 0 regardless of findings.",
140
140
  default: false,
141
141
  },
142
+ // #884: NOT folded into `--fix`. Every other auto-fix repairs a structural
143
+ // defect in the file it edits; this one DELETES an assertion the user (or
144
+ // the improve pipeline) made about the belief graph, and it does so in
145
+ // files that are themselves perfectly well-formed. That is a data decision,
146
+ // so it stays opt-in behind its own flag even when `--fix` is present.
147
+ "prune-dangling-edges": {
148
+ type: "boolean",
149
+ description: "Opt-in repair: drop supersededBy/contradictedBy entries whose target has neither a file nor a prune tombstone. Modifies memories — review the plain `akm lint` report first.",
150
+ default: false,
151
+ },
142
152
  type: {
143
153
  type: "string",
144
154
  description: "Only lint assets of this type (e.g. workflows, tasks, memories). akm bundles only — every other adapter validates the whole bundle and warns that the flag had no effect.",
@@ -151,6 +161,7 @@ export const lintCommand = defineCommand({
151
161
  fix: args.fix === true || getHyphenatedBoolean(args, "auto-fix"),
152
162
  dir: getStringArg(args, "dir"),
153
163
  typeFilter: getStringArg(args, "type"),
164
+ pruneDanglingEdges: getHyphenatedBoolean(args, "prune-dangling-edges"),
154
165
  });
155
166
  output("lint", result);
156
167
  if (args["fail-on-flagged"] && result.summary.flagged > 0) {
@@ -48,28 +48,9 @@
48
48
  import fs from "node:fs";
49
49
  import path from "node:path";
50
50
  import dotenv from "dotenv";
51
- import { writeFileAtomic } from "../../core/common.js";
51
+ import { scanEnvKeyNames, writeFileAtomic } from "../../core/common.js";
52
52
  import { sensitiveMarkerPath } from "./marker-path.js";
53
- /** Matches a KEY=value assignment line, capturing only the key. */
54
- const ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
55
- /** Scan lines and return KEY names in file order, without duplicates. */
56
- export function scanEnvKeyNames(text) {
57
- const keys = [];
58
- const seen = new Set();
59
- for (const line of text.split(/\r?\n/)) {
60
- const m = line.match(ASSIGN_RE);
61
- if (!m)
62
- continue;
63
- const key = m[1];
64
- if (!key)
65
- continue;
66
- if (seen.has(key))
67
- continue;
68
- seen.add(key);
69
- keys.push(key);
70
- }
71
- return keys;
72
- }
53
+ export { scanEnvKeyNames };
73
54
  /**
74
55
  * Read and return ONLY non-secret metadata: key names.
75
56
  *
@@ -706,29 +706,4 @@ export const HEALTH_CHECKS = [
706
706
  };
707
707
  },
708
708
  },
709
- {
710
- // Item 4: stale durable-transaction journals under $DATA/txn (stranded
711
- // recovery state seen twice in a real 0.9 migration) had zero health
712
- // visibility. `count`/`unreadable` already exclude journals younger than
713
- // the sweeper's grace period (a currently-running operation), so any
714
- // nonzero count here is a real leftover.
715
- name: "stale-txn-journals",
716
- channel: "advisory",
717
- run: (ctx) => {
718
- const s = ctx.staleTxnJournals;
719
- const warn = s.count > 0;
720
- const unreadablePart = s.unreadable > 0 ? `, ${s.unreadable} unreadable` : "";
721
- const agePart = s.oldestAgeMs !== null ? `, oldest ${Math.round(s.oldestAgeMs / 60000)}m old` : "";
722
- return {
723
- name: "stale-txn-journals",
724
- kind: "deterministic",
725
- status: warn ? "warn" : "pass",
726
- confidence: "high",
727
- message: warn
728
- ? `${s.count} stale transaction journal(s) found under ${s.dir}${unreadablePart}${agePart} — see docs/migration/v0.9.0-troubleshooting.md for journal reconciliation steps.`
729
- : "No stale transaction journals found.",
730
- evidence: { dir: s.dir, count: s.count, unreadable: s.unreadable, oldestAgeMs: s.oldestAgeMs },
731
- };
732
- },
733
- },
734
709
  ];