@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
|
@@ -3,13 +3,15 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
|
|
6
|
-
import { inspectPlatformCommentOperation,
|
|
6
|
+
import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.ts';
|
|
7
|
+
import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.ts';
|
|
8
|
+
import { enumerateArtifacts } from './artifacts.ts';
|
|
7
9
|
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
+
import '../platform/context.ts';
|
|
11
|
+
import '../platform/provider-bridge.ts';
|
|
10
12
|
import { inLabelMappingDigest } from '../platform/in-label-sync.ts';
|
|
11
|
-
import { bindPlatformPullRequest,
|
|
12
|
-
import {
|
|
13
|
+
import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
14
|
+
import { replayPullRequestSummary } from '../platform/pr-summary.ts';
|
|
13
15
|
import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.ts';
|
|
14
16
|
import type { PlatformClient } from '../platform/context.ts';
|
|
15
17
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
@@ -19,11 +21,12 @@ import { operationId } from './platform-operation-journal.ts';
|
|
|
19
21
|
import { canonicalizeSummaryBody } from '../platform/comment-safety.ts';
|
|
20
22
|
import { platformResult } from '../platform/types.ts';
|
|
21
23
|
import type { PlatformResult } from '../platform/types.ts';
|
|
24
|
+
import { mergeOperationWarnings, type OperationWarning } from './operation-outcome.ts';
|
|
22
25
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
23
26
|
import { readPlatformOperationJournal, recordPlatformOperation } from './platform-operation-journal.ts';
|
|
24
|
-
import
|
|
27
|
+
import '../platform/provider-validation.ts';
|
|
25
28
|
|
|
26
|
-
type RecoveryOptions = Readonly<{ agent: string; client?: PlatformClient; cwd?: string; limit?: number; attempts?: number; excludeId?: string }>;
|
|
29
|
+
type RecoveryOptions = Readonly<{ agent: string; client?: PlatformClient; cwd?: string; limit?: number; attempts?: number; excludeId?: string; beforeId?: string }>;
|
|
27
30
|
const DEFAULT_RECOVERY_ATTEMPTS = 3;
|
|
28
31
|
type RecoveryResult = Readonly<{
|
|
29
32
|
status: 'applied' | 'no-op' | 'blocked' | 'failed';
|
|
@@ -31,6 +34,8 @@ type RecoveryResult = Readonly<{
|
|
|
31
34
|
recovered: readonly string[];
|
|
32
35
|
pending: readonly string[];
|
|
33
36
|
error: { code: string; message: string } | null;
|
|
37
|
+
degraded?: readonly { id: string; reasonCode: string }[];
|
|
38
|
+
warnings?: readonly OperationWarning[];
|
|
34
39
|
}>;
|
|
35
40
|
type RecoveryError = { code: string; message: string; retryable?: boolean };
|
|
36
41
|
|
|
@@ -46,40 +51,64 @@ function fieldsMatchExpected(
|
|
|
46
51
|
return Object.entries(expected).every(([name, value]) => actual[name] === value);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
function result(status: RecoveryResult['status'], recovered: string[], pending: string[], error: RecoveryError | null = null): RecoveryResult {
|
|
50
|
-
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null };
|
|
54
|
+
function result(status: RecoveryResult['status'], recovered: string[], pending: string[], error: RecoveryError | null = null, warnings: readonly OperationWarning[] = []): RecoveryResult {
|
|
55
|
+
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null, ...(warnings.length ? { warnings } : {}) };
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
function summaryPayload(taskDir: string, taskId: string): { body: string; sha256: string } | null {
|
|
54
59
|
const file = path.join(taskDir, '.delivery-summary.json');
|
|
55
60
|
if (!fs.existsSync(file)) return null;
|
|
56
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; content?: unknown; contentSha256?: unknown };
|
|
62
|
+
let content;
|
|
63
|
+
try { content = parseDeliverySummaryContent(data.content); }
|
|
64
|
+
catch { content = null; }
|
|
65
|
+
if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
|
|
66
|
+
|| createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
|
|
61
67
|
throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
62
68
|
}
|
|
63
|
-
|
|
69
|
+
const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
|
|
70
|
+
if (!canonical?.ok) throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
71
|
+
return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cancellationPayload(taskDir: string, taskId: string): string | null {
|
|
75
|
+
const taskPath = path.join(taskDir, 'task.md');
|
|
76
|
+
if (!fs.existsSync(taskPath)) return null;
|
|
77
|
+
const taskContent = fs.readFileSync(taskPath, 'utf8');
|
|
78
|
+
const frontmatter = parseTaskFrontmatter(taskContent);
|
|
79
|
+
const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
|
|
80
|
+
const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
|
|
81
|
+
const content = parseCancelSummaryContent({
|
|
82
|
+
taskId,
|
|
83
|
+
title,
|
|
84
|
+
outcome: 'cancelled',
|
|
85
|
+
reason: frontmatter.cancel_reason,
|
|
86
|
+
cancelledAt: frontmatter.cancelled_at,
|
|
87
|
+
lastCompletedArtifact: artifacts.at(-1) ?? null
|
|
88
|
+
});
|
|
89
|
+
return renderCancelSummaryContent(content);
|
|
64
90
|
}
|
|
65
91
|
|
|
66
92
|
async function replayComment(taskId: string, taskDir: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
67
93
|
if (operation.kind === 'task-comment') {
|
|
68
|
-
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency,
|
|
94
|
+
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
69
95
|
}
|
|
70
96
|
if (operation.kind === 'artifact-comment') {
|
|
71
|
-
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency,
|
|
97
|
+
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
72
98
|
}
|
|
73
99
|
if (operation.kind === 'summary-comment') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
100
|
+
let summary;
|
|
101
|
+
try { summary = summaryPayload(taskDir, taskId); }
|
|
102
|
+
catch (error) { return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
103
|
+
if (!summary) return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
|
|
104
|
+
return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
|
|
80
105
|
}
|
|
81
106
|
if (operation.kind === 'cancel-comment') {
|
|
82
|
-
|
|
107
|
+
let body: string | null;
|
|
108
|
+
try { body = cancellationPayload(taskDir, taskId); }
|
|
109
|
+
catch (error) { return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
110
|
+
if (!body) return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
|
|
111
|
+
return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
83
112
|
}
|
|
84
113
|
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
|
|
85
114
|
}
|
|
@@ -138,7 +167,8 @@ async function replayIssueMetadata(taskId: string, operation: ReturnType<typeof
|
|
|
138
167
|
}
|
|
139
168
|
const issue = after.issue;
|
|
140
169
|
const metadata = operation.issueMetadata;
|
|
141
|
-
const failedOperation = sync.operations.find((item) => item.status === 'failed' || item.status === 'skipped'
|
|
170
|
+
const failedOperation = sync.operations.find((item) => item.status === 'failed' || (item.status === 'skipped'
|
|
171
|
+
&& !(sync.capabilities.push === false && ['fields', 'issue-type'].includes(item.name) && item.reasonCode === 'PUSH_REQUIRED')));
|
|
142
172
|
if (failedOperation) return platformResult('blocked', { error: {
|
|
143
173
|
code: failedOperation.reasonCode || 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
|
|
144
174
|
message: `Issue metadata recovery did not confirm operation '${failedOperation.name}'`, retryable: true
|
|
@@ -163,7 +193,7 @@ async function replayIssueMetadata(taskId: string, operation: ReturnType<typeof
|
|
|
163
193
|
code: 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
|
|
164
194
|
message: 'Issue metadata differs from the requested recovery target after reread', retryable: true
|
|
165
195
|
} });
|
|
166
|
-
return platformResult('no-op', { changed: sync.changed || after.changed });
|
|
196
|
+
return platformResult('no-op', { changed: sync.changed || after.changed, operations: sync.operations });
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
@@ -183,55 +213,6 @@ async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof re
|
|
|
183
213
|
return platformResult('no-op');
|
|
184
214
|
}
|
|
185
215
|
|
|
186
|
-
async function replayPullRequestSummary(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
187
|
-
let identity;
|
|
188
|
-
try { identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity'); }
|
|
189
|
-
catch { return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } }); }
|
|
190
|
-
const summary = operation.pullRequestSummary;
|
|
191
|
-
if (!summary) return platformResult('blocked', { error: {
|
|
192
|
-
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
193
|
-
} });
|
|
194
|
-
const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
|
|
195
|
-
if (manualValidationHeading && !summary.manualValidation) return platformResult('blocked', { error: {
|
|
196
|
-
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
|
|
197
|
-
} });
|
|
198
|
-
const resolved = await resolvePlatformProviderContext({ cwd, client });
|
|
199
|
-
if (!resolved.ok) return resolved.context;
|
|
200
|
-
const listed = resolved.value.provider.comments?.list
|
|
201
|
-
? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
|
|
202
|
-
: unsupportedProviderOperation(resolved.value.provider, 'comments.list');
|
|
203
|
-
if (!listed.ok) return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
|
|
204
|
-
const marker = `<!-- sync-pr:${taskId}:summary -->`;
|
|
205
|
-
const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
|
|
206
|
-
if (summaries.length > 1) return platformResult('blocked', { error: {
|
|
207
|
-
code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
|
|
208
|
-
} });
|
|
209
|
-
if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0]!.body)).digest('hex') === operation.expectedDigest) {
|
|
210
|
-
return platformResult('no-op');
|
|
211
|
-
}
|
|
212
|
-
const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
|
|
213
|
-
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity)) return platformResult('blocked', { error: bound.error ?? {
|
|
214
|
-
code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
|
|
215
|
-
} });
|
|
216
|
-
const replayed = await syncPullRequestSummary(taskId, {
|
|
217
|
-
agent,
|
|
218
|
-
cwd,
|
|
219
|
-
client,
|
|
220
|
-
body: summary.body,
|
|
221
|
-
changeReportFile: summary.changeReportFile,
|
|
222
|
-
...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
|
|
223
|
-
primaryResult: 'no_op',
|
|
224
|
-
strict: true,
|
|
225
|
-
skipQueue: true
|
|
226
|
-
});
|
|
227
|
-
if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
|
|
228
|
-
return platformResult(replayed.status, { changed: replayed.changed });
|
|
229
|
-
}
|
|
230
|
-
return platformResult('blocked', { error: replayed.error ?? {
|
|
231
|
-
code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
|
|
232
|
-
} });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
216
|
async function replayPullRequestReview(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
236
217
|
const intent = operation.pullRequestReview;
|
|
237
218
|
if (!intent) return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request review recovery parameters are missing', retryable: false } });
|
|
@@ -280,6 +261,20 @@ async function replayPullRequest(taskId: string, operation: ReturnType<typeof re
|
|
|
280
261
|
return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
|
|
281
262
|
}
|
|
282
263
|
|
|
264
|
+
function isRecoveryEligible(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], excludeId?: string): boolean {
|
|
265
|
+
if (operation.id === excludeId) return false;
|
|
266
|
+
if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown') return true;
|
|
267
|
+
return operation.state === 'failed'
|
|
268
|
+
&& operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'
|
|
269
|
+
;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function recoveryCandidates(journal: ReturnType<typeof readPlatformOperationJournal>, options: RecoveryOptions) {
|
|
273
|
+
const boundary = options.beforeId ? journal.operations.findIndex((item) => item.id === options.beforeId) : journal.operations.length;
|
|
274
|
+
if (boundary < 0) throw Object.assign(new Error('Current platform operation boundary is unavailable'), { code: 'PLATFORM_OPERATION_BOUNDARY_MISSING' });
|
|
275
|
+
return journal.operations.slice(0, boundary).filter((item) => isRecoveryEligible(item, options.excludeId));
|
|
276
|
+
}
|
|
277
|
+
|
|
283
278
|
async function recoverPlatformOperationsOnce(
|
|
284
279
|
taskRef: string,
|
|
285
280
|
selection: 'required' | 'deferred' | 'all',
|
|
@@ -293,9 +288,9 @@ async function recoverPlatformOperationsOnce(
|
|
|
293
288
|
const value = error as { code?: string; message?: string };
|
|
294
289
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
295
290
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
291
|
+
let candidates;
|
|
292
|
+
try { candidates = recoveryCandidates(journal, options); }
|
|
293
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', [], [], value); }
|
|
299
294
|
if (!candidates.length) return result('no-op', [], []);
|
|
300
295
|
const head = candidates[0]!;
|
|
301
296
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
@@ -308,8 +303,9 @@ async function recoverPlatformOperationsOnce(
|
|
|
308
303
|
const limit = options.limit === undefined ? candidates.length : Math.max(1, options.limit);
|
|
309
304
|
const recovered: string[] = [];
|
|
310
305
|
const pending: string[] = [];
|
|
306
|
+
let warnings: readonly OperationWarning[] = [];
|
|
311
307
|
const selected = candidates.slice(0, limit);
|
|
312
|
-
for (const
|
|
308
|
+
for (const operation of selected) {
|
|
313
309
|
{
|
|
314
310
|
try {
|
|
315
311
|
recordPlatformOperation({
|
|
@@ -357,11 +353,18 @@ async function recoverPlatformOperationsOnce(
|
|
|
357
353
|
const commentOptions = operation.kind === 'task-comment'
|
|
358
354
|
? { kind: 'task' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
359
355
|
: operation.kind === 'artifact-comment'
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
356
|
+
? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
357
|
+
: operation.kind === 'summary-comment' ? (() => {
|
|
358
|
+
try {
|
|
359
|
+
const summary = summaryPayload(resolved.taskDir, resolved.taskId);
|
|
360
|
+
return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
|
|
361
|
+
} catch { return null; }
|
|
362
|
+
})() : (() => {
|
|
363
|
+
try {
|
|
364
|
+
const body = cancellationPayload(resolved.taskDir, resolved.taskId);
|
|
365
|
+
return body ? { kind: 'cancel' as const, body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
|
|
366
|
+
} catch { return null; }
|
|
367
|
+
})();
|
|
365
368
|
const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
|
|
366
369
|
if (!current) {
|
|
367
370
|
pending.push(operation.id);
|
|
@@ -378,25 +381,26 @@ async function recoverPlatformOperationsOnce(
|
|
|
378
381
|
}
|
|
379
382
|
}
|
|
380
383
|
}
|
|
381
|
-
let remote: PlatformResult;
|
|
384
|
+
let remote: PlatformResult & { warnings?: readonly OperationWarning[] };
|
|
382
385
|
try {
|
|
383
386
|
remote = operation.kind === 'issue-create'
|
|
384
387
|
? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
|
|
385
388
|
: operation.kind === 'pull-request-summary'
|
|
386
|
-
? await replayPullRequestSummary(resolved.taskId,
|
|
389
|
+
? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
|
|
387
390
|
: operation.kind === 'pull-request-review'
|
|
388
391
|
? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
|
|
389
392
|
: operation.kind === 'issue-metadata'
|
|
390
393
|
? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
391
394
|
: operation.kind === 'pull-request'
|
|
392
395
|
? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
393
|
-
: await replayComment(resolved.taskId, resolved.taskDir,
|
|
396
|
+
: await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
|
|
394
397
|
}
|
|
395
398
|
catch (error) {
|
|
396
399
|
const value = error as { code?: string; message?: string };
|
|
397
400
|
remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
|
|
398
401
|
}
|
|
399
402
|
const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
|
|
403
|
+
const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
|
|
400
404
|
try {
|
|
401
405
|
recordPlatformOperation({
|
|
402
406
|
taskRef: resolved.taskId,
|
|
@@ -411,7 +415,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
411
415
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
412
416
|
dependency: operation.dependency,
|
|
413
417
|
state: succeeded ? 'succeeded' : 'unknown',
|
|
414
|
-
lastCode: remote.error?.code ?? null
|
|
418
|
+
lastCode: remote.error?.code ?? remote.operations.find((item) => item.status === 'skipped' && item.reasonCode === 'PUSH_REQUIRED')?.reasonCode ?? (succeeded ? null : latestOperation?.lastCode) ?? null
|
|
415
419
|
});
|
|
416
420
|
} catch (error) {
|
|
417
421
|
const value = error as { code?: string; message?: string };
|
|
@@ -421,11 +425,12 @@ async function recoverPlatformOperationsOnce(
|
|
|
421
425
|
pending.push(targetOperation.id);
|
|
422
426
|
return result(remote.status === 'failed' ? 'failed' : 'blocked', recovered, pending, remote.error ?? { code: 'PLATFORM_OPERATION_RECOVERY_UNKNOWN', message: 'Platform operation outcome remains unknown', retryable: true });
|
|
423
427
|
}
|
|
428
|
+
warnings = mergeOperationWarnings(warnings, remote.warnings ?? []);
|
|
424
429
|
recovered.push(targetOperation.id);
|
|
425
430
|
}
|
|
426
431
|
const remaining = candidates.slice(limit).map((operation) => operation.id);
|
|
427
432
|
pending.push(...remaining);
|
|
428
|
-
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);
|
|
433
|
+
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);
|
|
429
434
|
}
|
|
430
435
|
|
|
431
436
|
async function recoverPlatformOperations(
|
|
@@ -441,10 +446,9 @@ async function recoverPlatformOperations(
|
|
|
441
446
|
const value = error as { code?: string; message?: string };
|
|
442
447
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
443
448
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const first = initial.operations.filter(eligible);
|
|
449
|
+
let first;
|
|
450
|
+
try { first = recoveryCandidates(initial, options); }
|
|
451
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', [], [], value); }
|
|
448
452
|
if (!first.length) return result('no-op', [], []);
|
|
449
453
|
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|
|
450
454
|
const attemptLimit = options.attempts ?? DEFAULT_RECOVERY_ATTEMPTS;
|
|
@@ -452,47 +456,56 @@ async function recoverPlatformOperations(
|
|
|
452
456
|
return result('failed', [], [], { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'Recovery attempts must be a positive safe integer', retryable: false });
|
|
453
457
|
}
|
|
454
458
|
const recovered: string[] = [];
|
|
459
|
+
let warnings: readonly OperationWarning[] = [];
|
|
455
460
|
|
|
456
461
|
for (let operationIndex = 0; operationIndex < operationLimit; operationIndex += 1) {
|
|
457
462
|
let latest;
|
|
458
463
|
try { latest = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot); }
|
|
459
464
|
catch (error) {
|
|
460
465
|
const value = error as { code?: string; message?: string };
|
|
461
|
-
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
466
|
+
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false }, warnings);
|
|
462
467
|
}
|
|
463
|
-
|
|
468
|
+
let candidates;
|
|
469
|
+
try { candidates = recoveryCandidates(latest, options); }
|
|
470
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', recovered, [], value, warnings); }
|
|
471
|
+
const head = candidates[0];
|
|
464
472
|
if (!head) break;
|
|
465
473
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
466
|
-
const pending =
|
|
474
|
+
const pending = candidates.map((operation) => operation.id);
|
|
467
475
|
return result('blocked', recovered, pending, {
|
|
468
476
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
469
477
|
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
470
478
|
retryable: true
|
|
471
|
-
});
|
|
479
|
+
}, warnings);
|
|
472
480
|
}
|
|
473
481
|
let last: RecoveryResult | null = null;
|
|
474
482
|
for (let attempt = 0; attempt < attemptLimit; attempt += 1) {
|
|
475
483
|
last = await recoverPlatformOperationsOnce(taskRef, selection, { ...options, limit: 1 });
|
|
476
484
|
recovered.push(...last.recovered.filter((id) => !recovered.includes(id)));
|
|
485
|
+
warnings = mergeOperationWarnings(warnings, last.warnings ?? []);
|
|
477
486
|
if (last.recovered.length > 0 || last.status === 'applied' || last.status === 'no-op') break;
|
|
478
487
|
}
|
|
479
488
|
if (!last || (last.recovered.length === 0 && last.status !== 'applied' && last.status !== 'no-op')) {
|
|
480
489
|
let current;
|
|
481
490
|
try { current = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot); }
|
|
482
|
-
catch { return result('blocked', recovered, [head.id], last?.error ?? null); }
|
|
483
|
-
|
|
491
|
+
catch { return result('blocked', recovered, [head.id], last?.error ?? null, warnings); }
|
|
492
|
+
let pending: string[];
|
|
493
|
+
try { pending = recoveryCandidates(current, options).map((operation) => operation.id); }
|
|
494
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', recovered, [], value, warnings); }
|
|
484
495
|
return result(last?.status === 'failed' ? 'failed' : 'blocked', recovered, pending, last?.error ?? {
|
|
485
496
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
486
497
|
message: 'The first queued platform operation remains unresolved after this recovery run',
|
|
487
498
|
retryable: true
|
|
488
|
-
});
|
|
499
|
+
}, warnings);
|
|
489
500
|
}
|
|
490
501
|
}
|
|
491
502
|
let remaining: string[];
|
|
492
|
-
try { remaining = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot)
|
|
493
|
-
catch {
|
|
494
|
-
|
|
495
|
-
remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null);
|
|
503
|
+
try { remaining = recoveryCandidates(readPlatformOperationJournal(resolved.taskId, resolved.repoRoot), options).map((operation) => operation.id); }
|
|
504
|
+
catch (error) { const value = error as { code?: string; message?: string }; return result('blocked', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || 'Unable to read platform operations' }, warnings); }
|
|
505
|
+
const output = result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining,
|
|
506
|
+
remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null, warnings);
|
|
507
|
+
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! }));
|
|
508
|
+
return degraded.length ? { ...output, degraded } : output;
|
|
496
509
|
}
|
|
497
510
|
|
|
498
511
|
export { fieldsMatchExpected, labelsMatchOwnedPrefix, recoverPlatformOperations };
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { ResourceIdentity } from '../platform/resource-identity.ts';
|
|
2
|
-
import {
|
|
3
|
-
isResourceIdentity,
|
|
4
|
-
parseResourceIdentity,
|
|
5
|
-
resourceIdentityNumber,
|
|
6
|
-
serializeResourceIdentity
|
|
7
|
-
} from '../platform/resource-identity.ts';
|
|
2
|
+
import { parseResourceIdentity, serializeResourceIdentity } from '../platform/resource-identity.ts';
|
|
8
3
|
|
|
9
4
|
const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
|
|
10
5
|
|
|
@@ -182,9 +177,5 @@ function buildBoundFact(input: {
|
|
|
182
177
|
}, provenance: { establishedBy: provenanceForSource(input.source) } });
|
|
183
178
|
}
|
|
184
179
|
function factFrontmatterMutation(fact: PrDeliveryFact): { set: { [PR_DELIVERY_FACT_KEY]: string } } { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
|
|
185
|
-
|
|
186
|
-
return canonicalIdentity(pullRequest);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
|
|
180
|
+
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, readPrDeliveryFact, serializeResourceIdentity };
|
|
190
181
|
export type { CreationOutcome, PrDeliveryBindingSource, PrDeliveryFact, PrDeliveryFactError, PrDeliveryFactReadResult, PrDeliveryIdentity, PrDeliveryIdentityInput, PrDeliveryProvenance };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
|
|
4
|
-
import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
4
|
+
import { activityWarnings, appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
5
5
|
import { CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, constraintDigest, parseQualificationConfirmations, parseTaskQualification } from './qualification-audit.ts';
|
|
6
6
|
import type { QualificationConfirmation } from './qualification-audit.ts';
|
|
7
7
|
import { findSectionHeading } from './sections.ts';
|
|
@@ -25,6 +25,7 @@ type QualificationConfirmationResult = {
|
|
|
25
25
|
requestId: string | null;
|
|
26
26
|
operations: readonly TaskOperationSummary[];
|
|
27
27
|
error: { code: string; message: string } | null;
|
|
28
|
+
warnings?: readonly { code: string; message: string }[];
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
function failed(code: string, message: string, taskId: string | null = null): QualificationConfirmationResult {
|
|
@@ -73,13 +74,13 @@ function applyQualificationConfirmation(request: QualificationConfirmationReques
|
|
|
73
74
|
const previous = existing.confirmations.find((row) => row.qcrId === constraint.approvalEvidence);
|
|
74
75
|
if (operation === 'confirm' && constraint.status === 'confirmed') {
|
|
75
76
|
if (previous?.constraintId === constraint.constraintId && previous.approvedDigest === constraint.digest && previous.rationale === request.rationale.trim()) {
|
|
76
|
-
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null };
|
|
77
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null, warnings: activityWarnings(content) };
|
|
77
78
|
}
|
|
78
79
|
return failed('QUALIFICATION_CONFIRMATION_CONFLICT', `constraint '${request.constraintId}' is already confirmed with different evidence`, resolved.taskId);
|
|
79
80
|
}
|
|
80
81
|
const transitionStatus = operation === 'supersede' ? 'superseded' : 'open';
|
|
81
82
|
if (operation !== 'confirm' && constraint.status === transitionStatus && !constraint.approvalEvidence) {
|
|
82
|
-
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null };
|
|
83
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null, warnings: activityWarnings(content) };
|
|
83
84
|
}
|
|
84
85
|
if (operation !== 'confirm' && constraint.status !== 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `constraint '${request.constraintId}' is not confirmed`, resolved.taskId);
|
|
85
86
|
if (parsed.qualification.constraintDigest !== request.expectedDigest) return failed('QUALIFICATION_DIGEST_CONFLICT', 'constraint digest changed before human confirmation', resolved.taskId);
|
|
@@ -99,18 +100,17 @@ function applyQualificationConfirmation(request: QualificationConfirmationReques
|
|
|
99
100
|
} : null;
|
|
100
101
|
const confirmations = confirmation ? [...existing.confirmations, confirmation] : existing.confirmations;
|
|
101
102
|
const activity = locateActivityLog(content);
|
|
102
|
-
if (!activity) return failed('QUALIFICATION_ACTIVITY_MISSING', 'task has no unique Activity Log section', resolved.taskId);
|
|
103
103
|
const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
|
|
104
104
|
{
|
|
105
105
|
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,
|
|
106
106
|
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 ?? '' }
|
|
107
107
|
},
|
|
108
108
|
...(operation === 'confirm' ? [{ kind: 'section' as const, aliases: CONFIRMATION_HEADINGS, heading: findSectionHeading(content, [...CONFIRMATION_HEADINGS]), body: renderConfirmations(confirmations) }] : []),
|
|
109
|
-
{ 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` }) }
|
|
109
|
+
...(activity ? [{ kind: 'section' as const, 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` }) }] : [])
|
|
110
110
|
];
|
|
111
111
|
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 });
|
|
112
112
|
if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
|
|
113
|
-
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null };
|
|
113
|
+
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null, warnings: activityWarnings(content) };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export { applyQualificationConfirmation };
|
package/lib/task/resolve-ref.ts
CHANGED
|
@@ -114,28 +114,6 @@ function findTaskMd(
|
|
|
114
114
|
return archived ? { taskMdPath: archived, state: 'archive' } : null;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/**
|
|
118
|
-
* Enumerate every task directory under the flat workspace states
|
|
119
|
-
* (active / blocked / completed) — archive is intentionally excluded so a
|
|
120
|
-
* full-tree scan never pulls in cold data. Ordered by state, then task id
|
|
121
|
-
* ascending, giving callers a deterministic traversal.
|
|
122
|
-
*/
|
|
123
|
-
function enumerateTaskDirs(repoRoot: string): { taskId: string; taskDir: string }[] {
|
|
124
|
-
const out: { taskId: string; taskDir: string }[] = [];
|
|
125
|
-
const workspaceRoot = taskRoot(repoRoot);
|
|
126
|
-
for (const sub of FLAT_WORKSPACE_DIRS) {
|
|
127
|
-
const base = path.join(workspaceRoot, sub);
|
|
128
|
-
if (!fs.existsSync(base)) continue;
|
|
129
|
-
for (const entry of fs.readdirSync(base).sort()) {
|
|
130
|
-
if (!TASK_ID_RE.test(entry)) continue;
|
|
131
|
-
const taskDir = path.join(base, entry);
|
|
132
|
-
if (!fs.existsSync(path.join(taskDir, 'task.md'))) continue;
|
|
133
|
-
out.push({ taskId: entry, taskDir });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return out;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
117
|
/**
|
|
140
118
|
* Enumerate every task directory, including the cold YYYY/MM/DD archive.
|
|
141
119
|
* Invalid directory names, symlinks and entries without task.md are ignored;
|
|
@@ -364,7 +342,6 @@ export {
|
|
|
364
342
|
resolveTaskRef,
|
|
365
343
|
resolveTaskContext,
|
|
366
344
|
detectRepoRoot,
|
|
367
|
-
enumerateTaskDirs,
|
|
368
345
|
enumerateAllTaskDirs,
|
|
369
346
|
locateHotTaskDirs,
|
|
370
347
|
TASK_ID_RE
|
|
@@ -4,14 +4,14 @@ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationR
|
|
|
4
4
|
if (!args[0] || !['preflight', 'finalize-summary'].includes(args[1] ?? '')) throw new Error('task ref and a supported review operation are required');
|
|
5
5
|
const values: Record<string, string | boolean> = {};
|
|
6
6
|
const flags = {
|
|
7
|
-
'--stage': 'stage', '--artifact': 'artifact', '--
|
|
7
|
+
'--stage': 'stage', '--artifact': 'artifact', '--dry-run': 'dryRun'
|
|
8
8
|
} as const;
|
|
9
9
|
for (let index = 2; index < args.length; index += 1) {
|
|
10
10
|
const flag = args[index]!;
|
|
11
11
|
if (!Object.hasOwn(flags, flag)) throw new Error(`unknown option '${flag}'`);
|
|
12
12
|
const key = flags[flag as keyof typeof flags];
|
|
13
13
|
if (Object.hasOwn(values, key)) throw new Error(`duplicate option '${flag}'`);
|
|
14
|
-
const value = flag === '--
|
|
14
|
+
const value = flag === '--dry-run' ? true : args[++index];
|
|
15
15
|
if (!value || typeof value === 'string' && value.startsWith('--')) throw new Error(`option '${flag}' requires a value`);
|
|
16
16
|
values[key] = value;
|
|
17
17
|
}
|
|
@@ -9,7 +9,6 @@ import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
|
9
9
|
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
10
10
|
import { inspectDecisionDetailDuplicates } from './decision-details.ts';
|
|
11
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
12
|
-
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
13
12
|
import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
|
|
14
13
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
15
14
|
import { validateQualificationAudit } from './qualification-audit.ts';
|
|
@@ -36,7 +35,6 @@ type ReviewFinalizationRequest = {
|
|
|
36
35
|
taskRef: string;
|
|
37
36
|
stage: string;
|
|
38
37
|
artifact: string;
|
|
39
|
-
orchestrated?: boolean;
|
|
40
38
|
dryRun?: boolean;
|
|
41
39
|
};
|
|
42
40
|
type ReviewFinalizationResult = {
|
|
@@ -144,13 +142,6 @@ function preflightReviewSummary(
|
|
|
144
142
|
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
145
143
|
} catch (error) { return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
|
|
146
144
|
|
|
147
|
-
const execution = validateLifecycleExecution(request.taskRef, {
|
|
148
|
-
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
149
|
-
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
150
|
-
dryRun: request.dryRun
|
|
151
|
-
}, { repoRoot: options.repoRoot });
|
|
152
|
-
if (!execution.ok) return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
|
|
153
|
-
|
|
154
145
|
const artifactSha256 = sha256Content(content);
|
|
155
146
|
const semanticDigest = canonicalSemanticDigest(content);
|
|
156
147
|
const detail = inspectDecisionDetailDuplicates(content);
|
|
@@ -211,15 +202,6 @@ function prepareReviewSummaryCandidate(
|
|
|
211
202
|
catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
|
|
212
203
|
const artifactSha256 = sha256Content(artifactContent);
|
|
213
204
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
214
|
-
const digests = { artifactSha256, semanticDigest };
|
|
215
|
-
const execution = validateLifecycleExecution(request.taskRef, {
|
|
216
|
-
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
217
|
-
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
218
|
-
dryRun: request.dryRun
|
|
219
|
-
}, { repoRoot: options.repoRoot });
|
|
220
|
-
if (!execution.ok) {
|
|
221
|
-
return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
|
|
222
|
-
}
|
|
223
205
|
try {
|
|
224
206
|
const ledger = parseLedgerDocument(taskContent);
|
|
225
207
|
if (!ledger.present) return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
@@ -259,14 +241,6 @@ function prepareReviewSummaryCandidate(
|
|
|
259
241
|
};
|
|
260
242
|
}
|
|
261
243
|
|
|
262
|
-
function commitReviewSummaryProvenance(
|
|
263
|
-
prepared: ReviewSummaryCandidatePreparation,
|
|
264
|
-
repoRoot: string,
|
|
265
|
-
options: Readonly<{ afterPublish?: () => void }> = {}
|
|
266
|
-
): ReviewFinalizationResult {
|
|
267
|
-
return prepared.result;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
244
|
function replaceCurrentArtifactAtomically(file: string, content: string): void {
|
|
271
245
|
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
272
246
|
try {
|
|
@@ -316,7 +290,7 @@ function finalizeReviewSummary(
|
|
|
316
290
|
}
|
|
317
291
|
}
|
|
318
292
|
|
|
319
|
-
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate
|
|
293
|
+
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate };
|
|
320
294
|
export type {
|
|
321
295
|
ReviewFinalizationError,
|
|
322
296
|
ReviewFinalizationErrorCode,
|