@fitlab-ai/agent-infra 0.9.5 → 0.9.7
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/dist/bin/internal-cli.js +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
package/lib/task/sections.ts
CHANGED
|
@@ -73,7 +73,7 @@ class DocumentMutationError extends Error {
|
|
|
73
73
|
|
|
74
74
|
function parseTable(
|
|
75
75
|
content: string,
|
|
76
|
-
input: { sectionAliases: readonly string[]; columns: readonly string[] }
|
|
76
|
+
input: { sectionAliases: readonly string[]; columns: readonly string[]; keyColumn?: string }
|
|
77
77
|
): ParsedTable | null {
|
|
78
78
|
validateAliases(input.sectionAliases, 'table section aliases');
|
|
79
79
|
if (
|
|
@@ -83,6 +83,10 @@ function parseTable(
|
|
|
83
83
|
) {
|
|
84
84
|
throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
|
|
85
85
|
}
|
|
86
|
+
const keyColumn = input.keyColumn ?? input.columns[0]!;
|
|
87
|
+
if (!input.columns.includes(keyColumn)) {
|
|
88
|
+
throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
|
|
89
|
+
}
|
|
86
90
|
const sections = matchingSections(content, input.sectionAliases);
|
|
87
91
|
if (sections.length === 0) return null;
|
|
88
92
|
if (sections.length > 1) {
|
|
@@ -125,7 +129,7 @@ function parseTable(
|
|
|
125
129
|
column,
|
|
126
130
|
decodeCell(parsed.cells[columnIndex]!).trim()
|
|
127
131
|
]));
|
|
128
|
-
const key = values[
|
|
132
|
+
const key = values[keyColumn]!;
|
|
129
133
|
if (seen.has(key)) {
|
|
130
134
|
throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
|
|
131
135
|
}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
|
|
6
6
|
import { getProcessStartTime, processIdentityMatches } from '../server/process-state.ts';
|
|
7
|
+
import type { ProcessIdentity } from '../server/process-state.ts';
|
|
7
8
|
|
|
8
9
|
type TaskExecutionLockErrorCode =
|
|
9
10
|
| 'ORCHESTRATION_LOCK_BUSY'
|
|
@@ -11,9 +12,9 @@ type TaskExecutionLockErrorCode =
|
|
|
11
12
|
| 'ORCHESTRATION_LOCK_FAILED';
|
|
12
13
|
|
|
13
14
|
type TaskExecutionLockOwner = Readonly<{
|
|
14
|
-
version:
|
|
15
|
+
version: 2;
|
|
15
16
|
pid: number;
|
|
16
|
-
startTime:
|
|
17
|
+
startTime: number;
|
|
17
18
|
token: string;
|
|
18
19
|
owner: string;
|
|
19
20
|
canonicalRepoRoot: string;
|
|
@@ -28,8 +29,8 @@ type TaskExecutionLockOptions = Readonly<{
|
|
|
28
29
|
lockRoot?: string;
|
|
29
30
|
token?: () => string;
|
|
30
31
|
now?: () => string;
|
|
31
|
-
getStartTime?: (pid: number) =>
|
|
32
|
-
identityMatches?: (
|
|
32
|
+
getStartTime?: (pid: number) => number | null;
|
|
33
|
+
identityMatches?: (identity: ProcessIdentity) => boolean;
|
|
33
34
|
linkSync?: (existingPath: string, newPath: string) => void;
|
|
34
35
|
}>;
|
|
35
36
|
|
|
@@ -84,11 +85,12 @@ function parseOwner(raw: string, key: string): TaskExecutionLockOwner {
|
|
|
84
85
|
const value = JSON.parse(raw) as Partial<TaskExecutionLockOwner> | null;
|
|
85
86
|
if (
|
|
86
87
|
value !== null
|
|
87
|
-
&& value.version ===
|
|
88
|
+
&& value.version === 2
|
|
88
89
|
&& Number.isSafeInteger(value.pid)
|
|
89
90
|
&& (value.pid ?? 0) > 0
|
|
90
|
-
&& typeof value.startTime === '
|
|
91
|
-
&& value.startTime
|
|
91
|
+
&& typeof value.startTime === 'number'
|
|
92
|
+
&& Number.isSafeInteger(value.startTime)
|
|
93
|
+
&& value.startTime >= 0
|
|
92
94
|
&& typeof value.token === 'string'
|
|
93
95
|
&& value.token.length > 0
|
|
94
96
|
&& typeof value.owner === 'string'
|
|
@@ -164,7 +166,7 @@ function reclaimStaleLock(
|
|
|
164
166
|
function cleanStaleCandidates(
|
|
165
167
|
lockRoot: string,
|
|
166
168
|
key: string,
|
|
167
|
-
identityMatches: (
|
|
169
|
+
identityMatches: (identity: ProcessIdentity) => boolean
|
|
168
170
|
): void {
|
|
169
171
|
const prefix = `${key}.candidate.`;
|
|
170
172
|
for (const name of fs.readdirSync(lockRoot)) {
|
|
@@ -211,7 +213,7 @@ function withTaskExecutionLock<T>(
|
|
|
211
213
|
);
|
|
212
214
|
}
|
|
213
215
|
const owner: TaskExecutionLockOwner = {
|
|
214
|
-
version:
|
|
216
|
+
version: 2,
|
|
215
217
|
pid: process.pid,
|
|
216
218
|
startTime,
|
|
217
219
|
token,
|
|
@@ -23,6 +23,7 @@ import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.t
|
|
|
23
23
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.ts";
|
|
24
24
|
import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
|
|
25
25
|
import { parseLedger, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
|
|
26
|
+
import type { LedgerRow } from "./ledger.ts";
|
|
26
27
|
import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
|
|
27
28
|
import { loadVerificationConfig } from "./verification-config.ts";
|
|
28
29
|
import { snapshotReview } from "../git/review-snapshot.ts";
|
|
@@ -173,7 +174,7 @@ function checkOrchestrationState({ taskDir }: any): any {
|
|
|
173
174
|
if (isFailClosedLegacyPause(run)) {
|
|
174
175
|
return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
|
|
175
176
|
}
|
|
176
|
-
if (run.schemaVersion
|
|
177
|
+
if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
|
|
177
178
|
return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
|
|
178
179
|
}
|
|
179
180
|
if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
|
|
@@ -268,14 +269,29 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
268
269
|
return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
|
|
269
270
|
}
|
|
270
271
|
for (const recovery of run.recoveryHistory) {
|
|
271
|
-
|
|
272
|
-
recovery.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
272
|
+
const validModelRecovery = recovery.code === 'MODEL_POLICY_SUPPLEMENTED'
|
|
273
|
+
&& recovery.previousSchemaVersion === 1
|
|
274
|
+
&& recovery.receiptCount === 0
|
|
275
|
+
&& recovery.pendingDelegation === false
|
|
276
|
+
&& exactText(recovery.recoveredAt)
|
|
277
|
+
&& recovery.policySource;
|
|
278
|
+
const guards = recovery.guards;
|
|
279
|
+
const validCapabilityRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
|
|
280
|
+
&& recovery.previousSchemaVersion === 2
|
|
281
|
+
&& recovery.previousStatus === 'paused'
|
|
282
|
+
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
283
|
+
&& recovery.client === 'codex'
|
|
284
|
+
&& recovery.resultingStatus === 'running'
|
|
285
|
+
&& exactText(recovery.recoveredAt)
|
|
286
|
+
&& guards?.stepCount === 0
|
|
287
|
+
&& guards?.nextStage === null
|
|
288
|
+
&& guards?.baselineEmpty === true
|
|
289
|
+
&& guards?.receiptCount === 0
|
|
290
|
+
&& guards?.pendingDelegation === false
|
|
291
|
+
&& guards?.commitAuthorizationUnused === true
|
|
292
|
+
&& guards?.completionEvidenceAbsent === true
|
|
293
|
+
&& guards?.commitIntentAbsent === true;
|
|
294
|
+
if (!validModelRecovery && !validCapabilityRecovery) {
|
|
279
295
|
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
280
296
|
}
|
|
281
297
|
}
|
|
@@ -333,6 +349,25 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
333
349
|
) {
|
|
334
350
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
335
351
|
}
|
|
352
|
+
if (receipt.client === 'codex') {
|
|
353
|
+
const host = receipt.hostEvidence;
|
|
354
|
+
if (
|
|
355
|
+
host?.kind !== 'codex-lifecycle-v1'
|
|
356
|
+
|| !exactText(host.hookDefinitionHash)
|
|
357
|
+
|| !Number.isSafeInteger(host.startRevision)
|
|
358
|
+
|| host.startRevision < 1
|
|
359
|
+
) {
|
|
360
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
|
|
361
|
+
}
|
|
362
|
+
if (['sealed', 'consumed'].includes(receipt.status) && (
|
|
363
|
+
!Number.isSafeInteger(host.stopRevision)
|
|
364
|
+
|| host.stopRevision <= host.startRevision
|
|
365
|
+
|| host.consumer !== receipt.id
|
|
366
|
+
|| !exactText(host.consumedAt)
|
|
367
|
+
)) {
|
|
368
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex consumed stop evidence`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
336
371
|
}
|
|
337
372
|
}
|
|
338
373
|
if (run.receipts.some((receipt: any) => receipt.status !== 'consumed')) {
|
|
@@ -1036,11 +1071,13 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
1036
1071
|
return blockedResult("post-review-commit", relation.message, relation.code);
|
|
1037
1072
|
}
|
|
1038
1073
|
if (relation.status === "strict") {
|
|
1039
|
-
return
|
|
1040
|
-
"
|
|
1041
|
-
"Merged PR evidence did not establish squash merge equivalence"
|
|
1042
|
-
|
|
1043
|
-
|
|
1074
|
+
return resolvePostReviewIdentityFailure(task.content, {
|
|
1075
|
+
code: "PR_MERGE_IDENTITY_INVALID",
|
|
1076
|
+
message: "Merged PR evidence did not establish squash merge equivalence"
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
if (relation.code === "PR_MERGE_IDENTITY_INVALID") {
|
|
1080
|
+
return resolvePostReviewIdentityFailure(task.content, relation);
|
|
1044
1081
|
}
|
|
1045
1082
|
return failResult("post-review-commit", relation.message, relation.code);
|
|
1046
1083
|
}
|
|
@@ -1110,11 +1147,11 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
1110
1147
|
}
|
|
1111
1148
|
|
|
1112
1149
|
const exemption = resolvePostReviewExemption(task.content);
|
|
1113
|
-
if (!exemption.ok) return failResult("post-review-commit", exemption.message);
|
|
1114
|
-
if (exemption.
|
|
1150
|
+
if (!exemption.ok) return failResult("post-review-commit", exemption.message, exemption.code);
|
|
1151
|
+
if (exemption.exemptions.length > 0) {
|
|
1115
1152
|
return passResult(
|
|
1116
1153
|
"post-review-commit",
|
|
1117
|
-
`${commits.length} post-review commit(s) covered by a human-decided exemption`
|
|
1154
|
+
`${commits.length} post-review commit(s) covered by a human-decided exemption: ${formatPostReviewExemptions(exemption.exemptions)}`
|
|
1118
1155
|
);
|
|
1119
1156
|
}
|
|
1120
1157
|
|
|
@@ -1124,18 +1161,50 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
1124
1161
|
);
|
|
1125
1162
|
}
|
|
1126
1163
|
|
|
1164
|
+
function resolvePostReviewIdentityFailure(
|
|
1165
|
+
content: string,
|
|
1166
|
+
failure: { code: string; message: string }
|
|
1167
|
+
): any {
|
|
1168
|
+
const exemption = resolvePostReviewExemption(content);
|
|
1169
|
+
if (!exemption.ok) {
|
|
1170
|
+
return failResult("post-review-commit", exemption.message, exemption.code);
|
|
1171
|
+
}
|
|
1172
|
+
if (exemption.exemptions.length === 0) {
|
|
1173
|
+
return failResult("post-review-commit", failure.message, failure.code);
|
|
1174
|
+
}
|
|
1175
|
+
return passResult(
|
|
1176
|
+
"post-review-commit",
|
|
1177
|
+
`Human-decided post-review exemption overrode ${failure.code}: ${failure.message}; ${formatPostReviewExemptions(exemption.exemptions)}`
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function formatPostReviewExemptions(exemptions: readonly LedgerRow[]): string {
|
|
1182
|
+
return exemptions.map((row) => `${row.id}: ${row.evidence}`).join("; ");
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1127
1185
|
function resolvePostReviewExemption(content: string):
|
|
1128
|
-
| { ok: true;
|
|
1129
|
-
| { ok: false; message: string } {
|
|
1186
|
+
| { ok: true; exemptions: readonly LedgerRow[] }
|
|
1187
|
+
| { ok: false; code: "POST_REVIEW_EXEMPTION_INVALID"; message: string } {
|
|
1130
1188
|
try {
|
|
1189
|
+
const rows = parseLedger(content);
|
|
1190
|
+
const invalid = validateLedgerRows(rows);
|
|
1191
|
+
if (invalid) {
|
|
1192
|
+
return {
|
|
1193
|
+
ok: false,
|
|
1194
|
+
code: "POST_REVIEW_EXEMPTION_INVALID",
|
|
1195
|
+
message: `${invalid.code}: ${invalid.message}`
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1131
1198
|
return {
|
|
1132
1199
|
ok: true,
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1200
|
+
exemptions: rows.filter((row) => row.stage === POST_REVIEW_COMMIT_STAGE)
|
|
1201
|
+
};
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
return {
|
|
1204
|
+
ok: false,
|
|
1205
|
+
code: "POST_REVIEW_EXEMPTION_INVALID",
|
|
1206
|
+
message: `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`
|
|
1136
1207
|
};
|
|
1137
|
-
} catch {
|
|
1138
|
-
return { ok: false, message: "Invalid disagreement ledger" };
|
|
1139
1208
|
}
|
|
1140
1209
|
}
|
|
1141
1210
|
|
package/lib/task/verification.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { TaskWorkspaceState } from './resolve-ref.ts';
|
|
|
11
11
|
type VerificationEvent =
|
|
12
12
|
| 'analyze.awaiting-input' | 'analyze.completed'
|
|
13
13
|
| 'review-analysis.completed' | 'plan.completed' | 'review-plan.completed'
|
|
14
|
-
| 'code.completed' | 'review-code.completed' | 'manual-validation.completed'
|
|
14
|
+
| 'code.completed' | 'review-code.completed' | 'manual-validation.completed' | 'validation-run.completed'
|
|
15
15
|
| 'block-task.completed' | 'cancel-task.completed' | 'commit.completed'
|
|
16
16
|
| 'complete-task.preflight' | 'complete-task.completed'
|
|
17
17
|
| 'create-pr.completed' | 'create-task.completed'
|
|
@@ -62,6 +62,7 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
|
|
|
62
62
|
'code.completed': gate('code-task', 'active', 'code'),
|
|
63
63
|
'review-code.completed': gate('review-code', 'active', 'review-code'),
|
|
64
64
|
'manual-validation.completed': gate('complete-manual-validation', 'active', 'manual-validation'),
|
|
65
|
+
'validation-run.completed': gate('run-manual-validation', 'active', 'validation-run'),
|
|
65
66
|
'block-task.completed': gate('block-task', 'blocked'),
|
|
66
67
|
'cancel-task.completed': gate('cancel-task', 'completed'),
|
|
67
68
|
'commit.completed': gate('commit', 'active'),
|
package/package.json
CHANGED
|
@@ -47,6 +47,13 @@ This dual-config approach ensures every AI tool receives appropriate project con
|
|
|
47
47
|
settings.json # Claude settings
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
The whole `.agents/workspace/` tree is runtime-only and git-ignored. `active/`
|
|
51
|
+
contains current tasks, `blocked/` contains paused tasks, `completed/` contains
|
|
52
|
+
finished tasks, and `archive/` stores archived history. Runtime logs and process
|
|
53
|
+
evidence may also be created below this root. Do not place content here that must
|
|
54
|
+
be shared through Git; collaboration rules, templates, and workflows belong in
|
|
55
|
+
the version-controlled `.agents/` tree.
|
|
56
|
+
|
|
50
57
|
## Collaboration Model
|
|
51
58
|
|
|
52
59
|
The multi-AI collaboration follows a structured workflow:
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
settings.json # Claude 设置
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
整个 `.agents/workspace/` 目录树都只保存运行时数据,并被 git ignore。`active/`
|
|
51
|
+
保存当前任务,`blocked/` 保存暂停任务,`completed/` 保存已完成任务,`archive/`
|
|
52
|
+
保存归档历史;运行时日志和过程证据也可能出现在该根目录下。不要在这里存放必须
|
|
53
|
+
通过 Git 共享的内容;协作规则、模板和工作流应放在受版本控制的 `.agents/` 目录中。
|
|
54
|
+
|
|
50
55
|
## 协作模型
|
|
51
56
|
|
|
52
57
|
多 AI 协作遵循结构化工作流:
|
|
@@ -5,6 +5,8 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
7
|
const MAX_INPUT_BYTES = 64 * 1024;
|
|
8
|
+
const repoPackageJson = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
9
|
+
const sourceInternalCli = fileURLToPath(new URL('../../bin/internal-cli.ts', import.meta.url));
|
|
8
10
|
const localInternalCli = fileURLToPath(new URL('../../dist/bin/internal-cli.js', import.meta.url));
|
|
9
11
|
const codexHooks = fileURLToPath(new URL('../../.codex/hooks.json', import.meta.url));
|
|
10
12
|
const clientIndex = process.argv.indexOf('--client');
|
|
@@ -13,6 +15,49 @@ if (client !== 'claude-code' && client !== 'codex') {
|
|
|
13
15
|
process.stderr.write('Lifecycle delegation hook requires a supported --client value\n');
|
|
14
16
|
process.exit(1);
|
|
15
17
|
}
|
|
18
|
+
|
|
19
|
+
function internalCliInvocation(args) {
|
|
20
|
+
let isAgentInfraSourceCheckout = false;
|
|
21
|
+
try {
|
|
22
|
+
isAgentInfraSourceCheckout = JSON.parse(readFileSync(repoPackageJson, 'utf8')).name === '@fitlab-ai/agent-infra';
|
|
23
|
+
} catch {
|
|
24
|
+
// Installed project hooks do not require a repository package.json.
|
|
25
|
+
}
|
|
26
|
+
if (isAgentInfraSourceCheckout && existsSync(sourceInternalCli)) {
|
|
27
|
+
return {
|
|
28
|
+
command: process.execPath,
|
|
29
|
+
commandArgs: ['--experimental-strip-types', sourceInternalCli, ...args],
|
|
30
|
+
shell: false
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (existsSync(localInternalCli)) {
|
|
34
|
+
return { command: process.execPath, commandArgs: [localInternalCli, ...args], shell: false };
|
|
35
|
+
}
|
|
36
|
+
return { command: 'agent-infra-internal', commandArgs: args, shell: process.platform === 'win32' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function findCapabilityToken(value, depth = 0) {
|
|
40
|
+
if (depth > 5 || value == null) return '';
|
|
41
|
+
if (typeof value === 'string') {
|
|
42
|
+
const match = /agent-infra-codex-capability:([A-Za-z0-9_-]{20,})/.exec(value);
|
|
43
|
+
return match?.[1] || '';
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
for (const entry of value) {
|
|
47
|
+
const found = findCapabilityToken(entry, depth + 1);
|
|
48
|
+
if (found) return found;
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === 'object') {
|
|
53
|
+
for (const entry of Object.values(value)) {
|
|
54
|
+
const found = findCapabilityToken(entry, depth + 1);
|
|
55
|
+
if (found) return found;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
|
|
16
61
|
let input = '';
|
|
17
62
|
process.stdin.setEncoding('utf8');
|
|
18
63
|
process.stdin.on('data', (chunk) => {
|
|
@@ -34,15 +79,29 @@ process.stdin.on('end', () => {
|
|
|
34
79
|
const explicitPhase = phaseIndex >= 0 ? process.argv[phaseIndex + 1] : '';
|
|
35
80
|
const hook = String(explicitPhase || event.hook_event_name || event.event || '').toLowerCase();
|
|
36
81
|
const toolInput = event.tool_input && typeof event.tool_input === 'object' ? event.tool_input : {};
|
|
82
|
+
const toolName = String(event.tool_name || event.tool || '').toLowerCase();
|
|
83
|
+
let toolResponse = {};
|
|
84
|
+
try {
|
|
85
|
+
toolResponse = typeof event.tool_response === 'string'
|
|
86
|
+
? JSON.parse(event.tool_response)
|
|
87
|
+
: (event.tool_response || {});
|
|
88
|
+
} catch {
|
|
89
|
+
// Core ignores opaque tool responses; only the host timeout bit is forwarded.
|
|
90
|
+
}
|
|
37
91
|
const nativeAgent = event.agent_name || event.agent_type || event.agent?.name
|
|
38
92
|
|| toolInput.agent_type || toolInput.agent || toolInput.name;
|
|
39
|
-
if (!String(nativeAgent || '').startsWith('agent-infra-lifecycle-')) process.exit(0);
|
|
40
93
|
if (client === 'codex') {
|
|
41
94
|
const phases = new Set(['pre-tool', 'subagent-start', 'post-tool', 'subagent-stop']);
|
|
42
95
|
if (!phases.has(hook)) {
|
|
43
96
|
process.stderr.write('Managed Codex lifecycle hook event has an unknown type\n');
|
|
44
97
|
process.exit(1);
|
|
45
98
|
}
|
|
99
|
+
const managedAgent = /^agent-infra-lifecycle-(executor|reviewer)$/.test(String(nativeAgent || ''));
|
|
100
|
+
const capabilityToken = hook === 'post-tool' ? findCapabilityToken(toolResponse) : '';
|
|
101
|
+
const parentReconcile = hook === 'post-tool'
|
|
102
|
+
&& toolName === 'collaborationwait_agent'
|
|
103
|
+
&& toolResponse.timed_out !== true;
|
|
104
|
+
if (!managedAgent && !parentReconcile && !capabilityToken) process.exit(0);
|
|
46
105
|
const hookDefinitionHash = createHash('sha256').update(readFileSync(codexHooks)).digest('hex');
|
|
47
106
|
const normalized = {
|
|
48
107
|
sessionId: String(event.session_id || ''),
|
|
@@ -52,19 +111,21 @@ process.stdin.on('end', () => {
|
|
|
52
111
|
nativeAgent: String(nativeAgent),
|
|
53
112
|
requestedModel: String(toolInput.model || ''),
|
|
54
113
|
requestedReasoningEffort: String(toolInput.reasoning_effort || toolInput.reasoningEffort || ''),
|
|
55
|
-
hookDefinitionHash
|
|
114
|
+
hookDefinitionHash,
|
|
115
|
+
toolName,
|
|
116
|
+
taskName: String(toolInput.task_name || ''),
|
|
117
|
+
transcriptPath: String(event.transcript_path || '')
|
|
56
118
|
};
|
|
57
|
-
|
|
119
|
+
if (capabilityToken) normalized.capabilityToken = capabilityToken;
|
|
120
|
+
const args = ['codex-lifecycle', 'hook-event', '--event', hook, '--bridge', 'true'];
|
|
58
121
|
try {
|
|
59
|
-
const
|
|
60
|
-
const command = useLocal ? process.execPath : 'agent-infra-internal';
|
|
61
|
-
const commandArgs = useLocal ? [localInternalCli, ...args] : args;
|
|
122
|
+
const { command, commandArgs, shell } = internalCliInvocation(args);
|
|
62
123
|
const output = execFileSync(command, commandArgs, {
|
|
63
124
|
encoding: 'utf8',
|
|
64
125
|
input: JSON.stringify(normalized),
|
|
65
|
-
shell
|
|
126
|
+
shell,
|
|
66
127
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
-
timeout:
|
|
128
|
+
timeout: 15_000
|
|
68
129
|
});
|
|
69
130
|
process.stdout.write(output);
|
|
70
131
|
} catch (error) {
|
|
@@ -74,6 +135,7 @@ process.stdin.on('end', () => {
|
|
|
74
135
|
}
|
|
75
136
|
return;
|
|
76
137
|
}
|
|
138
|
+
if (!/^agent-infra-lifecycle-(executor|reviewer)$/.test(String(nativeAgent || ''))) process.exit(0);
|
|
77
139
|
const start = hook.includes('start');
|
|
78
140
|
const stop = hook.includes('stop');
|
|
79
141
|
if (!start && !stop) {
|
|
@@ -102,12 +164,10 @@ process.stdin.on('end', () => {
|
|
|
102
164
|
);
|
|
103
165
|
}
|
|
104
166
|
try {
|
|
105
|
-
const
|
|
106
|
-
const command = useLocal ? process.execPath : 'agent-infra-internal';
|
|
107
|
-
const commandArgs = useLocal ? [localInternalCli, ...args] : args;
|
|
167
|
+
const { command, commandArgs, shell } = internalCliInvocation(args);
|
|
108
168
|
const output = execFileSync(command, commandArgs, {
|
|
109
169
|
encoding: 'utf8',
|
|
110
|
-
shell
|
|
170
|
+
shell,
|
|
111
171
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
112
172
|
});
|
|
113
173
|
process.stdout.write(output);
|
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
- The orchestrator only routes and delegates. Stage skills remain the single source of truth for behavior and artifacts.
|
|
6
6
|
- Every stage and rework round uses a fresh executor. Every review uses a fresh reviewer; follow-up reuse is forbidden.
|
|
7
7
|
- A reviewer may only write its review artifact and core-generated task metadata. Business-code, HEAD, or index changes invalidate the receipt.
|
|
8
|
-
- An active run has one pending delegation. Missing, mismatched, forked, replayed, hook-less, or drifted evidence pauses the run.
|
|
8
|
+
- An active run has one pending delegation. The child must pass the activation barrier before any stage side effect. Missing, late, mismatched, forked, replayed, hook-less, or drifted evidence pauses the run.
|
|
9
9
|
- The first release ends after one existing safely gated `commit`; it does not create a PR, monitor checks, or complete the task.
|
|
10
10
|
|
|
11
11
|
## Recovery
|
|
12
12
|
|
|
13
|
-
`orchestration.json` is the detailed state source. A
|
|
13
|
+
`orchestration.json` is the detailed state source. A v3 run persists complete policy, append-only recovery, build/contract/hook-source/controller provenance, and activation monotonic timestamps. Historical v2 state is read-compatible, but new Codex delegations require v3. An expired prepared orphan may be explicitly recovered only with the exact task fingerprint, no commit intent or consumed authorization, and no matching unconsumed active lifecycle evidence. Unknown schemas and incomplete active v3 state fail closed.
|
|
14
|
+
|
|
15
|
+
## Codex Host and Capability
|
|
16
|
+
|
|
17
|
+
- Direct-host accepts only trusted project or managed lifecycle hooks. A task-bound sandbox requires a controlled nested controller and isolated `CODEX_HOME`; only user hooks bound to that controller context are accepted. Ordinary user/plugin hooks are not evidence.
|
|
18
|
+
- The controller starts a nested loop only after control generation, task binding, Codex version, closed bundle, build/contract manifest, profiles, and hook discovery pass. Both bypass flags are restricted to this task-bound launch path.
|
|
19
|
+
- Every prepare arms a one-use capability attested by the current loop's real PostToolUse. Atomic consumption binds task/session/build/controller and retains only a redacted tombstone.
|
|
14
20
|
|
|
15
21
|
## Model Policy
|
|
16
22
|
|
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
- 总控只路由和委派;阶段技能仍是业务规则与产物格式的单一事实源。
|
|
6
6
|
- 每个阶段和每轮返工都创建 fresh executor;每轮审查创建 fresh reviewer,禁止 follow-up 复用。
|
|
7
7
|
- reviewer 只能写当前审查产物和核心生成的任务元数据。业务代码、HEAD 或暂存区变化会使 receipt 失效。
|
|
8
|
-
- active run 中只有一个 pending delegation
|
|
8
|
+
- active run 中只有一个 pending delegation。child 必须在任何阶段副作用前通过 activation barrier;缺失、超时、错配、fork、重放、hook 不可用或工作区漂移一律暂停。
|
|
9
9
|
- 首版成功终点是一次通过既有安全门禁的 `commit`;不创建 PR、不监控 checks、不执行 `complete-task`。
|
|
10
10
|
|
|
11
11
|
## 恢复语义
|
|
12
12
|
|
|
13
|
-
`orchestration.json` 是详细状态源。
|
|
13
|
+
`orchestration.json` 是详细状态源。v3 run 保存完整策略、append-only 恢复历史、build/contract/hook source/controller provenance 与 activation 单调时钟。只有无 pending delegation 且零历史 receipt 的 v1 run 可在补齐完整策略后升级;历史 v2 状态仅兼容读取,新的 Codex delegation 必须写 v3。prepared orphan 只允许在 deadline 已过、task fingerprint 精确不变、无 commit intent/authorization 消耗且无匹配的未消费 active lifecycle evidence 时显式恢复。迁移是 forward-only,未知 schema 或缺 provenance 的 active v3 一律失败关闭。
|
|
14
|
+
|
|
15
|
+
## Codex 宿主与 capability
|
|
16
|
+
|
|
17
|
+
- direct-host 只接受 trusted project 或 managed lifecycle hooks。task-bound sandbox 必须由受控 nested controller 建立隔离 `CODEX_HOME`,仅接受绑定 controller context 的 user hooks;普通 user/plugin hook 不得成为证据源。
|
|
18
|
+
- controller 只有在 control generation、task binding、Codex 版本、closed bundle、build/contract manifest、profile 与 hook discovery 全部通过后才能启动 nested loop。两个 bypass 参数只允许出现在该 task-bound 启动路径。
|
|
19
|
+
- 每次 prepare 前必须 arm 一次性 capability,并由同一当前 loop 的真实 PostToolUse attestation;token 原子消费后保留去敏 tombstone,不能重放或跨 task/session/build/controller 使用。
|
|
14
20
|
|
|
15
21
|
## 模型策略
|
|
16
22
|
|
|
@@ -47,6 +47,7 @@ SKILLs currently covered by this exemption:
|
|
|
47
47
|
|
|
48
48
|
- `release`: may confirm publish after presenting the latest release snapshot
|
|
49
49
|
- `create-release-note`: may confirm publish after presenting exact staged notes and their digest
|
|
50
|
+
- `post-release`: may confirm a normal push after preparing the local post commit and presenting the complete `postConfirmation` and digest
|
|
50
51
|
|
|
51
52
|
### Exemption 5: Complete model policy missing at lifecycle start
|
|
52
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GitHub PR Readiness Intents
|
|
2
2
|
|
|
3
|
-
The GitHub adapter requires `gh >= 2.
|
|
3
|
+
The GitHub adapter requires `gh >= 2.72.0`. Platform context checks this dependency before any GitHub API, PR, or checks operation. Projects that do not select GitHub neither probe nor depend on `gh`.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
agent-infra-internal platform-checks inspect {task-id}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
PR 全部 checks 的获取、轮询 deadline、失败 run/job 定位和日志获取统一由 typed internal intent 执行。SKILL/模型只负责失败分类、根因分析、最小修复和授权后的提交推送。
|
|
4
4
|
|
|
5
|
-
GitHub adapter 要求 `gh >= 2.
|
|
5
|
+
GitHub adapter 要求 `gh >= 2.72.0`。平台上下文会在任何 GitHub API、PR 或 checks 操作前检查该依赖;未选择 GitHub 平台时不探测也不依赖 `gh`。
|
|
6
6
|
|
|
7
7
|
## 快照与监控
|
|
8
8
|
|
|
@@ -94,7 +94,9 @@ When an executor judges an item to be a key design decision that needs human rul
|
|
|
94
94
|
- The `complete-task` `post-review-commit` gate uses only B. When B is absent, malformed, or missing as a Git object, it reports `reviewed snapshot was not anchored` and never falls back to R.
|
|
95
95
|
- If new commits touch code / rule paths after B, the gate blocks and requires a fresh `review-code`. Automatic exceptions are limited to: (1) a bound platform change request (PR/MR) is merged and its platform adapter supplies an authoritative snapshot plus remote Git evidence refs; the gate fetches the reviewed head and target branch into an isolated temporary repository and proves B is the original change-request head, its merge commit is in authoritative target history, its reviewed changes exactly match the normalized patch of a single-parent squash commit, and its protected paths have no later commits; or (2) no valid change request, exactly one protected-path commit after B, that commit is a single-parent local rewrite in HEAD history, it has the same full tree or protected content as B, and no protected-path commits follow it. An unsupported adapter capability, missing required platform / Git evidence, or Git credentials that cannot read those remote refs fails closed.
|
|
96
96
|
- Required checks remain a pre-merge responsibility of branch protection / rulesets and the `review-code` / `watch-pr` routes. The post-review-commit gate still applies the applicable strict, PR-squash, or local-rewrite equivalence independently.
|
|
97
|
-
- **Exemption**:
|
|
97
|
+
- **Exemption**: the canonical row is `| PRC-1 | post-review-commit | - | - | human-decided | <ruling note> |`. The id must be `PRC-N`, round and severity must be `-`, status must be `human-decided`, and evidence must be non-empty single-line text that records the ruling reason and applicable commit scope. Any malformed post-review row fails closed.
|
|
98
|
+
- A merged-PR human exemption may override only the stable `PR_MERGE_IDENTITY_INVALID` failure. Blocked results, missing evidence, content/topology/target-history mismatches, and all other failed codes must not consume PRC. The passing result must preserve the original failure code/message and every consumed PRC id/evidence, and must not describe the result as automatic equivalence.
|
|
99
|
+
- task.md is the persistent source of truth. Before preflight, the platform summary mirrors an existing warning or, without one, states only that the ruling awaits verification. After the exemption gate passes, the caller uses the gate output to complete the original-failure evidence and updates the same summary marker in place. The summary also mirrors the ruling reason, commit scope, human identity, and time from task.md.
|
|
98
100
|
|
|
99
101
|
## Gate behavior cheat sheet
|
|
100
102
|
|
|
@@ -94,7 +94,9 @@
|
|
|
94
94
|
- `complete-task` 的 `post-review-commit` gate 只使用 B;B 缺失、畸形或对象不存在时报告 `reviewed snapshot was not anchored`,不得回退 R。
|
|
95
95
|
- 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。自动例外仅限两类:(1) 绑定的平台变更请求(PR/MR)已合并,且平台适配器提供权威快照与远端 Git 证据引用;gate 在隔离的临时仓库中抓取受审查 head 与目标分支,证明 B 是原变更请求 head、merge commit 位于权威目标分支历史中、受审查变更与单父 squash commit 的规范化补丁完全一致,并且 merge 后受保护路径没有新提交;(2) 无有效变更请求,B 后恰有一个受保护路径提交,该提交是本地 HEAD 历史中的单父重写,且它与 B 的完整 tree 相同或受保护内容相同,重写后也没有受保护路径提交。适配器不支持该能力,缺少所需平台 / Git 证据,或当前 Git 凭据不能读取这些远端 refs 时 fail closed。
|
|
96
96
|
- 全部 PR checks 由合并前的 `review-code` / `watch-pr` 路由负责,其中 required checks 仍受分支保护 / ruleset 强制。post-review-commit 仍独立执行适用的严格 / PR squash / 本地重写等价判定。
|
|
97
|
-
-
|
|
97
|
+
- **豁免**:canonical 行形状为 `| PRC-1 | post-review-commit | - | - | human-decided | <裁定说明> |`。id 必须为 `PRC-N`,round/severity 必须为 `-`,status 必须为 `human-decided`,evidence 必须为非空单行文本并记录裁定理由与适用提交范围;任何不符合形状的 post-review 行均 fail closed。
|
|
98
|
+
- merged-PR 人工豁免只可覆盖稳定的 `PR_MERGE_IDENTITY_INVALID`;blocked、证据缺失、内容/拓扑/目标历史不一致及其他 failed code 不得消费 PRC。通过消息必须保留原 failure code/message 与所消费的 PRC id/evidence,且不得表述为自动等价成功。
|
|
99
|
+
- task.md 是持久化事实源。平台摘要在 preflight 前镜像既有 warning,或在无 warning 时只声明裁决待验证;人工豁免 gate 通过后,再以 gate 输出补齐原始失败并原地更新同一 summary marker。摘要同时镜像 task.md 中的裁决理由、提交范围、人工身份与时间。
|
|
98
100
|
|
|
99
101
|
## gate 行为速查
|
|
100
102
|
|
|
@@ -10,6 +10,8 @@ agent-infra-internal task-artifact {task-id} inspect --family code
|
|
|
10
10
|
|
|
11
11
|
The core scans `plan.md` / `plan-r{N}.md`, `review-plan.md` / `review-plan-r{N}.md`, `code.md` / `code-r{N}.md`, and `review-code.md` / `review-code-r{N}.md` in the task directory.
|
|
12
12
|
|
|
13
|
+
`code.started` persists the plan identity and SHA-256 for the round in task.md frontmatter. `code.completed` must read the canonical plan from the implementation report's `Plan Input` field and match both that start context and the current latest plan; missing, mismatched, or changed input fails closed.
|
|
14
|
+
|
|
13
15
|
## Eight Branches
|
|
14
16
|
|
|
15
17
|
> Branches are evaluated top-down in this table; the first match returns and skips later rows.
|
|
@@ -17,7 +19,7 @@ The core scans `plan.md` / `plan-r{N}.md`, `review-plan.md` / `review-plan-r{N}.
|
|
|
17
19
|
| Condition | mode | exit | Behavior |
|
|
18
20
|
|---|---|---:|---|
|
|
19
21
|
| no code artifact, and the latest review-plan is exactly `Approved` and references the latest plan | `init` | 0 | initial implementation, output `code.md`; a missing matching approval or `Approved-with-issues` returns an error |
|
|
20
|
-
| latest review-plan is exactly `Approved`, references the latest plan, and
|
|
22
|
+
| latest review-plan is exactly `Approved`, references the latest plan, and the latest code completion receipt does not bind the same plan digest | `init` | 0 | enter a new implementation round. `Approved-with-issues` remains parse-compatible for history but is not cross-stage approval; missing or invalid receipts fail closed |
|
|
21
23
|
| `rev_max < code_max` | `error` | 2 | latest code round is unreviewed; run `review-code` first |
|
|
22
24
|
| latest review-code is Approved and a pending implementation input was decided after that review completed | `decision` | 0 | select the earliest `II-N` and enter decision-driven implementation; false/not-required and consumed inputs do not trigger |
|
|
23
25
|
| latest review-code is Approved with 0/0/0 | `refused` | 1 | already approved; do not run `code-task` again |
|
|
@@ -10,6 +10,8 @@ agent-infra-internal task-artifact {task-id} inspect --family code
|
|
|
10
10
|
|
|
11
11
|
核心扫描任务目录中的 `plan.md` / `plan-r{N}.md`、`review-plan.md` / `review-plan-r{N}.md`、`code.md` / `code-r{N}.md` 和 `review-code.md` / `review-code-r{N}.md`。
|
|
12
12
|
|
|
13
|
+
`code.started` 会在 task.md frontmatter 中绑定本轮方案文件及其 SHA-256。`code.completed` 必须从实现报告的“方案输入”/“Plan Input”字段读取 canonical plan,并同时匹配该开始上下文和当前最新 plan;缺失、身份不一致或内容变化均失败关闭。
|
|
14
|
+
|
|
13
15
|
## 8 个分支
|
|
14
16
|
|
|
15
17
|
> 分支按表中自上而下的顺序评估,命中即返回;后续分支不再判定。
|
|
@@ -17,7 +19,7 @@ agent-infra-internal task-artifact {task-id} inspect --family code
|
|
|
17
19
|
| 条件 | mode | exit | 行为 |
|
|
18
20
|
|---|---|---:|---|
|
|
19
21
|
| 无 code 产物,且最新 review-plan 精确为 `Approved` 并引用最新 plan | `init` | 0 | 初次实现,产物为 `code.md`;缺少匹配审批或为 `Approved-with-issues` 时返回 error |
|
|
20
|
-
| 最新 review-plan 精确为 `Approved`,且其「审查输入」/「Review Input」字段引用最新 plan
|
|
22
|
+
| 最新 review-plan 精确为 `Approved`,且其「审查输入」/「Review Input」字段引用最新 plan,而最新 code 的完成 receipt 未绑定相同 plan 摘要 | `init` | 0 | plan 内容在 code 输入之后获批;进入新一轮实现。`Approved-with-issues` 仅保留历史解析兼容,不构成跨阶段批准;缺失或无效 receipt 失败关闭 |
|
|
21
23
|
| `rev_max < code_max` | `error` | 2 | 最新代码未审查,先运行 `review-code` |
|
|
22
24
|
| 最新 review-code 为 Approved 且存在审查完成后产生的 pending 实现输入 | `decision` | 0 | 选择最早 `II-N`,进入裁决驱动实现;false/not-required 与 consumed 输入不触发 |
|
|
23
25
|
| 最新 review-code 为 Approved 且 0/0/0 | `refused` | 1 | 已通过,无需再次运行 `code-task` |
|