@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) 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 +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -24,20 +24,30 @@ import { resolveMaterializedReviewedHeadRelation } from "../platform/change-requ
24
24
  import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.ts";
25
25
  import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.ts";
26
26
  import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
27
- import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
27
+ import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
28
28
  import type { LedgerRow } from "./ledger.ts";
29
29
  import { isValidAgentInfraVersion } from "../version.ts";
30
30
  import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
31
31
  import { inspectDecisionDetailDuplicates } from "./decision-details.ts";
32
+ import { parseImplementationInputs, type ImplementationInput } from "./implementation-inputs.ts";
33
+ import { scanVisibleMarkdown } from "./markdown.ts";
32
34
  import { loadVerificationConfig } from "./verification-config.ts";
33
35
  import { snapshotReview } from "../git/review-snapshot.ts";
36
+ import { inspectActivityLog } from "./activity-log.ts";
37
+ import { parseWorkflowWarnings } from "./workflow-warnings.ts";
34
38
  import { OrchestrationStateError, readRun } from "./orchestration.ts";
39
+ import type { OrchestrationRun } from "./orchestration.ts";
35
40
  import { resolveDeliveryTarget } from "./delivery-target.ts";
36
41
  import { readPrDeliveryFact } from "./pr-delivery-fact.ts";
37
42
  import { validateLocalArtifact } from "./local-artifact-finalization.ts";
38
43
  import { validateQualificationAudit } from "./qualification-audit.ts";
39
44
  import { getArtifactSchema } from "./artifact-schema.ts";
40
45
  import { inspectArtifactContract } from "./artifact-operations.ts";
46
+ import type { VerificationShared } from "./verification-types.ts";
47
+ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.ts";
48
+ import { readManualValidationCompletion } from "./manual-validation-completion.ts";
49
+ import { sha256File } from "./artifact-receipts.ts";
50
+ import { summaryCommentState } from "../platform/pr-summary.ts";
41
51
 
42
52
  const TASK_ENUMS = {
43
53
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
@@ -58,7 +68,6 @@ const DEFAULT_REQUIRED_FIELDS = [
58
68
  ];
59
69
 
60
70
  const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
61
- const ACTIVITY_LOG_PATTERN = /^- (\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?) — \*\*(.+?)\*\* by (.+?) — (.+)$/;
62
71
  // Start markers (action suffixed with ` [started]`) are excluded from the
63
72
  // "latest action" / freshness computation so a step's in-flight marker never
64
73
  // satisfies a skill's expected_action_pattern; the matching done entry does.
@@ -67,28 +76,12 @@ const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
67
76
 
68
77
  // Review disagreement ledger (see .agents/rules/review-handshake.md).
69
78
  const LEDGER_SECTION_NAMES = ["审查分歧账本", "Review Disagreement Ledger"];
70
- const LEDGER_STATUSES = new Set([
71
- "open",
72
- "accepted",
73
- "adjusted",
74
- "refuted",
75
- "cannot-judge",
76
- "confirmed",
77
- "needs-human-decision",
78
- "closed",
79
- "human-decided"
80
- ]);
81
- const LEDGER_TERMINAL_OK = new Set(["confirmed", "closed", "human-decided"]);
82
79
  const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
83
80
  const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
84
81
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
85
- const WORKFLOW_WARNING_SECTION_NAMES = ["工作流告警", "Workflow Warnings"];
86
- const WORKFLOW_WARNING_STATUSES = new Set(["open", "resolved", "ignored"]);
87
- const WORKFLOW_WARNING_SEVERITIES = new Set(["IMPORTANT", "ACTION_REQUIRED"]);
88
- const WORKFLOW_WARNING_ID_PATTERN = /^WW-\d+$/;
89
82
 
90
83
  const scriptPath = fileURLToPath(import.meta.url);
91
- let repoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
84
+ const defaultRepoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
92
85
 
93
86
  const PLATFORM_ADAPTERS: Record<string, (context: any, shared: any) => any> = {
94
87
  "platform-sync": checkPlatformSync,
@@ -160,7 +153,7 @@ function checkOrchestrationState({ taskDir }: any): any {
160
153
  const file = path.join(taskDir, 'orchestration.json');
161
154
  const stat = safeStat(file);
162
155
  if (!stat?.isFile()) return failResult('orchestration-state', 'orchestration.json is missing');
163
- let run: any;
156
+ let run: OrchestrationRun | null;
164
157
  try {
165
158
  run = readRun(taskDir);
166
159
  } catch (error) {
@@ -192,18 +185,16 @@ function exactText(value: unknown): value is string {
192
185
  return typeof value === 'string' && value.length > 0 && value.trim() === value;
193
186
  }
194
187
 
195
- function validateCleanCompletionEvidence(run: any): string | null {
188
+ function validateCleanCompletionEvidence(run: OrchestrationRun): string | null {
196
189
  const evidence = run.completionEvidence;
197
190
  const shaPattern = /^[0-9a-f]{40}$/i;
198
191
  if (
199
- evidence?.kind !== 'reviewed-head-clean'
200
- || !exactText(evidence.observedAt)
192
+ !evidence
201
193
  || Number.isNaN(Date.parse(evidence.observedAt))
202
194
  || !shaPattern.test(evidence.head)
203
195
  || !shaPattern.test(evidence.headTree)
204
196
  || !shaPattern.test(evidence.worktreeTree)
205
197
  || !shaPattern.test(evidence.lastReviewedCommit)
206
- || !(evidence.prNumber === null || (Number.isInteger(evidence.prNumber) && evidence.prNumber > 0))
207
198
  || !(evidence.prHead === null || shaPattern.test(evidence.prHead))
208
199
  ) {
209
200
  return 'Clean completion evidence has an invalid structure';
@@ -220,8 +211,7 @@ function validateCleanCompletionEvidence(run: any): string | null {
220
211
  || run.pendingDelegation !== null
221
212
  || run.commitAuthorization?.issuedAt !== null
222
213
  || run.commitAuthorization?.consumedAt !== null
223
- || !Array.isArray(run.receipts)
224
- || run.receipts.some((receipt: any) => receipt.stage === 'commit')
214
+ || run.receipts.some((receipt) => receipt.stage === 'commit')
225
215
  ) {
226
216
  return 'Clean completion evidence conflicts with commit delegation state';
227
217
  }
@@ -232,54 +222,16 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
232
222
  const file = path.join(taskDir, 'orchestration.json');
233
223
  const stat = safeStat(file);
234
224
  if (!stat?.isFile()) return failResult('orchestration-evidence', 'orchestration.json is missing');
235
- let run: any;
225
+ let run: OrchestrationRun | null;
236
226
  try {
237
227
  run = readRun(taskDir);
238
228
  } catch (error) {
239
229
  const message = error instanceof OrchestrationStateError ? error.message : String(error);
240
230
  return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
241
231
  }
232
+ if (!run) return failResult('orchestration-evidence', 'orchestration.json is missing');
233
+ // readRun validates the persisted schema, receipt identities and client provenance.
242
234
  const policy = run.modelPolicy;
243
- if (
244
- !policy
245
- || !exactText(policy.executor?.model)
246
- || !exactText(policy.executor?.reasoningEffort)
247
- || !exactText(policy.reviewer?.model)
248
- || !exactText(policy.reviewer?.reasoningEffort)
249
- ) {
250
- return failResult('orchestration-evidence', 'Run model policy requires exact executor and reviewer model and effort');
251
- }
252
- if (
253
- !run.modelPolicySource
254
- || !['explicit', 'project-config'].includes(run.modelPolicySource.kind)
255
- || !exactText(run.modelPolicySource.client)
256
- || !exactText(run.modelPolicySource.resolvedAt)
257
- || !Array.isArray(run.recoveryHistory)
258
- ) {
259
- return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
260
- }
261
- for (const recovery of run.recoveryHistory) {
262
- const guards = recovery.guards;
263
- const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
264
- && recovery.previousStatus === 'paused'
265
- && recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
266
- && recovery.client === 'claude-code'
267
- && recovery.resultingStatus === 'running'
268
- && exactText(recovery.recoveredAt)
269
- && guards?.stepCount === 0
270
- && guards?.nextStage === null
271
- && guards?.baselineEmpty === true
272
- && guards?.receiptCount === 0
273
- && guards?.pendingDelegation === false
274
- && guards?.commitAuthorizationUnused === true
275
- && guards?.completionEvidenceAbsent === true;
276
- if (!validClaudeCodeRecovery) {
277
- return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
278
- }
279
- }
280
- if (!Array.isArray(run.receipts)) {
281
- return failResult('orchestration-evidence', 'Run receipts must be an array');
282
- }
283
235
  if (run.completionEvidence != null) {
284
236
  const evidenceError = validateCleanCompletionEvidence(run);
285
237
  if (evidenceError) return failResult('orchestration-evidence', evidenceError);
@@ -309,10 +261,6 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
309
261
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
310
262
  }
311
263
  }
312
- // parentId/childId 校验对所有 client 保持统一(不新增分支);spawnMode 检查按 client 判断
313
- if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId) {
314
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid delegation identity`);
315
- }
316
264
  if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
317
265
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
318
266
  }
@@ -338,50 +286,9 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
338
286
  ) {
339
287
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
340
288
  }
341
- if (receipt.client === 'codex') {
342
- const activatedCodex = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
343
- const host = receipt.hostEvidence;
344
- const provenance = receipt.lifecycleProvenance;
345
- if (
346
- !provenance
347
- || (activatedCodex && (
348
- host?.kind !== 'codex-lifecycle-v2'
349
- || host.protocolVersion !== provenance.protocolVersion
350
- || host.hookDefinitionHash !== provenance.hookDefinitionHash
351
- || host.hookSource !== provenance.hookSource
352
- || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
353
- || host.hookSourceHash !== provenance.hookSourceHash
354
- || host.capabilitySessionId !== provenance.capabilitySessionId
355
- || receipt.parentId !== provenance.capabilitySessionId
356
- || host.capabilityTurnId !== provenance.capabilityTurnId
357
- || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
358
- || host.controlGeneration !== provenance.controlGeneration
359
- || host.spawnToolUseId === provenance.capabilityToolUseId
360
- || !exactText(host.spawnToolUseId)
361
- || !Number.isFinite(Date.parse(host.spawnObservedAt ?? ''))
362
- || !Number.isFinite(Date.parse(receipt.spawnDispatchedAt ?? ''))
363
- || !Number.isFinite(Date.parse(receipt.activationDeadlineAt ?? ''))
364
- || Date.parse(host.spawnObservedAt ?? '') < Date.parse(receipt.spawnDispatchedAt ?? '')
365
- || Date.parse(host.spawnObservedAt ?? '') > Date.parse(receipt.activationDeadlineAt ?? '')
366
- || !exactText(host.hookDefinitionHash)
367
- || !Number.isSafeInteger(host.startRevision)
368
- || host.startRevision < 1
369
- ))
370
- ) {
371
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
372
- }
373
- if (['sealed', 'consumed'].includes(receipt.status) && (
374
- !Number.isSafeInteger(host.stopRevision)
375
- || host.stopRevision <= host.startRevision
376
- || host.consumer !== receipt.id
377
- || !exactText(host.consumedAt)
378
- )) {
379
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex consumed stop evidence`);
380
- }
381
- }
382
289
  }
383
290
  }
384
- if (run.receipts.some((receipt: any) => receipt.status !== 'consumed')) {
291
+ if (run.receipts.some((receipt) => receipt.status !== 'consumed')) {
385
292
  return failResult('orchestration-evidence', 'Historical receipts must be consumed');
386
293
  }
387
294
  return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
@@ -389,7 +296,7 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
389
296
 
390
297
  // === Check Functions ===
391
298
 
392
- function checkTaskMeta({ taskDir, config }: any): any {
299
+ function checkTaskMeta({ taskDir, config, repositoryRoot }: any): any {
393
300
  const task = loadTask(taskDir);
394
301
  if (!task.ok) {
395
302
  return failResult("task-meta", task.message);
@@ -427,14 +334,15 @@ function checkTaskMeta({ taskDir, config }: any): any {
427
334
  }
428
335
  }
429
336
 
430
- const branchValidationError = validateTaskBranch(metadata);
337
+ const branchValidationError = validateTaskBranch(metadata, repositoryRoot);
431
338
  if (branchValidationError) {
432
339
  return failResult("task-meta", branchValidationError);
433
340
  }
434
341
 
435
- const warningValidationErrors = validateWorkflowWarnings(task.content);
436
- if (warningValidationErrors.length > 0) {
437
- return failResult("task-meta", `Invalid Workflow Warnings: ${warningValidationErrors.join("; ")}`);
342
+ try {
343
+ parseWorkflowWarnings(task.content);
344
+ } catch (error) {
345
+ return failResult("task-meta", `Invalid Workflow Warnings: ${error instanceof Error ? error.message : String(error)}`);
438
346
  }
439
347
 
440
348
  const expectedStep = config.expected_step;
@@ -552,12 +460,12 @@ async function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any):
552
460
  return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
553
461
  }
554
462
 
555
- function validateTaskBranch(metadata: any): any {
463
+ function validateTaskBranch(metadata: any, repoRoot: string): any {
556
464
  if (isBlank(metadata.branch)) {
557
465
  return null;
558
466
  }
559
467
 
560
- const projectName = loadProjectName();
468
+ const projectName = loadProjectName(repoRoot);
561
469
  const expectedPrefix = projectName ? `${projectName}-${metadata.type}-` : "";
562
470
 
563
471
  if (expectedPrefix && !String(metadata.branch).startsWith(expectedPrefix)) {
@@ -572,7 +480,7 @@ function validateTaskBranch(metadata: any): any {
572
480
  return null;
573
481
  }
574
482
 
575
- function loadProjectName(): any {
483
+ function loadProjectName(repoRoot: string): any {
576
484
  const configPath = path.join(repoRoot, ".agents", ".airc.json");
577
485
  if (!fs.existsSync(configPath)) {
578
486
  return "";
@@ -743,31 +651,17 @@ function checkImplementationInput({ taskDir, artifactFile }: any): any {
743
651
  const artifactPath = path.join(taskDir, artifactFile);
744
652
  if (!safeStat(artifactPath)?.isFile()) return failResult("implementation-input", `Artifact not found: ${artifactFile}`);
745
653
 
746
- const inputSection = getSectionContent(task.content, ["实现输入", "Implementation Inputs"]);
747
- const rows = [];
748
- if (inputSection) {
749
- const table = inputSection.split(/\r?\n/).filter((line: any) => line.trim().startsWith("|"));
750
- const cells = (line: any) => line.split("|").slice(1, -1).map((cell: any) => cell.trim());
751
- const expected = ["id", "ledger_id", "decision_evidence", "stage", "needs_implementation", "decided_at", "status", "consumed_by"];
752
- if (table.length < 2 || JSON.stringify(cells(table[0])) !== JSON.stringify(expected)) {
753
- return failResult("implementation-input", "Implementation Inputs table schema is invalid");
754
- }
755
- const seen = new Set();
756
- for (const line of table.slice(2)) {
757
- const row = cells(line);
758
- if (row.length !== 8 || !/^II-[1-9]\d*$/.test(row[0]) || seen.has(row[0])) {
759
- return failResult("implementation-input", "Implementation Inputs table contains an invalid or duplicate id");
760
- }
761
- seen.add(row[0]);
762
- rows.push({ id: row[0], ledgerId: row[1], evidence: row[2], stage: row[3], needs: row[4], status: row[6], consumedBy: row[7] });
763
- }
654
+ let rows: ImplementationInput[];
655
+ try {
656
+ rows = parseImplementationInputs(task.content).rows;
657
+ } catch (error) {
658
+ return failResult("implementation-input", `Invalid Implementation Inputs: ${error instanceof Error ? error.message : String(error)}`);
764
659
  }
765
660
 
766
- const logSection = getSectionContent(task.content, ["活动日志", "Activity Log"]);
767
- const doneActions = logSection.split(/\r?\n/).flatMap((line: any) => {
768
- const match = line.trim().match(ACTIVITY_LOG_PATTERN);
769
- return match && !ACTIVITY_LOG_STARTED_RE.test(match[2]) ? [match[2]] : [];
770
- });
661
+ const doneActions = (inspectActivityLog(task.content).section?.entries ?? [])
662
+ .filter((entry) => !ACTIVITY_LOG_STARTED_RE.test(entry.step))
663
+ .map((entry) => entry.step)
664
+ .filter((step) => /^(?:Code Task|Code) \(Round \d+/.test(step));
771
665
  const latestAction = doneActions.at(-1) || "";
772
666
  const actionDecision = /(?:Code Task|Code) \(Round \d+, decision (II-[1-9]\d*)\)/.exec(latestAction)?.[1] || null;
773
667
  const report = fs.readFileSync(artifactPath, "utf8");
@@ -794,10 +688,10 @@ function checkImplementationInput({ taskDir, artifactFile }: any): any {
794
688
  const matches = rows.filter((row) => row.id === actionDecision);
795
689
  if (matches.length !== 1) return failResult("implementation-input", `${actionDecision} is missing or duplicated in task table`);
796
690
  const row = matches[0]!;
797
- if (row.stage !== "code" || row.needs !== "true" || row.status !== "consumed" || row.consumedBy !== artifactFile || !row.evidence) {
691
+ if (row.stage !== "code" || !row.needsImplementation || row.status !== "consumed" || row.consumedBy !== artifactFile || !row.decisionEvidence) {
798
692
  return failResult("implementation-input", `${actionDecision} is not a consumed input for ${artifactFile}`);
799
693
  }
800
- if (reportLedger !== row.ledgerId || reportEvidence !== row.evidence) {
694
+ if (reportLedger !== row.ledgerId || reportEvidence !== row.decisionEvidence) {
801
695
  return failResult("implementation-input", `${actionDecision} report identity does not match task table evidence`);
802
696
  }
803
697
  return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
@@ -809,19 +703,11 @@ function checkActivityLog({ taskDir, config }: any): any {
809
703
  return failResult("activity-log", task.message);
810
704
  }
811
705
 
812
- const logSection = getSectionContent(task.content, ["活动日志", "Activity Log"]);
813
- if (!logSection) {
814
- return failResult("activity-log", "Activity Log section not found");
815
- }
816
-
817
- const entries = logSection
818
- .split(/\r?\n/)
819
- .map((line: any) => line.trim())
820
- .filter((line: any) => line.startsWith("- "));
821
-
822
- if (entries.length === 0) {
823
- return failResult("activity-log", "Activity Log has no entries");
824
- }
706
+ const { section, invalidEntries } = inspectActivityLog(task.content);
707
+ if (!section) return failResult("activity-log", "Activity Log section not found or ambiguous");
708
+ if (invalidEntries.length) return failResult("activity-log", `Invalid Activity Log entry format: ${invalidEntries[0]}`);
709
+ const entries = section.entries;
710
+ if (entries.length === 0) return failResult("activity-log", "Activity Log has no entries");
825
711
 
826
712
  let previousTimestamp = "";
827
713
  let latestAction = "";
@@ -829,12 +715,8 @@ function checkActivityLog({ taskDir, config }: any): any {
829
715
  const doneActions: string[] = [];
830
716
 
831
717
  for (const entry of entries) {
832
- const match = entry.match(ACTIVITY_LOG_PATTERN);
833
- if (!match) {
834
- return failResult("activity-log", `Invalid Activity Log entry format: ${entry}`);
835
- }
836
-
837
- const [, timestamp, action] = match;
718
+ if (!entry.note.trim()) return failResult("activity-log", "Activity Log entry note is required");
719
+ const { time: timestamp, step: action } = entry;
838
720
  if (previousTimestamp && timestamp < previousTimestamp) {
839
721
  return failResult("activity-log", "Activity Log timestamps are not in ascending order");
840
722
  }
@@ -906,118 +788,7 @@ function checkCompletionChecklist({ taskDir, config }: any): any {
906
788
  return passResult("completion-checklist", `Completion Checklist valid (${items.length} items checked)`);
907
789
  }
908
790
 
909
- function splitMarkdownTableRow(line: any): any {
910
- let value = String(line || "").trim();
911
- if (!value.startsWith("|")) {
912
- return [];
913
- }
914
- value = value.replace(/^\|/, "").replace(/\|$/, "");
915
-
916
- const cells = [];
917
- let cell = "";
918
- for (let index = 0; index < value.length; index += 1) {
919
- const char = value[index];
920
- if (char === "|" && !isEscapedAt(value, index)) {
921
- cells.push(unescapeMarkdownTableCell(cell.trim()));
922
- cell = "";
923
- continue;
924
- }
925
- cell += char;
926
- }
927
- cells.push(unescapeMarkdownTableCell(cell.trim()));
928
- return cells;
929
- }
930
-
931
- function unescapeMarkdownTableCell(value: any): any {
932
- let output = "";
933
- for (let index = 0; index < value.length; index += 1) {
934
- const char = value[index];
935
- const next = value[index + 1];
936
- if (char === "\\" && (next === "\\" || next === "|")) {
937
- output += next;
938
- index += 1;
939
- continue;
940
- }
941
- output += char;
942
- }
943
- return output;
944
- }
945
-
946
- function isEscapedAt(value: any, index: any): any {
947
- let backslashes = 0;
948
- for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
949
- backslashes += 1;
950
- }
951
- return backslashes % 2 === 1;
952
- }
953
-
954
- function parseWorkflowWarningRows(section: any): any {
955
- const rows = [];
956
- for (const rawLine of String(section || "").split(/\r?\n/)) {
957
- const cells = splitMarkdownTableRow(rawLine);
958
- if (cells.length === 0) {
959
- continue;
960
- }
961
- if ((cells[0] || "").toLowerCase() === "id") {
962
- continue;
963
- }
964
- if (cells.every((cell: any) => /^:?-{3,}:?$/.test(cell))) {
965
- continue;
966
- }
967
- rows.push(cells);
968
- }
969
- return rows;
970
- }
971
-
972
- function validateWorkflowWarnings(content: any): any {
973
- const section = getSectionContent(content, WORKFLOW_WARNING_SECTION_NAMES);
974
- if (!section.trim()) {
975
- return [];
976
- }
977
-
978
- const rows = parseWorkflowWarningRows(section);
979
- const errors = [];
980
- for (const cells of rows) {
981
- if (cells.length < 11) {
982
- errors.push(`malformed row (expected 11 columns): ${cells.join(" | ")}`);
983
- continue;
984
- }
985
- const [id, time, step, severity, code, status, target, message, action, resolvedAt, resolution] = cells;
986
- if (!WORKFLOW_WARNING_ID_PATTERN.test(id)) {
987
- errors.push(`${id || "(empty id)"}: invalid id`);
988
- }
989
- if (!DATE_TIME_PATTERN.test(time)) {
990
- errors.push(`${id}: invalid time '${time}'`);
991
- }
992
- if (isBlank(step)) {
993
- errors.push(`${id}: step is required`);
994
- }
995
- if (!WORKFLOW_WARNING_SEVERITIES.has(severity)) {
996
- errors.push(`${id}: illegal severity '${severity}'`);
997
- }
998
- if (isBlank(code)) {
999
- errors.push(`${id}: code is required`);
1000
- }
1001
- if (!WORKFLOW_WARNING_STATUSES.has(status)) {
1002
- errors.push(`${id}: illegal status '${status}'`);
1003
- }
1004
- if (isBlank(target)) {
1005
- errors.push(`${id}: target is required`);
1006
- }
1007
- if (isBlank(message)) {
1008
- errors.push(`${id}: message is required`);
1009
- }
1010
- if (status === "open" && isBlank(action)) {
1011
- errors.push(`${id}: open warning requires action`);
1012
- }
1013
- if ((status === "resolved" || status === "ignored") && (isBlank(resolvedAt) || isBlank(resolution))) {
1014
- errors.push(`${id}: ${status} warning requires resolved_at and resolution`);
1015
- }
1016
- }
1017
- return errors;
1018
- }
1019
-
1020
- function resolveReviewSetting(config: any, key: any, fallback: any): any {
791
+ function resolveReviewSetting(config: any, key: any, fallback: any, repoRoot: string): any {
1021
792
  if (config && config[key] !== undefined && config[key] !== null) {
1022
793
  return config[key];
1023
794
  }
@@ -1028,7 +799,7 @@ function resolveReviewSetting(config: any, key: any, fallback: any): any {
1028
799
  return fallback;
1029
800
  }
1030
801
 
1031
- function checkReviewLedger({ taskDir, config }: any): any {
802
+ function checkReviewLedger({ taskDir, config, repositoryRoot }: any): any {
1032
803
  const task = loadTask(taskDir);
1033
804
  if (!task.ok) {
1034
805
  return failResult("review-ledger", task.message);
@@ -1038,9 +809,7 @@ function checkReviewLedger({ taskDir, config }: any): any {
1038
809
  try {
1039
810
  const ledger = parseLedgerDocument(task.content);
1040
811
  if (!ledger.present) return failResult("review-ledger", `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
1041
- rows = ledger.rows.map((row) => [
1042
- row.id, row.stage, row.round, row.severity, row.status, row.evidence
1043
- ]);
812
+ rows = ledger.rows;
1044
813
  } catch (error) {
1045
814
  return failResult("review-ledger", `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`);
1046
815
  }
@@ -1049,17 +818,12 @@ function checkReviewLedger({ taskDir, config }: any): any {
1049
818
  }
1050
819
 
1051
820
  const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
1052
- const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
821
+ const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS, repositoryRoot));
1053
822
  const problems = [];
1054
823
  let inScopeCount = 0;
1055
824
 
1056
- for (const cells of rows) {
1057
- if (cells.length < 6) {
1058
- problems.push(`malformed row (expected 6 columns): ${cells.join(" | ")}`);
1059
- continue;
1060
- }
1061
-
1062
- const [id, stage, roundRaw, , status, evidence] = cells;
825
+ for (const row of rows) {
826
+ const { id, stage, round: roundRaw, status, evidence } = row;
1063
827
  const stageScoped = stageScope ? stageScope.includes(stage) : true;
1064
828
  // post-review-commit exemption rows are consumed by the post-review-commit
1065
829
  // check, not enforced here.
@@ -1071,8 +835,9 @@ function checkReviewLedger({ taskDir, config }: any): any {
1071
835
  }
1072
836
  inScopeCount += 1;
1073
837
 
1074
- if (!LEDGER_STATUSES.has(status!)) {
1075
- problems.push(`${id}: illegal status '${status}'`);
838
+ const invalid = validateLedgerRows([row]);
839
+ if (invalid) {
840
+ problems.push(`${invalid.code}: ${invalid.message}`);
1076
841
  continue;
1077
842
  }
1078
843
  if (status !== "open" && evidence === "") {
@@ -1082,12 +847,12 @@ function checkReviewLedger({ taskDir, config }: any): any {
1082
847
  if (
1083
848
  Number.isFinite(round) &&
1084
849
  round >= maxRounds &&
1085
- !LEDGER_TERMINAL_OK.has(status!) &&
850
+ !LEDGER_TERMINAL.has(status!) &&
1086
851
  status !== "needs-human-decision"
1087
852
  ) {
1088
853
  problems.push(`${id}: round ${round} reached limit ${maxRounds} without convergence; escalate to needs-human-decision`);
1089
854
  }
1090
- if (!LEDGER_TERMINAL_OK.has(status!)) {
855
+ if (!LEDGER_TERMINAL.has(status!)) {
1091
856
  problems.push(`${id}: unresolved (status '${status}')`);
1092
857
  }
1093
858
  }
@@ -1100,7 +865,7 @@ function checkReviewLedger({ taskDir, config }: any): any {
1100
865
  return passResult("review-ledger", `Disagreement ledger clean (${inScopeCount} in-scope entries terminal${scopeLabel})`);
1101
866
  }
1102
867
 
1103
- function checkManualValidation({ taskDir }: any): any {
868
+ async function checkManualValidation({ taskDir, repositoryRoot }: any): Promise<any> {
1104
869
  // 1. Latest review-code artifact; none -> pass (no review, check not applicable).
1105
870
  const review = findAuthoritativeReviewCodeArtifact(taskDir);
1106
871
  if (!review.ok) {
@@ -1132,15 +897,23 @@ function checkManualValidation({ taskDir }: any): any {
1132
897
  const artifactName = path.basename(resolved.path);
1133
898
  const task = loadTask(taskDir);
1134
899
  if (!task.ok) return failResult("manual-validation", task.message);
1135
- const log = getSectionContent(task.content, ["活动日志", "Activity Log"]);
1136
- const completed = new RegExp(
1137
- `\\*\\*Complete Manual Validation\\*\\*[\\s\\S]*?Manual validation passed → ${escapeRegExp(artifactName)};`
1138
- );
1139
- if (!completed.test(log)) {
1140
- return failResult(
1141
- "manual-validation",
1142
- `Manual validation artifact exists but completion is not recorded for ${artifactName}: re-run /complete-manual-validation`
1143
- );
900
+ const completion = readManualValidationCompletion(taskDir, {
901
+ taskId: task.metadata.id,
902
+ artifact: artifactName
903
+ });
904
+ if (!completion.ok) {
905
+ const source = completion.error.code.startsWith('MANUAL_VALIDATION_RECEIPT_') ? 'receipt' : 'transaction';
906
+ return failResult("manual-validation", `Manual validation ${source} is unavailable: ${completion.error.message}`);
907
+ }
908
+ const { receipt, transaction } = completion.value;
909
+ if (transaction.phase !== "committed" || !transaction.eventAppended || !transaction.postWriteVerified) {
910
+ return failResult("manual-validation", "Manual validation transaction has not reached the committed post-write-verified state");
911
+ }
912
+ if (transaction.pendingSummaryDigest !== receipt.pendingSummaryDigest || transaction.finalSummaryDigest !== receipt.finalSummaryDigest) {
913
+ return failResult("manual-validation", "Manual validation summary digests do not match the committed receipt");
914
+ }
915
+ if (sha256File(resolved.path) !== receipt.artifactSha256) {
916
+ return failResult("manual-validation", `Manual validation artifact digest does not match the receipt for ${artifactName}`);
1144
917
  }
1145
918
  // 6. Timing correlation (PL-3 fix, PL-4 corrected): the completion entry must
1146
919
  // sit AFTER the latest review-code round's completion entry in the
@@ -1149,28 +922,32 @@ function checkManualValidation({ taskDir }: any): any {
1149
922
  // which indexOf never matches). Not reusing completed.exec().index: exec
1150
923
  // anchors to the first '**Complete Manual Validation**' heading, which can
1151
924
  // belong to an earlier round's entry and misorder the standard path.
1152
- const reviewDoneIndex = log.indexOf(`**Review Code (Round ${review.round})** by `);
1153
- const completedIndex = log.indexOf(`Manual validation passed → ${artifactName};`);
1154
- if (reviewDoneIndex === -1) {
1155
- // The latest review-code round's completion entry is missing entirely (abnormal
1156
- // state, e.g. a restored/historical task without a review-code.completed record).
1157
- // Re-running complete-manual-validation cannot restore it, so the remediation
1158
- // targets the review record itself. Fail closed either way.
1159
- return failResult(
1160
- "manual-validation",
1161
- `Latest review-code (round ${review.round}) completion entry is missing from the Activity Log (abnormal state): re-run review-code or restore the completion record`
1162
- );
1163
- }
1164
- if (completedIndex < reviewDoneIndex) {
1165
- return failResult(
1166
- "manual-validation",
1167
- `Latest review-code (round ${review.round}) came after the manual validation completion recorded for ${artifactName}: re-run /complete-manual-validation to cover new pending items`
1168
- );
925
+ const inspectedLog = inspectActivityLog(task.content);
926
+ if (!inspectedLog.section) return failResult("manual-validation", "Activity Log is missing or invalid");
927
+ const reviewDoneIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === `Review Code (Round ${review.round})`);
928
+ const completedIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === "Complete Manual Validation"
929
+ && entry.note.includes(`Manual validation passed → ${artifactName};`)
930
+ && entry.note.includes(`transaction=${receipt.transactionId};`)
931
+ && entry.note.includes(`receipt=${receipt.receiptDigest};`)
932
+ && entry.note.includes(`head=${receipt.prHeadSha}`));
933
+ if (reviewDoneIndex === -1) return failResult("manual-validation", `Latest review-code (round ${review.round}) completion entry is missing from the Activity Log`);
934
+ if (completedIndex === -1) return failResult("manual-validation", `Committed manual validation completion is not recorded for ${artifactName}`);
935
+ if (completedIndex < reviewDoneIndex) return failResult("manual-validation", `Latest review-code (round ${review.round}) came after the manual validation completion recorded for ${artifactName}`);
936
+
937
+ const fact = readPrDeliveryFact(task.metadata);
938
+ if (fact.status === "invalid") return failResult("manual-validation", fact.error.message);
939
+ if (fact.status === "valid" && fact.fact.state === "bound") {
940
+ const inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
941
+ if (!inspected.pullRequest) return failResult("manual-validation", inspected.error?.message ?? "canonical pull-request head is unavailable");
942
+ if (inspected.pullRequest.head.sha !== receipt.prHeadSha) return failResult("manual-validation", "manual validation receipt is stale for the current pull-request head");
943
+ const summary = await summaryCommentState(task.metadata.id, { cwd: repositoryRoot });
944
+ if (!summary.comment) return failResult("manual-validation", "canonical pull-request summary comment is unavailable");
945
+ if (!manualValidationFinalSummaryProjectionMatches(summary.comment.body, receipt)) return failResult("manual-validation", "canonical pull-request summary does not match the committed manual validation projection");
1169
946
  }
1170
- return passResult("manual-validation", `Manual validation completed → ${artifactName}`);
947
+ return passResult("manual-validation", `Manual validation completed → ${artifactName} (committed receipt and post-write verification)`);
1171
948
  }
1172
949
 
1173
- async function checkPostReviewCommit({ taskDir, config }: any): Promise<any> {
950
+ async function checkPostReviewCommit({ taskDir, config, repositoryRoot }: any): Promise<any> {
1174
951
  const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
1175
952
  if (!reviewArtifact.ok) {
1176
953
  if (reviewArtifact.error) return failResult("post-review-commit", `Review-code artifact is unavailable: ${reviewArtifact.error}`);
@@ -1190,7 +967,7 @@ async function checkPostReviewCommit({ taskDir, config }: any): Promise<any> {
1190
967
  if (factRead.status === 'invalid') return failResult("post-review-commit", factRead.error.message);
1191
968
  const fact = factRead.status === 'valid' ? factRead.fact : null;
1192
969
  const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
1193
- const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
970
+ const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repositoryRoot));
1194
971
  const hasPullRequest = fact?.state === 'bound';
1195
972
  let inspected: Awaited<ReturnType<typeof inspectPlatformPullRequest>> | null = null;
1196
973
  if (hasPullRequest) {
@@ -1364,7 +1141,7 @@ function resolvePostReviewExemption(content: string):
1364
1141
  }
1365
1142
  }
1366
1143
 
1367
- function checkReviewFact({ taskDir, artifactFile }: any): any {
1144
+ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }: any): any {
1368
1145
  const resolvedArtifact = resolveArtifactPath(
1369
1146
  taskDir,
1370
1147
  "review-code.md|review-code-r{N}.md",
@@ -1448,7 +1225,7 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
1448
1225
  mode: "worktree",
1449
1226
  baseline,
1450
1227
  diffBase,
1451
- globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repoRoot))
1228
+ globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
1452
1229
  });
1453
1230
  } catch {
1454
1231
  return blockedResult(
@@ -1575,46 +1352,13 @@ function resolveArtifactPath(taskDir: any, filePattern: any, artifactFile: any):
1575
1352
  return { ok: true, path: path.join(taskDir, matches[0]!.fileName) };
1576
1353
  }
1577
1354
 
1578
- function getSectionContent(content: any, names: any): any {
1579
- const lines = content.split(/\r?\n/);
1580
-
1581
- function visibleHeadings(): any {
1582
- const headings = [];
1583
- let fence = null;
1584
- for (let index = 0; index < lines.length; index += 1) {
1585
- const line = lines[index];
1586
- if (fence) {
1587
- const closer = line.match(/^ {0,3}(`+|~+)\s*$/);
1588
- if (closer && closer[1][0] === fence.character && closer[1].length >= fence.length) {
1589
- fence = null;
1590
- }
1591
- continue;
1592
- }
1593
- const opener = line.match(/^ {0,3}(`{3,}|~{3,})(?:[^`~].*)?$/);
1594
- if (opener) {
1595
- fence = { character: opener[1][0], length: opener[1].length };
1596
- continue;
1597
- }
1598
- if (line.startsWith("## ")) {
1599
- headings.push({ index, text: line.trim() });
1600
- }
1601
- }
1602
- return headings;
1603
- }
1604
-
1605
- const headings = visibleHeadings();
1606
-
1355
+ function getSectionContent(content: string, names: readonly string[]): string {
1356
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
1607
1357
  for (const name of names) {
1608
- const heading = `## ${name}`;
1609
- const position = headings.findIndex((item: any) => item.text === heading);
1610
- if (position === -1) {
1611
- continue;
1612
- }
1613
- const startIndex = headings[position]!.index;
1614
- const endIndex = headings[position + 1]?.index ?? lines.length;
1615
- return lines.slice(startIndex + 1, endIndex).join("\n").trim();
1358
+ const position = headings.findIndex((heading) => heading.text === name);
1359
+ if (position === -1) continue;
1360
+ return content.slice(headings[position]!.end + 1, headings[position + 1]?.start ?? content.length).replace(/\r\n/g, "\n").trim();
1616
1361
  }
1617
-
1618
1362
  return "";
1619
1363
  }
1620
1364
 
@@ -1742,14 +1486,14 @@ function isBlank(value: any): any {
1742
1486
  }
1743
1487
 
1744
1488
  async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }: any): Promise<any> {
1745
- if (repositoryRoot) repoRoot = path.resolve(repositoryRoot);
1746
- else {
1489
+ let repoRoot = repositoryRoot ? path.resolve(repositoryRoot) : defaultRepoRoot;
1490
+ if (!repositoryRoot) {
1747
1491
  let cursor = path.resolve(taskDir);
1748
1492
  while (path.dirname(cursor) !== cursor && !fs.existsSync(path.join(cursor, ".agents"))) cursor = path.dirname(cursor);
1749
1493
  if (fs.existsSync(path.join(cursor, ".agents"))) repoRoot = cursor;
1750
1494
  }
1751
1495
  const verifyConfig = loadVerificationConfig(repoRoot, skillName);
1752
- const shared = { ...sharedUtils, repoRoot };
1496
+ const shared: VerificationShared = { ...sharedUtils, repoRoot };
1753
1497
  if (mode === "gate") {
1754
1498
  const checks = [];
1755
1499
  for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {