@fitlab-ai/agent-infra 0.11.0 → 0.11.1

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 (80) hide show
  1. package/dist/lib/internal/manual-validation.js +6 -2
  2. package/dist/lib/platform/issues.js +3 -4
  3. package/dist/lib/platform/verification-sync.js +142 -154
  4. package/dist/lib/sandbox/control/workflow-executor.js +7 -1
  5. package/dist/lib/task/artifact-lifecycle.js +15 -52
  6. package/dist/lib/task/artifact-operations.js +27 -1
  7. package/dist/lib/task/capabilities.js +0 -2
  8. package/dist/lib/task/events.js +60 -43
  9. package/dist/lib/task/gate-policy.js +94 -0
  10. package/dist/lib/task/local-artifact-finalization.js +7 -4
  11. package/dist/lib/task/orchestration.js +8 -6
  12. package/dist/lib/task/review-artifacts.js +12 -6
  13. package/dist/lib/task/review-finalization.js +3 -9
  14. package/dist/lib/task/verification-engine.js +28 -17
  15. package/dist/lib/task/verification.js +25 -9
  16. package/dist/lifecycle-build-manifest.json +3 -3
  17. package/lib/internal/manual-validation.ts +6 -2
  18. package/lib/platform/issues.ts +3 -4
  19. package/lib/platform/verification-sync.ts +159 -177
  20. package/lib/sandbox/control/workflow-executor.ts +11 -1
  21. package/lib/task/artifact-lifecycle.ts +12 -49
  22. package/lib/task/artifact-operations.ts +27 -1
  23. package/lib/task/capabilities.ts +0 -1
  24. package/lib/task/events.ts +49 -32
  25. package/lib/task/gate-policy.ts +133 -0
  26. package/lib/task/local-artifact-finalization.ts +6 -4
  27. package/lib/task/orchestration.ts +12 -7
  28. package/lib/task/review-artifacts.ts +8 -2
  29. package/lib/task/review-finalization.ts +3 -9
  30. package/lib/task/verification-engine.ts +24 -14
  31. package/lib/task/verification-types.ts +7 -0
  32. package/lib/task/verification.ts +25 -9
  33. package/package.json +1 -1
  34. package/templates/.agents/rules/issue-sync.en.md +2 -0
  35. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  36. package/templates/.agents/rules/local-artifact-repair.en.md +11 -3
  37. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +11 -3
  38. package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
  39. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  40. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  41. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  42. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  43. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  44. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  45. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  46. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  47. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  48. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  49. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  50. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  51. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  52. package/templates/.agents/skills/commit/config/verify.json +1 -5
  53. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  54. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  55. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  56. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  57. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  58. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  59. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  60. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  61. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  62. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  63. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  64. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  65. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  66. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  67. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  68. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  69. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  70. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  71. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  72. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  73. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  74. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  75. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  76. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  77. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  78. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  79. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  80. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
@@ -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";
@@ -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;
@@ -319,30 +300,31 @@ function mapTaskTypeToLabel(taskType: any): any {
319
300
  return taskTypeLabel(taskType);
320
301
  }
321
302
 
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
- );
303
+ function hasEnabledAudit(context: any, auditIds: PlatformAuditId[]): boolean {
304
+ return auditIds
305
+ .some((id) => platformAuditPolicy(id as PlatformAuditId, context).enabled);
306
+ }
307
+
308
+ function shouldFetchIssueComments(context: any): boolean {
309
+ return hasEnabledAudit(context, ['comment-marker', 'comment-content', 'task-comment-content']);
310
+ }
311
+
312
+ function shouldFetchPrComments(context: any): boolean {
313
+ return hasEnabledAudit(context, ['pr-comment-marker', 'pr-comment-last-commit', 'pr-comment-content']);
332
314
  }
333
315
 
334
316
  function checkStatusLabel(context: any, remoteData: any, shared: VerificationShared): any {
335
317
  if (!context.expectedStatusLabel || !context.hasTriage) {
336
- return null;
318
+ return infoResult('Status label audit is not applicable because no expected label or triage capability is available');
337
319
  }
338
320
 
339
321
  if (String(remoteData.issue.state || "").toUpperCase() !== "OPEN") {
340
- return null;
322
+ return infoResult('Status label audit is not applicable because the Issue is closed');
341
323
  }
342
324
 
343
325
  const labels = extractLabelNames(remoteData.issue.labels);
344
326
  if (labels.includes(context.expectedStatusLabel)) {
345
- return null;
327
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has expected status label '${context.expectedStatusLabel}'`);
346
328
  }
347
329
 
348
330
  return shared.failResult(CHECK_TYPE,
@@ -352,18 +334,18 @@ function checkStatusLabel(context: any, remoteData: any, shared: VerificationSha
352
334
  }
353
335
 
354
336
  function checkClosedIssueStatusLabels(context: any, remoteData: any, shared: VerificationShared): any {
355
- if (!context.config.verify_closed_issue_has_no_status_labels) {
356
- return null;
337
+ if (!context.audit.enabled) {
338
+ return infoResult('Closed Issue status-label audit is not enabled for this workflow');
357
339
  }
358
340
 
359
341
  if (String(remoteData.issue.state || "").toUpperCase() !== "CLOSED") {
360
- return null;
342
+ return infoResult('Closed Issue status-label audit is not applicable because the Issue is open');
361
343
  }
362
344
 
363
345
  const statusLabels = extractLabelNames(remoteData.issue.labels)
364
346
  .filter((label: any) => label.startsWith("status:"));
365
347
  if (statusLabels.length === 0) {
366
- return null;
348
+ return shared.passResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} has no status labels`);
367
349
  }
368
350
 
369
351
  return shared.failResult(CHECK_TYPE,
@@ -373,13 +355,13 @@ function checkClosedIssueStatusLabels(context: any, remoteData: any, shared: Ver
373
355
  }
374
356
 
375
357
  function checkCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
376
- if (!context.marker) {
377
- return null;
358
+ if (!context.audit.enabled || !context.marker) {
359
+ return infoResult('Issue comment-marker audit is not applicable because no marker is configured');
378
360
  }
379
361
 
380
362
  const comment = findCommentByMarker(remoteData.comments, context.marker);
381
363
  if (comment) {
382
- return null;
364
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has expected comment marker`);
383
365
  }
384
366
 
385
367
  return shared.failResult(CHECK_TYPE,
@@ -389,13 +371,13 @@ function checkCommentMarker(context: any, remoteData: any, shared: VerificationS
389
371
  }
390
372
 
391
373
  function checkPrCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
392
- if (!context.prMarker) {
393
- return null;
374
+ if (!context.audit.enabled || !context.prMarker) {
375
+ return infoResult('PR comment-marker audit is not applicable because no marker is configured');
394
376
  }
395
377
 
396
378
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
397
379
  if (comment) {
398
- return null;
380
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected comment marker`);
399
381
  }
400
382
 
401
383
  return shared.failResult(CHECK_TYPE,
@@ -405,95 +387,95 @@ function checkPrCommentMarker(context: any, remoteData: any, shared: Verificatio
405
387
  }
406
388
 
407
389
  function checkPrCommentLastCommit(context: any, remoteData: any, shared: VerificationShared): any {
408
- if (!context.config.verify_pr_comment_last_commit_matches_head) {
409
- return null;
390
+ if (!context.audit.enabled) {
391
+ return infoResult('PR last-commit audit is not enabled for this workflow');
410
392
  }
411
393
 
412
394
  if (!context.prMarker) {
413
- return shared.failResult(CHECK_TYPE,
395
+ return hardResult(shared.failResult(CHECK_TYPE,
414
396
  "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker",
415
397
  "check_failed"
416
- );
398
+ ));
417
399
  }
418
400
 
419
401
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
420
402
  if (!comment) {
421
- return shared.failResult(CHECK_TYPE,
403
+ return hardResult(shared.failResult(CHECK_TYPE,
422
404
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
423
405
  "check_failed"
424
- );
406
+ ));
425
407
  }
426
408
 
427
409
  const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
428
410
  if (!match) {
429
- return shared.failResult(CHECK_TYPE,
411
+ return hardResult(shared.failResult(CHECK_TYPE,
430
412
  `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`,
431
413
  "check_failed"
432
- );
414
+ ));
433
415
  }
434
416
 
435
417
  const expectedHead = String(remoteData.prHeadSha || "").trim();
436
- if (!expectedHead) return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
418
+ if (!expectedHead) return hardResult(shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error"));
437
419
  const actualHead = match[1]!.trim();
438
420
  if (expectedHead === actualHead) {
439
- return null;
421
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit matches HEAD`);
440
422
  }
441
423
 
442
- return shared.failResult(CHECK_TYPE,
424
+ return hardResult(shared.failResult(CHECK_TYPE,
443
425
  `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`,
444
426
  "check_failed"
445
- );
427
+ ));
446
428
  }
447
429
 
448
430
  function checkPrCommentRequiredPatterns(context: any, remoteData: any, shared: VerificationShared): any {
449
- const patterns = context.config.expected_pr_comment_required_patterns || [];
431
+ const patterns = context.audit.enabled ? context.config.expected_pr_comment_required_patterns || [] : [];
450
432
  if (!Array.isArray(patterns) || patterns.length === 0) {
451
- return null;
433
+ return infoResult('PR comment-content audit is not enabled for this workflow');
452
434
  }
453
435
 
454
436
  if (!context.prMarker) {
455
- return shared.failResult(CHECK_TYPE,
437
+ return hardResult(shared.failResult(CHECK_TYPE,
456
438
  "expected_pr_comment_required_patterns requires expected_pr_comment_marker",
457
439
  "check_failed"
458
- );
440
+ ));
459
441
  }
460
442
 
461
443
  const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
462
444
  if (!comment) {
463
- return shared.failResult(CHECK_TYPE,
445
+ return hardResult(shared.failResult(CHECK_TYPE,
464
446
  `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
465
447
  "check_failed"
466
- );
448
+ ));
467
449
  }
468
450
 
469
451
  const body = String(comment.body || "");
470
452
  for (const pattern of patterns) {
471
453
  const regex = new RegExp(pattern, "m");
472
454
  if (!regex.test(body)) {
473
- return shared.failResult(CHECK_TYPE,
455
+ return hardResult(shared.failResult(CHECK_TYPE,
474
456
  `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`,
475
457
  "check_failed"
476
- );
458
+ ));
477
459
  }
478
460
  }
479
461
 
480
- return null;
462
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} summary comment has all required patterns`);
481
463
  }
482
464
 
483
465
  function checkCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
484
- if (!context.config.verify_comment_content) {
485
- return null;
466
+ if (!context.audit.enabled) {
467
+ return infoResult('Artifact comment-content audit is not enabled for this workflow');
486
468
  }
487
469
 
488
470
  if (!context.marker) {
489
- return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
471
+ return hardResult(shared.failResult(CHECK_TYPE, "Artifact comment-content audit requires an expected comment marker", "check_failed"));
490
472
  }
491
473
 
492
474
  if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
493
- return shared.failResult(CHECK_TYPE,
475
+ return hardResult(shared.failResult(CHECK_TYPE,
494
476
  `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`,
495
477
  "check_failed"
496
- );
478
+ ));
497
479
  }
498
480
 
499
481
  const comment = findCommentByMarker(remoteData.comments, context.marker);
@@ -503,28 +485,28 @@ function checkCommentContent(context: any, remoteData: any, shared: Verification
503
485
  const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
504
486
  const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
505
487
  if (!localCanonical.ok) {
506
- return shared.failResult(CHECK_TYPE,
488
+ return hardResult(shared.failResult(CHECK_TYPE,
507
489
  `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`,
508
490
  "check_failed"
509
- );
491
+ ));
510
492
  }
511
493
  if (!commentCanonical.ok) {
512
- return shared.failResult(CHECK_TYPE,
494
+ return hardResult(shared.failResult(CHECK_TYPE,
513
495
  `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`,
514
496
  "check_failed"
515
- );
497
+ ));
516
498
  }
517
499
  localContent = shared.normalizeContent(localCanonical.value);
518
500
  commentContent = shared.normalizeContent(commentCanonical.value);
519
501
  } catch (error) {
520
- return shared.failResult(CHECK_TYPE,
502
+ return hardResult(shared.failResult(CHECK_TYPE,
521
503
  `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`,
522
504
  "check_failed"
523
- );
505
+ ));
524
506
  }
525
507
 
526
508
  if (localContent === commentContent) {
527
- return null;
509
+ return shared.passResult(CHECK_TYPE, `Artifact comment content matches Issue #${context.issueNumber}`);
528
510
  }
529
511
 
530
512
  return shared.failResult(CHECK_TYPE,
@@ -539,8 +521,8 @@ function checkCommentContent(context: any, remoteData: any, shared: Verification
539
521
  }
540
522
 
541
523
  function checkTaskCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
542
- if (!context.config.verify_task_comment_content) {
543
- return null;
524
+ if (!context.audit.enabled) {
525
+ return infoResult('Task comment-content audit is not enabled for this workflow');
544
526
  }
545
527
  const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
546
528
  const comment = findCommentByMarker(remoteData.comments, taskMarker);
@@ -559,16 +541,16 @@ function checkTaskCommentContent(context: any, remoteData: any, shared: Verifica
559
541
  loadProjectLanguage(shared)
560
542
  );
561
543
  } catch {
562
- return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
544
+ return hardResult(shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed"));
563
545
  }
564
546
  if (renderedTask.byteLength > 60_000) {
565
- return shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed");
547
+ return hardResult(shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed"));
566
548
  }
567
549
  const expectedBody = shared.normalizeContent(extractCommentBody(renderedTask.body));
568
550
  const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
569
551
 
570
552
  if (expectedBody === commentBody) {
571
- return null;
553
+ return shared.passResult(CHECK_TYPE, `Task comment content matches Issue #${context.issueNumber}`);
572
554
  }
573
555
 
574
556
  return shared.failResult(CHECK_TYPE,
@@ -578,17 +560,17 @@ function checkTaskCommentContent(context: any, remoteData: any, shared: Verifica
578
560
  }
579
561
 
580
562
  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;
563
+ if (!context.audit.enabled || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
564
+ return infoResult('PR type-label audit is not applicable because the required PR metadata or capability is unavailable');
583
565
  }
584
566
 
585
567
  const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
586
568
  if (!expectedLabel) {
587
- return null;
569
+ return infoResult('PR type-label audit is not applicable because the task type has no label mapping');
588
570
  }
589
571
 
590
572
  if (remoteData.prLabels.includes(expectedLabel)) {
591
- return null;
573
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected type label '${expectedLabel}'`);
592
574
  }
593
575
 
594
576
  return shared.failResult(CHECK_TYPE,
@@ -598,8 +580,8 @@ function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationSha
598
580
  }
599
581
 
600
582
  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;
583
+ if (!context.audit.enabled || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
584
+ return infoResult('PR in: label audit is not applicable because the required PR metadata or capability is unavailable');
603
585
  }
604
586
 
605
587
  const issueInLabels = extractLabelNames(remoteData.issue.labels)
@@ -610,7 +592,7 @@ function checkInLabelsMatchPr(context: any, remoteData: any, shared: Verificatio
610
592
  .sort();
611
593
 
612
594
  if (arraysEqual(issueInLabels, prInLabels)) {
613
- return null;
595
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} in: labels match Issue #${context.issueNumber}`);
614
596
  }
615
597
 
616
598
  return shared.failResult(CHECK_TYPE,
@@ -620,8 +602,8 @@ function checkInLabelsMatchPr(context: any, remoteData: any, shared: Verificatio
620
602
  }
621
603
 
622
604
  function checkInLabelsComputed(context: any, remoteData: any, shared: VerificationShared): any {
623
- if (!context.config.verify_in_labels_computed || !context.hasTriage) {
624
- return null;
605
+ if (!context.audit.enabled || !context.hasTriage) {
606
+ return infoResult('Computed in: label audit is not applicable because it is disabled or triage capability is unavailable');
625
607
  }
626
608
 
627
609
  const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
@@ -632,7 +614,7 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
632
614
  }
633
615
 
634
616
  if (expectedInLabels.mode === "skipped") {
635
- return null;
617
+ return infoResult('Computed in: label audit is not applicable because no mapped changed path exists');
636
618
  }
637
619
 
638
620
  const actualInLabels = extractLabelNames(remoteData.issue.labels)
@@ -640,7 +622,7 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
640
622
  .sort();
641
623
 
642
624
  if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
643
- return null;
625
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels match committed changes`);
644
626
  }
645
627
 
646
628
  return shared.failResult(
@@ -651,13 +633,13 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
651
633
  }
652
634
 
653
635
  function checkSyncedRequirements(context: any, remoteData: any, shared: VerificationShared): any {
654
- if (!context.config.sync_checked_requirements || !context.hasTriage) {
655
- return null;
636
+ if (!context.audit.enabled || !context.hasTriage) {
637
+ return infoResult('Requirements audit is not applicable because it is disabled or triage capability is unavailable');
656
638
  }
657
639
 
658
640
  const checkedRequirements = shared.getCheckedRequirements(context.task.content);
659
641
  if (checkedRequirements.length === 0) {
660
- return null;
642
+ return infoResult('Requirements audit is not applicable because the task has no checked requirements');
661
643
  }
662
644
 
663
645
  const issueBody = remoteData.issue.body || "";
@@ -666,20 +648,20 @@ function checkSyncedRequirements(context: any, remoteData: any, shared: Verifica
666
648
  requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task")
667
649
  );
668
650
  if (resolution.status === "missing") {
669
- return null;
651
+ return infoResult('Requirements audit is not applicable because the Issue has no requirements section');
670
652
  }
671
653
  if (resolution.status === "ambiguous") {
672
- return shared.failResult(CHECK_TYPE,
654
+ return hardResult(shared.failResult(CHECK_TYPE,
673
655
  `Issue #${context.issueNumber} requirements section is ambiguous`,
674
656
  "check_failed"
675
- );
657
+ ));
676
658
  }
677
659
  const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
678
660
  const missingRequirements = checkedRequirements.filter(
679
661
  (item: any) => !hasCheckedRequirement(requirementBody, item)
680
662
  );
681
663
  if (missingRequirements.length === 0) {
682
- return null;
664
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} contains all checked requirements`);
683
665
  }
684
666
 
685
667
  return shared.failResult(CHECK_TYPE,
@@ -689,17 +671,17 @@ function checkSyncedRequirements(context: any, remoteData: any, shared: Verifica
689
671
  }
690
672
 
691
673
  function checkIssueType(context: any, remoteData: any, shared: VerificationShared): any {
692
- if (!context.config.verify_issue_type || !context.hasPush) {
693
- return null;
674
+ if (!context.hasPush) {
675
+ return infoResult('Issue Type audit is not applicable because push capability is unavailable');
694
676
  }
695
677
 
696
678
  if (remoteData.issueType === undefined) {
697
- return null;
679
+ return infoResult('Issue Type audit is not applicable because the provider does not expose Issue Type');
698
680
  }
699
681
 
700
682
  if (!remoteData.issueType) {
701
683
  if (context.repoOwnerType === "User") {
702
- return null;
684
+ return infoResult('Issue Type audit is not applicable for a user-owned repository');
703
685
  }
704
686
 
705
687
  return shared.failResult(CHECK_TYPE,
@@ -716,16 +698,16 @@ function checkIssueType(context: any, remoteData: any, shared: VerificationShare
716
698
  );
717
699
  }
718
700
 
719
- return null;
701
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has the expected Issue Type`);
720
702
  }
721
703
 
722
704
  function checkIssueFields(context: any, remoteData: any, shared: VerificationShared): any {
723
- if (!context.config.verify_issue_fields || !context.hasPush) {
724
- return null;
705
+ if (!context.hasPush) {
706
+ return infoResult('Issue field audit is not applicable because push capability is unavailable');
725
707
  }
726
708
 
727
709
  if (remoteData.issueFields === undefined) {
728
- return null;
710
+ return infoResult('Issue field audit is not applicable because the provider does not expose Issue fields');
729
711
  }
730
712
 
731
713
  for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
@@ -755,12 +737,12 @@ function checkIssueFields(context: any, remoteData: any, shared: VerificationSha
755
737
  }
756
738
  }
757
739
 
758
- return null;
740
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} fields match task metadata`);
759
741
  }
760
742
 
761
743
  function checkPrAssignee(context: any, remoteData: any, shared: VerificationShared): any {
762
- if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
763
- return null;
744
+ if (!context.audit.enabled || !context.hasPush || !context.prNumber) {
745
+ return infoResult('PR assignee audit is not applicable because it is disabled or required PR metadata/capability is unavailable');
764
746
  }
765
747
 
766
748
  if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
@@ -770,12 +752,12 @@ function checkPrAssignee(context: any, remoteData: any, shared: VerificationShar
770
752
  );
771
753
  }
772
754
 
773
- return null;
755
+ return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has an assignee`);
774
756
  }
775
757
 
776
758
  function checkMilestone(context: any, remoteData: any, shared: VerificationShared): any {
777
- if (!context.config.verify_milestone || !context.hasTriage) {
778
- return null;
759
+ if (!context.hasTriage) {
760
+ return infoResult('Milestone audit is not applicable because triage capability is unavailable');
779
761
  }
780
762
 
781
763
  if (!remoteData.issue?.milestone?.title) {
@@ -792,7 +774,7 @@ function checkMilestone(context: any, remoteData: any, shared: VerificationShare
792
774
  );
793
775
  }
794
776
 
795
- if (context.config.verify_milestone_specific) {
777
+ if (context.audit.requireSpecificMilestone) {
796
778
  const issueTitle = remoteData.issue.milestone.title;
797
779
  if (VERSION_LINE_REGEX.test(issueTitle)) {
798
780
  return shared.failResult(CHECK_TYPE,
@@ -808,7 +790,7 @@ function checkMilestone(context: any, remoteData: any, shared: VerificationShare
808
790
  }
809
791
  }
810
792
 
811
- return null;
793
+ return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone satisfies the configured policy`);
812
794
  }
813
795
 
814
796
  function findCommentByMarker(comments: any, marker: any): any {