akm-cli 0.8.0-rc2 → 0.8.1

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 (313) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +238 -3
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/assets/help/help-accept.md +12 -0
  5. package/dist/assets/help/help-improve.md +81 -0
  6. package/dist/{commands → assets}/help/help-proposals.md +7 -4
  7. package/dist/assets/help/help-reject.md +11 -0
  8. package/dist/{output → assets/hints}/cli-hints-full.md +60 -32
  9. package/dist/{output → assets/hints}/cli-hints-short.md +10 -7
  10. package/dist/assets/profiles/default.json +15 -0
  11. package/dist/assets/profiles/graph-refresh.json +13 -0
  12. package/dist/assets/profiles/memory-focus.json +12 -0
  13. package/dist/assets/profiles/quick.json +15 -0
  14. package/dist/assets/profiles/thorough.json +15 -0
  15. package/dist/assets/prompts/extract-session.md +80 -0
  16. package/dist/assets/prompts/graph-extract-user-prompt.md +35 -0
  17. package/dist/assets/tasks/graph-refresh-weekly.yml +10 -0
  18. package/dist/cli/config-migrate.js +144 -0
  19. package/dist/cli/config-validate.js +39 -0
  20. package/dist/cli/confirm.js +73 -0
  21. package/dist/cli/parse-args.js +93 -3
  22. package/dist/cli/shared.js +129 -0
  23. package/dist/cli.js +2141 -1268
  24. package/dist/commands/add-cli.js +279 -0
  25. package/dist/commands/agent-dispatch.js +20 -12
  26. package/dist/commands/agent-support.js +11 -5
  27. package/dist/commands/completions.js +3 -0
  28. package/dist/commands/config-cli.js +129 -517
  29. package/dist/commands/consolidate.js +1557 -147
  30. package/dist/commands/curate.js +44 -3
  31. package/dist/commands/db-cli.js +23 -0
  32. package/dist/commands/distill-promotion-policy.js +5 -3
  33. package/dist/commands/distill.js +906 -100
  34. package/dist/commands/env.js +213 -0
  35. package/dist/commands/eval-cases.js +3 -0
  36. package/dist/commands/events.js +3 -0
  37. package/dist/commands/extract-cli.js +127 -0
  38. package/dist/commands/extract-prompt.js +217 -0
  39. package/dist/commands/extract.js +477 -0
  40. package/dist/commands/feedback-cli.js +331 -0
  41. package/dist/commands/graph.js +260 -5
  42. package/dist/commands/health.js +1042 -55
  43. package/dist/commands/history.js +51 -16
  44. package/dist/commands/improve-auto-accept.js +97 -0
  45. package/dist/commands/improve-cli.js +236 -0
  46. package/dist/commands/improve-profiles.js +138 -0
  47. package/dist/commands/improve-result-file.js +167 -0
  48. package/dist/commands/improve.js +1736 -346
  49. package/dist/commands/info.js +26 -28
  50. package/dist/commands/init.js +49 -1
  51. package/dist/commands/installed-stashes.js +6 -23
  52. package/dist/commands/knowledge.js +3 -0
  53. package/dist/commands/lint/agent-linter.js +3 -0
  54. package/dist/commands/lint/base-linter.js +199 -5
  55. package/dist/commands/lint/command-linter.js +3 -0
  56. package/dist/commands/lint/default-linter.js +3 -0
  57. package/dist/commands/lint/env-key-rules.js +154 -0
  58. package/dist/commands/lint/index.js +92 -3
  59. package/dist/commands/lint/knowledge-linter.js +3 -0
  60. package/dist/commands/lint/markdown-insertion.js +343 -0
  61. package/dist/commands/lint/memory-linter.js +3 -0
  62. package/dist/commands/lint/registry.js +3 -0
  63. package/dist/commands/lint/skill-linter.js +3 -0
  64. package/dist/commands/lint/task-linter.js +15 -12
  65. package/dist/commands/lint/types.js +3 -0
  66. package/dist/commands/lint/workflow-linter.js +3 -0
  67. package/dist/commands/lint.js +3 -0
  68. package/dist/commands/migration-help.js +5 -2
  69. package/dist/commands/proposal-drain-policies.js +128 -0
  70. package/dist/commands/proposal-drain.js +477 -0
  71. package/dist/commands/proposal.js +60 -6
  72. package/dist/commands/propose.js +24 -19
  73. package/dist/commands/reflect.js +1004 -94
  74. package/dist/commands/registry-cli.js +150 -0
  75. package/dist/commands/registry-search.js +3 -0
  76. package/dist/commands/remember-cli.js +257 -0
  77. package/dist/commands/remember.js +15 -6
  78. package/dist/commands/schema-repair.js +88 -15
  79. package/dist/commands/search.js +99 -14
  80. package/dist/commands/secret.js +173 -0
  81. package/dist/commands/self-update.js +3 -0
  82. package/dist/commands/show.js +32 -13
  83. package/dist/commands/source-add.js +7 -35
  84. package/dist/commands/source-clone.js +3 -0
  85. package/dist/commands/source-manage.js +3 -0
  86. package/dist/commands/tasks.js +161 -95
  87. package/dist/commands/url-checker.js +3 -0
  88. package/dist/core/action-contributors.js +3 -0
  89. package/dist/core/asset-ref.js +13 -2
  90. package/dist/core/asset-registry.js +9 -2
  91. package/dist/core/asset-serialize.js +88 -0
  92. package/dist/core/asset-spec.js +61 -5
  93. package/dist/core/common.js +93 -5
  94. package/dist/core/concurrent.js +3 -0
  95. package/dist/core/config-io.js +347 -0
  96. package/dist/core/config-migration.js +622 -0
  97. package/dist/core/config-schema.js +558 -0
  98. package/dist/core/config-sources.js +108 -0
  99. package/dist/core/config-types.js +4 -0
  100. package/dist/core/config-walker.js +337 -0
  101. package/dist/core/config.js +366 -1077
  102. package/dist/core/errors.js +42 -20
  103. package/dist/core/events.js +31 -25
  104. package/dist/core/file-lock.js +104 -0
  105. package/dist/core/frontmatter.js +75 -10
  106. package/dist/core/lesson-lint.js +3 -0
  107. package/dist/core/markdown.js +3 -0
  108. package/dist/core/memory-belief.js +62 -0
  109. package/dist/core/memory-contradiction-detect.js +274 -0
  110. package/dist/core/memory-improve.js +142 -14
  111. package/dist/core/parse.js +3 -0
  112. package/dist/core/paths.js +218 -50
  113. package/dist/core/proposal-quality-validators.js +380 -0
  114. package/dist/core/proposal-validators.js +11 -3
  115. package/dist/core/proposals.js +464 -5
  116. package/dist/core/state-db.js +349 -56
  117. package/dist/core/text-truncation.js +107 -0
  118. package/dist/core/time.js +3 -0
  119. package/dist/core/tty.js +59 -0
  120. package/dist/core/warn.js +7 -2
  121. package/dist/core/write-source.js +12 -0
  122. package/dist/indexer/db-backup.js +391 -0
  123. package/dist/indexer/db-search.js +136 -28
  124. package/dist/indexer/db.js +661 -166
  125. package/dist/indexer/ensure-index.js +3 -0
  126. package/dist/indexer/file-context.js +3 -0
  127. package/dist/indexer/graph-boost.js +162 -40
  128. package/dist/indexer/graph-db.js +241 -51
  129. package/dist/indexer/graph-dedup.js +3 -7
  130. package/dist/indexer/graph-extraction.js +242 -149
  131. package/dist/indexer/index-context.js +3 -9
  132. package/dist/indexer/indexer.js +86 -16
  133. package/dist/indexer/llm-cache.js +24 -19
  134. package/dist/indexer/manifest.js +3 -0
  135. package/dist/indexer/matchers.js +184 -11
  136. package/dist/indexer/memory-inference.js +94 -50
  137. package/dist/indexer/metadata-contributors.js +3 -0
  138. package/dist/indexer/metadata.js +110 -50
  139. package/dist/indexer/path-resolver.js +3 -0
  140. package/dist/indexer/project-context.js +192 -0
  141. package/dist/indexer/ranking-contributors.js +134 -7
  142. package/dist/indexer/ranking.js +8 -1
  143. package/dist/indexer/search-fields.js +5 -9
  144. package/dist/indexer/search-hit-enrichers.js +91 -2
  145. package/dist/indexer/search-source.js +20 -1
  146. package/dist/indexer/semantic-status.js +4 -1
  147. package/dist/indexer/staleness-detect.js +447 -0
  148. package/dist/indexer/usage-events.js +12 -9
  149. package/dist/indexer/walker.js +3 -0
  150. package/dist/integrations/agent/builders.js +135 -0
  151. package/dist/integrations/agent/config.js +121 -401
  152. package/dist/integrations/agent/detect.js +3 -0
  153. package/dist/integrations/agent/index.js +6 -14
  154. package/dist/integrations/agent/model-aliases.js +55 -0
  155. package/dist/integrations/agent/profiles.js +3 -0
  156. package/dist/integrations/agent/prompts.js +137 -8
  157. package/dist/integrations/agent/runner.js +208 -0
  158. package/dist/integrations/agent/sdk-runner.js +8 -2
  159. package/dist/integrations/agent/spawn.js +54 -14
  160. package/dist/integrations/github.js +3 -0
  161. package/dist/integrations/lockfile.js +22 -51
  162. package/dist/integrations/session-logs/index.js +4 -0
  163. package/dist/integrations/session-logs/inline-refs.js +35 -0
  164. package/dist/integrations/session-logs/pre-filter.js +152 -0
  165. package/dist/integrations/session-logs/providers/claude-code.js +226 -0
  166. package/dist/integrations/session-logs/providers/opencode.js +231 -25
  167. package/dist/integrations/session-logs/types.js +3 -0
  168. package/dist/llm/call-ai.js +14 -26
  169. package/dist/llm/client.js +16 -2
  170. package/dist/llm/embedder.js +20 -29
  171. package/dist/llm/embedders/cache.js +3 -7
  172. package/dist/llm/embedders/local.js +42 -1
  173. package/dist/llm/embedders/remote.js +20 -8
  174. package/dist/llm/embedders/types.js +3 -7
  175. package/dist/llm/feature-gate.js +92 -56
  176. package/dist/llm/graph-extract.js +402 -31
  177. package/dist/llm/index-passes.js +44 -29
  178. package/dist/llm/memory-infer.js +30 -2
  179. package/dist/llm/metadata-enhance.js +3 -7
  180. package/dist/output/cli-hints.js +7 -4
  181. package/dist/output/context.js +60 -8
  182. package/dist/output/renderers.js +170 -194
  183. package/dist/output/shapes/curate.js +56 -0
  184. package/dist/output/shapes/distill.js +10 -0
  185. package/dist/output/shapes/env-list.js +19 -0
  186. package/dist/output/shapes/events.js +11 -0
  187. package/dist/output/shapes/helpers.js +424 -0
  188. package/dist/output/shapes/history.js +7 -0
  189. package/dist/output/shapes/passthrough.js +105 -0
  190. package/dist/output/shapes/proposal-accept.js +7 -0
  191. package/dist/output/shapes/proposal-diff.js +7 -0
  192. package/dist/output/shapes/proposal-list.js +7 -0
  193. package/dist/output/shapes/proposal-producer.js +11 -0
  194. package/dist/output/shapes/proposal-reject.js +7 -0
  195. package/dist/output/shapes/proposal-show.js +7 -0
  196. package/dist/output/shapes/registry-search.js +6 -0
  197. package/dist/output/shapes/registry.js +30 -0
  198. package/dist/output/shapes/search.js +6 -0
  199. package/dist/output/shapes/secret-list.js +19 -0
  200. package/dist/output/shapes/show.js +6 -0
  201. package/dist/output/shapes/vault-list.js +19 -0
  202. package/dist/output/shapes.js +51 -549
  203. package/dist/output/text/add.js +6 -0
  204. package/dist/output/text/clone.js +6 -0
  205. package/dist/output/text/config.js +6 -0
  206. package/dist/output/text/curate.js +6 -0
  207. package/dist/output/text/distill.js +7 -0
  208. package/dist/output/text/enable-disable.js +7 -0
  209. package/dist/output/text/events.js +10 -0
  210. package/dist/output/text/feedback.js +6 -0
  211. package/dist/output/text/helpers.js +1059 -0
  212. package/dist/output/text/history.js +7 -0
  213. package/dist/output/text/import.js +6 -0
  214. package/dist/output/text/index.js +6 -0
  215. package/dist/output/text/info.js +6 -0
  216. package/dist/output/text/init.js +6 -0
  217. package/dist/output/text/list.js +6 -0
  218. package/dist/output/text/proposal-producer.js +8 -0
  219. package/dist/output/text/proposal.js +12 -0
  220. package/dist/output/text/registry-commands.js +11 -0
  221. package/dist/output/text/registry.js +30 -0
  222. package/dist/output/text/remember.js +6 -0
  223. package/dist/output/text/remove.js +6 -0
  224. package/dist/output/text/save.js +6 -0
  225. package/dist/output/text/search.js +6 -0
  226. package/dist/output/text/show.js +6 -0
  227. package/dist/output/text/update.js +6 -0
  228. package/dist/output/text/upgrade.js +6 -0
  229. package/dist/output/text/vault.js +16 -0
  230. package/dist/output/text/wiki.js +15 -0
  231. package/dist/output/text/workflow.js +14 -0
  232. package/dist/output/text.js +44 -1329
  233. package/dist/registry/build-index.js +3 -0
  234. package/dist/registry/create-provider-registry.js +3 -0
  235. package/dist/registry/factory.js +4 -1
  236. package/dist/registry/origin-resolve.js +3 -0
  237. package/dist/registry/providers/index.js +3 -0
  238. package/dist/registry/providers/skills-sh.js +11 -2
  239. package/dist/registry/providers/static-index.js +10 -1
  240. package/dist/registry/providers/types.js +3 -24
  241. package/dist/registry/resolve.js +11 -16
  242. package/dist/registry/types.js +3 -0
  243. package/dist/scripts/migrate-storage.js +17767 -0
  244. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  245. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  246. package/dist/setup/detect.js +3 -0
  247. package/dist/setup/ripgrep-install.js +3 -0
  248. package/dist/setup/ripgrep-resolve.js +3 -0
  249. package/dist/setup/setup.js +306 -67
  250. package/dist/setup/steps.js +3 -15
  251. package/dist/sources/include.js +3 -0
  252. package/dist/sources/provider-factory.js +3 -11
  253. package/dist/sources/provider.js +3 -20
  254. package/dist/sources/providers/filesystem.js +19 -23
  255. package/dist/sources/providers/git.js +171 -21
  256. package/dist/sources/providers/index.js +3 -0
  257. package/dist/sources/providers/install-types.js +3 -13
  258. package/dist/sources/providers/npm.js +3 -4
  259. package/dist/sources/providers/provider-utils.js +3 -0
  260. package/dist/sources/providers/sync-from-ref.js +3 -11
  261. package/dist/sources/providers/tar-utils.js +3 -0
  262. package/dist/sources/providers/website.js +18 -22
  263. package/dist/sources/resolve.js +3 -0
  264. package/dist/sources/types.js +3 -0
  265. package/dist/sources/website-ingest.js +3 -0
  266. package/dist/tasks/backends/cron.js +3 -0
  267. package/dist/tasks/backends/exec-utils.js +3 -0
  268. package/dist/tasks/backends/index.js +3 -11
  269. package/dist/tasks/backends/launchd.js +4 -1
  270. package/dist/tasks/backends/schtasks.js +4 -1
  271. package/dist/tasks/parser.js +51 -38
  272. package/dist/tasks/resolveAkmBin.js +3 -0
  273. package/dist/tasks/runner.js +35 -9
  274. package/dist/tasks/schedule.js +20 -1
  275. package/dist/tasks/schema.js +5 -3
  276. package/dist/tasks/validator.js +6 -3
  277. package/dist/version.js +3 -0
  278. package/dist/wiki/wiki-templates.js +6 -3
  279. package/dist/wiki/wiki.js +4 -1
  280. package/dist/workflows/authoring.js +4 -1
  281. package/dist/workflows/cli.js +3 -0
  282. package/dist/workflows/db.js +140 -10
  283. package/dist/workflows/document-cache.js +3 -10
  284. package/dist/workflows/parser.js +3 -0
  285. package/dist/workflows/renderer.js +3 -0
  286. package/dist/workflows/runs.js +18 -1
  287. package/dist/workflows/schema.js +3 -0
  288. package/dist/workflows/scope-key.js +3 -0
  289. package/dist/workflows/validator.js +5 -9
  290. package/docs/README.md +7 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.5.md +2 -2
  293. package/docs/migration/release-notes/0.8.0.md +57 -5
  294. package/docs/migration/v0.7-to-v0.8.md +1378 -0
  295. package/package.json +28 -11
  296. package/.github/LICENSE +0 -374
  297. package/dist/commands/help/help-accept.md +0 -9
  298. package/dist/commands/help/help-improve.md +0 -53
  299. package/dist/commands/help/help-reject.md +0 -8
  300. package/dist/commands/install-audit.js +0 -385
  301. package/dist/commands/vault.js +0 -310
  302. package/dist/indexer/match-contributors.js +0 -141
  303. package/dist/integrations/agent/pipeline.js +0 -39
  304. package/dist/integrations/agent/runners.js +0 -31
  305. package/dist/llm/prompts/graph-extract-user-prompt.md +0 -12
  306. /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
  307. /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
  308. /package/dist/{commands → assets}/help/help-propose.md +0 -0
  309. /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
  310. /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
  311. /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
  312. /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
  313. /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
@@ -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, graph-refresh). 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
+ });
@@ -0,0 +1,138 @@
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 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";
11
+ /** Profile name used as the final fallback when nothing else resolves. */
12
+ const FALLBACK_PROFILE_NAME = "default";
13
+ // Built-in default allowed types per process
14
+ export const DEFAULT_ALLOWED_TYPES = {
15
+ reflect: ["agent", "command", "knowledge", "lesson", "memory", "skill", "wiki", "workflow"],
16
+ distill: ["memory"],
17
+ consolidate: ["memory"],
18
+ };
19
+ // Built-in profiles are loaded from embedded JSON files in src/assets/profiles/.
20
+ // To add a new profile: create a new .json file there, import it above, and add
21
+ // it to this map. No code change needed beyond those two steps.
22
+ const BUILTIN_PROFILES = {
23
+ default: profileDefault,
24
+ quick: profileQuick,
25
+ thorough: profileThorough,
26
+ "memory-focus": profileMemoryFocus,
27
+ "graph-refresh": profileGraphRefresh,
28
+ };
29
+ /**
30
+ * Default enabled-state for known improve processes when neither the user
31
+ * profile nor the built-in default profile specifies an override.
32
+ *
33
+ * These mirror the legacy `LlmFeatureFlags` defaults so callers that bypass
34
+ * the profile system (rare — most run through `resolveImproveProfile`) get
35
+ * the same answer.
36
+ */
37
+ const IMPROVE_PROCESS_DEFAULTS = {
38
+ reflect: true,
39
+ distill: true,
40
+ consolidate: true,
41
+ memoryInference: true,
42
+ graphExtraction: true,
43
+ validation: false,
44
+ // session-extraction reads native session files from claude-code / opencode
45
+ // and queues durable-insight proposals. Default on — opt out via
46
+ // profiles.improve.default.processes.extract.enabled: false.
47
+ extract: true,
48
+ // proposal-queue triage drains the standing backlog. Opt-in (default off),
49
+ // like `validation` — needs an explicit `enabled: true`.
50
+ triage: false,
51
+ };
52
+ /**
53
+ * Compute the effective enabled-state for a named improve process.
54
+ *
55
+ * Resolution order: explicit `profile.processes.<name>.enabled` (boolean) →
56
+ * the built-in {@link IMPROVE_PROCESS_DEFAULTS} fallback → `false`.
57
+ */
58
+ export function resolveProcessEnabled(processName, profile) {
59
+ const processes = profile.processes;
60
+ const entry = processes?.[processName];
61
+ if (entry && typeof entry.enabled === "boolean")
62
+ return entry.enabled;
63
+ return IMPROVE_PROCESS_DEFAULTS[processName] ?? false;
64
+ }
65
+ function deepMerge(base, override) {
66
+ if (typeof base !== "object" || base === null)
67
+ return override ?? base;
68
+ const result = { ...base };
69
+ for (const key of Object.keys(override)) {
70
+ const ov = override[key];
71
+ // Treat `null` the same as `undefined` so user overrides never wipe a
72
+ // built-in field with `null`. The on-disk parser already strips nulls,
73
+ // but the programmatic API exposes this path and callers occasionally
74
+ // pass JSON-shaped objects with explicit nulls.
75
+ if (ov !== undefined && ov !== null) {
76
+ const bv = base[key];
77
+ if (typeof bv === "object" && bv !== null && typeof ov === "object" && ov !== null && !Array.isArray(bv)) {
78
+ result[key] = deepMerge(bv, ov);
79
+ }
80
+ else {
81
+ result[key] = ov;
82
+ }
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ export function resolveImproveProfile(name, config) {
88
+ const requestedName = name ??
89
+ (typeof config.defaults?.improve === "string" ? config.defaults.improve : undefined) ??
90
+ FALLBACK_PROFILE_NAME;
91
+ const hasBuiltin = requestedName in BUILTIN_PROFILES;
92
+ const hasUserDefined = !!config.profiles?.improve?.[requestedName];
93
+ let effectiveName = requestedName;
94
+ if (!hasBuiltin && !hasUserDefined && requestedName !== FALLBACK_PROFILE_NAME) {
95
+ warn(`[akm] Improve profile "${requestedName}" not found in built-ins or config. ` +
96
+ `Falling back to "${FALLBACK_PROFILE_NAME}".`);
97
+ effectiveName = FALLBACK_PROFILE_NAME;
98
+ }
99
+ const builtin = BUILTIN_PROFILES[effectiveName] ?? BUILTIN_PROFILES[FALLBACK_PROFILE_NAME];
100
+ const userOverride = config.profiles?.improve?.[effectiveName] ?? {};
101
+ return deepMerge(builtin, userOverride);
102
+ }
103
+ export function shouldSkipRef(ref, processName, profile) {
104
+ const cfg = profile.processes?.[processName];
105
+ // Check if the process itself is disabled
106
+ if (cfg?.enabled === false)
107
+ return { skip: true, reason: "process-disabled" };
108
+ const parsed = parseAssetRef(ref);
109
+ const allowed = cfg?.allowedTypes ?? DEFAULT_ALLOWED_TYPES[processName];
110
+ if (!allowed.includes(parsed.type))
111
+ return { skip: true, reason: "type-filter" };
112
+ // Hardcoded: wiki raw directories are never processed by any improve process.
113
+ if (parsed.type === "wiki" && parsed.name.split("/")[1] === "raw") {
114
+ return { skip: true, reason: "raw-wiki" };
115
+ }
116
+ return { skip: false, reason: "" };
117
+ }
118
+ /**
119
+ * Planner-level pre-filter: return `true` when every per-ref improve pass that
120
+ * participates in the in-loop dispatch (today: `reflect` and `distill`) would
121
+ * refuse this ref under the active profile. Such refs cannot produce any work
122
+ * downstream — they only generate synthetic skip actions and inflate
123
+ * `plannedRefs` by a constant factor per cron run.
124
+ *
125
+ * Companion to `shouldSkipRef`. The 2026-05-27 planner/profile/metrics deep
126
+ * analysis (`/tmp/akm-health-investigations/planner-profile-metrics-deep-analysis.md`)
127
+ * documents the 99.07% synthetic-skip emission rate this pre-filter eliminates.
128
+ *
129
+ * NOTE: passes that operate on their own candidate set (consolidate,
130
+ * memoryInference, graphExtraction) are deliberately excluded — they do not
131
+ * iterate `plannedRefs` per-ref, so a ref being profile-incompatible at the
132
+ * reflect+distill layer says nothing about their work.
133
+ */
134
+ export function isProfileFilteredForAllPasses(ref, profile) {
135
+ const reflectSkip = shouldSkipRef(ref, "reflect", profile);
136
+ const distillSkip = shouldSkipRef(ref, "distill", profile);
137
+ return reflectSkip.skip && distillSkip.skip;
138
+ }