akm-cli 0.9.0-beta.4 → 0.9.0-beta.40

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 (131) hide show
  1. package/CHANGELOG.md +626 -0
  2. package/dist/assets/prompts/consolidate-system.md +23 -0
  3. package/dist/assets/prompts/contradiction-judge.md +33 -0
  4. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  5. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  6. package/dist/assets/prompts/extract-session.md +6 -2
  7. package/dist/assets/prompts/graph-extract-system.md +1 -0
  8. package/dist/assets/prompts/memory-infer-system.md +1 -0
  9. package/dist/assets/prompts/memory-infer-user.md +5 -0
  10. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  11. package/dist/assets/prompts/procedural-system.md +44 -0
  12. package/dist/assets/prompts/recombine-system.md +40 -0
  13. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  14. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +22 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +22 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +24 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +22 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +25 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +21 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +21 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +23 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +22 -0
  24. package/dist/assets/templates/html/health.html +281 -111
  25. package/dist/cli.js +14 -3
  26. package/dist/commands/agent/contribute-cli.js +16 -3
  27. package/dist/commands/feedback-cli.js +15 -6
  28. package/dist/commands/graph/graph.js +75 -71
  29. package/dist/commands/health/checks.js +48 -0
  30. package/dist/commands/health/html-report.js +422 -80
  31. package/dist/commands/health.js +381 -9
  32. package/dist/commands/improve/calibration.js +161 -0
  33. package/dist/commands/improve/consolidate.js +631 -111
  34. package/dist/commands/improve/dedup.js +482 -0
  35. package/dist/commands/improve/distill.js +163 -69
  36. package/dist/commands/improve/encoding-salience.js +205 -0
  37. package/dist/commands/improve/extract-cli.js +115 -1
  38. package/dist/commands/improve/extract-prompt.js +39 -2
  39. package/dist/commands/improve/extract-watch.js +140 -0
  40. package/dist/commands/improve/extract.js +403 -40
  41. package/dist/commands/improve/feedback-valence.js +54 -0
  42. package/dist/commands/improve/homeostatic.js +467 -0
  43. package/dist/commands/improve/improve-auto-accept.js +113 -6
  44. package/dist/commands/improve/improve-profiles.js +12 -0
  45. package/dist/commands/improve/improve.js +2042 -612
  46. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  47. package/dist/commands/improve/outcome-loop.js +256 -0
  48. package/dist/commands/improve/proactive-maintenance.js +115 -0
  49. package/dist/commands/improve/procedural.js +418 -0
  50. package/dist/commands/improve/recombine.js +602 -0
  51. package/dist/commands/improve/reflect-noise.js +0 -0
  52. package/dist/commands/improve/reflect.js +46 -4
  53. package/dist/commands/improve/related-sessions.js +120 -0
  54. package/dist/commands/improve/salience.js +438 -0
  55. package/dist/commands/improve/triage.js +93 -0
  56. package/dist/commands/lint/agent-linter.js +19 -24
  57. package/dist/commands/lint/base-linter.js +173 -60
  58. package/dist/commands/lint/command-linter.js +19 -24
  59. package/dist/commands/lint/env-key-rules.js +34 -1
  60. package/dist/commands/lint/fact-linter.js +39 -0
  61. package/dist/commands/lint/index.js +31 -13
  62. package/dist/commands/lint/memory-linter.js +1 -1
  63. package/dist/commands/lint/registry.js +7 -2
  64. package/dist/commands/lint/task-linter.js +3 -3
  65. package/dist/commands/lint/workflow-linter.js +26 -1
  66. package/dist/commands/proposal/drain-policies.js +5 -0
  67. package/dist/commands/proposal/drain.js +17 -1
  68. package/dist/commands/proposal/proposal.js +5 -0
  69. package/dist/commands/proposal/propose.js +5 -0
  70. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  71. package/dist/commands/proposal/validators/proposals.js +187 -57
  72. package/dist/commands/read/curate.js +344 -80
  73. package/dist/commands/read/search-cli.js +7 -0
  74. package/dist/commands/read/search.js +1 -0
  75. package/dist/commands/read/show.js +67 -2
  76. package/dist/commands/sources/init.js +36 -9
  77. package/dist/commands/sources/installed-stashes.js +5 -1
  78. package/dist/commands/sources/schema-repair.js +13 -1
  79. package/dist/commands/sources/stash-cli.js +19 -3
  80. package/dist/commands/sources/stash-skeleton.js +23 -8
  81. package/dist/core/asset/asset-registry.js +2 -0
  82. package/dist/core/asset/asset-spec.js +14 -0
  83. package/dist/core/asset/frontmatter.js +166 -167
  84. package/dist/core/asset/markdown.js +8 -0
  85. package/dist/core/authoring-rules.js +83 -0
  86. package/dist/core/config/config-schema.js +274 -2
  87. package/dist/core/config/config.js +2 -2
  88. package/dist/core/logs-db.js +4 -3
  89. package/dist/core/paths.js +3 -0
  90. package/dist/core/standards/resolve-standards-context.js +87 -0
  91. package/dist/core/standards/resolve-stash-standards.js +99 -0
  92. package/dist/core/standards/resolve-type-conventions.js +66 -0
  93. package/dist/core/state-db.js +691 -30
  94. package/dist/indexer/db/db.js +364 -38
  95. package/dist/indexer/db/graph-db.js +129 -86
  96. package/dist/indexer/ensure-index.js +152 -17
  97. package/dist/indexer/graph/graph-boost.js +51 -41
  98. package/dist/indexer/graph/graph-extraction.js +203 -3
  99. package/dist/indexer/index-writer-lock.js +99 -0
  100. package/dist/indexer/indexer.js +114 -111
  101. package/dist/indexer/passes/memory-inference.js +10 -3
  102. package/dist/indexer/passes/staleness-detect.js +2 -5
  103. package/dist/indexer/search/db-search.js +15 -4
  104. package/dist/indexer/search/ranking-contributors.js +22 -0
  105. package/dist/indexer/search/ranking.js +4 -0
  106. package/dist/indexer/walk/matchers.js +9 -0
  107. package/dist/integrations/agent/prompts.js +33 -0
  108. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  109. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  110. package/dist/integrations/session-logs/index.js +16 -0
  111. package/dist/llm/client.js +23 -4
  112. package/dist/llm/embedder.js +27 -3
  113. package/dist/llm/embedders/local.js +66 -2
  114. package/dist/llm/feature-gate.js +8 -4
  115. package/dist/llm/graph-extract.js +2 -1
  116. package/dist/llm/memory-infer.js +4 -8
  117. package/dist/llm/metadata-enhance.js +9 -1
  118. package/dist/output/renderers.js +73 -1
  119. package/dist/output/shapes/curate.js +14 -2
  120. package/dist/output/text/helpers.js +16 -1
  121. package/dist/runtime.js +25 -1
  122. package/dist/scripts/migrate-storage.js +1378 -599
  123. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +479 -270
  124. package/dist/setup/setup.js +3 -3
  125. package/dist/sources/providers/tar-utils.js +16 -8
  126. package/dist/storage/sqlite-pragmas.js +146 -0
  127. package/dist/wiki/wiki.js +37 -0
  128. package/dist/workflows/db.js +3 -4
  129. package/dist/workflows/validate-summary.js +2 -7
  130. package/docs/data-and-telemetry.md +1 -0
  131. package/package.json +8 -6
package/CHANGELOG.md CHANGED
@@ -6,6 +6,632 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Changed
10
+
11
+ - **BEHAVIOR CHANGE — `akm init --dir <path>` no longer silently repoints your
12
+ default stash.** Previously, `akm init --dir X` unconditionally wrote
13
+ `stashDir: X` to `config.json` whenever `X` differed from the configured
14
+ default — so initializing a throwaway or secondary stash (e.g.
15
+ `akm init --dir /tmp/scratch`) would hijack the user's real default stash
16
+ pointer (the footgun documented in `memory:akm-init-persists-stashdir-warning`).
17
+ Now `init` persists `stashDir` to config **only** when one of the following
18
+ holds: (a) **no `--dir`** was provided (the default `~/akm` setup flow —
19
+ unchanged), (b) `--dir` was provided and **no `stashDir` exists in config yet**
20
+ (first-time bootstrap), or (c) `--dir` was provided **with the new
21
+ `--set-default` flag** (explicit opt-in). Otherwise `init` still scaffolds and
22
+ backfills the target dir exactly as before, but **leaves your default stash
23
+ pointer untouched** and prints:
24
+ `Your default stash is unchanged (<existing>). Re-run with --set-default to make <dir> the default.`
25
+ The `InitResponse` JSON gains `defaultStashUpdated: boolean` and an optional
26
+ `previousStashDir`. To make a `--dir` target your default, pass
27
+ `akm init --dir <path> --set-default`. (`akm setup` is unaffected — it remains
28
+ the explicit configuration flow and always sets the default.)
29
+
30
+ ### Added
31
+
32
+ - **Per-type SOFT authoring conventions are now user-editable stash facts.** A
33
+ third authoring-guidance layer joins the hard rules (#645) and general stash
34
+ standards (#642): a stash owner can author
35
+ `facts/conventions/assets/<type>.md` (e.g. `…/skill.md`, `…/command.md`) to
36
+ capture soft, type-specific guidance — voice, structure, length *preference*,
37
+ naming style. When an agent authors a `skill:x`, the body of
38
+ `fact:conventions/assets/skill` is injected (type-scoped — authoring
39
+ `command:y` pulls the `command` convention, never the `skill` one), labeled
40
+ as soft guidance and kept separate from the validator-enforced hard rules.
41
+ The basename must be a `getAssetTypes()`-validated asset type; facts are read
42
+ straight from disk (no index rebuild) and degrade to empty safely. When no
43
+ per-type fact exists, the built-in `TYPE_HINTS` fallback is unchanged (no
44
+ regression). These facts carry soft conventions only and can never weaken the
45
+ authoring contract the gate enforces (`authoringRulesForType` remains the sole
46
+ source of validator-rejecting rules). The general convention/meta resolver now
47
+ excludes `facts/conventions/assets/*` so per-type guidance never leaks
48
+ un-type-scoped into other authoring flows. (#646)
49
+ - **`akm init` now seeds default per-type SOFT convention templates.** Starter
50
+ `facts/conventions/assets/<type>.md` templates ship in the stash skeleton for
51
+ the authored types (`lesson, skill, command, agent, knowledge, memory,
52
+ workflow, script, fact`; `wiki`/`env`/`secret` excluded) so a stash owner has
53
+ an editable starting point. Each expands the matching built-in `TYPE_HINTS`
54
+ one-liner into soft starter guidance, carries `category: convention`
55
+ frontmatter, and states in-body that it is advice, not enforced — it carries
56
+ **no** validator-rejecting rules, so editing or deleting one cannot weaken the
57
+ gate (#645). The stash-skeleton copy is now recursive (preserving nested
58
+ subpaths), and `akm init` seeds **unconditionally** rather than only on first
59
+ create: re-running it on an existing stash backfills any missing skeleton,
60
+ convention, or `.meta/index.md` files. Seeding stays absent-only and never
61
+ overwrites a user-edited file. (#646)
62
+
63
+ ## [0.9.0-beta.36] — 2026-06-22
64
+
65
+ ### Added
66
+
67
+ - **Stash standards + wiki schemas are surfaced to authoring agents at write
68
+ time.** When an agent edits a page under `wikis/<name>/`, that wiki's
69
+ `schema.md` body is injected into the prompt; when it creates/edits a non-wiki
70
+ asset, the bodies of `category: convention`/`meta` `fact` assets are injected.
71
+ Two mutually-exclusive features selected by target type, sharing one
72
+ `standardsContext` prompt seam. Wired into reflect, propose, and every
73
+ improve authoring pass (distill, consolidate, recombine, procedural, extract,
74
+ schema-repair). (#642)
75
+ - **Unified, validator-sourced authoring-rules seam.** A new
76
+ `authoringRulesForType(type)` injects the hard authoring rules (no
77
+ pseudo-frontmatter in body, exactly two `---` fences, description/`when_to_use`
78
+ length + shape) into every authoring prompt. The numeric bounds live in one
79
+ module that the validators import, so the prompt can no longer drift from what
80
+ the gate enforces. (#645)
81
+
82
+ ### Fixed
83
+
84
+ - **High-salience reflect lane now reflects each asset at most once.** The
85
+ `#608` admission gate lacked the cooldown its sibling high-retrieval gate has,
86
+ so zero-feedback assets were re-selected on every run (auto-accept emits
87
+ `promoted`, not `feedback`), burning LLM calls and churning assets. (#643)
88
+ - **Stuck validation-failing proposals no longer dead-end.** The triage drain no
89
+ longer overwrites an `auto-rejected` gate stamp with a misleading
90
+ `auto-accepted` (the failure stays truthful and visible). A bounded,
91
+ content-preserving auto-repair (strip pseudo-frontmatter / stray `---`, repair
92
+ truncated descriptions) runs at the promote boundary and re-validates — fixable
93
+ proposals promote; genuinely unrepairable ones stay `pending` for manual
94
+ review, with nothing fabricated and validation never bypassed. (#645)
95
+ - Corrected a prompt/validator drift where the distill system prompt asked for an
96
+ 80–200 char description while the gate enforced 20–400. (#645)
97
+
98
+ ## [0.9.0-beta.35] — 2026-06-21
99
+
100
+ ### Fixed
101
+
102
+ - **Default extract discovery window is now "since the last run" (floored at 48h),
103
+ not a fixed 24h.** An intermittently-online host that was off for longer than
104
+ the old 24h window could permanently miss sessions that ended during the gap.
105
+ Discovery now looks back to the last recorded extract run for the harness, never
106
+ less than 48h. Widening is free of redundant LLM cost — the content-hash ledger
107
+ skips unchanged sessions with zero LLM calls. An explicit `--since`/`defaultSince`
108
+ still wins.
109
+ - **Per-session lock prevents concurrent double-extraction.** A session-end hook
110
+ firing `extract --session-id` while the periodic `akm improve` extract pass runs
111
+ discovery could both LLM-process the SAME session (duplicate spend + near-dup
112
+ proposals). A per-(harness, session) advisory lock (co-located with state.db,
113
+ PID + age staleness recovery) now makes the second run skip without any LLM call.
114
+ - **`minNewSessions` is read from the ACTIVE improve profile, not always `default`.**
115
+ A non-default profile (e.g. `frequent`) setting `minNewSessions` was silently
116
+ ignored because the gate (and its candidate-count discovery window) read
117
+ `profiles.improve.default`. They now read the resolved active profile, matching
118
+ how `extract.enabled` already resolves.
119
+
120
+ ### Docs
121
+
122
+ - Documented that `processes.extract.indexSessions` (default on) makes a second
123
+ LLM call per processed session (the session summary); set it to `false` to halve
124
+ per-session extract cost. Unchanged/skipped sessions still cost zero.
125
+
126
+ ## [0.9.0-beta.34] — 2026-06-21
127
+
128
+ ### Fixed
129
+
130
+ - **`akm extract --type opencode` reads opencode's SQLite session store.** opencode
131
+ migrated session storage from per-file JSON (`storage/session/<projectId>/<id>.json`
132
+ + `storage/message/<id>/*.json`) to a single Drizzle-managed database at
133
+ `<base>/opencode.db` (tables `session`/`message`/`part`; message text lives in
134
+ `part` rows with `data` JSON `type:"text"`). The legacy JSON layout went stale
135
+ ~2026-02, so extract discovered 0 sessions on current opencode and the
136
+ `session.idle` extract hook had nothing to read. `OpenCodeProvider` now prefers
137
+ `opencode.db` when present (read-only, via the cross-driver `openDatabase` seam)
138
+ and falls back to the JSON layout. Verified end-to-end through the plugin's
139
+ `session.idle` hook.
140
+
141
+ ## [0.9.0-beta.33] — 2026-06-21
142
+
143
+ ### Fixed
144
+
145
+ - **`akm extract` decoupled from the improve-stage toggle.** `processes.extract.enabled`
146
+ now gates extract only as a STAGE of `akm improve` (the active improve profile, per
147
+ #593/#594); an explicit `akm extract` command always runs. Previously dropping extract
148
+ from the daily improve profile silently disabled the standalone command (and its LLM
149
+ calls, via the shared `session_extraction` feature gate).
150
+ - **`extract --session-id` now respects the content-hash ledger; `--force` overrides.**
151
+ Explicit single-session extraction previously bypassed the #602 already-extracted skip
152
+ unconditionally — re-paying the LLM on every call and risking double-extraction against
153
+ the cron. Now a targeted `extract --session-id <id>` is idempotent (skips an unchanged,
154
+ already-extracted session with zero LLM calls) and only `--force` re-extracts. This
155
+ makes a session-end hook firing `extract --session-id <id>` precise AND idempotent.
156
+
157
+ ## [0.9.0-beta.32] — 2026-06-21
158
+
159
+ ### Added
160
+
161
+ - **Recombine acceptance path — confirmed lessons now auto-accept.** Recombine
162
+ hypotheses that reach the confirmation threshold (promoted to `type: lesson`,
163
+ #625/#633) now flow to ACCEPTED by reusing the existing drain mechanism instead
164
+ of piling up pending forever: the `personal-stash` drain policy gains a
165
+ `{ generator: "recombine", requireType: "lesson", maxDiffLines: 200 }` rule, via
166
+ a new optional `requireType` frontmatter filter on `DrainAcceptRule`. Only
167
+ confirmed `type: lesson` proposals auto-accept; unconfirmed `type: hypothesis`
168
+ proposals stay pending; the existing proposal quality gate still applies.
169
+ - **`processes.reflect.lowValueFilter` (opt-in, default OFF)** — deterministic
170
+ semantic value-floor that defers trivial reflect rewrites (#639A).
171
+ - **`processes.extract.triage.proceduralAwareFloor` (opt-in, default OFF)** —
172
+ triage floor requiring markers/edits so real lessons always pass (#641).
173
+
174
+ ### Fixed
175
+
176
+ - **Select-time proactive cooldown leak.** `selectProactiveMaintenanceRefs` plans
177
+ the due set BEFORE acquiring `reflect-distill.lock`, so overlapping/back-to-back
178
+ improve runs reused stale due-state and re-reflected the same asset repeatedly
179
+ (observed up to ~16× in a day). The orchestrator now re-applies the dueDays gate
180
+ with freshly-read timestamp maps INSIDE the lock (`filterProactiveDue`), dropping
181
+ refs a concurrent run already reflected.
182
+
183
+ ## [0.9.0-beta.31] — 2026-06-20
184
+
185
+ ### Changed
186
+
187
+ - **#632 — recombine now filters junk tags structurally.** Frontmatter tags that
188
+ are pure numbers, dates (`20260529`), short hex hashes (`002c624c`), version
189
+ strings (`0.8.0`, `v2`), single chars, or common English stopwords (`is`, `the`,
190
+ `for`, `when`, …) carry no topical signal and never form a recombine cluster.
191
+ Unlike `excludeTags` (a fixed project list), this catches the OPEN-ENDED junk —
192
+ every new date or commit hash — with no config upkeep. Exposed as `isJunkTag`.
193
+ On the live stash this turns the recombine cluster set from generic 66–171-member
194
+ buckets into tight topical clusters (`auth`, `architecture`, `patterns`, …).
195
+
196
+ ## [0.9.0-beta.30] — 2026-06-20
197
+
198
+ ### Changed / Fixed
199
+
200
+ - **#632 — recombine cluster tuning (opt-in, default-preserving).** Recombine
201
+ clustered memories by frontmatter tag and preferred the LARGEST buckets, so it
202
+ always picked the coarsest whole-stash tags (`session`/`claude`/`akm`, 63–171
203
+ members) and produced bland generalizations. Two new `processes.recombine` knobs:
204
+ `maxClusterSize` (skip clusters larger than N, so over-broad buckets no longer
205
+ reach/starve the largest-first slice) and `excludeTags` (tags that may never form
206
+ a tag cluster). Both UNSET = byte-identical to prior behavior.
207
+ - **#633 — recombine confirmation loop fixed.** The hypothesis confirmation streak
208
+ was keyed on a hash of the EXACT member set, so a growing stash drifted the key
209
+ every run → a fresh row at count 1 → `confirmThreshold` never reached → no
210
+ hypothesis ever promoted to a lesson (a dead two-pass loop). A freshly-induced
211
+ cluster now matches an existing pending row by signature + Jaccard
212
+ membership-overlap (≥ 0.7) and reuses its stable ref, so the streak accumulates
213
+ through membership drift. First/non-overlapping induction is unchanged.
214
+
215
+ ## [0.9.0-beta.29] — 2026-06-20
216
+
217
+ ### Reverted
218
+
219
+ - **#630 — `fact` asset type phase 2 reverted (#631).** The pinned-core assembly +
220
+ `akm fact` CLI shipped in beta.28 was reverted pending rework. Phase 1 (#629, the
221
+ `fact` asset type itself) remains in place.
222
+
223
+ ## [0.9.0-beta.27] — 2026-06-20
224
+
225
+ All new behavior is **opt-in / default-preserving** — default runs are byte-identical.
226
+
227
+ ### Added
228
+
229
+ - **#624 P2 — priority-ranked graph extraction.** `processes.graphExtraction.topN`:
230
+ when set, the graph-extraction pass ranks eligible files by asset utility
231
+ (`utility_scores`, read-only join) and processes only the top-N per run, so
232
+ high-value assets get graphed first instead of a ~55h full-corpus sweep. Unset
233
+ (default) = no ranking, byte-identical.
234
+ - **#624 P3 — lazy on-demand graph extraction.** New `graph_extraction_queue` table
235
+ + `enqueueGraphExtraction`/`drainExtractionQueue`/`extractGraphForSingleFile`.
236
+ `akm curate` enqueues an ungraphed hit (non-blocking); `akm show` can extract a
237
+ missing graph inline — gated on `index.graph.lazyGraphExtraction: true`
238
+ (**default off**: `show` makes no LLM call by default), model-guarded, and bounded
239
+ by a 30s timeout so it never hangs. The pass drains the queue before the ranked
240
+ sweep. This **closes #624** (all three layers shipped).
241
+ - **#616 — bounded multi-cycle phasing.** `profiles.improve.<name>.maxCycles`
242
+ (default 1): when > 1, the improve passes run in an N-cycle loop so gate-accepted
243
+ output of cycle N feeds cycle N+1 within the same run (re-running ensureIndex +
244
+ ref selection each cycle), stopping at a fixed point and respecting the run budget.
245
+ `maxCycles: 1` = byte-identical to today.
246
+
247
+ ### Fixed
248
+
249
+ - **Release CI unblocked.** `runCliCapture` (test harness) restored `process.exitCode`
250
+ to a captured `undefined`, which under `bun test` does not clear a previously-set
251
+ non-zero exit code — so the unit suite exited 1 with 0 failures at `TEST_PARALLEL=1`
252
+ (exactly how `release.yml` runs), silently blocking every npm publish since beta.11.
253
+ Fixed to restore to `0`. (This is why beta.26 was the first successful workflow publish.)
254
+
255
+ ### Changed
256
+
257
+ - **CI/release tests sharded across runner jobs (~15 min → ~2 min).** Bun 1.3.x
258
+ in-process test parallelism (`--parallel=N`, N>1) hits an intermittent
259
+ `epoll_ctl EEXIST` race / busy-spin hang on the `--isolate` workers, which had
260
+ forced fully-sequential (`TEST_PARALLEL=1`) runs. Tests now shard across separate
261
+ runner jobs (each a separate process tree, so no cross-shard fd/epoll collisions)
262
+ with `--parallel=1` within each shard; the matrix runs shards concurrently. The
263
+ release gate runs the identical set of tests. Local `bun run check` defaults to
264
+ sequential too (the only safe mode on this Bun version). Coverage unchanged.
265
+ Each shard runs through `scripts/run-test-shard.sh`, which retries **only on a
266
+ hang/timeout** (the busy-spin can rarely fire even at `--parallel=1`) and never
267
+ on a real test failure, so genuine red tests still fail fast and are never masked.
268
+
269
+ ## [0.9.0-beta.26] — 2026-06-20
270
+
271
+ ### Added
272
+
273
+ - **#628 — configurable SQLite journal mode (`AKM_SQLITE_JOURNAL_MODE`) for network
274
+ filesystems.** AKM previously opened every database with `PRAGMA journal_mode = WAL`
275
+ unconditionally, which cannot run on a network filesystem (NFS/SMB/Azure Files) —
276
+ WAL's `-shm` shared-memory wal-index can't be `mmap`'d over a network mount. You can
277
+ now set `AKM_SQLITE_JOURNAL_MODE` to `WAL` (default), `DELETE`, or `TRUNCATE`, applied
278
+ at **all five** db openers (`state.db`, `index.db` ×2 paths, `workflow.db`, `logs.db`).
279
+ At the `WAL` default AKM auto-detects a network mount for the data dir and transparently
280
+ falls back to `DELETE` (rollback journal + `synchronous = FULL`) with a one-line warning;
281
+ invalid values warn once and fall back to `WAL`. **Default behavior is byte-identical.**
282
+ This lets the AKM database subtree live on a shared volume (e.g. Azure Files under
283
+ Azure Container Apps). New docs section "Hosting AKM databases on a network share
284
+ (NFS/SMB)" in `docs/configuration.md`.
285
+
286
+ ## [0.9.0-beta.25] — 2026-06-19
287
+
288
+ Completes the recombine / extract-efficiency / graph thread. All new improve
289
+ passes are **opt-in (default off)**, so default behavior is unchanged.
290
+
291
+ ### Added
292
+
293
+ - **#606 — event-driven extract (`akm extract --watch`).** Opt-in watch mode: an
294
+ injectable, debounced watcher triggers extraction shortly after a session file
295
+ appears, with a clean `stop()` handle. The `8,28,48` cron remains the fallback;
296
+ no daemon is auto-launched.
297
+ - **#625 — recombine second pass (hypothesis → lesson).** The opt-in `recombine`
298
+ process (#609) now consumes `confirmThreshold` (default 2): a generalization
299
+ re-induced that many consecutive runs is promoted from a `type: hypothesis`
300
+ proposal to a `type: lesson` proposal through the normal queue + quality gate
301
+ (never a direct stash write). Hypotheses that stop recurring decay. Backed by a
302
+ new `recombine_hypotheses` table in `state.db`.
303
+
304
+ ### Changed
305
+
306
+ - **#624 (P1) — graph storage decoupled from `entries.id`.** `graph_files` is
307
+ re-keyed on `(stash_root, file_path, body_hash)`, so extracted graph data now
308
+ **survives a reindex** of unchanged files instead of being cascade-wiped. The
309
+ upgrade is migrated in a **targeted, graph-only path** that preserves existing
310
+ graph data and leaves the entry index, embeddings, FTS, and LLM-enrichment cache
311
+ untouched — **no full index rebuild and no re-embed** on upgrade. (P2 priority-
312
+ ranked extraction and P3 lazy/on-demand extraction remain deferred.)
313
+
314
+ ### Fixed
315
+
316
+ - Graph re-key migration no longer triggers a destructive full-index rebuild: it
317
+ is a graph-scoped table migration (no `DB_VERSION` bump), and it **copies** the
318
+ existing graph rows into the new schema rather than dropping them.
319
+ - Test-suite `/tmp` hygiene: sandbox teardown now fires on `SIGINT`/`SIGTERM`/
320
+ `SIGHUP` (not just clean exit), and a `sweep:tmp` step reclaims stale `akm-*`
321
+ sandbox dirs left by force-killed workers — eliminating the tmpfs accumulation
322
+ that caused intermittent `EEXIST: epoll_ctl` test flakes.
323
+
324
+ ## [0.9.0-beta.20] — 2026-06-18
325
+
326
+ ### Fixed
327
+
328
+ - **`akm update --all` no longer fails for writable `github:` entries stored as `source:"git"`**. `updateRegistryEntry` was using `synced.source` (re-derived from the ref scheme as `"github"`) instead of the existing `entry.source`, causing the config validator to reject `writable:true` on every update cycle.
329
+
330
+ ## [0.9.0-beta.19] — 2026-06-17
331
+
332
+ ### Fixed
333
+
334
+ - **`akm feedback` now completes in ~0.3s** (was 3+ minutes). Root cause: the command was calling `ensureIndex` with `mode: "blocking"` inside `withIndexWriterLease`, triggering a full reindex on every feedback call. Fix: removed the `ensureIndex` call entirely (feedback only needs the index to exist, not be current — a stale index is fine for ref lookup); removed the application-level writer lock (SQLite WAL + `busy_timeout=30s` handles concurrent access with `akm improve`); added a fast DB-exists guard with a clear error for first-time users.
335
+ - **`akm health --format html` now completes in ~11s** (was ~18s). Root cause: `akmHealth()` was called twice — once for the main result and once to get `deltas`. Fix: merged into a single call passing both `groupBy: "run"` and `windowCompare` together.
336
+
337
+ ## [0.9.0-beta.18] — 2026-06-17
338
+
339
+ ### Changed
340
+
341
+ - **Health report: Recent Runs table now shows all filtered runs in descending order** (newest first) instead of capping at the last 10.
342
+ - **Health report: Removed "Command Set Used" section.**
343
+ - **Health report: All timestamps now display in the viewer's local timezone** (chart axis labels, runs table, freshness line, executive summary, footer). Server-rendered ISO strings are wrapped in `<time data-iso>` elements and converted to local time by client-side JS on page load.
344
+
345
+ ### Changed (migration required)
346
+
347
+ - **WS-2 outcome loop (#613) — default-off weight change (state.db migration 010).**
348
+ Every `akm improve` run now writes an `asset_outcome` row per processed asset
349
+ (state.db migration `010`) and computes a differential usefulness signal
350
+ (`outcome_score`) per ref. The outcome signal is persisted and visible in the
351
+ health report, but the **weight change is gated behind a config flag** (see
352
+ below). Ranking is unchanged from WS-1 by default.
353
+
354
+ **Opt-in weight change.** The WS-2 projection weights (`w_e=0.25, w_o=0.15,
355
+ w_r=0.60`) affect ranking only when you explicitly set
356
+ `improve.salience.outcomeWeightEnabled: true` in your `akm.yaml`. The default
357
+ (`false`) keeps WS-1 parity weights (`w_e=0.30, w_r=0.70`, `w_o=0`), so
358
+ existing users see no ranking change on upgrade.
359
+
360
+ **Part-V measurement gate.** Before enabling the weight change, run the Part-V
361
+ T0 baseline (`scripts/akm-eval` + `akm health`; confirm proactive accept
362
+ ≥ 0.9× reactive; reversion ≤ 0.15; retrieval-delta ≥ 0; coverage not
363
+ regressed). That gate requires a running production stash and cannot be
364
+ exercised in CI. Once confirmed, set
365
+ `improve.salience.outcomeWeightEnabled: true` to activate the three-way split.
366
+
367
+ **Outcome loop mechanics.** `outcome_score` is a differential prediction-error
368
+ signal: `(retrieval_delta − expected_delta) − PENALTY × retrieval_delta × (1 −
369
+ accepted_change_rate) + valence`, tracked via an EMA (α=0.3). New rows are
370
+ warm-started from the utility EMA score (clipped to 0.3) so the signal is
371
+ non-zero from launch. A stash-wide diversity floor (10% of the max score) prevents
372
+ rare-but-correct assets from being permanently outcompeted. An inverted-proxy
373
+ tripwire (`corr(outcome_score, accepted_change_rate) < −0.3`) emits an
374
+ `outcome_proxy_inverted` health event when the signal degrades.
375
+
376
+ `review_pressure` is computed and persisted per asset but is **not yet wired into
377
+ the admission policy** — that is deferred to a later work stream per plan §Part-VI
378
+ #613. The column is present and populated; routing it into the consolidation-
379
+ selection filter is the next step.
380
+
381
+ - **WS-1 salience vector (#618) — default-on ranking change.** The eligibility sort
382
+ for all `akm improve` runs (whole-stash, type, and ref scope) has changed from
383
+ `combinedEligibilityScore = utility·0.7 + negativeOnlyRatio·0.3` to
384
+ `rankScore = (0.3·encodingSalience + 0.7·retrievalSalience) × sizePenalty`
385
+ (feedback valence and utility EMA dropped from ordering until WS-2 re-introduces
386
+ outcome salience). Assets are now ranked by retrieval frequency × recency × type
387
+ importance rather than by feedback magnitude. Because the old
388
+ `combinedEligibilityScore` ordering was never persisted, a forgetting comparison is
389
+ not possible on the first run; instead a one-time `improve_salience_first_run` marker
390
+ event is emitted to record the transition. On every subsequent run a stash-wide
391
+ `improve_salience_rank_change` drift report (including `stashSize`) is emitted so
392
+ rank movement under the new scoring can be tracked over time.
393
+ The Part-V measurement protocol (T0 baseline via `scripts/akm-eval` + health report,
394
+ throughput/quality gate) is deferred to the WS-2 milestone, when outcome salience
395
+ re-joins the projection and re-tuning is triggered.
396
+
397
+ ## [0.9.0-beta.12] - 2026-06-15
398
+
399
+ Improve-tuning work streams (all **default-off / parity-preserving** — no behavior
400
+ change until explicitly enabled).
401
+
402
+ ### Added
403
+
404
+ - **#617 — deterministic near-duplicate memory dedup** (`processes.consolidate.dedup`,
405
+ default off). A cheap no-LLM pre-pass in front of consolidation collapses obvious
406
+ duplicates — `.derived`+origin pairs and content twins (normalized content-hash
407
+ equality, or embedding cosine ≥ `cosineThreshold`, default 0.97). Each dropped
408
+ variant is archived + backed up before deletion; hot memories are never
409
+ collapsed; distinct-but-related memories fall through to the LLM.
410
+ - **#581 — judged-state cache for consolidation** (`processes.consolidate.judgedCache`,
411
+ default off). New state.db table (`consolidation_judged`) records each memory's
412
+ content hash + outcome when the LLM judges it; subsequent runs skip
413
+ judged-unchanged memories, converting coverage from O(time-window) to
414
+ O(changed/new) so a run can sweep the full corpus. Fails open; failed chunks
415
+ and dry-runs never poison the cache. (state.db migration `007`.)
416
+ - **#612 — auto-accept gate calibration** (`improve.calibration`, auto-tune default
417
+ off). Joins predicted gate confidence to realized accept/reject outcomes into a
418
+ reliability table + calibration gap, surfaced in `akm health` (+ summary rows in
419
+ the HTML report). Opt-in bounded threshold auto-tune nudges the accept threshold
420
+ within a configured band toward a target accept rate, logged via a
421
+ `calibration_autotune` event. (Replay-prioritization from prediction error is
422
+ deferred — it depends on the #610 replay budget, a 0.10 item.)
423
+
424
+ ### Fixed
425
+
426
+ - **#614 — symmetric valence weighting** (`profiles.improve.*.symmetricValence`,
427
+ default off). The eligibility sort weighted feedback negative-only; when enabled
428
+ it uses a symmetric `|valence|` magnitude so strong positive and strong negative
429
+ feedback both drive attention (utility stays the dominant factor), routing
430
+ high-negative → fix and high-positive → reinforce lanes.
431
+
432
+ ## [0.9.0-beta.11] - 2026-06-15
433
+
434
+ ### Added
435
+
436
+ - **`extract.maxSessionsPerRun`** (default 25) — caps the NEW sessions the
437
+ extract pass LLM-processes in a single run so a backlog (e.g. after downtime)
438
+ can't push one run past its scheduled-task timeout. Overflow sessions stay
439
+ unseen and are picked up by later runs, so coverage is preserved. `0` disables.
440
+
441
+ ### Fixed
442
+
443
+ - **Auto-accept validation failures are no longer a blind leak.** When a
444
+ confidence-passing proposal fails promotion validation, the gate now captures
445
+ the reason (the `validateProposal` finding kind, e.g. `validation:description-quality`),
446
+ records it on the proposal (`akm proposal show` explains the rejection), logs
447
+ it, and exposes `failedByReason` on the gate result — so the ~5% leak is
448
+ diagnosable instead of silently warned-and-dropped.
449
+ - **Inflated skip-reason aggregates in `akm health`.** `no_new_signal` /
450
+ `profile_filtered_all_passes` are per-run snapshots of a stable set; the
451
+ window aggregator summed their per-run counts (≈2.7M / 3M). It now uses the
452
+ most recent run's count for these aggregated-snapshot reasons while still
453
+ summing genuine per-occurrence skips.
454
+
455
+ ## [0.9.0-beta.10] - 2026-06-15
456
+
457
+ ### Added
458
+
459
+ - **#603** — `akm health` pool-saturation advisory. Instead of alerting on the
460
+ raw `sessionsScanned` count (which false-alarmed on normal cadence changes),
461
+ a new `pool-saturation` advisory reports the ratio of new (unseen) sessions
462
+ to the total session pool: informational below 10% (expected steady state),
463
+ warning below 2% (possible discovery/dedup bug). Heuristic, never gates
464
+ overall status.
465
+ - **#576** — the `akm health` HTML report now renders the real per-stage LLM
466
+ token/time aggregate (a "🧠 LLM Work" KPI card + LLM token/call/wall-time
467
+ summary rows) from the captured `llm_usage` events, replacing the GPU-time
468
+ proxy.
469
+ - **Built-in `akm health --format html` report overhaul** — the report is now a
470
+ strict superset of (and supersedes) the external `akm-health-report` stash
471
+ skill. Restored the interactive filter bar (time-slice 1d–21d, task, status)
472
+ with client-side chart/table re-render and the Last-10 "Task" column;
473
+ reordered sections to a decision-first flow (verdict → action items → KPIs →
474
+ table → charts); added a synthesized one-sentence **Verdict** (status + 2–3
475
+ drivers) and a freshness line; merged the duplicate Advisories / What-to-Watch
476
+ into one prioritized, de-duplicated **Action Items** list (P1/P2/P3 +
477
+ remediation command); added a per-stage **LLM token** stacked-bar chart and
478
+ `dataZoom` sliders on dense charts; fixed the failed-run scatter x-alignment
479
+ (now shape-encoded); KPI-card colors are now health signals (not decoration);
480
+ added metric-glossary tooltips, chart `aria-label`s, contrast fixes, and
481
+ empty-state overlays. Deterministic output preserved.
482
+
483
+ ### Fixed
484
+
485
+ - **Health report accuracy** (follow-ups to the overhaul): the per-run **Task**
486
+ column/filter now show the real scheduled task (`akm-improve-frequent`, …) via
487
+ a ±5min `task_history` join instead of the run's scope (which is `all` for
488
+ every scheduled run); the time-**slice** filter options are now derived from
489
+ the report's `--since` window (e.g. All/3d/1d/12h/6h for a 7d report) and
490
+ default to "All" — replacing the hard-coded 1d–21d list that didn't match the
491
+ window; and the trend **deltas** now default their compare window to `--since`
492
+ (like-for-like, e.g. last 7d vs prior 7d) instead of a fixed 24h, which had
493
+ produced nonsensical period-over-period percentages on multi-day reports.
494
+ - **Inflated stash-snapshot metrics in `akm health`.** `memorySummary`
495
+ (derived/eligible) and `profileFilteredRefs` are whole-stash snapshots recorded
496
+ on every run, but the window aggregator was **summing** them across all runs —
497
+ e.g. "915,258 of 1,226,025 eligible" and a 2.4M filtered-ref count. They now
498
+ take the most recent run's snapshot (the current state). Per-run *work* metrics
499
+ (promoted, MI written, graph entities, …) remain genuine window sums.
500
+ - **Health report polish:** the akm version is stamped in the header (under the
501
+ AKM logo) and footer; the steady-state `no new signal since last proposal`
502
+ distill reason is excluded from the skip-reason chart (it drowned out the
503
+ actionable reasons); and the Consolidation Output chart now draws Promoted as a
504
+ line on a secondary right-hand axis (it dwarfs merged/deleted) with merged and
505
+ deleted as bars on the left axis.
506
+
507
+ - **#598** — process-level tuning fields (`consolidate.incrementalSince`,
508
+ `minPoolSize`, `neighborsPerChanged`, `extract.minContentChars`, per-process
509
+ `enabled` flags) now survive an `akm config` rewrite. They are first-class
510
+ typed `ImproveProcessConfigSchema` fields, so the load→save round trip no
511
+ longer silently drops them. Unknown process sub-keys hard-error at load
512
+ (`ConfigError`) rather than being silently discarded — the deliberate,
513
+ documented resolution. Regression-guarded by
514
+ `tests/config-process-roundtrip.test.ts`.
515
+
516
+ ## [0.9.0-beta.9] - 2026-06-14
517
+
518
+ Restore and instrument `akm improve` steady-state output. The reflect/distill
519
+ self-improvement lanes had been near-zero in steady state because the
520
+ signal-delta eligibility gate was the only lane (cache "no-access = no-work"
521
+ pathology) and the high-retrieval fallback was structurally dead. This release
522
+ revives proactive improvement, adds attribution + a measurement/kill-criterion
523
+ system so the lane must prove its value, and right-sizes reflect budgets to
524
+ their task timeouts.
525
+
526
+ ### Added
527
+
528
+ - **Proactive maintenance selector** (`proactiveMaintenance` improve process):
529
+ due-gated, composite-priority (`importance × log(1+retrievalFreq) ×
530
+ recencyDecay / log(size)`), bounded rotating top-N reflect/distill over
531
+ stale/never-reflected assets. **Disabled by default**; enable per profile.
532
+ - **Eligibility attribution**: every reflect/distill proposal is stamped
533
+ `eligibilitySource ∈ {signal-delta, high-retrieval, proactive, scope,
534
+ unknown}` on `reflect_invoked`/`distill_invoked`/`promoted` events and the
535
+ proposal record, so outcomes are sliceable by lane.
536
+ - **Measurement system** under `scripts/akm-eval/`: a real-query retrieval suite
537
+ generated from `usage_events`, and `akm-eval-proactive-verdict` — a read-only
538
+ kill-criterion runner comparing the proactive lane (treatment) vs due-but-
539
+ untouched assets (control). Emits PASS/FAIL/INCONCLUSIVE and recommends
540
+ disabling the lane on FAIL. New `proactive_selected` event +
541
+ `proactiveSelected`/`proactiveDueTotal`/`proactiveNeverReflected` fields on
542
+ `improve_completed`.
543
+
544
+ ### Fixed
545
+
546
+ - Revived the P0-A high-retrieval fallback: genuinely zero-feedback assets were
547
+ routed to the fully-skipped branch one phase before the fallback could see
548
+ them, so frequently-retrieved-but-never-rated assets were never improved.
549
+ - `getRetrievalCounts` now normalizes bare vs `origin//`-prefixed refs (it was
550
+ dropping ~half the retrieval signal) and counts `curate` events
551
+ (`akm curate` now records per-item `entry_ref`).
552
+ - The fully-skipped `no_new_signal` branch emitted one `improve_skipped` event
553
+ per ref (~11K writes/run, ~400K rows/day) — a contributor to 900s improve
554
+ timeouts and state.db bloat. Collapsed into one aggregated counted event.
555
+
556
+ ## [0.9.0-beta.8] - 2026-06-13
557
+
558
+ Fix multi-process SQLite contention in `index.db` and harden concurrent proposal
559
+ queue mutations.
560
+
561
+ ### Changed
562
+
563
+ - Added a global `index.db` writer lease used by foreground indexing,
564
+ background auto-index, improve maintenance index writers, graph updates, and
565
+ feedback writes.
566
+ - Replaced the racy background index PID-file dedup flow with lease-based
567
+ coordination and explicit handoff to the spawned worker.
568
+ - `akm feedback` now uses blocking index preparation and writes under the same
569
+ `index.db` lease, avoiding self-inflicted `database is locked` failures.
570
+ - Proposal queue create/archive/gate-decision mutations now run under
571
+ `BEGIN IMMEDIATE` state.db transactions so concurrent processes serialize on
572
+ live queue state.
573
+
574
+ ## [0.9.0-beta.7] - 2026-06-13
575
+
576
+ Fix the `akm improve` regression introduced by background `ensureIndex`.
577
+
578
+ ### Changed
579
+
580
+ - Added an explicit `ensureIndex` mode so callers choose `background` or
581
+ `blocking` behavior directly instead of relying on hidden environment state.
582
+ - `akm improve` now uses blocking index preparation before collecting eligible
583
+ refs, restoring the post-upgrade empty-index recovery path.
584
+ - Removed the `AKM_INDEX_INLINE` test-only override so tests exercise the same
585
+ index behavior model as production.
586
+
587
+ ## [0.9.0-beta.6] - 2026-06-12
588
+
589
+ Pipeline optimization: new per-process config fields wire up the consolidation
590
+ and improve pipeline knobs exposed by the optimization report — incremental
591
+ consolidation, pool caps, distill gating, and memory inference throttling.
592
+
593
+ ### Added
594
+
595
+ - **`consolidate.incrementalSince`** — profile config field that narrows the
596
+ consolidation candidate pool to memories modified within the given window
597
+ (e.g. `"1h"`, `"4h"`) plus their graph neighbours. Enables frequent
598
+ consolidation passes (e.g. `quick-shredder` every 15 min) without full-pool
599
+ sweeps. Absent = full-pool sweep (correct for nightly runs).
600
+ - **`consolidate.limit`** — hard cap on memories processed per consolidation
601
+ pass, applied after incremental narrowing. Prevents runaway full-pool sweeps
602
+ in the nightly default profile.
603
+ - **`consolidate.neighborsPerChanged`** — configurable graph-neighbour count
604
+ per changed memory during incremental consolidation (was hardcoded to 5).
605
+ `quick-shredder` sets this to 3 for a 40% candidate reduction per burst.
606
+ - **`distill.requirePlannedRefs`** — when `true`, the distill process is
607
+ skipped entirely for distill-only refs when the reflect phase produced zero
608
+ planned refs. Eliminates hundreds of `distill-skipped` events on quiet passes
609
+ where all refs are on reflect cooldown.
610
+ - **`memoryInference.minPendingCount`** — minimum pending split-parent memory
611
+ count below which the inference pass is skipped entirely (zero LLM calls).
612
+ Prevents lock acquisition on passes where there is nothing to infer.
613
+ - **`reflect.limit`** — per-process ref limit for the reflect/distill loop,
614
+ applied as the improve run limit when no CLI `--limit` is given.
615
+ - **New `reflect-distill` improve profile** — dedicated reflect + distill +
616
+ memoryInference + triage profile for the every-4h `akm-improve-frequent`
617
+ task. `reflect.limit: 25` bounds LLM cost per pass.
618
+
619
+ ### Changed
620
+
621
+ - **`quick-shredder` profile tuned**: `incrementalSince` `4h` → `1h`,
622
+ `maxChunkSize` 25 → 35, added `minPoolSize: 10`, `neighborsPerChanged: 3`,
623
+ `memoryInference.minPendingCount: 5`. All `profile: "qwen-9b-shredder"`
624
+ process references removed — falls back to default LLM.
625
+ - **`default` improve profile** (nightly): extract disabled (dedicated
626
+ `akm-extract` task runs at 01:48), consolidate gets `limit: 500`,
627
+ reflect gets `limit: 100` and `allowedTypes`, distill gets
628
+ `requirePlannedRefs: true`, triage enabled at 50 accepts/run,
629
+ graphExtraction explicitly enabled.
630
+ - **Cron schedule optimised**: extract reverted to `8,28,48 * * * *` (3×/hr),
631
+ quick-shredder shifted to `4,19,34,49` (4-min extract gap), health-report
632
+ shifted to `:03` (avoids `:00` collision), `akm-improve-frequent` re-enabled
633
+ at `45 */4` with `reflect-distill` profile.
634
+
9
635
  ## [0.9.0-beta.3] - 2026-06-12
10
636
 
11
637
  Stabilization batch closing the remaining 0.9.0 milestone: DB-locking and
@@ -0,0 +1,23 @@
1
+ You are the akm consolidate assistant analyzing memory assets.
2
+
3
+ Rules:
4
+ 1. MERGE: Two or more memories are substantially duplicated or closely related → propose merging. Return the primary ref to keep and secondary refs to delete. Do NOT include mergedContent — the merge will be executed in a separate step.
5
+ 2. DELETE: Memory is clearly outdated, contradicted, or redundant → propose deletion. NEVER propose delete for memories annotated `(captureMode: hot)` — they are user-explicit and only the user can retire them. The downstream guard will refuse these regardless, so proposing them just wastes tokens.
6
+ 3. PROMOTE: Memory expresses a stable, reusable fact suitable as a `knowledge:` asset → propose promotion. Do NOT delete the source memory. NEVER propose promote / merge / contradict for memories annotated `(already queued)` — they have a pending proposal whose body matches; a duplicate will be deterministically dropped, so proposing them just wastes tokens.
7
+ 4. CONTRADICT: Two memories assert logically exclusive facts such that following BOTH simultaneously is impossible — not merely related or overlapping. You MUST cite the exact sentence from Memory A and the exact sentence from Memory B that are in direct conflict. If you cannot cite specific opposing sentences, use KEEP instead. Sharing a topic, tool, domain, or workflow stage is NOT sufficient. Only direct factual opposites qualify: opposing recommended commands, opposing boolean flags, opposing version numbers, or mutually exclusive instructions. Use confidence ≥ 0.92 only; omit the op entirely if below that threshold.
8
+ 5. KEEP: Memory is unique and current → omit from output.
9
+
10
+ Return ONLY JSON (no prose, no code fences):
11
+ {
12
+ "operations": [
13
+ { "op": "merge", "primary": "memory:<name>", "secondaries": ["memory:<name>", ...], "mergeStrategy": "synthesize", "confidence": 0.95 },
14
+ { "op": "delete", "ref": "memory:<name>", "reason": "<brief reason>", "confidence": 0.90 },
15
+ { "op": "promote", "ref": "memory:<name>", "knowledgeRef": "knowledge:<suggested-slug>", "reason": "<brief reason>", "description": "<one sentence describing the new knowledge asset>", "confidence": 0.92 },
16
+ { "op": "contradict", "ref": "memory:<name>", "contradictedByRef": "memory:<name>", "reason": "<brief reason>", "confidence": 0.88 }
17
+ ],
18
+ "warnings": ["<optional concerns>"]
19
+ }
20
+
21
+ For every operation, emit a `confidence` field in [0, 1] expressing your certainty that the operation is correct and safe. Use 0.95+ only when evidence is unambiguous. Omit the field rather than guessing if you are uncertain.
22
+
23
+ When the merged content includes an `updated` frontmatter field, the value MUST be a real ISO date string (e.g. `updated: 2026-05-20`). NEVER emit `updated: today`, `updated: {today}`, `updated: {today: null}`, `updated: now`, or any other literal placeholder/template-variable. If you do not have a real source-of-truth date, OMIT the `updated` field entirely — the post-processor will not invent one for you.