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
@@ -5,15 +5,15 @@ import { spawnSync } from "node:child_process";
5
5
  import { createHash, randomBytes } from "node:crypto";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
- import { TYPE_DIRS } from "../../core/asset-spec";
9
- import { resolveStashDir } from "../../core/common";
10
- import { getSources, loadConfig } from "../../core/config";
11
- import { ConfigError, UsageError } from "../../core/errors";
12
- import { getRegistryCacheDir, getRegistryIndexCacheDir } from "../../core/paths";
13
- import { sanitizeCommitMessage } from "../../core/write-source";
14
- import { parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl } from "../../registry/resolve";
15
- import { registerSourceProvider } from "../provider-factory";
16
- import { applyAkmIncludeConfig, buildInstallCacheDir, copyDirectoryContents, detectStashRoot, isDirectory, isExpired, sanitizeString, } from "./provider-utils";
8
+ import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
9
+ import { resolveStashDir } from "../../core/common.js";
10
+ import { getSources, loadConfig } from "../../core/config/config.js";
11
+ import { ConfigError, UsageError } from "../../core/errors.js";
12
+ import { getRegistryCacheDir, getRegistryIndexCacheDir } from "../../core/paths.js";
13
+ import { sanitizeCommitMessage } from "../../core/write-source.js";
14
+ import { parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl } from "../../registry/resolve.js";
15
+ import { registerSourceProvider } from "../provider-factory.js";
16
+ import { applyAkmIncludeConfig, buildInstallCacheDir, copyDirectoryContents, detectStashRoot, isDirectory, isExpired, sanitizeString, } from "./provider-utils.js";
17
17
  /** Cache TTL before refreshing the mirrored repo (12 hours). */
18
18
  const CACHE_TTL_MS = 12 * 60 * 60 * 1000;
19
19
  /** Maximum stale age allowed when refresh fails (7 days). */
@@ -8,7 +8,7 @@
8
8
  * providers with the provider registry. This replaces the individual
9
9
  * side-effect imports that were duplicated in source-search.ts and source-show.ts.
10
10
  */
11
- import "./filesystem";
12
- import "./git";
13
- import "./npm";
14
- import "./website";
11
+ import "./filesystem.js";
12
+ import "./git.js";
13
+ import "./npm.js";
14
+ import "./website.js";
@@ -11,12 +11,12 @@
11
11
  */
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
- import { ConfigError, UsageError } from "../../core/errors";
15
- import { getRegistryCacheDir } from "../../core/paths";
16
- import { parseRegistryRef, resolveRegistryArtifact } from "../../registry/resolve";
17
- import { registerSourceProvider } from "../provider-factory";
18
- import { applyAkmIncludeConfig, buildInstallCacheDir, computeFileHash, detectStashRoot, downloadArchive, isDirectory, } from "./provider-utils";
19
- import { extractTarGzSecure, verifyArchiveIntegrity } from "./tar-utils";
14
+ import { ConfigError, UsageError } from "../../core/errors.js";
15
+ import { getRegistryCacheDir } from "../../core/paths.js";
16
+ import { parseRegistryRef, resolveRegistryArtifact } from "../../registry/resolve.js";
17
+ import { registerSourceProvider } from "../provider-factory.js";
18
+ import { applyAkmIncludeConfig, buildInstallCacheDir, computeFileHash, detectStashRoot, downloadArchive, isDirectory, } from "./provider-utils.js";
19
+ import { extractTarGzSecure, verifyArchiveIntegrity } from "./tar-utils.js";
20
20
  /**
21
21
  * NPM source provider — fetches a tarball from the npm registry and extracts
22
22
  * it into a local cache. Implements the v1 {@link SourceProvider} interface
@@ -1,12 +1,13 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { createHash } from "node:crypto";
4
+ import { createHash, randomUUID } from "node:crypto";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
- import { TYPE_DIRS } from "../../core/asset-spec";
8
- import { fetchWithRetry } from "../../core/common";
9
- import { copyIncludedPaths, findNearestIncludeConfig } from "../include";
7
+ import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
8
+ import { fetchWithRetry } from "../../core/common.js";
9
+ import { writeResponseToFile } from "../../runtime.js";
10
+ import { copyIncludedPaths, findNearestIncludeConfig } from "../include.js";
10
11
  const REGISTRY_STASH_DIR_NAMES = new Set(Object.values(TYPE_DIRS));
11
12
  /** Strip terminal control characters from untrusted strings. */
12
13
  export function sanitizeString(value, maxLength = 255) {
@@ -38,17 +39,19 @@ export function detectStashRoot(extractedDir) {
38
39
  return shallowest;
39
40
  return root;
40
41
  }
42
+ /** A collision-resistant slug used to isolate cache dirs that lack a stable version. */
43
+ function uniqueSlug() {
44
+ return randomUUID();
45
+ }
41
46
  /**
42
47
  * Build a per-source cache directory under `cacheRootDir`.
43
48
  *
44
49
  * Versioned sources get `${source}-${id}/${version}` for cache reuse;
45
- * `local` sources get a unique timestamped slug so each install is isolated.
50
+ * `local` sources get a unique slug so each install is isolated.
46
51
  */
47
52
  export function buildInstallCacheDir(cacheRootDir, source, id, version) {
48
53
  const slug = `${source}-${id.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "")}`;
49
- const versionSlug = source === "local"
50
- ? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
51
- : (version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
54
+ const versionSlug = source === "local" ? uniqueSlug() : (version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? uniqueSlug());
52
55
  return path.join(cacheRootDir, slug || source, versionSlug);
53
56
  }
54
57
  /**
@@ -66,24 +69,14 @@ export function applyAkmIncludeConfig(sourceRoot, cacheDir, searchRoot = sourceR
66
69
  copyIncludedPaths(includeConfig.include, includeConfig.baseDir, selectedDir);
67
70
  return selectedDir;
68
71
  }
69
- /** Stream a remote archive to disk using Bun.write when available. */
72
+ /** Stream a remote archive to disk via the runtime boundary's response writer. */
70
73
  export async function downloadArchive(url, destination) {
71
74
  const response = await fetchWithRetry(url, undefined, { timeout: 120_000 });
72
75
  if (!response.ok) {
73
76
  throw new Error(`Failed to download archive (${response.status}) from ${url}`);
74
77
  }
75
78
  // Stream response to disk instead of buffering the entire archive in memory.
76
- // Uses Bun.write which handles Response streaming natively.
77
- const BunRuntime = globalThis
78
- .Bun;
79
- if (BunRuntime?.write) {
80
- await BunRuntime.write(destination, response);
81
- }
82
- else {
83
- // Fallback for non-Bun environments (e.g., tests)
84
- const arrayBuffer = await response.arrayBuffer();
85
- fs.writeFileSync(destination, Buffer.from(arrayBuffer));
86
- }
79
+ await writeResponseToFile(destination, response);
87
80
  }
88
81
  /** SHA-256 of a file, returned as `sha256:<hex>`. */
89
82
  export async function computeFileHash(filePath) {
@@ -1,20 +1,20 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { UsageError } from "../../core/errors";
5
- import { parseRegistryRef } from "../../registry/resolve";
6
- import { detectStashRoot } from "./provider-utils";
4
+ import { UsageError } from "../../core/errors.js";
5
+ import { parseRegistryRef } from "../../registry/resolve.js";
6
+ import { detectStashRoot } from "./provider-utils.js";
7
7
  export async function syncFromRef(ref, options) {
8
8
  const parsed = parseRegistryRef(ref);
9
9
  if (parsed.source === "local") {
10
10
  return syncLocalRef(parsed, options);
11
11
  }
12
12
  if (parsed.source === "npm") {
13
- const { syncNpmRef } = await import("./npm");
13
+ const { syncNpmRef } = await import("./npm.js");
14
14
  return syncNpmRef(ref, options);
15
15
  }
16
16
  if (parsed.source === "git" || parsed.source === "github") {
17
- const { syncRegistryGitRef } = await import("./git");
17
+ const { syncRegistryGitRef } = await import("./git.js");
18
18
  return syncRegistryGitRef(ref, options);
19
19
  }
20
20
  // Exhaustiveness — `parseRegistryRef` only emits the four sources above.
@@ -16,8 +16,8 @@ import { spawnSync } from "node:child_process";
16
16
  import { createHash } from "node:crypto";
17
17
  import fs from "node:fs";
18
18
  import path from "node:path";
19
- import { isWithin } from "../../core/common";
20
- import { warn } from "../../core/warn";
19
+ import { isWithin } from "../../core/common.js";
20
+ import { warn } from "../../core/warn.js";
21
21
  /**
22
22
  * Verify an archive's integrity against a known hash. Throws and removes
23
23
  * the archive when verification fails.
@@ -1,8 +1,8 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { registerSourceProvider } from "../provider-factory";
5
- import { ensureWebsiteMirror, getWebsiteCachePaths, validateWebsiteUrl } from "../website-ingest";
4
+ import { registerSourceProvider } from "../provider-factory.js";
5
+ import { ensureWebsiteMirror, getWebsiteCachePaths, validateWebsiteUrl } from "../website-ingest.js";
6
6
  /**
7
7
  * Website source provider — thin adapter over the shared website ingest module.
8
8
  */
@@ -3,11 +3,11 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, resolveAssetPathFromName, TYPE_DIRS, } from "../core/asset-spec";
7
- import { hasErrnoCode, isWithin } from "../core/common";
8
- import { NotFoundError, UsageError } from "../core/errors";
9
- import { runMatchers } from "../indexer/file-context";
10
- import { walkStashFlat } from "../indexer/walker";
6
+ import { deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, resolveAssetPathFromName, TYPE_DIRS, } from "../core/asset/asset-spec.js";
7
+ import { hasErrnoCode, isWithin } from "../core/common.js";
8
+ import { NotFoundError, UsageError } from "../core/errors.js";
9
+ import { runMatchers } from "../indexer/walk/file-context.js";
10
+ import { walkStashFlat } from "../indexer/walk/walker.js";
11
11
  /**
12
12
  * Resolve an asset path from a stash directory, type, and name.
13
13
  */
@@ -4,11 +4,11 @@
4
4
  import { createHash } from "node:crypto";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
- import { fetchWithRetry, ResponseTooLargeError, readBodyWithByteCap } from "../core/common";
8
- import { ConfigError, UsageError } from "../core/errors";
9
- import { getRegistryIndexCacheDir } from "../core/paths";
10
- import { warn } from "../core/warn";
11
- import { isExpired, sanitizeString } from "./providers/provider-utils";
7
+ import { fetchWithRetry, ResponseTooLargeError, readBodyWithByteCap } from "../core/common.js";
8
+ import { ConfigError, UsageError } from "../core/errors.js";
9
+ import { getRegistryIndexCacheDir } from "../core/paths.js";
10
+ import { warn } from "../core/warn.js";
11
+ import { isExpired, sanitizeString } from "./providers/provider-utils.js";
12
12
  /** Refresh website snapshots every 12 hours to balance freshness with scraping load. */
13
13
  const CACHE_REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1000;
14
14
  /** Allow up to 7 days of stale snapshots when refresh fails so search remains available during outages. */
@@ -0,0 +1,102 @@
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
+ /**
5
+ * SQLite runtime boundary.
6
+ *
7
+ * Single source of truth for opening SQLite database handles. The rest of the
8
+ * codebase imports the {@link Database} type and {@link openDatabase} factory
9
+ * from here and NEVER imports `bun:sqlite` or `better-sqlite3` directly.
10
+ *
11
+ * Runtime selection:
12
+ * - On Bun (the primary/test runtime) we use the built-in `bun:sqlite`.
13
+ * - On Node.js (additive, not CI-tested this pass) we use `better-sqlite3`,
14
+ * loaded via a runtime-gated dynamic `require` so the Bun path never
15
+ * imports it (it is an optionalDependency and may be uninstalled or
16
+ * uncompiled when running under Bun).
17
+ *
18
+ * Both driver handles are structurally compatible across the small surface AKM
19
+ * uses (`prepare`, `exec`, `run`, `transaction`, `close` on the handle;
20
+ * `get`, `all`, `run` on prepared statements). The Bun-specific `db.query()`
21
+ * helper is normalised away — callers use `db.prepare(sql).all(...)` instead.
22
+ *
23
+ * This file is intentionally NOT an adapter/DI/ports-and-adapters layer. It is
24
+ * a plain module: a structural type plus a factory function. The handle it
25
+ * returns is the real underlying driver instance (so e.g. `sqlite-vec`'s
26
+ * `load(db)` receives the genuine driver handle and works unchanged).
27
+ *
28
+ * @module storage/database
29
+ */
30
+ import { createRequire } from "node:module";
31
+ // Detect the runtime exactly once at module load.
32
+ const isBun = !!process.versions?.bun;
33
+ // A CommonJS-style require usable from this ESM module on both runtimes. Used
34
+ // to load the runtime-specific driver lazily so that neither `bun:sqlite` (a
35
+ // Bun built-in, unresolvable on Node) nor `better-sqlite3` (an optional native
36
+ // dep, possibly absent under Bun) is statically imported.
37
+ const nodeRequire = createRequire(import.meta.url);
38
+ /**
39
+ * Open a SQLite database handle at `path`, selecting the driver for the current
40
+ * runtime. Returns a handle conforming to the structural {@link Database} type.
41
+ *
42
+ * The Node driver (`better-sqlite3`) is required lazily and ONLY when not on
43
+ * Bun, so importing this module under Bun never touches `better-sqlite3`.
44
+ */
45
+ export function openDatabase(path, opts) {
46
+ if (isBun) {
47
+ return openBunDatabase(path, opts);
48
+ }
49
+ return openNodeDatabase(path, opts);
50
+ }
51
+ function openBunDatabase(path, opts) {
52
+ const { Database: BunDatabase } = loadBunSqlite();
53
+ // Only pass an options object when an option is actually set. bun:sqlite
54
+ // raises SQLITE_MISUSE if handed an options bag with all-undefined fields,
55
+ // and every current caller opens with just a path — so the no-opts path must
56
+ // remain byte-identical to the original `new Database(path)`.
57
+ const db = opts ? new BunDatabase(path, bunOptions(opts)) : new BunDatabase(path);
58
+ return db;
59
+ }
60
+ function bunOptions(opts) {
61
+ const out = {};
62
+ if (opts.readonly !== undefined)
63
+ out.readonly = opts.readonly;
64
+ if (opts.create !== undefined)
65
+ out.create = opts.create;
66
+ return out;
67
+ }
68
+ let bunSqliteModule;
69
+ function loadBunSqlite() {
70
+ // `bun:sqlite` is a Bun built-in. This function is only ever called when
71
+ // `isBun` is true, so Node never resolves the `bun:` specifier. Loaded via
72
+ // require (not a static import) to keep Node's ESM resolver from choking on
73
+ // the `bun:` specifier when this module is merely imported under Node.
74
+ if (!bunSqliteModule) {
75
+ bunSqliteModule = nodeRequire("bun:sqlite");
76
+ }
77
+ return bunSqliteModule;
78
+ }
79
+ let betterSqlite3Ctor;
80
+ function loadBetterSqlite3() {
81
+ if (!betterSqlite3Ctor) {
82
+ // Runtime-gated dynamic require: only reached when NOT on Bun, so Bun never
83
+ // resolves or loads the optional `better-sqlite3` native dependency.
84
+ const mod = nodeRequire("better-sqlite3");
85
+ betterSqlite3Ctor = mod.default ?? mod;
86
+ }
87
+ return betterSqlite3Ctor;
88
+ }
89
+ function openNodeDatabase(path, opts) {
90
+ const BetterSqlite3 = loadBetterSqlite3();
91
+ // better-sqlite3 validates option *values* strictly and throws
92
+ // `Expected the "readonly" option to be a boolean` if the key is present with
93
+ // an `undefined` value — so only include each option when it is actually set,
94
+ // matching the no-opts byte-identical path on the Bun side.
95
+ const options = {};
96
+ if (opts?.readonly !== undefined)
97
+ options.readonly = opts.readonly;
98
+ if (opts?.create === false)
99
+ options.fileMustExist = true;
100
+ const db = opts ? new BetterSqlite3(path, options) : new BetterSqlite3(path);
101
+ return db;
102
+ }
@@ -0,0 +1,42 @@
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
+ /**
5
+ * Create the migrations ledger table if it does not exist. Must be called
6
+ * unconditionally on every open so a fresh database bootstraps correctly.
7
+ */
8
+ export function ensureMigrationsTable(db) {
9
+ db.exec(`
10
+ CREATE TABLE IF NOT EXISTS schema_migrations (
11
+ id TEXT PRIMARY KEY,
12
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
13
+ );
14
+ `);
15
+ }
16
+ /**
17
+ * Apply every pending migration, one transaction per migration.
18
+ *
19
+ * Each migration is applied in its own transaction so a failure in migration N
20
+ * does not roll back already-applied migrations 1..N-1. The migration row is
21
+ * inserted AFTER the DDL succeeds, so a crash mid-migration leaves no row and
22
+ * the migration is retried on next open (all DDL in `up` uses IF NOT EXISTS so
23
+ * the retry is safe).
24
+ *
25
+ * @param db The open SQLite database.
26
+ * @param migrations The module's ordered, append-only migration list.
27
+ * @param opts Optional `bootstrap` hook (see {@link RunMigrationsOptions}).
28
+ */
29
+ export function runMigrations(db, migrations, opts) {
30
+ ensureMigrationsTable(db);
31
+ opts?.bootstrap?.(db);
32
+ const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
33
+ const applied = new Set(appliedRows.map((r) => r.id));
34
+ for (const migration of migrations) {
35
+ if (applied.has(migration.id))
36
+ continue;
37
+ db.transaction(() => {
38
+ db.exec(migration.up);
39
+ db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
40
+ })();
41
+ }
42
+ }
@@ -0,0 +1,25 @@
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 { getCacheDir, getConfigDir, getDataDir, getDbPath, getDefaultStashDir, getLockfileLockPath, getLockfilePath, getWorkflowDbPath, } from "../core/paths.js";
5
+ import { getStateDbPath } from "../core/state-db.js";
6
+ /**
7
+ * Resolve the current {@link StorageLocations} by delegating to the existing
8
+ * path getters. Resolution honours the same environment overrides
9
+ * (`AKM_DATA_DIR`, XDG variables, transient-stash isolation) as the underlying
10
+ * getters, so this is safe to call at boot or per-operation — it carries no
11
+ * cached state of its own.
12
+ */
13
+ export function resolveStorageLocations() {
14
+ return {
15
+ indexDb: getDbPath(),
16
+ stateDb: getStateDbPath(),
17
+ workflowDb: getWorkflowDbPath(),
18
+ lockfile: getLockfilePath(),
19
+ lockfileSentinel: getLockfileLockPath(),
20
+ dataDir: getDataDir(),
21
+ cacheDir: getCacheDir(),
22
+ configDir: getConfigDir(),
23
+ stashDir: getDefaultStashDir(),
24
+ };
25
+ }
@@ -0,0 +1,43 @@
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 { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
5
+ import { resolveStorageLocations } from "../locations.js";
6
+ /**
7
+ * Scoped-resource (loan pattern) helper for the index database (`index.db`).
8
+ *
9
+ * This is the `index.db` twin of {@link ../../workflows/db withWorkflowDb} /
10
+ * {@link ./workflow-runs-repository withWorkflowRunsRepo}: it opens the index
11
+ * database bound to {@link StorageLocations.indexDb}, runs `fn` against the live
12
+ * {@link Database}, and closes the connection exactly once when `fn` returns —
13
+ * even if `fn` throws. Callers no longer hand-roll `open / try / finally / close`
14
+ * around `index.db`, and the dead `existingDb?` caller-owns ownership flag
15
+ * (search.ts / show.ts) is eliminated: every former call site passed
16
+ * `undefined`, so the connection was always opened and always closed here.
17
+ *
18
+ * ## Connection-lifetime contract (WS5)
19
+ *
20
+ * `fn` MUST fully materialise any result set (`.all()` / `.get()` into plain
21
+ * values or array copies) and return plain values BEFORE it returns. It MUST
22
+ * NOT return a live statement iterator or cursor across the scope boundary,
23
+ * because the connection is closed the instant `fn` settles — a leaked cursor
24
+ * would be read against a closed database.
25
+ *
26
+ * This helper is intentionally **synchronous**. The migrated call sites are
27
+ * synchronous fire-and-forget telemetry writers; keeping the lifecycle sync
28
+ * preserves their exact (non-deferred) timing — opening the DB, doing the work,
29
+ * and closing it within the same tick, identical to the inline blocks it
30
+ * replaces.
31
+ *
32
+ * @param fn Receives the open index database; must finish all DB work before returning.
33
+ * @returns Whatever `fn` returns.
34
+ */
35
+ export function withIndexDb(fn) {
36
+ const db = openExistingDatabase(resolveStorageLocations().indexDb);
37
+ try {
38
+ return fn(db);
39
+ }
40
+ finally {
41
+ closeDatabase(db);
42
+ }
43
+ }
@@ -0,0 +1,141 @@
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 { closeWorkflowDatabase, openWorkflowDatabase } from "../../workflows/db.js";
5
+ import { resolveStorageLocations } from "../locations.js";
6
+ /**
7
+ * Repository owning every raw SQL statement against `workflow_runs` and
8
+ * `workflow_run_steps`. It is DB-location-agnostic: the lifecycle helper
9
+ * {@link withWorkflowRunsRepo} binds it to {@link StorageLocations.workflowDb}
10
+ * so a future storage move (#489) changes only `locations.ts`.
11
+ *
12
+ * ## Connection-lifetime contract (WS5)
13
+ *
14
+ * Every read method fully materialises its result set (`.all()` / `.get()` into
15
+ * plain values/arrays) before returning. The repository NEVER hands a live
16
+ * statement iterator or cursor back across the {@link withWorkflowRunsRepo}
17
+ * scope boundary, so the connection can be closed immediately after `fn`
18
+ * resolves without truncating lazy iteration.
19
+ */
20
+ export class WorkflowRunsRepository {
21
+ db;
22
+ constructor(db) {
23
+ this.db = db;
24
+ }
25
+ /** Escape hatch for the transaction-bounded write paths still orchestrated
26
+ * in runs.ts. The repository owns the SQL; the caller owns the transaction
27
+ * boundary (unchanged from the pre-extraction `db.transaction(() => …)`). */
28
+ transaction(fn) {
29
+ return this.db.transaction(fn)();
30
+ }
31
+ // ── reads (fully materialised) ─────────────────────────────────────────────
32
+ findActiveRunForScope(workflowRef, scopeKey) {
33
+ return this.db
34
+ .prepare("SELECT id, current_step_id FROM workflow_runs WHERE workflow_ref = ? AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
35
+ .get(workflowRef, scopeKey);
36
+ }
37
+ getRunById(runId) {
38
+ return this.db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(runId);
39
+ }
40
+ getActiveRunRowForScope(workflowRef, scopeKey) {
41
+ return this.db
42
+ .prepare("SELECT * FROM workflow_runs WHERE workflow_ref = ? AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
43
+ .get(workflowRef, scopeKey);
44
+ }
45
+ hasRun(runId) {
46
+ const row = this.db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
47
+ return !!row;
48
+ }
49
+ listRuns(filter) {
50
+ const filters = [];
51
+ const params = [];
52
+ filters.push("scope_key = ?");
53
+ params.push(filter.scopeKey);
54
+ if (filter.workflowRef) {
55
+ filters.push("workflow_ref = ?");
56
+ params.push(filter.workflowRef);
57
+ }
58
+ if (filter.activeOnly) {
59
+ filters.push("status IN ('active', 'blocked')");
60
+ }
61
+ const where = filters.length > 0 ? `WHERE ${filters.join(" AND ")}` : "";
62
+ return this.db
63
+ .prepare(`SELECT * FROM workflow_runs ${where} ORDER BY updated_at DESC, created_at DESC`)
64
+ .all(...params);
65
+ }
66
+ getStepsForRun(runId) {
67
+ return this.db
68
+ .prepare("SELECT * FROM workflow_run_steps WHERE run_id = ? ORDER BY sequence_index ASC")
69
+ .all(runId);
70
+ }
71
+ getStep(runId, stepId) {
72
+ return this.db.prepare("SELECT * FROM workflow_run_steps WHERE run_id = ? AND step_id = ?").get(runId, stepId);
73
+ }
74
+ findActiveOrBlockedRunForScope(scopeKey) {
75
+ return (this.db
76
+ .prepare("SELECT id, current_step_id, workflow_ref FROM workflow_runs WHERE scope_key = ? AND status IN ('active', 'blocked') ORDER BY updated_at DESC LIMIT 1")
77
+ .get(scopeKey) ?? null);
78
+ }
79
+ // ── writes ─────────────────────────────────────────────────────────────────
80
+ insertRun(input) {
81
+ this.db
82
+ .prepare(`INSERT INTO workflow_runs (
83
+ id, workflow_ref, scope_key, workflow_entry_id, workflow_title, status, params_json, current_step_id, created_at, updated_at,
84
+ agent_harness, agent_session_id, checkin_armed_at
85
+ ) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?)`)
86
+ .run(input.id, input.workflowRef, input.scopeKey, input.workflowEntryId, input.workflowTitle, input.paramsJson, input.currentStepId, input.createdAt, input.updatedAt, input.agentHarness, input.agentSessionId, input.checkinArmedAt);
87
+ }
88
+ insertSteps(steps) {
89
+ const insertStep = this.db.prepare(`INSERT INTO workflow_run_steps (
90
+ run_id, step_id, step_title, instructions, completion_json, sequence_index, status
91
+ ) VALUES (?, ?, ?, ?, ?, ?, 'pending')`);
92
+ for (const step of steps) {
93
+ insertStep.run(step.runId, step.stepId, step.stepTitle, step.instructions, step.completionJson, step.sequenceIndex);
94
+ }
95
+ }
96
+ reopenStepsForResume(runId, currentStepId) {
97
+ this.db
98
+ .prepare(`UPDATE workflow_run_steps
99
+ SET status = 'pending', notes = NULL, evidence_json = NULL, completed_at = NULL
100
+ WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`)
101
+ .run(runId, currentStepId);
102
+ }
103
+ markRunActive(runId, updatedAt) {
104
+ this.db.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(updatedAt, runId);
105
+ }
106
+ updateStepCompletion(input) {
107
+ this.db
108
+ .prepare(`UPDATE workflow_run_steps
109
+ SET status = ?, notes = ?, evidence_json = ?, summary = ?, completed_at = ?
110
+ WHERE run_id = ? AND step_id = ?`)
111
+ .run(input.status, input.notes, input.evidenceJson, input.summary, input.completedAt, input.runId, input.stepId);
112
+ }
113
+ updateRunState(input) {
114
+ this.db
115
+ .prepare(`UPDATE workflow_runs
116
+ SET status = ?, current_step_id = ?, updated_at = ?, completed_at = ?, checkin_armed_at = ?
117
+ WHERE id = ?`)
118
+ .run(input.status, input.currentStepId, input.updatedAt, input.completedAt, input.checkinArmedAt, input.runId);
119
+ }
120
+ rearmCheckin(runId, checkinArmedAt) {
121
+ this.db.prepare("UPDATE workflow_runs SET checkin_armed_at = ? WHERE id = ?").run(checkinArmedAt, runId);
122
+ }
123
+ }
124
+ /**
125
+ * Open the workflow database (bound to {@link StorageLocations.workflowDb}),
126
+ * run `fn` against a {@link WorkflowRunsRepository}, and close the connection
127
+ * exactly once when `fn` settles.
128
+ *
129
+ * Generalises the former `withWorkflowDb` loan pattern in runs.ts. Repository
130
+ * read methods fully materialise their results, so closing here never truncates
131
+ * lazy iteration (WS5 connection-lifetime rule).
132
+ */
133
+ export async function withWorkflowRunsRepo(fn) {
134
+ const db = openWorkflowDatabase(resolveStorageLocations().workflowDb);
135
+ try {
136
+ return await Promise.resolve(fn(new WorkflowRunsRepository(db)));
137
+ }
138
+ finally {
139
+ closeWorkflowDatabase(db);
140
+ }
141
+ }
@@ -27,10 +27,10 @@
27
27
  import { spawnSync } from "node:child_process";
28
28
  import fs from "node:fs";
29
29
  import path from "node:path";
30
- import { ConfigError } from "../../core/errors";
31
- import { getTaskLogDir } from "../../core/paths";
32
- import { resolveAkmInvocation } from "../resolveAkmBin";
33
- import { parseSchedule, translateToCron } from "../schedule";
30
+ import { ConfigError } from "../../core/errors.js";
31
+ import { getTaskLogDir } from "../../core/paths.js";
32
+ import { resolveAkmInvocation } from "../resolveAkmBin.js";
33
+ import { parseSchedule, translateToCron } from "../schedule.js";
34
34
  const BEGIN = (id) => `# akm:task ${id} BEGIN`;
35
35
  const END = (id) => `# akm:task ${id} END`;
36
36
  const DISABLED_PREFIX = "# akm:disabled ";
@@ -2,6 +2,38 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { spawnSync } from "node:child_process";
5
+ import fs from "node:fs";
6
+ /**
7
+ * Default exec strategy: run commands synchronously via {@link spawnCommand}.
8
+ *
9
+ * This is the shared default for every task backend's `exec` seam. Backends
10
+ * that need extra fields (e.g. launchd's `uid()`) spread this and add them.
11
+ */
12
+ export function nodeExec() {
13
+ return {
14
+ run(args) {
15
+ return spawnCommand(args);
16
+ },
17
+ };
18
+ }
19
+ /**
20
+ * Default filesystem strategy for the shared subset of the backend `fs` seam
21
+ * (`writeFile` + `ensureDir`), backed by `node:fs`.
22
+ *
23
+ * Backends spread this and add their own members. Note `removeFile` is NOT
24
+ * shared here: the launchd and schtasks defaults differ observably (schtasks
25
+ * swallows errors; launchd does not), so each keeps its own.
26
+ */
27
+ export function nodeFs() {
28
+ return {
29
+ writeFile(file, content) {
30
+ fs.writeFileSync(file, content, { encoding: "utf8" });
31
+ },
32
+ ensureDir(dir) {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ },
35
+ };
36
+ }
5
37
  /**
6
38
  * Run a command synchronously, normalizing null results to safe defaults.
7
39
  * args[0] is the binary; args[1..] are its arguments.
@@ -1,9 +1,9 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { CRON_BACKEND } from "./cron";
5
- import { LAUNCHD_BACKEND } from "./launchd";
6
- import { SCHTASKS_BACKEND } from "./schtasks";
4
+ import { CRON_BACKEND } from "./cron.js";
5
+ import { LAUNCHD_BACKEND } from "./launchd.js";
6
+ import { SCHTASKS_BACKEND } from "./schtasks.js";
7
7
  export function selectBackend(options = {}) {
8
8
  const platform = options.platform ?? process.platform;
9
9
  switch (platform) {