@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
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
|
|
5
|
-
import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
2
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
3
|
+
import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.js';
|
|
6
4
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
7
5
|
import { finalizeReviewSummaryContent } from './review-artifacts.js';
|
|
8
6
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
@@ -12,18 +10,15 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
12
10
|
import { allowsManualOverride } from './guard-override.js';
|
|
13
11
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
14
12
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
15
|
-
import {
|
|
13
|
+
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
14
|
+
import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryCommit, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
16
15
|
const STAGES = {
|
|
17
|
-
analysis: { family: 'review-analysis'
|
|
18
|
-
plan: { family: 'review-plan'
|
|
19
|
-
code: { family: 'review-code'
|
|
16
|
+
analysis: { family: 'review-analysis' },
|
|
17
|
+
plan: { family: 'review-plan' },
|
|
18
|
+
code: { family: 'review-code' }
|
|
20
19
|
};
|
|
21
20
|
const DEFAULT_FILE_SYSTEM = {
|
|
22
|
-
readFileSync: (file) => fs.readFileSync(file, 'utf8')
|
|
23
|
-
statModeSync: (file) => fs.statSync(file).mode,
|
|
24
|
-
writeFileSync: (file, content, mode) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: 'wx', mode }),
|
|
25
|
-
renameSync: (from, to) => fs.renameSync(from, to),
|
|
26
|
-
unlinkSync: (file) => fs.unlinkSync(file)
|
|
21
|
+
readFileSync: (file) => fs.readFileSync(file, 'utf8')
|
|
27
22
|
};
|
|
28
23
|
function failed(request, code, message, taskId = null, stageStatus = null, extra = {}) {
|
|
29
24
|
return {
|
|
@@ -37,271 +32,235 @@ function failed(request, code, message, taskId = null, stageStatus = null, extra
|
|
|
37
32
|
stageStatus,
|
|
38
33
|
artifactSha256: null,
|
|
39
34
|
semanticDigest: null,
|
|
40
|
-
repairable: false,
|
|
41
|
-
operation: null,
|
|
42
35
|
operations: [],
|
|
43
36
|
error: { code, message },
|
|
44
37
|
...extra
|
|
45
38
|
};
|
|
46
39
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
function cleanupTemp(fileSystem, tempPath) {
|
|
55
|
-
try {
|
|
56
|
-
fileSystem.unlinkSync(tempPath);
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// Best-effort cleanup preserves the primary failure.
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
40
|
+
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
41
|
+
function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
42
|
+
let taskId = null;
|
|
43
|
+
let stageStatus = null;
|
|
44
|
+
const reject = (code, message, extra = {}) => ({
|
|
45
|
+
result: failed(request, code, message, taskId, stageStatus, extra), content: artifactContent
|
|
46
|
+
});
|
|
63
47
|
const stage = request.stage;
|
|
64
48
|
const spec = STAGES[stage];
|
|
65
49
|
if (!spec)
|
|
66
|
-
return
|
|
67
|
-
if (!request.taskRef || !request.artifact)
|
|
68
|
-
return
|
|
69
|
-
}
|
|
50
|
+
return reject('REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
|
|
51
|
+
if (!request.taskRef || !request.artifact)
|
|
52
|
+
return reject('REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
|
|
70
53
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
54
|
+
taskId = resolved.taskId ?? null;
|
|
71
55
|
if (!resolved.ok)
|
|
72
|
-
return
|
|
56
|
+
return reject(resolved.code, resolved.message);
|
|
73
57
|
if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
|
|
74
|
-
return
|
|
58
|
+
return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
|
|
75
59
|
}
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
|
|
83
|
-
? 'REVIEW_ARTIFACT_NOT_REGULAR'
|
|
84
|
-
: 'REVIEW_ARTIFACT_IDENTITY_INVALID';
|
|
85
|
-
return failed(request, code, validated.error.message, resolved.taskId);
|
|
86
|
-
}
|
|
87
|
-
const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
60
|
+
const parsed = parseArtifactName(request.artifact);
|
|
61
|
+
if (!parsed || parsed.family !== spec.family)
|
|
62
|
+
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`);
|
|
63
|
+
const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
|
|
64
|
+
if (publicationError)
|
|
65
|
+
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message);
|
|
88
66
|
let taskContent;
|
|
89
|
-
let artifactContent;
|
|
90
67
|
try {
|
|
91
|
-
taskContent = fileSystem.readFileSync(resolved.taskMdPath);
|
|
92
|
-
artifactContent = fileSystem.readFileSync(validated.artifact.path);
|
|
68
|
+
taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath);
|
|
93
69
|
}
|
|
94
70
|
catch (error) {
|
|
95
|
-
return
|
|
71
|
+
return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
|
|
72
|
+
}
|
|
73
|
+
let recovery;
|
|
74
|
+
if (request.recoveryId) {
|
|
75
|
+
try {
|
|
76
|
+
const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
77
|
+
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
78
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
|
|
79
|
+
if (!['awaiting-recovery', 'finalize-ready', 'commit-started'].includes(intent.state))
|
|
80
|
+
return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
|
|
81
|
+
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
82
|
+
artifactContent = fs.existsSync(recovery.stagingPath)
|
|
83
|
+
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
84
|
+
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error));
|
|
88
|
+
}
|
|
96
89
|
}
|
|
97
|
-
const schema = getArtifactSchema(spec.family);
|
|
98
|
-
const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
|
|
99
90
|
const artifactSha256 = sha256Content(artifactContent);
|
|
100
|
-
const
|
|
91
|
+
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
92
|
+
const digests = { artifactSha256, semanticDigest };
|
|
101
93
|
let repairIntent;
|
|
102
94
|
try {
|
|
103
|
-
repairIntent =
|
|
95
|
+
repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
104
96
|
}
|
|
105
97
|
catch (error) {
|
|
106
|
-
return
|
|
107
|
-
artifactSha256,
|
|
108
|
-
semanticDigest: artifactSemanticDigest
|
|
109
|
-
});
|
|
98
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
110
99
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (repairIntent && (repairIntent.state !== 'awaiting-repair' ||
|
|
115
|
-
repairIntent.artifactSha256 !== artifactSha256 ||
|
|
116
|
-
repairIntent.semanticDigest !== structure.semanticDigest)) {
|
|
117
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
|
|
118
|
-
artifactSha256,
|
|
119
|
-
semanticDigest: structure.semanticDigest
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
if (!repairIntent) {
|
|
124
|
-
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
125
|
-
version: 1,
|
|
126
|
-
taskId: resolved.taskId,
|
|
127
|
-
family: spec.family,
|
|
128
|
-
artifact: request.artifact,
|
|
129
|
-
state: 'awaiting-repair',
|
|
130
|
-
baselineSemanticDigest: structure.semanticDigest,
|
|
131
|
-
artifactSha256,
|
|
132
|
-
semanticDigest: structure.semanticDigest
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
|
|
138
|
-
artifactSha256,
|
|
139
|
-
semanticDigest: structure.semanticDigest,
|
|
140
|
-
operation: structure.repair
|
|
141
|
-
});
|
|
142
|
-
}
|
|
100
|
+
if (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed') {
|
|
101
|
+
if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
|
|
102
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
|
|
143
103
|
}
|
|
144
|
-
return failed(request, 'REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), resolved.taskId, null, {
|
|
145
|
-
artifactSha256,
|
|
146
|
-
semanticDigest: structure.semanticDigest,
|
|
147
|
-
repairable,
|
|
148
|
-
operation: structure.repair
|
|
149
|
-
});
|
|
150
104
|
}
|
|
151
|
-
if (
|
|
152
|
-
return
|
|
153
|
-
artifactSha256,
|
|
154
|
-
semanticDigest: artifactSemanticDigest
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
|
|
158
|
-
&& (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
|
|
159
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
|
|
160
|
-
artifactSha256,
|
|
161
|
-
semanticDigest: artifactSemanticDigest
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
|
|
165
|
-
return failed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
|
|
105
|
+
if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
|
|
106
|
+
return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
|
|
166
107
|
}
|
|
167
108
|
const execution = validateLifecycleExecution(request.taskRef, {
|
|
168
109
|
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
169
|
-
identity: {
|
|
170
|
-
stage: spec.family,
|
|
171
|
-
round: parsedArtifact.round,
|
|
172
|
-
artifact: request.artifact,
|
|
173
|
-
role: 'reviewer'
|
|
174
|
-
},
|
|
110
|
+
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
175
111
|
dryRun: request.dryRun
|
|
176
112
|
}, { repoRoot: options.repoRoot });
|
|
177
113
|
if (!execution.ok) {
|
|
178
|
-
return
|
|
114
|
+
return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
|
|
179
115
|
}
|
|
180
|
-
let rows;
|
|
181
116
|
try {
|
|
182
117
|
const ledger = parseLedgerDocument(taskContent);
|
|
183
|
-
if (!ledger.present)
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
|
|
118
|
+
if (!ledger.present)
|
|
119
|
+
return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
120
|
+
const error = validateLedgerRows(ledger.rows);
|
|
121
|
+
if (error)
|
|
122
|
+
return reject('REVIEW_LEDGER_INVALID', `${error.code}: ${error.message}`);
|
|
123
|
+
stageStatus = summarizeLedgerStage(ledger.rows, stage);
|
|
187
124
|
}
|
|
188
125
|
catch (error) {
|
|
189
|
-
return
|
|
126
|
+
return reject('REVIEW_LEDGER_INVALID', String(error));
|
|
190
127
|
}
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return failed(request, 'REVIEW_DECISION_DETAIL_INVALID', `${detailInspection.code}: ${detailInspection.message}`, resolved.taskId, stageStatus);
|
|
199
|
-
}
|
|
200
|
-
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
|
|
201
|
-
if (!transformed.ok) {
|
|
202
|
-
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
203
|
-
}
|
|
204
|
-
if (!transformed.changed) {
|
|
205
|
-
if (repairIntent?.state === 'awaiting-repair') {
|
|
128
|
+
const detail = inspectDecisionDetailDuplicates(artifactContent);
|
|
129
|
+
if (!detail.ok)
|
|
130
|
+
return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
|
|
131
|
+
const schema = getArtifactSchema(spec.family);
|
|
132
|
+
const structure = inspectArtifactContract(artifactContent, schema);
|
|
133
|
+
if (!structure.ok) {
|
|
134
|
+
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
206
135
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
semanticDigest: artifactSemanticDigest
|
|
212
|
-
});
|
|
136
|
+
recovery = beginArtifactRecovery({
|
|
137
|
+
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
138
|
+
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
139
|
+
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
213
140
|
}
|
|
214
141
|
catch (error) {
|
|
215
|
-
return
|
|
216
|
-
artifactSha256,
|
|
217
|
-
semanticDigest: artifactSemanticDigest
|
|
218
|
-
});
|
|
142
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
219
143
|
}
|
|
220
144
|
}
|
|
221
145
|
return {
|
|
222
|
-
...
|
|
223
|
-
artifactSha256,
|
|
224
|
-
|
|
146
|
+
...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
|
|
147
|
+
artifactSha256, semanticDigest: structure.semanticDigest,
|
|
148
|
+
...(recovery ? { recovery: {
|
|
149
|
+
recoveryId: recovery.recoveryId,
|
|
150
|
+
candidatePath: recovery.stagingPath,
|
|
151
|
+
baselineSha256: recovery.baselineSha256,
|
|
152
|
+
baselineSemanticDigest: recovery.baselineSemanticDigest
|
|
153
|
+
} } : {})
|
|
225
154
|
}),
|
|
226
|
-
|
|
227
|
-
error: null
|
|
155
|
+
...(recovery ? { recovery } : {})
|
|
228
156
|
};
|
|
229
157
|
}
|
|
230
|
-
const
|
|
231
|
-
if (
|
|
158
|
+
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
|
|
159
|
+
if (!transformed.ok)
|
|
160
|
+
return reject(transformed.code, transformed.message);
|
|
161
|
+
const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
|
|
162
|
+
if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
|
|
163
|
+
if (transformed.changed)
|
|
164
|
+
return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
|
|
232
165
|
return {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
166
|
+
content: transformed.content,
|
|
167
|
+
result: {
|
|
168
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
169
|
+
status: 'no-op',
|
|
170
|
+
changed: false,
|
|
171
|
+
error: null
|
|
172
|
+
},
|
|
173
|
+
lockAlreadyHeld: options.lockAlreadyHeld
|
|
241
174
|
};
|
|
242
175
|
}
|
|
243
|
-
|
|
244
|
-
|
|
176
|
+
if (!recovery && !request.dryRun && options.startRecovery) {
|
|
177
|
+
try {
|
|
178
|
+
recovery = beginArtifactRecovery({
|
|
179
|
+
taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
|
|
180
|
+
requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
|
|
181
|
+
}, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
content: transformed.content,
|
|
189
|
+
result: {
|
|
190
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
|
|
191
|
+
status: transformed.changed ? request.dryRun ? 'planned' : 'applied' : 'no-op',
|
|
192
|
+
changed: transformed.changed,
|
|
193
|
+
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
194
|
+
error: null
|
|
195
|
+
},
|
|
196
|
+
...(recovery ? { recovery } : {}),
|
|
197
|
+
lockAlreadyHeld: options.lockAlreadyHeld
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function commitReviewSummaryProvenance(prepared, repoRoot) {
|
|
201
|
+
if (prepared.result.status === 'failed' || !prepared.recovery)
|
|
202
|
+
return prepared.result;
|
|
245
203
|
try {
|
|
246
|
-
|
|
247
|
-
|
|
204
|
+
const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
|
|
205
|
+
if (!intent)
|
|
206
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
207
|
+
let committed;
|
|
208
|
+
if (intent.state === 'finalize-ready' || intent.state === 'commit-started') {
|
|
209
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
213
|
+
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
214
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
215
|
+
}
|
|
216
|
+
if (committed.state !== 'passed')
|
|
217
|
+
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed.state}'`);
|
|
218
|
+
return {
|
|
219
|
+
...prepared.result,
|
|
220
|
+
artifactSha256: committed.finalArtifactSha256,
|
|
221
|
+
semanticDigest: committed.finalSemanticDigest,
|
|
222
|
+
error: null
|
|
223
|
+
};
|
|
248
224
|
}
|
|
249
225
|
catch (error) {
|
|
250
|
-
|
|
251
|
-
|
|
226
|
+
return { ...prepared.result, status: 'failed', error: {
|
|
227
|
+
code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
|
|
228
|
+
} };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function finalizeReviewSummaryUnlocked(request, options = {}) {
|
|
232
|
+
const spec = STAGES[request.stage];
|
|
233
|
+
if (!spec || !request.taskRef || !request.artifact)
|
|
234
|
+
return prepareReviewSummaryCandidate(request, '', options).result;
|
|
235
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
236
|
+
if (!resolved.ok)
|
|
237
|
+
return failed(request, resolved.code, resolved.message, resolved.taskId);
|
|
238
|
+
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
239
|
+
if (!validated.ok) {
|
|
240
|
+
return failed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
252
241
|
}
|
|
242
|
+
const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
243
|
+
let content;
|
|
253
244
|
try {
|
|
254
|
-
|
|
255
|
-
cleanupTemp(fileSystem, tempPath);
|
|
256
|
-
return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, stageStatus);
|
|
257
|
-
}
|
|
258
|
-
fileSystem.renameSync(tempPath, validated.artifact.path);
|
|
245
|
+
content = fileSystem.readFileSync(validated.artifact.path);
|
|
259
246
|
}
|
|
260
247
|
catch (error) {
|
|
261
|
-
|
|
262
|
-
return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
|
|
263
|
-
}
|
|
264
|
-
if (repairIntent?.state === 'awaiting-repair') {
|
|
265
|
-
try {
|
|
266
|
-
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
267
|
-
...repairIntent,
|
|
268
|
-
state: 'passed',
|
|
269
|
-
artifactSha256: sha256Content(transformed.content),
|
|
270
|
-
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
|
|
275
|
-
artifactSha256: sha256Content(transformed.content),
|
|
276
|
-
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
277
|
-
});
|
|
278
|
-
}
|
|
248
|
+
return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
279
249
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
requestRef: request.taskRef,
|
|
285
|
-
taskId: resolved.taskId,
|
|
286
|
-
stage,
|
|
287
|
-
artifact: request.artifact,
|
|
288
|
-
stageStatus,
|
|
289
|
-
artifactSha256: sha256Content(transformed.content),
|
|
290
|
-
semanticDigest: canonicalSemanticDigest(transformed.content),
|
|
291
|
-
repairable: false,
|
|
292
|
-
operation: null,
|
|
293
|
-
operations,
|
|
294
|
-
error: null
|
|
295
|
-
};
|
|
250
|
+
const prepared = prepareReviewSummaryCandidate(request, content, options);
|
|
251
|
+
if (request.dryRun)
|
|
252
|
+
return prepared.result;
|
|
253
|
+
return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
|
|
296
254
|
}
|
|
297
255
|
function finalizeReviewSummary(request, options = {}) {
|
|
256
|
+
const effectiveOptions = { ...options, startRecovery: !request.dryRun };
|
|
298
257
|
if (request.dryRun || options.lockAlreadyHeld)
|
|
299
|
-
return finalizeReviewSummaryUnlocked(request,
|
|
258
|
+
return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
300
259
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
301
260
|
if (!resolved.ok)
|
|
302
|
-
return finalizeReviewSummaryUnlocked(request,
|
|
261
|
+
return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
303
262
|
try {
|
|
304
|
-
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => finalizeReviewSummaryUnlocked(request,
|
|
263
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-review.finalize-summary', () => finalizeReviewSummaryUnlocked(request, { ...effectiveOptions, lockAlreadyHeld: true }));
|
|
305
264
|
}
|
|
306
265
|
catch (error) {
|
|
307
266
|
if (!(error instanceof TaskExecutionLockError))
|
|
@@ -309,5 +268,5 @@ function finalizeReviewSummary(request, options = {}) {
|
|
|
309
268
|
return failed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
|
|
310
269
|
}
|
|
311
270
|
}
|
|
312
|
-
export { finalizeReviewSummary };
|
|
271
|
+
export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
|
|
313
272
|
//# sourceMappingURL=review-finalization.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { artifactName,
|
|
3
|
+
import { artifactName, maxArtifactRound } from "./artifact-name.js";
|
|
4
4
|
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.js";
|
|
5
5
|
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
6
6
|
export function parseReviewedGitTree(value) {
|
|
@@ -29,7 +29,7 @@ export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
|
|
|
29
29
|
}
|
|
30
30
|
export function findAuthoritativeReviewCodeArtifact(taskDir) {
|
|
31
31
|
const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
|
|
32
|
-
const round =
|
|
32
|
+
const round = maxArtifactRound(entries, "review-code");
|
|
33
33
|
if (round === 0) {
|
|
34
34
|
return { ok: false, round: 0, fileName: null, path: null, error: null };
|
|
35
35
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scanVisibleMarkdown } from './markdown.js';
|
|
1
2
|
function escapeRegExp(value) {
|
|
2
3
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
4
|
}
|
|
@@ -27,10 +28,13 @@ function parseTable(content, input) {
|
|
|
27
28
|
throw new DocumentMutationError('TASK_DOCUMENT_INVALID', 'table section is ambiguous');
|
|
28
29
|
}
|
|
29
30
|
const section = sections[0];
|
|
31
|
+
const visibleStarts = new Set(scanVisibleMarkdown(content).lines.map((line) => line.start));
|
|
30
32
|
const sectionLines = linesOf(content).filter((line) => line.start >= section.bodyStart && line.start < section.end);
|
|
31
33
|
const firstContent = sectionLines.findIndex((line) => line.text.trim() !== '');
|
|
32
34
|
const tables = [];
|
|
33
35
|
for (let index = 0; index + 1 < sectionLines.length; index += 1) {
|
|
36
|
+
if (!visibleStarts.has(sectionLines[index].start) || !visibleStarts.has(sectionLines[index + 1].start))
|
|
37
|
+
continue;
|
|
34
38
|
const header = splitTableCells(sectionLines[index].text);
|
|
35
39
|
const separator = splitTableCells(sectionLines[index + 1].text);
|
|
36
40
|
if (!header || !separator || header.cells.length !== input.columns.length)
|
|
@@ -97,24 +101,23 @@ function documentEol(content) {
|
|
|
97
101
|
}
|
|
98
102
|
function matchingSections(content, aliases) {
|
|
99
103
|
const allowed = new Set(aliases);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
const lines = new Map(linesOf(content).map((line) => [line.start, line]));
|
|
105
|
+
const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2 || heading.level === 3);
|
|
106
|
+
return headings
|
|
107
|
+
.map((heading, index) => {
|
|
108
|
+
if (!allowed.has(heading.text))
|
|
104
109
|
return null;
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const nextHeading = /^(#{2,3})\s+/.exec(lines[i].text);
|
|
109
|
-
if (nextHeading && nextHeading[1].length <= level) {
|
|
110
|
-
end = lines[i].start;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return { heading: match[2], line, bodyStart: line.end, end };
|
|
110
|
+
const next = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level);
|
|
111
|
+
const line = lines.get(heading.start);
|
|
112
|
+
return { heading: heading.text, level: heading.level, line, bodyStart: line.end, end: next?.start ?? content.length };
|
|
115
113
|
})
|
|
116
114
|
.filter((match) => match !== null);
|
|
117
115
|
}
|
|
116
|
+
// Select the first visible section at the requested level, in document order.
|
|
117
|
+
function findSectionRange(content, aliases, level = 2) {
|
|
118
|
+
const section = matchingSections(content, aliases).find((candidate) => candidate.level === level);
|
|
119
|
+
return section ? { start: section.line.start, bodyStart: section.bodyStart, end: section.end, heading: section.heading } : null;
|
|
120
|
+
}
|
|
118
121
|
function validateAliases(aliases, name) {
|
|
119
122
|
if (!Array.isArray(aliases) || aliases.length === 0 || aliases.some((alias) => !alias || /[\r\n]/.test(alias))) {
|
|
120
123
|
throw new DocumentMutationError('MUTATION_INVALID', `${name} must contain headings`);
|
|
@@ -261,9 +264,12 @@ function mutateTableRow(content, mutation) {
|
|
|
261
264
|
throw new DocumentMutationError(sections.length === 0 ? 'TABLE_NOT_FOUND' : 'TASK_DOCUMENT_INVALID', sections.length === 0 ? 'table section not found' : 'table section is ambiguous');
|
|
262
265
|
}
|
|
263
266
|
const section = sections[0];
|
|
267
|
+
const visibleStarts = new Set(scanVisibleMarkdown(content).lines.map((line) => line.start));
|
|
264
268
|
const sectionLines = linesOf(content).filter((line) => line.start >= section.bodyStart && line.start < section.end);
|
|
265
269
|
const tables = [];
|
|
266
270
|
for (let i = 0; i + 1 < sectionLines.length; i += 1) {
|
|
271
|
+
if (!visibleStarts.has(sectionLines[i].start) || !visibleStarts.has(sectionLines[i + 1].start))
|
|
272
|
+
continue;
|
|
267
273
|
const header = splitTableCells(sectionLines[i].text);
|
|
268
274
|
const separator = splitTableCells(sectionLines[i + 1].text);
|
|
269
275
|
if (!header || !separator || header.cells.length !== mutation.columns.length)
|
|
@@ -368,25 +374,12 @@ function mutateTableRow(content, mutation) {
|
|
|
368
374
|
* lines are trimmed. Returns '' when no alias heading is present.
|
|
369
375
|
*/
|
|
370
376
|
function extractSection(content, aliases) {
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const line = lines[i].trim();
|
|
375
|
-
if (aliases.some((alias) => new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`).test(line))) {
|
|
376
|
-
start = i + 1;
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (start === -1)
|
|
377
|
+
const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
|
|
378
|
+
const index = headings.findIndex((heading) => aliases.includes(heading.text));
|
|
379
|
+
if (index === -1)
|
|
381
380
|
return '';
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (/^##\s+/.test(lines[i])) {
|
|
385
|
-
end = i;
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
|
|
381
|
+
return content.slice(headings[index].end + 1, headings[index + 1]?.start ?? content.length)
|
|
382
|
+
.replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, '');
|
|
390
383
|
}
|
|
391
384
|
/**
|
|
392
385
|
* Return the heading alias that actually appears as a `## {alias}` line, so a
|
|
@@ -394,11 +387,8 @@ function extractSection(content, aliases) {
|
|
|
394
387
|
* alias when none is present.
|
|
395
388
|
*/
|
|
396
389
|
function findSectionHeading(content, aliases) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return alias;
|
|
400
|
-
}
|
|
401
|
-
return aliases[0];
|
|
390
|
+
const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
|
|
391
|
+
return aliases.find((alias) => headings.some((heading) => heading.text === alias)) ?? aliases[0];
|
|
402
392
|
}
|
|
403
393
|
/**
|
|
404
394
|
* Return the body of the first `### {headingPrefix}` sub-section, from the
|
|
@@ -409,25 +399,13 @@ function findSectionHeading(content, aliases) {
|
|
|
409
399
|
* and trailing blank lines are trimmed. Returns '' when no match is present.
|
|
410
400
|
*/
|
|
411
401
|
function extractSubSection(content, headingPrefix) {
|
|
412
|
-
const
|
|
413
|
-
const headRe = new RegExp(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (headRe.test(lines[i].trim())) {
|
|
417
|
-
start = i;
|
|
418
|
-
break;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
if (start === -1)
|
|
402
|
+
const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2 || heading.level === 3);
|
|
403
|
+
const headRe = new RegExp(`^${escapeRegExp(headingPrefix)}(?![\\w-])`);
|
|
404
|
+
const index = headings.findIndex((heading) => heading.level === 3 && headRe.test(heading.text));
|
|
405
|
+
if (index === -1)
|
|
422
406
|
return '';
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (/^###?\s+/.test(lines[i])) {
|
|
426
|
-
end = i;
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
|
|
407
|
+
return content.slice(headings[index].start, headings[index + 1]?.start ?? content.length)
|
|
408
|
+
.replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, '');
|
|
431
409
|
}
|
|
432
|
-
export { extractSection, findSectionHeading, extractSubSection, parseTable, upsertSection, mutateTableRow, DocumentMutationError };
|
|
410
|
+
export { findSectionRange, extractSection, findSectionHeading, extractSubSection, parseTable, upsertSection, mutateTableRow, DocumentMutationError };
|
|
433
411
|
//# sourceMappingURL=sections.js.map
|