akm-cli 0.9.16 → 0.9.17-alpha.10

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 (403) hide show
  1. package/CHANGELOG.md +2101 -0
  2. package/STABILITY.md +11 -10
  3. package/dist/akm +124 -193
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/prompts/consolidate-system.md +4 -11
  15. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  17. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  18. package/dist/assets/templates/html/health.html +3 -5
  19. package/dist/cli/retired-commands.js +1 -1
  20. package/dist/cli/shared.js +6 -2
  21. package/dist/cli/unknown-flags.js +24 -1
  22. package/dist/cli.js +68 -10
  23. package/dist/commands/agent/agent-dispatch.js +1 -1
  24. package/dist/commands/command/command-execution.js +24 -62
  25. package/dist/commands/feedback-cli.js +0 -1
  26. package/dist/commands/health/accept-rate.js +6 -0
  27. package/dist/commands/health/archive-usage.js +92 -0
  28. package/dist/commands/health/checks.js +83 -74
  29. package/dist/commands/health/config-skew.js +38 -0
  30. package/dist/commands/health/data-dir-usage.js +25 -13
  31. package/dist/commands/health/egress.js +54 -0
  32. package/dist/commands/health/html-report.js +1 -42
  33. package/dist/commands/health/improve-metrics.js +136 -591
  34. package/dist/commands/health/md-report.js +1 -6
  35. package/dist/commands/health/plugin-staleness.js +53 -3
  36. package/dist/commands/health/renderers.js +12 -4
  37. package/dist/commands/health/report-view-model.js +14 -120
  38. package/dist/commands/health/types-improve.js +4 -19
  39. package/dist/commands/health/windows.js +64 -74
  40. package/dist/commands/health.js +145 -143
  41. package/dist/commands/improve/consolidate/chunking.js +26 -117
  42. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  43. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  44. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  45. package/dist/commands/improve/consolidate.js +589 -1127
  46. package/dist/commands/improve/content-hash.js +16 -24
  47. package/dist/commands/improve/distill/content-repair.js +18 -100
  48. package/dist/commands/improve/distill-guards.js +20 -81
  49. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  50. package/dist/commands/improve/distill.js +608 -1041
  51. package/dist/commands/improve/eligibility.js +126 -390
  52. package/dist/commands/improve/execution.js +8 -10
  53. package/dist/commands/improve/extract-prompt.js +1 -2
  54. package/dist/commands/improve/extract.js +487 -1046
  55. package/dist/commands/improve/feedback-valence.js +0 -25
  56. package/dist/commands/improve/improve-cli.js +75 -169
  57. package/dist/commands/improve/improve-result-file.js +10 -66
  58. package/dist/commands/improve/improve-strategies.js +52 -4
  59. package/dist/commands/improve/improve-usage-report.js +18 -64
  60. package/dist/commands/improve/improve.js +480 -1074
  61. package/dist/commands/improve/ledger.js +119 -0
  62. package/dist/commands/improve/locks.js +2 -8
  63. package/dist/commands/improve/loop-stages.js +415 -1073
  64. package/dist/commands/improve/memory/derived-ref.js +12 -77
  65. package/dist/commands/improve/memory/memory-belief.js +16 -118
  66. package/dist/commands/improve/memory/memory-improve.js +266 -14
  67. package/dist/commands/improve/outcome-loop.js +28 -156
  68. package/dist/commands/improve/planner.js +5 -15
  69. package/dist/commands/improve/preparation.js +779 -2319
  70. package/dist/commands/improve/proactive-maintenance.js +34 -101
  71. package/dist/commands/improve/reflect-noise.js +104 -280
  72. package/dist/commands/improve/reflect.js +642 -1353
  73. package/dist/commands/improve/retrieval-gate.js +127 -0
  74. package/dist/commands/improve/retrieval-scope.js +92 -0
  75. package/dist/commands/improve/salience.js +41 -240
  76. package/dist/commands/improve/session-asset.js +19 -100
  77. package/dist/commands/improve/stage.js +322 -0
  78. package/dist/commands/lint/base-linter.js +37 -15
  79. package/dist/commands/proposal/drain.js +261 -578
  80. package/dist/commands/proposal/proposal-cli.js +19 -20
  81. package/dist/commands/proposal/proposal-types.js +31 -24
  82. package/dist/commands/proposal/proposal.js +38 -8
  83. package/dist/commands/proposal/propose.js +134 -160
  84. package/dist/commands/proposal/repository.js +1097 -1394
  85. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  86. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  87. package/dist/commands/proposal/validators/proposals.js +22 -89
  88. package/dist/commands/read/curate.js +105 -462
  89. package/dist/commands/read/knowledge.js +3 -2
  90. package/dist/commands/read/search-cli.js +16 -33
  91. package/dist/commands/read/search.js +17 -23
  92. package/dist/commands/read/show.js +57 -108
  93. package/dist/commands/sources/bundle-cli.js +25 -2
  94. package/dist/commands/sources/bundle-config-ops.js +4 -0
  95. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  96. package/dist/commands/sources/info.js +127 -29
  97. package/dist/commands/sources/installed-stashes.js +197 -746
  98. package/dist/commands/sources/schema-repair.js +98 -129
  99. package/dist/commands/sources/source-add.js +62 -12
  100. package/dist/commands/sources/source-manage.js +9 -2
  101. package/dist/commands/sources/stash-cli.js +24 -4
  102. package/dist/commands/tasks/explain.js +10 -13
  103. package/dist/commands/tasks/tasks-cli.js +12 -13
  104. package/dist/commands/tasks/tasks.js +350 -936
  105. package/dist/commands/tasks/validate.js +26 -24
  106. package/dist/commands/workflow/plan.js +22 -29
  107. package/dist/commands/workflow-cli.js +4 -4
  108. package/dist/core/adapter/adapters/akm-adapter.js +2 -1
  109. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  110. package/dist/core/adapter/adapters/akm-metadata.js +42 -12
  111. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  112. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  113. package/dist/core/adapter/execution-source.js +17 -29
  114. package/dist/core/asset/asset-placement.js +4 -13
  115. package/dist/core/asset/frontmatter.js +106 -1
  116. package/dist/core/asset/resolve-ref.js +1 -1
  117. package/dist/core/bundle-id.js +42 -5
  118. package/dist/core/bundle-rename.js +285 -0
  119. package/dist/core/config/config-io.js +1 -2
  120. package/dist/core/config/config-schema.js +9 -34
  121. package/dist/core/config/config-walker.js +1 -1
  122. package/dist/core/config/config.js +184 -111
  123. package/dist/core/config/engine-semantics.js +0 -2
  124. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  125. package/dist/core/config/schema/embedding.js +20 -5
  126. package/dist/core/config/schema/engines.js +5 -0
  127. package/dist/core/config/schema/execution.js +1 -1
  128. package/dist/core/config/schema/experimental.js +1 -1
  129. package/dist/core/config/schema/improve-processes.js +54 -125
  130. package/dist/core/config/schema/improve.js +4 -42
  131. package/dist/core/config/schema/index-config.js +9 -48
  132. package/dist/core/config/schema/scheduler.js +12 -12
  133. package/dist/core/config/schema/search.js +6 -22
  134. package/dist/core/env-secret-ref.js +0 -1
  135. package/dist/core/errors.js +8 -9
  136. package/dist/core/file-change.js +13 -5
  137. package/dist/core/file-lock.js +76 -173
  138. package/dist/core/improve-result.js +35 -7
  139. package/dist/core/improve-types.js +0 -1
  140. package/dist/core/logs-db.js +2 -2
  141. package/dist/core/loopback.js +7 -12
  142. package/dist/core/non-task-input.js +20 -0
  143. package/dist/core/parse.js +13 -16
  144. package/dist/core/paths.js +0 -24
  145. package/dist/core/redaction.js +109 -2
  146. package/dist/core/run-lock.js +2 -5
  147. package/dist/core/spawn-env.js +1 -1
  148. package/dist/core/state/migrations.js +123 -61
  149. package/dist/core/state-db-scope.js +2 -4
  150. package/dist/core/state-db.js +126 -692
  151. package/dist/core/time.js +0 -20
  152. package/dist/core/type-presentation.js +1 -9
  153. package/dist/core/write-source.js +294 -1005
  154. package/dist/execution/input-contract.js +1 -1
  155. package/dist/execution/resolved-request.js +135 -689
  156. package/dist/execution/source.js +63 -257
  157. package/dist/execution/target-ref.js +1 -1
  158. package/dist/indexer/bundle-identity-guard.js +2 -2
  159. package/dist/indexer/db/llm-cache.js +2 -2
  160. package/dist/indexer/ensure-index.js +77 -73
  161. package/dist/indexer/index-rebuild-lock.js +3 -11
  162. package/dist/indexer/index-writer-lock.js +8 -17
  163. package/dist/indexer/index-written-assets.js +141 -154
  164. package/dist/indexer/indexer.js +400 -1124
  165. package/dist/indexer/links/declared-links.js +90 -0
  166. package/dist/indexer/materialize-embeddings.js +60 -397
  167. package/dist/indexer/passes/memory-inference.js +96 -90
  168. package/dist/indexer/passes/metadata.js +132 -219
  169. package/dist/indexer/read-preflight.js +0 -7
  170. package/dist/indexer/scan/doc-to-entry.js +2 -3
  171. package/dist/indexer/scan/drain-dir.js +1 -1
  172. package/dist/indexer/search/db-search.js +190 -590
  173. package/dist/indexer/search/fts-query.js +30 -41
  174. package/dist/indexer/search/ranking.js +28 -154
  175. package/dist/indexer/search/search-attribution.js +12 -32
  176. package/dist/indexer/search/search-fields.js +11 -15
  177. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  178. package/dist/indexer/search/search-source.js +1 -4
  179. package/dist/indexer/usage/usage-events.js +36 -7
  180. package/dist/indexer/walk/walker.js +3 -4
  181. package/dist/integrations/agent/engine-fallback.js +23 -40
  182. package/dist/integrations/agent/engine-resolution.js +93 -183
  183. package/dist/integrations/agent/execution.js +507 -0
  184. package/dist/integrations/agent/model-map.js +28 -156
  185. package/dist/integrations/agent/request-lowering.js +66 -141
  186. package/dist/integrations/agent/runner-dispatch.js +143 -321
  187. package/dist/integrations/agent/runner.js +54 -14
  188. package/dist/integrations/lockfile.js +53 -101
  189. package/dist/llm/client.js +18 -6
  190. package/dist/llm/embedders/deterministic.js +2 -3
  191. package/dist/llm/embedders/profile.js +71 -0
  192. package/dist/llm/embedders/remote.js +11 -17
  193. package/dist/llm/feature-gate.js +0 -8
  194. package/dist/llm/index-passes.js +3 -5
  195. package/dist/llm/memory-infer.js +1 -2
  196. package/dist/llm/structured-call.js +5 -24
  197. package/dist/output/generic-render.js +23 -11
  198. package/dist/output/html-render.js +13 -10
  199. package/dist/output/render-registry.js +3 -32
  200. package/dist/output/shapes/helpers.js +25 -38
  201. package/dist/output/shapes/passthrough.js +1 -9
  202. package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
  203. package/dist/output/text/command-format.js +69 -31
  204. package/dist/output/text/helpers.js +1 -1
  205. package/dist/output/text/migrate.js +5 -14
  206. package/dist/output/text/proposal-format.js +48 -3
  207. package/dist/output/text/show-format.js +13 -17
  208. package/dist/output/text/workflow-format.js +0 -32
  209. package/dist/output/text.js +2 -0
  210. package/dist/registry/factory.js +4 -19
  211. package/dist/registry/network.js +66 -220
  212. package/dist/registry/providers/index.js +0 -2
  213. package/dist/registry/providers/skills-sh.js +3 -14
  214. package/dist/registry/providers/static-index.js +24 -26
  215. package/dist/registry/resolve.js +55 -131
  216. package/dist/scripts/akm-migrate-node.js +42948 -92369
  217. package/dist/scripts/akm-migrate.js +42935 -92354
  218. package/dist/setup/registry-stash-loader.js +4 -13
  219. package/dist/setup/semantic-assets.js +3 -44
  220. package/dist/setup/setup.js +1 -1
  221. package/dist/setup/steps/connection.js +5 -6
  222. package/dist/setup/steps/platforms.js +2 -2
  223. package/dist/setup/steps/tasks.js +25 -15
  224. package/dist/sources/provider-factory.js +17 -18
  225. package/dist/sources/providers/filesystem.js +2 -3
  226. package/dist/sources/providers/git-install.js +7 -1
  227. package/dist/sources/providers/git-provider.js +0 -3
  228. package/dist/sources/providers/git-stash.js +83 -21
  229. package/dist/sources/providers/npm.js +2 -4
  230. package/dist/sources/providers/provider-utils.js +5 -10
  231. package/dist/sources/providers/website.js +0 -2
  232. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  233. package/dist/sources/website-url.js +2 -2
  234. package/dist/storage/database.js +9 -35
  235. package/dist/storage/repositories/improve-ledger-repository.js +209 -0
  236. package/dist/storage/repositories/index-connection.js +39 -72
  237. package/dist/storage/repositories/index-entries-repository.js +131 -129
  238. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  239. package/dist/storage/repositories/index-entry-schema.js +101 -268
  240. package/dist/storage/repositories/index-fts-repository.js +86 -256
  241. package/dist/storage/repositories/index-links-repository.js +143 -0
  242. package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
  243. package/dist/storage/repositories/index-meta-repository.js +6 -4
  244. package/dist/storage/repositories/index-schema.js +257 -325
  245. package/dist/storage/repositories/index-utility-repository.js +8 -29
  246. package/dist/storage/repositories/index-vec-repository.js +133 -414
  247. package/dist/storage/repositories/outcome-repository.js +2 -1
  248. package/dist/storage/repositories/proposals-repository.js +104 -1
  249. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  250. package/dist/storage/repositories/salience-repository.js +1 -19
  251. package/dist/storage/repositories/task-history-repository.js +26 -4
  252. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  253. package/dist/storage/sqlite-migrations.js +136 -0
  254. package/dist/storage/sqlite-pragmas.js +11 -9
  255. package/dist/storage/sqlite-transaction.js +170 -0
  256. package/dist/storage/state-db-integrity.js +130 -0
  257. package/dist/tasks/activation-config.js +134 -62
  258. package/dist/tasks/backends/cron.js +191 -302
  259. package/dist/tasks/backends/exec-utils.js +2 -5
  260. package/dist/tasks/backends/launchd.js +141 -748
  261. package/dist/tasks/backends/schtasks.js +119 -623
  262. package/dist/tasks/prepare/prepare-support.js +5 -15
  263. package/dist/tasks/prepare/prepare.js +0 -2
  264. package/dist/tasks/resolve-akm-bin.js +20 -79
  265. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  266. package/dist/tasks/run/load-task.js +1 -1
  267. package/dist/tasks/scheduler-binding.js +20 -238
  268. package/dist/tasks/scheduler-invocation.js +136 -244
  269. package/dist/tasks/scheduler-lock.js +53 -0
  270. package/dist/tasks/scheduler-sync.js +368 -679
  271. package/dist/tasks/source/parse-task-source.js +55 -9
  272. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  273. package/dist/tasks/source/task-to-v4.js +464 -88
  274. package/dist/workflows/authoring/authoring.js +3 -12
  275. package/dist/workflows/compile.js +211 -0
  276. package/dist/workflows/concurrency-policy.js +13 -74
  277. package/dist/workflows/exec/child-invocation.js +3 -17
  278. package/dist/workflows/exec/child-workflow.js +32 -141
  279. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  280. package/dist/workflows/exec/environment.js +98 -0
  281. package/dist/workflows/exec/exec-unit.js +33 -140
  282. package/dist/workflows/exec/frozen-judge.js +7 -59
  283. package/dist/workflows/exec/native-executor.js +82 -341
  284. package/dist/workflows/exec/param-secrets.js +29 -47
  285. package/dist/workflows/exec/run-workflow.js +154 -387
  286. package/dist/workflows/exec/scheduler.js +9 -36
  287. package/dist/workflows/exec/step-work.js +127 -430
  288. package/dist/workflows/exec/unit-dispatch.js +11 -63
  289. package/dist/workflows/exec/unit-writer.js +8 -52
  290. package/dist/workflows/exec/worktree.js +39 -273
  291. package/dist/workflows/freeze/child-output-references.js +4 -15
  292. package/dist/workflows/freeze/environment.js +99 -92
  293. package/dist/workflows/freeze/freeze.js +172 -0
  294. package/dist/workflows/freeze/step-values.js +19 -21
  295. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  296. package/dist/workflows/freeze/targets/command.js +10 -33
  297. package/dist/workflows/freeze/targets/script.js +5 -12
  298. package/dist/workflows/freeze/targets/shell.js +3 -6
  299. package/dist/workflows/freeze/targets/task.js +25 -80
  300. package/dist/workflows/freeze/task-bindings.js +20 -67
  301. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  302. package/dist/workflows/ir/params.js +6 -51
  303. package/dist/workflows/ir/plan-hash.js +2 -34
  304. package/dist/workflows/parser.js +140 -43
  305. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  306. package/dist/workflows/renderer.js +36 -69
  307. package/dist/workflows/resource-limits.js +12 -120
  308. package/dist/workflows/runtime/agent-identity.js +8 -40
  309. package/dist/workflows/runtime/run-outputs.js +3 -6
  310. package/dist/workflows/runtime/run-plan.js +316 -0
  311. package/dist/workflows/runtime/runs.js +48 -200
  312. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  313. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  314. package/dist/workflows/validate-summary.js +2 -7
  315. package/docs/integration/bundling-akm.md +49 -42
  316. package/docs/migration/README.md +1 -0
  317. package/docs/migration/release-notes/0.9.17.md +43 -0
  318. package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
  319. package/docs/reference/cli.md +232 -135
  320. package/docs/reference/configuration.md +71 -57
  321. package/docs/reference/data-and-telemetry.md +20 -21
  322. package/docs/reference/tasks.md +105 -39
  323. package/docs/reference/workflow-schema.md +14 -18
  324. package/docs/reference/workflows.md +6 -9
  325. package/package.json +1 -1
  326. package/schemas/akm-config.json +115 -738
  327. package/schemas/akm-workflow.json +1 -0
  328. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  329. package/dist/assets/prompts/contradiction-judge.md +0 -33
  330. package/dist/assets/prompts/graph-extract-system.md +0 -1
  331. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  332. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  333. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  334. package/dist/commands/health/advisories.js +0 -150
  335. package/dist/commands/health/metrics.js +0 -329
  336. package/dist/commands/health/surfaces.js +0 -102
  337. package/dist/commands/improve/anti-collapse.js +0 -83
  338. package/dist/commands/improve/collapse-detector.js +0 -432
  339. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  340. package/dist/commands/improve/consolidate/merge.js +0 -149
  341. package/dist/commands/improve/distill/promote-memory.js +0 -291
  342. package/dist/commands/improve/distill/quality-gate.js +0 -337
  343. package/dist/commands/improve/eval-cases.js +0 -52
  344. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  345. package/dist/commands/improve/proposal-envelope.js +0 -31
  346. package/dist/commands/improve/run-context.js +0 -123
  347. package/dist/commands/improve/shared.js +0 -31
  348. package/dist/commands/improve/source-identity.js +0 -28
  349. package/dist/commands/improve/triage.js +0 -96
  350. package/dist/commands/proposal/drain-policies.js +0 -151
  351. package/dist/commands/sources/update-transaction.js +0 -220
  352. package/dist/core/action-contributors.js +0 -28
  353. package/dist/core/config/config-version-shim.js +0 -101
  354. package/dist/core/fs-txn.js +0 -405
  355. package/dist/core/lexical-score.js +0 -25
  356. package/dist/core/maintenance-barrier.js +0 -167
  357. package/dist/execution/executable-identity.js +0 -105
  358. package/dist/execution/guarded-source.js +0 -427
  359. package/dist/indexer/db/graph-db.js +0 -444
  360. package/dist/indexer/graph/graph-boost.js +0 -427
  361. package/dist/indexer/graph/graph-dedup.js +0 -95
  362. package/dist/indexer/graph/graph-extraction.js +0 -1108
  363. package/dist/indexer/search/name-match.js +0 -35
  364. package/dist/indexer/search/ranking-contributors.js +0 -515
  365. package/dist/indexer/search/ranking-types.js +0 -4
  366. package/dist/indexer/walk/project-context.js +0 -192
  367. package/dist/integrations/agent/execution-cascade.js +0 -566
  368. package/dist/integrations/agent/execution-definitions.js +0 -202
  369. package/dist/integrations/agent/execution-lowering.js +0 -841
  370. package/dist/integrations/agent/execution-preparation.js +0 -98
  371. package/dist/integrations/agent/inline-execution.js +0 -74
  372. package/dist/llm/graph-extract.js +0 -728
  373. package/dist/llm/metadata-enhance.js +0 -96
  374. package/dist/registry/create-provider-registry.js +0 -29
  375. package/dist/registry/pinned-request-helper.js +0 -247
  376. package/dist/registry/pinned-transport.js +0 -717
  377. package/dist/sources/providers/index.js +0 -14
  378. package/dist/storage/engines/sqlite-migrations.js +0 -271
  379. package/dist/storage/repositories/canaries-repository.js +0 -107
  380. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  381. package/dist/storage/repositories/registry-cache.js +0 -113
  382. package/dist/tasks/scheduler-sync-preview.js +0 -52
  383. package/dist/tasks/source/task-to-v3.js +0 -507
  384. package/dist/workflows/freeze/resolve-steps.js +0 -86
  385. package/dist/workflows/freeze/source-freeze.js +0 -64
  386. package/dist/workflows/ir/compile.js +0 -321
  387. package/dist/workflows/ir/environment-v4.js +0 -330
  388. package/dist/workflows/ir/freeze-v4.js +0 -153
  389. package/dist/workflows/ir/schema-v4.js +0 -745
  390. package/dist/workflows/ir/schema.js +0 -354
  391. package/dist/workflows/program/schema.js +0 -77
  392. package/dist/workflows/runtime/checkin.js +0 -57
  393. package/dist/workflows/runtime/plan-classifier.js +0 -196
  394. package/dist/workflows/runtime/unit-checkin.js +0 -45
  395. package/dist/workflows/runtime/unit-phases.js +0 -20
  396. package/dist/workflows/schema.js +0 -4
  397. package/dist/workflows/source-ir/compile.js +0 -200
  398. package/dist/workflows/source-ir/program.js +0 -50
  399. package/dist/workflows/source-ir/result.js +0 -26
  400. package/dist/workflows/source-ir/schema.js +0 -786
  401. package/dist/workflows/source-ir/triggers.js +0 -79
  402. package/dist/workflows/source-ir/uses.js +0 -40
  403. package/dist/workflows/validator.js +0 -60
@@ -1,13 +1,39 @@
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
+ /**
5
+ * How a native scheduler row invokes akm.
6
+ *
7
+ * A row is `<launcher…> task run <id> --bundle <bundle> --scheduled
8
+ * [--<input> <value>…]`, or `<launcher…> workflow run <qualified-ref>`, and it
9
+ * carries its own context: the environment the scheduled process needs,
10
+ * set inline by the row itself (a cron `VAR=value` prefix, a launchd
11
+ * `EnvironmentVariables` entry, a PowerShell `$env:` assignment).
12
+ *
13
+ * - `AKM_BUNDLE_DIR`: the working stash of the process that ran
14
+ * `akm task sync`, on every row. The scheduled run uses the same working
15
+ * stash, `--bundle <name>` finds a stash no config names, and sync
16
+ * attributes the row to the installation that wrote it (#846).
17
+ * - `AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` and `AKM_STATE_DIR`
18
+ * only when the process that ran `akm task sync` set them explicitly. A
19
+ * default resolves at fire time exactly as it does for an interactive
20
+ * command.
21
+ *
22
+ * That is exactly what the `--scheduler-context` descriptor held.
23
+ *
24
+ * `PATH` is the scheduler's own: the crontab's `# akm:env` block, the plist's
25
+ * `EnvironmentVariables`.
26
+ *
27
+ * Rows written by 0.9.0 through 0.9.17-alpha.6 name a
28
+ * `--scheduler-context <descriptor>` file instead. The CLI still accepts that
29
+ * argument and applies the file's environment, so such a row keeps firing
30
+ * until the next `akm task sync` rewrites it.
31
+ */
5
32
  import fs from "node:fs";
6
33
  import path from "node:path";
7
34
  import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
8
35
  import { resolveStashDir } from "../core/common.js";
9
36
  import { ConfigError } from "../core/errors.js";
10
- import { getCacheDir, getConfigDir, getDataDir, getTaskContextDir } from "../core/paths.js";
11
37
  import { INPUT_NAME_PATTERN } from "../execution/input-contract.js";
12
38
  import { normaliseTaskConceptId } from "./task-id.js";
13
39
  export const SCHEDULED_TASK_CONTEXT_KEYS = [
@@ -18,9 +44,18 @@ export const SCHEDULED_TASK_CONTEXT_KEYS = [
18
44
  "AKM_STATE_DIR",
19
45
  ];
20
46
  /**
21
- * The AKM directory context currently restored from a scheduler descriptor.
22
- * Child environments that are built from an allowlist must forward this
23
- * closed set explicitly so nested AKM commands stay in the same installation.
47
+ * Directories a row carries ONLY when the process that ran `task sync` had
48
+ * them set explicitly in its environment. They are never resolved from
49
+ * defaults: a sync run from inside an app whose environment pointed `$STATE`
50
+ * somewhere private (an OpenCode desktop session, 2026-08 → 2026-09) froze
51
+ * that directory into eight cron rows, and the scheduled improve runs then
52
+ * held their locks in a `locks/` directory no interactive command could see.
53
+ */
54
+ const EXPLICIT_CONTEXT_KEYS = ["AKM_CONFIG_DIR", "AKM_DATA_DIR", "AKM_CACHE_DIR", "AKM_STATE_DIR"];
55
+ /**
56
+ * The AKM directory context of the current process. Child environments that
57
+ * are built from an allowlist must forward this closed set explicitly so
58
+ * nested AKM commands stay in the same installation.
24
59
  */
25
60
  export function scheduledTaskContextEnv(env = process.env) {
26
61
  const out = {};
@@ -31,134 +66,112 @@ export function scheduledTaskContextEnv(env = process.env) {
31
66
  }
32
67
  return out;
33
68
  }
34
- export const SCHEDULER_CONTEXT_ARG = "--scheduler-context";
35
- /** Resolve the complete non-secret AKM directory context captured by schedulers. */
36
- export function resolveScheduledTaskContext(env = process.env, platform = process.platform) {
37
- return canonicalContext({
38
- AKM_BUNDLE_DIR: path.resolve(resolveStashDir(env)),
39
- AKM_CONFIG_DIR: path.resolve(getConfigDir(env, platform)),
40
- AKM_DATA_DIR: path.resolve(getDataDir(env, platform)),
41
- AKM_CACHE_DIR: path.resolve(getCacheDir(env)),
42
- // Capture the complete process directory context used by scheduled commands.
43
- AKM_STATE_DIR: path.resolve(resolveStateDir(env, platform)),
44
- });
45
- }
46
- /** Build an installed argv from one already-validated public scheduler tail. */
47
- export function buildScheduledBindingInvocation(akmArgv, contextPath, invocation) {
48
- const parsed = parsePublicSchedulerInvocation(invocation);
49
- if (!parsed)
50
- throw invalidSchedulerInvocation();
51
- return {
52
- argv: [...akmArgv, SCHEDULER_CONTEXT_ARG, assertAbsolutePath(contextPath), ...parsed.invocation],
53
- };
54
- }
55
- export function schedulerContextDescriptor(context = resolveScheduledTaskContext(), envPath = process.env.PATH ?? "") {
56
- return {
57
- version: 1,
58
- environment: { ...canonicalContext(context), PATH: validatePathValue(envPath) },
59
- };
60
- }
61
- export function schedulerContextPath(descriptor) {
62
- const bytes = serializeDescriptor(descriptor);
63
- const digest = createHash("sha256").update(bytes).digest("hex");
64
- return path.join(getTaskContextDir(descriptor.environment), `${digest}.json`);
65
- }
66
- /** Write a content-addressed descriptor without ever replacing existing content. */
67
- export function writeSchedulerContextDescriptor(descriptor = schedulerContextDescriptor()) {
68
- const file = schedulerContextPath(descriptor);
69
- const bytes = serializeDescriptor(descriptor);
70
- fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
71
- if (process.platform !== "win32") {
72
- fs.chmodSync(path.dirname(file), 0o700);
73
- }
74
- if (lstatIfExists(file)) {
75
- if (serializeDescriptor(validateSchedulerContextDescriptor(file)) !== bytes)
76
- throw invalidSchedulerContext();
77
- return file;
78
- }
79
- const temp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
69
+ /**
70
+ * The environment every row a sync writes sets inline: `AKM_BUNDLE_DIR`,
71
+ * the syncing process's working stash, plus whichever `AKM_*_DIR` overrides
72
+ * it set explicitly. With no working stash to resolve (no default bundle, no
73
+ * `AKM_BUNDLE_DIR`), rows name their bundle by `--bundle` alone.
74
+ */
75
+ export function scheduledRowEnvironment(env = process.env) {
76
+ const out = {};
80
77
  try {
81
- fs.writeFileSync(temp, bytes, { encoding: "utf8", mode: 0o600, flag: "wx" });
82
- try {
83
- fs.renameSync(temp, file);
84
- }
85
- catch (error) {
86
- if (!lstatIfExists(file) || serializeDescriptor(validateSchedulerContextDescriptor(file)) !== bytes)
87
- throw error;
88
- }
89
- restrictDescriptor(file);
90
- validateSchedulerContextDescriptor(file);
78
+ out.AKM_BUNDLE_DIR = path.resolve(resolveStashDir(env));
79
+ }
80
+ catch {
81
+ // No working stash: there is no primary bundle to attribute rows to either.
91
82
  }
92
- finally {
93
- fs.rmSync(temp, { force: true });
83
+ for (const key of EXPLICIT_CONTEXT_KEYS) {
84
+ const value = env[key]?.trim();
85
+ if (value)
86
+ out[key] = path.resolve(value);
94
87
  }
95
- return file;
88
+ return out;
96
89
  }
97
- export function loadSchedulerContextDescriptor(file, env = process.env) {
98
- const descriptor = validateSchedulerContextDescriptor(file);
99
- for (const key of SCHEDULED_TASK_CONTEXT_KEYS)
100
- env[key] = descriptor.environment[key];
101
- env.PATH = descriptor.environment.PATH;
90
+ /** A row environment's entries, in {@link SCHEDULED_TASK_CONTEXT_KEYS} order. */
91
+ export function scheduledRowEnvironmentEntries(environment) {
92
+ const entries = [];
93
+ for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
94
+ const value = environment?.[key];
95
+ if (value)
96
+ entries.push([key, value]);
97
+ }
98
+ return entries;
102
99
  }
103
- export function validateSchedulerContextDescriptor(file) {
104
- const absolute = assertAbsolutePath(file);
105
- let linkStat;
106
- try {
107
- linkStat = fs.lstatSync(absolute);
100
+ /** Keep the {@link SCHEDULED_TASK_CONTEXT_KEYS} a parsed row set; `undefined` when there are none. */
101
+ export function scheduledRowEnvironmentFrom(variables) {
102
+ const out = {};
103
+ for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
104
+ const value = variables[key];
105
+ if (value)
106
+ out[key] = value;
108
107
  }
109
- catch (error) {
110
- throw schedulerContextFileError(absolute, error instanceof Error ? error.message : String(error));
108
+ return Object.keys(out).length > 0 ? out : undefined;
109
+ }
110
+ const SCHEDULER_CONTEXT_ARG = "--scheduler-context";
111
+ /** The installed argv: the launcher, then one already-validated public scheduler tail. */
112
+ export function buildScheduledInvocation(akmArgv, invocation) {
113
+ const parsed = parsePublicSchedulerInvocation(invocation);
114
+ if (!parsed)
115
+ throw invalidSchedulerInvocation();
116
+ return [...akmArgv, ...parsed.invocation];
117
+ }
118
+ /**
119
+ * Parse an installed row's argv (after any inline environment): the current
120
+ * shape, the `--scheduler-context <descriptor>` shape 0.9.0 – 0.9.17-alpha.6
121
+ * wrote, or the bare shape before that. A row that carries
122
+ * `--scheduler-context` but does not parse around it is never reread as
123
+ * another shape.
124
+ */
125
+ export function parseScheduledInvocationArgv(argv) {
126
+ const contextIndex = argv.indexOf(SCHEDULER_CONTEXT_ARG);
127
+ if (contextIndex !== -1) {
128
+ if (contextIndex < 1 || argv.indexOf(SCHEDULER_CONTEXT_ARG, contextIndex + 1) !== -1)
129
+ return undefined;
130
+ const contextPath = argv[contextIndex + 1];
131
+ if (!contextPath)
132
+ return undefined;
133
+ const publicInvocation = parsePublicSchedulerInvocation(argv.slice(contextIndex + 2));
134
+ if (!publicInvocation)
135
+ return undefined;
136
+ return { binding: argv.slice(0, contextIndex), contextPath, ...publicInvocation };
111
137
  }
112
- if (linkStat.isSymbolicLink())
113
- throw schedulerContextFileError(absolute, "symbolic links are not allowed");
114
- if (!linkStat.isFile())
115
- throw schedulerContextFileError(absolute, "path is not a regular file");
116
- let descriptorBytes;
117
- let fd;
118
- try {
119
- const noFollow = process.platform === "win32" ? 0 : fs.constants.O_NOFOLLOW;
120
- fd = fs.openSync(absolute, fs.constants.O_RDONLY | noFollow);
121
- const stat = fs.fstatSync(fd);
122
- if (!stat.isFile())
123
- throw schedulerContextFileError(absolute, "path is not a regular file");
124
- if (process.platform !== "win32") {
125
- if (typeof process.getuid !== "function") {
126
- throw schedulerContextFileError(absolute, "current uid is unavailable for ownership verification");
127
- }
128
- const uid = process.getuid();
129
- if (stat.uid !== uid)
130
- throw schedulerContextFileError(absolute, `file owner ${stat.uid} does not match uid ${uid}`);
131
- if ((stat.mode & 0o077) !== 0) {
132
- throw schedulerContextFileError(absolute, "group or other permissions must be disabled");
133
- }
138
+ for (let index = 1; index < argv.length - 1; index += 1) {
139
+ if ((argv[index] === "task" || argv[index] === "workflow") && argv[index + 1] === "run") {
140
+ const publicInvocation = parsePublicSchedulerInvocation(argv.slice(index));
141
+ return publicInvocation ? { binding: argv.slice(0, index), ...publicInvocation } : undefined;
134
142
  }
135
- descriptorBytes = fs.readFileSync(fd);
136
- }
137
- catch (error) {
138
- if (error instanceof ConfigError)
139
- throw error;
140
- throw schedulerContextFileError(absolute, error instanceof Error ? error.message : String(error));
141
- }
142
- finally {
143
- if (fd !== undefined)
144
- fs.closeSync(fd);
145
- }
146
- const filename = path.basename(absolute);
147
- const match = /^([a-f0-9]{64})\.json$/.exec(filename);
148
- const digest = createHash("sha256").update(descriptorBytes).digest("hex");
149
- if (!match || match[1] !== digest) {
150
- throw schedulerContextFileError(absolute, "content SHA-256 does not match the descriptor filename");
151
143
  }
144
+ return undefined;
145
+ }
146
+ /**
147
+ * The environment a `--scheduler-context` descriptor names: its `AKM_*_DIR`
148
+ * keys and, in one written before 0.9.17, `PATH`. It is read as plain JSON —
149
+ * the file is this user's own, next to the data it names.
150
+ */
151
+ export function readLegacySchedulerContext(file) {
152
152
  let parsed;
153
153
  try {
154
- parsed = JSON.parse(descriptorBytes.toString("utf8"));
154
+ parsed = JSON.parse(fs.readFileSync(file, "utf8"));
155
155
  }
156
156
  catch (error) {
157
- throw schedulerContextFileError(absolute, error instanceof Error ? error.message : String(error));
157
+ throw new ConfigError(`Cannot read scheduler context "${file}": ${error instanceof Error ? error.message : String(error)}. ` +
158
+ "Run `akm task sync` to rewrite this scheduled row.", "INVALID_CONFIG_FILE");
158
159
  }
159
- return canonicalDescriptor(parsed);
160
+ const environment = parsed !== null && typeof parsed === "object" ? parsed.environment : undefined;
161
+ const out = {};
162
+ if (environment === null || typeof environment !== "object")
163
+ return out;
164
+ for (const key of [...SCHEDULED_TASK_CONTEXT_KEYS, "PATH"]) {
165
+ const value = environment[key];
166
+ if (typeof value === "string" && value.length > 0)
167
+ out[key] = value;
168
+ }
169
+ return out;
160
170
  }
161
- /** Load and remove the hidden descriptor argument before citty parses argv. */
171
+ /**
172
+ * Apply and remove a row's `--scheduler-context <descriptor>` before citty
173
+ * parses argv. Only a row written by 0.9.0 – 0.9.17-alpha.6 passes it.
174
+ */
162
175
  export function consumeSchedulerContextArg(argv, env = process.env) {
163
176
  const separator = argv.indexOf("--");
164
177
  const index = argv.slice(0, separator === -1 ? argv.length : separator).indexOf(SCHEDULER_CONTEXT_ARG);
@@ -166,37 +179,16 @@ export function consumeSchedulerContextArg(argv, env = process.env) {
166
179
  return argv;
167
180
  const file = argv[index + 1];
168
181
  if (!file) {
169
- throw new ConfigError(`${SCHEDULER_CONTEXT_ARG} requires an absolute descriptor path.`, "INVALID_CONFIG_FILE");
182
+ throw new ConfigError(`${SCHEDULER_CONTEXT_ARG} requires a descriptor path.`, "INVALID_CONFIG_FILE");
170
183
  }
171
- loadSchedulerContextDescriptor(file, env);
184
+ Object.assign(env, readLegacySchedulerContext(file));
172
185
  return [...argv.slice(0, index), ...argv.slice(index + 2)];
173
186
  }
174
- /** Parse either the public scheduled task or qualified workflow invocation. */
175
- export function parseScheduledBindingArgv(argv) {
176
- const contextIndex = argv.indexOf(SCHEDULER_CONTEXT_ARG);
177
- if (contextIndex < 1 || argv.indexOf(SCHEDULER_CONTEXT_ARG, contextIndex + 1) !== -1)
178
- return undefined;
179
- const contextPath = argv[contextIndex + 1];
180
- if (!contextPath)
181
- return undefined;
182
- const publicInvocation = parsePublicSchedulerInvocation(argv.slice(contextIndex + 2));
183
- if (!publicInvocation)
184
- return undefined;
185
- return {
186
- binding: [...argv.slice(0, contextIndex)],
187
- contextPath: assertAbsolutePath(contextPath),
188
- invocation: publicInvocation.invocation,
189
- ...(publicInvocation.target !== undefined ? { target: publicInvocation.target } : {}),
190
- };
191
- }
192
187
  /**
193
- * Parse just the public `task run …` / `workflow run …` tail, with no
194
- * `--scheduler-context` wrapper. Exported so a backend can recognize a
195
- * pre-`--scheduler-context` invocation still sitting inside akm's own
196
- * ownership-marked block (see `extractCronInvocation` in
197
- * `src/tasks/backends/cron.ts`) without re-implementing this grammar.
188
+ * Parse just the public `task run …` / `workflow run …` tail, the part of a
189
+ * row that names what it runs.
198
190
  */
199
- export function parsePublicSchedulerInvocation(invocation) {
191
+ function parsePublicSchedulerInvocation(invocation) {
200
192
  if (invocation[0] === "task" && invocation[1] === "run" && invocation[2]) {
201
193
  try {
202
194
  if (normaliseTaskConceptId(invocation[2]) !== invocation[2])
@@ -290,106 +282,6 @@ function isValidSchedulerInputFlagTail(tail) {
290
282
  }
291
283
  return true;
292
284
  }
293
- function canonicalContext(input) {
294
- const inputKeys = Object.keys(input);
295
- if (inputKeys.length !== SCHEDULED_TASK_CONTEXT_KEYS.length ||
296
- inputKeys.some((key) => !SCHEDULED_TASK_CONTEXT_KEYS.includes(key))) {
297
- throw invalidSchedulerContext();
298
- }
299
- const context = {};
300
- for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
301
- const value = input[key];
302
- if (typeof value !== "string" ||
303
- value.trim().length === 0 ||
304
- containsControlCharacter(value) ||
305
- (!path.posix.isAbsolute(value) && !path.win32.isAbsolute(value))) {
306
- throw invalidSchedulerContext();
307
- }
308
- context[key] = value;
309
- }
310
- return context;
311
- }
312
- function canonicalDescriptor(input) {
313
- if (typeof input !== "object" || input === null || Array.isArray(input))
314
- throw invalidSchedulerContext();
315
- const record = input;
316
- if (Object.keys(record).length !== 2 || record.version !== 1)
317
- throw invalidSchedulerContext();
318
- const rawEnvironment = record.environment;
319
- if (typeof rawEnvironment !== "object" || rawEnvironment === null || Array.isArray(rawEnvironment)) {
320
- throw invalidSchedulerContext();
321
- }
322
- const environment = rawEnvironment;
323
- if (Object.keys(environment).length !== SCHEDULED_TASK_CONTEXT_KEYS.length + 1)
324
- throw invalidSchedulerContext();
325
- const PATH = validatePathValue(environment.PATH);
326
- const { PATH: _, ...rawContext } = environment;
327
- return { version: 1, environment: { ...canonicalContext(rawContext), PATH } };
328
- }
329
- function serializeDescriptor(descriptor) {
330
- return `${JSON.stringify(canonicalDescriptor(descriptor))}\n`;
331
- }
332
- function validatePathValue(value) {
333
- if (typeof value !== "string" || containsControlCharacter(value))
334
- throw invalidSchedulerContext();
335
- return value;
336
- }
337
- function assertAbsolutePath(value) {
338
- if (containsControlCharacter(value) || (!path.posix.isAbsolute(value) && !path.win32.isAbsolute(value))) {
339
- throw invalidSchedulerContext();
340
- }
341
- return value;
342
- }
343
- function restrictDescriptor(file) {
344
- if (process.platform !== "win32")
345
- fs.chmodSync(file, 0o600);
346
- }
347
- function lstatIfExists(file) {
348
- try {
349
- return fs.lstatSync(file);
350
- }
351
- catch (error) {
352
- if (error.code === "ENOENT")
353
- return undefined;
354
- throw error;
355
- }
356
- }
357
- function schedulerContextFileError(file, reason) {
358
- return new ConfigError(`Invalid scheduler context descriptor "${file}": ${reason}.`, "INVALID_CONFIG_FILE");
359
- }
360
- function containsControlCharacter(value) {
361
- for (const char of value) {
362
- const code = char.codePointAt(0) ?? 0;
363
- if (code < 0x20 || (code >= 0x7f && code <= 0x9f))
364
- return true;
365
- }
366
- return false;
367
- }
368
- function resolveStateDir(env, platform) {
369
- const override = env.AKM_STATE_DIR?.trim();
370
- if (override)
371
- return override;
372
- if (platform === "win32") {
373
- const localAppData = env.LOCALAPPDATA?.trim();
374
- if (localAppData)
375
- return path.join(localAppData, "akm", "state");
376
- const userProfile = env.USERPROFILE?.trim();
377
- if (userProfile)
378
- return path.join(userProfile, "AppData", "Local", "akm", "state");
379
- const appData = env.APPDATA?.trim();
380
- if (appData)
381
- return path.join(appData, "..", "Local", "akm", "state");
382
- throw new ConfigError("Unable to determine state directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
383
- }
384
- const xdgStateHome = env.XDG_STATE_HOME?.trim();
385
- if (xdgStateHome)
386
- return path.join(xdgStateHome, "akm");
387
- const home = env.HOME?.trim();
388
- return home ? path.join(home, ".local", "state", "akm") : path.join("/tmp", "akm-state");
389
- }
390
- function invalidSchedulerContext() {
391
- return new ConfigError(`Invalid scheduler context; expected exactly ${SCHEDULED_TASK_CONTEXT_KEYS.join(", ")} as absolute paths.`, "INVALID_CONFIG_FILE");
392
- }
393
285
  function invalidSchedulerInvocation() {
394
286
  return new ConfigError("Invalid scheduler invocation; expected public " +
395
287
  "`task run <id> [--bundle <bundle>] --scheduled [--<input> <value>…]` or `workflow run <qualified-ref>` argv.", "INVALID_CONFIG_FILE");
@@ -0,0 +1,53 @@
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
+ * The one process lock around native scheduler writes.
6
+ *
7
+ * `task sync|add|enable|disable|prune --yes` read the installed rows, plan,
8
+ * and apply under it, so two akm processes never interleave their writes to
9
+ * the same crontab or LaunchAgents directory. It is an `O_EXCL` lock file
10
+ * under `$STATE/locks`, reclaimed only from a verifiably dead holder, and
11
+ * re-entrant within one process (`add` runs `sync` inside it).
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { TransientError } from "../core/errors.js";
16
+ import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "../core/file-lock.js";
17
+ import { getStateDir } from "../core/paths.js";
18
+ export function schedulerLockPath() {
19
+ return path.join(getStateDir(), "locks", "scheduler.lock");
20
+ }
21
+ let depth = 0;
22
+ export async function withSchedulerLock(fn) {
23
+ if (depth > 0) {
24
+ depth += 1;
25
+ try {
26
+ return await fn();
27
+ }
28
+ finally {
29
+ depth -= 1;
30
+ }
31
+ }
32
+ const lockPath = schedulerLockPath();
33
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
34
+ const acquire = () => tryAcquireLockSync(lockPath, createLockPayload({ purpose: "scheduler" }));
35
+ let ownership = acquire();
36
+ if (!ownership) {
37
+ const probe = probeLock(lockPath);
38
+ if (probe.state === "absent" || (probe.state === "stale" && reclaimStaleLock(lockPath, probe))) {
39
+ ownership = acquire();
40
+ }
41
+ }
42
+ if (!ownership) {
43
+ throw new TransientError("Another akm process is syncing the scheduler right now.", "SCHEDULER_LOCK_HELD");
44
+ }
45
+ depth = 1;
46
+ try {
47
+ return await fn();
48
+ }
49
+ finally {
50
+ depth = 0;
51
+ releaseLock(ownership);
52
+ }
53
+ }