akm-cli 0.9.0-rc.0 → 0.9.0-rc.13

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 (598) hide show
  1. package/CHANGELOG.md +1283 -22
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -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
- * `akm tasks` — register, inspect, run, and remove scheduled task assets.
5
+ * `akm task` — register, inspect, run, and remove scheduled task assets.
6
6
  *
7
7
  * Each handler exported here is a pure function that performs the real work;
8
8
  * `src/cli.ts` wraps these in citty `defineCommand`s and shapes their return
@@ -11,22 +11,26 @@
11
11
  import fs from "node:fs";
12
12
  import path from "node:path";
13
13
  import { stringify as yamlStringify } from "yaml";
14
- import { resolveAssetPathFromName } from "../../core/asset/asset-spec.js";
14
+ import { assetPathForName } from "../../core/asset/asset-placement.js";
15
+ import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
15
16
  import { isWithin, resolveStashDir } from "../../core/common.js";
16
17
  import { loadConfig } from "../../core/config/config.js";
18
+ import { IMPROVE_AUTONOMY_CONFIG_KEY, isImproveAutonomyEnabled } from "../../core/config/experimental.js";
17
19
  import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
18
20
  import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
19
- import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
20
- import { listAgentProfileNames } from "../../integrations/agent/index.js";
21
- import { resolveAssetPath } from "../../sources/resolve.js";
21
+ import { commitWriteTargetBoundary, deleteAssetFromSource, prepareWriteTargetForMutation, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
22
22
  import { backendNameForPlatform, selectBackend } from "../../tasks/backends/index.js";
23
23
  import { parseTaskDocument } from "../../tasks/parser.js";
24
24
  import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
25
- import { exitCodeForStatus, readTaskHistory, runTask } from "../../tasks/runner.js";
26
- import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT, translateToCron } from "../../tasks/schedule.js";
25
+ import { exitCodeForStatus, INVALID_TASK_ATTEMPT_ID, readTaskHistory, recordTaskAttemptFailure, runTask, } from "../../tasks/runner.js";
26
+ import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT } from "../../tasks/schedule.js";
27
+ import { schedulerContextDescriptor, validateSchedulerContextDescriptor, writeSchedulerContextDescriptor, } from "../../tasks/scheduler-invocation.js";
28
+ import { normaliseTaskId } from "../../tasks/task-id.js";
27
29
  import { validateTaskDocument } from "../../tasks/validator.js";
28
- import { resolveImproveProfile } from "../improve/improve-profiles.js";
29
- export async function akmTasksAdd(input) {
30
+ import { isWorkflowEngineEnabled, WORKFLOW_ENGINE_CONFIG_KEY } from "../../workflows/exec/workflow-engine-gate.js";
31
+ import { applyAutonomyGate, configuredDirectAutonomyLanes, describeGatedLanes } from "../improve/autonomy-gate.js";
32
+ import { resolveImproveStrategy } from "../improve/improve-strategies.js";
33
+ export async function akmTasksAdd(input, deps = {}) {
30
34
  const id = normaliseTaskId(input.id);
31
35
  const hasCommand = input.command !== undefined &&
32
36
  input.command !== null &&
@@ -36,19 +40,28 @@ export async function akmTasksAdd(input) {
36
40
  if (targetCount !== 1) {
37
41
  throw new UsageError("Pass exactly one of --workflow <ref>, --prompt <asset-ref|./file.md|text>, or --command <shell-command>.", "INVALID_FLAG_VALUE");
38
42
  }
43
+ if (input.workflow && (input.engine !== undefined || input.model !== undefined || input.timeoutMs !== undefined)) {
44
+ throw new UsageError("Workflow tasks accept only --params; engine, model, and timeout are prompt-task fields.", "INVALID_FLAG_VALUE");
45
+ }
46
+ if (hasCommand && (input.engine !== undefined || input.model !== undefined)) {
47
+ throw new UsageError("Command tasks accept --timeout-ms but not --engine or --model.", "INVALID_FLAG_VALUE");
48
+ }
39
49
  // Validate the schedule for the active backend before writing anything.
40
- const backend = backendNameForPlatform();
50
+ // WI-9.10e: the injected backend (tests) carries its own name, so derive it
51
+ // from `deps.backend` when present — retiring the `_setBackendsForTests` seam.
52
+ const backend = deps.backend?.name ?? backendNameForPlatform();
41
53
  parseSchedule(input.schedule, backend);
42
- const target = resolveTaskWriteTarget();
43
- const stashDir = target.source.path;
54
+ const bundle = resolveTaskBundle(input.target, { requireWritable: true });
55
+ const writeTarget = bundle.resolved;
56
+ const stashDir = bundle.stashDir;
57
+ const installOpts = bundle.installTarget !== undefined ? { target: bundle.installTarget } : undefined;
44
58
  const typeRoot = path.join(stashDir, "tasks");
45
- fs.mkdirSync(typeRoot, { recursive: true });
46
- const assetPath = resolveAssetPathFromName("task", typeRoot, id);
59
+ const assetPath = assetPathForName("task", typeRoot, id);
47
60
  if (!isWithin(assetPath, typeRoot)) {
48
61
  throw new UsageError(`Resolved task path escapes the stash: "${id}".`, "PATH_ESCAPE_VIOLATION");
49
62
  }
50
63
  if (fs.existsSync(assetPath) && !input.force) {
51
- throw new UsageError(`Task "${id}" already exists. Pass --force to overwrite, or use \`akm tasks remove ${id}\` first.`, "RESOURCE_ALREADY_EXISTS");
64
+ throw new UsageError(`Task "${id}" already exists. Pass --force to overwrite, or delete its file and run \`akm task sync\` first.`, "RESOURCE_ALREADY_EXISTS");
52
65
  }
53
66
  const yaml = renderTaskYaml({
54
67
  id,
@@ -56,7 +69,9 @@ export async function akmTasksAdd(input) {
56
69
  workflow: input.workflow,
57
70
  prompt: input.prompt,
58
71
  command: input.command,
59
- profile: input.profile,
72
+ engine: input.engine,
73
+ model: input.model,
74
+ timeoutMs: input.timeoutMs,
60
75
  params: input.params,
61
76
  name: input.name,
62
77
  description: input.description,
@@ -67,231 +82,187 @@ export async function akmTasksAdd(input) {
67
82
  const task = parseTaskDocument({ yaml, filePath: assetPath, id });
68
83
  await validateTaskDocument(task, { backend, stashDir });
69
84
  const ref = taskAssetRef(id);
70
- await writeAssetToSource(target.source, target.config, ref, yaml);
71
- // Install in the OS scheduler. If install fails after the file was written,
72
- // delete the file so the on-disk state never claims a task is registered
73
- // when it isn't.
85
+ const previousYaml = fs.existsSync(assetPath) ? fs.readFileSync(assetPath, "utf8") : undefined;
86
+ let previousTask;
87
+ let previousTaskError;
88
+ if (previousYaml !== undefined) {
89
+ try {
90
+ previousTask = parseTaskDocument({ yaml: previousYaml, filePath: assetPath, id });
91
+ }
92
+ catch (err) {
93
+ previousTaskError = err;
94
+ }
95
+ }
96
+ const sched = deps.backend ?? selectBackend();
97
+ const writeAsset = deps.writeAsset ?? writeAssetToSource;
98
+ const deleteAsset = deps.deleteAsset ?? deleteAssetFromSource;
99
+ const commitBoundary = deps.commitBoundary ?? commitWriteTargetBoundary;
100
+ const installedEntries = await sched.list();
101
+ assertNoForeignSchedule(installedEntries, id, bundle.installTarget);
102
+ const wasInstalled = previousYaml !== undefined && installedEntries.some((entry) => entry.id === id);
103
+ const installedEntry = installedEntries.find((entry) => entry.id === id);
104
+ const runtimeOpts = schedulerInstallOptions(installOpts, installedEntry, deps, installedEntry ? false : input.rebind === true, `create scheduler entry for task "${id}"`);
105
+ let sourceRestoreArmed = false;
106
+ let installSucceeded = false;
74
107
  try {
75
- const sched = selectBackend();
76
- await sched.install(task);
108
+ sourceRestoreArmed = true;
109
+ await writeAsset(writeTarget.source, writeTarget.config, ref, yaml);
110
+ await sched.install(task, runtimeOpts);
111
+ installSucceeded = true;
112
+ commitBoundary(writeTarget, `Update tasks/${id}`);
77
113
  }
78
114
  catch (err) {
79
- try {
80
- await deleteAssetFromSource(target.source, target.config, ref);
115
+ const rollbackErrors = [];
116
+ let sourceRestored = false;
117
+ if (sourceRestoreArmed) {
118
+ try {
119
+ if (previousYaml === undefined) {
120
+ if (fs.existsSync(assetPath)) {
121
+ await deleteAsset(writeTarget.source, writeTarget.config, ref);
122
+ sourceRestored = true;
123
+ }
124
+ }
125
+ else {
126
+ await restoreTaskSourceBytes(writeAsset, writeTarget.source, writeTarget.config, ref, assetPath, previousYaml);
127
+ sourceRestored = true;
128
+ }
129
+ }
130
+ catch (rollbackError) {
131
+ rollbackErrors.push(rollbackError);
132
+ }
81
133
  }
82
- catch {
83
- /* ignore */
134
+ if (installSucceeded && !wasInstalled) {
135
+ try {
136
+ await sched.uninstall(id);
137
+ }
138
+ catch (rollbackError) {
139
+ rollbackErrors.push(rollbackError);
140
+ }
141
+ }
142
+ else if (installSucceeded && previousTask) {
143
+ try {
144
+ await sched.install(previousTask, runtimeOpts);
145
+ }
146
+ catch (rollbackError) {
147
+ rollbackErrors.push(rollbackError);
148
+ try {
149
+ if (typeof sched.setEnabled !== "function") {
150
+ throw new Error(`Scheduler backend "${sched.name}" cannot disable task "${id}".`);
151
+ }
152
+ await sched.setEnabled(id, false);
153
+ }
154
+ catch (disableError) {
155
+ rollbackErrors.push(disableError);
156
+ try {
157
+ await sched.uninstall(id);
158
+ }
159
+ catch (uninstallError) {
160
+ rollbackErrors.push(uninstallError);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ else if (installSucceeded && wasInstalled) {
166
+ rollbackErrors.push(previousTaskError ?? new Error(`Prior task "${id}" could not be restored.`));
167
+ }
168
+ if (sourceRestored) {
169
+ try {
170
+ commitBoundary(writeTarget, `Restore tasks/${id}`);
171
+ }
172
+ catch (rollbackError) {
173
+ rollbackErrors.push(rollbackError);
174
+ }
175
+ }
176
+ if (rollbackErrors.length > 0) {
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ throw new AggregateError([err, ...rollbackErrors], `${message}; rollback for task "${id}" was incomplete.`);
84
179
  }
85
180
  throw err;
86
181
  }
87
- commitWriteTargetBoundary(target, `Update task:${id}`);
88
182
  return {
89
183
  id,
90
- ref: `task:${id}`,
184
+ ref: conceptIdFromTypeName("task", id),
91
185
  path: assetPath,
92
- stashDir,
186
+ bundleDir: stashDir,
93
187
  schedule: task.schedule,
94
188
  enabled: task.enabled,
95
189
  backend,
96
190
  target: task.target,
97
191
  };
98
192
  }
99
- /**
100
- * Emit a single grouped stderr warning for legacy `.md` task files in the
101
- * tasks directory. 0.8.0 requires task definitions to be pure `.yml`; any
102
- * leftover `.md` files from 0.7.x would otherwise be silently skipped, which
103
- * makes scheduled tasks vanish without operator notice. We do NOT auto-migrate
104
- * — that is a separate workstream — but operators must see the affected files.
105
- *
106
- * `seen` is module-level so the warning is emitted at most once per process,
107
- * even when both `akm tasks list` and `akm tasks sync` are invoked in the same
108
- * akm run.
109
- */
110
- const warnedLegacyMdDirs = new Set();
111
- function warnLegacyMdTaskFiles(typeRoot) {
112
- if (warnedLegacyMdDirs.has(typeRoot))
113
- return;
114
- let mdFiles;
193
+ export async function akmTasksRun(id, options = {}) {
194
+ const startedAt = new Date();
195
+ let normalised;
115
196
  try {
116
- mdFiles = fs.readdirSync(typeRoot).filter((f) => f.endsWith(".md"));
197
+ normalised = parseTaskRef(id).id;
117
198
  }
118
- catch {
119
- return;
120
- }
121
- if (mdFiles.length === 0)
122
- return;
123
- warnedLegacyMdDirs.add(typeRoot);
124
- const affected = mdFiles.map((f) => `tasks/${f}`).join(", ");
125
- process.stderr.write(`WARNING: ${mdFiles.length} task file(s) use the legacy .md format and were ignored.\n` +
126
- ` AKM 0.8.0 requires tasks as pure .yml. See docs/migration/v0.7-to-v0.8.md#task-definition-files-mdfrontmatter--yml.\n` +
127
- ` Affected: ${affected}\n`);
128
- }
129
- /**
130
- * Reset the legacy `.md` task warning de-duplication state. Test-only escape
131
- * hatch — production code should never call this.
132
- */
133
- export function _resetLegacyMdTaskWarningStateForTests() {
134
- warnedLegacyMdDirs.clear();
135
- }
136
- export async function akmTasksList() {
137
- const stashDir = resolveStashDir();
138
- const typeRoot = path.join(stashDir, "tasks");
139
- if (!fs.existsSync(typeRoot))
140
- return { tasks: [] };
141
- const entries = fs.readdirSync(typeRoot);
142
- warnLegacyMdTaskFiles(typeRoot);
143
- const files = entries.filter((f) => f.endsWith(".yml"));
144
- const tasks = [];
145
- for (const file of files) {
146
- const id = file.slice(0, -4);
147
- const filePath = path.join(typeRoot, file);
148
- let task;
149
- try {
150
- task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
151
- }
152
- catch {
153
- continue; // skip malformed files; `akm tasks show <id>` will surface the error
154
- }
155
- tasks.push({
156
- id: task.id,
157
- ref: `task:${task.id}`,
158
- path: filePath,
159
- schedule: task.schedule,
160
- enabled: task.enabled,
161
- target: task.target,
162
- name: task.name,
163
- description: task.description,
164
- when_to_use: task.when_to_use,
165
- tags: task.tags,
199
+ catch (failure) {
200
+ recordTaskAttemptFailure({
201
+ taskId: INVALID_TASK_ATTEMPT_ID,
202
+ reason: "invalid_task_id",
203
+ failure,
204
+ startedAt,
166
205
  });
206
+ throw failure;
167
207
  }
168
- return { tasks };
169
- }
170
- export async function akmTasksShow(id) {
171
- const normalised = normaliseTaskId(id);
172
- const stashDir = resolveStashDir();
173
- const typeRoot = path.join(stashDir, "tasks");
174
- if (fs.existsSync(typeRoot))
175
- warnLegacyMdTaskFiles(typeRoot);
176
- const filePath = await resolveAssetPath(stashDir, "task", normalised);
177
- const task = parseTaskDocument({
178
- yaml: fs.readFileSync(filePath, "utf8"),
179
- filePath,
180
- id: normalised,
181
- });
182
- const spec = parseSchedule(task.schedule, backendNameForPlatform());
183
- return {
184
- id: task.id,
185
- ref: `task:${task.id}`,
186
- path: filePath,
187
- schedule: task.schedule,
188
- cron: translateToCron(spec),
189
- enabled: task.enabled,
190
- target: task.target,
191
- name: task.name,
192
- description: task.description,
193
- when_to_use: task.when_to_use,
194
- tags: task.tags,
195
- };
196
- }
197
- export async function akmTasksRemove(id) {
198
- const normalised = normaliseTaskId(id);
199
- const target = resolveTaskWriteTarget();
200
- const stashDir = target.source.path;
201
- const typeRoot = path.join(stashDir, "tasks");
202
- if (fs.existsSync(typeRoot))
203
- warnLegacyMdTaskFiles(typeRoot);
204
- await resolveAssetPath(stashDir, "task", normalised);
205
- const ref = taskAssetRef(normalised);
206
- const sched = selectBackend();
207
- let uninstallError;
208
- let deleteError;
209
- try {
210
- await sched.uninstall(normalised);
211
- }
212
- catch (err) {
213
- uninstallError = err;
214
- }
208
+ let stashDir;
215
209
  try {
216
- await deleteAssetFromSource(target.source, target.config, ref);
210
+ // No --bundle uses the primary stash. With --bundle, resolve (read-only)
211
+ // the named bundle so the task file and
212
+ // its relative asset refs load from that bundle's path.
213
+ stashDir =
214
+ options.target !== undefined
215
+ ? resolveWriteTarget(loadConfig(), options.target, { requireWritable: false }).source.path
216
+ : resolveStashDir();
217
217
  }
218
- catch (err) {
219
- deleteError = err;
220
- }
221
- if (uninstallError !== undefined)
222
- throw uninstallError;
223
- if (deleteError !== undefined)
224
- throw deleteError;
225
- commitWriteTargetBoundary(target, `Remove task:${normalised}`);
226
- return { id: normalised, removed: true, backend: sched.name };
227
- }
228
- export async function akmTasksSetEnabled(id, enabled) {
229
- const normalised = normaliseTaskId(id);
230
- const target = resolveTaskWriteTarget();
231
- const stashDir = target.source.path;
232
- const typeRoot = path.join(stashDir, "tasks");
233
- if (fs.existsSync(typeRoot))
234
- warnLegacyMdTaskFiles(typeRoot);
235
- const filePath = await resolveAssetPath(stashDir, "task", normalised);
236
- const yaml = fs.readFileSync(filePath, "utf8");
237
- const updated = setEnabledInYaml(yaml, enabled);
238
- const ref = taskAssetRef(normalised);
239
- await writeAssetToSource(target.source, target.config, ref, updated);
240
- const sched = selectBackend();
241
- try {
242
- // Reinstall from the (just-updated) definition rather than only toggling
243
- // the comment. A plain toggle leaves a stale schedule in place if the
244
- // .yml's `schedule:` changed while the task was disabled — re-enabling
245
- // would silently keep the old cron line. install() renders the block with
246
- // both the current schedule and the new enabled state, and is idempotent.
247
- const task = parseTaskDocument({ yaml: updated, filePath, id: normalised });
248
- await sched.install(task);
249
- }
250
- catch (err) {
251
- // Roll the file back so the YAML source-of-truth and the OS
252
- // scheduler don't diverge silently when the backend call fails.
253
- await writeAssetToSource(target.source, target.config, ref, yaml);
254
- throw err;
218
+ catch (failure) {
219
+ recordTaskAttemptFailure({
220
+ taskId: normalised,
221
+ reason: "task_load_failed",
222
+ failure,
223
+ startedAt,
224
+ });
225
+ throw failure;
255
226
  }
256
- commitWriteTargetBoundary(target, `Update task:${normalised}`);
257
- return { id: normalised, enabled, backend: sched.name };
258
- }
259
- export async function akmTasksRun(id) {
260
- const normalised = normaliseTaskId(id);
261
- const stashDir = resolveStashDir();
262
- const typeRoot = path.join(stashDir, "tasks");
263
- if (fs.existsSync(typeRoot))
264
- warnLegacyMdTaskFiles(typeRoot);
265
- const result = await runTask(normalised);
227
+ const result = await runTask(normalised, { stashDir, scheduled: options.scheduled === true });
228
+ const exitCode = result.status === "failed" && result.target.kind === "command" && result.detail?.exitCode === 78
229
+ ? 78
230
+ : exitCodeForStatus(result.status);
266
231
  return {
267
232
  ok: result.status === "completed" || result.status === "disabled",
268
233
  result,
269
- exitCode: exitCodeForStatus(result.status),
234
+ exitCode,
270
235
  };
271
236
  }
272
237
  export async function akmTasksHistory(input) {
273
238
  const limit = input.limit !== undefined && input.limit > 0 ? input.limit : 50;
274
239
  const id = input.id ? normaliseTaskId(input.id) : undefined;
275
- const stashDir = resolveStashDir();
276
- const typeRoot = path.join(stashDir, "tasks");
277
- if (fs.existsSync(typeRoot))
278
- warnLegacyMdTaskFiles(typeRoot);
240
+ // History rows are keyed by task id in state.db, not per bundle.
279
241
  return { rows: readTaskHistory({ id, limit }) };
280
242
  }
281
243
  /**
282
- * Reconcile the on-disk task files with the OS scheduler.
244
+ * Reconcile the on-disk task files of ONE bundle with the OS scheduler.
283
245
  * • install missing tasks (after validating them — invalid files are
284
246
  * skipped with a per-task reason rather than aborting the whole sync)
285
247
  * • reinstall tasks whose schedule or enabled state changed in the .yml
286
248
  * (drift detected by comparing the backend's installed signature against
287
249
  * the signature the current definition would produce)
288
250
  * • remove orphan scheduler entries that no longer have a backing file
251
+ *
252
+ * `--bundle <bundle>` scopes the reconciliation to that bundle: the file set is
253
+ * the bundle's `tasks/*.yml` and — crucially — the scheduler entries considered
254
+ * are ONLY those attributed to the same bundle (parsed from the installed
255
+ * `--bundle` token; absent ⇒ primary). This is the security boundary that keeps
256
+ * "registering a bundle never activates code": a plain (primary) sync never
257
+ * installs from, updates, or removes another bundle's entries, and sync never
258
+ * scans all bundles. Activation happens only through explicit `add --bundle`
259
+ * (or `sync --bundle` on a bundle whose task files are already present).
289
260
  */
290
- export async function akmTasksSync(deps = {}) {
291
- const stashDir = resolveStashDir();
261
+ export async function akmTasksSync(deps = {}, bundleTarget, options = {}) {
262
+ const stashDir = resolveTaskInspectDir(bundleTarget);
263
+ // Primary-bundle scheduler entries omit --bundle; other bundles carry it.
264
+ const syncTarget = bundleTarget !== undefined && !isPrimaryStashPath(stashDir) ? bundleTarget : undefined;
292
265
  const typeRoot = path.join(stashDir, "tasks");
293
- if (fs.existsSync(typeRoot))
294
- warnLegacyMdTaskFiles(typeRoot);
295
266
  const fileIds = fs.existsSync(typeRoot)
296
267
  ? fs
297
268
  .readdirSync(typeRoot)
@@ -299,34 +270,58 @@ export async function akmTasksSync(deps = {}) {
299
270
  .map((f) => f.slice(0, -4))
300
271
  : [];
301
272
  const sched = deps.backend ?? selectBackend();
302
- const backend = backendNameForPlatform();
303
- // Map id → installed signature so sync can detect schedule/enabled drift on
304
- // tasks that already exist in the scheduler, not just presence/absence.
305
- const present = new Map((await sched.list()).map((t) => [t.id, t.signature]));
273
+ const backend = sched.name;
274
+ const installOpts = syncTarget !== undefined ? { target: syncTarget } : undefined;
275
+ const allEntries = options.rebind && sched.listForRebind ? await sched.listForRebind() : await sched.list();
276
+ // Attribution filter: only entries installed from THIS bundle are reconciled
277
+ // here. Entries carrying a `--bundle` for a different bundle are invisible to
278
+ // this sync — never removed, never touched.
279
+ const present = new Map(allEntries.filter((t) => sameBundle(t.target, syncTarget)).map((t) => [t.id, t]));
306
280
  const installed = [];
307
281
  const updated = [];
308
282
  const unchanged = [];
309
283
  const skipped = [];
284
+ const warnings = [];
310
285
  for (const id of fileIds) {
311
286
  const filePath = path.join(typeRoot, `${id}.yml`);
312
287
  let task;
313
288
  try {
314
289
  task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
315
- }
316
- catch (err) {
317
- skipped.push({ id, reason: err instanceof Error ? err.message : String(err) });
318
- continue;
319
- }
320
- try {
321
290
  await validateTaskDocument(task, { backend, stashDir });
291
+ // A bare id can only be scheduled from ONE bundle at a time (scheduler ids
292
+ // are never namespaced). If this id is already scheduled from a different
293
+ // bundle, refuse rather than clobber it — surface it as a per-task skip so
294
+ // the rest of the sync still proceeds.
295
+ const foreign = allEntries.find((e) => e.id === id && !sameBundle(e.target, syncTarget));
296
+ if (foreign)
297
+ throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
322
298
  }
323
299
  catch (err) {
324
300
  skipped.push({ id, reason: err instanceof Error ? err.message : String(err) });
301
+ if (present.has(id)) {
302
+ try {
303
+ await sched.setEnabled(id, false);
304
+ }
305
+ catch (disableError) {
306
+ try {
307
+ await sched.uninstall(id);
308
+ }
309
+ catch (uninstallError) {
310
+ throw new AggregateError([err, disableError, uninstallError], `Task "${id}" is invalid and its installed scheduler entry could not be disabled or removed.`);
311
+ }
312
+ }
313
+ }
325
314
  continue;
326
315
  }
327
316
  if (!present.has(id)) {
328
- await sched.install(task);
329
- installed.push(id);
317
+ try {
318
+ const runtimeOpts = schedulerInstallOptions(installOpts, undefined, deps, options.rebind === true, `create scheduler entry for task "${id}"`, warnings);
319
+ await sched.install(task, runtimeOpts);
320
+ installed.push(id);
321
+ }
322
+ catch (error) {
323
+ skipped.push({ id, reason: error instanceof Error ? error.message : String(error) });
324
+ }
330
325
  continue;
331
326
  }
332
327
  // Already installed — reconcile against the current definition. Compare the
@@ -334,13 +329,15 @@ export async function akmTasksSync(deps = {}) {
334
329
  // When the backend can't produce a signature (no expectedSignature, or it
335
330
  // didn't record one), reinstall unconditionally — install() is idempotent,
336
331
  // so the cost is one crontab write and correctness is guaranteed.
337
- const installedSig = present.get(id);
338
- const expectedSig = sched.expectedSignature?.(task);
332
+ const installedEntry = present.get(id);
333
+ const runtimeOpts = schedulerInstallOptions(installOpts, options.rebind ? undefined : installedEntry, deps, options.rebind === true, `rebind scheduler entry for task "${id}"`, warnings);
334
+ const installedSig = installedEntry.signature;
335
+ const expectedSig = sched.expectedSignature?.(task, runtimeOpts);
339
336
  if (installedSig !== undefined && expectedSig !== undefined && installedSig === expectedSig) {
340
337
  unchanged.push(id);
341
338
  }
342
339
  else {
343
- await sched.install(task);
340
+ await sched.install(task, runtimeOpts);
344
341
  updated.push(id);
345
342
  }
346
343
  }
@@ -351,39 +348,76 @@ export async function akmTasksSync(deps = {}) {
351
348
  removed.push(installedId);
352
349
  }
353
350
  }
354
- return { installed, updated, removed, unchanged, skipped, backend: sched.name };
351
+ return {
352
+ installed,
353
+ updated,
354
+ removed,
355
+ unchanged,
356
+ skipped,
357
+ backend: sched.name,
358
+ ...(warnings.length > 0 ? { warnings } : {}),
359
+ };
355
360
  }
356
- export async function akmTasksDoctor() {
361
+ export async function akmTasksDoctor(deps = {}) {
357
362
  const warnings = [];
358
- let invocation = { argv: [], via: "unresolved" };
363
+ let invocation = {
364
+ argv: [],
365
+ via: "unresolved",
366
+ };
359
367
  try {
360
- const r = resolveAkmInvocation();
361
- invocation = { argv: r.argv, via: r.via };
368
+ const r = (deps.resolveInvocation ?? resolveAkmInvocation)();
369
+ invocation = { argv: r.argv, via: r.via, kind: r.kind, eligible: r.eligible };
362
370
  }
363
371
  catch (err) {
364
372
  warnings.push(err instanceof Error ? err.message : String(err));
365
373
  }
366
- try {
367
- const stashDir = resolveStashDir();
368
- const typeRoot = path.join(stashDir, "tasks");
369
- if (fs.existsSync(typeRoot))
370
- warnLegacyMdTaskFiles(typeRoot);
374
+ const skipNativeInspection = process.env.BUN_TEST === "1" && !deps.backend;
375
+ const sched = deps.backend ?? (skipNativeInspection ? undefined : selectBackend());
376
+ const backend = sched?.name ?? backendNameForPlatform();
377
+ let installed = [];
378
+ if (skipNativeInspection) {
379
+ warnings.push("Native scheduler inspection is skipped inside the bun test harness.");
371
380
  }
372
- catch {
373
- // doctor must never fail on stash-resolution; the warning is best-effort
381
+ else {
382
+ try {
383
+ installed = await sched.list();
384
+ }
385
+ catch (error) {
386
+ warnings.push(`Unable to inspect installed ${backend} definitions: ${error instanceof Error ? error.message : String(error)}`);
387
+ }
374
388
  }
375
- const backend = backendNameForPlatform();
389
+ const bindings = groupInstalledBindings(installed, invocation);
376
390
  const config = loadConfig();
377
- // v2: prefer profiles.agent / defaults.agent; fall back to legacy agent.default
378
- const defaultProfile = config.defaults?.agent;
379
- const profiles = config.profiles?.agent ? Object.keys(config.profiles.agent) : listAgentProfileNames(config);
391
+ const defaultEngine = config.defaults?.engine;
392
+ const engines = Object.keys(config.engines ?? {});
380
393
  // §6.1: surface the effective triage settings for the default improve
381
- // profile. The struct is a fixed shape, so this is a deliberate addition.
382
- const improveProfileName = typeof config.defaults?.improve === "string" ? config.defaults.improve : "default";
383
- const triage = resolveImproveProfile(config.defaults?.improve, config).processes?.triage;
394
+ // strategy. The struct is a fixed shape, so this is a deliberate addition.
395
+ const improveStrategyName = typeof config.defaults?.improveStrategy === "string" ? config.defaults.improveStrategy : "default";
396
+ // D8 — report the EFFECTIVE strategy, not the raw one. Resolving the raw
397
+ // strategy here would report `applyMode: "promote"` for a promote strategy
398
+ // under a review-first config, while the run actually uses "queue" — a doctor
399
+ // command lying about the thing it exists to diagnose.
400
+ const rawStrategy = resolveImproveStrategy(config.defaults?.improveStrategy, config).config;
401
+ const { config: effectiveStrategy, gated } = applyAutonomyGate(rawStrategy, config);
402
+ const autonomyEnabled = isImproveAutonomyEnabled(config);
403
+ // Memory cleanup has no strategy flag to downgrade, so add that direct lane
404
+ // to the strategy-derived gate report.
405
+ const allGated = autonomyEnabled ? [] : [...gated, ...describeGatedLanes(configuredDirectAutonomyLanes())];
406
+ const improveAutonomy = {
407
+ enabled: autonomyEnabled,
408
+ configKey: IMPROVE_AUTONOMY_CONFIG_KEY,
409
+ gatedLanes: allGated.map((entry) => ({ lane: entry.lane, reason: entry.reason })),
410
+ };
411
+ // Q-05 — report the workflow engine gate's state alongside the autonomy
412
+ // gate: both are `experimental.*` opt-ins doctor exists to surface.
413
+ const workflowEngine = {
414
+ enabled: isWorkflowEngineEnabled(config),
415
+ configKey: WORKFLOW_ENGINE_CONFIG_KEY,
416
+ };
417
+ const triage = effectiveStrategy.processes?.triage;
384
418
  const improveTriage = triage
385
419
  ? {
386
- defaultProfile: improveProfileName,
420
+ defaultStrategy: improveStrategyName,
387
421
  enabled: triage.enabled === true,
388
422
  applyMode: triage.applyMode ?? "queue",
389
423
  policy: triage.policy ?? "personal-stash",
@@ -392,36 +426,172 @@ export async function akmTasksDoctor() {
392
426
  return {
393
427
  backend,
394
428
  akm: invocation,
429
+ caller: invocation,
430
+ bindings,
431
+ ...(bindings.some((binding) => !binding.status.includes("ok"))
432
+ ? { remediation: "akm task sync --rebind" }
433
+ : {}),
395
434
  logDir: getTaskLogDir(),
396
435
  historyDir: getTaskHistoryDir(),
397
- agent: { defaultProfile, available: profiles },
436
+ engine: { defaultEngine, available: engines },
398
437
  scheduleSubset: SCHEDULE_SUPPORTED_SUBSET_HINT,
399
438
  warnings,
439
+ improveAutonomy,
440
+ workflowEngine,
400
441
  ...(improveTriage ? { improveTriage } : {}),
401
442
  };
402
443
  }
403
444
  // ── helpers ─────────────────────────────────────────────────────────────────
404
- const VALID_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
405
- function normaliseTaskId(raw) {
406
- // Accept both .yml and .md suffixes from users so muscle memory from the
407
- // pre-0.8.0 markdown task format doesn't produce a confusing "task not found".
408
- const id = raw.trim().replace(/\.(yml|md)$/, "");
409
- if (!id) {
410
- throw new UsageError("Task id must be non-empty.", "MISSING_REQUIRED_ARGUMENT");
411
- }
412
- if (!VALID_ID_RE.test(id)) {
413
- throw new UsageError(`Task id "${id}" is invalid. Use letters, digits, dots, underscores, and dashes only.`, "INVALID_FLAG_VALUE");
414
- }
415
- return id;
445
+ function schedulerInstallOptions(base, installed, deps, explicitRebind, operation, warnings = []) {
446
+ if (installed && !explicitRebind) {
447
+ return {
448
+ ...base,
449
+ binding: installed.binding,
450
+ contextPath: installed.contextPath,
451
+ };
452
+ }
453
+ // Injected backends can own their default runtime unless a resolver is supplied.
454
+ if (deps.backend && !deps.schedulerRuntime)
455
+ return base;
456
+ const runtime = deps.schedulerRuntime?.() ?? prepareSchedulerRuntime(explicitRebind, operation);
457
+ // --rebind bypasses the eligibility refusal in prepareSchedulerRuntime; warn once
458
+ // per sync run rather than silently writing a mutable/unproven binary into cron.
459
+ if (explicitRebind && runtime.eligible === false && warnings.length === 0) {
460
+ warnings.push(`--rebind bound scheduled tasks to an ineligible ${runtime.kind ?? "unknown"} invocation (${runtime.binding.join(" ")}); scheduled runs will invoke a mutable, unproven binary. Install akm via \`npm install --global akm-cli\` or a standalone release, then re-run \`akm task sync --rebind\`.`);
461
+ }
462
+ return { ...base, binding: runtime.binding, contextPath: runtime.contextPath };
463
+ }
464
+ export function prepareSchedulerRuntime(explicitRebind, operation, deps = {}) {
465
+ const invocation = (deps.resolveInvocation ?? resolveAkmInvocation)();
466
+ if (!invocation.eligible && !explicitRebind) {
467
+ throw new UsageError(`Refusing to ${operation} from an ineligible ${invocation.kind ?? "unknown"} invocation (${invocation.argv.join(" ")}).`, "INVALID_FLAG_VALUE", "npm-global ownership could not be verified. Run `npm install --global akm-cli` and use that launcher, use a standalone installation, or explicitly repeat the operation with --rebind.");
468
+ }
469
+ const contextPath = (deps.writeDescriptor ?? writeSchedulerContextDescriptor)(schedulerContextDescriptor());
470
+ return { binding: invocation.argv, contextPath, eligible: invocation.eligible, kind: invocation.kind };
471
+ }
472
+ function groupInstalledBindings(entries, invocation) {
473
+ const groups = new Map();
474
+ for (const entry of entries) {
475
+ const argv = entry.binding;
476
+ const status = inspectInstalledBinding(entry, invocation);
477
+ const key = JSON.stringify([argv, entry.contextPath, status]);
478
+ const existing = groups.get(key);
479
+ if (existing) {
480
+ existing.taskIds.push(entry.id);
481
+ continue;
482
+ }
483
+ groups.set(key, {
484
+ argv,
485
+ contextPath: entry.contextPath,
486
+ taskIds: [entry.id],
487
+ status,
488
+ });
489
+ }
490
+ return [...groups.values()].map((group) => ({ ...group, taskIds: group.taskIds.sort() }));
491
+ }
492
+ function inspectInstalledBinding(entry, invocation) {
493
+ const status = [];
494
+ const binding = entry.binding;
495
+ if (!(invocation.eligible === true && sameArgv(binding, invocation.argv)) &&
496
+ binding.some((part) => /(?:^|[\\/])src[\\/]cli\.ts$|(?:^|[\\/])dist[\\/](?:cli\.js|cli-node\.mjs)$/i.test(part) ||
497
+ (path.isAbsolute(part) && hasGitAncestor(part)))) {
498
+ status.push("checkout");
499
+ }
500
+ if (binding.some((part) => part === "akm" || part === "bun" || part === "node"))
501
+ status.push("path-selected");
502
+ try {
503
+ validateSchedulerContextDescriptor(entry.contextPath);
504
+ }
505
+ catch {
506
+ status.push("invalid-context");
507
+ }
508
+ const absolutePaths = [...binding.filter((part) => path.isAbsolute(part)), entry.contextPath];
509
+ if (absolutePaths.some((part) => !fs.existsSync(part)))
510
+ status.push("missing-path");
511
+ if (status.length === 0)
512
+ status.push("ok");
513
+ return status;
514
+ }
515
+ function sameArgv(left, right) {
516
+ return left.length === right.length && left.every((value, index) => value === right[index]);
517
+ }
518
+ function hasGitAncestor(file) {
519
+ let current;
520
+ try {
521
+ current = path.dirname(fs.realpathSync(file));
522
+ }
523
+ catch {
524
+ return false;
525
+ }
526
+ for (;;) {
527
+ if (fs.existsSync(path.join(current, ".git")))
528
+ return true;
529
+ const parent = path.dirname(current);
530
+ if (parent === current)
531
+ return false;
532
+ current = parent;
533
+ }
416
534
  }
417
535
  function taskAssetRef(id) {
418
536
  return { type: "task", name: id };
419
537
  }
420
- function resolveTaskWriteTarget() {
421
- return resolveWriteTarget(loadConfig());
538
+ async function restoreTaskSourceBytes(writeAsset, source, config, ref, filePath, yaml) {
539
+ await writeAsset(source, config, ref, yaml);
540
+ // The normal write path adds a trailing newline; rollback restores the raw snapshot exactly.
541
+ fs.writeFileSync(filePath, yaml, "utf8");
542
+ }
543
+ /**
544
+ * Resolve the bundle a mutating/run task command targets. Returns the resolved
545
+ * write/read target, its stash path, and the `--bundle <bundle>` token to embed
546
+ * in scheduled invocations. The primary bundle uses the target-less form.
547
+ */
548
+ function resolveTaskBundle(target, opts) {
549
+ const selected = resolveWriteTarget(loadConfig(), target, { requireWritable: opts.requireWritable });
550
+ const resolved = opts.requireWritable ? prepareWriteTargetForMutation(selected) : selected;
551
+ const stashDir = resolved.source.path;
552
+ const installTarget = isPrimaryStashPath(stashDir) ? undefined : (resolved.selector ?? resolved.source.name);
553
+ return { resolved, stashDir, installTarget };
554
+ }
555
+ /**
556
+ * Resolve the tasks/ directory a read/inspect command operates on. No
557
+ * `--bundle` uses the primary stash; `--bundle X` resolves bundle X read-only.
558
+ */
559
+ function resolveTaskInspectDir(target) {
560
+ if (target === undefined)
561
+ return resolveStashDir();
562
+ return resolveWriteTarget(loadConfig(), target, { requireWritable: false }).source.path;
563
+ }
564
+ /** True when `candidate` resolves to the same directory as the primary stash. */
565
+ function isPrimaryStashPath(candidate) {
566
+ let primary;
567
+ try {
568
+ primary = path.resolve(resolveStashDir());
569
+ }
570
+ catch {
571
+ return false;
572
+ }
573
+ return path.resolve(candidate) === primary;
574
+ }
575
+ /** Two bundle attributions match when both are the primary (undefined) or equal names. */
576
+ function sameBundle(a, b) {
577
+ return (a ?? undefined) === (b ?? undefined);
578
+ }
579
+ function foreignScheduleMessage(id, existingTarget) {
580
+ const where = existingTarget === undefined ? "the default bundle" : `bundle "${existingTarget}"`;
581
+ return `Task id "${id}" is already scheduled from ${where}; rename the task or disable the existing one first.`;
582
+ }
583
+ /**
584
+ * Refuse to schedule an id already installed from a DIFFERENT bundle. Scheduler
585
+ * ids are the bare task id (never namespaced), so a single id can be active from
586
+ * only one bundle at a time — a collision is a hard error, not an auto-rename.
587
+ */
588
+ function assertNoForeignSchedule(entries, id, installTarget) {
589
+ const foreign = entries.find((entry) => entry.id === id && !sameBundle(entry.target, installTarget));
590
+ if (foreign)
591
+ throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
422
592
  }
423
593
  function renderTaskYaml(input) {
424
- const obj = { schedule: input.schedule };
594
+ const obj = { version: 2, schedule: input.schedule, enabled: input.enabled };
425
595
  if (input.workflow) {
426
596
  obj.workflow = input.workflow;
427
597
  if (input.params) {
@@ -430,16 +600,21 @@ function renderTaskYaml(input) {
430
600
  }
431
601
  else if (input.prompt) {
432
602
  obj.prompt = input.prompt;
433
- if (input.profile)
434
- obj.profile = input.profile;
603
+ if (input.engine)
604
+ obj.engine = input.engine;
605
+ if (input.model)
606
+ obj.model = input.model;
607
+ if (input.timeoutMs !== undefined)
608
+ obj.timeoutMs = input.timeoutMs;
435
609
  }
436
610
  else if (input.command !== undefined) {
437
611
  // Emit a string when given a string, an array when given an array. The
438
612
  // parser accepts both forms; preserving the caller's shape keeps the YAML
439
613
  // ergonomic for humans editing the file later.
440
614
  obj.command = input.command;
615
+ if (input.timeoutMs !== undefined)
616
+ obj.timeoutMs = input.timeoutMs;
441
617
  }
442
- obj.enabled = input.enabled;
443
618
  if (input.name)
444
619
  obj.name = input.name;
445
620
  if (input.description)
@@ -487,17 +662,25 @@ export function setEnabledInYaml(yaml, enabled) {
487
662
  // Re-exported so tests can verify the validator path directly.
488
663
  // Re-export error classes consumed by callers that want to instanceof-check.
489
664
  // Re-export this so the CLI can decide what process exit code to use after
490
- // `akm tasks run` completes.
665
+ // `akm task run` completes.
491
666
  export { ConfigError, exitCodeForStatus, NotFoundError, parseTaskDocument, UsageError };
492
- // Helper: ensure the asset-spec resolver agrees with our id rules. If the
493
- // user passes a ref, we accept the bare name part too.
667
+ // Accept a bare task id or the canonical `[bundle//]tasks/<id>` ref.
494
668
  export function parseTaskRef(input) {
495
- if (input.includes(":")) {
496
- const [typePart, ...rest] = input.split(":");
497
- if (typePart !== "task" || rest.length === 0) {
498
- throw new UsageError(`Expected a task id or task:<id> ref, got "${input}".`, "INVALID_FLAG_VALUE");
669
+ const trimmed = input.trim();
670
+ // Canonical conceptId form: `[bundle//]tasks/<id>`. A `/` unambiguously marks
671
+ // it — a bare task id can never contain `/` (`validateTaskId` forbids it) — so
672
+ // route it through the shared parser, which strips any bundle prefix and maps
673
+ // the `tasks/` stash-subdir back to the `task` type in one place.
674
+ if (trimmed.includes("/")) {
675
+ try {
676
+ const parsed = parseRefInput(trimmed);
677
+ if (parsed.type === "task")
678
+ return { id: normaliseTaskId(parsed.name) };
679
+ }
680
+ catch {
681
+ // fall through to the shared error below
499
682
  }
500
- return { id: normaliseTaskId(rest.join(":")) };
683
+ throw new UsageError(`Expected a task id or tasks/<id> ref, got "${input}".`, "INVALID_FLAG_VALUE");
501
684
  }
502
- return { id: normaliseTaskId(input) };
685
+ return { id: normaliseTaskId(trimmed) };
503
686
  }