@fitlab-ai/agent-infra 0.9.1 → 0.9.2

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 (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
 
8
8
  import {
9
9
  extractReviewBaseline,
10
+ extractReviewDiffBase,
10
11
  extractReviewDiffFingerprint,
11
12
  extractReviewedSnapshotTree,
12
13
  findAuthoritativeReviewCodeArtifact,
@@ -17,9 +18,12 @@ import {
17
18
  import { check as checkPlatformSync } from "../platform/verification-sync.ts";
18
19
  import { check as checkRequiredChecks } from "../platform/verification-required.ts";
19
20
  import { inspectPlatformPullRequest } from "../platform/pull-requests.ts";
21
+ import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.ts";
20
22
  import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.ts";
23
+ import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.ts";
21
24
  import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
22
- import { parseLedger } from "./ledger.ts";
25
+ import { parseLedger, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
26
+ import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
23
27
  import { loadVerificationConfig } from "./verification-config.ts";
24
28
  import { snapshotReview } from "../git/review-snapshot.ts";
25
29
 
@@ -110,10 +114,14 @@ function runCheck(type: any, context: any, shared: any): any {
110
114
  return checkCompletionChecklist(context);
111
115
  case "review-ledger":
112
116
  return checkReviewLedger(context);
117
+ case "review-summary":
118
+ return checkReviewSummary(context);
113
119
  case "review-fact":
114
120
  return checkReviewFact(context);
115
121
  case "post-review-commit":
116
122
  return checkPostReviewCommit(context);
123
+ case "orchestration-state":
124
+ return checkOrchestrationState(context);
117
125
  default: {
118
126
  const adapter = PLATFORM_ADAPTERS[type];
119
127
  if (!adapter) {
@@ -129,6 +137,32 @@ function runCheck(type: any, context: any, shared: any): any {
129
137
  }
130
138
  }
131
139
 
140
+ function checkOrchestrationState({ taskDir }: any): any {
141
+ const file = path.join(taskDir, 'orchestration.json');
142
+ const stat = safeStat(file);
143
+ if (!stat?.isFile()) return failResult('orchestration-state', 'orchestration.json is missing');
144
+ let run: any;
145
+ try {
146
+ run = JSON.parse(fs.readFileSync(file, 'utf8'));
147
+ } catch (error) {
148
+ return failResult('orchestration-state', `Invalid orchestration.json: ${String(error)}`);
149
+ }
150
+ if (run.schemaVersion !== 1 || !['paused', 'completed'].includes(run.status)) {
151
+ return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
152
+ }
153
+ if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
154
+ return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
155
+ }
156
+ if (run.status === 'completed') {
157
+ if (run.pendingDelegation !== null || !run.commitAuthorization?.consumedAt) {
158
+ return failResult('orchestration-state', 'Completed run must consume commit authorization and clear pending delegation');
159
+ }
160
+ } else if (run.pendingDelegation?.status === 'sealed') {
161
+ return failResult('orchestration-state', 'Paused run has a sealed delegation that could still advance');
162
+ }
163
+ return passResult('orchestration-state', `Orchestration run is safely ${run.status}`);
164
+ }
165
+
132
166
  // === Check Functions ===
133
167
 
134
168
  function checkTaskMeta({ taskDir, config }: any): any {
@@ -313,6 +347,43 @@ function checkArtifact({ taskDir, config, artifactFile }: any): any {
313
347
  );
314
348
  }
315
349
 
350
+ function checkReviewSummary({ taskDir, config, artifactFile }: any): any {
351
+ const stage = String(config.stage || "");
352
+ if (!["analysis", "plan", "code"].includes(stage)) {
353
+ return failResult("review-summary", `Invalid review stage '${stage}'`);
354
+ }
355
+ const resolvedArtifact = resolveArtifactPath(taskDir, "", artifactFile);
356
+ if (!resolvedArtifact.ok || !safeStat(resolvedArtifact.path)?.isFile()) {
357
+ return failResult("review-summary", resolvedArtifact.message || "Review artifact is missing");
358
+ }
359
+ const parsed = parseReviewSummary(fs.readFileSync(resolvedArtifact.path, "utf8"));
360
+ if (!parsed.ok) return failResult("review-summary", `${parsed.code}: ${parsed.message}`);
361
+ if (!parsed.summary.counts) {
362
+ return failResult("review-summary", "Review summary finding counts are not finalized");
363
+ }
364
+ const task = loadTask(taskDir);
365
+ if (!task.ok) return failResult("review-summary", task.message);
366
+ let rows;
367
+ try {
368
+ rows = parseLedger(task.content);
369
+ } catch (error) {
370
+ return failResult("review-summary", `Invalid disagreement ledger: ${String(error)}`);
371
+ }
372
+ const invalid = validateLedgerRows(rows);
373
+ if (invalid) return failResult("review-summary", `${invalid.code}: ${invalid.message}`);
374
+ const expected = summarizeLedgerStage(rows, stage as "analysis" | "plan" | "code").unresolvedFindingCounts;
375
+ if (!equalCounts(parsed.summary.counts, expected)) {
376
+ return failResult(
377
+ "review-summary",
378
+ `Review summary counts ${JSON.stringify(parsed.summary.counts)} do not match ledger ${JSON.stringify(expected)}`
379
+ );
380
+ }
381
+ return passResult(
382
+ "review-summary",
383
+ `${path.basename(resolvedArtifact.path)} finding counts match the ${stage} ledger`
384
+ );
385
+ }
386
+
316
387
  function checkImplementationInput({ taskDir, artifactFile }: any): any {
317
388
  const task = loadTask(taskDir);
318
389
  if (!task.ok) return failResult("implementation-input", task.message);
@@ -683,8 +754,51 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
683
754
  }
684
755
 
685
756
  const task = loadTask(taskDir);
686
- const content = fs.readFileSync(reviewArtifact.path!, "utf8");
757
+ if (!task.ok) return failResult("post-review-commit", task.message);
687
758
  const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
759
+ const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
760
+ const hasPullRequest = Number(task.metadata.pr_number) > 0;
761
+ let inspected: ReturnType<typeof inspectPlatformPullRequest> | null = null;
762
+ if (hasPullRequest) {
763
+ inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
764
+ if (!inspected.pullRequest) {
765
+ const message = inspected.error?.message || "PR merge snapshot is unavailable";
766
+ const code = inspected.error?.code || "PR_INSPECTION_INVALID";
767
+ return inspected.status === "blocked"
768
+ ? blockedResult("post-review-commit", message, code)
769
+ : failResult("post-review-commit", message, code);
770
+ }
771
+ if (
772
+ inspected.pullRequest.state === "closed" &&
773
+ inspected.pullRequest.mergedAt &&
774
+ inspected.pullRequest.mergeCommitSha
775
+ ) {
776
+ const relation = resolveMaterializedReviewedHeadRelation({
777
+ cwd: gitRoot,
778
+ platformType: inspected.platform.type,
779
+ lastReviewedCommit,
780
+ pullRequest: inspected.pullRequest
781
+ });
782
+ if (relation.status === "merged-equivalent") {
783
+ return passResult(
784
+ "post-review-commit",
785
+ `Reviewed PR head is content-equivalent to squash merge ${relation.mergeCommit.slice(0, 8)} on target ${relation.comparisonHead.slice(0, 8)}`
786
+ );
787
+ }
788
+ if (relation.status === "blocked") {
789
+ return blockedResult("post-review-commit", relation.message, relation.code);
790
+ }
791
+ if (relation.status === "strict") {
792
+ return failResult(
793
+ "post-review-commit",
794
+ "Merged PR evidence did not establish squash merge equivalence",
795
+ "PR_MERGE_IDENTITY_INVALID"
796
+ );
797
+ }
798
+ return failResult("post-review-commit", relation.message, relation.code);
799
+ }
800
+ }
801
+
688
802
  const baselineSource = resolvePostReviewBaseline({
689
803
  gitRoot,
690
804
  lastReviewedCommit,
@@ -695,7 +809,6 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
695
809
  }
696
810
 
697
811
  const sha = baselineSource.sha;
698
- const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
699
812
  let commits;
700
813
  try {
701
814
  const out = execFileSync("git", ["-C", gitRoot, "rev-list", `${sha}..HEAD`, "--", ...globs], { encoding: "utf8" });
@@ -708,42 +821,50 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
708
821
  return passResult("post-review-commit", `No post-review commits to code/rule paths since ${sha.slice(0, 8)}`);
709
822
  }
710
823
 
711
- if (task.ok && Number(task.metadata.pr_number) > 0) {
712
- const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
713
- if (inspected.pullRequest) {
714
- let localHead = "";
715
- try {
716
- localHead = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
717
- } catch {
718
- return blockedResult("post-review-commit", "Unable to resolve the local HEAD");
719
- }
824
+ let localHead = "";
825
+ try {
826
+ localHead = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
827
+ } catch {
828
+ return blockedResult("post-review-commit", "Unable to resolve the local HEAD");
829
+ }
830
+
831
+ if (hasPullRequest) {
832
+ if (inspected?.pullRequest) {
720
833
  const relation = resolveReviewedHeadRelation({
721
834
  gitRoot,
722
- localHead,
835
+ comparisonHead: localHead,
723
836
  lastReviewedCommit: sha,
724
- pullRequest: inspected.pullRequest,
725
- pathspecs: globs
837
+ pullRequest: inspected.pullRequest
726
838
  });
727
839
  if (relation.status === "merged-equivalent") {
728
840
  return passResult("post-review-commit", `Reviewed PR head is content-equivalent to squash merge ${relation.mergeCommit.slice(0, 8)}`);
729
841
  }
730
842
  if (relation.status === "blocked") {
731
- return blockedResult("post-review-commit", relation.message);
843
+ return blockedResult("post-review-commit", relation.message, relation.code);
732
844
  }
733
- } else if (inspected.status === "blocked") {
734
- return blockedResult("post-review-commit", inspected.error?.message || "PR merge snapshot is unavailable");
845
+ }
846
+ } else if (commits.length === 1) {
847
+ const relation = resolveLocalReviewedCommitRelation({
848
+ gitRoot,
849
+ localHead,
850
+ lastReviewedCommit: sha,
851
+ rewrittenCommit: commits[0]!,
852
+ pathspecs: globs
853
+ });
854
+ if (relation.status === "local-equivalent") {
855
+ return passResult(
856
+ "post-review-commit",
857
+ `Reviewed commit is content-equivalent to local rewrite ${relation.rewrittenCommit.slice(0, 8)}`
858
+ );
859
+ }
860
+ if (relation.status === "blocked") {
861
+ return blockedResult("post-review-commit", relation.message);
735
862
  }
736
863
  }
737
864
 
738
- let exempt = false;
739
- try {
740
- exempt = task.ok && parseLedger(task.content).some(
741
- (row) => row.stage === POST_REVIEW_COMMIT_STAGE && row.status === "human-decided"
742
- );
743
- } catch {
744
- return failResult("post-review-commit", "Invalid disagreement ledger");
745
- }
746
- if (exempt) {
865
+ const exemption = resolvePostReviewExemption(task.content);
866
+ if (!exemption.ok) return failResult("post-review-commit", exemption.message);
867
+ if (exemption.exempt) {
747
868
  return passResult(
748
869
  "post-review-commit",
749
870
  `${commits.length} post-review commit(s) covered by a human-decided exemption`
@@ -756,6 +877,21 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
756
877
  );
757
878
  }
758
879
 
880
+ function resolvePostReviewExemption(content: string):
881
+ | { ok: true; exempt: boolean }
882
+ | { ok: false; message: string } {
883
+ try {
884
+ return {
885
+ ok: true,
886
+ exempt: parseLedger(content).some(
887
+ (row) => row.stage === POST_REVIEW_COMMIT_STAGE && row.status === "human-decided"
888
+ )
889
+ };
890
+ } catch {
891
+ return { ok: false, message: "Invalid disagreement ledger" };
892
+ }
893
+ }
894
+
759
895
  function checkReviewFact({ taskDir, artifactFile }: any): any {
760
896
  const resolvedArtifact = resolveArtifactPath(
761
897
  taskDir,
@@ -774,6 +910,7 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
774
910
  const content = fs.readFileSync(resolvedArtifact.path, "utf8");
775
911
  const verdict = parseReviewVerdict(content);
776
912
  const reviewBaseline = extractReviewBaseline(content);
913
+ const reviewDiffBase = extractReviewDiffBase(content);
777
914
  const reviewedFingerprint = extractReviewDiffFingerprint(content);
778
915
  const reviewedTree = extractReviewedSnapshotTree(content);
779
916
 
@@ -784,10 +921,16 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
784
921
  let gitRoot;
785
922
  let head;
786
923
  let baseline;
924
+ let diffBase;
787
925
  try {
788
926
  gitRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
789
927
  head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
790
928
  baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewBaseline}^{commit}`], { encoding: "utf8" }).trim();
929
+ diffBase = execFileSync(
930
+ "git",
931
+ ["-C", gitRoot, "rev-parse", `${reviewDiffBase || reviewBaseline}^{commit}`],
932
+ { encoding: "utf8" }
933
+ ).trim();
791
934
  } catch {
792
935
  return blockedResult(
793
936
  "review-fact",
@@ -804,7 +947,13 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
804
947
 
805
948
  let actualSnapshot;
806
949
  try {
807
- actualSnapshot = snapshotReview({ cwd: gitRoot, mode: "worktree", baseline, globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repoRoot)) });
950
+ actualSnapshot = snapshotReview({
951
+ cwd: gitRoot,
952
+ mode: "worktree",
953
+ baseline,
954
+ diffBase,
955
+ globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repoRoot))
956
+ });
808
957
  } catch {
809
958
  return blockedResult(
810
959
  "review-fact",
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  extractReviewBaseline,
3
+ extractReviewDiffBase,
3
4
  extractReviewDiffFingerprint,
4
5
  extractReviewedSnapshotTree,
5
6
  findAuthoritativeReviewCodeArtifact,
@@ -16,7 +16,8 @@ type VerificationEvent =
16
16
  | 'complete-task.preflight' | 'complete-task.completed'
17
17
  | 'create-pr.completed' | 'create-task.completed'
18
18
  | 'import-codescan.completed' | 'import-dependabot.completed'
19
- | 'import-issue.completed' | 'watch-pr.completed';
19
+ | 'import-issue.completed' | 'watch-pr.completed'
20
+ | 'run-task.paused' | 'run-task.completed';
20
21
  type VerificationSpec = {
21
22
  skill: string;
22
23
  expectedState: 'active' | 'blocked' | 'completed';
@@ -70,7 +71,9 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
70
71
  'import-codescan.completed': gate('import-codescan', 'active'),
71
72
  'import-dependabot.completed': gate('import-dependabot', 'active'),
72
73
  'import-issue.completed': gate('import-issue', 'active'),
73
- 'watch-pr.completed': gate('watch-pr', 'active')
74
+ 'watch-pr.completed': gate('watch-pr', 'active'),
75
+ 'run-task.paused': { skill: 'run-task', expectedState: 'active', mode: 'checks', checks: ['orchestration-state'] },
76
+ 'run-task.completed': { skill: 'run-task', expectedState: 'active', mode: 'checks', checks: ['orchestration-state'] }
74
77
  };
75
78
 
76
79
  function failure(request: { taskRef: string; event: string; artifact?: string }, code: string, message: string, extra: Partial<TaskVerificationResult> = {}): TaskVerificationResult {
@@ -0,0 +1,36 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ function git(repoRoot: string, indexFile: string | null, args: string[]): string {
7
+ const env = indexFile ? { ...process.env, GIT_INDEX_FILE: indexFile } : process.env;
8
+ const result = spawnSync('git', args, { cwd: repoRoot, env, encoding: 'utf8' });
9
+ if (result.status !== 0) {
10
+ throw new Error(String(result.stderr || result.stdout || `git ${args[0]} failed`).trim());
11
+ }
12
+ return result.stdout;
13
+ }
14
+
15
+ function captureWorkspaceSnapshot(repoRoot: string): string {
16
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-orchestration-index-'));
17
+ const indexFile = path.join(tempDir, 'index');
18
+ try {
19
+ git(repoRoot, indexFile, ['read-tree', 'HEAD']);
20
+ git(repoRoot, indexFile, ['add', '-A', '--', ':/']);
21
+ const workspace = path.join(repoRoot, '.agents', 'workspace', 'active');
22
+ if (fs.existsSync(workspace)) {
23
+ git(repoRoot, indexFile, ['add', '-f', '--', '.agents/workspace/active']);
24
+ }
25
+ return git(repoRoot, indexFile, ['write-tree']).trim();
26
+ } finally {
27
+ fs.rmSync(tempDir, { recursive: true, force: true });
28
+ }
29
+ }
30
+
31
+ function diffWorkspaceSnapshots(repoRoot: string, before: string, after: string): string[] {
32
+ const output = git(repoRoot, null, ['diff', '--name-only', '-z', before, after]);
33
+ return output.split('\0').filter(Boolean);
34
+ }
35
+
36
+ export { captureWorkspaceSnapshot, diffWorkspaceSnapshots };
package/lib/update.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { normalizeAgentClients } from './agent-clients/config.ts';
4
+ import {
5
+ applyAgentClientReconciliation,
6
+ planAgentClientReconciliation
7
+ } from './agent-clients/reconcile.ts';
3
8
  import { info, ok, err } from './log.ts';
4
9
  import { resolveTemplateDir } from './paths.ts';
5
- import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.ts';
6
- import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from './builtin-tuis.ts';
7
- import type { BuiltinTUIId } from './builtin-tuis.ts';
10
+ import { renderFile, copySkillDir } from './render.ts';
8
11
 
9
12
  type FileRegistry = {
10
13
  managed: string[];
@@ -24,6 +27,8 @@ type UpdateConfig = {
24
27
  labels?: Record<string, unknown>;
25
28
  files?: Partial<FileRegistry>;
26
29
  tuis?: unknown;
30
+ agentClients?: unknown;
31
+ customTUIs?: unknown;
27
32
  };
28
33
 
29
34
  type Defaults = {
@@ -40,9 +45,6 @@ const defaults = JSON.parse(
40
45
 
41
46
  const CONFIG_DIR = '.agents';
42
47
  const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
43
- const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
44
- const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
45
- const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
46
48
 
47
49
  // One-time migration of the legacy project-level PR switch to the three-state
48
50
  // `prFlow` preference. `true` (the old default / "PR flow on") maps to the
@@ -63,83 +65,6 @@ function migratePrFlow(config: UpdateConfig): 'required' | 'disabled' | null {
63
65
  return null;
64
66
  }
65
67
 
66
- function isLegacyDefaultSandboxTools(value: unknown): value is string[] {
67
- if (!Array.isArray(value) || value.length !== LEGACY_DEFAULT_SANDBOX_TOOLS.length) {
68
- return false;
69
- }
70
- const tools = new Set(value);
71
- return LEGACY_DEFAULT_SANDBOX_TOOLS.every((tool) => tools.has(tool));
72
- }
73
-
74
- function migrateSandboxTools(config: UpdateConfig): boolean {
75
- const tools = config.sandbox?.tools;
76
- if (!isLegacyDefaultSandboxTools(tools)) {
77
- return false;
78
- }
79
- config.sandbox = {
80
- ...config.sandbox,
81
- tools: [...DEFAULT_SANDBOX_TOOLS]
82
- };
83
- return true;
84
- }
85
-
86
- function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
87
- const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
88
- if (!top.startsWith('.')) return false;
89
-
90
- const candidate = top.slice(1);
91
- if (!KNOWN_PLATFORMS.has(candidate)) return false;
92
- return candidate !== platformType;
93
- }
94
-
95
- function syncFileRegistry(config: UpdateConfig, platformType: string, enabledTUIs: Set<BuiltinTUIId>) {
96
- config.files ||= {};
97
- const before = JSON.stringify({
98
- files: {
99
- managed: config.files.managed || [],
100
- merged: config.files.merged || [],
101
- ejected: config.files.ejected || []
102
- }
103
- });
104
- config.files.managed = config.files.managed || [];
105
- config.files.merged = config.files.merged || [];
106
- config.files.ejected = config.files.ejected || [];
107
-
108
- const allExisting = [
109
- ...config.files.managed,
110
- ...config.files.merged,
111
- ...config.files.ejected
112
- ];
113
- const added: Pick<FileRegistry, 'managed' | 'merged'> = { managed: [], merged: [] };
114
-
115
- for (const entry of defaults.files.managed) {
116
- if (isPathOwnedByOtherPlatform(entry, platformType)) continue;
117
- if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) continue;
118
- if (!allExisting.includes(entry)) {
119
- config.files.managed.push(entry);
120
- added.managed.push(entry);
121
- }
122
- }
123
- for (const entry of defaults.files.merged) {
124
- if (isPathOwnedByOtherPlatform(entry, platformType)) continue;
125
- if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) continue;
126
- if (!allExisting.includes(entry)) {
127
- config.files.merged.push(entry);
128
- added.merged.push(entry);
129
- }
130
- }
131
-
132
- const after = JSON.stringify({
133
- files: {
134
- managed: config.files.managed,
135
- merged: config.files.merged,
136
- ejected: config.files.ejected
137
- }
138
- });
139
-
140
- return { added, changed: before !== after };
141
- }
142
-
143
68
  async function cmdUpdate(): Promise<void> {
144
69
  console.log('');
145
70
  console.log(' ai update');
@@ -167,24 +92,36 @@ async function cmdUpdate(): Promise<void> {
167
92
  const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) as UpdateConfig;
168
93
  const { project, org, language } = config;
169
94
  const platformType = config.platform?.type || defaults.platform.type;
170
- const enabledTUIs = resolveEnabledTUIs(config.tuis);
171
95
  const replacements = { project, org };
172
96
 
97
+ const normalizedOriginal = normalizeAgentClients(config);
98
+ const currentRegistry: FileRegistry = {
99
+ managed: config.files?.managed || [],
100
+ merged: config.files?.merged || [],
101
+ ejected: config.files?.ejected || []
102
+ };
103
+ const platformAdded = !config.platform;
104
+ const sandboxAdded = !config.sandbox;
105
+ const taskAdded = !config.task;
106
+ const labelsAdded = !config.labels;
107
+ const prFlowMigrated = migratePrFlow(config);
108
+ if (platformAdded) config.platform = structuredClone(defaults.platform);
109
+ if (sandboxAdded) config.sandbox = structuredClone(defaults.sandbox);
110
+ if (taskAdded) config.task = structuredClone(defaults.task);
111
+ if (labelsAdded) config.labels = structuredClone(defaults.labels);
112
+
113
+ const workflowPlan = planAgentClientReconciliation({
114
+ config: config as unknown as Record<string, unknown>,
115
+ mutation: { type: 'replace-state', state: normalizedOriginal.state },
116
+ projectRoot: process.cwd(),
117
+ templateRoot: templateDir,
118
+ platformType,
119
+ language
120
+ });
121
+
173
122
  info(`Updating seed files for: ${project}`);
174
123
  console.log('');
175
124
 
176
- // select language-specific template filenames
177
- let claudeSrc, geminiSrc, opencodeSrc;
178
- if (language === 'zh-CN') {
179
- claudeSrc = 'update-agent-infra.zh-CN.md';
180
- geminiSrc = 'update-agent-infra.zh-CN.toml';
181
- opencodeSrc = 'update-agent-infra.zh-CN.md';
182
- } else {
183
- claudeSrc = 'update-agent-infra.en.md';
184
- geminiSrc = 'update-agent-infra.en.toml';
185
- opencodeSrc = 'update-agent-infra.en.md';
186
- }
187
-
188
125
  // update skill
189
126
  copySkillDir(
190
127
  path.join(templateDir, '.agents', 'skills', 'update-agent-infra'),
@@ -206,76 +143,19 @@ async function cmdUpdate(): Promise<void> {
206
143
  // Ignore missing legacy script from pre-ESM installs.
207
144
  }
208
145
 
209
- // update Claude command (only if enabled)
210
- if (enabledTUIs.has('claude-code')) {
211
- renderFile(
212
- path.join(templateDir, '.claude', 'commands', claudeSrc),
213
- path.join('.claude', 'commands', 'update-agent-infra.md'),
214
- replacements
215
- );
216
- ok('Updated .claude/commands/update-agent-infra.md');
217
- }
218
-
219
- // update Gemini command (only if enabled)
220
- if (enabledTUIs.has('gemini-cli')) {
221
- renderFile(
222
- path.join(templateDir, '.gemini', 'commands', '_project_', geminiSrc),
223
- path.join('.gemini', 'commands', project, 'update-agent-infra.toml'),
224
- replacements
225
- );
226
- ok(`Updated .gemini/commands/${project}/update-agent-infra.toml`);
227
- }
228
-
229
- // update OpenCode command (only if enabled)
230
- if (enabledTUIs.has('opencode')) {
231
- renderFile(
232
- path.join(templateDir, '.opencode', 'commands', opencodeSrc),
233
- path.join('.opencode', 'commands', 'update-agent-infra.md'),
234
- replacements
235
- );
236
- ok('Updated .opencode/commands/update-agent-infra.md');
237
- }
238
-
239
- // sync file registry
240
- const { added, changed } = syncFileRegistry(config, platformType, enabledTUIs);
146
+ const added = {
147
+ managed: workflowPlan.projectAssets.registry.managed.filter(
148
+ (entry) => !currentRegistry.managed.includes(entry)
149
+ ),
150
+ merged: workflowPlan.projectAssets.registry.merged.filter(
151
+ (entry) => !currentRegistry.merged.includes(entry)
152
+ )
153
+ };
241
154
  const hasNewEntries = added.managed.length > 0 || added.merged.length > 0;
242
- const platformAdded = !config.platform;
243
- const sandboxAdded = !config.sandbox;
244
- const taskAdded = !config.task;
245
- const labelsAdded = !config.labels;
246
- const prFlowMigrated = migratePrFlow(config);
247
- const sandboxToolsMigrated = !sandboxAdded && migrateSandboxTools(config);
248
- let configChanged = changed;
155
+ const reconcileResult = applyAgentClientReconciliation(workflowPlan);
156
+ for (const target of reconcileResult.applied) ok(`Updated ${target}`);
249
157
 
250
- if (platformAdded) {
251
- config.platform = structuredClone(defaults.platform);
252
- configChanged = true;
253
- }
254
-
255
- if (sandboxAdded) {
256
- config.sandbox = structuredClone(defaults.sandbox);
257
- configChanged = true;
258
- }
259
-
260
- if (taskAdded) {
261
- config.task = structuredClone(defaults.task);
262
- configChanged = true;
263
- }
264
-
265
- if (labelsAdded) {
266
- config.labels = structuredClone(defaults.labels);
267
- configChanged = true;
268
- }
269
-
270
- if (prFlowMigrated) {
271
- configChanged = true;
272
- }
273
-
274
- if (sandboxToolsMigrated) {
275
- configChanged = true;
276
- }
277
-
278
- if (configChanged) {
158
+ if (reconcileResult.configUpdated) {
279
159
  console.log('');
280
160
  if (hasNewEntries) {
281
161
  info(`New file entries synced to ${CONFIG_PATH}:`);
@@ -301,9 +181,6 @@ async function cmdUpdate(): Promise<void> {
301
181
  if (prFlowMigrated) {
302
182
  info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
303
183
  }
304
- if (sandboxToolsMigrated) {
305
- info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
306
- }
307
184
  } else {
308
185
  info(`File registry changed in ${CONFIG_PATH}.`);
309
186
  }
@@ -322,10 +199,6 @@ async function cmdUpdate(): Promise<void> {
322
199
  if (hasNewEntries && prFlowMigrated) {
323
200
  info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
324
201
  }
325
- if (hasNewEntries && sandboxToolsMigrated) {
326
- info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
327
- }
328
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
329
202
  ok(`Updated ${CONFIG_PATH}`);
330
203
  }
331
204
 
@@ -333,24 +206,15 @@ async function cmdUpdate(): Promise<void> {
333
206
  console.log('');
334
207
  ok('Seed files updated successfully!');
335
208
  console.log('');
336
- if (enabledTUIs.size === 0) {
337
- console.log(' No built-in TUI enabled (tuis: []).');
209
+ if (workflowPlan.nextSteps.length === 0) {
210
+ console.log(' No Agent Client project integration enabled.');
338
211
  console.log(` Configure "customTUIs" in ${CONFIG_PATH} if needed.`);
339
212
  console.log('');
340
213
  } else {
341
214
  console.log(' Next step: run the full update in your AI TUI:');
342
215
  console.log('');
343
- const claudeOrOpencode: string[] = [];
344
- if (enabledTUIs.has('claude-code')) claudeOrOpencode.push('Claude Code');
345
- if (enabledTUIs.has('opencode')) claudeOrOpencode.push('OpenCode');
346
- if (claudeOrOpencode.length > 0) {
347
- console.log(` ${claudeOrOpencode.join(' / ')}: /update-agent-infra`);
348
- }
349
- if (enabledTUIs.has('gemini-cli')) {
350
- console.log(` Gemini CLI: /${project}:update-agent-infra`);
351
- }
352
- if (enabledTUIs.has('codex')) {
353
- console.log(' Codex CLI: $update-agent-infra');
216
+ for (const nextStep of workflowPlan.nextSteps) {
217
+ console.log(` ${nextStep.displayName}: ${nextStep.command}`);
354
218
  }
355
219
  console.log('');
356
220
  }