akm-cli 0.8.0-rc1 → 0.8.0

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 (295) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +191 -3
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +93 -3
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2162 -1258
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +20 -12
  12. package/dist/commands/agent-support.js +11 -5
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +129 -517
  15. package/dist/commands/consolidate.js +1533 -144
  16. package/dist/commands/curate.js +44 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +5 -3
  19. package/dist/commands/distill.js +906 -100
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +3 -0
  22. package/dist/commands/events.js +3 -0
  23. package/dist/commands/extract-cli.js +127 -0
  24. package/dist/commands/extract-prompt.js +204 -0
  25. package/dist/commands/extract.js +477 -0
  26. package/dist/commands/feedback-cli.js +331 -0
  27. package/dist/commands/graph.js +260 -5
  28. package/dist/commands/health.js +977 -51
  29. package/dist/commands/help/help-accept.md +6 -3
  30. package/dist/commands/help/help-improve.md +36 -8
  31. package/dist/commands/help/help-proposals.md +7 -4
  32. package/dist/commands/help/help-reject.md +5 -2
  33. package/dist/commands/history.js +51 -16
  34. package/dist/commands/improve-auto-accept.js +97 -0
  35. package/dist/commands/improve-cli.js +236 -0
  36. package/dist/commands/improve-profiles.js +184 -0
  37. package/dist/commands/improve-result-file.js +167 -0
  38. package/dist/commands/improve.js +1725 -332
  39. package/dist/commands/info.js +3 -0
  40. package/dist/commands/init.js +49 -1
  41. package/dist/commands/installed-stashes.js +6 -23
  42. package/dist/commands/knowledge.js +3 -0
  43. package/dist/commands/lint/agent-linter.js +3 -0
  44. package/dist/commands/lint/base-linter.js +233 -5
  45. package/dist/commands/lint/command-linter.js +3 -0
  46. package/dist/commands/lint/default-linter.js +3 -0
  47. package/dist/commands/lint/env-key-rules.js +154 -0
  48. package/dist/commands/lint/index.js +92 -3
  49. package/dist/commands/lint/knowledge-linter.js +3 -0
  50. package/dist/commands/lint/markdown-insertion.js +343 -0
  51. package/dist/commands/lint/memory-linter.js +3 -0
  52. package/dist/commands/lint/registry.js +3 -0
  53. package/dist/commands/lint/skill-linter.js +3 -0
  54. package/dist/commands/lint/task-linter.js +15 -12
  55. package/dist/commands/lint/types.js +3 -0
  56. package/dist/commands/lint/workflow-linter.js +3 -0
  57. package/dist/commands/lint.js +3 -0
  58. package/dist/commands/migration-help.js +5 -2
  59. package/dist/commands/proposal-drain-policies.js +128 -0
  60. package/dist/commands/proposal-drain.js +477 -0
  61. package/dist/commands/proposal.js +60 -6
  62. package/dist/commands/propose.js +24 -19
  63. package/dist/commands/reflect.js +1004 -94
  64. package/dist/commands/registry-cli.js +150 -0
  65. package/dist/commands/registry-search.js +3 -0
  66. package/dist/commands/remember-cli.js +257 -0
  67. package/dist/commands/remember.js +15 -6
  68. package/dist/commands/schema-repair.js +88 -15
  69. package/dist/commands/search.js +99 -14
  70. package/dist/commands/secret.js +173 -0
  71. package/dist/commands/self-update.js +3 -0
  72. package/dist/commands/show.js +32 -13
  73. package/dist/commands/source-add.js +7 -35
  74. package/dist/commands/source-clone.js +3 -0
  75. package/dist/commands/source-manage.js +3 -0
  76. package/dist/commands/tasks.js +161 -95
  77. package/dist/commands/url-checker.js +3 -0
  78. package/dist/core/action-contributors.js +3 -0
  79. package/dist/core/asset-ref.js +17 -2
  80. package/dist/core/asset-registry.js +9 -2
  81. package/dist/core/asset-serialize.js +88 -0
  82. package/dist/core/asset-spec.js +61 -5
  83. package/dist/core/common.js +93 -5
  84. package/dist/core/concurrent.js +3 -0
  85. package/dist/core/config-io.js +347 -0
  86. package/dist/core/config-migration.js +622 -0
  87. package/dist/core/config-schema.js +558 -0
  88. package/dist/core/config-sources.js +108 -0
  89. package/dist/core/config-types.js +4 -0
  90. package/dist/core/config-walker.js +337 -0
  91. package/dist/core/config.js +366 -1077
  92. package/dist/core/errors.js +42 -20
  93. package/dist/core/events.js +31 -25
  94. package/dist/core/file-lock.js +104 -0
  95. package/dist/core/frontmatter.js +75 -10
  96. package/dist/core/lesson-lint.js +3 -0
  97. package/dist/core/markdown.js +3 -0
  98. package/dist/core/memory-belief.js +62 -0
  99. package/dist/core/memory-contradiction-detect.js +274 -0
  100. package/dist/core/memory-improve.js +142 -14
  101. package/dist/core/parse.js +3 -0
  102. package/dist/core/paths.js +218 -50
  103. package/dist/core/proposal-quality-validators.js +380 -0
  104. package/dist/core/proposal-validators.js +11 -3
  105. package/dist/core/proposals.js +464 -5
  106. package/dist/core/state-db.js +349 -56
  107. package/dist/core/text-truncation.js +107 -0
  108. package/dist/core/time.js +3 -0
  109. package/dist/core/tty.js +59 -0
  110. package/dist/core/warn.js +7 -2
  111. package/dist/core/write-source.js +12 -0
  112. package/dist/indexer/db-backup.js +391 -0
  113. package/dist/indexer/db-search.js +136 -28
  114. package/dist/indexer/db.js +662 -166
  115. package/dist/indexer/ensure-index.js +3 -0
  116. package/dist/indexer/file-context.js +3 -0
  117. package/dist/indexer/graph-boost.js +162 -40
  118. package/dist/indexer/graph-db.js +241 -51
  119. package/dist/indexer/graph-dedup.js +3 -7
  120. package/dist/indexer/graph-extraction.js +242 -149
  121. package/dist/indexer/index-context.js +3 -9
  122. package/dist/indexer/indexer.js +84 -14
  123. package/dist/indexer/llm-cache.js +24 -19
  124. package/dist/indexer/manifest.js +3 -0
  125. package/dist/indexer/matchers.js +184 -11
  126. package/dist/indexer/memory-inference.js +94 -50
  127. package/dist/indexer/metadata-contributors.js +3 -0
  128. package/dist/indexer/metadata.js +114 -48
  129. package/dist/indexer/path-resolver.js +3 -0
  130. package/dist/indexer/project-context.js +192 -0
  131. package/dist/indexer/ranking-contributors.js +134 -7
  132. package/dist/indexer/ranking.js +8 -1
  133. package/dist/indexer/search-fields.js +5 -9
  134. package/dist/indexer/search-hit-enrichers.js +91 -2
  135. package/dist/indexer/search-source.js +20 -1
  136. package/dist/indexer/semantic-status.js +4 -1
  137. package/dist/indexer/staleness-detect.js +447 -0
  138. package/dist/indexer/usage-events.js +12 -9
  139. package/dist/indexer/walker.js +3 -0
  140. package/dist/integrations/agent/builders.js +135 -0
  141. package/dist/integrations/agent/config.js +121 -401
  142. package/dist/integrations/agent/detect.js +3 -0
  143. package/dist/integrations/agent/index.js +6 -14
  144. package/dist/integrations/agent/model-aliases.js +55 -0
  145. package/dist/integrations/agent/profiles.js +3 -0
  146. package/dist/integrations/agent/prompts.js +137 -8
  147. package/dist/integrations/agent/runner.js +208 -0
  148. package/dist/integrations/agent/sdk-runner.js +8 -2
  149. package/dist/integrations/agent/spawn.js +54 -14
  150. package/dist/integrations/github.js +3 -0
  151. package/dist/integrations/lockfile.js +22 -51
  152. package/dist/integrations/session-logs/index.js +4 -0
  153. package/dist/integrations/session-logs/inline-refs.js +35 -0
  154. package/dist/integrations/session-logs/pre-filter.js +152 -0
  155. package/dist/integrations/session-logs/providers/claude-code.js +226 -0
  156. package/dist/integrations/session-logs/providers/opencode.js +231 -25
  157. package/dist/integrations/session-logs/types.js +3 -0
  158. package/dist/llm/call-ai.js +14 -26
  159. package/dist/llm/client.js +16 -2
  160. package/dist/llm/embedder.js +20 -29
  161. package/dist/llm/embedders/cache.js +3 -7
  162. package/dist/llm/embedders/local.js +42 -1
  163. package/dist/llm/embedders/remote.js +20 -8
  164. package/dist/llm/embedders/types.js +3 -7
  165. package/dist/llm/feature-gate.js +92 -56
  166. package/dist/llm/graph-extract.js +401 -30
  167. package/dist/llm/index-passes.js +44 -29
  168. package/dist/llm/memory-infer.js +30 -2
  169. package/dist/llm/metadata-enhance.js +3 -7
  170. package/dist/llm/prompts/extract-session.md +80 -0
  171. package/dist/llm/prompts/graph-extract-user-prompt.md +24 -1
  172. package/dist/output/cli-hints-full.md +60 -32
  173. package/dist/output/cli-hints-short.md +10 -7
  174. package/dist/output/cli-hints.js +5 -2
  175. package/dist/output/context.js +60 -8
  176. package/dist/output/renderers.js +170 -194
  177. package/dist/output/shapes/curate.js +56 -0
  178. package/dist/output/shapes/distill.js +10 -0
  179. package/dist/output/shapes/env-list.js +19 -0
  180. package/dist/output/shapes/events.js +11 -0
  181. package/dist/output/shapes/helpers.js +424 -0
  182. package/dist/output/shapes/history.js +7 -0
  183. package/dist/output/shapes/passthrough.js +105 -0
  184. package/dist/output/shapes/proposal-accept.js +7 -0
  185. package/dist/output/shapes/proposal-diff.js +7 -0
  186. package/dist/output/shapes/proposal-list.js +7 -0
  187. package/dist/output/shapes/proposal-producer.js +11 -0
  188. package/dist/output/shapes/proposal-reject.js +7 -0
  189. package/dist/output/shapes/proposal-show.js +7 -0
  190. package/dist/output/shapes/registry-search.js +6 -0
  191. package/dist/output/shapes/registry.js +30 -0
  192. package/dist/output/shapes/search.js +6 -0
  193. package/dist/output/shapes/secret-list.js +19 -0
  194. package/dist/output/shapes/show.js +6 -0
  195. package/dist/output/shapes/vault-list.js +19 -0
  196. package/dist/output/shapes.js +51 -549
  197. package/dist/output/text/add.js +6 -0
  198. package/dist/output/text/clone.js +6 -0
  199. package/dist/output/text/config.js +6 -0
  200. package/dist/output/text/curate.js +6 -0
  201. package/dist/output/text/distill.js +7 -0
  202. package/dist/output/text/enable-disable.js +7 -0
  203. package/dist/output/text/events.js +10 -0
  204. package/dist/output/text/feedback.js +6 -0
  205. package/dist/output/text/helpers.js +1059 -0
  206. package/dist/output/text/history.js +7 -0
  207. package/dist/output/text/import.js +6 -0
  208. package/dist/output/text/index.js +6 -0
  209. package/dist/output/text/info.js +6 -0
  210. package/dist/output/text/init.js +6 -0
  211. package/dist/output/text/list.js +6 -0
  212. package/dist/output/text/proposal-producer.js +8 -0
  213. package/dist/output/text/proposal.js +12 -0
  214. package/dist/output/text/registry-commands.js +11 -0
  215. package/dist/output/text/registry.js +30 -0
  216. package/dist/output/text/remember.js +6 -0
  217. package/dist/output/text/remove.js +6 -0
  218. package/dist/output/text/save.js +6 -0
  219. package/dist/output/text/search.js +6 -0
  220. package/dist/output/text/show.js +6 -0
  221. package/dist/output/text/update.js +6 -0
  222. package/dist/output/text/upgrade.js +6 -0
  223. package/dist/output/text/vault.js +16 -0
  224. package/dist/output/text/wiki.js +15 -0
  225. package/dist/output/text/workflow.js +14 -0
  226. package/dist/output/text.js +44 -1329
  227. package/dist/registry/build-index.js +3 -0
  228. package/dist/registry/create-provider-registry.js +3 -0
  229. package/dist/registry/factory.js +4 -1
  230. package/dist/registry/origin-resolve.js +3 -0
  231. package/dist/registry/providers/index.js +3 -0
  232. package/dist/registry/providers/skills-sh.js +11 -2
  233. package/dist/registry/providers/static-index.js +10 -1
  234. package/dist/registry/providers/types.js +3 -24
  235. package/dist/registry/resolve.js +11 -16
  236. package/dist/registry/types.js +3 -0
  237. package/dist/scripts/migrate-storage.js +17767 -0
  238. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  239. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  240. package/dist/setup/detect.js +3 -0
  241. package/dist/setup/ripgrep-install.js +3 -0
  242. package/dist/setup/ripgrep-resolve.js +3 -0
  243. package/dist/setup/setup.js +306 -67
  244. package/dist/setup/steps.js +3 -15
  245. package/dist/sources/include.js +3 -0
  246. package/dist/sources/provider-factory.js +3 -11
  247. package/dist/sources/provider.js +3 -20
  248. package/dist/sources/providers/filesystem.js +19 -23
  249. package/dist/sources/providers/git.js +171 -21
  250. package/dist/sources/providers/index.js +3 -0
  251. package/dist/sources/providers/install-types.js +3 -13
  252. package/dist/sources/providers/npm.js +3 -4
  253. package/dist/sources/providers/provider-utils.js +3 -0
  254. package/dist/sources/providers/sync-from-ref.js +3 -11
  255. package/dist/sources/providers/tar-utils.js +3 -0
  256. package/dist/sources/providers/website.js +18 -22
  257. package/dist/sources/resolve.js +3 -0
  258. package/dist/sources/types.js +3 -0
  259. package/dist/sources/website-ingest.js +3 -0
  260. package/dist/tasks/backends/cron.js +3 -0
  261. package/dist/tasks/backends/exec-utils.js +3 -0
  262. package/dist/tasks/backends/index.js +3 -11
  263. package/dist/tasks/backends/launchd.js +3 -0
  264. package/dist/tasks/backends/schtasks.js +3 -0
  265. package/dist/tasks/parser.js +51 -38
  266. package/dist/tasks/resolveAkmBin.js +3 -0
  267. package/dist/tasks/runner.js +35 -9
  268. package/dist/tasks/schedule.js +20 -1
  269. package/dist/tasks/schema.js +5 -3
  270. package/dist/tasks/validator.js +6 -3
  271. package/dist/version.js +3 -0
  272. package/dist/wiki/wiki-templates.js +3 -0
  273. package/dist/wiki/wiki.js +3 -0
  274. package/dist/workflows/authoring.js +3 -0
  275. package/dist/workflows/cli.js +3 -0
  276. package/dist/workflows/db.js +140 -10
  277. package/dist/workflows/document-cache.js +3 -10
  278. package/dist/workflows/parser.js +3 -0
  279. package/dist/workflows/renderer.js +3 -0
  280. package/dist/workflows/runs.js +18 -1
  281. package/dist/workflows/schema.js +3 -0
  282. package/dist/workflows/scope-key.js +3 -0
  283. package/dist/workflows/validator.js +5 -9
  284. package/docs/README.md +7 -2
  285. package/docs/data-and-telemetry.md +225 -0
  286. package/docs/migration/release-notes/0.7.5.md +2 -2
  287. package/docs/migration/release-notes/0.8.0.md +57 -5
  288. package/docs/migration/v0.7-to-v0.8.md +1378 -0
  289. package/package.json +28 -11
  290. package/.github/LICENSE +0 -374
  291. package/dist/commands/install-audit.js +0 -385
  292. package/dist/commands/vault.js +0 -307
  293. package/dist/indexer/match-contributors.js +0 -141
  294. package/dist/integrations/agent/pipeline.js +0 -39
  295. package/dist/integrations/agent/runners.js +0 -31
@@ -1,9 +1,12 @@
1
1
  Usage:
2
- akm accept <id>
2
+ akm proposal accept <id>
3
3
 
4
4
  Description:
5
5
  Accept a proposal and promote it into the stash.
6
6
 
7
+ (`akm accept` is a deprecated alias for `akm proposal accept`; it warns on
8
+ stderr and is removed in 0.9.0.)
9
+
7
10
  Examples:
8
- akm accept proposal_123
9
- akm accept proposal_123 --target team-stash
11
+ akm proposal accept proposal_123
12
+ akm proposal accept proposal_123 --target team-stash
@@ -30,20 +30,44 @@ Options:
30
30
  --task <text> Add extra guidance for this improvement pass
31
31
  --dry-run Show planned actions without generating proposals
32
32
  --target <source> Override the write target for accepted proposals
33
- --auto-accept safe Automatically accept low-risk proposals
34
- --ignore-cooldown Disable reflect/distill/consolidate cooldown checks for this run
35
- --reflect-cooldown-days <n>
36
- Override reflect cooldown with a non-negative integer
37
- --distill-cooldown-days <n>
38
- Override distill cooldown with a non-negative integer
39
- --consolidate-cooldown-days <n>
40
- Override consolidate cooldown with a non-negative integer
33
+ --auto-accept[=<value>]
34
+ Confidence threshold (0-100) for auto-accepting proposals.
35
+ Default when flag is absent: ON at threshold 90 (all sub-processes).
36
+ --auto-accept same as --auto-accept=90
37
+ --auto-accept=<N> integer 0-100; accept proposals at or above N
38
+ --auto-accept=safe alias for 90 (back-compat, not deprecated)
39
+ --auto-accept=false disable auto-accept for all sub-processes;
40
+ reflect/distill proposals go to the queue and
41
+ consolidation will prompt interactively on HTTP paths
42
+ Note: until proposals carry real confidence scores, any non-`false`
43
+ value behaves like the legacy "safe" mode (whole-batch auto-accept).
44
+ --profile <name> Improve profile to apply. Built-ins: default, quick,
45
+ thorough, memory-focus. User-defined profiles under
46
+ `profiles.improve.<name>` in config are also accepted.
47
+ Profiles bundle process gating, type filters, and
48
+ run-level autoAccept/limit defaults. Falls back to
49
+ `defaults.improve` in config, then to "default".
50
+ Unknown names fall back to default with a warning.
51
+ Sync behavior by profile: default and thorough enable
52
+ auto-commit + push; quick and memory-focus skip sync.
53
+ --sync Commit (and optionally push) the git-backed primary
54
+ stash when the run finishes. Use --no-sync to disable.
55
+ Default: per profile config (enabled for default and
56
+ thorough, disabled for quick and memory-focus).
57
+ --push Push after the end-of-run sync commit when the stash
58
+ is writable and has a remote configured. Use --no-push
59
+ to commit only. Default: per profile config (true when
60
+ sync is enabled).
41
61
  --consolidate-recovery <mode>
42
62
  Recovery mode for stale consolidate journals: abort (default) or clean
43
63
  --require-feedback-signal
44
64
  Only process refs with recent feedback signal events
45
65
  --min-retrieval-count <n>
46
66
  Retrieval fallback threshold when no recent feedback exists (default: 5)
67
+ --json-to-stdout Emit the full JSON result on stdout (legacy behaviour).
68
+ (0.8.0+: full result is recorded in the improve_runs table of
69
+ state.db and stdout is empty; use --json-to-stdout for the prior
70
+ behaviour, e.g. `akm improve --json-to-stdout | jq`.)
47
71
 
48
72
  Examples:
49
73
  akm improve
@@ -51,3 +75,7 @@ Examples:
51
75
  akm improve skill
52
76
  akm improve skill:code-review
53
77
  akm improve workflow:incident-response --task "reduce duplication"
78
+ akm improve --profile quick
79
+ akm improve --profile memory-focus
80
+ akm improve --no-sync
81
+ akm improve --no-push
@@ -1,15 +1,18 @@
1
1
  Usage:
2
- akm proposals
2
+ akm proposal list
3
3
 
4
4
  Description:
5
5
  List proposal queue entries.
6
6
 
7
+ (`akm proposals` is a deprecated alias for `akm proposal list`; it warns on
8
+ stderr and is removed in 0.9.0.)
9
+
7
10
  Options:
8
11
  --status <status> Filter by pending, accepted, or rejected
9
12
  --type <type> Filter by asset type
10
13
  --ref <ref> Filter by exact asset ref
11
14
 
12
15
  Examples:
13
- akm proposals
14
- akm proposals --status pending
15
- akm proposals --type skill
16
+ akm proposal list
17
+ akm proposal list --status pending
18
+ akm proposal list --type skill
@@ -1,8 +1,11 @@
1
1
  Usage:
2
- akm reject <id> --reason "..."
2
+ akm proposal reject <id> --reason "..."
3
3
 
4
4
  Description:
5
5
  Reject a proposal and record the reason.
6
6
 
7
+ (`akm reject` is a deprecated alias for `akm proposal reject`; it warns on
8
+ stderr and is removed in 0.9.0.)
9
+
7
10
  Examples:
8
- akm reject proposal_123 --reason "duplicates existing workflow"
11
+ akm proposal reject proposal_123 --reason "duplicates existing workflow"
@@ -1,21 +1,10 @@
1
- /**
2
- * `akm history` surfaces internal mutation/usage events for a single asset
3
- * (`--ref`) or stash-wide.
4
- *
5
- * Event sources:
6
- * - `usage_events` SQLite table: search, show, and feedback events recorded
7
- * by the local indexer during normal CLI use.
8
- * - `events.jsonl` append-only stream (opt-in via `--include-proposals`):
9
- * proposal lifecycle events (`promoted`, `rejected`) emitted by
10
- * `akm proposal accept` / `akm proposal reject`. Use this flag to see
11
- * the full proposal review trail alongside usage events.
12
- *
13
- * The two sources are merged and sorted chronologically (oldest first) so
14
- * consumers see a coherent lifecycle trail in a single output.
15
- */
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
16
4
  import { parseAssetRef } from "../core/asset-ref";
17
5
  import { UsageError } from "../core/errors";
18
6
  import { readEvents } from "../core/events";
7
+ import { listProposals } from "../core/proposals";
19
8
  import { isoToSqlite, parseSinceToIso } from "../core/time";
20
9
  import { closeDatabase, openExistingDatabase } from "../indexer/db";
21
10
  // Proposal lifecycle event types emitted by the proposal substrate (#225).
@@ -39,6 +28,7 @@ function toEntry(row) {
39
28
  entryId: row.entry_id,
40
29
  query: row.query,
41
30
  signal: row.signal,
31
+ source: row.source,
42
32
  metadata: parseMetadata(row.metadata),
43
33
  createdAt: row.created_at,
44
34
  };
@@ -91,8 +81,12 @@ export async function akmHistory(options = {}) {
91
81
  conditions.push("created_at >= ?");
92
82
  params.push(sinceNormalized);
93
83
  }
84
+ if (options.source !== undefined) {
85
+ conditions.push("source = ?");
86
+ params.push(options.source);
87
+ }
94
88
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
95
- const sql = `SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, created_at
89
+ const sql = `SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, source, created_at
96
90
  FROM usage_events ${where}
97
91
  ORDER BY id ASC`;
98
92
  const rows = db.prepare(sql).all(...params);
@@ -127,6 +121,7 @@ export async function akmHistory(options = {}) {
127
121
  entryId: null,
128
122
  query: null,
129
123
  signal: null,
124
+ source: null,
130
125
  metadata: event.metadata ?? null,
131
126
  createdAt,
132
127
  });
@@ -142,6 +137,45 @@ export async function akmHistory(options = {}) {
142
137
  return 1;
143
138
  return a.id - b.id;
144
139
  });
140
+ // ── Accept-rate-per-source (F-4 / #385) ─────────────────────────────────
141
+ let acceptRateBySource;
142
+ if (options.acceptRateBySource) {
143
+ const stashDir = options.stashDir;
144
+ if (stashDir) {
145
+ const bySource = new Map();
146
+ const countProposals = (statuses, includeArchive) => {
147
+ for (const status of statuses) {
148
+ const proposals = listProposals(stashDir, { status, includeArchive });
149
+ for (const p of proposals) {
150
+ const src = p.source || "(unknown)";
151
+ const entry = bySource.get(src) ?? { accepted: 0, rejected: 0, pending: 0 };
152
+ if (status === "accepted")
153
+ entry.accepted++;
154
+ else if (status === "rejected")
155
+ entry.rejected++;
156
+ else
157
+ entry.pending++;
158
+ bySource.set(src, entry);
159
+ }
160
+ }
161
+ };
162
+ countProposals(["pending"], false);
163
+ countProposals(["accepted", "rejected"], true);
164
+ acceptRateBySource = Array.from(bySource.entries())
165
+ .map(([source, counts]) => {
166
+ const decided = counts.accepted + counts.rejected;
167
+ return {
168
+ source,
169
+ total: decided + counts.pending,
170
+ accepted: counts.accepted,
171
+ rejected: counts.rejected,
172
+ pending: counts.pending,
173
+ acceptRate: decided > 0 ? counts.accepted / decided : null,
174
+ };
175
+ })
176
+ .sort((a, b) => b.total - a.total); // Most active source first
177
+ }
178
+ }
145
179
  const response = {
146
180
  schemaVersion: 1,
147
181
  ...(normalizedRef !== undefined ? { ref: normalizedRef } : {}),
@@ -149,6 +183,7 @@ export async function akmHistory(options = {}) {
149
183
  totalCount: entries.length,
150
184
  entries,
151
185
  sources,
186
+ ...(acceptRateBySource !== undefined ? { acceptRateBySource } : {}),
152
187
  };
153
188
  return response;
154
189
  }
@@ -0,0 +1,97 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
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";
8
+ // ---------------------------------------------------------------------------
9
+ // Gate implementation
10
+ // ---------------------------------------------------------------------------
11
+ /**
12
+ * Attempt to auto-accept each candidate proposal whose confidence meets the
13
+ * effective threshold. Safe to call unconditionally — returns all-empty when
14
+ * the gate is disabled or the run is a dry-run.
15
+ *
16
+ * @param candidates Proposals to evaluate, each with an optional confidence.
17
+ * @param cfg Gate configuration (phase label, thresholds, context).
18
+ * @param promoteFn Injectable override for `promoteProposal` (test seam).
19
+ */
20
+ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProposal) {
21
+ const result = { promoted: [], skipped: [], failed: [] };
22
+ // --- Guard: gate is disabled or context is incomplete ---
23
+ if (cfg.dryRun || cfg.globalThreshold === undefined || !cfg.stashDir) {
24
+ result.skipped = candidates.map((c) => c.proposalId);
25
+ return result;
26
+ }
27
+ const effectiveThreshold = Math.max(cfg.globalThreshold, cfg.minimumThreshold ?? 0) / 100;
28
+ const resolvedConfig = typeof cfg.config === "function" ? cfg.config() : cfg.config;
29
+ for (const candidate of candidates) {
30
+ const { proposalId, confidence } = candidate;
31
+ if (confidence === undefined || confidence < effectiveThreshold) {
32
+ result.skipped.push(proposalId);
33
+ continue;
34
+ }
35
+ try {
36
+ const promotion = await promoteFn(cfg.stashDir, resolvedConfig, proposalId, {}, undefined);
37
+ appendEvent({
38
+ eventType: "promoted",
39
+ ref: promotion.ref,
40
+ metadata: {
41
+ proposalId: promotion.proposal.id,
42
+ source: promotion.proposal.source,
43
+ ...(promotion.proposal.sourceRun !== undefined ? { sourceRun: promotion.proposal.sourceRun } : {}),
44
+ assetPath: promotion.assetPath,
45
+ autoAccept: true,
46
+ confidence,
47
+ threshold: effectiveThreshold,
48
+ phase: cfg.phase,
49
+ },
50
+ }, cfg.eventsCtx ?? {});
51
+ info(`[improve] auto-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)} >= threshold=${effectiveThreshold.toFixed(2)})`);
52
+ result.promoted.push(proposalId);
53
+ }
54
+ catch (err) {
55
+ warn(`[improve] ${cfg.phase} auto-accept failed for ${proposalId}: ${err instanceof Error ? err.message : String(err)}`);
56
+ result.failed.push(proposalId);
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Confidence resolvers
63
+ // ---------------------------------------------------------------------------
64
+ /**
65
+ * Read the confidence value for an extract proposal.
66
+ * Extract stores confidence at `payload.frontmatter.confidence` (set by
67
+ * extract.ts when the LLM response is parsed), not at the top-level field.
68
+ */
69
+ export function resolveExtractConfidence(proposal) {
70
+ const fm = proposal.payload.frontmatter;
71
+ const fmConf = fm?.confidence;
72
+ if (typeof fmConf === "number")
73
+ return fmConf;
74
+ // Fall back to top-level in case a future extract version normalises the path
75
+ if (typeof proposal.confidence === "number")
76
+ return proposal.confidence;
77
+ return undefined;
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Config builder helpers
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Build a gate config for a phase, inheriting global settings from the
84
+ * improve options. Callers supply only the phase-specific overrides.
85
+ */
86
+ export function makeGateConfig(phase, shared, overrides = {}) {
87
+ return {
88
+ phase,
89
+ globalThreshold: shared.globalThreshold,
90
+ dryRun: shared.dryRun,
91
+ stashDir: shared.stashDir,
92
+ config: shared.config,
93
+ eventsCtx: shared.eventsCtx,
94
+ ...overrides,
95
+ };
96
+ }
97
+ export { loadConfig };
@@ -0,0 +1,236 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import path from "node:path";
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";
16
+ export const improveCommand = defineCommand({
17
+ meta: {
18
+ name: "improve",
19
+ description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when profiles.improve.default.processes.consolidate.enabled is true",
20
+ },
21
+ args: {
22
+ scope: {
23
+ type: "positional",
24
+ description: "Optional asset type or asset ref to improve",
25
+ required: false,
26
+ },
27
+ task: { type: "string", description: "Add extra guidance for this improvement pass" },
28
+ "dry-run": { type: "boolean", description: "Show planned actions without writing", default: false },
29
+ target: { type: "string", description: "Override the write target for accepted proposals" },
30
+ "auto-accept": {
31
+ type: "string",
32
+ description: "Auto-accept proposals at or above this confidence threshold (0-100). Default: disabled. Pass a value 0-100 to enable. 'safe' is an alias for 90. Pass 'false' to be explicit.",
33
+ },
34
+ limit: { type: "string", description: "Maximum number of assets to process (highest utility first)" },
35
+ "timeout-ms": {
36
+ type: "string",
37
+ description: "Wall-clock budget for the entire run in milliseconds (default: 7200000 = 2 hours)",
38
+ },
39
+ "consolidate-recovery": {
40
+ type: "string",
41
+ description: "How to handle stale/incomplete consolidation journals: abort (default) or clean (remove stale journal artifacts)",
42
+ },
43
+ "require-feedback-signal": {
44
+ type: "boolean",
45
+ description: "Only process assets with recent feedback signals (disables retrieval fallback)",
46
+ default: false,
47
+ },
48
+ "min-retrieval-count": {
49
+ type: "string",
50
+ description: "Minimum retrieval count for zero-feedback fallback eligibility (default: 1, set 0 to include all assets regardless of retrieval history)",
51
+ },
52
+ "json-to-stdout": {
53
+ type: "boolean",
54
+ description: "Emit the full JSON result on stdout (legacy behaviour). (0.8.0+: full result is recorded in the improve_runs table of state.db and stdout is empty; use this flag for the prior behaviour, e.g. `akm improve --json-to-stdout | jq`.)",
55
+ default: false,
56
+ },
57
+ profile: {
58
+ type: "string",
59
+ description: "Named improve profile from profiles.improve or built-in profiles (default, quick, thorough, memory-focus). Controls which sub-processes run and which asset types are processed.",
60
+ },
61
+ sync: {
62
+ type: "boolean",
63
+ description: "Commit (and optionally push) the git-backed primary stash when the run finishes. Use --no-sync to disable. Default: on for git-backed stashes (per profile config).",
64
+ },
65
+ push: {
66
+ type: "boolean",
67
+ description: "Push after the end-of-run sync commit when writable + remote configured. Use --no-push to commit only. Default: per profile config (true).",
68
+ },
69
+ },
70
+ async run({ args }) {
71
+ await runWithJsonErrors(async () => {
72
+ const formatFlagValue = parseFlagValue(process.argv, "--format");
73
+ if (formatFlagValue !== undefined) {
74
+ throw new UsageError(`akm improve does not accept --format. That flag controls output formatting for other commands (search, show, etc.).\n` +
75
+ `Did you mean: akm improve (no --format flag)?`, "INVALID_FLAG_VALUE");
76
+ }
77
+ const jsonToStdout = getHyphenatedBoolean(args, "json-to-stdout");
78
+ const autoAcceptRaw = getHyphenatedArg(args, "auto-accept");
79
+ const autoAccept = parseAutoAcceptFlag(autoAcceptRaw);
80
+ const targetArg = getStringArg(args, "target");
81
+ const taskArg = getStringArg(args, "task");
82
+ const dryRun = getHyphenatedBoolean(args, "dry-run");
83
+ const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
84
+ const timeoutMs = parsePositiveIntFlag(getHyphenatedArg(args, "timeout-ms"), "--timeout-ms");
85
+ const consolidateRecoveryRaw = getHyphenatedArg(args, "consolidate-recovery");
86
+ const consolidateRecovery = consolidateRecoveryRaw === undefined
87
+ ? undefined
88
+ : consolidateRecoveryRaw.trim().toLowerCase();
89
+ if (consolidateRecovery !== undefined && consolidateRecovery !== "abort" && consolidateRecovery !== "clean") {
90
+ throw new UsageError(`Invalid --consolidate-recovery value: "${consolidateRecoveryRaw}". Must be one of: abort, clean.`, "INVALID_FLAG_VALUE");
91
+ }
92
+ const minRetrievalCountRaw = getHyphenatedArg(args, "min-retrieval-count");
93
+ const minRetrievalCount = parseNonNegativeIntFlag(minRetrievalCountRaw, "--min-retrieval-count");
94
+ const requireFeedbackSignal = getHyphenatedBoolean(args, "require-feedback-signal");
95
+ const profileArg = getStringArg(args, "profile");
96
+ // Only set the keys the user actually passed (citty leaves the flag
97
+ // undefined unless `--sync`/`--no-sync` / `--push`/`--no-push` appears),
98
+ // so the resolved profile `sync` block wins by default.
99
+ const syncFlag = getHyphenatedArg(args, "sync");
100
+ const pushFlag = getHyphenatedArg(args, "push");
101
+ const syncOverride = {};
102
+ if (syncFlag !== undefined)
103
+ syncOverride.enabled = syncFlag;
104
+ if (pushFlag !== undefined)
105
+ syncOverride.push = pushFlag;
106
+ const improveLogFile = path.join(getCacheDir(), "logs", "improve", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
107
+ setLogFile(improveLogFile);
108
+ const startedAtMs = Date.now();
109
+ const startedAtIso = new Date(startedAtMs).toISOString();
110
+ // Mint the run-id up front so signal handlers can persist a partial
111
+ // record if the process is killed mid-run. Pre-2026-05-26 the runId
112
+ // was minted at end-of-run, so SIGTERM'd runs (cron timeout) left no
113
+ // row in improve_runs and effectively disappeared from `akm health`.
114
+ const runId = buildImproveRunId();
115
+ const primaryStashDir = resolveSourceEntries(undefined, loadConfig())[0]?.path;
116
+ const scopeArg = getStringArg(args, "scope");
117
+ const inferredScopeMode = (scopeArg ?? "").includes(":") ? "ref" : scopeArg ? "type" : "all";
118
+ // Signal handler + exception path both flow through this helper so
119
+ // every abnormal termination produces a row with ok:false and a
120
+ // reason in metadata.terminated.
121
+ let runRecorded = false;
122
+ const persistTerminated = (reason, errorMessage) => {
123
+ if (runRecorded)
124
+ return;
125
+ if (!primaryStashDir)
126
+ return;
127
+ runRecorded = true;
128
+ try {
129
+ recordTerminatedImproveRun(primaryStashDir, runId, startedAtIso, reason, {
130
+ scopeMode: inferredScopeMode,
131
+ scopeValue: scopeArg ?? null,
132
+ dryRun: Boolean(dryRun),
133
+ profile: profileArg ?? null,
134
+ ...(errorMessage ? { errorMessage } : {}),
135
+ });
136
+ }
137
+ catch (err) {
138
+ process.stderr.write(`warning: failed to persist terminated improve run ${runId}: ${err instanceof Error ? err.message : String(err)}\n`);
139
+ }
140
+ };
141
+ const sigtermHandler = () => {
142
+ persistTerminated("SIGTERM");
143
+ process.stderr.write(`[improve] received SIGTERM; recorded terminated run ${runId}\n`);
144
+ process.exit(143);
145
+ };
146
+ const sigintHandler = () => {
147
+ persistTerminated("SIGINT");
148
+ process.stderr.write(`[improve] received SIGINT; recorded terminated run ${runId}\n`);
149
+ process.exit(130);
150
+ };
151
+ const sighupHandler = () => {
152
+ persistTerminated("SIGHUP");
153
+ process.exit(129);
154
+ };
155
+ process.once("SIGTERM", sigtermHandler);
156
+ process.once("SIGINT", sigintHandler);
157
+ process.once("SIGHUP", sighupHandler);
158
+ let improveResult;
159
+ try {
160
+ improveResult = await akmImprove({
161
+ scope: scopeArg,
162
+ task: taskArg,
163
+ dryRun,
164
+ target: targetArg,
165
+ autoAccept,
166
+ ...(limitRaw !== undefined ? { limit: limitRaw } : {}),
167
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
168
+ ...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
169
+ ...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
170
+ ...(profileArg !== undefined ? { profile: profileArg } : {}),
171
+ ...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
172
+ consolidateOptions: {
173
+ target: targetArg,
174
+ dryRun,
175
+ autoAccept,
176
+ task: taskArg,
177
+ ...(consolidateRecovery !== undefined ? { recoveryMode: consolidateRecovery } : {}),
178
+ },
179
+ });
180
+ }
181
+ catch (err) {
182
+ // akmImprove threw — record the failure before letting runWithJsonErrors
183
+ // emit the standard JSON error envelope. Without this, exceptions in
184
+ // the main loop (LLM provider crash, OOM, etc.) leave no improve_runs
185
+ // row, matching the SIGTERM gap.
186
+ persistTerminated("exception", err instanceof Error ? err.message : String(err));
187
+ throw err;
188
+ }
189
+ finally {
190
+ process.removeListener("SIGTERM", sigtermHandler);
191
+ process.removeListener("SIGINT", sigintHandler);
192
+ process.removeListener("SIGHUP", sighupHandler);
193
+ clearLogFile();
194
+ }
195
+ const durationMs = Date.now() - startedAtMs;
196
+ if (jsonToStdout) {
197
+ // Legacy / escape-hatch mode: full JSON on stdout, no file write.
198
+ // Kept for scripts/agents that already pipe to jq.
199
+ output("improve", improveResult);
200
+ process.exit(0);
201
+ }
202
+ // Default mode (0.8.0+): persist the full result as a row in the
203
+ // `improve_runs` table of state.db (migration 003) and emit NOTHING
204
+ // on stdout. The verbose JSON would otherwise scroll earlier progress
205
+ // logs out of the terminal buffer. The existing `[improve] ...`
206
+ // progress log lines on stderr remain the canonical console UX —
207
+ // do NOT add any new console output here.
208
+ //
209
+ // Pre-0.8.0 wrote `<stash>/.akm/runs/<run-id>/improve-result.json`;
210
+ // those files are no longer authored. Query recent runs with:
211
+ // sqlite3 "$AKM_DATA_DIR/state.db" \
212
+ // "SELECT id, started_at, ok, dry_run FROM improve_runs \
213
+ // ORDER BY started_at DESC LIMIT 10"
214
+ // runId + primaryStashDir minted up-top so signal handlers can record
215
+ // partial runs; reuse them here for the success path.
216
+ const resultRef = relativeImproveResultPath(runId);
217
+ runRecorded = true; // Suppress any late signal-handler write — the success path owns the row now.
218
+ if (primaryStashDir) {
219
+ try {
220
+ writeImproveResultFile(primaryStashDir, runId, improveResult);
221
+ }
222
+ catch (err) {
223
+ // Stderr warning on the failure path is preferable to crashing
224
+ // the run after all the work has completed.
225
+ process.stderr.write(`warning: failed to record improve run ${resultRef}: ${err instanceof Error ? err.message : String(err)}\n`);
226
+ }
227
+ }
228
+ else {
229
+ process.stderr.write(`warning: no writable stash directory resolved; improve result not persisted to state.db (use --json-to-stdout to capture)\n`);
230
+ }
231
+ // durationMs reserved for future use (no console emission today).
232
+ void durationMs;
233
+ process.exit(0);
234
+ });
235
+ },
236
+ });