@fitlab-ai/agent-infra 0.10.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/README.md +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +22 -12
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +10 -5
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +84 -32
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +184 -162
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +90 -22
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +6 -3
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +28 -2
- package/dist/lib/task/artifact-recovery.js +114 -68
- package/dist/lib/task/artifact-repair-intent.js +25 -8
- package/dist/lib/task/capabilities.js +0 -2
- package/dist/lib/task/control-authority.js +1 -1
- package/dist/lib/task/events.js +66 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/intermediate-cleanup.js +28 -3
- package/dist/lib/task/local-artifact-finalization.js +85 -18
- package/dist/lib/task/orchestration.js +8 -6
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +198 -11
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lib/task/workflow-command.js +2 -0
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +22 -12
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +9 -5
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +98 -31
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +203 -188
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +1 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +113 -22
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +6 -4
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +28 -2
- package/lib/task/artifact-recovery.ts +120 -67
- package/lib/task/artifact-repair-intent.ts +38 -11
- package/lib/task/capabilities.ts +0 -1
- package/lib/task/control-authority.ts +1 -1
- package/lib/task/events.ts +55 -38
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/intermediate-cleanup.ts +18 -2
- package/lib/task/local-artifact-finalization.ts +91 -15
- package/lib/task/orchestration.ts +12 -7
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +1 -1
- package/lib/task/review-finalization.ts +198 -9
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- 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 +2 -0
- package/lib/task/workflow-dispatch.ts +171 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +11 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +6 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +18 -9
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +21 -11
- 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 +4 -4
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -5
- 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.en.md +9 -4
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +10 -5
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- 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 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -2
- 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 +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- 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/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { VerificationShared } from "../task/verification-types.ts";
|
|
2
|
+
import { normalizeVerificationRecord, platformAuditPolicy, type PlatformAuditId } from "../task/gate-policy.ts";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
@@ -12,7 +13,7 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.ts";
|
|
|
12
13
|
import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
|
|
13
14
|
import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.ts";
|
|
14
15
|
import { taskIssueIdentity } from "./task-identities.ts";
|
|
15
|
-
import {
|
|
16
|
+
import { renderTaskCommentResult } from "./issue-comments.ts";
|
|
16
17
|
import {
|
|
17
18
|
CONTROL_MARKER_PATTERN,
|
|
18
19
|
canonicalizeCommentBody,
|
|
@@ -60,8 +61,23 @@ function sanitizeCommentContent(content: string): string | null {
|
|
|
60
61
|
return result.ok ? result.value : null;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
|
|
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,13 +521,9 @@ 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
|
-
if (isTaskCommentTooLarge(context.task.content)) {
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
527
|
const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
|
|
550
528
|
const comment = findCommentByMarker(remoteData.comments, taskMarker);
|
|
551
529
|
if (!comment) {
|
|
@@ -555,18 +533,24 @@ function checkTaskCommentContent(context: any, remoteData: any, shared: Verifica
|
|
|
555
533
|
);
|
|
556
534
|
}
|
|
557
535
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
536
|
+
let renderedTask: { body: string; byteLength: number };
|
|
537
|
+
try {
|
|
538
|
+
renderedTask = renderTaskCommentResult(
|
|
539
|
+
context.task.content,
|
|
540
|
+
context.task.metadata.id,
|
|
541
|
+
loadProjectLanguage(shared)
|
|
563
542
|
);
|
|
543
|
+
} catch {
|
|
544
|
+
return hardResult(shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed"));
|
|
545
|
+
}
|
|
546
|
+
if (renderedTask.byteLength > 60_000) {
|
|
547
|
+
return hardResult(shared.failResult(CHECK_TYPE, "Task comment exceeds the platform byte limit", "check_failed"));
|
|
564
548
|
}
|
|
565
|
-
const expectedBody = shared.normalizeContent(
|
|
549
|
+
const expectedBody = shared.normalizeContent(extractCommentBody(renderedTask.body));
|
|
566
550
|
const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
|
|
567
551
|
|
|
568
552
|
if (expectedBody === commentBody) {
|
|
569
|
-
return
|
|
553
|
+
return shared.passResult(CHECK_TYPE, `Task comment content matches Issue #${context.issueNumber}`);
|
|
570
554
|
}
|
|
571
555
|
|
|
572
556
|
return shared.failResult(CHECK_TYPE,
|
|
@@ -576,17 +560,17 @@ function checkTaskCommentContent(context: any, remoteData: any, shared: Verifica
|
|
|
576
560
|
}
|
|
577
561
|
|
|
578
562
|
function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationShared): any {
|
|
579
|
-
if (!context.
|
|
580
|
-
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');
|
|
581
565
|
}
|
|
582
566
|
|
|
583
567
|
const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
|
|
584
568
|
if (!expectedLabel) {
|
|
585
|
-
return
|
|
569
|
+
return infoResult('PR type-label audit is not applicable because the task type has no label mapping');
|
|
586
570
|
}
|
|
587
571
|
|
|
588
572
|
if (remoteData.prLabels.includes(expectedLabel)) {
|
|
589
|
-
return
|
|
573
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has expected type label '${expectedLabel}'`);
|
|
590
574
|
}
|
|
591
575
|
|
|
592
576
|
return shared.failResult(CHECK_TYPE,
|
|
@@ -596,8 +580,8 @@ function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationSha
|
|
|
596
580
|
}
|
|
597
581
|
|
|
598
582
|
function checkInLabelsMatchPr(context: any, remoteData: any, shared: VerificationShared): any {
|
|
599
|
-
if (!context.
|
|
600
|
-
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');
|
|
601
585
|
}
|
|
602
586
|
|
|
603
587
|
const issueInLabels = extractLabelNames(remoteData.issue.labels)
|
|
@@ -608,7 +592,7 @@ function checkInLabelsMatchPr(context: any, remoteData: any, shared: Verificatio
|
|
|
608
592
|
.sort();
|
|
609
593
|
|
|
610
594
|
if (arraysEqual(issueInLabels, prInLabels)) {
|
|
611
|
-
return
|
|
595
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} in: labels match Issue #${context.issueNumber}`);
|
|
612
596
|
}
|
|
613
597
|
|
|
614
598
|
return shared.failResult(CHECK_TYPE,
|
|
@@ -618,8 +602,8 @@ function checkInLabelsMatchPr(context: any, remoteData: any, shared: Verificatio
|
|
|
618
602
|
}
|
|
619
603
|
|
|
620
604
|
function checkInLabelsComputed(context: any, remoteData: any, shared: VerificationShared): any {
|
|
621
|
-
if (!context.
|
|
622
|
-
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');
|
|
623
607
|
}
|
|
624
608
|
|
|
625
609
|
const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
|
|
@@ -630,7 +614,7 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
|
|
|
630
614
|
}
|
|
631
615
|
|
|
632
616
|
if (expectedInLabels.mode === "skipped") {
|
|
633
|
-
return
|
|
617
|
+
return infoResult('Computed in: label audit is not applicable because no mapped changed path exists');
|
|
634
618
|
}
|
|
635
619
|
|
|
636
620
|
const actualInLabels = extractLabelNames(remoteData.issue.labels)
|
|
@@ -638,7 +622,7 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
|
|
|
638
622
|
.sort();
|
|
639
623
|
|
|
640
624
|
if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
|
|
641
|
-
return
|
|
625
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels match committed changes`);
|
|
642
626
|
}
|
|
643
627
|
|
|
644
628
|
return shared.failResult(
|
|
@@ -649,13 +633,13 @@ function checkInLabelsComputed(context: any, remoteData: any, shared: Verificati
|
|
|
649
633
|
}
|
|
650
634
|
|
|
651
635
|
function checkSyncedRequirements(context: any, remoteData: any, shared: VerificationShared): any {
|
|
652
|
-
if (!context.
|
|
653
|
-
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');
|
|
654
638
|
}
|
|
655
639
|
|
|
656
640
|
const checkedRequirements = shared.getCheckedRequirements(context.task.content);
|
|
657
641
|
if (checkedRequirements.length === 0) {
|
|
658
|
-
return
|
|
642
|
+
return infoResult('Requirements audit is not applicable because the task has no checked requirements');
|
|
659
643
|
}
|
|
660
644
|
|
|
661
645
|
const issueBody = remoteData.issue.body || "";
|
|
@@ -664,20 +648,20 @@ function checkSyncedRequirements(context: any, remoteData: any, shared: Verifica
|
|
|
664
648
|
requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task")
|
|
665
649
|
);
|
|
666
650
|
if (resolution.status === "missing") {
|
|
667
|
-
return
|
|
651
|
+
return infoResult('Requirements audit is not applicable because the Issue has no requirements section');
|
|
668
652
|
}
|
|
669
653
|
if (resolution.status === "ambiguous") {
|
|
670
|
-
return shared.failResult(CHECK_TYPE,
|
|
654
|
+
return hardResult(shared.failResult(CHECK_TYPE,
|
|
671
655
|
`Issue #${context.issueNumber} requirements section is ambiguous`,
|
|
672
656
|
"check_failed"
|
|
673
|
-
);
|
|
657
|
+
));
|
|
674
658
|
}
|
|
675
659
|
const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
|
|
676
660
|
const missingRequirements = checkedRequirements.filter(
|
|
677
661
|
(item: any) => !hasCheckedRequirement(requirementBody, item)
|
|
678
662
|
);
|
|
679
663
|
if (missingRequirements.length === 0) {
|
|
680
|
-
return
|
|
664
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} contains all checked requirements`);
|
|
681
665
|
}
|
|
682
666
|
|
|
683
667
|
return shared.failResult(CHECK_TYPE,
|
|
@@ -687,17 +671,17 @@ function checkSyncedRequirements(context: any, remoteData: any, shared: Verifica
|
|
|
687
671
|
}
|
|
688
672
|
|
|
689
673
|
function checkIssueType(context: any, remoteData: any, shared: VerificationShared): any {
|
|
690
|
-
if (!context.
|
|
691
|
-
return
|
|
674
|
+
if (!context.hasPush) {
|
|
675
|
+
return infoResult('Issue Type audit is not applicable because push capability is unavailable');
|
|
692
676
|
}
|
|
693
677
|
|
|
694
678
|
if (remoteData.issueType === undefined) {
|
|
695
|
-
return
|
|
679
|
+
return infoResult('Issue Type audit is not applicable because the provider does not expose Issue Type');
|
|
696
680
|
}
|
|
697
681
|
|
|
698
682
|
if (!remoteData.issueType) {
|
|
699
683
|
if (context.repoOwnerType === "User") {
|
|
700
|
-
return
|
|
684
|
+
return infoResult('Issue Type audit is not applicable for a user-owned repository');
|
|
701
685
|
}
|
|
702
686
|
|
|
703
687
|
return shared.failResult(CHECK_TYPE,
|
|
@@ -714,16 +698,16 @@ function checkIssueType(context: any, remoteData: any, shared: VerificationShare
|
|
|
714
698
|
);
|
|
715
699
|
}
|
|
716
700
|
|
|
717
|
-
return
|
|
701
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} has the expected Issue Type`);
|
|
718
702
|
}
|
|
719
703
|
|
|
720
704
|
function checkIssueFields(context: any, remoteData: any, shared: VerificationShared): any {
|
|
721
|
-
if (!context.
|
|
722
|
-
return
|
|
705
|
+
if (!context.hasPush) {
|
|
706
|
+
return infoResult('Issue field audit is not applicable because push capability is unavailable');
|
|
723
707
|
}
|
|
724
708
|
|
|
725
709
|
if (remoteData.issueFields === undefined) {
|
|
726
|
-
return
|
|
710
|
+
return infoResult('Issue field audit is not applicable because the provider does not expose Issue fields');
|
|
727
711
|
}
|
|
728
712
|
|
|
729
713
|
for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
|
|
@@ -753,12 +737,12 @@ function checkIssueFields(context: any, remoteData: any, shared: VerificationSha
|
|
|
753
737
|
}
|
|
754
738
|
}
|
|
755
739
|
|
|
756
|
-
return
|
|
740
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} fields match task metadata`);
|
|
757
741
|
}
|
|
758
742
|
|
|
759
743
|
function checkPrAssignee(context: any, remoteData: any, shared: VerificationShared): any {
|
|
760
|
-
if (!context.
|
|
761
|
-
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');
|
|
762
746
|
}
|
|
763
747
|
|
|
764
748
|
if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
|
|
@@ -768,12 +752,12 @@ function checkPrAssignee(context: any, remoteData: any, shared: VerificationShar
|
|
|
768
752
|
);
|
|
769
753
|
}
|
|
770
754
|
|
|
771
|
-
return
|
|
755
|
+
return shared.passResult(CHECK_TYPE, `PR #${context.prNumber} has an assignee`);
|
|
772
756
|
}
|
|
773
757
|
|
|
774
758
|
function checkMilestone(context: any, remoteData: any, shared: VerificationShared): any {
|
|
775
|
-
if (!context.
|
|
776
|
-
return
|
|
759
|
+
if (!context.hasTriage) {
|
|
760
|
+
return infoResult('Milestone audit is not applicable because triage capability is unavailable');
|
|
777
761
|
}
|
|
778
762
|
|
|
779
763
|
if (!remoteData.issue?.milestone?.title) {
|
|
@@ -790,7 +774,7 @@ function checkMilestone(context: any, remoteData: any, shared: VerificationShare
|
|
|
790
774
|
);
|
|
791
775
|
}
|
|
792
776
|
|
|
793
|
-
if (context.
|
|
777
|
+
if (context.audit.requireSpecificMilestone) {
|
|
794
778
|
const issueTitle = remoteData.issue.milestone.title;
|
|
795
779
|
if (VERSION_LINE_REGEX.test(issueTitle)) {
|
|
796
780
|
return shared.failResult(CHECK_TYPE,
|
|
@@ -806,7 +790,7 @@ function checkMilestone(context: any, remoteData: any, shared: VerificationShare
|
|
|
806
790
|
}
|
|
807
791
|
}
|
|
808
792
|
|
|
809
|
-
return
|
|
793
|
+
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone satisfies the configured policy`);
|
|
810
794
|
}
|
|
811
795
|
|
|
812
796
|
function findCommentByMarker(comments: any, marker: any): any {
|
|
@@ -833,7 +817,7 @@ function extractCommentBody(commentBody: any): any {
|
|
|
833
817
|
start += 1;
|
|
834
818
|
}
|
|
835
819
|
|
|
836
|
-
if (start < lines.length && /^> \*\*.+\*\* · .+$/.test(lines[start]!.trim())) {
|
|
820
|
+
if (start < lines.length && (/^> \*\*.+\*\* · .+$/.test(lines[start]!.trim()) || /^> 任务同步 · .+$/.test(lines[start]!.trim()))) {
|
|
837
821
|
start += 1;
|
|
838
822
|
}
|
|
839
823
|
|
|
@@ -841,6 +825,16 @@ function extractCommentBody(commentBody: any): any {
|
|
|
841
825
|
start += 1;
|
|
842
826
|
}
|
|
843
827
|
|
|
828
|
+
if (lines[start]?.trim() === "<details><summary>恢复元数据</summary>") {
|
|
829
|
+
const metadataEnd = lines.indexOf("</details>", start + 1);
|
|
830
|
+
if (metadataEnd >= 0) {
|
|
831
|
+
start = metadataEnd + 1;
|
|
832
|
+
while (start < lines.length && lines[start]!.trim() === "") {
|
|
833
|
+
start += 1;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
844
838
|
let end = lines.length;
|
|
845
839
|
for (let index = lines.length - 1; index >= start; index -= 1) {
|
|
846
840
|
const trimmed = lines[index]!.trim();
|
|
@@ -1042,7 +1036,9 @@ function computeExpectedInLabels(taskDir: any, repositoryLabels: string[], mappi
|
|
|
1042
1036
|
if (!baseRef) {
|
|
1043
1037
|
return { ok: false, type: "check_failed", message: "Task has no delivery_base_ref for in-label evidence" };
|
|
1044
1038
|
}
|
|
1045
|
-
const
|
|
1039
|
+
const branch = typeof task.metadata?.branch === "string" ? task.metadata.branch.trim() : "";
|
|
1040
|
+
const gitCwd = branch ? worktreeForBranch(shared.repoRoot, branch) ?? taskDir : taskDir;
|
|
1041
|
+
const changedFilesResult = gitText(["diff", `${baseRef}...HEAD`, "--name-only"], gitCwd);
|
|
1046
1042
|
if (!changedFilesResult.ok) {
|
|
1047
1043
|
return { ...changedFilesResult, type: "network_error" };
|
|
1048
1044
|
}
|
|
@@ -1069,6 +1065,25 @@ function computeExpectedInLabels(taskDir: any, repositoryLabels: string[], mappi
|
|
|
1069
1065
|
return { ok: true, labels: planned.target, mode: "mapped" };
|
|
1070
1066
|
}
|
|
1071
1067
|
|
|
1068
|
+
function worktreeForBranch(repositoryRoot: string, branch: string): string | null {
|
|
1069
|
+
try {
|
|
1070
|
+
const records = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
1071
|
+
cwd: repositoryRoot,
|
|
1072
|
+
encoding: "utf8",
|
|
1073
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1074
|
+
}).trim().split(/\r?\n\r?\n/);
|
|
1075
|
+
const expectedRef = `refs/heads/${branch}`;
|
|
1076
|
+
for (const record of records) {
|
|
1077
|
+
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
1078
|
+
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
1079
|
+
if (worktree && branchRef === expectedRef) return worktree;
|
|
1080
|
+
}
|
|
1081
|
+
} catch {
|
|
1082
|
+
// Fall back to taskDir so ordinary in-repository task workspaces retain their behavior.
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1072
1087
|
function loadInLabelMapping(shared: VerificationShared): any {
|
|
1073
1088
|
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
1074
1089
|
if (!fs.existsSync(configPath)) {
|