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