@fitlab-ai/agent-infra 0.9.14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  8. package/dist/lib/defaults.json +103 -103
  9. package/dist/lib/fs/durable-write.js +39 -0
  10. package/dist/lib/fs/file-lock.js +111 -0
  11. package/dist/lib/host-control/audit.js +19 -0
  12. package/dist/lib/host-control/client.js +142 -0
  13. package/dist/lib/host-control/command.js +49 -0
  14. package/dist/lib/host-control/path.js +160 -0
  15. package/dist/lib/host-control/secure-fs.js +140 -0
  16. package/dist/lib/host-control/server.js +159 -0
  17. package/dist/lib/host-control/service.js +112 -0
  18. package/dist/lib/init.js +11 -2
  19. package/dist/lib/internal/cli-route-inventory.js +4 -2
  20. package/dist/lib/internal/codex-lifecycle.js +6 -6
  21. package/dist/lib/internal/demo-promotion.js +322 -0
  22. package/dist/lib/internal/demo-transcript.js +454 -0
  23. package/dist/lib/internal/host-control.js +28 -0
  24. package/dist/lib/internal/manual-validation.js +331 -0
  25. package/dist/lib/internal/platform-pr.js +18 -4
  26. package/dist/lib/internal/release-workflow.js +63 -84
  27. package/dist/lib/internal/sandbox-control.js +149 -2
  28. package/dist/lib/internal/task-artifact.js +16 -263
  29. package/dist/lib/internal/task-create.js +6 -5
  30. package/dist/lib/internal/task-event.js +37 -37
  31. package/dist/lib/internal/task-lifecycle.js +2 -2
  32. package/dist/lib/internal/task-operation-registry.js +101 -4
  33. package/dist/lib/internal/task-review.js +11 -56
  34. package/dist/lib/internal/task-validate.js +30 -10
  35. package/dist/lib/owned-path.js +33 -0
  36. package/dist/lib/platform/check-status.js +11 -0
  37. package/dist/lib/platform/comment-safety.js +4 -1
  38. package/dist/lib/platform/completion-backfill.js +1 -1
  39. package/dist/lib/platform/github-data.js +241 -0
  40. package/dist/lib/platform/github-provider.js +15 -20
  41. package/dist/lib/platform/issue-comments.js +18 -12
  42. package/dist/lib/platform/issues.js +2 -180
  43. package/dist/lib/platform/pr-change-report.js +5 -4
  44. package/dist/lib/platform/pr-checks.js +12 -65
  45. package/dist/lib/platform/pr-summary.js +117 -33
  46. package/dist/lib/platform/pull-requests.js +5 -108
  47. package/dist/lib/platform/snapshots.js +2 -0
  48. package/dist/lib/platform/task-identities.js +2 -9
  49. package/dist/lib/platform/verification-sync.js +132 -143
  50. package/dist/lib/pr-review/evidence-grading.js +4 -8
  51. package/dist/lib/release/workflow.js +1 -1
  52. package/dist/lib/sandbox/commands/create.js +7 -2
  53. package/dist/lib/sandbox/commands/rm.js +19 -1511
  54. package/dist/lib/sandbox/control/audit.js +203 -0
  55. package/dist/lib/sandbox/control/client.js +93 -54
  56. package/dist/lib/sandbox/control/executor.js +129 -65
  57. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  58. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  59. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  60. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  61. package/dist/lib/sandbox/control/protocol.js +55 -4
  62. package/dist/lib/sandbox/control/server.js +518 -127
  63. package/dist/lib/sandbox/control/state.js +64 -0
  64. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  65. package/dist/lib/sandbox/control/workflow-executor.js +89 -0
  66. package/dist/lib/sandbox/index.js +1 -2
  67. package/dist/lib/sandbox/recovery.js +28 -4
  68. package/dist/lib/sandbox/removal.js +822 -0
  69. package/dist/lib/sandbox/task-cleanup.js +217 -0
  70. package/dist/lib/sandbox/workspace-identity.js +17 -1
  71. package/dist/lib/sandbox/workspace-view.js +13 -14
  72. package/dist/lib/task/activity-intent.js +2 -1
  73. package/dist/lib/task/activity-log.js +30 -24
  74. package/dist/lib/task/artifact-command.js +79 -0
  75. package/dist/lib/task/artifact-lifecycle.js +30 -28
  76. package/dist/lib/task/artifact-name.js +36 -0
  77. package/dist/lib/task/artifact-operations.js +29 -289
  78. package/dist/lib/task/artifact-receipts.js +3 -15
  79. package/dist/lib/task/artifact-recovery.js +430 -0
  80. package/dist/lib/task/artifact-repair-intent.js +134 -29
  81. package/dist/lib/task/artifact-schema.js +6 -14
  82. package/dist/lib/task/capabilities.js +12 -19
  83. package/dist/lib/task/codex-orchestration.js +71 -25
  84. package/dist/lib/task/commit-identity.js +28 -0
  85. package/dist/lib/task/commit-operation.js +33 -20
  86. package/dist/lib/task/control-authority.js +403 -6
  87. package/dist/lib/task/control-recovery.js +140 -0
  88. package/dist/lib/task/create-service.js +1 -8
  89. package/dist/lib/task/create.js +62 -9
  90. package/dist/lib/task/decision-details.js +2 -46
  91. package/dist/lib/task/delegation-receipts.js +64 -3
  92. package/dist/lib/task/events.js +275 -22
  93. package/dist/lib/task/finalization-state.js +9 -0
  94. package/dist/lib/task/human-override.js +8 -14
  95. package/dist/lib/task/implementation-inputs.js +3 -3
  96. package/dist/lib/task/intermediate-cleanup.js +441 -0
  97. package/dist/lib/task/invalidation.js +18 -65
  98. package/dist/lib/task/ledger-intents.js +7 -22
  99. package/dist/lib/task/ledger.js +1 -3
  100. package/dist/lib/task/lifecycle-recovery.js +549 -0
  101. package/dist/lib/task/lifecycle.js +31 -4
  102. package/dist/lib/task/local-artifact-finalization.js +197 -197
  103. package/dist/lib/task/manual-validation-completion.js +26 -0
  104. package/dist/lib/task/manual-validation-evidence.js +130 -0
  105. package/dist/lib/task/manual-validation-receipt.js +105 -0
  106. package/dist/lib/task/manual-validation-shared.js +40 -0
  107. package/dist/lib/task/manual-validation-transaction.js +243 -0
  108. package/dist/lib/task/markdown.js +47 -0
  109. package/dist/lib/task/orchestration.js +66 -19
  110. package/dist/lib/task/pr-delivery-fact.js +12 -56
  111. package/dist/lib/task/qualification-audit.js +20 -34
  112. package/dist/lib/task/recovery-warning.js +19 -0
  113. package/dist/lib/task/review-artifacts.js +2 -18
  114. package/dist/lib/task/review-command.js +26 -0
  115. package/dist/lib/task/review-finalization.js +175 -216
  116. package/dist/lib/task/review-fingerprint.js +2 -2
  117. package/dist/lib/task/sections.js +34 -56
  118. package/dist/lib/task/short-id.js +15 -7
  119. package/dist/lib/task/task-execution-lock.js +1 -1
  120. package/dist/lib/task/verification-engine.js +119 -337
  121. package/dist/lib/task/verification.js +1 -1
  122. package/dist/lib/task/workflow-command.js +16 -0
  123. package/dist/lib/task/workflow-warning-intents.js +0 -16
  124. package/dist/lib/task/workflow-warnings.js +28 -2
  125. package/dist/lib/update.js +2 -0
  126. package/dist/lib/version.js +1 -11
  127. package/dist/lifecycle-build-manifest.json +3 -3
  128. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  129. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
  130. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  131. package/lib/fs/durable-write.ts +30 -0
  132. package/lib/fs/file-lock.ts +113 -0
  133. package/lib/host-control/audit.ts +22 -0
  134. package/lib/host-control/client.ts +172 -0
  135. package/lib/host-control/command.ts +60 -0
  136. package/lib/host-control/path.ts +167 -0
  137. package/lib/host-control/secure-fs.ts +135 -0
  138. package/lib/host-control/server.ts +180 -0
  139. package/lib/host-control/service.ts +133 -0
  140. package/lib/init.ts +15 -2
  141. package/lib/internal/cli-route-inventory.ts +4 -2
  142. package/lib/internal/codex-lifecycle.ts +6 -6
  143. package/lib/internal/demo-promotion.ts +354 -0
  144. package/lib/internal/demo-transcript.ts +419 -0
  145. package/lib/internal/host-control.ts +28 -0
  146. package/lib/internal/manual-validation.ts +315 -0
  147. package/lib/internal/platform-pr.ts +15 -4
  148. package/lib/internal/release-workflow.ts +74 -82
  149. package/lib/internal/sandbox-control.ts +162 -1
  150. package/lib/internal/task-artifact.ts +14 -217
  151. package/lib/internal/task-create.ts +5 -5
  152. package/lib/internal/task-event.ts +20 -11
  153. package/lib/internal/task-lifecycle.ts +4 -3
  154. package/lib/internal/task-operation-registry.ts +111 -4
  155. package/lib/internal/task-review.ts +9 -54
  156. package/lib/internal/task-validate.ts +26 -10
  157. package/lib/owned-path.ts +26 -0
  158. package/lib/platform/check-status.ts +9 -0
  159. package/lib/platform/comment-safety.ts +5 -0
  160. package/lib/platform/completion-backfill.ts +1 -1
  161. package/lib/platform/github-data.ts +322 -0
  162. package/lib/platform/github-provider.ts +15 -20
  163. package/lib/platform/issue-comments.ts +21 -14
  164. package/lib/platform/issues.ts +5 -246
  165. package/lib/platform/pr-change-report.ts +7 -5
  166. package/lib/platform/pr-checks.ts +13 -77
  167. package/lib/platform/pr-summary.ts +123 -35
  168. package/lib/platform/pull-requests.ts +6 -173
  169. package/lib/platform/snapshots.ts +53 -0
  170. package/lib/platform/task-identities.ts +3 -14
  171. package/lib/platform/verification-sync.ts +150 -156
  172. package/lib/pr-review/evidence-grading.ts +4 -9
  173. package/lib/release/workflow.ts +3 -3
  174. package/lib/sandbox/commands/create.ts +7 -6
  175. package/lib/sandbox/commands/rm.ts +21 -1895
  176. package/lib/sandbox/control/audit.ts +253 -0
  177. package/lib/sandbox/control/client.ts +109 -66
  178. package/lib/sandbox/control/executor.ts +134 -74
  179. package/lib/sandbox/control/finalization-response.ts +29 -0
  180. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  181. package/lib/sandbox/control/lifecycle.ts +45 -3
  182. package/lib/sandbox/control/native-file-lock.ts +6 -107
  183. package/lib/sandbox/control/protocol.ts +80 -6
  184. package/lib/sandbox/control/server.ts +567 -132
  185. package/lib/sandbox/control/state.ts +86 -0
  186. package/lib/sandbox/control/task-workflow.ts +95 -0
  187. package/lib/sandbox/control/workflow-executor.ts +92 -0
  188. package/lib/sandbox/index.ts +1 -2
  189. package/lib/sandbox/recovery.ts +28 -5
  190. package/lib/sandbox/removal.ts +1142 -0
  191. package/lib/sandbox/task-cleanup.ts +209 -0
  192. package/lib/sandbox/workspace-identity.ts +19 -3
  193. package/lib/sandbox/workspace-view.ts +17 -15
  194. package/lib/task/activity-intent.ts +2 -2
  195. package/lib/task/activity-log.ts +27 -19
  196. package/lib/task/artifact-command.ts +85 -0
  197. package/lib/task/artifact-lifecycle.ts +31 -26
  198. package/lib/task/artifact-name.ts +55 -0
  199. package/lib/task/artifact-operations.ts +34 -310
  200. package/lib/task/artifact-receipts.ts +3 -14
  201. package/lib/task/artifact-recovery.ts +519 -0
  202. package/lib/task/artifact-repair-intent.ts +162 -33
  203. package/lib/task/artifact-schema.ts +5 -34
  204. package/lib/task/capabilities.ts +13 -22
  205. package/lib/task/codex-orchestration.ts +85 -24
  206. package/lib/task/commit-identity.ts +31 -0
  207. package/lib/task/commit-operation.ts +37 -20
  208. package/lib/task/control-authority.ts +540 -10
  209. package/lib/task/control-recovery.ts +186 -0
  210. package/lib/task/create-service.ts +1 -10
  211. package/lib/task/create.ts +63 -8
  212. package/lib/task/decision-details.ts +3 -71
  213. package/lib/task/delegation-receipts.ts +73 -2
  214. package/lib/task/events.ts +309 -24
  215. package/lib/task/finalization-state.ts +11 -0
  216. package/lib/task/human-override.ts +8 -16
  217. package/lib/task/implementation-inputs.ts +3 -3
  218. package/lib/task/intermediate-cleanup.ts +493 -0
  219. package/lib/task/invalidation.ts +18 -62
  220. package/lib/task/ledger-intents.ts +7 -24
  221. package/lib/task/ledger.ts +1 -3
  222. package/lib/task/lifecycle-recovery.ts +691 -0
  223. package/lib/task/lifecycle.ts +30 -5
  224. package/lib/task/local-artifact-finalization.ts +244 -234
  225. package/lib/task/manual-validation-completion.ts +40 -0
  226. package/lib/task/manual-validation-evidence.ts +209 -0
  227. package/lib/task/manual-validation-receipt.ts +156 -0
  228. package/lib/task/manual-validation-shared.ts +50 -0
  229. package/lib/task/manual-validation-transaction.ts +304 -0
  230. package/lib/task/markdown.ts +70 -0
  231. package/lib/task/orchestration.ts +84 -19
  232. package/lib/task/pr-delivery-fact.ts +15 -62
  233. package/lib/task/qualification-audit.ts +22 -33
  234. package/lib/task/recovery-warning.ts +21 -0
  235. package/lib/task/review-artifacts.ts +1 -20
  236. package/lib/task/review-command.ts +24 -0
  237. package/lib/task/review-finalization.ts +183 -292
  238. package/lib/task/review-fingerprint.ts +2 -2
  239. package/lib/task/sections.ts +33 -54
  240. package/lib/task/short-id.ts +27 -5
  241. package/lib/task/task-execution-lock.ts +1 -1
  242. package/lib/task/verification-engine.ts +113 -383
  243. package/lib/task/verification-types.ts +15 -0
  244. package/lib/task/verification.ts +1 -1
  245. package/lib/task/workflow-command.ts +17 -0
  246. package/lib/task/workflow-warning-intents.ts +0 -13
  247. package/lib/task/workflow-warnings.ts +21 -2
  248. package/lib/update.ts +6 -0
  249. package/lib/version.ts +1 -15
  250. package/package.json +6 -6
  251. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  252. package/templates/.agents/rules/create-issue.en.md +1 -1
  253. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  254. package/templates/.agents/rules/issue-sync.en.md +4 -2
  255. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  256. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  257. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  258. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  259. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  260. package/templates/.agents/scripts/package.json +3 -0
  261. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  262. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  263. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  264. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  265. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  266. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  267. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  268. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  269. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  270. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  271. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  272. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  273. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  274. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  275. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  276. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  277. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
  278. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
  279. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  280. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  281. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  282. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  283. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  284. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  285. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  286. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  287. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  288. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  289. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  290. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  291. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  292. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  293. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  294. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  295. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  296. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  297. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  298. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  299. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  300. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  301. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  302. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  303. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  304. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  305. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  310. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  311. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  314. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  315. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  316. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  317. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  318. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  319. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  320. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  321. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  322. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  323. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  324. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  325. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  326. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  327. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  328. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  329. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  330. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  331. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  332. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  333. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  334. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  335. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  336. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  337. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  338. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  339. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  340. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  341. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  342. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  343. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  344. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  345. package/templates/.agents/templates/task.en.md +1 -1
  346. package/templates/.agents/templates/task.zh-CN.md +1 -1
  347. package/templates/.claude/commands/import-issue.en.md +1 -1
  348. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  349. package/templates/.claude/commands/restore-task.en.md +1 -1
  350. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  351. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  352. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  353. package/dist/lib/platform/adapters.js +0 -56
  354. package/lib/platform/adapters.ts +0 -197
@@ -0,0 +1,441 @@
1
+ import { inspectOwnedPath } from '../owned-path.js';
2
+ import { taskFinalizationReceiptState } from './finalization-state.js';
3
+ import { withTaskExecutionLock } from './task-execution-lock.js';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { checkpointCommitMatches } from './commit-identity.js';
7
+ import { readCheckpointIntent } from './commit-intent.js';
8
+ import { parseTaskFrontmatter } from './frontmatter.js';
9
+ import { readTaskFinalizationReceipt } from './finalization.js';
10
+ import { semanticDigest, sha256Content } from './local-artifact-finalization.js';
11
+ import { artifactRecoveryRoot, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
12
+ import { parseArtifactName } from './artifact-name.js';
13
+ import { enumerateAllTaskDirs } from './resolve-ref.js';
14
+ const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
15
+ const LOCAL_INTENT_RE = /^(TASK-\d{8}-\d{6})-(analysis|review-analysis|plan|review-plan|code|review-code)-(.+\.md)\.json$/;
16
+ const COMMIT_INTENT_RE = /^(TASK-\d{8}-\d{6})\.json$/;
17
+ const AUXILIARY_ROOTS = [
18
+ '.local-artifact-finalization-intents',
19
+ '.task-commit-intents'
20
+ ];
21
+ function auxiliaryRoot(repoRoot, name) {
22
+ return path.join(repoRoot, '.agents', 'workspace', name);
23
+ }
24
+ function taskRoot(repoRoot) {
25
+ return path.join(repoRoot, '.agents', 'workspace');
26
+ }
27
+ function fileIdentity(stat) {
28
+ return {
29
+ dev: String(stat.dev),
30
+ ino: String(stat.ino),
31
+ size: stat.size,
32
+ mtimeMs: stat.mtimeMs
33
+ };
34
+ }
35
+ function sameFileIdentity(left, right) {
36
+ return left.dev === right.dev && left.ino === right.ino
37
+ && left.size === right.size && left.mtimeMs === right.mtimeMs;
38
+ }
39
+ function errorCode(error) {
40
+ const code = error?.code;
41
+ return typeof code === 'string' ? code : null;
42
+ }
43
+ function safeLstat(root, target, expect) {
44
+ const stat = inspectOwnedPath(root, target, expect);
45
+ return stat ? fileIdentity(stat) : null;
46
+ }
47
+ function inspectTaskRecords(repoRoot, taskIds) {
48
+ const records = new Map();
49
+ for (const entry of enumerateAllTaskDirs(repoRoot)) {
50
+ if (taskIds && !taskIds.has(entry.taskId))
51
+ continue;
52
+ const taskMdPath = path.join(entry.taskDir, 'task.md');
53
+ if (safeLstat(taskRoot(repoRoot), taskMdPath, 'file') === null)
54
+ continue;
55
+ const content = fs.readFileSync(taskMdPath, 'utf8');
56
+ records.set(entry.taskId, {
57
+ taskId: entry.taskId,
58
+ taskDir: entry.taskDir,
59
+ state: entry.state,
60
+ frontmatter: parseTaskFrontmatter(content)
61
+ });
62
+ }
63
+ return records;
64
+ }
65
+ function item(kind, taskId, filePath, disposition, reason, identity = null) {
66
+ return { kind, taskId, path: path.resolve(filePath), disposition, reason, identity };
67
+ }
68
+ function taskGate(task) {
69
+ if (!task)
70
+ return 'TASK_NOT_FOUND';
71
+ if (task.state !== 'completed' && task.state !== 'archive')
72
+ return 'TASK_STATE_PROTECTED';
73
+ if (task.frontmatter.id !== task.taskId || task.frontmatter.status !== 'completed')
74
+ return 'TASK_STATE_PROTECTED';
75
+ if (!task.frontmatter.branch)
76
+ return 'TASK_IDENTITY_INVALID';
77
+ return null;
78
+ }
79
+ function receiptGate(repoRoot, task, options) {
80
+ let receipt;
81
+ try {
82
+ receipt = readTaskFinalizationReceipt(repoRoot, task.taskId);
83
+ }
84
+ catch {
85
+ return 'FINALIZATION_RECEIPT_INVALID';
86
+ }
87
+ if (!receipt)
88
+ return 'FINALIZATION_RECEIPT_MISSING';
89
+ const state = taskFinalizationReceiptState(receipt);
90
+ if (state === 'pending')
91
+ return 'FINALIZATION_RECEIPT_PENDING';
92
+ if (state === 'unresolved')
93
+ return 'LFAI_RETRY_OR_WARNING_OPEN';
94
+ if (receipt.controlBinding) {
95
+ const evidence = options.controlBindingEvidence?.(task.taskId, receipt.controlBinding);
96
+ if (evidence !== 'terminal' && !(options.preflight && evidence === 'pending'))
97
+ return 'CONTROL_BINDING_MISMATCH';
98
+ }
99
+ return null;
100
+ }
101
+ function canonicalLocalIntentPath(taskId, family, artifact) {
102
+ return `${taskId}-${family}-${artifact}.json`;
103
+ }
104
+ function localIntentCandidate(repoRoot, task, filePath, taskId, family, artifact, identity, options) {
105
+ const protectedReason = taskGate(task);
106
+ if (protectedReason)
107
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', protectedReason, identity);
108
+ if (path.basename(filePath) !== canonicalLocalIntentPath(taskId, family, artifact)
109
+ || !parseArtifactName(artifact)?.family || parseArtifactName(artifact).family !== family) {
110
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity);
111
+ }
112
+ let intent;
113
+ try {
114
+ intent = readArtifactRecoveryIntent(repoRoot, taskId, family, artifact);
115
+ }
116
+ catch {
117
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_SCHEMA_INVALID', identity);
118
+ }
119
+ if (!intent || intent.state !== 'consumed') {
120
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_STATE_PROTECTED', identity);
121
+ }
122
+ const artifactPath = path.join(task.taskDir, artifact);
123
+ if (safeLstat(task.taskDir, artifactPath, 'file') === null) {
124
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_IDENTITY_MISMATCH', identity);
125
+ }
126
+ let content;
127
+ try {
128
+ content = fs.readFileSync(artifactPath, 'utf8');
129
+ }
130
+ catch {
131
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_UNREADABLE', identity);
132
+ }
133
+ if (intent.finalArtifactSha256 !== sha256Content(content) || intent.finalSemanticDigest !== semanticDigest(content)) {
134
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_DIGEST_MISMATCH', identity);
135
+ }
136
+ const receiptReason = receiptGate(repoRoot, task, options);
137
+ if (receiptReason)
138
+ return item('LFAI-CONSUMED', taskId, filePath, 'protected', receiptReason, identity);
139
+ return item('LFAI-CONSUMED', taskId, filePath, 'planned', 'LFAI_CONSUMED_AND_VERIFIED', identity);
140
+ }
141
+ function commitIntentCandidate(repoRoot, task, filePath, taskId, identity, options) {
142
+ const protectedReason = taskGate(task);
143
+ if (protectedReason)
144
+ return item('COMMIT-SYNCED', taskId, filePath, 'protected', protectedReason, identity);
145
+ let intent;
146
+ try {
147
+ intent = readCheckpointIntent(repoRoot, taskId);
148
+ }
149
+ catch {
150
+ return item('COMMIT-SYNCED', taskId, filePath, 'protected', 'COMMIT_INTENT_INVALID', identity);
151
+ }
152
+ if (!intent || intent.state !== 'synced' || intent.taskId !== taskId
153
+ || intent.branch !== task.frontmatter.branch || !intent.committedHead
154
+ || task.frontmatter.checkpoint_commit !== intent.committedHead
155
+ || !checkpointCommitMatches(repoRoot, intent, intent.committedHead)) {
156
+ return item('COMMIT-SYNCED', taskId, filePath, 'protected', 'COMMIT_EVIDENCE_MISMATCH', identity);
157
+ }
158
+ const receiptReason = receiptGate(repoRoot, task, options);
159
+ if (receiptReason)
160
+ return item('COMMIT-SYNCED', taskId, filePath, 'protected', receiptReason, identity);
161
+ return item('COMMIT-SYNCED', taskId, filePath, 'planned', 'COMMIT_SYNCED_AND_VERIFIED', identity);
162
+ }
163
+ function readAuxiliaryCandidates(repoRoot, tasks, options) {
164
+ const candidates = [];
165
+ const selected = options.taskIds ? new Set(options.taskIds) : null;
166
+ for (const rootName of AUXILIARY_ROOTS) {
167
+ const root = auxiliaryRoot(repoRoot, rootName);
168
+ let rootIdentity;
169
+ try {
170
+ rootIdentity = safeLstat(taskRoot(repoRoot), root, 'directory');
171
+ }
172
+ catch {
173
+ rootIdentity = null;
174
+ }
175
+ if (rootIdentity === null) {
176
+ let stat = null;
177
+ try {
178
+ stat = fs.lstatSync(root);
179
+ }
180
+ catch {
181
+ stat = null;
182
+ }
183
+ if (stat?.isSymbolicLink())
184
+ candidates.push(item('AUXILIARY-UNKNOWN', null, root, 'protected', 'PATH_SYMLINK'));
185
+ else if (stat)
186
+ candidates.push(item('AUXILIARY-UNKNOWN', null, root, 'protected', 'PATH_IDENTITY_MISMATCH'));
187
+ continue;
188
+ }
189
+ let entries;
190
+ try {
191
+ entries = fs.readdirSync(root, { withFileTypes: true });
192
+ }
193
+ catch {
194
+ candidates.push(item('AUXILIARY-UNKNOWN', null, root, 'protected', 'AUXILIARY_ROOT_UNREADABLE', rootIdentity));
195
+ continue;
196
+ }
197
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
198
+ const filePath = path.join(root, entry.name);
199
+ if (entry.isSymbolicLink()) {
200
+ candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_SYMLINK'));
201
+ continue;
202
+ }
203
+ if (!entry.isFile()) {
204
+ candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH'));
205
+ continue;
206
+ }
207
+ const identity = safeLstat(root, filePath, 'file');
208
+ if (!identity) {
209
+ candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH'));
210
+ continue;
211
+ }
212
+ if (rootName === '.local-artifact-finalization-intents') {
213
+ const match = LOCAL_INTENT_RE.exec(entry.name);
214
+ if (!match || (selected && !selected.has(match[1]))) {
215
+ if (!selected || !match)
216
+ candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity));
217
+ continue;
218
+ }
219
+ candidates.push(localIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], match[2], match[3], identity, options));
220
+ }
221
+ else {
222
+ const match = COMMIT_INTENT_RE.exec(entry.name);
223
+ if (!match || (selected && !selected.has(match[1]))) {
224
+ if (!selected || !match)
225
+ candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity));
226
+ continue;
227
+ }
228
+ candidates.push(commitIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], identity, options));
229
+ }
230
+ }
231
+ if (entries.length === 0) {
232
+ candidates.push(item('EMPTY-AUX-PARENT', null, root, 'planned', 'EMPTY_CANONICAL_AUXILIARY_ROOT', rootIdentity));
233
+ }
234
+ }
235
+ return candidates;
236
+ }
237
+ function buildIntermediateCleanupReport(items, dryRun = false, partial = false) {
238
+ const remaining = items.filter((candidate) => candidate.disposition !== 'deleted' && candidate.disposition !== 'skipped');
239
+ return {
240
+ status: dryRun ? 'dry-run' : partial || remaining.some((candidate) => candidate.disposition === 'failed') ? 'partial' : 'completed',
241
+ items,
242
+ remaining
243
+ };
244
+ }
245
+ function scanInternal(repoRootInput, options) {
246
+ const repoRoot = path.resolve(repoRootInput);
247
+ if (safeLstat(path.dirname(repoRoot), repoRoot, 'directory') === null) {
248
+ throw new Error('INTERMEDIATE_CLEANUP_REPOSITORY_INVALID');
249
+ }
250
+ const selected = options.taskIds ? new Set(options.taskIds.filter((taskId) => TASK_ID_RE.test(taskId))) : undefined;
251
+ const tasks = inspectTaskRecords(repoRoot, selected);
252
+ return readAuxiliaryCandidates(repoRoot, tasks, options);
253
+ }
254
+ function scanIntermediateCleanup(repoRoot, options = {}) {
255
+ const candidates = scanInternal(repoRoot, options);
256
+ return buildIntermediateCleanupReport(candidates, true);
257
+ }
258
+ function cleanupConsumedRecoveryFiles(repoRoot, task, candidate) {
259
+ if (candidate.kind !== 'LFAI-CONSUMED' || !candidate.taskId)
260
+ return true;
261
+ if (!task)
262
+ return false;
263
+ const match = LOCAL_INTENT_RE.exec(path.basename(candidate.path));
264
+ if (!match)
265
+ return false;
266
+ let intent;
267
+ try {
268
+ intent = readArtifactRecoveryIntent(repoRoot, candidate.taskId, match[2], match[3]);
269
+ }
270
+ catch {
271
+ return false;
272
+ }
273
+ if (!intent || intent.state !== 'consumed')
274
+ return false;
275
+ const recovery = artifactRecoveryRoot(task.taskDir, intent.recoveryOperationId);
276
+ let stat = null;
277
+ try {
278
+ stat = fs.lstatSync(recovery);
279
+ }
280
+ catch (error) {
281
+ if (errorCode(error) === 'ENOENT')
282
+ return true;
283
+ return false;
284
+ }
285
+ if (!stat.isDirectory() || stat.isSymbolicLink())
286
+ return false;
287
+ let entries;
288
+ try {
289
+ entries = fs.readdirSync(recovery, { withFileTypes: true });
290
+ }
291
+ catch {
292
+ return false;
293
+ }
294
+ for (const entry of entries) {
295
+ if (entry.isSymbolicLink() || !entry.isFile() || !['baseline.md', 'candidate.md', 'final.md', 'publish.md'].includes(entry.name))
296
+ return false;
297
+ }
298
+ try {
299
+ for (const entry of entries)
300
+ fs.unlinkSync(path.join(recovery, entry.name));
301
+ fs.rmdirSync(recovery);
302
+ const recoveryRoot = path.dirname(recovery);
303
+ if (fs.readdirSync(recoveryRoot).length === 0)
304
+ fs.rmdirSync(recoveryRoot);
305
+ return true;
306
+ }
307
+ catch (error) {
308
+ return errorCode(error) === 'ENOENT';
309
+ }
310
+ }
311
+ function removeCandidate(repoRoot, candidate, task) {
312
+ if (candidate.disposition !== 'planned')
313
+ return candidate;
314
+ const root = candidate.kind === 'EMPTY-AUX-PARENT'
315
+ ? path.dirname(path.dirname(candidate.path))
316
+ : path.dirname(candidate.path);
317
+ const expected = candidate.identity;
318
+ if (!expected)
319
+ return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_MISSING' };
320
+ let raw;
321
+ try {
322
+ raw = fs.lstatSync(candidate.path);
323
+ }
324
+ catch (error) {
325
+ if (errorCode(error) === 'ENOENT')
326
+ return { ...candidate, disposition: 'skipped', reason: 'ALREADY_GONE' };
327
+ return { ...candidate, disposition: 'failed', reason: 'CLEANUP_RECHECK_FAILED' };
328
+ }
329
+ if (raw.isSymbolicLink())
330
+ return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
331
+ const actual = safeLstat(root, candidate.path, candidate.kind === 'EMPTY-AUX-PARENT' ? 'directory' : 'file');
332
+ if (!actual)
333
+ return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
334
+ if (!sameFileIdentity(expected, actual))
335
+ return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
336
+ if (!cleanupConsumedRecoveryFiles(repoRoot, task, candidate)) {
337
+ return { ...candidate, disposition: 'protected', reason: 'RECOVERY_STAGING_NOT_SAFE_TO_REMOVE' };
338
+ }
339
+ try {
340
+ if (candidate.kind === 'EMPTY-AUX-PARENT') {
341
+ if (fs.readdirSync(candidate.path).length !== 0)
342
+ return { ...candidate, disposition: 'protected', reason: 'AUXILIARY_ROOT_NOT_EMPTY' };
343
+ fs.rmdirSync(candidate.path);
344
+ }
345
+ else {
346
+ fs.unlinkSync(candidate.path);
347
+ }
348
+ return { ...candidate, disposition: 'deleted', reason: 'DELETED_AFTER_LOCKED_RECHECK' };
349
+ }
350
+ catch (error) {
351
+ if (errorCode(error) === 'ENOENT')
352
+ return { ...candidate, disposition: 'skipped', reason: 'ALREADY_GONE' };
353
+ return { ...candidate, disposition: 'failed', reason: 'CLEANUP_DELETE_FAILED' };
354
+ }
355
+ }
356
+ function intermediateCleanupKey(item) {
357
+ return `${item.kind}\0${item.path}`;
358
+ }
359
+ function cleanupIntermediateUnderRemovalCoordinator(repoRoot, options = {}) {
360
+ const scanOptions = {
361
+ ...(options.taskIds === undefined ? {} : { taskIds: options.taskIds }),
362
+ ...(options.controlBindingEvidence === undefined ? {} : { controlBindingEvidence: options.controlBindingEvidence })
363
+ };
364
+ const initial = scanIntermediateCleanup(repoRoot, scanOptions);
365
+ if (options.dryRun)
366
+ return initial;
367
+ const items = [...initial.items];
368
+ const lockedTaskIds = options.lockedTaskIds ?? new Set();
369
+ const byTask = new Map();
370
+ for (const [index, candidate] of items.entries()) {
371
+ if (!candidate.taskId || candidate.disposition !== 'planned')
372
+ continue;
373
+ const indexes = byTask.get(candidate.taskId) ?? [];
374
+ indexes.push(index);
375
+ byTask.set(candidate.taskId, indexes);
376
+ }
377
+ const tasks = inspectTaskRecords(repoRoot, new Set(byTask.keys()));
378
+ for (const taskId of [...byTask.keys()].sort()) {
379
+ const execute = () => {
380
+ const refreshed = scanIntermediateCleanup(repoRoot, {
381
+ ...scanOptions,
382
+ taskIds: [taskId]
383
+ });
384
+ const refreshedByKey = new Map(refreshed.items.map((candidate) => [intermediateCleanupKey(candidate), candidate]));
385
+ for (const index of byTask.get(taskId) ?? []) {
386
+ const initialCandidate = items[index];
387
+ if (initialCandidate.disposition !== 'planned')
388
+ continue;
389
+ const current = refreshedByKey.get(intermediateCleanupKey(initialCandidate));
390
+ items[index] = current
391
+ ? removeCandidate(repoRoot, current, tasks.get(taskId))
392
+ : { ...initialCandidate, disposition: 'protected', reason: 'CANDIDATE_NO_LONGER_ELIGIBLE' };
393
+ }
394
+ };
395
+ try {
396
+ if (lockedTaskIds.has(taskId))
397
+ execute();
398
+ else
399
+ withTaskExecutionLock(repoRoot, taskId, 'sandbox-removal', execute);
400
+ }
401
+ catch {
402
+ for (const index of byTask.get(taskId) ?? []) {
403
+ const candidate = items[index];
404
+ if (candidate.disposition === 'planned')
405
+ items[index] = { ...candidate, disposition: 'protected', reason: 'TASK_LOCK_BUSY' };
406
+ }
407
+ }
408
+ }
409
+ for (const [index, candidate] of items.entries()) {
410
+ if (candidate.taskId || candidate.disposition !== 'planned')
411
+ continue;
412
+ items[index] = removeCandidate(repoRoot, candidate);
413
+ }
414
+ const knownPaths = new Set(items.map((candidate) => intermediateCleanupKey(candidate)));
415
+ for (const candidate of scanIntermediateCleanup(repoRoot, scanOptions).items) {
416
+ if (candidate.kind !== 'EMPTY-AUX-PARENT' || candidate.disposition !== 'planned'
417
+ || knownPaths.has(intermediateCleanupKey(candidate)))
418
+ continue;
419
+ items.push(removeCandidate(repoRoot, candidate));
420
+ }
421
+ return buildIntermediateCleanupReport(items);
422
+ }
423
+ function protectIntermediateCleanupReport(report, reason) {
424
+ const items = report.items.map((item) => item.disposition === 'planned'
425
+ ? { ...item, disposition: 'protected', reason }
426
+ : item);
427
+ return buildIntermediateCleanupReport(items, false, true);
428
+ }
429
+ function mergeIntermediateCleanupReports(reports) {
430
+ const items = reports.flatMap((report) => report.items);
431
+ return buildIntermediateCleanupReport(items, false, reports.some((report) => report.status === 'partial'));
432
+ }
433
+ function formatIntermediateCleanupReport(report) {
434
+ return [
435
+ `remaining ${report.remaining.length} item(s)`,
436
+ ...report.items.map((candidate) => (`${candidate.disposition} ${candidate.kind} ${candidate.path}`
437
+ + ` (${candidate.taskId ?? 'unbound'}; ${candidate.reason})`))
438
+ ];
439
+ }
440
+ export { formatIntermediateCleanupReport, cleanupIntermediateUnderRemovalCoordinator, protectIntermediateCleanupReport, mergeIntermediateCleanupReports, scanIntermediateCleanup };
441
+ //# sourceMappingURL=intermediate-cleanup.js.map
@@ -1,4 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { findSectionRange, parseTable } from './sections.js';
3
+ import { scanVisibleMarkdown } from './markdown.js';
2
4
  const INVALIDATION_HEADINGS = ['产物失效记录', 'Artifact Invalidation'];
3
5
  const OPERATION_COLUMNS = [
4
6
  'operation_id', 'source_family', 'source_artifact', 'source_round', 'source_sha256',
@@ -29,61 +31,14 @@ function createInvalidationOperation(source, targets = []) {
29
31
  createdAt: source.createdAt, updatedAt: source.updatedAt, completedAt: '', error: ''
30
32
  };
31
33
  }
32
- function lineCells(line) {
33
- const first = line.indexOf('|');
34
- const last = line.lastIndexOf('|');
35
- if (first < 0 || first === last || line.slice(0, first).trim() || line.slice(last + 1).trim())
36
- return null;
37
- const inner = line.slice(first + 1, last);
38
- const cells = [];
39
- let start = 0;
40
- let escaped = false;
41
- for (let index = 0; index < inner.length; index += 1) {
42
- const char = inner[index];
43
- if (char === '|' && !escaped) {
44
- cells.push(inner.slice(start, index));
45
- start = index + 1;
46
- }
47
- escaped = char === '\\' && !escaped;
48
- if (char !== '\\')
49
- escaped = false;
50
- }
51
- cells.push(inner.slice(start));
52
- return cells.map((cell) => cell.replace(/\\([\\|])/g, '$1').trim());
53
- }
54
- function isSeparator(cells, count) {
55
- return cells.length === count && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
56
- }
57
- function sectionBody(content) {
58
- const heading = INVALIDATION_HEADINGS.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
59
- const match = new RegExp(`^##\\s+(${heading})\\s*$`, 'm').exec(content);
60
- if (!match)
61
- return null;
62
- const start = (match.index ?? 0) + match[0].length;
63
- const rest = content.slice(start);
64
- const end = rest.search(/^##\s+/m);
65
- return rest.slice(0, end < 0 ? rest.length : end);
66
- }
67
- function parseTable(body, heading, columns) {
68
- const marker = new RegExp(`^###\\s+${heading}\\s*$`, 'm').exec(body);
69
- if (!marker)
70
- return null;
71
- const start = (marker.index ?? 0) + marker[0].length;
72
- const rest = body.slice(start);
73
- const end = rest.search(/^###\s+/m);
74
- const lines = rest.slice(0, end < 0 ? rest.length : end).split(/\r?\n/).filter((line) => line.trim());
75
- const header = lines.length > 0 ? lineCells(lines[0]) : null;
76
- const separator = lines.length > 1 ? lineCells(lines[1]) : null;
77
- if (!header || !separator || header.length !== columns.length || !header.every((value, index) => value === columns[index]) || !isSeparator(separator, columns.length))
78
- return false;
79
- const rows = [];
80
- for (const line of lines.slice(2)) {
81
- const cells = lineCells(line);
82
- if (!cells || cells.length !== columns.length)
83
- return false;
84
- rows.push(Object.fromEntries(columns.map((column, index) => [column, cells[index]])));
34
+ function parseInvalidationTable(body, heading, columns) {
35
+ const section = findSectionRange(body, [heading], 3);
36
+ const lines = scanVisibleMarkdown(body).lines.filter((line) => line.text.trim());
37
+ const table = parseTable(lines.map((line) => line.text).join('\n'), { sectionAliases: [heading], columns });
38
+ if (!section || !table || lines.filter((line) => line.start >= section.bodyStart && line.start < section.end).length !== table.rows.length + 2) {
39
+ throw new Error(`invalidation ${heading} section must contain only its canonical table`);
85
40
  }
86
- return rows;
41
+ return table.rows;
87
42
  }
88
43
  function required(value, field) {
89
44
  if (!value)
@@ -102,16 +57,14 @@ function numberValue(value, field, allowZero = true) {
102
57
  return parsed;
103
58
  }
104
59
  function parseInvalidationDocument(content) {
105
- const body = sectionBody(content);
106
- if (body === null)
60
+ const section = findSectionRange(content, INVALIDATION_HEADINGS);
61
+ if (section === null)
107
62
  return { ok: true, present: false, document: { operations: [], targets: [] } };
108
63
  try {
109
- const operationRows = parseTable(body, 'Operations', OPERATION_COLUMNS);
110
- const targetRows = parseTable(body, 'Targets', TARGET_COLUMNS);
111
- if (operationRows === null || targetRows === null || operationRows === false || targetRows === false) {
112
- return invalid('invalidation section must contain Operations and Targets tables');
113
- }
114
- const operations = operationRows.map((row) => ({
64
+ const body = content.slice(section.bodyStart, section.end);
65
+ const operationRows = parseInvalidationTable(body, 'Operations', OPERATION_COLUMNS);
66
+ const targetRows = parseInvalidationTable(body, 'Targets', TARGET_COLUMNS);
67
+ const operations = operationRows.map(({ values: row }) => ({
115
68
  operationId: required(row.operation_id, 'operation_id'), sourceFamily: required(row.source_family, 'source_family'),
116
69
  sourceArtifact: required(row.source_artifact, 'source_artifact'), sourceRound: numberValue(row.source_round, 'source_round', false),
117
70
  sourceSha256: required(row.source_sha256, 'source_sha256'), status: status(row.status, 'operation status'),
@@ -119,7 +72,7 @@ function parseInvalidationDocument(content) {
119
72
  createdAt: required(row.created_at, 'created_at'), updatedAt: required(row.updated_at, 'updated_at'),
120
73
  completedAt: row.completed_at ?? '', error: row.error ?? ''
121
74
  }));
122
- const targets = targetRows.map((row) => ({
75
+ const targets = targetRows.map(({ values: row }) => ({
123
76
  targetId: required(row.target_id, 'target_id'), operationId: required(row.operation_id, 'operation_id'),
124
77
  targetKind: row.target_kind, targetFamily: required(row.target_family, 'target_family'),
125
78
  targetArtifact: required(row.target_artifact, 'target_artifact'), targetRound: numberValue(row.target_round, 'target_round', false),
@@ -248,11 +201,11 @@ function isArtifactInvalidated(document, family, artifact) {
248
201
  && target.targetArtifact === artifact);
249
202
  }
250
203
  function invalidationMutation(content, document) {
251
- const existing = sectionBody(content) !== null;
204
+ const existing = findSectionRange(content, INVALIDATION_HEADINGS);
252
205
  return {
253
206
  kind: 'section',
254
207
  aliases: INVALIDATION_HEADINGS,
255
- heading: existing && /^##\s+Artifact Invalidation\s*$/m.test(content) ? INVALIDATION_HEADINGS[1] : INVALIDATION_HEADINGS[0],
208
+ heading: existing?.heading ?? INVALIDATION_HEADINGS[0],
256
209
  body: renderInvalidation(document)
257
210
  };
258
211
  }
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { declareImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
5
+ import { parseArtifactName } from './artifact-name.js';
5
6
  import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, nextHdId, parseLedgerDocument, validateLedgerRows } from './ledger.js';
6
7
  import { resolveTaskRef } from './resolve-ref.js';
7
8
  import { writeTask } from './write.js';
@@ -10,16 +11,6 @@ import { parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } f
10
11
  const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
11
12
  const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
12
13
  const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
13
- const REVIEW_ARTIFACT = {
14
- analysis: /^review-analysis(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
15
- plan: /^review-plan(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
16
- code: /^review-code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/
17
- };
18
- const ARTIFACT = {
19
- analysis: /^analysis(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
20
- plan: /^plan(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
21
- code: /^code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/
22
- };
23
14
  function failed(intent, code, message, taskId = null, entityId = null) {
24
15
  return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, operations: [], error: { code, message } };
25
16
  }
@@ -52,10 +43,6 @@ function nextFindingId(rows, stage) {
52
43
  }
53
44
  return `${prefix}-${max + 1}`;
54
45
  }
55
- function reviewArtifactRound(reviewArtifact) {
56
- const match = /-r([1-9]\d*)\.md$/.exec(reviewArtifact);
57
- return match ? Number(match[1]) : 1;
58
- }
59
46
  function rowMutation(row) {
60
47
  return {
61
48
  kind: 'table-row', action: 'upsert', sectionAliases: LEDGER_HEADINGS,
@@ -63,9 +50,6 @@ function rowMutation(row) {
63
50
  values: { stage: row.stage, round: row.round, severity: row.severity, status: row.status, evidence: row.evidence }
64
51
  };
65
52
  }
66
- function ledgerSectionMutation(content) {
67
- return [];
68
- }
69
53
  function implementationInputMutation(content, rows) {
70
54
  const english = /^##\s+Activity Log\s*$/m.test(content);
71
55
  return {
@@ -112,7 +96,7 @@ function applyLedgerIntent(intent, options = {}) {
112
96
  if (!finding || finding.severity === 'decision')
113
97
  return failed(intent, 'LEDGER_NOT_FOUND', `finding '${intent.findingId}' was not found`, resolved.taskId, intent.findingId);
114
98
  const sourceFromEvidence = finding.evidence.split('#')[0];
115
- if (sourceFromEvidence !== intent.sourceArtifact || !REVIEW_ARTIFACT[finding.stage]?.test(intent.sourceArtifact)) {
99
+ if (sourceFromEvidence !== intent.sourceArtifact || parseArtifactName(intent.sourceArtifact)?.family !== `review-${finding.stage}`) {
116
100
  return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact does not match finding evidence', resolved.taskId, intent.intentId);
117
101
  }
118
102
  try {
@@ -142,7 +126,8 @@ function applyLedgerIntent(intent, options = {}) {
142
126
  let before = null;
143
127
  let after;
144
128
  if (intent.kind === 'finding-upsert') {
145
- if (!REVIEW_ARTIFACT[intent.stage]?.test(intent.reviewArtifact) || !Number.isInteger(intent.ordinal) || intent.ordinal < 1 || !['blocker', 'major', 'minor'].includes(intent.severity)) {
129
+ const identity = parseArtifactName(intent.reviewArtifact);
130
+ if (identity?.family !== `review-${intent.stage}` || !Number.isInteger(intent.ordinal) || intent.ordinal < 1 || !['blocker', 'major', 'minor'].includes(intent.severity)) {
146
131
  return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'finding identity, stage, ordinal, or severity is invalid', resolved.taskId);
147
132
  }
148
133
  const evidence = normalizedEvidence(intent.evidence);
@@ -162,13 +147,13 @@ function applyLedgerIntent(intent, options = {}) {
162
147
  else {
163
148
  const id = nextFindingId(rows, intent.stage);
164
149
  after = {
165
- id, stage: intent.stage, round: String(reviewArtifactRound(intent.reviewArtifact)),
150
+ id, stage: intent.stage, round: String(identity.round),
166
151
  severity: intent.severity, status: 'open', evidence, sourceLine: -1
167
152
  };
168
153
  }
169
154
  }
170
155
  else if (intent.kind === 'decision-upsert') {
171
- if (!/^HD-[1-9]\d*$/.test(intent.id) || !ARTIFACT[intent.stage]?.test(intent.artifact)) {
156
+ if (!/^HD-[1-9]\d*$/.test(intent.id) || !['analysis', 'plan', 'code'].includes(intent.stage) || parseArtifactName(intent.artifact)?.family !== intent.stage) {
172
157
  return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'decision id, stage, or artifact is invalid', resolved.taskId, intent.id);
173
158
  }
174
159
  const evidence = `${intent.artifact}#${intent.id}`;
@@ -249,7 +234,7 @@ function applyLedgerIntent(intent, options = {}) {
249
234
  }
250
235
  const result = writeTask({
251
236
  taskRef: intent.taskRef, expectedState: stateOverride ? resolved.state : 'active',
252
- mutations: [...ledgerSectionMutation(content), rowMutation(after), ...(implementationMutation ? [implementationMutation] : [])],
237
+ mutations: [rowMutation(after), ...(implementationMutation ? [implementationMutation] : [])],
253
238
  dryRun: 'dryRun' in intent ? intent.dryRun : false
254
239
  }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
255
240
  return mapWrite(intent, after.id, before, after, result);
@@ -17,9 +17,7 @@ const LEDGER_SECTION_MISSING_MESSAGE = 'review disagreement ledger section is mi
17
17
  function isReviewStage(stage) {
18
18
  return stage === 'analysis' || stage === 'plan' || stage === 'code';
19
19
  }
20
- // Parse all rows of the disagreement ledger table. Skips the heading, the
21
- // header row (`| id | ... |`) and the `|---|` separator; ignores non-`|` lines.
22
- // Rows with fewer than 6 columns are skipped (mirrors the JS gate parser).
20
+ // Parse ledger rows using the shared strict table structure contract.
23
21
  function parseLedgerDocument(content) {
24
22
  const table = parseTable(content, { sectionAliases: LEDGER_HEADINGS, columns: LEDGER_COLUMNS });
25
23
  if (!table)