@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.
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +4 -131
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -0
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +120 -13
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +161 -156
- package/dist/lib/sandbox/control/executor.js +0 -38
- package/dist/lib/sandbox/control/finalization-response.js +10 -2
- package/dist/lib/sandbox/control/task-view.js +3 -1
- package/dist/lib/sandbox/control/workflow-executor.js +32 -41
- package/dist/lib/task/artifact-command.js +6 -6
- 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/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +1 -8
- package/dist/lib/task/delegation-receipts.js +14 -76
- package/dist/lib/task/events.js +147 -278
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +31 -245
- package/dist/lib/task/orchestration.js +8 -24
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +47 -218
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lib/task/workflow-command.js +4 -1
- package/dist/lib/task/workflow-dispatch.js +55 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +3 -145
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -0
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +132 -13
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +178 -179
- package/lib/sandbox/control/executor.ts +0 -44
- package/lib/sandbox/control/finalization-response.ts +10 -2
- package/lib/sandbox/control/task-view.ts +3 -1
- package/lib/sandbox/control/workflow-executor.ts +31 -48
- package/lib/task/artifact-command.ts +6 -7
- 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/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +1 -8
- package/lib/task/delegation-receipts.ts +12 -79
- package/lib/task/events.ts +155 -287
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +29 -285
- package/lib/task/orchestration.ts +12 -41
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +46 -200
- package/lib/task/verification-engine.ts +24 -14
- package/lib/task/verification-types.ts +7 -0
- package/lib/task/verification.ts +25 -9
- package/lib/task/workflow-command.ts +4 -1
- package/lib/task/workflow-dispatch.ts +46 -3
- package/package.json +2 -2
- 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/lifecycle-orchestration.en.md +7 -7
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- 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/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- 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 +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- 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/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- 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/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- 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/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- 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
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeVerificationRecord, platformAuditPolicy } from "../task/gate-policy.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import process from "node:process";
|
|
@@ -10,7 +11,7 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.js";
|
|
|
10
11
|
import { readPrDeliveryFact } from "../task/pr-delivery-fact.js";
|
|
11
12
|
import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.js";
|
|
12
13
|
import { taskIssueIdentity } from "./task-identities.js";
|
|
13
|
-
import { renderTaskCommentResult } from "./issue-comments.js";
|
|
14
|
+
import { MARKERS, renderTaskCommentResult } from "./issue-comments.js";
|
|
14
15
|
import { CONTROL_MARKER_PATTERN, canonicalizeCommentBody, renderSafeCodeFence, sanitizeMarkdownDocument } from "./comment-safety.js";
|
|
15
16
|
const CHECK_TYPE = "platform-sync";
|
|
16
17
|
const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
|
|
@@ -49,8 +50,21 @@ function sanitizeCommentContent(content) {
|
|
|
49
50
|
const result = sanitizeMarkdownDocument(content, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
50
51
|
return result.ok ? result.value : null;
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
function infoResult(message) {
|
|
54
|
+
return {
|
|
55
|
+
type: CHECK_TYPE,
|
|
56
|
+
status: 'pass',
|
|
57
|
+
message,
|
|
58
|
+
classification: 'info',
|
|
59
|
+
reason: 'NOT_APPLICABLE',
|
|
60
|
+
action: 'No action required'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function hardResult(result) {
|
|
64
|
+
return { ...result, classification: 'hard' };
|
|
65
|
+
}
|
|
66
|
+
export async function check({ taskDir, config, artifactFile, skillName }, shared) {
|
|
67
|
+
const context = await buildSyncContext({ taskDir, config, artifactFile, skillName }, shared);
|
|
54
68
|
if (context.earlyReturn) {
|
|
55
69
|
return context.earlyReturn;
|
|
56
70
|
}
|
|
@@ -59,32 +73,35 @@ export async function check({ taskDir, config, artifactFile }, shared) {
|
|
|
59
73
|
return remoteData.earlyReturn;
|
|
60
74
|
}
|
|
61
75
|
const subChecks = [
|
|
62
|
-
checkClosedIssueStatusLabels,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
checkInLabelsComputed,
|
|
71
|
-
checkPrTypeLabel,
|
|
72
|
-
checkInLabelsMatchPr,
|
|
73
|
-
checkPrAssignee,
|
|
74
|
-
checkSyncedRequirements,
|
|
75
|
-
checkIssueType,
|
|
76
|
-
checkIssueFields,
|
|
77
|
-
checkMilestone
|
|
76
|
+
['closed-status-labels', checkClosedIssueStatusLabels], ['status-label', checkStatusLabel],
|
|
77
|
+
['comment-marker', checkCommentMarker], ['pr-comment-marker', checkPrCommentMarker],
|
|
78
|
+
['pr-comment-last-commit', checkPrCommentLastCommit], ['pr-comment-content', checkPrCommentRequiredPatterns],
|
|
79
|
+
['comment-content', checkCommentContent], ['task-comment-content', checkTaskCommentContent],
|
|
80
|
+
['in-labels-computed', checkInLabelsComputed], ['pr-type-label', checkPrTypeLabel],
|
|
81
|
+
['in-labels-match-pr', checkInLabelsMatchPr], ['pr-assignee', checkPrAssignee],
|
|
82
|
+
['requirements', checkSyncedRequirements], ['issue-type', checkIssueType],
|
|
83
|
+
['issue-fields', checkIssueFields], ['milestone', checkMilestone]
|
|
78
84
|
];
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return
|
|
85
|
+
const results = subChecks.map(([id, subCheck]) => {
|
|
86
|
+
const audit = platformAuditPolicy(id, context);
|
|
87
|
+
const result = subCheck({ ...context, audit }, remoteData, shared) ?? infoResult(`Platform audit '${id}' was not evaluated`);
|
|
88
|
+
return { ...result, checkId: `platform.${id}`, classification: result.classification ?? audit.classification };
|
|
89
|
+
});
|
|
90
|
+
const blocking = results.map(normalizeVerificationRecord).find((result) => result.effectiveStatus !== 'pass');
|
|
91
|
+
return {
|
|
92
|
+
...(blocking
|
|
93
|
+
? {
|
|
94
|
+
type: CHECK_TYPE,
|
|
95
|
+
status: blocking.effectiveStatus,
|
|
96
|
+
message: blocking.message,
|
|
97
|
+
...(blocking.fail_type ? { fail_type: blocking.fail_type } : {}),
|
|
98
|
+
classification: 'hard'
|
|
99
|
+
}
|
|
100
|
+
: shared.passResult(CHECK_TYPE, `Platform sync audits completed for Issue ${context.issueNumber || "identity"}`)),
|
|
101
|
+
subchecks: results
|
|
102
|
+
};
|
|
86
103
|
}
|
|
87
|
-
async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
|
|
104
|
+
async function buildSyncContext({ taskDir, config, artifactFile, skillName }, shared) {
|
|
88
105
|
const task = shared.loadTask(taskDir);
|
|
89
106
|
if (!task.ok) {
|
|
90
107
|
return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
|
|
@@ -120,7 +137,8 @@ async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
|
|
|
120
137
|
}
|
|
121
138
|
return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
|
|
122
139
|
}
|
|
123
|
-
const
|
|
140
|
+
const effectiveSkillName = skillName || config.skillName || "code-task";
|
|
141
|
+
const expectedValues = resolveExpectedValues(effectiveSkillName, artifactFile);
|
|
124
142
|
if (!expectedValues.ok) {
|
|
125
143
|
return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
126
144
|
}
|
|
@@ -135,6 +153,7 @@ async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
|
|
|
135
153
|
task,
|
|
136
154
|
taskDir,
|
|
137
155
|
config,
|
|
156
|
+
skillName: effectiveSkillName,
|
|
138
157
|
artifactFile,
|
|
139
158
|
artifactPath,
|
|
140
159
|
issueIdentity,
|
|
@@ -152,52 +171,20 @@ async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
|
|
|
152
171
|
loadedContext: loaded.ok ? loaded.value : null
|
|
153
172
|
};
|
|
154
173
|
}
|
|
155
|
-
function resolveExpectedValues(
|
|
174
|
+
function resolveExpectedValues(skillName, artifactFile) {
|
|
156
175
|
const defaults = getDefaults();
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
163
|
-
if (!statusLabel.ok) {
|
|
164
|
-
return statusLabel;
|
|
165
|
-
}
|
|
166
|
-
const commentMarker = resolveDefaultValue({
|
|
167
|
-
collection: defaults.markers,
|
|
168
|
-
key: config.expected_comment_marker_key,
|
|
169
|
-
value: config.expected_comment_marker,
|
|
170
|
-
configKey: "expected_comment_marker_key"
|
|
171
|
-
});
|
|
172
|
-
if (!commentMarker.ok) {
|
|
173
|
-
return commentMarker;
|
|
174
|
-
}
|
|
175
|
-
const prCommentMarker = resolveDefaultValue({
|
|
176
|
-
collection: defaults.markers,
|
|
177
|
-
key: config.expected_pr_comment_marker_key,
|
|
178
|
-
value: config.expected_pr_comment_marker,
|
|
179
|
-
configKey: "expected_pr_comment_marker_key"
|
|
180
|
-
});
|
|
181
|
-
if (!prCommentMarker.ok) {
|
|
182
|
-
return prCommentMarker;
|
|
183
|
-
}
|
|
176
|
+
const statusPolicy = platformAuditPolicy('status-label', { skillName, artifactFile });
|
|
177
|
+
const commentPolicy = platformAuditPolicy('comment-marker', { skillName, artifactFile });
|
|
178
|
+
const prCommentPolicy = ['pr-comment-marker', 'pr-comment-last-commit', 'pr-comment-content']
|
|
179
|
+
.map((id) => platformAuditPolicy(id, { skillName, artifactFile }))
|
|
180
|
+
.find((policy) => policy.enabled && policy.expectedPrCommentMarkerKey);
|
|
184
181
|
return {
|
|
185
182
|
ok: true,
|
|
186
|
-
statusLabel:
|
|
187
|
-
commentMarker:
|
|
188
|
-
prCommentMarker:
|
|
183
|
+
statusLabel: statusPolicy.enabled ? statusPolicy.expectedStatusLabel || null : null,
|
|
184
|
+
commentMarker: commentPolicy.enabled && commentPolicy.expectedCommentMarkerKey ? defaults.markers[commentPolicy.expectedCommentMarkerKey] : null,
|
|
185
|
+
prCommentMarker: prCommentPolicy?.expectedPrCommentMarkerKey ? defaults.markers[prCommentPolicy.expectedPrCommentMarkerKey] : null
|
|
189
186
|
};
|
|
190
187
|
}
|
|
191
|
-
function resolveDefaultValue({ collection, key, value, configKey }) {
|
|
192
|
-
if (!key) {
|
|
193
|
-
return { ok: true, value: value || null };
|
|
194
|
-
}
|
|
195
|
-
const resolvedValue = collection[key];
|
|
196
|
-
if (!resolvedValue) {
|
|
197
|
-
return { ok: false, message: `Unknown ${configKey}: ${key}` };
|
|
198
|
-
}
|
|
199
|
-
return { ok: true, value: resolvedValue };
|
|
200
|
-
}
|
|
201
188
|
async function fetchRemoteData(context, shared) {
|
|
202
189
|
const provider = context.provider;
|
|
203
190
|
const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
|
|
@@ -207,8 +194,8 @@ async function fetchRemoteData(context, shared) {
|
|
|
207
194
|
taskId: context.task?.metadata?.id || context.task?.id || "",
|
|
208
195
|
...(context.issueIdentity ? { issue: context.issueIdentity } : {}),
|
|
209
196
|
...(context.prIdentity ? { changeRequest: context.prIdentity } : {}),
|
|
210
|
-
includeComments:
|
|
211
|
-
includeFields:
|
|
197
|
+
includeComments: shouldFetchIssueComments(context),
|
|
198
|
+
includeFields: true
|
|
212
199
|
})
|
|
213
200
|
: unsupportedProviderOperation(provider, "verification.fetchRemoteFacts");
|
|
214
201
|
if (!facts.ok) {
|
|
@@ -228,7 +215,7 @@ async function fetchRemoteData(context, shared) {
|
|
|
228
215
|
}
|
|
229
216
|
: null;
|
|
230
217
|
let issueFields;
|
|
231
|
-
if (
|
|
218
|
+
if (issueSnapshot?.issueType) {
|
|
232
219
|
const fieldKinds = new Map(issueSnapshot.issueType.fields.map((field) => [field.name, field.kind]));
|
|
233
220
|
issueFields = {
|
|
234
221
|
pinnedNames: new Set(issueSnapshot.issueType.fields.map((field) => field.name)),
|
|
@@ -236,7 +223,7 @@ async function fetchRemoteData(context, shared) {
|
|
|
236
223
|
};
|
|
237
224
|
}
|
|
238
225
|
let prComments = null;
|
|
239
|
-
if (context.prMarker && context.prIdentity &&
|
|
226
|
+
if (context.prMarker && context.prIdentity && shouldFetchPrComments(context) && provider?.comments?.list) {
|
|
240
227
|
const listed = await provider.comments.list({ context: operationContext, parent: context.prIdentity });
|
|
241
228
|
if (!listed.ok)
|
|
242
229
|
return {
|
|
@@ -249,7 +236,7 @@ async function fetchRemoteData(context, shared) {
|
|
|
249
236
|
const changeRequest = facts.value.changeRequest;
|
|
250
237
|
let inLabelMapping = {};
|
|
251
238
|
let repositoryLabels = [];
|
|
252
|
-
if (context.
|
|
239
|
+
if (platformAuditPolicy('in-labels-computed', context).enabled && context.hasTriage) {
|
|
253
240
|
const mapping = loadInLabelMapping(shared);
|
|
254
241
|
if (!mapping.ok)
|
|
255
242
|
return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
|
|
@@ -271,7 +258,9 @@ async function fetchRemoteData(context, shared) {
|
|
|
271
258
|
}
|
|
272
259
|
return {
|
|
273
260
|
issue,
|
|
274
|
-
comments: facts.value.comments.map((comment) => ({
|
|
261
|
+
comments: facts.value.comments.map((comment) => ({
|
|
262
|
+
id: comment.id, body: comment.body, createdSequence: comment.createdSequence
|
|
263
|
+
})),
|
|
275
264
|
prComments,
|
|
276
265
|
prLabels: changeRequest?.labels || null,
|
|
277
266
|
issueType: issueSnapshot
|
|
@@ -290,116 +279,132 @@ async function fetchRemoteData(context, shared) {
|
|
|
290
279
|
function mapTaskTypeToLabel(taskType) {
|
|
291
280
|
return taskTypeLabel(taskType);
|
|
292
281
|
}
|
|
293
|
-
function
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
282
|
+
function hasEnabledAudit(context, auditIds) {
|
|
283
|
+
return auditIds
|
|
284
|
+
.some((id) => platformAuditPolicy(id, context).enabled);
|
|
285
|
+
}
|
|
286
|
+
function shouldFetchIssueComments(context) {
|
|
287
|
+
return hasEnabledAudit(context, ['comment-marker', 'comment-content', 'task-comment-content']);
|
|
288
|
+
}
|
|
289
|
+
function shouldFetchPrComments(context) {
|
|
290
|
+
return hasEnabledAudit(context, ['pr-comment-marker', 'pr-comment-last-commit', 'pr-comment-content']);
|
|
301
291
|
}
|
|
302
292
|
function checkStatusLabel(context, remoteData, shared) {
|
|
303
293
|
if (!context.expectedStatusLabel || !context.hasTriage) {
|
|
304
|
-
return
|
|
294
|
+
return infoResult('Status label audit is not applicable because no expected label or triage capability is available');
|
|
305
295
|
}
|
|
306
296
|
if (String(remoteData.issue.state || "").toUpperCase() !== "OPEN") {
|
|
307
|
-
return
|
|
297
|
+
return infoResult('Status label audit is not applicable because the Issue is closed');
|
|
308
298
|
}
|
|
309
299
|
const labels = extractLabelNames(remoteData.issue.labels);
|
|
310
300
|
if (labels.includes(context.expectedStatusLabel)) {
|
|
311
|
-
return
|
|
301
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has expected status label '${context.expectedStatusLabel}'`);
|
|
312
302
|
}
|
|
313
303
|
return shared.failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
314
304
|
}
|
|
315
305
|
function checkClosedIssueStatusLabels(context, remoteData, shared) {
|
|
316
|
-
if (!context.
|
|
317
|
-
return
|
|
306
|
+
if (!context.audit.enabled) {
|
|
307
|
+
return infoResult('Closed Issue status-label audit is not enabled for this workflow');
|
|
318
308
|
}
|
|
319
309
|
if (String(remoteData.issue.state || "").toUpperCase() !== "CLOSED") {
|
|
320
|
-
return
|
|
310
|
+
return infoResult('Closed Issue status-label audit is not applicable because the Issue is open');
|
|
321
311
|
}
|
|
322
312
|
const statusLabels = extractLabelNames(remoteData.issue.labels)
|
|
323
313
|
.filter((label) => label.startsWith("status:"));
|
|
324
314
|
if (statusLabels.length === 0) {
|
|
325
|
-
return
|
|
315
|
+
return shared.passResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} has no status labels`);
|
|
326
316
|
}
|
|
327
317
|
return shared.failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
|
|
328
318
|
}
|
|
329
319
|
function checkCommentMarker(context, remoteData, shared) {
|
|
330
|
-
if (!context.marker) {
|
|
331
|
-
return
|
|
320
|
+
if (!context.audit.enabled || !context.marker) {
|
|
321
|
+
return infoResult('Issue comment-marker audit is not applicable because no marker is configured');
|
|
332
322
|
}
|
|
333
323
|
const comment = findCommentByMarker(remoteData.comments, context.marker);
|
|
334
324
|
if (comment) {
|
|
335
|
-
|
|
325
|
+
if (context.marker === MARKERS.summary(context.task.metadata.id)) {
|
|
326
|
+
const managed = remoteData.comments.filter((candidate) => {
|
|
327
|
+
const first = String(candidate.body || '').replace(/\r\n/g, '\n').split('\n', 1)[0] || '';
|
|
328
|
+
return first === MARKERS.task(context.task.metadata.id)
|
|
329
|
+
|| first === MARKERS.summary(context.task.metadata.id)
|
|
330
|
+
|| first.startsWith(`<!-- sync-issue:${context.task.metadata.id}:`);
|
|
331
|
+
});
|
|
332
|
+
if (managed.some((candidate) => !Number.isSafeInteger(candidate.createdSequence) || candidate.createdSequence < 1)) {
|
|
333
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} summary comment ordering is not provable`, "check_failed");
|
|
334
|
+
}
|
|
335
|
+
const summarySequence = comment.createdSequence;
|
|
336
|
+
if (!managed.every((candidate) => summarySequence >= candidate.createdSequence)) {
|
|
337
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} summary comment is not last among task-managed comments`, "check_failed");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has expected comment marker`);
|
|
336
341
|
}
|
|
337
342
|
return shared.failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
338
343
|
}
|
|
339
344
|
function checkPrCommentMarker(context, remoteData, shared) {
|
|
340
|
-
if (!context.prMarker) {
|
|
341
|
-
return
|
|
345
|
+
if (!context.audit.enabled || !context.prMarker) {
|
|
346
|
+
return infoResult('PR comment-marker audit is not applicable because no marker is configured');
|
|
342
347
|
}
|
|
343
348
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
344
349
|
if (comment) {
|
|
345
|
-
return
|
|
350
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected comment marker`);
|
|
346
351
|
}
|
|
347
352
|
return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
348
353
|
}
|
|
349
354
|
function checkPrCommentLastCommit(context, remoteData, shared) {
|
|
350
|
-
if (!context.
|
|
351
|
-
return
|
|
355
|
+
if (!context.audit.enabled) {
|
|
356
|
+
return infoResult('PR last-commit audit is not enabled for this workflow');
|
|
352
357
|
}
|
|
353
358
|
if (!context.prMarker) {
|
|
354
|
-
return shared.failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
|
|
359
|
+
return hardResult(shared.failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed"));
|
|
355
360
|
}
|
|
356
361
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
357
362
|
if (!comment) {
|
|
358
|
-
return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
363
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed"));
|
|
359
364
|
}
|
|
360
365
|
const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
|
|
361
366
|
if (!match) {
|
|
362
|
-
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
|
|
367
|
+
return hardResult(shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed"));
|
|
363
368
|
}
|
|
364
369
|
const expectedHead = String(remoteData.prHeadSha || "").trim();
|
|
365
370
|
if (!expectedHead)
|
|
366
|
-
return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
|
|
371
|
+
return hardResult(shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error"));
|
|
367
372
|
const actualHead = match[1].trim();
|
|
368
373
|
if (expectedHead === actualHead) {
|
|
369
|
-
return
|
|
374
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit matches HEAD`);
|
|
370
375
|
}
|
|
371
|
-
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
|
|
376
|
+
return hardResult(shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed"));
|
|
372
377
|
}
|
|
373
378
|
function checkPrCommentRequiredPatterns(context, remoteData, shared) {
|
|
374
|
-
const patterns = context.config.expected_pr_comment_required_patterns || [];
|
|
379
|
+
const patterns = context.audit.enabled ? context.config.expected_pr_comment_required_patterns || [] : [];
|
|
375
380
|
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
376
|
-
return
|
|
381
|
+
return infoResult('PR comment-content audit is not enabled for this workflow');
|
|
377
382
|
}
|
|
378
383
|
if (!context.prMarker) {
|
|
379
|
-
return shared.failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
|
|
384
|
+
return hardResult(shared.failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed"));
|
|
380
385
|
}
|
|
381
386
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
382
387
|
if (!comment) {
|
|
383
|
-
return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
388
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed"));
|
|
384
389
|
}
|
|
385
390
|
const body = String(comment.body || "");
|
|
386
391
|
for (const pattern of patterns) {
|
|
387
392
|
const regex = new RegExp(pattern, "m");
|
|
388
393
|
if (!regex.test(body)) {
|
|
389
|
-
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
|
|
394
|
+
return hardResult(shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed"));
|
|
390
395
|
}
|
|
391
396
|
}
|
|
392
|
-
return
|
|
397
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} summary comment has all required patterns`);
|
|
393
398
|
}
|
|
394
399
|
function checkCommentContent(context, remoteData, shared) {
|
|
395
|
-
if (!context.
|
|
396
|
-
return
|
|
400
|
+
if (!context.audit.enabled) {
|
|
401
|
+
return infoResult('Artifact comment-content audit is not enabled for this workflow');
|
|
397
402
|
}
|
|
398
403
|
if (!context.marker) {
|
|
399
|
-
return shared.failResult(CHECK_TYPE, "
|
|
404
|
+
return hardResult(shared.failResult(CHECK_TYPE, "Artifact comment-content audit requires an expected comment marker", "check_failed"));
|
|
400
405
|
}
|
|
401
406
|
if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
|
|
402
|
-
return shared.failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
|
|
407
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed"));
|
|
403
408
|
}
|
|
404
409
|
const comment = findCommentByMarker(remoteData.comments, context.marker);
|
|
405
410
|
let localContent;
|
|
@@ -408,25 +413,25 @@ function checkCommentContent(context, remoteData, shared) {
|
|
|
408
413
|
const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
|
|
409
414
|
const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
|
|
410
415
|
if (!localCanonical.ok) {
|
|
411
|
-
return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`, "check_failed");
|
|
416
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`, "check_failed"));
|
|
412
417
|
}
|
|
413
418
|
if (!commentCanonical.ok) {
|
|
414
|
-
return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`, "check_failed");
|
|
419
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`, "check_failed"));
|
|
415
420
|
}
|
|
416
421
|
localContent = shared.normalizeContent(localCanonical.value);
|
|
417
422
|
commentContent = shared.normalizeContent(commentCanonical.value);
|
|
418
423
|
}
|
|
419
424
|
catch (error) {
|
|
420
|
-
return shared.failResult(CHECK_TYPE, `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`, "check_failed");
|
|
425
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`, "check_failed"));
|
|
421
426
|
}
|
|
422
427
|
if (localContent === commentContent) {
|
|
423
|
-
return
|
|
428
|
+
return shared.passResult(CHECK_TYPE, `Artifact comment content matches Issue #${context.issueNumber}`);
|
|
424
429
|
}
|
|
425
430
|
return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
|
|
426
431
|
}
|
|
427
432
|
function checkTaskCommentContent(context, remoteData, shared) {
|
|
428
|
-
if (!context.
|
|
429
|
-
return
|
|
433
|
+
if (!context.audit.enabled) {
|
|
434
|
+
return infoResult('Task comment-content audit is not enabled for this workflow');
|
|
430
435
|
}
|
|
431
436
|
const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
|
|
432
437
|
const comment = findCommentByMarker(remoteData.comments, taskMarker);
|
|
@@ -438,34 +443,34 @@ function checkTaskCommentContent(context, remoteData, shared) {
|
|
|
438
443
|
renderedTask = renderTaskCommentResult(context.task.content, context.task.metadata.id, loadProjectLanguage(shared));
|
|
439
444
|
}
|
|
440
445
|
catch {
|
|
441
|
-
return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
|
|
446
|
+
return hardResult(shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed"));
|
|
442
447
|
}
|
|
443
448
|
if (renderedTask.byteLength > 60_000) {
|
|
444
|
-
return shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed");
|
|
449
|
+
return hardResult(shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed"));
|
|
445
450
|
}
|
|
446
451
|
const expectedBody = shared.normalizeContent(extractCommentBody(renderedTask.body));
|
|
447
452
|
const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
|
|
448
453
|
if (expectedBody === commentBody) {
|
|
449
|
-
return
|
|
454
|
+
return shared.passResult(CHECK_TYPE, `Task comment content matches Issue #${context.issueNumber}`);
|
|
450
455
|
}
|
|
451
456
|
return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
|
|
452
457
|
}
|
|
453
458
|
function checkPrTypeLabel(context, remoteData, shared) {
|
|
454
|
-
if (!context.
|
|
455
|
-
return
|
|
459
|
+
if (!context.audit.enabled || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
460
|
+
return infoResult('PR type-label audit is not applicable because the required PR metadata or capability is unavailable');
|
|
456
461
|
}
|
|
457
462
|
const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
|
|
458
463
|
if (!expectedLabel) {
|
|
459
|
-
return
|
|
464
|
+
return infoResult('PR type-label audit is not applicable because the task type has no label mapping');
|
|
460
465
|
}
|
|
461
466
|
if (remoteData.prLabels.includes(expectedLabel)) {
|
|
462
|
-
return
|
|
467
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected type label '${expectedLabel}'`);
|
|
463
468
|
}
|
|
464
469
|
return shared.failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
|
|
465
470
|
}
|
|
466
471
|
function checkInLabelsMatchPr(context, remoteData, shared) {
|
|
467
|
-
if (!context.
|
|
468
|
-
return
|
|
472
|
+
if (!context.audit.enabled || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
473
|
+
return infoResult('PR in: label audit is not applicable because the required PR metadata or capability is unavailable');
|
|
469
474
|
}
|
|
470
475
|
const issueInLabels = extractLabelNames(remoteData.issue.labels)
|
|
471
476
|
.filter((label) => label.startsWith("in:"))
|
|
@@ -474,13 +479,13 @@ function checkInLabelsMatchPr(context, remoteData, shared) {
|
|
|
474
479
|
.filter((label) => label.startsWith("in:"))
|
|
475
480
|
.sort();
|
|
476
481
|
if (arraysEqual(issueInLabels, prInLabels)) {
|
|
477
|
-
return
|
|
482
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} in: labels match Issue #${context.issueNumber}`);
|
|
478
483
|
}
|
|
479
484
|
return shared.failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
|
|
480
485
|
}
|
|
481
486
|
function checkInLabelsComputed(context, remoteData, shared) {
|
|
482
|
-
if (!context.
|
|
483
|
-
return
|
|
487
|
+
if (!context.audit.enabled || !context.hasTriage) {
|
|
488
|
+
return infoResult('Computed in: label audit is not applicable because it is disabled or triage capability is unavailable');
|
|
484
489
|
}
|
|
485
490
|
const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
|
|
486
491
|
if (!expectedInLabels.ok) {
|
|
@@ -489,49 +494,49 @@ function checkInLabelsComputed(context, remoteData, shared) {
|
|
|
489
494
|
: shared.blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
|
|
490
495
|
}
|
|
491
496
|
if (expectedInLabels.mode === "skipped") {
|
|
492
|
-
return
|
|
497
|
+
return infoResult('Computed in: label audit is not applicable because no mapped changed path exists');
|
|
493
498
|
}
|
|
494
499
|
const actualInLabels = extractLabelNames(remoteData.issue.labels)
|
|
495
500
|
.filter((label) => label.startsWith("in:"))
|
|
496
501
|
.sort();
|
|
497
502
|
if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
|
|
498
|
-
return
|
|
503
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels match committed changes`);
|
|
499
504
|
}
|
|
500
505
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
|
|
501
506
|
}
|
|
502
507
|
function checkSyncedRequirements(context, remoteData, shared) {
|
|
503
|
-
if (!context.
|
|
504
|
-
return
|
|
508
|
+
if (!context.audit.enabled || !context.hasTriage) {
|
|
509
|
+
return infoResult('Requirements audit is not applicable because it is disabled or triage capability is unavailable');
|
|
505
510
|
}
|
|
506
511
|
const checkedRequirements = shared.getCheckedRequirements(context.task.content);
|
|
507
512
|
if (checkedRequirements.length === 0) {
|
|
508
|
-
return
|
|
513
|
+
return infoResult('Requirements audit is not applicable because the task has no checked requirements');
|
|
509
514
|
}
|
|
510
515
|
const issueBody = remoteData.issue.body || "";
|
|
511
516
|
const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task"));
|
|
512
517
|
if (resolution.status === "missing") {
|
|
513
|
-
return
|
|
518
|
+
return infoResult('Requirements audit is not applicable because the Issue has no requirements section');
|
|
514
519
|
}
|
|
515
520
|
if (resolution.status === "ambiguous") {
|
|
516
|
-
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
|
|
521
|
+
return hardResult(shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed"));
|
|
517
522
|
}
|
|
518
523
|
const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
|
|
519
524
|
const missingRequirements = checkedRequirements.filter((item) => !hasCheckedRequirement(requirementBody, item));
|
|
520
525
|
if (missingRequirements.length === 0) {
|
|
521
|
-
return
|
|
526
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} contains all checked requirements`);
|
|
522
527
|
}
|
|
523
528
|
return shared.failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
|
|
524
529
|
}
|
|
525
530
|
function checkIssueType(context, remoteData, shared) {
|
|
526
|
-
if (!context.
|
|
527
|
-
return
|
|
531
|
+
if (!context.hasPush) {
|
|
532
|
+
return infoResult('Issue Type audit is not applicable because push capability is unavailable');
|
|
528
533
|
}
|
|
529
534
|
if (remoteData.issueType === undefined) {
|
|
530
|
-
return
|
|
535
|
+
return infoResult('Issue Type audit is not applicable because the provider does not expose Issue Type');
|
|
531
536
|
}
|
|
532
537
|
if (!remoteData.issueType) {
|
|
533
538
|
if (context.repoOwnerType === "User") {
|
|
534
|
-
return
|
|
539
|
+
return infoResult('Issue Type audit is not applicable for a user-owned repository');
|
|
535
540
|
}
|
|
536
541
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
|
|
537
542
|
}
|
|
@@ -539,14 +544,14 @@ function checkIssueType(context, remoteData, shared) {
|
|
|
539
544
|
if (expectedType && remoteData.issueType !== expectedType) {
|
|
540
545
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
|
|
541
546
|
}
|
|
542
|
-
return
|
|
547
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has the expected Issue Type`);
|
|
543
548
|
}
|
|
544
549
|
function checkIssueFields(context, remoteData, shared) {
|
|
545
|
-
if (!context.
|
|
546
|
-
return
|
|
550
|
+
if (!context.hasPush) {
|
|
551
|
+
return infoResult('Issue field audit is not applicable because push capability is unavailable');
|
|
547
552
|
}
|
|
548
553
|
if (remoteData.issueFields === undefined) {
|
|
549
|
-
return
|
|
554
|
+
return infoResult('Issue field audit is not applicable because the provider does not expose Issue fields');
|
|
550
555
|
}
|
|
551
556
|
for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
|
|
552
557
|
const expectedRaw = context.task.metadata[metadataKey];
|
|
@@ -565,20 +570,20 @@ function checkIssueFields(context, remoteData, shared) {
|
|
|
565
570
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
|
|
566
571
|
}
|
|
567
572
|
}
|
|
568
|
-
return
|
|
573
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} fields match task metadata`);
|
|
569
574
|
}
|
|
570
575
|
function checkPrAssignee(context, remoteData, shared) {
|
|
571
|
-
if (!context.
|
|
572
|
-
return
|
|
576
|
+
if (!context.audit.enabled || !context.hasPush || !context.prNumber) {
|
|
577
|
+
return infoResult('PR assignee audit is not applicable because it is disabled or required PR metadata/capability is unavailable');
|
|
573
578
|
}
|
|
574
579
|
if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
|
|
575
580
|
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
|
|
576
581
|
}
|
|
577
|
-
return
|
|
582
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has an assignee`);
|
|
578
583
|
}
|
|
579
584
|
function checkMilestone(context, remoteData, shared) {
|
|
580
|
-
if (!context.
|
|
581
|
-
return
|
|
585
|
+
if (!context.hasTriage) {
|
|
586
|
+
return infoResult('Milestone audit is not applicable because triage capability is unavailable');
|
|
582
587
|
}
|
|
583
588
|
if (!remoteData.issue?.milestone?.title) {
|
|
584
589
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
|
|
@@ -586,7 +591,7 @@ function checkMilestone(context, remoteData, shared) {
|
|
|
586
591
|
if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
|
|
587
592
|
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
|
|
588
593
|
}
|
|
589
|
-
if (context.
|
|
594
|
+
if (context.audit.requireSpecificMilestone) {
|
|
590
595
|
const issueTitle = remoteData.issue.milestone.title;
|
|
591
596
|
if (VERSION_LINE_REGEX.test(issueTitle)) {
|
|
592
597
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
|
|
@@ -595,7 +600,7 @@ function checkMilestone(context, remoteData, shared) {
|
|
|
595
600
|
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
|
|
596
601
|
}
|
|
597
602
|
}
|
|
598
|
-
return
|
|
603
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone satisfies the configured policy`);
|
|
599
604
|
}
|
|
600
605
|
function findCommentByMarker(comments, marker) {
|
|
601
606
|
return (comments || []).find((comment) => typeof comment.body === "string" && comment.body.includes(marker)) || null;
|