akm-cli 0.9.0-rc.0 → 0.9.0-rc.2

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 (262) hide show
  1. package/CHANGELOG.md +339 -2
  2. package/SECURITY.md +23 -24
  3. package/dist/assets/help/help-improve.md +10 -10
  4. package/dist/assets/hints/cli-hints-full.md +44 -10
  5. package/dist/assets/hints/cli-hints-short.md +6 -2
  6. package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
  7. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
  8. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
  9. package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
  10. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  11. package/dist/assets/stash-skeleton/README.md +28 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
  24. package/dist/assets/tasks/core/backup.yml +1 -0
  25. package/dist/assets/tasks/core/extract.yml +1 -0
  26. package/dist/assets/tasks/core/improve.yml +1 -0
  27. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  28. package/dist/assets/tasks/core/sync.yml +1 -0
  29. package/dist/assets/tasks/core/version-check.yml +1 -0
  30. package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
  31. package/dist/assets/templates/html/health.html +5 -1
  32. package/dist/cli/config-migrate.js +31 -138
  33. package/dist/cli/config-validate.js +10 -8
  34. package/dist/cli.js +48 -14
  35. package/dist/commands/agent/agent-dispatch.js +17 -16
  36. package/dist/commands/agent/agent-support.js +0 -24
  37. package/dist/commands/agent/contribute-cli.js +5 -15
  38. package/dist/commands/backup-cli.js +54 -0
  39. package/dist/commands/config-cli.js +45 -159
  40. package/dist/commands/env/env-binding.js +95 -0
  41. package/dist/commands/env/env-cli.js +8 -65
  42. package/dist/commands/env/secret.js +8 -5
  43. package/dist/commands/health/checks.js +130 -83
  44. package/dist/commands/health/html-report.js +4 -0
  45. package/dist/commands/health/improve-metrics.js +30 -32
  46. package/dist/commands/health/llm-usage.js +19 -19
  47. package/dist/commands/health/md-report.js +4 -0
  48. package/dist/commands/health/metrics.js +2 -1
  49. package/dist/commands/health/surfaces.js +5 -4
  50. package/dist/commands/health.js +1 -1
  51. package/dist/commands/improve/consolidate/chunking.js +2 -2
  52. package/dist/commands/improve/consolidate.js +28 -25
  53. package/dist/commands/improve/distill/promote-memory.js +5 -12
  54. package/dist/commands/improve/distill/quality-gate.js +5 -7
  55. package/dist/commands/improve/distill.js +16 -5
  56. package/dist/commands/improve/eligibility.js +22 -12
  57. package/dist/commands/improve/extract-cli.js +47 -19
  58. package/dist/commands/improve/extract.js +110 -62
  59. package/dist/commands/improve/improve-cli.js +38 -16
  60. package/dist/commands/improve/improve-result-file.js +30 -24
  61. package/dist/commands/improve/improve-strategies.js +137 -0
  62. package/dist/commands/improve/improve.js +60 -30
  63. package/dist/commands/improve/locks.js +66 -45
  64. package/dist/commands/improve/loop-stages.js +75 -33
  65. package/dist/commands/improve/memory/memory-belief.js +79 -7
  66. package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
  67. package/dist/commands/improve/preparation.js +71 -73
  68. package/dist/commands/improve/procedural.js +3 -2
  69. package/dist/commands/improve/recombine.js +2 -1
  70. package/dist/commands/improve/reflect.js +119 -214
  71. package/dist/commands/improve/shared.js +11 -5
  72. package/dist/commands/lint/base-linter.js +152 -42
  73. package/dist/commands/mv-cli.js +809 -0
  74. package/dist/commands/proposal/proposal-cli.js +18 -8
  75. package/dist/commands/proposal/propose.js +64 -69
  76. package/dist/commands/read/knowledge.js +436 -4
  77. package/dist/commands/read/remember-cli.js +39 -2
  78. package/dist/commands/read/search-cli.js +6 -1
  79. package/dist/commands/registry-cli.js +29 -14
  80. package/dist/commands/remember.js +2 -0
  81. package/dist/commands/sources/init.js +13 -14
  82. package/dist/commands/sources/migration-help.js +7 -4
  83. package/dist/commands/sources/schema-repair.js +2 -4
  84. package/dist/commands/sources/source-add.js +62 -73
  85. package/dist/commands/sources/source-manage.js +50 -46
  86. package/dist/commands/sources/stash-cli.js +41 -4
  87. package/dist/commands/tasks/default-tasks.js +12 -12
  88. package/dist/commands/tasks/tasks-cli.js +7 -3
  89. package/dist/commands/tasks/tasks.js +113 -18
  90. package/dist/commands/wiki-cli.js +9 -10
  91. package/dist/commands/workflow-cli.js +276 -12
  92. package/dist/core/asset/asset-spec.js +58 -1
  93. package/dist/core/asset/frontmatter.js +12 -2
  94. package/dist/core/common.js +5 -3
  95. package/dist/core/config/config-io.js +28 -17
  96. package/dist/core/config/config-schema.js +379 -66
  97. package/dist/core/config/config-types.js +3 -3
  98. package/dist/core/config/config-version.js +29 -0
  99. package/dist/core/config/config-walker.js +98 -27
  100. package/dist/core/config/config.js +132 -266
  101. package/dist/core/config/deep-merge.js +41 -0
  102. package/dist/core/config/engine-semantics.js +32 -0
  103. package/dist/core/errors.js +2 -2
  104. package/dist/core/extra-params.js +61 -0
  105. package/dist/core/file-lock.js +201 -56
  106. package/dist/core/improve-result.js +178 -0
  107. package/dist/core/json-schema.js +142 -0
  108. package/dist/core/maintenance-barrier.js +119 -0
  109. package/dist/core/migration-backup.js +416 -0
  110. package/dist/core/paths.js +3 -0
  111. package/dist/core/redaction.js +358 -0
  112. package/dist/core/state/migrations.js +17 -2
  113. package/dist/core/state-db.js +44 -1
  114. package/dist/indexer/db/db.js +118 -2
  115. package/dist/indexer/graph/graph-extraction.js +28 -16
  116. package/dist/indexer/index-writer-lock.js +31 -24
  117. package/dist/indexer/index-written-assets.js +15 -6
  118. package/dist/indexer/indexer.js +47 -2
  119. package/dist/indexer/passes/memory-inference.js +10 -6
  120. package/dist/indexer/passes/metadata.js +250 -0
  121. package/dist/indexer/search/db-search.js +111 -44
  122. package/dist/indexer/search/fts-query.js +41 -0
  123. package/dist/indexer/search/ranking-contributors.js +48 -0
  124. package/dist/indexer/search/ranking.js +36 -23
  125. package/dist/indexer/search/search-fields.js +11 -1
  126. package/dist/indexer/walk/matchers.js +39 -0
  127. package/dist/integrations/agent/builder-shared.js +7 -0
  128. package/dist/integrations/agent/builders.js +5 -50
  129. package/dist/integrations/agent/config.js +3 -143
  130. package/dist/integrations/agent/detect.js +17 -2
  131. package/dist/integrations/agent/engine-resolution.js +202 -0
  132. package/dist/integrations/agent/index.js +1 -2
  133. package/dist/integrations/agent/model-aliases.js +16 -2
  134. package/dist/integrations/agent/profiles.js +36 -62
  135. package/dist/integrations/agent/runner-dispatch.js +91 -4
  136. package/dist/integrations/agent/runner.js +76 -207
  137. package/dist/integrations/agent/spawn.js +141 -20
  138. package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
  139. package/dist/integrations/harnesses/aider/index.js +57 -0
  140. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  141. package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
  142. package/dist/integrations/harnesses/amazonq/index.js +58 -0
  143. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  144. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  145. package/dist/integrations/harnesses/claude/index.js +25 -25
  146. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  147. package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
  148. package/dist/integrations/harnesses/codex/index.js +62 -0
  149. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  150. package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
  151. package/dist/integrations/harnesses/copilot/index.js +59 -0
  152. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  153. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  154. package/dist/integrations/harnesses/gemini/index.js +59 -0
  155. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  156. package/dist/integrations/harnesses/index.js +27 -28
  157. package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
  158. package/dist/integrations/harnesses/opencode/index.js +15 -22
  159. package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
  160. package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
  161. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  162. package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
  163. package/dist/integrations/harnesses/openhands/index.js +57 -0
  164. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  165. package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
  166. package/dist/integrations/harnesses/pi/index.js +57 -0
  167. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  168. package/dist/integrations/harnesses/types.js +8 -32
  169. package/dist/integrations/lockfile.js +32 -21
  170. package/dist/integrations/session-logs/index.js +24 -11
  171. package/dist/llm/client.js +48 -14
  172. package/dist/llm/feature-gate.js +15 -47
  173. package/dist/llm/graph-extract.js +1 -1
  174. package/dist/llm/index-passes.js +8 -42
  175. package/dist/llm/memory-infer-impl.js +1 -1
  176. package/dist/llm/usage-persist.js +4 -0
  177. package/dist/llm/usage-telemetry.js +35 -5
  178. package/dist/output/renderers.js +3 -2
  179. package/dist/output/shapes/helpers.js +2 -1
  180. package/dist/output/shapes/passthrough.js +6 -0
  181. package/dist/output/text/helpers.js +215 -2
  182. package/dist/output/text/workflow.js +3 -1
  183. package/dist/schemas/akm-config.json +16638 -0
  184. package/dist/schemas/akm-task.json +87 -0
  185. package/dist/schemas/akm-workflow.json +372 -0
  186. package/dist/scripts/migrate-storage.js +10944 -8801
  187. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
  188. package/dist/setup/detected-engines.js +142 -0
  189. package/dist/setup/engine-config.js +89 -0
  190. package/dist/setup/setup.js +236 -132
  191. package/dist/setup/steps/connection.js +61 -32
  192. package/dist/setup/steps/platforms.js +4 -4
  193. package/dist/setup/steps.js +3 -2
  194. package/dist/storage/database.js +13 -1
  195. package/dist/storage/engines/sqlite-migrations.js +1 -0
  196. package/dist/storage/repositories/improve-runs-repository.js +5 -5
  197. package/dist/storage/repositories/task-history-repository.js +78 -0
  198. package/dist/storage/repositories/workflow-runs-repository.js +190 -1
  199. package/dist/tasks/parser.js +138 -52
  200. package/dist/tasks/runner.js +71 -75
  201. package/dist/tasks/schema.js +1 -1
  202. package/dist/tasks/validator.js +11 -6
  203. package/dist/text-import-hook.mjs +1 -1
  204. package/dist/wiki/wiki.js +9 -8
  205. package/dist/workflows/authoring/authoring.js +123 -10
  206. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  207. package/dist/workflows/cli.js +4 -0
  208. package/dist/workflows/concurrency-policy.js +15 -0
  209. package/dist/workflows/db.js +200 -13
  210. package/dist/workflows/exec/brief.js +478 -0
  211. package/dist/workflows/exec/frozen-judge.js +47 -0
  212. package/dist/workflows/exec/native-executor.js +1034 -0
  213. package/dist/workflows/exec/param-secrets.js +115 -0
  214. package/dist/workflows/exec/report.js +1355 -0
  215. package/dist/workflows/exec/run-workflow.js +609 -0
  216. package/dist/workflows/exec/scheduler.js +71 -0
  217. package/dist/workflows/exec/step-work.js +1212 -0
  218. package/dist/workflows/exec/unit-writer.js +23 -0
  219. package/dist/workflows/exec/watch.js +116 -0
  220. package/dist/workflows/exec/worktree.js +171 -0
  221. package/dist/workflows/ir/compile.js +375 -0
  222. package/dist/workflows/ir/freeze.js +243 -0
  223. package/dist/workflows/ir/params.js +54 -0
  224. package/dist/workflows/ir/plan-hash.js +68 -0
  225. package/dist/workflows/ir/schema.js +545 -0
  226. package/dist/workflows/parser.js +10 -1
  227. package/dist/workflows/program/expressions.js +369 -0
  228. package/dist/workflows/program/parser.js +869 -0
  229. package/dist/workflows/program/project.js +104 -0
  230. package/dist/workflows/program/schema.js +54 -0
  231. package/dist/workflows/renderer.js +82 -5
  232. package/dist/workflows/resource-limits.js +20 -0
  233. package/dist/workflows/runtime/agent-identity.js +59 -14
  234. package/dist/workflows/runtime/plan-classifier.js +187 -0
  235. package/dist/workflows/runtime/runs.js +246 -69
  236. package/dist/workflows/runtime/unit-checkin.js +45 -0
  237. package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
  238. package/dist/workflows/validate-summary.js +24 -3
  239. package/dist/workflows/validator.js +26 -1
  240. package/docs/data-and-telemetry.md +4 -3
  241. package/docs/migration/release-notes/0.6.0.md +1 -1
  242. package/docs/migration/release-notes/0.7.0.md +5 -4
  243. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  244. package/docs/migration/v0.8-to-v0.9.md +401 -0
  245. package/package.json +4 -2
  246. package/schemas/akm-config.json +16638 -0
  247. package/schemas/akm-task.json +87 -0
  248. package/schemas/akm-workflow.json +372 -0
  249. package/dist/commands/improve/improve-profiles.js +0 -168
  250. package/dist/core/config/config-migration.js +0 -602
  251. package/dist/core/deep-merge.js +0 -38
  252. package/dist/llm/call-ai.js +0 -62
  253. package/dist/setup/legacy-config.js +0 -106
  254. package/docs/README.md +0 -104
  255. /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
  256. /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
  257. /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
  258. /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
  259. /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
  260. /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
  261. /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
  262. /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
@@ -15,7 +15,7 @@
15
15
  * never touch a real platform.
16
16
  * - Bounded LLM call wrapped by {@link tryLlmFeature} under the
17
17
  * `session_extraction` gate (default-on; opt out via
18
- * `profiles.improve.default.processes.extract.enabled: false`).
18
+ * `improve.strategies.<name>.processes.extract.enabled: false`).
19
19
  * - Proposals routed via `createProposal({ source: "extract", ... })` — the
20
20
  * same review queue as reflect / distill / consolidate. Never direct-write.
21
21
  * - Per-candidate body assembly merges description (+ when_to_use for lessons)
@@ -27,16 +27,18 @@ import fs from "node:fs";
27
27
  import path from "node:path";
28
28
  import { assembleAsset } from "../../core/asset/asset-serialize.js";
29
29
  import { resolveStashDir, timestampForFilename } from "../../core/common.js";
30
- import { getDefaultLlmConfig, getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
30
+ import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
31
31
  import { ConfigError, UsageError } from "../../core/errors.js";
32
32
  import { appendEvent } from "../../core/events.js";
33
- import { probeLock, releaseLock, tryAcquireLockSync } from "../../core/file-lock.js";
33
+ import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
34
+ import { tryAcquireMaintenanceBarrier } from "../../core/maintenance-barrier.js";
34
35
  import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
35
36
  import { getStateDbPath, openStateDatabase, withStateDb } from "../../core/state-db.js";
36
37
  import { repairTruncatedDescription } from "../../core/text-truncation.js";
37
38
  import { warn } from "../../core/warn.js";
38
39
  import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
39
- import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
40
+ import { resolveLlmEngineUse } from "../../integrations/agent/engine-resolution.js";
41
+ import { materializeLlmRunnerConnection, resolveImproveProcessRunner, } from "../../integrations/agent/runner.js";
40
42
  import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
41
43
  import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
42
44
  import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
@@ -48,7 +50,7 @@ import { getExtractedSessionsMap, getLastExtractRunAt, shouldSkipAlreadyExtracte
48
50
  import { createProposal, isProposalSkipped } from "../proposal/repository.js";
49
51
  import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
50
52
  import { buildHotProbationFrontmatter } from "./hot-probation.js";
51
- import { resolveProcessEnabled } from "./improve-profiles.js";
53
+ import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
52
54
  import { applySchemaSimilarityPenalty, loadDerivedLayerEmbeddings, } from "./schema-similarity-gate.js";
53
55
  import { buildSessionSummaryPrompt, parseSessionSummary, SESSION_SUMMARY_JSON_SCHEMA, sessionMeetsDurationGate, writeSessionAsset, } from "./session-asset.js";
54
56
  import { resolveTriageConfig, scoreSessionTriage } from "./triage.js";
@@ -123,20 +125,75 @@ function getExtractSessionLockPath(harness, sessionId, stateDbPath) {
123
125
  * extraction outright.
124
126
  */
125
127
  function acquireExtractSessionLock(lockPath) {
128
+ const releaseBarrier = tryAcquireMaintenanceBarrier();
129
+ if (!releaseBarrier)
130
+ return { proceed: false };
126
131
  try {
127
132
  fs.mkdirSync(path.dirname(lockPath), { recursive: true });
128
- if (tryAcquireLockSync(lockPath, String(process.pid)))
129
- return true;
133
+ let ownership = tryAcquireLockSync(lockPath, createLockPayload());
134
+ if (ownership)
135
+ return { proceed: true, ownership };
130
136
  const probe = probeLock(lockPath, { staleAfterMs: EXTRACT_SESSION_LOCK_STALE_MS });
131
137
  if (probe.state === "held")
132
- return false;
133
- // absent (released between attempt + probe) or stale → reclaim and retry once.
134
- releaseLock(lockPath);
135
- return tryAcquireLockSync(lockPath, String(process.pid));
138
+ return { proceed: false };
139
+ // Absent (released between attempt + probe) or successfully reclaimed stale lock → retry once.
140
+ if (probe.state === "stale" && !reclaimStaleLock(lockPath, probe))
141
+ return { proceed: false };
142
+ ownership = tryAcquireLockSync(lockPath, createLockPayload());
143
+ return ownership ? { proceed: true, ownership } : { proceed: false };
136
144
  }
137
145
  catch {
138
- return true;
146
+ return { proceed: true };
139
147
  }
148
+ finally {
149
+ releaseBarrier();
150
+ }
151
+ }
152
+ function cloneAndFreeze(value) {
153
+ const clone = structuredClone(value);
154
+ const freeze = (item) => {
155
+ if (typeof item !== "object" || item === null || Object.isFrozen(item))
156
+ return;
157
+ for (const child of Object.values(item))
158
+ freeze(child);
159
+ Object.freeze(item);
160
+ };
161
+ freeze(clone);
162
+ return clone;
163
+ }
164
+ /** Resolve standalone extract selection once before discovery, auto iteration, or watch startup. */
165
+ export function resolveStandaloneExtractPlan(config, selection) {
166
+ if (selection.engine && selection.strategy) {
167
+ throw new UsageError("--engine and --strategy are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE");
168
+ }
169
+ const selected = resolveImproveStrategy(selection.strategy, config);
170
+ const process = cloneAndFreeze(getImproveProcessConfig(config, "extract", selected.config) ?? {});
171
+ const invocation = {
172
+ ...(selection.engine ? { engine: selection.engine } : {}),
173
+ ...(Object.hasOwn(selection, "timeoutMs") ? { timeoutMs: selection.timeoutMs ?? null } : {}),
174
+ };
175
+ const resolved = resolveLlmEngineUse(config, [selected.config, process, invocation], { optional: true });
176
+ if (!resolved) {
177
+ throw new ConfigError("No LLM engine configured for extract. Set defaults.llmEngine, pass --engine, or select an improve strategy with processes.extract.engine.", "LLM_NOT_CONFIGURED");
178
+ }
179
+ const runner = {
180
+ kind: "llm",
181
+ engine: resolved.engine,
182
+ connection: resolved.connection,
183
+ ...(resolved.credential ? { credential: resolved.credential } : {}),
184
+ timeoutMs: resolved.timeoutMs,
185
+ };
186
+ return Object.freeze({
187
+ strategy: selected.name,
188
+ engine: resolved.engine,
189
+ // `akm extract` is an explicit operation. The strategy supplies behavior,
190
+ // but its improve-stage enablement gate does not disable this command.
191
+ enabled: true,
192
+ process,
193
+ runner: cloneAndFreeze(runner),
194
+ timeoutMs: resolved.timeoutMs,
195
+ embeddingConfig: cloneAndFreeze(config.embedding),
196
+ });
140
197
  }
141
198
  // ── Helpers ──────────────────────────────────────────────────────────────────
142
199
  /**
@@ -245,11 +302,11 @@ export function hashSessionContent(data) {
245
302
  * proposal validation failure) the session result records a warning and
246
303
  * keeps going — one session's bad luck never aborts a multi-session run.
247
304
  */
248
- async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars,
305
+ async function processSession(harness, sessionRef, stashDir, config, getLlmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars,
249
306
  // #626 — pre-LLM heuristic triage gate. Default-off (enabled:false) takes the
250
307
  // exact pre-change path (no scorer call, no new skipReason).
251
308
  // #641 — proceduralAwareFloor is opt-in, DEFAULT OFF.
252
- triage, sessionIndexing, schemaSimilarityCtx,
309
+ triage, sessionIndexing, schemaSimilarityCtx, hotProbationEnabled,
253
310
  // #602 — already-extracted skip moved INSIDE processSession: the content hash
254
311
  // can only be computed after readSession, so the skip decision lives here. The
255
312
  // prior row + bypass flags are threaded in from the caller. Skipping here still
@@ -390,7 +447,7 @@ standardsContext) {
390
447
  };
391
448
  let llmRaw = "";
392
449
  const llmResult = await tryLlmFeature("session_extraction", config, async () => {
393
- llmRaw = await chat(llmConfig, [{ role: "user", content: prompt }], {
450
+ llmRaw = await chat(getLlmConfig(), [{ role: "user", content: prompt }], {
394
451
  timeoutMs,
395
452
  responseSchema: EXTRACT_JSON_SCHEMA,
396
453
  });
@@ -451,12 +508,7 @@ standardsContext) {
451
508
  // When enabled, system-generated extractions enter captureMode: hot-probation
452
509
  // so they spend ONE consolidation cycle in probation before the deterministic
453
510
  // dedup+quality pass promotes them. Default OFF.
454
- // Reads the `default` profile deliberately: this per-session hot-probation
455
- // flag lives inside the 18-arg `processSession`, which has no handle to the
456
- // active profile, and threading a 19th positional arg for a DEFAULT-OFF flag
457
- // isn't worth the param bloat. The extract STAGE toggle (in `akmExtract`,
458
- // below) already honors `--profile`.
459
- const hotProbationEnabled = getImproveProcessConfig(config, "extract")?.hotProbation?.enabled === true;
511
+ // The invocation boundary resolves this from the frozen extract process.
460
512
  for (const candidate of payload.candidates) {
461
513
  if (dryRun) {
462
514
  proposalIds.push(`dry-run:${candidate.type}:${candidate.name}`);
@@ -555,20 +607,19 @@ export async function akmExtract(options) {
555
607
  const stashDir = options.stashDir ?? resolveStashDir();
556
608
  const dryRun = options.dryRun ?? false;
557
609
  const sourceRun = options.sourceRun ?? `extract-${timestampForFilename()}`;
558
- // Read the per-process extract config + enabled-state from the ACTIVE improve
559
- // profile when `akmImprove` threaded it; otherwise fall back to the `default`
560
- // profile (the explicit `akm extract` path, which has no active profile). This
561
- // is what stops a non-default profile's `extract.enabled` from being silently
562
- // overridden by the default profile and vice-versa.
563
- const activeProfile = options.improveProfile;
564
- const extractProcess = activeProfile ? activeProfile.processes?.extract : getImproveProcessConfig(config, "extract");
610
+ // Read process behavior from the frozen standalone plan or the active improve
611
+ // strategy. This prevents config changes during watch mode from changing later
612
+ // triggers and prevents one improve strategy from overriding another.
613
+ const activeProfile = options.improveProfile ?? (options.resolvedPlan ? undefined : resolveImproveStrategy(undefined, config).config);
614
+ const extractProcess = options.resolvedPlan?.process ?? getImproveProcessConfig(config, "extract", activeProfile);
565
615
  // The `extract.enabled` process toggle gates extract as a STAGE of `akm improve`
566
616
  // (the activeProfile path) — consistent with #593/#594 where the active profile,
567
617
  // not `default`, is the source of truth. An EXPLICIT `akm extract` invocation
568
618
  // (no activeProfile) is a direct user/cron action and always runs; gating it on
569
619
  // the default improve profile's stage toggle was a footgun — dropping extract
570
620
  // from the daily improve profile would silently disable the standalone command.
571
- const extractEnabled = activeProfile ? resolveProcessEnabled("extract", activeProfile) : true;
621
+ const extractEnabled = options.resolvedPlan?.enabled ??
622
+ (options.improveProfile ? resolveProcessEnabled("extract", options.improveProfile) : true);
572
623
  // Feature-gate early so we get a clean "skipped because disabled" envelope.
573
624
  if (!extractEnabled) {
574
625
  return {
@@ -582,35 +633,29 @@ export async function akmExtract(options) {
582
633
  candidatesCreated: 0,
583
634
  proposals: [],
584
635
  sessions: [],
585
- warnings: [
586
- "session_extraction feature disabled — set profiles.improve.default.processes.extract.enabled: true to use",
587
- ],
636
+ warnings: ["extract is disabled by the selected improve strategy"],
588
637
  durationMs: Date.now() - startMs,
589
638
  };
590
639
  }
591
- // Resolve the LLM connection. Priority order:
592
- // 1. Options.config.profiles.improve.default.processes.extract.profile
593
- // (per-process override, matches reflect/distill/consolidate)
594
- // 2. config.defaults.llm (the default LLM profile)
595
- // 3. throw extract requires an LLM.
596
- let llmConfig;
597
- const runnerSpec = resolveImproveProcessRunnerFromProfile(extractProcess, config);
598
- if (runnerSpec) {
599
- if (!runnerIsLlm(runnerSpec)) {
600
- throw new ConfigError(`Extract only supports mode: "llm" (in-tree LLM call). Got mode: "${runnerSpec.kind}" from profiles.improve.default.processes.extract — change it to "llm" or remove the override.`, "INVALID_CONFIG_FILE");
601
- }
602
- llmConfig = runnerSpec.connection;
603
- }
604
- else {
605
- llmConfig = getDefaultLlmConfig(config) ?? undefined;
606
- }
607
- if (!llmConfig) {
608
- throw new ConfigError("No LLM connection configured for extract. Set profiles.llm + defaults.llm, or set profiles.improve.default.processes.extract.profile to a configured LLM profile.");
640
+ // Improve supplies its invocation-owned connection. Standalone extract
641
+ // resolves the selected process engine, then defaults.llmEngine.
642
+ const runnerSpec = options.resolvedPlan
643
+ ? options.resolvedPlan.runner
644
+ : resolveImproveProcessRunner(activeProfile, "extract", config);
645
+ const fixedLlmConfig = options.resolvedPlan ? undefined : options.llmConfig;
646
+ if (!runnerSpec && !fixedLlmConfig) {
647
+ throw new ConfigError("No LLM engine configured for extract. Set defaults.llmEngine or improve.strategies.<name>.processes.extract.engine.", "LLM_NOT_CONFIGURED");
609
648
  }
610
- // Honor per-process timeoutMs override; fall back to options.timeoutMs; then 60s.
611
- const timeoutMs = options.timeoutMs ??
612
- (typeof extractProcess?.timeoutMs === "number" ? extractProcess.timeoutMs : undefined) ??
613
- 60_000;
649
+ const timeoutMs = options.resolvedPlan
650
+ ? options.resolvedPlan.timeoutMs
651
+ : Object.hasOwn(options, "timeoutMs")
652
+ ? (options.timeoutMs ?? null)
653
+ : runnerSpec?.timeoutMs !== undefined
654
+ ? runnerSpec.timeoutMs
655
+ : fixedLlmConfig && Object.hasOwn(fixedLlmConfig, "timeoutMs")
656
+ ? (fixedLlmConfig.timeoutMs ?? null)
657
+ : 600_000;
658
+ const getLlmConfig = () => runnerSpec ? materializeLlmRunnerConnection(runnerSpec) : fixedLlmConfig;
614
659
  // Pre-filter budget — process config can raise it for large-context models.
615
660
  const maxTotalChars = typeof extractProcess?.maxTotalChars === "number" ? extractProcess.maxTotalChars : undefined;
616
661
  // #595/#596 — minimum raw session size; sessions below it skip the LLM call
@@ -643,7 +688,7 @@ export async function akmExtract(options) {
643
688
  const defaultSessionSummaryGenerator = async (data) => {
644
689
  let raw = "";
645
690
  await tryLlmFeature("session_extraction", config, async () => {
646
- raw = await chatForSummary(llmConfig, [{ role: "user", content: buildSessionSummaryPrompt(data) }], {
691
+ raw = await chatForSummary(getLlmConfig(), [{ role: "user", content: buildSessionSummaryPrompt(data) }], {
647
692
  timeoutMs,
648
693
  responseSchema: SESSION_SUMMARY_JSON_SCHEMA,
649
694
  });
@@ -768,13 +813,14 @@ export async function akmExtract(options) {
768
813
  // processes.extract.schemaSimilarity.enabled: false. The gate is inert in
769
814
  // practice when no derived-layer embeddings exist (empty ctx → no penalty).
770
815
  const schemaSimilarityCfg = extractProcess?.schemaSimilarity;
816
+ const hotProbationEnabled = extractProcess?.hotProbation?.enabled === true;
771
817
  let schemaSimilarityCtx = null;
772
818
  if (schemaSimilarityCfg?.enabled !== false) {
773
819
  const derivedEmbeddings = options.schemaSimilarityEmbeddings ?? loadDerivedLayerEmbeddings();
774
820
  schemaSimilarityCtx = {
775
821
  config: { ...schemaSimilarityCfg, enabled: true },
776
822
  derivedEmbeddings,
777
- embeddingConfig: config.embedding,
823
+ embeddingConfig: options.resolvedPlan?.embeddingConfig ?? config.embedding,
778
824
  embedFn: options.schemaSimilarityEmbedFn,
779
825
  };
780
826
  }
@@ -802,10 +848,11 @@ export async function akmExtract(options) {
802
848
  // runs (those that open their own state.db): dry-run is read-only, an
803
849
  // injected `stateDb` handle is an in-process/test scenario with no cross-
804
850
  // process race, and skip-tracking-off opts out entirely.
805
- let sessionLockPath;
851
+ let sessionLockOwnership;
806
852
  if (trackingEnabled && !dryRun && !options.stateDb) {
807
- sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
808
- if (!acquireExtractSessionLock(sessionLockPath)) {
853
+ const sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
854
+ const sessionLock = acquireExtractSessionLock(sessionLockPath);
855
+ if (!sessionLock.proceed) {
809
856
  sessions.push({
810
857
  sessionId: summary.sessionId,
811
858
  harness: harness.name,
@@ -819,9 +866,10 @@ export async function akmExtract(options) {
819
866
  skippedCount += 1;
820
867
  continue;
821
868
  }
869
+ sessionLockOwnership = sessionLock.ownership;
822
870
  }
823
871
  try {
824
- const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, schemaSimilarityCtx, prior, options.force === true, extractStandardsContext);
872
+ const result = await processSession(harness, summary, stashDir, config, getLlmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, schemaSimilarityCtx, hotProbationEnabled, prior, options.force === true, extractStandardsContext);
825
873
  sessions.push(result);
826
874
  // #626 — triage aggregation. A session reached the triage gate only when it
827
875
  // was NOT already preempted by an earlier skip (read_failed / too_short /
@@ -916,8 +964,8 @@ export async function akmExtract(options) {
916
964
  skippedCount += 1;
917
965
  }
918
966
  finally {
919
- if (sessionLockPath)
920
- releaseLock(sessionLockPath);
967
+ if (sessionLockOwnership)
968
+ releaseLock(sessionLockOwnership);
921
969
  }
922
970
  }
923
971
  // Close the state.db connection we opened. Callers that injected stateDb
@@ -8,16 +8,24 @@ import { output, runWithJsonErrors } from "../../cli/shared.js";
8
8
  import { loadConfig } from "../../core/config/config.js";
9
9
  import { UsageError } from "../../core/errors.js";
10
10
  import { getCacheDir } from "../../core/paths.js";
11
+ import { redactSensitiveText } from "../../core/redaction.js";
11
12
  import { withStateDb } from "../../core/state-db.js";
12
13
  import { clearLogFile, setLogFile } from "../../core/warn.js";
13
14
  import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
14
15
  import { resolveSourceEntries } from "../../indexer/search/search-source.js";
16
+ import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
15
17
  import { parseFlagValue } from "../../output/context.js";
16
18
  import { getActiveCanaries, queryRecentCycleMetrics } from "../../storage/repositories/canaries-repository.js";
17
19
  import { refreshCanarySet } from "./collapse-detector.js";
18
20
  import { akmImprove } from "./improve.js";
19
21
  import { buildImproveRunId, recordTerminatedImproveRun, relativeImproveResultPath, writeImproveResultFile, } from "./improve-result-file.js";
20
22
  import { runImproveSession } from "./improve-session.js";
23
+ import { resolveImprovePlan } from "./improve-strategies.js";
24
+ let akmImproveForRun = akmImprove;
25
+ /** Swap the CLI's improve work implementation in deterministic subprocess tests. */
26
+ export function _setAkmImproveForTests(fake) {
27
+ akmImproveForRun = fake ?? akmImprove;
28
+ }
21
29
  // R5 — collapse-detector canary set inspection / explicit refresh. The
22
30
  // detector NEVER auto-refreshes the canary set (silent re-baselining is how a
23
31
  // slow collapse hides); this verb is the only refresh path.
@@ -72,7 +80,7 @@ async function runCanaryInspection(refresh) {
72
80
  export const improveCommand = defineCommand({
73
81
  meta: {
74
82
  name: "improve",
75
- description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when profiles.improve.default.processes.consolidate.enabled is true. `akm improve canary [--refresh]` inspects the collapse-detector canary set.",
83
+ description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when the selected strategy enables consolidate. `akm improve canary [--refresh]` inspects the collapse-detector canary set.",
76
84
  },
77
85
  args: {
78
86
  scope: {
@@ -120,9 +128,9 @@ export const improveCommand = defineCommand({
120
128
  description: "If another improve run already holds the lock, skip gracefully (exit 0) instead of failing with 'already running' (exit 78). Use for high-frequency scheduled runs so they don't pile up failures while a longer run is in progress.",
121
129
  default: false,
122
130
  },
123
- profile: {
131
+ strategy: {
124
132
  type: "string",
125
- description: "Named improve profile from profiles.improve or built-in profiles (default, quick, thorough, memory-focus, graph-refresh). Controls which sub-processes run and which asset types are processed.",
133
+ description: "Named improve strategy from improve.strategies or built-in strategies (default, quick, thorough, memory-focus, graph-refresh). Controls which sub-processes run and which asset types are processed.",
126
134
  },
127
135
  sync: {
128
136
  type: "boolean",
@@ -166,7 +174,13 @@ export const improveCommand = defineCommand({
166
174
  const minRetrievalCount = parseNonNegativeIntFlag(minRetrievalCountRaw, "--min-retrieval-count");
167
175
  const requireFeedbackSignal = args["require-feedback-signal"];
168
176
  const skipIfLocked = args["skip-if-locked"];
169
- const profileArg = getStringArg(args, "profile");
177
+ const strategyArg = getStringArg(args, "strategy");
178
+ const effectiveConfig = loadConfig();
179
+ // Resolve every enabled model-backed process before logging, signal
180
+ // lifecycle setup, or any filesystem/database side effect.
181
+ const resolvedPlan = resolveImprovePlan(strategyArg, effectiveConfig);
182
+ const selectedStrategyName = resolvedPlan.strategy.name;
183
+ const sensitiveValues = collectEngineCredentialValues(effectiveConfig);
170
184
  // Only set the keys the user actually passed (citty leaves the flag
171
185
  // undefined unless `--sync`/`--no-sync` / `--push`/`--no-push` appears),
172
186
  // so the resolved profile `sync` block wins by default.
@@ -177,8 +191,10 @@ export const improveCommand = defineCommand({
177
191
  syncOverride.enabled = syncFlag;
178
192
  if (pushFlag !== undefined)
179
193
  syncOverride.push = pushFlag;
180
- const improveLogFile = path.join(getCacheDir(), "logs", "improve", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
181
- setLogFile(improveLogFile);
194
+ if (!dryRun) {
195
+ const improveLogFile = path.join(getCacheDir(), "logs", "improve", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
196
+ setLogFile(improveLogFile);
197
+ }
182
198
  const startedAtMs = Date.now();
183
199
  const startedAtIso = new Date(startedAtMs).toISOString();
184
200
  // Mint the run-id up front so signal handlers can persist a partial
@@ -186,7 +202,7 @@ export const improveCommand = defineCommand({
186
202
  // was minted at end-of-run, so SIGTERM'd runs (cron timeout) left no
187
203
  // row in improve_runs and effectively disappeared from `akm health`.
188
204
  const runId = buildImproveRunId();
189
- const primaryStashDir = resolveSourceEntries(undefined, loadConfig())[0]?.path;
205
+ const primaryStashDir = resolveSourceEntries(undefined, effectiveConfig)[0]?.path;
190
206
  const scopeArg = getStringArg(args, "scope");
191
207
  const inferredScopeMode = (scopeArg ?? "").includes(":") ? "ref" : scopeArg ? "type" : "all";
192
208
  // Signal handler + exception path both flow through this helper so
@@ -194,6 +210,8 @@ export const improveCommand = defineCommand({
194
210
  // reason in metadata.terminated.
195
211
  let runRecorded = false;
196
212
  const persistTerminated = (reason, errorMessage) => {
213
+ if (dryRun)
214
+ return;
197
215
  if (runRecorded)
198
216
  return;
199
217
  if (!primaryStashDir)
@@ -204,8 +222,9 @@ export const improveCommand = defineCommand({
204
222
  scopeMode: inferredScopeMode,
205
223
  scopeValue: scopeArg ?? null,
206
224
  dryRun: Boolean(dryRun),
207
- profile: profileArg ?? null,
208
- ...(errorMessage ? { errorMessage } : {}),
225
+ strategy: selectedStrategyName,
226
+ ...(errorMessage ? { errorMessage: redactSensitiveText(errorMessage, sensitiveValues) } : {}),
227
+ sensitiveValues,
209
228
  });
210
229
  }
211
230
  catch (err) {
@@ -223,10 +242,11 @@ export const improveCommand = defineCommand({
223
242
  let improveResult;
224
243
  try {
225
244
  improveResult = await runImproveSession({
226
- runWork: () => akmImprove({
245
+ runWork: () => akmImproveForRun({
227
246
  scope: scopeArg,
228
247
  task: taskArg,
229
248
  dryRun,
249
+ resolvedPlan,
230
250
  target: targetArg,
231
251
  autoAccept,
232
252
  ...(runId !== undefined ? { runId } : {}),
@@ -235,7 +255,7 @@ export const improveCommand = defineCommand({
235
255
  ...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
236
256
  ...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
237
257
  ...(skipIfLocked ? { skipIfLocked } : {}),
238
- ...(profileArg !== undefined ? { profile: profileArg } : {}),
258
+ ...(strategyArg !== undefined ? { strategy: strategyArg } : {}),
239
259
  ...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
240
260
  consolidateOptions: {
241
261
  target: targetArg,
@@ -249,7 +269,9 @@ export const improveCommand = defineCommand({
249
269
  signalSource: process,
250
270
  exit: process.exit,
251
271
  onTerminate: (reason) => persistTerminated(reason),
252
- ack: (message) => process.stderr.write(`[improve] ${message}; recorded terminated run ${runId}\n`),
272
+ ack: (message) => process.stderr.write(dryRun
273
+ ? `[improve] ${message}; dry-run state was not persisted\n`
274
+ : `[improve] ${message}; recorded terminated run ${runId}\n`),
253
275
  });
254
276
  }
255
277
  catch (err) {
@@ -264,9 +286,9 @@ export const improveCommand = defineCommand({
264
286
  clearLogFile();
265
287
  }
266
288
  const durationMs = Date.now() - startedAtMs;
267
- if (jsonToStdout) {
268
- // Legacy / escape-hatch mode: full JSON on stdout, no file write.
269
- // Kept for scripts/agents that already pipe to jq.
289
+ if (dryRun || jsonToStdout) {
290
+ // A dry-run never persists its result, so stdout is its only result
291
+ // channel. --json-to-stdout remains the live-run escape hatch.
270
292
  output("improve", improveResult);
271
293
  process.exit(0);
272
294
  }
@@ -288,7 +310,7 @@ export const improveCommand = defineCommand({
288
310
  runRecorded = true; // Suppress any late signal-handler write — the success path owns the row now.
289
311
  if (primaryStashDir) {
290
312
  try {
291
- writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso, profileArg ?? null);
313
+ writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso, sensitiveValues);
292
314
  }
293
315
  catch (err) {
294
316
  // Stderr warning on the failure path is preferable to crashing
@@ -27,6 +27,8 @@
27
27
  */
28
28
  import crypto from "node:crypto";
29
29
  import path from "node:path";
30
+ import { decodeImproveResult } from "../../core/improve-result.js";
31
+ import { redactSensitiveValue } from "../../core/redaction.js";
30
32
  import { withStateDb } from "../../core/state-db.js";
31
33
  import { recordImproveRun } from "../../storage/repositories/improve-runs-repository.js";
32
34
  /**
@@ -74,14 +76,10 @@ export function relativeImproveResultPath(runId) {
74
76
  * (closes the dry-run/real-run artifact-trap recorded in MEMORY.md
75
77
  * `feedback_akm_dryrun_artifact_trap`).
76
78
  *
77
- * @param profile - The `--profile` value passed to this invocation (e.g.
78
- * `quick`, `reflect-distill`), or `null`/`undefined` when no profile was
79
- * given. Mirrors {@link recordTerminatedImproveRun}'s `ctx.profile`
80
- * convention so successful and terminated runs are equally queryable by
81
- * profile. Previously hardcoded to `null` here, which meant only
82
- * abnormally-terminated runs recorded their profile in state.db.
83
79
  */
84
- export function writeImproveResultFile(stashDir, runId, result, startedAt, profile) {
80
+ export function writeImproveResultFile(stashDir, runId, result, startedAt, sensitiveValues = []) {
81
+ const decoded = decodeImproveResult(result);
82
+ const persistedResult = redactSensitiveValue(result, sensitiveValues);
85
83
  withStateDb((db) => {
86
84
  const completedAt = new Date().toISOString();
87
85
  // startedAt is the ISO timestamp captured at process launch (passed from the
@@ -95,12 +93,13 @@ export function writeImproveResultFile(stashDir, runId, result, startedAt, profi
95
93
  completedAt,
96
94
  stashDir,
97
95
  dryRun: Boolean(result.dryRun),
98
- profile: profile ?? null,
96
+ legacyProfile: redactSensitiveValue(decoded.legacyProfile, sensitiveValues),
97
+ strategy: redactSensitiveValue(decoded.strategy, sensitiveValues),
99
98
  scopeMode: result.scope?.mode ?? "all",
100
- scopeValue: result.scope?.value ?? null,
101
- guidance: result.guidance ?? null,
99
+ scopeValue: persistedResult.scope?.value ?? null,
100
+ guidance: persistedResult.guidance ?? null,
102
101
  ok: Boolean(result.ok),
103
- result,
102
+ result: persistedResult,
104
103
  });
105
104
  });
106
105
  return relativeImproveResultPath(runId);
@@ -124,37 +123,44 @@ export function writeImproveResultFile(stashDir, runId, result, startedAt, profi
124
123
  */
125
124
  export function recordTerminatedImproveRun(stashDir, runId, startedAt, reason, ctx) {
126
125
  const completedAt = new Date().toISOString();
127
- const minimalResult = {
128
- schemaVersion: 1,
126
+ const persistedReason = redactSensitiveValue(reason, ctx.sensitiveValues ?? []);
127
+ const persistedScopeValue = redactSensitiveValue(ctx.scopeValue, ctx.sensitiveValues ?? []);
128
+ const persistedStrategy = redactSensitiveValue(ctx.strategy, ctx.sensitiveValues ?? []);
129
+ const minimalResult = redactSensitiveValue({
130
+ schemaVersion: 2,
129
131
  ok: false,
130
- scope: { mode: ctx?.scopeMode ?? "all", ...(ctx?.scopeValue ? { value: ctx.scopeValue } : {}) },
131
- dryRun: Boolean(ctx?.dryRun),
132
+ strategy: persistedStrategy,
133
+ scope: { mode: ctx.scopeMode ?? "all", ...(persistedScopeValue ? { value: persistedScopeValue } : {}) },
134
+ dryRun: Boolean(ctx.dryRun),
135
+ memorySummary: { eligible: 0, derived: 0 },
132
136
  actions: [],
133
137
  plannedRefs: [],
134
138
  terminated: {
135
- reason,
139
+ reason: persistedReason,
136
140
  at: completedAt,
137
- ...(ctx?.errorMessage ? { errorMessage: ctx.errorMessage } : {}),
141
+ ...(ctx.errorMessage ? { errorMessage: ctx.errorMessage } : {}),
138
142
  },
139
- };
143
+ }, ctx.sensitiveValues ?? []);
140
144
  withStateDb((db) => {
141
145
  recordImproveRun(db, {
142
146
  id: runId,
143
147
  startedAt,
144
148
  completedAt,
145
149
  stashDir,
146
- dryRun: Boolean(ctx?.dryRun),
147
- profile: ctx?.profile ?? null,
148
- scopeMode: ctx?.scopeMode ?? "all",
149
- scopeValue: ctx?.scopeValue ?? null,
150
+ dryRun: Boolean(ctx.dryRun),
151
+ strategy: persistedStrategy,
152
+ scopeMode: ctx.scopeMode ?? "all",
153
+ scopeValue: persistedScopeValue ?? null,
150
154
  guidance: null,
151
155
  ok: false,
152
156
  result: minimalResult,
153
157
  metadata: {
154
158
  terminated: {
155
- reason,
159
+ reason: persistedReason,
156
160
  at: completedAt,
157
- ...(ctx?.errorMessage ? { errorMessage: ctx.errorMessage } : {}),
161
+ ...(ctx.errorMessage
162
+ ? { errorMessage: redactSensitiveValue(ctx.errorMessage, ctx.sensitiveValues ?? []) }
163
+ : {}),
158
164
  },
159
165
  },
160
166
  });