@fitlab-ai/agent-infra 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/lib/internal/cli-route-inventory.js +1 -1
  2. package/dist/lib/internal/manual-validation.js +6 -2
  3. package/dist/lib/internal/platform-comment.js +59 -0
  4. package/dist/lib/internal/sandbox-control.js +4 -131
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-operation-registry.js +1 -0
  7. package/dist/lib/internal/task-review.js +5 -5
  8. package/dist/lib/internal/task-validate.js +0 -6
  9. package/dist/lib/platform/comment-safety.js +7 -1
  10. package/dist/lib/platform/github-provider.js +13 -3
  11. package/dist/lib/platform/issue-comments.js +120 -13
  12. package/dist/lib/platform/issues.js +3 -4
  13. package/dist/lib/platform/provider-contract.d.ts +2 -0
  14. package/dist/lib/platform/provider-validation.js +7 -2
  15. package/dist/lib/platform/verification-sync.js +161 -156
  16. package/dist/lib/sandbox/control/executor.js +0 -38
  17. package/dist/lib/sandbox/control/finalization-response.js +10 -2
  18. package/dist/lib/sandbox/control/task-view.js +3 -1
  19. package/dist/lib/sandbox/control/workflow-executor.js +32 -41
  20. package/dist/lib/task/artifact-command.js +6 -6
  21. package/dist/lib/task/artifact-lifecycle.js +15 -52
  22. package/dist/lib/task/artifact-operations.js +27 -1
  23. package/dist/lib/task/capabilities.js +0 -2
  24. package/dist/lib/task/codex-orchestration.js +10 -208
  25. package/dist/lib/task/commit-operation.js +3 -18
  26. package/dist/lib/task/control-authority.js +1 -8
  27. package/dist/lib/task/delegation-receipts.js +14 -76
  28. package/dist/lib/task/events.js +147 -278
  29. package/dist/lib/task/finalization-state.js +2 -1
  30. package/dist/lib/task/finalization.js +273 -49
  31. package/dist/lib/task/gate-policy.js +94 -0
  32. package/dist/lib/task/lifecycle-execution.js +2 -11
  33. package/dist/lib/task/local-artifact-finalization.js +31 -245
  34. package/dist/lib/task/orchestration.js +8 -24
  35. package/dist/lib/task/review-artifacts.js +12 -6
  36. package/dist/lib/task/review-command.js +0 -1
  37. package/dist/lib/task/review-finalization.js +47 -218
  38. package/dist/lib/task/verification-engine.js +28 -17
  39. package/dist/lib/task/verification.js +25 -9
  40. package/dist/lib/task/workflow-command.js +4 -1
  41. package/dist/lib/task/workflow-dispatch.js +55 -3
  42. package/dist/lifecycle-build-manifest.json +3 -3
  43. package/lib/internal/cli-route-inventory.ts +1 -1
  44. package/lib/internal/manual-validation.ts +6 -2
  45. package/lib/internal/platform-comment.ts +38 -0
  46. package/lib/internal/sandbox-control.ts +3 -145
  47. package/lib/internal/task-artifact.ts +1 -1
  48. package/lib/internal/task-operation-registry.ts +1 -0
  49. package/lib/internal/task-review.ts +5 -5
  50. package/lib/internal/task-validate.ts +0 -5
  51. package/lib/platform/comment-safety.ts +8 -0
  52. package/lib/platform/github-provider.ts +11 -2
  53. package/lib/platform/issue-comments.ts +132 -13
  54. package/lib/platform/issues.ts +3 -4
  55. package/lib/platform/provider-contract.ts +2 -0
  56. package/lib/platform/provider-validation.ts +6 -2
  57. package/lib/platform/verification-sync.ts +178 -179
  58. package/lib/sandbox/control/executor.ts +0 -44
  59. package/lib/sandbox/control/finalization-response.ts +10 -2
  60. package/lib/sandbox/control/task-view.ts +3 -1
  61. package/lib/sandbox/control/workflow-executor.ts +31 -48
  62. package/lib/task/artifact-command.ts +6 -7
  63. package/lib/task/artifact-lifecycle.ts +12 -49
  64. package/lib/task/artifact-operations.ts +27 -1
  65. package/lib/task/capabilities.ts +0 -1
  66. package/lib/task/codex-orchestration.ts +8 -241
  67. package/lib/task/commit-operation.ts +3 -23
  68. package/lib/task/control-authority.ts +1 -8
  69. package/lib/task/delegation-receipts.ts +12 -79
  70. package/lib/task/events.ts +155 -287
  71. package/lib/task/finalization-state.ts +2 -1
  72. package/lib/task/finalization.ts +277 -57
  73. package/lib/task/gate-policy.ts +133 -0
  74. package/lib/task/lifecycle-execution.ts +10 -21
  75. package/lib/task/local-artifact-finalization.ts +29 -285
  76. package/lib/task/orchestration.ts +12 -41
  77. package/lib/task/review-artifacts.ts +8 -2
  78. package/lib/task/review-command.ts +0 -1
  79. package/lib/task/review-finalization.ts +46 -200
  80. package/lib/task/verification-engine.ts +24 -14
  81. package/lib/task/verification-types.ts +7 -0
  82. package/lib/task/verification.ts +25 -9
  83. package/lib/task/workflow-command.ts +4 -1
  84. package/lib/task/workflow-dispatch.ts +46 -3
  85. package/package.json +2 -2
  86. package/templates/.agents/rules/issue-sync.en.md +2 -0
  87. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  88. package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
  89. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
  90. package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
  91. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
  92. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  93. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  94. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  95. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  96. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  97. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  98. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  101. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  102. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  103. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  104. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  105. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  106. package/templates/.agents/skills/commit/config/verify.json +1 -5
  107. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  108. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  109. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  110. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  111. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  112. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  113. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  114. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  115. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  116. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  117. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  118. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  119. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  120. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  121. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  122. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  123. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  124. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  125. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  126. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  127. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  128. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  129. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  130. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  131. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  132. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  133. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  134. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  135. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  136. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  137. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  138. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  139. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  140. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  141. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  142. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
  143. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  144. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  145. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  146. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
@@ -1,4 +1,5 @@
1
1
  import type { VerificationShared } from "../task/verification-types.ts";
2
+ import { normalizeVerificationRecord, platformAuditPolicy, type PlatformAuditId } from "../task/gate-policy.ts";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import process from "node:process";
@@ -12,7 +13,7 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.ts";
12
13
  import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
13
14
  import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.ts";
14
15
  import { taskIssueIdentity } from "./task-identities.ts";
15
- import { renderTaskCommentResult } from "./issue-comments.ts";
16
+ import { MARKERS, renderTaskCommentResult } from "./issue-comments.ts";
16
17
  import {
17
18
  CONTROL_MARKER_PATTERN,
18
19
  canonicalizeCommentBody,
@@ -60,8 +61,23 @@ function sanitizeCommentContent(content: string): string | null {
60
61
  return result.ok ? result.value : null;
61
62
  }
62
63
 
63
- export async function check({ taskDir, config, artifactFile }: any, shared: VerificationShared): Promise<any> {
64
- const context = await buildSyncContext({ taskDir, config, artifactFile }, shared);
64
+ function infoResult(message: string): any {
65
+ return {
66
+ type: CHECK_TYPE,
67
+ status: 'pass',
68
+ message,
69
+ classification: 'info',
70
+ reason: 'NOT_APPLICABLE',
71
+ action: 'No action required'
72
+ };
73
+ }
74
+
75
+ function hardResult(result: any): any {
76
+ return { ...result, classification: 'hard' };
77
+ }
78
+
79
+ export async function check({ taskDir, config, artifactFile, skillName }: any, shared: VerificationShared): Promise<any> {
80
+ const context = await buildSyncContext({ taskDir, config, artifactFile, skillName }, shared);
65
81
  if (context.earlyReturn) {
66
82
  return context.earlyReturn;
67
83
  }
@@ -72,35 +88,36 @@ export async function check({ taskDir, config, artifactFile }: any, shared: Veri
72
88
  }
73
89
 
74
90
  const subChecks = [
75
- checkClosedIssueStatusLabels,
76
- checkStatusLabel,
77
- checkCommentMarker,
78
- checkPrCommentMarker,
79
- checkPrCommentLastCommit,
80
- checkPrCommentRequiredPatterns,
81
- checkCommentContent,
82
- checkTaskCommentContent,
83
- checkInLabelsComputed,
84
- checkPrTypeLabel,
85
- checkInLabelsMatchPr,
86
- checkPrAssignee,
87
- checkSyncedRequirements,
88
- checkIssueType,
89
- checkIssueFields,
90
- checkMilestone
91
- ];
92
-
93
- for (const subCheck of subChecks) {
94
- const result = subCheck(context, remoteData, shared);
95
- if (result) {
96
- return result;
97
- }
98
- }
99
-
100
- return shared.passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
91
+ ['closed-status-labels', checkClosedIssueStatusLabels], ['status-label', checkStatusLabel],
92
+ ['comment-marker', checkCommentMarker], ['pr-comment-marker', checkPrCommentMarker],
93
+ ['pr-comment-last-commit', checkPrCommentLastCommit], ['pr-comment-content', checkPrCommentRequiredPatterns],
94
+ ['comment-content', checkCommentContent], ['task-comment-content', checkTaskCommentContent],
95
+ ['in-labels-computed', checkInLabelsComputed], ['pr-type-label', checkPrTypeLabel],
96
+ ['in-labels-match-pr', checkInLabelsMatchPr], ['pr-assignee', checkPrAssignee],
97
+ ['requirements', checkSyncedRequirements], ['issue-type', checkIssueType],
98
+ ['issue-fields', checkIssueFields], ['milestone', checkMilestone]
99
+ ] as const;
100
+ const results = subChecks.map(([id, subCheck]) => {
101
+ const audit = platformAuditPolicy(id as PlatformAuditId, context);
102
+ const result = subCheck({ ...context, audit }, remoteData, shared) ?? infoResult(`Platform audit '${id}' was not evaluated`);
103
+ return { ...result, checkId: `platform.${id}`, classification: result.classification ?? audit.classification };
104
+ });
105
+ const blocking = results.map(normalizeVerificationRecord).find((result) => result.effectiveStatus !== 'pass');
106
+ return {
107
+ ...(blocking
108
+ ? {
109
+ type: CHECK_TYPE,
110
+ status: blocking.effectiveStatus,
111
+ message: blocking.message,
112
+ ...(blocking.fail_type ? { fail_type: blocking.fail_type } : {}),
113
+ classification: 'hard'
114
+ }
115
+ : shared.passResult(CHECK_TYPE, `Platform sync audits completed for Issue ${context.issueNumber || "identity"}`)),
116
+ subchecks: results
117
+ };
101
118
  }
102
119
 
103
- async function buildSyncContext({ taskDir, config, artifactFile }: any, shared: VerificationShared): Promise<any> {
120
+ async function buildSyncContext({ taskDir, config, artifactFile, skillName }: any, shared: VerificationShared): Promise<any> {
104
121
  const task = shared.loadTask(taskDir);
105
122
  if (!task.ok) {
106
123
  return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
@@ -139,7 +156,8 @@ async function buildSyncContext({ taskDir, config, artifactFile }: any, shared:
139
156
  }
140
157
  return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
141
158
  }
142
- const expectedValues = resolveExpectedValues(config);
159
+ const effectiveSkillName = skillName || config.skillName || "code-task";
160
+ const expectedValues = resolveExpectedValues(effectiveSkillName, artifactFile);
143
161
  if (!expectedValues.ok) {
144
162
  return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
145
163
  }
@@ -156,6 +174,7 @@ async function buildSyncContext({ taskDir, config, artifactFile }: any, shared:
156
174
  task,
157
175
  taskDir,
158
176
  config,
177
+ skillName: effectiveSkillName,
159
178
  artifactFile,
160
179
  artifactPath,
161
180
  issueIdentity,
@@ -174,59 +193,21 @@ async function buildSyncContext({ taskDir, config, artifactFile }: any, shared:
174
193
  };
175
194
  }
176
195
 
177
- function resolveExpectedValues(config: any): any {
196
+ function resolveExpectedValues(skillName: string, artifactFile: string | undefined): any {
178
197
  const defaults = getDefaults();
179
- const statusLabel = resolveDefaultValue({
180
- collection: defaults.statusLabels,
181
- key: config.expected_status_label_key,
182
- value: config.expected_status_label,
183
- configKey: "expected_status_label_key"
184
- });
185
- if (!statusLabel.ok) {
186
- return statusLabel;
187
- }
188
-
189
- const commentMarker = resolveDefaultValue({
190
- collection: defaults.markers,
191
- key: config.expected_comment_marker_key,
192
- value: config.expected_comment_marker,
193
- configKey: "expected_comment_marker_key"
194
- });
195
- if (!commentMarker.ok) {
196
- return commentMarker;
197
- }
198
-
199
- const prCommentMarker = resolveDefaultValue({
200
- collection: defaults.markers,
201
- key: config.expected_pr_comment_marker_key,
202
- value: config.expected_pr_comment_marker,
203
- configKey: "expected_pr_comment_marker_key"
204
- });
205
- if (!prCommentMarker.ok) {
206
- return prCommentMarker;
207
- }
208
-
198
+ const statusPolicy = platformAuditPolicy('status-label', { skillName, artifactFile });
199
+ const commentPolicy = platformAuditPolicy('comment-marker', { skillName, artifactFile });
200
+ const prCommentPolicy = ['pr-comment-marker', 'pr-comment-last-commit', 'pr-comment-content']
201
+ .map((id) => platformAuditPolicy(id as PlatformAuditId, { skillName, artifactFile }))
202
+ .find((policy) => policy.enabled && policy.expectedPrCommentMarkerKey);
209
203
  return {
210
204
  ok: true,
211
- statusLabel: statusLabel.value,
212
- commentMarker: commentMarker.value,
213
- prCommentMarker: prCommentMarker.value
205
+ statusLabel: statusPolicy.enabled ? statusPolicy.expectedStatusLabel || null : null,
206
+ commentMarker: commentPolicy.enabled && commentPolicy.expectedCommentMarkerKey ? defaults.markers[commentPolicy.expectedCommentMarkerKey] : null,
207
+ prCommentMarker: prCommentPolicy?.expectedPrCommentMarkerKey ? defaults.markers[prCommentPolicy.expectedPrCommentMarkerKey] : null
214
208
  };
215
209
  }
216
210
 
217
- function resolveDefaultValue({ collection, key, value, configKey }: any): any {
218
- if (!key) {
219
- return { ok: true, value: value || null };
220
- }
221
-
222
- const resolvedValue = collection[key];
223
- if (!resolvedValue) {
224
- return { ok: false, message: `Unknown ${configKey}: ${key}` };
225
- }
226
-
227
- return { ok: true, value: resolvedValue };
228
- }
229
-
230
211
  async function fetchRemoteData(context: any, shared: VerificationShared): Promise<any> {
231
212
  const provider = context.provider;
232
213
  const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
@@ -236,8 +217,8 @@ async function fetchRemoteData(context: any, shared: VerificationShared): Promis
236
217
  taskId: context.task?.metadata?.id || context.task?.id || "",
237
218
  ...(context.issueIdentity ? { issue: context.issueIdentity } : {}),
238
219
  ...(context.prIdentity ? { changeRequest: context.prIdentity } : {}),
239
- includeComments: shouldFetchComments(context.config),
240
- includeFields: Boolean(context.config.verify_issue_fields)
220
+ includeComments: shouldFetchIssueComments(context),
221
+ includeFields: true
241
222
  })
242
223
  : unsupportedProviderOperation(provider, "verification.fetchRemoteFacts");
243
224
  if (!facts.ok) {
@@ -257,7 +238,7 @@ async function fetchRemoteData(context: any, shared: VerificationShared): Promis
257
238
  }
258
239
  : null;
259
240
  let issueFields: any;
260
- if (context.config.verify_issue_fields && issueSnapshot?.issueType) {
241
+ if (issueSnapshot?.issueType) {
261
242
  const fieldKinds = new Map(issueSnapshot.issueType.fields.map((field: { name: string; kind: string }) => [field.name, field.kind]));
262
243
  issueFields = {
263
244
  pinnedNames: new Set(issueSnapshot.issueType.fields.map((field: { name: string }) => field.name)),
@@ -265,7 +246,7 @@ async function fetchRemoteData(context: any, shared: VerificationShared): Promis
265
246
  };
266
247
  }
267
248
  let prComments = null;
268
- if (context.prMarker && context.prIdentity && shouldFetchComments(context.config) && provider?.comments?.list) {
249
+ if (context.prMarker && context.prIdentity && shouldFetchPrComments(context) && provider?.comments?.list) {
269
250
  const listed = await provider.comments.list({ context: operationContext, parent: context.prIdentity });
270
251
  if (!listed.ok) return {
271
252
  earlyReturn: listed.error.retryable
@@ -277,7 +258,7 @@ async function fetchRemoteData(context: any, shared: VerificationShared): Promis
277
258
  const changeRequest = facts.value.changeRequest;
278
259
  let inLabelMapping: Record<string, string[]> = {};
279
260
  let repositoryLabels: string[] = [];
280
- if (context.config.verify_in_labels_computed && context.hasTriage) {
261
+ if (platformAuditPolicy('in-labels-computed', context).enabled && context.hasTriage) {
281
262
  const mapping = loadInLabelMapping(shared);
282
263
  if (!mapping.ok) return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
283
264
  inLabelMapping = mapping.value;
@@ -298,7 +279,9 @@ async function fetchRemoteData(context: any, shared: VerificationShared): Promis
298
279
  }
299
280
  return {
300
281
  issue,
301
- comments: facts.value.comments.map((comment: { id: string; body: string }) => ({ id: comment.id, body: comment.body })),
282
+ comments: facts.value.comments.map((comment: { id: string; body: string; createdSequence: number | null }) => ({
283
+ id: comment.id, body: comment.body, createdSequence: comment.createdSequence
284
+ })),
302
285
  prComments,
303
286
  prLabels: changeRequest?.labels || null,
304
287
  issueType: issueSnapshot
@@ -319,30 +302,31 @@ function mapTaskTypeToLabel(taskType: any): any {
319
302
  return taskTypeLabel(taskType);
320
303
  }
321
304
 
322
- function shouldFetchComments(config: any): any {
323
- return Boolean(
324
- config.expected_comment_marker
325
- || config.expected_comment_marker_key
326
- || config.expected_pr_comment_marker
327
- || config.expected_pr_comment_marker_key
328
- || config.verify_pr_comment_last_commit_matches_head
329
- || config.verify_comment_content
330
- || config.verify_task_comment_content
331
- );
305
+ function hasEnabledAudit(context: any, auditIds: PlatformAuditId[]): boolean {
306
+ return auditIds
307
+ .some((id) => platformAuditPolicy(id as PlatformAuditId, context).enabled);
308
+ }
309
+
310
+ function shouldFetchIssueComments(context: any): boolean {
311
+ return hasEnabledAudit(context, ['comment-marker', 'comment-content', 'task-comment-content']);
312
+ }
313
+
314
+ function shouldFetchPrComments(context: any): boolean {
315
+ return hasEnabledAudit(context, ['pr-comment-marker', 'pr-comment-last-commit', 'pr-comment-content']);
332
316
  }
333
317
 
334
318
  function checkStatusLabel(context: any, remoteData: any, shared: VerificationShared): any {
335
319
  if (!context.expectedStatusLabel || !context.hasTriage) {
336
- return null;
320
+ return infoResult('Status label audit is not applicable because no expected label or triage capability is available');
337
321
  }
338
322
 
339
323
  if (String(remoteData.issue.state || "").toUpperCase() !== "OPEN") {
340
- return null;
324
+ return infoResult('Status label audit is not applicable because the Issue is closed');
341
325
  }
342
326
 
343
327
  const labels = extractLabelNames(remoteData.issue.labels);
344
328
  if (labels.includes(context.expectedStatusLabel)) {
345
- return null;
329
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has expected status label '${context.expectedStatusLabel}'`);
346
330
  }
347
331
 
348
332
  return shared.failResult(CHECK_TYPE,
@@ -352,18 +336,18 @@ function checkStatusLabel(context: any, remoteData: any, shared: VerificationSha
352
336
  }
353
337
 
354
338
  function checkClosedIssueStatusLabels(context: any, remoteData: any, shared: VerificationShared): any {
355
- if (!context.config.verify_closed_issue_has_no_status_labels) {
356
- return null;
339
+ if (!context.audit.enabled) {
340
+ return infoResult('Closed Issue status-label audit is not enabled for this workflow');
357
341
  }
358
342
 
359
343
  if (String(remoteData.issue.state || "").toUpperCase() !== "CLOSED") {
360
- return null;
344
+ return infoResult('Closed Issue status-label audit is not applicable because the Issue is open');
361
345
  }
362
346
 
363
347
  const statusLabels = extractLabelNames(remoteData.issue.labels)
364
348
  .filter((label: any) => label.startsWith("status:"));
365
349
  if (statusLabels.length === 0) {
366
- return null;
350
+ return shared.passResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} has no status labels`);
367
351
  }
368
352
 
369
353
  return shared.failResult(CHECK_TYPE,
@@ -373,13 +357,28 @@ function checkClosedIssueStatusLabels(context: any, remoteData: any, shared: Ver
373
357
  }
374
358
 
375
359
  function checkCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
376
- if (!context.marker) {
377
- return null;
360
+ if (!context.audit.enabled || !context.marker) {
361
+ return infoResult('Issue comment-marker audit is not applicable because no marker is configured');
378
362
  }
379
363
 
380
364
  const comment = findCommentByMarker(remoteData.comments, context.marker);
381
365
  if (comment) {
382
- return null;
366
+ if (context.marker === MARKERS.summary(context.task.metadata.id)) {
367
+ const managed = remoteData.comments.filter((candidate: any) => {
368
+ const first = String(candidate.body || '').replace(/\r\n/g, '\n').split('\n', 1)[0] || '';
369
+ return first === MARKERS.task(context.task.metadata.id)
370
+ || first === MARKERS.summary(context.task.metadata.id)
371
+ || first.startsWith(`<!-- sync-issue:${context.task.metadata.id}:`);
372
+ });
373
+ if (managed.some((candidate: any) => !Number.isSafeInteger(candidate.createdSequence) || candidate.createdSequence < 1)) {
374
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} summary comment ordering is not provable`, "check_failed");
375
+ }
376
+ const summarySequence = comment.createdSequence;
377
+ if (!managed.every((candidate: any) => summarySequence >= candidate.createdSequence)) {
378
+ return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} summary comment is not last among task-managed comments`, "check_failed");
379
+ }
380
+ }
381
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has expected comment marker`);
383
382
  }
384
383
 
385
384
  return shared.failResult(CHECK_TYPE,
@@ -389,13 +388,13 @@ function checkCommentMarker(context: any, remoteData: any, shared: VerificationS
389
388
  }
390
389
 
391
390
  function checkPrCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
392
- if (!context.prMarker) {
393
- return null;
391
+ if (!context.audit.enabled || !context.prMarker) {
392
+ return infoResult('PR comment-marker audit is not applicable because no marker is configured');
394
393
  }
395
394
 
396
395
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
397
396
  if (comment) {
398
- return null;
397
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected comment marker`);
399
398
  }
400
399
 
401
400
  return shared.failResult(CHECK_TYPE,
@@ -405,95 +404,95 @@ function checkPrCommentMarker(context: any, remoteData: any, shared: Verificatio
405
404
  }
406
405
 
407
406
  function checkPrCommentLastCommit(context: any, remoteData: any, shared: VerificationShared): any {
408
- if (!context.config.verify_pr_comment_last_commit_matches_head) {
409
- return null;
407
+ if (!context.audit.enabled) {
408
+ return infoResult('PR last-commit audit is not enabled for this workflow');
410
409
  }
411
410
 
412
411
  if (!context.prMarker) {
413
- return shared.failResult(CHECK_TYPE,
412
+ return hardResult(shared.failResult(CHECK_TYPE,
414
413
  "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker",
415
414
  "check_failed"
416
- );
415
+ ));
417
416
  }
418
417
 
419
418
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
420
419
  if (!comment) {
421
- return shared.failResult(CHECK_TYPE,
420
+ return hardResult(shared.failResult(CHECK_TYPE,
422
421
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
423
422
  "check_failed"
424
- );
423
+ ));
425
424
  }
426
425
 
427
426
  const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
428
427
  if (!match) {
429
- return shared.failResult(CHECK_TYPE,
428
+ return hardResult(shared.failResult(CHECK_TYPE,
430
429
  `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`,
431
430
  "check_failed"
432
- );
431
+ ));
433
432
  }
434
433
 
435
434
  const expectedHead = String(remoteData.prHeadSha || "").trim();
436
- if (!expectedHead) return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
435
+ if (!expectedHead) return hardResult(shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error"));
437
436
  const actualHead = match[1]!.trim();
438
437
  if (expectedHead === actualHead) {
439
- return null;
438
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit matches HEAD`);
440
439
  }
441
440
 
442
- return shared.failResult(CHECK_TYPE,
441
+ return hardResult(shared.failResult(CHECK_TYPE,
443
442
  `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`,
444
443
  "check_failed"
445
- );
444
+ ));
446
445
  }
447
446
 
448
447
  function checkPrCommentRequiredPatterns(context: any, remoteData: any, shared: VerificationShared): any {
449
- const patterns = context.config.expected_pr_comment_required_patterns || [];
448
+ const patterns = context.audit.enabled ? context.config.expected_pr_comment_required_patterns || [] : [];
450
449
  if (!Array.isArray(patterns) || patterns.length === 0) {
451
- return null;
450
+ return infoResult('PR comment-content audit is not enabled for this workflow');
452
451
  }
453
452
 
454
453
  if (!context.prMarker) {
455
- return shared.failResult(CHECK_TYPE,
454
+ return hardResult(shared.failResult(CHECK_TYPE,
456
455
  "expected_pr_comment_required_patterns requires expected_pr_comment_marker",
457
456
  "check_failed"
458
- );
457
+ ));
459
458
  }
460
459
 
461
460
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
462
461
  if (!comment) {
463
- return shared.failResult(CHECK_TYPE,
462
+ return hardResult(shared.failResult(CHECK_TYPE,
464
463
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
465
464
  "check_failed"
466
- );
465
+ ));
467
466
  }
468
467
 
469
468
  const body = String(comment.body || "");
470
469
  for (const pattern of patterns) {
471
470
  const regex = new RegExp(pattern, "m");
472
471
  if (!regex.test(body)) {
473
- return shared.failResult(CHECK_TYPE,
472
+ return hardResult(shared.failResult(CHECK_TYPE,
474
473
  `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`,
475
474
  "check_failed"
476
- );
475
+ ));
477
476
  }
478
477
  }
479
478
 
480
- return null;
479
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} summary comment has all required patterns`);
481
480
  }
482
481
 
483
482
  function checkCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
484
- if (!context.config.verify_comment_content) {
485
- return null;
483
+ if (!context.audit.enabled) {
484
+ return infoResult('Artifact comment-content audit is not enabled for this workflow');
486
485
  }
487
486
 
488
487
  if (!context.marker) {
489
- return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
488
+ return hardResult(shared.failResult(CHECK_TYPE, "Artifact comment-content audit requires an expected comment marker", "check_failed"));
490
489
  }
491
490
 
492
491
  if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
493
- return shared.failResult(CHECK_TYPE,
492
+ return hardResult(shared.failResult(CHECK_TYPE,
494
493
  `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`,
495
494
  "check_failed"
496
- );
495
+ ));
497
496
  }
498
497
 
499
498
  const comment = findCommentByMarker(remoteData.comments, context.marker);
@@ -503,28 +502,28 @@ function checkCommentContent(context: any, remoteData: any, shared: Verification
503
502
  const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
504
503
  const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
505
504
  if (!localCanonical.ok) {
506
- return shared.failResult(CHECK_TYPE,
505
+ return hardResult(shared.failResult(CHECK_TYPE,
507
506
  `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`,
508
507
  "check_failed"
509
- );
508
+ ));
510
509
  }
511
510
  if (!commentCanonical.ok) {
512
- return shared.failResult(CHECK_TYPE,
511
+ return hardResult(shared.failResult(CHECK_TYPE,
513
512
  `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`,
514
513
  "check_failed"
515
- );
514
+ ));
516
515
  }
517
516
  localContent = shared.normalizeContent(localCanonical.value);
518
517
  commentContent = shared.normalizeContent(commentCanonical.value);
519
518
  } catch (error) {
520
- return shared.failResult(CHECK_TYPE,
519
+ return hardResult(shared.failResult(CHECK_TYPE,
521
520
  `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`,
522
521
  "check_failed"
523
- );
522
+ ));
524
523
  }
525
524
 
526
525
  if (localContent === commentContent) {
527
- return null;
526
+ return shared.passResult(CHECK_TYPE, `Artifact comment content matches Issue #${context.issueNumber}`);
528
527
  }
529
528
 
530
529
  return shared.failResult(CHECK_TYPE,
@@ -539,8 +538,8 @@ function checkCommentContent(context: any, remoteData: any, shared: Verification
539
538
  }
540
539
 
541
540
  function checkTaskCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
542
- if (!context.config.verify_task_comment_content) {
543
- return null;
541
+ if (!context.audit.enabled) {
542
+ return infoResult('Task comment-content audit is not enabled for this workflow');
544
543
  }
545
544
  const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
546
545
  const comment = findCommentByMarker(remoteData.comments, taskMarker);
@@ -559,16 +558,16 @@ function checkTaskCommentContent(context: any, remoteData: any, shared: Verifica
559
558
  loadProjectLanguage(shared)
560
559
  );
561
560
  } catch {
562
- return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
561
+ return hardResult(shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed"));
563
562
  }
564
563
  if (renderedTask.byteLength > 60_000) {
565
- return shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed");
564
+ return hardResult(shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed"));
566
565
  }
567
566
  const expectedBody = shared.normalizeContent(extractCommentBody(renderedTask.body));
568
567
  const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
569
568
 
570
569
  if (expectedBody === commentBody) {
571
- return null;
570
+ return shared.passResult(CHECK_TYPE, `Task comment content matches Issue #${context.issueNumber}`);
572
571
  }
573
572
 
574
573
  return shared.failResult(CHECK_TYPE,
@@ -578,17 +577,17 @@ function checkTaskCommentContent(context: any, remoteData: any, shared: Verifica
578
577
  }
579
578
 
580
579
  function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationShared): any {
581
- if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
582
- return null;
580
+ if (!context.audit.enabled || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
581
+ return infoResult('PR type-label audit is not applicable because the required PR metadata or capability is unavailable');
583
582
  }
584
583
 
585
584
  const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
586
585
  if (!expectedLabel) {
587
- return null;
586
+ return infoResult('PR type-label audit is not applicable because the task type has no label mapping');
588
587
  }
589
588
 
590
589
  if (remoteData.prLabels.includes(expectedLabel)) {
591
- return null;
590
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected type label '${expectedLabel}'`);
592
591
  }
593
592
 
594
593
  return shared.failResult(CHECK_TYPE,
@@ -598,8 +597,8 @@ function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationSha
598
597
  }
599
598
 
600
599
  function checkInLabelsMatchPr(context: any, remoteData: any, shared: VerificationShared): any {
601
- if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
602
- return null;
600
+ if (!context.audit.enabled || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
601
+ return infoResult('PR in: label audit is not applicable because the required PR metadata or capability is unavailable');
603
602
  }
604
603
 
605
604
  const issueInLabels = extractLabelNames(remoteData.issue.labels)
@@ -610,7 +609,7 @@ function checkInLabelsMatchPr(context: any, remoteData: any, shared: Verificatio
610
609
  .sort();
611
610
 
612
611
  if (arraysEqual(issueInLabels, prInLabels)) {
613
- return null;
612
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} in: labels match Issue #${context.issueNumber}`);
614
613
  }
615
614
 
616
615
  return shared.failResult(CHECK_TYPE,
@@ -620,8 +619,8 @@ function checkInLabelsMatchPr(context: any, remoteData: any, shared: Verificatio
620
619
  }
621
620
 
622
621
  function checkInLabelsComputed(context: any, remoteData: any, shared: VerificationShared): any {
623
- if (!context.config.verify_in_labels_computed || !context.hasTriage) {
624
- return null;
622
+ if (!context.audit.enabled || !context.hasTriage) {
623
+ return infoResult('Computed in: label audit is not applicable because it is disabled or triage capability is unavailable');
625
624
  }
626
625
 
627
626
  const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
@@ -632,7 +631,7 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
632
631
  }
633
632
 
634
633
  if (expectedInLabels.mode === "skipped") {
635
- return null;
634
+ return infoResult('Computed in: label audit is not applicable because no mapped changed path exists');
636
635
  }
637
636
 
638
637
  const actualInLabels = extractLabelNames(remoteData.issue.labels)
@@ -640,7 +639,7 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
640
639
  .sort();
641
640
 
642
641
  if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
643
- return null;
642
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels match committed changes`);
644
643
  }
645
644
 
646
645
  return shared.failResult(
@@ -651,13 +650,13 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
651
650
  }
652
651
 
653
652
  function checkSyncedRequirements(context: any, remoteData: any, shared: VerificationShared): any {
654
- if (!context.config.sync_checked_requirements || !context.hasTriage) {
655
- return null;
653
+ if (!context.audit.enabled || !context.hasTriage) {
654
+ return infoResult('Requirements audit is not applicable because it is disabled or triage capability is unavailable');
656
655
  }
657
656
 
658
657
  const checkedRequirements = shared.getCheckedRequirements(context.task.content);
659
658
  if (checkedRequirements.length === 0) {
660
- return null;
659
+ return infoResult('Requirements audit is not applicable because the task has no checked requirements');
661
660
  }
662
661
 
663
662
  const issueBody = remoteData.issue.body || "";
@@ -666,20 +665,20 @@ function checkSyncedRequirements(context: any, remoteData: any, shared: Verifica
666
665
  requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task")
667
666
  );
668
667
  if (resolution.status === "missing") {
669
- return null;
668
+ return infoResult('Requirements audit is not applicable because the Issue has no requirements section');
670
669
  }
671
670
  if (resolution.status === "ambiguous") {
672
- return shared.failResult(CHECK_TYPE,
671
+ return hardResult(shared.failResult(CHECK_TYPE,
673
672
  `Issue #${context.issueNumber} requirements section is ambiguous`,
674
673
  "check_failed"
675
- );
674
+ ));
676
675
  }
677
676
  const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
678
677
  const missingRequirements = checkedRequirements.filter(
679
678
  (item: any) => !hasCheckedRequirement(requirementBody, item)
680
679
  );
681
680
  if (missingRequirements.length === 0) {
682
- return null;
681
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} contains all checked requirements`);
683
682
  }
684
683
 
685
684
  return shared.failResult(CHECK_TYPE,
@@ -689,17 +688,17 @@ function checkSyncedRequirements(context: any, remoteData: any, shared: Verifica
689
688
  }
690
689
 
691
690
  function checkIssueType(context: any, remoteData: any, shared: VerificationShared): any {
692
- if (!context.config.verify_issue_type || !context.hasPush) {
693
- return null;
691
+ if (!context.hasPush) {
692
+ return infoResult('Issue Type audit is not applicable because push capability is unavailable');
694
693
  }
695
694
 
696
695
  if (remoteData.issueType === undefined) {
697
- return null;
696
+ return infoResult('Issue Type audit is not applicable because the provider does not expose Issue Type');
698
697
  }
699
698
 
700
699
  if (!remoteData.issueType) {
701
700
  if (context.repoOwnerType === "User") {
702
- return null;
701
+ return infoResult('Issue Type audit is not applicable for a user-owned repository');
703
702
  }
704
703
 
705
704
  return shared.failResult(CHECK_TYPE,
@@ -716,16 +715,16 @@ function checkIssueType(context: any, remoteData: any, shared: VerificationShare
716
715
  );
717
716
  }
718
717
 
719
- return null;
718
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has the expected Issue Type`);
720
719
  }
721
720
 
722
721
  function checkIssueFields(context: any, remoteData: any, shared: VerificationShared): any {
723
- if (!context.config.verify_issue_fields || !context.hasPush) {
724
- return null;
722
+ if (!context.hasPush) {
723
+ return infoResult('Issue field audit is not applicable because push capability is unavailable');
725
724
  }
726
725
 
727
726
  if (remoteData.issueFields === undefined) {
728
- return null;
727
+ return infoResult('Issue field audit is not applicable because the provider does not expose Issue fields');
729
728
  }
730
729
 
731
730
  for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
@@ -755,12 +754,12 @@ function checkIssueFields(context: any, remoteData: any, shared: VerificationSha
755
754
  }
756
755
  }
757
756
 
758
- return null;
757
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} fields match task metadata`);
759
758
  }
760
759
 
761
760
  function checkPrAssignee(context: any, remoteData: any, shared: VerificationShared): any {
762
- if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
763
- return null;
761
+ if (!context.audit.enabled || !context.hasPush || !context.prNumber) {
762
+ return infoResult('PR assignee audit is not applicable because it is disabled or required PR metadata/capability is unavailable');
764
763
  }
765
764
 
766
765
  if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
@@ -770,12 +769,12 @@ function checkPrAssignee(context: any, remoteData: any, shared: VerificationShar
770
769
  );
771
770
  }
772
771
 
773
- return null;
772
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has an assignee`);
774
773
  }
775
774
 
776
775
  function checkMilestone(context: any, remoteData: any, shared: VerificationShared): any {
777
- if (!context.config.verify_milestone || !context.hasTriage) {
778
- return null;
776
+ if (!context.hasTriage) {
777
+ return infoResult('Milestone audit is not applicable because triage capability is unavailable');
779
778
  }
780
779
 
781
780
  if (!remoteData.issue?.milestone?.title) {
@@ -792,7 +791,7 @@ function checkMilestone(context: any, remoteData: any, shared: VerificationShare
792
791
  );
793
792
  }
794
793
 
795
- if (context.config.verify_milestone_specific) {
794
+ if (context.audit.requireSpecificMilestone) {
796
795
  const issueTitle = remoteData.issue.milestone.title;
797
796
  if (VERSION_LINE_REGEX.test(issueTitle)) {
798
797
  return shared.failResult(CHECK_TYPE,
@@ -808,7 +807,7 @@ function checkMilestone(context: any, remoteData: any, shared: VerificationShare
808
807
  }
809
808
  }
810
809
 
811
- return null;
810
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone satisfies the configured policy`);
812
811
  }
813
812
 
814
813
  function findCommentByMarker(comments: any, marker: any): any {