@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.
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/verification-sync.js +142 -154
- package/dist/lib/sandbox/control/workflow-executor.js +7 -1
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +27 -1
- package/dist/lib/task/capabilities.js +0 -2
- package/dist/lib/task/events.js +60 -43
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/local-artifact-finalization.js +7 -4
- package/dist/lib/task/orchestration.js +8 -6
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-finalization.js +3 -9
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/verification-sync.ts +159 -177
- package/lib/sandbox/control/workflow-executor.ts +11 -1
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +27 -1
- package/lib/task/capabilities.ts +0 -1
- package/lib/task/events.ts +49 -32
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/local-artifact-finalization.ts +6 -4
- package/lib/task/orchestration.ts +12 -7
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-finalization.ts +3 -9
- package/lib/task/verification-engine.ts +24 -14
- package/lib/task/verification-types.ts +7 -0
- package/lib/task/verification.ts +25 -9
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
- package/templates/.agents/rules/local-artifact-repair.en.md +11 -3
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +11 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/block-task/config/verify.json +1 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
- package/templates/.agents/skills/commit/config/verify.json +1 -5
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
- package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
- package/templates/.agents/skills/create-pr/config/verify.json +1 -10
- package/templates/.agents/skills/create-task/config/verify.json +1 -4
- package/templates/.agents/skills/import-issue/config/verify.json +1 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
- 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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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(
|
|
196
|
+
function resolveExpectedValues(skillName: string, artifactFile: string | undefined): any {
|
|
178
197
|
const defaults = getDefaults();
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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:
|
|
212
|
-
commentMarker:
|
|
213
|
-
prCommentMarker:
|
|
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:
|
|
240
|
-
includeFields:
|
|
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 (
|
|
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 &&
|
|
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.
|
|
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
|
|
323
|
-
return
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
356
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
409
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
485
|
-
return
|
|
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, "
|
|
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
|
|
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.
|
|
543
|
-
return
|
|
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
|
|
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.
|
|
582
|
-
return
|
|
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
|
|
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
|
|
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.
|
|
602
|
-
return
|
|
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
|
|
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.
|
|
624
|
-
return
|
|
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
|
|
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
|
|
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.
|
|
655
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
693
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
724
|
-
return
|
|
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
|
|
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
|
|
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.
|
|
763
|
-
return
|
|
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
|
|
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.
|
|
778
|
-
return
|
|
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.
|
|
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
|
|
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 {
|