akm-cli 0.9.0-rc.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/CHANGELOG.md +1063 -44
  2. package/README.md +51 -25
  3. package/SECURITY.md +14 -1
  4. package/STABILITY.md +497 -0
  5. package/dist/akm +148 -35
  6. package/dist/{akm-migrate-storage → akm-migrate} +6 -9
  7. package/dist/assets/hints/cli-hints-full.md +223 -95
  8. package/dist/assets/hints/cli-hints-short.md +85 -22
  9. package/dist/assets/improve-strategies/default.json +1 -1
  10. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  11. package/dist/assets/prompts/memory-infer-user.md +2 -3
  12. package/dist/assets/stash-skeleton/README.md +6 -5
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
  24. package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
  25. package/dist/assets/tasks/core/extract.yml +1 -1
  26. package/dist/assets/tasks/core/version-check.yml +1 -1
  27. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  28. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  29. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  30. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  31. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  32. package/dist/assets/templates/html/health.html +1 -3
  33. package/dist/assets/workflows/workflow-template.md +32 -15
  34. package/dist/cli/invocation.js +40 -15
  35. package/dist/cli/parse-args.js +0 -22
  36. package/dist/cli/retired-commands.js +121 -0
  37. package/dist/cli/shared.js +154 -22
  38. package/dist/cli/unknown-flags.js +236 -0
  39. package/dist/cli-node.mjs +2 -1
  40. package/dist/cli.js +696 -258
  41. package/dist/commands/agent/agent-dispatch.js +14 -3
  42. package/dist/commands/agent/contribute-cli.js +73 -88
  43. package/dist/commands/completions.js +79 -22
  44. package/dist/commands/config-cli.js +17 -150
  45. package/dist/commands/env/env-cli.js +59 -143
  46. package/dist/commands/env/env.js +12 -163
  47. package/dist/commands/env/marker-path.js +6 -0
  48. package/dist/commands/env/secret-cli.js +36 -66
  49. package/dist/commands/env/secret.js +24 -57
  50. package/dist/commands/feedback-cli.js +141 -87
  51. package/dist/commands/health/accept-rate.js +58 -0
  52. package/dist/commands/health/advisories.js +3 -4
  53. package/dist/commands/health/checks.js +85 -23
  54. package/dist/commands/health/html-report.js +7 -10
  55. package/dist/commands/health/improve-metrics.js +25 -83
  56. package/dist/commands/health/md-report.js +5 -9
  57. package/dist/commands/health/metrics.js +62 -20
  58. package/dist/commands/health/renderers.js +47 -0
  59. package/dist/commands/health/report-view-model.js +4 -5
  60. package/dist/commands/health/stash-exposure.js +1 -1
  61. package/dist/commands/health/surfaces.js +3 -48
  62. package/dist/commands/health/task-runs.js +3 -67
  63. package/dist/commands/health/types-improve.js +7 -0
  64. package/dist/commands/health.js +99 -28
  65. package/dist/commands/improve/anti-collapse.js +2 -2
  66. package/dist/commands/improve/autonomy-gate.js +68 -0
  67. package/dist/commands/improve/collapse-detector.js +41 -40
  68. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  69. package/dist/commands/improve/consolidate/merge.js +4 -0
  70. package/dist/commands/improve/consolidate.js +140 -1000
  71. package/dist/commands/improve/distill/promote-memory.js +12 -12
  72. package/dist/commands/improve/distill/quality-gate.js +6 -6
  73. package/dist/commands/improve/distill.js +58 -69
  74. package/dist/commands/improve/eligibility.js +95 -55
  75. package/dist/commands/improve/extract-cli.js +14 -133
  76. package/dist/commands/improve/improve-cli.js +98 -114
  77. package/dist/commands/improve/improve-result-file.js +1 -28
  78. package/dist/commands/improve/improve-strategies.js +8 -5
  79. package/dist/commands/improve/improve.js +128 -91
  80. package/dist/commands/improve/loop-stages.js +182 -20
  81. package/dist/commands/improve/memory/derived-ref.js +45 -43
  82. package/dist/commands/improve/memory/memory-belief.js +1 -1
  83. package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
  84. package/dist/commands/improve/memory/memory-improve.js +6 -5
  85. package/dist/commands/improve/outcome-loop.js +22 -65
  86. package/dist/commands/improve/preparation.js +114 -123
  87. package/dist/commands/improve/proactive-maintenance.js +2 -5
  88. package/dist/commands/improve/reflect.js +56 -160
  89. package/dist/commands/improve/salience.js +11 -122
  90. package/dist/commands/improve/source-identity.js +10 -38
  91. package/dist/commands/lint/base-linter.js +20 -124
  92. package/dist/commands/lint/env-key-rules.js +31 -47
  93. package/dist/commands/lint/index.js +249 -43
  94. package/dist/commands/{events.js → log.js} +33 -38
  95. package/dist/commands/migrate-cli.js +92 -12
  96. package/dist/commands/migration-tool.js +46 -0
  97. package/dist/commands/observability-cli.js +70 -209
  98. package/dist/commands/proposal/drain.js +101 -29
  99. package/dist/commands/proposal/proposal-cli.js +76 -48
  100. package/dist/commands/proposal/proposal.js +54 -18
  101. package/dist/commands/proposal/propose-cli.js +88 -0
  102. package/dist/commands/proposal/propose.js +23 -15
  103. package/dist/commands/proposal/repository.js +701 -278
  104. package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
  105. package/dist/commands/proposal/validators/proposal-validators.js +55 -7
  106. package/dist/commands/proposal/validators/proposals.js +4 -7
  107. package/dist/commands/read/curate.js +34 -53
  108. package/dist/commands/read/knowledge.js +150 -95
  109. package/dist/commands/read/registry-search.js +2 -2
  110. package/dist/commands/read/remember-cli.js +42 -15
  111. package/dist/commands/read/search-cli.js +180 -78
  112. package/dist/commands/read/search.js +58 -43
  113. package/dist/commands/read/show.js +197 -141
  114. package/dist/commands/registry-cli.js +12 -51
  115. package/dist/commands/remember.js +14 -57
  116. package/dist/commands/sources/add-cli.js +100 -31
  117. package/dist/commands/sources/bundle-cli.js +166 -0
  118. package/dist/commands/sources/bundle-config-ops.js +7 -2
  119. package/dist/commands/sources/info.js +18 -5
  120. package/dist/commands/sources/init.js +12 -12
  121. package/dist/commands/sources/installed-stashes.js +382 -98
  122. package/dist/commands/sources/schema-repair.js +3 -2
  123. package/dist/commands/sources/self-update.js +131 -38
  124. package/dist/commands/sources/source-add.js +72 -17
  125. package/dist/commands/sources/source-clone.js +129 -45
  126. package/dist/commands/sources/source-manage.js +43 -23
  127. package/dist/commands/sources/sources-cli.js +57 -208
  128. package/dist/commands/sources/stash-cli.js +46 -53
  129. package/dist/commands/tasks/tasks-cli.js +91 -97
  130. package/dist/commands/tasks/tasks.js +276 -421
  131. package/dist/commands/workflow-cli.js +175 -450
  132. package/dist/core/adapter/adapters/akm-adapter.js +47 -28
  133. package/dist/core/adapter/adapters/akm-lint.js +42 -27
  134. package/dist/core/adapter/adapters/akm-metadata.js +15 -44
  135. package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
  136. package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
  137. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  138. package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
  139. package/dist/core/adapter/adapters/index.js +6 -6
  140. package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
  141. package/dist/core/adapter/adapters/okf-adapter.js +187 -19
  142. package/dist/core/adapter/adapters/shared.js +3 -19
  143. package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
  144. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
  145. package/dist/core/adapter/detect-adapter.js +17 -0
  146. package/dist/core/adapter/recognize-match.js +6 -4
  147. package/dist/core/adapter/validate-context.js +214 -0
  148. package/dist/core/asset/akm-markdown.js +63 -0
  149. package/dist/core/asset/asset-placement.js +20 -6
  150. package/dist/core/asset/asset-ref.js +11 -9
  151. package/dist/core/asset/frontmatter-lint.js +30 -0
  152. package/dist/core/asset/frontmatter.js +25 -9
  153. package/dist/core/asset/markdown.js +40 -51
  154. package/dist/core/asset/resolve-ref.js +89 -18
  155. package/dist/core/asset/stash-meta.js +1 -1
  156. package/dist/core/bundle-id.js +51 -0
  157. package/dist/core/common.js +152 -38
  158. package/dist/core/config/config-io.js +12 -1
  159. package/dist/core/config/config-schema.js +35 -8
  160. package/dist/core/config/config-sources.js +55 -11
  161. package/dist/core/config/config-walker.js +25 -9
  162. package/dist/core/config/config.js +9 -48
  163. package/dist/core/config/experimental.js +21 -0
  164. package/dist/core/config/schema/embedding.js +5 -1
  165. package/dist/core/config/schema/experimental.js +30 -0
  166. package/dist/core/config/schema/improve-processes.js +0 -6
  167. package/dist/core/config/schema/improve.js +21 -3
  168. package/dist/core/config/schema/index-config.js +8 -15
  169. package/dist/core/config/schema/output.js +4 -1
  170. package/dist/core/config/schema/setup.js +9 -18
  171. package/dist/core/config/schema/sources-bundles.js +49 -33
  172. package/dist/core/config/schema/workflow.js +3 -3
  173. package/dist/core/env-secret-ref.js +76 -46
  174. package/dist/core/errors.js +18 -12
  175. package/dist/core/events.js +46 -128
  176. package/dist/core/file-change.js +6 -5
  177. package/dist/core/fs-txn.js +83 -7
  178. package/dist/core/git-message.js +2 -2
  179. package/dist/core/improve-result.js +1 -100
  180. package/dist/core/lesson-lint.js +1 -17
  181. package/dist/core/logs-db.js +2 -1
  182. package/dist/core/migration-operation.js +16 -0
  183. package/dist/core/mutation-target.js +78 -0
  184. package/dist/core/parse.js +4 -1
  185. package/dist/core/paths.js +17 -20
  186. package/dist/core/recognition-util.js +12 -14
  187. package/dist/core/redaction.js +34 -0
  188. package/dist/core/standards/resolve-standards-context.js +2 -14
  189. package/dist/core/standards/resolve-stash-standards.js +2 -2
  190. package/dist/core/standards/resolve-type-conventions.js +2 -2
  191. package/dist/core/state/migrations.js +41 -18
  192. package/dist/core/state-db.js +5 -14
  193. package/dist/core/structured.js +1 -1
  194. package/dist/core/subprocess.js +6 -4
  195. package/dist/core/text-truncation.js +9 -5
  196. package/dist/core/type-presentation.js +3 -3
  197. package/dist/core/warn.js +0 -3
  198. package/dist/core/write-source.js +771 -95
  199. package/dist/indexer/bundle-identity-guard.js +3 -2
  200. package/dist/indexer/db/graph-db.js +0 -24
  201. package/dist/indexer/ensure-index.js +1 -0
  202. package/dist/indexer/graph/graph-boost.js +9 -34
  203. package/dist/indexer/graph/graph-extraction.js +8 -5
  204. package/dist/indexer/index-writer-lock.js +53 -17
  205. package/dist/indexer/index-written-assets.js +16 -22
  206. package/dist/indexer/indexer.js +497 -239
  207. package/dist/indexer/installations.js +14 -96
  208. package/dist/indexer/passes/dir-staleness.js +16 -9
  209. package/dist/indexer/passes/memory-inference.js +11 -9
  210. package/dist/indexer/passes/metadata.js +113 -47
  211. package/dist/indexer/scan/doc-to-entry.js +38 -1
  212. package/dist/indexer/scan/drain-dir.js +13 -23
  213. package/dist/indexer/search/db-search.js +99 -54
  214. package/dist/indexer/search/fts-query.js +47 -24
  215. package/dist/indexer/search/ranking-contributors.js +42 -20
  216. package/dist/indexer/search/ranking.js +18 -99
  217. package/dist/indexer/search/search-fields.js +7 -2
  218. package/dist/indexer/search/search-source.js +82 -93
  219. package/dist/indexer/usage/usage-events.js +0 -89
  220. package/dist/indexer/walk/file-context.js +2 -1
  221. package/dist/indexer/walk/matchers.js +30 -43
  222. package/dist/indexer/walk/path-resolver.js +7 -2
  223. package/dist/indexer/walk/walker.js +38 -12
  224. package/dist/integrations/agent/builders.js +0 -6
  225. package/dist/integrations/agent/config.js +2 -2
  226. package/dist/integrations/agent/detect.js +49 -19
  227. package/dist/integrations/agent/engine-fallback.js +76 -0
  228. package/dist/integrations/agent/profiles.js +14 -0
  229. package/dist/integrations/agent/prompts.js +12 -8
  230. package/dist/integrations/agent/runner-dispatch.js +4 -2
  231. package/dist/integrations/agent/runner.js +0 -1
  232. package/dist/integrations/agent/spawn.js +5 -6
  233. package/dist/integrations/github.js +1 -1
  234. package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
  235. package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
  236. package/dist/integrations/harnesses/claude/session-log.js +0 -10
  237. package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
  238. package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
  239. package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
  240. package/dist/integrations/harnesses/index.js +3 -7
  241. package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
  242. package/dist/integrations/harnesses/opencode/session-log.js +0 -15
  243. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
  244. package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
  245. package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
  246. package/dist/integrations/lockfile.js +88 -7
  247. package/dist/integrations/session-logs/index.js +3 -28
  248. package/dist/llm/client.js +136 -100
  249. package/dist/llm/embedders/remote.js +13 -5
  250. package/dist/llm/feature-gate.js +4 -12
  251. package/dist/llm/graph-extract.js +5 -11
  252. package/dist/llm/memory-infer.js +144 -1
  253. package/dist/llm/metadata-enhance.js +5 -7
  254. package/dist/llm/structured-call.js +1 -1
  255. package/dist/llm/usage-persist.js +26 -5
  256. package/dist/llm/usage-telemetry.js +25 -2
  257. package/dist/output/cli-hints.js +1 -2
  258. package/dist/output/context.js +22 -7
  259. package/dist/output/format-exempt.js +80 -0
  260. package/dist/output/generic-render.js +259 -0
  261. package/dist/output/render-registry.js +57 -0
  262. package/dist/output/renderers.js +14 -36
  263. package/dist/output/shapes/curate.js +10 -1
  264. package/dist/output/shapes/events.js +12 -7
  265. package/dist/output/shapes/helpers.js +56 -83
  266. package/dist/output/shapes/migrate.js +8 -0
  267. package/dist/output/shapes/passthrough.js +7 -41
  268. package/dist/output/shapes/proposal/producer.js +15 -7
  269. package/dist/output/shapes.js +2 -9
  270. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  271. package/dist/output/text/bundle-show.js +7 -0
  272. package/dist/output/text/command-format.js +164 -96
  273. package/dist/output/text/env.js +1 -3
  274. package/dist/output/text/events.js +8 -7
  275. package/dist/output/text/health-format.js +103 -0
  276. package/dist/output/text/health.js +7 -0
  277. package/dist/output/text/helpers.js +10 -8
  278. package/dist/output/text/lint-format.js +43 -0
  279. package/dist/output/text/{save.js → lint.js} +2 -2
  280. package/dist/output/text/migrate.js +88 -0
  281. package/dist/output/text/proposal/producer.js +4 -2
  282. package/dist/output/text/proposal-format.js +44 -72
  283. package/dist/output/text/registry-commands.js +1 -2
  284. package/dist/output/text/show-directives.js +15 -7
  285. package/dist/output/text/status-list.js +32 -0
  286. package/dist/output/text/sync.js +5 -0
  287. package/dist/output/text/workflow-format.js +24 -203
  288. package/dist/output/text/workflow.js +1 -7
  289. package/dist/output/text.js +16 -17
  290. package/dist/registry/factory.js +4 -6
  291. package/dist/registry/origin-resolve.js +16 -27
  292. package/dist/registry/providers/skills-sh.js +3 -3
  293. package/dist/registry/providers/static-index.js +13 -23
  294. package/dist/registry/resolve.js +42 -7
  295. package/dist/registry/semver.js +34 -84
  296. package/dist/runtime.js +2 -23
  297. package/dist/scripts/akm-migrate-node.js +60290 -0
  298. package/dist/scripts/akm-migrate.js +59628 -0
  299. package/dist/setup/detect.js +42 -15
  300. package/dist/setup/registry-stash-loader.js +2 -2
  301. package/dist/setup/setup.js +236 -136
  302. package/dist/setup/steps/connection.js +7 -9
  303. package/dist/setup/steps/platforms.js +9 -9
  304. package/dist/setup/steps/semantic.js +15 -3
  305. package/dist/setup/steps/sources.js +12 -13
  306. package/dist/setup/steps/stashdir.js +2 -3
  307. package/dist/setup/steps/tasks.js +237 -120
  308. package/dist/sources/freshness.js +1 -1
  309. package/dist/sources/provider-factory.js +11 -17
  310. package/dist/sources/providers/filesystem.js +2 -3
  311. package/dist/sources/providers/git-install.js +278 -34
  312. package/dist/sources/providers/git-provider.js +25 -23
  313. package/dist/sources/providers/git-stash.js +395 -106
  314. package/dist/sources/providers/git.js +2 -2
  315. package/dist/sources/providers/npm.js +16 -19
  316. package/dist/sources/providers/provider-utils.js +7 -4
  317. package/dist/sources/providers/sync-from-ref.js +3 -9
  318. package/dist/sources/providers/website.js +6 -1
  319. package/dist/sources/resolve.js +6 -5
  320. package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
  321. package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
  322. package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
  323. package/dist/sources/snapshot-fetchers/github.js +100 -0
  324. package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
  325. package/dist/sources/snapshot-fetchers/registry.js +17 -1
  326. package/dist/sources/snapshot-fetchers/robots.js +348 -0
  327. package/dist/sources/snapshot-fetchers/rss.js +282 -0
  328. package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
  329. package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
  330. package/dist/sources/snapshot-fetchers/x.js +910 -0
  331. package/dist/storage/database.js +7 -0
  332. package/dist/storage/engines/sqlite-migrations.js +23 -111
  333. package/dist/storage/managed-db.js +2 -2
  334. package/dist/storage/repositories/canaries-repository.js +1 -1
  335. package/dist/storage/repositories/events-repository.js +27 -11
  336. package/dist/storage/repositories/improve-runs-repository.js +6 -12
  337. package/dist/storage/repositories/index-connection.js +17 -6
  338. package/dist/storage/repositories/index-entries-repository.js +151 -240
  339. package/dist/storage/repositories/index-entry-mapper.js +15 -11
  340. package/dist/storage/repositories/index-fts-repository.js +5 -2
  341. package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
  342. package/dist/storage/repositories/index-meta-repository.js +2 -3
  343. package/dist/storage/repositories/index-schema.js +10 -25
  344. package/dist/storage/repositories/index-utility-repository.js +15 -28
  345. package/dist/storage/repositories/index-vec-repository.js +6 -1
  346. package/dist/storage/repositories/outcome-repository.js +119 -0
  347. package/dist/storage/repositories/proposals-repository.js +296 -59
  348. package/dist/storage/repositories/registry-cache.js +19 -0
  349. package/dist/storage/repositories/salience-repository.js +172 -0
  350. package/dist/storage/repositories/task-history-repository.js +15 -13
  351. package/dist/storage/repositories/workflow-runs-repository.js +52 -40
  352. package/dist/tasks/backends/cron.js +105 -15
  353. package/dist/tasks/backends/index.js +1 -1
  354. package/dist/tasks/backends/launchd.js +85 -38
  355. package/dist/tasks/backends/schtasks.js +135 -15
  356. package/dist/tasks/embedded.js +56 -40
  357. package/dist/tasks/parser.js +7 -157
  358. package/dist/tasks/resolve-akm-bin.js +137 -59
  359. package/dist/tasks/runner.js +79 -42
  360. package/dist/tasks/scheduler-invocation.js +220 -10
  361. package/dist/tasks/schema.js +24 -1
  362. package/dist/tasks/task-id.js +1 -3
  363. package/dist/tasks/validator.js +20 -6
  364. package/dist/workflows/authoring/authoring.js +94 -143
  365. package/dist/workflows/authoring/scope-key.js +1 -1
  366. package/dist/workflows/exec/frozen-judge.js +28 -2
  367. package/dist/workflows/exec/native-executor.js +77 -57
  368. package/dist/workflows/exec/param-secrets.js +9 -9
  369. package/dist/workflows/exec/run-workflow.js +133 -79
  370. package/dist/workflows/exec/step-work.js +219 -346
  371. package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
  372. package/dist/workflows/ir/compile.js +141 -270
  373. package/dist/workflows/ir/freeze.js +40 -30
  374. package/dist/workflows/ir/params.js +135 -11
  375. package/dist/workflows/ir/plan-hash.js +1 -1
  376. package/dist/workflows/ir/schema.js +25 -26
  377. package/dist/workflows/parser.js +872 -307
  378. package/dist/workflows/program/expressions.js +20 -208
  379. package/dist/workflows/program/schema.js +7 -10
  380. package/dist/workflows/renderer.js +95 -68
  381. package/dist/workflows/resource-limits.js +2 -0
  382. package/dist/workflows/runtime/checkin.js +3 -3
  383. package/dist/workflows/runtime/plan-classifier.js +16 -75
  384. package/dist/workflows/runtime/runs.js +186 -127
  385. package/dist/workflows/runtime/unit-checkin.js +1 -1
  386. package/dist/workflows/runtime/unit-phases.js +2 -2
  387. package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
  388. package/dist/workflows/schema.js +1 -11
  389. package/dist/workflows/validate-summary.js +30 -36
  390. package/dist/workflows/validator.js +21 -62
  391. package/docs/README.md +68 -0
  392. package/docs/migration/README.md +8 -0
  393. package/docs/migration/release-notes/0.7.0.md +11 -11
  394. package/docs/migration/release-notes/0.9.0.md +208 -27
  395. package/docs/migration/v0.7-to-v0.8.md +46 -47
  396. package/docs/migration/v0.8-to-v0.9.md +564 -208
  397. package/docs/migration/v0.9.0-troubleshooting.md +561 -0
  398. package/docs/reference/README.md +12 -0
  399. package/docs/reference/cli.md +2253 -0
  400. package/docs/reference/configuration.md +358 -0
  401. package/docs/reference/data-and-telemetry.md +105 -42
  402. package/docs/reference/workflows.md +647 -0
  403. package/package.json +22 -11
  404. package/schemas/akm-asset-envelope.json +93 -0
  405. package/schemas/akm-config.json +81 -128
  406. package/schemas/akm-workflow.json +74 -73
  407. package/dist/assets/tasks/core/backup.yml +0 -5
  408. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  409. package/dist/cli/config-migrate.js +0 -1878
  410. package/dist/cli/config-validate.js +0 -41
  411. package/dist/commands/backup-cli.js +0 -56
  412. package/dist/commands/bundle/bundle-cli.js +0 -68
  413. package/dist/commands/bundle/bundle.js +0 -219
  414. package/dist/commands/graph/graph-cli.js +0 -124
  415. package/dist/commands/graph/graph.js +0 -489
  416. package/dist/commands/improve/extract-watch.js +0 -140
  417. package/dist/commands/mv-cli.js +0 -1221
  418. package/dist/commands/sources/history.js +0 -201
  419. package/dist/commands/tasks/default-tasks.js +0 -186
  420. package/dist/core/migration-backup.js +0 -1231
  421. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
  422. package/dist/llm/memory-infer-impl.js +0 -138
  423. package/dist/migrate/legacy/config-source-migration.js +0 -257
  424. package/dist/migrate/legacy/content-migration.js +0 -350
  425. package/dist/migrate/legacy/legacy-layout.js +0 -779
  426. package/dist/migrate/legacy/legacy-paths.js +0 -25
  427. package/dist/migrate/legacy/legacy-stash-json.js +0 -80
  428. package/dist/migrate/legacy/proposal-fs-import.js +0 -168
  429. package/dist/migrate/legacy/task-target-ref-migration.js +0 -278
  430. package/dist/migrate/legacy/three-db-cutover.js +0 -845
  431. package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
  432. package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
  433. package/dist/migrate/legacy-ref-grammar.js +0 -214
  434. package/dist/output/shapes/distill.js +0 -14
  435. package/dist/output/shapes/history.js +0 -11
  436. package/dist/output/text/distill.js +0 -6
  437. package/dist/output/text/enable-disable.js +0 -8
  438. package/dist/output/text/history.js +0 -6
  439. package/dist/registry/build-index.js +0 -382
  440. package/dist/schemas/akm-config.json +0 -4704
  441. package/dist/schemas/akm-task.json +0 -87
  442. package/dist/schemas/akm-workflow.json +0 -372
  443. package/dist/scripts/migrate-storage.js +0 -3816
  444. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  445. package/dist/workflows/cli.js +0 -53
  446. package/dist/workflows/exec/brief.js +0 -481
  447. package/dist/workflows/exec/report.js +0 -1460
  448. package/dist/workflows/exec/watch.js +0 -116
  449. package/dist/workflows/program/parser.js +0 -813
  450. package/dist/workflows/program/project.js +0 -104
@@ -24,7 +24,7 @@ function validateDetail(value) {
24
24
  metadataError("detail.exitCode must be a number or null");
25
25
  }
26
26
  }
27
- /** Decode historical v1 and current v2 metadata without cross-version aliases. */
27
+ /** Decode supported historical and current task-history metadata. */
28
28
  export function decodeTaskHistoryMetadata(input) {
29
29
  let parsed = input;
30
30
  if (typeof input === "string") {
@@ -37,27 +37,26 @@ export function decodeTaskHistoryMetadata(input) {
37
37
  }
38
38
  if (!isRecord(parsed))
39
39
  metadataError("root must be an object");
40
- const version = parsed.metadataVersion ?? 1;
41
- if (version === 1) {
40
+ if (parsed.metadataVersion === undefined || parsed.metadataVersion === 1) {
42
41
  const allowed = new Set(["metadataVersion", "durationMs", "detail", "profile"]);
43
42
  const unknown = Object.keys(parsed).filter((key) => !allowed.has(key));
44
43
  if (unknown.length > 0)
45
44
  metadataError(`unknown v1 fields: ${unknown.sort().join(", ")}`);
46
- if (parsed.metadataVersion !== undefined && parsed.metadataVersion !== 1)
47
- metadataError("invalid v1 metadataVersion");
48
- if (parsed.durationMs !== undefined && typeof parsed.durationMs !== "number")
45
+ if (parsed.durationMs !== undefined && typeof parsed.durationMs !== "number") {
49
46
  metadataError("durationMs must be a number");
50
- if (parsed.profile !== undefined && typeof parsed.profile !== "string")
47
+ }
48
+ if (parsed.profile !== undefined && typeof parsed.profile !== "string") {
51
49
  metadataError("profile must be a string");
50
+ }
52
51
  validateDetail(parsed.detail);
53
52
  return {
54
53
  metadataVersion: 1,
55
- ...(typeof parsed.durationMs === "number" ? { durationMs: parsed.durationMs } : {}),
56
- ...(parsed.detail !== undefined && parsed.detail !== null ? { detail: parsed.detail } : {}),
57
- ...(typeof parsed.profile === "string" ? { legacyProfile: parsed.profile } : {}),
54
+ durationMs: parsed.durationMs ?? 0,
55
+ detail: parsed.detail ?? null,
56
+ ...(parsed.profile !== undefined ? { legacyProfile: parsed.profile } : {}),
58
57
  };
59
58
  }
60
- if (version === 2) {
59
+ if (parsed.metadataVersion === 2) {
61
60
  const allowed = new Set(["metadataVersion", "durationMs", "detail", "engine"]);
62
61
  const unknown = Object.keys(parsed).filter((key) => !allowed.has(key));
63
62
  if (unknown.length > 0)
@@ -77,7 +76,7 @@ export function decodeTaskHistoryMetadata(input) {
77
76
  ...(parsed.engine !== undefined ? { engine: parsed.engine } : {}),
78
77
  };
79
78
  }
80
- metadataError(`unsupported metadataVersion: ${String(version)}`);
79
+ metadataError(`unsupported metadataVersion: ${String(parsed.metadataVersion)}`);
81
80
  }
82
81
  /**
83
82
  * Atomically reserve one task-attempt identity.
@@ -169,10 +168,13 @@ export function queryTaskHistory(db, options = {}) {
169
168
  params.push(options.targetRef);
170
169
  }
171
170
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
171
+ const limit = options.limit !== undefined ? "LIMIT ?" : "";
172
+ if (options.limit !== undefined)
173
+ params.push(options.limit);
172
174
  return db
173
175
  .prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
174
176
  target_kind, target_ref, metadata_json
175
- FROM task_history ${where} ORDER BY started_at DESC, id DESC`)
177
+ FROM task_history ${where} ORDER BY started_at DESC, id DESC ${limit}`)
176
178
  .all(...params);
177
179
  }
178
180
  /**
@@ -1,7 +1,7 @@
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 { openStateDatabase } from "../../core/state-db.js";
4
+ import { openStateDatabase, withImmediateTransaction } from "../../core/state-db.js";
5
5
  import { resolveStorageLocations } from "../locations.js";
6
6
  /**
7
7
  * Repository owning every raw SQL statement against `workflow_runs` and
@@ -30,19 +30,28 @@ export class WorkflowRunsRepository {
30
30
  transaction(fn) {
31
31
  return this.db.transaction(fn)();
32
32
  }
33
+ immediateTransaction(fn) {
34
+ return withImmediateTransaction(this.db, () => fn(this.db));
35
+ }
33
36
  // ── reads (fully materialised) ─────────────────────────────────────────────
34
- findActiveRunForScope(workflowRef, scopeKey) {
37
+ findActiveRunForScope(workflowRefs, scopeKey) {
38
+ const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
39
+ if (refs.length === 0)
40
+ return undefined;
35
41
  return this.db
36
- .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")
37
- .get(workflowRef, scopeKey);
42
+ .prepare(`SELECT id, current_step_id FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
43
+ .get(...refs, scopeKey);
38
44
  }
39
45
  getRunById(runId) {
40
46
  return this.db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(runId);
41
47
  }
42
- getActiveRunRowForScope(workflowRef, scopeKey) {
48
+ getActiveRunRowForScope(workflowRefs, scopeKey) {
49
+ const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
50
+ if (refs.length === 0)
51
+ return undefined;
43
52
  return this.db
44
- .prepare("SELECT * FROM workflow_runs WHERE workflow_ref = ? AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
45
- .get(workflowRef, scopeKey);
53
+ .prepare(`SELECT * FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
54
+ .get(...refs, scopeKey);
46
55
  }
47
56
  hasRun(runId) {
48
57
  const row = this.db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
@@ -57,6 +66,10 @@ export class WorkflowRunsRepository {
57
66
  filters.push("workflow_ref = ?");
58
67
  params.push(filter.workflowRef);
59
68
  }
69
+ else if (filter.workflowRefs && filter.workflowRefs.length > 0) {
70
+ filters.push(`workflow_ref IN (${filter.workflowRefs.map(() => "?").join(", ")})`);
71
+ params.push(...filter.workflowRefs);
72
+ }
60
73
  if (filter.activeOnly) {
61
74
  // `activeOnly` means EXACTLY status='active' — a run currently
62
75
  // executable. A `blocked` run is NOT active (it is parked awaiting a
@@ -113,7 +126,9 @@ export class WorkflowRunsRepository {
113
126
  .run(runId, currentStepId);
114
127
  }
115
128
  markRunActive(runId, updatedAt) {
116
- this.db.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(updatedAt, runId);
129
+ this.db
130
+ .prepare("UPDATE workflow_runs SET status = 'active', updated_at = ?, engine_lease_holder = NULL, engine_lease_until = NULL WHERE id = ?")
131
+ .run(updatedAt, runId);
117
132
  }
118
133
  updateStepCompletion(input) {
119
134
  this.db
@@ -129,6 +144,14 @@ export class WorkflowRunsRepository {
129
144
  WHERE id = ?`)
130
145
  .run(input.status, input.currentStepId, input.updatedAt, input.completedAt, input.checkinArmedAt, input.runId);
131
146
  }
147
+ markRunAbandoned(runId, updatedAt) {
148
+ const result = this.db
149
+ .prepare(`UPDATE workflow_runs
150
+ SET status = 'failed', updated_at = ?, completed_at = ?, checkin_armed_at = ?
151
+ WHERE id = ? AND status IN ('active', 'blocked')`)
152
+ .run(updatedAt, updatedAt, updatedAt, runId);
153
+ return Number(result.changes) === 1;
154
+ }
132
155
  rearmCheckin(runId, checkinArmedAt) {
133
156
  this.db.prepare("UPDATE workflow_runs SET checkin_armed_at = ? WHERE id = ?").run(checkinArmedAt, runId);
134
157
  }
@@ -161,7 +184,8 @@ export class WorkflowRunsRepository {
161
184
  const result = this.db
162
185
  .prepare(`UPDATE workflow_runs
163
186
  SET engine_lease_holder = ?, engine_lease_until = ?
164
- WHERE id = ? AND (engine_lease_holder IS NULL OR engine_lease_until IS NULL OR engine_lease_until < ?)`)
187
+ WHERE id = ? AND status = 'active'
188
+ AND (engine_lease_holder IS NULL OR engine_lease_until IS NULL OR engine_lease_until < ?)`)
165
189
  .run(holder, until, runId, now);
166
190
  return Number(result.changes) > 0;
167
191
  }
@@ -172,18 +196,18 @@ export class WorkflowRunsRepository {
172
196
  */
173
197
  renewEngineLease(runId, holder, until) {
174
198
  const result = this.db
175
- .prepare("UPDATE workflow_runs SET engine_lease_until = ? WHERE id = ? AND engine_lease_holder = ?")
199
+ .prepare("UPDATE workflow_runs SET engine_lease_until = ? WHERE id = ? AND engine_lease_holder = ? AND status = 'active'")
176
200
  .run(until, runId, holder);
177
201
  return Number(result.changes) > 0;
178
202
  }
179
203
  /**
180
- * Clear the lease only while `holder` still owns it, so a crashed-then-
181
- * recovered invocation can never release a lease another engine has since
182
- * claimed. Releasing an already-lost lease is a harmless no-op.
204
+ * Clear the lease only while `holder` still owns a non-failed run. Failed
205
+ * runs retain the final holder/expiry for forensics until explicit resume.
206
+ * Releasing an already-lost lease is a harmless no-op.
183
207
  */
184
208
  releaseEngineLease(runId, holder) {
185
209
  this.db
186
- .prepare("UPDATE workflow_runs SET engine_lease_holder = NULL, engine_lease_until = NULL WHERE id = ? AND engine_lease_holder = ?")
210
+ .prepare("UPDATE workflow_runs SET engine_lease_holder = NULL, engine_lease_until = NULL WHERE id = ? AND engine_lease_holder = ? AND status <> 'failed'")
187
211
  .run(runId, holder);
188
212
  }
189
213
  // ── unit rows (migration 004) ──────────────────────────────────────────────
@@ -202,25 +226,11 @@ export class WorkflowRunsRepository {
202
226
  .prepare("SELECT * FROM workflow_run_units WHERE run_id = ? AND step_id = ? ORDER BY started_at ASC, unit_id ASC")
203
227
  .all(runId, stepId);
204
228
  }
205
- /** One unit row by primary key, or undefined. Used by the R3 report path's
206
- * guarded read-then-write (idempotency / replay-divergence / claim checks). */
229
+ /** One unit row by primary key, or undefined. Used for guarded read-then-write
230
+ * (idempotency / replay-divergence / claim checks). */
207
231
  getUnit(runId, unitId) {
208
232
  return this.db.prepare("SELECT * FROM workflow_run_units WHERE run_id = ? AND unit_id = ?").get(runId, unitId);
209
233
  }
210
- /**
211
- * Stamp a unit-level check-in heartbeat (migration 007, R3 driver protocol):
212
- * a driver executing a unit calls `report --status running` to claim (first
213
- * call) or heartbeat (subsequent) it. Sets `status = 'running'` and
214
- * `last_checkin_at`; NEVER touches `started_at` (the first-claim marker set by
215
- * {@link insertUnit}) so the heartbeat window advances without resetting the
216
- * claim time. The caller guards against re-claiming a terminal unit inside its
217
- * transaction.
218
- */
219
- updateUnitCheckin(runId, unitId, lastCheckinAt) {
220
- this.db
221
- .prepare("UPDATE workflow_run_units SET status = 'running', last_checkin_at = ? WHERE run_id = ? AND unit_id = ?")
222
- .run(lastCheckinAt, runId, unitId);
223
- }
224
234
  /**
225
235
  * Insert a unit row in `running` state (a dispatch is starting now).
226
236
  *
@@ -267,20 +277,22 @@ export class WorkflowRunsRepository {
267
277
  .run(input.runId, input.unitId, input.stepId, input.nodeId, input.parentUnitId, input.phase, input.runner, input.engine ?? null, input.model, input.inputHash, input.worktreePath ?? null, input.startedAt, input.claimHolder ?? null, input.claimExpiresAt ?? null);
268
278
  }
269
279
  /**
270
- * Stamp a `--status running` claim + heartbeat on a unit row (migration 009,
271
- * PR #714 review round 2). Sets `status = 'running'`, refreshes the heartbeat
272
- * (`last_checkin_at`), and (re)writes the claim owner + expiry. Called inside
273
- * the report path's running-claim transaction AFTER it has validated that the
274
- * claim is free / expired / already held by this holder, so the write is the
275
- * final step of a checked reclaim. Never touches `started_at` (the first-claim
276
- * marker set by {@link insertUnit}).
280
+ * Stamp a `running` claim + heartbeat on a unit row (migration 009, PR #714
281
+ * review round 2). Sets `status = 'running'`, refreshes the heartbeat
282
+ * (`last_checkin_at`), and (re)writes the claim owner + expiry. Must be called
283
+ * inside a transaction that has ALREADY validated the claim is free / expired
284
+ * / already held by this holder, so the write is the final step of a checked
285
+ * reclaim. Never touches `started_at` (the first-claim marker set by
286
+ * {@link insertUnit}).
277
287
  */
278
288
  updateUnitClaim(runId, unitId, holder, expiresAt, lastCheckinAt) {
279
- this.db
289
+ const result = this.db
280
290
  .prepare(`UPDATE workflow_run_units
281
291
  SET status = 'running', last_checkin_at = ?, claim_holder = ?, claim_expires_at = ?
282
- WHERE run_id = ? AND unit_id = ?`)
283
- .run(lastCheckinAt, holder, expiresAt, runId, unitId);
292
+ WHERE run_id = ? AND unit_id = ?
293
+ AND EXISTS (SELECT 1 FROM workflow_runs WHERE id = ? AND status = 'active')`)
294
+ .run(lastCheckinAt, holder, expiresAt, runId, unitId, runId);
295
+ return Number(result.changes) === 1;
284
296
  }
285
297
  /**
286
298
  * Record a unit's terminal state (completed / failed / skipped).
@@ -1,13 +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
- // crontab backend for `akm tasks` (Linux default).
4
+ // crontab backend for `akm task` (Linux default).
5
5
  //
6
6
  // Each akm-owned entry is wrapped in markers so a hand-edited crontab keeps
7
7
  // its other lines untouched:
8
8
  //
9
9
  // # akm:task <id> BEGIN
10
- // [SCHED] /abs/akm tasks run <id> >> /home/.../tasks/logs/<id>.log 2>&1
10
+ // [SCHED] /abs/akm task run <id> >> /home/.../tasks/logs/<id>.log 2>&1
11
11
  // # akm:task <id> END
12
12
  //
13
13
  // The backend reads/writes the user's crontab via `crontab -l` and
@@ -30,13 +30,14 @@ import { ConfigError } from "../../core/errors.js";
30
30
  import { getTaskLogDir } from "../../core/paths.js";
31
31
  import { resolveAkmInvocation } from "../resolve-akm-bin.js";
32
32
  import { parseSchedule, translateToCron } from "../schedule.js";
33
- import { buildScheduledTaskInvocation, resolveScheduledTaskContext, } from "../scheduler-invocation.js";
33
+ import { buildScheduledTaskInvocation, parseScheduledTaskArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
34
34
  import { nodeFs, throwIfNotOk } from "./exec-utils.js";
35
35
  const BEGIN = (id) => `# akm:task ${assertCronValue(id)} BEGIN`;
36
36
  const END = (id) => `# akm:task ${assertCronValue(id)} END`;
37
37
  const DISABLED_PREFIX = "# akm:disabled ";
38
38
  const BLOCK_RE = /^# akm:task ([\w.@:_-]+) BEGIN$/;
39
39
  const BLOCK_END_RE = /^# akm:task ([\w.@:_-]+) END$/;
40
+ export const PORTABLE_CRON_LINE_LIMIT = 1000;
40
41
  export function CRON_BACKEND(options = {}) {
41
42
  const exec = options.exec ?? defaultCronExec();
42
43
  const fsLike = options.fs ?? nodeFs();
@@ -44,14 +45,16 @@ export function CRON_BACKEND(options = {}) {
44
45
  const akmArgv = options.akmArgv ?? resolveAkmInvocation().argv;
45
46
  const envPath = options.envPath === false ? undefined : (options.envPath ?? process.env.PATH);
46
47
  const scheduledContext = options.scheduledContext ?? resolveScheduledTaskContext();
48
+ const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, envPath ?? ""));
47
49
  return {
48
50
  name: "cron",
49
- install(task) {
51
+ install(task, opts) {
50
52
  // Create the log directory before writing the crontab line — cron
51
53
  // appends with `>>` and the surrounding shell will fail the entire
52
54
  // entry if the parent directory doesn't exist.
55
+ const cronLine = buildCronLine(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target);
56
+ assertPortableCronLine(cronLine);
53
57
  fsLike.ensureDir(logDir);
54
- const cronLine = buildCronLine(task, akmArgv, logDir, envPath, scheduledContext);
55
58
  const existing = readCrontab(exec);
56
59
  const block = renderBlock(task.id, cronLine, task.enabled);
57
60
  const next = upsertBlock(existing, task.id, block);
@@ -69,26 +72,54 @@ export function CRON_BACKEND(options = {}) {
69
72
  },
70
73
  list() {
71
74
  const existing = readCrontab(exec);
72
- return listBlocks(existing).map(({ id, body }) => ({ id, signature: normalizeSignature(body) }));
75
+ const refs = [];
76
+ for (const { id, body } of listBlocks(existing)) {
77
+ const installed = extractCronInvocation(body);
78
+ // An invocation that no longer parses (e.g. a pre-0.9 `tasks run`
79
+ // entry surviving the scheduler-ABI respelling, or any other
80
+ // foreign/malformed line between our markers) is an orphan of its
81
+ // marker id, not a hard failure: omit it here so `akmTasksSync`
82
+ // treats the id as "not present" and reinstalls it from the current
83
+ // task file — going through the normal eligibility/`--rebind` gate
84
+ // exactly like a fresh install.
85
+ if (!installed)
86
+ continue;
87
+ refs.push({
88
+ id,
89
+ signature: normalizeSignature(body),
90
+ ...(installed.target !== undefined ? { target: installed.target } : {}),
91
+ binding: installed.binding,
92
+ contextPath: installed.contextPath,
93
+ });
94
+ }
95
+ return refs;
96
+ },
97
+ listForRebind() {
98
+ const existing = readCrontab(exec);
99
+ return listBlocks(existing).map(({ id, body }) => {
100
+ const installed = extractCronInvocation(body);
101
+ return {
102
+ id,
103
+ signature: normalizeSignature(body),
104
+ ...(installed?.target !== undefined ? { target: installed.target } : {}),
105
+ };
106
+ });
73
107
  },
74
- expectedSignature(task) {
75
- const cronLine = buildCronLine(task, akmArgv, logDir, envPath, scheduledContext);
108
+ expectedSignature(task, opts) {
109
+ const cronLine = buildCronLine(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target);
110
+ assertPortableCronLine(cronLine);
76
111
  return normalizeSignature(cronBlockBody(cronLine, task.enabled));
77
112
  },
78
113
  };
79
114
  }
80
115
  // ── helpers (exported for tests) ────────────────────────────────────────────
81
- export function buildCronLine(task, akmArgv, logDir, envPath, scheduledContext) {
116
+ export function buildCronLine(task, akmArgv, logDir, contextPath, target) {
82
117
  const spec = parseSchedule(task.schedule, "cron");
83
118
  const cronExpr = translateToCron(spec);
84
119
  const logPath = path.join(logDir, `${task.id}.log`);
85
- const invocation = buildScheduledTaskInvocation(akmArgv, task.id, scheduledContext);
120
+ const invocation = buildScheduledTaskInvocation(akmArgv, task.id, contextPath, target);
86
121
  const cmd = invocation.argv.map((part) => quoteForCron(part)).join(" ");
87
- const contextPrefix = Object.entries(invocation.environment)
88
- .map(([key, value]) => `${key}=${quoteForCron(value)}`)
89
- .join(" ");
90
- const pathPrefix = envPath === undefined ? "" : `PATH=${quoteForCron(envPath)} `;
91
- return `${cronExpr} ${contextPrefix} ${pathPrefix}${cmd} >> ${quoteForCron(logPath)} 2>&1`;
122
+ return `${cronExpr} ${cmd} >> ${quoteForCron(logPath)} 2>&1`;
92
123
  }
93
124
  /** The crontab line as it appears inside a block — commented when disabled. */
94
125
  export function cronBlockBody(cronLine, enabled) {
@@ -141,6 +172,59 @@ function parseBlocks(existing) {
141
172
  function malformedBlockError(id) {
142
173
  return new ConfigError(`Crontab contains a malformed akm task block for "${id}"; refusing to modify it.`, "INVALID_CONFIG_FILE");
143
174
  }
175
+ /**
176
+ * Recover the bundle name from an installed cron block body by reading the
177
+ * `--bundle <bundle>` token embedded in the scheduled `akm task run …`
178
+ * invocation. Returns undefined for the byte-identical primary/default form
179
+ * (no `--bundle`). Bundle slugs never contain whitespace (config-schema
180
+ * `isBundleSlug`), so the quoted token is a single whitespace-delimited field
181
+ * even when cron-quoted — a plain field split recovers it, and
182
+ * the shell-word parser below reverses the quoting.
183
+ */
184
+ export function extractInstalledTarget(body) {
185
+ return extractCronInvocation(body)?.target;
186
+ }
187
+ export function extractCronInvocation(body) {
188
+ const line = body.startsWith(DISABLED_PREFIX) ? body.slice(DISABLED_PREFIX.length) : body;
189
+ const fields = splitCronShellWords(line);
190
+ if (fields.length < 6)
191
+ return undefined;
192
+ let commandStart = 5;
193
+ while (commandStart < fields.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(fields[commandStart]))
194
+ commandStart += 1;
195
+ const redirectIndex = fields.indexOf(">>", commandStart);
196
+ if (redirectIndex === -1)
197
+ return undefined;
198
+ return parseScheduledTaskArgv(fields.slice(commandStart, redirectIndex));
199
+ }
200
+ /** Reverse {@link quoteForCron} for a single whitespace-free token. */
201
+ function splitCronShellWords(value) {
202
+ const words = [];
203
+ let current = "";
204
+ let quoted = false;
205
+ for (let index = 0; index < value.length; index += 1) {
206
+ const char = value[index];
207
+ if (char === "'") {
208
+ quoted = !quoted;
209
+ continue;
210
+ }
211
+ if (char === "\\" && index + 1 < value.length) {
212
+ current += value[index + 1];
213
+ index += 1;
214
+ continue;
215
+ }
216
+ if (!quoted && /\s/.test(char)) {
217
+ if (current)
218
+ words.push(current);
219
+ current = "";
220
+ continue;
221
+ }
222
+ current += char;
223
+ }
224
+ if (current)
225
+ words.push(current);
226
+ return words;
227
+ }
144
228
  /** Collapse incidental whitespace so signature comparison ignores it. */
145
229
  function normalizeSignature(body) {
146
230
  return body
@@ -218,6 +302,12 @@ function assertCronValue(value) {
218
302
  }
219
303
  return value;
220
304
  }
305
+ function assertPortableCronLine(line) {
306
+ const bytes = Buffer.byteLength(line, "utf8");
307
+ if (bytes > PORTABLE_CRON_LINE_LIMIT) {
308
+ throw new ConfigError(`Generated cron definition is ${bytes} bytes; portable cron command lines are limited to ${PORTABLE_CRON_LINE_LIMIT} bytes.`, "INVALID_CONFIG_FILE");
309
+ }
310
+ }
221
311
  function readCrontab(exec) {
222
312
  const result = exec.read();
223
313
  if (result.status === 0)
@@ -6,7 +6,7 @@ import { LAUNCHD_BACKEND } from "./launchd.js";
6
6
  import { SCHTASKS_BACKEND } from "./schtasks.js";
7
7
  // WI-9.10e: the former `_setBackendsForTests` module-mutation seam was retired.
8
8
  // Tests inject a fake `TaskBackend` directly via the `deps.backend` parameter
9
- // the `akm tasks` mutation entries already accept (the backend carries its own
9
+ // the `akm task` mutation entries already accept (the backend carries its own
10
10
  // `name`), so no module-level override binding is needed. `selectBackend`'s
11
11
  // `options.platform` covers the platform-steering the seam's second override
12
12
  // used to provide.
@@ -2,7 +2,7 @@
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
  /**
5
- * launchd backend for `akm tasks` (macOS default).
5
+ * launchd backend for `akm task` (macOS default).
6
6
  *
7
7
  * Each task is written as a per-user LaunchAgent plist at
8
8
  * `~/Library/LaunchAgents/com.akm.task.<id>.plist` and registered via
@@ -29,7 +29,7 @@ import { ConfigError } from "../../core/errors.js";
29
29
  import { getTaskLogDir } from "../../core/paths.js";
30
30
  import { resolveAkmInvocation } from "../resolve-akm-bin.js";
31
31
  import { parseSchedule, translateToLaunchd } from "../schedule.js";
32
- import { buildScheduledTaskInvocation, resolveScheduledTaskContext, } from "../scheduler-invocation.js";
32
+ import { buildScheduledTaskInvocation, parseScheduledTaskArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
33
33
  import { escapeXml, nodeExec, nodeFs, runOrThrow } from "./exec-utils.js";
34
34
  export const LAUNCHD_LABEL_PREFIX = "com.akm.task.";
35
35
  export function LAUNCHD_BACKEND(options = {}) {
@@ -49,6 +49,7 @@ export function LAUNCHD_BACKEND(options = {}) {
49
49
  return options.envPath;
50
50
  return process.env.PATH ?? "";
51
51
  };
52
+ const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, pathEnv() ?? ""));
52
53
  const setEnableState = (id, enabled) => {
53
54
  const verb = enabled ? "enable" : "disable";
54
55
  runOrThrow(exec, ["launchctl", verb, target(id)], {
@@ -57,10 +58,10 @@ export function LAUNCHD_BACKEND(options = {}) {
57
58
  };
58
59
  return {
59
60
  name: "launchd",
60
- install(task) {
61
+ install(task, opts) {
61
62
  // Capture PATH at install time so launchd (which strips the environment
62
63
  // aggressively) can find the same binaries the user sees interactively.
63
- const xml = buildPlistXml(task, akmArgv, logDir, pathEnv(), scheduledContext);
64
+ const xml = buildPlistXml(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target);
64
65
  const file = plistPath(task.id);
65
66
  const previousPlist = fsLike.exists(file) ? fsLike.readFile(file) : undefined;
66
67
  let previousEnabled = true;
@@ -201,55 +202,101 @@ export function LAUNCHD_BACKEND(options = {}) {
201
202
  if (ids.length === 0)
202
203
  return [];
203
204
  const disabledLabels = readDisabledLabels(exec);
204
- return ids.map((id) => {
205
- if (!disabledLabels)
206
- return { id };
207
- try {
208
- const loaded = exec.run(["launchctl", "print", target(id)]);
209
- if (loaded.status !== 0)
210
- return { id };
211
- }
212
- catch {
213
- return { id };
214
- }
215
- try {
216
- const xml = fsLike
217
- .readFile(plistPath(id))
218
- .replace(/<!-- akm-enabled:(?:true|false) -->/, `<!-- akm-enabled:${!disabledLabels.has(label(id))} -->`);
219
- return { id, signature: normalizeSignature(xml) };
220
- }
221
- catch {
222
- return { id };
223
- }
224
- });
205
+ return ids
206
+ .map((id) => inspectInstalledLaunchdTask(id, fsLike.readFile(plistPath(id)), disabledLabels, exec))
207
+ .filter((ref) => ref !== undefined);
208
+ },
209
+ listForRebind() {
210
+ if (!fsLike.exists(agentsDir))
211
+ return [];
212
+ const refs = [];
213
+ for (const file of fsLike.list(agentsDir)) {
214
+ if (!file.startsWith(LAUNCHD_LABEL_PREFIX) || !file.endsWith(".plist"))
215
+ continue;
216
+ const id = file.slice(LAUNCHD_LABEL_PREFIX.length, -".plist".length);
217
+ const installed = extractPlistInvocation(fsLike.readFile(plistPath(id)));
218
+ refs.push({ id, ...(installed?.target !== undefined ? { target: installed.target } : {}) });
219
+ }
220
+ return refs;
225
221
  },
226
- expectedSignature(task) {
227
- return normalizeSignature(buildPlistXml(task, akmArgv, logDir, pathEnv(), scheduledContext));
222
+ expectedSignature(task, opts) {
223
+ return normalizeSignature(buildPlistXml(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target));
228
224
  },
229
225
  };
230
226
  }
227
+ function inspectInstalledLaunchdTask(id, raw, disabledLabels, exec) {
228
+ const installed = extractPlistInvocation(raw);
229
+ // An invocation that no longer parses (e.g. a pre-0.9 `tasks run` entry
230
+ // surviving the scheduler-ABI respelling) is an orphan of its marker id,
231
+ // not a hard failure: `list()` omits it so `akmTasksSync` treats the id as
232
+ // "not present" and reinstalls it from the current task file.
233
+ if (!installed)
234
+ return undefined;
235
+ const metadata = {
236
+ ...(installed.target !== undefined ? { target: installed.target } : {}),
237
+ binding: installed.binding,
238
+ contextPath: installed.contextPath,
239
+ };
240
+ if (!disabledLabels)
241
+ return { id, ...metadata };
242
+ const jobLabel = `${LAUNCHD_LABEL_PREFIX}${id}`;
243
+ try {
244
+ const loaded = exec.run(["launchctl", "print", `gui/${exec.uid()}/${jobLabel}`]);
245
+ if (loaded.status !== 0)
246
+ return { id, ...metadata };
247
+ }
248
+ catch {
249
+ return { id, ...metadata };
250
+ }
251
+ try {
252
+ const xml = raw.replace(/<!-- akm-enabled:(?:true|false) -->/, `<!-- akm-enabled:${!disabledLabels.has(jobLabel)} -->`);
253
+ return {
254
+ id,
255
+ signature: normalizeSignature(xml),
256
+ ...metadata,
257
+ };
258
+ }
259
+ catch {
260
+ return { id, ...metadata };
261
+ }
262
+ }
263
+ /**
264
+ * Recover the bundle name embedded as a `--bundle <bundle>` pair in a plist's
265
+ * `<ProgramArguments>`. Returns undefined for the primary/default form.
266
+ */
267
+ export function extractPlistTarget(xml) {
268
+ return extractPlistInvocation(xml)?.target;
269
+ }
270
+ export function extractPlistInvocation(xml) {
271
+ const block = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
272
+ if (!block)
273
+ return undefined;
274
+ const args = [...block[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => decodeXmlEntities(m[1]));
275
+ return parseScheduledTaskArgv(args);
276
+ }
277
+ function decodeXmlEntities(value) {
278
+ return value
279
+ .replaceAll("&lt;", "<")
280
+ .replaceAll("&gt;", ">")
281
+ .replaceAll("&quot;", '"')
282
+ .replaceAll("&apos;", "'")
283
+ .replaceAll("&amp;", "&");
284
+ }
231
285
  // ── XML builder (exported for tests) ────────────────────────────────────────
232
- export function buildPlistXml(task, akmArgv, logDir, pathEnv, scheduledContext) {
286
+ export function buildPlistXml(task, akmArgv, logDir, contextPath, target) {
233
287
  const spec = parseSchedule(task.schedule, "launchd");
234
288
  const trigger = translateToLaunchd(spec);
235
- const invocation = buildScheduledTaskInvocation(akmArgv, task.id, scheduledContext);
289
+ const invocation = buildScheduledTaskInvocation(akmArgv, task.id, contextPath, target);
236
290
  const argv = invocation.argv;
237
291
  const programArgs = argv.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
238
292
  const logPath = path.join(logDir, `${task.id}.log`);
239
293
  const triggerXml = renderLaunchdTrigger(trigger);
240
- const environment = { ...invocation.environment };
241
- if (pathEnv !== undefined)
242
- environment.PATH = pathEnv;
243
- const envEntries = Object.entries(environment)
244
- .map(([key, value]) => ` <key>${key}</key>\n <string>${escapeXml(value)}</string>`)
245
- .join("\n");
246
- const envVarsXml = ` <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n`;
247
294
  const xml = launchdTemplate
248
295
  .replace("<dict>\n", `<dict>\n <!-- akm-enabled:${task.enabled} -->\n`)
249
296
  .replace("{{LABEL}}", LAUNCHD_LABEL_PREFIX + escapeXml(task.id))
250
297
  .replace("{{PROGRAM_ARGS}}", programArgs)
251
298
  .replaceAll("{{LOG_PATH}}", escapeXml(logPath))
252
- .replace("{{ENV_VARS}}", envVarsXml)
299
+ .replace("{{ENV_VARS}}", "")
253
300
  .replace("{{TRIGGER_XML}}", triggerXml);
254
301
  for (const char of xml) {
255
302
  const code = char.codePointAt(0) ?? 0;
@@ -330,7 +377,7 @@ function defaultAgentsDir() {
330
377
  // relative path that would write somewhere unexpected.
331
378
  const home = os.homedir();
332
379
  if (!home) {
333
- throw new ConfigError("Cannot determine user home directory; launchd backend requires HOME to locate ~/Library/LaunchAgents.", "INVALID_CONFIG_FILE", "Set $HOME (POSIX) or the equivalent before running `akm tasks` on macOS.");
380
+ throw new ConfigError("Cannot determine user home directory; launchd backend requires HOME to locate ~/Library/LaunchAgents.", "INVALID_CONFIG_FILE", "Set $HOME (POSIX) or the equivalent before running `akm task` on macOS.");
334
381
  }
335
382
  return path.join(home, "Library", "LaunchAgents");
336
383
  }