@fitlab-ai/agent-infra 0.11.15 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
- package/dist/lib/decide.js +2 -7
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/manual-validation.js +19 -33
- package/dist/lib/internal/platform-comment.js +59 -9
- package/dist/lib/internal/task-activity.js +1 -12
- package/dist/lib/internal/task-event.js +1 -12
- package/dist/lib/internal/task-ledger.js +1 -12
- package/dist/lib/internal/task-operation-registry.js +2 -1
- package/dist/lib/internal/task-qualification.js +6 -15
- package/dist/lib/internal/task-review.js +3 -16
- package/dist/lib/internal/task-short-id.js +2 -1
- package/dist/lib/internal/task-verify.js +1 -11
- package/dist/lib/internal/task-warning.js +1 -12
- package/dist/lib/internal/task-workspace.js +43 -0
- package/dist/lib/merge.js +4 -3
- package/dist/lib/platform/issue-comments.js +373 -267
- package/dist/lib/platform/issue-summary-content.js +112 -0
- package/dist/lib/platform/pr-change-report.js +175 -26
- package/dist/lib/platform/pr-summary-content.js +54 -0
- package/dist/lib/platform/pr-summary.js +321 -296
- package/dist/lib/platform/pull-requests.js +3 -15
- package/dist/lib/platform/summary-content-pipeline.js +18 -0
- package/dist/lib/pr-review/evidence-grading.js +3 -2
- package/dist/lib/process-data/index.js +5 -4
- package/dist/lib/process-data/sources.js +6 -0
- package/dist/lib/run/index.js +3 -2
- package/dist/lib/sandbox/commands/create.js +5 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/list-running.js +2 -1
- package/dist/lib/sandbox/control/client.js +8 -13
- package/dist/lib/sandbox/control/completed-reentry.js +2 -1
- package/dist/lib/sandbox/control/task-view.js +4 -2
- package/dist/lib/sandbox/mounts.js +4 -4
- package/dist/lib/sandbox/recovery.js +26 -16
- package/dist/lib/sandbox/removal.js +62 -12
- package/dist/lib/sandbox/task-resolver.js +9 -6
- package/dist/lib/sandbox/workspace-identity.js +6 -5
- package/dist/lib/sandbox/workspace-view.js +29 -22
- package/dist/lib/task/activity-intent.js +2 -12
- package/dist/lib/task/artifact-lifecycle.js +17 -11
- package/dist/lib/task/artifact-operations.js +41 -49
- package/dist/lib/task/capabilities.js +2 -2
- package/dist/lib/task/commands/ls.js +4 -3
- package/dist/lib/task/commands/status.js +1 -1
- package/dist/lib/task/commit-operation.js +6 -6
- package/dist/lib/task/control-authority.js +32 -32
- package/dist/lib/task/create-service.js +2 -4
- package/dist/lib/task/create.js +12 -65
- package/dist/lib/task/delegation-receipts.js +1 -1
- package/dist/lib/task/delivery.js +2 -8
- package/dist/lib/task/events.js +5 -31
- package/dist/lib/task/finalization.js +33 -29
- package/dist/lib/task/lifecycle.js +9 -48
- package/dist/lib/task/local-artifact-finalization.js +1 -2
- package/dist/lib/task/orchestration.js +24 -57
- package/dist/lib/task/platform-operation-recovery.js +87 -88
- package/dist/lib/task/resolve-ref.js +17 -12
- package/dist/lib/task/review-finalization.js +2 -35
- package/dist/lib/task/review-identity.js +3 -1
- package/dist/lib/task/short-id.js +10 -8
- package/dist/lib/task/verification-engine.js +19 -58
- package/dist/lib/task/workspace-root.js +59 -0
- package/dist/lib/task/workspace-snapshot.js +4 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
- package/lib/decide.ts +2 -7
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/manual-validation.ts +20 -37
- package/lib/internal/platform-comment.ts +49 -6
- package/lib/internal/task-activity.ts +1 -10
- package/lib/internal/task-event.ts +1 -10
- package/lib/internal/task-ledger.ts +1 -10
- package/lib/internal/task-operation-registry.ts +2 -1
- package/lib/internal/task-qualification.ts +5 -11
- package/lib/internal/task-review.ts +3 -14
- package/lib/internal/task-short-id.ts +2 -1
- package/lib/internal/task-verify.ts +1 -9
- package/lib/internal/task-warning.ts +1 -10
- package/lib/internal/task-workspace.ts +39 -0
- package/lib/merge.ts +4 -3
- package/lib/platform/issue-comments.ts +478 -280
- package/lib/platform/issue-summary-content.ts +132 -0
- package/lib/platform/pr-change-report.ts +165 -20
- package/lib/platform/pr-summary-content.ts +60 -0
- package/lib/platform/pr-summary.ts +367 -269
- package/lib/platform/pull-requests.ts +3 -24
- package/lib/platform/summary-content-pipeline.ts +23 -0
- package/lib/pr-review/evidence-grading.ts +3 -2
- package/lib/process-data/index.ts +4 -3
- package/lib/process-data/sources.ts +6 -0
- package/lib/run/index.ts +3 -2
- package/lib/sandbox/commands/create.ts +5 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/list-running.ts +2 -1
- package/lib/sandbox/control/client.ts +7 -12
- package/lib/sandbox/control/completed-reentry.ts +2 -1
- package/lib/sandbox/control/task-view.ts +4 -2
- package/lib/sandbox/mounts.ts +4 -4
- package/lib/sandbox/recovery.ts +26 -18
- package/lib/sandbox/removal.ts +60 -27
- package/lib/sandbox/task-resolver.ts +9 -6
- package/lib/sandbox/workspace-identity.ts +6 -5
- package/lib/sandbox/workspace-view.ts +28 -20
- package/lib/task/activity-intent.ts +3 -17
- package/lib/task/artifact-lifecycle.ts +39 -11
- package/lib/task/artifact-operations.ts +28 -35
- package/lib/task/capabilities.ts +2 -2
- package/lib/task/commands/ls.ts +4 -3
- package/lib/task/commands/status.ts +1 -1
- package/lib/task/commit-operation.ts +5 -6
- package/lib/task/control-authority.ts +29 -35
- package/lib/task/create-service.ts +2 -4
- package/lib/task/create.ts +10 -51
- package/lib/task/delegation-receipts.ts +1 -1
- package/lib/task/delivery.ts +2 -10
- package/lib/task/events.ts +5 -32
- package/lib/task/finalization.ts +27 -30
- package/lib/task/lifecycle.ts +9 -50
- package/lib/task/local-artifact-finalization.ts +1 -4
- package/lib/task/orchestration.ts +31 -80
- package/lib/task/platform-operation-recovery.ts +70 -82
- package/lib/task/resolve-ref.ts +18 -12
- package/lib/task/review-finalization.ts +2 -52
- package/lib/task/review-identity.ts +3 -1
- package/lib/task/short-id.ts +10 -8
- package/lib/task/verification-engine.ts +20 -62
- package/lib/task/workspace-root.ts +78 -0
- package/lib/task/workspace-snapshot.ts +4 -3
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +19 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
- package/templates/.agents/README.en.md +10 -15
- package/templates/.agents/README.zh-CN.md +9 -13
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +2 -2
- package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-sync.en.md +5 -3
- package/templates/.agents/rules/pr-sync.zh-CN.md +7 -10
- package/templates/.agents/rules/release-commands.en.md +3 -0
- package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +0 -2
- package/templates/.agents/rules/task-management.zh-CN.md +0 -2
- package/templates/.agents/rules/task-short-id.en.md +2 -2
- package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
- package/templates/.agents/scripts/task-short-id.js +2 -8
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/config/verify.json +0 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/config/verify.json +0 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +2 -2
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -30
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -32
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/create-pr/config/verify.json +0 -1
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +10 -3
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +10 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +3 -3
- package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -22
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -22
- package/templates/.agents/skills/create-task/config/verify.json +0 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/config/verify.json +0 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/restore-task/config/verify.json +0 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
- 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 +0 -3
- package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
- package/templates/.agents/templates/handoff.en.md +1 -1
- package/templates/.agents/templates/handoff.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +1 -1
- package/templates/.agents/workflows/refactoring.en.yaml +1 -1
- package/dist/lib/task/task-execution-lock.js +0 -250
- package/lib/task/task-execution-lock.ts +0 -320
|
@@ -4,19 +4,21 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { enumerateArtifacts } from '../task/artifacts.js';
|
|
5
5
|
import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
|
|
6
6
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
7
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
8
7
|
import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
|
|
9
8
|
import { resolvePlatformProviderContext } from './context.js';
|
|
10
|
-
import {
|
|
9
|
+
import { inspectPlatformPullRequest } from './pull-requests.js';
|
|
10
|
+
import { normalizeCommentContent, syncCommentPlan } from './issue-comments.js';
|
|
11
11
|
import { CONTROL_MARKER_PATTERN, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.js';
|
|
12
12
|
import { platformResult } from './types.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { resourceIdentityEquals } from './resource-identity.js';
|
|
13
|
+
import { providerError, providerOperationContext, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
|
|
14
|
+
import { parseResourceIdentity, resourceIdentityEquals } from './resource-identity.js';
|
|
16
15
|
import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
|
|
17
16
|
import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
|
|
18
17
|
import { readManualValidationCompletion } from '../task/manual-validation-completion.js';
|
|
19
18
|
import { readManualValidationTransaction } from '../task/manual-validation-transaction.js';
|
|
19
|
+
import { parsePrSummaryContent, renderPrSummaryContent } from './pr-summary-content.js';
|
|
20
|
+
import { runSummaryContentPipeline } from './summary-content-pipeline.js';
|
|
21
|
+
import { parseLedgerDocument } from '../task/ledger.js';
|
|
20
22
|
function summaryMarker(taskId) {
|
|
21
23
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
22
24
|
}
|
|
@@ -59,17 +61,6 @@ function platformError(error) {
|
|
|
59
61
|
function buildPullRequestSummary(taskId, body, headSha) {
|
|
60
62
|
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${body.replace(/\s+$/, '')}\n`);
|
|
61
63
|
}
|
|
62
|
-
function reconcileSummaryComment(comments, taskId, desired) {
|
|
63
|
-
const marker = summaryMarker(taskId);
|
|
64
|
-
const matches = comments.filter((comment) => comment.body.includes(marker));
|
|
65
|
-
if (matches.length > 1)
|
|
66
|
-
return { action: 'conflict', commentId: null };
|
|
67
|
-
if (matches.length === 0)
|
|
68
|
-
return { action: 'create', commentId: null };
|
|
69
|
-
return normalizeCommentContent(matches[0].body) === normalizeCommentContent(desired)
|
|
70
|
-
? { action: 'no-op', commentId: matches[0].id }
|
|
71
|
-
: { action: 'update', commentId: matches[0].id };
|
|
72
|
-
}
|
|
73
64
|
function canonicalArtifacts(taskDir) {
|
|
74
65
|
const allowed = /^(plan|review-plan|code|review-code|manual-validation)(?:-r\d+)?\.md$/;
|
|
75
66
|
const byFamily = new Map();
|
|
@@ -103,6 +94,247 @@ function isHardSummaryFailure(error) {
|
|
|
103
94
|
function inspectionError(error) {
|
|
104
95
|
return error.retryable ? 'blocked' : 'failed';
|
|
105
96
|
}
|
|
97
|
+
function preparationError(code, message) {
|
|
98
|
+
return Object.assign(new Error(message), { code });
|
|
99
|
+
}
|
|
100
|
+
function validateManualSummaryPhase(manual, rendered) {
|
|
101
|
+
const hasFinal = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(rendered);
|
|
102
|
+
if (hasFinal && (!manual || manual.phase !== 'final'))
|
|
103
|
+
throw preparationError('MANUAL_VALIDATION_TRANSACTION_REQUIRED', 'final manual-validation summary requires the transaction coordinator');
|
|
104
|
+
if (manual && (manual.phase === 'pending' ? hasFinal : !hasFinal))
|
|
105
|
+
throw preparationError('MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', 'manual-validation summary phase does not match the requested writer phase');
|
|
106
|
+
}
|
|
107
|
+
function readPendingManualEvidence(resolved, manual, current, prNumber) {
|
|
108
|
+
const transaction = readManualValidationTransaction(resolved.taskDir, { transactionId: manual.transactionId, taskId: resolved.taskId, prNumber, prHeadSha: current.head.sha });
|
|
109
|
+
if (!transaction.ok)
|
|
110
|
+
throw preparationError(transaction.error.code, transaction.error.message);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function readFinalManualEvidence(resolved, manual, rendered, current, prNumber) {
|
|
114
|
+
if (!manual.receiptDigest || manual.prHeadSha !== current.head.sha || manual.authority !== 'coordinator')
|
|
115
|
+
throw preparationError('MANUAL_VALIDATION_TRANSACTION_REQUIRED', 'final manual-validation summary requires transaction, receipt, and current head identity');
|
|
116
|
+
const completion = readManualValidationCompletion(resolved.taskDir, { transactionId: manual.transactionId, taskId: resolved.taskId, prNumber, prHeadSha: current.head.sha, receiptDigest: manual.receiptDigest });
|
|
117
|
+
if (!completion.ok)
|
|
118
|
+
throw preparationError(completion.error.code, completion.error.message);
|
|
119
|
+
if (completion.value.transaction.phase !== 'final-promotion-in-progress' || !completion.value.transaction.eventAppended
|
|
120
|
+
|| !manualValidationFinalSummaryProjectionMatches(rendered, completion.value.receipt))
|
|
121
|
+
throw preparationError('MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', 'final summary does not match the receipt-backed promotion intent');
|
|
122
|
+
return completion.value.receipt.evidenceDigest;
|
|
123
|
+
}
|
|
124
|
+
async function manualValidationEvidence(resolved, manual, rendered, current) {
|
|
125
|
+
validateManualSummaryPhase(manual, rendered);
|
|
126
|
+
if (!manual)
|
|
127
|
+
return null;
|
|
128
|
+
const prNumber = current.number;
|
|
129
|
+
if (!Number.isSafeInteger(prNumber) || prNumber <= 0)
|
|
130
|
+
throw preparationError('MANUAL_VALIDATION_PR_NUMBER_REQUIRED', 'manual-validation summary requires a positive safe pull-request number from the authoritative snapshot');
|
|
131
|
+
if (!manual.transactionId)
|
|
132
|
+
throw preparationError('MANUAL_VALIDATION_TRANSACTION_REQUIRED', 'manual-validation summary requires a transaction ID');
|
|
133
|
+
return manual.phase === 'pending'
|
|
134
|
+
? readPendingManualEvidence(resolved, manual, current, prNumber)
|
|
135
|
+
: readFinalManualEvidence(resolved, manual, rendered, current, prNumber);
|
|
136
|
+
}
|
|
137
|
+
function validatedSummaryReport(resolved, options, current, taskContent) {
|
|
138
|
+
if (!options.changeReportFile)
|
|
139
|
+
throw preparationError('PR_CHANGE_REPORT_MISSING', 'summary-sync requires --change-report-file');
|
|
140
|
+
const reportPath = taskReportPath(resolved.taskDir);
|
|
141
|
+
if (path.resolve(resolved.repoRoot, options.changeReportFile) !== reportPath)
|
|
142
|
+
throw preparationError('PR_CHANGE_REPORT_PATH_INVALID', 'summary-sync must consume the task-bound pr-change-report.json');
|
|
143
|
+
const report = readPrChangeReport(reportPath);
|
|
144
|
+
if (!report.ok)
|
|
145
|
+
throw preparationError(report.error.code, report.error.message);
|
|
146
|
+
const checked = currentReportCheck(report.value, current, taskContent, resolved.repoRoot);
|
|
147
|
+
if (!checked.ok)
|
|
148
|
+
throw preparationError(checked.error.code, checked.error.message);
|
|
149
|
+
return report.value;
|
|
150
|
+
}
|
|
151
|
+
function renderedSummaryBody(taskId, rendered, report, manual, evidenceDigest, headSha) {
|
|
152
|
+
const split = splitDocumentPlaceholder(rendered, CANONICAL_REPORT_PLACEHOLDER);
|
|
153
|
+
if (!split.ok)
|
|
154
|
+
throw preparationError('PR_SUMMARY_BODY_CONTRACT_INVALID', split.error.message);
|
|
155
|
+
const prefix = sanitizeMarkdownDocument(split.value.prefix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
156
|
+
const suffix = sanitizeMarkdownDocument(split.value.suffix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
157
|
+
if (!prefix.ok || !suffix.ok)
|
|
158
|
+
throw preparationError('PR_SUMMARY_BODY_CONTRACT_INVALID', 'PR summary body contract is invalid');
|
|
159
|
+
const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${CANONICAL_REPORT_PLACEHOLDER}${suffix.value}`, report);
|
|
160
|
+
if (!replaced.ok)
|
|
161
|
+
throw preparationError(replaced.error.code, replaced.error.message);
|
|
162
|
+
const withReceipt = manual?.phase === 'final' ? replaced.value.replace(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${evidenceDigest}; head=${manual.prHeadSha} -->`) : replaced.value;
|
|
163
|
+
const body = buildPullRequestSummary(taskId, withReceipt, headSha);
|
|
164
|
+
if (!isSafeSummaryEnvelope(body, taskId))
|
|
165
|
+
throw preparationError('PR_SUMMARY_RENDER_INVALID', 'Summary contains an invalid or duplicated reserved control marker');
|
|
166
|
+
return body;
|
|
167
|
+
}
|
|
168
|
+
function prepareBoundSummaryContent(body, taskContent) {
|
|
169
|
+
try {
|
|
170
|
+
return prepareAndRenderPrSummaryContent(JSON.parse(body), taskContent);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
throw preparationError('PR_SUMMARY_CONTENT_INVALID', error instanceof Error ? error.message : String(error));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function prepareBoundPullRequestSummary(resolved, options, context, loaded, expectedIdentity) {
|
|
177
|
+
const taskSnapshot = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
178
|
+
if (taskSnapshot.factError)
|
|
179
|
+
throw preparationError(taskSnapshot.factError.code, taskSnapshot.factError.message);
|
|
180
|
+
if (!taskSnapshot.boundFact || !resourceIdentityEquals(taskSnapshot.boundFact.identity.resource, expectedIdentity))
|
|
181
|
+
throw preparationError('PLATFORM_OPERATION_IDENTITY_MISMATCH', 'Bound pull request identity changed while the summary was queued');
|
|
182
|
+
const preparedContent = prepareBoundSummaryContent(options.body, taskSnapshot.taskContent);
|
|
183
|
+
const current = await inspectBoundPullRequest(context, resolved.repoRoot, expectedIdentity, loaded);
|
|
184
|
+
if (!current.ok)
|
|
185
|
+
throw preparationError(current.error?.code || 'PR_SNAPSHOT_UNAVAILABLE', current.error?.message || 'Pull-request snapshot is unavailable');
|
|
186
|
+
const evidenceDigest = await manualValidationEvidence(resolved, options.manualValidation, preparedContent.rendered, current.value);
|
|
187
|
+
const report = validatedSummaryReport(resolved, options, current.value, taskSnapshot.taskContent);
|
|
188
|
+
const body = renderedSummaryBody(resolved.taskId, preparedContent.rendered, report, options.manualValidation, evidenceDigest, current.value.head.sha);
|
|
189
|
+
return {
|
|
190
|
+
parent: expectedIdentity,
|
|
191
|
+
item: { marker: summaryMarker(resolved.taskId), body, content: options.body, part: 1, total: 1 },
|
|
192
|
+
snapshot: current.value,
|
|
193
|
+
rendered: body,
|
|
194
|
+
manual: options.manualValidation,
|
|
195
|
+
prNumber: current.value.number,
|
|
196
|
+
precheckVerdict: report.precheck.verdict,
|
|
197
|
+
nextAction: report.precheck.route
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function validateBoundPullRequestSummary(resolved, options, context, loaded, prepared) {
|
|
201
|
+
const latest = await inspectBoundPullRequest(context, resolved.repoRoot, prepared.parent, loaded);
|
|
202
|
+
if (!latest.ok)
|
|
203
|
+
return latest.error;
|
|
204
|
+
if (!sameIdentity(prepared.snapshot, latest.value))
|
|
205
|
+
return { code: 'PR_SUMMARY_HEAD_RACE', message: 'Pull-request identity or base/head snapshot changed during summary publication', retryable: true };
|
|
206
|
+
const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
207
|
+
const bound = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
208
|
+
if (bound.status !== 'valid' || bound.fact.state !== 'bound' || !resourceIdentityEquals(bound.fact.identity.resource, prepared.parent))
|
|
209
|
+
return { code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request identity changed during summary publication', retryable: true };
|
|
210
|
+
try {
|
|
211
|
+
await manualValidationEvidence(resolved, prepared.manual, prepared.rendered, latest.value);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
const value = error;
|
|
215
|
+
return { code: value.code || 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: value.message || String(error), retryable: false };
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
async function syncBoundPullRequestSummary(resolved, options, context, loaded, expectedIdentity, preserveFailure) {
|
|
220
|
+
const state = { prepared: null };
|
|
221
|
+
const expectedDigest = createHash('sha256').update(options.body).digest('hex');
|
|
222
|
+
const operation = {
|
|
223
|
+
taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: 'pull-request-summary',
|
|
224
|
+
target: JSON.stringify(expectedIdentity), expectedDigest, dependency: 'deferred',
|
|
225
|
+
pullRequestSummary: {
|
|
226
|
+
body: options.body, changeReportFile: options.changeReportFile ?? taskReportPath(resolved.taskDir),
|
|
227
|
+
...(options.manualValidation?.transactionId ? { manualValidation: { ...options.manualValidation, transactionId: options.manualValidation.transactionId } } : {})
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const strategy = {
|
|
231
|
+
taskId: resolved.taskId, cwd: resolved.repoRoot, agent: options.agent, client: options.client, resolved: { loaded, context },
|
|
232
|
+
parentKind: 'pull-request', kind: 'summary', markerPrefix: summaryMarker(resolved.taskId).replace(/ -->$/u, ''),
|
|
233
|
+
dryRun: options.dryRun, verifyReadback: true,
|
|
234
|
+
prepare: async () => {
|
|
235
|
+
const prepared = await prepareBoundPullRequestSummary(resolved, options, context, loaded, expectedIdentity);
|
|
236
|
+
state.prepared = prepared;
|
|
237
|
+
return { parent: prepared.parent, items: [prepared.item] };
|
|
238
|
+
},
|
|
239
|
+
validate: async () => state.prepared ? validateBoundPullRequestSummary(resolved, options, context, loaded, state.prepared) : null
|
|
240
|
+
};
|
|
241
|
+
const output = await syncCommentPlan(strategy, operation, options.queuedOperation);
|
|
242
|
+
if (output.error || output.status === 'failed' || output.status === 'blocked')
|
|
243
|
+
return preserveFailure({
|
|
244
|
+
...output, resource: { kind: 'pull-request', number: state.prepared?.prNumber ?? resourceIdentityNumber(expectedIdentity), identity: expectedIdentity }
|
|
245
|
+
}, state.prepared?.prNumber ?? resourceIdentityNumber(expectedIdentity));
|
|
246
|
+
const info = state.prepared
|
|
247
|
+
? { precheckVerdict: state.prepared.precheckVerdict, nextAction: state.prepared.nextAction }
|
|
248
|
+
: { precheckVerdict: 'needs-review', nextAction: 'review-code' };
|
|
249
|
+
return {
|
|
250
|
+
...output,
|
|
251
|
+
resource: { kind: 'pull-request', number: state.prepared?.prNumber ?? resourceIdentityNumber(expectedIdentity), identity: expectedIdentity },
|
|
252
|
+
comment: output.comment ? { ...output.comment, kind: 'summary' } : null,
|
|
253
|
+
result: null, warnings: [], ...info
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function validateUnboundPrSummaryInput(options, taskContent, knownPrNumber) {
|
|
257
|
+
try {
|
|
258
|
+
prepareAndRenderPrSummaryContent(JSON.parse(options.body), taskContent);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
return platformResult('failed', {
|
|
263
|
+
resource: { kind: 'pull-request', number: knownPrNumber },
|
|
264
|
+
error: { code: 'PR_SUMMARY_CONTENT_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function boundSummaryPhase(raw) {
|
|
269
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
270
|
+
return null;
|
|
271
|
+
const input = raw;
|
|
272
|
+
const manual = input.manualValidation && typeof input.manualValidation === 'object' && !Array.isArray(input.manualValidation)
|
|
273
|
+
? input.manualValidation : {};
|
|
274
|
+
const state = typeof manual.state === 'string' ? manual.state : null;
|
|
275
|
+
const overview = typeof input.overview === 'string' ? input.overview : '';
|
|
276
|
+
return {
|
|
277
|
+
passed: state === 'passed' || /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(overview),
|
|
278
|
+
known: state !== null || overview.length > 0
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function manualSummaryPhaseFailure(options, phase) {
|
|
282
|
+
const manual = options.manualValidation;
|
|
283
|
+
if (phase.passed && (manual?.phase !== 'final' || manual.authority !== 'coordinator'))
|
|
284
|
+
return {
|
|
285
|
+
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator'
|
|
286
|
+
};
|
|
287
|
+
if (!manual || !phase.known)
|
|
288
|
+
return null;
|
|
289
|
+
if ((manual.phase === 'pending' && phase.passed) || (manual.phase === 'final' && !phase.passed))
|
|
290
|
+
return {
|
|
291
|
+
code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase'
|
|
292
|
+
};
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
function boundSummaryPhaseError(options, raw, knownPrNumber) {
|
|
296
|
+
const phase = boundSummaryPhase(raw);
|
|
297
|
+
if (!phase)
|
|
298
|
+
return null;
|
|
299
|
+
const error = manualSummaryPhaseFailure(options, phase);
|
|
300
|
+
return error ? platformResult('failed', { resource: { kind: 'pull-request', number: knownPrNumber }, error: { ...error, retryable: false } }) : null;
|
|
301
|
+
}
|
|
302
|
+
function validateBoundPrSummaryInput(options, knownPrNumber) {
|
|
303
|
+
try {
|
|
304
|
+
return boundSummaryPhaseError(options, JSON.parse(options.body), knownPrNumber);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
return platformResult('failed', {
|
|
308
|
+
resource: { kind: 'pull-request', number: knownPrNumber },
|
|
309
|
+
error: { code: 'PR_SUMMARY_CONTENT_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function prepareSummarySync(taskRef, options) {
|
|
314
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
315
|
+
if (!resolved.ok)
|
|
316
|
+
return { ok: false, knownPrNumber: null, error: platformResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
|
|
317
|
+
const knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
|
|
318
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
319
|
+
const fact = readPrDeliveryFact(parseTypedTaskFrontmatter(taskContent), options.runtimeVersion);
|
|
320
|
+
if (fact.status === 'invalid')
|
|
321
|
+
return { ok: false, knownPrNumber, error: platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }) };
|
|
322
|
+
const isBound = fact.status === 'valid' && fact.fact.state === 'bound';
|
|
323
|
+
const contentError = isBound
|
|
324
|
+
? validateBoundPrSummaryInput(options, knownPrNumber)
|
|
325
|
+
: validateUnboundPrSummaryInput(options, taskContent, knownPrNumber);
|
|
326
|
+
if (contentError)
|
|
327
|
+
return { ok: false, knownPrNumber, error: contentError };
|
|
328
|
+
if (!options.changeReportFile && (options.dryRun || fact.status !== 'valid' || fact.fact.state !== 'bound'))
|
|
329
|
+
return {
|
|
330
|
+
ok: false, knownPrNumber, error: platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_MISSING', message: 'summary-sync requires --change-report-file', retryable: false } })
|
|
331
|
+
};
|
|
332
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
333
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
334
|
+
if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status))
|
|
335
|
+
return { ok: false, knownPrNumber, error: context };
|
|
336
|
+
return { ok: true, resolved, fact, knownPrNumber, loaded: loaded.value, context };
|
|
337
|
+
}
|
|
106
338
|
async function inspectBoundPullRequest(context, repoRoot, prIdentity, loadedContext) {
|
|
107
339
|
if (!context.platform.repository) {
|
|
108
340
|
return { ok: false, status: context.status, error: context.error };
|
|
@@ -348,7 +580,7 @@ async function reportWrite(taskRef, options) {
|
|
|
348
580
|
return { ...context, report: null };
|
|
349
581
|
let knownPrNumber = null;
|
|
350
582
|
try {
|
|
351
|
-
return await
|
|
583
|
+
return await (async () => {
|
|
352
584
|
const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
353
585
|
if (factError)
|
|
354
586
|
return {
|
|
@@ -433,22 +665,19 @@ async function reportWrite(taskRef, options) {
|
|
|
433
665
|
operations: [{ name: 'change-report:write', status: 'applied', reasonCode: null }],
|
|
434
666
|
report: { ...reportInfo, status: 'written' }
|
|
435
667
|
};
|
|
436
|
-
});
|
|
668
|
+
})();
|
|
437
669
|
}
|
|
438
670
|
catch (error) {
|
|
439
|
-
|
|
440
|
-
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
441
|
-
: { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
442
|
-
return { ...basePlatformResult(lockError.retryable ? 'blocked' : 'failed', context, resolved.taskId, knownPrNumber, lockError), report: null };
|
|
671
|
+
return { ...basePlatformResult('failed', context, resolved.taskId, knownPrNumber, { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
|
|
443
672
|
}
|
|
444
673
|
}
|
|
445
674
|
async function syncPullRequestSummary(taskRef, options) {
|
|
446
675
|
const warningResult = warningResultForPrimary(options.primaryResult);
|
|
447
676
|
let knownPrNumber = null;
|
|
448
|
-
const softenFailure = (output) => {
|
|
677
|
+
const softenFailure = (output, fallbackNumber = knownPrNumber) => {
|
|
449
678
|
const prNumber = output.resource.kind === 'pull-request' && output.resource.number
|
|
450
679
|
? output.resource.number
|
|
451
|
-
:
|
|
680
|
+
: fallbackNumber;
|
|
452
681
|
const warning = output.error && prNumber && output.error.code !== 'PR_NOT_LINKED'
|
|
453
682
|
? {
|
|
454
683
|
code: output.error.code,
|
|
@@ -471,282 +700,78 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
471
700
|
}
|
|
472
701
|
: { ...output, result: null, warnings: [] };
|
|
473
702
|
};
|
|
474
|
-
const preserveFailure = (output) => options.strict
|
|
703
|
+
const preserveFailure = (output, fallbackNumber = knownPrNumber) => options.strict
|
|
704
|
+
|| isHardSummaryFailure(output.error)
|
|
475
705
|
? { ...output, result: null, warnings: [] }
|
|
476
|
-
: softenFailure(output);
|
|
477
|
-
const
|
|
478
|
-
if (!
|
|
479
|
-
return
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
: { ...softenFailure(output) };
|
|
485
|
-
};
|
|
486
|
-
const frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
487
|
-
const fact = readPrDeliveryFact(frontmatter, options.runtimeVersion);
|
|
488
|
-
if (fact.status === 'invalid')
|
|
489
|
-
return preserveFailure(platformResult('failed', { error: { code: fact.error.code, message: fact.error.message, retryable: false } }));
|
|
490
|
-
if (!options.changeReportFile)
|
|
491
|
-
return preserveFailure(platformResult('failed', { error: { code: 'PR_CHANGE_REPORT_MISSING', message: 'summary-sync requires --change-report-file', retryable: false } }));
|
|
492
|
-
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
493
|
-
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
494
|
-
if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) {
|
|
495
|
-
// This diagnostic-only read preserves the known PR target when context resolution fails before the execution lock.
|
|
496
|
-
knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
|
|
497
|
-
return preserveFailure(context);
|
|
706
|
+
: softenFailure(output, fallbackNumber);
|
|
707
|
+
const prepared = await prepareSummarySync(taskRef, options);
|
|
708
|
+
if (!prepared.ok)
|
|
709
|
+
return preserveFailure(prepared.error, prepared.knownPrNumber);
|
|
710
|
+
const { resolved, fact, loaded, context } = prepared;
|
|
711
|
+
knownPrNumber = prepared.knownPrNumber;
|
|
712
|
+
if (fact.status === 'valid' && fact.fact.state === 'bound') {
|
|
713
|
+
return syncBoundPullRequestSummary(resolved, options, context, loaded, fact.fact.identity.resource, preserveFailure);
|
|
498
714
|
}
|
|
715
|
+
return preserveFailure(platformResult(options.dryRun ? 'blocked' : 'failed', { error: {
|
|
716
|
+
code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false
|
|
717
|
+
} }));
|
|
718
|
+
}
|
|
719
|
+
async function replayPullRequestSummary(taskId, operation, options) {
|
|
720
|
+
const intent = operation.pullRequestSummary;
|
|
721
|
+
if (!intent)
|
|
722
|
+
return platformResult('blocked', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary content is missing', retryable: false } });
|
|
723
|
+
let target;
|
|
499
724
|
try {
|
|
500
|
-
|
|
501
|
-
const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
502
|
-
if (factError)
|
|
503
|
-
return {
|
|
504
|
-
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: factError.code, message: factError.message, retryable: false } }),
|
|
505
|
-
result: null, warnings: []
|
|
506
|
-
};
|
|
507
|
-
if (!boundFact)
|
|
508
|
-
return {
|
|
509
|
-
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
|
|
510
|
-
result: null, warnings: []
|
|
511
|
-
};
|
|
512
|
-
const prIdentity = boundFact.identity.resource;
|
|
513
|
-
const boundPrNumber = resourceIdentityNumber(prIdentity);
|
|
514
|
-
knownPrNumber = boundPrNumber;
|
|
515
|
-
let prNumber = boundPrNumber;
|
|
516
|
-
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
517
|
-
if (!initial.ok)
|
|
518
|
-
return fail(initial.status, context, initial.error);
|
|
519
|
-
const manual = options.manualValidation;
|
|
520
|
-
let manualEvidenceDigest = null;
|
|
521
|
-
const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
|
|
522
|
-
if (hasFinalManualValidation && (!manual || manual.phase !== 'final'))
|
|
523
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
|
|
524
|
-
if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation))
|
|
525
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
|
|
526
|
-
if (manual) {
|
|
527
|
-
if (!Number.isSafeInteger(initial.value.number) || initial.value.number <= 0)
|
|
528
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_PR_NUMBER_REQUIRED', message: 'manual-validation summary requires a positive safe pull-request number from the authoritative snapshot', retryable: false }, prNumber);
|
|
529
|
-
prNumber = initial.value.number;
|
|
530
|
-
knownPrNumber = prNumber;
|
|
531
|
-
if (!manual.transactionId)
|
|
532
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'manual-validation summary requires a transaction ID', retryable: false }, prNumber);
|
|
533
|
-
if (manual.phase === 'pending') {
|
|
534
|
-
const transaction = readManualValidationTransaction(resolved.taskDir, {
|
|
535
|
-
transactionId: manual.transactionId,
|
|
536
|
-
taskId: resolved.taskId,
|
|
537
|
-
prNumber,
|
|
538
|
-
prHeadSha: initial.value.head.sha
|
|
539
|
-
});
|
|
540
|
-
if (!transaction.ok)
|
|
541
|
-
return fail('failed', context, platformError(transaction.error), prNumber);
|
|
542
|
-
}
|
|
543
|
-
if (manual.phase === 'final' && (!manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha))
|
|
544
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
|
|
545
|
-
if (manual.phase === 'final') {
|
|
546
|
-
if (manual.authority !== 'coordinator')
|
|
547
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires coordinator authority', retryable: false }, prNumber);
|
|
548
|
-
const completion = readManualValidationCompletion(resolved.taskDir, {
|
|
549
|
-
transactionId: manual.transactionId,
|
|
550
|
-
taskId: resolved.taskId,
|
|
551
|
-
prNumber,
|
|
552
|
-
prHeadSha: initial.value.head.sha,
|
|
553
|
-
receiptDigest: manual.receiptDigest
|
|
554
|
-
});
|
|
555
|
-
if (!completion.ok)
|
|
556
|
-
return fail('failed', context, platformError(completion.error), prNumber);
|
|
557
|
-
const { receipt, transaction } = completion.value;
|
|
558
|
-
manualEvidenceDigest = receipt.evidenceDigest;
|
|
559
|
-
if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended)
|
|
560
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
|
|
561
|
-
if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt))
|
|
562
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
const expectedReportPath = taskReportPath(resolved.taskDir);
|
|
566
|
-
const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile);
|
|
567
|
-
if (suppliedReportPath !== expectedReportPath)
|
|
568
|
-
return fail('failed', context, { code: 'PR_CHANGE_REPORT_PATH_INVALID', message: 'summary-sync must consume the task-bound pr-change-report.json', retryable: false });
|
|
569
|
-
const report = readPrChangeReport(suppliedReportPath);
|
|
570
|
-
if (!report.ok)
|
|
571
|
-
return fail('failed', context, platformError(report.error));
|
|
572
|
-
const checked = currentReportCheck(report.value, initial.value, taskContent, resolved.repoRoot);
|
|
573
|
-
if (!checked.ok)
|
|
574
|
-
return fail('failed', context, checked.error);
|
|
575
|
-
const placeholder = CANONICAL_REPORT_PLACEHOLDER;
|
|
576
|
-
const split = splitDocumentPlaceholder(options.body, placeholder);
|
|
577
|
-
if (!split.ok)
|
|
578
|
-
return fail('failed', context, {
|
|
579
|
-
code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
|
|
580
|
-
message: split.error.message,
|
|
581
|
-
retryable: false
|
|
582
|
-
});
|
|
583
|
-
const prefix = sanitizeMarkdownDocument(split.value.prefix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
584
|
-
const suffix = sanitizeMarkdownDocument(split.value.suffix, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
|
|
585
|
-
if (!prefix.ok) {
|
|
586
|
-
return fail('failed', context, {
|
|
587
|
-
code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
|
|
588
|
-
message: `${prefix.error.message} at offset ${prefix.error.offset}`,
|
|
589
|
-
retryable: false
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
if (!suffix.ok) {
|
|
593
|
-
return fail('failed', context, {
|
|
594
|
-
code: 'PR_SUMMARY_BODY_CONTRACT_INVALID',
|
|
595
|
-
message: `${suffix.error.message} at offset ${suffix.error.offset}`,
|
|
596
|
-
retryable: false
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
|
|
600
|
-
if (!replaced.ok)
|
|
601
|
-
return fail('failed', context, platformError(replaced.error));
|
|
602
|
-
const renderedBody = manual?.phase === 'final'
|
|
603
|
-
? replaced.value.replace(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`)
|
|
604
|
-
: replaced.value;
|
|
605
|
-
const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha);
|
|
606
|
-
if (!isSafeSummaryEnvelope(desired, resolved.taskId))
|
|
607
|
-
return fail('failed', context, {
|
|
608
|
-
code: 'PR_SUMMARY_RENDER_INVALID',
|
|
609
|
-
message: 'Summary contains an invalid or duplicated reserved control marker',
|
|
610
|
-
retryable: false
|
|
611
|
-
});
|
|
612
|
-
const listed = loaded.value.provider.comments?.list
|
|
613
|
-
? await loaded.value.provider.comments.list({ context: providerOperationContext(loaded.value), parent: prIdentity }).then((response) => response.ok
|
|
614
|
-
? { ok: true, value: response.value.map((comment) => ({ id: /^\d+$/.test(comment.id) ? Number(comment.id) : comment.id, body: comment.body })) }
|
|
615
|
-
: response)
|
|
616
|
-
: unsupportedProviderOperation(loaded.value.provider, 'comments.list');
|
|
617
|
-
if (!listed.ok)
|
|
618
|
-
return fail(providerStatus(listed.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'));
|
|
619
|
-
const reconciliation = reconcileSummaryComment(listed.value, resolved.taskId, desired);
|
|
620
|
-
if (reconciliation.action === 'conflict')
|
|
621
|
-
return fail('failed', context, { code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple PR comments contain the summary marker', retryable: false });
|
|
622
|
-
const previousBody = reconciliation.action === 'update'
|
|
623
|
-
? listed.value.find((comment) => comment.id === reconciliation.commentId)?.body ?? null
|
|
624
|
-
: null;
|
|
625
|
-
const info = { precheckVerdict: report.value.precheck.verdict, nextAction: report.value.precheck.route };
|
|
626
|
-
const beforeWrite = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
627
|
-
if (!beforeWrite.ok)
|
|
628
|
-
return fail(beforeWrite.status, context, beforeWrite.error, prNumber);
|
|
629
|
-
if (!sameIdentity(initial.value, beforeWrite.value))
|
|
630
|
-
return fail('blocked', context, {
|
|
631
|
-
code: 'PR_SUMMARY_HEAD_RACE',
|
|
632
|
-
message: 'Pull request head changed before publishing the summary',
|
|
633
|
-
retryable: true
|
|
634
|
-
}, prNumber);
|
|
635
|
-
if (reconciliation.action === 'no-op')
|
|
636
|
-
return {
|
|
637
|
-
...basePlatformResult('no-op', context, resolved.taskId, prNumber),
|
|
638
|
-
comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: [reconciliation.commentId], parts: 1 },
|
|
639
|
-
result: null, warnings: [], ...info
|
|
640
|
-
};
|
|
641
|
-
if (options.dryRun)
|
|
642
|
-
return {
|
|
643
|
-
...basePlatformResult('planned', context, resolved.taskId, prNumber),
|
|
644
|
-
operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }],
|
|
645
|
-
result: null, warnings: [], ...info
|
|
646
|
-
};
|
|
647
|
-
const publish = async () => {
|
|
648
|
-
const existingComment = reconciliation.commentId === null
|
|
649
|
-
? undefined
|
|
650
|
-
: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId));
|
|
651
|
-
const written = loaded.value.provider.comments?.write
|
|
652
|
-
? await loaded.value.provider.comments.write({
|
|
653
|
-
context: providerOperationContext(loaded.value),
|
|
654
|
-
parent: prIdentity,
|
|
655
|
-
body: desired,
|
|
656
|
-
...(existingComment ? { existingComment } : {}),
|
|
657
|
-
mutation: { idempotencyKey: `pr-summary:${resolved.taskId}` }
|
|
658
|
-
})
|
|
659
|
-
: unsupportedProviderOperation(loaded.value.provider, 'comments.write');
|
|
660
|
-
if (!written.ok)
|
|
661
|
-
return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
|
|
662
|
-
const after = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
663
|
-
const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
|
|
664
|
-
const compensateWrittenSummary = async () => reconciliation.action === 'create'
|
|
665
|
-
? written.value.remoteId && loaded.value.provider.comments?.delete
|
|
666
|
-
? await loaded.value.provider.comments.delete({
|
|
667
|
-
context: providerOperationContext(loaded.value),
|
|
668
|
-
parent: prIdentity,
|
|
669
|
-
comment: providerResourceToken(loaded.value.provider, 'comment', written.value.remoteId),
|
|
670
|
-
mutation: { idempotencyKey: `pr-summary-delete:${resolved.taskId}:${written.value.remoteId}` }
|
|
671
|
-
})
|
|
672
|
-
: { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Created summary comment has no valid id', retryable: true } }
|
|
673
|
-
: reconciliation.commentId && previousBody !== null && loaded.value.provider.comments?.write
|
|
674
|
-
? await loaded.value.provider.comments.write({
|
|
675
|
-
context: providerOperationContext(loaded.value),
|
|
676
|
-
parent: prIdentity,
|
|
677
|
-
body: previousBody,
|
|
678
|
-
existingComment: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId)),
|
|
679
|
-
mutation: { idempotencyKey: `pr-summary-restore:${resolved.taskId}` }
|
|
680
|
-
})
|
|
681
|
-
: { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Previous summary body is unavailable for restoration', retryable: true } };
|
|
682
|
-
if (!after.ok) {
|
|
683
|
-
const compensated = await compensateWrittenSummary();
|
|
684
|
-
if (!compensated.ok)
|
|
685
|
-
return fail('blocked', context, {
|
|
686
|
-
code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
|
|
687
|
-
message: `Summary post-write verification failed and compensation failed: ${compensated.error.message}`,
|
|
688
|
-
retryable: true
|
|
689
|
-
}, prNumber);
|
|
690
|
-
return fail('blocked', context, {
|
|
691
|
-
code: 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED',
|
|
692
|
-
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`,
|
|
693
|
-
retryable: true
|
|
694
|
-
}, prNumber);
|
|
695
|
-
}
|
|
696
|
-
if (!sameIdentity(initial.value, after.value)) {
|
|
697
|
-
const compensated = await compensateWrittenSummary();
|
|
698
|
-
if (!compensated.ok)
|
|
699
|
-
return fail('blocked', context, {
|
|
700
|
-
code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
|
|
701
|
-
message: `Pull-request head changed and summary compensation failed: ${compensated.error.message}`,
|
|
702
|
-
retryable: true
|
|
703
|
-
}, prNumber);
|
|
704
|
-
return fail('blocked', context, {
|
|
705
|
-
code: 'PR_SUMMARY_HEAD_RACE',
|
|
706
|
-
message: 'Pull-request head changed while publishing; the previous summary was restored or the new comment was deleted; rerun report and summary sync',
|
|
707
|
-
retryable: true
|
|
708
|
-
}, prNumber);
|
|
709
|
-
}
|
|
710
|
-
return {
|
|
711
|
-
...basePlatformResult('applied', context, resolved.taskId, prNumber),
|
|
712
|
-
comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 },
|
|
713
|
-
operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
|
|
714
|
-
result: null, warnings: [], ...info
|
|
715
|
-
};
|
|
716
|
-
};
|
|
717
|
-
const operation = {
|
|
718
|
-
taskRef: resolved.taskId,
|
|
719
|
-
cwd: resolved.repoRoot,
|
|
720
|
-
kind: 'pull-request-summary',
|
|
721
|
-
target: JSON.stringify(prIdentity),
|
|
722
|
-
expectedDigest: createHash('sha256').update(desired).digest('hex'),
|
|
723
|
-
dependency: 'deferred',
|
|
724
|
-
pullRequestSummary: {
|
|
725
|
-
body: options.body,
|
|
726
|
-
changeReportFile: options.changeReportFile,
|
|
727
|
-
...(manual?.transactionId ? { manualValidation: { ...manual, transactionId: manual.transactionId } } : {})
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
if (options.skipQueue)
|
|
731
|
-
return publish();
|
|
732
|
-
return coordinatePlatformWrite({
|
|
733
|
-
operation,
|
|
734
|
-
agent: options.agent,
|
|
735
|
-
execute: publish,
|
|
736
|
-
block: (error) => fail('blocked', context, error, prNumber),
|
|
737
|
-
persistenceFailure: (error) => fail('failed', context, error, prNumber)
|
|
738
|
-
});
|
|
739
|
-
};
|
|
740
|
-
return await (options.lockAlreadyHeld
|
|
741
|
-
? execute()
|
|
742
|
-
: withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
|
|
725
|
+
target = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity');
|
|
743
726
|
}
|
|
744
|
-
catch
|
|
745
|
-
|
|
746
|
-
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
747
|
-
: { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
748
|
-
return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
|
|
727
|
+
catch {
|
|
728
|
+
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } });
|
|
749
729
|
}
|
|
730
|
+
const bound = await inspectPlatformPullRequest(taskId, { cwd: options.cwd, client: options.client });
|
|
731
|
+
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, target))
|
|
732
|
+
return platformResult('blocked', {
|
|
733
|
+
resource: bound.pullRequest ? { kind: 'pull-request', number: bound.pullRequest.number, identity: bound.pullRequest.identity } : undefined,
|
|
734
|
+
error: bound.error ?? { code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false }
|
|
735
|
+
});
|
|
736
|
+
const replayed = await syncPullRequestSummary(taskId, {
|
|
737
|
+
agent: options.agent, cwd: options.cwd, client: options.client, body: intent.body,
|
|
738
|
+
changeReportFile: intent.changeReportFile, ...(intent.manualValidation ? { manualValidation: intent.manualValidation } : {}),
|
|
739
|
+
primaryResult: 'no_op', strict: true, queuedOperation: operation
|
|
740
|
+
});
|
|
741
|
+
return (replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0
|
|
742
|
+
? replayed
|
|
743
|
+
: platformResult('blocked', { resource: replayed.resource, error: replayed.error ?? {
|
|
744
|
+
code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary preparation or recovery did not confirm the frozen operation', retryable: true
|
|
745
|
+
} });
|
|
746
|
+
}
|
|
747
|
+
function correctPrSummaryContent(value, taskContent) {
|
|
748
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
749
|
+
return parsePrSummaryContent(value);
|
|
750
|
+
const input = { ...value };
|
|
751
|
+
const ledger = parseLedgerDocument(taskContent);
|
|
752
|
+
if (ledger.present) {
|
|
753
|
+
const decisions = ledger.rows.filter((row) => row.id.startsWith('HD-'));
|
|
754
|
+
const open = decisions.filter((row) => row.status === 'needs-human-decision').length;
|
|
755
|
+
const resolved = decisions.filter((row) => row.status === 'human-decided').length;
|
|
756
|
+
const current = input.humanDecisions && typeof input.humanDecisions === 'object' && !Array.isArray(input.humanDecisions)
|
|
757
|
+
? { ...input.humanDecisions }
|
|
758
|
+
: {};
|
|
759
|
+
if (!Number.isSafeInteger(current.open) || Number(current.open) < 0)
|
|
760
|
+
current.open = open;
|
|
761
|
+
if (!Number.isSafeInteger(current.resolved) || Number(current.resolved) < 0)
|
|
762
|
+
current.resolved = resolved;
|
|
763
|
+
if (typeof current.detail !== 'string' || !current.detail.trim())
|
|
764
|
+
current.detail = `Task decision ledger: ${open} open, ${resolved} resolved.`;
|
|
765
|
+
input.humanDecisions = current;
|
|
766
|
+
}
|
|
767
|
+
return input;
|
|
768
|
+
}
|
|
769
|
+
export function prepareAndRenderPrSummaryContent(value, taskContent) {
|
|
770
|
+
return runSummaryContentPipeline(value, {
|
|
771
|
+
validate: parsePrSummaryContent,
|
|
772
|
+
correct: (input) => correctPrSummaryContent(input, taskContent),
|
|
773
|
+
render: renderPrSummaryContent
|
|
774
|
+
});
|
|
750
775
|
}
|
|
751
|
-
export { buildPullRequestSummary,
|
|
776
|
+
export { buildPullRequestSummary, reportWrite, summaryCommentState, summaryContext, summaryMarker, syncPullRequestSummary, replayPullRequestSummary, warningResultForPrimary };
|
|
752
777
|
//# sourceMappingURL=pr-summary.js.map
|