@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
@@ -12,12 +12,16 @@ import { resolveMaterializedReviewedHeadRelation } from "../platform/change-requ
12
12
  import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
13
13
  import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
14
14
  import { parseTypedTaskFrontmatter } from "./frontmatter.js";
15
- import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
15
+ import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
16
16
  import { isValidAgentInfraVersion } from "../version.js";
17
17
  import { equalCounts, parseReviewSummary } from "./review-artifacts.js";
18
18
  import { inspectDecisionDetailDuplicates } from "./decision-details.js";
19
+ import { parseImplementationInputs } from "./implementation-inputs.js";
20
+ import { scanVisibleMarkdown } from "./markdown.js";
19
21
  import { loadVerificationConfig } from "./verification-config.js";
20
22
  import { snapshotReview } from "../git/review-snapshot.js";
23
+ import { inspectActivityLog } from "./activity-log.js";
24
+ import { parseWorkflowWarnings } from "./workflow-warnings.js";
21
25
  import { OrchestrationStateError, readRun } from "./orchestration.js";
22
26
  import { resolveDeliveryTarget } from "./delivery-target.js";
23
27
  import { readPrDeliveryFact } from "./pr-delivery-fact.js";
@@ -25,6 +29,10 @@ import { validateLocalArtifact } from "./local-artifact-finalization.js";
25
29
  import { validateQualificationAudit } from "./qualification-audit.js";
26
30
  import { getArtifactSchema } from "./artifact-schema.js";
27
31
  import { inspectArtifactContract } from "./artifact-operations.js";
32
+ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
33
+ import { readManualValidationCompletion } from "./manual-validation-completion.js";
34
+ import { sha256File } from "./artifact-receipts.js";
35
+ import { summaryCommentState } from "../platform/pr-summary.js";
28
36
  const TASK_ENUMS = {
29
37
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
30
38
  workflow: ["feature-development", "bug-fix", "refactoring"],
@@ -42,7 +50,6 @@ const DEFAULT_REQUIRED_FIELDS = [
42
50
  "assigned_to"
43
51
  ];
44
52
  const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
45
- const ACTIVITY_LOG_PATTERN = /^- (\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?) — \*\*(.+?)\*\* by (.+?) — (.+)$/;
46
53
  // Start markers (action suffixed with ` [started]`) are excluded from the
47
54
  // "latest action" / freshness computation so a step's in-flight marker never
48
55
  // satisfies a skill's expected_action_pattern; the matching done entry does.
@@ -50,27 +57,11 @@ const ACTIVITY_LOG_STARTED_RE = /\s*\[started\]\s*$/;
50
57
  const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
51
58
  // Review disagreement ledger (see .agents/rules/review-handshake.md).
52
59
  const LEDGER_SECTION_NAMES = ["审查分歧账本", "Review Disagreement Ledger"];
53
- const LEDGER_STATUSES = new Set([
54
- "open",
55
- "accepted",
56
- "adjusted",
57
- "refuted",
58
- "cannot-judge",
59
- "confirmed",
60
- "needs-human-decision",
61
- "closed",
62
- "human-decided"
63
- ]);
64
- const LEDGER_TERMINAL_OK = new Set(["confirmed", "closed", "human-decided"]);
65
60
  const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
66
61
  const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
67
62
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
68
- const WORKFLOW_WARNING_SECTION_NAMES = ["工作流告警", "Workflow Warnings"];
69
- const WORKFLOW_WARNING_STATUSES = new Set(["open", "resolved", "ignored"]);
70
- const WORKFLOW_WARNING_SEVERITIES = new Set(["IMPORTANT", "ACTION_REQUIRED"]);
71
- const WORKFLOW_WARNING_ID_PATTERN = /^WW-\d+$/;
72
63
  const scriptPath = fileURLToPath(import.meta.url);
73
- let repoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
64
+ const defaultRepoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
74
65
  const PLATFORM_ADAPTERS = {
75
66
  "platform-sync": checkPlatformSync,
76
67
  "platform-sync-preflight": async (context, shared) => ({ ...await checkPlatformSync(context, shared), type: "platform-sync-preflight" }),
@@ -175,14 +166,12 @@ function exactText(value) {
175
166
  function validateCleanCompletionEvidence(run) {
176
167
  const evidence = run.completionEvidence;
177
168
  const shaPattern = /^[0-9a-f]{40}$/i;
178
- if (evidence?.kind !== 'reviewed-head-clean'
179
- || !exactText(evidence.observedAt)
169
+ if (!evidence
180
170
  || Number.isNaN(Date.parse(evidence.observedAt))
181
171
  || !shaPattern.test(evidence.head)
182
172
  || !shaPattern.test(evidence.headTree)
183
173
  || !shaPattern.test(evidence.worktreeTree)
184
174
  || !shaPattern.test(evidence.lastReviewedCommit)
185
- || !(evidence.prNumber === null || (Number.isInteger(evidence.prNumber) && evidence.prNumber > 0))
186
175
  || !(evidence.prHead === null || shaPattern.test(evidence.prHead))) {
187
176
  return 'Clean completion evidence has an invalid structure';
188
177
  }
@@ -195,7 +184,6 @@ function validateCleanCompletionEvidence(run) {
195
184
  || run.pendingDelegation !== null
196
185
  || run.commitAuthorization?.issuedAt !== null
197
186
  || run.commitAuthorization?.consumedAt !== null
198
- || !Array.isArray(run.receipts)
199
187
  || run.receipts.some((receipt) => receipt.stage === 'commit')) {
200
188
  return 'Clean completion evidence conflicts with commit delegation state';
201
189
  }
@@ -214,43 +202,10 @@ function checkOrchestrationEvidence({ taskDir }) {
214
202
  const message = error instanceof OrchestrationStateError ? error.message : String(error);
215
203
  return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
216
204
  }
205
+ if (!run)
206
+ return failResult('orchestration-evidence', 'orchestration.json is missing');
207
+ // readRun validates the persisted schema, receipt identities and client provenance.
217
208
  const policy = run.modelPolicy;
218
- if (!policy
219
- || !exactText(policy.executor?.model)
220
- || !exactText(policy.executor?.reasoningEffort)
221
- || !exactText(policy.reviewer?.model)
222
- || !exactText(policy.reviewer?.reasoningEffort)) {
223
- return failResult('orchestration-evidence', 'Run model policy requires exact executor and reviewer model and effort');
224
- }
225
- if (!run.modelPolicySource
226
- || !['explicit', 'project-config'].includes(run.modelPolicySource.kind)
227
- || !exactText(run.modelPolicySource.client)
228
- || !exactText(run.modelPolicySource.resolvedAt)
229
- || !Array.isArray(run.recoveryHistory)) {
230
- return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
231
- }
232
- for (const recovery of run.recoveryHistory) {
233
- const guards = recovery.guards;
234
- const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
235
- && recovery.previousStatus === 'paused'
236
- && recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
237
- && recovery.client === 'claude-code'
238
- && recovery.resultingStatus === 'running'
239
- && exactText(recovery.recoveredAt)
240
- && guards?.stepCount === 0
241
- && guards?.nextStage === null
242
- && guards?.baselineEmpty === true
243
- && guards?.receiptCount === 0
244
- && guards?.pendingDelegation === false
245
- && guards?.commitAuthorizationUnused === true
246
- && guards?.completionEvidenceAbsent === true;
247
- if (!validClaudeCodeRecovery) {
248
- return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
249
- }
250
- }
251
- if (!Array.isArray(run.receipts)) {
252
- return failResult('orchestration-evidence', 'Run receipts must be an array');
253
- }
254
209
  if (run.completionEvidence != null) {
255
210
  const evidenceError = validateCleanCompletionEvidence(run);
256
211
  if (evidenceError)
@@ -282,10 +237,6 @@ function checkOrchestrationEvidence({ taskDir }) {
282
237
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
283
238
  }
284
239
  }
285
- // parentId/childId 校验对所有 client 保持统一(不新增分支);spawnMode 检查按 client 判断
286
- if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId) {
287
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid delegation identity`);
288
- }
289
240
  if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
290
241
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
291
242
  }
@@ -307,41 +258,6 @@ function checkOrchestrationEvidence({ taskDir }) {
307
258
  && receipt.reasoningEffortFallbackReason !== null) {
308
259
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
309
260
  }
310
- if (receipt.client === 'codex') {
311
- const activatedCodex = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
312
- const host = receipt.hostEvidence;
313
- const provenance = receipt.lifecycleProvenance;
314
- if (!provenance
315
- || (activatedCodex && (host?.kind !== 'codex-lifecycle-v2'
316
- || host.protocolVersion !== provenance.protocolVersion
317
- || host.hookDefinitionHash !== provenance.hookDefinitionHash
318
- || host.hookSource !== provenance.hookSource
319
- || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
320
- || host.hookSourceHash !== provenance.hookSourceHash
321
- || host.capabilitySessionId !== provenance.capabilitySessionId
322
- || receipt.parentId !== provenance.capabilitySessionId
323
- || host.capabilityTurnId !== provenance.capabilityTurnId
324
- || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
325
- || host.controlGeneration !== provenance.controlGeneration
326
- || host.spawnToolUseId === provenance.capabilityToolUseId
327
- || !exactText(host.spawnToolUseId)
328
- || !Number.isFinite(Date.parse(host.spawnObservedAt ?? ''))
329
- || !Number.isFinite(Date.parse(receipt.spawnDispatchedAt ?? ''))
330
- || !Number.isFinite(Date.parse(receipt.activationDeadlineAt ?? ''))
331
- || Date.parse(host.spawnObservedAt ?? '') < Date.parse(receipt.spawnDispatchedAt ?? '')
332
- || Date.parse(host.spawnObservedAt ?? '') > Date.parse(receipt.activationDeadlineAt ?? '')
333
- || !exactText(host.hookDefinitionHash)
334
- || !Number.isSafeInteger(host.startRevision)
335
- || host.startRevision < 1))) {
336
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
337
- }
338
- if (['sealed', 'consumed'].includes(receipt.status) && (!Number.isSafeInteger(host.stopRevision)
339
- || host.stopRevision <= host.startRevision
340
- || host.consumer !== receipt.id
341
- || !exactText(host.consumedAt))) {
342
- return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex consumed stop evidence`);
343
- }
344
- }
345
261
  }
346
262
  }
347
263
  if (run.receipts.some((receipt) => receipt.status !== 'consumed')) {
@@ -350,7 +266,7 @@ function checkOrchestrationEvidence({ taskDir }) {
350
266
  return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
351
267
  }
352
268
  // === Check Functions ===
353
- function checkTaskMeta({ taskDir, config }) {
269
+ function checkTaskMeta({ taskDir, config, repositoryRoot }) {
354
270
  const task = loadTask(taskDir);
355
271
  if (!task.ok) {
356
272
  return failResult("task-meta", task.message);
@@ -380,13 +296,15 @@ function checkTaskMeta({ taskDir, config }) {
380
296
  return failResult("task-meta", `Invalid ${field}: ${metadata[field]}`);
381
297
  }
382
298
  }
383
- const branchValidationError = validateTaskBranch(metadata);
299
+ const branchValidationError = validateTaskBranch(metadata, repositoryRoot);
384
300
  if (branchValidationError) {
385
301
  return failResult("task-meta", branchValidationError);
386
302
  }
387
- const warningValidationErrors = validateWorkflowWarnings(task.content);
388
- if (warningValidationErrors.length > 0) {
389
- return failResult("task-meta", `Invalid Workflow Warnings: ${warningValidationErrors.join("; ")}`);
303
+ try {
304
+ parseWorkflowWarnings(task.content);
305
+ }
306
+ catch (error) {
307
+ return failResult("task-meta", `Invalid Workflow Warnings: ${error instanceof Error ? error.message : String(error)}`);
390
308
  }
391
309
  const expectedStep = config.expected_step;
392
310
  if (expectedStep && metadata.current_step !== expectedStep) {
@@ -481,11 +399,11 @@ async function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }) {
481
399
  }
482
400
  return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
483
401
  }
484
- function validateTaskBranch(metadata) {
402
+ function validateTaskBranch(metadata, repoRoot) {
485
403
  if (isBlank(metadata.branch)) {
486
404
  return null;
487
405
  }
488
- const projectName = loadProjectName();
406
+ const projectName = loadProjectName(repoRoot);
489
407
  const expectedPrefix = projectName ? `${projectName}-${metadata.type}-` : "";
490
408
  if (expectedPrefix && !String(metadata.branch).startsWith(expectedPrefix)) {
491
409
  return `Invalid branch: expected prefix '${expectedPrefix}', got '${metadata.branch}'`;
@@ -496,7 +414,7 @@ function validateTaskBranch(metadata) {
496
414
  }
497
415
  return null;
498
416
  }
499
- function loadProjectName() {
417
+ function loadProjectName(repoRoot) {
500
418
  const configPath = path.join(repoRoot, ".agents", ".airc.json");
501
419
  if (!fs.existsSync(configPath)) {
502
420
  return "";
@@ -647,30 +565,17 @@ function checkImplementationInput({ taskDir, artifactFile }) {
647
565
  const artifactPath = path.join(taskDir, artifactFile);
648
566
  if (!safeStat(artifactPath)?.isFile())
649
567
  return failResult("implementation-input", `Artifact not found: ${artifactFile}`);
650
- const inputSection = getSectionContent(task.content, ["实现输入", "Implementation Inputs"]);
651
- const rows = [];
652
- if (inputSection) {
653
- const table = inputSection.split(/\r?\n/).filter((line) => line.trim().startsWith("|"));
654
- const cells = (line) => line.split("|").slice(1, -1).map((cell) => cell.trim());
655
- const expected = ["id", "ledger_id", "decision_evidence", "stage", "needs_implementation", "decided_at", "status", "consumed_by"];
656
- if (table.length < 2 || JSON.stringify(cells(table[0])) !== JSON.stringify(expected)) {
657
- return failResult("implementation-input", "Implementation Inputs table schema is invalid");
658
- }
659
- const seen = new Set();
660
- for (const line of table.slice(2)) {
661
- const row = cells(line);
662
- if (row.length !== 8 || !/^II-[1-9]\d*$/.test(row[0]) || seen.has(row[0])) {
663
- return failResult("implementation-input", "Implementation Inputs table contains an invalid or duplicate id");
664
- }
665
- seen.add(row[0]);
666
- rows.push({ id: row[0], ledgerId: row[1], evidence: row[2], stage: row[3], needs: row[4], status: row[6], consumedBy: row[7] });
667
- }
568
+ let rows;
569
+ try {
570
+ rows = parseImplementationInputs(task.content).rows;
668
571
  }
669
- const logSection = getSectionContent(task.content, ["活动日志", "Activity Log"]);
670
- const doneActions = logSection.split(/\r?\n/).flatMap((line) => {
671
- const match = line.trim().match(ACTIVITY_LOG_PATTERN);
672
- return match && !ACTIVITY_LOG_STARTED_RE.test(match[2]) ? [match[2]] : [];
673
- });
572
+ catch (error) {
573
+ return failResult("implementation-input", `Invalid Implementation Inputs: ${error instanceof Error ? error.message : String(error)}`);
574
+ }
575
+ const doneActions = (inspectActivityLog(task.content).section?.entries ?? [])
576
+ .filter((entry) => !ACTIVITY_LOG_STARTED_RE.test(entry.step))
577
+ .map((entry) => entry.step)
578
+ .filter((step) => /^(?:Code Task|Code) \(Round \d+/.test(step));
674
579
  const latestAction = doneActions.at(-1) || "";
675
580
  const actionDecision = /(?:Code Task|Code) \(Round \d+, decision (II-[1-9]\d*)\)/.exec(latestAction)?.[1] || null;
676
581
  const report = fs.readFileSync(artifactPath, "utf8");
@@ -698,10 +603,10 @@ function checkImplementationInput({ taskDir, artifactFile }) {
698
603
  if (matches.length !== 1)
699
604
  return failResult("implementation-input", `${actionDecision} is missing or duplicated in task table`);
700
605
  const row = matches[0];
701
- if (row.stage !== "code" || row.needs !== "true" || row.status !== "consumed" || row.consumedBy !== artifactFile || !row.evidence) {
606
+ if (row.stage !== "code" || !row.needsImplementation || row.status !== "consumed" || row.consumedBy !== artifactFile || !row.decisionEvidence) {
702
607
  return failResult("implementation-input", `${actionDecision} is not a consumed input for ${artifactFile}`);
703
608
  }
704
- if (reportLedger !== row.ledgerId || reportEvidence !== row.evidence) {
609
+ if (reportLedger !== row.ledgerId || reportEvidence !== row.decisionEvidence) {
705
610
  return failResult("implementation-input", `${actionDecision} report identity does not match task table evidence`);
706
611
  }
707
612
  return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
@@ -711,27 +616,22 @@ function checkActivityLog({ taskDir, config }) {
711
616
  if (!task.ok) {
712
617
  return failResult("activity-log", task.message);
713
618
  }
714
- const logSection = getSectionContent(task.content, ["活动日志", "Activity Log"]);
715
- if (!logSection) {
716
- return failResult("activity-log", "Activity Log section not found");
717
- }
718
- const entries = logSection
719
- .split(/\r?\n/)
720
- .map((line) => line.trim())
721
- .filter((line) => line.startsWith("- "));
722
- if (entries.length === 0) {
619
+ const { section, invalidEntries } = inspectActivityLog(task.content);
620
+ if (!section)
621
+ return failResult("activity-log", "Activity Log section not found or ambiguous");
622
+ if (invalidEntries.length)
623
+ return failResult("activity-log", `Invalid Activity Log entry format: ${invalidEntries[0]}`);
624
+ const entries = section.entries;
625
+ if (entries.length === 0)
723
626
  return failResult("activity-log", "Activity Log has no entries");
724
- }
725
627
  let previousTimestamp = "";
726
628
  let latestAction = "";
727
629
  let latestTimestamp = "";
728
630
  const doneActions = [];
729
631
  for (const entry of entries) {
730
- const match = entry.match(ACTIVITY_LOG_PATTERN);
731
- if (!match) {
732
- return failResult("activity-log", `Invalid Activity Log entry format: ${entry}`);
733
- }
734
- const [, timestamp, action] = match;
632
+ if (!entry.note.trim())
633
+ return failResult("activity-log", "Activity Log entry note is required");
634
+ const { time: timestamp, step: action } = entry;
735
635
  if (previousTimestamp && timestamp < previousTimestamp) {
736
636
  return failResult("activity-log", "Activity Log timestamps are not in ascending order");
737
637
  }
@@ -786,111 +686,7 @@ function checkCompletionChecklist({ taskDir, config }) {
786
686
  }
787
687
  return passResult("completion-checklist", `Completion Checklist valid (${items.length} items checked)`);
788
688
  }
789
- function splitMarkdownTableRow(line) {
790
- let value = String(line || "").trim();
791
- if (!value.startsWith("|")) {
792
- return [];
793
- }
794
- value = value.replace(/^\|/, "").replace(/\|$/, "");
795
- const cells = [];
796
- let cell = "";
797
- for (let index = 0; index < value.length; index += 1) {
798
- const char = value[index];
799
- if (char === "|" && !isEscapedAt(value, index)) {
800
- cells.push(unescapeMarkdownTableCell(cell.trim()));
801
- cell = "";
802
- continue;
803
- }
804
- cell += char;
805
- }
806
- cells.push(unescapeMarkdownTableCell(cell.trim()));
807
- return cells;
808
- }
809
- function unescapeMarkdownTableCell(value) {
810
- let output = "";
811
- for (let index = 0; index < value.length; index += 1) {
812
- const char = value[index];
813
- const next = value[index + 1];
814
- if (char === "\\" && (next === "\\" || next === "|")) {
815
- output += next;
816
- index += 1;
817
- continue;
818
- }
819
- output += char;
820
- }
821
- return output;
822
- }
823
- function isEscapedAt(value, index) {
824
- let backslashes = 0;
825
- for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
826
- backslashes += 1;
827
- }
828
- return backslashes % 2 === 1;
829
- }
830
- function parseWorkflowWarningRows(section) {
831
- const rows = [];
832
- for (const rawLine of String(section || "").split(/\r?\n/)) {
833
- const cells = splitMarkdownTableRow(rawLine);
834
- if (cells.length === 0) {
835
- continue;
836
- }
837
- if ((cells[0] || "").toLowerCase() === "id") {
838
- continue;
839
- }
840
- if (cells.every((cell) => /^:?-{3,}:?$/.test(cell))) {
841
- continue;
842
- }
843
- rows.push(cells);
844
- }
845
- return rows;
846
- }
847
- function validateWorkflowWarnings(content) {
848
- const section = getSectionContent(content, WORKFLOW_WARNING_SECTION_NAMES);
849
- if (!section.trim()) {
850
- return [];
851
- }
852
- const rows = parseWorkflowWarningRows(section);
853
- const errors = [];
854
- for (const cells of rows) {
855
- if (cells.length < 11) {
856
- errors.push(`malformed row (expected 11 columns): ${cells.join(" | ")}`);
857
- continue;
858
- }
859
- const [id, time, step, severity, code, status, target, message, action, resolvedAt, resolution] = cells;
860
- if (!WORKFLOW_WARNING_ID_PATTERN.test(id)) {
861
- errors.push(`${id || "(empty id)"}: invalid id`);
862
- }
863
- if (!DATE_TIME_PATTERN.test(time)) {
864
- errors.push(`${id}: invalid time '${time}'`);
865
- }
866
- if (isBlank(step)) {
867
- errors.push(`${id}: step is required`);
868
- }
869
- if (!WORKFLOW_WARNING_SEVERITIES.has(severity)) {
870
- errors.push(`${id}: illegal severity '${severity}'`);
871
- }
872
- if (isBlank(code)) {
873
- errors.push(`${id}: code is required`);
874
- }
875
- if (!WORKFLOW_WARNING_STATUSES.has(status)) {
876
- errors.push(`${id}: illegal status '${status}'`);
877
- }
878
- if (isBlank(target)) {
879
- errors.push(`${id}: target is required`);
880
- }
881
- if (isBlank(message)) {
882
- errors.push(`${id}: message is required`);
883
- }
884
- if (status === "open" && isBlank(action)) {
885
- errors.push(`${id}: open warning requires action`);
886
- }
887
- if ((status === "resolved" || status === "ignored") && (isBlank(resolvedAt) || isBlank(resolution))) {
888
- errors.push(`${id}: ${status} warning requires resolved_at and resolution`);
889
- }
890
- }
891
- return errors;
892
- }
893
- function resolveReviewSetting(config, key, fallback) {
689
+ function resolveReviewSetting(config, key, fallback, repoRoot) {
894
690
  if (config && config[key] !== undefined && config[key] !== null) {
895
691
  return config[key];
896
692
  }
@@ -900,7 +696,7 @@ function resolveReviewSetting(config, key, fallback) {
900
696
  }
901
697
  return fallback;
902
698
  }
903
- function checkReviewLedger({ taskDir, config }) {
699
+ function checkReviewLedger({ taskDir, config, repositoryRoot }) {
904
700
  const task = loadTask(taskDir);
905
701
  if (!task.ok) {
906
702
  return failResult("review-ledger", task.message);
@@ -910,9 +706,7 @@ function checkReviewLedger({ taskDir, config }) {
910
706
  const ledger = parseLedgerDocument(task.content);
911
707
  if (!ledger.present)
912
708
  return failResult("review-ledger", `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
913
- rows = ledger.rows.map((row) => [
914
- row.id, row.stage, row.round, row.severity, row.status, row.evidence
915
- ]);
709
+ rows = ledger.rows;
916
710
  }
917
711
  catch (error) {
918
712
  return failResult("review-ledger", `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`);
@@ -921,15 +715,11 @@ function checkReviewLedger({ taskDir, config }) {
921
715
  return passResult("review-ledger", "Disagreement ledger has no entries");
922
716
  }
923
717
  const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
924
- const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
718
+ const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS, repositoryRoot));
925
719
  const problems = [];
926
720
  let inScopeCount = 0;
927
- for (const cells of rows) {
928
- if (cells.length < 6) {
929
- problems.push(`malformed row (expected 6 columns): ${cells.join(" | ")}`);
930
- continue;
931
- }
932
- const [id, stage, roundRaw, , status, evidence] = cells;
721
+ for (const row of rows) {
722
+ const { id, stage, round: roundRaw, status, evidence } = row;
933
723
  const stageScoped = stageScope ? stageScope.includes(stage) : true;
934
724
  // post-review-commit exemption rows are consumed by the post-review-commit
935
725
  // check, not enforced here.
@@ -940,8 +730,9 @@ function checkReviewLedger({ taskDir, config }) {
940
730
  continue;
941
731
  }
942
732
  inScopeCount += 1;
943
- if (!LEDGER_STATUSES.has(status)) {
944
- problems.push(`${id}: illegal status '${status}'`);
733
+ const invalid = validateLedgerRows([row]);
734
+ if (invalid) {
735
+ problems.push(`${invalid.code}: ${invalid.message}`);
945
736
  continue;
946
737
  }
947
738
  if (status !== "open" && evidence === "") {
@@ -950,11 +741,11 @@ function checkReviewLedger({ taskDir, config }) {
950
741
  const round = Number.parseInt(roundRaw, 10);
951
742
  if (Number.isFinite(round) &&
952
743
  round >= maxRounds &&
953
- !LEDGER_TERMINAL_OK.has(status) &&
744
+ !LEDGER_TERMINAL.has(status) &&
954
745
  status !== "needs-human-decision") {
955
746
  problems.push(`${id}: round ${round} reached limit ${maxRounds} without convergence; escalate to needs-human-decision`);
956
747
  }
957
- if (!LEDGER_TERMINAL_OK.has(status)) {
748
+ if (!LEDGER_TERMINAL.has(status)) {
958
749
  problems.push(`${id}: unresolved (status '${status}')`);
959
750
  }
960
751
  }
@@ -964,7 +755,7 @@ function checkReviewLedger({ taskDir, config }) {
964
755
  const scopeLabel = stageScope ? ` for stages [${stageScope.join(", ")}]` : "";
965
756
  return passResult("review-ledger", `Disagreement ledger clean (${inScopeCount} in-scope entries terminal${scopeLabel})`);
966
757
  }
967
- function checkManualValidation({ taskDir }) {
758
+ async function checkManualValidation({ taskDir, repositoryRoot }) {
968
759
  // 1. Latest review-code artifact; none -> pass (no review, check not applicable).
969
760
  const review = findAuthoritativeReviewCodeArtifact(taskDir);
970
761
  if (!review.ok) {
@@ -995,10 +786,23 @@ function checkManualValidation({ taskDir }) {
995
786
  const task = loadTask(taskDir);
996
787
  if (!task.ok)
997
788
  return failResult("manual-validation", task.message);
998
- const log = getSectionContent(task.content, ["活动日志", "Activity Log"]);
999
- const completed = new RegExp(`\\*\\*Complete Manual Validation\\*\\*[\\s\\S]*?Manual validation passed → ${escapeRegExp(artifactName)};`);
1000
- if (!completed.test(log)) {
1001
- return failResult("manual-validation", `Manual validation artifact exists but completion is not recorded for ${artifactName}: re-run /complete-manual-validation`);
789
+ const completion = readManualValidationCompletion(taskDir, {
790
+ taskId: task.metadata.id,
791
+ artifact: artifactName
792
+ });
793
+ if (!completion.ok) {
794
+ const source = completion.error.code.startsWith('MANUAL_VALIDATION_RECEIPT_') ? 'receipt' : 'transaction';
795
+ return failResult("manual-validation", `Manual validation ${source} is unavailable: ${completion.error.message}`);
796
+ }
797
+ const { receipt, transaction } = completion.value;
798
+ if (transaction.phase !== "committed" || !transaction.eventAppended || !transaction.postWriteVerified) {
799
+ return failResult("manual-validation", "Manual validation transaction has not reached the committed post-write-verified state");
800
+ }
801
+ if (transaction.pendingSummaryDigest !== receipt.pendingSummaryDigest || transaction.finalSummaryDigest !== receipt.finalSummaryDigest) {
802
+ return failResult("manual-validation", "Manual validation summary digests do not match the committed receipt");
803
+ }
804
+ if (sha256File(resolved.path) !== receipt.artifactSha256) {
805
+ return failResult("manual-validation", `Manual validation artifact digest does not match the receipt for ${artifactName}`);
1002
806
  }
1003
807
  // 6. Timing correlation (PL-3 fix, PL-4 corrected): the completion entry must
1004
808
  // sit AFTER the latest review-code round's completion entry in the
@@ -1007,21 +811,39 @@ function checkManualValidation({ taskDir }) {
1007
811
  // which indexOf never matches). Not reusing completed.exec().index: exec
1008
812
  // anchors to the first '**Complete Manual Validation**' heading, which can
1009
813
  // belong to an earlier round's entry and misorder the standard path.
1010
- const reviewDoneIndex = log.indexOf(`**Review Code (Round ${review.round})** by `);
1011
- const completedIndex = log.indexOf(`Manual validation passed → ${artifactName};`);
1012
- if (reviewDoneIndex === -1) {
1013
- // The latest review-code round's completion entry is missing entirely (abnormal
1014
- // state, e.g. a restored/historical task without a review-code.completed record).
1015
- // Re-running complete-manual-validation cannot restore it, so the remediation
1016
- // targets the review record itself. Fail closed either way.
1017
- return failResult("manual-validation", `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`);
1018
- }
1019
- if (completedIndex < reviewDoneIndex) {
1020
- return failResult("manual-validation", `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`);
1021
- }
1022
- return passResult("manual-validation", `Manual validation completed → ${artifactName}`);
814
+ const inspectedLog = inspectActivityLog(task.content);
815
+ if (!inspectedLog.section)
816
+ return failResult("manual-validation", "Activity Log is missing or invalid");
817
+ const reviewDoneIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === `Review Code (Round ${review.round})`);
818
+ const completedIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === "Complete Manual Validation"
819
+ && entry.note.includes(`Manual validation passed → ${artifactName};`)
820
+ && entry.note.includes(`transaction=${receipt.transactionId};`)
821
+ && entry.note.includes(`receipt=${receipt.receiptDigest};`)
822
+ && entry.note.includes(`head=${receipt.prHeadSha}`));
823
+ if (reviewDoneIndex === -1)
824
+ return failResult("manual-validation", `Latest review-code (round ${review.round}) completion entry is missing from the Activity Log`);
825
+ if (completedIndex === -1)
826
+ return failResult("manual-validation", `Committed manual validation completion is not recorded for ${artifactName}`);
827
+ if (completedIndex < reviewDoneIndex)
828
+ return failResult("manual-validation", `Latest review-code (round ${review.round}) came after the manual validation completion recorded for ${artifactName}`);
829
+ const fact = readPrDeliveryFact(task.metadata);
830
+ if (fact.status === "invalid")
831
+ return failResult("manual-validation", fact.error.message);
832
+ if (fact.status === "valid" && fact.fact.state === "bound") {
833
+ const inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
834
+ if (!inspected.pullRequest)
835
+ return failResult("manual-validation", inspected.error?.message ?? "canonical pull-request head is unavailable");
836
+ if (inspected.pullRequest.head.sha !== receipt.prHeadSha)
837
+ return failResult("manual-validation", "manual validation receipt is stale for the current pull-request head");
838
+ const summary = await summaryCommentState(task.metadata.id, { cwd: repositoryRoot });
839
+ if (!summary.comment)
840
+ return failResult("manual-validation", "canonical pull-request summary comment is unavailable");
841
+ if (!manualValidationFinalSummaryProjectionMatches(summary.comment.body, receipt))
842
+ return failResult("manual-validation", "canonical pull-request summary does not match the committed manual validation projection");
843
+ }
844
+ return passResult("manual-validation", `Manual validation completed → ${artifactName} (committed receipt and post-write verification)`);
1023
845
  }
1024
- async function checkPostReviewCommit({ taskDir, config }) {
846
+ async function checkPostReviewCommit({ taskDir, config, repositoryRoot }) {
1025
847
  const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
1026
848
  if (!reviewArtifact.ok) {
1027
849
  if (reviewArtifact.error)
@@ -1043,7 +865,7 @@ async function checkPostReviewCommit({ taskDir, config }) {
1043
865
  return failResult("post-review-commit", factRead.error.message);
1044
866
  const fact = factRead.status === 'valid' ? factRead.fact : null;
1045
867
  const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
1046
- const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
868
+ const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repositoryRoot));
1047
869
  const hasPullRequest = fact?.state === 'bound';
1048
870
  let inspected = null;
1049
871
  if (hasPullRequest) {
@@ -1189,7 +1011,7 @@ function resolvePostReviewExemption(content) {
1189
1011
  };
1190
1012
  }
1191
1013
  }
1192
- function checkReviewFact({ taskDir, artifactFile }) {
1014
+ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
1193
1015
  const resolvedArtifact = resolveArtifactPath(taskDir, "review-code.md|review-code-r{N}.md", artifactFile);
1194
1016
  if (!resolvedArtifact.ok) {
1195
1017
  return failResult("review-fact", resolvedArtifact.message);
@@ -1255,7 +1077,7 @@ function checkReviewFact({ taskDir, artifactFile }) {
1255
1077
  mode: "worktree",
1256
1078
  baseline,
1257
1079
  diffBase,
1258
- globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repoRoot))
1080
+ globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
1259
1081
  });
1260
1082
  }
1261
1083
  catch {
@@ -1342,40 +1164,12 @@ function resolveArtifactPath(taskDir, filePattern, artifactFile) {
1342
1164
  return { ok: true, path: path.join(taskDir, matches[0].fileName) };
1343
1165
  }
1344
1166
  function getSectionContent(content, names) {
1345
- const lines = content.split(/\r?\n/);
1346
- function visibleHeadings() {
1347
- const headings = [];
1348
- let fence = null;
1349
- for (let index = 0; index < lines.length; index += 1) {
1350
- const line = lines[index];
1351
- if (fence) {
1352
- const closer = line.match(/^ {0,3}(`+|~+)\s*$/);
1353
- if (closer && closer[1][0] === fence.character && closer[1].length >= fence.length) {
1354
- fence = null;
1355
- }
1356
- continue;
1357
- }
1358
- const opener = line.match(/^ {0,3}(`{3,}|~{3,})(?:[^`~].*)?$/);
1359
- if (opener) {
1360
- fence = { character: opener[1][0], length: opener[1].length };
1361
- continue;
1362
- }
1363
- if (line.startsWith("## ")) {
1364
- headings.push({ index, text: line.trim() });
1365
- }
1366
- }
1367
- return headings;
1368
- }
1369
- const headings = visibleHeadings();
1167
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
1370
1168
  for (const name of names) {
1371
- const heading = `## ${name}`;
1372
- const position = headings.findIndex((item) => item.text === heading);
1373
- if (position === -1) {
1169
+ const position = headings.findIndex((heading) => heading.text === name);
1170
+ if (position === -1)
1374
1171
  continue;
1375
- }
1376
- const startIndex = headings[position].index;
1377
- const endIndex = headings[position + 1]?.index ?? lines.length;
1378
- return lines.slice(startIndex + 1, endIndex).join("\n").trim();
1172
+ return content.slice(headings[position].end + 1, headings[position + 1]?.start ?? content.length).replace(/\r\n/g, "\n").trim();
1379
1173
  }
1380
1174
  return "";
1381
1175
  }
@@ -1479,9 +1273,8 @@ function isBlank(value) {
1479
1273
  return value === undefined || value === null || String(value).trim() === "";
1480
1274
  }
1481
1275
  async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }) {
1482
- if (repositoryRoot)
1483
- repoRoot = path.resolve(repositoryRoot);
1484
- else {
1276
+ let repoRoot = repositoryRoot ? path.resolve(repositoryRoot) : defaultRepoRoot;
1277
+ if (!repositoryRoot) {
1485
1278
  let cursor = path.resolve(taskDir);
1486
1279
  while (path.dirname(cursor) !== cursor && !fs.existsSync(path.join(cursor, ".agents")))
1487
1280
  cursor = path.dirname(cursor);