@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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 +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VerificationShared } from "../task/verification-types.ts";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import process from "node:process";
|
|
@@ -11,6 +12,12 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.ts";
|
|
|
11
12
|
import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
|
|
12
13
|
import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.ts";
|
|
13
14
|
import { taskIssueIdentity } from "./task-identities.ts";
|
|
15
|
+
import {
|
|
16
|
+
CONTROL_MARKER_PATTERN,
|
|
17
|
+
canonicalizeCommentBody,
|
|
18
|
+
renderSafeCodeFence,
|
|
19
|
+
sanitizeMarkdownDocument
|
|
20
|
+
} from "./comment-safety.ts";
|
|
14
21
|
|
|
15
22
|
const CHECK_TYPE = "platform-sync";
|
|
16
23
|
const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
|
|
@@ -27,9 +34,6 @@ const OPTION_LOCALIZATION: Record<string, string> = {
|
|
|
27
34
|
"低": "Low"
|
|
28
35
|
};
|
|
29
36
|
|
|
30
|
-
let activeShared: any = null;
|
|
31
|
-
let repoRoot = "";
|
|
32
|
-
|
|
33
37
|
export function getDefaults(): any {
|
|
34
38
|
return {
|
|
35
39
|
statusLabels: {
|
|
@@ -50,59 +54,18 @@ export function getDefaults(): any {
|
|
|
50
54
|
};
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return activeShared;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function loadTask(...args: any[]): any {
|
|
62
|
-
return getShared().loadTask(...args);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getCheckedRequirements(...args: any[]): any {
|
|
66
|
-
return getShared().getCheckedRequirements(...args);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function normalizeContent(...args: any[]): any {
|
|
70
|
-
return getShared().normalizeContent(...args);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function isBlank(...args: any[]): any {
|
|
74
|
-
return getShared().isBlank(...args);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function escapeRegExp(...args: any[]): any {
|
|
78
|
-
return getShared().escapeRegExp(...args);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function passResult(...args: any[]): any {
|
|
82
|
-
return getShared().passResult(...args);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function failResult(...args: any[]): any {
|
|
86
|
-
return getShared().failResult(...args);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function blockedResult(...args: any[]): any {
|
|
90
|
-
return getShared().blockedResult(...args);
|
|
57
|
+
function sanitizeCommentContent(content: string): string | null {
|
|
58
|
+
const result = sanitizeMarkdownDocument(content, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
59
|
+
return result.ok ? result.value : null;
|
|
91
60
|
}
|
|
92
61
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function check({ taskDir, config, artifactFile }: any, shared: any): Promise<any> {
|
|
98
|
-
activeShared = shared;
|
|
99
|
-
repoRoot = shared.repoRoot;
|
|
100
|
-
const context = await buildSyncContext({ taskDir, config, artifactFile });
|
|
62
|
+
export async function check({ taskDir, config, artifactFile }: any, shared: VerificationShared): Promise<any> {
|
|
63
|
+
const context = await buildSyncContext({ taskDir, config, artifactFile }, shared);
|
|
101
64
|
if (context.earlyReturn) {
|
|
102
65
|
return context.earlyReturn;
|
|
103
66
|
}
|
|
104
67
|
|
|
105
|
-
const remoteData = await fetchRemoteData(context);
|
|
68
|
+
const remoteData = await fetchRemoteData(context, shared);
|
|
106
69
|
if (remoteData.earlyReturn) {
|
|
107
70
|
return remoteData.earlyReturn;
|
|
108
71
|
}
|
|
@@ -127,57 +90,57 @@ export async function check({ taskDir, config, artifactFile }: any, shared: any)
|
|
|
127
90
|
];
|
|
128
91
|
|
|
129
92
|
for (const subCheck of subChecks) {
|
|
130
|
-
const result = subCheck(context, remoteData);
|
|
93
|
+
const result = subCheck(context, remoteData, shared);
|
|
131
94
|
if (result) {
|
|
132
95
|
return result;
|
|
133
96
|
}
|
|
134
97
|
}
|
|
135
98
|
|
|
136
|
-
return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
|
|
99
|
+
return shared.passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
|
|
137
100
|
}
|
|
138
101
|
|
|
139
|
-
async function buildSyncContext({ taskDir, config, artifactFile }: any): Promise<any> {
|
|
140
|
-
const task = loadTask(taskDir);
|
|
102
|
+
async function buildSyncContext({ taskDir, config, artifactFile }: any, shared: VerificationShared): Promise<any> {
|
|
103
|
+
const task = shared.loadTask(taskDir);
|
|
141
104
|
if (!task.ok) {
|
|
142
|
-
return { earlyReturn: failResult(CHECK_TYPE, task.message) };
|
|
105
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
|
|
143
106
|
}
|
|
144
107
|
|
|
145
108
|
const issueIdentity = taskIssueIdentity(task.metadata);
|
|
146
109
|
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
147
110
|
const fact = readPrDeliveryFact(task.metadata);
|
|
148
111
|
if (fact.status === "invalid") {
|
|
149
|
-
return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
|
|
112
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, fact.error.message, "check_failed") };
|
|
150
113
|
}
|
|
151
114
|
const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
|
|
152
115
|
const prNumber = resourceIdentityNumber(prIdentity);
|
|
153
116
|
if (config.when === "issue_number_exists" && !issueIdentity) {
|
|
154
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
|
|
117
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
|
|
155
118
|
}
|
|
156
119
|
if (config.when === "pr_fact_bound" && !prIdentity) {
|
|
157
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
|
|
120
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
|
|
158
121
|
}
|
|
159
122
|
|
|
160
123
|
if (!issueIdentity) {
|
|
161
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
|
|
124
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
|
|
162
125
|
}
|
|
163
126
|
|
|
164
|
-
const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
|
|
127
|
+
const loaded = await resolvePlatformProviderContext({ cwd: shared.repoRoot });
|
|
165
128
|
const platformContext = loaded.ok ? loaded.value.context : loaded.context;
|
|
166
129
|
if (platformContext.status === "failed") {
|
|
167
|
-
return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
|
|
130
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
|
|
168
131
|
}
|
|
169
132
|
if (platformContext.status === "blocked") {
|
|
170
|
-
return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
|
|
133
|
+
return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
|
|
171
134
|
}
|
|
172
135
|
if (!platformContext.platform.repository) {
|
|
173
136
|
if (platformContext.error?.code === "REMOTE_MISSING" || platformContext.error?.code === "REMOTE_INVALID") {
|
|
174
|
-
return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
|
|
137
|
+
return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
|
|
175
138
|
}
|
|
176
|
-
return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
|
|
139
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
|
|
177
140
|
}
|
|
178
141
|
const expectedValues = resolveExpectedValues(config);
|
|
179
142
|
if (!expectedValues.ok) {
|
|
180
|
-
return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
143
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
181
144
|
}
|
|
182
145
|
|
|
183
146
|
const marker = expectedValues.commentMarker
|
|
@@ -263,7 +226,7 @@ function resolveDefaultValue({ collection, key, value, configKey }: any): any {
|
|
|
263
226
|
return { ok: true, value: resolvedValue };
|
|
264
227
|
}
|
|
265
228
|
|
|
266
|
-
async function fetchRemoteData(context: any): Promise<any> {
|
|
229
|
+
async function fetchRemoteData(context: any, shared: VerificationShared): Promise<any> {
|
|
267
230
|
const provider = context.provider;
|
|
268
231
|
const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
|
|
269
232
|
const facts = provider?.verification?.fetchRemoteFacts
|
|
@@ -279,8 +242,8 @@ async function fetchRemoteData(context: any): Promise<any> {
|
|
|
279
242
|
if (!facts.ok) {
|
|
280
243
|
return {
|
|
281
244
|
earlyReturn: facts.error.retryable
|
|
282
|
-
? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
|
|
283
|
-
: failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
|
|
245
|
+
? shared.blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
|
|
246
|
+
: shared.failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
|
|
284
247
|
};
|
|
285
248
|
}
|
|
286
249
|
const issueSnapshot = facts.value.issue;
|
|
@@ -305,8 +268,8 @@ async function fetchRemoteData(context: any): Promise<any> {
|
|
|
305
268
|
const listed = await provider.comments.list({ context: operationContext, parent: context.prIdentity });
|
|
306
269
|
if (!listed.ok) return {
|
|
307
270
|
earlyReturn: listed.error.retryable
|
|
308
|
-
? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
|
|
309
|
-
: failResult(CHECK_TYPE, listed.error.message, "check_failed")
|
|
271
|
+
? shared.blockedResult(CHECK_TYPE, listed.error.message, "network_error")
|
|
272
|
+
: shared.failResult(CHECK_TYPE, listed.error.message, "check_failed")
|
|
310
273
|
};
|
|
311
274
|
prComments = listed.value.map((comment: { id: string; body: string }) => ({ id: comment.id, body: comment.body }));
|
|
312
275
|
}
|
|
@@ -314,8 +277,8 @@ async function fetchRemoteData(context: any): Promise<any> {
|
|
|
314
277
|
let inLabelMapping: Record<string, string[]> = {};
|
|
315
278
|
let repositoryLabels: string[] = [];
|
|
316
279
|
if (context.config.verify_in_labels_computed && context.hasTriage) {
|
|
317
|
-
const mapping = loadInLabelMapping();
|
|
318
|
-
if (!mapping.ok) return { earlyReturn: failResult(CHECK_TYPE, mapping.message, "check_failed") };
|
|
280
|
+
const mapping = loadInLabelMapping(shared);
|
|
281
|
+
if (!mapping.ok) return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
|
|
319
282
|
inLabelMapping = mapping.value;
|
|
320
283
|
if (Object.keys(inLabelMapping).length > 0) {
|
|
321
284
|
const labels = provider?.issues?.listLabels
|
|
@@ -325,8 +288,8 @@ async function fetchRemoteData(context: any): Promise<any> {
|
|
|
325
288
|
const error = providerError(labels.error, "PLATFORM_PROVIDER_OPERATION_FAILED");
|
|
326
289
|
return {
|
|
327
290
|
earlyReturn: error.retryable
|
|
328
|
-
? blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
|
|
329
|
-
: failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
|
|
291
|
+
? shared.blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
|
|
292
|
+
: shared.failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
|
|
330
293
|
};
|
|
331
294
|
}
|
|
332
295
|
repositoryLabels = labels.value;
|
|
@@ -367,7 +330,7 @@ function shouldFetchComments(config: any): any {
|
|
|
367
330
|
);
|
|
368
331
|
}
|
|
369
332
|
|
|
370
|
-
function checkStatusLabel(context: any, remoteData: any): any {
|
|
333
|
+
function checkStatusLabel(context: any, remoteData: any, shared: VerificationShared): any {
|
|
371
334
|
if (!context.expectedStatusLabel || !context.hasTriage) {
|
|
372
335
|
return null;
|
|
373
336
|
}
|
|
@@ -381,13 +344,13 @@ function checkStatusLabel(context: any, remoteData: any): any {
|
|
|
381
344
|
return null;
|
|
382
345
|
}
|
|
383
346
|
|
|
384
|
-
return failResult(CHECK_TYPE,
|
|
347
|
+
return shared.failResult(CHECK_TYPE,
|
|
385
348
|
`Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`,
|
|
386
349
|
"check_failed"
|
|
387
350
|
);
|
|
388
351
|
}
|
|
389
352
|
|
|
390
|
-
function checkClosedIssueStatusLabels(context: any, remoteData: any): any {
|
|
353
|
+
function checkClosedIssueStatusLabels(context: any, remoteData: any, shared: VerificationShared): any {
|
|
391
354
|
if (!context.config.verify_closed_issue_has_no_status_labels) {
|
|
392
355
|
return null;
|
|
393
356
|
}
|
|
@@ -402,13 +365,13 @@ function checkClosedIssueStatusLabels(context: any, remoteData: any): any {
|
|
|
402
365
|
return null;
|
|
403
366
|
}
|
|
404
367
|
|
|
405
|
-
return failResult(CHECK_TYPE,
|
|
368
|
+
return shared.failResult(CHECK_TYPE,
|
|
406
369
|
`Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`,
|
|
407
370
|
"check_failed"
|
|
408
371
|
);
|
|
409
372
|
}
|
|
410
373
|
|
|
411
|
-
function checkCommentMarker(context: any, remoteData: any): any {
|
|
374
|
+
function checkCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
|
|
412
375
|
if (!context.marker) {
|
|
413
376
|
return null;
|
|
414
377
|
}
|
|
@@ -418,13 +381,13 @@ function checkCommentMarker(context: any, remoteData: any): any {
|
|
|
418
381
|
return null;
|
|
419
382
|
}
|
|
420
383
|
|
|
421
|
-
return failResult(CHECK_TYPE,
|
|
384
|
+
return shared.failResult(CHECK_TYPE,
|
|
422
385
|
`Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`,
|
|
423
386
|
"check_failed"
|
|
424
387
|
);
|
|
425
388
|
}
|
|
426
389
|
|
|
427
|
-
function checkPrCommentMarker(context: any, remoteData: any): any {
|
|
390
|
+
function checkPrCommentMarker(context: any, remoteData: any, shared: VerificationShared): any {
|
|
428
391
|
if (!context.prMarker) {
|
|
429
392
|
return null;
|
|
430
393
|
}
|
|
@@ -434,19 +397,19 @@ function checkPrCommentMarker(context: any, remoteData: any): any {
|
|
|
434
397
|
return null;
|
|
435
398
|
}
|
|
436
399
|
|
|
437
|
-
return failResult(CHECK_TYPE,
|
|
400
|
+
return shared.failResult(CHECK_TYPE,
|
|
438
401
|
`Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
|
|
439
402
|
"check_failed"
|
|
440
403
|
);
|
|
441
404
|
}
|
|
442
405
|
|
|
443
|
-
function checkPrCommentLastCommit(context: any, remoteData: any): any {
|
|
406
|
+
function checkPrCommentLastCommit(context: any, remoteData: any, shared: VerificationShared): any {
|
|
444
407
|
if (!context.config.verify_pr_comment_last_commit_matches_head) {
|
|
445
408
|
return null;
|
|
446
409
|
}
|
|
447
410
|
|
|
448
411
|
if (!context.prMarker) {
|
|
449
|
-
return failResult(CHECK_TYPE,
|
|
412
|
+
return shared.failResult(CHECK_TYPE,
|
|
450
413
|
"verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker",
|
|
451
414
|
"check_failed"
|
|
452
415
|
);
|
|
@@ -454,7 +417,7 @@ function checkPrCommentLastCommit(context: any, remoteData: any): any {
|
|
|
454
417
|
|
|
455
418
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
456
419
|
if (!comment) {
|
|
457
|
-
return failResult(CHECK_TYPE,
|
|
420
|
+
return shared.failResult(CHECK_TYPE,
|
|
458
421
|
`Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
|
|
459
422
|
"check_failed"
|
|
460
423
|
);
|
|
@@ -462,33 +425,33 @@ function checkPrCommentLastCommit(context: any, remoteData: any): any {
|
|
|
462
425
|
|
|
463
426
|
const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
|
|
464
427
|
if (!match) {
|
|
465
|
-
return failResult(CHECK_TYPE,
|
|
428
|
+
return shared.failResult(CHECK_TYPE,
|
|
466
429
|
`PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`,
|
|
467
430
|
"check_failed"
|
|
468
431
|
);
|
|
469
432
|
}
|
|
470
433
|
|
|
471
434
|
const expectedHead = String(remoteData.prHeadSha || "").trim();
|
|
472
|
-
if (!expectedHead) return blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
|
|
435
|
+
if (!expectedHead) return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
|
|
473
436
|
const actualHead = match[1]!.trim();
|
|
474
437
|
if (expectedHead === actualHead) {
|
|
475
438
|
return null;
|
|
476
439
|
}
|
|
477
440
|
|
|
478
|
-
return failResult(CHECK_TYPE,
|
|
441
|
+
return shared.failResult(CHECK_TYPE,
|
|
479
442
|
`PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`,
|
|
480
443
|
"check_failed"
|
|
481
444
|
);
|
|
482
445
|
}
|
|
483
446
|
|
|
484
|
-
function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
|
|
447
|
+
function checkPrCommentRequiredPatterns(context: any, remoteData: any, shared: VerificationShared): any {
|
|
485
448
|
const patterns = context.config.expected_pr_comment_required_patterns || [];
|
|
486
449
|
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
487
450
|
return null;
|
|
488
451
|
}
|
|
489
452
|
|
|
490
453
|
if (!context.prMarker) {
|
|
491
|
-
return failResult(CHECK_TYPE,
|
|
454
|
+
return shared.failResult(CHECK_TYPE,
|
|
492
455
|
"expected_pr_comment_required_patterns requires expected_pr_comment_marker",
|
|
493
456
|
"check_failed"
|
|
494
457
|
);
|
|
@@ -496,7 +459,7 @@ function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
|
|
|
496
459
|
|
|
497
460
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
498
461
|
if (!comment) {
|
|
499
|
-
return failResult(CHECK_TYPE,
|
|
462
|
+
return shared.failResult(CHECK_TYPE,
|
|
500
463
|
`Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
|
|
501
464
|
"check_failed"
|
|
502
465
|
);
|
|
@@ -506,7 +469,7 @@ function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
|
|
|
506
469
|
for (const pattern of patterns) {
|
|
507
470
|
const regex = new RegExp(pattern, "m");
|
|
508
471
|
if (!regex.test(body)) {
|
|
509
|
-
return failResult(CHECK_TYPE,
|
|
472
|
+
return shared.failResult(CHECK_TYPE,
|
|
510
473
|
`PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`,
|
|
511
474
|
"check_failed"
|
|
512
475
|
);
|
|
@@ -516,31 +479,54 @@ function checkPrCommentRequiredPatterns(context: any, remoteData: any): any {
|
|
|
516
479
|
return null;
|
|
517
480
|
}
|
|
518
481
|
|
|
519
|
-
function checkCommentContent(context: any, remoteData: any): any {
|
|
482
|
+
function checkCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
|
|
520
483
|
if (!context.config.verify_comment_content) {
|
|
521
484
|
return null;
|
|
522
485
|
}
|
|
523
486
|
|
|
524
487
|
if (!context.marker) {
|
|
525
|
-
return failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
|
|
488
|
+
return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
|
|
526
489
|
}
|
|
527
490
|
|
|
528
|
-
if (!context.artifactPath || !safeStat(context.artifactPath)) {
|
|
529
|
-
return failResult(CHECK_TYPE,
|
|
491
|
+
if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
|
|
492
|
+
return shared.failResult(CHECK_TYPE,
|
|
530
493
|
`Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`,
|
|
531
494
|
"check_failed"
|
|
532
495
|
);
|
|
533
496
|
}
|
|
534
497
|
|
|
535
498
|
const comment = findCommentByMarker(remoteData.comments, context.marker);
|
|
536
|
-
|
|
537
|
-
|
|
499
|
+
let localContent: string;
|
|
500
|
+
let commentContent: string;
|
|
501
|
+
try {
|
|
502
|
+
const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
|
|
503
|
+
const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
|
|
504
|
+
if (!localCanonical.ok) {
|
|
505
|
+
return shared.failResult(CHECK_TYPE,
|
|
506
|
+
`Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`,
|
|
507
|
+
"check_failed"
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
if (!commentCanonical.ok) {
|
|
511
|
+
return shared.failResult(CHECK_TYPE,
|
|
512
|
+
`Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`,
|
|
513
|
+
"check_failed"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
localContent = shared.normalizeContent(localCanonical.value);
|
|
517
|
+
commentContent = shared.normalizeContent(commentCanonical.value);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
return shared.failResult(CHECK_TYPE,
|
|
520
|
+
`Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`,
|
|
521
|
+
"check_failed"
|
|
522
|
+
);
|
|
523
|
+
}
|
|
538
524
|
|
|
539
525
|
if (localContent === commentContent) {
|
|
540
526
|
return null;
|
|
541
527
|
}
|
|
542
528
|
|
|
543
|
-
return failResult(CHECK_TYPE,
|
|
529
|
+
return shared.failResult(CHECK_TYPE,
|
|
544
530
|
buildCommentContentMismatchMessage(
|
|
545
531
|
path.basename(context.artifactPath, path.extname(context.artifactPath)),
|
|
546
532
|
context.issueNumber,
|
|
@@ -551,7 +537,7 @@ function checkCommentContent(context: any, remoteData: any): any {
|
|
|
551
537
|
);
|
|
552
538
|
}
|
|
553
539
|
|
|
554
|
-
function checkTaskCommentContent(context: any, remoteData: any): any {
|
|
540
|
+
function checkTaskCommentContent(context: any, remoteData: any, shared: VerificationShared): any {
|
|
555
541
|
if (!context.config.verify_task_comment_content) {
|
|
556
542
|
return null;
|
|
557
543
|
}
|
|
@@ -559,26 +545,33 @@ function checkTaskCommentContent(context: any, remoteData: any): any {
|
|
|
559
545
|
const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
|
|
560
546
|
const comment = findCommentByMarker(remoteData.comments, taskMarker);
|
|
561
547
|
if (!comment) {
|
|
562
|
-
return failResult(CHECK_TYPE,
|
|
548
|
+
return shared.failResult(CHECK_TYPE,
|
|
563
549
|
`Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`,
|
|
564
550
|
"check_failed"
|
|
565
551
|
);
|
|
566
552
|
}
|
|
567
553
|
|
|
568
|
-
const
|
|
569
|
-
|
|
554
|
+
const expectedTaskBody = buildExpectedTaskBody(context.task.content, shared);
|
|
555
|
+
if (expectedTaskBody === null) {
|
|
556
|
+
return shared.failResult(CHECK_TYPE,
|
|
557
|
+
"Task content cannot be rendered safely for comment verification",
|
|
558
|
+
"check_failed"
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
const expectedBody = shared.normalizeContent(expectedTaskBody);
|
|
562
|
+
const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
|
|
570
563
|
|
|
571
564
|
if (expectedBody === commentBody) {
|
|
572
565
|
return null;
|
|
573
566
|
}
|
|
574
567
|
|
|
575
|
-
return failResult(CHECK_TYPE,
|
|
568
|
+
return shared.failResult(CHECK_TYPE,
|
|
576
569
|
buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody),
|
|
577
570
|
"check_failed"
|
|
578
571
|
);
|
|
579
572
|
}
|
|
580
573
|
|
|
581
|
-
function checkPrTypeLabel(context: any, remoteData: any): any {
|
|
574
|
+
function checkPrTypeLabel(context: any, remoteData: any, shared: VerificationShared): any {
|
|
582
575
|
if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
583
576
|
return null;
|
|
584
577
|
}
|
|
@@ -592,13 +585,13 @@ function checkPrTypeLabel(context: any, remoteData: any): any {
|
|
|
592
585
|
return null;
|
|
593
586
|
}
|
|
594
587
|
|
|
595
|
-
return failResult(CHECK_TYPE,
|
|
588
|
+
return shared.failResult(CHECK_TYPE,
|
|
596
589
|
`Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`,
|
|
597
590
|
"check_failed"
|
|
598
591
|
);
|
|
599
592
|
}
|
|
600
593
|
|
|
601
|
-
function checkInLabelsMatchPr(context: any, remoteData: any): any {
|
|
594
|
+
function checkInLabelsMatchPr(context: any, remoteData: any, shared: VerificationShared): any {
|
|
602
595
|
if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
603
596
|
return null;
|
|
604
597
|
}
|
|
@@ -614,22 +607,22 @@ function checkInLabelsMatchPr(context: any, remoteData: any): any {
|
|
|
614
607
|
return null;
|
|
615
608
|
}
|
|
616
609
|
|
|
617
|
-
return failResult(CHECK_TYPE,
|
|
610
|
+
return shared.failResult(CHECK_TYPE,
|
|
618
611
|
`in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`,
|
|
619
612
|
"check_failed"
|
|
620
613
|
);
|
|
621
614
|
}
|
|
622
615
|
|
|
623
|
-
function checkInLabelsComputed(context: any, remoteData: any): any {
|
|
616
|
+
function checkInLabelsComputed(context: any, remoteData: any, shared: VerificationShared): any {
|
|
624
617
|
if (!context.config.verify_in_labels_computed || !context.hasTriage) {
|
|
625
618
|
return null;
|
|
626
619
|
}
|
|
627
620
|
|
|
628
|
-
const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping);
|
|
621
|
+
const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
|
|
629
622
|
if (!expectedInLabels.ok) {
|
|
630
623
|
return expectedInLabels.type === "check_failed"
|
|
631
|
-
? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
|
|
632
|
-
: blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
|
|
624
|
+
? shared.failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
|
|
625
|
+
: shared.blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
|
|
633
626
|
}
|
|
634
627
|
|
|
635
628
|
if (expectedInLabels.mode === "skipped") {
|
|
@@ -644,19 +637,19 @@ function checkInLabelsComputed(context: any, remoteData: any): any {
|
|
|
644
637
|
return null;
|
|
645
638
|
}
|
|
646
639
|
|
|
647
|
-
return failResult(
|
|
640
|
+
return shared.failResult(
|
|
648
641
|
CHECK_TYPE,
|
|
649
642
|
`Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`,
|
|
650
643
|
"check_failed"
|
|
651
644
|
);
|
|
652
645
|
}
|
|
653
646
|
|
|
654
|
-
function checkSyncedRequirements(context: any, remoteData: any): any {
|
|
647
|
+
function checkSyncedRequirements(context: any, remoteData: any, shared: VerificationShared): any {
|
|
655
648
|
if (!context.config.sync_checked_requirements || !context.hasTriage) {
|
|
656
649
|
return null;
|
|
657
650
|
}
|
|
658
651
|
|
|
659
|
-
const checkedRequirements = getCheckedRequirements(context.task.content);
|
|
652
|
+
const checkedRequirements = shared.getCheckedRequirements(context.task.content);
|
|
660
653
|
if (checkedRequirements.length === 0) {
|
|
661
654
|
return null;
|
|
662
655
|
}
|
|
@@ -664,13 +657,13 @@ function checkSyncedRequirements(context: any, remoteData: any): any {
|
|
|
664
657
|
const issueBody = remoteData.issue.body || "";
|
|
665
658
|
const resolution = resolveRequirementSection(
|
|
666
659
|
issueBody,
|
|
667
|
-
requirementSectionAnchors(repoRoot, context.task.metadata.type || "task")
|
|
660
|
+
requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task")
|
|
668
661
|
);
|
|
669
662
|
if (resolution.status === "missing") {
|
|
670
663
|
return null;
|
|
671
664
|
}
|
|
672
665
|
if (resolution.status === "ambiguous") {
|
|
673
|
-
return failResult(CHECK_TYPE,
|
|
666
|
+
return shared.failResult(CHECK_TYPE,
|
|
674
667
|
`Issue #${context.issueNumber} requirements section is ambiguous`,
|
|
675
668
|
"check_failed"
|
|
676
669
|
);
|
|
@@ -683,13 +676,13 @@ function checkSyncedRequirements(context: any, remoteData: any): any {
|
|
|
683
676
|
return null;
|
|
684
677
|
}
|
|
685
678
|
|
|
686
|
-
return failResult(CHECK_TYPE,
|
|
679
|
+
return shared.failResult(CHECK_TYPE,
|
|
687
680
|
`Issue body is missing checked requirements: ${missingRequirements.join(", ")}`,
|
|
688
681
|
"check_failed"
|
|
689
682
|
);
|
|
690
683
|
}
|
|
691
684
|
|
|
692
|
-
function checkIssueType(context: any, remoteData: any): any {
|
|
685
|
+
function checkIssueType(context: any, remoteData: any, shared: VerificationShared): any {
|
|
693
686
|
if (!context.config.verify_issue_type || !context.hasPush) {
|
|
694
687
|
return null;
|
|
695
688
|
}
|
|
@@ -703,7 +696,7 @@ function checkIssueType(context: any, remoteData: any): any {
|
|
|
703
696
|
return null;
|
|
704
697
|
}
|
|
705
698
|
|
|
706
|
-
return failResult(CHECK_TYPE,
|
|
699
|
+
return shared.failResult(CHECK_TYPE,
|
|
707
700
|
`Issue #${context.issueNumber} has no Issue Type set`,
|
|
708
701
|
"check_failed"
|
|
709
702
|
);
|
|
@@ -711,7 +704,7 @@ function checkIssueType(context: any, remoteData: any): any {
|
|
|
711
704
|
|
|
712
705
|
const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
|
|
713
706
|
if (expectedType && remoteData.issueType !== expectedType) {
|
|
714
|
-
return failResult(CHECK_TYPE,
|
|
707
|
+
return shared.failResult(CHECK_TYPE,
|
|
715
708
|
`Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`,
|
|
716
709
|
"check_failed"
|
|
717
710
|
);
|
|
@@ -720,7 +713,7 @@ function checkIssueType(context: any, remoteData: any): any {
|
|
|
720
713
|
return null;
|
|
721
714
|
}
|
|
722
715
|
|
|
723
|
-
function checkIssueFields(context: any, remoteData: any): any {
|
|
716
|
+
function checkIssueFields(context: any, remoteData: any, shared: VerificationShared): any {
|
|
724
717
|
if (!context.config.verify_issue_fields || !context.hasPush) {
|
|
725
718
|
return null;
|
|
726
719
|
}
|
|
@@ -731,7 +724,7 @@ function checkIssueFields(context: any, remoteData: any): any {
|
|
|
731
724
|
|
|
732
725
|
for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
|
|
733
726
|
const expectedRaw = context.task.metadata[metadataKey];
|
|
734
|
-
if (isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
|
|
727
|
+
if (shared.isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
|
|
735
728
|
continue;
|
|
736
729
|
}
|
|
737
730
|
|
|
@@ -742,14 +735,14 @@ function checkIssueFields(context: any, remoteData: any): any {
|
|
|
742
735
|
}
|
|
743
736
|
|
|
744
737
|
if (!actual) {
|
|
745
|
-
return failResult(CHECK_TYPE,
|
|
738
|
+
return shared.failResult(CHECK_TYPE,
|
|
746
739
|
`Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`,
|
|
747
740
|
"check_failed"
|
|
748
741
|
);
|
|
749
742
|
}
|
|
750
743
|
|
|
751
744
|
if (actual.kind !== expected.kind || actual.value !== expected.value) {
|
|
752
|
-
return failResult(CHECK_TYPE,
|
|
745
|
+
return shared.failResult(CHECK_TYPE,
|
|
753
746
|
`Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`,
|
|
754
747
|
"check_failed"
|
|
755
748
|
);
|
|
@@ -759,13 +752,13 @@ function checkIssueFields(context: any, remoteData: any): any {
|
|
|
759
752
|
return null;
|
|
760
753
|
}
|
|
761
754
|
|
|
762
|
-
function checkPrAssignee(context: any, remoteData: any): any {
|
|
755
|
+
function checkPrAssignee(context: any, remoteData: any, shared: VerificationShared): any {
|
|
763
756
|
if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
|
|
764
757
|
return null;
|
|
765
758
|
}
|
|
766
759
|
|
|
767
760
|
if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
|
|
768
|
-
return failResult(CHECK_TYPE,
|
|
761
|
+
return shared.failResult(CHECK_TYPE,
|
|
769
762
|
`PR #${context.prNumber} has no assignee`,
|
|
770
763
|
"check_failed"
|
|
771
764
|
);
|
|
@@ -774,20 +767,20 @@ function checkPrAssignee(context: any, remoteData: any): any {
|
|
|
774
767
|
return null;
|
|
775
768
|
}
|
|
776
769
|
|
|
777
|
-
function checkMilestone(context: any, remoteData: any): any {
|
|
770
|
+
function checkMilestone(context: any, remoteData: any, shared: VerificationShared): any {
|
|
778
771
|
if (!context.config.verify_milestone || !context.hasTriage) {
|
|
779
772
|
return null;
|
|
780
773
|
}
|
|
781
774
|
|
|
782
775
|
if (!remoteData.issue?.milestone?.title) {
|
|
783
|
-
return failResult(CHECK_TYPE,
|
|
776
|
+
return shared.failResult(CHECK_TYPE,
|
|
784
777
|
`Issue #${context.issueNumber} has no milestone set`,
|
|
785
778
|
"check_failed"
|
|
786
779
|
);
|
|
787
780
|
}
|
|
788
781
|
|
|
789
782
|
if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
|
|
790
|
-
return failResult(CHECK_TYPE,
|
|
783
|
+
return shared.failResult(CHECK_TYPE,
|
|
791
784
|
`PR #${context.prNumber} has no milestone set`,
|
|
792
785
|
"check_failed"
|
|
793
786
|
);
|
|
@@ -796,13 +789,13 @@ function checkMilestone(context: any, remoteData: any): any {
|
|
|
796
789
|
if (context.config.verify_milestone_specific) {
|
|
797
790
|
const issueTitle = remoteData.issue.milestone.title;
|
|
798
791
|
if (VERSION_LINE_REGEX.test(issueTitle)) {
|
|
799
|
-
return failResult(CHECK_TYPE,
|
|
792
|
+
return shared.failResult(CHECK_TYPE,
|
|
800
793
|
`Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`,
|
|
801
794
|
"check_failed"
|
|
802
795
|
);
|
|
803
796
|
}
|
|
804
797
|
if (context.prNumber && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
|
|
805
|
-
return failResult(CHECK_TYPE,
|
|
798
|
+
return shared.failResult(CHECK_TYPE,
|
|
806
799
|
`PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`,
|
|
807
800
|
"check_failed"
|
|
808
801
|
);
|
|
@@ -863,19 +856,20 @@ function extractCommentBody(commentBody: any): any {
|
|
|
863
856
|
return lines.slice(start, end).join("\n");
|
|
864
857
|
}
|
|
865
858
|
|
|
866
|
-
function buildExpectedTaskBody(taskContent: any): any {
|
|
859
|
+
function buildExpectedTaskBody(taskContent: any, shared: VerificationShared): any {
|
|
867
860
|
const frontmatterMatch = taskContent.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
868
861
|
if (!frontmatterMatch) {
|
|
869
|
-
return taskContent.trim();
|
|
862
|
+
return sanitizeCommentContent(taskContent.trim());
|
|
870
863
|
}
|
|
871
864
|
|
|
872
|
-
const body = taskContent.slice(frontmatterMatch[0].length).trim();
|
|
865
|
+
const body = sanitizeCommentContent(taskContent.slice(frontmatterMatch[0].length).trim());
|
|
866
|
+
if (body === null) {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
873
869
|
return [
|
|
874
|
-
buildTaskFrontmatterSummary(),
|
|
870
|
+
buildTaskFrontmatterSummary(shared),
|
|
875
871
|
"",
|
|
876
|
-
"
|
|
877
|
-
frontmatterMatch[0].trim(),
|
|
878
|
-
"```",
|
|
872
|
+
renderSafeCodeFence(frontmatterMatch[0].trim(), "yaml"),
|
|
879
873
|
"",
|
|
880
874
|
"</details>",
|
|
881
875
|
"",
|
|
@@ -883,8 +877,8 @@ function buildExpectedTaskBody(taskContent: any): any {
|
|
|
883
877
|
].join("\n").trim();
|
|
884
878
|
}
|
|
885
879
|
|
|
886
|
-
function buildTaskFrontmatterSummary(): any {
|
|
887
|
-
const language = loadProjectLanguage();
|
|
880
|
+
function buildTaskFrontmatterSummary(shared: VerificationShared): any {
|
|
881
|
+
const language = loadProjectLanguage(shared);
|
|
888
882
|
if (language === "en" || language === "en-US") {
|
|
889
883
|
return "<details><summary>Metadata (frontmatter)</summary>";
|
|
890
884
|
}
|
|
@@ -892,13 +886,13 @@ function buildTaskFrontmatterSummary(): any {
|
|
|
892
886
|
return "<details><summary>元数据 (frontmatter)</summary>";
|
|
893
887
|
}
|
|
894
888
|
|
|
895
|
-
function loadProjectLanguage(): any {
|
|
889
|
+
function loadProjectLanguage(shared: VerificationShared): any {
|
|
896
890
|
const override = process.env.VALIDATE_ARTIFACT_LANGUAGE;
|
|
897
|
-
if (!isBlank(override)) {
|
|
891
|
+
if (!shared.isBlank(override)) {
|
|
898
892
|
return String(override).trim();
|
|
899
893
|
}
|
|
900
894
|
|
|
901
|
-
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
895
|
+
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
902
896
|
if (!fs.existsSync(configPath)) {
|
|
903
897
|
return "";
|
|
904
898
|
}
|
|
@@ -1035,8 +1029,8 @@ function formatLabelList(labels: any): any {
|
|
|
1035
1029
|
return labels.length > 0 ? labels.join(", ") : "none";
|
|
1036
1030
|
}
|
|
1037
1031
|
|
|
1038
|
-
function computeExpectedInLabels(taskDir: any, repositoryLabels: string[]
|
|
1039
|
-
const task = loadTask(taskDir);
|
|
1032
|
+
function computeExpectedInLabels(taskDir: any, repositoryLabels: string[], mappingOverride: Record<string, string[]> | undefined, shared: VerificationShared): any {
|
|
1033
|
+
const task = shared.loadTask(taskDir);
|
|
1040
1034
|
if (!task.ok) {
|
|
1041
1035
|
return task;
|
|
1042
1036
|
}
|
|
@@ -1071,8 +1065,8 @@ function computeExpectedInLabels(taskDir: any, repositoryLabels: string[] = [],
|
|
|
1071
1065
|
return { ok: true, labels: planned.target, mode: "mapped" };
|
|
1072
1066
|
}
|
|
1073
1067
|
|
|
1074
|
-
function loadInLabelMapping(): any {
|
|
1075
|
-
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
1068
|
+
function loadInLabelMapping(shared: VerificationShared): any {
|
|
1069
|
+
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
1076
1070
|
if (!fs.existsSync(configPath)) {
|
|
1077
1071
|
return { ok: true, value: {} };
|
|
1078
1072
|
}
|
|
@@ -1105,13 +1099,13 @@ function gitText(args: any, cwd: any): any {
|
|
|
1105
1099
|
}
|
|
1106
1100
|
|
|
1107
1101
|
function resolvePrHeadSha(context: any): any {
|
|
1108
|
-
const fallback = () =>
|
|
1102
|
+
const fallback = () => gitText(["rev-parse", "HEAD"], context.taskDir);
|
|
1109
1103
|
const branch = String(context.task?.metadata?.branch || "").trim();
|
|
1110
1104
|
if (!branch) {
|
|
1111
1105
|
return fallback();
|
|
1112
1106
|
}
|
|
1113
1107
|
|
|
1114
|
-
const worktreeList =
|
|
1108
|
+
const worktreeList = gitText(["worktree", "list", "--porcelain"], context.taskDir);
|
|
1115
1109
|
if (!worktreeList.ok) {
|
|
1116
1110
|
return fallback();
|
|
1117
1111
|
}
|
|
@@ -1121,7 +1115,7 @@ function resolvePrHeadSha(context: any): any {
|
|
|
1121
1115
|
return fallback();
|
|
1122
1116
|
}
|
|
1123
1117
|
|
|
1124
|
-
const headInWorktree =
|
|
1118
|
+
const headInWorktree = gitText(["rev-parse", "HEAD"], matchedWorktree);
|
|
1125
1119
|
if (!headInWorktree.ok) {
|
|
1126
1120
|
return fallback();
|
|
1127
1121
|
}
|
|
@@ -1149,10 +1143,6 @@ function findWorktreeForBranch(porcelainOutput: any, branch: any): any {
|
|
|
1149
1143
|
return null;
|
|
1150
1144
|
}
|
|
1151
1145
|
|
|
1152
|
-
function withRetry(operation: any): any {
|
|
1153
|
-
return operation();
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
1146
|
function interpolate(template: any, taskDir: any, artifactFile: any): any {
|
|
1157
1147
|
const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
|
|
1158
1148
|
return template
|