@fitlab-ai/agent-infra 0.12.0 → 0.12.2
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 +0 -2
- package/README.zh-CN.md +0 -2
- package/dist/bin/internal-cli.js +2 -17
- package/dist/lib/agent-clients/adapter.js +0 -13
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
- package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
- package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
- package/dist/lib/agent-clients/adapters/codex.js +0 -52
- package/dist/lib/agent-clients/adapters/opencode.js +0 -5
- package/dist/lib/agent-clients/adapters/traecli.js +0 -5
- package/dist/lib/agent-clients/config.js +19 -54
- package/dist/lib/agent-clients/registry.js +1 -12
- package/dist/lib/agent-clients/schema.js +0 -18
- package/dist/lib/agent-clients/tokens.js +1 -2
- package/dist/lib/agent-clients/types.js +0 -1
- package/dist/lib/decide.js +2 -0
- package/dist/lib/git/worktree-identity.js +1 -9
- package/dist/lib/internal/agent-client.js +2 -62
- package/dist/lib/internal/cli-route-inventory.js +1 -5
- package/dist/lib/internal/manual-validation.js +29 -27
- package/dist/lib/internal/platform-comment.js +56 -7
- package/dist/lib/internal/task-create.js +1 -1
- package/dist/lib/internal/task-event.js +1 -8
- package/dist/lib/internal/task-finalization.js +0 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +4 -20
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/comment-safety.js +1 -7
- package/dist/lib/platform/in-label-sync.js +1 -14
- package/dist/lib/platform/issue-comments.js +373 -282
- package/dist/lib/platform/issue-summary-content.js +112 -0
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/operation-coordinator.js +4 -2
- package/dist/lib/platform/pr-change-report.js +176 -30
- package/dist/lib/platform/pr-checks.js +2 -8
- package/dist/lib/platform/pr-summary-content.js +54 -0
- package/dist/lib/platform/pr-summary.js +318 -284
- package/dist/lib/platform/provider-validation.js +0 -20
- package/dist/lib/platform/pull-requests.js +73 -84
- package/dist/lib/platform/releases.js +1 -43
- package/dist/lib/platform/summary-content-pipeline.js +18 -0
- package/dist/lib/platform/verification-required.js +0 -2
- package/dist/lib/platform/verification-sync.js +1 -79
- package/dist/lib/process-data/normalize.js +1 -185
- package/dist/lib/process-data/privacy.js +1 -16
- package/dist/lib/process-data/sources.js +2 -279
- package/dist/lib/render.js +0 -12
- package/dist/lib/run/index.js +15 -10
- package/dist/lib/run/skills.js +0 -3
- package/dist/lib/sandbox/commands/create.js +40 -53
- package/dist/lib/sandbox/commands/list-running.js +0 -18
- package/dist/lib/sandbox/commands/start.js +7 -8
- package/dist/lib/sandbox/constants.js +0 -6
- package/dist/lib/sandbox/control/client.js +0 -1
- package/dist/lib/sandbox/control/container-identity.js +1 -16
- package/dist/lib/sandbox/control/executor.js +0 -4
- package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
- package/dist/lib/sandbox/control/lifecycle.js +21 -37
- package/dist/lib/sandbox/control/native-file-lock.js +0 -3
- package/dist/lib/sandbox/control/server.js +18 -38
- package/dist/lib/sandbox/control/state.js +1 -9
- package/dist/lib/sandbox/dockerfile.js +0 -6
- package/dist/lib/sandbox/engine.js +0 -14
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
- package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
- package/dist/lib/sandbox/recovery.js +24 -26
- package/dist/lib/sandbox/removal.js +67 -518
- package/dist/lib/sandbox/task-resolver.js +0 -41
- package/dist/lib/sandbox/tools.js +1 -4
- package/dist/lib/sandbox/workspace-view.js +1 -2
- package/dist/lib/server/display.js +0 -6
- package/dist/lib/task/activity-intent.js +45 -40
- package/dist/lib/task/activity-log.js +9 -16
- package/dist/lib/task/artifact-command.js +1 -1
- package/dist/lib/task/artifact-lifecycle.js +61 -63
- package/dist/lib/task/artifact-name.js +0 -6
- package/dist/lib/task/artifact-operations.js +3 -3
- package/dist/lib/task/capabilities.js +4 -5
- package/dist/lib/task/commands/log.js +0 -6
- package/dist/lib/task/commands/status.js +17 -60
- package/dist/lib/task/commit-operation.js +28 -39
- package/dist/lib/task/completion-facts.js +56 -43
- package/dist/lib/task/control-authority.js +23 -256
- package/dist/lib/task/control-recovery.js +3 -5
- package/dist/lib/task/create.js +2 -4
- package/dist/lib/task/decision-intents.js +8 -9
- package/dist/lib/task/delivery-target.js +1 -40
- package/dist/lib/task/events.js +84 -107
- package/dist/lib/task/finalization.js +60 -15
- package/dist/lib/task/ledger-intents.js +1 -1
- package/dist/lib/task/lifecycle-execution.js +38 -33
- package/dist/lib/task/lifecycle.js +49 -41
- package/dist/lib/task/local-artifact-finalization.js +1 -4
- package/dist/lib/task/manual-validation-evidence.js +1 -37
- package/dist/lib/task/platform-operation-recovery.js +151 -108
- package/dist/lib/task/pr-delivery-fact.js +2 -5
- package/dist/lib/task/qualification-confirmation.js +5 -7
- package/dist/lib/task/resolve-ref.js +1 -25
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +1 -21
- package/dist/lib/task/review-fingerprint.js +0 -4
- package/dist/lib/task/secure-fs.js +0 -56
- package/dist/lib/task/short-id.js +1 -39
- package/dist/lib/task/skill-scope.js +0 -1
- package/dist/lib/task/verification-engine.js +58 -282
- package/dist/lib/task/verification.js +6 -3
- package/dist/lib/task/write.js +11 -1
- package/lib/agent-clients/adapter.ts +0 -76
- package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
- package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
- package/lib/agent-clients/adapters/claude-code.ts +0 -9
- package/lib/agent-clients/adapters/codex.ts +0 -56
- package/lib/agent-clients/adapters/opencode.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +0 -5
- package/lib/agent-clients/config.ts +20 -61
- package/lib/agent-clients/registry.ts +1 -34
- package/lib/agent-clients/schema.ts +0 -18
- package/lib/agent-clients/tokens.ts +1 -2
- package/lib/agent-clients/types.ts +0 -15
- package/lib/decide.ts +1 -0
- package/lib/git/worktree-identity.ts +0 -10
- package/lib/internal/agent-client.ts +2 -70
- package/lib/internal/cli-route-inventory.ts +1 -4
- package/lib/internal/manual-validation.ts +28 -27
- package/lib/internal/platform-comment.ts +46 -4
- package/lib/internal/task-create.ts +0 -1
- package/lib/internal/task-event.ts +1 -5
- package/lib/internal/task-finalization.ts +0 -1
- package/lib/internal/task-lifecycle.ts +3 -5
- package/lib/internal/task-operation-registry.ts +3 -19
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/comment-safety.ts +0 -10
- package/lib/platform/github-provider.ts +0 -2
- package/lib/platform/in-label-sync.ts +0 -16
- package/lib/platform/issue-comments.ts +478 -301
- package/lib/platform/issue-summary-content.ts +132 -0
- package/lib/platform/issues.ts +0 -2
- package/lib/platform/operation-coordinator.ts +4 -2
- package/lib/platform/pr-change-report.ts +165 -25
- package/lib/platform/pr-checks.ts +1 -10
- package/lib/platform/pr-review.ts +1 -1
- package/lib/platform/pr-summary-content.ts +60 -0
- package/lib/platform/pr-summary.ts +364 -257
- package/lib/platform/provider-bridge.ts +0 -1
- package/lib/platform/provider-loader.ts +1 -5
- package/lib/platform/provider-validation.ts +1 -49
- package/lib/platform/pull-requests.ts +64 -99
- package/lib/platform/releases.ts +1 -40
- package/lib/platform/summary-content-pipeline.ts +23 -0
- package/lib/platform/verification-required.ts +0 -2
- package/lib/platform/verification-sync.ts +0 -94
- package/lib/process-data/normalize.ts +1 -186
- package/lib/process-data/privacy.ts +2 -19
- package/lib/process-data/sources.ts +3 -283
- package/lib/render.ts +0 -15
- package/lib/run/index.ts +13 -7
- package/lib/run/skills.ts +0 -4
- package/lib/sandbox/commands/create.ts +37 -74
- package/lib/sandbox/commands/list-running.ts +0 -19
- package/lib/sandbox/commands/start.ts +7 -10
- package/lib/sandbox/constants.ts +0 -8
- package/lib/sandbox/control/client.ts +0 -1
- package/lib/sandbox/control/container-identity.ts +1 -16
- package/lib/sandbox/control/executor.ts +1 -5
- package/lib/sandbox/control/identity-sentinel.ts +0 -8
- package/lib/sandbox/control/lifecycle.ts +18 -51
- package/lib/sandbox/control/native-file-lock.ts +0 -8
- package/lib/sandbox/control/protocol.ts +0 -2
- package/lib/sandbox/control/server.ts +13 -39
- package/lib/sandbox/control/state.ts +1 -10
- package/lib/sandbox/dockerfile.ts +0 -11
- package/lib/sandbox/engine.ts +0 -17
- package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
- package/lib/sandbox/engines/wsl2-paths.ts +0 -4
- package/lib/sandbox/recovery.ts +24 -26
- package/lib/sandbox/removal.ts +58 -687
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/task-resolver.ts +0 -48
- package/lib/sandbox/tools.ts +1 -5
- package/lib/sandbox/workspace-view.ts +2 -4
- package/lib/server/display.ts +0 -14
- package/lib/server/process-control.ts +1 -1
- package/lib/task/activity-intent.ts +36 -44
- package/lib/task/activity-log.ts +9 -22
- package/lib/task/artifact-command.ts +1 -1
- package/lib/task/artifact-lifecycle.ts +58 -67
- package/lib/task/artifact-name.ts +0 -7
- package/lib/task/artifact-operations.ts +3 -3
- package/lib/task/capabilities.ts +5 -6
- package/lib/task/commands/log.ts +0 -7
- package/lib/task/commands/status.ts +14 -79
- package/lib/task/commit-operation.ts +29 -51
- package/lib/task/completion-facts.ts +45 -42
- package/lib/task/control-authority.ts +27 -317
- package/lib/task/control-recovery.ts +2 -5
- package/lib/task/create.ts +2 -5
- package/lib/task/decision-intents.ts +5 -5
- package/lib/task/delivery-target.ts +0 -40
- package/lib/task/events.ts +89 -123
- package/lib/task/finalization.ts +52 -15
- package/lib/task/ledger-intents.ts +1 -1
- package/lib/task/lifecycle-execution.ts +37 -89
- package/lib/task/lifecycle.ts +48 -44
- package/lib/task/local-artifact-finalization.ts +0 -7
- package/lib/task/manual-validation-evidence.ts +1 -47
- package/lib/task/platform-operation-recovery.ts +117 -104
- package/lib/task/pr-delivery-fact.ts +2 -11
- package/lib/task/qualification-confirmation.ts +6 -6
- package/lib/task/resolve-ref.ts +0 -23
- package/lib/task/review-command.ts +2 -2
- package/lib/task/review-finalization.ts +1 -27
- package/lib/task/review-fingerprint.ts +0 -8
- package/lib/task/secure-fs.ts +0 -42
- package/lib/task/short-id.ts +0 -43
- package/lib/task/skill-scope.ts +0 -1
- package/lib/task/verification-engine.ts +56 -287
- package/lib/task/verification.ts +5 -5
- package/lib/task/write.ts +12 -1
- package/package.json +2 -2
- package/templates/.agents/README.en.md +1 -3
- package/templates/.agents/README.zh-CN.md +0 -2
- package/templates/.agents/rules/README.en.md +0 -1
- package/templates/.agents/rules/README.zh-CN.md +0 -1
- package/templates/.agents/rules/commit-and-pr.en.md +1 -1
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
- package/templates/.agents/rules/pr-sync.en.md +4 -2
- package/templates/.agents/rules/pr-sync.zh-CN.md +6 -9
- package/templates/.agents/rules/task-management.en.md +3 -3
- package/templates/.agents/rules/task-management.zh-CN.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/commit/SKILL.en.md +2 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +10 -25
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -26
- package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -2
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +0 -18
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +0 -18
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/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 +5 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
- 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/review-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
- package/templates/.agents/skills/watch-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -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/task.en.md +1 -0
- package/templates/.agents/templates/task.zh-CN.md +1 -0
- package/templates/.claude/settings.json +0 -24
- package/templates/.codex/hooks.json +0 -50
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
- package/dist/lib/internal/task-orchestration.js +0 -71
- package/dist/lib/platform/verification.js +0 -6
- package/dist/lib/run/prompt.js +0 -2
- package/dist/lib/sandbox/engines/authority.js +0 -289
- package/dist/lib/task/delegation-receipts.js +0 -410
- package/dist/lib/task/orchestration.js +0 -1233
- package/dist/lib/task/verification-checks.js +0 -9
- package/dist/lib/task/workspace-snapshot.js +0 -121
- package/dist/lifecycle-build-manifest.json +0 -6
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
- package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
- package/lib/internal/task-orchestration.ts +0 -74
- package/lib/platform/verification.ts +0 -11
- package/lib/run/prompt.ts +0 -1
- package/lib/sandbox/engines/authority.ts +0 -365
- package/lib/task/delegation-receipts.ts +0 -547
- package/lib/task/orchestration.ts +0 -1651
- package/lib/task/verification-checks.ts +0 -10
- package/lib/task/workspace-snapshot.ts +0 -144
- package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
- package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
- package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
- package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
- package/templates/.claude/commands/run-task.en.md +0 -8
- package/templates/.claude/commands/run-task.zh-CN.md +0 -8
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
- package/templates/.opencode/commands/run-task.en.md +0 -11
- package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
|
@@ -2,13 +2,15 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import { inspectPlatformCommentOperation,
|
|
5
|
+
import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.js';
|
|
6
|
+
import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.js';
|
|
7
|
+
import { enumerateArtifacts } from './artifacts.js';
|
|
6
8
|
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
+
import '../platform/context.js';
|
|
10
|
+
import '../platform/provider-bridge.js';
|
|
9
11
|
import { inLabelMappingDigest } from '../platform/in-label-sync.js';
|
|
10
|
-
import { bindPlatformPullRequest,
|
|
11
|
-
import {
|
|
12
|
+
import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.js';
|
|
13
|
+
import { replayPullRequestSummary } from '../platform/pr-summary.js';
|
|
12
14
|
import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.js';
|
|
13
15
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
14
16
|
import { taskIssueIdentity } from '../platform/task-identities.js';
|
|
@@ -16,9 +18,10 @@ import { parseResourceIdentity, resourceIdentityEquals } from '../platform/resou
|
|
|
16
18
|
import { operationId } from './platform-operation-journal.js';
|
|
17
19
|
import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
18
20
|
import { platformResult } from '../platform/types.js';
|
|
21
|
+
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
19
22
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
20
23
|
import { readPlatformOperationJournal, recordPlatformOperation } from './platform-operation-journal.js';
|
|
21
|
-
import
|
|
24
|
+
import '../platform/provider-validation.js';
|
|
22
25
|
const DEFAULT_RECOVERY_ATTEMPTS = 3;
|
|
23
26
|
function labelsMatchOwnedPrefix(actual, expected, prefix) {
|
|
24
27
|
return actual.filter((label) => label.startsWith(prefix)).sort().join('\0')
|
|
@@ -27,40 +30,78 @@ function labelsMatchOwnedPrefix(actual, expected, prefix) {
|
|
|
27
30
|
function fieldsMatchExpected(actual, expected) {
|
|
28
31
|
return Object.entries(expected).every(([name, value]) => actual[name] === value);
|
|
29
32
|
}
|
|
30
|
-
function result(status, recovered, pending, error = null) {
|
|
31
|
-
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null };
|
|
33
|
+
function result(status, recovered, pending, error = null, warnings = []) {
|
|
34
|
+
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null, ...(warnings.length ? { warnings } : {}) };
|
|
32
35
|
}
|
|
33
36
|
function summaryPayload(taskDir, taskId) {
|
|
34
37
|
const file = path.join(taskDir, '.delivery-summary.json');
|
|
35
38
|
if (!fs.existsSync(file))
|
|
36
39
|
return null;
|
|
37
40
|
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
let content;
|
|
42
|
+
try {
|
|
43
|
+
content = parseDeliverySummaryContent(data.content);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
content = null;
|
|
47
|
+
}
|
|
48
|
+
if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
|
|
49
|
+
|| createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
|
|
42
50
|
throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
43
51
|
}
|
|
44
|
-
|
|
52
|
+
const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
|
|
53
|
+
if (!canonical?.ok)
|
|
54
|
+
throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
55
|
+
return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
|
|
56
|
+
}
|
|
57
|
+
function cancellationPayload(taskDir, taskId) {
|
|
58
|
+
const taskPath = path.join(taskDir, 'task.md');
|
|
59
|
+
if (!fs.existsSync(taskPath))
|
|
60
|
+
return null;
|
|
61
|
+
const taskContent = fs.readFileSync(taskPath, 'utf8');
|
|
62
|
+
const frontmatter = parseTaskFrontmatter(taskContent);
|
|
63
|
+
const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
|
|
64
|
+
const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
|
|
65
|
+
const content = parseCancelSummaryContent({
|
|
66
|
+
taskId,
|
|
67
|
+
title,
|
|
68
|
+
outcome: 'cancelled',
|
|
69
|
+
reason: frontmatter.cancel_reason,
|
|
70
|
+
cancelledAt: frontmatter.cancelled_at,
|
|
71
|
+
lastCompletedArtifact: artifacts.at(-1) ?? null
|
|
72
|
+
});
|
|
73
|
+
return renderCancelSummaryContent(content);
|
|
45
74
|
}
|
|
46
75
|
async function replayComment(taskId, taskDir, operation, agent, cwd, client) {
|
|
47
76
|
if (operation.kind === 'task-comment') {
|
|
48
|
-
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency,
|
|
77
|
+
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
49
78
|
}
|
|
50
79
|
if (operation.kind === 'artifact-comment') {
|
|
51
|
-
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency,
|
|
80
|
+
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
52
81
|
}
|
|
53
82
|
if (operation.kind === 'summary-comment') {
|
|
54
|
-
|
|
83
|
+
let summary;
|
|
84
|
+
try {
|
|
85
|
+
summary = summaryPayload(taskDir, taskId);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } });
|
|
89
|
+
}
|
|
55
90
|
if (!summary)
|
|
56
|
-
return platformResult('
|
|
57
|
-
return syncPlatformComment(taskId, {
|
|
58
|
-
kind: 'summary', body: summary.body, agent, cwd,
|
|
59
|
-
summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, client, skipQueue: true
|
|
60
|
-
});
|
|
91
|
+
return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
|
|
92
|
+
return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
|
|
61
93
|
}
|
|
62
94
|
if (operation.kind === 'cancel-comment') {
|
|
63
|
-
|
|
95
|
+
let body;
|
|
96
|
+
try {
|
|
97
|
+
body = cancellationPayload(taskDir, taskId);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } });
|
|
101
|
+
}
|
|
102
|
+
if (!body)
|
|
103
|
+
return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
|
|
104
|
+
return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
64
105
|
}
|
|
65
106
|
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
|
|
66
107
|
}
|
|
@@ -130,7 +171,8 @@ async function replayIssueMetadata(taskId, operation, agent, cwd, client) {
|
|
|
130
171
|
}
|
|
131
172
|
const issue = after.issue;
|
|
132
173
|
const metadata = operation.issueMetadata;
|
|
133
|
-
const failedOperation = sync.operations.find((item) => item.status === 'failed' || item.status === 'skipped'
|
|
174
|
+
const failedOperation = sync.operations.find((item) => item.status === 'failed' || (item.status === 'skipped'
|
|
175
|
+
&& !(sync.capabilities.push === false && ['fields', 'issue-type'].includes(item.name) && item.reasonCode === 'PUSH_REQUIRED')));
|
|
134
176
|
if (failedOperation)
|
|
135
177
|
return platformResult('blocked', { error: {
|
|
136
178
|
code: failedOperation.reasonCode || 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
|
|
@@ -164,7 +206,7 @@ async function replayIssueMetadata(taskId, operation, agent, cwd, client) {
|
|
|
164
206
|
code: 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
|
|
165
207
|
message: 'Issue metadata differs from the requested recovery target after reread', retryable: true
|
|
166
208
|
} });
|
|
167
|
-
return platformResult('no-op', { changed: sync.changed || after.changed });
|
|
209
|
+
return platformResult('no-op', { changed: sync.changed || after.changed, operations: sync.operations });
|
|
168
210
|
}
|
|
169
211
|
async function verifyIssueCreate(taskId, operation, cwd, client) {
|
|
170
212
|
if (!operation.issueCreate)
|
|
@@ -185,64 +227,6 @@ async function verifyIssueCreate(taskId, operation, cwd, client) {
|
|
|
185
227
|
} });
|
|
186
228
|
return platformResult('no-op');
|
|
187
229
|
}
|
|
188
|
-
async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
189
|
-
let identity;
|
|
190
|
-
try {
|
|
191
|
-
identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity');
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } });
|
|
195
|
-
}
|
|
196
|
-
const summary = operation.pullRequestSummary;
|
|
197
|
-
if (!summary)
|
|
198
|
-
return platformResult('blocked', { error: {
|
|
199
|
-
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
200
|
-
} });
|
|
201
|
-
const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
|
|
202
|
-
if (manualValidationHeading && !summary.manualValidation)
|
|
203
|
-
return platformResult('blocked', { error: {
|
|
204
|
-
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
|
|
205
|
-
} });
|
|
206
|
-
const resolved = await resolvePlatformProviderContext({ cwd, client });
|
|
207
|
-
if (!resolved.ok)
|
|
208
|
-
return resolved.context;
|
|
209
|
-
const listed = resolved.value.provider.comments?.list
|
|
210
|
-
? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
|
|
211
|
-
: unsupportedProviderOperation(resolved.value.provider, 'comments.list');
|
|
212
|
-
if (!listed.ok)
|
|
213
|
-
return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
|
|
214
|
-
const marker = `<!-- sync-pr:${taskId}:summary -->`;
|
|
215
|
-
const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
|
|
216
|
-
if (summaries.length > 1)
|
|
217
|
-
return platformResult('blocked', { error: {
|
|
218
|
-
code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
|
|
219
|
-
} });
|
|
220
|
-
if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0].body)).digest('hex') === operation.expectedDigest) {
|
|
221
|
-
return platformResult('no-op');
|
|
222
|
-
}
|
|
223
|
-
const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
|
|
224
|
-
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity))
|
|
225
|
-
return platformResult('blocked', { error: bound.error ?? {
|
|
226
|
-
code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
|
|
227
|
-
} });
|
|
228
|
-
const replayed = await syncPullRequestSummary(taskId, {
|
|
229
|
-
agent,
|
|
230
|
-
cwd,
|
|
231
|
-
client,
|
|
232
|
-
body: summary.body,
|
|
233
|
-
changeReportFile: summary.changeReportFile,
|
|
234
|
-
...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
|
|
235
|
-
primaryResult: 'no_op',
|
|
236
|
-
strict: true,
|
|
237
|
-
skipQueue: true
|
|
238
|
-
});
|
|
239
|
-
if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
|
|
240
|
-
return platformResult(replayed.status, { changed: replayed.changed });
|
|
241
|
-
}
|
|
242
|
-
return platformResult('blocked', { error: replayed.error ?? {
|
|
243
|
-
code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
|
|
244
|
-
} });
|
|
245
|
-
}
|
|
246
230
|
async function replayPullRequestReview(operation, agent, cwd, client) {
|
|
247
231
|
const intent = operation.pullRequestReview;
|
|
248
232
|
if (!intent)
|
|
@@ -301,6 +285,20 @@ async function replayPullRequest(taskId, operation, agent, cwd) {
|
|
|
301
285
|
}
|
|
302
286
|
return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
|
|
303
287
|
}
|
|
288
|
+
function isRecoveryEligible(operation, excludeId) {
|
|
289
|
+
if (operation.id === excludeId)
|
|
290
|
+
return false;
|
|
291
|
+
if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown')
|
|
292
|
+
return true;
|
|
293
|
+
return operation.state === 'failed'
|
|
294
|
+
&& operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED';
|
|
295
|
+
}
|
|
296
|
+
function recoveryCandidates(journal, options) {
|
|
297
|
+
const boundary = options.beforeId ? journal.operations.findIndex((item) => item.id === options.beforeId) : journal.operations.length;
|
|
298
|
+
if (boundary < 0)
|
|
299
|
+
throw Object.assign(new Error('Current platform operation boundary is unavailable'), { code: 'PLATFORM_OPERATION_BOUNDARY_MISSING' });
|
|
300
|
+
return journal.operations.slice(0, boundary).filter((item) => isRecoveryEligible(item, options.excludeId));
|
|
301
|
+
}
|
|
304
302
|
async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
305
303
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
306
304
|
if (!resolved.ok)
|
|
@@ -313,9 +311,14 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
|
313
311
|
const value = error;
|
|
314
312
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
315
313
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
let candidates;
|
|
315
|
+
try {
|
|
316
|
+
candidates = recoveryCandidates(journal, options);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
const value = error;
|
|
320
|
+
return result('blocked', [], [], value);
|
|
321
|
+
}
|
|
319
322
|
if (!candidates.length)
|
|
320
323
|
return result('no-op', [], []);
|
|
321
324
|
const head = candidates[0];
|
|
@@ -329,8 +332,9 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
|
329
332
|
const limit = options.limit === undefined ? candidates.length : Math.max(1, options.limit);
|
|
330
333
|
const recovered = [];
|
|
331
334
|
const pending = [];
|
|
335
|
+
let warnings = [];
|
|
332
336
|
const selected = candidates.slice(0, limit);
|
|
333
|
-
for (const
|
|
337
|
+
for (const operation of selected) {
|
|
334
338
|
{
|
|
335
339
|
try {
|
|
336
340
|
recordPlatformOperation({
|
|
@@ -382,9 +386,22 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
|
382
386
|
: operation.kind === 'artifact-comment'
|
|
383
387
|
? { kind: 'artifact', artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
384
388
|
: operation.kind === 'summary-comment' ? (() => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
389
|
+
try {
|
|
390
|
+
const summary = summaryPayload(resolved.taskDir, resolved.taskId);
|
|
391
|
+
return summary ? { kind: 'summary', body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
})() : (() => {
|
|
397
|
+
try {
|
|
398
|
+
const body = cancellationPayload(resolved.taskDir, resolved.taskId);
|
|
399
|
+
return body ? { kind: 'cancel', body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
})();
|
|
388
405
|
const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
|
|
389
406
|
if (!current) {
|
|
390
407
|
pending.push(operation.id);
|
|
@@ -407,20 +424,21 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
|
407
424
|
remote = operation.kind === 'issue-create'
|
|
408
425
|
? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
|
|
409
426
|
: operation.kind === 'pull-request-summary'
|
|
410
|
-
? await replayPullRequestSummary(resolved.taskId,
|
|
427
|
+
? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
|
|
411
428
|
: operation.kind === 'pull-request-review'
|
|
412
429
|
? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
|
|
413
430
|
: operation.kind === 'issue-metadata'
|
|
414
431
|
? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
415
432
|
: operation.kind === 'pull-request'
|
|
416
433
|
? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
417
|
-
: await replayComment(resolved.taskId, resolved.taskDir,
|
|
434
|
+
: await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
|
|
418
435
|
}
|
|
419
436
|
catch (error) {
|
|
420
437
|
const value = error;
|
|
421
438
|
remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
|
|
422
439
|
}
|
|
423
440
|
const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
|
|
441
|
+
const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
|
|
424
442
|
try {
|
|
425
443
|
recordPlatformOperation({
|
|
426
444
|
taskRef: resolved.taskId,
|
|
@@ -435,7 +453,7 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
|
435
453
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
436
454
|
dependency: operation.dependency,
|
|
437
455
|
state: succeeded ? 'succeeded' : 'unknown',
|
|
438
|
-
lastCode: remote.error?.code ?? null
|
|
456
|
+
lastCode: remote.error?.code ?? remote.operations.find((item) => item.status === 'skipped' && item.reasonCode === 'PUSH_REQUIRED')?.reasonCode ?? (succeeded ? null : latestOperation?.lastCode) ?? null
|
|
439
457
|
});
|
|
440
458
|
}
|
|
441
459
|
catch (error) {
|
|
@@ -446,11 +464,12 @@ async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
|
446
464
|
pending.push(targetOperation.id);
|
|
447
465
|
return result(remote.status === 'failed' ? 'failed' : 'blocked', recovered, pending, remote.error ?? { code: 'PLATFORM_OPERATION_RECOVERY_UNKNOWN', message: 'Platform operation outcome remains unknown', retryable: true });
|
|
448
466
|
}
|
|
467
|
+
warnings = mergeOperationWarnings(warnings, remote.warnings ?? []);
|
|
449
468
|
recovered.push(targetOperation.id);
|
|
450
469
|
}
|
|
451
470
|
const remaining = candidates.slice(limit).map((operation) => operation.id);
|
|
452
471
|
pending.push(...remaining);
|
|
453
|
-
return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null);
|
|
472
|
+
return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null, warnings);
|
|
454
473
|
}
|
|
455
474
|
async function recoverPlatformOperations(taskRef, selection, options) {
|
|
456
475
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
@@ -464,10 +483,14 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
464
483
|
const value = error;
|
|
465
484
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
466
485
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
486
|
+
let first;
|
|
487
|
+
try {
|
|
488
|
+
first = recoveryCandidates(initial, options);
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
const value = error;
|
|
492
|
+
return result('blocked', [], [], value);
|
|
493
|
+
}
|
|
471
494
|
if (!first.length)
|
|
472
495
|
return result('no-op', [], []);
|
|
473
496
|
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|
|
@@ -476,6 +499,7 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
476
499
|
return result('failed', [], [], { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'Recovery attempts must be a positive safe integer', retryable: false });
|
|
477
500
|
}
|
|
478
501
|
const recovered = [];
|
|
502
|
+
let warnings = [];
|
|
479
503
|
for (let operationIndex = 0; operationIndex < operationLimit; operationIndex += 1) {
|
|
480
504
|
let latest;
|
|
481
505
|
try {
|
|
@@ -483,23 +507,32 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
483
507
|
}
|
|
484
508
|
catch (error) {
|
|
485
509
|
const value = error;
|
|
486
|
-
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
510
|
+
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false }, warnings);
|
|
511
|
+
}
|
|
512
|
+
let candidates;
|
|
513
|
+
try {
|
|
514
|
+
candidates = recoveryCandidates(latest, options);
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const value = error;
|
|
518
|
+
return result('blocked', recovered, [], value, warnings);
|
|
487
519
|
}
|
|
488
|
-
const head =
|
|
520
|
+
const head = candidates[0];
|
|
489
521
|
if (!head)
|
|
490
522
|
break;
|
|
491
523
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
492
|
-
const pending =
|
|
524
|
+
const pending = candidates.map((operation) => operation.id);
|
|
493
525
|
return result('blocked', recovered, pending, {
|
|
494
526
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
495
527
|
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
496
528
|
retryable: true
|
|
497
|
-
});
|
|
529
|
+
}, warnings);
|
|
498
530
|
}
|
|
499
531
|
let last = null;
|
|
500
532
|
for (let attempt = 0; attempt < attemptLimit; attempt += 1) {
|
|
501
533
|
last = await recoverPlatformOperationsOnce(taskRef, selection, { ...options, limit: 1 });
|
|
502
534
|
recovered.push(...last.recovered.filter((id) => !recovered.includes(id)));
|
|
535
|
+
warnings = mergeOperationWarnings(warnings, last.warnings ?? []);
|
|
503
536
|
if (last.recovered.length > 0 || last.status === 'applied' || last.status === 'no-op')
|
|
504
537
|
break;
|
|
505
538
|
}
|
|
@@ -509,24 +542,34 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
509
542
|
current = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
510
543
|
}
|
|
511
544
|
catch {
|
|
512
|
-
return result('blocked', recovered, [head.id], last?.error ?? null);
|
|
545
|
+
return result('blocked', recovered, [head.id], last?.error ?? null, warnings);
|
|
546
|
+
}
|
|
547
|
+
let pending;
|
|
548
|
+
try {
|
|
549
|
+
pending = recoveryCandidates(current, options).map((operation) => operation.id);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
const value = error;
|
|
553
|
+
return result('blocked', recovered, [], value, warnings);
|
|
513
554
|
}
|
|
514
|
-
const pending = current.operations.filter(eligible).map((operation) => operation.id);
|
|
515
555
|
return result(last?.status === 'failed' ? 'failed' : 'blocked', recovered, pending, last?.error ?? {
|
|
516
556
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
517
557
|
message: 'The first queued platform operation remains unresolved after this recovery run',
|
|
518
558
|
retryable: true
|
|
519
|
-
});
|
|
559
|
+
}, warnings);
|
|
520
560
|
}
|
|
521
561
|
}
|
|
522
562
|
let remaining;
|
|
523
563
|
try {
|
|
524
|
-
remaining = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot)
|
|
564
|
+
remaining = recoveryCandidates(readPlatformOperationJournal(resolved.taskId, resolved.repoRoot), options).map((operation) => operation.id);
|
|
525
565
|
}
|
|
526
|
-
catch {
|
|
527
|
-
|
|
566
|
+
catch (error) {
|
|
567
|
+
const value = error;
|
|
568
|
+
return result('blocked', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || 'Unable to read platform operations' }, warnings);
|
|
528
569
|
}
|
|
529
|
-
|
|
570
|
+
const output = result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining, remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null, warnings);
|
|
571
|
+
const degraded = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.filter((item) => recovered.includes(item.id) && item.lastCode === 'PUSH_REQUIRED').map((item) => ({ id: item.id, reasonCode: item.lastCode }));
|
|
572
|
+
return degraded.length ? { ...output, degraded } : output;
|
|
530
573
|
}
|
|
531
574
|
export { fieldsMatchExpected, labelsMatchOwnedPrefix, recoverPlatformOperations };
|
|
532
575
|
//# sourceMappingURL=platform-operation-recovery.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseResourceIdentity, serializeResourceIdentity } from '../platform/resource-identity.js';
|
|
2
2
|
const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
|
|
3
3
|
function factError(message) {
|
|
4
4
|
return Object.assign(new Error(`PR_DELIVERY_FACT_INVALID: ${message}`), { code: 'PR_DELIVERY_FACT_INVALID' });
|
|
@@ -132,8 +132,5 @@ function buildBoundFact(input) {
|
|
|
132
132
|
}, provenance: { establishedBy: provenanceForSource(input.source) } });
|
|
133
133
|
}
|
|
134
134
|
function factFrontmatterMutation(fact) { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
|
|
135
|
-
|
|
136
|
-
return canonicalIdentity(pullRequest);
|
|
137
|
-
}
|
|
138
|
-
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
|
|
135
|
+
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, readPrDeliveryFact, serializeResourceIdentity };
|
|
139
136
|
//# sourceMappingURL=pr-delivery-fact.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
-
import { appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
3
|
+
import { activityWarnings, appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
4
4
|
import { CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, constraintDigest, parseQualificationConfirmations, parseTaskQualification } from './qualification-audit.js';
|
|
5
5
|
import { findSectionHeading } from './sections.js';
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
@@ -58,13 +58,13 @@ function applyQualificationConfirmation(request, options = {}) {
|
|
|
58
58
|
const previous = existing.confirmations.find((row) => row.qcrId === constraint.approvalEvidence);
|
|
59
59
|
if (operation === 'confirm' && constraint.status === 'confirmed') {
|
|
60
60
|
if (previous?.constraintId === constraint.constraintId && previous.approvedDigest === constraint.digest && previous.rationale === request.rationale.trim()) {
|
|
61
|
-
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null };
|
|
61
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null, warnings: activityWarnings(content) };
|
|
62
62
|
}
|
|
63
63
|
return failed('QUALIFICATION_CONFIRMATION_CONFLICT', `constraint '${request.constraintId}' is already confirmed with different evidence`, resolved.taskId);
|
|
64
64
|
}
|
|
65
65
|
const transitionStatus = operation === 'supersede' ? 'superseded' : 'open';
|
|
66
66
|
if (operation !== 'confirm' && constraint.status === transitionStatus && !constraint.approvalEvidence) {
|
|
67
|
-
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null };
|
|
67
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null, warnings: activityWarnings(content) };
|
|
68
68
|
}
|
|
69
69
|
if (operation !== 'confirm' && constraint.status !== 'confirmed')
|
|
70
70
|
return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `constraint '${request.constraintId}' is not confirmed`, resolved.taskId);
|
|
@@ -90,20 +90,18 @@ function applyQualificationConfirmation(request, options = {}) {
|
|
|
90
90
|
} : null;
|
|
91
91
|
const confirmations = confirmation ? [...existing.confirmations, confirmation] : existing.confirmations;
|
|
92
92
|
const activity = locateActivityLog(content);
|
|
93
|
-
if (!activity)
|
|
94
|
-
return failed('QUALIFICATION_ACTIVITY_MISSING', 'task has no unique Activity Log section', resolved.taskId);
|
|
95
93
|
const mutations = [
|
|
96
94
|
{
|
|
97
95
|
kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'], keyColumn: 'constraint_id', key: constraint.constraintId,
|
|
98
96
|
values: { statement: constraint.statement, status: operation === 'confirm' ? 'confirmed' : transitionStatus, authority: operation === 'confirm' ? 'human-declared' : constraint.authority, source: constraint.source, evidence: constraint.evidence, derived_from: constraint.derivedFrom.join(','), approval_evidence: qcrId ?? '' }
|
|
99
97
|
},
|
|
100
98
|
...(operation === 'confirm' ? [{ kind: 'section', aliases: CONFIRMATION_HEADINGS, heading: findSectionHeading(content, [...CONFIRMATION_HEADINGS]), body: renderConfirmations(confirmations) }] : []),
|
|
101
|
-
{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }
|
|
99
|
+
...(activity ? [{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }] : [])
|
|
102
100
|
];
|
|
103
101
|
const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
|
|
104
102
|
if (result.status === 'failed')
|
|
105
103
|
return failed(result.error.code, result.error.message, result.taskId);
|
|
106
|
-
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null };
|
|
104
|
+
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null, warnings: activityWarnings(content) };
|
|
107
105
|
}
|
|
108
106
|
export { applyQualificationConfirmation };
|
|
109
107
|
//# sourceMappingURL=qualification-confirmation.js.map
|
|
@@ -70,30 +70,6 @@ function findTaskMd(workspaceRoot, taskId) {
|
|
|
70
70
|
const archived = findInArchive(workspaceRoot, taskId);
|
|
71
71
|
return archived ? { taskMdPath: archived, state: 'archive' } : null;
|
|
72
72
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Enumerate every task directory under the flat workspace states
|
|
75
|
-
* (active / blocked / completed) — archive is intentionally excluded so a
|
|
76
|
-
* full-tree scan never pulls in cold data. Ordered by state, then task id
|
|
77
|
-
* ascending, giving callers a deterministic traversal.
|
|
78
|
-
*/
|
|
79
|
-
function enumerateTaskDirs(repoRoot) {
|
|
80
|
-
const out = [];
|
|
81
|
-
const workspaceRoot = taskRoot(repoRoot);
|
|
82
|
-
for (const sub of FLAT_WORKSPACE_DIRS) {
|
|
83
|
-
const base = path.join(workspaceRoot, sub);
|
|
84
|
-
if (!fs.existsSync(base))
|
|
85
|
-
continue;
|
|
86
|
-
for (const entry of fs.readdirSync(base).sort()) {
|
|
87
|
-
if (!TASK_ID_RE.test(entry))
|
|
88
|
-
continue;
|
|
89
|
-
const taskDir = path.join(base, entry);
|
|
90
|
-
if (!fs.existsSync(path.join(taskDir, 'task.md')))
|
|
91
|
-
continue;
|
|
92
|
-
out.push({ taskId: entry, taskDir });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return out;
|
|
96
|
-
}
|
|
97
73
|
/**
|
|
98
74
|
* Enumerate every task directory, including the cold YYYY/MM/DD archive.
|
|
99
75
|
* Invalid directory names, symlinks and entries without task.md are ignored;
|
|
@@ -316,5 +292,5 @@ function resolveTaskContext(taskRef, options = {}) {
|
|
|
316
292
|
const match = matches[0];
|
|
317
293
|
return { ok: true, repoRoot, ...match, state: 'active' };
|
|
318
294
|
}
|
|
319
|
-
export { resolveTaskRef, resolveTaskContext, detectRepoRoot,
|
|
295
|
+
export { resolveTaskRef, resolveTaskContext, detectRepoRoot, enumerateAllTaskDirs, locateHotTaskDirs, TASK_ID_RE };
|
|
320
296
|
//# sourceMappingURL=resolve-ref.js.map
|
|
@@ -3,7 +3,7 @@ export function parseReviewCommand(args) {
|
|
|
3
3
|
throw new Error('task ref and a supported review operation are required');
|
|
4
4
|
const values = {};
|
|
5
5
|
const flags = {
|
|
6
|
-
'--stage': 'stage', '--artifact': 'artifact', '--
|
|
6
|
+
'--stage': 'stage', '--artifact': 'artifact', '--dry-run': 'dryRun'
|
|
7
7
|
};
|
|
8
8
|
for (let index = 2; index < args.length; index += 1) {
|
|
9
9
|
const flag = args[index];
|
|
@@ -12,7 +12,7 @@ export function parseReviewCommand(args) {
|
|
|
12
12
|
const key = flags[flag];
|
|
13
13
|
if (Object.hasOwn(values, key))
|
|
14
14
|
throw new Error(`duplicate option '${flag}'`);
|
|
15
|
-
const value = flag === '--
|
|
15
|
+
const value = flag === '--dry-run' ? true : args[++index];
|
|
16
16
|
if (!value || typeof value === 'string' && value.startsWith('--'))
|
|
17
17
|
throw new Error(`option '${flag}' requires a value`);
|
|
18
18
|
values[key] = value;
|
|
@@ -7,7 +7,6 @@ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedge
|
|
|
7
7
|
import { finalizeReviewSummaryContent } from './review-artifacts.js';
|
|
8
8
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
9
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
|
-
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
11
10
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
12
11
|
import { validateQualificationAudit } from './qualification-audit.js';
|
|
13
12
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
@@ -74,13 +73,6 @@ function preflightReviewSummary(request, options = {}) {
|
|
|
74
73
|
catch (error) {
|
|
75
74
|
return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
76
75
|
}
|
|
77
|
-
const execution = validateLifecycleExecution(request.taskRef, {
|
|
78
|
-
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
79
|
-
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
80
|
-
dryRun: request.dryRun
|
|
81
|
-
}, { repoRoot: options.repoRoot });
|
|
82
|
-
if (!execution.ok)
|
|
83
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
|
|
84
76
|
const artifactSha256 = sha256Content(content);
|
|
85
77
|
const semanticDigest = canonicalSemanticDigest(content);
|
|
86
78
|
const detail = inspectDecisionDetailDuplicates(content);
|
|
@@ -140,15 +132,6 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
140
132
|
}
|
|
141
133
|
const artifactSha256 = sha256Content(artifactContent);
|
|
142
134
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
143
|
-
const digests = { artifactSha256, semanticDigest };
|
|
144
|
-
const execution = validateLifecycleExecution(request.taskRef, {
|
|
145
|
-
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
146
|
-
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
147
|
-
dryRun: request.dryRun
|
|
148
|
-
}, { repoRoot: options.repoRoot });
|
|
149
|
-
if (!execution.ok) {
|
|
150
|
-
return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
|
|
151
|
-
}
|
|
152
135
|
try {
|
|
153
136
|
const ledger = parseLedgerDocument(taskContent);
|
|
154
137
|
if (!ledger.present)
|
|
@@ -194,9 +177,6 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
194
177
|
},
|
|
195
178
|
};
|
|
196
179
|
}
|
|
197
|
-
function commitReviewSummaryProvenance(prepared, repoRoot, options = {}) {
|
|
198
|
-
return prepared.result;
|
|
199
|
-
}
|
|
200
180
|
function replaceCurrentArtifactAtomically(file, content) {
|
|
201
181
|
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
202
182
|
try {
|
|
@@ -250,5 +230,5 @@ function finalizeReviewSummary(request, options = {}) {
|
|
|
250
230
|
return failed(request, 'REVIEW_RECOVERY_COMMIT_FAILED', `cannot update current review artifact: ${error instanceof Error ? error.message : String(error)}`, resolved.taskId);
|
|
251
231
|
}
|
|
252
232
|
}
|
|
253
|
-
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate
|
|
233
|
+
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate };
|
|
254
234
|
//# sourceMappingURL=review-finalization.js.map
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { inspectArtifactDirectory } from "./artifact-lifecycle.js";
|
|
4
|
-
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
5
|
-
export function parseReviewedGitTree(value) {
|
|
6
|
-
return REVIEWED_TREE_RE.test(value) ? value : null;
|
|
7
|
-
}
|
|
8
4
|
// Paths excluded from the post-review coverage gate. Fail-closed: the gate
|
|
9
5
|
// covers ALL tracked changes by default. This default denylist is EMPTY —
|
|
10
6
|
// projects opt into exclusions explicitly via review.post_review_exclude_globs.
|