@fitlab-ai/agent-infra 0.9.1 → 0.9.3

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 (476) hide show
  1. package/README.md +24 -11
  2. package/README.zh-CN.md +24 -11
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +30 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +337 -0
  8. package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
  9. package/dist/lib/agent-clients/adapters/claude-code.js +71 -0
  10. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  11. package/dist/lib/agent-clients/adapters/codex.js +63 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +73 -0
  14. package/dist/lib/agent-clients/config.js +215 -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 +47 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +236 -0
  20. package/dist/lib/agent-clients/registry.js +78 -0
  21. package/dist/lib/agent-clients/schema.js +75 -0
  22. package/dist/lib/agent-clients/tokens.js +49 -0
  23. package/dist/lib/agent-clients/types.js +29 -0
  24. package/dist/lib/builtin-tuis.js +9 -14
  25. package/dist/lib/defaults.json +85 -73
  26. package/dist/lib/git/review-snapshot.js +25 -10
  27. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  28. package/dist/lib/git/workflow.js +54 -1
  29. package/dist/lib/init.js +47 -91
  30. package/dist/lib/internal/agent-client.js +174 -0
  31. package/dist/lib/internal/git-workflow.js +7 -2
  32. package/dist/lib/internal/platform-checks.js +3 -3
  33. package/dist/lib/internal/platform-comment.js +29 -2
  34. package/dist/lib/internal/platform-issue.js +7 -0
  35. package/dist/lib/internal/platform-pr-review.js +132 -0
  36. package/dist/lib/internal/platform-pr.js +20 -2
  37. package/dist/lib/internal/platform-release-notes.js +52 -16
  38. package/dist/lib/internal/pr-review-grade.js +140 -0
  39. package/dist/lib/internal/release-workflow.js +82 -20
  40. package/dist/lib/internal/task-activity.js +73 -0
  41. package/dist/lib/internal/task-event.js +17 -1
  42. package/dist/lib/internal/task-lifecycle.js +7 -0
  43. package/dist/lib/internal/task-orchestration.js +196 -0
  44. package/dist/lib/internal/task-review.js +70 -0
  45. package/dist/lib/merge.js +2 -1
  46. package/dist/lib/platform/adapters.js +45 -1
  47. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  48. package/dist/lib/platform/completion-backfill.js +122 -0
  49. package/dist/lib/platform/issue-comments.js +3 -2
  50. package/dist/lib/platform/merged-pr-equivalence.js +156 -16
  51. package/dist/lib/platform/pr-checks.js +101 -50
  52. package/dist/lib/platform/pr-review.js +166 -0
  53. package/dist/lib/platform/pull-requests.js +432 -7
  54. package/dist/lib/platform/release-notes.js +85 -3
  55. package/dist/lib/platform/releases.js +1 -1
  56. package/dist/lib/platform/verification-required.js +8 -10
  57. package/dist/lib/pr-review/evidence-grading.js +229 -0
  58. package/dist/lib/prompt.js +15 -3
  59. package/dist/lib/release/channels.js +4 -1
  60. package/dist/lib/release/workflow.js +4 -2
  61. package/dist/lib/remove-dir.js +25 -0
  62. package/dist/lib/run/tui.js +11 -7
  63. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  64. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  65. package/dist/lib/sandbox/commands/create.js +76 -200
  66. package/dist/lib/sandbox/commands/enter.js +17 -1
  67. package/dist/lib/sandbox/commands/prune.js +2 -2
  68. package/dist/lib/sandbox/commands/rm.js +3 -2
  69. package/dist/lib/sandbox/commands/show.js +1 -1
  70. package/dist/lib/sandbox/config.js +21 -4
  71. package/dist/lib/sandbox/constants.js +3 -0
  72. package/dist/lib/sandbox/dockerfile.js +2 -1
  73. package/dist/lib/sandbox/managed-fs.js +10 -5
  74. package/dist/lib/sandbox/recovery.js +52 -36
  75. package/dist/lib/sandbox/shell.js +1 -1
  76. package/dist/lib/sandbox/tool-types.js +8 -0
  77. package/dist/lib/sandbox/tools.js +2 -126
  78. package/dist/lib/task/activity-intent.js +101 -0
  79. package/dist/lib/task/artifact-lifecycle.js +3 -50
  80. package/dist/lib/task/commands/decisions.js +50 -20
  81. package/dist/lib/task/commands/log.js +15 -14
  82. package/dist/lib/task/delegation-receipts.js +128 -0
  83. package/dist/lib/task/events.js +146 -8
  84. package/dist/lib/task/finalization-artifacts.js +35 -0
  85. package/dist/lib/task/lifecycle-execution.js +46 -0
  86. package/dist/lib/task/orchestration.js +784 -0
  87. package/dist/lib/task/review-artifacts.js +143 -59
  88. package/dist/lib/task/review-finalization.js +186 -0
  89. package/dist/lib/task/review-fingerprint.js +4 -0
  90. package/dist/lib/task/task-execution-lock.js +231 -0
  91. package/dist/lib/task/verification-checks.js +2 -1
  92. package/dist/lib/task/verification-engine.js +372 -28
  93. package/dist/lib/task/verification-review.js +1 -1
  94. package/dist/lib/task/verification.js +5 -2
  95. package/dist/lib/task/workspace-snapshot.js +46 -0
  96. package/dist/lib/update.js +42 -161
  97. package/lib/agent-client.ts +214 -0
  98. package/lib/agent-clients/adapter.ts +534 -0
  99. package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
  100. package/lib/agent-clients/adapters/claude-code.ts +79 -0
  101. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  102. package/lib/agent-clients/adapters/codex.ts +67 -0
  103. package/lib/agent-clients/adapters/index.ts +27 -0
  104. package/lib/agent-clients/adapters/opencode.ts +75 -0
  105. package/lib/agent-clients/config.ts +323 -0
  106. package/lib/agent-clients/custom-tuis.ts +149 -0
  107. package/lib/agent-clients/invocation.ts +19 -0
  108. package/lib/agent-clients/next-steps.ts +85 -0
  109. package/lib/agent-clients/project-assets.ts +96 -0
  110. package/lib/agent-clients/reconcile.ts +325 -0
  111. package/lib/agent-clients/registry.ts +154 -0
  112. package/lib/agent-clients/schema.ts +80 -0
  113. package/lib/agent-clients/tokens.ts +53 -0
  114. package/lib/agent-clients/types.ts +92 -0
  115. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  116. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  117. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  118. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  119. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  120. package/lib/builtin-tuis.ts +16 -15
  121. package/lib/defaults.json +24 -12
  122. package/lib/git/review-snapshot.ts +39 -9
  123. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  124. package/lib/git/workflow.ts +66 -2
  125. package/lib/init.ts +53 -121
  126. package/lib/internal/agent-client.ts +186 -0
  127. package/lib/internal/git-workflow.ts +6 -2
  128. package/lib/internal/platform-checks.ts +4 -4
  129. package/lib/internal/platform-comment.ts +17 -2
  130. package/lib/internal/platform-issue.ts +4 -0
  131. package/lib/internal/platform-pr-review.ts +104 -0
  132. package/lib/internal/platform-pr.ts +14 -1
  133. package/lib/internal/platform-release-notes.ts +59 -16
  134. package/lib/internal/pr-review-grade.ts +130 -0
  135. package/lib/internal/release-workflow.ts +79 -20
  136. package/lib/internal/task-activity.ts +55 -0
  137. package/lib/internal/task-event.ts +9 -1
  138. package/lib/internal/task-lifecycle.ts +4 -0
  139. package/lib/internal/task-orchestration.ts +171 -0
  140. package/lib/internal/task-review.ts +70 -0
  141. package/lib/merge.ts +2 -1
  142. package/lib/platform/adapters.ts +169 -3
  143. package/lib/platform/change-request-git-evidence.ts +159 -0
  144. package/lib/platform/completion-backfill.ts +134 -0
  145. package/lib/platform/issue-comments.ts +3 -2
  146. package/lib/platform/merged-pr-equivalence.ts +178 -19
  147. package/lib/platform/pr-checks.ts +121 -66
  148. package/lib/platform/pr-review.ts +201 -0
  149. package/lib/platform/pull-requests.ts +529 -27
  150. package/lib/platform/release-notes.ts +100 -4
  151. package/lib/platform/releases.ts +1 -1
  152. package/lib/platform/verification-required.ts +12 -10
  153. package/lib/pr-review/evidence-grading.ts +318 -0
  154. package/lib/prompt.ts +16 -3
  155. package/lib/release/channels.ts +4 -1
  156. package/lib/release/workflow.ts +4 -3
  157. package/lib/remove-dir.ts +28 -0
  158. package/lib/run/tui.ts +12 -5
  159. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  160. package/lib/sandbox/clipboard/linux.ts +2 -1
  161. package/lib/sandbox/commands/create.ts +111 -212
  162. package/lib/sandbox/commands/enter.ts +23 -1
  163. package/lib/sandbox/commands/prune.ts +2 -2
  164. package/lib/sandbox/commands/rm.ts +3 -2
  165. package/lib/sandbox/commands/show.ts +1 -1
  166. package/lib/sandbox/config.ts +31 -4
  167. package/lib/sandbox/constants.ts +6 -0
  168. package/lib/sandbox/dockerfile.ts +2 -1
  169. package/lib/sandbox/managed-fs.ts +18 -5
  170. package/lib/sandbox/recovery.ts +98 -67
  171. package/lib/sandbox/shell.ts +1 -1
  172. package/lib/sandbox/tool-types.ts +143 -0
  173. package/lib/sandbox/tools.ts +5 -157
  174. package/lib/server/server.schema.json +2 -2
  175. package/lib/task/activity-intent.ts +128 -0
  176. package/lib/task/artifact-lifecycle.ts +3 -39
  177. package/lib/task/commands/decisions.ts +53 -20
  178. package/lib/task/commands/log.ts +15 -14
  179. package/lib/task/delegation-receipts.ts +199 -0
  180. package/lib/task/events.ts +164 -11
  181. package/lib/task/finalization-artifacts.ts +58 -0
  182. package/lib/task/lifecycle-execution.ts +106 -0
  183. package/lib/task/orchestration.ts +1022 -0
  184. package/lib/task/review-artifacts.ts +201 -69
  185. package/lib/task/review-finalization.ts +306 -0
  186. package/lib/task/review-fingerprint.ts +5 -0
  187. package/lib/task/task-execution-lock.ts +296 -0
  188. package/lib/task/verification-checks.ts +2 -1
  189. package/lib/task/verification-engine.ts +424 -28
  190. package/lib/task/verification-review.ts +1 -0
  191. package/lib/task/verification.ts +8 -3
  192. package/lib/task/workspace-snapshot.ts +57 -0
  193. package/lib/update.ts +48 -184
  194. package/package.json +4 -4
  195. package/templates/.agents/QUICKSTART.en.md +25 -8
  196. package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
  197. package/templates/.agents/README.en.md +29 -25
  198. package/templates/.agents/README.zh-CN.md +29 -25
  199. package/templates/.agents/hooks/lifecycle-delegation.js +75 -0
  200. package/templates/.agents/rules/README.en.md +2 -0
  201. package/templates/.agents/rules/README.zh-CN.md +2 -0
  202. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  203. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  204. package/templates/.agents/rules/create-issue.github.en.md +4 -2
  205. package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
  206. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  207. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  208. package/templates/.agents/rules/issue-fields.github.en.md +3 -1
  209. package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
  210. package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
  211. package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
  212. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
  213. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
  214. package/templates/.agents/rules/issue-sync.github.en.md +7 -3
  215. package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
  216. package/templates/.agents/rules/lifecycle-orchestration.en.md +24 -0
  217. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +24 -0
  218. package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
  219. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
  220. package/templates/.agents/rules/next-step-output.en.md +18 -0
  221. package/templates/.agents/rules/next-step-output.zh-CN.md +18 -0
  222. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  223. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  224. package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
  225. package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
  226. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  227. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
  228. package/templates/.agents/rules/pr-sync.en.md +3 -1
  229. package/templates/.agents/rules/pr-sync.github.en.md +11 -1
  230. package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
  231. package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
  232. package/templates/.agents/rules/release-commands.en.md +1 -1
  233. package/templates/.agents/rules/release-commands.github.en.md +21 -5
  234. package/templates/.agents/rules/release-commands.github.zh-CN.md +21 -5
  235. package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
  236. package/templates/.agents/rules/review-handshake.en.md +4 -4
  237. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  238. package/templates/.agents/rules/review-method.en.md +113 -0
  239. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  240. package/templates/.agents/rules/task-management.en.md +11 -0
  241. package/templates/.agents/rules/task-management.zh-CN.md +11 -0
  242. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -12
  243. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +15 -12
  244. package/templates/.agents/skills/block-task/SKILL.en.md +11 -9
  245. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +11 -9
  246. package/templates/.agents/skills/cancel-task/SKILL.en.md +10 -8
  247. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +11 -9
  248. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  249. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  250. package/templates/.agents/skills/close-codescan/SKILL.en.md +7 -5
  251. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +7 -5
  252. package/templates/.agents/skills/close-dependabot/SKILL.en.md +7 -5
  253. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +7 -5
  254. package/templates/.agents/skills/code-task/SKILL.en.md +10 -7
  255. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +13 -10
  256. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  257. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  258. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  259. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  260. package/templates/.agents/skills/commit/SKILL.en.md +13 -1
  261. package/templates/.agents/skills/commit/SKILL.zh-CN.md +13 -1
  262. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  263. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  264. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  265. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  266. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  267. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  268. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  269. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  270. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +6 -4
  271. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
  272. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  273. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  274. package/templates/.agents/skills/complete-task/SKILL.en.md +23 -8
  275. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +23 -8
  276. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  277. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  278. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
  279. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
  280. package/templates/.agents/skills/create-pr/SKILL.en.md +13 -11
  281. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +13 -11
  282. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  283. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  284. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  285. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  286. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
  287. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
  288. package/templates/.agents/skills/create-task/SKILL.en.md +16 -14
  289. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +16 -14
  290. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  291. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  292. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  293. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  294. package/templates/.agents/skills/import-issue/SKILL.en.md +10 -8
  295. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -8
  296. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  297. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  298. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  299. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  300. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  301. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  302. package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
  303. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
  304. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  305. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  306. package/templates/.agents/skills/release/SKILL.en.md +16 -10
  307. package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
  308. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
  309. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
  310. package/templates/.agents/skills/review-analysis/SKILL.en.md +18 -4
  311. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +19 -5
  312. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  313. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  314. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  315. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  316. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  317. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  318. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  319. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  320. package/templates/.agents/skills/review-code/SKILL.en.md +35 -12
  321. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +35 -12
  322. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  323. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  324. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  325. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  326. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  327. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  328. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  329. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  330. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  331. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  332. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  333. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  334. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  335. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  336. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  337. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  338. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  339. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  340. package/templates/.agents/skills/review-plan/SKILL.en.md +18 -4
  341. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +19 -5
  342. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  343. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  344. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  345. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  346. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  347. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  348. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  349. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  350. package/templates/.agents/skills/review-pr/SKILL.en.md +158 -0
  351. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +158 -0
  352. package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
  353. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
  354. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
  355. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
  356. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
  357. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
  358. package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
  359. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
  360. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  361. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  362. package/templates/.agents/skills/run-task/config/verify.en.json +10 -0
  363. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +10 -0
  364. package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
  365. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
  366. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  367. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  368. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  369. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  370. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  371. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  372. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +664 -246
  373. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  374. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  375. package/templates/.agents/skills/watch-pr/SKILL.en.md +21 -21
  376. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +21 -21
  377. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
  378. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
  379. package/templates/.agents/templates/task.en.md +1 -1
  380. package/templates/.agents/templates/task.zh-CN.md +1 -1
  381. package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
  382. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
  383. package/templates/.agents/workflows/code-review.en.yaml +1 -1
  384. package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
  385. package/templates/.agents/workflows/feature-development.en.yaml +4 -4
  386. package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
  387. package/templates/.agents/workflows/refactoring.en.yaml +3 -3
  388. package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
  389. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  390. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  391. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  392. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  393. package/templates/.claude/commands/post-release.en.md +1 -0
  394. package/templates/.claude/commands/post-release.zh-CN.md +1 -0
  395. package/templates/.claude/commands/review-pr.en.md +8 -0
  396. package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
  397. package/templates/.claude/commands/run-task.en.md +8 -0
  398. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  399. package/templates/.claude/settings.json +24 -0
  400. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  401. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  402. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  403. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  404. package/templates/.gemini/commands/_project_/review-pr.en.toml +10 -0
  405. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +10 -0
  406. package/templates/.opencode/commands/post-release.en.md +2 -0
  407. package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
  408. package/templates/.opencode/commands/review-pr.en.md +11 -0
  409. package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
  410. package/templates/.opencode/commands/run-task.en.md +11 -0
  411. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
  412. package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
  413. package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
  414. package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
  415. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
  416. package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
  417. package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
  418. package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
  419. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
  420. package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
  421. package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
  422. package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
  423. package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
  424. package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
  425. package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
  426. package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
  427. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
  428. package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
  429. package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
  430. package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
  431. package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
  432. package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
  433. package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
  434. package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
  435. package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
  436. package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
  437. package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
  438. package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
  439. package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
  440. package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
  441. package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
  442. package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
  443. package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
  444. package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
  445. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
  446. package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
  447. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
  448. package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
  449. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
  450. package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
  451. package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
  452. package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
  453. package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
  454. package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
  455. package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
  456. package/templates/.gemini/commands/_project_/release.en.toml +0 -8
  457. package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
  458. package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
  459. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
  460. package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
  461. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
  462. package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
  463. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
  464. package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
  465. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
  466. package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
  467. package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
  468. package/templates/.gemini/commands/_project_/test.en.toml +0 -6
  469. package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
  470. package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
  471. package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
  472. package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
  473. package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
  474. package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
  475. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
  476. package/templates/.gemini/settings.json +0 -3
@@ -28,17 +28,35 @@ const DEFAULTS = {
28
28
  "platform": {
29
29
  "type": "github"
30
30
  },
31
+ "agentClients": [
32
+ {
33
+ "id": "claude-code",
34
+ "enabled": true,
35
+ "installInSandbox": true
36
+ },
37
+ {
38
+ "id": "codex",
39
+ "enabled": true,
40
+ "installInSandbox": true
41
+ },
42
+ {
43
+ "id": "antigravity-cli",
44
+ "enabled": true,
45
+ "installInSandbox": true
46
+ },
47
+ {
48
+ "id": "opencode",
49
+ "enabled": true,
50
+ "installInSandbox": true
51
+ }
52
+ ],
31
53
  "sandbox": {
32
54
  "engine": null,
33
55
  "runtimes": [
34
56
  "node22"
35
57
  ],
36
58
  "tools": [
37
- "agent-infra",
38
- "claude-code",
39
- "codex",
40
- "gemini-cli",
41
- "opencode"
59
+ "agent-infra"
42
60
  ],
43
61
  "refreshIntervalDays": 7,
44
62
  "dockerfile": null,
@@ -65,16 +83,12 @@ const DEFAULTS = {
65
83
  ".agents/templates/",
66
84
  ".agents/workflows/",
67
85
  ".agents/workspace/README.md",
68
- ".claude/commands/",
69
- ".codex/hooks.json",
70
- ".gemini/commands/",
71
86
  ".git-hooks/check-large-files.cjs",
72
87
  ".git-hooks/check-version-format.sh",
73
88
  ".github/scripts/",
74
89
  ".github/workflows/metadata-sync.yml",
75
90
  ".github/workflows/pr-label.yml",
76
- ".github/workflows/status-label.yml",
77
- ".opencode/commands/"
91
+ ".github/workflows/status-label.yml"
78
92
  ],
79
93
  "guardedManaged": [
80
94
  ".github/workflows/metadata-sync.yml",
@@ -93,8 +107,6 @@ const DEFAULTS = {
93
107
  ".agents/skills/test-integration/SKILL.*",
94
108
  ".agents/skills/test/SKILL.*",
95
109
  ".agents/skills/upgrade-dependency/SKILL.*",
96
- ".claude/settings.json",
97
- ".gemini/settings.json",
98
110
  ".git-hooks/pre-commit",
99
111
  ".gitignore"
100
112
  ],
@@ -102,64 +114,301 @@ const DEFAULTS = {
102
114
  }
103
115
  };
104
116
 
105
- const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
106
- const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
107
- const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
117
+ const AGENT_CLIENT_MANIFEST = [
118
+ {
119
+ "id": "claude-code",
120
+ "displayName": "Claude Code",
121
+ "invocation": "/${skillName}",
122
+ "ownedPathPrefixes": [
123
+ ".claude/"
124
+ ],
125
+ "managed": [
126
+ ".claude/commands/",
127
+ ".claude/agents/"
128
+ ],
129
+ "merged": [
130
+ ".claude/settings.json"
131
+ ],
132
+ "ejected": []
133
+ },
134
+ {
135
+ "id": "codex",
136
+ "displayName": "Codex",
137
+ "invocation": "$${skillName}",
138
+ "ownedPathPrefixes": [
139
+ ".codex/"
140
+ ],
141
+ "managed": [
142
+ ".codex/hooks.json",
143
+ ".codex/agents/"
144
+ ],
145
+ "merged": [],
146
+ "ejected": []
147
+ },
148
+ {
149
+ "id": "antigravity-cli",
150
+ "displayName": "Antigravity CLI",
151
+ "invocation": "/${skillName}",
152
+ "ownedPathPrefixes": [],
153
+ "managed": [],
154
+ "merged": [],
155
+ "ejected": []
156
+ },
157
+ {
158
+ "id": "opencode",
159
+ "displayName": "OpenCode",
160
+ "invocation": "/${skillName}",
161
+ "ownedPathPrefixes": [
162
+ ".opencode/"
163
+ ],
164
+ "managed": [
165
+ ".opencode/commands/"
166
+ ],
167
+ "merged": [],
168
+ "ejected": []
169
+ }
170
+ ];
171
+ const CUSTOM_TUI_CONTRACT = {
172
+ "requiredFields": [
173
+ "name",
174
+ "dir",
175
+ "invoke"
176
+ ],
177
+ "allowedPlaceholders": [
178
+ "skillName",
179
+ "projectName"
180
+ ]
181
+ };
108
182
  // Add a new identifier here only after shipping matching .{platform}. template variants.
109
183
  const KNOWN_PLATFORMS = new Set(['github', 'none']);
110
184
  const KNOWN_LANGUAGES = new Set(['en', 'zh-CN']);
111
185
 
112
- // Single source of truth for built-in TUI ids and owned path prefixes.
113
- // Keep in sync with lib/builtin-tuis.ts (enforced by tests/unit/scripts/sync-templates-consts.test.ts).
114
- const BUILTIN_TUI_IDS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
115
- const BUILTIN_TUI_OWNED_PATH_PREFIXES = {
116
- 'claude-code': ['.claude/'],
117
- 'codex': ['.codex/'],
118
- 'gemini-cli': ['.gemini/'],
119
- 'opencode': ['.opencode/']
120
- };
121
-
122
- function resolveEnabledTUIs(value) {
123
- // Missing field / null / non-array → full set (backward compat).
124
- if (!Array.isArray(value)) return new Set(BUILTIN_TUI_IDS);
125
- // Empty array is a meaningful user choice: no built-in TUI managed.
126
- const set = new Set();
127
- for (const v of value) {
128
- if (typeof v === 'string' && BUILTIN_TUI_IDS.includes(v)) set.add(v);
186
+ const BUILTIN_TUI_IDS = AGENT_CLIENT_MANIFEST.map((entry) => entry.id);
187
+
188
+ function normalizeAgentClientConfig(cfg) {
189
+ const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
190
+ const diagnostics = [];
191
+ const failure = (code, path) => ({
192
+ source: null,
193
+ state: null,
194
+ canonical: null,
195
+ remainingSandboxTools: undefined,
196
+ removeLegacyTuis: false,
197
+ changed: false,
198
+ diagnostics,
199
+ error: `${code} at ${path}`,
200
+ errorCode: code,
201
+ errorPath: path
202
+ });
203
+ const projectTuis = (value) => {
204
+ if (!Array.isArray(value)) {
205
+ return Object.fromEntries(BUILTIN_TUI_IDS.map((id) => [id, true]));
206
+ }
207
+ const enabled = new Set();
208
+ for (const [index, candidate] of value.entries()) {
209
+ if (BUILTIN_TUI_IDS.includes(candidate)) enabled.add(candidate);
210
+ else diagnostics.push({ code: 'LEGACY_VALUE_IGNORED', path: `tuis[${index}]` });
211
+ }
212
+ return Object.fromEntries(BUILTIN_TUI_IDS.map((id) => [id, enabled.has(id)]));
213
+ };
214
+ const projectSandbox = (value) => {
215
+ if (!Array.isArray(value) || value.length === 0) {
216
+ return {
217
+ installed: Object.fromEntries(BUILTIN_TUI_IDS.map((id) => [id, true])),
218
+ clientSignals: new Set(),
219
+ remainingTools: Array.isArray(value) ? [] : undefined
220
+ };
221
+ }
222
+ const installed = new Set();
223
+ const remainingTools = [];
224
+ for (const [index, candidate] of value.entries()) {
225
+ if (BUILTIN_TUI_IDS.includes(candidate)) installed.add(candidate);
226
+ else if (typeof candidate === 'string') remainingTools.push(candidate);
227
+ else diagnostics.push({ code: 'LEGACY_VALUE_IGNORED', path: `sandbox.tools[${index}]` });
228
+ }
229
+ return {
230
+ installed: Object.fromEntries(BUILTIN_TUI_IDS.map((id) => [id, installed.has(id)])),
231
+ clientSignals: installed,
232
+ remainingTools
233
+ };
234
+ };
235
+ const serialize = (state) => BUILTIN_TUI_IDS.map((id) => ({ id, ...state[id] }));
236
+ const hasCanonical = own(cfg, 'agentClients');
237
+ const hasLegacyTuis = own(cfg, 'tuis');
238
+ const sandbox = cfg.sandbox && typeof cfg.sandbox === 'object' && !Array.isArray(cfg.sandbox)
239
+ ? cfg.sandbox
240
+ : undefined;
241
+ const hasSandboxTools = sandbox !== undefined && own(sandbox, 'tools');
242
+ const tuiProjection = projectTuis(cfg.tuis);
243
+ const sandboxProjection = projectSandbox(sandbox?.tools);
244
+
245
+ if (!hasCanonical) {
246
+ const state = Object.fromEntries(BUILTIN_TUI_IDS.map((id) => [id, {
247
+ enabled: tuiProjection[id],
248
+ installInSandbox: sandboxProjection.installed[id]
249
+ }]));
250
+ return {
251
+ source: 'legacy',
252
+ state,
253
+ canonical: serialize(state),
254
+ remainingSandboxTools: sandboxProjection.remainingTools,
255
+ removeLegacyTuis: hasLegacyTuis,
256
+ changed: true,
257
+ diagnostics,
258
+ error: null,
259
+ errorCode: null,
260
+ errorPath: null
261
+ };
129
262
  }
130
- return set;
131
- }
132
263
 
133
- function isPathOwnedByDisabledTUI(rel, enabledSet) {
134
- const normalized = String(rel || '').replace(/\\/g, '/').replace(/^\.\//, '');
135
- for (const tui of BUILTIN_TUI_IDS) {
136
- if (enabledSet.has(tui)) continue;
137
- for (const prefix of BUILTIN_TUI_OWNED_PATH_PREFIXES[tui]) {
138
- const trimmed = prefix.replace(/\/$/, '');
139
- if (normalized === trimmed || normalized.startsWith(prefix)) return true;
264
+ if (!Array.isArray(cfg.agentClients)) return failure('INVALID_AGENT_CLIENTS', 'agentClients');
265
+ const entries = new Map();
266
+ for (const [index, candidate] of cfg.agentClients.entries()) {
267
+ const entryPath = `agentClients[${index}]`;
268
+ if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {
269
+ return failure('INVALID_AGENT_CLIENTS', entryPath);
270
+ }
271
+ const keys = Object.keys(candidate);
272
+ if (
273
+ (keys.length !== 3 && keys.length !== 4)
274
+ || !own(candidate, 'id')
275
+ || !own(candidate, 'enabled')
276
+ || !own(candidate, 'installInSandbox')
277
+ || (keys.length === 4 && !own(candidate, 'orchestration'))
278
+ ) {
279
+ return failure('INVALID_AGENT_CLIENTS', entryPath);
280
+ }
281
+ if (!BUILTIN_TUI_IDS.includes(candidate.id)) {
282
+ return failure('UNKNOWN_AGENT_CLIENT', `${entryPath}.id`);
283
+ }
284
+ if (entries.has(candidate.id)) return failure('DUPLICATE_AGENT_CLIENT', `${entryPath}.id`);
285
+ if (typeof candidate.enabled !== 'boolean' || typeof candidate.installInSandbox !== 'boolean') {
286
+ return failure('INVALID_AGENT_CLIENTS', entryPath);
140
287
  }
288
+ let orchestration;
289
+ if (own(candidate, 'orchestration')) {
290
+ const policy = candidate.orchestration;
291
+ const exact = (value) => typeof value === 'string' && value.length > 0 && value.trim() === value;
292
+ const validRole = (role) => role
293
+ && typeof role === 'object'
294
+ && !Array.isArray(role)
295
+ && Object.keys(role).length === 2
296
+ && exact(role.model)
297
+ && exact(role.reasoningEffort);
298
+ if (
299
+ !policy
300
+ || typeof policy !== 'object'
301
+ || Array.isArray(policy)
302
+ || Object.keys(policy).length !== 2
303
+ || !validRole(policy.executor)
304
+ || !validRole(policy.reviewer)
305
+ ) {
306
+ return failure('INVALID_AGENT_CLIENTS', `${entryPath}.orchestration`);
307
+ }
308
+ orchestration = structuredClone(policy);
309
+ }
310
+ entries.set(candidate.id, {
311
+ enabled: candidate.enabled,
312
+ installInSandbox: candidate.installInSandbox,
313
+ ...(orchestration ? { orchestration } : {})
314
+ });
141
315
  }
142
- return false;
316
+ if (BUILTIN_TUI_IDS.some((id) => !entries.has(id))) {
317
+ return failure('MISSING_AGENT_CLIENT', 'agentClients');
318
+ }
319
+ const state = Object.fromEntries(BUILTIN_TUI_IDS.map((id) => [id, entries.get(id)]));
320
+ if (hasLegacyTuis && BUILTIN_TUI_IDS.some((id) => state[id].enabled !== tuiProjection[id])) {
321
+ return failure('LEGACY_CONFLICT', 'tuis');
322
+ }
323
+ if ([...sandboxProjection.clientSignals].some((id) => !state[id].installInSandbox)) {
324
+ return failure('LEGACY_CONFLICT', 'sandbox.tools');
325
+ }
326
+ const sameOrder = cfg.agentClients.every((entry, index) => entry.id === BUILTIN_TUI_IDS[index]);
327
+ return {
328
+ source: 'canonical',
329
+ state,
330
+ canonical: serialize(state),
331
+ remainingSandboxTools: hasSandboxTools ? sandboxProjection.remainingTools : undefined,
332
+ removeLegacyTuis: hasLegacyTuis,
333
+ changed: hasLegacyTuis || sandboxProjection.clientSignals.size > 0 || !sameOrder,
334
+ diagnostics,
335
+ error: null,
336
+ errorCode: null,
337
+ errorPath: null
338
+ };
143
339
  }
144
340
 
145
- function isLegacyDefaultSandboxTools(value) {
146
- if (!Array.isArray(value) || value.length !== LEGACY_DEFAULT_SANDBOX_TOOLS.length) {
147
- return false;
341
+ function materializeAgentClientConfig(cfg, normalized) {
342
+ cfg.agentClients = normalized.canonical;
343
+ delete cfg.tuis;
344
+ if (normalized.remainingSandboxTools !== undefined) {
345
+ cfg.sandbox = {
346
+ ...(cfg.sandbox || {}),
347
+ tools: [
348
+ 'agent-infra',
349
+ ...normalized.remainingSandboxTools.filter((tool) => tool !== 'agent-infra')
350
+ ]
351
+ };
148
352
  }
149
- const tools = new Set(value);
150
- return LEGACY_DEFAULT_SANDBOX_TOOLS.every(tool => tools.has(tool));
151
353
  }
152
354
 
153
- function migrateSandboxTools(cfg) {
154
- const tools = cfg.sandbox?.tools;
155
- if (!isLegacyDefaultSandboxTools(tools)) {
156
- return false;
157
- }
158
- cfg.sandbox = {
159
- ...cfg.sandbox,
160
- tools: [...DEFAULT_SANDBOX_TOOLS]
355
+ function assetMatches(entry, target) {
356
+ const normalizedEntry = norm(entry);
357
+ const normalizedTarget = norm(target);
358
+ return normalizedEntry.endsWith('/')
359
+ ? normalizedTarget.startsWith(normalizedEntry)
360
+ : normalizedTarget === normalizedEntry;
361
+ }
362
+
363
+ function adapterAssets(adapters, category) {
364
+ return adapters.flatMap((adapter) => adapter[category]);
365
+ }
366
+
367
+ function planProjectRegistry(current, sharedDefaults, enabledSet) {
368
+ const enabledAdapters = AGENT_CLIENT_MANIFEST.filter((adapter) => enabledSet.has(adapter.id));
369
+ const disabledAdapters = AGENT_CLIENT_MANIFEST.filter((adapter) => !enabledSet.has(adapter.id));
370
+ const allAdapterAssets = new Set(
371
+ AGENT_CLIENT_MANIFEST.flatMap((adapter) => [
372
+ ...adapter.managed,
373
+ ...adapter.merged,
374
+ ...adapter.ejected
375
+ ])
376
+ );
377
+ const enabled = {
378
+ managed: adapterAssets(enabledAdapters, 'managed'),
379
+ merged: adapterAssets(enabledAdapters, 'merged'),
380
+ ejected: adapterAssets(enabledAdapters, 'ejected')
381
+ };
382
+ const unique = (values) => [...new Set(values)];
383
+ const ejected = unique([
384
+ ...current.ejected,
385
+ ...enabled.ejected,
386
+ ...sharedDefaults.ejected
387
+ ]);
388
+ const ejectedSet = new Set(ejected);
389
+ const preserve = (values, enabledValues) => values.filter((entry) =>
390
+ !ejectedSet.has(entry)
391
+ && (!allAdapterAssets.has(entry) || enabledValues.includes(entry))
392
+ );
393
+ const managed = unique([
394
+ ...preserve(current.managed, enabled.managed),
395
+ ...enabled.managed.filter((entry) => !ejectedSet.has(entry)),
396
+ ...sharedDefaults.managed.filter((entry) => !ejectedSet.has(entry))
397
+ ]);
398
+ const managedSet = new Set(managed);
399
+ const merged = unique([
400
+ ...preserve(current.merged, enabled.merged),
401
+ ...enabled.merged.filter((entry) => !ejectedSet.has(entry) && !managedSet.has(entry)),
402
+ ...sharedDefaults.merged.filter((entry) => !ejectedSet.has(entry) && !managedSet.has(entry))
403
+ ]);
404
+
405
+ return {
406
+ registry: { managed, merged, ejected },
407
+ enabledManaged: enabled.managed,
408
+ enabledMerged: enabled.merged,
409
+ enabledEjected: enabled.ejected,
410
+ disabledManaged: adapterAssets(disabledAdapters, 'managed')
161
411
  };
162
- return true;
163
412
  }
164
413
 
165
414
  function norm(p) { return p.replace(/\\/g, '/'); }
@@ -316,14 +565,13 @@ function detectCustomSkills(projectRoot, templateSkillNames) {
316
565
  .sort((left, right) => left.dirName.localeCompare(right.dirName));
317
566
  }
318
567
 
319
- function isCustomProtected(targetPath, customSkills, project, customTUICommandTargets) {
568
+ function isCustomProtected(targetPath, customSkills, customTUICommandTargets) {
320
569
  const normalized = norm(targetPath);
321
570
 
322
571
  return customSkills.some(({ dirName }) => (
323
572
  normalized.startsWith(`.agents/skills/${dirName}/`) ||
324
573
  normalized === `.claude/commands/${dirName}.md` ||
325
574
  normalized === `.opencode/commands/${dirName}.md` ||
326
- normalized === '.gemini/commands/' + project + '/' + dirName + '.toml' ||
327
575
  customTUICommandTargets.has(normalized)
328
576
  ));
329
577
  }
@@ -532,15 +780,6 @@ function formatYamlMetadata(key, value) {
532
780
  return [`${key}: |-`, ...value.split('\n').map((line) => ` ${line}`)];
533
781
  }
534
782
 
535
- function formatTomlMetadata(key, value) {
536
- if (!value.includes('\n')) {
537
- return `${key} = ${JSON.stringify(value)}`;
538
- }
539
-
540
- const lines = value.split('\n').map((line) => JSON.stringify(line).slice(1, -1));
541
- return `${key} = """${lines.join('\n')}"""`;
542
- }
543
-
544
783
  function generateClaudeCommand(skill, lang) {
545
784
  const isZhCN = lang === 'zh-CN';
546
785
  const lines = ['---', ...formatYamlMetadata('description', skill.description)];
@@ -565,31 +804,6 @@ function generateClaudeCommand(skill, lang) {
565
804
  return `${lines.join('\n')}\n`;
566
805
  }
567
806
 
568
- function generateGeminiCommand(skill, lang) {
569
- const isZhCN = lang === 'zh-CN';
570
- const promptLines = [];
571
-
572
- if (skill.args) {
573
- promptLines.push(isZhCN ? '参数:{{args}}' : 'Arguments: {{args}}');
574
- promptLines.push('');
575
- }
576
-
577
- promptLines.push(
578
- isZhCN
579
- ? `读取并执行 \`.agents/skills/${skill.dirName}/SKILL.md\` 中的 ${skill.dirName} 技能。`
580
- : `Read and execute the ${skill.dirName} skill from \`.agents/skills/${skill.dirName}/SKILL.md\`.`
581
- );
582
- promptLines.push('');
583
- promptLines.push(isZhCN ? '严格按照技能中定义的所有步骤执行。' : 'Follow all steps defined in the skill exactly.');
584
-
585
- return [
586
- formatTomlMetadata('description', skill.description),
587
- 'prompt = """',
588
- ...promptLines,
589
- '"""'
590
- ].join('\n') + '\n';
591
- }
592
-
593
807
  function generateOpenCodeCommand(skill, lang) {
594
808
  const isZhCN = lang === 'zh-CN';
595
809
  const lines = [
@@ -618,9 +832,49 @@ function generateOpenCodeCommand(skill, lang) {
618
832
  }
619
833
 
620
834
  function validateCustomTUIs(projectRoot, customTUIs, report) {
621
- const tools = Array.isArray(customTUIs) ? customTUIs : [];
835
+ if (customTUIs === undefined) return [];
836
+ if (!Array.isArray(customTUIs)) {
837
+ recordCustomTUISkipped(report, {
838
+ index: -1,
839
+ name: '',
840
+ dir: '',
841
+ reason: 'customTUIs must be an array'
842
+ });
843
+ return [];
844
+ }
845
+ const tools = customTUIs;
622
846
  return tools
623
847
  .map((tool, index) => {
848
+ if (
849
+ typeof tool !== 'object'
850
+ || tool === null
851
+ || Array.isArray(tool)
852
+ ) {
853
+ recordCustomTUISkipped(report, {
854
+ index,
855
+ name: '',
856
+ dir: '',
857
+ reason: 'invalid custom TUI'
858
+ });
859
+ return null;
860
+ }
861
+
862
+ for (const field of CUSTOM_TUI_CONTRACT.requiredFields) {
863
+ if (
864
+ typeof tool[field] !== 'string'
865
+ || tool[field].trim() === ''
866
+ || /[\r\n]/.test(tool[field])
867
+ ) {
868
+ recordCustomTUISkipped(report, {
869
+ index,
870
+ name: String(tool.name || ''),
871
+ dir: String(tool.dir || ''),
872
+ reason: `invalid ${field}`
873
+ });
874
+ return null;
875
+ }
876
+ }
877
+
624
878
  if (typeof tool?.dir !== 'string' || tool.dir.trim() === '') {
625
879
  recordCustomTUISkipped(report, {
626
880
  index,
@@ -631,6 +885,16 @@ function validateCustomTUIs(projectRoot, customTUIs, report) {
631
885
  return null;
632
886
  }
633
887
 
888
+ if (tool.dir.includes('\\')) {
889
+ recordCustomTUISkipped(report, {
890
+ index,
891
+ name: tool.name,
892
+ dir: tool.dir,
893
+ reason: 'dir must use POSIX separators'
894
+ });
895
+ return null;
896
+ }
897
+
634
898
  if (!isInsideProject(projectRoot, tool.dir)) {
635
899
  recordCustomTUISkipped(report, {
636
900
  index,
@@ -641,6 +905,27 @@ function validateCustomTUIs(projectRoot, customTUIs, report) {
641
905
  return null;
642
906
  }
643
907
 
908
+ const placeholders = [...tool.invoke.matchAll(/\$\{([^}]+)\}/g)]
909
+ .map((match) => match[1]);
910
+ if (
911
+ !placeholders.includes('skillName')
912
+ || placeholders.some((placeholder) =>
913
+ !CUSTOM_TUI_CONTRACT.allowedPlaceholders.includes(placeholder)
914
+ )
915
+ || tool.invoke
916
+ .replaceAll('${skillName}', '')
917
+ .replaceAll('${projectName}', '')
918
+ .includes('${')
919
+ ) {
920
+ recordCustomTUISkipped(report, {
921
+ index,
922
+ name: tool.name,
923
+ dir: tool.dir,
924
+ reason: 'invalid invoke placeholders'
925
+ });
926
+ return null;
927
+ }
928
+
644
929
  return { ...tool, index, dir: normDir(tool.dir) };
645
930
  })
646
931
  .filter(Boolean);
@@ -764,27 +1049,26 @@ function learnAndGenerateCommands(projectRoot, customSkills, tool, templateSkill
764
1049
  }
765
1050
  }
766
1051
 
767
- function generateCustomCommands(projectRoot, customSkills, project, lang, report, customTUIs, templateSkillNames, enabledTUIs) {
1052
+ function generateCustomCommands(
1053
+ projectRoot,
1054
+ customSkills,
1055
+ lang,
1056
+ report,
1057
+ customTUIs,
1058
+ templateSkillNames,
1059
+ enabledTUIs,
1060
+ managedWriter
1061
+ ) {
768
1062
  for (const skill of customSkills) {
769
1063
  if (enabledTUIs.has('claude-code')) {
770
- writeIfChanged(
771
- projectRoot,
1064
+ managedWriter(
772
1065
  `.claude/commands/${skill.dirName}.md`,
773
1066
  generateClaudeCommand(skill, lang),
774
1067
  report.custom.commands
775
1068
  );
776
1069
  }
777
- if (enabledTUIs.has('gemini-cli')) {
778
- writeIfChanged(
779
- projectRoot,
780
- '.gemini/commands/' + project + '/' + skill.dirName + '.toml',
781
- generateGeminiCommand(skill, lang),
782
- report.custom.commands
783
- );
784
- }
785
1070
  if (enabledTUIs.has('opencode')) {
786
- writeIfChanged(
787
- projectRoot,
1071
+ managedWriter(
788
1072
  `.opencode/commands/${skill.dirName}.md`,
789
1073
  generateOpenCodeCommand(skill, lang),
790
1074
  report.custom.commands
@@ -935,7 +1219,8 @@ function syncTemplates(projectRoot, templateRootOverride) {
935
1219
  return { error: 'No .agents/.airc.json in project root.' };
936
1220
  }
937
1221
 
938
- const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
1222
+ const originalConfigText = fs.readFileSync(cfgPath, 'utf8');
1223
+ const cfg = JSON.parse(originalConfigText);
939
1224
  const configPathRel = norm(path.relative(projectRoot, cfgPath));
940
1225
  let templateRoot = templateRootOverride;
941
1226
  if (!templateRoot) {
@@ -951,20 +1236,43 @@ function syncTemplates(projectRoot, templateRootOverride) {
951
1236
  }
952
1237
  }
953
1238
  const version = resolveVersionFromTemplateRoot(templateRoot);
954
- const hadTemplateSource = Object.prototype.hasOwnProperty.call(cfg, 'templateSource');
955
1239
 
956
1240
  const { project, org, language: lang = 'en' } = cfg;
957
1241
  const platformType = cfg.platform?.type || DEFAULTS.platform.type;
958
- const enabledTUIs = resolveEnabledTUIs(cfg.tuis);
1242
+ const enabledResolution = normalizeAgentClientConfig(cfg);
1243
+ if (enabledResolution.error) {
1244
+ return { error: enabledResolution.error };
1245
+ }
1246
+ const enabledTUIs = new Set(
1247
+ BUILTIN_TUI_IDS.filter((id) => enabledResolution.state[id].enabled)
1248
+ );
1249
+ materializeAgentClientConfig(cfg, enabledResolution);
959
1250
  const customTUIsConfig = Array.isArray(cfg.customTUIs) ? cfg.customTUIs : [];
960
1251
  const vars = { project, org };
961
1252
  const templateSkillNames = listTemplateSkillNames(templateRoot);
962
1253
  const protectedCustomSkills = detectCustomSkills(projectRoot, templateSkillNames);
963
1254
 
964
- const managed = [...(cfg.files.managed || [])];
965
- const merged = [...(cfg.files.merged || [])];
966
- const ejected = [...(cfg.files.ejected || [])];
1255
+ cfg.files ||= {};
1256
+ const currentRegistry = {
1257
+ managed: [...(cfg.files.managed || [])],
1258
+ merged: [...(cfg.files.merged || [])],
1259
+ ejected: [...(cfg.files.ejected || [])]
1260
+ };
1261
+ const sharedDefaults = {
1262
+ managed: (DEFAULTS.files.managed || []).filter(
1263
+ (entry) => !isPathOwnedByOtherPlatform(entry, platformType)
1264
+ ),
1265
+ merged: (DEFAULTS.files.merged || []).filter(
1266
+ (entry) => !isPathOwnedByOtherPlatform(entry, platformType)
1267
+ ),
1268
+ ejected: [...(DEFAULTS.files.ejected || [])]
1269
+ };
1270
+ const assetPlan = planProjectRegistry(currentRegistry, sharedDefaults, enabledTUIs);
1271
+ const managed = [...assetPlan.registry.managed];
1272
+ const merged = [...assetPlan.registry.merged];
1273
+ const ejected = [...assetPlan.registry.ejected];
967
1274
  const guardedManaged = new Set((DEFAULTS.files.guardedManaged || []).map(norm));
1275
+ const allClientManaged = adapterAssets(AGENT_CLIENT_MANIFEST, 'managed');
968
1276
  const managedBaselines = cfg.files.managedBaselines && typeof cfg.files.managedBaselines === 'object'
969
1277
  && !Array.isArray(cfg.files.managedBaselines)
970
1278
  ? { ...cfg.files.managedBaselines }
@@ -972,7 +1280,10 @@ function syncTemplates(projectRoot, templateRootOverride) {
972
1280
  let baselinesChanged = false;
973
1281
 
974
1282
  for (const target of Object.keys(managedBaselines)) {
975
- if (!guardedManaged.has(norm(target))) {
1283
+ if (
1284
+ !guardedManaged.has(norm(target))
1285
+ && !allClientManaged.some((entry) => assetMatches(entry, target))
1286
+ ) {
976
1287
  delete managedBaselines[target];
977
1288
  baselinesChanged = true;
978
1289
  }
@@ -982,6 +1293,7 @@ function syncTemplates(projectRoot, templateRootOverride) {
982
1293
  templateVersion: version,
983
1294
  templateRoot: norm(templateRoot),
984
1295
  registryAdded: [],
1296
+ registryRemoved: [],
985
1297
  templateSources: {
986
1298
  configured: 0,
987
1299
  loaded: 0,
@@ -1022,17 +1334,23 @@ function syncTemplates(projectRoot, templateRootOverride) {
1022
1334
  templateSkillNames
1023
1335
  );
1024
1336
 
1025
- const known = new Set([...managed, ...merged, ...ejected]);
1026
- for (const e of (DEFAULTS.files.managed || [])) {
1027
- if (isPathOwnedByOtherPlatform(e, platformType)) continue;
1028
- if (isPathOwnedByDisabledTUI(e, enabledTUIs)) continue;
1029
- if (!known.has(e)) { managed.push(e); known.add(e); report.registryAdded.push({ entry: e, list: 'managed' }); }
1030
- }
1031
- for (const e of (DEFAULTS.files.merged || [])) {
1032
- if (isPathOwnedByOtherPlatform(e, platformType)) continue;
1033
- if (isPathOwnedByDisabledTUI(e, enabledTUIs)) continue;
1034
- if (!known.has(e)) { merged.push(e); known.add(e); report.registryAdded.push({ entry: e, list: 'merged' }); }
1337
+ for (const category of ['managed', 'merged', 'ejected']) {
1338
+ const before = currentRegistry[category];
1339
+ const after = assetPlan.registry[category];
1340
+ for (const entry of after) {
1341
+ if (!before.includes(entry)) report.registryAdded.push({ entry, list: category });
1342
+ }
1343
+ for (const entry of before) {
1344
+ if (!after.includes(entry)) report.registryRemoved.push({ entry, list: category });
1345
+ }
1035
1346
  }
1347
+ report.managed.skippedTUI.push(
1348
+ ...assetPlan.disabledManaged,
1349
+ ...adapterAssets(
1350
+ AGENT_CLIENT_MANIFEST.filter((adapter) => !enabledTUIs.has(adapter.id)),
1351
+ 'merged'
1352
+ )
1353
+ );
1036
1354
 
1037
1355
  const templateSources = Array.isArray(cfg.templates?.sources) ? cfg.templates.sources : [];
1038
1356
  report.templateSources.configured = templateSources.length;
@@ -1056,6 +1374,97 @@ function syncTemplates(projectRoot, templateRootOverride) {
1056
1374
  : renderContent(fs.readFileSync(srcFull, 'utf8'), vars);
1057
1375
  }
1058
1376
 
1377
+ function selectedTargetsForEntry(entry) {
1378
+ let entryRels;
1379
+ if (entry.endsWith('/')) {
1380
+ const builtinDir = path.join(templateRoot, entry);
1381
+ const builtinRels = fs.existsSync(builtinDir)
1382
+ ? walkDir(builtinDir).map((filePath) => norm(path.relative(templateRoot, filePath)))
1383
+ : [];
1384
+ const prefix = norm(entry);
1385
+ const externalRels = allRels.filter((rel) =>
1386
+ rel.startsWith(prefix) && !builtinRels.includes(rel)
1387
+ );
1388
+ entryRels = [...builtinRels, ...externalRels];
1389
+ } else {
1390
+ entryRels = entryVariantRels(entry, allSet, platformType);
1391
+ }
1392
+ return platformSelect(
1393
+ langSelect(entryRels, lang, allSet, project),
1394
+ platformType,
1395
+ project
1396
+ );
1397
+ }
1398
+
1399
+ function writeProtectedManaged(target, content, bucket) {
1400
+ const dstFull = path.join(projectRoot, target);
1401
+ const exists = fs.existsSync(dstFull);
1402
+ const rawBaseline = managedBaselines[target];
1403
+ const baseline = trustedBaseline(rawBaseline);
1404
+ const templateHash = sha256(content);
1405
+ const localHash = exists ? sha256(fs.readFileSync(dstFull)) : null;
1406
+
1407
+ if (rawBaseline !== undefined && baseline === null) {
1408
+ delete managedBaselines[target];
1409
+ baselinesChanged = true;
1410
+ }
1411
+ if (baseline === null && localHash !== null && localHash !== templateHash) {
1412
+ report.managed.conflicts.push({
1413
+ target,
1414
+ reason: 'unknown-origin',
1415
+ baseline: null,
1416
+ local: localHash,
1417
+ template: templateHash
1418
+ });
1419
+ return false;
1420
+ }
1421
+ if (baseline !== null && templateHash === baseline && localHash !== baseline) {
1422
+ report.managed.protected.push({
1423
+ target,
1424
+ reason: localHash === null ? 'user-deleted' : 'user-modified',
1425
+ baseline,
1426
+ local: localHash,
1427
+ template: templateHash
1428
+ });
1429
+ return false;
1430
+ }
1431
+ if (
1432
+ baseline !== null
1433
+ && localHash !== baseline
1434
+ && templateHash !== baseline
1435
+ && localHash !== templateHash
1436
+ ) {
1437
+ report.managed.conflicts.push({
1438
+ target,
1439
+ reason: 'both-modified',
1440
+ baseline,
1441
+ local: localHash,
1442
+ template: templateHash
1443
+ });
1444
+ return false;
1445
+ }
1446
+ if (localHash === templateHash) {
1447
+ bucket.unchanged.push(target);
1448
+ if (managedBaselines[target] !== templateHash) {
1449
+ managedBaselines[target] = templateHash;
1450
+ baselinesChanged = true;
1451
+ }
1452
+ return false;
1453
+ }
1454
+
1455
+ const dir = path.dirname(dstFull);
1456
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1457
+ fs.writeFileSync(dstFull, content);
1458
+ managedBaselines[target] = templateHash;
1459
+ baselinesChanged = true;
1460
+ if (Object.prototype.hasOwnProperty.call(bucket, 'written')) {
1461
+ (exists ? bucket.written : bucket.created).push(target);
1462
+ } else {
1463
+ (exists ? bucket.updated : bucket.generated).push(target);
1464
+ }
1465
+ return true;
1466
+ }
1467
+
1059
1468
  for (const entry of [...managed, ...merged, ...ejected]) {
1060
1469
  if (!isPathOwnedByOtherPlatform(entry, platformType)) continue;
1061
1470
 
@@ -1109,39 +1518,81 @@ function syncTemplates(projectRoot, templateRootOverride) {
1109
1518
  }
1110
1519
  }
1111
1520
 
1112
- // Cleanup files owned by disabled built-in TUIs. Iterates managed + merged
1113
- // only (ejected entries are explicitly user-retained, see ejected loop below).
1114
- //
1115
- // Protection rule: only skip files registered as customTUI command targets
1116
- // (e.g. a customTUI configured with dir=.codex/commands/ when codex is
1117
- // disabled). Built-in TUI custom-skill commands like
1118
- // .gemini/commands/<project>/<dirName>.toml are intentionally NOT protected
1119
- // here so that disabling gemini-cli actually frees the gemini directory —
1120
- // they will be regenerated only for still-enabled TUIs (see
1121
- // generateCustomCommands).
1122
- for (const entry of [...managed, ...merged]) {
1123
- if (!isPathOwnedByDisabledTUI(entry, enabledTUIs)) continue;
1124
-
1125
- if (entry.endsWith('/')) {
1126
- const dir = path.join(projectRoot, entry);
1127
- if (!fs.existsSync(dir)) continue;
1128
-
1129
- for (const filePath of walkDir(dir)) {
1130
- const relProj = norm(path.relative(projectRoot, filePath));
1131
- if (customTUICommandTargets.has(relProj)) continue;
1132
- fs.unlinkSync(filePath);
1133
- report.managed.removed.push(relProj);
1521
+ // Disabled clients are converged conservatively: only exact manifest
1522
+ // managed targets or previously baselined generated targets are candidates.
1523
+ // Merged/ejected and content without trustworthy origin evidence are kept.
1524
+ for (const entry of assetPlan.disabledManaged) {
1525
+ const selectedTargets = selectedTargetsForEntry(entry);
1526
+ const candidates = new Set(
1527
+ Object.keys(managedBaselines).filter((target) => assetMatches(entry, target))
1528
+ );
1529
+ for (const target of selectedTargets.keys()) candidates.add(target);
1530
+
1531
+ for (const target of candidates) {
1532
+ const dstFull = path.join(projectRoot, target);
1533
+ const baseline = trustedBaseline(managedBaselines[target]);
1534
+ const selectedSource = selectedTargets.get(target);
1535
+ const sourceRoot = selectedSource ? (sourceMap.get(selectedSource) || templateRoot) : null;
1536
+ const templateContent = selectedSource && sourceRoot === templateRoot
1537
+ ? (
1538
+ isBinary(path.join(sourceRoot, selectedSource))
1539
+ ? fs.readFileSync(path.join(sourceRoot, selectedSource))
1540
+ : renderContent(fs.readFileSync(path.join(sourceRoot, selectedSource), 'utf8'), vars)
1541
+ )
1542
+ : null;
1543
+ const localHash = fs.existsSync(dstFull) ? sha256(fs.readFileSync(dstFull)) : null;
1544
+ const templateHash = templateContent === null ? null : sha256(templateContent);
1545
+ const protectedByEjection = ejected.some((pattern) =>
1546
+ assetMatches(pattern, target) || globMatch(pattern, target)
1547
+ );
1548
+ const protectedByCustom = customTUICommandTargets.has(target);
1549
+
1550
+ if (protectedByEjection || protectedByCustom || (sourceRoot && sourceRoot !== templateRoot)) {
1551
+ report.managed.protected.push({
1552
+ target,
1553
+ reason: protectedByEjection
1554
+ ? 'ejected'
1555
+ : protectedByCustom
1556
+ ? 'custom'
1557
+ : 'external-source',
1558
+ baseline,
1559
+ local: localHash,
1560
+ template: templateHash
1561
+ });
1562
+ continue;
1134
1563
  }
1135
- removeEmptyDirs(dir);
1136
- continue;
1564
+ if (localHash === null) {
1565
+ if (Object.prototype.hasOwnProperty.call(managedBaselines, target)) {
1566
+ delete managedBaselines[target];
1567
+ baselinesChanged = true;
1568
+ }
1569
+ continue;
1570
+ }
1571
+ const legacyManaged = currentRegistry.managed.includes(entry);
1572
+ if (
1573
+ (baseline !== null && localHash === baseline)
1574
+ || (baseline === null && legacyManaged && templateHash !== null && localHash === templateHash)
1575
+ ) {
1576
+ fs.unlinkSync(dstFull);
1577
+ report.managed.removed.push(target);
1578
+ if (Object.prototype.hasOwnProperty.call(managedBaselines, target)) {
1579
+ delete managedBaselines[target];
1580
+ baselinesChanged = true;
1581
+ }
1582
+ removeEmptyDirs(path.dirname(dstFull));
1583
+ continue;
1584
+ }
1585
+ report.managed.protected.push({
1586
+ target,
1587
+ reason: baseline === null ? 'unknown-origin' : 'user-modified',
1588
+ baseline,
1589
+ local: localHash,
1590
+ template: templateHash
1591
+ });
1592
+ }
1593
+ if (entry.endsWith('/')) {
1594
+ removeEmptyDirs(path.join(projectRoot, entry));
1137
1595
  }
1138
-
1139
- const target = path.join(projectRoot, renderPathname(entry, project));
1140
- if (!fs.existsSync(target)) continue;
1141
- const relProj = norm(path.relative(projectRoot, target));
1142
- if (customTUICommandTargets.has(relProj)) continue;
1143
- fs.unlinkSync(target);
1144
- report.managed.removed.push(relProj);
1145
1596
  }
1146
1597
 
1147
1598
  for (const entry of managed) {
@@ -1149,11 +1600,6 @@ function syncTemplates(projectRoot, templateRootOverride) {
1149
1600
  report.managed.skippedPlatform.push(entry);
1150
1601
  continue;
1151
1602
  }
1152
- if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) {
1153
- report.managed.skippedTUI.push(entry);
1154
- continue;
1155
- }
1156
-
1157
1603
  const isDir = entry.endsWith('/');
1158
1604
  let entryRels;
1159
1605
  const expectedTargets = isDir ? new Set() : null;
@@ -1191,65 +1637,14 @@ function syncTemplates(projectRoot, templateRootOverride) {
1191
1637
  : renderContent(fs.readFileSync(srcFull, 'utf8'), vars);
1192
1638
 
1193
1639
  const exists = fs.existsSync(dstFull);
1194
- if (guardedManaged.has(tgt)) {
1195
- const rawBaseline = managedBaselines[tgt];
1196
- const baseline = trustedBaseline(rawBaseline);
1197
- const templateHash = sha256(content);
1198
- const localHash = exists ? sha256(fs.readFileSync(dstFull)) : null;
1199
-
1200
- if (rawBaseline !== undefined && baseline === null) {
1201
- delete managedBaselines[tgt];
1202
- baselinesChanged = true;
1203
- }
1204
-
1205
- if (baseline === null && localHash !== null && localHash !== templateHash) {
1206
- report.managed.conflicts.push({
1207
- target: tgt,
1208
- reason: 'unknown-origin',
1209
- baseline: null,
1210
- local: localHash,
1211
- template: templateHash
1212
- });
1213
- continue;
1214
- }
1215
-
1216
- if (baseline !== null && templateHash === baseline && localHash !== baseline) {
1217
- report.managed.protected.push({
1218
- target: tgt,
1219
- reason: localHash === null ? 'user-deleted' : 'user-modified',
1220
- baseline,
1221
- local: localHash,
1222
- template: templateHash
1223
- });
1224
- continue;
1225
- }
1226
-
1227
- if (baseline !== null && localHash !== baseline && templateHash !== baseline && localHash !== templateHash) {
1228
- report.managed.conflicts.push({
1229
- target: tgt,
1230
- reason: 'both-modified',
1231
- baseline,
1232
- local: localHash,
1233
- template: templateHash
1234
- });
1235
- continue;
1236
- }
1237
-
1238
- if (localHash === templateHash) {
1239
- report.managed.unchanged.push(tgt);
1240
- if (managedBaselines[tgt] !== templateHash) {
1241
- managedBaselines[tgt] = templateHash;
1242
- baselinesChanged = true;
1243
- }
1244
- continue;
1640
+ const clientManagedTarget = assetPlan.enabledManaged.some((asset) =>
1641
+ assetMatches(asset, tgt)
1642
+ );
1643
+ if (guardedManaged.has(tgt) || (clientManagedTarget && srcRoot === templateRoot)) {
1644
+ const written = writeProtectedManaged(tgt, content, report.managed);
1645
+ if (written && tgt.endsWith('.sh')) {
1646
+ try { fs.chmodSync(dstFull, 0o755); } catch { /* Windows */ }
1245
1647
  }
1246
-
1247
- const dir = path.dirname(dstFull);
1248
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1249
- fs.writeFileSync(dstFull, content);
1250
- managedBaselines[tgt] = templateHash;
1251
- baselinesChanged = true;
1252
- (exists ? report.managed.written : report.managed.created).push(tgt);
1253
1648
  continue;
1254
1649
  }
1255
1650
 
@@ -1279,9 +1674,27 @@ function syncTemplates(projectRoot, templateRootOverride) {
1279
1674
  for (const projFile of projFiles) {
1280
1675
  if (expectedTargets.has(projFile)) continue;
1281
1676
  if (projFile === configPathRel) continue;
1282
- if (isCustomProtected(projFile, protectedCustomSkills, project, customTUICommandTargets)) continue;
1677
+ if (isCustomProtected(projFile, protectedCustomSkills, customTUICommandTargets)) continue;
1283
1678
  if (matchesAny(projFile, merged) || matchesAny(projFile, ejected)) continue;
1284
-
1679
+ if (assetPlan.enabledManaged.includes(entry)) {
1680
+ const baseline = trustedBaseline(managedBaselines[projFile]);
1681
+ const localHash = sha256(fs.readFileSync(path.join(projectRoot, projFile)));
1682
+ if (baseline !== null && localHash === baseline) {
1683
+ fs.unlinkSync(path.join(projectRoot, projFile));
1684
+ delete managedBaselines[projFile];
1685
+ baselinesChanged = true;
1686
+ report.managed.removed.push(projFile);
1687
+ } else {
1688
+ report.managed.protected.push({
1689
+ target: projFile,
1690
+ reason: baseline === null ? 'unknown-origin' : 'user-modified',
1691
+ baseline,
1692
+ local: localHash,
1693
+ template: null
1694
+ });
1695
+ }
1696
+ continue;
1697
+ }
1285
1698
  fs.unlinkSync(path.join(projectRoot, projFile));
1286
1699
  report.managed.removed.push(projFile);
1287
1700
  }
@@ -1300,7 +1713,16 @@ function syncTemplates(projectRoot, templateRootOverride) {
1300
1713
 
1301
1714
  const customSkills = detectCustomSkills(projectRoot, templateSkillNames);
1302
1715
  report.custom.detected = customSkills.map((skill) => skill.dirName);
1303
- generateCustomCommands(projectRoot, customSkills, project, lang, report, customTUIs, templateSkillNames, enabledTUIs);
1716
+ generateCustomCommands(
1717
+ projectRoot,
1718
+ customSkills,
1719
+ lang,
1720
+ report,
1721
+ customTUIs,
1722
+ templateSkillNames,
1723
+ enabledTUIs,
1724
+ writeProtectedManaged
1725
+ );
1304
1726
 
1305
1727
  for (const entry of ejected) {
1306
1728
  const dstFull = path.join(projectRoot, entry);
@@ -1310,7 +1732,11 @@ function syncTemplates(projectRoot, templateRootOverride) {
1310
1732
  }
1311
1733
  // Do not (re)create ejected files for disabled TUIs. Existing files are
1312
1734
  // never touched by sync (handled above); this guard only blocks creation.
1313
- if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) continue;
1735
+ const disabledEjected = AGENT_CLIENT_MANIFEST.some((adapter) =>
1736
+ !enabledTUIs.has(adapter.id)
1737
+ && adapter.ejected.some((asset) => assetMatches(asset, entry))
1738
+ );
1739
+ if (disabledEjected) continue;
1314
1740
 
1315
1741
  const selected = platformSelect(langSelect(entryVariantRels(entry, allSet, platformType), lang, allSet, project), platformType, project);
1316
1742
  const target = norm(renderPathname(entry, project));
@@ -1331,11 +1757,6 @@ function syncTemplates(projectRoot, templateRootOverride) {
1331
1757
  report.managed.skippedPlatform.push(entry);
1332
1758
  continue;
1333
1759
  }
1334
- if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) {
1335
- report.managed.skippedTUI.push(entry);
1336
- continue;
1337
- }
1338
-
1339
1760
  if (entry.includes('*')) {
1340
1761
  const hits = allRels.filter(r => {
1341
1762
  const t = norm(renderPathname(stripLangVariant(r), project));
@@ -1356,22 +1777,6 @@ function syncTemplates(projectRoot, templateRootOverride) {
1356
1777
  ([target, template]) => ({ target, template })
1357
1778
  );
1358
1779
 
1359
- const hasChanges = (
1360
- report.managed.written.length +
1361
- report.managed.created.length +
1362
- report.managed.removed.length +
1363
- report.custom.generated.length +
1364
- report.custom.updated.length +
1365
- report.custom.removed.length +
1366
- report.custom.commands.generated.length +
1367
- report.custom.commands.updated.length +
1368
- report.ejected.created.length +
1369
- report.registryAdded.length
1370
- ) > 0;
1371
-
1372
- const prevVersion = cfg.templateVersion;
1373
- const sandboxToolsMigrated = migrateSandboxTools(cfg);
1374
-
1375
1780
  cfg.files.managed = managed;
1376
1781
  cfg.files.merged = merged;
1377
1782
  cfg.files.ejected = ejected;
@@ -1384,9 +1789,22 @@ function syncTemplates(projectRoot, templateRootOverride) {
1384
1789
  cfg.templateVersion = version;
1385
1790
  delete cfg.templateSource;
1386
1791
 
1387
- report.configUpdated = hasChanges || baselinesChanged || prevVersion !== version || hadTemplateSource || sandboxToolsMigrated;
1388
-
1389
- fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
1792
+ const nextConfigText = JSON.stringify(cfg, null, 2) + '\n';
1793
+ report.configUpdated = originalConfigText !== nextConfigText;
1794
+ if (report.configUpdated) {
1795
+ const temporary = `${cfgPath}.tmp-${process.pid}-${Date.now()}`;
1796
+ try {
1797
+ fs.writeFileSync(temporary, nextConfigText, 'utf8');
1798
+ fs.renameSync(temporary, cfgPath);
1799
+ } catch (error) {
1800
+ try {
1801
+ if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
1802
+ } catch {
1803
+ // Preserve the original write/rename failure.
1804
+ }
1805
+ throw error;
1806
+ }
1807
+ }
1390
1808
 
1391
1809
  return report;
1392
1810
  }
@@ -1399,4 +1817,4 @@ if (entryPath === fileURLToPath(import.meta.url)) {
1399
1817
  if (result.error) process.exitCode = 1;
1400
1818
  }
1401
1819
 
1402
- export { syncTemplates };
1820
+ export { normalizeAgentClientConfig, syncTemplates };