@fitlab-ai/agent-infra 0.9.12 → 0.9.14

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 (402) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/bin/cli.ts +26 -9
  4. package/dist/bin/cli.js +173 -157
  5. package/dist/bin/internal-cli.js +66 -40
  6. package/dist/lib/agent-client.js +5 -0
  7. package/dist/lib/agent-clients/next-steps.js +6 -1
  8. package/dist/lib/decide.js +3 -3
  9. package/dist/lib/internal/agent-client.js +5 -2
  10. package/dist/lib/internal/cli-route-inventory.js +152 -0
  11. package/dist/lib/internal/codex-lifecycle.js +9 -6
  12. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  13. package/dist/lib/internal/git-workflow.js +10 -6
  14. package/dist/lib/internal/platform-checks.js +19 -7
  15. package/dist/lib/internal/platform-comment.js +25 -13
  16. package/dist/lib/internal/platform-context.js +6 -3
  17. package/dist/lib/internal/platform-issue.js +20 -13
  18. package/dist/lib/internal/platform-metadata.js +88 -0
  19. package/dist/lib/internal/platform-pr-review.js +26 -13
  20. package/dist/lib/internal/platform-pr.js +84 -31
  21. package/dist/lib/internal/platform-release-notes.js +9 -6
  22. package/dist/lib/internal/platform-security.js +93 -0
  23. package/dist/lib/internal/pr-review-grade.js +25 -12
  24. package/dist/lib/internal/release-workflow.js +23 -9
  25. package/dist/lib/internal/sandbox-control.js +50 -7
  26. package/dist/lib/internal/task-activity.js +16 -7
  27. package/dist/lib/internal/task-artifact.js +235 -24
  28. package/dist/lib/internal/task-context.js +4 -1
  29. package/dist/lib/internal/task-create.js +50 -17
  30. package/dist/lib/internal/task-delivery.js +4 -1
  31. package/dist/lib/internal/task-event.js +10 -4
  32. package/dist/lib/internal/task-finalization.js +6 -3
  33. package/dist/lib/internal/task-invalidation.js +63 -0
  34. package/dist/lib/internal/task-ledger.js +20 -6
  35. package/dist/lib/internal/task-lifecycle.js +6 -3
  36. package/dist/lib/internal/task-operation-registry.js +421 -0
  37. package/dist/lib/internal/task-orchestration.js +12 -0
  38. package/dist/lib/internal/task-override.js +14 -7
  39. package/dist/lib/internal/task-qualification.js +90 -0
  40. package/dist/lib/internal/task-review.js +7 -4
  41. package/dist/lib/internal/task-short-id.js +11 -1
  42. package/dist/lib/internal/task-snapshot.js +4 -1
  43. package/dist/lib/internal/task-validate.js +7 -2
  44. package/dist/lib/internal/task-verify.js +11 -7
  45. package/dist/lib/internal/task-warning.js +11 -4
  46. package/dist/lib/platform/adapters.js +1 -3
  47. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  48. package/dist/lib/platform/comment-safety.js +226 -0
  49. package/dist/lib/platform/completion-backfill.js +6 -5
  50. package/dist/lib/platform/context.js +70 -130
  51. package/dist/lib/platform/github-client.js +1 -1
  52. package/dist/lib/platform/github-provider.js +1130 -0
  53. package/dist/lib/platform/in-label-sync.js +143 -0
  54. package/dist/lib/platform/issue-comments.js +237 -72
  55. package/dist/lib/platform/issues.js +335 -189
  56. package/dist/lib/platform/metadata-labels.js +6 -2
  57. package/dist/lib/platform/none-provider.js +28 -0
  58. package/dist/lib/platform/pr-change-report.js +545 -0
  59. package/dist/lib/platform/pr-checks.js +119 -104
  60. package/dist/lib/platform/pr-review.js +88 -72
  61. package/dist/lib/platform/pr-summary.js +541 -50
  62. package/dist/lib/platform/provider-bridge.js +45 -0
  63. package/dist/lib/platform/provider-contract.d.ts +553 -0
  64. package/dist/lib/platform/provider-contract.js +99 -0
  65. package/dist/lib/platform/provider-loader.js +251 -0
  66. package/dist/lib/platform/provider-validation.js +577 -0
  67. package/dist/lib/platform/pull-request-metadata.js +8 -6
  68. package/dist/lib/platform/pull-requests.js +734 -543
  69. package/dist/lib/platform/release-notes.js +78 -55
  70. package/dist/lib/platform/releases.js +79 -50
  71. package/dist/lib/platform/repository-metadata.js +377 -0
  72. package/dist/lib/platform/resource-identity.d.ts +26 -0
  73. package/dist/lib/platform/resource-identity.js +92 -0
  74. package/dist/lib/platform/security-alerts.js +155 -0
  75. package/dist/lib/platform/task-identities.js +31 -0
  76. package/dist/lib/platform/verification-required.js +11 -12
  77. package/dist/lib/platform/verification-sync.js +129 -248
  78. package/dist/lib/pr-review/evidence-grading.js +19 -6
  79. package/dist/lib/process-data/index.js +7 -0
  80. package/dist/lib/run/index.js +68 -23
  81. package/dist/lib/sandbox/commands/control-status.js +22 -0
  82. package/dist/lib/sandbox/commands/create.js +32 -6
  83. package/dist/lib/sandbox/commands/ls.js +5 -0
  84. package/dist/lib/sandbox/commands/rm.js +827 -120
  85. package/dist/lib/sandbox/commands/show.js +16 -0
  86. package/dist/lib/sandbox/control/client.js +174 -19
  87. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  88. package/dist/lib/sandbox/control/container-identity.js +28 -31
  89. package/dist/lib/sandbox/control/executor.js +151 -16
  90. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  91. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  92. package/dist/lib/sandbox/control/protocol.js +77 -0
  93. package/dist/lib/sandbox/control/server.js +677 -34
  94. package/dist/lib/sandbox/control/state.js +209 -4
  95. package/dist/lib/sandbox/control/task-view.js +183 -0
  96. package/dist/lib/sandbox/engines/authority.js +289 -0
  97. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  98. package/dist/lib/sandbox/index.js +5 -0
  99. package/dist/lib/sandbox/recovery.js +21 -1
  100. package/dist/lib/sandbox/workspace-view.js +2 -1
  101. package/dist/lib/sandbox/worktree-safety.js +4 -1
  102. package/dist/lib/server/index.js +8 -0
  103. package/dist/lib/server/protocol.js +38 -1
  104. package/dist/lib/task/activity-log.js +8 -1
  105. package/dist/lib/task/artifact-lifecycle.js +75 -18
  106. package/dist/lib/task/artifact-operations.js +520 -0
  107. package/dist/lib/task/artifact-repair-intent.js +59 -0
  108. package/dist/lib/task/artifact-schema.js +135 -0
  109. package/dist/lib/task/capabilities.js +310 -0
  110. package/dist/lib/task/commands/status.js +32 -0
  111. package/dist/lib/task/commit-operation.js +12 -1
  112. package/dist/lib/task/control-authority.js +12 -5
  113. package/dist/lib/task/create-service.js +111 -12
  114. package/dist/lib/task/create.js +25 -0
  115. package/dist/lib/task/current-contract.js +5 -1
  116. package/dist/lib/task/decision-details.js +1 -1
  117. package/dist/lib/task/events.js +416 -17
  118. package/dist/lib/task/finalization.js +37 -13
  119. package/dist/lib/task/human-override.js +58 -36
  120. package/dist/lib/task/index.js +11 -3
  121. package/dist/lib/task/invalidation-command.js +86 -0
  122. package/dist/lib/task/invalidation.js +260 -0
  123. package/dist/lib/task/ledger-intents.js +37 -0
  124. package/dist/lib/task/local-artifact-finalization.js +299 -0
  125. package/dist/lib/task/orchestration.js +240 -144
  126. package/dist/lib/task/pr-delivery-fact.js +102 -101
  127. package/dist/lib/task/qualification-audit.js +490 -0
  128. package/dist/lib/task/qualification-confirmation.js +109 -0
  129. package/dist/lib/task/qualification-intents.js +148 -0
  130. package/dist/lib/task/review-artifacts.js +22 -23
  131. package/dist/lib/task/review-finalization.js +121 -4
  132. package/dist/lib/task/review-fingerprint.js +24 -2
  133. package/dist/lib/task/rework-intent.js +138 -0
  134. package/dist/lib/task/sections.js +6 -4
  135. package/dist/lib/task/skill-scope.js +23 -0
  136. package/dist/lib/task/task-execution-lock.js +27 -17
  137. package/dist/lib/task/verification-config.js +11 -0
  138. package/dist/lib/task/verification-engine.js +93 -16
  139. package/dist/lib/task/verification.js +3 -3
  140. package/dist/lib/task/workflow-warning-intents.js +1 -1
  141. package/dist/lib/task/write.js +11 -0
  142. package/dist/lib/version.js +11 -1
  143. package/dist/lifecycle-build-manifest.json +3 -3
  144. package/lib/agent-client.ts +9 -0
  145. package/lib/agent-clients/next-steps.ts +6 -1
  146. package/lib/decide.ts +3 -3
  147. package/lib/init.ts +1 -1
  148. package/lib/internal/agent-client.ts +4 -2
  149. package/lib/internal/cli-route-inventory.ts +140 -0
  150. package/lib/internal/codex-lifecycle.ts +8 -6
  151. package/lib/internal/codex-sandbox-controller.ts +7 -2
  152. package/lib/internal/git-workflow.ts +9 -6
  153. package/lib/internal/platform-checks.ts +14 -6
  154. package/lib/internal/platform-comment.ts +20 -12
  155. package/lib/internal/platform-context.ts +5 -3
  156. package/lib/internal/platform-issue.ts +17 -11
  157. package/lib/internal/platform-metadata.ts +70 -0
  158. package/lib/internal/platform-pr-review.ts +23 -12
  159. package/lib/internal/platform-pr.ts +65 -27
  160. package/lib/internal/platform-release-notes.ts +8 -6
  161. package/lib/internal/platform-security.ts +83 -0
  162. package/lib/internal/pr-review-grade.ts +20 -11
  163. package/lib/internal/release-workflow.ts +21 -9
  164. package/lib/internal/sandbox-control.ts +44 -6
  165. package/lib/internal/task-activity.ts +15 -7
  166. package/lib/internal/task-artifact.ts +204 -22
  167. package/lib/internal/task-context.ts +3 -1
  168. package/lib/internal/task-create.ts +78 -17
  169. package/lib/internal/task-delivery.ts +3 -1
  170. package/lib/internal/task-event.ts +9 -4
  171. package/lib/internal/task-finalization.ts +6 -4
  172. package/lib/internal/task-invalidation.ts +41 -0
  173. package/lib/internal/task-ledger.ts +21 -8
  174. package/lib/internal/task-lifecycle.ts +5 -3
  175. package/lib/internal/task-operation-registry.ts +482 -0
  176. package/lib/internal/task-orchestration.ts +12 -0
  177. package/lib/internal/task-override.ts +15 -9
  178. package/lib/internal/task-qualification.ts +61 -0
  179. package/lib/internal/task-review.ts +6 -4
  180. package/lib/internal/task-short-id.ts +10 -1
  181. package/lib/internal/task-snapshot.ts +3 -1
  182. package/lib/internal/task-validate.ts +6 -2
  183. package/lib/internal/task-verify.ts +10 -7
  184. package/lib/internal/task-warning.ts +10 -4
  185. package/lib/platform/adapters.ts +3 -2
  186. package/lib/platform/change-request-git-evidence.ts +36 -9
  187. package/lib/platform/comment-safety.ts +250 -0
  188. package/lib/platform/completion-backfill.ts +9 -8
  189. package/lib/platform/context.ts +97 -134
  190. package/lib/platform/github-client.ts +2 -2
  191. package/lib/platform/github-provider.ts +1132 -0
  192. package/lib/platform/in-label-sync.ts +208 -0
  193. package/lib/platform/issue-comments.ts +255 -80
  194. package/lib/platform/issues.ts +344 -174
  195. package/lib/platform/metadata-labels.ts +5 -2
  196. package/lib/platform/none-provider.ts +35 -0
  197. package/lib/platform/pr-change-report.ts +639 -0
  198. package/lib/platform/pr-checks.ts +126 -106
  199. package/lib/platform/pr-review.ts +88 -85
  200. package/lib/platform/pr-summary.ts +581 -53
  201. package/lib/platform/provider-bridge.ts +82 -0
  202. package/lib/platform/provider-contract.ts +611 -0
  203. package/lib/platform/provider-loader.ts +307 -0
  204. package/lib/platform/provider-validation.ts +611 -0
  205. package/lib/platform/pull-request-metadata.ts +10 -7
  206. package/lib/platform/pull-requests.ts +781 -550
  207. package/lib/platform/release-notes.ts +76 -59
  208. package/lib/platform/releases.ts +77 -43
  209. package/lib/platform/repository-metadata.ts +453 -0
  210. package/lib/platform/resource-identity.ts +121 -0
  211. package/lib/platform/security-alerts.ts +191 -0
  212. package/lib/platform/task-identities.ts +40 -0
  213. package/lib/platform/types.ts +4 -2
  214. package/lib/platform/verification-required.ts +11 -13
  215. package/lib/platform/verification-sync.ts +128 -304
  216. package/lib/pr-review/evidence-grading.ts +25 -8
  217. package/lib/process-data/index.ts +7 -0
  218. package/lib/run/index.ts +62 -22
  219. package/lib/sandbox/commands/control-status.ts +26 -0
  220. package/lib/sandbox/commands/create.ts +46 -6
  221. package/lib/sandbox/commands/ls.ts +7 -0
  222. package/lib/sandbox/commands/rm.ts +1000 -87
  223. package/lib/sandbox/commands/show.ts +16 -0
  224. package/lib/sandbox/control/client.ts +203 -19
  225. package/lib/sandbox/control/completed-reentry.ts +98 -0
  226. package/lib/sandbox/control/container-identity.ts +27 -43
  227. package/lib/sandbox/control/executor.ts +180 -15
  228. package/lib/sandbox/control/lifecycle.ts +629 -17
  229. package/lib/sandbox/control/native-file-lock.ts +209 -0
  230. package/lib/sandbox/control/protocol.ts +118 -2
  231. package/lib/sandbox/control/server.ts +724 -34
  232. package/lib/sandbox/control/state.ts +257 -4
  233. package/lib/sandbox/control/task-view.ts +240 -0
  234. package/lib/sandbox/engines/authority.ts +365 -0
  235. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  236. package/lib/sandbox/index.ts +7 -0
  237. package/lib/sandbox/recovery.ts +21 -1
  238. package/lib/sandbox/workspace-view.ts +9 -3
  239. package/lib/sandbox/worktree-safety.ts +4 -1
  240. package/lib/server/index.ts +10 -0
  241. package/lib/server/protocol.ts +36 -1
  242. package/lib/task/activity-log.ts +8 -0
  243. package/lib/task/artifact-lifecycle.ts +70 -22
  244. package/lib/task/artifact-operations.ts +626 -0
  245. package/lib/task/artifact-repair-intent.ts +71 -0
  246. package/lib/task/artifact-schema.ts +192 -0
  247. package/lib/task/capabilities.ts +331 -0
  248. package/lib/task/commands/status.ts +38 -0
  249. package/lib/task/commit-operation.ts +12 -1
  250. package/lib/task/control-authority.ts +22 -9
  251. package/lib/task/create-service.ts +139 -14
  252. package/lib/task/create.ts +27 -0
  253. package/lib/task/current-contract.ts +6 -1
  254. package/lib/task/decision-details.ts +6 -2
  255. package/lib/task/events.ts +392 -16
  256. package/lib/task/finalization.ts +50 -14
  257. package/lib/task/human-override.ts +70 -46
  258. package/lib/task/index.ts +16 -3
  259. package/lib/task/invalidation-command.ts +86 -0
  260. package/lib/task/invalidation.ts +332 -0
  261. package/lib/task/ledger-intents.ts +35 -1
  262. package/lib/task/local-artifact-finalization.ts +432 -0
  263. package/lib/task/orchestration.ts +223 -133
  264. package/lib/task/pr-delivery-fact.ts +131 -148
  265. package/lib/task/qualification-audit.ts +599 -0
  266. package/lib/task/qualification-confirmation.ts +117 -0
  267. package/lib/task/qualification-intents.ts +150 -0
  268. package/lib/task/review-artifacts.ts +21 -22
  269. package/lib/task/review-finalization.ts +133 -4
  270. package/lib/task/review-fingerprint.ts +23 -2
  271. package/lib/task/rework-intent.ts +165 -0
  272. package/lib/task/sections.ts +6 -4
  273. package/lib/task/skill-scope.ts +24 -0
  274. package/lib/task/task-execution-lock.ts +23 -14
  275. package/lib/task/verification-config.ts +12 -0
  276. package/lib/task/verification-engine.ts +103 -17
  277. package/lib/task/verification.ts +4 -4
  278. package/lib/task/workflow-warning-intents.ts +1 -1
  279. package/lib/task/write.ts +19 -0
  280. package/lib/update.ts +1 -1
  281. package/lib/version.ts +15 -1
  282. package/package.json +12 -1
  283. package/templates/.agents/rules/README.en.md +3 -1
  284. package/templates/.agents/rules/README.zh-CN.md +3 -1
  285. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  286. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  287. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  288. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  289. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  290. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  291. package/templates/.agents/rules/issue-sync.en.md +2 -0
  292. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  293. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  294. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  295. package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
  296. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
  297. package/templates/.agents/rules/pr-sync.en.md +10 -4
  298. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  299. package/templates/.agents/rules/security-alerts.en.md +8 -8
  300. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  301. package/templates/.agents/rules/task-short-id.en.md +1 -4
  302. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  303. package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
  304. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
  305. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  306. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  307. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  308. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  309. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  310. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  311. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/code-task/SKILL.en.md +41 -9
  314. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
  315. package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
  316. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
  317. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  318. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  319. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  320. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  321. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  322. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  323. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  324. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  325. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  326. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  327. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  328. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  329. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
  330. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
  331. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  332. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  333. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  334. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  335. package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
  336. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
  337. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  338. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  339. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  340. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  341. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  342. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  343. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  344. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  345. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  346. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  347. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  348. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  349. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  350. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  351. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  352. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  353. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  354. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  355. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  356. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  357. package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
  358. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
  359. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  360. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  361. package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
  362. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
  363. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  364. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  365. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  366. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  367. package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
  368. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
  369. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  370. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  371. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  372. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  373. package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
  374. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
  375. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  376. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  377. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  378. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  379. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  380. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  381. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  382. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  383. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
  384. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
  385. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  386. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  387. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  388. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  389. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  390. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  391. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  392. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  393. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  394. package/templates/.agents/templates/task.en.md +24 -1
  395. package/templates/.agents/templates/task.zh-CN.md +24 -1
  396. package/templates/.github/workflows/pr-label.yml +7 -26
  397. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  398. package/templates/.agents/scripts/security-alerts.sh +0 -31
  399. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  400. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  401. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  402. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -0,0 +1,117 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+
4
+ import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
5
+ import { CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, constraintDigest, parseQualificationConfirmations, parseTaskQualification } from './qualification-audit.ts';
6
+ import type { QualificationConfirmation } from './qualification-audit.ts';
7
+ import { findSectionHeading } from './sections.ts';
8
+ import { resolveTaskRef } from './resolve-ref.ts';
9
+ import { captureTaskWriteMetadata, writeTask } from './write.ts';
10
+ import type { TaskOperationSummary, TaskWriteOptions } from './write.ts';
11
+
12
+ type QualificationConfirmationRequest = {
13
+ taskRef: string;
14
+ constraintId: string;
15
+ expectedDigest: string;
16
+ rationale: string;
17
+ operation?: 'confirm' | 'supersede' | 'revoke';
18
+ dryRun?: boolean;
19
+ };
20
+ type QualificationConfirmationResult = {
21
+ status: 'planned' | 'applied' | 'no-op' | 'failed';
22
+ changed: boolean;
23
+ taskId: string | null;
24
+ qcrId: string | null;
25
+ requestId: string | null;
26
+ operations: readonly TaskOperationSummary[];
27
+ error: { code: string; message: string } | null;
28
+ };
29
+
30
+ function failed(code: string, message: string, taskId: string | null = null): QualificationConfirmationResult {
31
+ return { status: 'failed', changed: false, taskId, qcrId: null, requestId: null, operations: [], error: { code, message } };
32
+ }
33
+
34
+ function nextQcrId(content: string): string {
35
+ let max = 0;
36
+ for (const match of content.matchAll(/^\|\s*(QCR-(\d+))\s*\|/gm)) max = Math.max(max, Number(match[2]));
37
+ return `QCR-${max + 1}`;
38
+ }
39
+
40
+ function requestIdFor(taskId: string, constraintId: string, expectedDigest: string, rationale: string, timestamp: string): string {
41
+ const hash = createHash('sha256').update(JSON.stringify([taskId, constraintId, expectedDigest, rationale, timestamp])).digest('hex');
42
+ return `QCR-REQUEST-${hash.slice(0, 20)}`;
43
+ }
44
+
45
+ function renderConfirmations(rows: readonly QualificationConfirmation[]): string {
46
+ return [
47
+ `| ${CONFIRMATION_COLUMNS.join(' | ')} |`,
48
+ `| ${CONFIRMATION_COLUMNS.map(() => '---').join(' | ')} |`,
49
+ ...rows.map((row) => [row.qcrId, row.constraintId, row.actor, row.entrypoint, row.requestId, row.approvedDigest, row.confirmedAt, row.rationale]
50
+ .map((value) => value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' '))
51
+ .join(' | ')).map((line) => `| ${line} |`)
52
+ ].join('\n');
53
+ }
54
+
55
+ function applyQualificationConfirmation(request: QualificationConfirmationRequest, options: TaskWriteOptions = {}): QualificationConfirmationResult {
56
+ if (!request || typeof request !== 'object' || !request.taskRef || !/^C-[1-9]\d*$/.test(request.constraintId) || !/^[a-f0-9]{64}$/i.test(request.expectedDigest) || !request.rationale?.trim() || /[\r\n]/.test(request.rationale)) {
57
+ return failed('QUALIFICATION_CONFIRMATION_INVALID', 'taskRef, constraintId, expectedDigest, and a single-line rationale are required');
58
+ }
59
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
60
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
61
+ if (resolved.state !== 'active') return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
62
+ let content: string;
63
+ try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
64
+ catch (error) { return failed('TASK_READ_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
65
+ const parsed = parseTaskQualification(content);
66
+ if (!parsed.ok) return failed(parsed.code, parsed.message, resolved.taskId);
67
+ if (!parsed.qualification.present) return failed('QUALIFICATION_TASK_CONTRACT_MISSING', 'task qualification contract is missing', resolved.taskId);
68
+ const constraint = parsed.qualification.constraints.find((row) => row.constraintId === request.constraintId);
69
+ if (!constraint) return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `unknown constraint '${request.constraintId}'`, resolved.taskId);
70
+ const operation = request.operation ?? 'confirm';
71
+ const existing = parseQualificationConfirmations(content);
72
+ if (!existing.ok) return failed(existing.code, existing.message, resolved.taskId);
73
+ const previous = existing.confirmations.find((row) => row.qcrId === constraint.approvalEvidence);
74
+ if (operation === 'confirm' && constraint.status === 'confirmed') {
75
+ if (previous?.constraintId === constraint.constraintId && previous.approvedDigest === constraint.digest && previous.rationale === request.rationale.trim()) {
76
+ return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null };
77
+ }
78
+ return failed('QUALIFICATION_CONFIRMATION_CONFLICT', `constraint '${request.constraintId}' is already confirmed with different evidence`, resolved.taskId);
79
+ }
80
+ const transitionStatus = operation === 'supersede' ? 'superseded' : 'open';
81
+ if (operation !== 'confirm' && constraint.status === transitionStatus && !constraint.approvalEvidence) {
82
+ return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null };
83
+ }
84
+ if (operation !== 'confirm' && constraint.status !== 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `constraint '${request.constraintId}' is not confirmed`, resolved.taskId);
85
+ if (parsed.qualification.constraintDigest !== request.expectedDigest) return failed('QUALIFICATION_DIGEST_CONFLICT', 'constraint digest changed before human confirmation', resolved.taskId);
86
+ let metadata;
87
+ try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
88
+ catch (error) { return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
89
+ const qcrId = operation === 'confirm' ? nextQcrId(content) : null;
90
+ const requestId = operation === 'confirm' ? requestIdFor(resolved.taskId, request.constraintId, request.expectedDigest, request.rationale.trim(), metadata.timestamp) : null;
91
+ const confirmedConstraint = qcrId ? {
92
+ constraintId: constraint.constraintId, statement: constraint.statement, status: 'confirmed' as const,
93
+ authority: 'human-declared', source: constraint.source, evidence: constraint.evidence,
94
+ derivedFrom: constraint.derivedFrom, approvalEvidence: qcrId
95
+ } : null;
96
+ const confirmation = confirmedConstraint && qcrId && requestId ? {
97
+ qcrId, constraintId: request.constraintId, actor: 'human-declared' as const, entrypoint: 'task-qualification' as const,
98
+ requestId, approvedDigest: constraintDigest(confirmedConstraint), confirmedAt: metadata.timestamp, rationale: request.rationale.trim()
99
+ } : null;
100
+ const confirmations = confirmation ? [...existing.confirmations, confirmation] : existing.confirmations;
101
+ const activity = locateActivityLog(content);
102
+ if (!activity) return failed('QUALIFICATION_ACTIVITY_MISSING', 'task has no unique Activity Log section', resolved.taskId);
103
+ const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
104
+ {
105
+ kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'], keyColumn: 'constraint_id', key: constraint.constraintId,
106
+ values: { statement: constraint.statement, status: operation === 'confirm' ? 'confirmed' : transitionStatus, authority: operation === 'confirm' ? 'human-declared' : constraint.authority, source: constraint.source, evidence: constraint.evidence, derived_from: constraint.derivedFrom.join(','), approval_evidence: qcrId ?? '' }
107
+ },
108
+ ...(operation === 'confirm' ? [{ kind: 'section' as const, aliases: CONFIRMATION_HEADINGS, heading: findSectionHeading(content, [...CONFIRMATION_HEADINGS]), body: renderConfirmations(confirmations) }] : []),
109
+ { kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }
110
+ ];
111
+ const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
112
+ if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
113
+ return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null };
114
+ }
115
+
116
+ export { applyQualificationConfirmation };
117
+ export type { QualificationConfirmationRequest, QualificationConfirmationResult };
@@ -0,0 +1,150 @@
1
+ import fs from 'node:fs';
2
+
3
+ import { CONSTRAINT_COLUMNS, CANDIDATE_COLUMNS, parseTaskQualification } from './qualification-audit.ts';
4
+ import type { CandidateStatus, ConstraintStatus } from './qualification-audit.ts';
5
+ import { resolveTaskRef } from './resolve-ref.ts';
6
+ import { captureTaskWriteMetadata, writeTask } from './write.ts';
7
+ import type { TaskOperationSummary, TaskWriteOptions } from './write.ts';
8
+
9
+ type QualificationConstraintProposal = {
10
+ constraintId: string;
11
+ statement: string;
12
+ status: Exclude<ConstraintStatus, 'confirmed'>;
13
+ authority: string;
14
+ source: string;
15
+ evidence: string;
16
+ derivedFrom: readonly string[];
17
+ approvalEvidence: string;
18
+ };
19
+ type QualificationCandidateProposal = {
20
+ candidateId: string;
21
+ statement: string;
22
+ status: Extract<CandidateStatus, 'pending'>;
23
+ constraintIds: readonly string[];
24
+ impact: string;
25
+ evidence: string;
26
+ };
27
+ type QualificationProposalRequest = {
28
+ taskRef: string;
29
+ expectedTaskInputDigest: string;
30
+ constraints?: readonly QualificationConstraintProposal[];
31
+ candidates?: readonly QualificationCandidateProposal[];
32
+ dryRun?: boolean;
33
+ };
34
+ type QualificationProposalResult = {
35
+ status: 'planned' | 'applied' | 'no-op' | 'failed';
36
+ changed: boolean;
37
+ taskId: string | null;
38
+ operations: readonly TaskOperationSummary[];
39
+ error: { code: string; message: string } | null;
40
+ };
41
+
42
+ function failed(code: string, message: string, taskId: string | null = null): QualificationProposalResult {
43
+ return { status: 'failed', changed: false, taskId, operations: [], error: { code, message } };
44
+ }
45
+
46
+ function validSingleLine(value: unknown): value is string {
47
+ return typeof value === 'string' && Boolean(value.trim()) && !/[\r\n]/.test(value);
48
+ }
49
+
50
+ function assertProposalKeys(value: unknown, keys: readonly string[], label: string): Record<string, unknown> {
51
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`);
52
+ const record = value as Record<string, unknown>;
53
+ const unexpected = Object.keys(record).find((key) => !keys.includes(key));
54
+ if (unexpected) throw new Error(`${label} does not accept '${unexpected}'`);
55
+ return record;
56
+ }
57
+
58
+ function parseConstraintProposal(value: unknown): QualificationConstraintProposal {
59
+ const row = assertProposalKeys(value, ['constraintId', 'statement', 'status', 'authority', 'source', 'evidence', 'derivedFrom', 'approvalEvidence'], 'constraint proposal');
60
+ for (const key of ['constraintId', 'statement', 'status', 'authority', 'source', 'evidence', 'approvalEvidence']) {
61
+ if (typeof row[key] !== 'string' || /[\r\n]/.test(row[key] as string)) throw new Error(`constraint proposal '${key}' must be a single-line string`);
62
+ }
63
+ if (!/^C-[1-9]\d*$/.test(row.constraintId as string)) throw new Error(`constraint proposal id '${row.constraintId as string}' is invalid`);
64
+ if (row.status === 'confirmed' || !['derived', 'assumption', 'open', 'conflicted', 'superseded'].includes(row.status as string)) throw new Error('proposal cannot write confirmed constraint status');
65
+ if ((row.approvalEvidence as string) !== '') throw new Error('proposal cannot write approval_evidence');
66
+ if (!Array.isArray(row.derivedFrom) || row.derivedFrom.some((item) => typeof item !== 'string' || !/^C-[1-9]\d*$/.test(item))) throw new Error('constraint proposal derivedFrom is invalid');
67
+ return {
68
+ constraintId: row.constraintId as string, statement: row.statement as string, status: row.status as Exclude<ConstraintStatus, 'confirmed'>,
69
+ authority: row.authority as string, source: row.source as string, evidence: row.evidence as string,
70
+ derivedFrom: [...row.derivedFrom as string[]], approvalEvidence: ''
71
+ };
72
+ }
73
+
74
+ function parseCandidateProposal(value: unknown): QualificationCandidateProposal {
75
+ const row = assertProposalKeys(value, ['candidateId', 'statement', 'status', 'constraintIds', 'impact', 'evidence'], 'candidate proposal');
76
+ for (const key of ['candidateId', 'statement', 'status', 'impact', 'evidence']) if (!validSingleLine(row[key])) throw new Error(`candidate proposal '${key}' must be a non-empty single-line string`);
77
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(row.candidateId as string)) throw new Error(`candidate proposal id '${row.candidateId as string}' is invalid`);
78
+ if (row.status !== 'pending') throw new Error('proposal can only write pending candidate status');
79
+ if (!Array.isArray(row.constraintIds) || row.constraintIds.some((item) => typeof item !== 'string' || !/^C-[1-9]\d*$/.test(item))) throw new Error('candidate proposal constraintIds is invalid');
80
+ return row as unknown as QualificationCandidateProposal;
81
+ }
82
+
83
+ function proposalValues(request: QualificationProposalRequest): { constraints: QualificationConstraintProposal[]; candidates: QualificationCandidateProposal[] } {
84
+ if (!/^[a-f0-9]{64}$/i.test(request.expectedTaskInputDigest)) throw new Error('expectedTaskInputDigest must be a sha256 digest');
85
+ if (Object.keys(request).some((key) => !['taskRef', 'expectedTaskInputDigest', 'constraints', 'candidates', 'dryRun'].includes(key))) throw new Error('proposal contains an unsupported field');
86
+ if (!Array.isArray(request.constraints) && request.constraints !== undefined) throw new Error('constraints must be an array');
87
+ if (!Array.isArray(request.candidates) && request.candidates !== undefined) throw new Error('candidates must be an array');
88
+ return {
89
+ constraints: (request.constraints ?? []).map(parseConstraintProposal),
90
+ candidates: (request.candidates ?? []).map(parseCandidateProposal)
91
+ };
92
+ }
93
+
94
+ function applyQualificationProposal(request: QualificationProposalRequest, options: TaskWriteOptions = {}): QualificationProposalResult {
95
+ if (!request || typeof request !== 'object' || !validSingleLine(request.taskRef)) return failed('QUALIFICATION_PROPOSAL_INVALID', 'taskRef is required');
96
+ let values: ReturnType<typeof proposalValues>;
97
+ try { values = proposalValues(request); }
98
+ catch (error) { return failed('QUALIFICATION_PROPOSAL_INVALID', error instanceof Error ? error.message : String(error)); }
99
+ if (values.constraints.length === 0 && values.candidates.length === 0) return failed('QUALIFICATION_PROPOSAL_INVALID', 'proposal must contain a constraint or candidate');
100
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
101
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
102
+ if (resolved.state !== 'active') return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
103
+ let content: string;
104
+ try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
105
+ catch (error) { return failed('TASK_READ_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
106
+ const parsed = parseTaskQualification(content);
107
+ if (!parsed.ok) return failed(parsed.code, parsed.message, resolved.taskId);
108
+ if (!parsed.qualification.present) return failed('QUALIFICATION_TASK_CONTRACT_MISSING', 'task qualification contract is missing', resolved.taskId);
109
+ const currentConstraintMap = new Map(parsed.qualification.constraints.map((row) => [row.constraintId, row]));
110
+ const currentCandidateMap = new Map(parsed.qualification.candidates.map((row) => [row.candidateId, row]));
111
+ const proposalAlreadyApplied = values.constraints.every((row) => {
112
+ const current = currentConstraintMap.get(row.constraintId);
113
+ return current && current.statement === row.statement && current.status === row.status && current.authority === row.authority
114
+ && current.source === row.source && current.evidence === row.evidence && current.derivedFrom.join(',') === row.derivedFrom.join(',') && current.approvalEvidence === '';
115
+ }) && values.candidates.every((row) => {
116
+ const current = currentCandidateMap.get(row.candidateId);
117
+ return current && current.statement === row.statement && current.status === row.status && current.constraintIds.join(',') === row.constraintIds.join(',')
118
+ && current.impact === row.impact && current.evidence === row.evidence;
119
+ });
120
+ if (parsed.qualification.taskInputDigest !== request.expectedTaskInputDigest) {
121
+ return proposalAlreadyApplied
122
+ ? { status: 'no-op', changed: false, taskId: resolved.taskId, operations: [], error: null }
123
+ : failed('QUALIFICATION_DIGEST_CONFLICT', 'task qualification input changed before proposal was applied', resolved.taskId);
124
+ }
125
+ for (const row of values.constraints) {
126
+ const current = currentConstraintMap.get(row.constraintId);
127
+ if (current?.status === 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `confirmed constraint '${row.constraintId}' must be superseded or revoked through task-qualification`, resolved.taskId);
128
+ }
129
+ const knownConstraintIds = new Set([...parsed.qualification.constraints.map((row) => row.constraintId), ...values.constraints.map((row) => row.constraintId)]);
130
+ for (const row of values.constraints) for (const parent of row.derivedFrom) if (!knownConstraintIds.has(parent)) return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `constraint proposal '${row.constraintId}' references unknown '${parent}'`, resolved.taskId);
131
+ for (const row of values.candidates) for (const id of row.constraintIds) if (!knownConstraintIds.has(id)) return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `candidate proposal '${row.candidateId}' references unknown '${id}'`, resolved.taskId);
132
+ const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [];
133
+ for (const row of values.constraints) {
134
+ mutations.push({ kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: CONSTRAINT_COLUMNS, keyColumn: 'constraint_id', key: row.constraintId, values: { statement: row.statement, status: row.status, authority: row.authority, source: row.source, evidence: row.evidence, derived_from: row.derivedFrom.join(','), approval_evidence: '' } });
135
+ }
136
+ for (const row of values.candidates) {
137
+ mutations.push({ kind: 'table-row', action: 'upsert', sectionAliases: ['候选与否决方案', 'Candidate and Rejected Options', 'Candidates and Rejected Options', 'Candidates and Rejected Alternatives'], columns: CANDIDATE_COLUMNS, keyColumn: 'candidate_id', key: row.candidateId, values: { statement: row.statement, status: row.status, constraint_ids: row.constraintIds.join(','), impact: row.impact, evidence: row.evidence } });
138
+ }
139
+ // Only the submitted rows are mutated; no actor, confirmation, or QCR fields
140
+ // can be smuggled into this internal proposal operation.
141
+ let metadata;
142
+ try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
143
+ catch (error) { return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
144
+ const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
145
+ if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
146
+ return { status: result.status, changed: result.changed, taskId: result.taskId, operations: result.operations, error: null };
147
+ }
148
+
149
+ export { applyQualificationProposal };
150
+ export type { QualificationCandidateProposal, QualificationConstraintProposal, QualificationProposalRequest, QualificationProposalResult };
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ import { scanVisibleMarkdown } from './decision-details.ts';
5
+
4
6
  type ReviewFindingCounts = {
5
7
  blocker: number;
6
8
  major: number;
@@ -77,29 +79,21 @@ function normalizeVerdict(raw: unknown): ReviewVerdict | '' {
77
79
  }
78
80
 
79
81
  function extractSection(content: string, names: string[]): string {
80
- const lines = content.split(/\r?\n/);
81
82
  const nameSet = new Set(names);
82
- const start = lines.findIndex((line) => {
83
- const match = line.trim().match(/^##\s+(.+?)\s*$/);
84
- return match ? nameSet.has(match[1]!) : false;
85
- });
86
- if (start === -1) return '';
87
- const sectionLines = [];
88
- for (let index = start + 1; index < lines.length; index += 1) {
89
- if (/^##\s+/.test(lines[index]!)) break;
90
- sectionLines.push(lines[index]);
91
- }
92
- return sectionLines.join('\n');
83
+ const scanned = scanVisibleMarkdown(content);
84
+ const heading = scanned.headings.find((item) => item.level === 2 && nameSet.has(item.text));
85
+ if (!heading) return '';
86
+ const next = scanned.headings.find((item) => item.start > heading.start && item.level <= 2);
87
+ return content.slice(heading.end, next?.start ?? content.length);
93
88
  }
94
89
 
95
90
  function summaryBounds(content: string): { start: number; end: number } | null {
96
- const headings = [...content.matchAll(/^##\s+(.+?)\s*$/gm)]
97
- .map((match) => ({ index: match.index, end: match.index + match[0].length, name: match[1]! }));
98
- const summaries = headings.filter((heading) => SUMMARY_HEADINGS.has(heading.name));
91
+ const scanned = scanVisibleMarkdown(content);
92
+ const summaries = scanned.headings.filter((heading) => heading.level === 2 && SUMMARY_HEADINGS.has(heading.text));
99
93
  if (summaries.length !== 1) return null;
100
94
  const summary = summaries[0]!;
101
- const next = headings.find((heading) => heading.index > summary.index);
102
- return { start: summary.end, end: next?.index ?? content.length };
95
+ const next = scanned.headings.find((heading) => heading.start > summary.start && heading.level <= 2);
96
+ return { start: summary.end, end: next?.start ?? content.length };
103
97
  }
104
98
 
105
99
  function uniqueLine(
@@ -107,14 +101,19 @@ function uniqueLine(
107
101
  bounds: { start: number; end: number },
108
102
  pattern: RegExp
109
103
  ): { value: string; start: number; end: number } | null {
110
- const section = content.slice(bounds.start, bounds.end);
111
- const matches = [...section.matchAll(pattern)];
104
+ const matches: Array<{ value: string; index: number; length: number }> = [];
105
+ for (const line of scanVisibleMarkdown(content).lines) {
106
+ if (line.start < bounds.start || line.start >= bounds.end) continue;
107
+ pattern.lastIndex = 0;
108
+ const match = pattern.exec(line.text);
109
+ if (match) matches.push({ value: match[1]!, index: line.start + match.index, length: match[0].length });
110
+ }
112
111
  if (matches.length !== 1) return null;
113
112
  const match = matches[0]!;
114
113
  return {
115
- value: match[1]!,
116
- start: bounds.start + match.index,
117
- end: bounds.start + match.index + match[0].length
114
+ value: match.value,
115
+ start: match.index,
116
+ end: match.index + match.length
118
117
  };
119
118
  }
120
119
 
@@ -14,6 +14,10 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
14
14
  import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
15
15
  import { allowsManualOverride } from './guard-override.ts';
16
16
  import type { ManualOverrideCapability } from './guard-override.ts';
17
+ import { getArtifactSchema } from './artifact-schema.ts';
18
+ import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
19
+ import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
20
+ import type { ArtifactRepairOperation } from './artifact-operations.ts';
17
21
 
18
22
  type ReviewFinalizationErrorCode =
19
23
  | ResolveTaskRefErrorCode
@@ -26,6 +30,7 @@ type ReviewFinalizationErrorCode =
26
30
  | 'REVIEW_SUMMARY_NOT_FOUND'
27
31
  | 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
28
32
  | 'REVIEW_SUMMARY_COUNT_MISMATCH'
33
+ | 'REVIEW_ARTIFACT_STRUCTURE_INVALID'
29
34
  | 'REVIEW_DECISION_DETAIL_INVALID'
30
35
  | 'REVIEW_ARTIFACT_CONFLICT'
31
36
  | 'REVIEW_PROVENANCE_INVALID'
@@ -48,6 +53,10 @@ type ReviewFinalizationResult = {
48
53
  stage: string;
49
54
  artifact: string;
50
55
  stageStatus: LedgerStageStatus | null;
56
+ artifactSha256: string | null;
57
+ semanticDigest: string | null;
58
+ repairable: boolean;
59
+ operation: ArtifactRepairOperation | null;
51
60
  operations: readonly {
52
61
  kind: 'artifact';
53
62
  artifact: string;
@@ -88,7 +97,8 @@ function failed(
88
97
  code: ReviewFinalizationErrorCode,
89
98
  message: string,
90
99
  taskId: string | null = null,
91
- stageStatus: LedgerStageStatus | null = null
100
+ stageStatus: LedgerStageStatus | null = null,
101
+ extra: Partial<ReviewFinalizationResult> = {}
92
102
  ): ReviewFinalizationResult {
93
103
  return {
94
104
  status: 'failed',
@@ -99,8 +109,13 @@ function failed(
99
109
  stage: request.stage,
100
110
  artifact: request.artifact,
101
111
  stageStatus,
112
+ artifactSha256: null,
113
+ semanticDigest: null,
114
+ repairable: false,
115
+ operation: null,
102
116
  operations: [],
103
- error: { code, message }
117
+ error: { code, message },
118
+ ...extra
104
119
  };
105
120
  }
106
121
 
@@ -167,6 +182,80 @@ function finalizeReviewSummaryUnlocked(
167
182
  } catch (error) {
168
183
  return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
169
184
  }
185
+ const schema = getArtifactSchema(spec.family);
186
+ const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
187
+ const artifactSha256 = sha256Content(artifactContent);
188
+ const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
189
+ let repairIntent;
190
+ try {
191
+ repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
192
+ } catch (error) {
193
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
194
+ artifactSha256,
195
+ semanticDigest: artifactSemanticDigest
196
+ });
197
+ }
198
+ if (structure && !structure.ok) {
199
+ const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
200
+ if (repairable) {
201
+ if (repairIntent && (
202
+ repairIntent.state !== 'awaiting-repair' ||
203
+ repairIntent.artifactSha256 !== artifactSha256 ||
204
+ repairIntent.semanticDigest !== structure.semanticDigest
205
+ )) {
206
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
207
+ artifactSha256,
208
+ semanticDigest: structure.semanticDigest
209
+ });
210
+ }
211
+ try {
212
+ if (!repairIntent) {
213
+ writeArtifactRepairIntent(resolved.repoRoot, {
214
+ version: 1,
215
+ taskId: resolved.taskId,
216
+ family: spec.family,
217
+ artifact: request.artifact,
218
+ state: 'awaiting-repair',
219
+ baselineSemanticDigest: structure.semanticDigest,
220
+ artifactSha256,
221
+ semanticDigest: structure.semanticDigest
222
+ });
223
+ }
224
+ } catch (error) {
225
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
226
+ artifactSha256,
227
+ semanticDigest: structure.semanticDigest,
228
+ operation: structure.repair
229
+ });
230
+ }
231
+ }
232
+ return failed(
233
+ request,
234
+ 'REVIEW_ARTIFACT_STRUCTURE_INVALID',
235
+ structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '),
236
+ resolved.taskId,
237
+ null,
238
+ {
239
+ artifactSha256,
240
+ semanticDigest: structure.semanticDigest,
241
+ repairable,
242
+ operation: structure.repair
243
+ }
244
+ );
245
+ }
246
+ if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
247
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
248
+ artifactSha256,
249
+ semanticDigest: artifactSemanticDigest
250
+ });
251
+ }
252
+ if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
253
+ && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
254
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
255
+ artifactSha256,
256
+ semanticDigest: artifactSemanticDigest
257
+ });
258
+ }
170
259
  if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
171
260
  return failed(
172
261
  request,
@@ -232,8 +321,26 @@ function finalizeReviewSummaryUnlocked(
232
321
  return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
233
322
  }
234
323
  if (!transformed.changed) {
324
+ if (repairIntent?.state === 'awaiting-repair') {
325
+ try {
326
+ writeArtifactRepairIntent(resolved.repoRoot, {
327
+ ...repairIntent,
328
+ state: 'passed',
329
+ artifactSha256,
330
+ semanticDigest: artifactSemanticDigest
331
+ });
332
+ } catch (error) {
333
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
334
+ artifactSha256,
335
+ semanticDigest: artifactSemanticDigest
336
+ });
337
+ }
338
+ }
235
339
  return {
236
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
340
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
341
+ artifactSha256,
342
+ semanticDigest: artifactSemanticDigest
343
+ }),
237
344
  status: 'no-op',
238
345
  error: null
239
346
  };
@@ -241,7 +348,10 @@ function finalizeReviewSummaryUnlocked(
241
348
  const operations = [{ kind: 'artifact' as const, artifact: request.artifact, operation: 'update' as const }];
242
349
  if (request.dryRun) {
243
350
  return {
244
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
351
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
352
+ artifactSha256: sha256Content(transformed.content),
353
+ semanticDigest: canonicalSemanticDigest(transformed.content)
354
+ }),
245
355
  status: 'planned',
246
356
  changed: true,
247
357
  operations,
@@ -277,6 +387,21 @@ function finalizeReviewSummaryUnlocked(
277
387
  cleanupTemp(fileSystem, tempPath);
278
388
  return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
279
389
  }
390
+ if (repairIntent?.state === 'awaiting-repair') {
391
+ try {
392
+ writeArtifactRepairIntent(resolved.repoRoot, {
393
+ ...repairIntent,
394
+ state: 'passed',
395
+ artifactSha256: sha256Content(transformed.content),
396
+ semanticDigest: canonicalSemanticDigest(transformed.content)
397
+ });
398
+ } catch (error) {
399
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
400
+ artifactSha256: sha256Content(transformed.content),
401
+ semanticDigest: canonicalSemanticDigest(transformed.content)
402
+ });
403
+ }
404
+ }
280
405
  return {
281
406
  status: 'applied',
282
407
  changed: true,
@@ -286,6 +411,10 @@ function finalizeReviewSummaryUnlocked(
286
411
  stage,
287
412
  artifact: request.artifact,
288
413
  stageStatus,
414
+ artifactSha256: sha256Content(transformed.content),
415
+ semanticDigest: canonicalSemanticDigest(transformed.content),
416
+ repairable: false,
417
+ operation: null,
289
418
  operations,
290
419
  error: null
291
420
  };
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { artifactName, maxRound } from "./review-artifacts.ts";
5
+ import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.ts";
5
6
 
6
7
  type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
7
8
  const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
@@ -46,15 +47,35 @@ export function findAuthoritativeReviewCodeArtifact(taskDir: string) {
46
47
  const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
47
48
  const round = maxRound(entries, "review-code");
48
49
  if (round === 0) {
49
- return { ok: false, round: 0, fileName: null, path: null };
50
+ return { ok: false, round: 0, fileName: null, path: null, error: null };
50
51
  }
51
52
 
52
53
  const fileName = artifactName("review-code", round);
54
+ let taskContent: string;
55
+ try {
56
+ taskContent = fs.readFileSync(path.join(taskDir, "task.md"), "utf8");
57
+ } catch (error) {
58
+ return {
59
+ ok: false, round, fileName, path: null,
60
+ error: error instanceof Error ? error.message : String(error)
61
+ };
62
+ }
63
+ const invalidation = parseInvalidationDocument(taskContent);
64
+ if (!invalidation.ok) {
65
+ return { ok: false, round, fileName, path: null, error: invalidation.message };
66
+ }
67
+ if (invalidationBlocks(invalidation.document)) {
68
+ return { ok: false, round, fileName, path: null, error: "artifact invalidation is incomplete; reconcile task.md before using review-code" };
69
+ }
70
+ if (isArtifactInvalidated(invalidation.document, "review-code", fileName)) {
71
+ return { ok: false, round, fileName, path: null, error: `${fileName} is invalidated; re-run review-code` };
72
+ }
53
73
  return {
54
74
  ok: true,
55
75
  round,
56
76
  fileName,
57
- path: path.join(taskDir, fileName)
77
+ path: path.join(taskDir, fileName),
78
+ error: null
58
79
  };
59
80
  }
60
81