akm-cli 0.8.6 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +442 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +63 -38
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -1,21 +1,55 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { stringify as yamlStringify } from "yaml";
5
- import { assembleAssetFromString } from "../core/asset-serialize";
6
- import { resolveStashDir, timestampForFilename } from "../core/common";
7
- import { getDefaultLlmConfig, loadConfig } from "../core/config";
8
- import { ConfigError, UsageError } from "../core/errors";
9
- import { appendEvent } from "../core/events";
10
- import { createProposal, isProposalSkipped } from "../core/proposals";
11
- import { getExtractedSessionsMap, openStateDatabase, shouldSkipAlreadyExtractedSession, upsertExtractedSession, } from "../core/state-db";
12
- import { warn } from "../core/warn";
13
- import { resolveImproveProcessRunnerFromProfile } from "../integrations/agent/runner";
14
- import { getAvailableHarnesses } from "../integrations/session-logs";
15
- import { preFilterSession } from "../integrations/session-logs/pre-filter";
16
- import { chatCompletion } from "../llm/client";
17
- import { isLlmFeatureEnabled, tryLlmFeature } from "../llm/feature-gate";
18
- import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt";
4
+ /**
5
+ * `akm extract` session-insight extractor.
6
+ *
7
+ * Replaces the akm-plugin session-checkpoint hook with an on-demand extractor
8
+ * that reads native session files (claude-code JSONL, opencode storage tree)
9
+ * through the {@link SessionLogHarness} registry, pre-filters noise, and asks
10
+ * a bounded in-tree LLM to produce candidate memory/lesson/knowledge proposals
11
+ * for content the agent did NOT preserve via inline `akm remember`/`akm feedback`.
12
+ *
13
+ * Architectural notes:
14
+ * - Stateless. All file/LLM access goes through injectable seams so tests
15
+ * never touch a real platform.
16
+ * - Bounded LLM call wrapped by {@link tryLlmFeature} under the
17
+ * `session_extraction` gate (default-on; opt out via
18
+ * `profiles.improve.default.processes.extract.enabled: false`).
19
+ * - Proposals routed via `createProposal({ source: "extract", ... })` — the
20
+ * same review queue as reflect / distill / consolidate. Never direct-write.
21
+ * - Per-candidate body assembly merges description (+ when_to_use for lessons)
22
+ * into the body's YAML frontmatter so the accept-time
23
+ * descriptionQualityValidator passes — same pattern as the
24
+ * consolidate-writer fix.
25
+ */
26
+ import { assembleAsset } from "../../core/asset/asset-serialize.js";
27
+ import { resolveStashDir, timestampForFilename } from "../../core/common.js";
28
+ import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
29
+ import { ConfigError, UsageError } from "../../core/errors.js";
30
+ import { appendEvent } from "../../core/events.js";
31
+ import { getExtractedSessionsMap, openStateDatabase, shouldSkipAlreadyExtractedSession, upsertExtractedSession, } from "../../core/state-db.js";
32
+ import { repairTruncatedDescription } from "../../core/text-truncation.js";
33
+ import { warn } from "../../core/warn.js";
34
+ import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
35
+ import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
36
+ import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
37
+ import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
38
+ import { chatCompletion } from "../../llm/client.js";
39
+ import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
40
+ import { createProposal, isProposalSkipped } from "../proposal/validators/proposals.js";
41
+ import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
42
+ import { buildSessionSummaryPrompt, parseSessionSummary, SESSION_SUMMARY_JSON_SCHEMA, sessionMeetsDurationGate, writeSessionAsset, } from "./session-asset.js";
43
+ /** Default minimum session duration (minutes) for session indexing (#561). */
44
+ const DEFAULT_MIN_SESSION_DURATION_MINUTES = 5;
45
+ /**
46
+ * Default minimum raw session size (chars) below which the extract LLM call is
47
+ * skipped (#595/#596). Deliberately tiny: analysis of 218 candidate-producing
48
+ * sessions showed sessions of 22–368 raw chars regularly yield 1–5 candidates,
49
+ * so size is not a reliable proxy for value — only truly empty sessions
50
+ * (0 chars, journal files) are safe to skip.
51
+ */
52
+ const DEFAULT_MIN_CONTENT_CHARS = 10;
19
53
  // ── Helpers ──────────────────────────────────────────────────────────────────
20
54
  /**
21
55
  * Parse a since-string into an absolute ms-epoch cutoff. Accepts:
@@ -49,7 +83,15 @@ export function parseSinceArg(value, now = Date.now()) {
49
83
  */
50
84
  function resolveHarness(type, harnesses) {
51
85
  const pool = harnesses ?? getAvailableHarnesses();
52
- return pool.find((h) => h.name === type);
86
+ // #563 id-normalization bridge: a provider's `name` is its runtime id (e.g.
87
+ // the Claude provider is "claude-code"), but the canonical harness id is
88
+ // "claude". Normalize BOTH the requested `--type` and each provider name to
89
+ // canonical before comparing, so `--type claude` and `--type claude-code`
90
+ // both resolve to the Claude provider. Behaviour fix: previously only the
91
+ // exact runtime string ("claude-code") matched; the canonical "claude" used
92
+ // everywhere else (agent profiles, config schema) silently found nothing.
93
+ const wanted = normalizeHarnessId(type);
94
+ return pool.find((h) => normalizeHarnessId(h.name) === wanted);
53
95
  }
54
96
  /**
55
97
  * Build the ref + content for a candidate. The body must contain a
@@ -59,16 +101,19 @@ function resolveHarness(type, harnesses) {
59
101
  */
60
102
  function buildCandidateProposal(candidate, sourceRef) {
61
103
  const ref = `${candidate.type}:${candidate.name}`;
104
+ // Post-generation repair pass (#556): deterministically complete a
105
+ // description the LLM sliced mid-sentence before it reaches the
106
+ // auto-accept validators. No-op (byte-identical) for valid descriptions.
107
+ const description = repairTruncatedDescription(candidate.description, candidate.body);
62
108
  const fm = {
63
- description: candidate.description,
109
+ description,
64
110
  sources: [`session:${sourceRef.harness}:${sourceRef.sessionId}`],
65
111
  };
66
112
  if (candidate.type === "lesson" && candidate.when_to_use) {
67
113
  fm.when_to_use = candidate.when_to_use;
68
114
  }
69
- const serialized = yamlStringify(fm).trimEnd();
70
- const content = assembleAssetFromString(serialized, candidate.body);
71
- return { ref, content };
115
+ const content = assembleAsset(fm, candidate.body);
116
+ return { ref, content, description };
72
117
  }
73
118
  /**
74
119
  * Process one session through the full pipeline: read → pre-filter → LLM →
@@ -78,7 +123,7 @@ function buildCandidateProposal(candidate, sourceRef) {
78
123
  * proposal validation failure) the session result records a warning and
79
124
  * keeps going — one session's bad luck never aborts a multi-session run.
80
125
  */
81
- async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars) {
126
+ async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, sessionIndexing) {
82
127
  const warnings = [];
83
128
  let data;
84
129
  try {
@@ -99,7 +144,56 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
99
144
  const filtered = preFilterSession(data, {
100
145
  ...(typeof maxTotalChars === "number" ? { maxTotalChars } : {}),
101
146
  });
147
+ // #595/#596 — minContentChars gate: skip the LLM call for sessions whose RAW
148
+ // size is below threshold. Measured on the raw event text BEFORE the noise
149
+ // pre-filter, NOT on post-filter output — the pre-filter strips boilerplate
150
+ // so aggressively that even signal-bearing sessions can have tiny output
151
+ // (#596: gating post-filter filtered out 100% of sessions). Note: the 0.8.x
152
+ // fix gated on `filtered.stats.inputCount`, which is an EVENT count, not a
153
+ // char count — this port measures actual raw chars so the threshold matches
154
+ // the config key's documented unit.
155
+ const rawContentChars = data.events.reduce((sum, event) => sum + event.text.length, 0);
156
+ if (minContentChars > 0 && rawContentChars < minContentChars) {
157
+ return {
158
+ sessionId: sessionRef.sessionId,
159
+ harness: harness.name,
160
+ candidateCount: 0,
161
+ proposalIds: [],
162
+ preFilter: {
163
+ inputCount: filtered.stats.inputCount,
164
+ outputCount: filtered.stats.outputCount,
165
+ truncatedCount: filtered.stats.truncatedCount,
166
+ },
167
+ warnings: [],
168
+ skipped: true,
169
+ skipReason: "too_short",
170
+ };
171
+ }
102
172
  const prompt = buildExtractPrompt({ data, events: filtered.events, inlineRefs: data.inlineRefs });
173
+ // #561 — ADDITIVE session indexing. Generate + write the session asset
174
+ // (`sessions/<harness>/<id>.md`). FAIL-OPEN: any failure only records a
175
+ // warning; it NEVER changes the proposal/skip outcome of extract. Returns the
176
+ // frontmatter fields to merge into the per-session result for state-db
177
+ // correlation. When disabled this closure makes NO LLM call and writes NOTHING.
178
+ const maybeWriteSessionAsset = async () => {
179
+ if (!sessionIndexing.enabled || dryRun)
180
+ return {};
181
+ if (!sessionMeetsDurationGate(data, sessionIndexing.minDurationMinutes))
182
+ return {};
183
+ try {
184
+ const result = await writeSessionAsset(data, stashDir, sessionIndexing.generate);
185
+ if (result.written) {
186
+ return {
187
+ ...(result.ref ? { sessionAssetRef: result.ref } : {}),
188
+ ...(result.logPath ? { sessionLogPath: result.logPath } : {}),
189
+ };
190
+ }
191
+ }
192
+ catch (err) {
193
+ warnings.push(`session asset write failed: ${err instanceof Error ? err.message : String(err)}`);
194
+ }
195
+ return {};
196
+ };
103
197
  let llmRaw = "";
104
198
  const llmResult = await tryLlmFeature("session_extraction", config, async () => {
105
199
  llmRaw = await chat(llmConfig, [{ role: "user", content: prompt }], {
@@ -141,6 +235,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
141
235
  preFilterOutput: filtered.stats.outputCount,
142
236
  },
143
237
  }, ctx);
238
+ const sessionAsset = await maybeWriteSessionAsset();
144
239
  return {
145
240
  sessionId: sessionRef.sessionId,
146
241
  harness: harness.name,
@@ -153,6 +248,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
153
248
  truncatedCount: filtered.stats.truncatedCount,
154
249
  },
155
250
  warnings,
251
+ ...sessionAsset,
156
252
  };
157
253
  }
158
254
  for (const candidate of payload.candidates) {
@@ -161,7 +257,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
161
257
  continue;
162
258
  }
163
259
  try {
164
- const { ref, content } = buildCandidateProposal(candidate, sessionRef);
260
+ const { ref, content, description } = buildCandidateProposal(candidate, sessionRef);
165
261
  const result = createProposal(stashDir, {
166
262
  ref,
167
263
  source: "extract",
@@ -169,7 +265,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
169
265
  payload: {
170
266
  content,
171
267
  frontmatter: {
172
- description: candidate.description,
268
+ description,
173
269
  ...(candidate.when_to_use ? { when_to_use: candidate.when_to_use } : {}),
174
270
  ...(typeof candidate.confidence === "number" ? { confidence: candidate.confidence } : {}),
175
271
  sources: [`session:${sessionRef.harness}:${sessionRef.sessionId}`],
@@ -202,6 +298,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
202
298
  preFilterOutput: filtered.stats.outputCount,
203
299
  },
204
300
  }, ctx);
301
+ const sessionAsset = await maybeWriteSessionAsset();
205
302
  return {
206
303
  sessionId: sessionRef.sessionId,
207
304
  harness: harness.name,
@@ -213,6 +310,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
213
310
  truncatedCount: filtered.stats.truncatedCount,
214
311
  },
215
312
  warnings,
313
+ ...sessionAsset,
216
314
  };
217
315
  }
218
316
  // ── Public entrypoint ────────────────────────────────────────────────────────
@@ -257,7 +355,7 @@ export async function akmExtract(options) {
257
355
  let llmConfig;
258
356
  const runnerSpec = resolveImproveProcessRunnerFromProfile(extractProcess, config);
259
357
  if (runnerSpec) {
260
- if (runnerSpec.kind !== "llm") {
358
+ if (!runnerIsLlm(runnerSpec)) {
261
359
  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");
262
360
  }
263
361
  llmConfig = runnerSpec.connection;
@@ -274,8 +372,40 @@ export async function akmExtract(options) {
274
372
  60_000;
275
373
  // Pre-filter budget — process config can raise it for large-context models.
276
374
  const maxTotalChars = typeof extractProcess?.maxTotalChars === "number" ? extractProcess.maxTotalChars : undefined;
375
+ // #595/#596 — minimum raw session size; sessions below it skip the LLM call
376
+ // entirely. Set `processes.extract.minContentChars: 0` to disable the gate.
377
+ const minContentChars = typeof extractProcess?.minContentChars === "number" ? extractProcess.minContentChars : DEFAULT_MIN_CONTENT_CHARS;
277
378
  // Default discovery window — process config can override the built-in 24h.
278
379
  const effectiveSince = options.since ?? extractProcess?.defaultSince;
380
+ // #561 — resolve session-indexing config. Default ON: we only reach this code
381
+ // when `session_extraction` is enabled AND an LLM is configured (both checked
382
+ // above), so defaulting on costs nothing offline (the summary call fails open)
383
+ // while making sessions searchable in the common LLM-configured case. Set
384
+ // `processes.extract.indexSessions: false` for byte-identical legacy behaviour.
385
+ const sessionIndexingEnabled = extractProcess?.indexSessions ?? true;
386
+ const minSessionDuration = typeof extractProcess?.minSessionDuration === "number"
387
+ ? extractProcess.minSessionDuration
388
+ : DEFAULT_MIN_SESSION_DURATION_MINUTES;
389
+ // Production summary generator: a bounded in-tree LLM call wrapped in the same
390
+ // fail-open `tryLlmFeature` seam as the rest of extract. Returns `undefined`
391
+ // on disablement / timeout / error so no asset is written. Tests inject a fake.
392
+ const chatForSummary = options.chat ?? chatCompletion;
393
+ const defaultSessionSummaryGenerator = async (data) => {
394
+ let raw = "";
395
+ await tryLlmFeature("session_extraction", config, async () => {
396
+ raw = await chatForSummary(llmConfig, [{ role: "user", content: buildSessionSummaryPrompt(data) }], {
397
+ timeoutMs,
398
+ responseSchema: SESSION_SUMMARY_JSON_SCHEMA,
399
+ });
400
+ return raw;
401
+ }, "", { timeoutMs });
402
+ return parseSessionSummary(raw);
403
+ };
404
+ const sessionIndexing = {
405
+ enabled: sessionIndexingEnabled,
406
+ minDurationMinutes: minSessionDuration,
407
+ generate: options.generateSessionSummary ?? defaultSessionSummaryGenerator,
408
+ };
279
409
  const harness = resolveHarness(options.type, options.harnesses);
280
410
  if (!harness) {
281
411
  return {
@@ -355,7 +485,7 @@ export async function akmExtract(options) {
355
485
  let seenMap = new Map();
356
486
  if (trackingEnabled && candidates.length > 0) {
357
487
  try {
358
- stateDb = options.stateDb ?? openStateDatabase();
488
+ stateDb = options.stateDb ?? openStateDatabase(options.stateDbPath);
359
489
  seenMap = getExtractedSessionsMap(stateDb, harness.name, candidates.map((c) => c.sessionId));
360
490
  }
361
491
  catch (err) {
@@ -389,7 +519,7 @@ export async function akmExtract(options) {
389
519
  continue;
390
520
  }
391
521
  try {
392
- const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars);
522
+ const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, sessionIndexing);
393
523
  sessions.push(result);
394
524
  if (result.skipped)
395
525
  skippedCount += 1;
@@ -423,6 +553,11 @@ export async function akmExtract(options) {
423
553
  preFilterOutputCount: result.preFilter.outputCount,
424
554
  preFilterTruncatedCount: result.preFilter.truncatedCount,
425
555
  ...(result.skipReason ? { skipReason: result.skipReason } : {}),
556
+ // #561 — record the session's log_path for correlation across
557
+ // index rebuilds (the session asset frontmatter is the primary
558
+ // durable key; this is the state-db mirror of it).
559
+ ...(result.sessionLogPath ? { logPath: result.sessionLogPath } : {}),
560
+ ...(result.sessionAssetRef ? { sessionAssetRef: result.sessionAssetRef } : {}),
426
561
  },
427
562
  });
428
563
  }
@@ -475,3 +610,63 @@ export async function akmExtract(options) {
475
610
  durationMs: Date.now() - startMs,
476
611
  };
477
612
  }
613
+ /**
614
+ * Count NEW (unseen, in-window) extract candidate sessions across all available
615
+ * harnesses WITHOUT making any LLM calls. Mirrors the discovery + seen-filter
616
+ * logic in {@link akmExtract} so the `#554 minNewSessions` gate in `improve`
617
+ * can decide whether the extract pass is worth running before any work begins.
618
+ *
619
+ * A session is a "new candidate" when it is in the `since` window AND it would
620
+ * not be skipped by {@link shouldSkipAlreadyExtractedSession} (i.e. it has never
621
+ * been extracted, or new events have arrived since it was last extracted).
622
+ */
623
+ export function countNewExtractCandidates(config, options = {}) {
624
+ const extractProcess = config.profiles?.improve?.default?.processes?.extract;
625
+ const effectiveSince = options.since ?? extractProcess?.defaultSince;
626
+ const sinceMs = parseSinceArg(effectiveSince);
627
+ const harnesses = (options.harnesses ?? getAvailableHarnesses()).filter((h) => h.isAvailable());
628
+ let stateDb = options.stateDb;
629
+ let openedStateDb = false;
630
+ let total = 0;
631
+ try {
632
+ for (const harness of harnesses) {
633
+ const candidates = harness.listSessions({ sinceMs });
634
+ if (candidates.length === 0)
635
+ continue;
636
+ let seenMap = new Map();
637
+ try {
638
+ if (!stateDb) {
639
+ stateDb = openStateDatabase(options.stateDbPath);
640
+ openedStateDb = true;
641
+ }
642
+ seenMap = getExtractedSessionsMap(stateDb, harness.name, candidates.map((c) => c.sessionId));
643
+ }
644
+ catch (err) {
645
+ // state.db unavailable — treat every in-window session as a new
646
+ // candidate (fail-open: never let a transient sqlite error wrongly
647
+ // trip the gate and skip a pass that should have run).
648
+ const msg = err instanceof Error ? err.message : String(err);
649
+ warn(`[extract] state.db unavailable while counting candidates, treating all as new: ${msg}`);
650
+ total += candidates.length;
651
+ continue;
652
+ }
653
+ for (const summary of candidates) {
654
+ const prior = seenMap.get(summary.sessionId);
655
+ if (shouldSkipAlreadyExtractedSession(prior, summary.endedAt))
656
+ continue;
657
+ total += 1;
658
+ }
659
+ }
660
+ }
661
+ finally {
662
+ if (stateDb && openedStateDb) {
663
+ try {
664
+ stateDb.close();
665
+ }
666
+ catch {
667
+ // best-effort close
668
+ }
669
+ }
670
+ }
671
+ return total;
672
+ }
@@ -1,10 +1,10 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { loadConfig } from "../core/config";
5
- import { appendEvent } from "../core/events";
6
- import { promoteProposal } from "../core/proposals";
7
- import { info, warn } from "../core/warn";
4
+ import { loadConfig } from "../../core/config/config.js";
5
+ import { appendEvent } from "../../core/events.js";
6
+ import { info, warn } from "../../core/warn.js";
7
+ import { promoteProposal, recordGateDecision } from "../proposal/validators/proposals.js";
8
8
  // ---------------------------------------------------------------------------
9
9
  // Gate implementation
10
10
  // ---------------------------------------------------------------------------
@@ -26,14 +26,40 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
26
26
  }
27
27
  const effectiveThreshold = Math.max(cfg.globalThreshold, cfg.minimumThreshold ?? 0) / 100;
28
28
  const resolvedConfig = typeof cfg.config === "function" ? cfg.config() : cfg.config;
29
+ const gateLabel = `improve:${cfg.phase}`;
30
+ // #577: stamp the gate's verdict onto each proposal so `akm proposal show`
31
+ // can explain why a proposal is pending (e.g. "deferred: below-threshold,
32
+ // 0.72 < 0.90"). Best-effort — a recording failure must never abort the gate.
33
+ const stamp = (proposalId, decision) => {
34
+ try {
35
+ recordGateDecision(cfg.stashDir, proposalId, decision);
36
+ }
37
+ catch (err) {
38
+ warn(`[improve] ${cfg.phase} failed to record gate decision for ${proposalId}: ${err instanceof Error ? err.message : String(err)}`);
39
+ }
40
+ };
29
41
  for (const candidate of candidates) {
30
42
  const { proposalId, confidence } = candidate;
31
43
  if (confidence === undefined || confidence < effectiveThreshold) {
44
+ stamp(proposalId, {
45
+ outcome: "deferred",
46
+ reason: confidence === undefined ? "no-confidence" : "below-threshold",
47
+ ...(confidence !== undefined ? { confidence } : {}),
48
+ thresholds: { autoAccept: effectiveThreshold },
49
+ gate: gateLabel,
50
+ });
32
51
  result.skipped.push(proposalId);
33
52
  continue;
34
53
  }
35
54
  try {
36
55
  const promotion = await promoteFn(cfg.stashDir, resolvedConfig, proposalId, {}, undefined);
56
+ stamp(promotion.proposal.id, {
57
+ outcome: "auto-accepted",
58
+ reason: "above-threshold",
59
+ confidence,
60
+ thresholds: { autoAccept: effectiveThreshold },
61
+ gate: gateLabel,
62
+ });
37
63
  appendEvent({
38
64
  eventType: "promoted",
39
65
  ref: promotion.ref,
@@ -3,16 +3,16 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import path from "node:path";
5
5
  import { defineCommand } from "citty";
6
- import { getStringArg, parseAutoAcceptFlag, parseNonNegativeIntFlag, parsePositiveIntFlag } from "../cli/parse-args";
7
- import { output, runWithJsonErrors } from "../cli/shared";
8
- import { loadConfig } from "../core/config";
9
- import { UsageError } from "../core/errors";
10
- import { getCacheDir } from "../core/paths";
11
- import { clearLogFile, setLogFile } from "../core/warn";
12
- import { resolveSourceEntries } from "../indexer/search-source";
13
- import { getHyphenatedArg, getHyphenatedBoolean, parseFlagValue } from "../output/context";
14
- import { akmImprove } from "./improve";
15
- import { buildImproveRunId, recordTerminatedImproveRun, relativeImproveResultPath, writeImproveResultFile, } from "./improve-result-file";
6
+ import { getStringArg, parseAutoAcceptFlag, parseNonNegativeIntFlag, parsePositiveIntFlag } from "../../cli/parse-args.js";
7
+ import { output, runWithJsonErrors } from "../../cli/shared.js";
8
+ import { loadConfig } from "../../core/config/config.js";
9
+ import { UsageError } from "../../core/errors.js";
10
+ import { getCacheDir } from "../../core/paths.js";
11
+ import { clearLogFile, setLogFile } from "../../core/warn.js";
12
+ import { resolveSourceEntries } from "../../indexer/search/search-source.js";
13
+ import { getHyphenatedArg, getHyphenatedBoolean, parseFlagValue } from "../../output/context.js";
14
+ import { akmImprove } from "./improve.js";
15
+ import { buildImproveRunId, recordTerminatedImproveRun, relativeImproveResultPath, writeImproveResultFile, } from "./improve-result-file.js";
16
16
  export const improveCommand = defineCommand({
17
17
  meta: {
18
18
  name: "improve",
@@ -144,20 +144,41 @@ export const improveCommand = defineCommand({
144
144
  process.stderr.write(`warning: failed to persist terminated improve run ${runId}: ${err instanceof Error ? err.message : String(err)}\n`);
145
145
  }
146
146
  };
147
- const sigtermHandler = () => {
148
- persistTerminated("SIGTERM");
149
- process.stderr.write(`[improve] received SIGTERM; recorded terminated run ${runId}\n`);
150
- process.exit(143);
147
+ // M5 (code-health round 2): signal -> {exit code, reason, ack message}
148
+ // as an explicit table instead of three near-identical handlers. The
149
+ // persist of the terminated-run row MUST complete before process.exit so
150
+ // a SIGTERM'd run (e.g. cron timeout) always leaves a row in
151
+ // improve_runs. recordTerminatedImproveRun is fully synchronous
152
+ // (bun:sqlite writes are sync), so the in-line call below blocks until
153
+ // the row is flushed before we exit.
154
+ const SIGNAL_TABLE = {
155
+ SIGTERM: { code: 143, reason: "SIGTERM", ack: true },
156
+ SIGINT: { code: 130, reason: "SIGINT", ack: true },
157
+ SIGHUP: { code: 129, reason: "SIGHUP", ack: false },
151
158
  };
152
- const sigintHandler = () => {
153
- persistTerminated("SIGINT");
154
- process.stderr.write(`[improve] received SIGINT; recorded terminated run ${runId}\n`);
155
- process.exit(130);
156
- };
157
- const sighupHandler = () => {
158
- persistTerminated("SIGHUP");
159
- process.exit(129);
159
+ const makeSignalHandler = (sig) => () => {
160
+ const { code, reason, ack } = SIGNAL_TABLE[sig];
161
+ // Hard-exit fallback: if the synchronous persist ever hangs (e.g. a
162
+ // stuck sqlite lock under contention), the watchdog still exits with
163
+ // the correct code instead of leaving a zombie process. .unref() keeps
164
+ // the timer from holding the loop open on the normal (fast) path.
165
+ const watchdog = setTimeout(() => process.exit(code), 2000);
166
+ if (typeof watchdog.unref === "function")
167
+ watchdog.unref();
168
+ try {
169
+ persistTerminated(reason);
170
+ }
171
+ finally {
172
+ clearTimeout(watchdog);
173
+ }
174
+ if (ack) {
175
+ process.stderr.write(`[improve] received ${sig}; recorded terminated run ${runId}\n`);
176
+ }
177
+ process.exit(code);
160
178
  };
179
+ const sigtermHandler = makeSignalHandler("SIGTERM");
180
+ const sigintHandler = makeSignalHandler("SIGINT");
181
+ const sighupHandler = makeSignalHandler("SIGHUP");
161
182
  process.once("SIGTERM", sigtermHandler);
162
183
  process.once("SIGINT", sigintHandler);
163
184
  process.once("SIGHUP", sighupHandler);
@@ -169,6 +190,7 @@ export const improveCommand = defineCommand({
169
190
  dryRun,
170
191
  target: targetArg,
171
192
  autoAccept,
193
+ ...(runId !== undefined ? { runId } : {}),
172
194
  ...(limitRaw !== undefined ? { limit: limitRaw } : {}),
173
195
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
174
196
  ...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
@@ -1,13 +1,16 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import profileDefault from "../assets/profiles/default.json" with { type: "json" };
5
- import profileGraphRefresh from "../assets/profiles/graph-refresh.json" with { type: "json" };
6
- import profileMemoryFocus from "../assets/profiles/memory-focus.json" with { type: "json" };
7
- import profileQuick from "../assets/profiles/quick.json" with { type: "json" };
8
- import profileThorough from "../assets/profiles/thorough.json" with { type: "json" };
9
- import { parseAssetRef } from "../core/asset-ref";
10
- import { warn } from "../core/warn";
4
+ import profileCatchup from "../../assets/profiles/catchup.json" with { type: "json" };
5
+ import profileConsolidate from "../../assets/profiles/consolidate.json" with { type: "json" };
6
+ import profileDefault from "../../assets/profiles/default.json" with { type: "json" };
7
+ import profileFrequent from "../../assets/profiles/frequent.json" with { type: "json" };
8
+ import profileGraphRefresh from "../../assets/profiles/graph-refresh.json" with { type: "json" };
9
+ import profileMemoryFocus from "../../assets/profiles/memory-focus.json" with { type: "json" };
10
+ import profileQuick from "../../assets/profiles/quick.json" with { type: "json" };
11
+ import profileThorough from "../../assets/profiles/thorough.json" with { type: "json" };
12
+ import { parseAssetRef } from "../../core/asset/asset-ref.js";
13
+ import { warn } from "../../core/warn.js";
11
14
  /** Profile name used as the final fallback when nothing else resolves. */
12
15
  const FALLBACK_PROFILE_NAME = "default";
13
16
  // Built-in default allowed types per process
@@ -25,6 +28,9 @@ const BUILTIN_PROFILES = {
25
28
  thorough: profileThorough,
26
29
  "memory-focus": profileMemoryFocus,
27
30
  "graph-refresh": profileGraphRefresh,
31
+ frequent: profileFrequent,
32
+ consolidate: profileConsolidate,
33
+ catchup: profileCatchup,
28
34
  };
29
35
  /**
30
36
  * Default enabled-state for known improve processes when neither the user
@@ -27,7 +27,7 @@
27
27
  */
28
28
  import crypto from "node:crypto";
29
29
  import path from "node:path";
30
- import { openStateDatabase, recordImproveRun } from "../core/state-db";
30
+ import { openStateDatabase, recordImproveRun } from "../../core/state-db.js";
31
31
  /**
32
32
  * Build a stable run-id for a single improve invocation.
33
33
  *