@fitlab-ai/agent-infra 0.8.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/bin/cli.ts +2 -2
  2. package/dist/bin/cli.js +2 -2
  3. package/dist/bin/internal-cli.js +98 -0
  4. package/dist/lib/decide.js +81 -90
  5. package/dist/lib/defaults.json +1 -0
  6. package/dist/lib/git/review-snapshot.js +79 -0
  7. package/dist/lib/git/workflow.js +101 -0
  8. package/dist/lib/internal/git-workflow.js +42 -0
  9. package/dist/lib/internal/platform-checks.js +119 -0
  10. package/dist/lib/internal/platform-comment.js +129 -0
  11. package/dist/lib/internal/platform-context.js +46 -0
  12. package/dist/lib/internal/platform-issue.js +142 -0
  13. package/dist/lib/internal/platform-pr.js +146 -0
  14. package/dist/lib/internal/release-workflow.js +217 -0
  15. package/dist/lib/internal/task-artifact.js +67 -0
  16. package/dist/lib/internal/task-context.js +43 -0
  17. package/dist/lib/internal/task-event.js +70 -0
  18. package/dist/lib/internal/task-ledger.js +114 -0
  19. package/dist/lib/internal/task-lifecycle.js +58 -0
  20. package/dist/lib/internal/task-short-id.js +83 -0
  21. package/dist/lib/internal/task-snapshot.js +47 -0
  22. package/dist/lib/internal/task-verify.js +50 -0
  23. package/dist/lib/internal/task-warning.js +74 -0
  24. package/dist/lib/platform/adapters.js +14 -0
  25. package/dist/lib/platform/context.js +146 -0
  26. package/dist/lib/platform/github-client.js +124 -0
  27. package/dist/lib/platform/issue-comments.js +409 -0
  28. package/dist/lib/platform/issue-metadata.js +183 -0
  29. package/dist/lib/platform/issues.js +617 -0
  30. package/dist/lib/platform/metadata-labels.js +28 -0
  31. package/dist/lib/platform/pr-checks.js +216 -0
  32. package/dist/lib/platform/pr-summary.js +88 -0
  33. package/dist/lib/platform/pull-request-metadata.js +38 -0
  34. package/dist/lib/platform/pull-requests.js +338 -0
  35. package/dist/lib/platform/releases.js +82 -0
  36. package/dist/lib/platform/types.js +22 -0
  37. package/dist/lib/platform/verification-required.js +78 -0
  38. package/dist/lib/platform/verification-sync.js +1014 -0
  39. package/dist/lib/platform/verification.js +6 -0
  40. package/dist/lib/release/channels.js +31 -0
  41. package/dist/lib/release/workflow.js +32 -0
  42. package/dist/lib/render.js +3 -2
  43. package/dist/lib/run/index.js +24 -7
  44. package/dist/lib/sandbox/capture.js +25 -3
  45. package/dist/lib/sandbox/commands/create.js +36 -22
  46. package/dist/lib/sandbox/commands/enter.js +34 -11
  47. package/dist/lib/sandbox/commands/list-running.js +10 -8
  48. package/dist/lib/sandbox/commands/show.js +2 -2
  49. package/dist/lib/sandbox/commands/start.js +53 -15
  50. package/dist/lib/sandbox/index.js +6 -6
  51. package/dist/lib/sandbox/mounts.js +36 -0
  52. package/dist/lib/sandbox/recovery.js +562 -0
  53. package/dist/lib/sandbox/task-resolver.js +5 -1
  54. package/dist/lib/sandbox/tools.js +19 -0
  55. package/dist/lib/server/protocol.js +5 -5
  56. package/dist/lib/task/activity-log.js +61 -0
  57. package/dist/lib/task/artifact-lifecycle.js +427 -0
  58. package/dist/lib/task/command-options.js +30 -0
  59. package/dist/lib/task/commands/cat.js +36 -6
  60. package/dist/lib/task/commands/decisions.js +80 -46
  61. package/dist/lib/task/commands/files.js +21 -7
  62. package/dist/lib/task/commands/grep.js +55 -7
  63. package/dist/lib/task/commands/issue-body.js +23 -14
  64. package/dist/lib/task/commands/log.js +27 -70
  65. package/dist/lib/task/commands/show.js +20 -7
  66. package/dist/lib/task/commands/status.js +18 -8
  67. package/dist/lib/task/decision-intents.js +154 -0
  68. package/dist/lib/task/decision-items.js +53 -0
  69. package/dist/lib/task/events.js +318 -0
  70. package/dist/lib/task/frontmatter.js +159 -1
  71. package/dist/lib/task/implementation-inputs.js +117 -0
  72. package/dist/lib/task/index.js +12 -9
  73. package/dist/lib/task/issue-form.js +19 -6
  74. package/dist/lib/task/ledger-intents.js +185 -0
  75. package/dist/lib/task/ledger.js +65 -35
  76. package/dist/lib/task/lifecycle.js +484 -0
  77. package/dist/lib/task/resolve-ref.js +152 -32
  78. package/dist/lib/task/review-artifacts.js +101 -0
  79. package/dist/lib/task/review-fingerprint.js +46 -0
  80. package/dist/lib/task/sections.js +358 -4
  81. package/dist/lib/task/short-id.js +292 -7
  82. package/dist/lib/task/snapshot.js +95 -0
  83. package/dist/lib/task/verification-checks.js +8 -0
  84. package/dist/lib/task/verification-config.js +14 -0
  85. package/dist/lib/task/verification-engine.js +906 -0
  86. package/dist/lib/task/verification-review.js +3 -0
  87. package/dist/lib/task/verification-types.js +2 -0
  88. package/dist/lib/task/verification.js +113 -0
  89. package/dist/lib/task/workflow-warning-intents.js +131 -0
  90. package/dist/lib/task/workflow-warnings.js +10 -78
  91. package/dist/lib/task/write.js +241 -0
  92. package/lib/decide.ts +57 -97
  93. package/lib/defaults.json +1 -0
  94. package/lib/git/review-snapshot.ts +73 -0
  95. package/lib/git/workflow.ts +101 -0
  96. package/lib/internal/git-workflow.ts +37 -0
  97. package/lib/internal/platform-checks.ts +101 -0
  98. package/lib/internal/platform-comment.ts +99 -0
  99. package/lib/internal/platform-context.ts +33 -0
  100. package/lib/internal/platform-issue.ts +116 -0
  101. package/lib/internal/platform-pr.ts +119 -0
  102. package/lib/internal/release-workflow.ts +186 -0
  103. package/lib/internal/task-artifact.ts +47 -0
  104. package/lib/internal/task-context.ts +35 -0
  105. package/lib/internal/task-event.ts +51 -0
  106. package/lib/internal/task-ledger.ts +86 -0
  107. package/lib/internal/task-lifecycle.ts +41 -0
  108. package/lib/internal/task-short-id.ts +55 -0
  109. package/lib/internal/task-snapshot.ts +32 -0
  110. package/lib/internal/task-verify.ts +33 -0
  111. package/lib/internal/task-warning.ts +50 -0
  112. package/lib/platform/adapters.ts +30 -0
  113. package/lib/platform/context.ts +164 -0
  114. package/lib/platform/github-client.ts +146 -0
  115. package/lib/platform/issue-comments.ts +479 -0
  116. package/lib/platform/issue-metadata.ts +250 -0
  117. package/lib/platform/issues.ts +691 -0
  118. package/lib/platform/metadata-labels.ts +33 -0
  119. package/lib/platform/pr-checks.ts +246 -0
  120. package/lib/platform/pr-summary.ts +90 -0
  121. package/lib/platform/pull-request-metadata.ts +69 -0
  122. package/lib/platform/pull-requests.ts +380 -0
  123. package/lib/platform/releases.ts +71 -0
  124. package/lib/platform/types.ts +74 -0
  125. package/lib/platform/verification-required.ts +83 -0
  126. package/{runtime/platform-adapters/platform-sync.github.js → lib/platform/verification-sync.ts} +198 -367
  127. package/lib/platform/verification.ts +11 -0
  128. package/lib/release/channels.ts +30 -0
  129. package/lib/release/workflow.ts +31 -0
  130. package/lib/render.ts +3 -2
  131. package/lib/run/index.ts +25 -7
  132. package/lib/sandbox/capture.ts +29 -3
  133. package/lib/sandbox/commands/create.ts +43 -25
  134. package/lib/sandbox/commands/enter.ts +38 -11
  135. package/lib/sandbox/commands/list-running.ts +10 -8
  136. package/lib/sandbox/commands/show.ts +2 -2
  137. package/lib/sandbox/commands/start.ts +50 -16
  138. package/lib/sandbox/index.ts +6 -6
  139. package/lib/sandbox/mounts.ts +55 -0
  140. package/lib/sandbox/recovery.ts +828 -0
  141. package/lib/sandbox/task-resolver.ts +5 -1
  142. package/lib/sandbox/tools.ts +31 -0
  143. package/lib/server/protocol.ts +5 -5
  144. package/lib/task/activity-log.ts +62 -0
  145. package/lib/task/artifact-lifecycle.ts +484 -0
  146. package/lib/task/command-options.ts +32 -0
  147. package/lib/task/commands/cat.ts +22 -9
  148. package/lib/task/commands/decisions.ts +49 -47
  149. package/lib/task/commands/files.ts +14 -6
  150. package/lib/task/commands/grep.ts +34 -7
  151. package/lib/task/commands/issue-body.ts +16 -16
  152. package/lib/task/commands/log.ts +17 -70
  153. package/lib/task/commands/show.ts +14 -6
  154. package/lib/task/commands/status.ts +17 -7
  155. package/lib/task/decision-intents.ts +163 -0
  156. package/lib/task/decision-items.ts +64 -0
  157. package/lib/task/events.ts +320 -0
  158. package/lib/task/frontmatter.ts +202 -2
  159. package/lib/task/implementation-inputs.ts +137 -0
  160. package/lib/task/index.ts +12 -9
  161. package/lib/task/issue-form.ts +20 -6
  162. package/lib/task/ledger-intents.ts +201 -0
  163. package/lib/task/ledger.ts +77 -31
  164. package/lib/task/lifecycle.ts +458 -0
  165. package/lib/task/resolve-ref.ts +197 -36
  166. package/{templates/.agents/scripts/lib/review-artifacts.js → lib/task/review-artifacts.ts} +11 -12
  167. package/{templates/.agents/scripts/lib/post-review-commit.js → lib/task/review-fingerprint.ts} +17 -12
  168. package/lib/task/sections.ts +486 -4
  169. package/lib/task/short-id.ts +403 -9
  170. package/lib/task/snapshot.ts +143 -0
  171. package/lib/task/verification-checks.ts +9 -0
  172. package/lib/task/verification-config.ts +20 -0
  173. package/{templates/.agents/scripts/validate-artifact.js → lib/task/verification-engine.ts} +199 -406
  174. package/lib/task/verification-review.ts +9 -0
  175. package/lib/task/verification-types.ts +29 -0
  176. package/lib/task/verification.ts +156 -0
  177. package/lib/task/workflow-warning-intents.ts +142 -0
  178. package/lib/task/workflow-warnings.ts +10 -78
  179. package/lib/task/write.ts +468 -0
  180. package/package.json +4 -2
  181. package/templates/.agents/README.en.md +4 -3
  182. package/templates/.agents/README.zh-CN.md +4 -3
  183. package/templates/.agents/rules/README.en.md +6 -5
  184. package/templates/.agents/rules/README.zh-CN.md +6 -5
  185. package/templates/.agents/rules/create-issue.en.md +1 -1
  186. package/templates/.agents/rules/create-issue.github.en.md +5 -184
  187. package/templates/.agents/rules/create-issue.github.zh-CN.md +12 -178
  188. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  189. package/templates/.agents/rules/human-decision-context.en.md +36 -0
  190. package/templates/.agents/rules/human-decision-context.zh-CN.md +36 -0
  191. package/templates/.agents/rules/issue-fields.github.en.md +4 -148
  192. package/templates/.agents/rules/issue-fields.github.zh-CN.md +9 -144
  193. package/templates/.agents/rules/issue-pr-commands.en.md +43 -3
  194. package/templates/.agents/rules/issue-pr-commands.github.en.md +15 -220
  195. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +23 -213
  196. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +45 -3
  197. package/templates/.agents/rules/issue-sync.en.md +1 -1
  198. package/templates/.agents/rules/issue-sync.github.en.md +14 -301
  199. package/templates/.agents/rules/issue-sync.github.zh-CN.md +36 -287
  200. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  201. package/templates/.agents/rules/milestone-inference.en.md +1 -1
  202. package/templates/.agents/rules/milestone-inference.github.en.md +7 -119
  203. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +8 -108
  204. package/templates/.agents/rules/milestone-inference.zh-CN.md +1 -1
  205. package/templates/.agents/rules/next-step-output.en.md +12 -11
  206. package/templates/.agents/rules/next-step-output.zh-CN.md +12 -11
  207. package/templates/.agents/rules/no-mid-flow-questions.en.md +3 -3
  208. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +3 -3
  209. package/templates/.agents/rules/pr-checks-commands.en.md +16 -3
  210. package/templates/.agents/rules/pr-checks-commands.github.en.md +10 -54
  211. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +19 -47
  212. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +30 -3
  213. package/templates/.agents/rules/pr-sync.en.md +30 -3
  214. package/templates/.agents/rules/pr-sync.github.en.md +12 -115
  215. package/templates/.agents/rules/pr-sync.github.zh-CN.md +26 -104
  216. package/templates/.agents/rules/pr-sync.zh-CN.md +43 -3
  217. package/templates/.agents/rules/review-handshake.en.md +8 -5
  218. package/templates/.agents/rules/review-handshake.zh-CN.md +8 -5
  219. package/templates/.agents/rules/task-management.en.md +11 -2
  220. package/templates/.agents/rules/task-management.zh-CN.md +11 -3
  221. package/templates/.agents/rules/task-short-id.en.md +25 -29
  222. package/templates/.agents/rules/task-short-id.zh-CN.md +20 -23
  223. package/templates/.agents/scripts/lib/agent-infra-package.js +1 -9
  224. package/templates/.agents/scripts/task-short-id.js +71 -533
  225. package/templates/.agents/skills/analyze-task/SKILL.en.md +22 -55
  226. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +26 -59
  227. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -3
  228. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -3
  229. package/templates/.agents/skills/block-task/SKILL.en.md +21 -47
  230. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +23 -47
  231. package/templates/.agents/skills/block-task/config/verify.json +1 -2
  232. package/templates/.agents/skills/cancel-task/SKILL.en.md +16 -45
  233. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +16 -45
  234. package/templates/.agents/skills/cancel-task/config/verify.json +1 -2
  235. package/templates/.agents/skills/check-task/SKILL.en.md +6 -3
  236. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +6 -3
  237. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -24
  238. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -24
  239. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -24
  240. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -24
  241. package/templates/.agents/skills/code-task/SKILL.en.md +22 -35
  242. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +28 -44
  243. package/templates/.agents/skills/code-task/config/verify.en.json +3 -3
  244. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +3 -3
  245. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +13 -9
  246. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +13 -9
  247. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
  248. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
  249. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -1
  250. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -1
  251. package/templates/.agents/skills/commit/SKILL.en.md +13 -11
  252. package/templates/.agents/skills/commit/SKILL.zh-CN.md +13 -11
  253. package/templates/.agents/skills/commit/config/verify.json +1 -2
  254. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -11
  255. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -11
  256. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +4 -8
  257. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +4 -8
  258. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +6 -18
  259. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +5 -17
  260. package/templates/.agents/skills/commit/reference/task-status-update.en.md +13 -0
  261. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +13 -0
  262. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +13 -36
  263. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +13 -36
  264. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -3
  265. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -3
  266. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +10 -54
  267. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +10 -79
  268. package/templates/.agents/skills/complete-task/SKILL.en.md +59 -63
  269. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +59 -63
  270. package/templates/.agents/skills/complete-task/config/verify.en.json +11 -7
  271. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +11 -7
  272. package/templates/.agents/skills/create-pr/SKILL.en.md +19 -44
  273. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +19 -43
  274. package/templates/.agents/skills/create-pr/config/verify.json +1 -2
  275. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +8 -12
  276. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +7 -11
  277. package/templates/.agents/skills/create-task/SKILL.en.md +12 -13
  278. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +12 -13
  279. package/templates/.agents/skills/create-task/config/verify.json +1 -2
  280. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  281. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
  282. package/templates/.agents/skills/import-codescan/config/verify.json +1 -2
  283. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  284. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
  285. package/templates/.agents/skills/import-dependabot/config/verify.json +1 -2
  286. package/templates/.agents/skills/import-issue/SKILL.en.md +9 -13
  287. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -14
  288. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  289. package/templates/.agents/skills/plan-task/SKILL.en.md +17 -42
  290. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +22 -49
  291. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -3
  292. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -3
  293. package/templates/.agents/skills/post-release/SKILL.en.md +11 -81
  294. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +8 -78
  295. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  296. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  297. package/templates/.agents/skills/release/SKILL.en.md +16 -163
  298. package/templates/.agents/skills/release/SKILL.zh-CN.md +15 -162
  299. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -29
  300. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -29
  301. package/templates/.agents/skills/restore-task/config/verify.json +1 -2
  302. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -25
  303. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +13 -33
  304. package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -4
  305. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -4
  306. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +9 -12
  307. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +9 -12
  308. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +9 -2
  309. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +9 -2
  310. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +7 -0
  311. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +7 -0
  312. package/templates/.agents/skills/review-code/SKILL.en.md +22 -36
  313. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +23 -41
  314. package/templates/.agents/skills/review-code/config/verify.en.json +3 -4
  315. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -4
  316. package/templates/.agents/skills/review-code/reference/output-templates.en.md +56 -18
  317. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +56 -18
  318. package/templates/.agents/skills/review-code/reference/report-template.en.md +11 -4
  319. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +11 -4
  320. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +7 -0
  321. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +7 -0
  322. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -25
  323. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +13 -33
  324. package/templates/.agents/skills/review-plan/config/verify.en.json +3 -4
  325. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -4
  326. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +9 -12
  327. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +9 -12
  328. package/templates/.agents/skills/review-plan/reference/report-template.en.md +9 -2
  329. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +9 -2
  330. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +7 -0
  331. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +7 -0
  332. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -2
  333. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -2
  334. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -15
  335. package/templates/.agents/skills/watch-pr/SKILL.en.md +24 -14
  336. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +25 -15
  337. package/templates/.agents/skills/watch-pr/config/verify.json +1 -2
  338. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  339. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  340. package/templates/.agents/templates/task.en.md +6 -1
  341. package/templates/.agents/templates/task.zh-CN.md +6 -1
  342. package/templates/.claude/commands/analyze-task.en.md +1 -1
  343. package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
  344. package/templates/.claude/commands/block-task.en.md +1 -1
  345. package/templates/.claude/commands/block-task.zh-CN.md +1 -1
  346. package/templates/.claude/commands/cancel-task.en.md +1 -1
  347. package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
  348. package/templates/.claude/commands/check-task.en.md +1 -1
  349. package/templates/.claude/commands/check-task.zh-CN.md +1 -1
  350. package/templates/.claude/commands/code-task.en.md +1 -1
  351. package/templates/.claude/commands/code-task.zh-CN.md +1 -1
  352. package/templates/.claude/commands/commit.en.md +1 -0
  353. package/templates/.claude/commands/commit.zh-CN.md +1 -0
  354. package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
  355. package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
  356. package/templates/.claude/commands/complete-task.en.md +1 -1
  357. package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
  358. package/templates/.claude/commands/create-pr.en.md +1 -1
  359. package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
  360. package/templates/.claude/commands/plan-task.en.md +1 -1
  361. package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
  362. package/templates/.claude/commands/review-analysis.en.md +1 -1
  363. package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
  364. package/templates/.claude/commands/review-code.en.md +1 -1
  365. package/templates/.claude/commands/review-code.zh-CN.md +1 -1
  366. package/templates/.claude/commands/review-plan.en.md +1 -1
  367. package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
  368. package/templates/.claude/commands/watch-pr.en.md +1 -1
  369. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  370. package/templates/.gemini/commands/_project_/analyze-task.en.toml +2 -0
  371. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +2 -0
  372. package/templates/.gemini/commands/_project_/block-task.en.toml +2 -0
  373. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +2 -0
  374. package/templates/.gemini/commands/_project_/cancel-task.en.toml +2 -0
  375. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +2 -0
  376. package/templates/.gemini/commands/_project_/check-task.en.toml +2 -0
  377. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +2 -0
  378. package/templates/.gemini/commands/_project_/code-task.en.toml +2 -0
  379. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +2 -0
  380. package/templates/.gemini/commands/_project_/commit.en.toml +4 -0
  381. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +4 -0
  382. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +2 -0
  383. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +2 -0
  384. package/templates/.gemini/commands/_project_/complete-task.en.toml +2 -0
  385. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +2 -0
  386. package/templates/.gemini/commands/_project_/create-pr.en.toml +2 -0
  387. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +2 -0
  388. package/templates/.gemini/commands/_project_/plan-task.en.toml +2 -0
  389. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +2 -0
  390. package/templates/.gemini/commands/_project_/review-analysis.en.toml +2 -0
  391. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +2 -0
  392. package/templates/.gemini/commands/_project_/review-code.en.toml +2 -0
  393. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +2 -0
  394. package/templates/.gemini/commands/_project_/review-plan.en.toml +2 -0
  395. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +2 -0
  396. package/templates/.gemini/commands/_project_/watch-pr.en.toml +2 -0
  397. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +2 -0
  398. package/templates/.git-hooks/check-large-files.cjs +92 -0
  399. package/templates/.git-hooks/check-version-format.sh +5 -1
  400. package/templates/.git-hooks/pre-commit +1 -0
  401. package/templates/.opencode/commands/analyze-task.en.md +1 -1
  402. package/templates/.opencode/commands/analyze-task.zh-CN.md +1 -1
  403. package/templates/.opencode/commands/check-task.en.md +1 -1
  404. package/templates/.opencode/commands/check-task.zh-CN.md +1 -1
  405. package/templates/.opencode/commands/code-task.en.md +1 -1
  406. package/templates/.opencode/commands/code-task.zh-CN.md +1 -1
  407. package/templates/.opencode/commands/commit.en.md +2 -0
  408. package/templates/.opencode/commands/commit.zh-CN.md +2 -0
  409. package/templates/.opencode/commands/complete-task.en.md +1 -1
  410. package/templates/.opencode/commands/complete-task.zh-CN.md +1 -1
  411. package/templates/.opencode/commands/plan-task.en.md +1 -1
  412. package/templates/.opencode/commands/plan-task.zh-CN.md +1 -1
  413. package/templates/.opencode/commands/review-analysis.en.md +1 -1
  414. package/templates/.opencode/commands/review-analysis.zh-CN.md +1 -1
  415. package/templates/.opencode/commands/review-code.en.md +1 -1
  416. package/templates/.opencode/commands/review-code.zh-CN.md +1 -1
  417. package/templates/.opencode/commands/review-plan.en.md +1 -1
  418. package/templates/.opencode/commands/review-plan.zh-CN.md +1 -1
  419. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +0 -40
  420. package/templates/.agents/scripts/platform-adapters/platform-sync.js +0 -13
  421. package/templates/.agents/scripts/review-diff-fingerprint.js +0 -170
  422. package/templates/.agents/scripts/workflow-warnings.js +0 -290
  423. package/templates/.agents/skills/code-task/scripts/detect-mode.js +0 -257
  424. package/templates/.agents/skills/release/scripts/manage-milestones.github.sh +0 -80
  425. package/templates/.agents/skills/release/scripts/manage-milestones.sh +0 -6
@@ -1,12 +1,14 @@
1
1
  // Shared parser for the task.md `## 审查分歧账本` (Review Disagreement Ledger).
2
2
  // Single source of truth for ledger row parsing across `ai task` commands
3
3
  // (log, decisions) — see the plan's D1 constraint "no third parser". The gate
4
- // parser in `.agents/scripts/validate-artifact.js` is a separate concern and
5
- // must be kept semantically in sync by hand (noted in review-handshake.md).
4
+ // verification parsing consumes this module directly, so commands and gates
5
+ // share the same table parser.
6
6
 
7
7
  // The ledger H2 heading is language-dependent (zh template / en template).
8
- const LEDGER_HEADING_RE = /^##\s+(审查分歧账本|Review Disagreement Ledger)\s*$/;
9
- const NEXT_H2_RE = /^##\s/;
8
+ import { parseTable } from './sections.ts';
9
+
10
+ const LEDGER_HEADINGS = ['审查分歧账本', 'Review Disagreement Ledger'] as const;
11
+ const LEDGER_COLUMNS = ['id', 'stage', 'round', 'severity', 'status', 'evidence'] as const;
10
12
 
11
13
  type LedgerRow = {
12
14
  id: string;
@@ -15,43 +17,87 @@ type LedgerRow = {
15
17
  severity: string;
16
18
  status: string;
17
19
  evidence: string;
20
+ /** Zero-based line within the normalized ledger section body. */
21
+ sourceLine: number;
22
+ };
23
+
24
+ type ReviewStage = 'analysis' | 'plan' | 'code';
25
+ type ReviewSeverity = 'blocker' | 'major' | 'minor';
26
+
27
+ type LedgerValidationError = { code: string; message: string };
28
+ type LedgerStageStatus = {
29
+ stage: ReviewStage;
30
+ canAdvance: boolean;
31
+ findingCounts: Record<ReviewSeverity, number>;
32
+ unresolvedFindingCounts: Record<ReviewSeverity, number>;
33
+ pendingHumanDecisions: number;
34
+ unresolved: LedgerRow[];
35
+ terminal: LedgerRow[];
18
36
  };
19
37
 
20
38
  // Terminal statuses the completion gates treat as resolved.
21
39
  const LEDGER_TERMINAL = new Set(['confirmed', 'closed', 'human-decided']);
22
- // Statuses that represent an executor-raised human-decision row (pending or done).
40
+ // Statuses that represent a decision row awaiting or carrying a human ruling.
23
41
  const HUMAN_DECISION_STATUSES = new Set(['needs-human-decision', 'human-decided']);
42
+ const FINDING_STATUSES = new Set(['open', 'accepted', 'adjusted', 'refuted', 'cannot-judge', 'confirmed', 'needs-human-decision', 'closed', 'human-decided']);
43
+
44
+ function isReviewStage(stage: string): stage is ReviewStage {
45
+ return stage === 'analysis' || stage === 'plan' || stage === 'code';
46
+ }
24
47
 
25
48
  // Parse all rows of the disagreement ledger table. Skips the heading, the
26
49
  // header row (`| id | ... |`) and the `|---|` separator; ignores non-`|` lines.
27
50
  // Rows with fewer than 6 columns are skipped (mirrors the JS gate parser).
28
51
  function parseLedger(content: string): LedgerRow[] {
29
- const lines = content.split('\n');
30
- let i = 0;
31
- while (i < lines.length && !LEDGER_HEADING_RE.test(lines[i]!)) i += 1;
32
- if (i >= lines.length) return [];
52
+ const table = parseTable(content, { sectionAliases: LEDGER_HEADINGS, columns: LEDGER_COLUMNS });
53
+ return table?.rows.map(({ values, sourceLine }) => ({
54
+ id: values.id!, stage: values.stage!, round: values.round!, severity: values.severity!,
55
+ status: values.status!, evidence: values.evidence!, sourceLine
56
+ })) ?? [];
57
+ }
58
+
59
+ function validateLedgerRows(rows: readonly LedgerRow[]): LedgerValidationError | null {
60
+ const seen = new Set<string>();
61
+ for (const row of rows) {
62
+ if (seen.has(row.id)) return { code: 'LEDGER_DUPLICATE_ID', message: `duplicate ledger id '${row.id}'` };
63
+ seen.add(row.id);
64
+ if (/^PRC-[1-9]\d*$/.test(row.id)) continue;
65
+ if (!/^(AN|PL|CD|HD)-[1-9]\d*$/.test(row.id)) return { code: 'LEDGER_ID_INVALID', message: `ledger id '${row.id}' is invalid` };
66
+ if (!['analysis', 'plan', 'code', 'post-review-commit'].includes(row.stage)) return { code: 'LEDGER_STAGE_INVALID', message: `ledger stage '${row.stage}' is invalid` };
67
+ if (!FINDING_STATUSES.has(row.status)) return { code: 'LEDGER_STATUS_INVALID', message: `ledger status '${row.status}' is invalid` };
68
+ if (row.id.startsWith('AN-') && row.stage !== 'analysis' || row.id.startsWith('PL-') && row.stage !== 'plan' || row.id.startsWith('CD-') && row.stage !== 'code') {
69
+ return { code: 'LEDGER_STAGE_INVALID', message: `ledger id '${row.id}' conflicts with stage '${row.stage}'` };
70
+ }
71
+ if (row.id.startsWith('HD-')) {
72
+ if (row.round !== '-' || row.severity !== 'decision' || !HUMAN_DECISION_STATUSES.has(row.status)) return { code: 'LEDGER_DOCUMENT_INVALID', message: `decision row '${row.id}' is invalid` };
73
+ } else if (!/^[1-9]\d*$/.test(row.round) || !['blocker', 'major', 'minor'].includes(row.severity)) {
74
+ return { code: 'LEDGER_DOCUMENT_INVALID', message: `finding row '${row.id}' is invalid` };
75
+ }
76
+ }
77
+ return null;
78
+ }
33
79
 
34
- const rows: LedgerRow[] = [];
35
- for (let j = i + 1; j < lines.length; j += 1) {
36
- if (NEXT_H2_RE.test(lines[j]!)) break;
37
- const line = lines[j]!.trim();
38
- if (!line.startsWith('|')) continue;
39
- const cells = line
40
- .split('|')
41
- .slice(1, -1)
42
- .map((cell) => cell.trim());
43
- if (cells.length < 6) continue;
44
- if (cells[0] === 'id' || /^-+$/.test(cells[0] ?? '')) continue;
45
- rows.push({
46
- id: cells[0]!,
47
- stage: cells[1]!,
48
- round: cells[2]!,
49
- severity: cells[3]!,
50
- status: cells[4]!,
51
- evidence: cells[5]!
52
- });
80
+ function summarizeLedgerStage(rows: readonly LedgerRow[], stage: ReviewStage): LedgerStageStatus {
81
+ const scoped = rows.filter((row) => row.stage === stage);
82
+ const unresolved = scoped.filter((row) => !LEDGER_TERMINAL.has(row.status));
83
+ const terminal = scoped.filter((row) => LEDGER_TERMINAL.has(row.status));
84
+ const findingCounts = { blocker: 0, major: 0, minor: 0 };
85
+ const unresolvedFindingCounts = { blocker: 0, major: 0, minor: 0 };
86
+ for (const row of scoped) {
87
+ if (row.severity === 'blocker' || row.severity === 'major' || row.severity === 'minor') findingCounts[row.severity] += 1;
88
+ }
89
+ for (const row of unresolved) {
90
+ if (row.severity === 'blocker' || row.severity === 'major' || row.severity === 'minor') unresolvedFindingCounts[row.severity] += 1;
53
91
  }
54
- return rows;
92
+ return {
93
+ stage,
94
+ canAdvance: unresolved.length === 0,
95
+ findingCounts,
96
+ unresolvedFindingCounts,
97
+ pendingHumanDecisions: unresolved.filter((row) => row.status === 'needs-human-decision').length,
98
+ unresolved,
99
+ terminal
100
+ };
55
101
  }
56
102
 
57
103
  // Allocate the next globally-unique human-decision id. Scans every `HD-<n>`
@@ -69,5 +115,5 @@ function nextHdId(rows: readonly LedgerRow[]): string {
69
115
  return `HD-${max + 1}`;
70
116
  }
71
117
 
72
- export { parseLedger, nextHdId, LEDGER_TERMINAL, HUMAN_DECISION_STATUSES };
73
- export type { LedgerRow };
118
+ export { LEDGER_HEADINGS, LEDGER_COLUMNS, parseLedger, nextHdId, isReviewStage, validateLedgerRows, summarizeLedgerStage, LEDGER_TERMINAL, HUMAN_DECISION_STATUSES };
119
+ export type { LedgerRow, ReviewStage, ReviewSeverity, LedgerValidationError, LedgerStageStatus };
@@ -0,0 +1,458 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
6
+ import { artifactFamilyCatalog, inspectArtifactDirectory, parseArtifactName } from './artifact-lifecycle.ts';
7
+ import { parseTypedTaskFrontmatter } from './frontmatter.ts';
8
+ import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
9
+ import {
10
+ configuredShortIdLength, executeShortIdCommand, loadShortIdByTaskId,
11
+ mutateShortIdRegistry
12
+ } from './short-id.ts';
13
+ import { captureTaskWriteMetadata, writeTask } from './write.ts';
14
+ import type { TaskFileSystem, TaskMutation, TaskOperationSummary, TaskWriteMetadata } from './write.ts';
15
+
16
+ const lifecycleIntentCatalog = [
17
+ 'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'
18
+ ] as const;
19
+ type TaskLifecycleIntent = (typeof lifecycleIntentCatalog)[number];
20
+ type TaskLifecycleRequest =
21
+ | { taskRef: string; intent: 'block'; agent: string; reason: string; unblockCondition: string; dryRun?: boolean }
22
+ | { taskRef: string; intent: 'activate'; agent: string; note: string; dryRun?: boolean }
23
+ | { taskRef: string; intent: 'cancel'; agent: string; reason: string; dryRun?: boolean }
24
+ | { taskRef: string; intent: 'complete'; agent: string; dryRun?: boolean }
25
+ | { taskRef: string; intent: 'close-codescan'; agent: string; alertNumber: number; reason: string; dryRun?: boolean }
26
+ | { taskRef: string; intent: 'close-dependabot'; agent: string; alertNumber: number; reason: string; dryRun?: boolean }
27
+ | { taskRef: string; intent: 'restore'; agent: string; stagingDir: string; issueNumber: number; dryRun?: boolean };
28
+
29
+ type HotState = 'active' | 'blocked' | 'completed';
30
+ type SourceState = HotState | 'staging';
31
+ type LifecycleStep = 'task-written' | 'directory-moved' | 'registry-committed';
32
+ type LifecycleError = { code: string; message: string };
33
+ type LifecycleJournal = {
34
+ version: 1;
35
+ taskId: string;
36
+ intent: TaskLifecycleIntent;
37
+ intentDigest: string;
38
+ sourceState: SourceState;
39
+ targetState: HotState;
40
+ metadata: TaskWriteMetadata;
41
+ completedSteps: LifecycleStep[];
42
+ };
43
+ type TaskLifecycleResult = {
44
+ status: 'planned' | 'applied' | 'no-op' | 'failed';
45
+ changed: boolean;
46
+ requestRef: string;
47
+ taskId: string | null;
48
+ intent: TaskLifecycleIntent | string;
49
+ sourceState: SourceState | null;
50
+ targetState: HotState | null;
51
+ sourcePath: string | null;
52
+ targetPath: string | null;
53
+ task: { operations: readonly TaskOperationSummary[] };
54
+ directory: { effect: 'move' | 'unchanged'; changed: boolean };
55
+ shortId: { effect: 'allocated' | 'released' | 'unchanged'; shortId: string | null; changed: boolean };
56
+ timestamp: string | null;
57
+ agentInfraVersion: string | null;
58
+ journalPath: string | null;
59
+ completedSteps: readonly LifecycleStep[];
60
+ pendingSteps: readonly LifecycleStep[];
61
+ error: LifecycleError | null;
62
+ };
63
+ type LifecycleFileSystem = {
64
+ renameSync: (source: string, target: string) => void;
65
+ readFileSync: (file: string) => string;
66
+ writeFileSync: (file: string, content: string, flag?: string) => void;
67
+ unlinkSync: (file: string) => void;
68
+ };
69
+ type TaskLifecycleOptions = {
70
+ repoRoot?: string;
71
+ metadataProvider?: () => TaskWriteMetadata;
72
+ fileSystem?: Partial<LifecycleFileSystem>;
73
+ taskFileSystem?: Partial<TaskFileSystem>;
74
+ directoryRenameSync?: (source: string, target: string) => void;
75
+ };
76
+
77
+ const STEPS: readonly LifecycleStep[] = ['task-written', 'directory-moved', 'registry-committed'];
78
+ const DEFAULT_IO: LifecycleFileSystem = {
79
+ renameSync: (source, target) => fs.renameSync(source, target),
80
+ readFileSync: (file) => fs.readFileSync(file, 'utf8'),
81
+ writeFileSync: (file, content, flag) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: flag ?? 'w' }),
82
+ unlinkSync: (file) => fs.unlinkSync(file)
83
+ };
84
+
85
+ function failed(request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>, error: LifecycleError, extra: Partial<TaskLifecycleResult> = {}): TaskLifecycleResult {
86
+ return {
87
+ status: 'failed', changed: false, requestRef: request.taskRef, taskId: null,
88
+ intent: request.intent, sourceState: null, targetState: null, sourcePath: null,
89
+ targetPath: null, task: { operations: [] }, directory: { effect: 'unchanged', changed: false },
90
+ shortId: { effect: 'unchanged', shortId: null, changed: false }, timestamp: null,
91
+ agentInfraVersion: null, journalPath: null, completedSteps: [], pendingSteps: STEPS,
92
+ error, ...extra
93
+ };
94
+ }
95
+
96
+ function normalizedRequest(request: TaskLifecycleRequest): TaskLifecycleRequest | LifecycleError {
97
+ if (!request || typeof request !== 'object' || !lifecycleIntentCatalog.includes(request.intent as TaskLifecycleIntent)) {
98
+ return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'intent is not in the lifecycle catalog' };
99
+ }
100
+ if (typeof request.taskRef !== 'string' || !request.taskRef || typeof request.agent !== 'string' || !request.agent.trim()) {
101
+ return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'taskRef and agent are required' };
102
+ }
103
+ const singleLine = (name: string, value: unknown): string | LifecycleError => {
104
+ if (typeof value !== 'string' || !value.trim() || /[\r\n]/.test(value)) {
105
+ return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: `${name} must be a non-empty single line` };
106
+ }
107
+ return value.trim();
108
+ };
109
+ const agent = singleLine('agent', request.agent);
110
+ if (typeof agent !== 'string') return agent;
111
+ if (request.intent === 'block') {
112
+ const reason = singleLine('reason', request.reason);
113
+ const unblockCondition = singleLine('unblockCondition', request.unblockCondition);
114
+ if (typeof reason !== 'string') return reason;
115
+ if (typeof unblockCondition !== 'string') return unblockCondition;
116
+ return { ...request, agent, reason, unblockCondition };
117
+ }
118
+ if (request.intent === 'activate') {
119
+ const note = singleLine('note', request.note);
120
+ return typeof note === 'string' ? { ...request, agent, note } : note;
121
+ }
122
+ if (request.intent === 'cancel' || request.intent === 'close-codescan' || request.intent === 'close-dependabot') {
123
+ const reason = singleLine('reason', request.reason);
124
+ if (typeof reason !== 'string') return reason;
125
+ if (request.intent !== 'cancel' && (!Number.isSafeInteger(request.alertNumber) || request.alertNumber < 1)) {
126
+ return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'alertNumber must be a positive integer' };
127
+ }
128
+ return { ...request, agent, reason };
129
+ }
130
+ if (request.intent === 'restore') {
131
+ if (typeof request.stagingDir !== 'string' || !request.stagingDir || !Number.isSafeInteger(request.issueNumber) || request.issueNumber < 1) {
132
+ return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'restore requires stagingDir and a positive issueNumber' };
133
+ }
134
+ return { ...request, agent };
135
+ }
136
+ return { ...request, agent };
137
+ }
138
+
139
+ function intentIdentity(request: TaskLifecycleRequest, taskId: string): string {
140
+ const payload: Record<string, unknown> = { taskId, intent: request.intent, agent: request.agent };
141
+ if ('reason' in request) payload.reason = request.reason;
142
+ if ('unblockCondition' in request) payload.unblockCondition = request.unblockCondition;
143
+ if ('note' in request) payload.note = request.note;
144
+ if ('alertNumber' in request) payload.alertNumber = request.alertNumber;
145
+ if ('issueNumber' in request) payload.issueNumber = request.issueNumber;
146
+ return createHash('sha256').update(JSON.stringify(payload)).digest('hex');
147
+ }
148
+
149
+ function transition(request: TaskLifecycleRequest): { sources: readonly SourceState[]; target: HotState; registry: 'alloc' | 'release' | 'none' } {
150
+ if (request.intent === 'block') return { sources: ['active'], target: 'blocked', registry: 'release' };
151
+ if (request.intent === 'activate') return { sources: ['blocked'], target: 'active', registry: 'alloc' };
152
+ if (request.intent === 'cancel') return { sources: ['active', 'blocked'], target: 'completed', registry: 'release' };
153
+ if (request.intent === 'restore') return { sources: ['staging'], target: 'active', registry: 'alloc' };
154
+ return { sources: ['active'], target: 'completed', registry: 'release' };
155
+ }
156
+
157
+ function actionAndNote(request: TaskLifecycleRequest, restoredFiles = 0): { action: string; note: string } {
158
+ if (request.intent === 'block') return { action: 'Block Task', note: request.reason };
159
+ if (request.intent === 'activate') return { action: 'Activate Task', note: request.note };
160
+ if (request.intent === 'cancel') return { action: 'Cancel Task', note: request.reason };
161
+ if (request.intent === 'complete') return { action: 'Complete Task', note: 'Task moved to completed/' };
162
+ if (request.intent === 'close-codescan') return { action: 'Close Codescan', note: `Code Scanning alert #${request.alertNumber} dismissed: ${request.reason}` };
163
+ if (request.intent === 'close-dependabot') return { action: 'Close Dependabot', note: `Dependabot alert #${request.alertNumber} dismissed: ${request.reason}` };
164
+ return { action: 'Restore Task', note: `Restored ${restoredFiles} files from Issue #${request.issueNumber}` };
165
+ }
166
+
167
+ function matchingCompletion(content: string, request: TaskLifecycleRequest, restoredFiles = 0): boolean {
168
+ const section = locateActivityLog(content);
169
+ if (!section) return false;
170
+ const identity = actionAndNote(request, restoredFiles);
171
+ return section.entries.some((entry) => entry.step === identity.action && entry.agent === request.agent && entry.note === identity.note);
172
+ }
173
+
174
+ function validateRestoreStaging(request: Extract<TaskLifecycleRequest, { intent: 'restore' }>, repoRoot: string, taskId: string): LifecycleError | { stagingDir: string; fileCount: number } {
175
+ const workspace = path.resolve(repoRoot, '.agents', 'workspace');
176
+ const stagingDir = path.resolve(request.stagingDir);
177
+ const relative = path.relative(workspace, stagingDir);
178
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative) || path.basename(stagingDir) === taskId) {
179
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside .agents/workspace' };
180
+ }
181
+ let entries: fs.Dirent[];
182
+ try { entries = fs.readdirSync(stagingDir, { withFileTypes: true }); }
183
+ catch (error) { return { code: 'LIFECYCLE_STAGING_INVALID', message: String(error) }; }
184
+ for (const entry of entries) {
185
+ const stat = fs.lstatSync(path.join(stagingDir, entry.name));
186
+ if (stat.isSymbolicLink() || !stat.isFile() || (entry.name !== 'task.md' && entry.name !== '.task-lifecycle.json' && !parseArtifactName(entry.name))) {
187
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: `unsupported restore entry '${entry.name}'` };
188
+ }
189
+ }
190
+ try {
191
+ if (fs.statSync(workspace).dev !== fs.statSync(stagingDir).dev) {
192
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging and active workspace must share a filesystem' };
193
+ }
194
+ } catch (error) {
195
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: String(error) };
196
+ }
197
+ const taskFile = path.join(stagingDir, 'task.md');
198
+ if (!fs.existsSync(taskFile)) return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging has no task.md' };
199
+ try {
200
+ const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(taskFile, 'utf8'));
201
+ if (frontmatter.id !== taskId || Number(frontmatter.issue_number) !== request.issueNumber || frontmatter.current_step === 'completed') {
202
+ return { code: 'LIFECYCLE_STAGING_IDENTITY_INVALID', message: 'restore task identity, issue, or current_step is invalid' };
203
+ }
204
+ } catch (error) {
205
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: error instanceof Error ? error.message : String(error) };
206
+ }
207
+ for (const family of artifactFamilyCatalog) {
208
+ const inventory = inspectArtifactDirectory(stagingDir, family.family);
209
+ if (inventory.diagnostics.some((item) => item.code !== 'BROKEN_REFERENCE')) {
210
+ return { code: 'LIFECYCLE_STAGING_INVALID', message: `restore artifact topology is invalid for ${family.family}` };
211
+ }
212
+ }
213
+ return { stagingDir, fileCount: entries.filter((entry) => !entry.name.startsWith('.')).length };
214
+ }
215
+
216
+ function mutationsFor(request: TaskLifecycleRequest, content: string, metadata: TaskWriteMetadata, restoredFiles: number): TaskMutation[] | LifecycleError {
217
+ const section = locateActivityLog(content);
218
+ if (!section) return { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' };
219
+ const identity = actionAndNote(request, restoredFiles);
220
+ let body = appendActivityEntry(section, { time: metadata.timestamp, step: `${identity.action} [started]`, agent: request.agent, note: 'started' });
221
+ body = appendActivityEntry({ ...section, body }, { time: metadata.timestamp, step: identity.action, agent: request.agent, note: identity.note });
222
+ const set: Record<string, string> = {};
223
+ const remove: string[] = [];
224
+ if (request.intent === 'block') {
225
+ set.status = 'blocked'; set.blocked_at = metadata.timestamp;
226
+ remove.push('completed_at', 'cancelled_at', 'cancel_reason');
227
+ } else if (request.intent === 'activate' || request.intent === 'restore') {
228
+ set.status = 'active'; set.assigned_to = request.agent;
229
+ remove.push('blocked_at', 'completed_at', 'cancelled_at', 'cancel_reason');
230
+ } else if (request.intent === 'cancel') {
231
+ set.status = 'completed'; set.cancelled_at = metadata.timestamp; set.cancel_reason = request.reason;
232
+ remove.push('blocked_at', 'completed_at');
233
+ } else {
234
+ set.status = 'completed'; set.current_step = 'completed'; set.completed_at = metadata.timestamp;
235
+ remove.push('blocked_at', 'cancelled_at', 'cancel_reason');
236
+ const frontmatter = parseTypedTaskFrontmatter(content);
237
+ if (frontmatter.target_date === '') set.target_date = metadata.timestamp.slice(0, 10);
238
+ }
239
+ const mutations: TaskMutation[] = [
240
+ { kind: 'frontmatter', set, remove: [...new Set(remove)] },
241
+ { kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body }
242
+ ];
243
+ if (request.intent === 'block') {
244
+ mutations.splice(1, 0, {
245
+ kind: 'section', aliases: ['阻塞信息', 'Blocking Information'], heading: '阻塞信息',
246
+ body: `- **原因**:${request.reason}\n- **解除条件**:${request.unblockCondition}`
247
+ });
248
+ }
249
+ return mutations;
250
+ }
251
+
252
+ function writeJournal(file: string, journal: LifecycleJournal, io: LifecycleFileSystem, create = false): void {
253
+ const temporary = `${file}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
254
+ io.writeFileSync(temporary, `${JSON.stringify(journal, null, 2)}\n`, 'wx');
255
+ try {
256
+ if (create && fs.existsSync(file)) throw Object.assign(new Error('lifecycle journal already exists'), { code: 'LIFECYCLE_INTENT_CONFLICT' });
257
+ io.renameSync(temporary, file);
258
+ } catch (error) {
259
+ try { io.unlinkSync(temporary); } catch { /* preserve primary error */ }
260
+ throw error;
261
+ }
262
+ }
263
+
264
+ function parseJournal(content: string): LifecycleJournal {
265
+ const value = JSON.parse(content) as Partial<LifecycleJournal>;
266
+ if (
267
+ value.version !== 1 || typeof value.taskId !== 'string' ||
268
+ !lifecycleIntentCatalog.includes(value.intent as TaskLifecycleIntent) ||
269
+ typeof value.intentDigest !== 'string' || !value.intentDigest ||
270
+ !['active', 'blocked', 'completed', 'staging'].includes(value.sourceState ?? '') ||
271
+ !['active', 'blocked', 'completed'].includes(value.targetState ?? '') ||
272
+ !value.metadata || typeof value.metadata.timestamp !== 'string' ||
273
+ typeof value.metadata.agentInfraVersion !== 'string' || !Array.isArray(value.completedSteps) ||
274
+ value.completedSteps.some((step) => !STEPS.includes(step)) ||
275
+ new Set(value.completedSteps).size !== value.completedSteps.length
276
+ ) {
277
+ throw new Error('lifecycle journal has invalid schema');
278
+ }
279
+ return value as LifecycleJournal;
280
+ }
281
+
282
+ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
283
+ const normalized = normalizedRequest(requestInput);
284
+ if ('code' in normalized) return failed(requestInput, normalized);
285
+ const request = normalized;
286
+ const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
287
+ const io = { ...DEFAULT_IO, ...options.fileSystem };
288
+ let taskId: string;
289
+ if (request.intent === 'restore') {
290
+ if (!TASK_ID_RE.test(request.taskRef)) return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'restore requires a full TASK-id' });
291
+ taskId = request.taskRef;
292
+ } else {
293
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot });
294
+ if (!resolved.ok) return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
295
+ taskId = resolved.taskId;
296
+ }
297
+ const spec = transition(request);
298
+ const hot = locateHotTaskDirs(repoRoot, taskId);
299
+ if (hot.length > 1) return failed(request, { code: 'LIFECYCLE_IDENTITY_CONFLICT', message: `task ${taskId} exists in multiple hot states` }, { taskId });
300
+ let sourceState: SourceState;
301
+ let sourcePath: string;
302
+ let restoredFiles = 0;
303
+ if (request.intent === 'restore' && hot.length === 0) {
304
+ const validation = validateRestoreStaging(request, repoRoot, taskId);
305
+ if ('code' in validation) return failed(request, validation, { taskId });
306
+ sourceState = 'staging'; sourcePath = validation.stagingDir; restoredFiles = validation.fileCount;
307
+ } else if (hot[0]) {
308
+ sourceState = hot[0].state; sourcePath = hot[0].taskDir;
309
+ restoredFiles = fs.readdirSync(sourcePath).filter((name) => !name.startsWith('.')).length;
310
+ } else {
311
+ return failed(request, { code: 'LIFECYCLE_TASK_NOT_FOUND', message: `task ${taskId} has no lifecycle source` }, { taskId });
312
+ }
313
+ const targetPath = path.join(repoRoot, '.agents', 'workspace', spec.target, taskId);
314
+ const currentTaskFile = path.join(sourcePath, 'task.md');
315
+ const existingJournal = fs.existsSync(path.join(sourcePath, '.task-lifecycle.json'));
316
+ if (sourceState === spec.target && !existingJournal) {
317
+ const content = io.readFileSync(currentTaskFile);
318
+ if (matchingCompletion(content, request, restoredFiles)) {
319
+ const frontmatter = parseTypedTaskFrontmatter(content);
320
+ return {
321
+ ...failed(request, { code: '', message: '' }), status: 'no-op', error: null, taskId,
322
+ sourceState, targetState: spec.target, sourcePath, targetPath, pendingSteps: [],
323
+ completedSteps: STEPS, timestamp: typeof frontmatter.updated_at === 'string' ? frontmatter.updated_at : null,
324
+ agentInfraVersion: typeof frontmatter.agent_infra_version === 'string' ? frontmatter.agent_infra_version : null
325
+ };
326
+ }
327
+ return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: `task ${taskId} is already ${spec.target} with a different lifecycle intent` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
328
+ }
329
+ if (!spec.sources.includes(sourceState) && !existingJournal) {
330
+ return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
331
+ }
332
+ if (sourcePath !== targetPath && fs.existsSync(targetPath)) {
333
+ return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
334
+ }
335
+ const content = io.readFileSync(currentTaskFile);
336
+ let frontmatter;
337
+ try { frontmatter = parseTypedTaskFrontmatter(content); }
338
+ catch (error) { return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
339
+ if (frontmatter.id !== taskId) return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
340
+ if (!locateActivityLog(content)) return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
341
+ const registryRequest = executeShortIdCommand({
342
+ operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
343
+ shortIdLength: configuredShortIdLength(repoRoot)
344
+ });
345
+ if (registryRequest.status === 'failed') {
346
+ return failed(request, registryRequest.error!, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
347
+ }
348
+ const registry = JSON.parse(registryRequest.output || '{"version":1,"ids":{}}') as { ids: Record<string, string> };
349
+ const registeredKey = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId)?.[0];
350
+ if (spec.registry === 'release' && sourceState === 'active' && !registeredKey) {
351
+ return failed(request, { code: 'LIFECYCLE_SHORT_ID_PRECONDITION', message: 'active source has no short-id registry entry' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
352
+ }
353
+ if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1) {
354
+ return failed(request, { code: 'SHORT_ID_CAPACITY_EXCEEDED', message: 'short-id registry has no free slot' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
355
+ }
356
+ const digest = intentIdentity(request, taskId);
357
+ const initialJournalPath = path.join(sourcePath, '.task-lifecycle.json');
358
+ let journal: LifecycleJournal;
359
+ let journalPath = initialJournalPath;
360
+ if (fs.existsSync(initialJournalPath)) {
361
+ try { journal = parseJournal(io.readFileSync(initialJournalPath)); }
362
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath }); }
363
+ if (journal.intentDigest !== digest || journal.taskId !== taskId) return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
364
+ } else {
365
+ let metadata: TaskWriteMetadata;
366
+ try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
367
+ catch (error) { return failed(request, { code: 'LIFECYCLE_METADATA_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
368
+ journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
369
+ if (request.dryRun) {
370
+ const plannedMutations = mutationsFor(request, content, metadata, restoredFiles);
371
+ if ('code' in plannedMutations) return failed(request, plannedMutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
372
+ const taskPlan = writeTask(
373
+ { taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations: plannedMutations, dryRun: true },
374
+ {
375
+ repoRoot, metadataProvider: () => metadata,
376
+ taskLocation: sourceState === 'staging' ? { repoRoot, taskId, taskMdPath: path.join(sourcePath, 'task.md'), state: 'active' } : undefined
377
+ }
378
+ );
379
+ if (taskPlan.status === 'failed') return failed(request, taskPlan.error, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, task: { operations: taskPlan.operations } });
380
+ return {
381
+ ...failed(request, { code: '', message: '' }), status: 'planned', changed: true, error: null,
382
+ taskId, sourceState, targetState: spec.target, sourcePath, targetPath, task: { operations: taskPlan.operations },
383
+ directory: { effect: 'move', changed: true },
384
+ shortId: {
385
+ effect: spec.registry === 'alloc' ? (registeredKey ? 'unchanged' : 'allocated') : spec.registry === 'release' ? (registeredKey ? 'released' : 'unchanged') : 'unchanged',
386
+ shortId: registeredKey ?? null,
387
+ changed: spec.registry === 'alloc' ? !registeredKey : spec.registry === 'release' ? Boolean(registeredKey) : false
388
+ },
389
+ timestamp: metadata.timestamp, agentInfraVersion: metadata.agentInfraVersion,
390
+ journalPath: initialJournalPath
391
+ };
392
+ }
393
+ try { writeJournal(initialJournalPath, journal, io, true); }
394
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
395
+ }
396
+ const completed = new Set(journal.completedSteps);
397
+ if (sourceState === spec.target && journal.sourceState !== spec.target) completed.add('directory-moved');
398
+ const currentMappings = loadShortIdByTaskId(repoRoot);
399
+ if ((spec.target === 'active' && currentMappings.has(taskId)) || (spec.target !== 'active' && !currentMappings.has(taskId))) {
400
+ if (completed.has('directory-moved')) completed.add('registry-committed');
401
+ }
402
+ let taskOperations: readonly TaskOperationSummary[] = [];
403
+ if (matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles)) completed.add('task-written');
404
+ if (!completed.has('task-written')) {
405
+ const current = io.readFileSync(path.join(sourcePath, 'task.md'));
406
+ const mutations = mutationsFor(request, current, journal.metadata, restoredFiles);
407
+ if ('code' in mutations) return failed(request, mutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, completedSteps: [...completed], changed: completed.size > 0 });
408
+ const writeResult = writeTask(
409
+ { taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations },
410
+ {
411
+ repoRoot, metadataProvider: () => journal.metadata,
412
+ fileSystem: options.taskFileSystem,
413
+ taskLocation: sourceState === 'staging' ? { repoRoot, taskId, taskMdPath: path.join(sourcePath, 'task.md'), state: 'active' } : undefined
414
+ }
415
+ );
416
+ taskOperations = writeResult.operations;
417
+ if (writeResult.status === 'failed') return failed(request, writeResult.error, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, completedSteps: [...completed], changed: completed.size > 0, timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
418
+ completed.add('task-written'); journal.completedSteps = [...completed];
419
+ try { writeJournal(journalPath, journal, io); }
420
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
421
+ }
422
+ if (fs.existsSync(targetPath) && !fs.existsSync(sourcePath)) completed.add('directory-moved');
423
+ if (!completed.has('directory-moved')) {
424
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
425
+ if (fs.existsSync(targetPath)) return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
426
+ try { (options.directoryRenameSync ?? io.renameSync)(sourcePath, targetPath); }
427
+ catch (error) { return failed(request, { code: 'LIFECYCLE_DIRECTORY_RENAME_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
428
+ completed.add('directory-moved'); journal.completedSteps = [...completed]; journalPath = path.join(targetPath, '.task-lifecycle.json');
429
+ try { writeJournal(journalPath, journal, io); }
430
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
431
+ }
432
+ let shortId: TaskLifecycleResult['shortId'] = { effect: 'unchanged', shortId: null, changed: false };
433
+ if (!completed.has('registry-committed')) {
434
+ try { shortId = mutateShortIdRegistry(repoRoot, taskId, spec.registry); }
435
+ catch (error) { return failed(request, { code: (error as { code?: string }).code ?? 'LIFECYCLE_SHORT_ID_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
436
+ completed.add('registry-committed'); journal.completedSteps = [...completed];
437
+ try { writeJournal(journalPath, journal, io); }
438
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, shortId, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
439
+ }
440
+ const mappings = loadShortIdByTaskId(repoRoot);
441
+ if (spec.target === 'active' && !mappings.has(taskId)) return failed(request, { code: 'LIFECYCLE_FINAL_STATE_INVALID', message: 'active lifecycle target has no short id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, changed: true, completedSteps: [...completed], pendingSteps: [], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
442
+ if (spec.target !== 'active' && mappings.has(taskId)) return failed(request, { code: 'LIFECYCLE_FINAL_STATE_INVALID', message: 'non-active lifecycle target retained a short id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, changed: true, completedSteps: [...completed], pendingSteps: [], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
443
+ try { io.unlinkSync(journalPath); }
444
+ catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_CLEANUP_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, shortId, changed: true, completedSteps: [...completed], pendingSteps: [], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
445
+ return {
446
+ ...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
447
+ taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
448
+ task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, shortId,
449
+ timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
450
+ journalPath, completedSteps: [...completed], pendingSteps: []
451
+ };
452
+ }
453
+
454
+ export { lifecycleIntentCatalog, applyTaskLifecycle };
455
+ export type {
456
+ TaskLifecycleIntent, TaskLifecycleRequest, TaskLifecycleResult, TaskLifecycleOptions,
457
+ LifecycleError, LifecycleJournal, LifecycleStep
458
+ };