akm-cli 0.9.0-rc.1 → 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 (587) hide show
  1. package/CHANGELOG.md +1190 -52
  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/stash-skeleton/README.md +38 -10
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  38. package/dist/assets/tasks/core/extract.yml +3 -2
  39. package/dist/assets/tasks/core/improve.yml +2 -1
  40. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  41. package/dist/assets/tasks/core/sync.yml +1 -0
  42. package/dist/assets/tasks/core/version-check.yml +2 -1
  43. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  44. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  45. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  46. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  48. package/dist/assets/templates/html/health.html +5 -4
  49. package/dist/assets/workflows/workflow-template.md +31 -15
  50. package/dist/cli/invocation.js +279 -0
  51. package/dist/cli/parse-args.js +5 -90
  52. package/dist/cli/retired-commands.js +78 -0
  53. package/dist/cli/shared.js +158 -48
  54. package/dist/cli-node.mjs +2 -1
  55. package/dist/cli.js +747 -293
  56. package/dist/commands/agent/agent-dispatch.js +19 -18
  57. package/dist/commands/agent/agent-support.js +0 -24
  58. package/dist/commands/agent/contribute-cli.js +43 -97
  59. package/dist/commands/completions.js +80 -23
  60. package/dist/commands/config-cli.js +44 -281
  61. package/dist/commands/env/env-binding.js +13 -9
  62. package/dist/commands/env/env-cli.js +76 -159
  63. package/dist/commands/env/env.js +12 -163
  64. package/dist/commands/env/marker-path.js +6 -0
  65. package/dist/commands/env/secret-cli.js +45 -61
  66. package/dist/commands/env/secret.js +32 -62
  67. package/dist/commands/feedback-cli.js +179 -85
  68. package/dist/commands/health/accept-rate.js +58 -0
  69. package/dist/commands/health/advisories.js +7 -8
  70. package/dist/commands/health/checks.js +279 -94
  71. package/dist/commands/health/html-report.js +197 -578
  72. package/dist/commands/health/improve-metrics.js +277 -246
  73. package/dist/commands/health/llm-usage.js +19 -19
  74. package/dist/commands/health/md-report.js +16 -7
  75. package/dist/commands/health/metrics.js +67 -32
  76. package/dist/commands/health/renderers.js +47 -0
  77. package/dist/commands/health/report-view-model.js +508 -0
  78. package/dist/commands/health/stash-exposure.js +1 -1
  79. package/dist/commands/health/surfaces.js +16 -56
  80. package/dist/commands/health/task-runs.js +3 -67
  81. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  82. package/dist/commands/health/types-improve.js +29 -0
  83. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  84. package/dist/commands/health/types-result.js +7 -0
  85. package/dist/commands/health/types-runs.js +4 -0
  86. package/dist/commands/health/types-session-log.js +4 -0
  87. package/dist/commands/health/types-windows.js +4 -0
  88. package/dist/commands/health/types.js +26 -21
  89. package/dist/commands/health/windows.js +2 -3
  90. package/dist/commands/health.js +296 -167
  91. package/dist/commands/improve/anti-collapse.js +5 -5
  92. package/dist/commands/improve/autonomy-gate.js +68 -0
  93. package/dist/commands/improve/collapse-detector.js +65 -52
  94. package/dist/commands/improve/consolidate/chunking.js +9 -7
  95. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  96. package/dist/commands/improve/consolidate/merge.js +4 -0
  97. package/dist/commands/improve/consolidate.js +454 -1354
  98. package/dist/commands/improve/content-hash.js +39 -0
  99. package/dist/commands/improve/distill/content-repair.js +4 -10
  100. package/dist/commands/improve/distill/promote-memory.js +89 -64
  101. package/dist/commands/improve/distill/quality-gate.js +118 -42
  102. package/dist/commands/improve/distill-guards.js +1 -1
  103. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  104. package/dist/commands/improve/distill.js +607 -363
  105. package/dist/commands/improve/eligibility.js +165 -79
  106. package/dist/commands/improve/extract-cli.js +35 -126
  107. package/dist/commands/improve/extract-prompt.js +6 -35
  108. package/dist/commands/improve/extract.js +640 -391
  109. package/dist/commands/improve/feedback-valence.js +2 -12
  110. package/dist/commands/improve/improve-cli.js +134 -135
  111. package/dist/commands/improve/improve-result-file.js +30 -50
  112. package/dist/commands/improve/improve-run-types.js +4 -0
  113. package/dist/commands/improve/improve-strategies.js +135 -0
  114. package/dist/commands/improve/improve.js +904 -701
  115. package/dist/commands/improve/locks.js +64 -111
  116. package/dist/commands/improve/loop-stages.js +1110 -923
  117. package/dist/commands/improve/memory/derived-ref.js +124 -0
  118. package/dist/commands/improve/memory/memory-belief.js +79 -7
  119. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  120. package/dist/commands/improve/memory/memory-improve.js +25 -37
  121. package/dist/commands/improve/outcome-loop.js +25 -88
  122. package/dist/commands/improve/preparation.js +1034 -813
  123. package/dist/commands/improve/proactive-maintenance.js +34 -9
  124. package/dist/commands/improve/proposal-envelope.js +31 -0
  125. package/dist/commands/improve/reflect.js +983 -794
  126. package/dist/commands/improve/run-context.js +119 -0
  127. package/dist/commands/improve/salience.js +24 -127
  128. package/dist/commands/improve/session-asset.js +7 -3
  129. package/dist/commands/improve/shared.js +14 -34
  130. package/dist/commands/improve/source-identity.js +28 -0
  131. package/dist/commands/improve/triage.js +20 -17
  132. package/dist/commands/lint/base-linter.js +340 -313
  133. package/dist/commands/lint/env-key-rules.js +31 -47
  134. package/dist/commands/lint/index.js +185 -30
  135. package/dist/commands/{events.js → log.js} +28 -38
  136. package/dist/commands/migrate-cli.js +54 -0
  137. package/dist/commands/migration-tool.js +55 -0
  138. package/dist/commands/observability-cli.js +70 -208
  139. package/dist/commands/proposal/diff-format.js +50 -0
  140. package/dist/commands/proposal/drain-policies.js +0 -6
  141. package/dist/commands/proposal/drain.js +91 -40
  142. package/dist/commands/proposal/proposal-cli.js +134 -132
  143. package/dist/commands/proposal/proposal-types.js +56 -0
  144. package/dist/commands/proposal/proposal.js +83 -65
  145. package/dist/commands/proposal/propose-cli.js +88 -0
  146. package/dist/commands/proposal/propose.js +105 -88
  147. package/dist/commands/proposal/repository.js +1303 -278
  148. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  149. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  150. package/dist/commands/proposal/validators/proposals.js +6 -8
  151. package/dist/commands/read/curate.js +78 -73
  152. package/dist/commands/read/knowledge.js +510 -13
  153. package/dist/commands/read/registry-search.js +2 -2
  154. package/dist/commands/read/remember-cli.js +84 -15
  155. package/dist/commands/read/search-cli.js +203 -96
  156. package/dist/commands/read/search.js +126 -94
  157. package/dist/commands/read/show.js +226 -250
  158. package/dist/commands/registry-cli.js +34 -60
  159. package/dist/commands/remember.js +18 -57
  160. package/dist/commands/sources/add-cli.js +104 -49
  161. package/dist/commands/sources/bundle-cli.js +166 -0
  162. package/dist/commands/sources/bundle-config-ops.js +63 -0
  163. package/dist/commands/sources/info.js +27 -15
  164. package/dist/commands/sources/init.js +30 -40
  165. package/dist/commands/sources/installed-stashes.js +469 -172
  166. package/dist/commands/sources/schema-repair.js +10 -9
  167. package/dist/commands/sources/self-update.js +182 -121
  168. package/dist/commands/sources/source-add.js +169 -178
  169. package/dist/commands/sources/source-clone.js +144 -41
  170. package/dist/commands/sources/source-manage.js +94 -59
  171. package/dist/commands/sources/sources-cli.js +64 -205
  172. package/dist/commands/sources/stash-cli.js +91 -54
  173. package/dist/commands/sources/stash-skeleton.js +1 -1
  174. package/dist/commands/tasks/tasks-cli.js +106 -104
  175. package/dist/commands/tasks/tasks.js +445 -262
  176. package/dist/commands/workflow-cli.js +75 -228
  177. package/dist/core/action-contributors.js +1 -1
  178. package/dist/core/activation-policy.js +49 -0
  179. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  180. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  181. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  182. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  183. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  184. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  185. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  186. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  187. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  188. package/dist/core/adapter/adapters/index.js +80 -0
  189. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  190. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  191. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  192. package/dist/core/adapter/adapters/shared.js +286 -0
  193. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  194. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  195. package/dist/core/adapter/bundle-adapter.js +4 -0
  196. package/dist/core/adapter/detect-adapter.js +17 -0
  197. package/dist/core/adapter/recognize-match.js +44 -0
  198. package/dist/core/adapter/registry.js +56 -0
  199. package/dist/core/adapter/types.js +4 -0
  200. package/dist/core/asset/akm-markdown.js +30 -0
  201. package/dist/core/asset/asset-placement.js +243 -0
  202. package/dist/core/asset/asset-ref.js +110 -79
  203. package/dist/core/asset/asset-serialize.js +20 -0
  204. package/dist/core/asset/frontmatter.js +28 -12
  205. package/dist/core/asset/markdown.js +40 -51
  206. package/dist/core/asset/resolve-ref.js +274 -0
  207. package/dist/core/asset/stash-meta.js +2 -2
  208. package/dist/core/bundle-id.js +51 -0
  209. package/dist/core/common.js +281 -86
  210. package/dist/core/config/config-io.js +42 -128
  211. package/dist/core/config/config-schema.js +233 -855
  212. package/dist/core/config/config-sources.js +162 -39
  213. package/dist/core/config/config-types.js +16 -11
  214. package/dist/core/config/config-version.js +29 -0
  215. package/dist/core/config/config-walker.js +126 -37
  216. package/dist/core/config/config.js +154 -331
  217. package/dist/core/config/deep-merge.js +41 -0
  218. package/dist/core/config/engine-semantics.js +28 -0
  219. package/dist/core/config/experimental.js +21 -0
  220. package/dist/core/config/schema/embedding.js +38 -0
  221. package/dist/core/config/schema/engines.js +116 -0
  222. package/dist/core/config/schema/experimental.js +47 -0
  223. package/dist/core/config/schema/feedback.js +31 -0
  224. package/dist/core/config/schema/improve-processes.js +389 -0
  225. package/dist/core/config/schema/improve.js +94 -0
  226. package/dist/core/config/schema/index-config.js +176 -0
  227. package/dist/core/config/schema/output.js +18 -0
  228. package/dist/core/config/schema/primitives.js +94 -0
  229. package/dist/core/config/schema/search.js +30 -0
  230. package/dist/core/config/schema/setup.js +18 -0
  231. package/dist/core/config/schema/sources-bundles.js +169 -0
  232. package/dist/core/config/schema/workflow.js +29 -0
  233. package/dist/core/env-secret-ref.js +155 -20
  234. package/dist/core/errors.js +17 -15
  235. package/dist/core/events-types.js +4 -0
  236. package/dist/core/events.js +46 -128
  237. package/dist/core/extra-params.js +62 -0
  238. package/dist/core/file-change.js +17 -0
  239. package/dist/core/file-lock.js +202 -57
  240. package/dist/core/fs-txn.js +392 -0
  241. package/dist/core/git-message.js +59 -0
  242. package/dist/core/improve-result.js +167 -0
  243. package/dist/core/lesson-lint.js +1 -17
  244. package/dist/core/logs-db.js +1 -1
  245. package/dist/core/maintenance-barrier.js +135 -0
  246. package/dist/core/migration-operation.js +44 -0
  247. package/dist/core/mutation-target.js +78 -0
  248. package/dist/core/paths.js +22 -25
  249. package/dist/core/platform.js +10 -0
  250. package/dist/core/recognition-util.js +128 -0
  251. package/dist/core/redaction.js +392 -0
  252. package/dist/core/standards/resolve-standards-context.js +36 -65
  253. package/dist/core/standards/resolve-stash-standards.js +2 -2
  254. package/dist/core/standards/resolve-type-conventions.js +5 -5
  255. package/dist/core/state/migrations.js +242 -11
  256. package/dist/core/state-db.js +98 -10
  257. package/dist/core/structured.js +1 -1
  258. package/dist/core/subprocess.js +303 -0
  259. package/dist/core/text-truncation.js +9 -5
  260. package/dist/core/time.js +20 -0
  261. package/dist/core/type-presentation.js +130 -0
  262. package/dist/core/warn.js +0 -3
  263. package/dist/core/write-source.js +834 -118
  264. package/dist/indexer/bundle-identity-guard.js +92 -0
  265. package/dist/indexer/db/graph-db.js +1 -25
  266. package/dist/indexer/db/llm-cache.js +1 -1
  267. package/dist/indexer/ensure-index.js +30 -9
  268. package/dist/indexer/graph/graph-boost.js +9 -30
  269. package/dist/indexer/graph/graph-extraction.js +41 -27
  270. package/dist/indexer/graph/graph-types.js +4 -0
  271. package/dist/indexer/index-writer-lock.js +93 -49
  272. package/dist/indexer/index-written-assets.js +100 -53
  273. package/dist/indexer/indexer.js +746 -329
  274. package/dist/indexer/init.js +18 -25
  275. package/dist/indexer/installations.js +142 -0
  276. package/dist/indexer/passes/dir-staleness.js +18 -10
  277. package/dist/indexer/passes/memory-inference.js +25 -15
  278. package/dist/indexer/passes/metadata.js +412 -243
  279. package/dist/indexer/scan/doc-to-entry.js +160 -0
  280. package/dist/indexer/scan/drain-dir.js +134 -0
  281. package/dist/indexer/search/db-search.js +292 -108
  282. package/dist/indexer/search/fts-query.js +64 -0
  283. package/dist/indexer/search/ranking-contributors.js +145 -25
  284. package/dist/indexer/search/ranking-types.js +4 -0
  285. package/dist/indexer/search/ranking.js +28 -71
  286. package/dist/indexer/search/search-attribution.js +67 -0
  287. package/dist/indexer/search/search-fields.js +18 -3
  288. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  289. package/dist/indexer/search/search-source.js +157 -111
  290. package/dist/indexer/search/semantic-status.js +4 -1
  291. package/dist/indexer/usage/usage-events.js +10 -30
  292. package/dist/indexer/walk/file-context.js +3 -45
  293. package/dist/indexer/walk/matchers.js +42 -73
  294. package/dist/indexer/walk/path-resolver.js +11 -5
  295. package/dist/indexer/walk/walker.js +42 -14
  296. package/dist/integrations/agent/builder-shared.js +7 -0
  297. package/dist/integrations/agent/builders.js +5 -58
  298. package/dist/integrations/agent/config.js +3 -143
  299. package/dist/integrations/agent/detect.js +17 -2
  300. package/dist/integrations/agent/engine-resolution.js +231 -0
  301. package/dist/integrations/agent/index.js +1 -2
  302. package/dist/integrations/agent/model-aliases.js +8 -3
  303. package/dist/integrations/agent/profiles.js +6 -99
  304. package/dist/integrations/agent/prompts.js +46 -18
  305. package/dist/integrations/agent/runner-dispatch.js +78 -13
  306. package/dist/integrations/agent/runner.js +76 -208
  307. package/dist/integrations/agent/spawn.js +48 -279
  308. package/dist/integrations/harnesses/aider/agent-builder.js +9 -8
  309. package/dist/integrations/harnesses/aider/index.js +2 -12
  310. package/dist/integrations/harnesses/amazonq/agent-builder.js +10 -16
  311. package/dist/integrations/harnesses/amazonq/index.js +3 -17
  312. package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
  313. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  314. package/dist/integrations/harnesses/claude/index.js +1 -14
  315. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  316. package/dist/integrations/harnesses/codex/agent-builder.js +8 -7
  317. package/dist/integrations/harnesses/codex/index.js +2 -13
  318. package/dist/integrations/harnesses/copilot/agent-builder.js +9 -9
  319. package/dist/integrations/harnesses/copilot/index.js +1 -13
  320. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -9
  321. package/dist/integrations/harnesses/gemini/index.js +1 -13
  322. package/dist/integrations/harnesses/ids.js +24 -0
  323. package/dist/integrations/harnesses/index.js +31 -33
  324. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  325. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  326. package/dist/integrations/harnesses/opencode/index.js +1 -18
  327. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  328. package/dist/integrations/harnesses/opencode-sdk/harness.js +3 -17
  329. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
  330. package/dist/integrations/harnesses/openhands/agent-builder.js +12 -10
  331. package/dist/integrations/harnesses/openhands/index.js +2 -12
  332. package/dist/integrations/harnesses/pi/agent-builder.js +11 -18
  333. package/dist/integrations/harnesses/pi/index.js +3 -16
  334. package/dist/integrations/harnesses/shared.js +17 -0
  335. package/dist/integrations/harnesses/types.js +38 -34
  336. package/dist/integrations/lockfile.js +211 -24
  337. package/dist/integrations/session-logs/index.js +24 -40
  338. package/dist/integrations/session-logs/provider-base.js +113 -0
  339. package/dist/llm/client.js +182 -110
  340. package/dist/llm/embedders/deterministic.js +2 -2
  341. package/dist/llm/embedders/remote.js +21 -9
  342. package/dist/llm/feature-gate.js +17 -57
  343. package/dist/llm/graph-extract.js +12 -13
  344. package/dist/llm/index-passes.js +8 -42
  345. package/dist/llm/memory-infer.js +144 -1
  346. package/dist/llm/metadata-enhance.js +45 -30
  347. package/dist/llm/structured-call.js +16 -8
  348. package/dist/llm/usage-persist.js +30 -5
  349. package/dist/llm/usage-telemetry.js +59 -6
  350. package/dist/output/cli-hints.js +1 -2
  351. package/dist/output/command-registry.js +27 -0
  352. package/dist/output/context.js +22 -7
  353. package/dist/output/format-exempt.js +80 -0
  354. package/dist/output/generic-render.js +251 -0
  355. package/dist/output/html-render.js +11 -16
  356. package/dist/output/render-registry.js +57 -0
  357. package/dist/output/renderers.js +15 -281
  358. package/dist/output/shapes/curate.js +10 -1
  359. package/dist/output/shapes/events.js +12 -7
  360. package/dist/output/shapes/helpers.js +58 -84
  361. package/dist/output/shapes/passthrough.js +8 -40
  362. package/dist/output/shapes/proposal/producer.js +15 -7
  363. package/dist/output/shapes/registry.js +12 -6
  364. package/dist/output/shapes.js +0 -9
  365. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  366. package/dist/output/text/bundle-show.js +7 -0
  367. package/dist/output/text/command-format.js +562 -0
  368. package/dist/output/text/env.js +1 -3
  369. package/dist/output/text/events.js +8 -7
  370. package/dist/output/text/helpers.js +15 -1375
  371. package/dist/output/text/proposal/producer.js +4 -2
  372. package/dist/output/text/proposal-format.js +202 -0
  373. package/dist/output/text/registry-commands.js +1 -2
  374. package/dist/output/text/registry.js +12 -6
  375. package/dist/output/text/show-directives.js +117 -0
  376. package/dist/output/text/show-format.js +103 -0
  377. package/dist/output/text/sync.js +5 -0
  378. package/dist/output/text/workflow-format.js +332 -0
  379. package/dist/output/text/workflow.js +1 -2
  380. package/dist/output/text.js +10 -19
  381. package/dist/registry/factory.js +4 -6
  382. package/dist/registry/origin-resolve.js +16 -27
  383. package/dist/registry/providers/skills-sh.js +3 -3
  384. package/dist/registry/providers/static-index.js +15 -25
  385. package/dist/registry/resolve.js +43 -94
  386. package/dist/registry/semver.js +43 -0
  387. package/dist/runtime.js +81 -12
  388. package/dist/scripts/akm-migrate.js +35529 -0
  389. package/dist/setup/detect.js +5 -7
  390. package/dist/setup/detected-engines.js +136 -0
  391. package/dist/setup/engine-config.js +100 -0
  392. package/dist/setup/registry-stash-loader.js +3 -3
  393. package/dist/setup/semantic-assets.js +12 -9
  394. package/dist/setup/setup.js +444 -208
  395. package/dist/setup/steps/connection-shared.js +120 -0
  396. package/dist/setup/steps/connection.js +108 -305
  397. package/dist/setup/steps/platforms.js +13 -12
  398. package/dist/setup/steps/semantic.js +15 -3
  399. package/dist/setup/steps/sources.js +21 -15
  400. package/dist/setup/steps/stashdir.js +6 -4
  401. package/dist/setup/steps/tasks.js +236 -119
  402. package/dist/setup/steps.js +3 -2
  403. package/dist/sources/freshness.js +39 -0
  404. package/dist/sources/provider-factory.js +11 -17
  405. package/dist/sources/providers/filesystem.js +2 -3
  406. package/dist/sources/providers/git-install.js +278 -34
  407. package/dist/sources/providers/git-provider.js +54 -56
  408. package/dist/sources/providers/git-stash.js +420 -91
  409. package/dist/sources/providers/git.js +2 -2
  410. package/dist/sources/providers/npm.js +16 -19
  411. package/dist/sources/providers/provider-utils.js +47 -22
  412. package/dist/sources/providers/sync-from-ref.js +3 -9
  413. package/dist/sources/providers/website.js +2 -2
  414. package/dist/sources/resolve.js +11 -10
  415. package/dist/sources/snapshot-fetchers/types.js +4 -0
  416. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  417. package/dist/storage/database.js +60 -4
  418. package/dist/storage/engines/sqlite-migrations.js +156 -5
  419. package/dist/storage/locations.js +1 -2
  420. package/dist/storage/repositories/canaries-repository.js +1 -1
  421. package/dist/storage/repositories/events-repository.js +51 -11
  422. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  423. package/dist/storage/repositories/index-connection.js +79 -0
  424. package/dist/storage/repositories/index-db.js +4 -3
  425. package/dist/storage/repositories/index-entries-repository.js +863 -0
  426. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  427. package/dist/storage/repositories/index-entry-types.js +4 -0
  428. package/dist/storage/repositories/index-fts-repository.js +167 -0
  429. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  430. package/dist/storage/repositories/index-meta-repository.js +49 -0
  431. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  432. package/dist/storage/repositories/index-sql.js +12 -0
  433. package/dist/storage/repositories/index-utility-repository.js +356 -0
  434. package/dist/storage/repositories/index-vec-repository.js +250 -0
  435. package/dist/storage/repositories/outcome-repository.js +119 -0
  436. package/dist/storage/repositories/proposals-repository.js +317 -75
  437. package/dist/storage/repositories/registry-cache.js +1 -1
  438. package/dist/storage/repositories/salience-repository.js +172 -0
  439. package/dist/storage/repositories/task-history-repository.js +110 -3
  440. package/dist/storage/repositories/workflow-runs-repository.js +68 -35
  441. package/dist/tasks/backends/cron.js +169 -46
  442. package/dist/tasks/backends/exec-utils.js +76 -3
  443. package/dist/tasks/backends/index.js +6 -9
  444. package/dist/tasks/backends/launchd.js +292 -55
  445. package/dist/tasks/backends/schtasks.js +557 -70
  446. package/dist/tasks/backends/types.js +4 -0
  447. package/dist/tasks/command-executable.js +93 -0
  448. package/dist/tasks/embedded.js +56 -38
  449. package/dist/tasks/parser.js +156 -64
  450. package/dist/tasks/resolve-akm-bin.js +144 -51
  451. package/dist/tasks/runner.js +377 -209
  452. package/dist/tasks/schedule.js +108 -19
  453. package/dist/tasks/scheduler-invocation.js +296 -0
  454. package/dist/tasks/schema.js +1 -1
  455. package/dist/tasks/task-id.js +35 -0
  456. package/dist/tasks/validator.js +30 -16
  457. package/dist/workflows/authoring/authoring.js +96 -148
  458. package/dist/workflows/authoring/scope-key.js +1 -1
  459. package/dist/workflows/cli.js +0 -20
  460. package/dist/workflows/concurrency-policy.js +15 -0
  461. package/dist/workflows/exec/brief.js +25 -59
  462. package/dist/workflows/exec/frozen-judge.js +47 -0
  463. package/dist/workflows/exec/native-executor.js +157 -94
  464. package/dist/workflows/exec/report.js +365 -200
  465. package/dist/workflows/exec/run-workflow.js +46 -40
  466. package/dist/workflows/exec/scheduler.js +12 -41
  467. package/dist/workflows/exec/step-work.js +239 -205
  468. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  469. package/dist/workflows/ir/compile.js +141 -283
  470. package/dist/workflows/ir/freeze.js +233 -0
  471. package/dist/workflows/ir/plan-hash.js +40 -5
  472. package/dist/workflows/ir/schema.js +537 -1
  473. package/dist/workflows/parser.js +878 -306
  474. package/dist/workflows/program/expressions.js +20 -208
  475. package/dist/workflows/program/schema.js +7 -10
  476. package/dist/workflows/renderer.js +99 -121
  477. package/dist/workflows/resource-limits.js +22 -0
  478. package/dist/workflows/runtime/checkin.js +1 -1
  479. package/dist/workflows/runtime/plan-classifier.js +131 -0
  480. package/dist/workflows/runtime/runs.js +200 -113
  481. package/dist/workflows/runtime/unit-checkin.js +1 -1
  482. package/dist/workflows/runtime/unit-phases.js +20 -0
  483. package/dist/workflows/runtime/workflow-asset-loader.js +235 -97
  484. package/dist/workflows/schema.js +1 -11
  485. package/dist/workflows/validate-summary.js +4 -26
  486. package/dist/workflows/validator.js +52 -30
  487. package/docs/README.md +42 -78
  488. package/docs/migration/README.md +8 -0
  489. package/docs/migration/release-notes/0.6.0.md +1 -1
  490. package/docs/migration/release-notes/0.7.0.md +9 -8
  491. package/docs/migration/release-notes/0.9.0.md +158 -14
  492. package/docs/migration/v0.7-to-v0.8.md +46 -47
  493. package/docs/migration/v0.8-to-v0.9.md +844 -0
  494. package/docs/reference/README.md +12 -0
  495. package/docs/reference/data-and-telemetry.md +333 -0
  496. package/package.json +21 -17
  497. package/schemas/akm-asset-envelope.json +93 -0
  498. package/schemas/akm-config.json +4636 -0
  499. package/schemas/akm-task.json +87 -0
  500. package/{dist/schemas → schemas}/akm-workflow.json +127 -82
  501. package/dist/akm-migrate-storage +0 -38
  502. package/dist/assets/help/help-accept.md +0 -12
  503. package/dist/assets/help/help-improve.md +0 -84
  504. package/dist/assets/help/help-proposals.md +0 -17
  505. package/dist/assets/help/help-propose.md +0 -17
  506. package/dist/assets/help/help-reject.md +0 -11
  507. package/dist/assets/profiles/frequent.json +0 -13
  508. package/dist/assets/profiles/recombine-only.json +0 -21
  509. package/dist/assets/profiles/reflect-distill.json +0 -30
  510. package/dist/assets/profiles/synthesize.json +0 -15
  511. package/dist/assets/prompts/procedural-system.md +0 -44
  512. package/dist/assets/prompts/recombine-system.md +0 -40
  513. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  514. package/dist/assets/tasks/core/backup.yml +0 -4
  515. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  516. package/dist/assets/templates/html/default.html +0 -78
  517. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  518. package/dist/assets/wiki/index-template.md +0 -12
  519. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  520. package/dist/assets/wiki/log-template.md +0 -8
  521. package/dist/assets/wiki/schema-template.md +0 -61
  522. package/dist/cli/config-migrate.js +0 -150
  523. package/dist/cli/config-validate.js +0 -39
  524. package/dist/commands/graph/graph-cli.js +0 -124
  525. package/dist/commands/graph/graph.js +0 -487
  526. package/dist/commands/improve/calibration.js +0 -161
  527. package/dist/commands/improve/dedup.js +0 -482
  528. package/dist/commands/improve/extract-watch.js +0 -140
  529. package/dist/commands/improve/hot-probation.js +0 -45
  530. package/dist/commands/improve/improve-auto-accept.js +0 -276
  531. package/dist/commands/improve/improve-profiles.js +0 -168
  532. package/dist/commands/improve/procedural.js +0 -398
  533. package/dist/commands/improve/recombine.js +0 -818
  534. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  535. package/dist/commands/lint/agent-linter.js +0 -44
  536. package/dist/commands/lint/command-linter.js +0 -44
  537. package/dist/commands/lint/default-linter.js +0 -16
  538. package/dist/commands/lint/fact-linter.js +0 -39
  539. package/dist/commands/lint/knowledge-linter.js +0 -16
  540. package/dist/commands/lint/memory-linter.js +0 -61
  541. package/dist/commands/lint/registry.js +0 -41
  542. package/dist/commands/lint/skill-linter.js +0 -45
  543. package/dist/commands/lint/task-linter.js +0 -50
  544. package/dist/commands/lint/workflow-linter.js +0 -81
  545. package/dist/commands/proposal/legacy-import.js +0 -115
  546. package/dist/commands/sources/history.js +0 -196
  547. package/dist/commands/tasks/default-tasks.js +0 -186
  548. package/dist/commands/wiki-cli.js +0 -292
  549. package/dist/core/asset/asset-registry.js +0 -76
  550. package/dist/core/asset/asset-spec.js +0 -316
  551. package/dist/core/config/config-migration.js +0 -602
  552. package/dist/core/deep-merge.js +0 -38
  553. package/dist/core/eval/rank-metrics.js +0 -113
  554. package/dist/core/ripgrep/install.js +0 -163
  555. package/dist/core/ripgrep/resolve.js +0 -81
  556. package/dist/indexer/db/db.js +0 -1414
  557. package/dist/indexer/manifest.js +0 -170
  558. package/dist/indexer/passes/metadata-contributors.js +0 -31
  559. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  560. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -25
  561. package/dist/llm/call-ai.js +0 -62
  562. package/dist/llm/memory-infer-impl.js +0 -138
  563. package/dist/output/shapes/distill.js +0 -14
  564. package/dist/output/shapes/history.js +0 -11
  565. package/dist/output/text/distill.js +0 -6
  566. package/dist/output/text/enable-disable.js +0 -8
  567. package/dist/output/text/history.js +0 -6
  568. package/dist/output/text/wiki.js +0 -16
  569. package/dist/registry/build-index.js +0 -386
  570. package/dist/schemas/akm-config.json +0 -14225
  571. package/dist/scripts/migrate-storage.js +0 -13169
  572. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -10169
  573. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  574. package/dist/setup/legacy-config.js +0 -106
  575. package/dist/storage/repositories/consolidation-repository.js +0 -38
  576. package/dist/storage/repositories/recombine-repository.js +0 -213
  577. package/dist/wiki/wiki-templates.js +0 -15
  578. package/dist/wiki/wiki.js +0 -1012
  579. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  580. package/dist/workflows/db.js +0 -350
  581. package/dist/workflows/exec/watch.js +0 -116
  582. package/dist/workflows/program/parser.js +0 -760
  583. package/dist/workflows/program/project.js +0 -105
  584. package/docs/data-and-telemetry.md +0 -227
  585. package/docs/migration/release-notes/0.9.0-beta.60.md +0 -19
  586. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  587. /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
  }