@fitlab-ai/agent-infra 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -5
- package/README.zh-CN.md +21 -5
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +20 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/platform-release-notes.js +53 -0
- package/dist/lib/internal/release-workflow.js +175 -17
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/github-release-notes.js +96 -0
- package/dist/lib/platform/issues.js +4 -2
- package/dist/lib/platform/merged-pr-equivalence.js +84 -0
- package/dist/lib/platform/pr-checks.js +43 -15
- package/dist/lib/platform/pull-requests.js +113 -7
- package/dist/lib/platform/release-notes.js +143 -0
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +31 -9
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/build-proxy.js +63 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +133 -189
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rebuild.js +24 -7
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/image-build.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/dist/lib/sandbox/shell.js +3 -2
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/commands/issue-body.js +10 -5
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +101 -2
- package/dist/lib/task/issue-form.js +2 -0
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +13 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +168 -23
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +4 -2
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/platform-release-notes.ts +55 -0
- package/lib/internal/release-workflow.ts +172 -18
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/github-release-notes.ts +132 -0
- package/lib/platform/issues.ts +6 -4
- package/lib/platform/merged-pr-equivalence.ts +107 -0
- package/lib/platform/pr-checks.ts +57 -28
- package/lib/platform/pull-requests.ts +140 -25
- package/lib/platform/release-notes.ts +167 -0
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +37 -9
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/build-proxy.ts +94 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +188 -203
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rebuild.ts +29 -7
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/image-build.ts +4 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/lib/sandbox/shell.ts +4 -2
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/commands/issue-body.ts +10 -5
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +111 -3
- package/lib/task/issue-form.ts +4 -0
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +15 -1
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +190 -25
- package/lib/task/verification-review.ts +2 -0
- package/lib/task/verification.ts +6 -3
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.en.md +2 -2
- package/templates/.agents/rules/release-commands.github.en.md +15 -39
- package/templates/.agents/rules/release-commands.github.zh-CN.md +14 -38
- package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
- package/templates/.agents/rules/review-handshake.en.md +4 -3
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -3
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/rules/testing-discipline.en.md +20 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +9 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- 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 +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/create-task/SKILL.en.md +15 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +15 -11
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.agents/templates/task.en.md +18 -0
- package/templates/.agents/templates/task.zh-CN.md +18 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
|
@@ -1,116 +1,248 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
type ReviewFindingCounts = {
|
|
5
|
+
blocker: number;
|
|
6
|
+
major: number;
|
|
7
|
+
minor: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type ReviewVerdict = 'Approved' | 'Changes Requested' | 'Rejected';
|
|
11
|
+
type ReviewSummary = {
|
|
12
|
+
verdict: ReviewVerdict;
|
|
13
|
+
counts: ReviewFindingCounts | null;
|
|
14
|
+
manualValidation: number | null;
|
|
15
|
+
countState: 'placeholders' | 'numeric';
|
|
16
|
+
};
|
|
17
|
+
type ReviewSummaryErrorCode =
|
|
18
|
+
| 'REVIEW_SUMMARY_NOT_FOUND'
|
|
19
|
+
| 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
|
|
20
|
+
| 'REVIEW_SUMMARY_COUNT_MISMATCH';
|
|
21
|
+
type ReviewSummaryParseResult =
|
|
22
|
+
| { ok: true; summary: ReviewSummary; findingsStart: number; findingsEnd: number }
|
|
23
|
+
| { ok: false; code: ReviewSummaryErrorCode; message: string };
|
|
24
|
+
type ReviewSummaryFinalizeResult =
|
|
25
|
+
| { ok: true; changed: boolean; content: string }
|
|
26
|
+
| { ok: false; code: ReviewSummaryErrorCode; message: string };
|
|
27
|
+
|
|
28
|
+
const SUMMARY_HEADINGS = new Set(['审查摘要', 'Review Summary']);
|
|
29
|
+
const PLACEHOLDERS = [
|
|
30
|
+
'{unresolved-blockers}',
|
|
31
|
+
'{unresolved-major}',
|
|
32
|
+
'{unresolved-minor}'
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
function escapeRegExp(value: string): string {
|
|
36
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
10
37
|
}
|
|
11
38
|
|
|
12
|
-
|
|
39
|
+
function maxRound(entries: string[], stem: string): number {
|
|
13
40
|
let max = 0;
|
|
14
41
|
for (const entry of entries) {
|
|
15
42
|
if (entry === `${stem}.md`) {
|
|
16
43
|
max = Math.max(max, 1);
|
|
17
44
|
continue;
|
|
18
45
|
}
|
|
19
|
-
|
|
20
46
|
const match = entry.match(new RegExp(`^${escapeRegExp(stem)}-r(\\d+)\\.md$`));
|
|
21
|
-
if (match)
|
|
22
|
-
max = Math.max(max, Number(match[1]));
|
|
23
|
-
}
|
|
47
|
+
if (match) max = Math.max(max, Number(match[1]));
|
|
24
48
|
}
|
|
25
49
|
return max;
|
|
26
50
|
}
|
|
27
51
|
|
|
28
|
-
|
|
52
|
+
function artifactName(stem: string, round: number): string {
|
|
29
53
|
return round === 1 ? `${stem}.md` : `${stem}-r${round}.md`;
|
|
30
54
|
}
|
|
31
55
|
|
|
32
|
-
|
|
56
|
+
function normalizeVerdict(raw: unknown): ReviewVerdict | '' {
|
|
33
57
|
const value = String(raw).trim().toLowerCase();
|
|
34
|
-
if (value ===
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return "Changes Requested";
|
|
39
|
-
}
|
|
40
|
-
if (value === "拒绝" || value === "rejected") {
|
|
41
|
-
return "Rejected";
|
|
42
|
-
}
|
|
43
|
-
return "";
|
|
58
|
+
if (value === '通过' || value === 'approved') return 'Approved';
|
|
59
|
+
if (value === '需要修改' || value === 'changes requested') return 'Changes Requested';
|
|
60
|
+
if (value === '拒绝' || value === 'rejected') return 'Rejected';
|
|
61
|
+
return '';
|
|
44
62
|
}
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
function extractSection(content: string, names: string[]): string {
|
|
47
65
|
const lines = content.split(/\r?\n/);
|
|
48
66
|
const nameSet = new Set(names);
|
|
49
67
|
const start = lines.findIndex((line) => {
|
|
50
68
|
const match = line.trim().match(/^##\s+(.+?)\s*$/);
|
|
51
69
|
return match ? nameSet.has(match[1]!) : false;
|
|
52
70
|
});
|
|
53
|
-
|
|
54
|
-
if (start === -1) {
|
|
55
|
-
return "";
|
|
56
|
-
}
|
|
57
|
-
|
|
71
|
+
if (start === -1) return '';
|
|
58
72
|
const sectionLines = [];
|
|
59
73
|
for (let index = start + 1; index < lines.length; index += 1) {
|
|
60
|
-
if (/^##\s+/.test(lines[index]!))
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
74
|
+
if (/^##\s+/.test(lines[index]!)) break;
|
|
63
75
|
sectionLines.push(lines[index]);
|
|
64
76
|
}
|
|
65
|
-
return sectionLines.join(
|
|
77
|
+
return sectionLines.join('\n');
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
function summaryBounds(content: string): { start: number; end: number } | null {
|
|
81
|
+
const headings = [...content.matchAll(/^##\s+(.+?)\s*$/gm)]
|
|
82
|
+
.map((match) => ({ index: match.index, end: match.index + match[0].length, name: match[1]! }));
|
|
83
|
+
const summaries = headings.filter((heading) => SUMMARY_HEADINGS.has(heading.name));
|
|
84
|
+
if (summaries.length !== 1) return null;
|
|
85
|
+
const summary = summaries[0]!;
|
|
86
|
+
const next = headings.find((heading) => heading.index > summary.index);
|
|
87
|
+
return { start: summary.end, end: next?.index ?? content.length };
|
|
88
|
+
}
|
|
75
89
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
function uniqueLine(
|
|
91
|
+
content: string,
|
|
92
|
+
bounds: { start: number; end: number },
|
|
93
|
+
pattern: RegExp
|
|
94
|
+
): { value: string; start: number; end: number } | null {
|
|
95
|
+
const section = content.slice(bounds.start, bounds.end);
|
|
96
|
+
const matches = [...section.matchAll(pattern)];
|
|
97
|
+
if (matches.length !== 1) return null;
|
|
98
|
+
const match = matches[0]!;
|
|
99
|
+
return {
|
|
100
|
+
value: match[1]!,
|
|
101
|
+
start: bounds.start + match.index,
|
|
102
|
+
end: bounds.start + match.index + match[0].length
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseCounts(value: string): ReviewFindingCounts | null {
|
|
107
|
+
const zh = /^(\d+)\s*阻塞项,\s*(\d+)\s*主要,\s*(\d+)\s*次要(?:\s*\/.*)?$/.exec(value);
|
|
108
|
+
const en = /^(\d+)\s*blockers?,\s*(\d+)\s*majors?,\s*(\d+)\s*minors?(?:\s*\/.*)?$/i.exec(value);
|
|
109
|
+
const match = zh ?? en;
|
|
110
|
+
if (!match) return null;
|
|
111
|
+
const [blocker, major, minor] = match.slice(1, 4).map(Number);
|
|
112
|
+
if (![blocker, major, minor].every(Number.isSafeInteger)) return null;
|
|
113
|
+
return { blocker: blocker!, major: major!, minor: minor! };
|
|
114
|
+
}
|
|
82
115
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
116
|
+
function parseManualValidation(value: string): number | null {
|
|
117
|
+
const match = /\*\*(?:人工校验|Manual[- ]validation)\*\*[::]\s*(\d+)/i.exec(value);
|
|
118
|
+
if (!match) return null;
|
|
119
|
+
const count = Number(match[1]);
|
|
120
|
+
return Number.isSafeInteger(count) ? count : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hasCanonicalPlaceholders(value: string): boolean {
|
|
124
|
+
const zh = /^\{unresolved-blockers\}\s*阻塞项,\s*\{unresolved-major\}\s*主要,\s*\{unresolved-minor\}\s*次要(?:\s*\/.*)?$/.test(value);
|
|
125
|
+
const en = /^\{unresolved-blockers\}\s*blockers?,\s*\{unresolved-major\}\s*majors?,\s*\{unresolved-minor\}\s*minors?(?:\s*\/.*)?$/i.test(value);
|
|
126
|
+
return zh || en;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseReviewSummary(content: string): ReviewSummaryParseResult {
|
|
130
|
+
const bounds = summaryBounds(content);
|
|
131
|
+
if (!bounds) {
|
|
132
|
+
return { ok: false, code: 'REVIEW_SUMMARY_NOT_FOUND', message: 'review artifact must contain one review summary section' };
|
|
133
|
+
}
|
|
134
|
+
const verdictLine = uniqueLine(
|
|
135
|
+
content,
|
|
136
|
+
bounds,
|
|
137
|
+
/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/gim
|
|
138
|
+
);
|
|
139
|
+
const findingsLine = uniqueLine(
|
|
140
|
+
content,
|
|
141
|
+
bounds,
|
|
142
|
+
/^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/gim
|
|
143
|
+
);
|
|
144
|
+
const verdict = verdictLine ? normalizeVerdict(verdictLine.value) : '';
|
|
145
|
+
if (!verdict || !findingsLine) {
|
|
146
|
+
return { ok: false, code: 'REVIEW_SUMMARY_NOT_FOUND', message: 'review summary verdict or finding count line is missing or repeated' };
|
|
86
147
|
}
|
|
87
148
|
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
149
|
+
const counts = parseCounts(findingsLine.value);
|
|
150
|
+
if (counts) {
|
|
90
151
|
return {
|
|
91
|
-
ok:
|
|
92
|
-
verdict:
|
|
93
|
-
|
|
152
|
+
ok: true,
|
|
153
|
+
summary: { verdict, counts, manualValidation: parseManualValidation(findingsLine.value), countState: 'numeric' },
|
|
154
|
+
findingsStart: findingsLine.start,
|
|
155
|
+
findingsEnd: findingsLine.end
|
|
94
156
|
};
|
|
95
157
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
158
|
+
const placeholderOccurrences = PLACEHOLDERS.map((token) => (
|
|
159
|
+
[...findingsLine.value.matchAll(new RegExp(escapeRegExp(token), 'g'))].length
|
|
160
|
+
));
|
|
161
|
+
if (hasCanonicalPlaceholders(findingsLine.value) && placeholderOccurrences.every((count) => count === 1)) {
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
summary: { verdict, counts: null, manualValidation: parseManualValidation(findingsLine.value), countState: 'placeholders' },
|
|
165
|
+
findingsStart: findingsLine.start,
|
|
166
|
+
findingsEnd: findingsLine.end
|
|
167
|
+
};
|
|
99
168
|
}
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
code: 'REVIEW_SUMMARY_PLACEHOLDER_INVALID',
|
|
172
|
+
message: 'review summary finding counts must be three canonical placeholders or non-negative integers'
|
|
173
|
+
};
|
|
174
|
+
}
|
|
100
175
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
176
|
+
function equalCounts(left: ReviewFindingCounts, right: ReviewFindingCounts): boolean {
|
|
177
|
+
return left.blocker === right.blocker && left.major === right.major && left.minor === right.minor;
|
|
178
|
+
}
|
|
105
179
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
180
|
+
function finalizeReviewSummaryContent(
|
|
181
|
+
content: string,
|
|
182
|
+
counts: ReviewFindingCounts
|
|
183
|
+
): ReviewSummaryFinalizeResult {
|
|
184
|
+
const parsed = parseReviewSummary(content);
|
|
185
|
+
if (!parsed.ok) return parsed;
|
|
186
|
+
if (parsed.summary.countState === 'numeric') {
|
|
187
|
+
if (equalCounts(parsed.summary.counts!, counts)) return { ok: true, changed: false, content };
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
|
|
191
|
+
message: 'review summary finding counts do not match the ledger snapshot'
|
|
192
|
+
};
|
|
109
193
|
}
|
|
194
|
+
let line = content.slice(parsed.findingsStart, parsed.findingsEnd);
|
|
195
|
+
line = line
|
|
196
|
+
.replace(PLACEHOLDERS[0], String(counts.blocker))
|
|
197
|
+
.replace(PLACEHOLDERS[1], String(counts.major))
|
|
198
|
+
.replace(PLACEHOLDERS[2], String(counts.minor));
|
|
199
|
+
return {
|
|
200
|
+
ok: true,
|
|
201
|
+
changed: true,
|
|
202
|
+
content: content.slice(0, parsed.findingsStart) + line + content.slice(parsed.findingsEnd)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
110
205
|
|
|
111
|
-
|
|
206
|
+
function parseVerdict(reviewPath: string):
|
|
207
|
+
| { ok: true; verdict: 'Approved' | 'Approved-with-issues' | 'Changes Requested' | 'Rejected' }
|
|
208
|
+
| { ok: false; verdict: null; message: string } {
|
|
209
|
+
if (!fs.existsSync(reviewPath)) {
|
|
210
|
+
return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
|
|
211
|
+
}
|
|
212
|
+
const parsed = parseReviewSummary(fs.readFileSync(reviewPath, 'utf8'));
|
|
213
|
+
if (!parsed.ok) {
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
verdict: null,
|
|
217
|
+
message: `cannot parse review summary in ${path.basename(reviewPath)}: ${parsed.message}`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const { verdict, counts } = parsed.summary;
|
|
221
|
+
if (verdict !== 'Approved') return { ok: true, verdict };
|
|
112
222
|
return {
|
|
113
223
|
ok: true,
|
|
114
|
-
verdict:
|
|
224
|
+
verdict: counts && equalCounts(counts, { blocker: 0, major: 0, minor: 0 })
|
|
225
|
+
? 'Approved'
|
|
226
|
+
: 'Approved-with-issues'
|
|
115
227
|
};
|
|
116
228
|
}
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
artifactName,
|
|
232
|
+
equalCounts,
|
|
233
|
+
escapeRegExp,
|
|
234
|
+
extractSection,
|
|
235
|
+
finalizeReviewSummaryContent,
|
|
236
|
+
maxRound,
|
|
237
|
+
normalizeVerdict,
|
|
238
|
+
parseReviewSummary,
|
|
239
|
+
parseVerdict
|
|
240
|
+
};
|
|
241
|
+
export type {
|
|
242
|
+
ReviewFindingCounts,
|
|
243
|
+
ReviewSummary,
|
|
244
|
+
ReviewSummaryErrorCode,
|
|
245
|
+
ReviewSummaryFinalizeResult,
|
|
246
|
+
ReviewSummaryParseResult,
|
|
247
|
+
ReviewVerdict
|
|
248
|
+
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
import { locateActivityLog, pairEntries } from './activity-log.ts';
|
|
6
|
+
import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.ts';
|
|
7
|
+
import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
8
|
+
import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
9
|
+
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
10
|
+
import { resolveTaskRef } from './resolve-ref.ts';
|
|
11
|
+
import { validateOrchestrationStage } from './orchestration.ts';
|
|
12
|
+
import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
|
|
13
|
+
|
|
14
|
+
type ReviewFinalizationErrorCode =
|
|
15
|
+
| ResolveTaskRefErrorCode
|
|
16
|
+
| 'TASK_STATE_MISMATCH'
|
|
17
|
+
| 'REVIEW_PAYLOAD_INVALID'
|
|
18
|
+
| 'REVIEW_STAGE_INVALID'
|
|
19
|
+
| 'REVIEW_ARTIFACT_IDENTITY_INVALID'
|
|
20
|
+
| 'REVIEW_ARTIFACT_NOT_REGULAR'
|
|
21
|
+
| 'REVIEW_LEDGER_INVALID'
|
|
22
|
+
| 'REVIEW_SUMMARY_NOT_FOUND'
|
|
23
|
+
| 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
|
|
24
|
+
| 'REVIEW_SUMMARY_COUNT_MISMATCH'
|
|
25
|
+
| 'REVIEW_ARTIFACT_CONFLICT'
|
|
26
|
+
| 'REVIEW_PROVENANCE_INVALID'
|
|
27
|
+
| 'REVIEW_TEMP_WRITE_FAILED'
|
|
28
|
+
| 'REVIEW_RENAME_FAILED';
|
|
29
|
+
type ReviewFinalizationError = { code: ReviewFinalizationErrorCode; message: string };
|
|
30
|
+
type ReviewFinalizationRequest = {
|
|
31
|
+
taskRef: string;
|
|
32
|
+
stage: string;
|
|
33
|
+
artifact: string;
|
|
34
|
+
dryRun?: boolean;
|
|
35
|
+
};
|
|
36
|
+
type ReviewFinalizationResult = {
|
|
37
|
+
status: 'planned' | 'applied' | 'no-op' | 'failed';
|
|
38
|
+
changed: boolean;
|
|
39
|
+
intent: 'finalize-summary';
|
|
40
|
+
requestRef: string;
|
|
41
|
+
taskId: string | null;
|
|
42
|
+
stage: string;
|
|
43
|
+
artifact: string;
|
|
44
|
+
stageStatus: LedgerStageStatus | null;
|
|
45
|
+
operations: readonly {
|
|
46
|
+
kind: 'artifact';
|
|
47
|
+
artifact: string;
|
|
48
|
+
operation: 'update';
|
|
49
|
+
}[];
|
|
50
|
+
error: ReviewFinalizationError | null;
|
|
51
|
+
};
|
|
52
|
+
type ReviewFileSystem = {
|
|
53
|
+
readFileSync: (file: string) => string;
|
|
54
|
+
statModeSync: (file: string) => number;
|
|
55
|
+
writeFileSync: (file: string, content: string, mode: number) => void;
|
|
56
|
+
renameSync: (from: string, to: string) => void;
|
|
57
|
+
unlinkSync: (file: string) => void;
|
|
58
|
+
};
|
|
59
|
+
type ReviewFinalizationOptions = {
|
|
60
|
+
repoRoot?: string;
|
|
61
|
+
randomSuffix?: () => string;
|
|
62
|
+
fileSystem?: Partial<ReviewFileSystem>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code'; action: string }> = {
|
|
66
|
+
analysis: { family: 'review-analysis', action: 'Review Analysis' },
|
|
67
|
+
plan: { family: 'review-plan', action: 'Review Plan' },
|
|
68
|
+
code: { family: 'review-code', action: 'Review Code' }
|
|
69
|
+
};
|
|
70
|
+
const DEFAULT_FILE_SYSTEM: ReviewFileSystem = {
|
|
71
|
+
readFileSync: (file) => fs.readFileSync(file, 'utf8'),
|
|
72
|
+
statModeSync: (file) => fs.statSync(file).mode,
|
|
73
|
+
writeFileSync: (file, content, mode) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: 'wx', mode }),
|
|
74
|
+
renameSync: (from, to) => fs.renameSync(from, to),
|
|
75
|
+
unlinkSync: (file) => fs.unlinkSync(file)
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function failed(
|
|
79
|
+
request: ReviewFinalizationRequest,
|
|
80
|
+
code: ReviewFinalizationErrorCode,
|
|
81
|
+
message: string,
|
|
82
|
+
taskId: string | null = null,
|
|
83
|
+
stageStatus: LedgerStageStatus | null = null
|
|
84
|
+
): ReviewFinalizationResult {
|
|
85
|
+
return {
|
|
86
|
+
status: 'failed',
|
|
87
|
+
changed: false,
|
|
88
|
+
intent: 'finalize-summary',
|
|
89
|
+
requestRef: request.taskRef,
|
|
90
|
+
taskId,
|
|
91
|
+
stage: request.stage,
|
|
92
|
+
artifact: request.artifact,
|
|
93
|
+
stageStatus,
|
|
94
|
+
operations: [],
|
|
95
|
+
error: { code, message }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function openReviewRound(content: string, action: string, round: number): boolean {
|
|
100
|
+
const section = locateActivityLog(content);
|
|
101
|
+
if (!section) return false;
|
|
102
|
+
const expected = `${action} (Round ${round})`;
|
|
103
|
+
return pairEntries(section.entries).filter((row) => (
|
|
104
|
+
row.step === expected && Boolean(row.started) && !row.done
|
|
105
|
+
)).length === 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
|
|
109
|
+
try {
|
|
110
|
+
fileSystem.unlinkSync(tempPath);
|
|
111
|
+
} catch {
|
|
112
|
+
// Best-effort cleanup preserves the primary failure.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function finalizeReviewSummary(
|
|
117
|
+
request: ReviewFinalizationRequest,
|
|
118
|
+
options: ReviewFinalizationOptions = {}
|
|
119
|
+
): ReviewFinalizationResult {
|
|
120
|
+
const stage = request.stage as ReviewStage;
|
|
121
|
+
const spec = STAGES[stage];
|
|
122
|
+
if (!spec) return failed(request, 'REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
|
|
123
|
+
if (!request.taskRef || !request.artifact) {
|
|
124
|
+
return failed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
|
|
125
|
+
}
|
|
126
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
127
|
+
if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
|
|
128
|
+
if (resolved.state !== 'active') {
|
|
129
|
+
return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
130
|
+
}
|
|
131
|
+
const parsedArtifact = parseArtifactName(request.artifact);
|
|
132
|
+
if (!parsedArtifact || parsedArtifact.family !== spec.family) {
|
|
133
|
+
return failed(
|
|
134
|
+
request,
|
|
135
|
+
'REVIEW_ARTIFACT_IDENTITY_INVALID',
|
|
136
|
+
`artifact '${request.artifact}' does not match ${spec.family}`,
|
|
137
|
+
resolved.taskId
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const validated = validateCompletedArtifact(
|
|
141
|
+
resolved.taskDir,
|
|
142
|
+
spec.family,
|
|
143
|
+
request.artifact,
|
|
144
|
+
parsedArtifact.round
|
|
145
|
+
);
|
|
146
|
+
if (!validated.ok) {
|
|
147
|
+
const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
|
|
148
|
+
? 'REVIEW_ARTIFACT_NOT_REGULAR'
|
|
149
|
+
: 'REVIEW_ARTIFACT_IDENTITY_INVALID';
|
|
150
|
+
return failed(request, code, validated.error.message, resolved.taskId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
154
|
+
let taskContent: string;
|
|
155
|
+
let artifactContent: string;
|
|
156
|
+
try {
|
|
157
|
+
taskContent = fileSystem.readFileSync(resolved.taskMdPath);
|
|
158
|
+
artifactContent = fileSystem.readFileSync(validated.artifact.path);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
161
|
+
}
|
|
162
|
+
if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
|
|
163
|
+
return failed(
|
|
164
|
+
request,
|
|
165
|
+
'REVIEW_ARTIFACT_IDENTITY_INVALID',
|
|
166
|
+
`${request.artifact} does not have one matching open started review event`,
|
|
167
|
+
resolved.taskId
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const provenance = validateOrchestrationStage(request.taskRef, {
|
|
171
|
+
stage: spec.family,
|
|
172
|
+
round: parsedArtifact.round,
|
|
173
|
+
artifact: request.artifact,
|
|
174
|
+
role: 'reviewer'
|
|
175
|
+
}, { repoRoot: options.repoRoot });
|
|
176
|
+
if (provenance.status === 'failed' || provenance.status === 'paused') {
|
|
177
|
+
return failed(
|
|
178
|
+
request,
|
|
179
|
+
'REVIEW_PROVENANCE_INVALID',
|
|
180
|
+
provenance.error?.message ?? provenance.run?.pause?.message ?? 'orchestration provenance validation failed',
|
|
181
|
+
resolved.taskId
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let rows;
|
|
186
|
+
try {
|
|
187
|
+
rows = parseLedger(taskContent);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return failed(request, 'REVIEW_LEDGER_INVALID', String(error), resolved.taskId);
|
|
190
|
+
}
|
|
191
|
+
const ledgerError = validateLedgerRows(rows);
|
|
192
|
+
if (ledgerError) {
|
|
193
|
+
return failed(
|
|
194
|
+
request,
|
|
195
|
+
'REVIEW_LEDGER_INVALID',
|
|
196
|
+
`${ledgerError.code}: ${ledgerError.message}`,
|
|
197
|
+
resolved.taskId
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const stageStatus = summarizeLedgerStage(rows, stage);
|
|
201
|
+
const transformed = finalizeReviewSummaryContent(
|
|
202
|
+
artifactContent,
|
|
203
|
+
stageStatus.unresolvedFindingCounts
|
|
204
|
+
);
|
|
205
|
+
if (!transformed.ok) {
|
|
206
|
+
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
207
|
+
}
|
|
208
|
+
if (!transformed.changed) {
|
|
209
|
+
return {
|
|
210
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
|
|
211
|
+
status: 'no-op',
|
|
212
|
+
error: null
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const operations = [{ kind: 'artifact' as const, artifact: request.artifact, operation: 'update' as const }];
|
|
216
|
+
if (request.dryRun) {
|
|
217
|
+
return {
|
|
218
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
|
|
219
|
+
status: 'planned',
|
|
220
|
+
changed: true,
|
|
221
|
+
operations,
|
|
222
|
+
error: null
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const tempPath = path.join(
|
|
227
|
+
resolved.taskDir,
|
|
228
|
+
`.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`
|
|
229
|
+
);
|
|
230
|
+
let mode: number;
|
|
231
|
+
try {
|
|
232
|
+
mode = fileSystem.statModeSync(validated.artifact.path);
|
|
233
|
+
fileSystem.writeFileSync(tempPath, transformed.content, mode);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
cleanupTemp(fileSystem, tempPath);
|
|
236
|
+
return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, stageStatus);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
if (fileSystem.readFileSync(validated.artifact.path) !== artifactContent) {
|
|
240
|
+
cleanupTemp(fileSystem, tempPath);
|
|
241
|
+
return failed(
|
|
242
|
+
request,
|
|
243
|
+
'REVIEW_ARTIFACT_CONFLICT',
|
|
244
|
+
`${request.artifact} changed during finalization`,
|
|
245
|
+
resolved.taskId,
|
|
246
|
+
stageStatus
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
fileSystem.renameSync(tempPath, validated.artifact.path);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
cleanupTemp(fileSystem, tempPath);
|
|
252
|
+
return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
status: 'applied',
|
|
256
|
+
changed: true,
|
|
257
|
+
intent: 'finalize-summary',
|
|
258
|
+
requestRef: request.taskRef,
|
|
259
|
+
taskId: resolved.taskId,
|
|
260
|
+
stage,
|
|
261
|
+
artifact: request.artifact,
|
|
262
|
+
stageStatus,
|
|
263
|
+
operations,
|
|
264
|
+
error: null
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export { finalizeReviewSummary };
|
|
269
|
+
export type {
|
|
270
|
+
ReviewFinalizationError,
|
|
271
|
+
ReviewFinalizationErrorCode,
|
|
272
|
+
ReviewFinalizationOptions,
|
|
273
|
+
ReviewFinalizationRequest,
|
|
274
|
+
ReviewFinalizationResult
|
|
275
|
+
};
|
|
@@ -12,7 +12,16 @@ export const DEFAULT_POST_REVIEW_EXCLUDES: string[] = [];
|
|
|
12
12
|
// The leading ":/" makes coverage fail-closed (everything from the repo root);
|
|
13
13
|
// each exclude becomes a top-level negative pathspec. Projects extend the
|
|
14
14
|
// denylist via review.post_review_exclude_globs (union, deduped).
|
|
15
|
-
type ReviewGlobConfig = { post_review_exclude_globs?: unknown };
|
|
15
|
+
type ReviewGlobConfig = Record<string, unknown> & { post_review_exclude_globs?: unknown };
|
|
16
|
+
|
|
17
|
+
export function loadPostReviewConfig(repoRoot: string): ReviewGlobConfig {
|
|
18
|
+
try {
|
|
19
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, ".agents", ".airc.json"), "utf8"));
|
|
20
|
+
return config.review && typeof config.review === "object" ? config.review : {};
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
|
|
17
26
|
export function resolvePostReviewGlobs(
|
|
18
27
|
config: ReviewGlobConfig = {},
|
|
@@ -47,6 +56,11 @@ export function extractReviewBaseline(content: string): string {
|
|
|
47
56
|
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
export function extractReviewDiffBase(content: string): string {
|
|
60
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查差异基线|Reviewed Diff Base)\*\*[::]\s*(.*?)\s*$/m);
|
|
61
|
+
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
export function extractReviewDiffFingerprint(content: string): string {
|
|
51
65
|
const match = String(content).match(/^[-*]?\s*\*\*(?:审查差异指纹|Reviewed Diff Fingerprint)\*\*[::]\s*(.*?)\s*$/m);
|
|
52
66
|
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const LOCAL_VERIFICATION_CHECKS = [
|
|
2
|
-
'task-meta', 'artifact', 'implementation-input', 'activity-log', 'completion-checklist'
|
|
2
|
+
'task-meta', 'artifact', 'implementation-input', 'activity-log', 'completion-checklist',
|
|
3
|
+
'orchestration-state'
|
|
3
4
|
] as const;
|
|
4
5
|
|
|
5
6
|
function isLocalVerificationCheck(value: string): value is typeof LOCAL_VERIFICATION_CHECKS[number] {
|