@fitlab-ai/agent-infra 0.9.14 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { locateActivityLog, pairEntries } from '../task/activity-log.ts';
|
|
5
|
+
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
6
|
+
import { applyTaskEvent } from '../task/events.ts';
|
|
7
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
8
|
+
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
9
|
+
import { createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.ts';
|
|
10
|
+
import { archiveManualValidationGeneration, createManualValidationTransaction, readManualValidationTransaction, retryManualValidationTransaction, summaryPreimageDigest, transitionManualValidationTransaction, validateManualValidationGenerationArchive, writeManualValidationTransactionAtomic } from '../task/manual-validation-transaction.ts';
|
|
11
|
+
import type { ManualValidationTransaction } from '../task/manual-validation-transaction.ts';
|
|
12
|
+
import type { ManualValidationReceipt } from '../task/manual-validation-receipt.ts';
|
|
13
|
+
import { sha256File } from '../task/artifact-receipts.ts';
|
|
14
|
+
import { summaryCommentState, syncPullRequestSummary } from '../platform/pr-summary.ts';
|
|
15
|
+
import type { PlatformClient } from '../platform/context.ts';
|
|
16
|
+
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
17
|
+
|
|
18
|
+
const USAGE = `Usage: agent-infra-internal manual-validation transaction <task-ref> --prepare --artifact <artifact> --summary-file <path> --agent <agent> [--cwd <path>]
|
|
19
|
+
agent-infra-internal manual-validation transaction <task-ref> --artifact <artifact> --summary-file <path> --change-report-file <path> --agent <agent> [--result pr_created|pr_reused|no_op] [--cwd <path>]`;
|
|
20
|
+
|
|
21
|
+
type OutputFormat = 'json' | 'text';
|
|
22
|
+
type ManualValidationCoordinatorOptions = {
|
|
23
|
+
client?: PlatformClient;
|
|
24
|
+
archiveGeneration?: typeof archiveManualValidationGeneration;
|
|
25
|
+
};
|
|
26
|
+
type ManualValidationResult = {
|
|
27
|
+
status: 'applied' | 'failed';
|
|
28
|
+
changed: false;
|
|
29
|
+
error: { code: string; message: string } | null;
|
|
30
|
+
transaction?: ManualValidationTransaction;
|
|
31
|
+
receipt?: ManualValidationReceipt;
|
|
32
|
+
prepared?: boolean;
|
|
33
|
+
idempotent?: boolean;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
};
|
|
36
|
+
function result(status: 'applied' | 'failed', error: { code: string; message: string } | null = null, extra: Record<string, unknown> = {}): ManualValidationResult {
|
|
37
|
+
return { status, changed: false, error, ...extra };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function printFailure(format: OutputFormat, error: { code: string; message: string }): void {
|
|
41
|
+
if (format === 'json') process.stdout.write(`${JSON.stringify(result('failed', error))}\n`);
|
|
42
|
+
else process.stderr.write(`${error.code}: ${error.message}\n`);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
|
|
47
|
+
|
|
48
|
+
function manualSummaryBody(body: string, phase: 'pending' | 'final', transactionId: string, receiptDigest = '', evidenceDigest = '', prHeadSha = ''): string {
|
|
49
|
+
const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
|
|
50
|
+
const section = phase === 'pending'
|
|
51
|
+
? chinese
|
|
52
|
+
? `### ⏳ 人工验证待收尾\n\n人工验证证据已暂存;事务 ${transactionId} 正在等待最终完成。`
|
|
53
|
+
: `### ⏳ Manual Validation Pending\n\nManual validation evidence is staged; transaction ${transactionId} is awaiting final promotion.`
|
|
54
|
+
: chinese
|
|
55
|
+
? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
|
|
56
|
+
: `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
|
|
57
|
+
let inserted = false;
|
|
58
|
+
const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
|
|
59
|
+
if (inserted) return '';
|
|
60
|
+
inserted = true;
|
|
61
|
+
return `${section}\n\n`;
|
|
62
|
+
});
|
|
63
|
+
return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function openManualValidationStarted(taskMdPath: string): { present: boolean; transactionId: string | null } {
|
|
67
|
+
let content: string;
|
|
68
|
+
try { content = fs.readFileSync(taskMdPath, 'utf8'); }
|
|
69
|
+
catch { return { present: false, transactionId: null }; }
|
|
70
|
+
const section = locateActivityLog(content);
|
|
71
|
+
if (!section) return { present: false, transactionId: null };
|
|
72
|
+
const row = pairEntries(section.entries)
|
|
73
|
+
.filter((item) => item.step === 'Complete Manual Validation' && item.started && !item.done)
|
|
74
|
+
.at(-1);
|
|
75
|
+
if (!row) return { present: false, transactionId: null };
|
|
76
|
+
return {
|
|
77
|
+
present: true,
|
|
78
|
+
transactionId: /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1] ?? null
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function executeManualValidationTransaction(taskRef: string, values: Record<string, string>, cwd: string, options: ManualValidationCoordinatorOptions = {}) {
|
|
83
|
+
const prepareOnly = values.prepare === 'true';
|
|
84
|
+
const required = prepareOnly
|
|
85
|
+
? ['artifact', 'summaryFile', 'agent'] as const
|
|
86
|
+
: ['artifact', 'summaryFile', 'changeReportFile', 'agent'] as const;
|
|
87
|
+
const missing = required.find((key) => !values[key]);
|
|
88
|
+
if (missing) return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: `transaction requires --${missing.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}` });
|
|
89
|
+
const agent = normalizeAgentToken(values.agent!);
|
|
90
|
+
if (!agent) return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: 'transaction agent is invalid' });
|
|
91
|
+
const primaryResult = values.result ?? 'no_op';
|
|
92
|
+
if (!['pr_created', 'pr_reused', 'no_op'].includes(primaryResult)) return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_ARGS_INVALID', message: '--result must be pr_created, pr_reused, or no_op' });
|
|
93
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: cwd });
|
|
94
|
+
if (!resolved.ok) return result('failed', { code: resolved.code, message: resolved.message });
|
|
95
|
+
try {
|
|
96
|
+
return await withTaskExecutionLock(
|
|
97
|
+
resolved.repoRoot,
|
|
98
|
+
resolved.taskId,
|
|
99
|
+
`manual-validation:${taskRef}`,
|
|
100
|
+
() => executeManualValidationTransactionLocked(taskRef, values, cwd, resolved, agent, primaryResult as 'pr_created' | 'pr_reused' | 'no_op', prepareOnly, options)
|
|
101
|
+
);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof TaskExecutionLockError) return result('failed', { code: error.code, message: error.message });
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function executeManualValidationTransactionLocked(
|
|
109
|
+
taskRef: string,
|
|
110
|
+
values: Record<string, string>,
|
|
111
|
+
cwd: string,
|
|
112
|
+
resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
|
|
113
|
+
agent: string,
|
|
114
|
+
primaryResult: 'pr_created' | 'pr_reused' | 'no_op',
|
|
115
|
+
prepareOnly: boolean,
|
|
116
|
+
options: ManualValidationCoordinatorOptions
|
|
117
|
+
) {
|
|
118
|
+
const artifactPath = path.resolve(resolved.taskDir, values.artifact!);
|
|
119
|
+
if (!prepareOnly && !fs.existsSync(artifactPath)) return result('failed', { code: 'MANUAL_VALIDATION_ARTIFACT_MISSING', message: `manual-validation artifact is missing: ${values.artifact}` });
|
|
120
|
+
const currentState = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
121
|
+
if (!currentState.pullRequest) return result('failed', { code: 'MANUAL_VALIDATION_PR_REQUIRED', message: 'canonical pull-request head is unavailable' });
|
|
122
|
+
const pullRequest = currentState.pullRequest;
|
|
123
|
+
const preimageBody = currentState.comment?.body ?? '';
|
|
124
|
+
const preimage = { commentId: currentState.comment?.id ?? null, body: preimageBody, digest: summaryPreimageDigest(preimageBody) };
|
|
125
|
+
const evidenceDigest = sha256File(path.resolve(cwd, values.summaryFile!));
|
|
126
|
+
const archiveGeneration = options.archiveGeneration ?? archiveManualValidationGeneration;
|
|
127
|
+
let transactionResult = readManualValidationTransaction(resolved.taskDir, { taskId: resolved.taskId, prNumber: pullRequest.number, prHeadSha: pullRequest.head.sha, evidenceDigest, artifact: values.artifact! });
|
|
128
|
+
let previousTransaction: ManualValidationTransaction | null = null;
|
|
129
|
+
if (!transactionResult.ok && transactionResult.error.code === 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH') {
|
|
130
|
+
const existing = readManualValidationTransaction(resolved.taskDir);
|
|
131
|
+
if (!existing.ok) return result('failed', existing.error);
|
|
132
|
+
if (!['aborted', 'recovery-required', 'committed'].includes(existing.value.phase)) return result('failed', transactionResult.error);
|
|
133
|
+
try { validateManualValidationGenerationArchive(resolved.taskDir, existing.value, existing.value.phase === 'committed'); }
|
|
134
|
+
catch (error) { return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) }); }
|
|
135
|
+
previousTransaction = existing.value;
|
|
136
|
+
transactionResult = { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_MISSING', message: 'previous failed transaction was archived for a new pull-request head' } };
|
|
137
|
+
}
|
|
138
|
+
if (!transactionResult.ok && transactionResult.error.code !== 'MANUAL_VALIDATION_TRANSACTION_MISSING') return result('failed', transactionResult.error);
|
|
139
|
+
if (transactionResult.ok && transactionResult.value.phase === 'committed') return result('applied', null, { transaction: transactionResult.value, receipt: transactionResult.value.committedReceipt, idempotent: true });
|
|
140
|
+
const openStarted = transactionResult.ok ? { present: false, transactionId: null } : openManualValidationStarted(resolved.taskMdPath);
|
|
141
|
+
if (openStarted.present && !openStarted.transactionId) {
|
|
142
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'open manual-validation started event has no recoverable transactionId' });
|
|
143
|
+
}
|
|
144
|
+
if (previousTransaction && openStarted.transactionId === previousTransaction.transactionId) {
|
|
145
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'previous manual-validation generation is still open; retry the existing transaction before starting a new generation' });
|
|
146
|
+
}
|
|
147
|
+
const transactionId = transactionResult.ok
|
|
148
|
+
? transactionResult.value.transactionId
|
|
149
|
+
: (openStarted.transactionId ?? values.transactionId ?? `mv-${Date.now()}`);
|
|
150
|
+
const finalBodyWithoutReceipt = fs.readFileSync(path.resolve(cwd, values.summaryFile!), 'utf8');
|
|
151
|
+
const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending', transactionId);
|
|
152
|
+
const createTransaction = (): ManualValidationTransaction => createManualValidationTransaction({
|
|
153
|
+
transactionId,
|
|
154
|
+
taskId: resolved.taskId,
|
|
155
|
+
prNumber: pullRequest.number,
|
|
156
|
+
prHeadSha: pullRequest.head.sha,
|
|
157
|
+
evidenceDigest,
|
|
158
|
+
summaryPreimage: preimage,
|
|
159
|
+
pendingSummaryDigest: summaryPreimageDigest(pendingBody),
|
|
160
|
+
finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final', transactionId, '<receipt>', evidenceDigest, pullRequest.head.sha)),
|
|
161
|
+
artifact: values.artifact!,
|
|
162
|
+
attempt: 1,
|
|
163
|
+
createdAt: new Date().toISOString(),
|
|
164
|
+
updatedAt: new Date().toISOString()
|
|
165
|
+
});
|
|
166
|
+
let transaction: ManualValidationTransaction;
|
|
167
|
+
const idempotent = transactionResult.ok;
|
|
168
|
+
let startedEventApplied = false;
|
|
169
|
+
if (!transactionResult.ok) {
|
|
170
|
+
transaction = createTransaction();
|
|
171
|
+
const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transactionId, reasonCode: 'user-request', transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
|
|
172
|
+
if (started.status === 'failed') return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
|
|
173
|
+
startedEventApplied = true;
|
|
174
|
+
if (previousTransaction) {
|
|
175
|
+
try { archiveGeneration(resolved.taskDir, previousTransaction, previousTransaction.phase === 'committed'); }
|
|
176
|
+
catch (error) { return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: error instanceof Error ? error.message : String(error) }); }
|
|
177
|
+
}
|
|
178
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
|
|
179
|
+
} else {
|
|
180
|
+
transaction = transactionResult.value;
|
|
181
|
+
if (['aborted', 'recovery-required'].includes(transaction.phase)) {
|
|
182
|
+
const retried = retryManualValidationTransaction(transaction);
|
|
183
|
+
if (!retried.ok) return result('failed', retried.error);
|
|
184
|
+
transaction = retried.value;
|
|
185
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (transaction.phase === 'prepared' && !startedEventApplied) {
|
|
189
|
+
const started = applyTaskEvent({ taskRef, event: 'manual-validation.started', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', transactionId: transaction.transactionId }, { lockAlreadyHeld: true, repoRoot: cwd });
|
|
190
|
+
if (started.status === 'failed') return result('failed', started.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation started event failed' });
|
|
191
|
+
}
|
|
192
|
+
if (prepareOnly) return result('applied', null, { transaction, prepared: true, idempotent });
|
|
193
|
+
const transactionFailure = (failure: { code: string; message: string }): never => {
|
|
194
|
+
throw new Error(`${failure.code}: ${failure.message}`);
|
|
195
|
+
};
|
|
196
|
+
const transitionAndPersist = (
|
|
197
|
+
phase: Parameters<typeof transitionManualValidationTransaction>[1],
|
|
198
|
+
patch: Parameters<typeof transitionManualValidationTransaction>[2] = {}
|
|
199
|
+
): void => {
|
|
200
|
+
const next = transitionManualValidationTransaction(transaction, phase, patch);
|
|
201
|
+
if (!next.ok) throw new Error(`${next.error.code}: ${next.error.message}`);
|
|
202
|
+
transaction = next.value;
|
|
203
|
+
writeManualValidationTransactionAtomic(resolved.taskDir, transaction);
|
|
204
|
+
};
|
|
205
|
+
try {
|
|
206
|
+
if (transaction.phase === 'prepared') {
|
|
207
|
+
const staged = await syncPullRequestSummary(taskRef, {
|
|
208
|
+
cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
|
|
209
|
+
manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
|
|
210
|
+
});
|
|
211
|
+
if (!['applied', 'no-op'].includes(staged.status)) transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
|
|
212
|
+
transitionAndPersist('summary-staged');
|
|
213
|
+
}
|
|
214
|
+
let receiptResult = readManualValidationReceipt(resolved.taskDir, {
|
|
215
|
+
transactionId: transaction.transactionId,
|
|
216
|
+
taskId: resolved.taskId,
|
|
217
|
+
prNumber: pullRequest.number,
|
|
218
|
+
prHeadSha: pullRequest.head.sha,
|
|
219
|
+
evidenceDigest: transaction.evidenceDigest,
|
|
220
|
+
artifact: values.artifact!
|
|
221
|
+
});
|
|
222
|
+
if (!receiptResult.ok && receiptResult.error.code !== 'MANUAL_VALIDATION_RECEIPT_MISSING') transactionFailure(receiptResult.error);
|
|
223
|
+
let receipt: ManualValidationReceipt;
|
|
224
|
+
if (receiptResult.ok) {
|
|
225
|
+
receipt = receiptResult.value;
|
|
226
|
+
} else {
|
|
227
|
+
receipt = createManualValidationReceipt({
|
|
228
|
+
transactionId: transaction.transactionId,
|
|
229
|
+
taskId: resolved.taskId,
|
|
230
|
+
prNumber: pullRequest.number,
|
|
231
|
+
prHeadSha: pullRequest.head.sha,
|
|
232
|
+
evidenceDigest: transaction.evidenceDigest,
|
|
233
|
+
artifact: values.artifact!,
|
|
234
|
+
artifactSha256: sha256File(artifactPath),
|
|
235
|
+
pendingSummaryDigest: transaction.pendingSummaryDigest,
|
|
236
|
+
finalSummaryDigest: transaction.finalSummaryDigest,
|
|
237
|
+
committedAt: new Date().toISOString()
|
|
238
|
+
});
|
|
239
|
+
writeManualValidationReceiptAtomic(resolved.taskDir, receipt);
|
|
240
|
+
}
|
|
241
|
+
if (transaction.phase === 'summary-staged') {
|
|
242
|
+
transitionAndPersist('receipt-committed', { committedReceipt: receipt.receiptDigest });
|
|
243
|
+
}
|
|
244
|
+
if (transaction.phase === 'receipt-committed' && !transaction.eventAppended) {
|
|
245
|
+
const completed = applyTaskEvent({ taskRef, event: 'manual-validation.completed', agent, initiator: 'model', requestId: transaction.transactionId, reasonCode: 'user-request', artifact: values.artifact, summaryResult: 'human-confirmed validation and committed receipt', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha }, { lockAlreadyHeld: true, repoRoot: cwd });
|
|
246
|
+
if (completed.status === 'failed') transactionFailure(completed.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'manual-validation completion event failed' });
|
|
247
|
+
transitionAndPersist('receipt-committed', { eventAppended: true });
|
|
248
|
+
}
|
|
249
|
+
if (transaction.phase === 'receipt-committed') {
|
|
250
|
+
transitionAndPersist('final-promotion-in-progress');
|
|
251
|
+
}
|
|
252
|
+
const promoted = await syncPullRequestSummary(taskRef, {
|
|
253
|
+
cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final', transaction.transactionId, receipt.receiptDigest, receipt.evidenceDigest, receipt.prHeadSha), changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult, strict: true,
|
|
254
|
+
manualValidation: { phase: 'final', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha, authority: 'coordinator' }, lockAlreadyHeld: true
|
|
255
|
+
});
|
|
256
|
+
if (!['applied', 'no-op'].includes(promoted.status)) transactionFailure(promoted.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary promotion failed' });
|
|
257
|
+
const postWrite = await summaryCommentState(taskRef, { cwd, client: options.client });
|
|
258
|
+
if (!/^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.test(postWrite.comment?.body ?? '') || postWrite.pullRequest?.head.sha !== receipt.prHeadSha || !manualValidationFinalSummaryProjectionMatches(postWrite.comment?.body ?? '', receipt)) transactionFailure({ code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message: 'final summary post-write verification failed' });
|
|
259
|
+
transitionAndPersist('committed', { postWriteVerified: true });
|
|
260
|
+
return result('applied', null, { transaction, receipt, idempotent: false });
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
263
|
+
if (transaction.phase === 'final-promotion-in-progress') {
|
|
264
|
+
try {
|
|
265
|
+
await syncPullRequestSummary(taskRef, {
|
|
266
|
+
cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
|
|
267
|
+
manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
|
|
268
|
+
});
|
|
269
|
+
} catch {
|
|
270
|
+
// Preserve recovery-required when pending compensation cannot be confirmed.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const failedTransaction = transitionManualValidationTransaction(transaction, 'recovery-required', { error: message.slice(0, 400) });
|
|
274
|
+
if (failedTransaction.ok) writeManualValidationTransactionAtomic(resolved.taskDir, failedTransaction.value);
|
|
275
|
+
return result('failed', { code: 'MANUAL_VALIDATION_TRANSACTION_RECOVERY_REQUIRED', message });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function parse(args: string[], start: number): { values: Record<string, string>; error?: string } {
|
|
280
|
+
const values: Record<string, string> = {};
|
|
281
|
+
for (let index = start; index < args.length; index += 1) {
|
|
282
|
+
const flag = args[index]!;
|
|
283
|
+
if (flag === '--prepare') {
|
|
284
|
+
if (values.prepare) return { values, error: `duplicate option '${flag}'` };
|
|
285
|
+
values.prepare = 'true';
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!['--format', '--cwd', '--artifact', '--summary-file', '--change-report-file', '--agent', '--result'].includes(flag)) return { values, error: `unknown option '${flag}'` };
|
|
289
|
+
const value = args[++index];
|
|
290
|
+
if (!value || value.startsWith('--')) return { values, error: `option '${flag}' requires a value` };
|
|
291
|
+
const key = flag.slice(2).replace(/-([a-z])/gu, (_, letter: string) => letter.toUpperCase());
|
|
292
|
+
if (values[key]) return { values, error: `duplicate option '${flag}'` };
|
|
293
|
+
values[key] = value;
|
|
294
|
+
}
|
|
295
|
+
return { values };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function manualValidation(args: string[] = []): Promise<void> {
|
|
299
|
+
if (!ensureInternalHandlerRoute('manual-validation', args)) return;
|
|
300
|
+
if (args[0] === '--help' || args[0] === '-h') { process.stdout.write(`${USAGE}\n`); return; }
|
|
301
|
+
const operation = args[0];
|
|
302
|
+
const isTransaction = operation === 'transaction' && internalHandlerRoute('manual-validation', 'transaction', operation);
|
|
303
|
+
if (!isTransaction) { printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'transaction operation is required' }); return; }
|
|
304
|
+
const taskRef = args[1];
|
|
305
|
+
if (!taskRef || taskRef.startsWith('--')) { printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: 'task ref is required' }); return; }
|
|
306
|
+
const parsed = parse(args, 2);
|
|
307
|
+
if (parsed.error) { printFailure('json', { code: 'MANUAL_VALIDATION_ARGS_INVALID', message: parsed.error }); return; }
|
|
308
|
+
const values = parsed.values;
|
|
309
|
+
const format = values.format === 'text' ? 'text' : 'json';
|
|
310
|
+
const output = await executeManualValidationTransaction(taskRef, values, path.resolve(values.cwd ?? process.cwd()));
|
|
311
|
+
process.stdout.write(format === 'json' ? `${JSON.stringify(output)}\n` : `${output.status === 'applied' ? 'Manual validation transaction committed.' : `${output.error?.code}: ${output.error?.message}`}\n`);
|
|
312
|
+
if (output.status === 'failed') process.exitCode = 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export { executeManualValidationTransaction, manualValidation };
|
|
@@ -25,11 +25,11 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
|
|
|
25
25
|
agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
|
|
26
26
|
agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
|
|
27
27
|
agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
|
|
28
|
-
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--strict] [--dry-run] [--cwd <path>]
|
|
28
|
+
agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--manual-phase pending|final ...] [--strict] [--dry-run] [--cwd <path>]
|
|
29
29
|
`;
|
|
30
30
|
|
|
31
31
|
const BOOLEAN_FLAGS = new Set(['--draft', '--dry-run', '--metadata', '--closing-issue', '--strict']);
|
|
32
|
-
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result']);
|
|
32
|
+
const VALUE_FLAGS = new Set(['--cwd', '--agent', '--base', '--head', '--title-file', '--body-file', '--change-report-file', '--mechanical-file', '--precheck-file', '--pr', '--result', '--manual-phase', '--transaction-id', '--receipt-digest', '--pr-head-sha']);
|
|
33
33
|
|
|
34
34
|
function key(flag: string): string {
|
|
35
35
|
return flag.slice(2).replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
@@ -111,7 +111,7 @@ async function platformPr(args: string[] = []): Promise<void> {
|
|
|
111
111
|
'sync-in-labels': ['cwd', 'pr', 'dryRun'],
|
|
112
112
|
'summary-context': ['cwd'],
|
|
113
113
|
'change-report': ['cwd', 'agent', 'mechanicalFile', 'precheckFile', 'dryRun'],
|
|
114
|
-
'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'dryRun', 'strict']
|
|
114
|
+
'summary-sync': ['cwd', 'agent', 'bodyFile', 'changeReportFile', 'result', 'manualPhase', 'transactionId', 'receiptDigest', 'prHeadSha', 'dryRun', 'strict']
|
|
115
115
|
};
|
|
116
116
|
const unexpected = Object.keys(values).find((name) => !allowed[operation]!.includes(name));
|
|
117
117
|
if (unexpected) { fail(`${operation} does not accept --${unexpected}`); return; }
|
|
@@ -160,7 +160,18 @@ async function platformPr(args: string[] = []): Promise<void> {
|
|
|
160
160
|
if (typeof values.changeReportFile !== 'string') { fail('summary-sync requires --change-report-file'); return; }
|
|
161
161
|
if (!primaryResult) { fail('summary-sync requires --result pr_created, pr_reused, or no_op'); return; }
|
|
162
162
|
try {
|
|
163
|
-
|
|
163
|
+
const manualPhase = values.manualPhase;
|
|
164
|
+
if (manualPhase !== undefined && manualPhase !== 'pending' && manualPhase !== 'final') { fail('--manual-phase must be pending or final'); return; }
|
|
165
|
+
finish(await syncPullRequestSummary(taskRef, {
|
|
166
|
+
cwd, agent: values.agent, body: readFile(values.bodyFile, cwd), changeReportFile: values.changeReportFile,
|
|
167
|
+
primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', dryRun: values.dryRun === true, strict: values.strict === true,
|
|
168
|
+
...(manualPhase ? { manualValidation: {
|
|
169
|
+
phase: manualPhase,
|
|
170
|
+
...(typeof values.transactionId === 'string' ? { transactionId: values.transactionId } : {}),
|
|
171
|
+
...(typeof values.receiptDigest === 'string' ? { receiptDigest: values.receiptDigest } : {}),
|
|
172
|
+
...(typeof values.prHeadSha === 'string' ? { prHeadSha: values.prHeadSha } : {})
|
|
173
|
+
} } : {})
|
|
174
|
+
}));
|
|
164
175
|
} catch (error) {
|
|
165
176
|
fail(`unable to read body file: ${error instanceof Error ? error.message : String(error)}`);
|
|
166
177
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
3
|
import { spawnSync } from 'node:child_process';
|
|
5
4
|
import semver from 'semver';
|
|
6
5
|
|
|
@@ -9,115 +8,95 @@ import { inspectPlatformRelease, reconcileReleaseMilestones } from '../platform/
|
|
|
9
8
|
import { inspectHomebrewChannel, inspectNpmChannel } from '../release/channels.ts';
|
|
10
9
|
import { releaseSnapshot } from '../release/workflow.ts';
|
|
11
10
|
import type { PostReleaseFacts, ReleaseFacts } from '../release/workflow.ts';
|
|
11
|
+
import { collectDemoTranscript } from './demo-transcript.ts';
|
|
12
|
+
import { createDemoPromotion, promoteDemoAssets, recoverDemoPromotion } from './demo-promotion.ts';
|
|
12
13
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
type CommandOptions = { env?: NodeJS.ProcessEnv };
|
|
16
|
+
|
|
17
|
+
function command(cwd: string, executable: string, args: string[], options: CommandOptions = {}) {
|
|
18
|
+
return spawnSync(executable, args, {
|
|
19
|
+
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...options.env }
|
|
20
|
+
});
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
type CommandResult = ReturnType<typeof command>;
|
|
19
|
-
type CommandRunner = (cwd: string, executable: string, args: string[]) => CommandResult;
|
|
24
|
+
type CommandRunner = (cwd: string, executable: string, args: string[], options?: CommandOptions) => CommandResult;
|
|
20
25
|
type DemoResult = {
|
|
21
26
|
status: 'recorded' | 'skipped' | 'failed';
|
|
22
|
-
reasonCode: '
|
|
27
|
+
reasonCode: 'DEMO_TRANSCRIPT_UNCHANGED' | 'DEMO_TRANSCRIPT_UNAVAILABLE' | 'DEMO_TRANSCRIPT_FAILED'
|
|
28
|
+
| 'DEMO_PROMOTION_FAILED' | 'DEMO_PROMOTION_RECOVERY_REQUIRED' | 'DEMO_PROMOTION_CLEANUP_FAILED'
|
|
29
|
+
| 'GIT_LFS_MISSING' | 'VHS_MISSING' | 'FFMPEG_MISSING'
|
|
23
30
|
| 'DEMO_COMMAND_FAILED' | 'DEMO_OUTPUT_MISSING' | 'DEMO_OUTPUT_INVALID'
|
|
24
|
-
| 'DEMO_OUTPUT_TOO_LARGE' |
|
|
31
|
+
| 'DEMO_OUTPUT_TOO_LARGE' | null;
|
|
25
32
|
message: string | null;
|
|
26
33
|
outputPath: string | null;
|
|
27
34
|
};
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
// lib/init.ts: non-visual config serialization changes must not re-record it.
|
|
31
|
-
const DEMO_INPUT_PATHS = [
|
|
32
|
-
'assets/demo-init.tape',
|
|
33
|
-
'scripts/demo-regen.sh',
|
|
34
|
-
'scripts/normalize-gif-duration.py',
|
|
35
|
-
'bin/cli.ts',
|
|
36
|
-
'lib/log.ts',
|
|
37
|
-
'lib/prompt.ts',
|
|
38
|
-
'lib/paths.ts',
|
|
39
|
-
'lib/render.ts',
|
|
40
|
-
'lib/sandbox/engines/'
|
|
41
|
-
] as const;
|
|
42
|
-
const DEMO_DIGEST_PATH = 'assets/demo-init.inputs.sha256';
|
|
36
|
+
const DEMO_DIGEST_PATH = 'assets/demo-init.transcript.sha256';
|
|
43
37
|
const DEMO_OUTPUT_PATH = 'assets/demo-init.gif';
|
|
44
38
|
const DEMO_MAX_BYTES = 4 * 1024 * 1024;
|
|
45
39
|
|
|
46
|
-
function demoInputFiles(cwd: string): string[] {
|
|
47
|
-
const files = new Set<string>();
|
|
48
|
-
for (const input of DEMO_INPUT_PATHS) {
|
|
49
|
-
if (input.endsWith('/')) {
|
|
50
|
-
const listed = command(cwd, 'git', ['ls-files', '--', input]);
|
|
51
|
-
if (listed.status !== 0) throw new Error(String(listed.stderr || `Unable to list ${input}`));
|
|
52
|
-
const directoryFiles = String(listed.stdout).split('\n').filter(Boolean);
|
|
53
|
-
if (!directoryFiles.length) throw new Error(`Canonical demo input directory is empty: ${input}`);
|
|
54
|
-
for (const file of directoryFiles) files.add(file.replaceAll('\\', '/'));
|
|
55
|
-
} else {
|
|
56
|
-
files.add(input);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const sorted = [...files].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
|
|
60
|
-
if (!sorted.length) throw new Error('Canonical demo input set is empty');
|
|
61
|
-
for (const file of sorted) {
|
|
62
|
-
const absolute = path.join(cwd, file);
|
|
63
|
-
if (!fs.statSync(absolute).isFile()) throw new Error(`Canonical demo input is not a file: ${file}`);
|
|
64
|
-
}
|
|
65
|
-
return sorted;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function computeDemoInputDigest(cwd: string): string {
|
|
69
|
-
const hash = crypto.createHash('sha256');
|
|
70
|
-
for (const file of demoInputFiles(cwd)) {
|
|
71
|
-
const bytes = fs.readFileSync(path.join(cwd, file));
|
|
72
|
-
hash.update(file);
|
|
73
|
-
hash.update('\0');
|
|
74
|
-
hash.update(String(bytes.byteLength));
|
|
75
|
-
hash.update('\0');
|
|
76
|
-
hash.update(bytes);
|
|
77
|
-
hash.update('\0');
|
|
78
|
-
}
|
|
79
|
-
return hash.digest('hex');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
40
|
function validGif(filePath: string): boolean {
|
|
83
41
|
if (!fs.existsSync(filePath)) return false;
|
|
84
42
|
const header = fs.readFileSync(filePath).subarray(0, 6).toString('ascii');
|
|
85
43
|
return header === 'GIF87a' || header === 'GIF89a';
|
|
86
44
|
}
|
|
87
45
|
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return { status: 'failed', reasonCode: 'DEMO_DIGEST_FAILED', message: String(error), outputPath: null };
|
|
46
|
+
async function runOptionalDemo(
|
|
47
|
+
cwd: string,
|
|
48
|
+
run: CommandRunner = command,
|
|
49
|
+
collect = collectDemoTranscript
|
|
50
|
+
): Promise<DemoResult> {
|
|
51
|
+
const recovery = recoverDemoPromotion(cwd);
|
|
52
|
+
if (recovery?.status === 'failed') {
|
|
53
|
+
return { status: 'failed', reasonCode: recovery.code === 'DEMO_PROMOTION_CLEANUP_FAILED' ? recovery.code : 'DEMO_PROMOTION_RECOVERY_REQUIRED', message: recovery.message, outputPath: null };
|
|
54
|
+
}
|
|
55
|
+
const transcript = await collect(cwd);
|
|
56
|
+
if (transcript.status === 'failed') {
|
|
57
|
+
return { status: 'failed', reasonCode: transcript.reasonCode, message: transcript.message, outputPath: null };
|
|
101
58
|
}
|
|
102
59
|
const digestPath = path.join(cwd, DEMO_DIGEST_PATH);
|
|
103
60
|
const priorDigest = fs.existsSync(digestPath) ? fs.readFileSync(digestPath, 'utf8').trim() : '';
|
|
104
|
-
if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest ===
|
|
105
|
-
return { status: 'skipped', reasonCode: '
|
|
61
|
+
if (/^[0-9a-f]{64}$/.test(priorDigest) && priorDigest === transcript.sha256) {
|
|
62
|
+
return { status: 'skipped', reasonCode: 'DEMO_TRANSCRIPT_UNCHANGED', message: null, outputPath: null };
|
|
106
63
|
}
|
|
64
|
+
const promotion = createDemoPromotion(cwd, transcript.transcript);
|
|
65
|
+
const discardPromotion = () => fs.rmSync(promotion.directory, { recursive: true, force: true });
|
|
107
66
|
const lfs = run(cwd, 'git', ['lfs', 'version']);
|
|
108
67
|
if (lfs.status !== 0) {
|
|
68
|
+
discardPromotion();
|
|
109
69
|
return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: String(lfs.stderr || lfs.stdout), outputPath: null };
|
|
110
70
|
}
|
|
111
71
|
const lfsAttribute = run(cwd, 'git', ['check-attr', 'filter', '--', DEMO_OUTPUT_PATH]);
|
|
112
72
|
if (lfsAttribute.status !== 0 || !String(lfsAttribute.stdout).trim().endsWith(': lfs')) {
|
|
73
|
+
discardPromotion();
|
|
113
74
|
return { status: 'failed', reasonCode: 'GIT_LFS_MISSING', message: `${DEMO_OUTPUT_PATH} is not tracked by Git LFS`, outputPath: null };
|
|
114
75
|
}
|
|
115
76
|
const vhs = run(cwd, 'vhs', ['--version']);
|
|
116
|
-
if (vhs.status !== 0)
|
|
77
|
+
if (vhs.status !== 0) {
|
|
78
|
+
discardPromotion();
|
|
79
|
+
return {
|
|
80
|
+
status: 'failed',
|
|
81
|
+
reasonCode: 'VHS_MISSING',
|
|
82
|
+
message: String(vhs.stderr || vhs.stdout) || 'VHS is required to record the changed demo transcript',
|
|
83
|
+
outputPath: null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
117
86
|
const ffmpeg = run(cwd, 'ffmpeg', ['-version']);
|
|
118
|
-
if (ffmpeg.status !== 0)
|
|
119
|
-
|
|
87
|
+
if (ffmpeg.status !== 0) {
|
|
88
|
+
discardPromotion();
|
|
89
|
+
return {
|
|
90
|
+
status: 'failed',
|
|
91
|
+
reasonCode: 'FFMPEG_MISSING',
|
|
92
|
+
message: String(ffmpeg.stderr || ffmpeg.stdout) || 'ffmpeg is required to record the changed demo transcript',
|
|
93
|
+
outputPath: null
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const stagedOutputPath = path.relative(cwd, promotion.staging.gif).replaceAll(path.sep, '/');
|
|
97
|
+
const demo = run(cwd, 'npm', ['run', 'demo:regen'], { env: { DEMO_OUTPUT_PATH: stagedOutputPath } });
|
|
120
98
|
if (demo.status !== 0) {
|
|
99
|
+
discardPromotion();
|
|
121
100
|
return {
|
|
122
101
|
status: 'failed',
|
|
123
102
|
reasonCode: 'DEMO_COMMAND_FAILED',
|
|
@@ -126,7 +105,8 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
|
|
|
126
105
|
};
|
|
127
106
|
}
|
|
128
107
|
const outputPath = DEMO_OUTPUT_PATH;
|
|
129
|
-
if (!fs.existsSync(
|
|
108
|
+
if (!fs.existsSync(promotion.staging.gif)) {
|
|
109
|
+
discardPromotion();
|
|
130
110
|
return {
|
|
131
111
|
status: 'failed',
|
|
132
112
|
reasonCode: 'DEMO_OUTPUT_MISSING',
|
|
@@ -134,18 +114,24 @@ function runOptionalDemo(cwd: string, run: CommandRunner = command): DemoResult
|
|
|
134
114
|
outputPath: null
|
|
135
115
|
};
|
|
136
116
|
}
|
|
137
|
-
const absoluteOutput =
|
|
117
|
+
const absoluteOutput = promotion.staging.gif;
|
|
138
118
|
if (!validGif(absoluteOutput)) {
|
|
119
|
+
discardPromotion();
|
|
139
120
|
return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: `${outputPath} is not a GIF`, outputPath: null };
|
|
140
121
|
}
|
|
141
122
|
if (fs.statSync(absoluteOutput).size > DEMO_MAX_BYTES) {
|
|
123
|
+
discardPromotion();
|
|
142
124
|
return { status: 'failed', reasonCode: 'DEMO_OUTPUT_TOO_LARGE', message: `${outputPath} exceeds 4 MiB`, outputPath: null };
|
|
143
125
|
}
|
|
144
|
-
const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${
|
|
126
|
+
const pointer = run(cwd, 'git', ['lfs', 'pointer', `--file=${stagedOutputPath}`]);
|
|
145
127
|
if (pointer.status !== 0 || !String(pointer.stdout).includes(`size ${fs.statSync(absoluteOutput).size}`)) {
|
|
128
|
+
discardPromotion();
|
|
146
129
|
return { status: 'failed', reasonCode: 'DEMO_OUTPUT_INVALID', message: 'Git LFS could not produce a matching pointer', outputPath: null };
|
|
147
130
|
}
|
|
148
|
-
|
|
131
|
+
const promoted = promoteDemoAssets(cwd, promotion.staging, `${process.pid}-${Date.now()}`);
|
|
132
|
+
if (promoted.status === 'failed') {
|
|
133
|
+
return { status: 'failed', reasonCode: promoted.code ?? 'DEMO_PROMOTION_FAILED', message: promoted.message, outputPath: null };
|
|
134
|
+
}
|
|
149
135
|
return { status: 'recorded', reasonCode: null, message: null, outputPath };
|
|
150
136
|
}
|
|
151
137
|
|
|
@@ -207,7 +193,7 @@ function inspectPostReleaseFacts(
|
|
|
207
193
|
remoteHead,
|
|
208
194
|
newVersion,
|
|
209
195
|
changedPaths,
|
|
210
|
-
|
|
196
|
+
demoTranscriptSha256: digest && /^[0-9a-f]{64}$/.test(digest) ? digest : null,
|
|
211
197
|
worktree: snapshot?.worktree ?? [],
|
|
212
198
|
staged: snapshot?.staged ?? []
|
|
213
199
|
};
|
|
@@ -333,7 +319,8 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
333
319
|
if (internalHandlerRoute('release-workflow', 'prepare', action || '')) {
|
|
334
320
|
if (before.phase !== 'unprepared') {
|
|
335
321
|
const milestones = await reconcileReleaseMilestones(version, { cwd });
|
|
336
|
-
|
|
322
|
+
const snapshot = releaseSnapshot(version, await inspectFacts(cwd, version));
|
|
323
|
+
process.stdout.write(`${JSON.stringify({ ...milestones, snapshot })}\n`);
|
|
337
324
|
process.exitCode = milestones.status === 'failed' ? 1 : milestones.status === 'blocked' ? 2 : 0;
|
|
338
325
|
return;
|
|
339
326
|
}
|
|
@@ -420,6 +407,11 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
420
407
|
if (!['published', 'post-pending'].includes(before.phase) || !channelsComplete || before.facts.smoke !== 'success') {
|
|
421
408
|
process.stdout.write(`${JSON.stringify({ status: before.facts.smoke === 'failed' ? 'failed' : 'blocked', changed: false, snapshot: before, error: { code: 'RELEASE_CHANNELS_PENDING', message: 'Release channels or smoke workflow are not complete' } })}\n`); process.exitCode = before.facts.smoke === 'failed' ? 1 : 2; return;
|
|
422
409
|
}
|
|
410
|
+
const recovery = recoverDemoPromotion(cwd);
|
|
411
|
+
if (recovery?.status === 'failed') {
|
|
412
|
+
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: recovery.code, message: recovery.message } })}\n`);
|
|
413
|
+
process.exitCode = 1; return;
|
|
414
|
+
}
|
|
423
415
|
const worktreeError = inspectPostWorktree(cwd);
|
|
424
416
|
if (worktreeError) {
|
|
425
417
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, snapshot: before, error: worktreeError })}\n`);
|
|
@@ -430,7 +422,7 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
430
422
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, error: { code: 'RELEASE_POST_COMMAND_FAILED', message: String(built.stderr || built.stdout) } })}\n`);
|
|
431
423
|
process.exitCode = 1; return;
|
|
432
424
|
}
|
|
433
|
-
const demo = runOptionalDemo(cwd);
|
|
425
|
+
const demo = await runOptionalDemo(cwd);
|
|
434
426
|
if (demo.status === 'failed') {
|
|
435
427
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: true, snapshot: before, demo, error: { code: 'RELEASE_POST_DEMO_FAILED', message: demo.message } })}\n`);
|
|
436
428
|
process.exitCode = 1; return;
|
|
@@ -449,5 +441,5 @@ async function releaseWorkflow(args: string[] = []): Promise<void> {
|
|
|
449
441
|
process.stdout.write(`${JSON.stringify({ status: 'applied', changed: true, demo, snapshot, error: null })}\n`);
|
|
450
442
|
}
|
|
451
443
|
|
|
452
|
-
export { changedPaths,
|
|
444
|
+
export { changedPaths, inspectFacts, inspectLocalReleaseFacts, inspectPostReleaseFacts, inspectPostWorktree, releaseSmokeStatus, releaseWorkflow, runOptionalDemo };
|
|
453
445
|
export type { CommandRunner, DemoResult };
|