akm-cli 0.9.0-beta.3 → 0.9.0-beta.30

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 (107) hide show
  1. package/CHANGELOG.md +600 -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 +5 -1
  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/templates/html/health.html +281 -111
  16. package/dist/cli.js +14 -3
  17. package/dist/commands/agent/contribute-cli.js +16 -3
  18. package/dist/commands/feedback-cli.js +15 -6
  19. package/dist/commands/graph/graph.js +75 -71
  20. package/dist/commands/health/checks.js +48 -0
  21. package/dist/commands/health/html-report.js +422 -80
  22. package/dist/commands/health.js +381 -9
  23. package/dist/commands/improve/calibration.js +161 -0
  24. package/dist/commands/improve/consolidate.js +634 -111
  25. package/dist/commands/improve/dedup.js +482 -0
  26. package/dist/commands/improve/distill.js +145 -69
  27. package/dist/commands/improve/encoding-salience.js +205 -0
  28. package/dist/commands/improve/extract-cli.js +115 -1
  29. package/dist/commands/improve/extract-prompt.js +33 -2
  30. package/dist/commands/improve/extract-watch.js +140 -0
  31. package/dist/commands/improve/extract.js +244 -35
  32. package/dist/commands/improve/feedback-valence.js +54 -0
  33. package/dist/commands/improve/homeostatic.js +467 -0
  34. package/dist/commands/improve/improve-auto-accept.js +113 -6
  35. package/dist/commands/improve/improve-profiles.js +12 -0
  36. package/dist/commands/improve/improve.js +1974 -614
  37. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  38. package/dist/commands/improve/outcome-loop.js +256 -0
  39. package/dist/commands/improve/proactive-maintenance.js +87 -0
  40. package/dist/commands/improve/procedural.js +409 -0
  41. package/dist/commands/improve/recombine.js +528 -0
  42. package/dist/commands/improve/reflect.js +26 -1
  43. package/dist/commands/improve/related-sessions.js +120 -0
  44. package/dist/commands/improve/salience.js +386 -0
  45. package/dist/commands/improve/triage.js +95 -0
  46. package/dist/commands/lint/agent-linter.js +19 -24
  47. package/dist/commands/lint/base-linter.js +173 -60
  48. package/dist/commands/lint/command-linter.js +19 -24
  49. package/dist/commands/lint/env-key-rules.js +34 -1
  50. package/dist/commands/lint/fact-linter.js +39 -0
  51. package/dist/commands/lint/index.js +31 -13
  52. package/dist/commands/lint/memory-linter.js +1 -1
  53. package/dist/commands/lint/registry.js +7 -2
  54. package/dist/commands/lint/task-linter.js +3 -3
  55. package/dist/commands/lint/workflow-linter.js +26 -1
  56. package/dist/commands/proposal/proposal.js +5 -0
  57. package/dist/commands/proposal/validators/proposals.js +71 -54
  58. package/dist/commands/read/curate.js +344 -80
  59. package/dist/commands/read/search-cli.js +7 -0
  60. package/dist/commands/read/search.js +1 -0
  61. package/dist/commands/read/show.js +67 -2
  62. package/dist/commands/sources/installed-stashes.js +5 -1
  63. package/dist/commands/sources/stash-cli.js +10 -2
  64. package/dist/core/asset/asset-registry.js +2 -0
  65. package/dist/core/asset/asset-spec.js +14 -0
  66. package/dist/core/asset/frontmatter.js +166 -167
  67. package/dist/core/asset/markdown.js +8 -0
  68. package/dist/core/config/config-schema.js +259 -2
  69. package/dist/core/config/config.js +2 -2
  70. package/dist/core/logs-db.js +4 -3
  71. package/dist/core/paths.js +3 -0
  72. package/dist/core/state-db.js +649 -30
  73. package/dist/indexer/db/db.js +364 -38
  74. package/dist/indexer/db/graph-db.js +129 -86
  75. package/dist/indexer/ensure-index.js +152 -17
  76. package/dist/indexer/graph/graph-boost.js +51 -41
  77. package/dist/indexer/graph/graph-extraction.js +203 -3
  78. package/dist/indexer/index-writer-lock.js +99 -0
  79. package/dist/indexer/indexer.js +114 -111
  80. package/dist/indexer/passes/memory-inference.js +10 -3
  81. package/dist/indexer/passes/staleness-detect.js +2 -5
  82. package/dist/indexer/search/db-search.js +15 -4
  83. package/dist/indexer/search/ranking-contributors.js +22 -0
  84. package/dist/indexer/search/ranking.js +4 -0
  85. package/dist/indexer/walk/matchers.js +9 -0
  86. package/dist/integrations/agent/prompts.js +1 -0
  87. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  88. package/dist/integrations/harnesses/opencode/session-log.js +9 -0
  89. package/dist/integrations/session-logs/index.js +16 -0
  90. package/dist/llm/client.js +23 -4
  91. package/dist/llm/embedder.js +27 -3
  92. package/dist/llm/embedders/local.js +66 -2
  93. package/dist/llm/graph-extract.js +2 -1
  94. package/dist/llm/memory-infer.js +4 -8
  95. package/dist/llm/metadata-enhance.js +9 -1
  96. package/dist/output/renderers.js +73 -1
  97. package/dist/output/shapes/curate.js +14 -2
  98. package/dist/output/text/helpers.js +9 -0
  99. package/dist/runtime.js +25 -1
  100. package/dist/scripts/migrate-storage.js +1242 -594
  101. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +473 -270
  102. package/dist/sources/providers/tar-utils.js +16 -8
  103. package/dist/storage/sqlite-pragmas.js +146 -0
  104. package/dist/workflows/db.js +3 -4
  105. package/dist/workflows/validate-summary.js +2 -7
  106. package/docs/data-and-telemetry.md +1 -0
  107. package/package.json +9 -6
@@ -133,6 +133,34 @@ export const ImproveProcessConfigSchema = z
133
133
  // consolidation pass skips entirely (emits `pool_below_min_size`). 0 disables
134
134
  // the guard. Only meaningful on the `consolidate` process. Default 500.
135
135
  minPoolSize: z.number().int().min(0).optional(),
136
+ // Consolidate process: deterministic near-duplicate dedup pre-pass (#617).
137
+ // A cheap, no-LLM fast path that collapses obvious duplicates (`.derived`
138
+ // origin pairs + content twins) before the LLM consolidation. Default OFF
139
+ // — when absent the consolidate pass behaves byte-identically to today.
140
+ // `cosineThreshold` is a strict floor in [0, 1] (default 0.97) for the
141
+ // optional embedding-similarity match; exact normalized content-hash
142
+ // equality always collapses regardless of the threshold. Only meaningful
143
+ // on the `consolidate` process.
144
+ dedup: z
145
+ .object({
146
+ enabled: z.boolean().optional(),
147
+ cosineThreshold: z.number().min(0).max(1).optional(),
148
+ // WS-3a: maximum pool size for the O(n²) cosine-similarity twin compare.
149
+ // Only the first `cosineCandidateLimit` memories are cosine-compared;
150
+ // exact-hash matches still run over the full pool. Default 500. Raise
151
+ // with care — cost is O(n²).
152
+ cosineCandidateLimit: z.number().int().positive().optional(),
153
+ })
154
+ .strict()
155
+ .optional(),
156
+ // Consolidate process: judged-state cache (#581). When enabled, a memory
157
+ // whose current content hash equals its cached judged hash is SKIPPED from
158
+ // the LLM pool (judged-unchanged → no re-judge), letting one run sweep the
159
+ // whole corpus at O(changed/new) cost instead of narrowing to a recent
160
+ // time-window slice. Default OFF — when absent the consolidate pass behaves
161
+ // byte-identically to today (the incrementalSince path is unaffected). Only
162
+ // meaningful on the `consolidate` process.
163
+ judgedCache: z.object({ enabled: z.boolean().optional() }).strict().optional(),
136
164
  qualityGate: z.object({ enabled: z.boolean().optional() }).strict().optional(),
137
165
  contradictionDetection: z.object({ enabled: z.boolean().optional() }).strict().optional(),
138
166
  // Extract process config (only meaningful for extract process)
@@ -144,18 +172,163 @@ export const ImproveProcessConfigSchema = z
144
172
  // on the `extract` process.
145
173
  minContentChars: z.number().int().min(0).optional(),
146
174
  maxChunkSize: z.number().int().min(1).max(50).optional(),
175
+ // Consolidate process: narrow candidate pool to memories modified within
176
+ // this duration window plus their graph neighbours. Only meaningful on
177
+ // the `consolidate` process. Absent = full-pool sweep.
178
+ incrementalSince: z.string().optional(),
179
+ // Consolidate process: hard cap on memories processed per pass.
180
+ // Reflect/distill: max refs processed (same as profile-level `limit`).
181
+ limit: positiveInt.optional(),
182
+ // Consolidate process: graph neighbours per changed memory during
183
+ // incremental consolidation. Default 5. Only meaningful with incrementalSince.
184
+ neighborsPerChanged: z.number().int().min(1).optional(),
185
+ // Distill process: skip distill entirely when reflect produced zero planned refs.
186
+ requirePlannedRefs: z.boolean().optional(),
187
+ // proactiveMaintenance process (Layer 2): staleness gate + rotation cooldown
188
+ // in days (default 30). Only meaningful on `proactiveMaintenance`.
189
+ dueDays: z.number().int().min(0).optional(),
190
+ // proactiveMaintenance process: top-N bound per run (default 25). Alias for
191
+ // `limit`; `maxPerRun` wins when both are set.
192
+ maxPerRun: positiveInt.optional(),
193
+ // graphExtraction process (#624 P2): when set, rank eligible files by
194
+ // utility_scores DESC and process only the top-N per run (incremental
195
+ // high-signal-first sweep). Unset = process all eligible (current
196
+ // behavior). Only meaningful on `graphExtraction`.
197
+ topN: positiveInt.optional(),
198
+ // MemoryInference process: minimum pending memory count to run the pass.
199
+ minPendingCount: z.number().int().min(0).optional(),
147
200
  // Extract process: minimum number of new (unseen, in-window) candidate
148
201
  // sessions below which the extract pass skips entirely (emits an
149
202
  // `improve_skipped` event with `reason: "below_min_new_sessions"`). 0
150
203
  // disables the guard. Only meaningful on the `extract` process. Default 0
151
204
  // (disabled) so existing behaviour is preserved; only opted-in profiles set it.
152
205
  minNewSessions: z.number().int().min(0).optional(),
206
+ // Extract process: cap on NEW sessions processed (LLM-called) per run; the
207
+ // rest roll to the next run (still unseen). 0 disables. Absent = default 25.
208
+ maxSessionsPerRun: z.number().int().min(0).optional(),
153
209
  // #561 — index agent sessions as a searchable `session` asset (extract
154
210
  // process). Absent = on-when-an-LLM-is-available (fail-open when offline).
155
211
  indexSessions: z.boolean().optional(),
156
212
  // #561 — minimum session duration in minutes for session indexing. 0
157
213
  // disables the gate. Absent = default 5. Only meaningful on `extract`.
158
214
  minSessionDuration: z.number().min(0).optional(),
215
+ // Consolidate process: fallback p90 wall-clock time per consolidation chunk
216
+ // in seconds, used for cold-start budget estimation when no telemetry
217
+ // history exists. The actual p90 is derived from observed run durations
218
+ // once sufficient history accumulates; this value is only used on the very
219
+ // first run. Default 30 s. Only meaningful on the `consolidate` process.
220
+ p90ChunkSecondsDefault: z.number().finite().positive().optional(),
221
+ // WS-3b: Homeostatic demotion (step 0a). Before any LLM merge, demote
222
+ // retrievalSalience for stale/low-value assets so the merge pool is bounded
223
+ // and high-SNR. Demotion is state.db-only (file content untouched);
224
+ // re-promotable on re-retrieval. Default OFF. Only meaningful on the
225
+ // `consolidate` process.
226
+ homeostaticDemotion: z
227
+ .object({
228
+ enabled: z.boolean().optional(),
229
+ // Minimum days since last retrieval to consider an asset stale (default 30).
230
+ staleDays: z.number().int().min(0).optional(),
231
+ // Demotion factor: multiply retrievalSalience by this when stale (default 0.5).
232
+ demotionFactor: z.number().min(0).max(1).optional(),
233
+ })
234
+ .strict()
235
+ .optional(),
236
+ // WS-3b: Schema-similarity gate (step 0b). At intake, if a new candidate's
237
+ // body embedding is within epsilon of an existing derived-layer lesson/knowledge
238
+ // node, mark it schema-consistent and lower its priority. Default OFF.
239
+ // Only meaningful on the `consolidate` and `extract` processes.
240
+ schemaSimilarity: z
241
+ .object({
242
+ enabled: z.boolean().optional(),
243
+ // Epsilon: cosine similarity threshold above which a candidate is schema-consistent
244
+ // (default 0.85 — looser than dedup's 0.97 since we want to catch conceptual overlap).
245
+ epsilon: z.number().min(0).max(1).optional(),
246
+ // Multiplicative factor applied to candidate confidence when schema-consistent.
247
+ // Default 0.5 — halves the confidence so schema-consistent candidates are less likely
248
+ // to pass the quality gate and create redundant stash entries.
249
+ confidencePenalty: z.number().min(0).max(1).optional(),
250
+ })
251
+ .strict()
252
+ .optional(),
253
+ // WS-3b: Hot-probation intake buffer (step 0c, #604). New system-generated
254
+ // extractions enter captureMode: hot-probation and spend ONE consolidation
255
+ // cycle in probation. Dedup + quality second-pass runs before promotion.
256
+ // Default OFF. Only meaningful on the `extract` process.
257
+ hotProbation: z
258
+ .object({
259
+ enabled: z.boolean().optional(),
260
+ })
261
+ .strict()
262
+ .optional(),
263
+ // WS-3b: Anti-collapse guards (step 8). Prevents the consolidation pipeline
264
+ // from collapsing too aggressively and losing diversity.
265
+ // - maxGeneration: refuse to merge two assets both above this generation (default 2).
266
+ // - lexicalDiversityCheck: low n-gram diversity ⇒ raise merge threshold.
267
+ // - randomClusterFraction: occasional random (non-similar) cluster in pool (default 0.05).
268
+ // Default OFF. Only meaningful on the `consolidate` process.
269
+ antiCollapse: z
270
+ .object({
271
+ enabled: z.boolean().optional(),
272
+ maxGeneration: z.number().int().min(1).optional(),
273
+ lexicalDiversityCheck: z.boolean().optional(),
274
+ randomClusterFraction: z.number().min(0).max(1).optional(),
275
+ })
276
+ .strict()
277
+ .optional(),
278
+ // WS-3b: CLS (Complementary Learning System) interleaving (step 9).
279
+ // distill/memoryInference prompts include embedding-retrieved existing adjacent
280
+ // lessons/knowledge to prevent catastrophic interference with prior generalizations.
281
+ // Default OFF. Only meaningful on `distill` and `memoryInference` processes.
282
+ cls: z
283
+ .object({
284
+ enabled: z.boolean().optional(),
285
+ // Number of adjacent lessons/knowledge to include in prompts (default 3).
286
+ adjacentCount: z.number().int().min(1).optional(),
287
+ })
288
+ .strict()
289
+ .optional(),
290
+ // WS-3b: Distill→source fidelity check (step 10). After a distill proposal,
291
+ // check it against its cited source memories; a contradiction flag forces
292
+ // human review. Default OFF. Only meaningful on `distill` process.
293
+ fidelityCheck: z
294
+ .object({
295
+ enabled: z.boolean().optional(),
296
+ })
297
+ .strict()
298
+ .optional(),
299
+ // #609 — recombine process: minimum related-memory cluster size before an
300
+ // LLM generalization call. Default 3. Only meaningful on `recombine`.
301
+ minClusterSize: z.number().int().min(2).optional(),
302
+ // #609 — recombine process: hard cap on clusters processed per run (one
303
+ // bounded LLM call each). Default 5. Only meaningful on `recombine`.
304
+ maxClustersPerRun: positiveInt.optional(),
305
+ // #632 — recombine process: max members a cluster may contain before it is
306
+ // SKIPPED (drops bland over-broad buckets). When set, largest-first ranking
307
+ // no longer starves tighter clusters. Default UNSET = no cap. Only
308
+ // meaningful on `recombine`.
309
+ maxClusterSize: positiveInt.optional(),
310
+ // #632 — recombine process: tag values that must never form a tag cluster
311
+ // (generic project-wide tags). Default UNSET/[]. Only meaningful on
312
+ // `recombine`.
313
+ excludeTags: z.array(z.string().min(1)).optional(),
314
+ // #609 — recombine process: relatedness signal used to form clusters
315
+ // (tags | graph | both). Clustering is by relatedness, never embedding
316
+ // similarity. Default "tags". Only meaningful on `recombine`.
317
+ relatednessSource: z.enum(["tags", "graph", "both"]).optional(),
318
+ // #609 — recombine process: consecutive re-inductions required before a
319
+ // hypothesis is promoted to a lesson. Default 2. Only meaningful on
320
+ // `recombine`.
321
+ confirmThreshold: z.number().int().min(1).optional(),
322
+ // #615 — procedural process: minimum number of distinct assets sharing the
323
+ // same successful normalized ordered-action sequence before it is compiled
324
+ // into a workflow proposal. Default 3. Only meaningful on `procedural`.
325
+ minRecurrence: z.number().int().min(2).optional(),
326
+ // #615 — procedural process: hard cap on workflow proposals emitted per run
327
+ // (one bounded LLM call each). Default 3. Only meaningful on `procedural`.
328
+ maxProposalsPerRun: positiveInt.optional(),
329
+ // #615 — procedural process: asset type a compiled sequence is emitted as.
330
+ // Reserved; v1 always emits "workflow". Only meaningful on `procedural`.
331
+ emitAs: z.enum(["workflow", "skill"]).optional(),
159
332
  // Triage process config (only meaningful for the `triage` process)
160
333
  applyMode: z.enum(["queue", "promote"]).optional(),
161
334
  policy: z.string().min(1).optional(),
@@ -181,6 +354,9 @@ const ImproveProfileProcessesSchema = z
181
354
  graphExtraction: ImproveProcessConfigSchema.optional(),
182
355
  validation: ImproveProcessConfigSchema.optional(),
183
356
  triage: ImproveProcessConfigSchema.optional(),
357
+ proactiveMaintenance: ImproveProcessConfigSchema.optional(),
358
+ recombine: ImproveProcessConfigSchema.optional(),
359
+ procedural: ImproveProcessConfigSchema.optional(),
184
360
  })
185
361
  .passthrough()
186
362
  .superRefine((val, ctx) => {
@@ -204,6 +380,9 @@ const ImproveProfileProcessesSchema = z
204
380
  "validation",
205
381
  "extract",
206
382
  "triage",
383
+ "proactiveMaintenance",
384
+ "recombine",
385
+ "procedural",
207
386
  ]);
208
387
  for (const k of Object.keys(raw)) {
209
388
  if (!allowed.has(k)) {
@@ -221,6 +400,16 @@ export const ImproveProfileConfigSchema = z
221
400
  processes: ImproveProfileProcessesSchema.optional(),
222
401
  autoAccept: nonNegativeNumber.optional(),
223
402
  limit: positiveInt.optional(),
403
+ // #616 — bounded multi-cycle phasing. Number of prep->loop->post-loop
404
+ // cycles per run. positiveInt forbids 0/negative. DEFAULT 1 => byte-identical
405
+ // single-pass behavior.
406
+ maxCycles: positiveInt.optional(),
407
+ // #614 — symmetric valence weighting in the eligibility sort. When true,
408
+ // the attention term becomes |valence| MAGNITUDE so BOTH strong positive
409
+ // and strong negative feedback drive attention (utility stays dominant) and
410
+ // strong-signed assets are routed to a fix/reinforce lane. DEFAULT OFF —
411
+ // false/absent preserves the legacy negative-only ranking byte-for-byte.
412
+ symmetricValence: z.boolean().optional(),
224
413
  sync: z
225
414
  .object({
226
415
  enabled: z.boolean().optional(),
@@ -337,6 +526,7 @@ const SearchGraphBoostSchema = z
337
526
  export const SearchConfigSchema = z
338
527
  .object({
339
528
  minScore: nonNegativeNumber.optional(),
529
+ defaultExcludeTypes: z.array(nonEmptyString).optional(),
340
530
  curateRerank: z.object({ enabled: z.boolean().optional() }).strict().optional(),
341
531
  graphBoost: SearchGraphBoostSchema.optional(),
342
532
  })
@@ -355,10 +545,74 @@ const ImproveUtilityDecaySchema = z
355
545
  feedbackStabilityBoost: z.number().finite().min(1).optional(),
356
546
  })
357
547
  .strict();
548
+ // #612 / WS-4 — auto-accept gate calibration + bounded, opt-in per-phase
549
+ // threshold auto-tune. DEFAULT OFF: when absent (or `autoTune: false`) no
550
+ // tuning occurs, so the gate behaves byte-identically to today.
551
+ // WS-4 adds: per-phase persistence (state.db) + auto-tune ceiling default 85.
552
+ const ImproveCalibrationSchema = z
553
+ .object({
554
+ /** Master switch for the bounded threshold auto-tune. Default false (parity). */
555
+ autoTune: z.boolean().optional(),
556
+ /** Lower bound (0-100) the tuned threshold may never drop below. */
557
+ minThreshold: z.number().int().min(0).max(100).optional(),
558
+ /**
559
+ * Upper bound (0-100) the tuned threshold may never rise above.
560
+ * WS-4 default: 85 (prevents gate converging to pure exploitation).
561
+ */
562
+ maxThreshold: z.number().int().min(0).max(100).optional(),
563
+ /** Maximum adjustment magnitude (points) applied in one tune step. */
564
+ maxStep: positiveInt.optional(),
565
+ /** Minimum acted-on sample count required before any adjustment. */
566
+ minSamples: nonNegativeNumber.optional(),
567
+ /** Target realized accept rate in [0, 1]. Default 0.9. */
568
+ targetAcceptRate: z.number().finite().min(0).max(1).optional(),
569
+ })
570
+ .strict();
571
+ // WS-4 — exploration budget: a fixed fraction of proposals accepted per run
572
+ // regardless of confidence. DEFAULT OFF.
573
+ const ImproveExplorationSchema = z
574
+ .object({
575
+ /**
576
+ * Enable the exploration budget lane. Default false (parity).
577
+ * When true, a fraction of proposals are accepted regardless of confidence.
578
+ */
579
+ enabled: z.boolean().optional(),
580
+ /**
581
+ * Fraction of proposals per run to accept as exploration [0, 1].
582
+ * Default 0.05 (5%). Clamped to [0, 1] at read time.
583
+ */
584
+ budgetFraction: z.number().finite().min(0).max(1).optional(),
585
+ })
586
+ .strict();
587
+ const ImproveSalienceSchema = z
588
+ .object({
589
+ /**
590
+ * WS-2 Part-V gate: enable the outcome-weight term in the salience projection.
591
+ * Default false (parity — WS-1 weights w_e=0.30, w_r=0.70 until Part-V confirms
592
+ * no regression). Set to true after running scripts/akm-eval + health report.
593
+ */
594
+ outcomeWeightEnabled: z.boolean().optional(),
595
+ /**
596
+ * Minimum encoding salience score [0, 1] for a zero-feedback asset to be
597
+ * admitted to the high-salience improve lane (#608).
598
+ * Default 0.75. Set to 1.0 to disable the lane entirely.
599
+ */
600
+ salienceThreshold: z.number().min(0).max(1).optional(),
601
+ /**
602
+ * Per-run additive replay budget (#610). Up to this many top-salience refs are
603
+ * revisited even with no reactive signal and regardless of cooldown. Additive
604
+ * on top of --limit. Default 0 = no replay.
605
+ */
606
+ replayBudget: z.number().int().min(0).optional(),
607
+ })
608
+ .strict();
358
609
  export const ImproveConfigSchema = z
359
610
  .object({
360
611
  utilityDecay: ImproveUtilityDecaySchema.optional(),
361
612
  eventRetentionDays: nonNegativeNumber.optional(),
613
+ calibration: ImproveCalibrationSchema.optional(),
614
+ exploration: ImproveExplorationSchema.optional(),
615
+ salience: ImproveSalienceSchema.optional(),
362
616
  })
363
617
  .strict();
364
618
  // ── Index / per-pass ────────────────────────────────────────────────────────
@@ -372,6 +626,7 @@ const GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED = [
372
626
  "lesson",
373
627
  "task",
374
628
  "wiki",
629
+ "fact",
375
630
  ];
376
631
  const INDEX_PASS_PROVIDER_KEYS = new Set([
377
632
  "endpoint",
@@ -388,6 +643,7 @@ const INDEX_PASS_KNOWN_KEYS = new Set([
388
643
  "graphExtractionBatchSize",
389
644
  "graphExtractionIncludeTypes",
390
645
  "memoryInferenceBatchSize",
646
+ "lazyGraphExtraction",
391
647
  ]);
392
648
  /**
393
649
  * Per-pass `index.<pass>` entry. Uses preprocess + manual validation so we can
@@ -414,8 +670,8 @@ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
414
670
  ctx.addIssue({
415
671
  code: z.ZodIssueCode.custom,
416
672
  message: `Unknown key \`${[...(ctx.path ?? []), key].join(".")}\`. Per-pass entries support \`llm\` ` +
417
- "(boolean opt-out), `graphExtractionBatchSize`, `graphExtractionIncludeTypes`, and " +
418
- "`memoryInferenceBatchSize`.",
673
+ "(boolean opt-out), `graphExtractionBatchSize`, `graphExtractionIncludeTypes`, " +
674
+ "`memoryInferenceBatchSize`, and `lazyGraphExtraction`.",
419
675
  });
420
676
  return raw;
421
677
  }
@@ -434,6 +690,7 @@ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
434
690
  graphExtractionBatchSize: positiveInt.optional(),
435
691
  graphExtractionIncludeTypes: z.array(z.enum(GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED)).nonempty().optional(),
436
692
  memoryInferenceBatchSize: positiveInt.optional(),
693
+ lazyGraphExtraction: z.boolean().optional(),
437
694
  })
438
695
  .passthrough());
439
696
  const MetadataEnhanceSchema = z.object({ enabled: z.boolean().optional() }).strict();
@@ -249,8 +249,8 @@ function maybeAutoMigrateConfigFile(configPath, text) {
249
249
  " to preview a dry-run diff: akm config migrate --dry-run --print-diff",
250
250
  "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
251
251
  ].join("\n");
252
- process.stderr.write(`${banner}\n`);
253
- process.stdout.write(`${banner}\n`);
252
+ process.stderr?.write?.(`${banner}\n`);
253
+ process.stdout?.write?.(`${banner}\n`);
254
254
  }
255
255
  catch (err) {
256
256
  // #461: never return migrated bytes when disk write fails — that triggers
@@ -39,6 +39,7 @@ import fs from "node:fs";
39
39
  import path from "node:path";
40
40
  import { openDatabase } from "../storage/database.js";
41
41
  import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
42
+ import { applyStandardPragmas } from "../storage/sqlite-pragmas.js";
42
43
  import { getDataDir } from "./paths.js";
43
44
  import { getStateDbPath } from "./state-db.js";
44
45
  // ── Path helper ──────────────────────────────────────────────────────────────
@@ -75,9 +76,9 @@ export function openLogsDatabase(dbPath) {
75
76
  fs.mkdirSync(dir, { recursive: true });
76
77
  }
77
78
  const db = openDatabase(resolvedPath);
78
- // PRAGMAs must run before any DDL or DML.
79
- db.exec("PRAGMA journal_mode = WAL");
80
- db.exec("PRAGMA busy_timeout = 30000");
79
+ // PRAGMAs must run before any DDL or DML. foreignKeys:false preserves this
80
+ // opener's historical behaviour — logs.db has never enforced foreign keys.
81
+ applyStandardPragmas(db, { dataDir: dir, foreignKeys: false });
81
82
  runMigrations(db);
82
83
  return db;
83
84
  }
@@ -215,6 +215,9 @@ export function getDataDir(env = process.env, platform = process.platform) {
215
215
  export function getDbPath() {
216
216
  return path.join(getDataDir(), "index.db");
217
217
  }
218
+ export function getIndexWriterLockPath() {
219
+ return path.join(getDataDir(), "index.db.write.lock");
220
+ }
218
221
  export function getWorkflowDbPath() {
219
222
  return path.join(getDataDir(), "workflow.db");
220
223
  }