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
@@ -49,11 +49,13 @@
49
49
  *
50
50
  * @module state-db
51
51
  */
52
- import { Database } from "bun:sqlite";
53
52
  import fs from "node:fs";
54
53
  import path from "node:path";
55
- import { getDataDir } from "./paths";
56
- import { error } from "./warn";
54
+ import { openDatabase } from "../storage/database.js";
55
+ import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
56
+ import { classifyImproveAction } from "./improve-types.js";
57
+ import { getDataDir } from "./paths.js";
58
+ import { error } from "./warn.js";
57
59
  // ── Path helper ──────────────────────────────────────────────────────────────
58
60
  /**
59
61
  * Default path: `<dataDir>/state.db`.
@@ -84,11 +86,12 @@ export function getStateDbPath() {
84
86
  * backwards compatibility; enabling them prevents orphaned rows in tables
85
87
  * that reference each other (not used in v1 schema but guards future ones).
86
88
  *
87
- * busy_timeout = 5000
89
+ * busy_timeout = 30000
88
90
  * When another connection holds a write lock, SQLite retries for up to
89
- * 5 000 ms before returning SQLITE_BUSY. Without this, the default timeout
90
- * is 0 ms — any concurrent writer causes an immediate error. 5 s matches
91
- * the same value used in openDatabase() for index.db.
91
+ * 30 000 ms before returning SQLITE_BUSY. Without this, the default timeout
92
+ * is 0 ms — any concurrent writer causes an immediate error. 30 s (#589)
93
+ * matches the value used in openDatabase() for index.db; 5 s proved too
94
+ * narrow when a post-inference reindex overlapped a parallel event write.
92
95
  */
93
96
  export function openStateDatabase(dbPath) {
94
97
  const resolvedPath = dbPath ?? getStateDbPath();
@@ -96,14 +99,20 @@ export function openStateDatabase(dbPath) {
96
99
  if (!fs.existsSync(dir)) {
97
100
  fs.mkdirSync(dir, { recursive: true });
98
101
  }
99
- const db = new Database(resolvedPath);
102
+ const db = openDatabase(resolvedPath);
100
103
  // PRAGMAs must run before any DDL or DML.
101
104
  db.exec("PRAGMA journal_mode = WAL");
102
105
  db.exec("PRAGMA foreign_keys = ON");
103
- db.exec("PRAGMA busy_timeout = 5000");
106
+ db.exec("PRAGMA busy_timeout = 30000");
104
107
  runMigrations(db);
105
108
  return db;
106
109
  }
110
+ // ── Migration engine ─────────────────────────────────────────────────────────
111
+ //
112
+ // The runner itself (ensureMigrationsTable + runMigrations) lives in the shared
113
+ // engine at src/storage/engines/sqlite-migrations.ts. This module owns only its
114
+ // own MIGRATIONS array and delegates application to that shared runner. The
115
+ // {@link Migration} interface is imported from there.
107
116
  /**
108
117
  * All migrations in application order. New migrations are APPENDED to this
109
118
  * array — never inserted in the middle or reordered.
@@ -182,7 +191,9 @@ const MIGRATIONS = [
182
191
  --
183
192
  -- Extensible (metadata_json) columns:
184
193
  -- metadata_json TEXT — JSON object for future proposal fields.
185
- -- Current fields stored here: sourceRun, review.
194
+ -- Current fields stored here: sourceRun,
195
+ -- review, confidence, gateDecision (#577),
196
+ -- backupContent.
186
197
  --
187
198
  -- ADD COLUMN extension points (future migrations):
188
199
  -- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
@@ -450,42 +461,44 @@ const MIGRATIONS = [
450
461
  ON extract_sessions_seen(processed_at);
451
462
  `,
452
463
  },
464
+ // ── Migration 005 — proposal_fs_imports ─────────────────────────────────────
465
+ //
466
+ // One-shot ledger for the legacy filesystem→SQLite proposal import (#578).
467
+ //
468
+ // Before 0.9.0 the proposal queue lived as per-uuid JSON directories under
469
+ // `<stashDir>/.akm/proposals/` and the `proposals` table (created in 001) was
470
+ // dead weight. 0.9.0 makes the table canonical; the first proposal operation
471
+ // against a stash imports any legacy `proposal.json` files it finds (INSERT
472
+ // OR IGNORE, so re-runs never duplicate) and records the stash here so later
473
+ // invocations skip the directory walk entirely.
474
+ //
475
+ // Indexed (query) columns:
476
+ // stash_dir TEXT PK — absolute stash root the import ran against.
477
+ //
478
+ // Non-indexed columns:
479
+ // imported_at TEXT — ISO-8601 UTC; when the import completed.
480
+ // imported_count INTEGER — rows actually inserted by the import.
481
+ {
482
+ id: "005-proposal-fs-imports",
483
+ up: `
484
+ CREATE TABLE IF NOT EXISTS proposal_fs_imports (
485
+ stash_dir TEXT PRIMARY KEY,
486
+ imported_at TEXT NOT NULL,
487
+ imported_count INTEGER NOT NULL DEFAULT 0
488
+ );
489
+ `,
490
+ },
453
491
  ];
454
- /**
455
- * Create the migrations table if it does not exist. This must be called
456
- * unconditionally on every open so a fresh database bootstraps correctly.
457
- */
458
- function ensureMigrationsTable(db) {
459
- db.exec(`
460
- CREATE TABLE IF NOT EXISTS schema_migrations (
461
- id TEXT PRIMARY KEY,
462
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
463
- );
464
- `);
465
- }
466
492
  /**
467
493
  * Apply every pending migration in a single transaction per migration.
468
494
  *
469
- * Each migration is applied in its own transaction so a failure in migration N
470
- * does not roll back already-applied migrations 1..N-1. The migration row is
471
- * inserted AFTER the DDL succeeds, so a crash mid-migration leaves no row and
472
- * the migration will be retried on next open (all DDL in `up` uses IF NOT
473
- * EXISTS so the retry is safe).
495
+ * Delegates to the shared SQLite migration engine; state.db has no
496
+ * pre-versioning bootstrap step, so no `bootstrap` hook is passed.
474
497
  *
475
498
  * Called automatically by `openStateDatabase()`.
476
499
  */
477
500
  export function runMigrations(db) {
478
- ensureMigrationsTable(db);
479
- const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
480
- const applied = new Set(appliedRows.map((r) => r.id));
481
- for (const migration of MIGRATIONS) {
482
- if (applied.has(migration.id))
483
- continue;
484
- db.transaction(() => {
485
- db.exec(migration.up);
486
- db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
487
- })();
488
- }
501
+ runSqliteMigrations(db, MIGRATIONS);
489
502
  }
490
503
  /**
491
504
  * Convert a raw `EventRow` from the database to the public `EventEnvelope`
@@ -546,6 +559,9 @@ export function proposalRowToProposal(row) {
546
559
  ...(frontmatter !== undefined ? { frontmatter } : {}),
547
560
  },
548
561
  ...(meta.review !== undefined ? { review: meta.review } : {}),
562
+ ...(typeof meta.confidence === "number" ? { confidence: meta.confidence } : {}),
563
+ ...(meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {}),
564
+ ...(typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {}),
549
565
  };
550
566
  }
551
567
  /**
@@ -559,6 +575,12 @@ export function proposalToRowValues(proposal, stashDir) {
559
575
  metaObj.sourceRun = proposal.sourceRun;
560
576
  if (proposal.review !== undefined)
561
577
  metaObj.review = proposal.review;
578
+ if (proposal.confidence !== undefined)
579
+ metaObj.confidence = proposal.confidence;
580
+ if (proposal.gateDecision !== undefined)
581
+ metaObj.gateDecision = proposal.gateDecision;
582
+ if (proposal.backupContent !== undefined)
583
+ metaObj.backupContent = proposal.backupContent;
562
584
  return {
563
585
  id: proposal.id,
564
586
  stash_dir: stashDir,
@@ -673,7 +695,10 @@ export function upsertProposal(db, proposal, stashDir) {
673
695
  }
674
696
  /**
675
697
  * List proposals, optionally filtered by stashDir, status, and/or ref.
676
- * Results are sorted by created_at ASC to match the existing listProposals() behaviour.
698
+ *
699
+ * Results are ordered by `created_at ASC` (matching the historical
700
+ * `listProposals()` sort), with `rowid` as a deterministic tiebreak so two
701
+ * proposals created in the same millisecond list in insertion order.
677
702
  */
678
703
  export function listStateProposals(db, options = {}) {
679
704
  const conditions = [];
@@ -694,21 +719,72 @@ export function listStateProposals(db, options = {}) {
694
719
  const rows = db
695
720
  .prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
696
721
  content, frontmatter_json, metadata_json
697
- FROM proposals ${where} ORDER BY created_at ASC`)
722
+ FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`)
698
723
  .all(...params);
699
724
  return rows.map(proposalRowToProposal);
700
725
  }
701
726
  /**
702
- * Look up a single proposal by id. Returns undefined when not found.
727
+ * Look up a single proposal by id, optionally scoped to one stash root.
728
+ * Returns undefined when not found.
703
729
  */
704
- export function getStateProposal(db, id) {
705
- const row = db
706
- .prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
730
+ export function getStateProposal(db, id, stashDir) {
731
+ const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
707
732
  content, frontmatter_json, metadata_json
708
- FROM proposals WHERE id = ?`)
709
- .get(id);
733
+ FROM proposals WHERE id = ?${stashDir ? " AND stash_dir = ?" : ""}`;
734
+ const row = (stashDir ? db.prepare(sql).get(id, stashDir) : db.prepare(sql).get(id));
710
735
  return row ? proposalRowToProposal(row) : undefined;
711
736
  }
737
+ /**
738
+ * Find PENDING proposal ids in one stash whose id starts with `idPrefix`.
739
+ * Backs the UUID-prefix form of `akm proposal show/accept/... <prefix>` —
740
+ * prefix resolution is deliberately scoped to the live (pending) queue,
741
+ * mirroring the historical behaviour of scanning only the live directory.
742
+ *
743
+ * `%` / `_` / `\` in the prefix are escaped so the LIKE pattern is literal.
744
+ */
745
+ export function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
746
+ const escaped = idPrefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
747
+ const rows = db
748
+ .prepare(`SELECT id FROM proposals
749
+ WHERE stash_dir = ? AND status = 'pending' AND id LIKE ? ESCAPE '\\'
750
+ ORDER BY id ASC`)
751
+ .all(stashDir, `${escaped}%`);
752
+ return rows.map((r) => r.id);
753
+ }
754
+ /**
755
+ * Whether the legacy filesystem proposal import has already run for `stashDir`.
756
+ * See migration 005 (`proposal_fs_imports`).
757
+ */
758
+ export function hasImportedFsProposals(db, stashDir) {
759
+ // Drivers disagree on the no-row sentinel (bun:sqlite → null,
760
+ // better-sqlite3 → undefined) — Boolean() covers both.
761
+ return Boolean(db.prepare("SELECT 1 FROM proposal_fs_imports WHERE stash_dir = ?").get(stashDir));
762
+ }
763
+ /**
764
+ * Record that the legacy filesystem proposal import completed for `stashDir`
765
+ * so subsequent invocations skip the directory walk. INSERT OR REPLACE keeps
766
+ * the call idempotent.
767
+ */
768
+ export function recordFsProposalsImport(db, stashDir, importedCount) {
769
+ db.prepare("INSERT OR REPLACE INTO proposal_fs_imports (stash_dir, imported_at, imported_count) VALUES (?, ?, ?)").run(stashDir, new Date().toISOString(), importedCount);
770
+ }
771
+ /**
772
+ * Insert a proposal row ONLY when the id is not already present (used by the
773
+ * legacy filesystem import so re-runs never clobber rows that have since been
774
+ * mutated through the canonical store). Returns true when a row was inserted.
775
+ */
776
+ export function insertProposalIfAbsent(db, proposal, stashDir) {
777
+ const v = proposalToRowValues(proposal, stashDir);
778
+ const result = db
779
+ .prepare(`
780
+ INSERT OR IGNORE INTO proposals
781
+ (id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
782
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
783
+ `)
784
+ .run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
785
+ const changes = result.changes ?? 0;
786
+ return Number(changes) > 0;
787
+ }
712
788
  // ── task_history table helpers ───────────────────────────────────────────────
713
789
  /**
714
790
  * Upsert a task history row.
@@ -779,6 +855,47 @@ export function queryTaskHistory(db, options = {}) {
779
855
  FROM task_history ${where} ORDER BY started_at DESC`)
780
856
  .all(...params);
781
857
  }
858
+ /**
859
+ * Read COMPLETED `akm-improve` task_history runs whose `started_at` falls in
860
+ * `[since, until)` (or `started_at >= since` when `until` is omitted), ordered
861
+ * oldest-first by `started_at`. Only rows with a non-null `completed_at` are
862
+ * returned (in-flight runs are excluded). The `task_id = 'akm-improve'`
863
+ * predicate is fixed because the only caller (commands/health.ts
864
+ * `loadTaskIntervals`) builds wall-time intervals for the improve cron task.
865
+ *
866
+ * Owns the SQL formerly inlined in commands/health.ts. Note the bound is
867
+ * EXCLUSIVE on the upper end (`started_at < ?`) — callers pass an already
868
+ * widened window; this helper does not widen.
869
+ *
870
+ * Connection-lifetime rule (WS5): `.all()` materializes a plain array before
871
+ * returning.
872
+ */
873
+ export function queryCompletedTaskIntervals(db, since, until) {
874
+ const sql = until
875
+ ? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at"
876
+ : "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
877
+ return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
878
+ }
879
+ // ── schema introspection ─────────────────────────────────────────────────────
880
+ /**
881
+ * Return the subset of `names` that exist as TABLEs in this database, ordered
882
+ * by name. Used by health's state-db-schema check to detect missing required
883
+ * tables without leaking a `sqlite_master` query into command code.
884
+ *
885
+ * The `IN (...)` predicate is built from parameter placeholders so table names
886
+ * are bound, never interpolated.
887
+ *
888
+ * Connection-lifetime rule (WS5): `.all()` materializes a plain array before
889
+ * returning.
890
+ */
891
+ export function listExistingTableNames(db, names) {
892
+ if (names.length === 0)
893
+ return [];
894
+ const placeholders = names.map(() => "?").join(", ");
895
+ return db
896
+ .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders}) ORDER BY name`)
897
+ .all(...names);
898
+ }
782
899
  // ── events.jsonl import ──────────────────────────────────────────────────────
783
900
  /**
784
901
  * Import all events from an `events.jsonl` file into the `events` table.
@@ -877,25 +994,23 @@ export function computeImproveRunMetrics(result) {
877
994
  let autoAcceptedCount = 0;
878
995
  let errorCount = 0;
879
996
  for (const action of actions) {
880
- switch (action.mode) {
881
- case "reflect":
882
- case "distill":
883
- case "memory-inference":
884
- case "graph-extraction":
997
+ // Bucketing delegated to the shared classifyImproveAction so this aggregate
998
+ // and the improve_completed event in improve.ts can never disagree, and so a
999
+ // new union variant is a compile error rather than a silent drop. Note:
1000
+ // `reflect-guard-rejected` now counts as "rejected" (previously this switch
1001
+ // omitted it entirely — a data-integrity miscount). "noop" (memory-prune) is
1002
+ // intentionally counted in none of the three numeric buckets.
1003
+ switch (classifyImproveAction(action.mode)) {
1004
+ case "accepted":
885
1005
  acceptedCount++;
886
1006
  break;
887
- case "reflect-cooldown":
888
- case "reflect-skipped":
889
- case "distill-skipped":
1007
+ case "rejected":
890
1008
  rejectedCount++;
891
1009
  break;
892
- case "reflect-failed":
893
1010
  case "error":
894
1011
  errorCount++;
895
1012
  break;
896
- case "memory-prune":
897
- // Prune is bookkeeping, not "accepted" content authoring; count
898
- // separately as a no-op for the audit aggregate.
1013
+ case "noop":
899
1014
  break;
900
1015
  }
901
1016
  // Legacy: pre-gate action results may carry autoAccepted: true (reflect path).
@@ -929,6 +1044,26 @@ export function recordImproveRun(db, input) {
929
1044
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
930
1045
  `).run(input.id, input.startedAt, input.completedAt, input.stashDir, input.dryRun ? 1 : 0, input.profile, input.scopeMode, input.scopeValue, input.guidance, input.ok ? 1 : 0, JSON.stringify(input.result), JSON.stringify(metricsObj), JSON.stringify(input.metadata ?? {}));
931
1046
  }
1047
+ /**
1048
+ * Read real (non-dry-run) improve_runs rows whose `started_at` falls in the
1049
+ * window `[since, until)`. When `until` is omitted the window is open-ended
1050
+ * (`started_at >= since`). Rows are returned newest-first (`ORDER BY
1051
+ * started_at DESC`).
1052
+ *
1053
+ * Owns the SQL formerly inlined in commands/health.ts (`loadImproveRunRows`).
1054
+ * The `dry_run = 0` filter is first-class so dry-run probes never pollute
1055
+ * productivity audits.
1056
+ *
1057
+ * Connection-lifetime rule (WS5): `.all()` fully materializes the result set
1058
+ * into a plain array before returning — no live cursor escapes the caller's
1059
+ * `openStateDatabase` scope.
1060
+ */
1061
+ export function queryImproveRuns(db, since, until) {
1062
+ const sql = until
1063
+ ? "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND started_at < ? AND dry_run = 0 ORDER BY started_at DESC"
1064
+ : "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND dry_run = 0 ORDER BY started_at DESC";
1065
+ return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
1066
+ }
932
1067
  /**
933
1068
  * Delete improve_runs rows older than `retentionDays` (default: 90). Mirrors
934
1069
  * {@link purgeOldEvents} — same default, same return shape (number of rows
@@ -1027,7 +1162,7 @@ export function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
1027
1162
  // ── registry_index_cache (goes in index.db, not state.db) ───────────────────
1028
1163
  /**
1029
1164
  * DDL for the `registry_index_cache` table that lives in the EXISTING index.db
1030
- * (managed by src/indexer/db.ts).
1165
+ * (managed by src/indexer/db/db.ts).
1031
1166
  *
1032
1167
  * Design: uses the same migration-safe ADD COLUMN approach. The table is
1033
1168
  * created with CREATE TABLE IF NOT EXISTS so it is safe to call inside
@@ -1051,7 +1186,7 @@ export function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
1051
1186
  * ALTER TABLE registry_index_cache ADD COLUMN error_message TEXT DEFAULT NULL;
1052
1187
  *
1053
1188
  * To add this table to index.db, call ensureRegistryIndexCacheSchema(db) from
1054
- * within ensureSchema() in src/indexer/db.ts, or add it as a new CREATE TABLE
1189
+ * within ensureSchema() in src/indexer/db/db.ts, or add it as a new CREATE TABLE
1055
1190
  * IF NOT EXISTS block inside the existing ensureSchema() call.
1056
1191
  */
1057
1192
  export const REGISTRY_INDEX_CACHE_DDL = `
@@ -105,3 +105,151 @@ export function detectTruncatedDescription(description) {
105
105
  }
106
106
  return null;
107
107
  }
108
+ // ── Post-generation repair pass (issue #556) ─────────────────────────────────
109
+ /**
110
+ * Minimum length (chars) for a repaired description to be considered usable.
111
+ * Mirrors the floor in `isValidDescription` (≥20). Kept local so this module
112
+ * stays dependency-free of the validators (avoids an import cycle).
113
+ */
114
+ const MIN_REPAIRED_DESCRIPTION_LEN = 20;
115
+ /** Maximum length (chars) for a repaired description. Mirrors `isValidDescription` (≤400). */
116
+ const MAX_REPAIRED_DESCRIPTION_LEN = 400;
117
+ /**
118
+ * Strip trailing truncation fragments from a candidate clause: repeatedly drop
119
+ * a hanging-connector word and/or trailing `,` `;` `:` `+` and ellipses until
120
+ * the clause no longer looks truncated. PURE — no fabrication, only removal.
121
+ *
122
+ * Returns the trimmed-to-last-complete-token clause (may be empty if the whole
123
+ * thing was connectors/punctuation).
124
+ */
125
+ function stripTrailingTruncationFragment(clause) {
126
+ let s = clause.trim();
127
+ // Loop because a tail can stack: "… related to the" → drop "the" → drop "to".
128
+ for (let guard = 0; guard < 64; guard++) {
129
+ const before = s;
130
+ // Drop trailing ellipsis / connector-only punctuation+operators.
131
+ s = s.replace(/\s*(\.{3,}|…)$/u, "").trim();
132
+ s = s.replace(/\s*[,;:+]+$/u, "").trim();
133
+ // Drop a trailing hanging-connector word (and any punctuation glued to it).
134
+ const m = s.match(/(?:^|\s)([A-Za-z']+)[.!?]*$/u);
135
+ if (m) {
136
+ const word = (m[1] ?? "").toLowerCase();
137
+ if (TRUNCATION_TRAILING_WORDS.has(word)) {
138
+ // Remove just the final word token (keep preceding text).
139
+ s = s.slice(0, s.length - m[0].length).trim();
140
+ }
141
+ }
142
+ if (s === before)
143
+ break;
144
+ }
145
+ return s;
146
+ }
147
+ /**
148
+ * Split prose into sentences on `.`/`!`/`?` boundaries (followed by whitespace
149
+ * or end). Keeps the terminating punctuation. Deliberately simple — good enough
150
+ * for the short, single-paragraph descriptions/bodies this repair operates on.
151
+ */
152
+ function splitSentences(text) {
153
+ const out = [];
154
+ for (const match of text.matchAll(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/gu)) {
155
+ const s = match[0].trim();
156
+ if (s)
157
+ out.push(s);
158
+ }
159
+ return out;
160
+ }
161
+ /**
162
+ * True when `candidate` is a self-contained, non-truncated clause of acceptable
163
+ * length. Used to gate repair outputs without importing the full validator
164
+ * (which would create a cycle). The full `isValidDescription` still runs
165
+ * downstream — this is only the truncation/length subset the repair targets.
166
+ */
167
+ function isUsableClause(candidate) {
168
+ const c = candidate.trim();
169
+ if (c.length < MIN_REPAIRED_DESCRIPTION_LEN)
170
+ return false;
171
+ if (c.length > MAX_REPAIRED_DESCRIPTION_LEN)
172
+ return false;
173
+ if (detectTruncatedDescription(c) !== null)
174
+ return false;
175
+ // Must contain at least one word character (not pure punctuation).
176
+ if (!/[A-Za-z0-9]/.test(c))
177
+ return false;
178
+ return true;
179
+ }
180
+ /** Append a period when the clause lacks terminal sentence punctuation. PURE. */
181
+ function ensureTerminalPunctuation(clause) {
182
+ const c = clause.trim();
183
+ if (/[.!?]$/.test(c))
184
+ return c;
185
+ return `${c}.`;
186
+ }
187
+ /**
188
+ * Deterministically repair an LLM-generated description that was sliced
189
+ * mid-sentence (issue #556). The repair NEVER fabricates new claims:
190
+ *
191
+ * 1. If the description is not truncated, it is returned **byte-identical**
192
+ * (zero behaviour change for already-valid descriptions).
193
+ * 2. Otherwise:
194
+ * a. **trim-to-last-complete-clause** — strip the trailing
195
+ * truncation-indicator word(s)/punctuation; if a complete earlier
196
+ * sentence survives, use the longest non-truncated prefix.
197
+ * b. **swap-in first body sentence** — if (a) yields nothing usable and a
198
+ * `body` is provided, use the first clean, complete sentence of the
199
+ * body.
200
+ * c. **fallback** — if neither produces a usable, non-truncated clause,
201
+ * return the original string unchanged so the existing validation
202
+ * rejects it exactly as before (no regression, no fabrication).
203
+ *
204
+ * @param description The raw description (possibly truncated).
205
+ * @param body Optional asset body to source a clean completion sentence from.
206
+ */
207
+ export function repairTruncatedDescription(description, body) {
208
+ if (typeof description !== "string")
209
+ return description;
210
+ // Guarantee: untruncated input passes through byte-identical.
211
+ if (detectTruncatedDescription(description) === null)
212
+ return description;
213
+ const trimmed = description.trim();
214
+ // (a) trim-to-last-complete-clause.
215
+ // First, try the whole string with its trailing fragment stripped.
216
+ const stripped = stripTrailingTruncationFragment(trimmed);
217
+ if (isUsableClause(stripped)) {
218
+ return ensureTerminalPunctuation(stripped);
219
+ }
220
+ // If the description has multiple sentences, the truncation is in the last
221
+ // one — fall back to the longest leading run of complete sentences.
222
+ const sentences = splitSentences(trimmed);
223
+ if (sentences.length > 1) {
224
+ for (let take = sentences.length - 1; take >= 1; take--) {
225
+ const prefix = sentences.slice(0, take).join(" ").trim();
226
+ const cleaned = stripTrailingTruncationFragment(prefix);
227
+ if (isUsableClause(cleaned))
228
+ return ensureTerminalPunctuation(cleaned);
229
+ }
230
+ }
231
+ // (b) swap-in first clean, complete body sentence.
232
+ if (typeof body === "string" && body.trim().length > 0) {
233
+ const bodyText = body
234
+ .replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "") // drop any frontmatter
235
+ .replace(/```[\s\S]*?```/g, " ") // drop fenced code
236
+ .replace(/`[^`]*`/g, " "); // drop inline code spans
237
+ for (const rawLine of bodyText.split(/\n/)) {
238
+ const line = rawLine
239
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
240
+ .replace(/\*([^*]+)\*/g, "$1")
241
+ .replace(/^[#*\->_\s]+/, "")
242
+ .trim();
243
+ if (!line)
244
+ continue;
245
+ if (/^[a-z_]+:\s/i.test(line))
246
+ continue; // skip yaml-ish leak lines
247
+ for (const sentence of splitSentences(line)) {
248
+ if (isUsableClause(sentence))
249
+ return ensureTerminalPunctuation(sentence);
250
+ }
251
+ }
252
+ }
253
+ // (c) fallback: return original unchanged — validation rejects as before.
254
+ return description;
255
+ }
package/dist/core/time.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * interpret the same set of formats (ISO-8601, epoch ms, plain date strings)
9
9
  * consistently without private re-implementations drifting apart.
10
10
  */
11
- import { UsageError } from "./errors";
11
+ import { UsageError } from "./errors.js";
12
12
  // ── Since-flag parsing ───────────────────────────────────────────────────────
13
13
  /**
14
14
  * Parse a user-supplied `--since` value and return an ISO-8601 timestamp