@fitlab-ai/agent-infra 0.9.11 → 0.9.12

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.
Files changed (193) hide show
  1. package/README.md +6 -6
  2. package/README.zh-CN.md +6 -6
  3. package/dist/bin/internal-cli.js +5 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
  5. package/dist/lib/defaults.json +4 -0
  6. package/dist/lib/git/workflow.js +43 -1
  7. package/dist/lib/init.js +1 -0
  8. package/dist/lib/internal/git-workflow.js +8 -1
  9. package/dist/lib/internal/platform-pr.js +8 -2
  10. package/dist/lib/internal/task-delivery.js +59 -0
  11. package/dist/lib/platform/github-client.js +7 -1
  12. package/dist/lib/platform/pr-checks.js +8 -5
  13. package/dist/lib/platform/pr-summary.js +12 -8
  14. package/dist/lib/platform/pull-requests.js +393 -91
  15. package/dist/lib/platform/verification-required.js +13 -2
  16. package/dist/lib/platform/verification-sync.js +9 -4
  17. package/dist/lib/pr-review/evidence-grading.js +12 -4
  18. package/dist/lib/sandbox/config.js +5 -0
  19. package/dist/lib/sandbox/control/lifecycle.js +29 -7
  20. package/dist/lib/server/process-state.js +2 -2
  21. package/dist/lib/task/artifact-lifecycle.js +2 -2
  22. package/dist/lib/task/commit-intent.js +100 -0
  23. package/dist/lib/task/commit-operation.js +195 -101
  24. package/dist/lib/task/create.js +33 -7
  25. package/dist/lib/task/decision-details.js +3 -48
  26. package/dist/lib/task/delivery-target.js +97 -0
  27. package/dist/lib/task/delivery.js +153 -0
  28. package/dist/lib/task/human-override.js +5 -2
  29. package/dist/lib/task/lifecycle.js +106 -9
  30. package/dist/lib/task/orchestration.js +55 -62
  31. package/dist/lib/task/pr-delivery-fact.js +182 -0
  32. package/dist/lib/task/review-finalization.js +4 -8
  33. package/dist/lib/task/review-fingerprint.js +8 -0
  34. package/dist/lib/task/verification-engine.js +48 -24
  35. package/dist/lib/task/verification-review.js +1 -1
  36. package/dist/lib/update.js +9 -0
  37. package/dist/lifecycle-build-manifest.json +3 -3
  38. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
  39. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
  40. package/lib/defaults.json +4 -0
  41. package/lib/git/workflow.ts +38 -1
  42. package/lib/init.ts +3 -0
  43. package/lib/internal/git-workflow.ts +7 -1
  44. package/lib/internal/platform-pr.ts +8 -1
  45. package/lib/internal/task-delivery.ts +53 -0
  46. package/lib/platform/github-client.ts +8 -1
  47. package/lib/platform/pr-checks.ts +7 -5
  48. package/lib/platform/pr-summary.ts +10 -7
  49. package/lib/platform/pull-requests.ts +425 -87
  50. package/lib/platform/verification-required.ts +12 -2
  51. package/lib/platform/verification-sync.ts +9 -4
  52. package/lib/pr-review/evidence-grading.ts +7 -4
  53. package/lib/sandbox/config.ts +7 -0
  54. package/lib/sandbox/control/lifecycle.ts +30 -8
  55. package/lib/server/process-state.ts +2 -2
  56. package/lib/task/artifact-lifecycle.ts +2 -2
  57. package/lib/task/commit-intent.ts +137 -0
  58. package/lib/task/commit-operation.ts +174 -87
  59. package/lib/task/create.ts +34 -9
  60. package/lib/task/decision-details.ts +2 -62
  61. package/lib/task/delivery-target.ts +130 -0
  62. package/lib/task/delivery.ts +160 -0
  63. package/lib/task/human-override.ts +5 -2
  64. package/lib/task/lifecycle.ts +93 -4
  65. package/lib/task/orchestration.ts +62 -65
  66. package/lib/task/pr-delivery-fact.ts +254 -0
  67. package/lib/task/review-finalization.ts +4 -14
  68. package/lib/task/review-fingerprint.ts +10 -0
  69. package/lib/task/verification-engine.ts +45 -28
  70. package/lib/task/verification-review.ts +2 -0
  71. package/lib/update.ts +10 -0
  72. package/package.json +1 -1
  73. package/templates/.agents/rules/README.en.md +4 -2
  74. package/templates/.agents/rules/README.zh-CN.md +4 -2
  75. package/templates/.agents/rules/create-issue.en.md +11 -2
  76. package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
  77. package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
  78. package/templates/.agents/rules/issue-sync.en.md +26 -5
  79. package/templates/.agents/rules/issue-sync.zh-CN.md +62 -5
  80. package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
  81. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
  82. package/templates/.agents/rules/local-artifact-repair.en.md +46 -0
  83. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -0
  84. package/templates/.agents/rules/milestone-inference.en.md +12 -2
  85. package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
  86. package/templates/.agents/rules/pr-sync.en.md +22 -13
  87. package/templates/.agents/rules/pr-sync.zh-CN.md +22 -1
  88. package/templates/.agents/rules/release-commands.en.md +42 -3
  89. package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
  90. package/templates/.agents/rules/security-alerts.en.md +17 -2
  91. package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
  92. package/templates/.agents/rules/sync-content-generation.en.md +8 -0
  93. package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
  94. package/templates/.agents/rules/task-management.en.md +1 -1
  95. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  96. package/templates/.agents/scripts/security-alerts.github.sh +151 -0
  97. package/templates/.agents/scripts/security-alerts.sh +31 -0
  98. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
  99. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
  100. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  101. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  102. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  103. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  104. package/templates/.agents/skills/code-task/SKILL.en.md +5 -2
  105. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +6 -3
  106. package/templates/.agents/skills/commit/SKILL.en.md +3 -3
  107. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
  108. package/templates/.agents/skills/commit/config/verify.json +1 -1
  109. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
  110. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
  111. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  112. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  113. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
  114. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
  115. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -1
  116. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -1
  117. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  118. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  119. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  120. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  121. package/templates/.agents/skills/complete-task/SKILL.en.md +10 -10
  122. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -10
  123. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
  124. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
  125. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  126. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  127. package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
  128. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
  129. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  130. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  131. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  132. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  133. package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
  134. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
  135. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
  136. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
  137. package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
  138. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
  139. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -0
  140. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -0
  141. package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -3
  142. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -3
  143. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
  144. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
  145. package/templates/.agents/skills/review-code/SKILL.en.md +7 -6
  146. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +7 -6
  147. package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
  148. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
  149. package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
  150. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
  151. package/templates/.agents/skills/review-plan/SKILL.en.md +4 -3
  152. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -3
  153. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
  154. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
  155. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
  156. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
  157. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
  158. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
  159. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -0
  160. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -0
  161. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  162. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  163. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +4 -0
  164. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  165. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  166. package/templates/.agents/templates/task.en.md +5 -1
  167. package/templates/.agents/templates/task.zh-CN.md +5 -1
  168. package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
  169. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
  170. package/templates/.agents/workflows/feature-development.en.yaml +9 -8
  171. package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
  172. package/templates/.agents/workflows/refactoring.en.yaml +9 -8
  173. package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
  174. package/templates/.agents/rules/create-issue.github.en.md +0 -14
  175. package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
  176. package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
  177. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
  178. package/templates/.agents/rules/issue-sync.github.en.md +0 -40
  179. package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
  180. package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
  181. package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
  182. package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
  183. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
  184. package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
  185. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
  186. package/templates/.agents/rules/pr-sync.github.en.md +0 -43
  187. package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
  188. package/templates/.agents/rules/release-commands.github.en.md +0 -44
  189. package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
  190. package/templates/.agents/rules/security-alerts.github.en.md +0 -43
  191. package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
  192. /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
  193. /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ const REMOTE_RE = /^(?!-)(?!.*\.\.)(?!.*[~^:?*\\\s])[A-Za-z0-9._/-]+$/;
5
+ const REF_RE = /^(?!-)(?!.*\.\.)(?!.*(?:^|\/)\.)(?!.*[~^:?*\\\s])[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/;
6
+ function validateRemote(value) {
7
+ return typeof value === 'string' && value.length > 0 && REMOTE_RE.test(value);
8
+ }
9
+ function validateBaseRef(value) {
10
+ return typeof value === 'string' && value.length > 0 && !value.startsWith('refs/') && REF_RE.test(value);
11
+ }
12
+ function invalid(field, value) {
13
+ return { ok: false, code: 'DELIVERY_TARGET_INVALID', message: `${field} is invalid: ${String(value ?? '')}` };
14
+ }
15
+ function bindDeliveryTarget(input) {
16
+ const remote = input.existing?.remote ?? input.explicit?.remote ?? input.defaults?.remote;
17
+ const baseRef = input.existing?.baseRef ?? input.explicit?.baseRef ?? input.defaults?.baseRef;
18
+ if (input.existing?.remote !== undefined && input.explicit?.remote !== undefined && input.existing.remote !== input.explicit.remote) {
19
+ return { ok: false, code: 'DELIVERY_TARGET_CONFLICT', message: 'Explicit delivery remote does not match the task binding' };
20
+ }
21
+ if (input.existing?.baseRef !== undefined && input.explicit?.baseRef !== undefined && input.existing.baseRef !== input.explicit.baseRef) {
22
+ return { ok: false, code: 'DELIVERY_TARGET_CONFLICT', message: 'Explicit delivery base ref does not match the task binding' };
23
+ }
24
+ if (!validateRemote(remote))
25
+ return invalid('delivery remote', remote);
26
+ if (!validateBaseRef(baseRef))
27
+ return invalid('delivery base ref', baseRef);
28
+ return { ok: true, value: { remote, baseRef } };
29
+ }
30
+ function readDeliveryDefaults(repoRoot) {
31
+ try {
32
+ const configPath = path.join(repoRoot, '.agents', '.airc.json');
33
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
34
+ if (!config.delivery || typeof config.delivery !== 'object' || Array.isArray(config.delivery)) {
35
+ return { ok: false, code: 'DELIVERY_CONFIG_INVALID', message: 'delivery config must declare remote and baseRef' };
36
+ }
37
+ const delivery = config.delivery;
38
+ if (typeof delivery.remote !== 'string' || typeof delivery.baseRef !== 'string') {
39
+ return { ok: false, code: 'DELIVERY_CONFIG_INVALID', message: 'delivery config must declare remote and baseRef' };
40
+ }
41
+ return bindDeliveryTarget({
42
+ defaults: {
43
+ remote: delivery.remote,
44
+ baseRef: delivery.baseRef
45
+ }
46
+ });
47
+ }
48
+ catch (error) {
49
+ return { ok: false, code: 'DELIVERY_CONFIG_INVALID', message: error instanceof Error ? error.message : String(error) };
50
+ }
51
+ }
52
+ function resolveDeliveryTarget(repoRoot, existing = {}, explicit = {}) {
53
+ const defaults = readDeliveryDefaults(repoRoot);
54
+ if (!defaults.ok)
55
+ return defaults;
56
+ return bindDeliveryTarget({ defaults: defaults.value, existing, explicit });
57
+ }
58
+ function gitText(repoRoot, args) {
59
+ return execFileSync('git', [...args], {
60
+ cwd: repoRoot,
61
+ encoding: 'utf8',
62
+ stdio: ['ignore', 'pipe', 'pipe']
63
+ }).trim();
64
+ }
65
+ function resolveTargetHead(repoRoot, target) {
66
+ try {
67
+ const output = gitText(repoRoot, ['ls-remote', '--refs', target.remote, `refs/heads/${target.baseRef}`]);
68
+ const head = output.split(/\s+/)[0] ?? '';
69
+ if (!/^[a-f0-9]{40}$/i.test(head)) {
70
+ return { ok: false, code: 'DELIVERY_TARGET_UNAVAILABLE', message: `Delivery target ${target.remote}/${target.baseRef} is unavailable` };
71
+ }
72
+ try {
73
+ gitText(repoRoot, ['cat-file', '-e', `${head}^{commit}`]);
74
+ }
75
+ catch {
76
+ return { ok: false, code: 'DELIVERY_TARGET_OBJECT_MISSING', message: `Delivery target commit ${head} is not available locally` };
77
+ }
78
+ return { ok: true, value: target, head };
79
+ }
80
+ catch (error) {
81
+ return { ok: false, code: 'DELIVERY_TARGET_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) };
82
+ }
83
+ }
84
+ function resolveDiffBase(repoRoot, reviewedHead, targetHead) {
85
+ try {
86
+ const diffBase = gitText(repoRoot, ['merge-base', reviewedHead, targetHead]);
87
+ if (!/^[a-f0-9]{40}$/i.test(diffBase)) {
88
+ return { ok: false, code: 'DELIVERY_DIFF_BASE_UNAVAILABLE', message: 'Git returned an invalid diff base' };
89
+ }
90
+ return { ok: true, diffBase };
91
+ }
92
+ catch (error) {
93
+ return { ok: false, code: 'DELIVERY_DIFF_BASE_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) };
94
+ }
95
+ }
96
+ export { bindDeliveryTarget, readDeliveryDefaults, resolveDeliveryTarget, resolveTargetHead, resolveDiffBase, validateBaseRef, validateRemote };
97
+ //# sourceMappingURL=delivery-target.js.map
@@ -0,0 +1,153 @@
1
+ import fs from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { parseTaskFrontmatter } from './frontmatter.js';
5
+ import { resolveTaskRef } from './resolve-ref.js';
6
+ import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead, validateBaseRef, validateRemote } from './delivery-target.js';
7
+ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead, findAuthoritativeReviewCodeArtifact } from './review-fingerprint.js';
8
+ import { captureTaskWriteMetadata, writeTask } from './write.js';
9
+ import { withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
10
+ function classifyDeliveryState(localHead, remoteHead, lastDelivered) {
11
+ if (remoteHead === null)
12
+ return { state: 'absent', shouldPush: true };
13
+ if (remoteHead === localHead)
14
+ return { state: 'same', shouldPush: false };
15
+ if (lastDelivered && remoteHead === lastDelivered)
16
+ return { state: 'known-old', shouldPush: true };
17
+ return { state: 'unknown-drift', shouldPush: false };
18
+ }
19
+ function git(cwd, args) {
20
+ const result = spawnSync('git', [...args], { cwd, encoding: 'utf8' });
21
+ return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
22
+ }
23
+ function gitValue(cwd, args) {
24
+ const result = git(cwd, args);
25
+ return result.status === 0 ? result.stdout.trim() : null;
26
+ }
27
+ function validateBranch(cwd, branch) {
28
+ return git(cwd, ['check-ref-format', `refs/heads/${branch}`]).status === 0;
29
+ }
30
+ function validateReviewTargetCompatibility(repoRoot, taskDir, target, reviewedHead) {
31
+ const artifact = findAuthoritativeReviewCodeArtifact(taskDir);
32
+ if (!artifact.ok || !artifact.path)
33
+ return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'A current review-code artifact is required before delivery' };
34
+ let content;
35
+ try {
36
+ content = fs.readFileSync(artifact.path, 'utf8');
37
+ }
38
+ catch (error) {
39
+ return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: error instanceof Error ? error.message : String(error) };
40
+ }
41
+ const savedReviewedHead = extractReviewedHead(content) || extractReviewBaseline(content);
42
+ const savedTargetHead = extractReviewTargetHead(content);
43
+ const savedDiffBase = extractReviewDiffBase(content);
44
+ if (!savedReviewedHead || !savedTargetHead || !savedDiffBase) {
45
+ return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: 'review-code must save reviewed head, target head, and diff base before delivery' };
46
+ }
47
+ if (savedReviewedHead !== reviewedHead) {
48
+ return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'review-code reviewed head does not match last_reviewed_commit' };
49
+ }
50
+ const saved = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
51
+ if (!saved.ok || saved.diffBase !== savedDiffBase) {
52
+ return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: saved.ok ? 'saved review diff base does not match saved target head' : saved.message };
53
+ }
54
+ const currentTarget = resolveTargetHead(repoRoot, target);
55
+ if (!currentTarget.ok)
56
+ return { ok: false, code: currentTarget.code, message: currentTarget.message };
57
+ const current = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head);
58
+ if (!current.ok)
59
+ return { ok: false, code: current.code, message: current.message };
60
+ if (current.diffBase !== savedDiffBase) {
61
+ return { ok: false, code: 'DELIVERY_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
62
+ }
63
+ return { ok: true };
64
+ }
65
+ function failure(taskId, code, message, retryable = false, status = retryable ? 'blocked' : 'failed') {
66
+ return { status, changed: false, taskId, remoteHead: null, localHead: null, state: null, error: { code, message, retryable } };
67
+ }
68
+ function deliverUnlocked(taskId, taskDir, repoRoot, options) {
69
+ let frontmatter;
70
+ try {
71
+ frontmatter = parseTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
72
+ }
73
+ catch (error) {
74
+ return failure(taskId, 'TASK_CONTEXT_UNREADABLE', error instanceof Error ? error.message : String(error));
75
+ }
76
+ if (frontmatter.status !== 'active')
77
+ return failure(taskId, 'TASK_STATE_INVALID', 'task branch delivery requires an active task');
78
+ const branch = frontmatter.branch ?? '';
79
+ if (!branch)
80
+ return failure(taskId, 'DELIVERY_BRANCH_MISSING', 'task branch is missing');
81
+ if (!validateBranch(repoRoot, branch))
82
+ return failure(taskId, 'DELIVERY_BRANCH_INVALID', 'task branch is invalid');
83
+ if (!frontmatter.delivery_remote || !frontmatter.delivery_base_ref) {
84
+ return failure(taskId, 'DELIVERY_TARGET_MISSING', 'task delivery target is not bound; recreate or repair task metadata before delivery');
85
+ }
86
+ const target = resolveDeliveryTarget(repoRoot, {
87
+ remote: frontmatter.delivery_remote,
88
+ baseRef: frontmatter.delivery_base_ref
89
+ }, {
90
+ ...(options.remote === undefined ? {} : { remote: options.remote }),
91
+ ...(options.baseRef === undefined ? {} : { baseRef: options.baseRef })
92
+ });
93
+ if (!target.ok)
94
+ return failure(taskId, target.code, target.message);
95
+ if (!validateRemote(target.value.remote) || !validateBaseRef(target.value.baseRef))
96
+ return failure(taskId, 'DELIVERY_TARGET_INVALID', 'task delivery target is invalid');
97
+ const localHead = gitValue(repoRoot, ['rev-parse', 'HEAD']);
98
+ if (!localHead)
99
+ return failure(taskId, 'DELIVERY_LOCAL_HEAD_UNAVAILABLE', 'Unable to resolve local HEAD');
100
+ const status = git(repoRoot, ['status', '--porcelain=v1']);
101
+ if (status.status !== 0)
102
+ return failure(taskId, 'DELIVERY_GIT_INSPECT_FAILED', status.stderr.trim() || 'Unable to inspect Git state');
103
+ if (status.stdout.trim())
104
+ return failure(taskId, 'DELIVERY_WORKTREE_DIRTY', 'Working tree must be clean before branch delivery');
105
+ const reviewed = frontmatter.last_reviewed_commit ?? '';
106
+ if (reviewed !== localHead)
107
+ return failure(taskId, 'DELIVERY_REVIEW_REQUIRED', 'Local HEAD must equal last_reviewed_commit before delivery');
108
+ const reviewTarget = validateReviewTargetCompatibility(repoRoot, taskDir, target.value, reviewed);
109
+ if (!reviewTarget.ok)
110
+ return failure(taskId, reviewTarget.code, reviewTarget.message, reviewTarget.code.includes('UNAVAILABLE'), reviewTarget.code.includes('UNAVAILABLE') ? 'blocked' : 'failed');
111
+ const ref = `refs/heads/${branch}`;
112
+ const remoteOutput = git(repoRoot, ['ls-remote', '--refs', target.value.remote, ref]);
113
+ if (remoteOutput.status !== 0)
114
+ return failure(taskId, 'DELIVERY_REMOTE_UNAVAILABLE', remoteOutput.stderr.trim() || 'Unable to inspect the delivery remote', true);
115
+ const remoteHead = remoteOutput.stdout.trim().split(/\s+/)[0] || null;
116
+ const classification = classifyDeliveryState(localHead, remoteHead, frontmatter.delivery_remote_head ?? null);
117
+ if (classification.state === 'unknown-drift')
118
+ return failure(taskId, 'DELIVERY_REMOTE_DRIFT', `Remote branch ${branch} has an unexpected head; refusing to overwrite it`);
119
+ if (options.dryRun)
120
+ return { status: 'planned', changed: classification.shouldPush, taskId, remoteHead, localHead, state: classification.state, error: null };
121
+ if (classification.shouldPush) {
122
+ const args = classification.state === 'known-old'
123
+ ? ['push', `--force-with-lease=${ref}:${frontmatter.delivery_remote_head}`, target.value.remote, `HEAD:${ref}`]
124
+ : ['push', `--force-with-lease=${ref}:`, target.value.remote, `HEAD:${ref}`];
125
+ const pushed = git(repoRoot, args);
126
+ if (pushed.status !== 0)
127
+ return failure(taskId, 'DELIVERY_PUSH_FAILED', pushed.stderr.trim() || 'Task branch delivery was rejected', true, 'blocked');
128
+ }
129
+ const verified = gitValue(repoRoot, ['ls-remote', '--refs', target.value.remote, ref])?.split(/\s+/)[0] ?? null;
130
+ if (verified !== localHead)
131
+ return failure(taskId, 'DELIVERY_REMOTE_VERIFY_FAILED', `Expected remote head ${localHead}, received ${verified ?? 'unavailable'}`, true, 'blocked');
132
+ const written = writeTask({
133
+ taskRef: taskId,
134
+ expectedState: 'active',
135
+ mutations: [{ kind: 'frontmatter', set: { delivery_remote: target.value.remote, delivery_base_ref: target.value.baseRef, delivery_remote_head: localHead, assigned_to: options.agent } }]
136
+ }, { repoRoot, metadataProvider: captureTaskWriteMetadata });
137
+ if (written.status === 'failed')
138
+ return failure(taskId, 'DELIVERY_TASK_SYNC_FAILED', written.error.message, true, 'blocked');
139
+ return { status: classification.shouldPush ? 'applied' : 'no-op', changed: classification.shouldPush || written.changed, taskId, remoteHead: localHead, localHead, state: classification.state, error: null };
140
+ }
141
+ function deliverTaskBranch(taskRef, options) {
142
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
143
+ if (!resolved.ok)
144
+ return failure(resolved.taskId, resolved.code, resolved.message);
145
+ try {
146
+ return withRepositoryMutationLock(resolved.repoRoot, () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-delivery', () => deliverUnlocked(resolved.taskId, resolved.taskDir, resolved.repoRoot, options)));
147
+ }
148
+ catch (error) {
149
+ return failure(resolved.taskId, 'DELIVERY_LOCK_FAILED', error instanceof Error ? error.message : String(error), true, 'blocked');
150
+ }
151
+ }
152
+ export { classifyDeliveryState, deliverTaskBranch };
153
+ //# sourceMappingURL=delivery.js.map
@@ -19,6 +19,7 @@ import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
19
19
  import { verifyTaskEvent } from './verification.js';
20
20
  import { verifyInProcess } from './verification-engine.js';
21
21
  import { canonicalTimestamp, writeTask } from './write.js';
22
+ import { readPrDeliveryFact } from './pr-delivery-fact.js';
22
23
  const LIFECYCLE_PRODUCER = 'lifecycle.apply';
23
24
  const HUMAN_OVERRIDE_SECTION_ALIASES = ['人工豁免账本', 'Human Override Ledger'];
24
25
  const HUMAN_OVERRIDE_COLUMNS = [
@@ -405,7 +406,8 @@ function resourceForSnapshot(policy, options, frontmatter) {
405
406
  return number === null ? null : { kind: 'issue', number };
406
407
  }
407
408
  if (policy.producerId === 'platform.pull-request') {
408
- const number = options.probePullRequestNumber ?? toPositiveInteger(frontmatter?.pr_number);
409
+ const fact = frontmatter ? readPrDeliveryFact(frontmatter) : null;
410
+ const number = options.probePullRequestNumber ?? (fact?.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null);
409
411
  return number === null ? null : { kind: 'pull-request', number };
410
412
  }
411
413
  return null;
@@ -468,7 +470,8 @@ function probePlatformFailure(taskRef, policy, resolved, options) {
468
470
  return error('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause));
469
471
  }
470
472
  const issueNumber = options.probeIssueNumber ?? toPositiveInteger(frontmatter.issue_number);
471
- const pullRequestNumber = options.probePullRequestNumber ?? toPositiveInteger(frontmatter.pr_number);
473
+ const fact = readPrDeliveryFact(frontmatter);
474
+ const pullRequestNumber = options.probePullRequestNumber ?? (fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null);
472
475
  const operation = policy.producerId === 'platform.issue'
473
476
  ? issueNumber === null
474
477
  ? null
@@ -14,6 +14,7 @@ const lifecycleIntentCatalog = [
14
14
  ];
15
15
  const lifecycleFailureCatalog = [
16
16
  'LIFECYCLE_DIRECTORY_RENAME_FAILED',
17
+ 'LIFECYCLE_DIRECTORY_COPY_FAILED',
17
18
  'LIFECYCLE_DOCUMENT_INVALID',
18
19
  'LIFECYCLE_FINAL_STATE_INVALID',
19
20
  'LIFECYCLE_IDENTITY_CONFLICT',
@@ -295,6 +296,65 @@ function parseJournal(content) {
295
296
  }
296
297
  return value;
297
298
  }
299
+ function directoryManifest(root) {
300
+ const entries = [];
301
+ const visit = (current, relative = '') => {
302
+ for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
303
+ const relativePath = relative ? path.join(relative, entry.name) : entry.name;
304
+ const absolute = path.join(current, entry.name);
305
+ const stat = fs.lstatSync(absolute);
306
+ if (stat.isSymbolicLink())
307
+ throw new Error(`symbolic link is not allowed in lifecycle directory: ${relativePath}`);
308
+ if (entry.isDirectory()) {
309
+ entries.push({ relativePath, kind: 'directory', size: 0, digest: '' });
310
+ visit(absolute, relativePath);
311
+ }
312
+ else if (entry.isFile()) {
313
+ const digest = createHash('sha256').update(fs.readFileSync(absolute)).digest('hex');
314
+ entries.push({ relativePath, kind: 'file', size: stat.size, digest });
315
+ }
316
+ else {
317
+ throw new Error(`unsupported lifecycle directory entry: ${relativePath}`);
318
+ }
319
+ }
320
+ };
321
+ visit(root);
322
+ return entries;
323
+ }
324
+ function copyDirectoryVerified(source, target) {
325
+ if (fs.existsSync(target))
326
+ throw new Error(`target directory already exists: ${target}`);
327
+ const copy = (currentSource, currentTarget) => {
328
+ fs.mkdirSync(currentTarget);
329
+ for (const entry of fs.readdirSync(currentSource, { withFileTypes: true })) {
330
+ const sourcePath = path.join(currentSource, entry.name);
331
+ const targetPath = path.join(currentTarget, entry.name);
332
+ const stat = fs.lstatSync(sourcePath);
333
+ if (stat.isSymbolicLink())
334
+ throw new Error(`symbolic link is not allowed in lifecycle directory: ${entry.name}`);
335
+ if (entry.isDirectory())
336
+ copy(sourcePath, targetPath);
337
+ else if (entry.isFile())
338
+ fs.copyFileSync(sourcePath, targetPath);
339
+ else
340
+ throw new Error(`unsupported lifecycle directory entry: ${entry.name}`);
341
+ }
342
+ };
343
+ copy(source, target);
344
+ const sourceManifest = directoryManifest(source);
345
+ const targetManifest = directoryManifest(target);
346
+ if (JSON.stringify(sourceManifest) !== JSON.stringify(targetManifest)) {
347
+ throw new Error('copied lifecycle directory failed manifest verification');
348
+ }
349
+ }
350
+ function removeCopiedSource(source, target) {
351
+ const sourceManifest = directoryManifest(source);
352
+ const targetManifest = directoryManifest(target);
353
+ if (JSON.stringify(sourceManifest) !== JSON.stringify(targetManifest)) {
354
+ throw new Error('lifecycle source and target diverged before source cleanup');
355
+ }
356
+ fs.rmSync(source, { recursive: true, force: false });
357
+ }
298
358
  function applyTaskLifecycle(requestInput, options = {}) {
299
359
  const normalized = normalizedRequest(requestInput);
300
360
  if ('code' in normalized)
@@ -356,7 +416,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
356
416
  if (!spec.sources.includes(sourceState) && !existingJournal && !allowsManualOverride(options, 'LIFECYCLE_SOURCE_INVALID')) {
357
417
  return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
358
418
  }
359
- if (sourcePath !== targetPath && fs.existsSync(targetPath)) {
419
+ if (sourcePath !== targetPath && fs.existsSync(targetPath) && !existingJournal) {
360
420
  return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
361
421
  }
362
422
  const content = io.readFileSync(currentTaskFile);
@@ -501,18 +561,55 @@ function applyTaskLifecycle(requestInput, options = {}) {
501
561
  (options.directoryRenameSync ?? io.renameSync)(sourcePath, targetPath);
502
562
  }
503
563
  catch (error) {
504
- const failure = { code: 'LIFECYCLE_DIRECTORY_RENAME_FAILED', message: String(error) };
505
- rememberJournalFailure(journalPath, journal, io, failure);
506
- return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
564
+ if (error.code === 'EXDEV') {
565
+ try {
566
+ copyDirectoryVerified(sourcePath, targetPath);
567
+ completed.add('directory-moved');
568
+ journal.completedSteps = [...completed];
569
+ const copiedJournalPath = path.join(targetPath, '.task-lifecycle.json');
570
+ writeJournal(initialJournalPath, journal, io);
571
+ writeJournal(copiedJournalPath, journal, io);
572
+ removeCopiedSource(sourcePath, targetPath);
573
+ journalPath = copiedJournalPath;
574
+ }
575
+ catch (copyError) {
576
+ if (fs.existsSync(targetPath))
577
+ fs.rmSync(targetPath, { recursive: true, force: false });
578
+ const failure = { code: 'LIFECYCLE_DIRECTORY_COPY_FAILED', message: String(copyError) };
579
+ rememberJournalFailure(initialJournalPath, journal, io, failure);
580
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
581
+ }
582
+ }
583
+ else {
584
+ const failure = { code: 'LIFECYCLE_DIRECTORY_RENAME_FAILED', message: String(error) };
585
+ rememberJournalFailure(journalPath, journal, io, failure);
586
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
587
+ }
507
588
  }
508
- completed.add('directory-moved');
509
- journal.completedSteps = [...completed];
510
- journalPath = path.join(targetPath, '.task-lifecycle.json');
589
+ if (completed.has('directory-moved') && !fs.existsSync(sourcePath)) {
590
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
591
+ }
592
+ else if (!completed.has('directory-moved')) {
593
+ completed.add('directory-moved');
594
+ journal.completedSteps = [...completed];
595
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
596
+ try {
597
+ writeJournal(journalPath, journal, io);
598
+ }
599
+ catch (error) {
600
+ return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
601
+ }
602
+ }
603
+ }
604
+ if (completed.has('directory-moved') && sourcePath !== targetPath && fs.existsSync(sourcePath) && fs.existsSync(targetPath)) {
511
605
  try {
512
- writeJournal(journalPath, journal, io);
606
+ removeCopiedSource(sourcePath, targetPath);
607
+ journalPath = path.join(targetPath, '.task-lifecycle.json');
513
608
  }
514
609
  catch (error) {
515
- return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
610
+ const failure = { code: 'LIFECYCLE_DIRECTORY_COPY_FAILED', message: String(error) };
611
+ rememberJournalFailure(journalPath, journal, io, failure);
612
+ return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
516
613
  }
517
614
  }
518
615
  let shortId = { effect: 'unchanged', shortId: null, changed: false };
@@ -10,13 +10,14 @@ import { resolveTaskRef } from './resolve-ref.js';
10
10
  import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
11
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
12
  import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
13
- import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
14
13
  import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
15
14
  import { isAgentClientId } from '../agent-clients/types.js';
16
15
  import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
17
16
  import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
18
17
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
19
18
  import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
19
+ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.js';
20
+ import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
20
21
  function supportsLifecycleDelegation(client) {
21
22
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
22
23
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -106,9 +107,8 @@ function isCompletionEvidence(value) {
106
107
  && exactText(value.headTree)
107
108
  && exactText(value.worktreeTree)
108
109
  && exactText(value.lastReviewedCommit)
109
- && Number.isSafeInteger(value.prNumber)
110
- && value.prNumber > 0
111
- && exactText(value.prHead);
110
+ && (value.prNumber === null || (Number.isSafeInteger(value.prNumber) && value.prNumber > 0))
111
+ && (value.prHead === null || exactText(value.prHead));
112
112
  }
113
113
  const ORCHESTRATION_RUN_KEYS = [
114
114
  'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
@@ -340,6 +340,43 @@ function highestRound(taskDir, family) {
340
340
  function artifactName(family, round) {
341
341
  return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
342
342
  }
343
+ function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead) {
344
+ const reviewRound = highestRound(taskDir, 'review-code');
345
+ const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
346
+ const savedTargetHead = extractReviewTargetHead(reviewContent);
347
+ const savedDiffBase = extractReviewDiffBase(reviewContent);
348
+ const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
349
+ if (!savedTargetHead && !savedDiffBase && !savedReviewedHead)
350
+ return null;
351
+ if (savedReviewedHead && savedReviewedHead !== reviewedHead) {
352
+ return { code: 'ORCHESTRATION_REVIEWED_HEAD_MISMATCH', message: 'review-code saved reviewed head does not match last_reviewed_commit' };
353
+ }
354
+ if (!savedTargetHead || !savedDiffBase) {
355
+ return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: 'review-code must save target head and diff base together' };
356
+ }
357
+ const savedBase = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
358
+ if (!savedBase.ok)
359
+ return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: savedBase.message };
360
+ if (savedBase.diffBase !== savedDiffBase) {
361
+ return { code: 'ORCHESTRATION_REVIEW_DIFF_BASE_MISMATCH', message: 'saved review diff base does not match reviewed head and saved target head' };
362
+ }
363
+ const target = resolveDeliveryTarget(repoRoot, {
364
+ remote: typeof metadata.delivery_remote === 'string' ? metadata.delivery_remote : undefined,
365
+ baseRef: typeof metadata.delivery_base_ref === 'string' ? metadata.delivery_base_ref : undefined
366
+ });
367
+ if (!target.ok)
368
+ return { code: target.code, message: target.message };
369
+ const currentTarget = resolveTargetHead(repoRoot, target.value);
370
+ if (!currentTarget.ok)
371
+ return { code: currentTarget.code, message: currentTarget.message };
372
+ const currentBase = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head);
373
+ if (!currentBase.ok)
374
+ return { code: currentBase.code, message: currentBase.message };
375
+ if (currentBase.diffBase !== savedDiffBase) {
376
+ return { code: 'ORCHESTRATION_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
377
+ }
378
+ return null;
379
+ }
343
380
  function hasReviewAfterArtifact(taskDir, family, reviewFamily) {
344
381
  const artifact = inspectArtifactDirectory(taskDir, family);
345
382
  const review = inspectArtifactDirectory(taskDir, reviewFamily);
@@ -487,29 +524,8 @@ function routeOrchestration(taskRef, options = {}) {
487
524
  if (run?.status === 'completed') {
488
525
  return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
489
526
  }
490
- let config;
491
- try {
492
- config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
493
- }
494
- catch (error) {
495
- return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
496
- }
497
- if (!config || typeof config !== 'object' || Array.isArray(config)) {
498
- return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
499
- }
500
- const projectConfig = config;
501
- if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
502
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
503
- }
504
- if (projectConfig.prFlow !== 'required') {
505
- return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
506
- }
507
- const prNumber = Number(metadata.pr_number);
508
- if (!Number.isInteger(prNumber) || prNumber <= 0) {
509
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
510
- }
511
527
  if (!run) {
512
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
528
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
513
529
  }
514
530
  if (run.status !== 'running' || run.pendingDelegation !== null) {
515
531
  return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
@@ -523,34 +539,15 @@ function routeOrchestration(taskRef, options = {}) {
523
539
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
524
540
  }
525
541
  if (before.headTree !== before.worktreeTree) {
526
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
542
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
527
543
  }
528
544
  const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
529
545
  if (before.head !== lastReviewedCommit) {
530
546
  return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
531
547
  }
532
- const inspect = options.inspectPullRequest ?? inspectPlatformPullRequest;
533
- let inspection;
534
- try {
535
- inspection = inspect(resolved.taskId, { cwd: gitRoot });
536
- }
537
- catch (error) {
538
- return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
539
- }
540
- if (inspection.status === 'blocked') {
541
- return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
542
- }
543
- if (inspection.status === 'failed') {
544
- return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
545
- }
546
- const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
547
- const prHead = inspection.pullRequest?.head?.sha;
548
- if (typeof prHead !== 'string') {
549
- return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
550
- }
551
- if (inspectedPrNumber !== prNumber || prHead !== before.head) {
552
- return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
553
- }
548
+ const diffBaseError = validateSavedCurrentDiffBase(resolved.repoRoot, metadata, resolved.taskDir, lastReviewedCommit);
549
+ if (diffBaseError)
550
+ return failed(diffBaseError.code, diffBaseError.message, resolved.taskId);
554
551
  let after;
555
552
  try {
556
553
  after = capture(gitRoot);
@@ -559,7 +556,7 @@ function routeOrchestration(taskRef, options = {}) {
559
556
  return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
560
557
  }
561
558
  if (after.headTree !== after.worktreeTree) {
562
- return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
559
+ return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
563
560
  }
564
561
  if (after.head !== before.head || after.headTree !== before.headTree) {
565
562
  return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
@@ -572,8 +569,8 @@ function routeOrchestration(taskRef, options = {}) {
572
569
  headTree: after.headTree,
573
570
  worktreeTree: after.worktreeTree,
574
571
  lastReviewedCommit,
575
- prNumber,
576
- prHead
572
+ prNumber: null,
573
+ prHead: null
577
574
  };
578
575
  const completed = withUpdatedRun(run, {
579
576
  status: 'completed',
@@ -668,10 +665,7 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
668
665
  }
669
666
  const updated = withUpdatedRun(run, {
670
667
  nextStage: next.stage,
671
- pendingDelegation: receipt,
672
- commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
673
- ? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
674
- : run.commitAuthorization
668
+ pendingDelegation: receipt
675
669
  });
676
670
  saveRun(resolved.taskDir, updated);
677
671
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
@@ -1093,16 +1087,15 @@ function advanceOrchestration(taskRef, options = {}) {
1093
1087
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1094
1088
  if (!result.ok)
1095
1089
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
1096
- const completed = result.receipt.stage === 'commit';
1090
+ if (result.receipt.stage === 'commit') {
1091
+ return failed('ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', resolved.taskId);
1092
+ }
1097
1093
  const updated = withUpdatedRun(run, {
1098
- status: completed ? 'completed' : 'running',
1094
+ status: 'running',
1099
1095
  nextStage: null,
1100
1096
  stepCount: run.stepCount + 1,
1101
1097
  pendingDelegation: null,
1102
- receipts: Object.freeze([...run.receipts, result.receipt]),
1103
- commitAuthorization: completed
1104
- ? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
1105
- : run.commitAuthorization
1098
+ receipts: Object.freeze([...run.receipts, result.receipt])
1106
1099
  });
1107
1100
  saveRun(resolved.taskDir, updated);
1108
1101
  return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };