@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
@@ -8,7 +8,7 @@ import { mergeOperationWarnings } from './operation-outcome.js';
8
8
  import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
9
9
  import { appendActivityEntry, locateActivityLog } from './activity-log.js';
10
10
  import { captureTaskWriteMetadata, writeTask } from './write.js';
11
- import { commitOrchestrationStageCompletion, planOrchestrationStageCompletion } from './orchestration.js';
11
+ import { checkpointIntentDigest, readCheckpointIntent, removeCheckpointIntent, sameCheckpointIntent, updateCheckpointIntent, writeCheckpointIntent } from './commit-intent.js';
12
12
  const HEAD_REF = /^refs\/heads\/(.+)$/;
13
13
  function isCommitExecutionMode(value) {
14
14
  return value === 'direct' || value === 'orchestrated';
@@ -20,6 +20,13 @@ function gitText(repoRoot, args) {
20
20
  stdio: ['ignore', 'pipe', 'pipe']
21
21
  }).trim();
22
22
  }
23
+ function effectiveDelivery(input) {
24
+ if (input.delivery)
25
+ return input.delivery;
26
+ if (input.push)
27
+ return { mode: 'push', remote: input.push.remote, refs: input.push.refs };
28
+ return null;
29
+ }
23
30
  function canonicalRepositoryRoot(cwd) {
24
31
  return gitText(cwd, ['rev-parse', '--show-toplevel']);
25
32
  }
@@ -86,8 +93,9 @@ function validateCommon(input, branch) {
86
93
  return { code: 'GIT_HEAD_EXPECTATION_REQUIRED', message: 'A valid expected HEAD is required' };
87
94
  if (!/^[a-f0-9]{40,64}$/.test(input.expectedTree))
88
95
  return { code: 'GIT_TREE_EXPECTATION_REQUIRED', message: 'A valid expected tree is required' };
89
- if (!input.push)
90
- return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Commit delivery requires a push policy' };
96
+ const delivery = effectiveDelivery(input);
97
+ if (!delivery)
98
+ return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Commit delivery requires a delivery policy' };
91
99
  if (input.paths.some((candidate) => !candidate || candidate.startsWith('/') || candidate.split(/[\\/]/).includes('..'))) {
92
100
  return { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Commit paths must stay inside the repository' };
93
101
  }
@@ -96,38 +104,16 @@ function validateCommon(input, branch) {
96
104
  }
97
105
  if (branch !== null && branch === '')
98
106
  return { code: 'GIT_BRANCH_INVALID', message: 'A named branch is required' };
99
- if (!input.push.remote || input.push.refs.length !== 1)
100
- return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push requires one remote and one ref' };
101
- const ref = input.push.refs[0] ?? '';
102
- if (!HEAD_REF.test(ref) || HEAD_REF.exec(ref)?.[1] !== branch) {
103
- return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push ref must be the current full heads ref' };
107
+ if (delivery.mode === 'push') {
108
+ if (!delivery.remote || delivery.refs.length !== 1)
109
+ return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push requires one remote and one ref' };
110
+ const ref = delivery.refs[0] ?? '';
111
+ if (!HEAD_REF.test(ref) || HEAD_REF.exec(ref)?.[1] !== branch) {
112
+ return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push ref must be the current full heads ref' };
113
+ }
104
114
  }
105
115
  return null;
106
116
  }
107
- function validateOrchestrated(input, task) {
108
- if (!input.agent)
109
- return { error: { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'orchestrated commit requires an agent' } };
110
- const planned = planOrchestrationStageCompletion(task.taskId, {
111
- stage: 'commit',
112
- round: 1,
113
- artifact: 'commit',
114
- role: 'executor',
115
- agent: input.agent
116
- }, { repoRoot: task.repoRoot });
117
- const run = planned.result.run;
118
- if (!planned.plan
119
- || !run
120
- || run.status !== 'running'
121
- || !run.commitAuthorization.issuedAt
122
- || run.commitAuthorization.consumedAt)
123
- return {
124
- error: {
125
- code: planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH',
126
- message: planned.result.error?.message ?? 'orchestrated commit requires one matching activated commit delegation'
127
- }
128
- };
129
- return { plan: planned.plan };
130
- }
131
117
  function syncTaskCommit(input, task, committedHead) {
132
118
  let content;
133
119
  try {
@@ -146,7 +132,10 @@ function syncTaskCommit(input, task, committedHead) {
146
132
  heading: activity.heading,
147
133
  body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Commit', agent: input.agent, note: commitNote })
148
134
  });
149
- const frontmatter = { assigned_to: input.agent };
135
+ const frontmatter = {
136
+ assigned_to: input.agent,
137
+ checkpoint_commit: committedHead
138
+ };
150
139
  mutations.push({ kind: 'frontmatter', set: frontmatter });
151
140
  if (mutations.length === 0)
152
141
  return null;
@@ -163,10 +152,44 @@ function syncTaskCommit(input, task, committedHead) {
163
152
  return warning('TASK_STATUS_SYNC_FAILED', error instanceof Error ? error.message : String(error), true, 'task', task.taskId, 'ACTION_REQUIRED');
164
153
  }
165
154
  }
155
+ function checkpointChildMatches(task, intent, head) {
156
+ try {
157
+ const parent = gitText(task.repoRoot, ['rev-parse', `${head}^`]);
158
+ const tree = gitText(task.repoRoot, ['rev-parse', `${head}^{tree}`]);
159
+ const message = gitText(task.repoRoot, ['show', '-s', '--format=%s', head]);
160
+ const changed = gitText(task.repoRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', head]).split('\n').filter(Boolean).sort();
161
+ return parent === intent.expectedHead
162
+ && tree === intent.expectedTree
163
+ && message === intent.message
164
+ && JSON.stringify(changed) === JSON.stringify([...intent.paths].sort());
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
170
+ function checkpointIdentity(input, task, delivery) {
171
+ if (delivery.mode !== 'local')
172
+ throw new Error('checkpoint identity requires local delivery');
173
+ return {
174
+ taskId: task.taskId,
175
+ branch: task.branch,
176
+ mode: 'local',
177
+ expectedHead: input.expectedHead,
178
+ expectedTree: input.expectedTree,
179
+ paths: [...input.paths],
180
+ message: input.message,
181
+ round: input.round ?? 1
182
+ };
183
+ }
184
+ function checkpointTimestamp() {
185
+ return new Date().toISOString();
186
+ }
166
187
  function executeUnlocked(input, task, mode) {
167
188
  const inspected = inspectGitWorkflow(input.cwd);
168
189
  if (!inspected.snapshot)
169
190
  return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
191
+ if (mode === 'orchestrated')
192
+ return failure(input, mode, task?.taskId ?? null, 'ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', 'blocked', inspected.snapshot);
170
193
  const branch = inspected.snapshot.branch;
171
194
  const commonError = validateCommon(input, branch);
172
195
  if (commonError)
@@ -177,60 +200,131 @@ function executeUnlocked(input, task, mode) {
177
200
  return failure(input, mode, task.taskId, 'GIT_BRANCH_MISMATCH', `Task branch '${task.branch}' does not match current branch '${branch}'`, 'blocked', inspected.snapshot);
178
201
  if (task && !input.agent)
179
202
  return failure(input, mode, task.taskId, 'COMMIT_AGENT_REQUIRED', 'task-bound commit requires an agent', 'blocked', inspected.snapshot);
180
- const orchestrationValidation = mode === 'orchestrated'
181
- ? (task
182
- ? validateOrchestrated(input, task)
183
- : { error: { code: 'ORCHESTRATION_TASK_REQUIRED', message: 'orchestrated commit requires a task context' } })
184
- : null;
185
- if (orchestrationValidation && 'error' in orchestrationValidation) {
186
- const error = orchestrationValidation.error;
187
- return failure(input, mode, task?.taskId ?? null, error.code, error.message, 'blocked', inspected.snapshot);
188
- }
189
- const orchestrationCompletion = orchestrationValidation && 'plan' in orchestrationValidation
190
- ? orchestrationValidation.plan
191
- : null;
192
- const committed = input.paths.length === 0
203
+ const delivery = effectiveDelivery(input);
204
+ if (delivery.mode === 'local' && !task)
205
+ return failure(input, mode, null, 'CHECKPOINT_TASK_REQUIRED', 'local checkpoint delivery requires a task context', 'blocked', inspected.snapshot);
206
+ let pendingIntent = null;
207
+ let checkpointCommittedHead = null;
208
+ const committed = delivery.mode === 'local'
193
209
  ? (() => {
194
- if (inspected.snapshot.worktree.length > 0 || inspected.snapshot.staged.length > 0)
210
+ const identity = checkpointIdentity(input, task, delivery);
211
+ try {
212
+ pendingIntent = readCheckpointIntent(task.repoRoot, task.taskId);
213
+ }
214
+ catch (error) {
195
215
  return {
196
- status: 'failed',
197
- changed: false,
198
- snapshot: inspected.snapshot,
199
- operations: [],
200
- error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Push-only retry requires a clean working tree' }
216
+ status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
217
+ error: { code: 'COMMIT_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) }
201
218
  };
202
- if (inspected.snapshot.head !== input.expectedHead)
219
+ }
220
+ if (pendingIntent && !sameCheckpointIntent(pendingIntent, identity))
203
221
  return {
204
- status: 'failed',
205
- changed: false,
206
- snapshot: inspected.snapshot,
207
- operations: [],
208
- error: { code: 'GIT_HEAD_MISMATCH', message: `Expected HEAD ${input.expectedHead}, received ${inspected.snapshot.head}` }
222
+ status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
223
+ error: { code: 'COMMIT_INTENT_CONFLICT', message: 'A different checkpoint intent is still pending for this task' }
224
+ };
225
+ if (!pendingIntent) {
226
+ const timestamp = checkpointTimestamp();
227
+ pendingIntent = {
228
+ version: 1, ...identity, digest: checkpointIntentDigest(identity), state: 'prepared',
229
+ committedHead: null, createdAt: timestamp, updatedAt: timestamp
230
+ };
231
+ try {
232
+ writeCheckpointIntent(task.repoRoot, pendingIntent);
233
+ }
234
+ catch (error) {
235
+ return {
236
+ status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
237
+ error: { code: 'COMMIT_INTENT_WRITE_FAILED', message: error instanceof Error ? error.message : String(error) }
238
+ };
239
+ }
240
+ }
241
+ if (pendingIntent.state !== 'prepared') {
242
+ checkpointCommittedHead = pendingIntent.committedHead;
243
+ return {
244
+ status: 'no-op', changed: false, snapshot: inspected.snapshot,
245
+ operations: [{ name: 'commit', status: 'no-op' }], error: null
246
+ };
247
+ }
248
+ if (inspected.snapshot.head !== identity.expectedHead && !checkpointChildMatches(task, pendingIntent, inspected.snapshot.head))
249
+ return {
250
+ status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
251
+ error: { code: 'COMMIT_INTENT_HEAD_CONFLICT', message: 'Current HEAD does not match the pending checkpoint intent' }
209
252
  };
210
- const currentTree = gitText(input.cwd, ['rev-parse', 'HEAD^{tree}']);
211
- if (currentTree !== input.expectedTree)
253
+ if (inspected.snapshot.head !== identity.expectedHead) {
254
+ checkpointCommittedHead = inspected.snapshot.head;
255
+ pendingIntent = updateCheckpointIntent(pendingIntent, {
256
+ state: 'committed', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
257
+ });
258
+ writeCheckpointIntent(task.repoRoot, pendingIntent);
259
+ return {
260
+ status: 'no-op', changed: false, snapshot: inspected.snapshot,
261
+ operations: [{ name: 'commit', status: 'no-op' }], error: null
262
+ };
263
+ }
264
+ if (input.paths.length === 0)
265
+ return {
266
+ status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
267
+ error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Local checkpoint requires explicit paths' }
268
+ };
269
+ const created = commitExplicitPaths({
270
+ cwd: input.cwd, paths: input.paths, message: input.message,
271
+ expectedHead: identity.expectedHead, expectedTree: identity.expectedTree
272
+ });
273
+ if (created.status === 'applied' && created.snapshot?.head) {
274
+ checkpointCommittedHead = created.snapshot.head;
275
+ pendingIntent = updateCheckpointIntent(pendingIntent, {
276
+ state: 'committed', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
277
+ });
278
+ writeCheckpointIntent(task.repoRoot, pendingIntent);
279
+ }
280
+ else if (created.status === 'no-op') {
281
+ removeCheckpointIntent(task.repoRoot, task.taskId);
282
+ pendingIntent = null;
283
+ }
284
+ return created;
285
+ })()
286
+ : input.paths.length === 0
287
+ ? (() => {
288
+ if (inspected.snapshot.worktree.length > 0 || inspected.snapshot.staged.length > 0)
289
+ return {
290
+ status: 'failed',
291
+ changed: false,
292
+ snapshot: inspected.snapshot,
293
+ operations: [],
294
+ error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Push-only retry requires a clean working tree' }
295
+ };
296
+ if (inspected.snapshot.head !== input.expectedHead)
297
+ return {
298
+ status: 'failed',
299
+ changed: false,
300
+ snapshot: inspected.snapshot,
301
+ operations: [],
302
+ error: { code: 'GIT_HEAD_MISMATCH', message: `Expected HEAD ${input.expectedHead}, received ${inspected.snapshot.head}` }
303
+ };
304
+ const currentTree = gitText(input.cwd, ['rev-parse', 'HEAD^{tree}']);
305
+ if (currentTree !== input.expectedTree)
306
+ return {
307
+ status: 'failed',
308
+ changed: false,
309
+ snapshot: inspected.snapshot,
310
+ operations: [],
311
+ error: { code: 'GIT_TREE_MISMATCH', message: `Expected current tree ${input.expectedTree}, received ${currentTree}` }
312
+ };
212
313
  return {
213
- status: 'failed',
314
+ status: 'no-op',
214
315
  changed: false,
215
316
  snapshot: inspected.snapshot,
216
- operations: [],
217
- error: { code: 'GIT_TREE_MISMATCH', message: `Expected current tree ${input.expectedTree}, received ${currentTree}` }
317
+ operations: [{ name: 'commit', status: 'no-op' }],
318
+ error: null
218
319
  };
219
- return {
220
- status: 'no-op',
221
- changed: false,
222
- snapshot: inspected.snapshot,
223
- operations: [{ name: 'commit', status: 'no-op' }],
224
- error: null
225
- };
226
- })()
227
- : commitExplicitPaths({
228
- cwd: input.cwd,
229
- paths: input.paths,
230
- message: input.message,
231
- expectedHead: input.expectedHead,
232
- expectedTree: input.expectedTree
233
- });
320
+ })()
321
+ : commitExplicitPaths({
322
+ cwd: input.cwd,
323
+ paths: input.paths,
324
+ message: input.message,
325
+ expectedHead: input.expectedHead,
326
+ expectedTree: input.expectedTree
327
+ });
234
328
  if (committed.status === 'failed')
235
329
  return {
236
330
  ...committed,
@@ -241,42 +335,42 @@ function executeUnlocked(input, task, mode) {
241
335
  snapshot: committed.snapshot
242
336
  };
243
337
  const warnings = [];
244
- if (orchestrationCompletion) {
245
- try {
246
- commitOrchestrationStageCompletion(orchestrationCompletion);
247
- }
248
- catch (error) {
249
- warnings.push(warning('ORCHESTRATION_COMPLETION_FAILED', error instanceof Error ? error.message : String(error), true, 'orchestration', task?.taskId ?? 'orchestration', 'ACTION_REQUIRED'));
250
- }
251
- }
252
338
  let status = committed.status;
253
339
  let result = committed.status === 'applied' ? 'committed' : 'no_op';
254
- const target = `${input.push.remote}:${input.push.refs[0]}`;
255
- const decision = commitPushDecision({ branch }, target);
256
- if (!decision.shouldPush) {
257
- warnings.push(decision.warning);
258
- }
259
- else {
260
- const pushed = pushGitRefs({
261
- cwd: input.cwd,
262
- remote: input.push.remote,
263
- refs: input.push.refs,
264
- expectedSha: committed.snapshot?.head ?? gitText(input.cwd, ['rev-parse', 'HEAD'])
265
- });
266
- if (pushed.status !== 'applied')
267
- warnings.push(warning('COMMIT_PUSH_FAILED', pushed.error?.message ?? 'Git push failed', true, 'push', target, 'ACTION_REQUIRED'));
340
+ if (delivery.mode === 'push') {
341
+ const target = `${delivery.remote}:${delivery.refs[0]}`;
342
+ const decision = commitPushDecision({ branch }, target);
343
+ if (!decision.shouldPush) {
344
+ warnings.push(decision.warning);
345
+ }
346
+ else {
347
+ const pushed = pushGitRefs({
348
+ cwd: input.cwd, remote: delivery.remote, refs: delivery.refs,
349
+ expectedSha: committed.snapshot?.head ?? gitText(input.cwd, ['rev-parse', 'HEAD'])
350
+ });
351
+ if (pushed.status !== 'applied')
352
+ warnings.push(warning('COMMIT_PUSH_FAILED', pushed.error?.message ?? 'Git push failed', true, 'push', target, 'ACTION_REQUIRED'));
353
+ }
268
354
  }
269
355
  if (warnings.length > 0) {
270
356
  status = 'applied';
271
357
  result = 'committed_with_warnings';
272
358
  }
273
359
  if (task && (committed.status === 'applied' || committed.status === 'no-op')) {
274
- const taskWarning = syncTaskCommit(input, task, committed.snapshot?.head ?? gitText(task.repoRoot, ['rev-parse', 'HEAD']));
360
+ const committedHead = checkpointCommittedHead ?? committed.snapshot?.head ?? gitText(task.repoRoot, ['rev-parse', 'HEAD']);
361
+ const taskWarning = syncTaskCommit(input, task, committedHead);
275
362
  if (taskWarning) {
276
363
  warnings.push(taskWarning);
277
364
  status = 'applied';
278
365
  result = 'committed_with_warnings';
279
366
  }
367
+ if (delivery.mode === 'local' && !taskWarning && pendingIntent && checkpointCommittedHead) {
368
+ const synced = updateCheckpointIntent(pendingIntent, {
369
+ state: 'synced', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
370
+ });
371
+ writeCheckpointIntent(task.repoRoot, synced);
372
+ removeCheckpointIntent(task.repoRoot, task.taskId);
373
+ }
280
374
  }
281
375
  return {
282
376
  status,
@@ -4,6 +4,8 @@ import path from 'node:path';
4
4
  import { isValidAgentInfraVersion, VERSION } from '../version.js';
5
5
  import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
6
6
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
7
+ import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.js';
8
+ import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.js';
7
9
  const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'];
8
10
  const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'];
9
11
  const PRIORITIES = ['Urgent', 'High', 'Medium', 'Low'];
@@ -15,12 +17,15 @@ const TASK_INPUT_KEYS = [
15
17
  function payloadError(message) {
16
18
  throw new Error(`TASK_CREATE_PAYLOAD_INVALID: ${message}`);
17
19
  }
18
- function exactKeys(value, expected, label) {
20
+ function exactKeys(value, expected, label, optional = []) {
19
21
  const actual = Object.keys(value).sort();
20
22
  const wanted = [...expected].sort();
21
- if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
23
+ const allowed = new Set([...expected, ...optional]);
24
+ if (actual.some((key) => !allowed.has(key))) {
22
25
  payloadError(`${label} has unknown or missing fields`);
23
26
  }
27
+ if (expected.some((key) => !actual.includes(key)))
28
+ payloadError(`${label} has unknown or missing fields`);
24
29
  }
25
30
  function cleanString(value, label, maxBytes, oneLine = false) {
26
31
  if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > maxBytes) {
@@ -43,7 +48,7 @@ function validateTaskCreateCandidate(value) {
43
48
  exactKeys(candidate, [
44
49
  'version', 'idempotencyKey', 'agent', 'title', 'type', 'branchSlug',
45
50
  'priority', 'effort', 'description', 'taskInput'
46
- ], 'candidate');
51
+ ], 'candidate', ['deliveryRemote', 'deliveryBaseRef']);
47
52
  if (candidate.version !== 1)
48
53
  payloadError('version must be 1');
49
54
  const idempotencyKey = cleanString(candidate.idempotencyKey, 'idempotencyKey', 64, true);
@@ -58,6 +63,16 @@ function validateTaskCreateCandidate(value) {
58
63
  payloadError('branchSlug must be lowercase ASCII kebab-case with length 3-64');
59
64
  }
60
65
  const description = cleanString(candidate.description, 'description', 8 * 1024);
66
+ const deliveryRemote = candidate.deliveryRemote === undefined
67
+ ? undefined
68
+ : cleanString(candidate.deliveryRemote, 'deliveryRemote', 200, true);
69
+ const deliveryBaseRef = candidate.deliveryBaseRef === undefined
70
+ ? undefined
71
+ : cleanString(candidate.deliveryBaseRef, 'deliveryBaseRef', 200, true);
72
+ if (deliveryRemote !== undefined && !validateRemote(deliveryRemote))
73
+ payloadError('deliveryRemote is invalid');
74
+ if (deliveryBaseRef !== undefined && !validateBaseRef(deliveryBaseRef))
75
+ payloadError('deliveryBaseRef is invalid');
61
76
  if (!candidate.taskInput || typeof candidate.taskInput !== 'object' || Array.isArray(candidate.taskInput)) {
62
77
  payloadError('taskInput must be an object');
63
78
  }
@@ -79,6 +94,8 @@ function validateTaskCreateCandidate(value) {
79
94
  priority: enumValue(candidate.priority, PRIORITIES, 'priority'),
80
95
  effort: enumValue(candidate.effort, EFFORTS, 'effort'),
81
96
  description,
97
+ ...(deliveryRemote === undefined ? {} : { deliveryRemote }),
98
+ ...(deliveryBaseRef === undefined ? {} : { deliveryBaseRef }),
82
99
  taskInput
83
100
  };
84
101
  }
@@ -94,6 +111,8 @@ function canonicalTaskCreateCandidate(candidate) {
94
111
  priority: valid.priority,
95
112
  effort: valid.effort,
96
113
  description: valid.description,
114
+ deliveryRemote: valid.deliveryRemote ?? null,
115
+ deliveryBaseRef: valid.deliveryBaseRef ?? null,
97
116
  taskInput: Object.fromEntries(TASK_INPUT_KEYS.map((key) => [key, valid.taskInput[key]]))
98
117
  });
99
118
  }
@@ -145,7 +164,11 @@ function renderTask(params) {
145
164
  'target_date:',
146
165
  'current_step: requirement-analysis',
147
166
  `assigned_to: ${candidate.agent}`,
148
- 'pr_status: pending',
167
+ `pr_delivery_fact: ${JSON.stringify(encodePrDeliveryFact(buildUnboundFact()))}`,
168
+ `delivery_remote: ${candidate.deliveryRemote ?? params.delivery.remote}`,
169
+ `delivery_base_ref: ${candidate.deliveryBaseRef ?? params.delivery.baseRef}`,
170
+ 'checkpoint_commit:',
171
+ 'delivery_remote_head:',
149
172
  `task_create_key_digest: ${params.keyDigest}`,
150
173
  `task_create_candidate_digest: ${params.candidateDigest}`,
151
174
  '---'
@@ -175,7 +198,10 @@ function readProject(repoRoot) {
175
198
  if (typeof config.project !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.project)) {
176
199
  throw new Error('TASK_CREATE_CONFIG_INVALID: project is missing or invalid');
177
200
  }
178
- return config.project;
201
+ const delivery = readDeliveryDefaults(repoRoot);
202
+ if (!delivery.ok)
203
+ throw new Error(`TASK_CREATE_CONFIG_INVALID: ${delivery.message}`);
204
+ return { project: config.project, delivery: delivery.value };
179
205
  }
180
206
  function assertRealDirectory(directory, code) {
181
207
  const stat = fs.lstatSync(directory);
@@ -269,7 +295,7 @@ function createLocalTask(value, options) {
269
295
  ensureRealDirectory(path.join(repoRoot, '.agents'), 'TASK_CREATE_WORKSPACE_INVALID');
270
296
  ensureRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
271
297
  ensureRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
272
- const project = readProject(repoRoot);
298
+ const projectConfig = readProject(repoRoot);
273
299
  const canonical = canonicalTaskCreateCandidate(candidate);
274
300
  const candidateDigest = sha256(canonical);
275
301
  const keyDigest = sha256(candidate.idempotencyKey);
@@ -312,7 +338,7 @@ function createLocalTask(value, options) {
312
338
  if (!templateStat.isFile() || templateStat.isSymbolicLink())
313
339
  throw new Error('TASK_CREATE_TEMPLATE_INVALID');
314
340
  const rendered = renderTask({
315
- template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project, timestamp,
341
+ template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project: projectConfig.project, delivery: projectConfig.delivery, timestamp,
316
342
  agentInfraVersion, keyDigest, candidateDigest
317
343
  });
318
344
  const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
@@ -109,31 +109,8 @@ function resolveDecisionDetail(content, rowId, anchor) {
109
109
  }
110
110
  return { status: 'found', block: matches[0] };
111
111
  }
112
- const INFORMAL_SHORT_HEADING = /^(?:AN|PL|CD|HD)-[1-9]\d*[::]\s*(?:上一轮)?简短复核$/i;
113
- const INFORMAL_SHORT_FRAGMENT = /^简短(?:结论|说明)$/;
114
- function isInformalShortBlock(block) {
115
- if (block.canonical || block.content.length > 600)
116
- return false;
117
- if (!INFORMAL_SHORT_HEADING.test(block.heading))
118
- return false;
119
- const scanned = scanVisibleMarkdown(block.content);
120
- const bodyLines = scanned.lines
121
- .filter((line) => !scanned.headings.some((heading) => heading.start === line.start) && line.text.trim())
122
- .map((line) => line.text);
123
- if (bodyLines.length === 0 || bodyLines.length > 2)
124
- return false;
125
- const fragments = bodyLines.map((line) => line.match(/^\s*[-*]\s+([^\s].*?)\s*$/)?.[1] ?? null);
126
- if (fragments.some((fragment) => fragment === null))
127
- return false;
128
- return fragments.every((fragment) => INFORMAL_SHORT_FRAGMENT.test(fragment));
129
- }
130
- function hasDirectPrecedingAnchor(block, scanned) {
131
- return scanned.anchors.some((anchor) => (anchor.end < block.start
132
- && !scanned.lines.some((line) => line.start > anchor.end && line.start < block.start && line.text.trim())));
133
- }
134
112
  function inspectDecisionDetailDuplicates(content) {
135
113
  const blocks = parseDecisionDetailBlocks(content);
136
- const scanned = scanVisibleMarkdown(content);
137
114
  const grouped = new Map();
138
115
  for (const block of blocks) {
139
116
  const key = block.id.toUpperCase();
@@ -145,15 +122,13 @@ function inspectDecisionDetailDuplicates(content) {
145
122
  for (const group of grouped.values()) {
146
123
  if (group.length < 2)
147
124
  continue;
148
- const canonical = group.filter((block) => block.canonical);
149
- const repairable = canonical.length === 1 && group.every((block) => (block.canonical || (!hasDirectPrecedingAnchor(block, scanned) && isInformalShortBlock(block))));
150
- duplicates.push({ id: canonical[0]?.id ?? group[0].id, blocks: group, repairable });
125
+ duplicates.push({ id: group[0].id, blocks: group });
151
126
  }
152
127
  if (duplicates.length === 0)
153
128
  return { ok: true, blocks };
154
129
  const summary = duplicates.map((duplicate) => {
155
130
  const types = duplicate.blocks.map((block) => block.canonical ? 'canonical' : 'informal').join(', ');
156
- return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types}${duplicate.repairable ? '; safe repair available' : ''})`;
131
+ return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types})`;
157
132
  }).join(', ');
158
133
  return {
159
134
  ok: false,
@@ -163,26 +138,6 @@ function inspectDecisionDetailDuplicates(content) {
163
138
  duplicates
164
139
  };
165
140
  }
166
- function repairDecisionDetailDuplicates(content) {
167
- const inspection = inspectDecisionDetailDuplicates(content);
168
- if (inspection.ok)
169
- return { ok: true, changed: false, content };
170
- const unsafe = inspection.duplicates.filter((duplicate) => !duplicate.repairable);
171
- if (unsafe.length > 0) {
172
- return {
173
- ok: false,
174
- code: 'DECISION_DETAIL_AMBIGUOUS',
175
- message: `Cannot safely repair duplicate decision detail ids: ${unsafe.map((duplicate) => duplicate.id).join(', ')}`,
176
- content
177
- };
178
- }
179
- const removals = inspection.duplicates.flatMap((duplicate) => duplicate.blocks.filter((block) => !block.canonical));
180
- let repaired = content;
181
- for (const block of removals.sort((left, right) => right.start - left.start)) {
182
- repaired = repaired.slice(0, block.start) + repaired.slice(block.end);
183
- }
184
- return { ok: true, changed: repaired !== content, content: repaired };
185
- }
186
141
  const FIELD_KEYS = {
187
142
  '为什么现在要决定': 'why', 'why decide now': 'why', 'why a decision is needed now': 'why',
188
143
  '要决定什么': 'decision', 'what to decide': 'decision', 'what needs a decision': 'decision',
@@ -259,5 +214,5 @@ function normalizeDecisionDetailForDisplay(block) {
259
214
  ordered.push(byKey.get('implementation'));
260
215
  return [body.slice(heading.start, heading.end).trim(), ...ordered].join('\n\n').trim();
261
216
  }
262
- export { inspectDecisionDetailDuplicates, normalizeDecisionDetailForDisplay, parseDecisionDetailBlocks, parseDecisionEvidence, repairDecisionDetailDuplicates, resolveDecisionDetail };
217
+ export { inspectDecisionDetailDuplicates, normalizeDecisionDetailForDisplay, parseDecisionDetailBlocks, parseDecisionEvidence, resolveDecisionDetail };
263
218
  //# sourceMappingURL=decision-details.js.map