@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
|
@@ -10,6 +10,7 @@ import { planInLabelUpdate, validateInLabelMapping } from "./in-label-sync.js";
|
|
|
10
10
|
import { readPrDeliveryFact } from "../task/pr-delivery-fact.js";
|
|
11
11
|
import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from "./provider-bridge.js";
|
|
12
12
|
import { taskIssueIdentity } from "./task-identities.js";
|
|
13
|
+
import { CONTROL_MARKER_PATTERN, canonicalizeCommentBody, renderSafeCodeFence, sanitizeMarkdownDocument } from "./comment-safety.js";
|
|
13
14
|
const CHECK_TYPE = "platform-sync";
|
|
14
15
|
const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
|
|
15
16
|
const FRONTMATTER_FIELD_MAP = {
|
|
@@ -24,8 +25,6 @@ const OPTION_LOCALIZATION = {
|
|
|
24
25
|
"中": "Medium",
|
|
25
26
|
"低": "Low"
|
|
26
27
|
};
|
|
27
|
-
let activeShared = null;
|
|
28
|
-
let repoRoot = "";
|
|
29
28
|
export function getDefaults() {
|
|
30
29
|
return {
|
|
31
30
|
statusLabels: {
|
|
@@ -45,47 +44,16 @@ export function getDefaults() {
|
|
|
45
44
|
}
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
return activeShared;
|
|
53
|
-
}
|
|
54
|
-
function loadTask(...args) {
|
|
55
|
-
return getShared().loadTask(...args);
|
|
56
|
-
}
|
|
57
|
-
function getCheckedRequirements(...args) {
|
|
58
|
-
return getShared().getCheckedRequirements(...args);
|
|
59
|
-
}
|
|
60
|
-
function normalizeContent(...args) {
|
|
61
|
-
return getShared().normalizeContent(...args);
|
|
62
|
-
}
|
|
63
|
-
function isBlank(...args) {
|
|
64
|
-
return getShared().isBlank(...args);
|
|
65
|
-
}
|
|
66
|
-
function escapeRegExp(...args) {
|
|
67
|
-
return getShared().escapeRegExp(...args);
|
|
68
|
-
}
|
|
69
|
-
function passResult(...args) {
|
|
70
|
-
return getShared().passResult(...args);
|
|
71
|
-
}
|
|
72
|
-
function failResult(...args) {
|
|
73
|
-
return getShared().failResult(...args);
|
|
74
|
-
}
|
|
75
|
-
function blockedResult(...args) {
|
|
76
|
-
return getShared().blockedResult(...args);
|
|
77
|
-
}
|
|
78
|
-
function safeStat(...args) {
|
|
79
|
-
return getShared().safeStat(...args);
|
|
47
|
+
function sanitizeCommentContent(content) {
|
|
48
|
+
const result = sanitizeMarkdownDocument(content, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
49
|
+
return result.ok ? result.value : null;
|
|
80
50
|
}
|
|
81
51
|
export async function check({ taskDir, config, artifactFile }, shared) {
|
|
82
|
-
|
|
83
|
-
repoRoot = shared.repoRoot;
|
|
84
|
-
const context = await buildSyncContext({ taskDir, config, artifactFile });
|
|
52
|
+
const context = await buildSyncContext({ taskDir, config, artifactFile }, shared);
|
|
85
53
|
if (context.earlyReturn) {
|
|
86
54
|
return context.earlyReturn;
|
|
87
55
|
}
|
|
88
|
-
const remoteData = await fetchRemoteData(context);
|
|
56
|
+
const remoteData = await fetchRemoteData(context, shared);
|
|
89
57
|
if (remoteData.earlyReturn) {
|
|
90
58
|
return remoteData.earlyReturn;
|
|
91
59
|
}
|
|
@@ -108,52 +76,52 @@ export async function check({ taskDir, config, artifactFile }, shared) {
|
|
|
108
76
|
checkMilestone
|
|
109
77
|
];
|
|
110
78
|
for (const subCheck of subChecks) {
|
|
111
|
-
const result = subCheck(context, remoteData);
|
|
79
|
+
const result = subCheck(context, remoteData, shared);
|
|
112
80
|
if (result) {
|
|
113
81
|
return result;
|
|
114
82
|
}
|
|
115
83
|
}
|
|
116
|
-
return passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
|
|
84
|
+
return shared.passResult(CHECK_TYPE, `Platform sync checks passed for Issue ${context.issueNumber || "identity"}`);
|
|
117
85
|
}
|
|
118
|
-
async function buildSyncContext({ taskDir, config, artifactFile }) {
|
|
119
|
-
const task = loadTask(taskDir);
|
|
86
|
+
async function buildSyncContext({ taskDir, config, artifactFile }, shared) {
|
|
87
|
+
const task = shared.loadTask(taskDir);
|
|
120
88
|
if (!task.ok) {
|
|
121
|
-
return { earlyReturn: failResult(CHECK_TYPE, task.message) };
|
|
89
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, task.message) };
|
|
122
90
|
}
|
|
123
91
|
const issueIdentity = taskIssueIdentity(task.metadata);
|
|
124
92
|
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
125
93
|
const fact = readPrDeliveryFact(task.metadata);
|
|
126
94
|
if (fact.status === "invalid") {
|
|
127
|
-
return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
|
|
95
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, fact.error.message, "check_failed") };
|
|
128
96
|
}
|
|
129
97
|
const prIdentity = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.resource : null;
|
|
130
98
|
const prNumber = resourceIdentityNumber(prIdentity);
|
|
131
99
|
if (config.when === "issue_number_exists" && !issueIdentity) {
|
|
132
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
|
|
100
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
|
|
133
101
|
}
|
|
134
102
|
if (config.when === "pr_fact_bound" && !prIdentity) {
|
|
135
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
|
|
103
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
|
|
136
104
|
}
|
|
137
105
|
if (!issueIdentity) {
|
|
138
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
|
|
106
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, "Skipped: platform-sync not required for this task") };
|
|
139
107
|
}
|
|
140
|
-
const loaded = await resolvePlatformProviderContext({ cwd: repoRoot });
|
|
108
|
+
const loaded = await resolvePlatformProviderContext({ cwd: shared.repoRoot });
|
|
141
109
|
const platformContext = loaded.ok ? loaded.value.context : loaded.context;
|
|
142
110
|
if (platformContext.status === "failed") {
|
|
143
|
-
return { earlyReturn: failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
|
|
111
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, platformContext.error?.message || "Platform context failed", "check_failed") };
|
|
144
112
|
}
|
|
145
113
|
if (platformContext.status === "blocked") {
|
|
146
|
-
return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
|
|
114
|
+
return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error?.message || "Platform context blocked", "network_error") };
|
|
147
115
|
}
|
|
148
116
|
if (!platformContext.platform.repository) {
|
|
149
117
|
if (platformContext.error?.code === "REMOTE_MISSING" || platformContext.error?.code === "REMOTE_INVALID") {
|
|
150
|
-
return { earlyReturn: blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
|
|
118
|
+
return { earlyReturn: shared.blockedResult(CHECK_TYPE, platformContext.error.message, "network_error") };
|
|
151
119
|
}
|
|
152
|
-
return { earlyReturn: passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
|
|
120
|
+
return { earlyReturn: shared.passResult(CHECK_TYPE, `Skipped: ${platformContext.error?.message || "platform unavailable"}`) };
|
|
153
121
|
}
|
|
154
122
|
const expectedValues = resolveExpectedValues(config);
|
|
155
123
|
if (!expectedValues.ok) {
|
|
156
|
-
return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
124
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
157
125
|
}
|
|
158
126
|
const marker = expectedValues.commentMarker
|
|
159
127
|
? interpolate(expectedValues.commentMarker, taskDir, artifactFile)
|
|
@@ -229,7 +197,7 @@ function resolveDefaultValue({ collection, key, value, configKey }) {
|
|
|
229
197
|
}
|
|
230
198
|
return { ok: true, value: resolvedValue };
|
|
231
199
|
}
|
|
232
|
-
async function fetchRemoteData(context) {
|
|
200
|
+
async function fetchRemoteData(context, shared) {
|
|
233
201
|
const provider = context.provider;
|
|
234
202
|
const operationContext = providerOperationContext(context.loadedContext, context.taskDir);
|
|
235
203
|
const facts = provider?.verification?.fetchRemoteFacts
|
|
@@ -245,8 +213,8 @@ async function fetchRemoteData(context) {
|
|
|
245
213
|
if (!facts.ok) {
|
|
246
214
|
return {
|
|
247
215
|
earlyReturn: facts.error.retryable
|
|
248
|
-
? blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
|
|
249
|
-
: failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
|
|
216
|
+
? shared.blockedResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "network_error")
|
|
217
|
+
: shared.failResult(CHECK_TYPE, providerError(facts.error, "PLATFORM_PROVIDER_OPERATION_FAILED").message, "check_failed")
|
|
250
218
|
};
|
|
251
219
|
}
|
|
252
220
|
const issueSnapshot = facts.value.issue;
|
|
@@ -272,8 +240,8 @@ async function fetchRemoteData(context) {
|
|
|
272
240
|
if (!listed.ok)
|
|
273
241
|
return {
|
|
274
242
|
earlyReturn: listed.error.retryable
|
|
275
|
-
? blockedResult(CHECK_TYPE, listed.error.message, "network_error")
|
|
276
|
-
: failResult(CHECK_TYPE, listed.error.message, "check_failed")
|
|
243
|
+
? shared.blockedResult(CHECK_TYPE, listed.error.message, "network_error")
|
|
244
|
+
: shared.failResult(CHECK_TYPE, listed.error.message, "check_failed")
|
|
277
245
|
};
|
|
278
246
|
prComments = listed.value.map((comment) => ({ id: comment.id, body: comment.body }));
|
|
279
247
|
}
|
|
@@ -281,9 +249,9 @@ async function fetchRemoteData(context) {
|
|
|
281
249
|
let inLabelMapping = {};
|
|
282
250
|
let repositoryLabels = [];
|
|
283
251
|
if (context.config.verify_in_labels_computed && context.hasTriage) {
|
|
284
|
-
const mapping = loadInLabelMapping();
|
|
252
|
+
const mapping = loadInLabelMapping(shared);
|
|
285
253
|
if (!mapping.ok)
|
|
286
|
-
return { earlyReturn: failResult(CHECK_TYPE, mapping.message, "check_failed") };
|
|
254
|
+
return { earlyReturn: shared.failResult(CHECK_TYPE, mapping.message, "check_failed") };
|
|
287
255
|
inLabelMapping = mapping.value;
|
|
288
256
|
if (Object.keys(inLabelMapping).length > 0) {
|
|
289
257
|
const labels = provider?.issues?.listLabels
|
|
@@ -293,8 +261,8 @@ async function fetchRemoteData(context) {
|
|
|
293
261
|
const error = providerError(labels.error, "PLATFORM_PROVIDER_OPERATION_FAILED");
|
|
294
262
|
return {
|
|
295
263
|
earlyReturn: error.retryable
|
|
296
|
-
? blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
|
|
297
|
-
: failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
|
|
264
|
+
? shared.blockedResult(CHECK_TYPE, `${error.code}: ${error.message}`, "network_error")
|
|
265
|
+
: shared.failResult(CHECK_TYPE, `${error.code}: ${error.message}`, "check_failed")
|
|
298
266
|
};
|
|
299
267
|
}
|
|
300
268
|
repositoryLabels = labels.value;
|
|
@@ -330,7 +298,7 @@ function shouldFetchComments(config) {
|
|
|
330
298
|
|| config.verify_comment_content
|
|
331
299
|
|| config.verify_task_comment_content);
|
|
332
300
|
}
|
|
333
|
-
function checkStatusLabel(context, remoteData) {
|
|
301
|
+
function checkStatusLabel(context, remoteData, shared) {
|
|
334
302
|
if (!context.expectedStatusLabel || !context.hasTriage) {
|
|
335
303
|
return null;
|
|
336
304
|
}
|
|
@@ -341,9 +309,9 @@ function checkStatusLabel(context, remoteData) {
|
|
|
341
309
|
if (labels.includes(context.expectedStatusLabel)) {
|
|
342
310
|
return null;
|
|
343
311
|
}
|
|
344
|
-
return failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
312
|
+
return shared.failResult(CHECK_TYPE, `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
345
313
|
}
|
|
346
|
-
function checkClosedIssueStatusLabels(context, remoteData) {
|
|
314
|
+
function checkClosedIssueStatusLabels(context, remoteData, shared) {
|
|
347
315
|
if (!context.config.verify_closed_issue_has_no_status_labels) {
|
|
348
316
|
return null;
|
|
349
317
|
}
|
|
@@ -355,9 +323,9 @@ function checkClosedIssueStatusLabels(context, remoteData) {
|
|
|
355
323
|
if (statusLabels.length === 0) {
|
|
356
324
|
return null;
|
|
357
325
|
}
|
|
358
|
-
return failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
|
|
326
|
+
return shared.failResult(CHECK_TYPE, `Closed Issue #${context.issueNumber} retains status labels: ${statusLabels.join(", ")}`, "check_failed");
|
|
359
327
|
}
|
|
360
|
-
function checkCommentMarker(context, remoteData) {
|
|
328
|
+
function checkCommentMarker(context, remoteData, shared) {
|
|
361
329
|
if (!context.marker) {
|
|
362
330
|
return null;
|
|
363
331
|
}
|
|
@@ -365,9 +333,9 @@ function checkCommentMarker(context, remoteData) {
|
|
|
365
333
|
if (comment) {
|
|
366
334
|
return null;
|
|
367
335
|
}
|
|
368
|
-
return failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
336
|
+
return shared.failResult(CHECK_TYPE, `Expected comment marker '${context.marker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
369
337
|
}
|
|
370
|
-
function checkPrCommentMarker(context, remoteData) {
|
|
338
|
+
function checkPrCommentMarker(context, remoteData, shared) {
|
|
371
339
|
if (!context.prMarker) {
|
|
372
340
|
return null;
|
|
373
341
|
}
|
|
@@ -375,88 +343,107 @@ function checkPrCommentMarker(context, remoteData) {
|
|
|
375
343
|
if (comment) {
|
|
376
344
|
return null;
|
|
377
345
|
}
|
|
378
|
-
return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
346
|
+
return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
379
347
|
}
|
|
380
|
-
function checkPrCommentLastCommit(context, remoteData) {
|
|
348
|
+
function checkPrCommentLastCommit(context, remoteData, shared) {
|
|
381
349
|
if (!context.config.verify_pr_comment_last_commit_matches_head) {
|
|
382
350
|
return null;
|
|
383
351
|
}
|
|
384
352
|
if (!context.prMarker) {
|
|
385
|
-
return failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
|
|
353
|
+
return shared.failResult(CHECK_TYPE, "verify_pr_comment_last_commit_matches_head requires expected_pr_comment_marker", "check_failed");
|
|
386
354
|
}
|
|
387
355
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
388
356
|
if (!comment) {
|
|
389
|
-
return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
357
|
+
return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
390
358
|
}
|
|
391
359
|
const match = String(comment.body || "").match(/<!--\s*last-commit:\s*([0-9a-f]{7,40})\s*-->/i);
|
|
392
360
|
if (!match) {
|
|
393
|
-
return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
|
|
361
|
+
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing '<!-- last-commit: <sha> -->' metadata`, "check_failed");
|
|
394
362
|
}
|
|
395
363
|
const expectedHead = String(remoteData.prHeadSha || "").trim();
|
|
396
364
|
if (!expectedHead)
|
|
397
|
-
return blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
|
|
365
|
+
return shared.blockedResult(CHECK_TYPE, "Unable to resolve the PR head SHA", "network_error");
|
|
398
366
|
const actualHead = match[1].trim();
|
|
399
367
|
if (expectedHead === actualHead) {
|
|
400
368
|
return null;
|
|
401
369
|
}
|
|
402
|
-
return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
|
|
370
|
+
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment last-commit metadata mismatch: expected ${expectedHead}, got ${actualHead}`, "check_failed");
|
|
403
371
|
}
|
|
404
|
-
function checkPrCommentRequiredPatterns(context, remoteData) {
|
|
372
|
+
function checkPrCommentRequiredPatterns(context, remoteData, shared) {
|
|
405
373
|
const patterns = context.config.expected_pr_comment_required_patterns || [];
|
|
406
374
|
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
407
375
|
return null;
|
|
408
376
|
}
|
|
409
377
|
if (!context.prMarker) {
|
|
410
|
-
return failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
|
|
378
|
+
return shared.failResult(CHECK_TYPE, "expected_pr_comment_required_patterns requires expected_pr_comment_marker", "check_failed");
|
|
411
379
|
}
|
|
412
380
|
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
413
381
|
if (!comment) {
|
|
414
|
-
return failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
382
|
+
return shared.failResult(CHECK_TYPE, `Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`, "check_failed");
|
|
415
383
|
}
|
|
416
384
|
const body = String(comment.body || "");
|
|
417
385
|
for (const pattern of patterns) {
|
|
418
386
|
const regex = new RegExp(pattern, "m");
|
|
419
387
|
if (!regex.test(body)) {
|
|
420
|
-
return failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
|
|
388
|
+
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`, "check_failed");
|
|
421
389
|
}
|
|
422
390
|
}
|
|
423
391
|
return null;
|
|
424
392
|
}
|
|
425
|
-
function checkCommentContent(context, remoteData) {
|
|
393
|
+
function checkCommentContent(context, remoteData, shared) {
|
|
426
394
|
if (!context.config.verify_comment_content) {
|
|
427
395
|
return null;
|
|
428
396
|
}
|
|
429
397
|
if (!context.marker) {
|
|
430
|
-
return failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
|
|
398
|
+
return shared.failResult(CHECK_TYPE, "verify_comment_content requires expected_comment_marker", "check_failed");
|
|
431
399
|
}
|
|
432
|
-
if (!context.artifactPath || !safeStat(context.artifactPath)) {
|
|
433
|
-
return failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
|
|
400
|
+
if (!context.artifactPath || !shared.safeStat(context.artifactPath)) {
|
|
401
|
+
return shared.failResult(CHECK_TYPE, `Artifact not found for comment verification: ${context.artifactFile || "(missing artifactFile)"}`, "check_failed");
|
|
434
402
|
}
|
|
435
403
|
const comment = findCommentByMarker(remoteData.comments, context.marker);
|
|
436
|
-
|
|
437
|
-
|
|
404
|
+
let localContent;
|
|
405
|
+
let commentContent;
|
|
406
|
+
try {
|
|
407
|
+
const localCanonical = canonicalizeCommentBody(fs.readFileSync(context.artifactPath, "utf8"));
|
|
408
|
+
const commentCanonical = canonicalizeCommentBody(extractCommentBody(comment?.body || ""));
|
|
409
|
+
if (!localCanonical.ok) {
|
|
410
|
+
return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${localCanonical.error.message}`, "check_failed");
|
|
411
|
+
}
|
|
412
|
+
if (!commentCanonical.ok) {
|
|
413
|
+
return shared.failResult(CHECK_TYPE, `Comment content cannot be canonicalized for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${commentCanonical.error.message}`, "check_failed");
|
|
414
|
+
}
|
|
415
|
+
localContent = shared.normalizeContent(localCanonical.value);
|
|
416
|
+
commentContent = shared.normalizeContent(commentCanonical.value);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
return shared.failResult(CHECK_TYPE, `Comment content cannot be read for '${path.basename(context.artifactPath, path.extname(context.artifactPath))}': ${error instanceof Error ? error.message : String(error)}`, "check_failed");
|
|
420
|
+
}
|
|
438
421
|
if (localContent === commentContent) {
|
|
439
422
|
return null;
|
|
440
423
|
}
|
|
441
|
-
return failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
|
|
424
|
+
return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage(path.basename(context.artifactPath, path.extname(context.artifactPath)), context.issueNumber, localContent, commentContent), "check_failed");
|
|
442
425
|
}
|
|
443
|
-
function checkTaskCommentContent(context, remoteData) {
|
|
426
|
+
function checkTaskCommentContent(context, remoteData, shared) {
|
|
444
427
|
if (!context.config.verify_task_comment_content) {
|
|
445
428
|
return null;
|
|
446
429
|
}
|
|
447
430
|
const taskMarker = `<!-- sync-issue:${context.task.metadata.id}:task -->`;
|
|
448
431
|
const comment = findCommentByMarker(remoteData.comments, taskMarker);
|
|
449
432
|
if (!comment) {
|
|
450
|
-
return failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
433
|
+
return shared.failResult(CHECK_TYPE, `Expected comment marker '${taskMarker}' not found on Issue #${context.issueNumber}`, "check_failed");
|
|
434
|
+
}
|
|
435
|
+
const expectedTaskBody = buildExpectedTaskBody(context.task.content, shared);
|
|
436
|
+
if (expectedTaskBody === null) {
|
|
437
|
+
return shared.failResult(CHECK_TYPE, "Task content cannot be rendered safely for comment verification", "check_failed");
|
|
451
438
|
}
|
|
452
|
-
const expectedBody = normalizeContent(
|
|
453
|
-
const commentBody = normalizeContent(extractCommentBody(comment.body || ""));
|
|
439
|
+
const expectedBody = shared.normalizeContent(expectedTaskBody);
|
|
440
|
+
const commentBody = shared.normalizeContent(extractCommentBody(comment.body || ""));
|
|
454
441
|
if (expectedBody === commentBody) {
|
|
455
442
|
return null;
|
|
456
443
|
}
|
|
457
|
-
return failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
|
|
444
|
+
return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
|
|
458
445
|
}
|
|
459
|
-
function checkPrTypeLabel(context, remoteData) {
|
|
446
|
+
function checkPrTypeLabel(context, remoteData, shared) {
|
|
460
447
|
if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
461
448
|
return null;
|
|
462
449
|
}
|
|
@@ -467,9 +454,9 @@ function checkPrTypeLabel(context, remoteData) {
|
|
|
467
454
|
if (remoteData.prLabels.includes(expectedLabel)) {
|
|
468
455
|
return null;
|
|
469
456
|
}
|
|
470
|
-
return failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
|
|
457
|
+
return shared.failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`, "check_failed");
|
|
471
458
|
}
|
|
472
|
-
function checkInLabelsMatchPr(context, remoteData) {
|
|
459
|
+
function checkInLabelsMatchPr(context, remoteData, shared) {
|
|
473
460
|
if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
474
461
|
return null;
|
|
475
462
|
}
|
|
@@ -482,17 +469,17 @@ function checkInLabelsMatchPr(context, remoteData) {
|
|
|
482
469
|
if (arraysEqual(issueInLabels, prInLabels)) {
|
|
483
470
|
return null;
|
|
484
471
|
}
|
|
485
|
-
return failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
|
|
472
|
+
return shared.failResult(CHECK_TYPE, `in: labels mismatch — PR #${context.prNumber} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
|
|
486
473
|
}
|
|
487
|
-
function checkInLabelsComputed(context, remoteData) {
|
|
474
|
+
function checkInLabelsComputed(context, remoteData, shared) {
|
|
488
475
|
if (!context.config.verify_in_labels_computed || !context.hasTriage) {
|
|
489
476
|
return null;
|
|
490
477
|
}
|
|
491
|
-
const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping);
|
|
478
|
+
const expectedInLabels = computeExpectedInLabels(context.taskDir, remoteData.repositoryLabels, remoteData.inLabelMapping, shared);
|
|
492
479
|
if (!expectedInLabels.ok) {
|
|
493
480
|
return expectedInLabels.type === "check_failed"
|
|
494
|
-
? failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
|
|
495
|
-
: blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
|
|
481
|
+
? shared.failResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type)
|
|
482
|
+
: shared.blockedResult(CHECK_TYPE, expectedInLabels.message, expectedInLabels.type);
|
|
496
483
|
}
|
|
497
484
|
if (expectedInLabels.mode === "skipped") {
|
|
498
485
|
return null;
|
|
@@ -503,32 +490,32 @@ function checkInLabelsComputed(context, remoteData) {
|
|
|
503
490
|
if (arraysEqual(expectedInLabels.labels, actualInLabels)) {
|
|
504
491
|
return null;
|
|
505
492
|
}
|
|
506
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
|
|
493
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} in: labels do not match committed changes: expected [${formatLabelList(expectedInLabels.labels)}], got [${formatLabelList(actualInLabels)}]`, "check_failed");
|
|
507
494
|
}
|
|
508
|
-
function checkSyncedRequirements(context, remoteData) {
|
|
495
|
+
function checkSyncedRequirements(context, remoteData, shared) {
|
|
509
496
|
if (!context.config.sync_checked_requirements || !context.hasTriage) {
|
|
510
497
|
return null;
|
|
511
498
|
}
|
|
512
|
-
const checkedRequirements = getCheckedRequirements(context.task.content);
|
|
499
|
+
const checkedRequirements = shared.getCheckedRequirements(context.task.content);
|
|
513
500
|
if (checkedRequirements.length === 0) {
|
|
514
501
|
return null;
|
|
515
502
|
}
|
|
516
503
|
const issueBody = remoteData.issue.body || "";
|
|
517
|
-
const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(repoRoot, context.task.metadata.type || "task"));
|
|
504
|
+
const resolution = resolveRequirementSection(issueBody, requirementSectionAnchors(shared.repoRoot, context.task.metadata.type || "task"));
|
|
518
505
|
if (resolution.status === "missing") {
|
|
519
506
|
return null;
|
|
520
507
|
}
|
|
521
508
|
if (resolution.status === "ambiguous") {
|
|
522
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
|
|
509
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} requirements section is ambiguous`, "check_failed");
|
|
523
510
|
}
|
|
524
511
|
const requirementBody = issueBody.slice(resolution.bodyStart, resolution.bodyEnd);
|
|
525
512
|
const missingRequirements = checkedRequirements.filter((item) => !hasCheckedRequirement(requirementBody, item));
|
|
526
513
|
if (missingRequirements.length === 0) {
|
|
527
514
|
return null;
|
|
528
515
|
}
|
|
529
|
-
return failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
|
|
516
|
+
return shared.failResult(CHECK_TYPE, `Issue body is missing checked requirements: ${missingRequirements.join(", ")}`, "check_failed");
|
|
530
517
|
}
|
|
531
|
-
function checkIssueType(context, remoteData) {
|
|
518
|
+
function checkIssueType(context, remoteData, shared) {
|
|
532
519
|
if (!context.config.verify_issue_type || !context.hasPush) {
|
|
533
520
|
return null;
|
|
534
521
|
}
|
|
@@ -539,15 +526,15 @@ function checkIssueType(context, remoteData) {
|
|
|
539
526
|
if (context.repoOwnerType === "User") {
|
|
540
527
|
return null;
|
|
541
528
|
}
|
|
542
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
|
|
529
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no Issue Type set`, "check_failed");
|
|
543
530
|
}
|
|
544
531
|
const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
|
|
545
532
|
if (expectedType && remoteData.issueType !== expectedType) {
|
|
546
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
|
|
533
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`, "check_failed");
|
|
547
534
|
}
|
|
548
535
|
return null;
|
|
549
536
|
}
|
|
550
|
-
function checkIssueFields(context, remoteData) {
|
|
537
|
+
function checkIssueFields(context, remoteData, shared) {
|
|
551
538
|
if (!context.config.verify_issue_fields || !context.hasPush) {
|
|
552
539
|
return null;
|
|
553
540
|
}
|
|
@@ -556,7 +543,7 @@ function checkIssueFields(context, remoteData) {
|
|
|
556
543
|
}
|
|
557
544
|
for (const [metadataKey, fieldName] of Object.entries(FRONTMATTER_FIELD_MAP)) {
|
|
558
545
|
const expectedRaw = context.task.metadata[metadataKey];
|
|
559
|
-
if (isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
|
|
546
|
+
if (shared.isBlank(expectedRaw) || !remoteData.issueFields.pinnedNames.has(fieldName)) {
|
|
560
547
|
continue;
|
|
561
548
|
}
|
|
562
549
|
const actual = remoteData.issueFields.values.get(fieldName);
|
|
@@ -565,40 +552,40 @@ function checkIssueFields(context, remoteData) {
|
|
|
565
552
|
continue;
|
|
566
553
|
}
|
|
567
554
|
if (!actual) {
|
|
568
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
|
|
555
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is missing, expected '${expected.value}'`, "check_failed");
|
|
569
556
|
}
|
|
570
557
|
if (actual.kind !== expected.kind || actual.value !== expected.value) {
|
|
571
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
|
|
558
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} field '${fieldName}' is '${actual.value}', expected '${expected.value}'`, "check_failed");
|
|
572
559
|
}
|
|
573
560
|
}
|
|
574
561
|
return null;
|
|
575
562
|
}
|
|
576
|
-
function checkPrAssignee(context, remoteData) {
|
|
563
|
+
function checkPrAssignee(context, remoteData, shared) {
|
|
577
564
|
if (!context.config.verify_pr_assignee || !context.hasPush || !context.prNumber) {
|
|
578
565
|
return null;
|
|
579
566
|
}
|
|
580
567
|
if (!remoteData.prAssignees || remoteData.prAssignees.length === 0) {
|
|
581
|
-
return failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
|
|
568
|
+
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no assignee`, "check_failed");
|
|
582
569
|
}
|
|
583
570
|
return null;
|
|
584
571
|
}
|
|
585
|
-
function checkMilestone(context, remoteData) {
|
|
572
|
+
function checkMilestone(context, remoteData, shared) {
|
|
586
573
|
if (!context.config.verify_milestone || !context.hasTriage) {
|
|
587
574
|
return null;
|
|
588
575
|
}
|
|
589
576
|
if (!remoteData.issue?.milestone?.title) {
|
|
590
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
|
|
577
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
|
|
591
578
|
}
|
|
592
579
|
if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
|
|
593
|
-
return failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
|
|
580
|
+
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} has no milestone set`, "check_failed");
|
|
594
581
|
}
|
|
595
582
|
if (context.config.verify_milestone_specific) {
|
|
596
583
|
const issueTitle = remoteData.issue.milestone.title;
|
|
597
584
|
if (VERSION_LINE_REGEX.test(issueTitle)) {
|
|
598
|
-
return failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
|
|
585
|
+
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
|
|
599
586
|
}
|
|
600
587
|
if (context.prNumber && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
|
|
601
|
-
return failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
|
|
588
|
+
return shared.failResult(CHECK_TYPE, `PR #${context.prNumber} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
|
|
602
589
|
}
|
|
603
590
|
}
|
|
604
591
|
return null;
|
|
@@ -643,37 +630,38 @@ function extractCommentBody(commentBody) {
|
|
|
643
630
|
}
|
|
644
631
|
return lines.slice(start, end).join("\n");
|
|
645
632
|
}
|
|
646
|
-
function buildExpectedTaskBody(taskContent) {
|
|
633
|
+
function buildExpectedTaskBody(taskContent, shared) {
|
|
647
634
|
const frontmatterMatch = taskContent.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
648
635
|
if (!frontmatterMatch) {
|
|
649
|
-
return taskContent.trim();
|
|
636
|
+
return sanitizeCommentContent(taskContent.trim());
|
|
637
|
+
}
|
|
638
|
+
const body = sanitizeCommentContent(taskContent.slice(frontmatterMatch[0].length).trim());
|
|
639
|
+
if (body === null) {
|
|
640
|
+
return null;
|
|
650
641
|
}
|
|
651
|
-
const body = taskContent.slice(frontmatterMatch[0].length).trim();
|
|
652
642
|
return [
|
|
653
|
-
buildTaskFrontmatterSummary(),
|
|
643
|
+
buildTaskFrontmatterSummary(shared),
|
|
654
644
|
"",
|
|
655
|
-
"
|
|
656
|
-
frontmatterMatch[0].trim(),
|
|
657
|
-
"```",
|
|
645
|
+
renderSafeCodeFence(frontmatterMatch[0].trim(), "yaml"),
|
|
658
646
|
"",
|
|
659
647
|
"</details>",
|
|
660
648
|
"",
|
|
661
649
|
body
|
|
662
650
|
].join("\n").trim();
|
|
663
651
|
}
|
|
664
|
-
function buildTaskFrontmatterSummary() {
|
|
665
|
-
const language = loadProjectLanguage();
|
|
652
|
+
function buildTaskFrontmatterSummary(shared) {
|
|
653
|
+
const language = loadProjectLanguage(shared);
|
|
666
654
|
if (language === "en" || language === "en-US") {
|
|
667
655
|
return "<details><summary>Metadata (frontmatter)</summary>";
|
|
668
656
|
}
|
|
669
657
|
return "<details><summary>元数据 (frontmatter)</summary>";
|
|
670
658
|
}
|
|
671
|
-
function loadProjectLanguage() {
|
|
659
|
+
function loadProjectLanguage(shared) {
|
|
672
660
|
const override = process.env.VALIDATE_ARTIFACT_LANGUAGE;
|
|
673
|
-
if (!isBlank(override)) {
|
|
661
|
+
if (!shared.isBlank(override)) {
|
|
674
662
|
return String(override).trim();
|
|
675
663
|
}
|
|
676
|
-
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
664
|
+
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
677
665
|
if (!fs.existsSync(configPath)) {
|
|
678
666
|
return "";
|
|
679
667
|
}
|
|
@@ -788,8 +776,8 @@ function arraysEqual(left, right) {
|
|
|
788
776
|
function formatLabelList(labels) {
|
|
789
777
|
return labels.length > 0 ? labels.join(", ") : "none";
|
|
790
778
|
}
|
|
791
|
-
function computeExpectedInLabels(taskDir, repositoryLabels
|
|
792
|
-
const task = loadTask(taskDir);
|
|
779
|
+
function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, shared) {
|
|
780
|
+
const task = shared.loadTask(taskDir);
|
|
793
781
|
if (!task.ok) {
|
|
794
782
|
return task;
|
|
795
783
|
}
|
|
@@ -820,8 +808,8 @@ function computeExpectedInLabels(taskDir, repositoryLabels = [], mappingOverride
|
|
|
820
808
|
}
|
|
821
809
|
return { ok: true, labels: planned.target, mode: "mapped" };
|
|
822
810
|
}
|
|
823
|
-
function loadInLabelMapping() {
|
|
824
|
-
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
811
|
+
function loadInLabelMapping(shared) {
|
|
812
|
+
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
825
813
|
if (!fs.existsSync(configPath)) {
|
|
826
814
|
return { ok: true, value: {} };
|
|
827
815
|
}
|
|
@@ -852,12 +840,12 @@ function gitText(args, cwd) {
|
|
|
852
840
|
}
|
|
853
841
|
}
|
|
854
842
|
function resolvePrHeadSha(context) {
|
|
855
|
-
const fallback = () =>
|
|
843
|
+
const fallback = () => gitText(["rev-parse", "HEAD"], context.taskDir);
|
|
856
844
|
const branch = String(context.task?.metadata?.branch || "").trim();
|
|
857
845
|
if (!branch) {
|
|
858
846
|
return fallback();
|
|
859
847
|
}
|
|
860
|
-
const worktreeList =
|
|
848
|
+
const worktreeList = gitText(["worktree", "list", "--porcelain"], context.taskDir);
|
|
861
849
|
if (!worktreeList.ok) {
|
|
862
850
|
return fallback();
|
|
863
851
|
}
|
|
@@ -865,7 +853,7 @@ function resolvePrHeadSha(context) {
|
|
|
865
853
|
if (!matchedWorktree) {
|
|
866
854
|
return fallback();
|
|
867
855
|
}
|
|
868
|
-
const headInWorktree =
|
|
856
|
+
const headInWorktree = gitText(["rev-parse", "HEAD"], matchedWorktree);
|
|
869
857
|
if (!headInWorktree.ok) {
|
|
870
858
|
return fallback();
|
|
871
859
|
}
|
|
@@ -888,9 +876,6 @@ function findWorktreeForBranch(porcelainOutput, branch) {
|
|
|
888
876
|
}
|
|
889
877
|
return null;
|
|
890
878
|
}
|
|
891
|
-
function withRetry(operation) {
|
|
892
|
-
return operation();
|
|
893
|
-
}
|
|
894
879
|
function interpolate(template, taskDir, artifactFile) {
|
|
895
880
|
const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
|
|
896
881
|
return template
|
|
@@ -26,7 +26,7 @@ function createPostConfirmation(version, post) {
|
|
|
26
26
|
remoteHead: post.remoteHead,
|
|
27
27
|
newVersion: post.newVersion,
|
|
28
28
|
changedPaths: byteSort(post.changedPaths),
|
|
29
|
-
|
|
29
|
+
demoTranscriptSha256: post.demoTranscriptSha256,
|
|
30
30
|
worktree: byteSort(post.worktree),
|
|
31
31
|
staged: byteSort(post.staged)
|
|
32
32
|
};
|