@fitlab-ai/agent-infra 0.11.6 → 0.11.8

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 (192) hide show
  1. package/dist/bin/internal-cli.js +8 -2
  2. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  3. package/dist/lib/agent-clients/config.js +11 -3
  4. package/dist/lib/defaults.json +6 -3
  5. package/dist/lib/git/primary-worktree.js +58 -0
  6. package/dist/lib/internal/cli-route-inventory.js +3 -0
  7. package/dist/lib/internal/platform-pr-review.js +20 -3
  8. package/dist/lib/internal/task-operation-registry.js +3 -1
  9. package/dist/lib/internal/task-platform-recovery.js +56 -0
  10. package/dist/lib/merge.js +25 -5
  11. package/dist/lib/platform/completion-backfill.js +2 -1
  12. package/dist/lib/platform/in-label-sync.js +12 -1
  13. package/dist/lib/platform/issue-comments.js +58 -4
  14. package/dist/lib/platform/issues.js +105 -5
  15. package/dist/lib/platform/operation-coordinator.js +74 -0
  16. package/dist/lib/platform/pr-review.js +107 -1
  17. package/dist/lib/platform/pr-summary.js +79 -57
  18. package/dist/lib/platform/pull-requests.js +125 -7
  19. package/dist/lib/sandbox/commands/create.js +6 -3
  20. package/dist/lib/sandbox/commands/ls.js +4 -3
  21. package/dist/lib/sandbox/commands/rm.js +1 -1
  22. package/dist/lib/sandbox/config.js +22 -4
  23. package/dist/lib/sandbox/control/server.js +6 -2
  24. package/dist/lib/sandbox/git-metadata.js +50 -0
  25. package/dist/lib/sandbox/index.js +1 -1
  26. package/dist/lib/sandbox/mounts.js +12 -0
  27. package/dist/lib/sandbox/recovery.js +11 -0
  28. package/dist/lib/sandbox/removal.js +58 -20
  29. package/dist/lib/sandbox/tools.js +29 -7
  30. package/dist/lib/server/config.js +7 -27
  31. package/dist/lib/server/daemon.js +4 -3
  32. package/dist/lib/server/process-control.js +4 -2
  33. package/dist/lib/server/runner.js +4 -3
  34. package/dist/lib/task/activity-intent.js +4 -3
  35. package/dist/lib/task/artifact-lifecycle.js +42 -10
  36. package/dist/lib/task/artifact-receipts.js +62 -22
  37. package/dist/lib/task/capabilities.js +22 -4
  38. package/dist/lib/task/completion-facts.js +56 -0
  39. package/dist/lib/task/create-service.js +11 -5
  40. package/dist/lib/task/create.js +60 -79
  41. package/dist/lib/task/events.js +268 -195
  42. package/dist/lib/task/finalization-artifacts.js +4 -1
  43. package/dist/lib/task/finalization-handoff.js +2 -1
  44. package/dist/lib/task/finalization.js +157 -24
  45. package/dist/lib/task/invalidation-command.js +4 -2
  46. package/dist/lib/task/invalidation.js +38 -9
  47. package/dist/lib/task/lifecycle.js +38 -2
  48. package/dist/lib/task/local-artifact-finalization.js +17 -3
  49. package/dist/lib/task/platform-operation-journal.js +152 -0
  50. package/dist/lib/task/platform-operation-recovery.js +459 -0
  51. package/dist/lib/task/qualification-audit.js +23 -105
  52. package/dist/lib/task/review-finalization.js +7 -0
  53. package/dist/lib/task/review-identity.js +69 -0
  54. package/dist/lib/task/sections.js +6 -5
  55. package/dist/lib/task/task-execution-lock.js +6 -3
  56. package/dist/lib/task/verification-engine.js +14 -63
  57. package/dist/lib/task/verification.js +35 -38
  58. package/dist/lib/task/workflow-warning-intents.js +7 -1
  59. package/dist/lifecycle-build-manifest.json +3 -3
  60. package/lib/agent-clients/adapters/codex.ts +1 -1
  61. package/lib/agent-clients/config.ts +9 -3
  62. package/lib/defaults.json +6 -3
  63. package/lib/git/primary-worktree.ts +62 -0
  64. package/lib/internal/cli-route-inventory.ts +2 -0
  65. package/lib/internal/platform-pr-review.ts +14 -3
  66. package/lib/internal/task-operation-registry.ts +3 -1
  67. package/lib/internal/task-platform-recovery.ts +53 -0
  68. package/lib/merge.ts +26 -6
  69. package/lib/platform/completion-backfill.ts +2 -1
  70. package/lib/platform/in-label-sync.ts +12 -0
  71. package/lib/platform/issue-comments.ts +59 -3
  72. package/lib/platform/issues.ts +103 -5
  73. package/lib/platform/operation-coordinator.ts +96 -0
  74. package/lib/platform/pr-review.ts +113 -1
  75. package/lib/platform/pr-summary.ts +22 -1
  76. package/lib/platform/pull-requests.ts +132 -8
  77. package/lib/sandbox/commands/create.ts +7 -8
  78. package/lib/sandbox/commands/ls.ts +4 -3
  79. package/lib/sandbox/commands/rm.ts +1 -1
  80. package/lib/sandbox/config.ts +23 -5
  81. package/lib/sandbox/control/server.ts +5 -2
  82. package/lib/sandbox/git-metadata.ts +66 -0
  83. package/lib/sandbox/index.ts +1 -1
  84. package/lib/sandbox/mounts.ts +14 -0
  85. package/lib/sandbox/recovery.ts +13 -0
  86. package/lib/sandbox/removal.ts +57 -19
  87. package/lib/sandbox/tool-types.ts +1 -1
  88. package/lib/sandbox/tools.ts +36 -7
  89. package/lib/server/config.ts +7 -29
  90. package/lib/server/daemon.ts +5 -3
  91. package/lib/server/process-control.ts +4 -2
  92. package/lib/server/runner.ts +7 -4
  93. package/lib/server/server.schema.json +0 -4
  94. package/lib/task/activity-intent.ts +4 -3
  95. package/lib/task/artifact-lifecycle.ts +35 -11
  96. package/lib/task/artifact-receipts.ts +55 -15
  97. package/lib/task/capabilities.ts +17 -3
  98. package/lib/task/completion-facts.ts +66 -0
  99. package/lib/task/create-service.ts +12 -6
  100. package/lib/task/create.ts +54 -69
  101. package/lib/task/events.ts +248 -182
  102. package/lib/task/finalization-artifacts.ts +4 -1
  103. package/lib/task/finalization-handoff.ts +2 -1
  104. package/lib/task/finalization.ts +154 -21
  105. package/lib/task/invalidation-command.ts +4 -2
  106. package/lib/task/invalidation.ts +35 -10
  107. package/lib/task/lifecycle.ts +35 -3
  108. package/lib/task/local-artifact-finalization.ts +13 -3
  109. package/lib/task/platform-operation-journal.ts +230 -0
  110. package/lib/task/platform-operation-recovery.ts +435 -0
  111. package/lib/task/qualification-audit.ts +21 -114
  112. package/lib/task/review-finalization.ts +7 -0
  113. package/lib/task/review-identity.ts +85 -0
  114. package/lib/task/sections.ts +6 -6
  115. package/lib/task/task-execution-lock.ts +7 -3
  116. package/lib/task/verification-engine.ts +13 -86
  117. package/lib/task/verification.ts +34 -38
  118. package/lib/task/workflow-warning-intents.ts +9 -1
  119. package/package.json +1 -1
  120. package/templates/.agents/README.en.md +19 -17
  121. package/templates/.agents/README.zh-CN.md +18 -16
  122. package/templates/.agents/rules/README.en.md +2 -1
  123. package/templates/.agents/rules/README.zh-CN.md +2 -1
  124. package/templates/.agents/rules/decision-qualification.en.md +4 -3
  125. package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
  126. package/templates/.agents/rules/task-management.en.md +12 -0
  127. package/templates/.agents/rules/task-management.zh-CN.md +12 -0
  128. package/templates/.agents/rules/validation-output.en.md +19 -0
  129. package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
  130. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
  131. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  132. package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
  133. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  134. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -2
  135. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -3
  136. package/templates/.agents/skills/code-task/SKILL.en.md +5 -3
  137. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  138. package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
  139. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
  140. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  141. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  142. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  143. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  144. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  145. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  146. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
  147. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  148. package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
  149. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  150. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
  151. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  152. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
  153. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  154. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
  155. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
  157. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  158. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  159. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  160. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
  161. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
  162. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
  163. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
  164. package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
  165. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
  166. package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
  167. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
  168. package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
  169. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
  170. package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
  171. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
  172. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
  173. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
  174. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
  175. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
  176. package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
  177. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
  178. package/templates/.agents/skills/review-pr/SKILL.en.md +13 -11
  179. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +13 -11
  180. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
  181. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
  182. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
  183. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
  184. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +19 -6
  189. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
  190. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  191. package/templates/.agents/templates/task.en.md +2 -2
  192. package/templates/.agents/templates/task.zh-CN.md +2 -2
@@ -1,30 +1,26 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { parseArtifactName } from './artifact-name.js';
3
- import { parseTypedTaskFrontmatter } from './frontmatter.js';
4
3
  import { findSectionRange, parseTable } from './sections.js';
5
4
  import { scanVisibleMarkdown } from './markdown.js';
6
5
  const TASK_CONSTRAINT_HEADINGS = ['约束', 'Constraints'];
7
6
  const TASK_CANDIDATE_HEADINGS = ['候选与否决方案', 'Candidate and Rejected Options', 'Candidates and Rejected Options', 'Candidates and Rejected Alternatives'];
8
7
  const AUDIT_HEADINGS = ['资格审计', '资格审计复核', 'Qualification Audit', 'Qualification Audit Review'];
9
8
  const CONFIRMATION_HEADINGS = ['资格确认记录', 'Qualification Confirmations'];
10
- const AUDIT_SUBSECTIONS = ['约束依赖', '候选资格', '分类结果', '上游关系', '依赖快照'];
9
+ const AUDIT_SUBSECTIONS = ['约束依赖', '候选资格', '分类结果', '资格快照'];
11
10
  const AUDIT_SUBSECTION_HEADINGS = {
12
11
  '约束依赖': ['约束依赖', 'Constraint Dependencies'],
13
12
  '候选资格': ['候选资格', 'Candidate Qualification'],
14
13
  '分类结果': ['分类结果', 'Classification Results'],
15
- '上游关系': ['上游关系', 'Upstream Relations'],
16
- '依赖快照': ['依赖快照', 'Dependency Snapshot']
14
+ '资格快照': ['资格快照', 'Qualification Snapshot']
17
15
  };
18
16
  const CONSTRAINT_COLUMNS = ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'];
19
17
  const CANDIDATE_COLUMNS = ['candidate_id', 'statement', 'status', 'constraint_ids', 'impact', 'evidence'];
20
18
  const DEPENDENCY_COLUMNS = ['constraint_id', 'constraint_digest', 'role', 'evidence'];
21
19
  const QUALIFICATION_COLUMNS = ['candidate_id', 'status', 'impact', 'constraint_ids', 'evidence'];
22
20
  const CLASSIFICATION_COLUMNS = ['decision_id', 'classification', 'evidence'];
23
- const RELATION_COLUMNS = ['upstream_family', 'upstream_artifact', 'upstream_round', 'upstream_sha256', 'relation'];
24
- const SNAPSHOT_COLUMNS = ['task_input_digest', 'non_constraint_input_digest', 'upstream_artifact_digest'];
21
+ const SNAPSHOT_COLUMNS = ['task_input_digest', 'non_constraint_input_digest'];
25
22
  const CONFIRMATION_COLUMNS = ['qcr_id', 'constraint_id', 'actor', 'entrypoint', 'request_id', 'approved_digest', 'confirmed_at', 'rationale'];
26
23
  const ARTIFACT_FAMILIES = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
27
- const RELATIONS = ['required-input', 'reviewed-input', 'review-context', 'approval-context'];
28
24
  const CONSTRAINT_STATUSES = ['confirmed', 'derived', 'assumption', 'open', 'conflicted', 'superseded'];
29
25
  const CANDIDATE_STATUSES = ['qualified', 'rejected', 'pending', 'needs-human-decision', 'confirmed', 'excluded'];
30
26
  const CLASSIFICATIONS = ['deterministic', 'qualified', 'rejected', 'needs-human-decision', 'not-applicable'];
@@ -215,86 +211,20 @@ function parseAuditTable(content, heading, columns) {
215
211
  const table = parseTable(`## Qualification Audit Body\n${subsection}`, { sectionAliases: ['Qualification Audit Body'], columns });
216
212
  return table?.rows.map((row) => ({ ...row.values })) ?? null;
217
213
  }
218
- function parseAuditArtifactName(value) {
219
- const identity = parseArtifactName(value);
220
- return identity && ARTIFACT_FAMILIES.includes(identity.family)
221
- ? { family: identity.family, round: identity.round } : null;
222
- }
223
- function upstreamArtifactDigest(rows) {
224
- return digest([...rows].map((row) => ({ ...row })).sort((a, b) => `${a.upstreamFamily}/${a.upstreamArtifact}/${a.relation}`.localeCompare(`${b.upstreamFamily}/${b.upstreamArtifact}/${b.relation}`)));
225
- }
226
- function validateUpstreamRelation(row) {
227
- const identity = parseAuditArtifactName(row.upstreamArtifact);
228
- if (!ARTIFACT_FAMILIES.includes(row.upstreamFamily) || !identity || identity.family !== row.upstreamFamily
229
- || identity.round !== row.upstreamRound || !Number.isSafeInteger(row.upstreamRound)
230
- || !/^[a-f0-9]{64}$/i.test(row.upstreamSha256) || !RELATIONS.includes(row.relation)) {
231
- throw new Error(`invalid qualification upstream relation '${row.upstreamArtifact}'`);
232
- }
233
- }
234
- function expectedQualificationRelations(taskContent, family) {
235
- let frontmatter;
236
- try {
237
- frontmatter = parseTypedTaskFrontmatter(taskContent);
238
- }
239
- catch (error) {
240
- return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: error instanceof Error ? error.message : String(error) };
241
- }
242
- const encoded = frontmatter.qualification_input_relations;
243
- if (typeof encoded === 'string' && encoded) {
244
- try {
245
- const parsed = JSON.parse(encoded);
246
- if (!Array.isArray(parsed))
247
- throw new Error('qualification_input_relations must be an array');
248
- const relations = parsed.map((value) => {
249
- if (!value || typeof value !== 'object' || Array.isArray(value))
250
- throw new Error('qualification input relation must be an object');
251
- const row = value;
252
- const relation = {
253
- upstreamFamily: row.upstreamFamily,
254
- upstreamArtifact: String(row.upstreamArtifact ?? ''),
255
- upstreamRound: Number(row.upstreamRound),
256
- upstreamSha256: String(row.upstreamSha256 ?? ''),
257
- relation: row.relation
258
- };
259
- validateUpstreamRelation(relation);
260
- return relation;
261
- });
262
- return { ok: true, relations };
263
- }
264
- catch (error) {
265
- return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: error instanceof Error ? error.message : String(error) };
266
- }
267
- }
268
- const inputName = family === 'code' ? frontmatter.code_input_artifact : family.startsWith('review-') ? frontmatter.review_input_artifact : '';
269
- const inputSha256 = family === 'code' ? frontmatter.code_input_sha256 : family.startsWith('review-') ? frontmatter.review_input_sha256 : '';
270
- if (typeof inputName !== 'string' || !inputName || typeof inputSha256 !== 'string' || !inputSha256)
271
- return { ok: true, relations: undefined };
272
- const identity = parseAuditArtifactName(inputName);
273
- if (!identity)
274
- return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: `started input '${inputName}' is not canonical` };
275
- const relation = {
276
- upstreamFamily: identity.family, upstreamArtifact: inputName, upstreamRound: identity.round,
277
- upstreamSha256: inputSha256, relation: family === 'code' ? 'required-input' : 'reviewed-input'
278
- };
279
- try {
280
- validateUpstreamRelation(relation);
281
- }
282
- catch (error) {
283
- return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: error instanceof Error ? error.message : String(error) };
284
- }
285
- return { ok: true, relations: [relation] };
286
- }
287
214
  function parseQualificationAudit(content) {
288
- if (sectionBody(content, AUDIT_HEADINGS) === null)
289
- return { ok: true, audit: { present: false, constraintDependencies: [], candidateQualifications: [], classifications: [], upstreamRelations: [], snapshot: null } };
215
+ const auditBody = sectionBody(content, AUDIT_HEADINGS);
216
+ if (auditBody === null)
217
+ return { ok: true, audit: { present: false, constraintDependencies: [], candidateQualifications: [], classifications: [], snapshot: null } };
290
218
  try {
219
+ if (sectionBody(auditBody, ['上游关系', 'Upstream Relations'], 3) !== null) {
220
+ throw new Error('qualification audit uses the obsolete lifecycle-relation format; regenerate it using the current format');
221
+ }
291
222
  const dependencies = parseAuditTable(content, '约束依赖', DEPENDENCY_COLUMNS);
292
223
  const qualifications = parseAuditTable(content, '候选资格', QUALIFICATION_COLUMNS);
293
224
  const classifications = parseAuditTable(content, '分类结果', CLASSIFICATION_COLUMNS);
294
- const relations = parseAuditTable(content, '上游关系', RELATION_COLUMNS);
295
- const snapshots = parseAuditTable(content, '依赖快照', SNAPSHOT_COLUMNS);
296
- if (!dependencies || !qualifications || !classifications || !relations || !snapshots || snapshots.length !== 1) {
297
- throw new Error('qualification audit requires all five canonical tables and exactly one dependency snapshot');
225
+ const snapshots = parseAuditTable(content, '资格快照', SNAPSHOT_COLUMNS);
226
+ if (!dependencies || !qualifications || !classifications || !snapshots || snapshots.length !== 1) {
227
+ throw new Error('qualification audit requires three decision tables and exactly one qualification snapshot');
298
228
  }
299
229
  const constraintDependencies = dependencies.map((row) => {
300
230
  if (!/^C-[1-9]\d*$/.test(row.constraint_id ?? '') || !/^[a-f0-9]{64}$/i.test(row.constraint_digest ?? '') || !ROLES.includes(row.role) || !row.evidence?.trim())
@@ -311,20 +241,15 @@ function parseQualificationAudit(content) {
311
241
  throw new Error(`invalid qualification classification '${row.decision_id ?? ''}'`);
312
242
  return { decisionId: row.decision_id, classification: row.classification, evidence: row.evidence };
313
243
  });
314
- const upstreamRelations = relations.map((row) => {
315
- const relation = { upstreamFamily: row.upstream_family, upstreamArtifact: row.upstream_artifact ?? '', upstreamRound: Number(row.upstream_round), upstreamSha256: row.upstream_sha256 ?? '', relation: row.relation };
316
- validateUpstreamRelation(relation);
317
- return relation;
318
- });
319
244
  const snapshotRow = snapshots[0];
320
- if (!/^[a-f0-9]{64}$/i.test(snapshotRow.task_input_digest ?? '') || !/^[a-f0-9]{64}$/i.test(snapshotRow.non_constraint_input_digest ?? '') || !/^[a-f0-9]{64}$/i.test(snapshotRow.upstream_artifact_digest ?? ''))
321
- throw new Error('qualification dependency snapshot has invalid digest');
245
+ if (!/^[a-f0-9]{64}$/i.test(snapshotRow.task_input_digest ?? '') || !/^[a-f0-9]{64}$/i.test(snapshotRow.non_constraint_input_digest ?? ''))
246
+ throw new Error('qualification snapshot has invalid digest');
322
247
  return {
323
248
  ok: true,
324
249
  audit: {
325
250
  present: true, constraintDependencies, candidateQualifications,
326
- classifications: classificationRows, upstreamRelations,
327
- snapshot: { taskInputDigest: snapshotRow.task_input_digest, nonConstraintInputDigest: snapshotRow.non_constraint_input_digest, upstreamArtifactDigest: snapshotRow.upstream_artifact_digest }
251
+ classifications: classificationRows,
252
+ snapshot: { taskInputDigest: snapshotRow.task_input_digest, nonConstraintInputDigest: snapshotRow.non_constraint_input_digest }
328
253
  }
329
254
  };
330
255
  }
@@ -367,7 +292,7 @@ function validateQualificationAudit(taskContent, artifactContent, options = {})
367
292
  const audit = parseQualificationAudit(artifactContent);
368
293
  if (!audit.ok)
369
294
  return audit;
370
- if (!task.qualification.present && !options.require)
295
+ if (!audit.audit.present && !options.require)
371
296
  return { ok: true, qualification: task.qualification, audit: audit.audit };
372
297
  if (!task.qualification.present)
373
298
  return { ok: false, code: 'QUALIFICATION_TASK_CONTRACT_MISSING', message: 'task qualification contract is missing' };
@@ -424,13 +349,8 @@ function validateQualificationAudit(taskContent, artifactContent, options = {})
424
349
  return { ok: false, code: 'QUALIFICATION_TASK_DIGEST_MISMATCH', message: 'qualification audit task input digest is stale' };
425
350
  if (snapshot.nonConstraintInputDigest !== task.qualification.nonConstraintInputDigest)
426
351
  return { ok: false, code: 'QUALIFICATION_NON_CONSTRAINT_DIGEST_MISMATCH', message: 'qualification audit non-constraint input digest is stale' };
427
- if (snapshot.upstreamArtifactDigest !== upstreamArtifactDigest(audit.audit.upstreamRelations))
428
- return { ok: false, code: 'QUALIFICATION_UPSTREAM_DIGEST_MISMATCH', message: 'qualification audit upstream digest does not match its relation rows' };
429
- if (options.expectedUpstreamRelations && (options.expectedUpstreamRelations.length !== audit.audit.upstreamRelations.length
430
- || upstreamArtifactDigest(options.expectedUpstreamRelations) !== upstreamArtifactDigest(audit.audit.upstreamRelations)))
431
- return { ok: false, code: 'QUALIFICATION_UPSTREAM_RELATION_MISMATCH', message: 'qualification audit upstream relations do not match the started artifact inputs' };
432
352
  if (options.family && options.artifact) {
433
- const identity = parseAuditArtifactName(options.artifact);
353
+ const identity = parseArtifactName(options.artifact);
434
354
  if (!identity || identity.family !== options.family)
435
355
  return { ok: false, code: 'QUALIFICATION_ARTIFACT_IDENTITY_INVALID', message: `artifact '${options.artifact}' is not canonical for '${options.family}'` };
436
356
  }
@@ -443,13 +363,12 @@ function renderQualificationAudit(audit) {
443
363
  ...rows.map((row) => `| ${row.map((value) => value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' ')).join(' | ')} |`)
444
364
  ].join('\n');
445
365
  const list = (values) => values.join(',');
446
- const snapshot = audit.snapshot ?? { taskInputDigest: '', nonConstraintInputDigest: '', upstreamArtifactDigest: upstreamArtifactDigest(audit.upstreamRelations) };
366
+ const snapshot = audit.snapshot ?? { taskInputDigest: '', nonConstraintInputDigest: '' };
447
367
  return [
448
368
  '### 约束依赖', '', table(DEPENDENCY_COLUMNS, audit.constraintDependencies.map((row) => [row.constraintId, row.constraintDigest, row.role, row.evidence])), '',
449
369
  '### 候选资格', '', table(QUALIFICATION_COLUMNS, audit.candidateQualifications.map((row) => [row.candidateId, row.status, row.impact, list(row.constraintIds), row.evidence])), '',
450
370
  '### 分类结果', '', table(CLASSIFICATION_COLUMNS, audit.classifications.map((row) => [row.decisionId, row.classification, row.evidence])), '',
451
- '### 上游关系', '', table(RELATION_COLUMNS, audit.upstreamRelations.map((row) => [row.upstreamFamily, row.upstreamArtifact, String(row.upstreamRound), row.upstreamSha256, row.relation])), '',
452
- '### 依赖快照', '', table(SNAPSHOT_COLUMNS, [[snapshot.taskInputDigest, snapshot.nonConstraintInputDigest, snapshot.upstreamArtifactDigest]])
371
+ '### 资格快照', '', table(SNAPSHOT_COLUMNS, [[snapshot.taskInputDigest, snapshot.nonConstraintInputDigest]])
453
372
  ].join('\n');
454
373
  }
455
374
  function buildQualificationAudit(taskContent, input = {}) {
@@ -462,15 +381,14 @@ function buildQualificationAudit(taskContent, input = {}) {
462
381
  const candidates = input.candidates ?? parsed.qualification.candidates.map((row) => row.candidateId);
463
382
  const constraintDependencies = parsed.qualification.constraints.filter((row) => ids.has(row.constraintId)).map((row) => ({ constraintId: row.constraintId, constraintDigest: row.digest, role: 'required', evidence: row.evidence }));
464
383
  const candidateQualifications = parsed.qualification.candidates.filter((row) => candidates.includes(row.candidateId)).map((row) => ({ candidateId: row.candidateId, status: row.status, impact: row.impact, constraintIds: row.constraintIds, evidence: row.evidence }));
465
- const upstreamRelations = [...(input.upstreamRelations ?? [])];
466
384
  return {
467
385
  ok: true,
468
386
  audit: {
469
387
  present: true, constraintDependencies, candidateQualifications,
470
- classifications: input.classifications ?? [], upstreamRelations,
471
- snapshot: { taskInputDigest: parsed.qualification.taskInputDigest, nonConstraintInputDigest: parsed.qualification.nonConstraintInputDigest, upstreamArtifactDigest: upstreamArtifactDigest(upstreamRelations) }
388
+ classifications: input.classifications ?? [],
389
+ snapshot: { taskInputDigest: parsed.qualification.taskInputDigest, nonConstraintInputDigest: parsed.qualification.nonConstraintInputDigest }
472
390
  }
473
391
  };
474
392
  }
475
- export { ARTIFACT_FAMILIES, AUDIT_HEADINGS, AUDIT_SUBSECTIONS, CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, DEPENDENCY_COLUMNS, QUALIFICATION_COLUMNS, CLASSIFICATION_COLUMNS, RELATION_COLUMNS, SNAPSHOT_COLUMNS, constraintDigest, expectedQualificationRelations, buildQualificationAudit, nonConstraintInputDigest, parseQualificationAudit, parseQualificationConfirmations, parseTaskQualification, renderQualificationAudit, taskInputDigest, upstreamArtifactDigest, validateQualificationAudit };
393
+ export { ARTIFACT_FAMILIES, AUDIT_HEADINGS, AUDIT_SUBSECTIONS, CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, DEPENDENCY_COLUMNS, QUALIFICATION_COLUMNS, CLASSIFICATION_COLUMNS, SNAPSHOT_COLUMNS, constraintDigest, buildQualificationAudit, nonConstraintInputDigest, parseQualificationAudit, parseQualificationConfirmations, parseTaskQualification, renderQualificationAudit, taskInputDigest, validateQualificationAudit };
476
394
  //# sourceMappingURL=qualification-audit.js.map
@@ -10,6 +10,7 @@ import { resolveTaskRef } from './resolve-ref.js';
10
10
  import { validateLifecycleExecution } from './lifecycle-execution.js';
11
11
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
12
12
  import { getArtifactSchema } from './artifact-schema.js';
13
+ import { validateQualificationAudit } from './qualification-audit.js';
13
14
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
14
15
  const STAGES = {
15
16
  analysis: { family: 'review-analysis' },
@@ -186,6 +187,12 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
186
187
  artifactSha256, semanticDigest: structure.semanticDigest
187
188
  });
188
189
  }
190
+ const qualification = validateQualificationAudit(taskContent, artifactContent, { family: spec.family, artifact: request.artifact });
191
+ if (!qualification.ok) {
192
+ return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', `${qualification.code}: ${qualification.message}`, {
193
+ artifactSha256, semanticDigest
194
+ });
195
+ }
189
196
  const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
190
197
  refreshNumericCounts: true
191
198
  });
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { resolveBranchWorktree } from '../git/branch-worktree.js';
5
+ import { snapshotReview } from '../git/review-snapshot.js';
6
+ import { resolveDeliveryTarget } from './delivery-target.js';
7
+ import { parseTypedTaskFrontmatter } from './frontmatter.js';
8
+ import { extractReviewDiffBase, extractReviewDiffFingerprint, extractReviewedHead, extractReviewTargetHead, extractReviewedSnapshotTree, loadPostReviewConfig, resolvePostReviewGlobs } from './review-fingerprint.js';
9
+ function git(cwd, args) {
10
+ return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8' }).trim();
11
+ }
12
+ function result(status, message, reviewedHead = null, currentHead = null) {
13
+ return { status, message, reviewedHead, currentHead };
14
+ }
15
+ export function inspectReviewIdentity(taskDir, reportContent, repositoryRoot) {
16
+ const reviewedHead = extractReviewedHead(reportContent);
17
+ const targetHead = extractReviewTargetHead(reportContent);
18
+ const diffBase = extractReviewDiffBase(reportContent);
19
+ const fingerprint = extractReviewDiffFingerprint(reportContent);
20
+ const reviewedTree = extractReviewedSnapshotTree(reportContent);
21
+ if (!reviewedHead || !targetHead || !diffBase || !fingerprint || !reviewedTree) {
22
+ return result('indeterminate', 'Review report is missing commit or snapshot identity', reviewedHead || null);
23
+ }
24
+ try {
25
+ const metadata = parseTypedTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
26
+ const taskRepositoryRoot = git(taskDir, ['rev-parse', '--show-toplevel']);
27
+ const branch = String(metadata.branch ?? '').trim();
28
+ const gitRoot = branch ? resolveBranchWorktree(taskRepositoryRoot, branch) : taskRepositoryRoot;
29
+ if (!gitRoot)
30
+ return result('indeterminate', `Task branch '${branch}' is not checked out in a registered worktree`, reviewedHead);
31
+ const currentHead = git(gitRoot, ['rev-parse', 'HEAD']);
32
+ const baseline = git(gitRoot, ['rev-parse', `${reviewedHead}^{commit}`]);
33
+ if (baseline !== currentHead)
34
+ return result('different', 'Reviewed commit differs from current HEAD', baseline, currentHead);
35
+ const remote = String(metadata.delivery_remote ?? '').trim();
36
+ const baseRef = String(metadata.delivery_base_ref ?? '').trim();
37
+ if (!remote || !baseRef)
38
+ return result('indeterminate', 'Task delivery target is not bound', baseline, currentHead);
39
+ const target = resolveDeliveryTarget(gitRoot, { remote, baseRef });
40
+ if (!target.ok)
41
+ return result('indeterminate', target.message, baseline, currentHead);
42
+ const currentTarget = git(gitRoot, ['rev-parse', `${targetHead}^{commit}`]);
43
+ const computedDiffBase = git(gitRoot, ['merge-base', baseline, currentTarget]);
44
+ if (git(gitRoot, ['rev-parse', `${diffBase}^{commit}`]) !== computedDiffBase) {
45
+ return result('different', 'Saved review diff base no longer matches the current target', baseline, currentHead);
46
+ }
47
+ const globs = resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot ?? taskRepositoryRoot));
48
+ const trackedChanges = git(gitRoot, ['diff', '--name-only', '-z', baseline, '--', ...globs]);
49
+ const untrackedChanges = git(gitRoot, ['ls-files', '-o', '--exclude-standard', '-z', '--', ...globs]);
50
+ if (trackedChanges || untrackedChanges)
51
+ return result('different', 'Reviewed worktree contains uncommitted changes', baseline, currentHead);
52
+ const snapshot = snapshotReview({
53
+ cwd: gitRoot,
54
+ mode: 'worktree',
55
+ baseline,
56
+ diffBase: computedDiffBase,
57
+ globs
58
+ });
59
+ if (snapshot.fingerprint !== fingerprint)
60
+ return result('different', 'Reviewed diff fingerprint differs from the current worktree', baseline, currentHead);
61
+ if (snapshot.tree !== reviewedTree)
62
+ return result('different', 'Reviewed snapshot tree differs from the current worktree', baseline, currentHead);
63
+ return result('matched', 'Review report matches the current commit and clean worktree', baseline, currentHead);
64
+ }
65
+ catch (error) {
66
+ return result('indeterminate', error instanceof Error ? error.message : String(error), reviewedHead);
67
+ }
68
+ }
69
+ //# sourceMappingURL=review-identity.js.map
@@ -17,8 +17,8 @@ function parseTable(content, input) {
17
17
  new Set(input.columns).size !== input.columns.length) {
18
18
  throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
19
19
  }
20
- const keyColumn = input.keyColumn ?? input.columns[0];
21
- if (!input.columns.includes(keyColumn)) {
20
+ const keyColumn = input.keyColumn === undefined ? input.columns[0] : input.keyColumn;
21
+ if (keyColumn !== null && !input.columns.includes(keyColumn)) {
22
22
  throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
23
23
  }
24
24
  const sections = matchingSections(content, input.sectionAliases);
@@ -68,11 +68,12 @@ function parseTable(content, input) {
68
68
  column,
69
69
  decodeCell(parsed.cells[columnIndex]).trim()
70
70
  ]));
71
- const key = values[keyColumn];
72
- if (seen.has(key)) {
71
+ const key = keyColumn === null ? null : values[keyColumn];
72
+ if (key !== null && seen.has(key)) {
73
73
  throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
74
74
  }
75
- seen.add(key);
75
+ if (key !== null)
76
+ seen.add(key);
76
77
  rows.push({ values, sourceLine: index - Math.max(firstContent, 0) });
77
78
  }
78
79
  return { heading: section.heading, rows };
@@ -29,9 +29,12 @@ function mapLinkError(operation, error, key) {
29
29
  }
30
30
  return new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', `lifecycle lock hard-link operation '${operation}' failed`, detail);
31
31
  }
32
- function lockKey(repoRoot, taskId) {
32
+ function lockKey(repoRoot, taskId, identityScope) {
33
33
  const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
34
- const key = createHash('sha256').update(`${canonicalRepoRoot}\0${taskId}`).digest('hex');
34
+ const identity = identityScope === undefined
35
+ ? `${canonicalRepoRoot}\0${taskId}`
36
+ : `${identityScope}\0${taskId}`;
37
+ const key = createHash('sha256').update(identity).digest('hex');
35
38
  return { canonicalRepoRoot, key };
36
39
  }
37
40
  function parseOwner(raw, key) {
@@ -133,7 +136,7 @@ function cleanStaleCandidates(lockRoot, key, identityMatches) {
133
136
  function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options = {}) {
134
137
  let identity;
135
138
  try {
136
- identity = lockKey(repoRoot, taskId);
139
+ identity = lockKey(repoRoot, taskId, options.identityScope);
137
140
  }
138
141
  catch (error) {
139
142
  throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'failed to canonicalize the repository for lifecycle locking', { operation: 'canonicalize', errno: errno(error), key: 'unresolved' });
@@ -4,14 +4,13 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { execFileSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
- import { extractReviewBaseline, extractReviewTargetHead, extractReviewedHead, extractReviewDiffBase, extractReviewDiffFingerprint, extractReviewedSnapshotTree, findAuthoritativeReviewCodeArtifact, loadPostReviewConfig, parseReviewVerdict, resolvePostReviewGlobs } from "./verification-review.js";
7
+ import { extractReviewTargetHead, extractReviewedHead, extractReviewDiffBase, findAuthoritativeReviewCodeArtifact, loadPostReviewConfig, parseReviewVerdict, resolvePostReviewGlobs } from "./verification-review.js";
8
8
  import { check as checkPlatformSync } from "../platform/verification-sync.js";
9
9
  import { check as checkRequiredChecks } from "../platform/verification-required.js";
10
10
  import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
11
11
  import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
12
12
  import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
13
13
  import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
14
- import { resolveBranchWorktree } from "../git/branch-worktree.js";
15
14
  import { parseTypedTaskFrontmatter } from "./frontmatter.js";
16
15
  import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
17
16
  import { isValidAgentInfraVersion } from "../version.js";
@@ -20,11 +19,9 @@ import { inspectDecisionDetailDuplicates } from "./decision-details.js";
20
19
  import { parseImplementationInputs } from "./implementation-inputs.js";
21
20
  import { scanVisibleMarkdown } from "./markdown.js";
22
21
  import { loadVerificationConfig } from "./verification-config.js";
23
- import { snapshotReview } from "../git/review-snapshot.js";
24
22
  import { inspectActivityLog } from "./activity-log.js";
25
23
  import { parseWorkflowWarnings } from "./workflow-warnings.js";
26
24
  import { OrchestrationStateError, readRun } from "./orchestration.js";
27
- import { resolveDeliveryTarget } from "./delivery-target.js";
28
25
  import { readPrDeliveryFact } from "./pr-delivery-fact.js";
29
26
  import { validateLocalArtifact } from "./local-artifact-finalization.js";
30
27
  import { getArtifactSchema } from "./artifact-schema.js";
@@ -35,6 +32,8 @@ import { readManualValidationCompletion } from "./manual-validation-completion.j
35
32
  import { sha256File } from "./artifact-receipts.js";
36
33
  import { summaryCommentState } from "../platform/pr-summary.js";
37
34
  import { taskIssueIdentity } from "../platform/task-identities.js";
35
+ import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.js';
36
+ import { inspectReviewIdentity } from './review-identity.js';
38
37
  const TASK_ENUMS = {
39
38
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
40
39
  workflow: ["feature-development", "bug-fix", "refactoring"],
@@ -1014,12 +1013,16 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
1014
1013
  return failResult("review-fact", task.message);
1015
1014
  }
1016
1015
  const content = fs.readFileSync(resolvedArtifact.path, "utf8");
1016
+ if (!hasArtifactCompletionFact(task.content, resolvedArtifact.path, 'review-code.completed')) {
1017
+ return failResult("review-fact", `Review Code completion fact does not match ${path.basename(resolvedArtifact.path)}`);
1018
+ }
1019
+ if (!hasArtifactCompletionLog(task.content, path.basename(resolvedArtifact.path), 'review-code.completed')) {
1020
+ return failResult("review-fact", `Review Code completion entry does not reference ${path.basename(resolvedArtifact.path)}`);
1021
+ }
1017
1022
  const verdict = parseReviewVerdict(content);
1018
1023
  const reviewTargetHead = extractReviewTargetHead(content);
1019
1024
  const reviewReviewedHead = extractReviewedHead(content);
1020
1025
  const reviewDiffBase = extractReviewDiffBase(content);
1021
- const reviewedFingerprint = extractReviewDiffFingerprint(content);
1022
- const reviewedTree = extractReviewedSnapshotTree(content);
1023
1026
  if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
1024
1027
  return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
1025
1028
  }
@@ -1031,71 +1034,19 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
1031
1034
  if (!reviewTargetHead || !reviewReviewedHead || !reviewDiffBase) {
1032
1035
  return failResult("review-fact", "Review fact must record target head, reviewed head, and diff base");
1033
1036
  }
1034
- let gitRoot;
1035
- let head;
1036
- let baseline;
1037
- let targetHead;
1038
- let diffBase;
1039
- try {
1040
- const taskRepositoryRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
1041
- gitRoot = reviewWorktreeForTask(taskRepositoryRoot, String(task.metadata.branch || "").trim()) ?? taskRepositoryRoot;
1042
- head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
1043
- const target = resolveDeliveryTarget(gitRoot, { remote: deliveryRemote, baseRef: deliveryBaseRef });
1044
- if (!target.ok)
1045
- throw new Error(target.message);
1046
- const reviewedRef = reviewReviewedHead;
1047
- baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewedRef}^{commit}`], { encoding: "utf8" }).trim();
1048
- targetHead = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewTargetHead}^{commit}`], { encoding: "utf8" }).trim();
1049
- const computed = execFileSync("git", ["-C", gitRoot, "merge-base", baseline, targetHead], { encoding: "utf8" }).trim();
1050
- diffBase = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewDiffBase}^{commit}`], { encoding: "utf8" }).trim();
1051
- if (diffBase !== computed)
1052
- throw new Error('saved review diff base does not match merge-base(reviewed head, target head)');
1053
- }
1054
- catch {
1055
- return blockedResult("review-fact", `Unable to resolve saved review commits in the task repository; re-run review-code`);
1056
- }
1057
- if (baseline !== head) {
1058
- return failResult("review-fact", `Reviewed head ${baseline.slice(0, 8)} does not match current HEAD ${head.slice(0, 8)}; re-run review-code`);
1059
- }
1060
- let actualSnapshot;
1061
- try {
1062
- actualSnapshot = snapshotReview({
1063
- cwd: gitRoot,
1064
- mode: "worktree",
1065
- baseline,
1066
- diffBase,
1067
- globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
1068
- });
1069
- }
1070
- catch {
1071
- return blockedResult("review-fact", `Unable to recompute reviewed diff fingerprint from baseline ${baseline.slice(0, 8)}; re-run review-code`);
1072
- }
1073
- if (actualSnapshot.fingerprint !== reviewedFingerprint) {
1074
- return failResult("review-fact", `Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
1075
- }
1076
- if (!reviewedTree || actualSnapshot.tree !== reviewedTree) {
1077
- return failResult("review-fact", `Reviewed snapshot tree does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
1037
+ const identity = inspectReviewIdentity(taskDir, content, repositoryRoot);
1038
+ if (identity.status !== 'matched' || !identity.reviewedHead) {
1039
+ return failResult('review-fact', `Review identity is ${identity.status}: ${identity.message}; re-run review-code`);
1078
1040
  }
1041
+ const baseline = identity.reviewedHead;
1079
1042
  if (["通过", "Approved"].includes(verdict)) {
1080
1043
  const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
1081
- const cleanSnapshot = actualSnapshot.tree === execFileSync("git", ["-C", gitRoot, "rev-parse", `${baseline}^{tree}`], { encoding: "utf8" }).trim();
1082
- if (cleanSnapshot && lastReviewedCommit !== baseline) {
1044
+ if (lastReviewedCommit !== baseline) {
1083
1045
  return failResult("review-fact", `Approved clean review must set task last_reviewed_commit to reviewed head ${baseline.slice(0, 8)}`);
1084
1046
  }
1085
- if (!cleanSnapshot && lastReviewedCommit) {
1086
- return failResult("review-fact", "Approved review with uncommitted changes must remain unanchored until commit");
1087
- }
1088
1047
  }
1089
1048
  return passResult("review-fact", `Review fact valid for ${path.basename(resolvedArtifact.path)} at ${baseline.slice(0, 8)}`);
1090
1049
  }
1091
- function reviewWorktreeForTask(repositoryRoot, branch) {
1092
- if (!branch)
1093
- return null;
1094
- const worktree = resolveBranchWorktree(repositoryRoot, branch);
1095
- if (worktree)
1096
- return worktree;
1097
- throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
1098
- }
1099
1050
  function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
1100
1051
  if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
1101
1052
  return { ok: true, sha: lastReviewedCommit };
@@ -89,47 +89,44 @@ async function verifyTaskEvent(request, options = {}) {
89
89
  function statusLabel(status) {
90
90
  return status === 'fail' ? 'FAIL' : status === 'blocked' ? 'BLOCKED' : 'pass';
91
91
  }
92
- function renderPayload(payload) {
93
- const lines = [];
94
- if (Array.isArray(payload.checks)) {
95
- const gateMode = typeof payload.gate === 'string';
96
- lines.push(gateMode
97
- ? `Verification: ${payload.gate} | Skill: ${payload.skill}`
98
- : `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
99
- for (const check of payload.checks) {
100
- const raw = statusLabel(check.status);
101
- const effective = statusLabel(check.effectiveStatus ?? check.status);
102
- const reason = check.reason ?? 'CHECK_FAILED';
103
- const message = check.message ?? reason;
104
- lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
105
- }
106
- const summary = payload.summary ?? (() => {
107
- const checks = payload.checks;
108
- const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
109
- return `${statuses.filter((status) => status === 'pass').length} passed, ${statuses.filter((status) => status === 'fail').length} failed${statuses.includes('blocked') ? `, ${statuses.filter((status) => status === 'blocked').length} blocked` : ''}`;
110
- })();
111
- lines.push('', `Result: ${summary} - ${payload.action}`);
112
- }
113
- else {
114
- lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
115
- const raw = statusLabel(payload.status);
116
- const effective = statusLabel(payload.effectiveStatus ?? payload.status);
117
- const reason = payload.reason ?? 'CHECK_FAILED';
118
- lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
119
- const status = payload.effectiveStatus ?? payload.status;
120
- const summary = status === 'pass' ? '1 passed, 0 failed'
121
- : status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
122
- const action = status === 'pass' ? 'Requested check passed'
123
- : status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
124
- : `Fix ${payload.type} issues and re-run check`;
125
- lines.push('', `Result: ${summary} - ${action}`);
126
- }
127
- return `${lines.join('\n')}\n`;
128
- }
129
92
  function renderTaskVerification(result) {
130
93
  if (result.status === 'failed')
131
94
  return `Verification failed: ${result.error?.code ?? 'VERIFY_FAILED'} - ${result.error?.message ?? 'unknown error'}\n`;
132
- return result.invocations.map((invocation) => renderPayload(invocation.payload)).join('');
95
+ const target = `${result.event}${result.artifact ? ` (${result.artifact})` : ''}`;
96
+ const lines = [`Verification: ${result.status} | Target: ${target} | Skill: ${result.skill}`];
97
+ const checks = result.invocations.flatMap(({ payload }) => {
98
+ if (Array.isArray(payload.checks))
99
+ return payload.checks;
100
+ return [payload];
101
+ });
102
+ const counts = {
103
+ pass: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'pass').length,
104
+ fail: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'fail').length,
105
+ blocked: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'blocked').length
106
+ };
107
+ const summary = `${counts.pass} passed, ${counts.fail} failed${counts.blocked > 0 ? `, ${counts.blocked} blocked` : ''}`;
108
+ lines.push('', `Result: ${summary}`);
109
+ for (const check of checks) {
110
+ const rawStatus = check.status;
111
+ const effectiveStatus = check.effectiveStatus ?? rawStatus;
112
+ const isSoftWarning = (rawStatus === 'fail' || rawStatus === 'blocked') && effectiveStatus === 'pass';
113
+ const reason = check.reason ?? 'CHECK_FAILED';
114
+ const message = check.message ?? reason;
115
+ const action = check.action ?? 'Review validation output';
116
+ const id = check.checkId ?? check.type;
117
+ const isHumanExemptionNotice = id === 'post-review-commit'
118
+ && typeof message === 'string'
119
+ && /\bhuman-decided (?:post-review )?exemption\b/i.test(message)
120
+ && /\bPRC-\d+\s*:/.test(message);
121
+ if (effectiveStatus === 'pass' && !isSoftWarning && !isHumanExemptionNotice)
122
+ continue;
123
+ lines.push(isSoftWarning
124
+ ? `Warning: ${id} (${rawStatus === 'blocked' ? `raw BLOCKED; ` : ''}${reason}) - ${message}; ${action}`
125
+ : isHumanExemptionNotice
126
+ ? `Notice: ${id} - ${message}; ${action}`
127
+ : ` [${statusLabel(effectiveStatus)}] ${id} (${reason}) - ${message}; ${action}`);
128
+ }
129
+ return `${lines.join('\n')}\n`;
133
130
  }
134
131
  export { VERIFICATION_CATALOG, renderTaskVerification, verifyTaskEvent };
135
132
  //# sourceMappingURL=verification.js.map
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
  import { resolveTaskRef } from './resolve-ref.js';
3
4
  import { parseTaskFrontmatter } from './frontmatter.js';
4
5
  import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
@@ -120,7 +121,12 @@ function projectFinalizationWarning(taskRef, warning, options = {}) {
120
121
  let prepared = resolved.state === 'completed';
121
122
  if (!prepared && resolved.state === 'active') {
122
123
  try {
123
- prepared = parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')).status === 'completed';
124
+ const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
125
+ prepared = parseTaskFrontmatter(taskContent).status === 'completed';
126
+ if (!prepared) {
127
+ const lifecycle = JSON.parse(fs.readFileSync(path.join(resolved.taskDir, '.task-lifecycle.json'), 'utf8'));
128
+ prepared = lifecycle.taskId === resolved.taskId && lifecycle.intent === 'complete';
129
+ }
124
130
  }
125
131
  catch {
126
132
  prepared = false;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.6",
4
- "sourceInputHash": "402048e73650c5ac4edfb1bc4b48ca3fceb7b9068790b1321281c4994121e80c",
5
- "internalExecutableBuildHash": "47ecd04ac30183443b2b86f83d1668b8bdf9e59162cc9e2cedd333d3011843e9"
3
+ "packageVersion": "0.11.8",
4
+ "sourceInputHash": "6ddb72f8b50137d5148a47172bd7eb2628b17a937ae647ac38350e582ba08124",
5
+ "internalExecutableBuildHash": "c886e015506879620bb490f1ea5977d9952f2bbb4515d3a0b49e0ec184b48028"
6
6
  }
@@ -54,7 +54,7 @@ const codexAdapter = defineAgentClientAdapter({
54
54
  containerMount: '/home/devuser/.codex',
55
55
  versionCmd: 'codex --version',
56
56
  setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
57
- tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
57
+ tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
58
58
  hostLiveMounts: [
59
59
  {
60
60
  hostPath: hostJoin(home, '.codex', 'auth.json'),