@fitlab-ai/agent-infra 0.9.12 → 0.9.13
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 +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
|
@@ -1,14 +1,65 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { enumerateArtifacts } from '../task/artifacts.js';
|
|
4
4
|
import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
|
|
5
5
|
import { renderHumanOverrideAudit } from '../task/human-override.js';
|
|
6
6
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
7
|
-
import {
|
|
8
|
-
import { createGitHubClient } from './github-client.js';
|
|
9
|
-
import { listRemoteComments, normalizeCommentContent, writeComment } from './issue-comments.js';
|
|
10
|
-
import { platformResult } from './types.js';
|
|
7
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
11
8
|
import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
|
|
9
|
+
import { resolvePlatformProviderContext } from './context.js';
|
|
10
|
+
import { normalizeCommentContent } from './issue-comments.js';
|
|
11
|
+
import { platformResult } from './types.js';
|
|
12
|
+
import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
|
|
13
|
+
import { buildPrChangeReport, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
|
|
14
|
+
function summaryMarker(taskId) {
|
|
15
|
+
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
16
|
+
}
|
|
17
|
+
function escapeSummaryAuditText(value) {
|
|
18
|
+
return value
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<')
|
|
21
|
+
.replace(/>/g, '>')
|
|
22
|
+
.replace(/\r\n?/g, '\n');
|
|
23
|
+
}
|
|
24
|
+
function renderSummaryAuditLine(value) {
|
|
25
|
+
const safe = escapeSummaryAuditText(value);
|
|
26
|
+
const longestRun = Math.max(0, ...(safe.match(/`+/g) || []).map((run) => run.length));
|
|
27
|
+
const delimiter = '`'.repeat(longestRun + 1);
|
|
28
|
+
return `${delimiter} ${safe} ${delimiter}`;
|
|
29
|
+
}
|
|
30
|
+
function renderSafeHumanOverrideAudit(value) {
|
|
31
|
+
const normalized = value.trim().replace(/\r\n?/g, '\n');
|
|
32
|
+
if (!normalized)
|
|
33
|
+
return '';
|
|
34
|
+
const [heading, ...rows] = normalized.split('\n');
|
|
35
|
+
const safeHeading = heading === '## Human Override Audit' ? heading : renderSummaryAuditLine(heading || '');
|
|
36
|
+
return [safeHeading, ...rows.map((row) => row ? renderSummaryAuditLine(row) : '')].join('\n');
|
|
37
|
+
}
|
|
38
|
+
function isSafeSummaryEnvelope(value, taskId) {
|
|
39
|
+
return value.includes(summaryMarker(taskId))
|
|
40
|
+
&& (value.match(/<!--\s*sync-pr:/gi) || []).length === 1
|
|
41
|
+
&& (value.match(/<!--\s*last-commit:/gi) || []).length === 1
|
|
42
|
+
&& !/<!--\s*canonical-pr-change-report\b/i.test(value);
|
|
43
|
+
}
|
|
44
|
+
function taskReportPath(taskDir) {
|
|
45
|
+
return path.join(taskDir, 'pr-change-report.json');
|
|
46
|
+
}
|
|
47
|
+
function readBoundTaskSnapshot(taskMdPath) {
|
|
48
|
+
const taskContent = fs.readFileSync(taskMdPath, 'utf8');
|
|
49
|
+
const frontmatter = parseTypedTaskFrontmatter(taskContent);
|
|
50
|
+
const fact = readPrDeliveryFact(frontmatter);
|
|
51
|
+
const boundFact = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact : null;
|
|
52
|
+
return { taskContent, boundFact };
|
|
53
|
+
}
|
|
54
|
+
function boundPrNumberForWarning(taskMdPath) {
|
|
55
|
+
try {
|
|
56
|
+
const { boundFact } = readBoundTaskSnapshot(taskMdPath);
|
|
57
|
+
return boundFact ? resourceIdentityNumber(boundFact.identity.resource) : null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
12
63
|
function warningResultForPrimary(primaryResult) {
|
|
13
64
|
if (primaryResult === 'pr_created')
|
|
14
65
|
return 'pr_created_with_warnings';
|
|
@@ -16,11 +67,11 @@ function warningResultForPrimary(primaryResult) {
|
|
|
16
67
|
return 'pr_reused_with_warnings';
|
|
17
68
|
return 'no_op_with_warnings';
|
|
18
69
|
}
|
|
19
|
-
function
|
|
20
|
-
return
|
|
70
|
+
function platformError(error) {
|
|
71
|
+
return { code: error.code, message: error.message, retryable: error.retryable ?? false };
|
|
21
72
|
}
|
|
22
73
|
function buildPullRequestSummary(taskId, body, headSha, humanOverrideAudit = '') {
|
|
23
|
-
const sections = [body.replace(/\s+$/, ''), humanOverrideAudit
|
|
74
|
+
const sections = [body.replace(/\s+$/, ''), renderSafeHumanOverrideAudit(humanOverrideAudit)].filter(Boolean).join('\n\n');
|
|
24
75
|
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
|
|
25
76
|
}
|
|
26
77
|
function reconcileSummaryComment(comments, taskId, desired) {
|
|
@@ -47,26 +98,308 @@ function canonicalArtifacts(taskDir) {
|
|
|
47
98
|
if (!current || round > current.round)
|
|
48
99
|
byFamily.set(family, { family, name: artifact.name, path: artifact.path, round });
|
|
49
100
|
}
|
|
50
|
-
return [...byFamily.values()].map(({ family, name, path }) => ({ family, name, path }));
|
|
101
|
+
return [...byFamily.values()].map(({ family, name, path: artifactPath }) => ({ family, name, path: artifactPath }));
|
|
102
|
+
}
|
|
103
|
+
function basePlatformResult(status, context, taskId, prNumber, error = null) {
|
|
104
|
+
return platformResult(status, {
|
|
105
|
+
platform: context.platform,
|
|
106
|
+
capabilities: context.capabilities,
|
|
107
|
+
operations: context.operations,
|
|
108
|
+
resource: { kind: 'pull-request', number: prNumber },
|
|
109
|
+
error,
|
|
110
|
+
...(taskId ? { task: { id: taskId, prNumber } } : {})
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function isHardSummaryFailure(error) {
|
|
114
|
+
return error?.code === 'PR_SUMMARY_HEAD_RACE'
|
|
115
|
+
|| error?.code === 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED'
|
|
116
|
+
|| error?.code === 'PR_SUMMARY_RACE_COMPENSATION_FAILED';
|
|
117
|
+
}
|
|
118
|
+
function inspectionError(error) {
|
|
119
|
+
return error.retryable ? 'blocked' : 'failed';
|
|
120
|
+
}
|
|
121
|
+
async function inspectBoundPullRequest(context, repoRoot, prNumber, loadedContext) {
|
|
122
|
+
if (!context.platform.repository) {
|
|
123
|
+
return { ok: false, status: context.status, error: context.error };
|
|
124
|
+
}
|
|
125
|
+
const loaded = loadedContext
|
|
126
|
+
? { ok: true, value: loadedContext }
|
|
127
|
+
: await resolvePlatformProviderContext({ cwd: repoRoot });
|
|
128
|
+
if (!loaded.ok)
|
|
129
|
+
return { ok: false, status: loaded.context.status, error: loaded.context.error };
|
|
130
|
+
const target = providerResourceToken(loaded.value.provider, 'pull-request', String(prNumber));
|
|
131
|
+
const inspected = loaded.value.provider.changeRequests?.inspect
|
|
132
|
+
? await loaded.value.provider.changeRequests.inspect({ context: providerOperationContext(loaded.value), target })
|
|
133
|
+
: unsupportedProviderOperation(loaded.value.provider, 'changeRequests.inspect');
|
|
134
|
+
if (!inspected.ok) {
|
|
135
|
+
const error = providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED');
|
|
136
|
+
return { ok: false, status: inspectionError(error), error };
|
|
137
|
+
}
|
|
138
|
+
const remote = inspected.value;
|
|
139
|
+
const number = remote.number ?? resourceIdentityNumber(remote.identity) ?? prNumber;
|
|
140
|
+
const head = remote.head || { repository: context.platform.repository, ref: '', sha: remote.headSha || '' };
|
|
141
|
+
const base = remote.base || { repository: context.platform.repository, ref: '', sha: remote.baseSha || '' };
|
|
142
|
+
return {
|
|
143
|
+
ok: true,
|
|
144
|
+
value: {
|
|
145
|
+
repository: context.platform.repository,
|
|
146
|
+
number,
|
|
147
|
+
nodeId: remote.id,
|
|
148
|
+
url: remote.displayUrl || '',
|
|
149
|
+
state: remote.state === 'closed' ? 'closed' : 'open',
|
|
150
|
+
title: remote.title,
|
|
151
|
+
body: remote.body,
|
|
152
|
+
draft: remote.draft ?? false,
|
|
153
|
+
head: { ...head, sha: remote.headSha || head.sha },
|
|
154
|
+
base: { ...base, sha: remote.baseSha || base.sha },
|
|
155
|
+
mergedAt: remote.mergedAt ?? null,
|
|
156
|
+
mergeCommitSha: remote.mergeCommitSha ?? null,
|
|
157
|
+
labels: [...(remote.labels || [])].sort(),
|
|
158
|
+
assignees: [...(remote.assignees || [])].sort(),
|
|
159
|
+
milestone: remote.milestone ?? null,
|
|
160
|
+
mergeability: remote.mergeability || { state: 'unknown', detail: null }
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function sameIdentity(left, right) {
|
|
165
|
+
return JSON.stringify({
|
|
166
|
+
repository: left.repository,
|
|
167
|
+
number: left.number,
|
|
168
|
+
base: left.base,
|
|
169
|
+
head: left.head
|
|
170
|
+
}) === JSON.stringify({
|
|
171
|
+
repository: right.repository,
|
|
172
|
+
number: right.number,
|
|
173
|
+
base: right.base,
|
|
174
|
+
head: right.head
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function identityFromSnapshot(snapshot) {
|
|
178
|
+
return {
|
|
179
|
+
repository: snapshot.repository,
|
|
180
|
+
number: snapshot.number,
|
|
181
|
+
base: { ...snapshot.base },
|
|
182
|
+
head: { ...snapshot.head }
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function readJsonInput(file) {
|
|
186
|
+
try {
|
|
187
|
+
return { ok: true, value: JSON.parse(fs.readFileSync(file, 'utf8')) };
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const errno = error.code;
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
error: {
|
|
194
|
+
code: errno === 'ENOENT' ? 'PR_CHANGE_REPORT_INPUT_MISSING' : 'PR_CHANGE_REPORT_INPUT_INVALID',
|
|
195
|
+
message: error instanceof Error ? error.message : String(error),
|
|
196
|
+
retryable: false
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function sameMechanical(left, right) {
|
|
202
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
203
|
+
}
|
|
204
|
+
function currentReportCheck(report, snapshot, taskContent, repoRoot) {
|
|
205
|
+
const digest = taskIntentDigest(taskContent);
|
|
206
|
+
if (!digest.ok)
|
|
207
|
+
return { ok: false, error: platformError(digest.error) };
|
|
208
|
+
const reportIdentity = report.identity;
|
|
209
|
+
if (reportIdentity.repository !== snapshot.repository || reportIdentity.number !== snapshot.number ||
|
|
210
|
+
JSON.stringify(reportIdentity.base) !== JSON.stringify(snapshot.base) || JSON.stringify(reportIdentity.head) !== JSON.stringify(snapshot.head))
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
error: { code: 'PR_CHANGE_REPORT_STALE', message: 'Change report identity does not match the authoritative pull request snapshot', retryable: false }
|
|
214
|
+
};
|
|
215
|
+
if (report.inputs.taskIntentSha256 !== digest.value.sha256)
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
error: { code: 'PR_CHANGE_REPORT_STALE', message: 'Change report task intent digest is stale', retryable: false }
|
|
219
|
+
};
|
|
220
|
+
let mechanical;
|
|
221
|
+
try {
|
|
222
|
+
mechanical = runMechanicalChangeReport(repoRoot, snapshot.base.sha, snapshot.head.sha);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return { ok: false, error: { code: 'PR_CHANGE_REPORT_GIT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false } };
|
|
226
|
+
}
|
|
227
|
+
const reportMechanical = {
|
|
228
|
+
version: 1,
|
|
229
|
+
base: snapshot.base.sha,
|
|
230
|
+
head: snapshot.head.sha,
|
|
231
|
+
mergeBase: report.diff.mergeBase,
|
|
232
|
+
patchSha256: report.diff.patchSha256,
|
|
233
|
+
files: report.diff.files,
|
|
234
|
+
totals: report.diff.totals
|
|
235
|
+
};
|
|
236
|
+
if (!sameMechanical(reportMechanical, mechanical))
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
error: { code: 'PR_CHANGE_REPORT_STALE', message: 'Change report patch or mechanical statistics are stale', retryable: false }
|
|
240
|
+
};
|
|
241
|
+
return { ok: true, digest: digest.value.sha256, mechanical };
|
|
242
|
+
}
|
|
243
|
+
function summaryReportStatus(reportPath, snapshot, taskContent, repoRoot) {
|
|
244
|
+
const digest = taskIntentDigest(taskContent);
|
|
245
|
+
if (!digest.ok)
|
|
246
|
+
return { status: 'invalid', taskIntentSha256: null, reason: digest.error.message };
|
|
247
|
+
const report = readPrChangeReport(reportPath);
|
|
248
|
+
if (!report.ok)
|
|
249
|
+
return { status: report.error.code === 'PR_CHANGE_REPORT_MISSING' ? 'missing' : 'invalid', taskIntentSha256: digest.value.sha256, reason: report.error.message };
|
|
250
|
+
if (!snapshot)
|
|
251
|
+
return { status: 'stale', taskIntentSha256: digest.value.sha256, reason: 'No bound pull request snapshot is available' };
|
|
252
|
+
const checked = currentReportCheck(report.value, snapshot, taskContent, repoRoot);
|
|
253
|
+
return checked.ok
|
|
254
|
+
? { status: 'ready', taskIntentSha256: checked.digest }
|
|
255
|
+
: { status: checked.error.code === 'PR_CHANGE_REPORT_STALE' ? 'stale' : 'invalid', taskIntentSha256: digest.value.sha256, reason: checked.error.message };
|
|
51
256
|
}
|
|
52
|
-
function summaryContext(taskRef, options = {}) {
|
|
257
|
+
async function summaryContext(taskRef, options = {}) {
|
|
53
258
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
54
259
|
if (!resolved.ok)
|
|
55
|
-
return {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
260
|
+
return {
|
|
261
|
+
...platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }),
|
|
262
|
+
task: { id: resolved.taskId, prNumber: null }, pullRequest: null,
|
|
263
|
+
changeReport: { status: 'invalid', path: '', taskIntentSha256: null, reason: resolved.message }, artifacts: []
|
|
264
|
+
};
|
|
265
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
266
|
+
const frontmatter = parseTypedTaskFrontmatter(taskContent);
|
|
267
|
+
const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
268
|
+
const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? resourceIdentityNumber(fact.fact.identity.resource) : null;
|
|
269
|
+
const reportPath = taskReportPath(resolved.taskDir);
|
|
270
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
271
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
272
|
+
let pullRequest = null;
|
|
273
|
+
if (loaded.ok && prNumber && context.platform.repository && ['no-op', 'degraded'].includes(context.status)) {
|
|
274
|
+
const inspected = await inspectBoundPullRequest(context, resolved.repoRoot, prNumber, loaded.value);
|
|
275
|
+
if (inspected.ok)
|
|
276
|
+
pullRequest = inspected.value;
|
|
277
|
+
}
|
|
278
|
+
const report = summaryReportStatus(reportPath, pullRequest, taskContent, resolved.repoRoot);
|
|
279
|
+
return {
|
|
280
|
+
...platformResult(context.status, {
|
|
281
|
+
platform: context.platform,
|
|
282
|
+
capabilities: context.capabilities,
|
|
283
|
+
operations: context.operations,
|
|
284
|
+
error: context.error
|
|
285
|
+
}),
|
|
286
|
+
task: { id: resolved.taskId, prNumber },
|
|
287
|
+
pullRequest,
|
|
288
|
+
changeReport: { status: report.status, path: reportPath, taskIntentSha256: report.taskIntentSha256, ...(report.reason ? { reason: report.reason } : {}) },
|
|
289
|
+
artifacts: canonicalArtifacts(resolved.taskDir)
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
async function reportWrite(taskRef, options) {
|
|
293
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
294
|
+
if (!resolved.ok)
|
|
295
|
+
return { ...platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }), report: null };
|
|
296
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
297
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
298
|
+
if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status))
|
|
299
|
+
return { ...context, report: null };
|
|
300
|
+
let knownPrNumber = null;
|
|
301
|
+
try {
|
|
302
|
+
return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
|
|
303
|
+
const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
304
|
+
if (!boundFact)
|
|
305
|
+
return {
|
|
306
|
+
...basePlatformResult('failed', context, resolved.taskId, null, { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false }),
|
|
307
|
+
report: null
|
|
308
|
+
};
|
|
309
|
+
const boundPrNumber = resourceIdentityNumber(boundFact.identity.resource);
|
|
310
|
+
if (!boundPrNumber)
|
|
311
|
+
return {
|
|
312
|
+
...basePlatformResult('failed', context, resolved.taskId, null, { code: 'PR_NUMBER_INVALID', message: 'Bound pull request has no numeric identifier', retryable: false }),
|
|
313
|
+
report: null
|
|
314
|
+
};
|
|
315
|
+
knownPrNumber = boundPrNumber;
|
|
316
|
+
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
|
|
317
|
+
if (!initial.ok)
|
|
318
|
+
return { ...basePlatformResult(initial.status, context, resolved.taskId, boundPrNumber, initial.error), report: null };
|
|
319
|
+
const digest = taskIntentDigest(taskContent);
|
|
320
|
+
if (!digest.ok)
|
|
321
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(digest.error)), report: null };
|
|
322
|
+
const mechanicalInput = readJsonInput(path.resolve(resolved.repoRoot, options.mechanicalFile));
|
|
323
|
+
if (!mechanicalInput.ok)
|
|
324
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, mechanicalInput.error), report: null };
|
|
325
|
+
const mechanical = validateMechanicalReport(mechanicalInput.value);
|
|
326
|
+
if (!mechanical.ok)
|
|
327
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(mechanical.error)), report: null };
|
|
328
|
+
const candidateInput = readJsonInput(path.resolve(resolved.repoRoot, options.precheckFile));
|
|
329
|
+
if (!candidateInput.ok)
|
|
330
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, candidateInput.error), report: null };
|
|
331
|
+
const candidate = validatePrecheckCandidate(candidateInput.value);
|
|
332
|
+
if (!candidate.ok)
|
|
333
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(candidate.error)), report: null };
|
|
334
|
+
let recomputed;
|
|
335
|
+
try {
|
|
336
|
+
recomputed = runMechanicalChangeReport(resolved.repoRoot, initial.value.base.sha, initial.value.head.sha);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_GIT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
|
|
340
|
+
}
|
|
341
|
+
if (!sameMechanical(mechanical.value, recomputed))
|
|
342
|
+
return {
|
|
343
|
+
...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_MECHANICAL_MISMATCH', message: 'Mechanical report input does not match the current complete diff', retryable: false }),
|
|
344
|
+
report: null
|
|
345
|
+
};
|
|
346
|
+
const built = buildPrChangeReport(identityFromSnapshot(initial.value), digest.value.sha256, mechanical.value, candidate.value);
|
|
347
|
+
if (!built.ok)
|
|
348
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, platformError(built.error)), report: null };
|
|
349
|
+
const final = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
|
|
350
|
+
if (!final.ok)
|
|
351
|
+
return { ...basePlatformResult(final.status, context, resolved.taskId, boundPrNumber, final.error), report: null };
|
|
352
|
+
if (!sameIdentity(initial.value, final.value))
|
|
353
|
+
return {
|
|
354
|
+
...basePlatformResult('blocked', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_HEAD_RACE', message: 'Pull request identity changed while generating the change report', retryable: true }),
|
|
355
|
+
report: null
|
|
356
|
+
};
|
|
357
|
+
const target = taskReportPath(resolved.taskDir);
|
|
358
|
+
const existing = readPrChangeReport(target);
|
|
359
|
+
const reportInfo = {
|
|
360
|
+
path: target,
|
|
361
|
+
precheckVerdict: built.value.precheck.verdict,
|
|
362
|
+
nextAction: built.value.precheck.route
|
|
363
|
+
};
|
|
364
|
+
if (existing.ok && JSON.stringify(existing.value) === JSON.stringify(built.value))
|
|
365
|
+
return {
|
|
366
|
+
...basePlatformResult('no-op', context, resolved.taskId, boundPrNumber),
|
|
367
|
+
report: { ...reportInfo, status: 'no-op' }
|
|
368
|
+
};
|
|
369
|
+
if (options.dryRun)
|
|
370
|
+
return {
|
|
371
|
+
...basePlatformResult('planned', context, resolved.taskId, boundPrNumber),
|
|
372
|
+
operations: [{ name: 'change-report:write', status: 'planned', reasonCode: null }],
|
|
373
|
+
report: { ...reportInfo, status: 'planned' }
|
|
374
|
+
};
|
|
375
|
+
try {
|
|
376
|
+
writePrChangeReportAtomic(target, built.value);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, boundPrNumber, { code: 'PR_CHANGE_REPORT_WRITE_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
...basePlatformResult('applied', context, resolved.taskId, boundPrNumber),
|
|
383
|
+
operations: [{ name: 'change-report:write', status: 'applied', reasonCode: null }],
|
|
384
|
+
report: { ...reportInfo, status: 'written' }
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
const lockError = error instanceof TaskExecutionLockError
|
|
390
|
+
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
391
|
+
: { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
392
|
+
return { ...basePlatformResult(lockError.retryable ? 'blocked' : 'failed', context, resolved.taskId, knownPrNumber, lockError), report: null };
|
|
393
|
+
}
|
|
60
394
|
}
|
|
61
|
-
function syncPullRequestSummary(taskRef, options) {
|
|
395
|
+
async function syncPullRequestSummary(taskRef, options) {
|
|
62
396
|
const warningResult = warningResultForPrimary(options.primaryResult);
|
|
63
397
|
let knownPrNumber = null;
|
|
64
398
|
const softenFailure = (output) => {
|
|
65
399
|
const prNumber = output.resource.kind === 'pull-request' && output.resource.number
|
|
66
400
|
? output.resource.number
|
|
67
401
|
: knownPrNumber;
|
|
68
|
-
const warning = output.error && prNumber
|
|
69
|
-
&& output.error.code !== 'PR_NOT_LINKED'
|
|
402
|
+
const warning = output.error && prNumber && output.error.code !== 'PR_NOT_LINKED'
|
|
70
403
|
? {
|
|
71
404
|
code: output.error.code,
|
|
72
405
|
message: output.error.message,
|
|
@@ -88,45 +421,182 @@ function syncPullRequestSummary(taskRef, options) {
|
|
|
88
421
|
}
|
|
89
422
|
: { ...output, result: null, warnings: [] };
|
|
90
423
|
};
|
|
424
|
+
const preserveFailure = (output) => options.strict
|
|
425
|
+
? { ...output, result: null, warnings: [] }
|
|
426
|
+
: softenFailure(output);
|
|
91
427
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
92
428
|
if (!resolved.ok)
|
|
93
429
|
return softenFailure(platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }));
|
|
430
|
+
const fail = (status, context, error, prNumber = knownPrNumber) => {
|
|
431
|
+
const output = basePlatformResult(status, context, resolved.taskId, prNumber, error);
|
|
432
|
+
return options.strict || isHardSummaryFailure(error)
|
|
433
|
+
? { ...output, result: null, warnings: [] }
|
|
434
|
+
: { ...softenFailure(output) };
|
|
435
|
+
};
|
|
94
436
|
const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
95
|
-
const fact = readPrDeliveryFact(frontmatter);
|
|
437
|
+
const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
96
438
|
if (fact.status === 'invalid')
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
106
|
-
|
|
439
|
+
return preserveFailure(platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }));
|
|
440
|
+
if (!options.changeReportFile)
|
|
441
|
+
return preserveFailure(platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_MISSING', message: 'summary-sync requires --change-report-file', retryable: false } }));
|
|
442
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
443
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
444
|
+
if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) {
|
|
445
|
+
// This diagnostic-only read preserves the known PR target when context resolution fails before the execution lock.
|
|
446
|
+
knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
|
|
447
|
+
return preserveFailure(context);
|
|
448
|
+
}
|
|
107
449
|
try {
|
|
108
|
-
|
|
450
|
+
return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
|
|
451
|
+
const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
452
|
+
if (!boundFact)
|
|
453
|
+
return {
|
|
454
|
+
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
|
|
455
|
+
result: null, warnings: []
|
|
456
|
+
};
|
|
457
|
+
const prIdentity = boundFact.identity.resource;
|
|
458
|
+
const boundPrNumber = resourceIdentityNumber(prIdentity);
|
|
459
|
+
if (!boundPrNumber)
|
|
460
|
+
return {
|
|
461
|
+
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NUMBER_INVALID', message: 'Bound pull request has no numeric identifier', retryable: false } }),
|
|
462
|
+
result: null, warnings: []
|
|
463
|
+
};
|
|
464
|
+
knownPrNumber = boundPrNumber;
|
|
465
|
+
const prNumber = boundPrNumber;
|
|
466
|
+
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
|
|
467
|
+
if (!initial.ok)
|
|
468
|
+
return fail(initial.status, context, initial.error);
|
|
469
|
+
const expectedReportPath = taskReportPath(resolved.taskDir);
|
|
470
|
+
const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile);
|
|
471
|
+
if (suppliedReportPath !== expectedReportPath)
|
|
472
|
+
return fail('failed', context, { code: 'PR_CHANGE_REPORT_PATH_INVALID', message: 'summary-sync must consume the task-bound pr-change-report.json', retryable: false });
|
|
473
|
+
const report = readPrChangeReport(suppliedReportPath);
|
|
474
|
+
if (!report.ok)
|
|
475
|
+
return fail('failed', context, platformError(report.error));
|
|
476
|
+
const checked = currentReportCheck(report.value, initial.value, taskContent, resolved.repoRoot);
|
|
477
|
+
if (!checked.ok)
|
|
478
|
+
return fail('failed', context, checked.error);
|
|
479
|
+
const replaced = replaceCanonicalReportPlaceholder(options.body, report.value);
|
|
480
|
+
if (!replaced.ok)
|
|
481
|
+
return fail('failed', context, platformError(replaced.error));
|
|
482
|
+
const desired = buildPullRequestSummary(resolved.taskId, replaced.value, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
|
|
483
|
+
if (!isSafeSummaryEnvelope(desired, resolved.taskId))
|
|
484
|
+
return fail('failed', context, {
|
|
485
|
+
code: 'PR_SUMMARY_RENDER_INVALID',
|
|
486
|
+
message: 'Summary contains an invalid or duplicated reserved control marker',
|
|
487
|
+
retryable: false
|
|
488
|
+
});
|
|
489
|
+
const listed = loaded.value.provider.comments?.list
|
|
490
|
+
? await loaded.value.provider.comments.list({ context: providerOperationContext(loaded.value), parent: prIdentity }).then((response) => response.ok
|
|
491
|
+
? { ok: true, value: response.value.map((comment) => ({ id: /^\d+$/.test(comment.id) ? Number(comment.id) : comment.id, body: comment.body })) }
|
|
492
|
+
: response)
|
|
493
|
+
: unsupportedProviderOperation(loaded.value.provider, 'comments.list');
|
|
494
|
+
if (!listed.ok)
|
|
495
|
+
return fail(providerStatus(listed.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'));
|
|
496
|
+
const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
|
|
497
|
+
if (reconciliation.action === 'conflict')
|
|
498
|
+
return fail('failed', context, { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false });
|
|
499
|
+
const previousBody = reconciliation.action === 'update'
|
|
500
|
+
? listed.value.find((comment) => comment.id === reconciliation.commentId)?.body ?? null
|
|
501
|
+
: null;
|
|
502
|
+
const info = { precheckVerdict: report.value.precheck.verdict, nextAction: report.value.precheck.route };
|
|
503
|
+
const beforeWrite = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
|
|
504
|
+
if (!beforeWrite.ok)
|
|
505
|
+
return fail(beforeWrite.status, context, beforeWrite.error, prNumber);
|
|
506
|
+
if (!sameIdentity(initial.value, beforeWrite.value))
|
|
507
|
+
return fail('blocked', context, {
|
|
508
|
+
code: 'PR_SUMMARY_HEAD_RACE',
|
|
509
|
+
message: 'Pull request head changed before publishing the summary',
|
|
510
|
+
retryable: true
|
|
511
|
+
}, prNumber);
|
|
512
|
+
if (reconciliation.action === 'no-op')
|
|
513
|
+
return {
|
|
514
|
+
...basePlatformResult('no-op', context, resolved.taskId, prNumber),
|
|
515
|
+
comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId], parts: 1 },
|
|
516
|
+
result: null, warnings: [], ...info
|
|
517
|
+
};
|
|
518
|
+
if (options.dryRun)
|
|
519
|
+
return {
|
|
520
|
+
...basePlatformResult('planned', context, resolved.taskId, prNumber),
|
|
521
|
+
operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }],
|
|
522
|
+
result: null, warnings: [], ...info
|
|
523
|
+
};
|
|
524
|
+
const existingComment = reconciliation.commentId === null
|
|
525
|
+
? undefined
|
|
526
|
+
: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId));
|
|
527
|
+
const written = loaded.value.provider.comments?.write
|
|
528
|
+
? await loaded.value.provider.comments.write({
|
|
529
|
+
context: providerOperationContext(loaded.value),
|
|
530
|
+
parent: prIdentity,
|
|
531
|
+
body: desired,
|
|
532
|
+
...(existingComment ? { existingComment } : {}),
|
|
533
|
+
mutation: { idempotencyKey: `pr-summary:${resolved.taskId}` }
|
|
534
|
+
})
|
|
535
|
+
: unsupportedProviderOperation(loaded.value.provider, 'comments.write');
|
|
536
|
+
if (!written.ok)
|
|
537
|
+
return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
|
|
538
|
+
const after = await inspectBoundPullRequest(context, resolved.repoRoot, prNumber, loaded.value);
|
|
539
|
+
const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
|
|
540
|
+
const compensateWrittenSummary = async () => reconciliation.action === 'create'
|
|
541
|
+
? written.value.remoteId && loaded.value.provider.comments?.delete
|
|
542
|
+
? await loaded.value.provider.comments.delete({
|
|
543
|
+
context: providerOperationContext(loaded.value),
|
|
544
|
+
parent: prIdentity,
|
|
545
|
+
comment: providerResourceToken(loaded.value.provider, 'comment', written.value.remoteId),
|
|
546
|
+
mutation: { idempotencyKey: `pr-summary-delete:${resolved.taskId}:${written.value.remoteId}` }
|
|
547
|
+
})
|
|
548
|
+
: { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Created summary comment has no valid id', retryable: true } }
|
|
549
|
+
: reconciliation.commentId && previousBody !== null && loaded.value.provider.comments?.write
|
|
550
|
+
? await loaded.value.provider.comments.write({
|
|
551
|
+
context: providerOperationContext(loaded.value),
|
|
552
|
+
parent: prIdentity,
|
|
553
|
+
body: previousBody,
|
|
554
|
+
existingComment: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId)),
|
|
555
|
+
mutation: { idempotencyKey: `pr-summary-restore:${resolved.taskId}` }
|
|
556
|
+
})
|
|
557
|
+
: { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Previous summary body is unavailable for restoration', retryable: true } };
|
|
558
|
+
if (!after.ok) {
|
|
559
|
+
const compensated = await compensateWrittenSummary();
|
|
560
|
+
if (!compensated.ok)
|
|
561
|
+
return fail('blocked', context, {
|
|
562
|
+
code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
|
|
563
|
+
message: `Summary post-write verification failed and compensation failed: ${compensated.error.message}`,
|
|
564
|
+
retryable: true
|
|
565
|
+
}, prNumber);
|
|
566
|
+
return fail('blocked', context, {
|
|
567
|
+
code: 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED',
|
|
568
|
+
message: `Summary was written but pull-request verification failed: ${after.error?.message || 'unknown verification error'}; the previous summary was restored or the new comment was deleted`,
|
|
569
|
+
retryable: true
|
|
570
|
+
}, prNumber);
|
|
571
|
+
}
|
|
572
|
+
if (!sameIdentity(initial.value, after.value)) {
|
|
573
|
+
const compensated = await compensateWrittenSummary();
|
|
574
|
+
if (!compensated.ok)
|
|
575
|
+
return fail('blocked', context, {
|
|
576
|
+
code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
|
|
577
|
+
message: `Pull-request head changed and summary compensation failed: ${compensated.error.message}`,
|
|
578
|
+
retryable: true
|
|
579
|
+
}, prNumber);
|
|
580
|
+
return fail('blocked', context, {
|
|
581
|
+
code: 'PR_SUMMARY_HEAD_RACE',
|
|
582
|
+
message: 'Pull-request head changed while publishing; the previous summary was restored or the new comment was deleted; rerun report and summary sync',
|
|
583
|
+
retryable: true
|
|
584
|
+
}, prNumber);
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
...basePlatformResult('applied', context, resolved.taskId, prNumber),
|
|
588
|
+
comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 },
|
|
589
|
+
operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
|
|
590
|
+
result: null, warnings: [], ...info
|
|
591
|
+
};
|
|
592
|
+
});
|
|
109
593
|
}
|
|
110
594
|
catch (error) {
|
|
111
|
-
|
|
595
|
+
const lockError = error instanceof TaskExecutionLockError
|
|
596
|
+
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
597
|
+
: { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
598
|
+
return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
|
|
112
599
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!listed.ok)
|
|
116
|
-
return softenFailure(platformResult(listed.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: listed.error }));
|
|
117
|
-
const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
|
|
118
|
-
if (reconciliation.action === 'conflict')
|
|
119
|
-
return softenFailure(platformResult('failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false } }));
|
|
120
|
-
if (reconciliation.action === 'no-op')
|
|
121
|
-
return softenFailure(platformResult('no-op', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId], parts: 1 }, error: null }));
|
|
122
|
-
if (options.dryRun)
|
|
123
|
-
return softenFailure(platformResult('planned', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }], error: null }));
|
|
124
|
-
const written = writeComment(client, context.platform.repository, prNumber, resolved.repoRoot, desired, reconciliation.commentId || undefined);
|
|
125
|
-
if (!written.ok)
|
|
126
|
-
return softenFailure(platformResult(written.error.retryable ? 'blocked' : 'failed', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, error: written.error }));
|
|
127
|
-
const id = Number(written.value.id || reconciliation.commentId);
|
|
128
|
-
return softenFailure(platformResult('applied', { platform: context.platform, capabilities: context.capabilities, resource: { kind: 'pull-request', number: prNumber }, comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 }, operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }], error: null }));
|
|
129
|
-
}
|
|
130
|
-
export { buildPullRequestSummary, reconcileSummaryComment, summaryContext, summaryMarker, syncPullRequestSummary };
|
|
131
|
-
export { warningResultForPrimary };
|
|
600
|
+
}
|
|
601
|
+
export { buildPullRequestSummary, reconcileSummaryComment, reportWrite, summaryContext, summaryMarker, syncPullRequestSummary, warningResultForPrimary };
|
|
132
602
|
//# sourceMappingURL=pr-summary.js.map
|