@fitlab-ai/agent-infra 0.9.11 → 0.9.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 (356) hide show
  1. package/README.md +13 -13
  2. package/README.zh-CN.md +13 -13
  3. package/dist/bin/internal-cli.js +29 -19
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
  5. package/dist/lib/agent-clients/next-steps.js +6 -1
  6. package/dist/lib/decide.js +3 -3
  7. package/dist/lib/defaults.json +4 -0
  8. package/dist/lib/git/workflow.js +43 -1
  9. package/dist/lib/init.js +1 -0
  10. package/dist/lib/internal/git-workflow.js +8 -1
  11. package/dist/lib/internal/platform-checks.js +3 -3
  12. package/dist/lib/internal/platform-comment.js +9 -9
  13. package/dist/lib/internal/platform-context.js +2 -2
  14. package/dist/lib/internal/platform-issue.js +8 -9
  15. package/dist/lib/internal/platform-pr-review.js +10 -10
  16. package/dist/lib/internal/platform-pr.js +68 -23
  17. package/dist/lib/internal/platform-release-notes.js +3 -3
  18. package/dist/lib/internal/pr-review-grade.js +11 -9
  19. package/dist/lib/internal/release-workflow.js +4 -4
  20. package/dist/lib/internal/sandbox-control.js +30 -4
  21. package/dist/lib/internal/task-activity.js +3 -3
  22. package/dist/lib/internal/task-artifact.js +76 -24
  23. package/dist/lib/internal/task-create.js +2 -2
  24. package/dist/lib/internal/task-delivery.js +59 -0
  25. package/dist/lib/internal/task-event.js +6 -3
  26. package/dist/lib/internal/task-finalization.js +2 -2
  27. package/dist/lib/internal/task-invalidation.js +60 -0
  28. package/dist/lib/internal/task-ledger.js +9 -6
  29. package/dist/lib/internal/task-lifecycle.js +2 -2
  30. package/dist/lib/internal/task-override.js +4 -4
  31. package/dist/lib/internal/task-review.js +3 -3
  32. package/dist/lib/internal/task-verify.js +6 -6
  33. package/dist/lib/internal/task-warning.js +3 -3
  34. package/dist/lib/platform/adapters.js +1 -3
  35. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  36. package/dist/lib/platform/completion-backfill.js +6 -5
  37. package/dist/lib/platform/context.js +70 -130
  38. package/dist/lib/platform/github-client.js +8 -2
  39. package/dist/lib/platform/github-provider.js +903 -0
  40. package/dist/lib/platform/in-label-sync.js +143 -0
  41. package/dist/lib/platform/issue-comments.js +94 -41
  42. package/dist/lib/platform/issues.js +335 -189
  43. package/dist/lib/platform/metadata-labels.js +6 -2
  44. package/dist/lib/platform/none-provider.js +28 -0
  45. package/dist/lib/platform/pr-change-report.js +551 -0
  46. package/dist/lib/platform/pr-checks.js +124 -106
  47. package/dist/lib/platform/pr-review.js +88 -72
  48. package/dist/lib/platform/pr-summary.js +523 -49
  49. package/dist/lib/platform/provider-bridge.js +45 -0
  50. package/dist/lib/platform/provider-contract.d.ts +487 -0
  51. package/dist/lib/platform/provider-contract.js +95 -0
  52. package/dist/lib/platform/provider-loader.js +251 -0
  53. package/dist/lib/platform/provider-validation.js +504 -0
  54. package/dist/lib/platform/pull-request-metadata.js +8 -6
  55. package/dist/lib/platform/pull-requests.js +1050 -502
  56. package/dist/lib/platform/release-notes.js +78 -55
  57. package/dist/lib/platform/releases.js +79 -50
  58. package/dist/lib/platform/resource-identity.d.ts +26 -0
  59. package/dist/lib/platform/resource-identity.js +92 -0
  60. package/dist/lib/platform/task-identities.js +31 -0
  61. package/dist/lib/platform/verification-required.js +22 -12
  62. package/dist/lib/platform/verification-sync.js +118 -239
  63. package/dist/lib/pr-review/evidence-grading.js +30 -9
  64. package/dist/lib/run/index.js +63 -23
  65. package/dist/lib/sandbox/config.js +5 -0
  66. package/dist/lib/sandbox/control/client.js +96 -15
  67. package/dist/lib/sandbox/control/executor.js +3 -3
  68. package/dist/lib/sandbox/control/lifecycle.js +29 -7
  69. package/dist/lib/sandbox/control/protocol.js +77 -0
  70. package/dist/lib/sandbox/control/server.js +398 -25
  71. package/dist/lib/sandbox/control/state.js +204 -1
  72. package/dist/lib/server/process-state.js +2 -2
  73. package/dist/lib/server/protocol.js +38 -1
  74. package/dist/lib/task/activity-log.js +8 -1
  75. package/dist/lib/task/artifact-lifecycle.js +35 -7
  76. package/dist/lib/task/capabilities.js +239 -0
  77. package/dist/lib/task/commands/status.js +32 -0
  78. package/dist/lib/task/commit-intent.js +100 -0
  79. package/dist/lib/task/commit-operation.js +207 -102
  80. package/dist/lib/task/control-authority.js +8 -5
  81. package/dist/lib/task/create-service.js +8 -8
  82. package/dist/lib/task/create.js +33 -7
  83. package/dist/lib/task/current-contract.js +5 -1
  84. package/dist/lib/task/decision-details.js +3 -48
  85. package/dist/lib/task/delivery-target.js +97 -0
  86. package/dist/lib/task/delivery.js +153 -0
  87. package/dist/lib/task/events.js +297 -17
  88. package/dist/lib/task/finalization.js +37 -13
  89. package/dist/lib/task/human-override.js +61 -36
  90. package/dist/lib/task/invalidation-command.js +86 -0
  91. package/dist/lib/task/invalidation.js +260 -0
  92. package/dist/lib/task/ledger-intents.js +37 -0
  93. package/dist/lib/task/lifecycle.js +106 -9
  94. package/dist/lib/task/local-artifact-finalization.js +374 -0
  95. package/dist/lib/task/orchestration.js +100 -151
  96. package/dist/lib/task/pr-delivery-fact.js +183 -0
  97. package/dist/lib/task/review-finalization.js +4 -8
  98. package/dist/lib/task/review-fingerprint.js +32 -2
  99. package/dist/lib/task/rework-intent.js +138 -0
  100. package/dist/lib/task/skill-scope.js +23 -0
  101. package/dist/lib/task/task-execution-lock.js +27 -17
  102. package/dist/lib/task/verification-engine.js +89 -38
  103. package/dist/lib/task/verification-review.js +1 -1
  104. package/dist/lib/task/verification.js +3 -3
  105. package/dist/lib/task/workflow-warning-intents.js +1 -1
  106. package/dist/lib/task/write.js +11 -0
  107. package/dist/lib/update.js +9 -0
  108. package/dist/lib/version.js +11 -1
  109. package/dist/lifecycle-build-manifest.json +3 -3
  110. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
  111. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
  112. package/lib/agent-clients/next-steps.ts +6 -1
  113. package/lib/decide.ts +3 -3
  114. package/lib/defaults.json +4 -0
  115. package/lib/git/workflow.ts +38 -1
  116. package/lib/init.ts +4 -1
  117. package/lib/internal/git-workflow.ts +7 -1
  118. package/lib/internal/platform-checks.ts +3 -3
  119. package/lib/internal/platform-comment.ts +8 -8
  120. package/lib/internal/platform-context.ts +2 -2
  121. package/lib/internal/platform-issue.ts +7 -8
  122. package/lib/internal/platform-pr-review.ts +9 -9
  123. package/lib/internal/platform-pr.ts +53 -21
  124. package/lib/internal/platform-release-notes.ts +3 -3
  125. package/lib/internal/pr-review-grade.ts +10 -8
  126. package/lib/internal/release-workflow.ts +4 -4
  127. package/lib/internal/sandbox-control.ts +27 -3
  128. package/lib/internal/task-activity.ts +3 -3
  129. package/lib/internal/task-artifact.ts +65 -22
  130. package/lib/internal/task-create.ts +2 -2
  131. package/lib/internal/task-delivery.ts +53 -0
  132. package/lib/internal/task-event.ts +6 -3
  133. package/lib/internal/task-finalization.ts +3 -3
  134. package/lib/internal/task-invalidation.ts +39 -0
  135. package/lib/internal/task-ledger.ts +11 -8
  136. package/lib/internal/task-lifecycle.ts +2 -2
  137. package/lib/internal/task-override.ts +6 -6
  138. package/lib/internal/task-review.ts +3 -3
  139. package/lib/internal/task-verify.ts +6 -6
  140. package/lib/internal/task-warning.ts +3 -3
  141. package/lib/platform/adapters.ts +3 -2
  142. package/lib/platform/change-request-git-evidence.ts +36 -9
  143. package/lib/platform/completion-backfill.ts +9 -8
  144. package/lib/platform/context.ts +97 -134
  145. package/lib/platform/github-client.ts +10 -3
  146. package/lib/platform/github-provider.ts +910 -0
  147. package/lib/platform/in-label-sync.ts +208 -0
  148. package/lib/platform/issue-comments.ts +97 -47
  149. package/lib/platform/issues.ts +344 -174
  150. package/lib/platform/metadata-labels.ts +5 -2
  151. package/lib/platform/none-provider.ts +35 -0
  152. package/lib/platform/pr-change-report.ts +645 -0
  153. package/lib/platform/pr-checks.ts +129 -107
  154. package/lib/platform/pr-review.ts +88 -85
  155. package/lib/platform/pr-summary.ts +562 -52
  156. package/lib/platform/provider-bridge.ts +82 -0
  157. package/lib/platform/provider-contract.ts +536 -0
  158. package/lib/platform/provider-loader.ts +307 -0
  159. package/lib/platform/provider-validation.ts +529 -0
  160. package/lib/platform/pull-request-metadata.ts +10 -7
  161. package/lib/platform/pull-requests.ts +1126 -505
  162. package/lib/platform/release-notes.ts +76 -59
  163. package/lib/platform/releases.ts +77 -43
  164. package/lib/platform/resource-identity.ts +121 -0
  165. package/lib/platform/task-identities.ts +40 -0
  166. package/lib/platform/types.ts +4 -2
  167. package/lib/platform/verification-required.ts +21 -13
  168. package/lib/platform/verification-sync.ts +118 -293
  169. package/lib/pr-review/evidence-grading.ts +31 -11
  170. package/lib/run/index.ts +57 -22
  171. package/lib/sandbox/config.ts +7 -0
  172. package/lib/sandbox/control/client.ts +105 -15
  173. package/lib/sandbox/control/executor.ts +4 -4
  174. package/lib/sandbox/control/lifecycle.ts +30 -8
  175. package/lib/sandbox/control/protocol.ts +113 -0
  176. package/lib/sandbox/control/server.ts +431 -23
  177. package/lib/sandbox/control/state.ts +251 -1
  178. package/lib/server/process-state.ts +2 -2
  179. package/lib/server/protocol.ts +36 -1
  180. package/lib/task/activity-log.ts +8 -0
  181. package/lib/task/artifact-lifecycle.ts +34 -8
  182. package/lib/task/capabilities.ts +262 -0
  183. package/lib/task/commands/status.ts +38 -0
  184. package/lib/task/commit-intent.ts +137 -0
  185. package/lib/task/commit-operation.ts +186 -88
  186. package/lib/task/control-authority.ts +11 -8
  187. package/lib/task/create-service.ts +8 -8
  188. package/lib/task/create.ts +34 -9
  189. package/lib/task/current-contract.ts +6 -1
  190. package/lib/task/decision-details.ts +8 -64
  191. package/lib/task/delivery-target.ts +130 -0
  192. package/lib/task/delivery.ts +160 -0
  193. package/lib/task/events.ts +296 -16
  194. package/lib/task/finalization.ts +50 -14
  195. package/lib/task/human-override.ts +73 -46
  196. package/lib/task/invalidation-command.ts +86 -0
  197. package/lib/task/invalidation.ts +332 -0
  198. package/lib/task/ledger-intents.ts +35 -1
  199. package/lib/task/lifecycle.ts +93 -4
  200. package/lib/task/local-artifact-finalization.ts +563 -0
  201. package/lib/task/orchestration.ts +110 -146
  202. package/lib/task/pr-delivery-fact.ts +237 -0
  203. package/lib/task/review-finalization.ts +4 -14
  204. package/lib/task/review-fingerprint.ts +33 -2
  205. package/lib/task/rework-intent.ts +165 -0
  206. package/lib/task/skill-scope.ts +24 -0
  207. package/lib/task/task-execution-lock.ts +23 -14
  208. package/lib/task/verification-engine.ts +95 -43
  209. package/lib/task/verification-review.ts +2 -0
  210. package/lib/task/verification.ts +4 -4
  211. package/lib/task/workflow-warning-intents.ts +1 -1
  212. package/lib/task/write.ts +19 -0
  213. package/lib/update.ts +11 -1
  214. package/lib/version.ts +15 -1
  215. package/package.json +7 -1
  216. package/templates/.agents/rules/README.en.md +4 -2
  217. package/templates/.agents/rules/README.zh-CN.md +4 -2
  218. package/templates/.agents/rules/create-issue.en.md +11 -2
  219. package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
  220. package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
  221. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  222. package/templates/.agents/rules/issue-sync.en.md +28 -5
  223. package/templates/.agents/rules/issue-sync.zh-CN.md +64 -5
  224. package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
  225. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
  226. package/templates/.agents/rules/local-artifact-repair.en.md +53 -0
  227. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +53 -0
  228. package/templates/.agents/rules/milestone-inference.en.md +12 -2
  229. package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
  230. package/templates/.agents/rules/pr-sync.en.md +28 -13
  231. package/templates/.agents/rules/pr-sync.zh-CN.md +39 -5
  232. package/templates/.agents/rules/release-commands.en.md +42 -3
  233. package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
  234. package/templates/.agents/rules/security-alerts.en.md +17 -2
  235. package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
  236. package/templates/.agents/rules/sync-content-generation.en.md +8 -0
  237. package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
  238. package/templates/.agents/rules/task-management.en.md +1 -1
  239. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  240. package/templates/.agents/rules/task-short-id.en.md +1 -4
  241. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  242. package/templates/.agents/scripts/security-alerts.github.sh +151 -0
  243. package/templates/.agents/scripts/security-alerts.sh +31 -0
  244. package/templates/.agents/skills/analyze-task/SKILL.en.md +14 -3
  245. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -3
  246. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  247. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  248. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  249. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  250. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  251. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  252. package/templates/.agents/skills/code-task/SKILL.en.md +32 -10
  253. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +35 -13
  254. package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
  255. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
  256. package/templates/.agents/skills/commit/SKILL.en.md +3 -3
  257. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
  258. package/templates/.agents/skills/commit/config/verify.json +1 -1
  259. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
  260. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
  261. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  262. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  263. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +4 -4
  264. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +4 -4
  265. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
  266. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
  267. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +7 -4
  268. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +7 -4
  269. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  270. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  271. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +5 -4
  272. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +5 -4
  273. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -13
  274. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +30 -13
  275. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
  276. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
  277. package/templates/.agents/skills/create-pr/SKILL.en.md +11 -10
  278. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +11 -11
  279. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  280. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  281. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  282. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  283. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  284. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  285. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  286. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  287. package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
  288. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
  289. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  290. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  291. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  292. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  293. package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
  294. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
  295. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
  296. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
  297. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
  298. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
  299. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -3
  300. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +11 -3
  301. package/templates/.agents/skills/review-analysis/SKILL.en.md +6 -5
  302. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +6 -5
  303. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
  304. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
  305. package/templates/.agents/skills/review-code/SKILL.en.md +10 -9
  306. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +10 -9
  307. package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
  308. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
  309. package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
  310. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
  311. package/templates/.agents/skills/review-plan/SKILL.en.md +6 -5
  312. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +6 -5
  313. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
  314. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
  315. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
  316. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
  317. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
  318. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
  319. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -2
  320. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -2
  321. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  322. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  323. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +4 -0
  324. package/templates/.agents/skills/watch-pr/SKILL.en.md +6 -6
  325. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +6 -6
  326. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  327. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  328. package/templates/.agents/templates/task.en.md +23 -2
  329. package/templates/.agents/templates/task.zh-CN.md +23 -2
  330. package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
  331. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
  332. package/templates/.agents/workflows/feature-development.en.yaml +9 -8
  333. package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
  334. package/templates/.agents/workflows/refactoring.en.yaml +9 -8
  335. package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
  336. package/templates/.github/workflows/pr-label.yml +7 -26
  337. package/templates/.agents/rules/create-issue.github.en.md +0 -14
  338. package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
  339. package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
  340. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
  341. package/templates/.agents/rules/issue-sync.github.en.md +0 -40
  342. package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
  343. package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
  344. package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
  345. package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
  346. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
  347. package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
  348. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
  349. package/templates/.agents/rules/pr-sync.github.en.md +0 -43
  350. package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
  351. package/templates/.agents/rules/release-commands.github.en.md +0 -44
  352. package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
  353. package/templates/.agents/rules/security-alerts.github.en.md +0 -43
  354. package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
  355. /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
  356. /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
@@ -0,0 +1,332 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ const INVALIDATION_HEADINGS = ['产物失效记录', 'Artifact Invalidation'] as const;
4
+ const OPERATION_COLUMNS = [
5
+ 'operation_id', 'source_family', 'source_artifact', 'source_round', 'source_sha256',
6
+ 'status', 'processed', 'total', 'created_at', 'updated_at', 'completed_at', 'error'
7
+ ] as const;
8
+ const TARGET_COLUMNS = [
9
+ 'target_id', 'operation_id', 'target_kind', 'target_family', 'target_artifact',
10
+ 'target_round', 'target_sha256', 'status', 'reason_code', 'updated_at'
11
+ ] as const;
12
+
13
+ type InvalidationStatus = 'pending' | 'in-progress' | 'completed';
14
+ type InvalidationTargetKind = 'artifact' | 'receipt' | 'approval' | 'reviewed-snapshot';
15
+ type InvalidationOperation = {
16
+ operationId: string;
17
+ sourceFamily: string;
18
+ sourceArtifact: string;
19
+ sourceRound: number;
20
+ sourceSha256: string;
21
+ status: InvalidationStatus;
22
+ processed: number;
23
+ total: number;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ completedAt: string;
27
+ error: string;
28
+ };
29
+ type InvalidationTarget = {
30
+ targetId: string;
31
+ operationId: string;
32
+ targetKind: InvalidationTargetKind;
33
+ targetFamily: string;
34
+ targetArtifact: string;
35
+ targetRound: number;
36
+ targetSha256: string;
37
+ status: InvalidationStatus;
38
+ reasonCode: string;
39
+ updatedAt: string;
40
+ };
41
+ type InvalidationDocument = {
42
+ operations: readonly InvalidationOperation[];
43
+ targets: readonly InvalidationTarget[];
44
+ };
45
+ type InvalidationParseResult =
46
+ | { ok: true; present: boolean; document: InvalidationDocument }
47
+ | { ok: false; code: 'TASK_INVALIDATION_INVALID'; message: string };
48
+ type InvalidationMutationResult = {
49
+ ok: true;
50
+ changed: boolean;
51
+ document: InvalidationDocument;
52
+ } | {
53
+ ok: false;
54
+ code: 'TASK_INVALIDATION_INVALID' | 'INVALIDATION_IDENTITY_CONFLICT';
55
+ message: string;
56
+ };
57
+
58
+ function invalid(message: string): InvalidationParseResult {
59
+ return { ok: false, code: 'TASK_INVALIDATION_INVALID', message };
60
+ }
61
+
62
+ function hashIdentity(value: unknown): string {
63
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16);
64
+ }
65
+
66
+ function operationIdFor(source: Pick<InvalidationOperation, 'sourceFamily' | 'sourceArtifact' | 'sourceRound' | 'sourceSha256'>): string {
67
+ return `INV-${hashIdentity([source.sourceFamily, source.sourceArtifact, source.sourceRound, source.sourceSha256])}`;
68
+ }
69
+
70
+ function targetIdFor(operationId: string, target: Pick<InvalidationTarget, 'targetKind' | 'targetFamily' | 'targetArtifact' | 'targetRound' | 'targetSha256'>): string {
71
+ return `INV-T-${hashIdentity([operationId, target.targetKind, target.targetFamily, target.targetArtifact, target.targetRound, target.targetSha256])}`;
72
+ }
73
+
74
+ function createInvalidationOperation(
75
+ source: Partial<Pick<InvalidationOperation, 'operationId'>> & Omit<InvalidationOperation, 'operationId' | 'status' | 'processed' | 'total' | 'completedAt' | 'error'>,
76
+ targets: readonly InvalidationTarget[] = []
77
+ ): InvalidationOperation {
78
+ const operationId = source.operationId ?? operationIdFor(source);
79
+ return {
80
+ operationId, sourceFamily: source.sourceFamily, sourceArtifact: source.sourceArtifact,
81
+ sourceRound: source.sourceRound, sourceSha256: source.sourceSha256,
82
+ status: 'pending', processed: 0, total: targets.length,
83
+ createdAt: source.createdAt, updatedAt: source.updatedAt, completedAt: '', error: ''
84
+ };
85
+ }
86
+
87
+ function lineCells(line: string): string[] | null {
88
+ const first = line.indexOf('|');
89
+ const last = line.lastIndexOf('|');
90
+ if (first < 0 || first === last || line.slice(0, first).trim() || line.slice(last + 1).trim()) return null;
91
+ const inner = line.slice(first + 1, last);
92
+ const cells: string[] = [];
93
+ let start = 0;
94
+ let escaped = false;
95
+ for (let index = 0; index < inner.length; index += 1) {
96
+ const char = inner[index]!;
97
+ if (char === '|' && !escaped) {
98
+ cells.push(inner.slice(start, index));
99
+ start = index + 1;
100
+ }
101
+ escaped = char === '\\' && !escaped;
102
+ if (char !== '\\') escaped = false;
103
+ }
104
+ cells.push(inner.slice(start));
105
+ return cells.map((cell) => cell.replace(/\\([\\|])/g, '$1').trim());
106
+ }
107
+
108
+ function isSeparator(cells: readonly string[], count: number): boolean {
109
+ return cells.length === count && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
110
+ }
111
+
112
+ function sectionBody(content: string): string | null {
113
+ const heading = INVALIDATION_HEADINGS.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
114
+ const match = new RegExp(`^##\\s+(${heading})\\s*$`, 'm').exec(content);
115
+ if (!match) return null;
116
+ const start = (match.index ?? 0) + match[0].length;
117
+ const rest = content.slice(start);
118
+ const end = rest.search(/^##\s+/m);
119
+ return rest.slice(0, end < 0 ? rest.length : end);
120
+ }
121
+
122
+ function parseTable(body: string, heading: string, columns: readonly string[]): Record<string, string>[] | null | false {
123
+ const marker = new RegExp(`^###\\s+${heading}\\s*$`, 'm').exec(body);
124
+ if (!marker) return null;
125
+ const start = (marker.index ?? 0) + marker[0].length;
126
+ const rest = body.slice(start);
127
+ const end = rest.search(/^###\s+/m);
128
+ const lines = rest.slice(0, end < 0 ? rest.length : end).split(/\r?\n/).filter((line) => line.trim());
129
+ const header = lines.length > 0 ? lineCells(lines[0]!) : null;
130
+ const separator = lines.length > 1 ? lineCells(lines[1]!) : null;
131
+ if (!header || !separator || header.length !== columns.length || !header.every((value, index) => value === columns[index]) || !isSeparator(separator, columns.length)) return false;
132
+ const rows: Record<string, string>[] = [];
133
+ for (const line of lines.slice(2)) {
134
+ const cells = lineCells(line);
135
+ if (!cells || cells.length !== columns.length) return false;
136
+ rows.push(Object.fromEntries(columns.map((column, index) => [column, cells[index]!])));
137
+ }
138
+ return rows;
139
+ }
140
+
141
+ function required(value: string, field: string): string {
142
+ if (!value) throw new Error(`${field} is required`);
143
+ return value;
144
+ }
145
+
146
+ function status(value: string, field: string): InvalidationStatus {
147
+ if (value !== 'pending' && value !== 'in-progress' && value !== 'completed') throw new Error(`${field} has an invalid status`);
148
+ return value;
149
+ }
150
+
151
+ function numberValue(value: string, field: string, allowZero = true): number {
152
+ const parsed = Number(value);
153
+ if (!Number.isInteger(parsed) || (allowZero ? parsed < 0 : parsed < 1)) throw new Error(`${field} must be an integer`);
154
+ return parsed;
155
+ }
156
+
157
+ function parseInvalidationDocument(content: string): InvalidationParseResult {
158
+ const body = sectionBody(content);
159
+ if (body === null) return { ok: true, present: false, document: { operations: [], targets: [] } };
160
+ try {
161
+ const operationRows = parseTable(body, 'Operations', OPERATION_COLUMNS);
162
+ const targetRows = parseTable(body, 'Targets', TARGET_COLUMNS);
163
+ if (operationRows === null || targetRows === null || operationRows === false || targetRows === false) {
164
+ return invalid('invalidation section must contain Operations and Targets tables');
165
+ }
166
+ const operations = operationRows.map((row) => ({
167
+ operationId: required(row.operation_id!, 'operation_id'), sourceFamily: required(row.source_family!, 'source_family'),
168
+ sourceArtifact: required(row.source_artifact!, 'source_artifact'), sourceRound: numberValue(row.source_round!, 'source_round', false),
169
+ sourceSha256: required(row.source_sha256!, 'source_sha256'), status: status(row.status!, 'operation status'),
170
+ processed: numberValue(row.processed!, 'processed'), total: numberValue(row.total!, 'total'),
171
+ createdAt: required(row.created_at!, 'created_at'), updatedAt: required(row.updated_at!, 'updated_at'),
172
+ completedAt: row.completed_at ?? '', error: row.error ?? ''
173
+ } satisfies InvalidationOperation));
174
+ const targets = targetRows.map((row) => ({
175
+ targetId: required(row.target_id!, 'target_id'), operationId: required(row.operation_id!, 'operation_id'),
176
+ targetKind: row.target_kind as InvalidationTargetKind, targetFamily: required(row.target_family!, 'target_family'),
177
+ targetArtifact: required(row.target_artifact!, 'target_artifact'), targetRound: numberValue(row.target_round!, 'target_round', false),
178
+ targetSha256: required(row.target_sha256!, 'target_sha256'), status: status(row.status!, 'target status'),
179
+ reasonCode: required(row.reason_code!, 'reason_code'), updatedAt: required(row.updated_at!, 'updated_at')
180
+ } satisfies InvalidationTarget));
181
+ const operationIds = new Set<string>();
182
+ for (const operation of operations) {
183
+ if (operationIds.has(operation.operationId)) throw new Error(`duplicate operation '${operation.operationId}'`);
184
+ operationIds.add(operation.operationId);
185
+ if (operation.operationId !== operationIdFor(operation)) throw new Error(`operation '${operation.operationId}' has a non-canonical identity`);
186
+ if (operation.processed > operation.total) throw new Error(`operation '${operation.operationId}' progress is invalid`);
187
+ if (!/^[a-f0-9]{64}$/.test(operation.sourceSha256)) throw new Error(`operation '${operation.operationId}' source hash is invalid`);
188
+ }
189
+ const targetIds = new Set<string>();
190
+ for (const target of targets) {
191
+ if (!['artifact', 'receipt', 'approval', 'reviewed-snapshot'].includes(target.targetKind)) throw new Error(`target '${target.targetId}' kind is invalid`);
192
+ if (targetIds.has(target.targetId)) throw new Error(`duplicate target '${target.targetId}'`);
193
+ targetIds.add(target.targetId);
194
+ if (!operationIds.has(target.operationId)) throw new Error(`target '${target.targetId}' references an unknown operation`);
195
+ if (target.targetId !== targetIdFor(target.operationId, target)) throw new Error(`target '${target.targetId}' has a non-canonical identity`);
196
+ if (!/^[a-f0-9]{64}$/.test(target.targetSha256)) throw new Error(`target '${target.targetId}' hash is invalid`);
197
+ }
198
+ for (const operation of operations) {
199
+ const scoped = targets.filter((target) => target.operationId === operation.operationId);
200
+ const completed = scoped.filter((target) => target.status === 'completed').length;
201
+ if (operation.total !== scoped.length || operation.processed !== completed) throw new Error(`operation '${operation.operationId}' progress does not match its targets`);
202
+ if (operation.status === 'completed' && (completed !== scoped.length || !operation.completedAt)) throw new Error(`completed operation '${operation.operationId}' has incomplete targets`);
203
+ if (operation.status === 'pending' && completed !== 0) throw new Error(`pending operation '${operation.operationId}' has completed targets`);
204
+ if (operation.status === 'in-progress' && (completed === scoped.length || (completed === 0 && !scoped.some((target) => target.status === 'in-progress')))) throw new Error(`in-progress operation '${operation.operationId}' has invalid progress`);
205
+ }
206
+ return { ok: true, present: true, document: { operations, targets } };
207
+ } catch (error) {
208
+ return invalid(error instanceof Error ? error.message : String(error));
209
+ }
210
+ }
211
+
212
+ function cell(value: string | number): string {
213
+ return String(value).replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' ');
214
+ }
215
+
216
+ function table(columns: readonly string[], rows: readonly (readonly (string | number)[])[]): string {
217
+ const header = `| ${columns.join(' | ')} |`;
218
+ const separator = `| ${columns.map(() => '---').join(' | ')} |`;
219
+ return [header, separator, ...rows.map((row) => `| ${row.map(cell).join(' | ')} |`)].join('\n');
220
+ }
221
+
222
+ function renderInvalidation(document: InvalidationDocument): string {
223
+ const operations = [...document.operations].sort((left, right) => left.operationId.localeCompare(right.operationId));
224
+ const targets = [...document.targets].sort((left, right) => left.targetId.localeCompare(right.targetId));
225
+ return [
226
+ '### Operations', '',
227
+ table(OPERATION_COLUMNS, operations.map((row) => [
228
+ row.operationId, row.sourceFamily, row.sourceArtifact, row.sourceRound, row.sourceSha256,
229
+ row.status, row.processed, row.total, row.createdAt, row.updatedAt, row.completedAt, row.error
230
+ ])), '',
231
+ '### Targets', '',
232
+ table(TARGET_COLUMNS, targets.map((row) => [
233
+ row.targetId, row.operationId, row.targetKind, row.targetFamily, row.targetArtifact,
234
+ row.targetRound, row.targetSha256, row.status, row.reasonCode, row.updatedAt
235
+ ]))
236
+ ].join('\n');
237
+ }
238
+
239
+ function reconcileInvalidation(document: InvalidationDocument, now: string, maxTargets = Number.POSITIVE_INFINITY): InvalidationMutationResult {
240
+ const nextTargets = document.targets.map((target) => {
241
+ if (target.status === 'completed' || maxTargets <= 0) return target;
242
+ maxTargets -= 1;
243
+ return { ...target, status: 'completed' as const, updatedAt: now };
244
+ });
245
+ const changed = nextTargets.some((target, index) => target !== document.targets[index]);
246
+ const nextOperations = document.operations.map((operation) => {
247
+ const scoped = nextTargets.filter((target) => target.operationId === operation.operationId);
248
+ const processed = scoped.filter((target) => target.status === 'completed').length;
249
+ const total = scoped.length;
250
+ const complete = total === processed;
251
+ const nextStatus: InvalidationStatus = complete ? 'completed' : processed > 0 ? 'in-progress' : 'pending';
252
+ if (operation.status === nextStatus && operation.processed === processed && operation.total === total && (!complete || operation.completedAt)) return operation;
253
+ return {
254
+ ...operation, status: nextStatus, processed, total, updatedAt: changed ? now : operation.updatedAt,
255
+ completedAt: complete ? (operation.completedAt || now) : ''
256
+ };
257
+ });
258
+ const operationChanged = nextOperations.some((operation, index) => operation !== document.operations[index]);
259
+ return { ok: true, changed: changed || operationChanged, document: { operations: nextOperations, targets: nextTargets } };
260
+ }
261
+
262
+ function upsertInvalidation(
263
+ document: InvalidationDocument,
264
+ operation: InvalidationOperation,
265
+ targets: readonly InvalidationTarget[]
266
+ ): InvalidationMutationResult {
267
+ const existing = document.operations.find((candidate) => candidate.operationId === operation.operationId);
268
+ if (existing && JSON.stringify(existing) !== JSON.stringify(operation)) {
269
+ return { ok: false, code: 'INVALIDATION_IDENTITY_CONFLICT', message: `operation '${operation.operationId}' identity conflicts with existing state` };
270
+ }
271
+ const existingTargets = document.targets.filter((target) => target.operationId === operation.operationId);
272
+ for (const target of targets) {
273
+ const old = existingTargets.find((candidate) => candidate.targetId === target.targetId);
274
+ if (old && JSON.stringify(old) !== JSON.stringify(target)) {
275
+ return { ok: false, code: 'INVALIDATION_IDENTITY_CONFLICT', message: `target '${target.targetId}' identity conflicts with existing state` };
276
+ }
277
+ }
278
+ if (existing) return { ok: true, changed: false, document };
279
+ return {
280
+ ok: true, changed: true,
281
+ document: { operations: [...document.operations, operation], targets: [...document.targets, ...targets] }
282
+ };
283
+ }
284
+
285
+ function invalidationBlocks(document: InvalidationDocument): boolean {
286
+ return document.operations.some((operation) => operation.status !== 'completed')
287
+ || document.targets.some((target) => target.status !== 'completed');
288
+ }
289
+
290
+ function isArtifactInvalidated(document: InvalidationDocument, family: string, artifact: string): boolean {
291
+ return document.targets.some((target) =>
292
+ target.status === 'completed'
293
+ && target.targetKind === 'artifact'
294
+ && target.targetFamily === family
295
+ && target.targetArtifact === artifact
296
+ );
297
+ }
298
+
299
+ function invalidationMutation(content: string, document: InvalidationDocument) {
300
+ const existing = sectionBody(content) !== null;
301
+ return {
302
+ kind: 'section' as const,
303
+ aliases: INVALIDATION_HEADINGS,
304
+ heading: existing && /^##\s+Artifact Invalidation\s*$/m.test(content) ? INVALIDATION_HEADINGS[1] : INVALIDATION_HEADINGS[0],
305
+ body: renderInvalidation(document)
306
+ };
307
+ }
308
+
309
+ export {
310
+ INVALIDATION_HEADINGS,
311
+ OPERATION_COLUMNS,
312
+ TARGET_COLUMNS,
313
+ createInvalidationOperation,
314
+ isArtifactInvalidated,
315
+ invalidationBlocks,
316
+ invalidationMutation,
317
+ operationIdFor,
318
+ parseInvalidationDocument,
319
+ reconcileInvalidation,
320
+ renderInvalidation,
321
+ targetIdFor,
322
+ upsertInvalidation
323
+ };
324
+ export type {
325
+ InvalidationDocument,
326
+ InvalidationMutationResult,
327
+ InvalidationOperation,
328
+ InvalidationParseResult,
329
+ InvalidationStatus,
330
+ InvalidationTarget,
331
+ InvalidationTargetKind
332
+ };
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
3
4
 
4
5
  import {
5
6
  declareImplementationInput,
@@ -13,6 +14,8 @@ import { resolveTaskRef } from './resolve-ref.ts';
13
14
  import { writeTask } from './write.ts';
14
15
  import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
15
16
  import { allowsManualOverride } from './guard-override.ts';
17
+ import { parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } from './rework-intent.ts';
18
+ import type { ReworkIntent, ReworkTarget } from './rework-intent.ts';
16
19
 
17
20
  type ReviewSeverity = 'blocker' | 'major' | 'minor';
18
21
  type ExecutorResponse = 'accepted' | 'adjusted' | 'refuted' | 'cannot-judge';
@@ -22,7 +25,8 @@ type LedgerIntent =
22
25
  | { kind: 'finding-respond'; taskRef: string; id: string; round: number; status: ExecutorResponse; evidence: string; dryRun?: boolean }
23
26
  | { kind: 'finding-review'; taskRef: string; id: string; status: ReviewDisposition; evidence: string; needsImplementation?: boolean; dryRun?: boolean }
24
27
  | { kind: 'decision-next-id'; taskRef: string }
25
- | { kind: 'decision-upsert'; taskRef: string; id: string; stage: ReviewStage; artifact: string; needsImplementation?: boolean; dryRun?: boolean };
28
+ | { kind: 'decision-upsert'; taskRef: string; id: string; stage: ReviewStage; artifact: string; needsImplementation?: boolean; dryRun?: boolean }
29
+ | { kind: 'rework-intent-upsert'; taskRef: string; intentId: string; findingId: string; sourceArtifact: string; sourceSha256: string; target: ReworkTarget; dryRun?: boolean };
26
30
 
27
31
  type LedgerIntentError = { code: string; message: string };
28
32
  type LedgerIntentResult = {
@@ -136,6 +140,36 @@ function applyLedgerIntent(intent: LedgerIntent, options: TaskWriteOptions = {})
136
140
  return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: nextHdId(rows), before: null, after: null, operations: [], error: null };
137
141
  }
138
142
 
143
+ if (intent.kind === 'rework-intent-upsert') {
144
+ if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !['analysis', 'plan', 'code'].includes(intent.target) || !/^[a-f0-9]{64}$/.test(intent.sourceSha256)) {
145
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity, target, or source hash is invalid', resolved.taskId, intent.intentId);
146
+ }
147
+ const finding = rows.find((row) => row.id === intent.findingId);
148
+ if (!finding || finding.severity === 'decision') return failed(intent, 'LEDGER_NOT_FOUND', `finding '${intent.findingId}' was not found`, resolved.taskId, intent.findingId);
149
+ const sourceFromEvidence = finding.evidence.split('#')[0];
150
+ if (sourceFromEvidence !== intent.sourceArtifact || !REVIEW_ARTIFACT[finding.stage as ReviewStage]?.test(intent.sourceArtifact)) {
151
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact does not match finding evidence', resolved.taskId, intent.intentId);
152
+ }
153
+ try {
154
+ const actualHash = createHash('sha256').update(fs.readFileSync(path.join(resolved.taskDir, intent.sourceArtifact))).digest('hex');
155
+ if (actualHash !== intent.sourceSha256) return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact hash does not match', resolved.taskId, intent.intentId);
156
+ const parsed = parseReworkIntentDocument(content);
157
+ if (!parsed.ok) return failed(intent, parsed.code, parsed.message, resolved.taskId, intent.intentId);
158
+ const nextIntent: ReworkIntent = {
159
+ intentId: intent.intentId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact,
160
+ sourceSha256: intent.sourceSha256, target: intent.target, status: 'pending',
161
+ declaredAt: new Date().toISOString(), consumedAt: ''
162
+ };
163
+ const next = upsertReworkIntent(parsed.intents, nextIntent);
164
+ if (!next.changed) return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
165
+ const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next.intents)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
166
+ if (result.status === 'failed') return failed(intent, result.error.code, result.error.message, result.taskId, intent.intentId);
167
+ return { status: result.status, changed: result.changed, intent: intent.kind, taskId: result.taskId, entityId: intent.intentId, before: null, after: null, operations: result.operations, error: null };
168
+ } catch (error) {
169
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', error instanceof Error ? error.message : String(error), resolved.taskId, intent.intentId);
170
+ }
171
+ }
172
+
139
173
  let before: LedgerRow | null = null;
140
174
  let after: LedgerRow;
141
175
  if (intent.kind === 'finding-upsert') {
@@ -20,6 +20,7 @@ const lifecycleIntentCatalog = [
20
20
  ] as const;
21
21
  const lifecycleFailureCatalog = [
22
22
  'LIFECYCLE_DIRECTORY_RENAME_FAILED',
23
+ 'LIFECYCLE_DIRECTORY_COPY_FAILED',
23
24
  'LIFECYCLE_DOCUMENT_INVALID',
24
25
  'LIFECYCLE_FINAL_STATE_INVALID',
25
26
  'LIFECYCLE_IDENTITY_CONFLICT',
@@ -336,6 +337,62 @@ function parseJournal(content: string): LifecycleJournal {
336
337
  return value as LifecycleJournal;
337
338
  }
338
339
 
340
+ type DirectoryEntry = Readonly<{ relativePath: string; kind: 'file' | 'directory'; size: number; digest: string }>;
341
+
342
+ function directoryManifest(root: string): DirectoryEntry[] {
343
+ const entries: DirectoryEntry[] = [];
344
+ const visit = (current: string, relative = '') => {
345
+ for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
346
+ const relativePath = relative ? path.join(relative, entry.name) : entry.name;
347
+ const absolute = path.join(current, entry.name);
348
+ const stat = fs.lstatSync(absolute);
349
+ if (stat.isSymbolicLink()) throw new Error(`symbolic link is not allowed in lifecycle directory: ${relativePath}`);
350
+ if (entry.isDirectory()) {
351
+ entries.push({ relativePath, kind: 'directory', size: 0, digest: '' });
352
+ visit(absolute, relativePath);
353
+ } else if (entry.isFile()) {
354
+ const digest = createHash('sha256').update(fs.readFileSync(absolute)).digest('hex');
355
+ entries.push({ relativePath, kind: 'file', size: stat.size, digest });
356
+ } else {
357
+ throw new Error(`unsupported lifecycle directory entry: ${relativePath}`);
358
+ }
359
+ }
360
+ };
361
+ visit(root);
362
+ return entries;
363
+ }
364
+
365
+ function copyDirectoryVerified(source: string, target: string): void {
366
+ if (fs.existsSync(target)) throw new Error(`target directory already exists: ${target}`);
367
+ const copy = (currentSource: string, currentTarget: string) => {
368
+ fs.mkdirSync(currentTarget);
369
+ for (const entry of fs.readdirSync(currentSource, { withFileTypes: true })) {
370
+ const sourcePath = path.join(currentSource, entry.name);
371
+ const targetPath = path.join(currentTarget, entry.name);
372
+ const stat = fs.lstatSync(sourcePath);
373
+ if (stat.isSymbolicLink()) throw new Error(`symbolic link is not allowed in lifecycle directory: ${entry.name}`);
374
+ if (entry.isDirectory()) copy(sourcePath, targetPath);
375
+ else if (entry.isFile()) fs.copyFileSync(sourcePath, targetPath);
376
+ else throw new Error(`unsupported lifecycle directory entry: ${entry.name}`);
377
+ }
378
+ };
379
+ copy(source, target);
380
+ const sourceManifest = directoryManifest(source);
381
+ const targetManifest = directoryManifest(target);
382
+ if (JSON.stringify(sourceManifest) !== JSON.stringify(targetManifest)) {
383
+ throw new Error('copied lifecycle directory failed manifest verification');
384
+ }
385
+ }
386
+
387
+ function removeCopiedSource(source: string, target: string): void {
388
+ const sourceManifest = directoryManifest(source);
389
+ const targetManifest = directoryManifest(target);
390
+ if (JSON.stringify(sourceManifest) !== JSON.stringify(targetManifest)) {
391
+ throw new Error('lifecycle source and target diverged before source cleanup');
392
+ }
393
+ fs.rmSync(source, { recursive: true, force: false });
394
+ }
395
+
339
396
  function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
340
397
  const normalized = normalizedRequest(requestInput);
341
398
  if ('code' in normalized) return failed(requestInput, normalized);
@@ -386,7 +443,7 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
386
443
  if (!spec.sources.includes(sourceState) && !existingJournal && !allowsManualOverride(options, 'LIFECYCLE_SOURCE_INVALID')) {
387
444
  return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
388
445
  }
389
- if (sourcePath !== targetPath && fs.existsSync(targetPath)) {
446
+ if (sourcePath !== targetPath && fs.existsSync(targetPath) && !existingJournal) {
390
447
  return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
391
448
  }
392
449
  const content = io.readFileSync(currentTaskFile);
@@ -498,13 +555,45 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
498
555
  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 });
499
556
  try { (options.directoryRenameSync ?? io.renameSync)(sourcePath, targetPath); }
500
557
  catch (error) {
558
+ if ((error as { code?: string }).code === 'EXDEV') {
559
+ try {
560
+ copyDirectoryVerified(sourcePath, targetPath);
561
+ completed.add('directory-moved');
562
+ journal.completedSteps = [...completed];
563
+ const copiedJournalPath = path.join(targetPath, '.task-lifecycle.json');
564
+ writeJournal(initialJournalPath, journal, io);
565
+ writeJournal(copiedJournalPath, journal, io);
566
+ removeCopiedSource(sourcePath, targetPath);
567
+ journalPath = copiedJournalPath;
568
+ } catch (copyError) {
569
+ if (fs.existsSync(targetPath)) fs.rmSync(targetPath, { recursive: true, force: false });
570
+ const failure = { code: 'LIFECYCLE_DIRECTORY_COPY_FAILED', message: String(copyError) } as const;
571
+ rememberJournalFailure(initialJournalPath, journal, io, failure);
572
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
573
+ }
574
+ } else {
501
575
  const failure = { code: 'LIFECYCLE_DIRECTORY_RENAME_FAILED', message: String(error) } as const;
502
576
  rememberJournalFailure(journalPath, journal, io, failure);
503
577
  return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
578
+ }
579
+ }
580
+ if (completed.has('directory-moved') && !fs.existsSync(sourcePath)) {
581
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
582
+ } else if (!completed.has('directory-moved')) {
583
+ completed.add('directory-moved'); journal.completedSteps = [...completed]; journalPath = path.join(targetPath, '.task-lifecycle.json');
584
+ try { writeJournal(journalPath, journal, io); }
585
+ 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 }); }
586
+ }
587
+ }
588
+ if (completed.has('directory-moved') && sourcePath !== targetPath && fs.existsSync(sourcePath) && fs.existsSync(targetPath)) {
589
+ try {
590
+ removeCopiedSource(sourcePath, targetPath);
591
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
592
+ } catch (error) {
593
+ const failure = { code: 'LIFECYCLE_DIRECTORY_COPY_FAILED', message: String(error) } as const;
594
+ rememberJournalFailure(journalPath, journal, io, failure);
595
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
504
596
  }
505
- completed.add('directory-moved'); journal.completedSteps = [...completed]; journalPath = path.join(targetPath, '.task-lifecycle.json');
506
- try { writeJournal(journalPath, journal, io); }
507
- 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 }); }
508
597
  }
509
598
  let shortId: TaskLifecycleResult['shortId'] = { effect: 'unchanged', shortId: null, changed: false };
510
599
  if (!completed.has('registry-committed')) {