akm-cli 0.9.0-beta.1 → 0.9.0-beta.11

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 (56) hide show
  1. package/CHANGELOG.md +492 -0
  2. package/dist/assets/templates/html/default.html +78 -0
  3. package/dist/assets/templates/html/health.html +732 -0
  4. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  5. package/dist/cli/shared.js +21 -5
  6. package/dist/cli.js +47 -5
  7. package/dist/commands/config-cli.js +0 -10
  8. package/dist/commands/feedback-cli.js +42 -37
  9. package/dist/commands/graph/graph.js +75 -71
  10. package/dist/commands/health/checks.js +48 -0
  11. package/dist/commands/health/html-report.js +666 -0
  12. package/dist/commands/health.js +201 -14
  13. package/dist/commands/improve/consolidate.js +39 -6
  14. package/dist/commands/improve/distill.js +26 -5
  15. package/dist/commands/improve/extract-prompt.js +1 -1
  16. package/dist/commands/improve/extract.js +73 -8
  17. package/dist/commands/improve/improve-auto-accept.js +63 -3
  18. package/dist/commands/improve/improve-cli.js +7 -0
  19. package/dist/commands/improve/improve-profiles.js +4 -0
  20. package/dist/commands/improve/improve.js +874 -447
  21. package/dist/commands/improve/proactive-maintenance.js +113 -0
  22. package/dist/commands/improve/reflect-noise.js +0 -0
  23. package/dist/commands/improve/reflect.js +31 -0
  24. package/dist/commands/proposal/drain.js +73 -6
  25. package/dist/commands/proposal/proposal-cli.js +22 -10
  26. package/dist/commands/proposal/proposal.js +17 -1
  27. package/dist/commands/proposal/validators/proposals.js +365 -329
  28. package/dist/commands/read/curate.js +17 -0
  29. package/dist/commands/remember.js +6 -2
  30. package/dist/commands/sources/stash-cli.js +10 -2
  31. package/dist/commands/tasks/tasks.js +32 -8
  32. package/dist/core/config/config-schema.js +33 -0
  33. package/dist/core/logs-db.js +304 -0
  34. package/dist/core/paths.js +3 -0
  35. package/dist/core/state-db.js +152 -14
  36. package/dist/indexer/db/db.js +99 -13
  37. package/dist/indexer/ensure-index.js +152 -17
  38. package/dist/indexer/index-writer-lock.js +99 -0
  39. package/dist/indexer/indexer.js +114 -111
  40. package/dist/indexer/passes/memory-inference.js +61 -22
  41. package/dist/integrations/harnesses/claude/session-log.js +17 -5
  42. package/dist/llm/client.js +38 -4
  43. package/dist/llm/usage-persist.js +77 -0
  44. package/dist/llm/usage-telemetry.js +103 -0
  45. package/dist/output/context.js +3 -2
  46. package/dist/output/html-render.js +73 -0
  47. package/dist/output/shapes/helpers.js +17 -1
  48. package/dist/output/text/helpers.js +69 -1
  49. package/dist/scripts/migrate-storage.js +154 -25
  50. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +21 -2
  51. package/dist/sources/providers/tar-utils.js +16 -8
  52. package/dist/tasks/backends/cron.js +46 -9
  53. package/dist/tasks/runner.js +99 -16
  54. package/dist/workflows/db.js +4 -0
  55. package/package.json +3 -2
  56. package/dist/commands/config-edit.js +0 -344
package/CHANGELOG.md CHANGED
@@ -6,6 +6,337 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.0-beta.11] - 2026-06-15
10
+
11
+ ### Added
12
+
13
+ - **`extract.maxSessionsPerRun`** (default 25) — caps the NEW sessions the
14
+ extract pass LLM-processes in a single run so a backlog (e.g. after downtime)
15
+ can't push one run past its scheduled-task timeout. Overflow sessions stay
16
+ unseen and are picked up by later runs, so coverage is preserved. `0` disables.
17
+
18
+ ### Fixed
19
+
20
+ - **Auto-accept validation failures are no longer a blind leak.** When a
21
+ confidence-passing proposal fails promotion validation, the gate now captures
22
+ the reason (the `validateProposal` finding kind, e.g. `validation:description-quality`),
23
+ records it on the proposal (`akm proposal show` explains the rejection), logs
24
+ it, and exposes `failedByReason` on the gate result — so the ~5% leak is
25
+ diagnosable instead of silently warned-and-dropped.
26
+ - **Inflated skip-reason aggregates in `akm health`.** `no_new_signal` /
27
+ `profile_filtered_all_passes` are per-run snapshots of a stable set; the
28
+ window aggregator summed their per-run counts (≈2.7M / 3M). It now uses the
29
+ most recent run's count for these aggregated-snapshot reasons while still
30
+ summing genuine per-occurrence skips.
31
+
32
+ ## [0.9.0-beta.10] - 2026-06-15
33
+
34
+ ### Added
35
+
36
+ - **#603** — `akm health` pool-saturation advisory. Instead of alerting on the
37
+ raw `sessionsScanned` count (which false-alarmed on normal cadence changes),
38
+ a new `pool-saturation` advisory reports the ratio of new (unseen) sessions
39
+ to the total session pool: informational below 10% (expected steady state),
40
+ warning below 2% (possible discovery/dedup bug). Heuristic, never gates
41
+ overall status.
42
+ - **#576** — the `akm health` HTML report now renders the real per-stage LLM
43
+ token/time aggregate (a "🧠 LLM Work" KPI card + LLM token/call/wall-time
44
+ summary rows) from the captured `llm_usage` events, replacing the GPU-time
45
+ proxy.
46
+ - **Built-in `akm health --format html` report overhaul** — the report is now a
47
+ strict superset of (and supersedes) the external `akm-health-report` stash
48
+ skill. Restored the interactive filter bar (time-slice 1d–21d, task, status)
49
+ with client-side chart/table re-render and the Last-10 "Task" column;
50
+ reordered sections to a decision-first flow (verdict → action items → KPIs →
51
+ table → charts); added a synthesized one-sentence **Verdict** (status + 2–3
52
+ drivers) and a freshness line; merged the duplicate Advisories / What-to-Watch
53
+ into one prioritized, de-duplicated **Action Items** list (P1/P2/P3 +
54
+ remediation command); added a per-stage **LLM token** stacked-bar chart and
55
+ `dataZoom` sliders on dense charts; fixed the failed-run scatter x-alignment
56
+ (now shape-encoded); KPI-card colors are now health signals (not decoration);
57
+ added metric-glossary tooltips, chart `aria-label`s, contrast fixes, and
58
+ empty-state overlays. Deterministic output preserved.
59
+
60
+ ### Fixed
61
+
62
+ - **Health report accuracy** (follow-ups to the overhaul): the per-run **Task**
63
+ column/filter now show the real scheduled task (`akm-improve-frequent`, …) via
64
+ a ±5min `task_history` join instead of the run's scope (which is `all` for
65
+ every scheduled run); the time-**slice** filter options are now derived from
66
+ the report's `--since` window (e.g. All/3d/1d/12h/6h for a 7d report) and
67
+ default to "All" — replacing the hard-coded 1d–21d list that didn't match the
68
+ window; and the trend **deltas** now default their compare window to `--since`
69
+ (like-for-like, e.g. last 7d vs prior 7d) instead of a fixed 24h, which had
70
+ produced nonsensical period-over-period percentages on multi-day reports.
71
+ - **Inflated stash-snapshot metrics in `akm health`.** `memorySummary`
72
+ (derived/eligible) and `profileFilteredRefs` are whole-stash snapshots recorded
73
+ on every run, but the window aggregator was **summing** them across all runs —
74
+ e.g. "915,258 of 1,226,025 eligible" and a 2.4M filtered-ref count. They now
75
+ take the most recent run's snapshot (the current state). Per-run *work* metrics
76
+ (promoted, MI written, graph entities, …) remain genuine window sums.
77
+ - **Health report polish:** the akm version is stamped in the header (under the
78
+ AKM logo) and footer; the steady-state `no new signal since last proposal`
79
+ distill reason is excluded from the skip-reason chart (it drowned out the
80
+ actionable reasons); and the Consolidation Output chart now draws Promoted as a
81
+ line on a secondary right-hand axis (it dwarfs merged/deleted) with merged and
82
+ deleted as bars on the left axis.
83
+
84
+ - **#598** — process-level tuning fields (`consolidate.incrementalSince`,
85
+ `minPoolSize`, `neighborsPerChanged`, `extract.minContentChars`, per-process
86
+ `enabled` flags) now survive an `akm config` rewrite. They are first-class
87
+ typed `ImproveProcessConfigSchema` fields, so the load→save round trip no
88
+ longer silently drops them. Unknown process sub-keys hard-error at load
89
+ (`ConfigError`) rather than being silently discarded — the deliberate,
90
+ documented resolution. Regression-guarded by
91
+ `tests/config-process-roundtrip.test.ts`.
92
+
93
+ ## [0.9.0-beta.9] - 2026-06-14
94
+
95
+ Restore and instrument `akm improve` steady-state output. The reflect/distill
96
+ self-improvement lanes had been near-zero in steady state because the
97
+ signal-delta eligibility gate was the only lane (cache "no-access = no-work"
98
+ pathology) and the high-retrieval fallback was structurally dead. This release
99
+ revives proactive improvement, adds attribution + a measurement/kill-criterion
100
+ system so the lane must prove its value, and right-sizes reflect budgets to
101
+ their task timeouts.
102
+
103
+ ### Added
104
+
105
+ - **Proactive maintenance selector** (`proactiveMaintenance` improve process):
106
+ due-gated, composite-priority (`importance × log(1+retrievalFreq) ×
107
+ recencyDecay / log(size)`), bounded rotating top-N reflect/distill over
108
+ stale/never-reflected assets. **Disabled by default**; enable per profile.
109
+ - **Eligibility attribution**: every reflect/distill proposal is stamped
110
+ `eligibilitySource ∈ {signal-delta, high-retrieval, proactive, scope,
111
+ unknown}` on `reflect_invoked`/`distill_invoked`/`promoted` events and the
112
+ proposal record, so outcomes are sliceable by lane.
113
+ - **Measurement system** under `scripts/akm-eval/`: a real-query retrieval suite
114
+ generated from `usage_events`, and `akm-eval-proactive-verdict` — a read-only
115
+ kill-criterion runner comparing the proactive lane (treatment) vs due-but-
116
+ untouched assets (control). Emits PASS/FAIL/INCONCLUSIVE and recommends
117
+ disabling the lane on FAIL. New `proactive_selected` event +
118
+ `proactiveSelected`/`proactiveDueTotal`/`proactiveNeverReflected` fields on
119
+ `improve_completed`.
120
+
121
+ ### Fixed
122
+
123
+ - Revived the P0-A high-retrieval fallback: genuinely zero-feedback assets were
124
+ routed to the fully-skipped branch one phase before the fallback could see
125
+ them, so frequently-retrieved-but-never-rated assets were never improved.
126
+ - `getRetrievalCounts` now normalizes bare vs `origin//`-prefixed refs (it was
127
+ dropping ~half the retrieval signal) and counts `curate` events
128
+ (`akm curate` now records per-item `entry_ref`).
129
+ - The fully-skipped `no_new_signal` branch emitted one `improve_skipped` event
130
+ per ref (~11K writes/run, ~400K rows/day) — a contributor to 900s improve
131
+ timeouts and state.db bloat. Collapsed into one aggregated counted event.
132
+
133
+ ## [0.9.0-beta.8] - 2026-06-13
134
+
135
+ Fix multi-process SQLite contention in `index.db` and harden concurrent proposal
136
+ queue mutations.
137
+
138
+ ### Changed
139
+
140
+ - Added a global `index.db` writer lease used by foreground indexing,
141
+ background auto-index, improve maintenance index writers, graph updates, and
142
+ feedback writes.
143
+ - Replaced the racy background index PID-file dedup flow with lease-based
144
+ coordination and explicit handoff to the spawned worker.
145
+ - `akm feedback` now uses blocking index preparation and writes under the same
146
+ `index.db` lease, avoiding self-inflicted `database is locked` failures.
147
+ - Proposal queue create/archive/gate-decision mutations now run under
148
+ `BEGIN IMMEDIATE` state.db transactions so concurrent processes serialize on
149
+ live queue state.
150
+
151
+ ## [0.9.0-beta.7] - 2026-06-13
152
+
153
+ Fix the `akm improve` regression introduced by background `ensureIndex`.
154
+
155
+ ### Changed
156
+
157
+ - Added an explicit `ensureIndex` mode so callers choose `background` or
158
+ `blocking` behavior directly instead of relying on hidden environment state.
159
+ - `akm improve` now uses blocking index preparation before collecting eligible
160
+ refs, restoring the post-upgrade empty-index recovery path.
161
+ - Removed the `AKM_INDEX_INLINE` test-only override so tests exercise the same
162
+ index behavior model as production.
163
+
164
+ ## [0.9.0-beta.6] - 2026-06-12
165
+
166
+ Pipeline optimization: new per-process config fields wire up the consolidation
167
+ and improve pipeline knobs exposed by the optimization report — incremental
168
+ consolidation, pool caps, distill gating, and memory inference throttling.
169
+
170
+ ### Added
171
+
172
+ - **`consolidate.incrementalSince`** — profile config field that narrows the
173
+ consolidation candidate pool to memories modified within the given window
174
+ (e.g. `"1h"`, `"4h"`) plus their graph neighbours. Enables frequent
175
+ consolidation passes (e.g. `quick-shredder` every 15 min) without full-pool
176
+ sweeps. Absent = full-pool sweep (correct for nightly runs).
177
+ - **`consolidate.limit`** — hard cap on memories processed per consolidation
178
+ pass, applied after incremental narrowing. Prevents runaway full-pool sweeps
179
+ in the nightly default profile.
180
+ - **`consolidate.neighborsPerChanged`** — configurable graph-neighbour count
181
+ per changed memory during incremental consolidation (was hardcoded to 5).
182
+ `quick-shredder` sets this to 3 for a 40% candidate reduction per burst.
183
+ - **`distill.requirePlannedRefs`** — when `true`, the distill process is
184
+ skipped entirely for distill-only refs when the reflect phase produced zero
185
+ planned refs. Eliminates hundreds of `distill-skipped` events on quiet passes
186
+ where all refs are on reflect cooldown.
187
+ - **`memoryInference.minPendingCount`** — minimum pending split-parent memory
188
+ count below which the inference pass is skipped entirely (zero LLM calls).
189
+ Prevents lock acquisition on passes where there is nothing to infer.
190
+ - **`reflect.limit`** — per-process ref limit for the reflect/distill loop,
191
+ applied as the improve run limit when no CLI `--limit` is given.
192
+ - **New `reflect-distill` improve profile** — dedicated reflect + distill +
193
+ memoryInference + triage profile for the every-4h `akm-improve-frequent`
194
+ task. `reflect.limit: 25` bounds LLM cost per pass.
195
+
196
+ ### Changed
197
+
198
+ - **`quick-shredder` profile tuned**: `incrementalSince` `4h` → `1h`,
199
+ `maxChunkSize` 25 → 35, added `minPoolSize: 10`, `neighborsPerChanged: 3`,
200
+ `memoryInference.minPendingCount: 5`. All `profile: "qwen-9b-shredder"`
201
+ process references removed — falls back to default LLM.
202
+ - **`default` improve profile** (nightly): extract disabled (dedicated
203
+ `akm-extract` task runs at 01:48), consolidate gets `limit: 500`,
204
+ reflect gets `limit: 100` and `allowedTypes`, distill gets
205
+ `requirePlannedRefs: true`, triage enabled at 50 accepts/run,
206
+ graphExtraction explicitly enabled.
207
+ - **Cron schedule optimised**: extract reverted to `8,28,48 * * * *` (3×/hr),
208
+ quick-shredder shifted to `4,19,34,49` (4-min extract gap), health-report
209
+ shifted to `:03` (avoids `:00` collision), `akm-improve-frequent` re-enabled
210
+ at `45 */4` with `reflect-distill` profile.
211
+
212
+ ## [0.9.0-beta.3] - 2026-06-12
213
+
214
+ Stabilization batch closing the remaining 0.9.0 milestone: DB-locking and
215
+ improve-pipeline perf backports, extract/reflect gate fixes, SQLite-first
216
+ proposal and log storage, `--format html` output, and per-stage LLM telemetry.
217
+
218
+ ### Added
219
+
220
+ - **`--format html` output with per-command templates** (#582). `akm health
221
+ --format html` renders the full interactive health report (ECharts inlined by
222
+ default, or via CDN with `AKM_ECHARTS=cdn`); every other command falls back to
223
+ a dark-mode default template that pretty-prints its JSON. A global `--output
224
+ <path>` flag writes the rendered HTML to a file instead of stdout. Token
225
+ replacement only — no template engine. The standalone health-report skill is
226
+ now folded into core.
227
+ - **Per-stage LLM telemetry** (#576). Every `chatCompletion` call now records
228
+ tokens (prompt/completion/total/reasoning), wall-time, model, and
229
+ finish_reason as an `llm_usage` event, attributed to the pipeline stage via an
230
+ ambient `AsyncLocalStorage` context (`withLlmStage`) set once per phase — no
231
+ `stage` parameter threaded through call sites. `akm health` exposes per-stage
232
+ token and time aggregates. Telemetry is best-effort and can never fail a run;
233
+ capture is forward-only.
234
+ - **Per-proposal gate decision + confidence** (#577). When a proposal passes
235
+ through the auto-accept/triage gate, its outcome (`auto-accepted` /
236
+ `deferred` / `auto-rejected`), reason, confidence, measured value, and the
237
+ thresholds in effect are persisted on the proposal (in the SQLite metadata).
238
+ `akm proposal show`/`list` surface them with reconstructable comparisons
239
+ (e.g. `0.72 < 0.90`), so tooling can explain *why* each proposal is pending
240
+ instead of relying on a run-level aggregate. Forward-only; legacy proposals
241
+ render `unknown`.
242
+
243
+ ### Fixed
244
+
245
+ - **`SQLITE_BUSY` / "database is locked" under concurrent runs** (#584, #585,
246
+ #589). `busy_timeout` raised from 5 s to 30 s on every SQLite open path
247
+ (index.db and state.db); the improve maintenance pass now closes its index.db
248
+ handle before each reindex (which opens its own writer to the same WAL file);
249
+ and the post-loop purge reuses the long-lived events connection instead of
250
+ opening a second state.db writer. Together these eliminate all observed
251
+ lock failures from overlapping cron improve runs. (Backports of 0.8.8.)
252
+ - **Extract gate ignored the active profile's `extract.enabled: false`** (#593,
253
+ #594). The session-extraction gate hardcoded the `default` profile, so a
254
+ non-default profile (e.g. a quick pass) ran extract anyway — 300–600 s of
255
+ redundant work per run when a dedicated extract task also exists. The gate
256
+ now resolves `extract` against the active improve profile. (Backport of
257
+ 0.8.11.)
258
+ - **Memory inference burned LLM calls on already-derived parents** (#588). The
259
+ primary pass now checks for the `<parent>.derived.md` child on disk *before*
260
+ the LLM/cache call, and opportunistically marks the parent processed so it
261
+ never re-pends. Previously ~55 % of the inference budget was spent
262
+ rediscovering children that already existed.
263
+ - **Reflect no longer queues empty-diff or cosmetic-only proposals** (#580).
264
+ A deterministic, LLM-free noise gate diffs each candidate against the current
265
+ asset; byte-identical edits are dropped and changes that are pure formatting
266
+ (whitespace reflow, hard-wrap changes, code-fence language hints, YAML scalar
267
+ re-folding) are suppressed, each recorded via summary events so suppression
268
+ rates are visible in `akm health`.
269
+
270
+ ### Added
271
+
272
+ - **`minContentChars` pre-LLM extract gate** (#595, #596). Sessions whose raw
273
+ size is below `profiles.improve.<name>.processes.extract.minContentChars`
274
+ (default 10 — only truly empty sessions/journal files) skip the extract LLM
275
+ call entirely. Gates on raw input size, not post-noise-filter size.
276
+ (Backports of 0.8.12–0.8.14.)
277
+ - **Structured logs database** (#579). Task and run log lines now land in a
278
+ dedicated `logs.db` (WAL, 30 s busy_timeout) keyed by task, run, stream, and
279
+ time, with retention/purge wired into the existing purge pass and `ATTACH`
280
+ support for joining log lines to `state.db` rows (e.g. a failed
281
+ `task_history` row to its log output). The scattered-log audit and per-source
282
+ keep/move/drop decisions are documented in `docs/technical/logs-audit.md`.
283
+
284
+ ### Changed
285
+
286
+ - **Proposals are now stored canonically in SQLite** (#578). The previously
287
+ bypassed `proposals` table in state.db is the single source of truth; all
288
+ proposal commands (`list`/`show`/`diff`/`accept`/`reject`/`revert`/`drain`),
289
+ the improve auto-accept gate, and health metrics read and write it through
290
+ one storage layer. Pending file-based proposals are imported on first read;
291
+ `akm proposal *` UX is unchanged. Design and migration notes live in
292
+ `docs/technical/proposal-storage.md`.
293
+ - **Improve planning no longer does per-ref DB lookups or per-ref skip events**
294
+ (#591, #592). Eligible refs carry a pre-resolved `filePath`, removing a
295
+ serial async lookup per ref (~500 s on 9 k-ref stashes), and the
296
+ profile-filtered skip loop emits one summary event with a count instead of
297
+ thousands of rows. (Backports of 0.8.9–0.8.10.)
298
+
299
+ ## [0.9.0-beta.2] - 2026-06-09
300
+
301
+ ### Fixed
302
+
303
+ - **Consolidation starved merge recall; the memory pool grew unbounded.** Commit
304
+ `633ece41` made the `incrementalSince` narrowing unconditional, so every
305
+ consolidation run only judged memories changed since the last run plus their
306
+ immediate vector-neighbors. Stale-but-unmerged duplicate clusters were never
307
+ re-examined, so the eligible pool grew monotonically and never shrank, and
308
+ contradiction detection (which rides on the consolidation pass) went dark.
309
+ Consolidation only runs on the nightly default-profile pass (`quick`/`frequent`
310
+ disable it), so a full-pool sweep is correct and affordable; the override is
311
+ removed. `lastConsolidateTs` still gates whether the pass runs. (Forward-port
312
+ of the 0.8.5 fix.)
313
+ - **`akm tasks sync` ignored schedule changes** — forward-ported from 0.8.4.
314
+ Sync classified any task already present in the OS scheduler as "unchanged"
315
+ without comparing its installed entry, so editing a task's `schedule:` in the
316
+ `.yml` never reached the crontab; the same gap affected `tasks enable`/`disable`
317
+ (toggled the comment, re-enabling a stale schedule). Sync now compares the
318
+ backend's installed signature against the signature the current definition
319
+ renders to and reinstalls on drift (new `updated[]` field); `enable`/`disable`
320
+ reinstall from the current `.yml`. The cron backend gains `expectedSignature()`
321
+ and a per-entry signature on `list()`; other backends fall back to an
322
+ idempotent reinstall.
323
+
324
+ ### Added
325
+
326
+ - **`akm improve --skip-if-locked`** — forward-ported from 0.8.4. When another
327
+ improve run already holds the lock, the run logs and exits 0 with a no-op
328
+ result (`skipped.reason: "lock-held"`) instead of failing with the "already
329
+ running" config error (exit 78). Intended for high-frequency scheduled runs
330
+ (e.g. an every-30-min `quick` pass) that overlap a longer run. Default off.
331
+
332
+ ### Removed
333
+
334
+ - **`akm config edit`** — the interactive menu-based editor was removed. A
335
+ prompt-driven drill-down was clunkier than just editing the file. Edit the
336
+ config directly (the path is shown by `akm config path`), use
337
+ `akm config set/get/unset` for scripted changes, and `akm config validate` to
338
+ check it.
339
+
9
340
  ## [0.9.0-beta.1] - 2026-06-08
10
341
 
11
342
  ### Fixed
@@ -213,6 +544,167 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
213
544
  `migrate-storage` change is pinned by a sha256 + file-mode fixture-stash
214
545
  differential test.
215
546
 
547
+ ## [0.8.14] - 2026-06-11
548
+
549
+ ### Fixed
550
+
551
+ - **`akm extract` minContentChars default lowered from 500 to 10.** The 500-char
552
+ threshold used inputCount (raw session size) but analysis showed 209 of 218
553
+ candidate-producing sessions had inputCount < 500 — tiny agent sessions (22–368
554
+ chars) regularly yield 1–5 candidates. The only reliably skippable sessions are
555
+ empty ones (0 chars, journal files). Default lowered to 10 to catch only
556
+ truly empty sessions while preserving all signal-bearing content. Closes #597.
557
+
558
+ ## [0.8.13] - 2026-06-11
559
+
560
+ ### Fixed
561
+
562
+ - **`akm extract` minContentChars gate filtered all sessions.** The threshold was
563
+ checked against `filtered.stats.outputCount` (post-noise-filter chars), but the
564
+ pre-filter strips so much boilerplate that even signal-bearing sessions end up
565
+ below 500 chars of output. All 75 sessions in the first post-deploy run were
566
+ filtered, dropping candidates from 4–13 to 0. Fix: gate on `inputCount` (raw
567
+ session size) instead — a session with < 500 raw chars has nothing worth
568
+ extracting regardless of what the pre-filter produces. Closes #596.
569
+
570
+ ## [0.8.12] - 2026-06-11
571
+
572
+ ### Fixed
573
+
574
+ - **`akm extract` calling the LLM for noise sessions that never yield candidates.**
575
+ 96% of processed sessions (72/75 measured) produced zero candidates, consuming
576
+ ~330 s of LLM time per run. The pre-filter had no minimum content threshold —
577
+ sessions as short as 50 chars were sent to the LLM regardless. A new
578
+ `minContentChars` gate (default 500) skips the LLM call when post-filter
579
+ content falls below threshold, cutting extract LLM calls by ~95% on typical
580
+ stashes. Configurable via `profiles.improve.<name>.processes.extract.minContentChars`.
581
+ Closes #595.
582
+
583
+ ## [0.8.11] - 2026-06-11
584
+
585
+ ### Fixed
586
+
587
+ - **`akm improve --profile <name>` ignored profile's `extract.enabled: false` setting.**
588
+ The session-extraction gate in the preparation stage called
589
+ `isLlmFeatureEnabled(config, "session_extraction")`, which hardcodes a lookup
590
+ against `profiles.improve.default.processes.extract.enabled`. Any non-default
591
+ profile that set `extract.enabled: false` (e.g. `quick-shredder`) was silently
592
+ ignored, causing the extract pass to run regardless. The fix adds a
593
+ `resolveProcessEnabled("extract", improveProfile)` check so the active
594
+ resolved profile gates the pass correctly. Closes #593.
595
+
596
+ ## [0.8.10] - 2026-06-11
597
+
598
+ ### Fixed
599
+
600
+ - **`akm improve` taking 8–10 minutes per run due to O(n) DB writes for
601
+ profile-filtered refs.** When a profile disables reflect and distill for
602
+ certain asset types, `collectEligibleRefs` marks those refs as
603
+ `profile_filtered_all_passes`. The caller then emitted one `improve_skipped`
604
+ event per ref — a sequential DB write for each. On a ~9 000-ref stash this
605
+ was ~500 s of SQLite writes before any consolidation or memory inference
606
+ began. The fix collapses the per-ref loop into a single summary event
607
+ carrying a `count` field, eliminating ~9 000 sequential writes per run.
608
+ Closes #590.
609
+
610
+ ## [0.8.9] - 2026-06-11
611
+
612
+ ### Fixed
613
+
614
+ - **`akm improve` validation pass was O(n) in stash size, causing ~510 s overhead
615
+ on large stashes.** For every indexed ref, the preparation phase called
616
+ `findAssetFilePath()` — an async round-trip to the index DB followed by a
617
+ filesystem probe — serially inside a `for…await` loop. With ~9 000 indexed
618
+ refs at ~55 ms each, this loop consumed the entire 600–900 s run budget before
619
+ any reflect, triage, or memory-inference work began. The fix threads
620
+ `filePath` from the planning stage (`collectEligibleRefs`) through
621
+ `ImproveEligibleRef` so the validation pass and the disk-existence guard can
622
+ use the pre-resolved path directly. The async lookup is retained only as a
623
+ fallback for refs that enter via a narrow scope (e.g. `--scope ref:foo`).
624
+ Closes #587.
625
+
626
+ ## [0.8.8] - 2026-06-11
627
+
628
+ ### Fixed
629
+
630
+ - **SQLite `SQLITE_BUSY` errors under concurrent improve runs.** `busy_timeout`
631
+ was set to 5 000 ms in all three database open paths (`openDatabase`,
632
+ `openExistingDatabase`, `openStateDatabase`). Under a busy cron schedule — or
633
+ when a reindex triggered by memory inference ran concurrently with an event
634
+ write — the 5 s window was routinely exhausted, producing "database is locked"
635
+ failures. Raised to 30 000 ms across all three paths so transient lock
636
+ contention is retried for up to 30 s before surfacing as an error.
637
+
638
+ ## [0.8.7] - 2026-06-09
639
+
640
+ ### Fixed
641
+
642
+ - **`incrementalSince` duration strings were silently ignored.** Values like
643
+ `"30m"`, `"24h"`, `"7d"` were passed raw to `narrowToIncrementalCandidates`,
644
+ which compared them against ISO timestamps via string sort. All `2026-...`
645
+ timestamps are lexicographically less than `"30m"` (`'2' < '3'`) and `"24h"`
646
+ (`"20" < "24"`), so `isChanged()` always returned `false` and the candidate
647
+ pool was silently emptied rather than filtered to the window. The fix adds
648
+ `parseSinceToIso()`, which resolves human duration strings to absolute ISO
649
+ timestamps before comparison. Values that already look like ISO timestamps
650
+ are passed through unchanged.
651
+
652
+ ## [0.8.6] - 2026-06-09
653
+
654
+ ### Added
655
+
656
+ - **`consolidate.incrementalSince` profile config field.** Setting
657
+ `incrementalSince: "7d"` (or any duration string) in the `consolidate` block
658
+ of an improve profile narrows the candidate pool to memories modified within
659
+ that window plus their top-5 graph neighbours, keeping each pass focused on
660
+ recent changes. This makes it practical to run consolidation more often than
661
+ once per day (e.g. via `akm-improve-consolidate` every 4 h) without
662
+ re-scanning the full pool every time. The nightly default profile leaves this
663
+ unset (full-pool sweep, same as before). The `incrementalSince` option already
664
+ existed in `akmConsolidate()` but was hardcoded off at the call site; the
665
+ field is now surfaced in the config schema and read from the profile.
666
+
667
+ ## [0.8.5] - 2026-06-09
668
+
669
+ ### Fixed
670
+
671
+ - **Consolidation starved merge recall; the memory pool grew unbounded.** Commit
672
+ `633ece41` made the `incrementalSince` narrowing unconditional, so every
673
+ consolidation run only judged memories changed since the last run plus their
674
+ immediate vector-neighbors. Stale-but-unmerged duplicate clusters were never
675
+ re-examined, so the eligible pool grew monotonically and never shrank, and
676
+ contradiction detection (which rides on the consolidation pass) went dark.
677
+ Consolidation only runs on the nightly default-profile pass (`quick`/`frequent`
678
+ disable it), so a full-pool sweep is correct and affordable; the override is
679
+ removed. `lastConsolidateTs` still gates whether the pass runs.
680
+
681
+ ## [0.8.4] - 2026-06-08
682
+
683
+ ### Fixed
684
+
685
+ - **`akm tasks sync` ignored schedule changes.** Sync classified any task already
686
+ present in the OS scheduler as "unchanged" without comparing its installed
687
+ entry, so editing a task's `schedule:` in the `.yml` never reached the crontab —
688
+ the only way to apply a new schedule was to `remove` and re-`add` the task. The
689
+ same gap affected `tasks enable`/`disable`, which merely toggled the existing
690
+ cron line's comment and so re-enabled a stale schedule. Sync now compares the
691
+ backend's installed signature against the signature the current definition would
692
+ produce and reinstalls on drift (reported in a new `updated[]` field);
693
+ `enable`/`disable` reinstall from the current `.yml` instead of toggling in
694
+ place. Backends that can't cheaply read their installed form fall back to an
695
+ idempotent reinstall, so the fix is correct on launchd/schtasks too. The cron
696
+ backend gains `expectedSignature()` and a signature on each `list()` entry.
697
+
698
+ ### Added
699
+
700
+ - **`akm improve --skip-if-locked`.** When another improve run already holds the
701
+ lock, the run logs and exits 0 with a no-op result (`skipped.reason:
702
+ "lock-held"`) instead of failing with the "already running" config error
703
+ (exit 78). Intended for high-frequency scheduled runs (e.g. an every-30-min
704
+ `quick` pass) that would otherwise pile up exit-78 failures whenever a longer
705
+ run overlaps them. Default off — the hard error is preserved for interactive
706
+ use. The result is still recorded so the skip is auditable.
707
+
216
708
  ## [0.8.3] - 2026-06-08
217
709
 
218
710
  ### Fixed
@@ -0,0 +1,78 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>akm %%COMMAND%%</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: #0d1117;
12
+ --surface: #161b22;
13
+ --border: #30363d;
14
+ --text: #e6edf3;
15
+ --muted: #8b949e;
16
+ --accent: #58a6ff;
17
+ }
18
+
19
+ body {
20
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', sans-serif;
21
+ background: var(--bg);
22
+ color: var(--text);
23
+ font-size: 14px;
24
+ line-height: 1.6;
25
+ padding: 24px;
26
+ }
27
+
28
+ header {
29
+ max-width: 980px;
30
+ margin: 0 auto 16px;
31
+ display: flex;
32
+ align-items: baseline;
33
+ gap: 12px;
34
+ flex-wrap: wrap;
35
+ }
36
+ header .logo { font-size: 20px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; }
37
+ header .command { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted); }
38
+
39
+ main { max-width: 980px; margin: 0 auto; }
40
+
41
+ pre {
42
+ background: var(--surface);
43
+ border: 1px solid var(--border);
44
+ border-radius: 8px;
45
+ padding: 16px 20px;
46
+ overflow-x: auto;
47
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
48
+ font-size: 13px;
49
+ white-space: pre-wrap;
50
+ word-break: break-word;
51
+ }
52
+
53
+ footer {
54
+ max-width: 980px;
55
+ margin: 16px auto 0;
56
+ color: var(--muted);
57
+ font-size: 12px;
58
+ display: flex;
59
+ justify-content: space-between;
60
+ gap: 12px;
61
+ flex-wrap: wrap;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <header>
67
+ <span class="logo">akm</span>
68
+ <span class="command">%%COMMAND%%</span>
69
+ </header>
70
+ <main>
71
+ <pre>%%CONTENT_JSON%%</pre>
72
+ </main>
73
+ <footer>
74
+ <span>akm — Agent Knowledge Management</span>
75
+ <span>Generated %%GENERATED_AT%%</span>
76
+ </footer>
77
+ </body>
78
+ </html>