@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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 +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -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 +3 -1
- 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 +322 -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 +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- 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 +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- 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 +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- 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 +101 -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 +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- 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 +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -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.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- 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 +101 -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 +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -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/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 +3 -2
- package/lib/fs/durable-write.ts +29 -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 +3 -1
- 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 +306 -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 +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- 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 +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- 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 +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- 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 +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- 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 +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -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.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- 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 +152 -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 +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -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/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- 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/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- 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 +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +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/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/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/.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
|
@@ -24,20 +24,30 @@ import { resolveMaterializedReviewedHeadRelation } from "../platform/change-requ
|
|
|
24
24
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.ts";
|
|
25
25
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.ts";
|
|
26
26
|
import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
|
|
27
|
-
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
|
|
27
|
+
import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
|
|
28
28
|
import type { LedgerRow } from "./ledger.ts";
|
|
29
29
|
import { isValidAgentInfraVersion } from "../version.ts";
|
|
30
30
|
import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
|
|
31
31
|
import { inspectDecisionDetailDuplicates } from "./decision-details.ts";
|
|
32
|
+
import { parseImplementationInputs, type ImplementationInput } from "./implementation-inputs.ts";
|
|
33
|
+
import { scanVisibleMarkdown } from "./markdown.ts";
|
|
32
34
|
import { loadVerificationConfig } from "./verification-config.ts";
|
|
33
35
|
import { snapshotReview } from "../git/review-snapshot.ts";
|
|
36
|
+
import { inspectActivityLog } from "./activity-log.ts";
|
|
37
|
+
import { parseWorkflowWarnings } from "./workflow-warnings.ts";
|
|
34
38
|
import { OrchestrationStateError, readRun } from "./orchestration.ts";
|
|
39
|
+
import type { OrchestrationRun } from "./orchestration.ts";
|
|
35
40
|
import { resolveDeliveryTarget } from "./delivery-target.ts";
|
|
36
41
|
import { readPrDeliveryFact } from "./pr-delivery-fact.ts";
|
|
37
42
|
import { validateLocalArtifact } from "./local-artifact-finalization.ts";
|
|
38
43
|
import { validateQualificationAudit } from "./qualification-audit.ts";
|
|
39
44
|
import { getArtifactSchema } from "./artifact-schema.ts";
|
|
40
45
|
import { inspectArtifactContract } from "./artifact-operations.ts";
|
|
46
|
+
import type { VerificationShared } from "./verification-types.ts";
|
|
47
|
+
import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.ts";
|
|
48
|
+
import { readManualValidationCompletion } from "./manual-validation-completion.ts";
|
|
49
|
+
import { sha256File } from "./artifact-receipts.ts";
|
|
50
|
+
import { summaryCommentState } from "../platform/pr-summary.ts";
|
|
41
51
|
|
|
42
52
|
const TASK_ENUMS = {
|
|
43
53
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
@@ -58,7 +68,6 @@ const DEFAULT_REQUIRED_FIELDS = [
|
|
|
58
68
|
];
|
|
59
69
|
|
|
60
70
|
const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
|
|
61
|
-
const ACTIVITY_LOG_PATTERN = /^- (\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?) — \*\*(.+?)\*\* by (.+?) — (.+)$/;
|
|
62
71
|
// Start markers (action suffixed with ` [started]`) are excluded from the
|
|
63
72
|
// "latest action" / freshness computation so a step's in-flight marker never
|
|
64
73
|
// satisfies a skill's expected_action_pattern; the matching done entry does.
|
|
@@ -67,28 +76,12 @@ const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
67
76
|
|
|
68
77
|
// Review disagreement ledger (see .agents/rules/review-handshake.md).
|
|
69
78
|
const LEDGER_SECTION_NAMES = ["审查分歧账本", "Review Disagreement Ledger"];
|
|
70
|
-
const LEDGER_STATUSES = new Set([
|
|
71
|
-
"open",
|
|
72
|
-
"accepted",
|
|
73
|
-
"adjusted",
|
|
74
|
-
"refuted",
|
|
75
|
-
"cannot-judge",
|
|
76
|
-
"confirmed",
|
|
77
|
-
"needs-human-decision",
|
|
78
|
-
"closed",
|
|
79
|
-
"human-decided"
|
|
80
|
-
]);
|
|
81
|
-
const LEDGER_TERMINAL_OK = new Set(["confirmed", "closed", "human-decided"]);
|
|
82
79
|
const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
|
|
83
80
|
const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
|
|
84
81
|
const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
85
|
-
const WORKFLOW_WARNING_SECTION_NAMES = ["工作流告警", "Workflow Warnings"];
|
|
86
|
-
const WORKFLOW_WARNING_STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
87
|
-
const WORKFLOW_WARNING_SEVERITIES = new Set(["IMPORTANT", "ACTION_REQUIRED"]);
|
|
88
|
-
const WORKFLOW_WARNING_ID_PATTERN = /^WW-\d+$/;
|
|
89
82
|
|
|
90
83
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
91
|
-
|
|
84
|
+
const defaultRepoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
|
|
92
85
|
|
|
93
86
|
const PLATFORM_ADAPTERS: Record<string, (context: any, shared: any) => any> = {
|
|
94
87
|
"platform-sync": checkPlatformSync,
|
|
@@ -160,7 +153,7 @@ function checkOrchestrationState({ taskDir }: any): any {
|
|
|
160
153
|
const file = path.join(taskDir, 'orchestration.json');
|
|
161
154
|
const stat = safeStat(file);
|
|
162
155
|
if (!stat?.isFile()) return failResult('orchestration-state', 'orchestration.json is missing');
|
|
163
|
-
let run:
|
|
156
|
+
let run: OrchestrationRun | null;
|
|
164
157
|
try {
|
|
165
158
|
run = readRun(taskDir);
|
|
166
159
|
} catch (error) {
|
|
@@ -192,18 +185,16 @@ function exactText(value: unknown): value is string {
|
|
|
192
185
|
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
193
186
|
}
|
|
194
187
|
|
|
195
|
-
function validateCleanCompletionEvidence(run:
|
|
188
|
+
function validateCleanCompletionEvidence(run: OrchestrationRun): string | null {
|
|
196
189
|
const evidence = run.completionEvidence;
|
|
197
190
|
const shaPattern = /^[0-9a-f]{40}$/i;
|
|
198
191
|
if (
|
|
199
|
-
evidence
|
|
200
|
-
|| !exactText(evidence.observedAt)
|
|
192
|
+
!evidence
|
|
201
193
|
|| Number.isNaN(Date.parse(evidence.observedAt))
|
|
202
194
|
|| !shaPattern.test(evidence.head)
|
|
203
195
|
|| !shaPattern.test(evidence.headTree)
|
|
204
196
|
|| !shaPattern.test(evidence.worktreeTree)
|
|
205
197
|
|| !shaPattern.test(evidence.lastReviewedCommit)
|
|
206
|
-
|| !(evidence.prNumber === null || (Number.isInteger(evidence.prNumber) && evidence.prNumber > 0))
|
|
207
198
|
|| !(evidence.prHead === null || shaPattern.test(evidence.prHead))
|
|
208
199
|
) {
|
|
209
200
|
return 'Clean completion evidence has an invalid structure';
|
|
@@ -220,8 +211,7 @@ function validateCleanCompletionEvidence(run: any): string | null {
|
|
|
220
211
|
|| run.pendingDelegation !== null
|
|
221
212
|
|| run.commitAuthorization?.issuedAt !== null
|
|
222
213
|
|| run.commitAuthorization?.consumedAt !== null
|
|
223
|
-
||
|
|
224
|
-
|| run.receipts.some((receipt: any) => receipt.stage === 'commit')
|
|
214
|
+
|| run.receipts.some((receipt) => receipt.stage === 'commit')
|
|
225
215
|
) {
|
|
226
216
|
return 'Clean completion evidence conflicts with commit delegation state';
|
|
227
217
|
}
|
|
@@ -232,54 +222,16 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
232
222
|
const file = path.join(taskDir, 'orchestration.json');
|
|
233
223
|
const stat = safeStat(file);
|
|
234
224
|
if (!stat?.isFile()) return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
235
|
-
let run:
|
|
225
|
+
let run: OrchestrationRun | null;
|
|
236
226
|
try {
|
|
237
227
|
run = readRun(taskDir);
|
|
238
228
|
} catch (error) {
|
|
239
229
|
const message = error instanceof OrchestrationStateError ? error.message : String(error);
|
|
240
230
|
return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
|
|
241
231
|
}
|
|
232
|
+
if (!run) return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
233
|
+
// readRun validates the persisted schema, receipt identities and client provenance.
|
|
242
234
|
const policy = run.modelPolicy;
|
|
243
|
-
if (
|
|
244
|
-
!policy
|
|
245
|
-
|| !exactText(policy.executor?.model)
|
|
246
|
-
|| !exactText(policy.executor?.reasoningEffort)
|
|
247
|
-
|| !exactText(policy.reviewer?.model)
|
|
248
|
-
|| !exactText(policy.reviewer?.reasoningEffort)
|
|
249
|
-
) {
|
|
250
|
-
return failResult('orchestration-evidence', 'Run model policy requires exact executor and reviewer model and effort');
|
|
251
|
-
}
|
|
252
|
-
if (
|
|
253
|
-
!run.modelPolicySource
|
|
254
|
-
|| !['explicit', 'project-config'].includes(run.modelPolicySource.kind)
|
|
255
|
-
|| !exactText(run.modelPolicySource.client)
|
|
256
|
-
|| !exactText(run.modelPolicySource.resolvedAt)
|
|
257
|
-
|| !Array.isArray(run.recoveryHistory)
|
|
258
|
-
) {
|
|
259
|
-
return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
|
|
260
|
-
}
|
|
261
|
-
for (const recovery of run.recoveryHistory) {
|
|
262
|
-
const guards = recovery.guards;
|
|
263
|
-
const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
|
|
264
|
-
&& recovery.previousStatus === 'paused'
|
|
265
|
-
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
266
|
-
&& recovery.client === 'claude-code'
|
|
267
|
-
&& recovery.resultingStatus === 'running'
|
|
268
|
-
&& exactText(recovery.recoveredAt)
|
|
269
|
-
&& guards?.stepCount === 0
|
|
270
|
-
&& guards?.nextStage === null
|
|
271
|
-
&& guards?.baselineEmpty === true
|
|
272
|
-
&& guards?.receiptCount === 0
|
|
273
|
-
&& guards?.pendingDelegation === false
|
|
274
|
-
&& guards?.commitAuthorizationUnused === true
|
|
275
|
-
&& guards?.completionEvidenceAbsent === true;
|
|
276
|
-
if (!validClaudeCodeRecovery) {
|
|
277
|
-
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (!Array.isArray(run.receipts)) {
|
|
281
|
-
return failResult('orchestration-evidence', 'Run receipts must be an array');
|
|
282
|
-
}
|
|
283
235
|
if (run.completionEvidence != null) {
|
|
284
236
|
const evidenceError = validateCleanCompletionEvidence(run);
|
|
285
237
|
if (evidenceError) return failResult('orchestration-evidence', evidenceError);
|
|
@@ -309,10 +261,6 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
309
261
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
|
|
310
262
|
}
|
|
311
263
|
}
|
|
312
|
-
// parentId/childId 校验对所有 client 保持统一(不新增分支);spawnMode 检查按 client 判断
|
|
313
|
-
if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId) {
|
|
314
|
-
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid delegation identity`);
|
|
315
|
-
}
|
|
316
264
|
if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
|
|
317
265
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
|
|
318
266
|
}
|
|
@@ -338,50 +286,9 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
338
286
|
) {
|
|
339
287
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
340
288
|
}
|
|
341
|
-
if (receipt.client === 'codex') {
|
|
342
|
-
const activatedCodex = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
343
|
-
const host = receipt.hostEvidence;
|
|
344
|
-
const provenance = receipt.lifecycleProvenance;
|
|
345
|
-
if (
|
|
346
|
-
!provenance
|
|
347
|
-
|| (activatedCodex && (
|
|
348
|
-
host?.kind !== 'codex-lifecycle-v2'
|
|
349
|
-
|| host.protocolVersion !== provenance.protocolVersion
|
|
350
|
-
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
351
|
-
|| host.hookSource !== provenance.hookSource
|
|
352
|
-
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
353
|
-
|| host.hookSourceHash !== provenance.hookSourceHash
|
|
354
|
-
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
355
|
-
|| receipt.parentId !== provenance.capabilitySessionId
|
|
356
|
-
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
357
|
-
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
358
|
-
|| host.controlGeneration !== provenance.controlGeneration
|
|
359
|
-
|| host.spawnToolUseId === provenance.capabilityToolUseId
|
|
360
|
-
|| !exactText(host.spawnToolUseId)
|
|
361
|
-
|| !Number.isFinite(Date.parse(host.spawnObservedAt ?? ''))
|
|
362
|
-
|| !Number.isFinite(Date.parse(receipt.spawnDispatchedAt ?? ''))
|
|
363
|
-
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt ?? ''))
|
|
364
|
-
|| Date.parse(host.spawnObservedAt ?? '') < Date.parse(receipt.spawnDispatchedAt ?? '')
|
|
365
|
-
|| Date.parse(host.spawnObservedAt ?? '') > Date.parse(receipt.activationDeadlineAt ?? '')
|
|
366
|
-
|| !exactText(host.hookDefinitionHash)
|
|
367
|
-
|| !Number.isSafeInteger(host.startRevision)
|
|
368
|
-
|| host.startRevision < 1
|
|
369
|
-
))
|
|
370
|
-
) {
|
|
371
|
-
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
|
|
372
|
-
}
|
|
373
|
-
if (['sealed', 'consumed'].includes(receipt.status) && (
|
|
374
|
-
!Number.isSafeInteger(host.stopRevision)
|
|
375
|
-
|| host.stopRevision <= host.startRevision
|
|
376
|
-
|| host.consumer !== receipt.id
|
|
377
|
-
|| !exactText(host.consumedAt)
|
|
378
|
-
)) {
|
|
379
|
-
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex consumed stop evidence`);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
289
|
}
|
|
383
290
|
}
|
|
384
|
-
if (run.receipts.some((receipt
|
|
291
|
+
if (run.receipts.some((receipt) => receipt.status !== 'consumed')) {
|
|
385
292
|
return failResult('orchestration-evidence', 'Historical receipts must be consumed');
|
|
386
293
|
}
|
|
387
294
|
return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
|
|
@@ -389,7 +296,7 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
389
296
|
|
|
390
297
|
// === Check Functions ===
|
|
391
298
|
|
|
392
|
-
function checkTaskMeta({ taskDir, config }: any): any {
|
|
299
|
+
function checkTaskMeta({ taskDir, config, repositoryRoot }: any): any {
|
|
393
300
|
const task = loadTask(taskDir);
|
|
394
301
|
if (!task.ok) {
|
|
395
302
|
return failResult("task-meta", task.message);
|
|
@@ -427,14 +334,15 @@ function checkTaskMeta({ taskDir, config }: any): any {
|
|
|
427
334
|
}
|
|
428
335
|
}
|
|
429
336
|
|
|
430
|
-
const branchValidationError = validateTaskBranch(metadata);
|
|
337
|
+
const branchValidationError = validateTaskBranch(metadata, repositoryRoot);
|
|
431
338
|
if (branchValidationError) {
|
|
432
339
|
return failResult("task-meta", branchValidationError);
|
|
433
340
|
}
|
|
434
341
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
342
|
+
try {
|
|
343
|
+
parseWorkflowWarnings(task.content);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
return failResult("task-meta", `Invalid Workflow Warnings: ${error instanceof Error ? error.message : String(error)}`);
|
|
438
346
|
}
|
|
439
347
|
|
|
440
348
|
const expectedStep = config.expected_step;
|
|
@@ -552,12 +460,12 @@ async function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any):
|
|
|
552
460
|
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
|
|
553
461
|
}
|
|
554
462
|
|
|
555
|
-
function validateTaskBranch(metadata: any): any {
|
|
463
|
+
function validateTaskBranch(metadata: any, repoRoot: string): any {
|
|
556
464
|
if (isBlank(metadata.branch)) {
|
|
557
465
|
return null;
|
|
558
466
|
}
|
|
559
467
|
|
|
560
|
-
const projectName = loadProjectName();
|
|
468
|
+
const projectName = loadProjectName(repoRoot);
|
|
561
469
|
const expectedPrefix = projectName ? `${projectName}-${metadata.type}-` : "";
|
|
562
470
|
|
|
563
471
|
if (expectedPrefix && !String(metadata.branch).startsWith(expectedPrefix)) {
|
|
@@ -572,7 +480,7 @@ function validateTaskBranch(metadata: any): any {
|
|
|
572
480
|
return null;
|
|
573
481
|
}
|
|
574
482
|
|
|
575
|
-
function loadProjectName(): any {
|
|
483
|
+
function loadProjectName(repoRoot: string): any {
|
|
576
484
|
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
577
485
|
if (!fs.existsSync(configPath)) {
|
|
578
486
|
return "";
|
|
@@ -743,31 +651,17 @@ function checkImplementationInput({ taskDir, artifactFile }: any): any {
|
|
|
743
651
|
const artifactPath = path.join(taskDir, artifactFile);
|
|
744
652
|
if (!safeStat(artifactPath)?.isFile()) return failResult("implementation-input", `Artifact not found: ${artifactFile}`);
|
|
745
653
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const expected = ["id", "ledger_id", "decision_evidence", "stage", "needs_implementation", "decided_at", "status", "consumed_by"];
|
|
752
|
-
if (table.length < 2 || JSON.stringify(cells(table[0])) !== JSON.stringify(expected)) {
|
|
753
|
-
return failResult("implementation-input", "Implementation Inputs table schema is invalid");
|
|
754
|
-
}
|
|
755
|
-
const seen = new Set();
|
|
756
|
-
for (const line of table.slice(2)) {
|
|
757
|
-
const row = cells(line);
|
|
758
|
-
if (row.length !== 8 || !/^II-[1-9]\d*$/.test(row[0]) || seen.has(row[0])) {
|
|
759
|
-
return failResult("implementation-input", "Implementation Inputs table contains an invalid or duplicate id");
|
|
760
|
-
}
|
|
761
|
-
seen.add(row[0]);
|
|
762
|
-
rows.push({ id: row[0], ledgerId: row[1], evidence: row[2], stage: row[3], needs: row[4], status: row[6], consumedBy: row[7] });
|
|
763
|
-
}
|
|
654
|
+
let rows: ImplementationInput[];
|
|
655
|
+
try {
|
|
656
|
+
rows = parseImplementationInputs(task.content).rows;
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return failResult("implementation-input", `Invalid Implementation Inputs: ${error instanceof Error ? error.message : String(error)}`);
|
|
764
659
|
}
|
|
765
660
|
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
});
|
|
661
|
+
const doneActions = (inspectActivityLog(task.content).section?.entries ?? [])
|
|
662
|
+
.filter((entry) => !ACTIVITY_LOG_STARTED_RE.test(entry.step))
|
|
663
|
+
.map((entry) => entry.step)
|
|
664
|
+
.filter((step) => /^(?:Code Task|Code) \(Round \d+/.test(step));
|
|
771
665
|
const latestAction = doneActions.at(-1) || "";
|
|
772
666
|
const actionDecision = /(?:Code Task|Code) \(Round \d+, decision (II-[1-9]\d*)\)/.exec(latestAction)?.[1] || null;
|
|
773
667
|
const report = fs.readFileSync(artifactPath, "utf8");
|
|
@@ -794,10 +688,10 @@ function checkImplementationInput({ taskDir, artifactFile }: any): any {
|
|
|
794
688
|
const matches = rows.filter((row) => row.id === actionDecision);
|
|
795
689
|
if (matches.length !== 1) return failResult("implementation-input", `${actionDecision} is missing or duplicated in task table`);
|
|
796
690
|
const row = matches[0]!;
|
|
797
|
-
if (row.stage !== "code" || row.
|
|
691
|
+
if (row.stage !== "code" || !row.needsImplementation || row.status !== "consumed" || row.consumedBy !== artifactFile || !row.decisionEvidence) {
|
|
798
692
|
return failResult("implementation-input", `${actionDecision} is not a consumed input for ${artifactFile}`);
|
|
799
693
|
}
|
|
800
|
-
if (reportLedger !== row.ledgerId || reportEvidence !== row.
|
|
694
|
+
if (reportLedger !== row.ledgerId || reportEvidence !== row.decisionEvidence) {
|
|
801
695
|
return failResult("implementation-input", `${actionDecision} report identity does not match task table evidence`);
|
|
802
696
|
}
|
|
803
697
|
return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
|
|
@@ -809,19 +703,11 @@ function checkActivityLog({ taskDir, config }: any): any {
|
|
|
809
703
|
return failResult("activity-log", task.message);
|
|
810
704
|
}
|
|
811
705
|
|
|
812
|
-
const
|
|
813
|
-
if (!
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const entries = logSection
|
|
818
|
-
.split(/\r?\n/)
|
|
819
|
-
.map((line: any) => line.trim())
|
|
820
|
-
.filter((line: any) => line.startsWith("- "));
|
|
821
|
-
|
|
822
|
-
if (entries.length === 0) {
|
|
823
|
-
return failResult("activity-log", "Activity Log has no entries");
|
|
824
|
-
}
|
|
706
|
+
const { section, invalidEntries } = inspectActivityLog(task.content);
|
|
707
|
+
if (!section) return failResult("activity-log", "Activity Log section not found or ambiguous");
|
|
708
|
+
if (invalidEntries.length) return failResult("activity-log", `Invalid Activity Log entry format: ${invalidEntries[0]}`);
|
|
709
|
+
const entries = section.entries;
|
|
710
|
+
if (entries.length === 0) return failResult("activity-log", "Activity Log has no entries");
|
|
825
711
|
|
|
826
712
|
let previousTimestamp = "";
|
|
827
713
|
let latestAction = "";
|
|
@@ -829,12 +715,8 @@ function checkActivityLog({ taskDir, config }: any): any {
|
|
|
829
715
|
const doneActions: string[] = [];
|
|
830
716
|
|
|
831
717
|
for (const entry of entries) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
return failResult("activity-log", `Invalid Activity Log entry format: ${entry}`);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
const [, timestamp, action] = match;
|
|
718
|
+
if (!entry.note.trim()) return failResult("activity-log", "Activity Log entry note is required");
|
|
719
|
+
const { time: timestamp, step: action } = entry;
|
|
838
720
|
if (previousTimestamp && timestamp < previousTimestamp) {
|
|
839
721
|
return failResult("activity-log", "Activity Log timestamps are not in ascending order");
|
|
840
722
|
}
|
|
@@ -906,118 +788,7 @@ function checkCompletionChecklist({ taskDir, config }: any): any {
|
|
|
906
788
|
return passResult("completion-checklist", `Completion Checklist valid (${items.length} items checked)`);
|
|
907
789
|
}
|
|
908
790
|
|
|
909
|
-
function
|
|
910
|
-
let value = String(line || "").trim();
|
|
911
|
-
if (!value.startsWith("|")) {
|
|
912
|
-
return [];
|
|
913
|
-
}
|
|
914
|
-
value = value.replace(/^\|/, "").replace(/\|$/, "");
|
|
915
|
-
|
|
916
|
-
const cells = [];
|
|
917
|
-
let cell = "";
|
|
918
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
919
|
-
const char = value[index];
|
|
920
|
-
if (char === "|" && !isEscapedAt(value, index)) {
|
|
921
|
-
cells.push(unescapeMarkdownTableCell(cell.trim()));
|
|
922
|
-
cell = "";
|
|
923
|
-
continue;
|
|
924
|
-
}
|
|
925
|
-
cell += char;
|
|
926
|
-
}
|
|
927
|
-
cells.push(unescapeMarkdownTableCell(cell.trim()));
|
|
928
|
-
return cells;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
function unescapeMarkdownTableCell(value: any): any {
|
|
932
|
-
let output = "";
|
|
933
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
934
|
-
const char = value[index];
|
|
935
|
-
const next = value[index + 1];
|
|
936
|
-
if (char === "\\" && (next === "\\" || next === "|")) {
|
|
937
|
-
output += next;
|
|
938
|
-
index += 1;
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
output += char;
|
|
942
|
-
}
|
|
943
|
-
return output;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
function isEscapedAt(value: any, index: any): any {
|
|
947
|
-
let backslashes = 0;
|
|
948
|
-
for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
|
|
949
|
-
backslashes += 1;
|
|
950
|
-
}
|
|
951
|
-
return backslashes % 2 === 1;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
function parseWorkflowWarningRows(section: any): any {
|
|
955
|
-
const rows = [];
|
|
956
|
-
for (const rawLine of String(section || "").split(/\r?\n/)) {
|
|
957
|
-
const cells = splitMarkdownTableRow(rawLine);
|
|
958
|
-
if (cells.length === 0) {
|
|
959
|
-
continue;
|
|
960
|
-
}
|
|
961
|
-
if ((cells[0] || "").toLowerCase() === "id") {
|
|
962
|
-
continue;
|
|
963
|
-
}
|
|
964
|
-
if (cells.every((cell: any) => /^:?-{3,}:?$/.test(cell))) {
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
rows.push(cells);
|
|
968
|
-
}
|
|
969
|
-
return rows;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function validateWorkflowWarnings(content: any): any {
|
|
973
|
-
const section = getSectionContent(content, WORKFLOW_WARNING_SECTION_NAMES);
|
|
974
|
-
if (!section.trim()) {
|
|
975
|
-
return [];
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const rows = parseWorkflowWarningRows(section);
|
|
979
|
-
const errors = [];
|
|
980
|
-
for (const cells of rows) {
|
|
981
|
-
if (cells.length < 11) {
|
|
982
|
-
errors.push(`malformed row (expected 11 columns): ${cells.join(" | ")}`);
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
const [id, time, step, severity, code, status, target, message, action, resolvedAt, resolution] = cells;
|
|
986
|
-
if (!WORKFLOW_WARNING_ID_PATTERN.test(id)) {
|
|
987
|
-
errors.push(`${id || "(empty id)"}: invalid id`);
|
|
988
|
-
}
|
|
989
|
-
if (!DATE_TIME_PATTERN.test(time)) {
|
|
990
|
-
errors.push(`${id}: invalid time '${time}'`);
|
|
991
|
-
}
|
|
992
|
-
if (isBlank(step)) {
|
|
993
|
-
errors.push(`${id}: step is required`);
|
|
994
|
-
}
|
|
995
|
-
if (!WORKFLOW_WARNING_SEVERITIES.has(severity)) {
|
|
996
|
-
errors.push(`${id}: illegal severity '${severity}'`);
|
|
997
|
-
}
|
|
998
|
-
if (isBlank(code)) {
|
|
999
|
-
errors.push(`${id}: code is required`);
|
|
1000
|
-
}
|
|
1001
|
-
if (!WORKFLOW_WARNING_STATUSES.has(status)) {
|
|
1002
|
-
errors.push(`${id}: illegal status '${status}'`);
|
|
1003
|
-
}
|
|
1004
|
-
if (isBlank(target)) {
|
|
1005
|
-
errors.push(`${id}: target is required`);
|
|
1006
|
-
}
|
|
1007
|
-
if (isBlank(message)) {
|
|
1008
|
-
errors.push(`${id}: message is required`);
|
|
1009
|
-
}
|
|
1010
|
-
if (status === "open" && isBlank(action)) {
|
|
1011
|
-
errors.push(`${id}: open warning requires action`);
|
|
1012
|
-
}
|
|
1013
|
-
if ((status === "resolved" || status === "ignored") && (isBlank(resolvedAt) || isBlank(resolution))) {
|
|
1014
|
-
errors.push(`${id}: ${status} warning requires resolved_at and resolution`);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
return errors;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
function resolveReviewSetting(config: any, key: any, fallback: any): any {
|
|
791
|
+
function resolveReviewSetting(config: any, key: any, fallback: any, repoRoot: string): any {
|
|
1021
792
|
if (config && config[key] !== undefined && config[key] !== null) {
|
|
1022
793
|
return config[key];
|
|
1023
794
|
}
|
|
@@ -1028,7 +799,7 @@ function resolveReviewSetting(config: any, key: any, fallback: any): any {
|
|
|
1028
799
|
return fallback;
|
|
1029
800
|
}
|
|
1030
801
|
|
|
1031
|
-
function checkReviewLedger({ taskDir, config }: any): any {
|
|
802
|
+
function checkReviewLedger({ taskDir, config, repositoryRoot }: any): any {
|
|
1032
803
|
const task = loadTask(taskDir);
|
|
1033
804
|
if (!task.ok) {
|
|
1034
805
|
return failResult("review-ledger", task.message);
|
|
@@ -1038,9 +809,7 @@ function checkReviewLedger({ taskDir, config }: any): any {
|
|
|
1038
809
|
try {
|
|
1039
810
|
const ledger = parseLedgerDocument(task.content);
|
|
1040
811
|
if (!ledger.present) return failResult("review-ledger", `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
1041
|
-
rows = ledger.rows
|
|
1042
|
-
row.id, row.stage, row.round, row.severity, row.status, row.evidence
|
|
1043
|
-
]);
|
|
812
|
+
rows = ledger.rows;
|
|
1044
813
|
} catch (error) {
|
|
1045
814
|
return failResult("review-ledger", `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`);
|
|
1046
815
|
}
|
|
@@ -1049,17 +818,12 @@ function checkReviewLedger({ taskDir, config }: any): any {
|
|
|
1049
818
|
}
|
|
1050
819
|
|
|
1051
820
|
const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
|
|
1052
|
-
const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
|
|
821
|
+
const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS, repositoryRoot));
|
|
1053
822
|
const problems = [];
|
|
1054
823
|
let inScopeCount = 0;
|
|
1055
824
|
|
|
1056
|
-
for (const
|
|
1057
|
-
|
|
1058
|
-
problems.push(`malformed row (expected 6 columns): ${cells.join(" | ")}`);
|
|
1059
|
-
continue;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
const [id, stage, roundRaw, , status, evidence] = cells;
|
|
825
|
+
for (const row of rows) {
|
|
826
|
+
const { id, stage, round: roundRaw, status, evidence } = row;
|
|
1063
827
|
const stageScoped = stageScope ? stageScope.includes(stage) : true;
|
|
1064
828
|
// post-review-commit exemption rows are consumed by the post-review-commit
|
|
1065
829
|
// check, not enforced here.
|
|
@@ -1071,8 +835,9 @@ function checkReviewLedger({ taskDir, config }: any): any {
|
|
|
1071
835
|
}
|
|
1072
836
|
inScopeCount += 1;
|
|
1073
837
|
|
|
1074
|
-
|
|
1075
|
-
|
|
838
|
+
const invalid = validateLedgerRows([row]);
|
|
839
|
+
if (invalid) {
|
|
840
|
+
problems.push(`${invalid.code}: ${invalid.message}`);
|
|
1076
841
|
continue;
|
|
1077
842
|
}
|
|
1078
843
|
if (status !== "open" && evidence === "") {
|
|
@@ -1082,12 +847,12 @@ function checkReviewLedger({ taskDir, config }: any): any {
|
|
|
1082
847
|
if (
|
|
1083
848
|
Number.isFinite(round) &&
|
|
1084
849
|
round >= maxRounds &&
|
|
1085
|
-
!
|
|
850
|
+
!LEDGER_TERMINAL.has(status!) &&
|
|
1086
851
|
status !== "needs-human-decision"
|
|
1087
852
|
) {
|
|
1088
853
|
problems.push(`${id}: round ${round} reached limit ${maxRounds} without convergence; escalate to needs-human-decision`);
|
|
1089
854
|
}
|
|
1090
|
-
if (!
|
|
855
|
+
if (!LEDGER_TERMINAL.has(status!)) {
|
|
1091
856
|
problems.push(`${id}: unresolved (status '${status}')`);
|
|
1092
857
|
}
|
|
1093
858
|
}
|
|
@@ -1100,7 +865,7 @@ function checkReviewLedger({ taskDir, config }: any): any {
|
|
|
1100
865
|
return passResult("review-ledger", `Disagreement ledger clean (${inScopeCount} in-scope entries terminal${scopeLabel})`);
|
|
1101
866
|
}
|
|
1102
867
|
|
|
1103
|
-
function checkManualValidation({ taskDir }: any): any {
|
|
868
|
+
async function checkManualValidation({ taskDir, repositoryRoot }: any): Promise<any> {
|
|
1104
869
|
// 1. Latest review-code artifact; none -> pass (no review, check not applicable).
|
|
1105
870
|
const review = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
1106
871
|
if (!review.ok) {
|
|
@@ -1132,15 +897,23 @@ function checkManualValidation({ taskDir }: any): any {
|
|
|
1132
897
|
const artifactName = path.basename(resolved.path);
|
|
1133
898
|
const task = loadTask(taskDir);
|
|
1134
899
|
if (!task.ok) return failResult("manual-validation", task.message);
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
);
|
|
1139
|
-
if (!
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
900
|
+
const completion = readManualValidationCompletion(taskDir, {
|
|
901
|
+
taskId: task.metadata.id,
|
|
902
|
+
artifact: artifactName
|
|
903
|
+
});
|
|
904
|
+
if (!completion.ok) {
|
|
905
|
+
const source = completion.error.code.startsWith('MANUAL_VALIDATION_RECEIPT_') ? 'receipt' : 'transaction';
|
|
906
|
+
return failResult("manual-validation", `Manual validation ${source} is unavailable: ${completion.error.message}`);
|
|
907
|
+
}
|
|
908
|
+
const { receipt, transaction } = completion.value;
|
|
909
|
+
if (transaction.phase !== "committed" || !transaction.eventAppended || !transaction.postWriteVerified) {
|
|
910
|
+
return failResult("manual-validation", "Manual validation transaction has not reached the committed post-write-verified state");
|
|
911
|
+
}
|
|
912
|
+
if (transaction.pendingSummaryDigest !== receipt.pendingSummaryDigest || transaction.finalSummaryDigest !== receipt.finalSummaryDigest) {
|
|
913
|
+
return failResult("manual-validation", "Manual validation summary digests do not match the committed receipt");
|
|
914
|
+
}
|
|
915
|
+
if (sha256File(resolved.path) !== receipt.artifactSha256) {
|
|
916
|
+
return failResult("manual-validation", `Manual validation artifact digest does not match the receipt for ${artifactName}`);
|
|
1144
917
|
}
|
|
1145
918
|
// 6. Timing correlation (PL-3 fix, PL-4 corrected): the completion entry must
|
|
1146
919
|
// sit AFTER the latest review-code round's completion entry in the
|
|
@@ -1149,28 +922,32 @@ function checkManualValidation({ taskDir }: any): any {
|
|
|
1149
922
|
// which indexOf never matches). Not reusing completed.exec().index: exec
|
|
1150
923
|
// anchors to the first '**Complete Manual Validation**' heading, which can
|
|
1151
924
|
// belong to an earlier round's entry and misorder the standard path.
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
);
|
|
925
|
+
const inspectedLog = inspectActivityLog(task.content);
|
|
926
|
+
if (!inspectedLog.section) return failResult("manual-validation", "Activity Log is missing or invalid");
|
|
927
|
+
const reviewDoneIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === `Review Code (Round ${review.round})`);
|
|
928
|
+
const completedIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === "Complete Manual Validation"
|
|
929
|
+
&& entry.note.includes(`Manual validation passed → ${artifactName};`)
|
|
930
|
+
&& entry.note.includes(`transaction=${receipt.transactionId};`)
|
|
931
|
+
&& entry.note.includes(`receipt=${receipt.receiptDigest};`)
|
|
932
|
+
&& entry.note.includes(`head=${receipt.prHeadSha}`));
|
|
933
|
+
if (reviewDoneIndex === -1) return failResult("manual-validation", `Latest review-code (round ${review.round}) completion entry is missing from the Activity Log`);
|
|
934
|
+
if (completedIndex === -1) return failResult("manual-validation", `Committed manual validation completion is not recorded for ${artifactName}`);
|
|
935
|
+
if (completedIndex < reviewDoneIndex) return failResult("manual-validation", `Latest review-code (round ${review.round}) came after the manual validation completion recorded for ${artifactName}`);
|
|
936
|
+
|
|
937
|
+
const fact = readPrDeliveryFact(task.metadata);
|
|
938
|
+
if (fact.status === "invalid") return failResult("manual-validation", fact.error.message);
|
|
939
|
+
if (fact.status === "valid" && fact.fact.state === "bound") {
|
|
940
|
+
const inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
|
|
941
|
+
if (!inspected.pullRequest) return failResult("manual-validation", inspected.error?.message ?? "canonical pull-request head is unavailable");
|
|
942
|
+
if (inspected.pullRequest.head.sha !== receipt.prHeadSha) return failResult("manual-validation", "manual validation receipt is stale for the current pull-request head");
|
|
943
|
+
const summary = await summaryCommentState(task.metadata.id, { cwd: repositoryRoot });
|
|
944
|
+
if (!summary.comment) return failResult("manual-validation", "canonical pull-request summary comment is unavailable");
|
|
945
|
+
if (!manualValidationFinalSummaryProjectionMatches(summary.comment.body, receipt)) return failResult("manual-validation", "canonical pull-request summary does not match the committed manual validation projection");
|
|
1169
946
|
}
|
|
1170
|
-
return passResult("manual-validation", `Manual validation completed → ${artifactName}`);
|
|
947
|
+
return passResult("manual-validation", `Manual validation completed → ${artifactName} (committed receipt and post-write verification)`);
|
|
1171
948
|
}
|
|
1172
949
|
|
|
1173
|
-
async function checkPostReviewCommit({ taskDir, config }: any): Promise<any> {
|
|
950
|
+
async function checkPostReviewCommit({ taskDir, config, repositoryRoot }: any): Promise<any> {
|
|
1174
951
|
const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
1175
952
|
if (!reviewArtifact.ok) {
|
|
1176
953
|
if (reviewArtifact.error) return failResult("post-review-commit", `Review-code artifact is unavailable: ${reviewArtifact.error}`);
|
|
@@ -1190,7 +967,7 @@ async function checkPostReviewCommit({ taskDir, config }: any): Promise<any> {
|
|
|
1190
967
|
if (factRead.status === 'invalid') return failResult("post-review-commit", factRead.error.message);
|
|
1191
968
|
const fact = factRead.status === 'valid' ? factRead.fact : null;
|
|
1192
969
|
const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
|
|
1193
|
-
const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(
|
|
970
|
+
const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repositoryRoot));
|
|
1194
971
|
const hasPullRequest = fact?.state === 'bound';
|
|
1195
972
|
let inspected: Awaited<ReturnType<typeof inspectPlatformPullRequest>> | null = null;
|
|
1196
973
|
if (hasPullRequest) {
|
|
@@ -1364,7 +1141,7 @@ function resolvePostReviewExemption(content: string):
|
|
|
1364
1141
|
}
|
|
1365
1142
|
}
|
|
1366
1143
|
|
|
1367
|
-
function checkReviewFact({ taskDir, artifactFile }: any): any {
|
|
1144
|
+
function checkReviewFact({ taskDir, artifactFile, repositoryRoot }: any): any {
|
|
1368
1145
|
const resolvedArtifact = resolveArtifactPath(
|
|
1369
1146
|
taskDir,
|
|
1370
1147
|
"review-code.md|review-code-r{N}.md",
|
|
@@ -1448,7 +1225,7 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
|
|
|
1448
1225
|
mode: "worktree",
|
|
1449
1226
|
baseline,
|
|
1450
1227
|
diffBase,
|
|
1451
|
-
globs: resolvePostReviewGlobs({}, loadPostReviewConfig(
|
|
1228
|
+
globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
|
|
1452
1229
|
});
|
|
1453
1230
|
} catch {
|
|
1454
1231
|
return blockedResult(
|
|
@@ -1575,46 +1352,13 @@ function resolveArtifactPath(taskDir: any, filePattern: any, artifactFile: any):
|
|
|
1575
1352
|
return { ok: true, path: path.join(taskDir, matches[0]!.fileName) };
|
|
1576
1353
|
}
|
|
1577
1354
|
|
|
1578
|
-
function getSectionContent(content:
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
function visibleHeadings(): any {
|
|
1582
|
-
const headings = [];
|
|
1583
|
-
let fence = null;
|
|
1584
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
1585
|
-
const line = lines[index];
|
|
1586
|
-
if (fence) {
|
|
1587
|
-
const closer = line.match(/^ {0,3}(`+|~+)\s*$/);
|
|
1588
|
-
if (closer && closer[1][0] === fence.character && closer[1].length >= fence.length) {
|
|
1589
|
-
fence = null;
|
|
1590
|
-
}
|
|
1591
|
-
continue;
|
|
1592
|
-
}
|
|
1593
|
-
const opener = line.match(/^ {0,3}(`{3,}|~{3,})(?:[^`~].*)?$/);
|
|
1594
|
-
if (opener) {
|
|
1595
|
-
fence = { character: opener[1][0], length: opener[1].length };
|
|
1596
|
-
continue;
|
|
1597
|
-
}
|
|
1598
|
-
if (line.startsWith("## ")) {
|
|
1599
|
-
headings.push({ index, text: line.trim() });
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
return headings;
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
const headings = visibleHeadings();
|
|
1606
|
-
|
|
1355
|
+
function getSectionContent(content: string, names: readonly string[]): string {
|
|
1356
|
+
const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
|
|
1607
1357
|
for (const name of names) {
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
continue;
|
|
1612
|
-
}
|
|
1613
|
-
const startIndex = headings[position]!.index;
|
|
1614
|
-
const endIndex = headings[position + 1]?.index ?? lines.length;
|
|
1615
|
-
return lines.slice(startIndex + 1, endIndex).join("\n").trim();
|
|
1358
|
+
const position = headings.findIndex((heading) => heading.text === name);
|
|
1359
|
+
if (position === -1) continue;
|
|
1360
|
+
return content.slice(headings[position]!.end + 1, headings[position + 1]?.start ?? content.length).replace(/\r\n/g, "\n").trim();
|
|
1616
1361
|
}
|
|
1617
|
-
|
|
1618
1362
|
return "";
|
|
1619
1363
|
}
|
|
1620
1364
|
|
|
@@ -1742,14 +1486,14 @@ function isBlank(value: any): any {
|
|
|
1742
1486
|
}
|
|
1743
1487
|
|
|
1744
1488
|
async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }: any): Promise<any> {
|
|
1745
|
-
|
|
1746
|
-
|
|
1489
|
+
let repoRoot = repositoryRoot ? path.resolve(repositoryRoot) : defaultRepoRoot;
|
|
1490
|
+
if (!repositoryRoot) {
|
|
1747
1491
|
let cursor = path.resolve(taskDir);
|
|
1748
1492
|
while (path.dirname(cursor) !== cursor && !fs.existsSync(path.join(cursor, ".agents"))) cursor = path.dirname(cursor);
|
|
1749
1493
|
if (fs.existsSync(path.join(cursor, ".agents"))) repoRoot = cursor;
|
|
1750
1494
|
}
|
|
1751
1495
|
const verifyConfig = loadVerificationConfig(repoRoot, skillName);
|
|
1752
|
-
const shared = { ...sharedUtils, repoRoot };
|
|
1496
|
+
const shared: VerificationShared = { ...sharedUtils, repoRoot };
|
|
1753
1497
|
if (mode === "gate") {
|
|
1754
1498
|
const checks = [];
|
|
1755
1499
|
for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
|