@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
|
@@ -12,12 +12,16 @@ import { resolveMaterializedReviewedHeadRelation } from "../platform/change-requ
|
|
|
12
12
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
|
|
13
13
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
|
|
14
14
|
import { parseTypedTaskFrontmatter } from "./frontmatter.js";
|
|
15
|
-
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
|
|
15
|
+
import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
|
|
16
16
|
import { isValidAgentInfraVersion } from "../version.js";
|
|
17
17
|
import { equalCounts, parseReviewSummary } from "./review-artifacts.js";
|
|
18
18
|
import { inspectDecisionDetailDuplicates } from "./decision-details.js";
|
|
19
|
+
import { parseImplementationInputs } from "./implementation-inputs.js";
|
|
20
|
+
import { scanVisibleMarkdown } from "./markdown.js";
|
|
19
21
|
import { loadVerificationConfig } from "./verification-config.js";
|
|
20
22
|
import { snapshotReview } from "../git/review-snapshot.js";
|
|
23
|
+
import { inspectActivityLog } from "./activity-log.js";
|
|
24
|
+
import { parseWorkflowWarnings } from "./workflow-warnings.js";
|
|
21
25
|
import { OrchestrationStateError, readRun } from "./orchestration.js";
|
|
22
26
|
import { resolveDeliveryTarget } from "./delivery-target.js";
|
|
23
27
|
import { readPrDeliveryFact } from "./pr-delivery-fact.js";
|
|
@@ -25,6 +29,10 @@ import { validateLocalArtifact } from "./local-artifact-finalization.js";
|
|
|
25
29
|
import { validateQualificationAudit } from "./qualification-audit.js";
|
|
26
30
|
import { getArtifactSchema } from "./artifact-schema.js";
|
|
27
31
|
import { inspectArtifactContract } from "./artifact-operations.js";
|
|
32
|
+
import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
|
|
33
|
+
import { readManualValidationCompletion } from "./manual-validation-completion.js";
|
|
34
|
+
import { sha256File } from "./artifact-receipts.js";
|
|
35
|
+
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
28
36
|
const TASK_ENUMS = {
|
|
29
37
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
30
38
|
workflow: ["feature-development", "bug-fix", "refactoring"],
|
|
@@ -42,7 +50,6 @@ const DEFAULT_REQUIRED_FIELDS = [
|
|
|
42
50
|
"assigned_to"
|
|
43
51
|
];
|
|
44
52
|
const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
|
|
45
|
-
const ACTIVITY_LOG_PATTERN = /^- (\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?) — \*\*(.+?)\*\* by (.+?) — (.+)$/;
|
|
46
53
|
// Start markers (action suffixed with ` [started]`) are excluded from the
|
|
47
54
|
// "latest action" / freshness computation so a step's in-flight marker never
|
|
48
55
|
// satisfies a skill's expected_action_pattern; the matching done entry does.
|
|
@@ -50,27 +57,11 @@ const ACTIVITY_LOG_STARTED_RE = /\s*\[started\]\s*$/;
|
|
|
50
57
|
const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
51
58
|
// Review disagreement ledger (see .agents/rules/review-handshake.md).
|
|
52
59
|
const LEDGER_SECTION_NAMES = ["审查分歧账本", "Review Disagreement Ledger"];
|
|
53
|
-
const LEDGER_STATUSES = new Set([
|
|
54
|
-
"open",
|
|
55
|
-
"accepted",
|
|
56
|
-
"adjusted",
|
|
57
|
-
"refuted",
|
|
58
|
-
"cannot-judge",
|
|
59
|
-
"confirmed",
|
|
60
|
-
"needs-human-decision",
|
|
61
|
-
"closed",
|
|
62
|
-
"human-decided"
|
|
63
|
-
]);
|
|
64
|
-
const LEDGER_TERMINAL_OK = new Set(["confirmed", "closed", "human-decided"]);
|
|
65
60
|
const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
|
|
66
61
|
const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
|
|
67
62
|
const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
68
|
-
const WORKFLOW_WARNING_SECTION_NAMES = ["工作流告警", "Workflow Warnings"];
|
|
69
|
-
const WORKFLOW_WARNING_STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
70
|
-
const WORKFLOW_WARNING_SEVERITIES = new Set(["IMPORTANT", "ACTION_REQUIRED"]);
|
|
71
|
-
const WORKFLOW_WARNING_ID_PATTERN = /^WW-\d+$/;
|
|
72
63
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
73
|
-
|
|
64
|
+
const defaultRepoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
|
|
74
65
|
const PLATFORM_ADAPTERS = {
|
|
75
66
|
"platform-sync": checkPlatformSync,
|
|
76
67
|
"platform-sync-preflight": async (context, shared) => ({ ...await checkPlatformSync(context, shared), type: "platform-sync-preflight" }),
|
|
@@ -175,14 +166,12 @@ function exactText(value) {
|
|
|
175
166
|
function validateCleanCompletionEvidence(run) {
|
|
176
167
|
const evidence = run.completionEvidence;
|
|
177
168
|
const shaPattern = /^[0-9a-f]{40}$/i;
|
|
178
|
-
if (evidence
|
|
179
|
-
|| !exactText(evidence.observedAt)
|
|
169
|
+
if (!evidence
|
|
180
170
|
|| Number.isNaN(Date.parse(evidence.observedAt))
|
|
181
171
|
|| !shaPattern.test(evidence.head)
|
|
182
172
|
|| !shaPattern.test(evidence.headTree)
|
|
183
173
|
|| !shaPattern.test(evidence.worktreeTree)
|
|
184
174
|
|| !shaPattern.test(evidence.lastReviewedCommit)
|
|
185
|
-
|| !(evidence.prNumber === null || (Number.isInteger(evidence.prNumber) && evidence.prNumber > 0))
|
|
186
175
|
|| !(evidence.prHead === null || shaPattern.test(evidence.prHead))) {
|
|
187
176
|
return 'Clean completion evidence has an invalid structure';
|
|
188
177
|
}
|
|
@@ -195,7 +184,6 @@ function validateCleanCompletionEvidence(run) {
|
|
|
195
184
|
|| run.pendingDelegation !== null
|
|
196
185
|
|| run.commitAuthorization?.issuedAt !== null
|
|
197
186
|
|| run.commitAuthorization?.consumedAt !== null
|
|
198
|
-
|| !Array.isArray(run.receipts)
|
|
199
187
|
|| run.receipts.some((receipt) => receipt.stage === 'commit')) {
|
|
200
188
|
return 'Clean completion evidence conflicts with commit delegation state';
|
|
201
189
|
}
|
|
@@ -214,43 +202,10 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
214
202
|
const message = error instanceof OrchestrationStateError ? error.message : String(error);
|
|
215
203
|
return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
|
|
216
204
|
}
|
|
205
|
+
if (!run)
|
|
206
|
+
return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
207
|
+
// readRun validates the persisted schema, receipt identities and client provenance.
|
|
217
208
|
const policy = run.modelPolicy;
|
|
218
|
-
if (!policy
|
|
219
|
-
|| !exactText(policy.executor?.model)
|
|
220
|
-
|| !exactText(policy.executor?.reasoningEffort)
|
|
221
|
-
|| !exactText(policy.reviewer?.model)
|
|
222
|
-
|| !exactText(policy.reviewer?.reasoningEffort)) {
|
|
223
|
-
return failResult('orchestration-evidence', 'Run model policy requires exact executor and reviewer model and effort');
|
|
224
|
-
}
|
|
225
|
-
if (!run.modelPolicySource
|
|
226
|
-
|| !['explicit', 'project-config'].includes(run.modelPolicySource.kind)
|
|
227
|
-
|| !exactText(run.modelPolicySource.client)
|
|
228
|
-
|| !exactText(run.modelPolicySource.resolvedAt)
|
|
229
|
-
|| !Array.isArray(run.recoveryHistory)) {
|
|
230
|
-
return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
|
|
231
|
-
}
|
|
232
|
-
for (const recovery of run.recoveryHistory) {
|
|
233
|
-
const guards = recovery.guards;
|
|
234
|
-
const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
|
|
235
|
-
&& recovery.previousStatus === 'paused'
|
|
236
|
-
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
237
|
-
&& recovery.client === 'claude-code'
|
|
238
|
-
&& recovery.resultingStatus === 'running'
|
|
239
|
-
&& exactText(recovery.recoveredAt)
|
|
240
|
-
&& guards?.stepCount === 0
|
|
241
|
-
&& guards?.nextStage === null
|
|
242
|
-
&& guards?.baselineEmpty === true
|
|
243
|
-
&& guards?.receiptCount === 0
|
|
244
|
-
&& guards?.pendingDelegation === false
|
|
245
|
-
&& guards?.commitAuthorizationUnused === true
|
|
246
|
-
&& guards?.completionEvidenceAbsent === true;
|
|
247
|
-
if (!validClaudeCodeRecovery) {
|
|
248
|
-
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (!Array.isArray(run.receipts)) {
|
|
252
|
-
return failResult('orchestration-evidence', 'Run receipts must be an array');
|
|
253
|
-
}
|
|
254
209
|
if (run.completionEvidence != null) {
|
|
255
210
|
const evidenceError = validateCleanCompletionEvidence(run);
|
|
256
211
|
if (evidenceError)
|
|
@@ -282,10 +237,6 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
282
237
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
|
|
283
238
|
}
|
|
284
239
|
}
|
|
285
|
-
// parentId/childId 校验对所有 client 保持统一(不新增分支);spawnMode 检查按 client 判断
|
|
286
|
-
if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId) {
|
|
287
|
-
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid delegation identity`);
|
|
288
|
-
}
|
|
289
240
|
if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
|
|
290
241
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
|
|
291
242
|
}
|
|
@@ -307,41 +258,6 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
307
258
|
&& receipt.reasoningEffortFallbackReason !== null) {
|
|
308
259
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
309
260
|
}
|
|
310
|
-
if (receipt.client === 'codex') {
|
|
311
|
-
const activatedCodex = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
312
|
-
const host = receipt.hostEvidence;
|
|
313
|
-
const provenance = receipt.lifecycleProvenance;
|
|
314
|
-
if (!provenance
|
|
315
|
-
|| (activatedCodex && (host?.kind !== 'codex-lifecycle-v2'
|
|
316
|
-
|| host.protocolVersion !== provenance.protocolVersion
|
|
317
|
-
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
318
|
-
|| host.hookSource !== provenance.hookSource
|
|
319
|
-
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
320
|
-
|| host.hookSourceHash !== provenance.hookSourceHash
|
|
321
|
-
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
322
|
-
|| receipt.parentId !== provenance.capabilitySessionId
|
|
323
|
-
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
324
|
-
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
325
|
-
|| host.controlGeneration !== provenance.controlGeneration
|
|
326
|
-
|| host.spawnToolUseId === provenance.capabilityToolUseId
|
|
327
|
-
|| !exactText(host.spawnToolUseId)
|
|
328
|
-
|| !Number.isFinite(Date.parse(host.spawnObservedAt ?? ''))
|
|
329
|
-
|| !Number.isFinite(Date.parse(receipt.spawnDispatchedAt ?? ''))
|
|
330
|
-
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt ?? ''))
|
|
331
|
-
|| Date.parse(host.spawnObservedAt ?? '') < Date.parse(receipt.spawnDispatchedAt ?? '')
|
|
332
|
-
|| Date.parse(host.spawnObservedAt ?? '') > Date.parse(receipt.activationDeadlineAt ?? '')
|
|
333
|
-
|| !exactText(host.hookDefinitionHash)
|
|
334
|
-
|| !Number.isSafeInteger(host.startRevision)
|
|
335
|
-
|| host.startRevision < 1))) {
|
|
336
|
-
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
|
|
337
|
-
}
|
|
338
|
-
if (['sealed', 'consumed'].includes(receipt.status) && (!Number.isSafeInteger(host.stopRevision)
|
|
339
|
-
|| host.stopRevision <= host.startRevision
|
|
340
|
-
|| host.consumer !== receipt.id
|
|
341
|
-
|| !exactText(host.consumedAt))) {
|
|
342
|
-
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex consumed stop evidence`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
261
|
}
|
|
346
262
|
}
|
|
347
263
|
if (run.receipts.some((receipt) => receipt.status !== 'consumed')) {
|
|
@@ -350,7 +266,7 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
350
266
|
return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
|
|
351
267
|
}
|
|
352
268
|
// === Check Functions ===
|
|
353
|
-
function checkTaskMeta({ taskDir, config }) {
|
|
269
|
+
function checkTaskMeta({ taskDir, config, repositoryRoot }) {
|
|
354
270
|
const task = loadTask(taskDir);
|
|
355
271
|
if (!task.ok) {
|
|
356
272
|
return failResult("task-meta", task.message);
|
|
@@ -380,13 +296,15 @@ function checkTaskMeta({ taskDir, config }) {
|
|
|
380
296
|
return failResult("task-meta", `Invalid ${field}: ${metadata[field]}`);
|
|
381
297
|
}
|
|
382
298
|
}
|
|
383
|
-
const branchValidationError = validateTaskBranch(metadata);
|
|
299
|
+
const branchValidationError = validateTaskBranch(metadata, repositoryRoot);
|
|
384
300
|
if (branchValidationError) {
|
|
385
301
|
return failResult("task-meta", branchValidationError);
|
|
386
302
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
303
|
+
try {
|
|
304
|
+
parseWorkflowWarnings(task.content);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
return failResult("task-meta", `Invalid Workflow Warnings: ${error instanceof Error ? error.message : String(error)}`);
|
|
390
308
|
}
|
|
391
309
|
const expectedStep = config.expected_step;
|
|
392
310
|
if (expectedStep && metadata.current_step !== expectedStep) {
|
|
@@ -481,11 +399,11 @@ async function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }) {
|
|
|
481
399
|
}
|
|
482
400
|
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
|
|
483
401
|
}
|
|
484
|
-
function validateTaskBranch(metadata) {
|
|
402
|
+
function validateTaskBranch(metadata, repoRoot) {
|
|
485
403
|
if (isBlank(metadata.branch)) {
|
|
486
404
|
return null;
|
|
487
405
|
}
|
|
488
|
-
const projectName = loadProjectName();
|
|
406
|
+
const projectName = loadProjectName(repoRoot);
|
|
489
407
|
const expectedPrefix = projectName ? `${projectName}-${metadata.type}-` : "";
|
|
490
408
|
if (expectedPrefix && !String(metadata.branch).startsWith(expectedPrefix)) {
|
|
491
409
|
return `Invalid branch: expected prefix '${expectedPrefix}', got '${metadata.branch}'`;
|
|
@@ -496,7 +414,7 @@ function validateTaskBranch(metadata) {
|
|
|
496
414
|
}
|
|
497
415
|
return null;
|
|
498
416
|
}
|
|
499
|
-
function loadProjectName() {
|
|
417
|
+
function loadProjectName(repoRoot) {
|
|
500
418
|
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
501
419
|
if (!fs.existsSync(configPath)) {
|
|
502
420
|
return "";
|
|
@@ -647,30 +565,17 @@ function checkImplementationInput({ taskDir, artifactFile }) {
|
|
|
647
565
|
const artifactPath = path.join(taskDir, artifactFile);
|
|
648
566
|
if (!safeStat(artifactPath)?.isFile())
|
|
649
567
|
return failResult("implementation-input", `Artifact not found: ${artifactFile}`);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const table = inputSection.split(/\r?\n/).filter((line) => line.trim().startsWith("|"));
|
|
654
|
-
const cells = (line) => line.split("|").slice(1, -1).map((cell) => cell.trim());
|
|
655
|
-
const expected = ["id", "ledger_id", "decision_evidence", "stage", "needs_implementation", "decided_at", "status", "consumed_by"];
|
|
656
|
-
if (table.length < 2 || JSON.stringify(cells(table[0])) !== JSON.stringify(expected)) {
|
|
657
|
-
return failResult("implementation-input", "Implementation Inputs table schema is invalid");
|
|
658
|
-
}
|
|
659
|
-
const seen = new Set();
|
|
660
|
-
for (const line of table.slice(2)) {
|
|
661
|
-
const row = cells(line);
|
|
662
|
-
if (row.length !== 8 || !/^II-[1-9]\d*$/.test(row[0]) || seen.has(row[0])) {
|
|
663
|
-
return failResult("implementation-input", "Implementation Inputs table contains an invalid or duplicate id");
|
|
664
|
-
}
|
|
665
|
-
seen.add(row[0]);
|
|
666
|
-
rows.push({ id: row[0], ledgerId: row[1], evidence: row[2], stage: row[3], needs: row[4], status: row[6], consumedBy: row[7] });
|
|
667
|
-
}
|
|
568
|
+
let rows;
|
|
569
|
+
try {
|
|
570
|
+
rows = parseImplementationInputs(task.content).rows;
|
|
668
571
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
572
|
+
catch (error) {
|
|
573
|
+
return failResult("implementation-input", `Invalid Implementation Inputs: ${error instanceof Error ? error.message : String(error)}`);
|
|
574
|
+
}
|
|
575
|
+
const doneActions = (inspectActivityLog(task.content).section?.entries ?? [])
|
|
576
|
+
.filter((entry) => !ACTIVITY_LOG_STARTED_RE.test(entry.step))
|
|
577
|
+
.map((entry) => entry.step)
|
|
578
|
+
.filter((step) => /^(?:Code Task|Code) \(Round \d+/.test(step));
|
|
674
579
|
const latestAction = doneActions.at(-1) || "";
|
|
675
580
|
const actionDecision = /(?:Code Task|Code) \(Round \d+, decision (II-[1-9]\d*)\)/.exec(latestAction)?.[1] || null;
|
|
676
581
|
const report = fs.readFileSync(artifactPath, "utf8");
|
|
@@ -698,10 +603,10 @@ function checkImplementationInput({ taskDir, artifactFile }) {
|
|
|
698
603
|
if (matches.length !== 1)
|
|
699
604
|
return failResult("implementation-input", `${actionDecision} is missing or duplicated in task table`);
|
|
700
605
|
const row = matches[0];
|
|
701
|
-
if (row.stage !== "code" || row.
|
|
606
|
+
if (row.stage !== "code" || !row.needsImplementation || row.status !== "consumed" || row.consumedBy !== artifactFile || !row.decisionEvidence) {
|
|
702
607
|
return failResult("implementation-input", `${actionDecision} is not a consumed input for ${artifactFile}`);
|
|
703
608
|
}
|
|
704
|
-
if (reportLedger !== row.ledgerId || reportEvidence !== row.
|
|
609
|
+
if (reportLedger !== row.ledgerId || reportEvidence !== row.decisionEvidence) {
|
|
705
610
|
return failResult("implementation-input", `${actionDecision} report identity does not match task table evidence`);
|
|
706
611
|
}
|
|
707
612
|
return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
|
|
@@ -711,27 +616,22 @@ function checkActivityLog({ taskDir, config }) {
|
|
|
711
616
|
if (!task.ok) {
|
|
712
617
|
return failResult("activity-log", task.message);
|
|
713
618
|
}
|
|
714
|
-
const
|
|
715
|
-
if (!
|
|
716
|
-
return failResult("activity-log", "Activity Log section not found");
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
.filter((line) => line.startsWith("- "));
|
|
722
|
-
if (entries.length === 0) {
|
|
619
|
+
const { section, invalidEntries } = inspectActivityLog(task.content);
|
|
620
|
+
if (!section)
|
|
621
|
+
return failResult("activity-log", "Activity Log section not found or ambiguous");
|
|
622
|
+
if (invalidEntries.length)
|
|
623
|
+
return failResult("activity-log", `Invalid Activity Log entry format: ${invalidEntries[0]}`);
|
|
624
|
+
const entries = section.entries;
|
|
625
|
+
if (entries.length === 0)
|
|
723
626
|
return failResult("activity-log", "Activity Log has no entries");
|
|
724
|
-
}
|
|
725
627
|
let previousTimestamp = "";
|
|
726
628
|
let latestAction = "";
|
|
727
629
|
let latestTimestamp = "";
|
|
728
630
|
const doneActions = [];
|
|
729
631
|
for (const entry of entries) {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
734
|
-
const [, timestamp, action] = match;
|
|
632
|
+
if (!entry.note.trim())
|
|
633
|
+
return failResult("activity-log", "Activity Log entry note is required");
|
|
634
|
+
const { time: timestamp, step: action } = entry;
|
|
735
635
|
if (previousTimestamp && timestamp < previousTimestamp) {
|
|
736
636
|
return failResult("activity-log", "Activity Log timestamps are not in ascending order");
|
|
737
637
|
}
|
|
@@ -786,111 +686,7 @@ function checkCompletionChecklist({ taskDir, config }) {
|
|
|
786
686
|
}
|
|
787
687
|
return passResult("completion-checklist", `Completion Checklist valid (${items.length} items checked)`);
|
|
788
688
|
}
|
|
789
|
-
function
|
|
790
|
-
let value = String(line || "").trim();
|
|
791
|
-
if (!value.startsWith("|")) {
|
|
792
|
-
return [];
|
|
793
|
-
}
|
|
794
|
-
value = value.replace(/^\|/, "").replace(/\|$/, "");
|
|
795
|
-
const cells = [];
|
|
796
|
-
let cell = "";
|
|
797
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
798
|
-
const char = value[index];
|
|
799
|
-
if (char === "|" && !isEscapedAt(value, index)) {
|
|
800
|
-
cells.push(unescapeMarkdownTableCell(cell.trim()));
|
|
801
|
-
cell = "";
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
cell += char;
|
|
805
|
-
}
|
|
806
|
-
cells.push(unescapeMarkdownTableCell(cell.trim()));
|
|
807
|
-
return cells;
|
|
808
|
-
}
|
|
809
|
-
function unescapeMarkdownTableCell(value) {
|
|
810
|
-
let output = "";
|
|
811
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
812
|
-
const char = value[index];
|
|
813
|
-
const next = value[index + 1];
|
|
814
|
-
if (char === "\\" && (next === "\\" || next === "|")) {
|
|
815
|
-
output += next;
|
|
816
|
-
index += 1;
|
|
817
|
-
continue;
|
|
818
|
-
}
|
|
819
|
-
output += char;
|
|
820
|
-
}
|
|
821
|
-
return output;
|
|
822
|
-
}
|
|
823
|
-
function isEscapedAt(value, index) {
|
|
824
|
-
let backslashes = 0;
|
|
825
|
-
for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
|
|
826
|
-
backslashes += 1;
|
|
827
|
-
}
|
|
828
|
-
return backslashes % 2 === 1;
|
|
829
|
-
}
|
|
830
|
-
function parseWorkflowWarningRows(section) {
|
|
831
|
-
const rows = [];
|
|
832
|
-
for (const rawLine of String(section || "").split(/\r?\n/)) {
|
|
833
|
-
const cells = splitMarkdownTableRow(rawLine);
|
|
834
|
-
if (cells.length === 0) {
|
|
835
|
-
continue;
|
|
836
|
-
}
|
|
837
|
-
if ((cells[0] || "").toLowerCase() === "id") {
|
|
838
|
-
continue;
|
|
839
|
-
}
|
|
840
|
-
if (cells.every((cell) => /^:?-{3,}:?$/.test(cell))) {
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
rows.push(cells);
|
|
844
|
-
}
|
|
845
|
-
return rows;
|
|
846
|
-
}
|
|
847
|
-
function validateWorkflowWarnings(content) {
|
|
848
|
-
const section = getSectionContent(content, WORKFLOW_WARNING_SECTION_NAMES);
|
|
849
|
-
if (!section.trim()) {
|
|
850
|
-
return [];
|
|
851
|
-
}
|
|
852
|
-
const rows = parseWorkflowWarningRows(section);
|
|
853
|
-
const errors = [];
|
|
854
|
-
for (const cells of rows) {
|
|
855
|
-
if (cells.length < 11) {
|
|
856
|
-
errors.push(`malformed row (expected 11 columns): ${cells.join(" | ")}`);
|
|
857
|
-
continue;
|
|
858
|
-
}
|
|
859
|
-
const [id, time, step, severity, code, status, target, message, action, resolvedAt, resolution] = cells;
|
|
860
|
-
if (!WORKFLOW_WARNING_ID_PATTERN.test(id)) {
|
|
861
|
-
errors.push(`${id || "(empty id)"}: invalid id`);
|
|
862
|
-
}
|
|
863
|
-
if (!DATE_TIME_PATTERN.test(time)) {
|
|
864
|
-
errors.push(`${id}: invalid time '${time}'`);
|
|
865
|
-
}
|
|
866
|
-
if (isBlank(step)) {
|
|
867
|
-
errors.push(`${id}: step is required`);
|
|
868
|
-
}
|
|
869
|
-
if (!WORKFLOW_WARNING_SEVERITIES.has(severity)) {
|
|
870
|
-
errors.push(`${id}: illegal severity '${severity}'`);
|
|
871
|
-
}
|
|
872
|
-
if (isBlank(code)) {
|
|
873
|
-
errors.push(`${id}: code is required`);
|
|
874
|
-
}
|
|
875
|
-
if (!WORKFLOW_WARNING_STATUSES.has(status)) {
|
|
876
|
-
errors.push(`${id}: illegal status '${status}'`);
|
|
877
|
-
}
|
|
878
|
-
if (isBlank(target)) {
|
|
879
|
-
errors.push(`${id}: target is required`);
|
|
880
|
-
}
|
|
881
|
-
if (isBlank(message)) {
|
|
882
|
-
errors.push(`${id}: message is required`);
|
|
883
|
-
}
|
|
884
|
-
if (status === "open" && isBlank(action)) {
|
|
885
|
-
errors.push(`${id}: open warning requires action`);
|
|
886
|
-
}
|
|
887
|
-
if ((status === "resolved" || status === "ignored") && (isBlank(resolvedAt) || isBlank(resolution))) {
|
|
888
|
-
errors.push(`${id}: ${status} warning requires resolved_at and resolution`);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
return errors;
|
|
892
|
-
}
|
|
893
|
-
function resolveReviewSetting(config, key, fallback) {
|
|
689
|
+
function resolveReviewSetting(config, key, fallback, repoRoot) {
|
|
894
690
|
if (config && config[key] !== undefined && config[key] !== null) {
|
|
895
691
|
return config[key];
|
|
896
692
|
}
|
|
@@ -900,7 +696,7 @@ function resolveReviewSetting(config, key, fallback) {
|
|
|
900
696
|
}
|
|
901
697
|
return fallback;
|
|
902
698
|
}
|
|
903
|
-
function checkReviewLedger({ taskDir, config }) {
|
|
699
|
+
function checkReviewLedger({ taskDir, config, repositoryRoot }) {
|
|
904
700
|
const task = loadTask(taskDir);
|
|
905
701
|
if (!task.ok) {
|
|
906
702
|
return failResult("review-ledger", task.message);
|
|
@@ -910,9 +706,7 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
910
706
|
const ledger = parseLedgerDocument(task.content);
|
|
911
707
|
if (!ledger.present)
|
|
912
708
|
return failResult("review-ledger", `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
913
|
-
rows = ledger.rows
|
|
914
|
-
row.id, row.stage, row.round, row.severity, row.status, row.evidence
|
|
915
|
-
]);
|
|
709
|
+
rows = ledger.rows;
|
|
916
710
|
}
|
|
917
711
|
catch (error) {
|
|
918
712
|
return failResult("review-ledger", `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -921,15 +715,11 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
921
715
|
return passResult("review-ledger", "Disagreement ledger has no entries");
|
|
922
716
|
}
|
|
923
717
|
const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
|
|
924
|
-
const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
|
|
718
|
+
const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS, repositoryRoot));
|
|
925
719
|
const problems = [];
|
|
926
720
|
let inScopeCount = 0;
|
|
927
|
-
for (const
|
|
928
|
-
|
|
929
|
-
problems.push(`malformed row (expected 6 columns): ${cells.join(" | ")}`);
|
|
930
|
-
continue;
|
|
931
|
-
}
|
|
932
|
-
const [id, stage, roundRaw, , status, evidence] = cells;
|
|
721
|
+
for (const row of rows) {
|
|
722
|
+
const { id, stage, round: roundRaw, status, evidence } = row;
|
|
933
723
|
const stageScoped = stageScope ? stageScope.includes(stage) : true;
|
|
934
724
|
// post-review-commit exemption rows are consumed by the post-review-commit
|
|
935
725
|
// check, not enforced here.
|
|
@@ -940,8 +730,9 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
940
730
|
continue;
|
|
941
731
|
}
|
|
942
732
|
inScopeCount += 1;
|
|
943
|
-
|
|
944
|
-
|
|
733
|
+
const invalid = validateLedgerRows([row]);
|
|
734
|
+
if (invalid) {
|
|
735
|
+
problems.push(`${invalid.code}: ${invalid.message}`);
|
|
945
736
|
continue;
|
|
946
737
|
}
|
|
947
738
|
if (status !== "open" && evidence === "") {
|
|
@@ -950,11 +741,11 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
950
741
|
const round = Number.parseInt(roundRaw, 10);
|
|
951
742
|
if (Number.isFinite(round) &&
|
|
952
743
|
round >= maxRounds &&
|
|
953
|
-
!
|
|
744
|
+
!LEDGER_TERMINAL.has(status) &&
|
|
954
745
|
status !== "needs-human-decision") {
|
|
955
746
|
problems.push(`${id}: round ${round} reached limit ${maxRounds} without convergence; escalate to needs-human-decision`);
|
|
956
747
|
}
|
|
957
|
-
if (!
|
|
748
|
+
if (!LEDGER_TERMINAL.has(status)) {
|
|
958
749
|
problems.push(`${id}: unresolved (status '${status}')`);
|
|
959
750
|
}
|
|
960
751
|
}
|
|
@@ -964,7 +755,7 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
964
755
|
const scopeLabel = stageScope ? ` for stages [${stageScope.join(", ")}]` : "";
|
|
965
756
|
return passResult("review-ledger", `Disagreement ledger clean (${inScopeCount} in-scope entries terminal${scopeLabel})`);
|
|
966
757
|
}
|
|
967
|
-
function checkManualValidation({ taskDir }) {
|
|
758
|
+
async function checkManualValidation({ taskDir, repositoryRoot }) {
|
|
968
759
|
// 1. Latest review-code artifact; none -> pass (no review, check not applicable).
|
|
969
760
|
const review = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
970
761
|
if (!review.ok) {
|
|
@@ -995,10 +786,23 @@ function checkManualValidation({ taskDir }) {
|
|
|
995
786
|
const task = loadTask(taskDir);
|
|
996
787
|
if (!task.ok)
|
|
997
788
|
return failResult("manual-validation", task.message);
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
789
|
+
const completion = readManualValidationCompletion(taskDir, {
|
|
790
|
+
taskId: task.metadata.id,
|
|
791
|
+
artifact: artifactName
|
|
792
|
+
});
|
|
793
|
+
if (!completion.ok) {
|
|
794
|
+
const source = completion.error.code.startsWith('MANUAL_VALIDATION_RECEIPT_') ? 'receipt' : 'transaction';
|
|
795
|
+
return failResult("manual-validation", `Manual validation ${source} is unavailable: ${completion.error.message}`);
|
|
796
|
+
}
|
|
797
|
+
const { receipt, transaction } = completion.value;
|
|
798
|
+
if (transaction.phase !== "committed" || !transaction.eventAppended || !transaction.postWriteVerified) {
|
|
799
|
+
return failResult("manual-validation", "Manual validation transaction has not reached the committed post-write-verified state");
|
|
800
|
+
}
|
|
801
|
+
if (transaction.pendingSummaryDigest !== receipt.pendingSummaryDigest || transaction.finalSummaryDigest !== receipt.finalSummaryDigest) {
|
|
802
|
+
return failResult("manual-validation", "Manual validation summary digests do not match the committed receipt");
|
|
803
|
+
}
|
|
804
|
+
if (sha256File(resolved.path) !== receipt.artifactSha256) {
|
|
805
|
+
return failResult("manual-validation", `Manual validation artifact digest does not match the receipt for ${artifactName}`);
|
|
1002
806
|
}
|
|
1003
807
|
// 6. Timing correlation (PL-3 fix, PL-4 corrected): the completion entry must
|
|
1004
808
|
// sit AFTER the latest review-code round's completion entry in the
|
|
@@ -1007,21 +811,39 @@ function checkManualValidation({ taskDir }) {
|
|
|
1007
811
|
// which indexOf never matches). Not reusing completed.exec().index: exec
|
|
1008
812
|
// anchors to the first '**Complete Manual Validation**' heading, which can
|
|
1009
813
|
// belong to an earlier round's entry and misorder the standard path.
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
if (
|
|
1020
|
-
return failResult("manual-validation", `Latest review-code (round ${review.round})
|
|
1021
|
-
|
|
1022
|
-
|
|
814
|
+
const inspectedLog = inspectActivityLog(task.content);
|
|
815
|
+
if (!inspectedLog.section)
|
|
816
|
+
return failResult("manual-validation", "Activity Log is missing or invalid");
|
|
817
|
+
const reviewDoneIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === `Review Code (Round ${review.round})`);
|
|
818
|
+
const completedIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === "Complete Manual Validation"
|
|
819
|
+
&& entry.note.includes(`Manual validation passed → ${artifactName};`)
|
|
820
|
+
&& entry.note.includes(`transaction=${receipt.transactionId};`)
|
|
821
|
+
&& entry.note.includes(`receipt=${receipt.receiptDigest};`)
|
|
822
|
+
&& entry.note.includes(`head=${receipt.prHeadSha}`));
|
|
823
|
+
if (reviewDoneIndex === -1)
|
|
824
|
+
return failResult("manual-validation", `Latest review-code (round ${review.round}) completion entry is missing from the Activity Log`);
|
|
825
|
+
if (completedIndex === -1)
|
|
826
|
+
return failResult("manual-validation", `Committed manual validation completion is not recorded for ${artifactName}`);
|
|
827
|
+
if (completedIndex < reviewDoneIndex)
|
|
828
|
+
return failResult("manual-validation", `Latest review-code (round ${review.round}) came after the manual validation completion recorded for ${artifactName}`);
|
|
829
|
+
const fact = readPrDeliveryFact(task.metadata);
|
|
830
|
+
if (fact.status === "invalid")
|
|
831
|
+
return failResult("manual-validation", fact.error.message);
|
|
832
|
+
if (fact.status === "valid" && fact.fact.state === "bound") {
|
|
833
|
+
const inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
|
|
834
|
+
if (!inspected.pullRequest)
|
|
835
|
+
return failResult("manual-validation", inspected.error?.message ?? "canonical pull-request head is unavailable");
|
|
836
|
+
if (inspected.pullRequest.head.sha !== receipt.prHeadSha)
|
|
837
|
+
return failResult("manual-validation", "manual validation receipt is stale for the current pull-request head");
|
|
838
|
+
const summary = await summaryCommentState(task.metadata.id, { cwd: repositoryRoot });
|
|
839
|
+
if (!summary.comment)
|
|
840
|
+
return failResult("manual-validation", "canonical pull-request summary comment is unavailable");
|
|
841
|
+
if (!manualValidationFinalSummaryProjectionMatches(summary.comment.body, receipt))
|
|
842
|
+
return failResult("manual-validation", "canonical pull-request summary does not match the committed manual validation projection");
|
|
843
|
+
}
|
|
844
|
+
return passResult("manual-validation", `Manual validation completed → ${artifactName} (committed receipt and post-write verification)`);
|
|
1023
845
|
}
|
|
1024
|
-
async function checkPostReviewCommit({ taskDir, config }) {
|
|
846
|
+
async function checkPostReviewCommit({ taskDir, config, repositoryRoot }) {
|
|
1025
847
|
const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
1026
848
|
if (!reviewArtifact.ok) {
|
|
1027
849
|
if (reviewArtifact.error)
|
|
@@ -1043,7 +865,7 @@ async function checkPostReviewCommit({ taskDir, config }) {
|
|
|
1043
865
|
return failResult("post-review-commit", factRead.error.message);
|
|
1044
866
|
const fact = factRead.status === 'valid' ? factRead.fact : null;
|
|
1045
867
|
const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
|
|
1046
|
-
const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(
|
|
868
|
+
const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repositoryRoot));
|
|
1047
869
|
const hasPullRequest = fact?.state === 'bound';
|
|
1048
870
|
let inspected = null;
|
|
1049
871
|
if (hasPullRequest) {
|
|
@@ -1189,7 +1011,7 @@ function resolvePostReviewExemption(content) {
|
|
|
1189
1011
|
};
|
|
1190
1012
|
}
|
|
1191
1013
|
}
|
|
1192
|
-
function checkReviewFact({ taskDir, artifactFile }) {
|
|
1014
|
+
function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
1193
1015
|
const resolvedArtifact = resolveArtifactPath(taskDir, "review-code.md|review-code-r{N}.md", artifactFile);
|
|
1194
1016
|
if (!resolvedArtifact.ok) {
|
|
1195
1017
|
return failResult("review-fact", resolvedArtifact.message);
|
|
@@ -1255,7 +1077,7 @@ function checkReviewFact({ taskDir, artifactFile }) {
|
|
|
1255
1077
|
mode: "worktree",
|
|
1256
1078
|
baseline,
|
|
1257
1079
|
diffBase,
|
|
1258
|
-
globs: resolvePostReviewGlobs({}, loadPostReviewConfig(
|
|
1080
|
+
globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
|
|
1259
1081
|
});
|
|
1260
1082
|
}
|
|
1261
1083
|
catch {
|
|
@@ -1342,40 +1164,12 @@ function resolveArtifactPath(taskDir, filePattern, artifactFile) {
|
|
|
1342
1164
|
return { ok: true, path: path.join(taskDir, matches[0].fileName) };
|
|
1343
1165
|
}
|
|
1344
1166
|
function getSectionContent(content, names) {
|
|
1345
|
-
const
|
|
1346
|
-
function visibleHeadings() {
|
|
1347
|
-
const headings = [];
|
|
1348
|
-
let fence = null;
|
|
1349
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
1350
|
-
const line = lines[index];
|
|
1351
|
-
if (fence) {
|
|
1352
|
-
const closer = line.match(/^ {0,3}(`+|~+)\s*$/);
|
|
1353
|
-
if (closer && closer[1][0] === fence.character && closer[1].length >= fence.length) {
|
|
1354
|
-
fence = null;
|
|
1355
|
-
}
|
|
1356
|
-
continue;
|
|
1357
|
-
}
|
|
1358
|
-
const opener = line.match(/^ {0,3}(`{3,}|~{3,})(?:[^`~].*)?$/);
|
|
1359
|
-
if (opener) {
|
|
1360
|
-
fence = { character: opener[1][0], length: opener[1].length };
|
|
1361
|
-
continue;
|
|
1362
|
-
}
|
|
1363
|
-
if (line.startsWith("## ")) {
|
|
1364
|
-
headings.push({ index, text: line.trim() });
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
return headings;
|
|
1368
|
-
}
|
|
1369
|
-
const headings = visibleHeadings();
|
|
1167
|
+
const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
|
|
1370
1168
|
for (const name of names) {
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
if (position === -1) {
|
|
1169
|
+
const position = headings.findIndex((heading) => heading.text === name);
|
|
1170
|
+
if (position === -1)
|
|
1374
1171
|
continue;
|
|
1375
|
-
|
|
1376
|
-
const startIndex = headings[position].index;
|
|
1377
|
-
const endIndex = headings[position + 1]?.index ?? lines.length;
|
|
1378
|
-
return lines.slice(startIndex + 1, endIndex).join("\n").trim();
|
|
1172
|
+
return content.slice(headings[position].end + 1, headings[position + 1]?.start ?? content.length).replace(/\r\n/g, "\n").trim();
|
|
1379
1173
|
}
|
|
1380
1174
|
return "";
|
|
1381
1175
|
}
|
|
@@ -1479,9 +1273,8 @@ function isBlank(value) {
|
|
|
1479
1273
|
return value === undefined || value === null || String(value).trim() === "";
|
|
1480
1274
|
}
|
|
1481
1275
|
async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }) {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
else {
|
|
1276
|
+
let repoRoot = repositoryRoot ? path.resolve(repositoryRoot) : defaultRepoRoot;
|
|
1277
|
+
if (!repositoryRoot) {
|
|
1485
1278
|
let cursor = path.resolve(taskDir);
|
|
1486
1279
|
while (path.dirname(cursor) !== cursor && !fs.existsSync(path.join(cursor, ".agents")))
|
|
1487
1280
|
cursor = path.dirname(cursor);
|