akm-cli 0.9.5 → 0.9.7

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 (130) hide show
  1. package/CHANGELOG.md +295 -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-cli.js +1 -2
  8. package/dist/commands/env/secret-cli.js +1 -5
  9. package/dist/commands/feedback-cli.js +0 -4
  10. package/dist/commands/health/checks.js +0 -32
  11. package/dist/commands/health/surfaces.js +2 -2
  12. package/dist/commands/health.js +6 -15
  13. package/dist/commands/improve/autonomy-gate.js +1 -1
  14. package/dist/commands/improve/consolidate.js +25 -26
  15. package/dist/commands/improve/distill.js +2 -2
  16. package/dist/commands/improve/extract.js +8 -1
  17. package/dist/commands/improve/improve-cli.js +1 -1
  18. package/dist/commands/improve/improve-strategies.js +0 -4
  19. package/dist/commands/improve/improve.js +3 -5
  20. package/dist/commands/improve/locks.js +13 -5
  21. package/dist/commands/improve/loop-stages.js +8 -9
  22. package/dist/commands/improve/memory/memory-contradiction-detect.js +1 -19
  23. package/dist/commands/improve/memory/memory-improve.js +2 -1
  24. package/dist/commands/improve/outcome-loop.js +0 -6
  25. package/dist/commands/improve/preparation.js +1 -1
  26. package/dist/commands/improve/reflect.js +21 -10
  27. package/dist/commands/lint/base-linter.js +141 -18
  28. package/dist/commands/lint/index.js +21 -10
  29. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -7
  30. package/dist/commands/read/curate.js +47 -0
  31. package/dist/commands/read/search-cli.js +24 -1
  32. package/dist/commands/sources/bundle-config-ops.js +9 -4
  33. package/dist/commands/sources/info.js +14 -10
  34. package/dist/commands/tasks/tasks.js +12 -16
  35. package/dist/commands/workflow-cli.js +2 -2
  36. package/dist/core/asset/asset-placement.js +13 -2
  37. package/dist/core/asset/frontmatter.js +116 -0
  38. package/dist/core/asset/memory-archive.js +97 -0
  39. package/dist/core/common.js +61 -24
  40. package/dist/core/config/config-io.js +2 -2
  41. package/dist/core/config/config-sources.js +32 -2
  42. package/dist/core/config/config.js +1 -1
  43. package/dist/core/config/engine-semantics.js +0 -2
  44. package/dist/core/config/schema/engines.js +9 -7
  45. package/dist/core/config/schema/primitives.js +0 -5
  46. package/dist/core/config/schema/search.js +2 -1
  47. package/dist/core/file-lock.js +2 -1
  48. package/dist/core/maintenance-barrier.js +2 -14
  49. package/dist/core/paths.js +0 -3
  50. package/dist/core/redaction.js +2 -2
  51. package/dist/core/spawn-env.js +8 -12
  52. package/dist/core/state/migrations.js +1 -12
  53. package/dist/core/state-db.js +9 -27
  54. package/dist/core/write-source.js +12 -19
  55. package/dist/execution/directory-identity.js +36 -10
  56. package/dist/indexer/graph/graph-boost.js +0 -4
  57. package/dist/indexer/index-writer-lock.js +43 -24
  58. package/dist/indexer/index-written-assets.js +5 -6
  59. package/dist/indexer/indexer.js +2 -39
  60. package/dist/indexer/materialize-embeddings.js +85 -41
  61. package/dist/indexer/search/db-search.js +15 -48
  62. package/dist/indexer/search/ranking-contributors.js +0 -25
  63. package/dist/indexer/search/ranking.js +3 -13
  64. package/dist/integrations/agent/builder-shared.js +0 -25
  65. package/dist/integrations/agent/model-map.js +2 -60
  66. package/dist/integrations/harnesses/aider/agent-builder.js +1 -3
  67. package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -7
  68. package/dist/integrations/harnesses/claude/agent-builder.js +1 -4
  69. package/dist/integrations/harnesses/codex/agent-builder.js +1 -4
  70. package/dist/integrations/harnesses/copilot/agent-builder.js +2 -6
  71. package/dist/integrations/harnesses/gemini/agent-builder.js +2 -7
  72. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -4
  73. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +2 -16
  74. package/dist/integrations/harnesses/openhands/agent-builder.js +1 -3
  75. package/dist/integrations/harnesses/pi/agent-builder.js +2 -4
  76. package/dist/integrations/session-logs/index.js +0 -9
  77. package/dist/llm/client.js +75 -42
  78. package/dist/llm/embedder.js +7 -3
  79. package/dist/llm/embedders/remote.js +141 -42
  80. package/dist/registry/network.js +5 -37
  81. package/dist/runtime.js +2 -10
  82. package/dist/scripts/akm-migrate-node.js +73 -133
  83. package/dist/scripts/akm-migrate.js +73 -133
  84. package/dist/setup/engine-config.js +2 -5
  85. package/dist/setup/registry-stash-loader.js +0 -8
  86. package/dist/setup/setup.js +9 -46
  87. package/dist/setup/steps/connection-shared.js +10 -13
  88. package/dist/sources/providers/git-install.js +1 -1
  89. package/dist/sources/snapshot-fetchers/website-ingest.js +126 -0
  90. package/dist/storage/engines/sqlite-migrations.js +20 -1
  91. package/dist/storage/repositories/index-connection.js +45 -3
  92. package/dist/storage/repositories/index-entries-repository.js +0 -15
  93. package/dist/tasks/backends/cron.js +49 -9
  94. package/dist/tasks/backends/launchd.js +15 -20
  95. package/dist/tasks/backends/schtasks.js +18 -8
  96. package/dist/tasks/resolve-akm-bin.js +17 -2
  97. package/dist/tasks/run/run-native-task.js +8 -6
  98. package/dist/tasks/scheduler-invocation.js +8 -1
  99. package/dist/tasks/source/bounded-document.js +2 -4
  100. package/dist/tasks/source/parse-task-source.js +23 -9
  101. package/dist/tasks/source/task-source-v3-frozen.js +5 -7
  102. package/dist/tasks/source/task-source-v4.js +5 -10
  103. package/dist/tasks/source/task-to-v3.js +5 -10
  104. package/dist/tasks/source/task-to-v4.js +1 -4
  105. package/dist/tasks/source-v3.js +6 -6
  106. package/dist/workflows/exec/native-executor.js +21 -31
  107. package/dist/workflows/exec/run-workflow.js +5 -6
  108. package/dist/workflows/exec/scheduler.js +3 -19
  109. package/dist/workflows/exec/step-work.js +1 -4
  110. package/dist/workflows/exec/unit-dispatch.js +2 -2
  111. package/dist/workflows/exec/worktree.js +1 -13
  112. package/dist/workflows/freeze/targets/child-workflow.js +2 -10
  113. package/dist/workflows/ir/plan-hash.js +4 -6
  114. package/dist/workflows/ir/schema-v4.js +0 -12
  115. package/dist/workflows/ir/schema.js +20 -31
  116. package/dist/workflows/parser.js +11 -52
  117. package/dist/workflows/renderer.js +2 -3
  118. package/dist/workflows/resource-limits.js +11 -41
  119. package/dist/workflows/runtime/runs.js +3 -4
  120. package/dist/workflows/source-ir/schema.js +14 -30
  121. package/dist/workflows/validator.js +1 -7
  122. package/docs/reference/cli.md +5 -1
  123. package/docs/reference/configuration.md +1 -1
  124. package/package.json +1 -1
  125. package/schemas/akm-config.json +4 -23
  126. package/schemas/akm-task.json +1 -2
  127. package/schemas/akm-workflow.json +1 -13
  128. package/dist/assets/improve-strategies/frequent.json +0 -15
  129. package/dist/assets/improve-strategies/memory-focus.json +0 -15
  130. package/dist/indexer/search/semantic-status.js +0 -142
package/CHANGELOG.md CHANGED
@@ -4,6 +4,301 @@ 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.7] - 2026-08-31
8
+
9
+ ### Added
10
+
11
+ - **`akm curate --pack <tokens>` (#746).** Packs the ranked stash hits' full
12
+ content into a single token-budgeted blob instead of returning refs the
13
+ caller has to follow up on with N separate `akm show` calls. Content is
14
+ resolved through the same path `akm show` uses, so a `ref#fragment` hit
15
+ packs just that section. Drops whole assets from the tail of the ranked
16
+ list first; only a single highest-rank hit that alone exceeds the budget
17
+ is truncated. Registry hits are never packed — that separation stays
18
+ opt-in and there is no flag to override it. Named `--pack` rather than
19
+ `--budget` because the workflow asset schema already uses `budget` for
20
+ run-cost caps.
21
+
22
+ - **`llms.txt`-aware website ingestion (#749).** `akm bundle add` against an
23
+ origin root now probes `<origin>/llms.txt` first and, when present, uses
24
+ that author-curated link list as the crawl frontier instead of discovering
25
+ links by parsing HTML. Each linked page still flows through the same
26
+ robots-compliant, host-guarded, content-extracting fetch as before, so
27
+ ingested pages stay individually addressable. Off-origin manifest entries
28
+ are dropped. The probe is restricted to origin-root URLs, so adding a
29
+ specific page still fetches that page. Sites without the file fall through
30
+ to the existing crawler unchanged. `llms-full.txt` is deliberately not
31
+ read: its `## <path>` separators are ambiguous against real page content.
32
+
33
+ ### Changed
34
+
35
+ - **The improve-strategy presets were reviewed as a set (#878).** The
36
+ investigation first believed `thorough` silently ran without validation;
37
+ that was wrong — every strategy deep-merges onto `default` before
38
+ resolving, so the old asset behaved correctly. What was real: `thorough`
39
+ was the only preset of ten relying on that invisible inheritance instead
40
+ of an explicit process matrix, and its triage used `applyMode: "queue"`
41
+ while its description promised to "drain the backlog" — queue mode never
42
+ promotes, so nothing drained. `catchup` had the same inertness:
43
+ `maxAcceptsPerRun: 100` under queue mode, a key the promote loop never
44
+ reads.
45
+
46
+ Both now use the same judged-promotion pattern as `reflect-distill` and
47
+ `proactive-maintenance`: `applyMode: "promote"`, `judgment: true`, a
48
+ per-run accept cap (`thorough` 25, `catchup` 100), and `maxDiffLines:
49
+ 200`. The autonomy gate demotes promote back to queue unless
50
+ `experimental.improveAutonomy` is enabled, so by default both still
51
+ review-only; with autonomy on, they actually drain. `thorough` now
52
+ carries default's full explicit matrix, and a regression test pins
53
+ "everything default enables, thorough enables identically, plus triage."
54
+
55
+ ### Fixed
56
+
57
+ - **Pruning a memory no longer leaves dangling belief edges (#884).**
58
+ `analyzeMemoryCleanup` archives a pruned memory rather than deleting it —
59
+ the file moves under `.akm/memory-cleanup/archive/` beside a `cleanup.md`
60
+ audit record naming its `ref` and `originalPath`. Ref resolution only ever
61
+ looked at the memory's original location, so once #882 made
62
+ `contradictedBy`/`supersededBy` validatable, every memory pointing at a
63
+ pruned one began reporting `missing-ref`. `refExistsInAnyStash` now resolves
64
+ the archive tombstone, so a belief edge to an archived memory is satisfied
65
+ instead of dangling. Existence only: `resolveRefPathInStash` still ignores
66
+ the archive, because it returns a path callers mutate (`--supersedes`
67
+ demotion) and an archived file must never be written to. The contradiction
68
+ an edge records is preserved rather than erased, which a bare edge-scrub
69
+ would have destroyed.
70
+
71
+ A ref whose target has neither a file nor a tombstone was removed by
72
+ something other than prune — a hand `git rm`, or a pre-fix release — and is
73
+ genuinely dangling. Clearing those deletes user assertions, so it is opt-in
74
+ behind `akm lint --prune-dangling-edges` and is deliberately NOT folded into
75
+ `--fix`: every other auto-fix repairs a malformed file, whereas this one
76
+ edits well-formed files to drop a claim about the belief graph. The repair
77
+ is scoped to the `supersededBy`/`contradictedBy` channels — a stale `xrefs`
78
+ entry is an ordinary broken link that the author may want to re-target, not
79
+ delete — and preserves comments, key order, and surviving list entries. It
80
+ clears the same `writable: false` gate `--fix` does.
81
+
82
+ Known limitation: if a memory's only edge on a channel is dropped, a
83
+ `beliefState:` naming that channel is left without a supporting edge.
84
+ Choosing a demotion target is a belief-semantics decision this fix does not
85
+ make. No occurrences existed in the stash that surfaced #884.
86
+
87
+ - **A corrupt `index.db` now rebuilds instead of failing the command
88
+ (#865).** `src/core/state-db.ts` documented that "a corrupt index is
89
+ recovered by deleting it and re-running `akm index`" — that recovery was
90
+ never implemented. Real on-disk corruption surfaced a raw `SQLITE_CORRUPT`
91
+ and exited 70, and the existing inline-reindex fallback could not help
92
+ because it reopened the same corrupt file. `openIndexDatabase` now detects
93
+ corruption, removes the file and its `-wal`/`-shm` sidecars, and retries
94
+ the open once. Scoped to `index.db`, which is fully regenerable from the
95
+ stash; `state.db` is never touched.
96
+
97
+ - **`akm task sync` accepts an immutable package-local install (#868).** An
98
+ image-baked akm pinned at a path outside the npm global root was refused
99
+ outright, and `--rebind` then warned on every sync that the pinned binary
100
+ was "mutable, unproven" — the opposite of true, training operators to
101
+ ignore a recurring warning. The npm-global check was only ever a proxy for
102
+ "this binary won't change out from under the scheduler"; that property is
103
+ now tested directly, so a launcher this process cannot write to (a
104
+ read-only mount) is eligible without `--rebind` and without the warning.
105
+ Writable `npx`/project-`node_modules` installs remain ineligible, which is
106
+ the case the original check existed for. No new config knob.
107
+
108
+ - **`TASK_SCHEMA_VERSION_UNSUPPORTED` names the actual blocker (#869).** For
109
+ a v2/v3 file the message is only reached after the read-time shim has
110
+ already tried and failed to convert it — meaning a human decision is
111
+ required — yet it pointed at `akm migrate apply`, which would report the
112
+ identical block for that same file. The blocked `reason`/`detail` were
113
+ already computed and then discarded; they are now surfaced, so the error
114
+ names the specific reason (e.g.
115
+ `shell-command-resolution-changes-v2-literal-argv-semantics`) and says a
116
+ decision is needed rather than sending the operator to a command that
117
+ cannot help.
118
+
119
+ Also verified as already fixed and left alone: cross-bundle scoping (a
120
+ blocked file in one bundle does not prevent conversion in another —
121
+ resolved by #866) and `env`-prefixed command conversion (resolved by
122
+ #867).
123
+
124
+ - **`akm task sync` no longer dead-ends on pre-0.9.2 crontab rows (#881).**
125
+ Rows written by v0.9.0/v0.9.1 carry no `--scheduler-context` marker, so
126
+ `extractCronInvocation` could not parse them. Sync therefore saw the task
127
+ as absent, tried to install it, and collided with the row that was still
128
+ physically present — permanently blocking sync for every task on any
129
+ install upgrading past v0.9.2. Such a row is now recognized and
130
+ reconciled. The fallback fires only when the marker is entirely absent
131
+ and only inside akm's own `# akm:task … BEGIN/END` sentinels, so a row
132
+ carrying the marker that fails to parse for any other reason is never
133
+ reinterpreted, and no ownership or cardinality assertion changed.
134
+
135
+ - **`akm lint` validates `type:slug` xrefs instead of silently skipping
136
+ them (#882).** `typeNameFromConceptId` returns `undefined` for the colon
137
+ grammar, so `classifyConceptRef` treated those refs as "not a local ref"
138
+ and dropped them before validation — roughly half the xref channel on a
139
+ real corpus. Zero `missing-ref` findings read as "every reference is
140
+ healthy" when half had never been examined. akm still writes this
141
+ grammar today (`memory-contradiction-detect.ts` emits `contradictedBy:`
142
+ as `memory:<name>`), so it is a live channel, not legacy residue; it is
143
+ now accepted as a fallback in lint only, leaving the parser and write
144
+ paths on the single conceptId grammar.
145
+
146
+ - **Refs to derived memories resolve (#882).** `assetPathCandidatesForName`
147
+ only ever offered `<name>.md`, so any ref to a memory stored as
148
+ `<name>.derived.md` failed to resolve — affecting the conceptId grammar
149
+ equally, and invisible until the above fix made these refs validatable.
150
+ `.derived` is a provenance marker on the same identity, not part of the
151
+ name, so `<name>.derived.md` is now a secondary candidate with plain
152
+ `<name>.md` still taking precedence when both exist. Fixed at the
153
+ resolution primitive, so write-time `--xref`/`--supersedes` validation
154
+ benefits as well.
155
+
156
+ ### Removed
157
+
158
+ - **The `frequent` and `memory-focus` improve strategies.** Zero recorded
159
+ invocations, and both were mid-points of other presets (`frequent` ≈
160
+ `reflect-distill` without distill or triage; `memory-focus` a subset of
161
+ `frequent`). The shipped hourly task template now uses `reflect-distill`
162
+ — matching what real deployments had already switched to by hand. Any
163
+ removed combination remains expressible via `improve.strategies` in
164
+ config. One caveat: a user-config strategy *named* `frequent` or
165
+ `memory-focus` previously merged over the built-in of the same name; it
166
+ now merges over `default` only, so a partial override that relied on the
167
+ built-in's values will resolve differently and should be made explicit.
168
+
169
+ ### Testing
170
+
171
+ - **The previous-release corpus covers more than task sources (#880).**
172
+ Added fixtures for the synthesized `AKM_BUNDLE_DIR` duplicate-`stash`
173
+ bundle shape that caused #870, the retired 0.8 `stashDir`/`sources[]`/
174
+ `installed[]` config keys, and a downstream consumer's `config.json` plus
175
+ four task-source-v4 files. The 0.8 fixture deliberately asserts the
176
+ opposite of the others: those keys are hard-rejected by design, so it
177
+ guards that the rejection stays loud and actionable rather than silently
178
+ loading. Every fixture was proven to catch a regression by breaking the
179
+ guarantee, observing the failure, and reverting.
180
+
181
+ - Pinned current handling of truncated LLM responses (`finishReason:
182
+ "length"`) as a regression test (#865). No distinct classification or
183
+ retry was added: the underlying truncation was already resolved by
184
+ `reasoningEffort`, and threading the finish reason through four layers to
185
+ earn one diagnostic label was not justified.
186
+
187
+ ### Not shipped
188
+
189
+ - **Orphan-asset detection for `akm health` (#750)** was implemented and
190
+ then backed out after measurement. Against a live 23,859-entry index it
191
+ flagged 16,609 of 16,615 in-scope assets (99.96%) and took 135 s. The
192
+ corpus is not densely cross-linked — 1,591 distinct xref tokens exist in
193
+ total — so the check has no signal to give. The broken-ref half of #750
194
+ had already shipped previously. Moved to Backlog; see #882 for the
195
+ resolver defect the investigation exposed.
196
+
197
+ ## [0.9.6] - 2026-08-31
198
+
199
+ The deletion release: **net −2,100 lines**, almost all of it machinery that
200
+ gated, refused, verified, or cached a judgment. Nearly every guard removed
201
+ here had **zero confirmed firings** across 38,341 production telemetry events,
202
+ while several had already broken real installs.
203
+
204
+ The standard applied is now written down in `AGENTS.md` (`## Defensive Code`):
205
+ a guard survives only if it has demonstrably helped a real user, its failure
206
+ mode costs less than the hazard it prevents, and the operation is not already
207
+ gated behind a deliberate human command. "This hazard is conceivable" is not a
208
+ justification.
209
+
210
+ ### Fixed
211
+
212
+ - **Embedding no longer discards an entire index because one batch was too
213
+ big (#874).** Remote embeddings batched by document count (100) against a
214
+ fixed 30s timeout; a single oversized batch failed the whole phase, leaving
215
+ `embeddings` at 0 rows on a real 23,857-entry bundle and silently disabling
216
+ semantic search. Batching is now bounded by a token budget, failures are
217
+ skipped-and-reported per batch, and an oversized single document is a named
218
+ skip rather than a phase failure.
219
+ - **`akm lint` no longer silently skips user directories named `.cache` or
220
+ `registry`.** Two name-based exclusion sites remained after the 0.9.5 fix, so
221
+ a bundle's own `knowledge/registry/` was never linted and reported clean.
222
+ Exclusion is now anchored to akm's resolved registry-cache path.
223
+ - **One directory can no longer register as two bundles (#870).** When
224
+ `AKM_BUNDLE_DIR` pointed at a directory already configured under another id,
225
+ akm minted a second bundle for it; `akm migrate` then enumerated every task
226
+ file twice and failed with `duplicate task migration file path` (exit 70) —
227
+ permanently, while health checks kept passing. Bundle identity is now the
228
+ resolved content root (`path.resolve(entry.path, component.root ?? ".")`) at
229
+ both registration sites, existing duplicates reconcile instead of throwing,
230
+ and a genuinely irreconcilable pair reports both bundle ids and the shared
231
+ path.
232
+
233
+ ### Removed
234
+
235
+ - **The index writer lease (#872).** It guarded a *regenerable cache*, had zero
236
+ lease events in telemetry, and a live-but-wedged holder stranded all indexing
237
+ for 12 hours — `probeLock` only reclaims a dead PID. It blocked legitimate
238
+ work twice in a single day of real use. `withAssetMutationLease` is **kept**
239
+ (it guards authored, git-backed asset writes) but its identical 12h
240
+ age-based stale reclaim is gone; only a verifiably-dead holder is reclaimed.
241
+ - **`semantic-status.ts` and its cached `blocked` verdict (#873).** A failed
242
+ probe was persisted with a 24h TTL, and search consulted that verdict
243
+ *before attempting semantic search at all* — so one failure silently
244
+ disabled a working feature for a day. Semantic search now attempts per query
245
+ and falls back to FTS with a live warning. The remaining pre-flight check is
246
+ a real-time embedding count, not a stored judgment.
247
+ - **The improve-lock 4h stale reclaim.** Same hole: `--skip-if-locked`
248
+ silently no-oped nightly `improve` for up to four hours and reported success.
249
+ - **The persisted `supportsJsonSchema` capability cache**, which was never
250
+ invalidated by `akm config set`; a stale `true` sent `response_format:
251
+ json_schema` to an incompatible endpoint with no fallback (`isRetryable`
252
+ excludes 4xx). Replaced by attempt-then-fallback held in memory for the
253
+ process only. The config field survives as an explicit user override.
254
+ - **Workflow authoring resource caps** — steps, params, route branches,
255
+ inputs, outputs, gate loops, retries, JSON depth/node, composition depth,
256
+ and exec argv/env caps. None ever fired; several duplicated OS limits.
257
+ - **`LIFETIME_UNIT_CAP` (10,000)**, which hard-aborted workflows mid-run. The
258
+ maximum ever observed was 14 units.
259
+ - **`state.db` open-path identity re-verification**, which ran on *every*
260
+ command and threw a bare `Error`.
261
+ - **The task-source 1 MiB cap** at four sites, restating the `guarded-source`
262
+ cap already deleted in 0.9.5.
263
+ - **TOCTOU identity checks** — `assertGitPublicationIdentity` (git's
264
+ `--force-with-lease` already covers it), `assertFrozenDirectoryIdentity`
265
+ (replaced with a path-containment recheck: containment and resolved-path
266
+ identity stay, device/inode comparison goes, so a remount or container
267
+ rebuild no longer aborts a dispatch), `assertTaskSourceExpectation`'s stat
268
+ fields (its content hash stays), and redundant repeat HEAD-generation
269
+ compares in `write-source`.
270
+ - **`assertNotFlag`** on persona/system-prompt content; **`MAX_ENV_BYTES`**,
271
+ **`MAX_SECRET_BYTES`**, **`MAX_FEEDBACK_TAGS`**, **`MAX_CONFIG_FILE_BYTES`**,
272
+ model-map JSON budgets, memory-contradiction family caps, and two launchd /
273
+ schtasks re-checks that duplicated an existing fallback. `map.concurrency`
274
+ now clamps instead of rejecting a human-authored value.
275
+ - **17 dead symbols**, including several whose docstrings described behavior
276
+ nothing implemented. Three "canonical" constants that call sites were
277
+ ignoring in favour of hardcoded literals were **wired in** rather than
278
+ deleted, closing the drift instead of removing the evidence of it.
279
+ - **Speculative flexibility** — `AKM_ABLATE_CONTRIBUTORS` ablation plumbing
280
+ and the unexercised `graphBoost.confidenceMode` branches.
281
+
282
+ ### Changed
283
+
284
+ - **Drifted duplicate implementations consolidated.** HTTP retry/backoff (the
285
+ generic copy's `Retry-After` parsing was unbounded and numeric-only; the
286
+ capped, date-aware one now applies everywhere), the child-process env
287
+ allowlist (the opencode-sdk copy was missing `AKM_EVENT_SOURCE` and the
288
+ Windows HOME equivalents), portable synchronous sleep (five call sites had
289
+ been silently taking the Node fallback instead of the Bun fast path), a
290
+ stacked LLM chunk retry, and a duplicate `isProcessAlive`.
291
+
292
+ ### Kept, deliberately
293
+
294
+ Not everything unused is disposable. `isVecFastPathReady` stays: a partial
295
+ `entries_vec` table does not throw, it silently returns wrong neighbours, so
296
+ there is no error for a fallback to catch. `assertSupportedKind` stays: it has
297
+ a proven independent bypass path and is the real last-line check, not a
298
+ duplicate. The maintenance barrier and registry TTL cache stay. The four
299
+ improve strategies with zero recorded invocations stay — that measures one
300
+ install's cron schedule, not their worth.
301
+
7
302
  ## [0.9.5] - 2026-08-30
8
303
 
9
304
  ### Action required after upgrading
@@ -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) {
@@ -127,8 +127,7 @@ const envCreateCommand = defineJsonCommand({
127
127
  content = fs.readFileSync(fromFile, "utf8");
128
128
  }
129
129
  else {
130
- const MAX_ENV_BYTES = 1024 * 1024; // 1 MB
131
- const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
130
+ const buf = await readStdin();
132
131
  content = buf.toString("utf8");
133
132
  }
134
133
  }
@@ -122,16 +122,12 @@ const secretSetCommand = defineJsonCommand({
122
122
  if (fromEnv !== undefined && fromFile !== undefined) {
123
123
  throw new UsageError("Pass only one of --from-file or --from-env (or use stdin).", "INVALID_FLAG_VALUE");
124
124
  }
125
- const MAX_SECRET_BYTES = 5 * 1024 * 1024; // 5 MB
126
125
  let value;
127
126
  if (fromFile !== undefined) {
128
127
  if (!fs.existsSync(fromFile)) {
129
128
  throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
130
129
  }
131
130
  value = fs.readFileSync(fromFile);
132
- if (value.byteLength > MAX_SECRET_BYTES) {
133
- throw new UsageError("Secret exceeds the 5 MB limit.");
134
- }
135
131
  }
136
132
  else if (fromEnv !== undefined) {
137
133
  const envVal = process.env[fromEnv];
@@ -144,7 +140,7 @@ const secretSetCommand = defineJsonCommand({
144
140
  if (process.stdin.isTTY) {
145
141
  process.stderr.write(`Enter value for secret "${name}" (Ctrl-D when done):\n`);
146
142
  }
147
- const stdinBuf = await readStdin(MAX_SECRET_BYTES, () => new UsageError("Secret exceeds the 5 MB limit."));
143
+ const stdinBuf = await readStdin();
148
144
  // Strip a single trailing newline so `echo "$TOKEN" | akm secret set`
149
145
  // stores the token without the shell-added newline. Use --from-file for
150
146
  // byte-exact storage of multi-line material (PEM keys, certs).
@@ -25,7 +25,6 @@ import { findEntryIdByRef, getEntryFilePathById, getItemRefById, } from "../stor
25
25
  import { applyFeedbackToUtilityScore } from "../storage/repositories/index-utility-repository.js";
26
26
  // ── Tag validation ────────────────────────────────────────────────────────────
27
27
  const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
28
- const MAX_FEEDBACK_TAGS = 10;
29
28
  function validateFeedbackTags(raw) {
30
29
  const seen = new Set();
31
30
  const out = [];
@@ -43,9 +42,6 @@ function validateFeedbackTags(raw) {
43
42
  seen.add(tag);
44
43
  out.push(tag);
45
44
  }
46
- if (out.length > MAX_FEEDBACK_TAGS) {
47
- throw new UsageError(`Too many tags: ${out.length}. Maximum is ${MAX_FEEDBACK_TAGS}.`, "INVALID_FLAG_VALUE");
48
- }
49
45
  return out;
50
46
  }
51
47
  // ── Lesson strength helper ────────────────────────────────────────────────────
@@ -607,38 +607,6 @@ export const HEALTH_CHECKS = [
607
607
  };
608
608
  },
609
609
  },
610
- {
611
- name: "semantic-search-runtime",
612
- channel: "advisory",
613
- run: (ctx) => {
614
- const blocked = ctx.semanticStatus?.status === "blocked";
615
- // The generic "status: blocked" line is not actionable when the real
616
- // problem is a configured remote embedding endpoint that is down while
617
- // semanticSearchMode leaves semantic search enabled — every index run
618
- // burns time failing against it and searches silently degrade to
619
- // keyword-only. Name the endpoint and the two ways out.
620
- const remoteReason = ctx.semanticStatus?.reason?.startsWith("remote-") === true;
621
- const endpointAdvisory = blocked && remoteReason && ctx.embeddingEndpoint
622
- ? `Configured embedding endpoint ${ctx.embeddingEndpoint} is failing ` +
623
- `(${ctx.semanticStatus?.reason}${ctx.semanticStatus?.message ? `: ${ctx.semanticStatus.message}` : ""}) ` +
624
- `while semanticSearchMode is "${ctx.semanticSearchMode ?? "off"}". Searches fall back to keyword-only. ` +
625
- `Restore the endpoint, or set semanticSearchMode to "off" (or remove embedding.endpoint to use the local model).`
626
- : undefined;
627
- return {
628
- name: "semantic-search-runtime",
629
- kind: "deterministic",
630
- status: !ctx.semanticStatus || !blocked ? "pass" : "warn",
631
- confidence: "medium",
632
- message: endpointAdvisory ??
633
- (ctx.semanticStatus
634
- ? `Semantic search status: ${ctx.semanticStatus.status}`
635
- : "No semantic-search runtime status recorded yet."),
636
- evidence: ctx.semanticStatus
637
- ? { ...ctx.semanticStatus, ...(ctx.embeddingEndpoint ? { embeddingEndpoint: ctx.embeddingEndpoint } : {}) }
638
- : undefined,
639
- };
640
- },
641
- },
642
610
  {
643
611
  name: "session-extraction",
644
612
  channel: "advisory",
@@ -14,7 +14,7 @@
14
14
  * the stash-exposure pattern. `egress-endpoints` is the one informational
15
15
  * (pass-status) entry: it emits whenever any remote endpoint is configured.
16
16
  */
17
- import { MAX_CONFIG_FILE_BYTES, readTextFileWithLimit } from "../../core/common.js";
17
+ import { readTextFile } from "../../core/common.js";
18
18
  import { CURRENT_CONFIG_VERSION } from "../../core/config/config-schema.js";
19
19
  import { compareConfigVersion } from "../../core/config/config-version.js";
20
20
  import { formatRegistryUrl } from "../../core/registry-url.js";
@@ -28,7 +28,7 @@ import { formatRegistryUrl } from "../../core/registry-url.js";
28
28
  export function collectConfigSkewAdvisory(configPath) {
29
29
  let raw;
30
30
  try {
31
- raw = JSON.parse(readTextFileWithLimit(configPath, MAX_CONFIG_FILE_BYTES, "Config file"));
31
+ raw = JSON.parse(readTextFile(configPath, "Config file"));
32
32
  }
33
33
  catch {
34
34
  return undefined;
@@ -13,7 +13,6 @@ import { classifyPathAccess, describeInaccessiblePath } from "../core/path-acces
13
13
  import { getConfigPath, getDataDir, getDbPath, getStateDbPathInDataDir } from "../core/paths.js";
14
14
  import { listExistingTableNames, openStateDatabase } from "../core/state-db.js";
15
15
  import { DURATION_UNITS, parseDuration, parseSinceToIso } from "../core/time.js";
16
- import { readSemanticStatus } from "../indexer/search/semantic-status.js";
17
16
  import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
18
17
  import { getAllEntries } from "../storage/repositories/index-entries-repository.js";
19
18
  import { queryTaskHistory } from "../storage/repositories/task-history-repository.js";
@@ -183,25 +182,20 @@ function gatherStaleTxnJournalsPhase(now) {
183
182
  }
184
183
  }
185
184
  /**
186
- * Semantic-search status + the config fields the embedding-endpoint and
187
- * surfaces advisories need. Best-effort: an unloadable config leaves the
188
- * config-derived fields undefined and callers fall back to generic messages.
185
+ * Config fields the surfaces advisory needs. Best-effort: an unloadable
186
+ * config leaves the field undefined and the caller falls back to a generic
187
+ * message.
189
188
  */
190
- function gatherSemanticConfigPhase() {
191
- const semanticStatus = readSemanticStatus();
192
- let semanticSearchMode;
193
- let embeddingEndpoint;
189
+ function gatherEgressConfigPhase() {
194
190
  let egressConfigView;
195
191
  try {
196
192
  const config = loadConfig();
197
- semanticSearchMode = config.semanticSearchMode;
198
- embeddingEndpoint = config.embedding?.endpoint;
199
193
  egressConfigView = config;
200
194
  }
201
195
  catch {
202
196
  // fall through with undefined
203
197
  }
204
- return { semanticStatus, semanticSearchMode, embeddingEndpoint, egressConfigView };
198
+ return { egressConfigView };
205
199
  }
206
200
  /**
207
201
  * Assemble the window's improve-pipeline summary: invoked/completed/skipped
@@ -519,7 +513,7 @@ export function akmHealth(options = {}) {
519
513
  const taskHistory = gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now);
520
514
  const { tableNames, missingTables, probe } = taskHistory;
521
515
  const staleTxnJournals = gatherStaleTxnJournalsPhase(now);
522
- const { semanticStatus, semanticSearchMode, embeddingEndpoint, egressConfigView } = gatherSemanticConfigPhase();
516
+ const { egressConfigView } = gatherEgressConfigPhase();
523
517
  const { improveSummary } = gatherImproveSummaryPhase(db, stateDbPath, since, now);
524
518
  advisories.push(...gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView));
525
519
  const engineProbes = runHealthEngineProbes();
@@ -542,9 +536,6 @@ export function akmHealth(options = {}) {
542
536
  stuckActiveTasks: taskHistory.stuckActiveTasks,
543
537
  worstTaskFailRate: taskHistory.worstTaskFailRate,
544
538
  staleTxnJournals,
545
- semanticStatus,
546
- semanticSearchMode,
547
- embeddingEndpoint,
548
539
  sessionExtraction: improveSummary.sessionExtraction,
549
540
  autoAccept: improveSummary.autoAccept,
550
541
  engineProbes,