@fitlab-ai/agent-infra 0.9.1 → 0.9.2

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 (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -5,6 +5,7 @@ import { resolveTaskRef } from './resolve-ref.ts';
5
5
  import type { ResolveTaskRefErrorCode, TaskWorkspaceState } from './resolve-ref.ts';
6
6
  import { locateActivityLog } from './activity-log.ts';
7
7
  import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.ts';
8
+ import { parseVerdict } from './review-artifacts.ts';
8
9
  import { extractSection, findSectionHeading } from './sections.ts';
9
10
 
10
11
  const artifactFamilyCatalog = [
@@ -239,21 +240,6 @@ function resolveReviewedInput(
239
240
  }
240
241
  }
241
242
 
242
- function extractMarkdownSection(content: string, names: readonly string[]): string {
243
- const lines = content.split(/\r?\n/);
244
- const allowed = new Set(names);
245
- const start = lines.findIndex((line) => {
246
- const match = /^##\s+(.+?)\s*$/.exec(line.trim());
247
- return Boolean(match?.[1] && allowed.has(match[1]));
248
- });
249
- if (start === -1) return '';
250
- let end = lines.length;
251
- for (let index = start + 1; index < lines.length; index += 1) {
252
- if (/^##\s+/.test(lines[index]!)) { end = index; break; }
253
- }
254
- return lines.slice(start + 1, end).join('\n');
255
- }
256
-
257
243
  function assertWritableInventory(inventory: ArtifactInventoryResult): ArtifactError | null {
258
244
  if (inventory.status === 'failed') return inventory.error;
259
245
  const blockers = inventory.diagnostics.filter((item) => BLOCKING_DIAGNOSTICS.has(item.code));
@@ -398,29 +384,6 @@ function resolveDecisionImplementationInput(
398
384
  catch (error) { return { error: error instanceof Error ? error.message : String(error) }; }
399
385
  }
400
386
 
401
- function normalizeVerdict(value: string): 'Approved' | 'Changes Requested' | 'Rejected' | null {
402
- const normalized = value.trim().toLowerCase();
403
- if (normalized === '通过' || normalized === 'approved') return 'Approved';
404
- if (normalized === '需要修改' || normalized === 'changes requested') return 'Changes Requested';
405
- if (normalized === '拒绝' || normalized === 'rejected') return 'Rejected';
406
- return null;
407
- }
408
-
409
- function parseVerdict(file: string): { ok: true; verdict: string } | { ok: false; message: string } {
410
- let content: string;
411
- try { content = fs.readFileSync(file, 'utf8'); }
412
- catch (error) { return { ok: false, message: String(error) }; }
413
- const summary = extractMarkdownSection(content, ['审查摘要', 'Review Summary']);
414
- const verdictMatch = /^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/im.exec(summary);
415
- const verdict = verdictMatch?.[1] ? normalizeVerdict(verdictMatch[1]) : null;
416
- if (!verdict) return { ok: false, message: `cannot parse verdict in ${path.basename(file)}` };
417
- if (verdict !== 'Approved') return { ok: true, verdict };
418
- const findings = /^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/im.exec(summary)?.[1];
419
- const counts = findings?.match(/(\d+)\s*(?:阻塞项|blockers?).*?(\d+)\s*(?:主要|majors?).*?(\d+)\s*(?:次要|minors?)/i);
420
- if (!counts) return { ok: false, message: `cannot parse findings count in ${path.basename(file)}` };
421
- return { ok: true, verdict: counts.slice(1).some((value) => Number(value) > 0) ? 'Approved-with-issues' : 'Approved' };
422
- }
423
-
424
387
  function validateCompletedArtifact(taskDir: string, family: ArtifactFamily, name: string, round?: number): { ok: true; artifact: ArtifactIdentity } | { ok: false; error: ArtifactError } {
425
388
  if (path.basename(name) !== name) return { ok: false, error: { code: 'ARTIFACT_PATH_INVALID', message: 'artifact must be a top-level basename' } };
426
389
  const parsed = parseArtifactName(name);
@@ -10,8 +10,9 @@ import { extractSubSection } from '../sections.ts';
10
10
  const USAGE = `Usage: ai task decisions [--task <ref> | -t <ref>] [--item <selector> | -i <selector>] [options]
11
11
  ai task decisions <ref> [selector] [options]
12
12
 
13
- Lists decision items recorded in a task's review disagreement
14
- ledger, or prints the full detail block for a single item. Read-only.
13
+ Shows what still needs a maintainer's decision. Without a selector, it lists
14
+ the choices. With a selector, it shows the explanation and how to respond.
15
+ Read-only.
15
16
 
16
17
  Omit <ref> Resolve the unique active task for the current branch.
17
18
  <ref> Legacy positional task ref.
@@ -150,29 +151,31 @@ function findDecisionRecord(id: string, content: string): string[] {
150
151
  function titleOf(row: LedgerRow, taskDir: string): string {
151
152
  const block = findDetailBlock(row, taskDir);
152
153
  if (block) {
153
- return block
154
+ const title = block
154
155
  .split('\n')[0]!
155
156
  .replace(/^###\s+/, '')
156
157
  .replace(/\s*\[needs-human-decision\]\s*$/, '')
157
158
  .trim();
159
+ return title.replace(new RegExp(`^${row.id}\\s*[::]\\s*`, 'i'), '').trim();
158
160
  }
159
- return row.evidence || '(无详情)';
161
+ return '(explanation unavailable)';
162
+ }
163
+
164
+ function trackingOf(row: LedgerRow): string {
165
+ return `stage=${row.stage} · severity=${row.severity} · status=${row.status} · evidence=${row.evidence}`;
160
166
  }
161
167
 
162
168
  function renderList(rows: LedgerRow[], format: string, taskDir: string): void {
163
169
  if (rows.length === 0) {
164
- process.stdout.write('无待裁决项。\n');
170
+ process.stdout.write('No pending decisions.\n');
165
171
  return;
166
172
  }
167
- const headers = ['#', 'ID', 'STAGE', 'SEVERITY', 'STATUS', 'EVIDENCE', 'TITLE'];
173
+ const headers = ['#', 'CHOICE', 'WHAT NEEDS A DECISION', 'TRACKING'];
168
174
  const data = rows.map((r, i) => [
169
175
  String(i + 1),
170
176
  r.id,
171
- r.stage,
172
- r.severity,
173
- r.status,
174
- r.evidence,
175
- titleOf(r, taskDir)
177
+ titleOf(r, taskDir),
178
+ trackingOf(r)
176
179
  ]);
177
180
  if (format === 'markdown') {
178
181
  const sep = headers.map(() => '---');
@@ -181,10 +184,29 @@ function renderList(rows: LedgerRow[], format: string, taskDir: string): void {
181
184
  `| ${sep.join(' | ')} |`,
182
185
  ...data.map((row) => `| ${row.join(' | ')} |`)
183
186
  ];
184
- process.stdout.write(`${md.join('\n')}\n`);
187
+ process.stdout.write(`${md.join('\n')}\n\nView one item: ai task decisions <task-ref> <ordinal|ledger-id>\n`);
185
188
  return;
186
189
  }
187
- process.stdout.write(`${formatTable(headers, data).join('\n')}\n`);
190
+ process.stdout.write(`${formatTable(headers, data).join('\n')}\n\nView one item: ai task decisions <task-ref> <ordinal|ledger-id>\n`);
191
+ }
192
+
193
+ function renderTracking(row: LedgerRow, format: string): string[] {
194
+ if (format === 'markdown') {
195
+ return [
196
+ '**Tracking**',
197
+ `- stage: \`${row.stage}\``,
198
+ `- severity: \`${row.severity}\``,
199
+ `- status: \`${row.status}\``,
200
+ `- evidence: \`${row.evidence}\``
201
+ ];
202
+ }
203
+ return [
204
+ 'Tracking:',
205
+ `stage: ${row.stage}`,
206
+ `severity: ${row.severity}`,
207
+ `status: ${row.status}`,
208
+ `evidence: ${row.evidence}`
209
+ ];
188
210
  }
189
211
 
190
212
  function renderDetail(
@@ -201,25 +223,36 @@ function renderDetail(
201
223
  }
202
224
  const r = selected.row;
203
225
  const block = findDetailBlock(r, taskDir);
226
+ const title = titleOf(r, taskDir);
204
227
  const lines: string[] = [];
228
+ const decided = r.status === 'human-decided';
205
229
  if (format === 'markdown') {
206
- lines.push(`**${r.id}** (${r.stage}/${r.severity}) · status=\`${r.status}\` · evidence: \`${r.evidence}\``, '');
230
+ lines.push(`## ${decided ? 'Decision already recorded' : 'Decision needed'}: ${title}`, '');
231
+ if (!decided) {
232
+ lines.push('**How to record your choice**', `\`ai decide <task-ref> ${r.id} <your choice and rationale>\``, '');
233
+ }
234
+ lines.push('**Original context**', '');
207
235
  } else {
208
- lines.push(`${r.id} (${r.stage}/${r.severity}) status=${r.status}`, `evidence: ${r.evidence}`, '');
236
+ lines.push(`${decided ? 'Decision already recorded' : 'Decision needed'}: ${title}`, '');
237
+ if (!decided) {
238
+ lines.push('How to record your choice:', `ai decide <task-ref> ${r.id} <your choice and rationale>`, '');
239
+ }
240
+ lines.push('Original context:');
209
241
  }
210
242
  if (block) {
211
243
  lines.push(block);
212
244
  } else {
213
245
  lines.push(
214
- `(详情块未找到:未在任务产物中定位到 \`### ${r.id}\` 锚点,可能为历史产物或尚未写入;evidence 指向 ${r.evidence})`
246
+ `This older task does not include a full explanation for ${r.id}.`,
247
+ 'Use the tracking reference below to find the original context.'
215
248
  );
216
249
  }
217
- if (r.status === 'human-decided') {
250
+ if (decided) {
218
251
  const record = findDecisionRecord(r.id, content);
219
- if (record.length) {
220
- lines.push('', '人工裁定:', ...record);
221
- }
252
+ lines.push('', format === 'markdown' ? '**Recorded choice**' : 'Recorded choice:');
253
+ lines.push(...(record.length ? record : ['No separate ruling record was found.']));
222
254
  }
255
+ lines.push('', ...renderTracking(r, format));
223
256
  process.stdout.write(`${lines.join('\n')}\n`);
224
257
  }
225
258
 
@@ -0,0 +1,170 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ import type { AgentClientId } from '../agent-clients/types.ts';
4
+
5
+ type DelegationRole = 'executor' | 'reviewer';
6
+ type DelegationStage = 'analysis' | 'review-analysis' | 'plan' | 'review-plan' | 'code' | 'review-code' | 'commit';
7
+ type DelegationStatus = 'prepared' | 'activated' | 'stage-completed' | 'sealed' | 'consumed' | 'aborted' | 'expired';
8
+
9
+ type DelegationReceipt = Readonly<{
10
+ id: string;
11
+ taskId: string;
12
+ runId: string;
13
+ role: DelegationRole;
14
+ stage: DelegationStage;
15
+ round: number;
16
+ artifact: string;
17
+ client: AgentClientId;
18
+ requestedModel: string | null;
19
+ actualModel: string | null;
20
+ modelFallbackReason: string | null;
21
+ parentId: string | null;
22
+ childId: string | null;
23
+ spawnMode: string | null;
24
+ agent: string | null;
25
+ status: DelegationStatus;
26
+ beforeFingerprint: string;
27
+ afterFingerprint: string | null;
28
+ changedPaths: readonly string[];
29
+ createdAt: string;
30
+ activatedAt: string | null;
31
+ sealedAt: string | null;
32
+ consumedAt: string | null;
33
+ }>;
34
+
35
+ type ReceiptFailure = Readonly<{ ok: false; code: string; message: string; receipt?: never }>;
36
+ type ReceiptSuccess = Readonly<{ ok: true; receipt: DelegationReceipt; code?: never; message?: never }>;
37
+ type ReceiptResult = ReceiptSuccess | ReceiptFailure;
38
+
39
+ const MANAGED_AGENTS = {
40
+ 'agent-infra-lifecycle-executor': 'executor',
41
+ 'agent-infra-lifecycle-reviewer': 'reviewer'
42
+ } as const;
43
+
44
+ function managedDelegationRole(nativeAgent: string): DelegationRole | null {
45
+ return MANAGED_AGENTS[nativeAgent as keyof typeof MANAGED_AGENTS] ?? null;
46
+ }
47
+
48
+ function fail(code: string, message: string): ReceiptFailure {
49
+ return { ok: false, code, message };
50
+ }
51
+
52
+ function prepareDelegation(
53
+ input: Omit<DelegationReceipt, 'id' | 'actualModel' | 'modelFallbackReason' | 'parentId' | 'childId' | 'spawnMode' | 'agent' | 'status' | 'afterFingerprint' | 'changedPaths' | 'createdAt' | 'activatedAt' | 'sealedAt' | 'consumedAt'>,
54
+ options: { id?: () => string; now?: () => string } = {}
55
+ ): DelegationReceipt {
56
+ return Object.freeze({
57
+ ...input,
58
+ id: (options.id ?? randomUUID)(),
59
+ actualModel: null,
60
+ modelFallbackReason: null,
61
+ parentId: null,
62
+ childId: null,
63
+ spawnMode: null,
64
+ agent: null,
65
+ status: 'prepared' as const,
66
+ afterFingerprint: null,
67
+ changedPaths: Object.freeze([]),
68
+ createdAt: (options.now ?? (() => new Date().toISOString()))(),
69
+ activatedAt: null,
70
+ sealedAt: null,
71
+ consumedAt: null
72
+ });
73
+ }
74
+
75
+ function activateDelegation(
76
+ receipt: DelegationReceipt,
77
+ event: Readonly<{
78
+ nativeAgent: string;
79
+ childId: string;
80
+ parentId: string;
81
+ spawnMode: string;
82
+ actualModel?: string;
83
+ modelFallbackReason?: string;
84
+ }>,
85
+ options: { now?: () => string } = {}
86
+ ): ReceiptResult {
87
+ const managedRole = managedDelegationRole(event.nativeAgent);
88
+ if (!managedRole) return fail('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
89
+ if (receipt.status !== 'prepared') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
90
+ if (managedRole !== receipt.role) return fail('DELEGATION_ROLE_MISMATCH', `managed role ${managedRole} does not match ${receipt.role}`);
91
+ if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
92
+ return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
93
+ }
94
+ if (event.spawnMode !== 'fresh') return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
95
+ const actualModel = event.actualModel ?? null;
96
+ if (actualModel && receipt.requestedModel && actualModel !== receipt.requestedModel && !event.modelFallbackReason) {
97
+ return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
98
+ }
99
+ return { ok: true, receipt: Object.freeze({
100
+ ...receipt,
101
+ status: 'activated',
102
+ parentId: event.parentId,
103
+ childId: event.childId,
104
+ spawnMode: event.spawnMode,
105
+ actualModel,
106
+ modelFallbackReason: event.modelFallbackReason ?? null,
107
+ activatedAt: (options.now ?? (() => new Date().toISOString()))()
108
+ }) };
109
+ }
110
+
111
+ function completeDelegationStage(
112
+ receipt: DelegationReceipt,
113
+ event: Readonly<{ stage: DelegationStage; round: number; artifact: string; agent: string }>
114
+ ): ReceiptResult {
115
+ if (receipt.status !== 'activated') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
116
+ if (event.stage !== receipt.stage || event.round !== receipt.round || event.artifact !== receipt.artifact) {
117
+ return fail('DELEGATION_STAGE_MISMATCH', 'stage completion identity does not match the active delegation');
118
+ }
119
+ const acceptedAgents = receipt.client === 'claude-code' ? ['claude', 'claude-code'] : [receipt.client];
120
+ if (!acceptedAgents.includes(event.agent)) {
121
+ return fail('DELEGATION_AGENT_MISMATCH', `stage agent '${event.agent}' does not match client '${receipt.client}'`);
122
+ }
123
+ return { ok: true, receipt: Object.freeze({ ...receipt, status: 'stage-completed', agent: event.agent }) };
124
+ }
125
+
126
+ function sealDelegation(
127
+ receipt: DelegationReceipt,
128
+ event: Readonly<{ childId: string; exitCode: number; afterFingerprint: string; changedPaths: readonly string[] }>,
129
+ options: { now?: () => string } = {}
130
+ ): ReceiptResult {
131
+ if (receipt.status !== 'stage-completed') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
132
+ if (event.childId !== receipt.childId || event.exitCode !== 0) return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
133
+ if (receipt.role === 'reviewer') {
134
+ const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
135
+ const allowed = new Set([
136
+ `${taskRoot}${receipt.artifact}`,
137
+ `${taskRoot}task.md`,
138
+ `${taskRoot}orchestration.json`
139
+ ]);
140
+ const disallowed = event.changedPaths.find((entry) => !allowed.has(entry));
141
+ if (disallowed) return fail('DELEGATION_REVIEWER_WRITE_FORBIDDEN', `reviewer changed forbidden path '${disallowed}'`);
142
+ }
143
+ return { ok: true, receipt: Object.freeze({
144
+ ...receipt,
145
+ status: 'sealed',
146
+ afterFingerprint: event.afterFingerprint,
147
+ changedPaths: Object.freeze([...event.changedPaths]),
148
+ sealedAt: (options.now ?? (() => new Date().toISOString()))()
149
+ }) };
150
+ }
151
+
152
+ function consumeDelegation(receipt: DelegationReceipt, options: { now?: () => string } = {}): ReceiptResult {
153
+ if (receipt.status === 'consumed') return fail('DELEGATION_REPLAY', `delegation ${receipt.id} was already consumed`);
154
+ if (receipt.status !== 'sealed') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected sealed`);
155
+ return { ok: true, receipt: Object.freeze({
156
+ ...receipt,
157
+ status: 'consumed',
158
+ consumedAt: (options.now ?? (() => new Date().toISOString()))()
159
+ }) };
160
+ }
161
+
162
+ export {
163
+ activateDelegation,
164
+ completeDelegationStage,
165
+ consumeDelegation,
166
+ managedDelegationRole,
167
+ prepareDelegation,
168
+ sealDelegation
169
+ };
170
+ export type { DelegationReceipt, DelegationRole, DelegationStage, DelegationStatus, ReceiptResult };
@@ -16,8 +16,12 @@ import {
16
16
  parseImplementationInputs,
17
17
  renderImplementationInputs
18
18
  } from './implementation-inputs.ts';
19
+ import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
20
+ import type { ReviewStage } from './ledger.ts';
21
+ import { parseReviewSummary } from './review-artifacts.ts';
19
22
  import { resolveTaskRef } from './resolve-ref.ts';
20
23
  import { findSectionHeading } from './sections.ts';
24
+ import { completeOrchestrationStage, validateOrchestrationStage } from './orchestration.ts';
21
25
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
22
26
  import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
23
27
 
@@ -35,7 +39,8 @@ type Verdict = 'approved' | 'changes-requested' | 'rejected';
35
39
  type TaskEventErrorCode =
36
40
  | 'EVENT_UNKNOWN' | 'EVENT_PAYLOAD_INVALID' | 'EVENT_TRANSITION_INVALID'
37
41
  | 'EVENT_LOG_MISSING' | 'EVENT_START_MISSING' | 'EVENT_ALREADY_COMPLETED'
38
- | 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | ArtifactErrorCode | TaskWriteErrorCode;
42
+ | 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
43
+ | ArtifactErrorCode | TaskWriteErrorCode;
39
44
  type TaskEventRequest = {
40
45
  taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean;
41
46
  round?: number; question?: number; artifact?: string; fixFor?: string; implementationInput?: string;
@@ -105,7 +110,7 @@ function validateTaskEventRequest(request: TaskEventRequest): TaskEventError | n
105
110
  const FAMILY = {
106
111
  analyze: { artifact: 'analysis', started: ['requirement-analysis', 'requirement-analysis-review', 'code'], completed: ['requirement-analysis', 'requirement-analysis-review', 'code'], target: 'requirement-analysis', label: 'Analyze Task' },
107
112
  'review-analysis': { artifact: 'review-analysis', started: ['requirement-analysis', 'requirement-analysis-review'], completed: ['requirement-analysis', 'requirement-analysis-review'], target: 'requirement-analysis-review', label: 'Review Analysis' },
108
- plan: { artifact: 'plan', started: ['requirement-analysis-review', 'technical-design-review'], completed: ['requirement-analysis-review', 'technical-design-review'], target: 'technical-design', label: 'Plan Task' },
113
+ plan: { artifact: 'plan', started: ['requirement-analysis-review', 'technical-design-review', 'commit'], completed: ['requirement-analysis-review', 'technical-design-review', 'commit'], target: 'technical-design', label: 'Plan Task' },
109
114
  'review-plan': { artifact: 'review-plan', started: ['technical-design', 'technical-design-review'], completed: ['technical-design', 'technical-design-review'], target: 'technical-design-review', label: 'Review Plan' },
110
115
  code: { artifact: 'code', started: ['technical-design-review', 'code-review'], completed: ['technical-design-review', 'code-review'], target: 'code', label: 'Code Task' },
111
116
  'review-code': { artifact: 'review-code', started: ['code', 'code-review', 'commit'], completed: ['code', 'code-review', 'commit'], target: 'code-review', label: 'Review Code' },
@@ -113,6 +118,72 @@ const FAMILY = {
113
118
  } as const;
114
119
  type EventFamily = keyof typeof FAMILY;
115
120
 
121
+ const REVIEW_LEDGER_STAGES: Partial<Record<EventFamily, ReviewStage>> = {
122
+ 'review-analysis': 'analysis',
123
+ 'review-plan': 'plan',
124
+ 'review-code': 'code'
125
+ };
126
+
127
+ function validateReviewFindingCounts(
128
+ request: TaskEventRequest,
129
+ content: string,
130
+ family: EventFamily,
131
+ artifactPath: string | null
132
+ ): TaskEventError | null {
133
+ const stage = REVIEW_LEDGER_STAGES[family];
134
+ if (!stage || !artifactPath) return null;
135
+ const rows = parseLedger(content);
136
+ const invalid = validateLedgerRows(rows);
137
+ if (invalid) return { code: 'TASK_DOCUMENT_INVALID', message: `${invalid.code}: ${invalid.message}` };
138
+ const expected = summarizeLedgerStage(rows, stage).unresolvedFindingCounts;
139
+ const payload = {
140
+ blocker: request.blockers!,
141
+ major: request.major!,
142
+ minor: request.minor!
143
+ };
144
+ let reportContent: string;
145
+ try {
146
+ reportContent = fs.readFileSync(artifactPath, 'utf8');
147
+ } catch (error) {
148
+ return { code: 'EVENT_FINDING_COUNT_MISMATCH', message: String(error) };
149
+ }
150
+ const parsed = parseReviewSummary(reportContent);
151
+ if (!parsed.ok || !parsed.summary.counts) {
152
+ return {
153
+ code: 'EVENT_FINDING_COUNT_MISMATCH',
154
+ message: parsed.ok ? 'review summary finding counts are not finalized' : parsed.message
155
+ };
156
+ }
157
+ const report = parsed.summary.counts;
158
+ const reportVerdict = parsed.summary.verdict === 'Approved'
159
+ ? 'approved'
160
+ : parsed.summary.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
161
+ const fields = [
162
+ ['blocker', 'blockers'],
163
+ ['major', 'major'],
164
+ ['minor', 'minor']
165
+ ] as const;
166
+ const differences = fields.flatMap(([severity, cliField]) => {
167
+ const values = [
168
+ expected[severity] === payload[severity]
169
+ ? null
170
+ : `${cliField} ledger ${expected[severity]}, payload ${payload[severity]}`,
171
+ expected[severity] === report[severity]
172
+ ? null
173
+ : `${cliField} ledger ${expected[severity]}, report ${report[severity]}`
174
+ ];
175
+ return values.filter((value): value is string => value !== null);
176
+ });
177
+ if (request.verdict !== reportVerdict) {
178
+ differences.push(`verdict report ${reportVerdict}, payload ${request.verdict}`);
179
+ }
180
+ if (differences.length === 0) return null;
181
+ return {
182
+ code: 'EVENT_FINDING_COUNT_MISMATCH',
183
+ message: `review summary, payload, and ${stage} ledger do not match: ${differences.join('; ')}`
184
+ };
185
+ }
186
+
116
187
  function eventParts(event: string): { family: EventFamily; phase: 'started' | 'waiting' | 'completed' } {
117
188
  const [family, suffix] = event.split('.') as [EventFamily, string];
118
189
  return { family, phase: suffix === 'started' ? 'started' : suffix === 'awaiting-input' ? 'waiting' : 'completed' };
@@ -256,6 +327,28 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskWriteOptions = {
256
327
  }
257
328
  const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
258
329
  if (!(allowed as readonly string[]).includes(currentStep)) return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
330
+ const findingCountError = validateReviewFindingCounts(
331
+ normalized,
332
+ content,
333
+ eventIdentity.family,
334
+ completedArtifact?.path ?? null
335
+ );
336
+ if (findingCountError) return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
337
+ if (eventIdentity.phase === 'completed' && !normalized.dryRun && eventIdentity.family !== 'manual-validation') {
338
+ const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
339
+ const provenance = validateOrchestrationStage(normalized.taskRef, {
340
+ stage: orchestrationStage,
341
+ round: normalized.round!,
342
+ artifact: normalized.artifact!,
343
+ role: eventIdentity.family.startsWith('review-') ? 'reviewer' : 'executor'
344
+ }, { repoRoot: options.repoRoot });
345
+ if (provenance.status === 'failed' || provenance.status === 'paused') {
346
+ return failed(normalized, {
347
+ code: 'EVENT_TRANSITION_INVALID',
348
+ message: provenance.error?.message ?? provenance.run?.pause?.message ?? 'orchestration provenance validation failed'
349
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
350
+ }
351
+ }
259
352
  let metadata;
260
353
  try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
261
354
  catch (error) { return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
@@ -291,6 +384,21 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskWriteOptions = {
291
384
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
292
385
  const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
293
386
  if (result.status === 'failed') return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
387
+ if (eventIdentity.phase === 'completed' && !normalized.dryRun && eventIdentity.family !== 'manual-validation') {
388
+ const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
389
+ const orchestration = completeOrchestrationStage(normalized.taskRef, {
390
+ stage: orchestrationStage,
391
+ round: normalized.round!,
392
+ artifact: normalized.artifact!,
393
+ agent: normalized.agent
394
+ }, { repoRoot: options.repoRoot });
395
+ if (orchestration.status === 'failed' || orchestration.status === 'paused') {
396
+ return failed(normalized, {
397
+ code: 'EVENT_TRANSITION_INVALID',
398
+ message: orchestration.error?.message ?? orchestration.run?.pause?.message ?? 'orchestration provenance validation failed'
399
+ }, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
400
+ }
401
+ }
294
402
  return {
295
403
  status: result.status, changed: result.changed, event: normalized.event,
296
404
  requestRef: normalized.taskRef, taskId: result.taskId, taskMdPath: result.taskMdPath,