@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
@@ -0,0 +1,324 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import defaults from '../defaults.json' with { type: 'json' };
6
+ import { normalizeAgentClients, serializeAgentClients } from './config.ts';
7
+ import { normalizeCustomTUIs } from './custom-tuis.ts';
8
+ import { renderNextStepCommands } from './next-steps.ts';
9
+ import { planAgentClientProjectAssets } from './project-assets.ts';
10
+ import {
11
+ listAgentClientAdapters,
12
+ listEnabledAgentClientAdapters
13
+ } from './registry.ts';
14
+ import type { AgentClientProjectAssetPlan, ProjectFileRegistry } from './project-assets.ts';
15
+ import type { NextStepCommand } from './next-steps.ts';
16
+ import type { AgentClientDiagnostic } from './config.ts';
17
+ import type { AgentClientId, AgentClientState } from './types.ts';
18
+
19
+ type AgentClientWorkflowMutation =
20
+ | Readonly<{ type: 'none' }>
21
+ | Readonly<{ type: 'set-enabled'; id: AgentClientId; value: boolean }>
22
+ | Readonly<{ type: 'replace-state'; state: AgentClientState }>;
23
+
24
+ type AgentClientSeedOperation = Readonly<{
25
+ kind: 'write' | 'remove' | 'protect' | 'unchanged';
26
+ clientId: AgentClientId;
27
+ target: string;
28
+ content?: string;
29
+ reason?: 'user-modified' | 'unknown-origin';
30
+ }>;
31
+
32
+ type WorkflowDiagnostic = AgentClientDiagnostic | Readonly<{ code: string; path: string }>;
33
+
34
+ type AgentClientWorkflowPlan = Readonly<{
35
+ source: 'canonical' | 'legacy';
36
+ before: AgentClientState;
37
+ desired: AgentClientState;
38
+ nextConfig: Record<string, unknown>;
39
+ projectAssets: AgentClientProjectAssetPlan;
40
+ seedOperations: readonly AgentClientSeedOperation[];
41
+ nextSteps: readonly NextStepCommand[];
42
+ diagnostics: readonly WorkflowDiagnostic[];
43
+ changed: boolean;
44
+ projectRoot: string;
45
+ configPath: string;
46
+ }>;
47
+
48
+ type AgentClientReconcileDependencies = Readonly<{
49
+ mkdirSync?: typeof fs.mkdirSync;
50
+ writeFileSync?: typeof fs.writeFileSync;
51
+ renameSync?: typeof fs.renameSync;
52
+ unlinkSync?: typeof fs.unlinkSync;
53
+ }>;
54
+
55
+ type AgentClientReconcileResult = Readonly<{
56
+ status: 'applied' | 'unchanged';
57
+ configUpdated: boolean;
58
+ applied: readonly string[];
59
+ protected: readonly string[];
60
+ unchanged: readonly string[];
61
+ }>;
62
+
63
+ function isRecord(value: unknown): value is Record<string, unknown> {
64
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
65
+ }
66
+
67
+ function hash(content: string | Buffer): string {
68
+ return `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`;
69
+ }
70
+
71
+ function trustedBaseline(value: unknown): string | null {
72
+ return typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value)
73
+ ? value
74
+ : null;
75
+ }
76
+
77
+ function cloneState(state: AgentClientState): AgentClientState {
78
+ return Object.fromEntries(
79
+ Object.entries(state).map(([id, value]) => [id, { ...value }])
80
+ ) as AgentClientState;
81
+ }
82
+
83
+ function applyMutation(
84
+ state: AgentClientState,
85
+ mutation: AgentClientWorkflowMutation
86
+ ): AgentClientState {
87
+ if (mutation.type === 'none') return cloneState(state);
88
+ if (mutation.type === 'replace-state') return cloneState(mutation.state);
89
+ return Object.fromEntries(
90
+ Object.entries(state).map(([id, value]) => [
91
+ id,
92
+ id === mutation.id ? { ...value, enabled: mutation.value } : { ...value }
93
+ ])
94
+ ) as AgentClientState;
95
+ }
96
+
97
+ function registryFrom(value: unknown): ProjectFileRegistry {
98
+ const files = isRecord(value) ? value : {};
99
+ const strings = (candidate: unknown): string[] => Array.isArray(candidate)
100
+ ? candidate.filter((entry): entry is string => typeof entry === 'string')
101
+ : [];
102
+ return {
103
+ managed: strings(files.managed),
104
+ merged: strings(files.merged),
105
+ ejected: strings(files.ejected)
106
+ };
107
+ }
108
+
109
+ function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
110
+ const top = relativePath.replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
111
+ return top.startsWith('.')
112
+ && ['github', 'none'].includes(top.slice(1))
113
+ && top.slice(1) !== platformType;
114
+ }
115
+
116
+ function renderSeed(content: string, config: Record<string, unknown>): string {
117
+ return content
118
+ .replaceAll('{{project}}', String(config.project ?? ''))
119
+ .replaceAll('{{org}}', String(config.org ?? ''));
120
+ }
121
+
122
+ function planAgentClientReconciliation(input: Readonly<{
123
+ config: Record<string, unknown>;
124
+ mutation: AgentClientWorkflowMutation;
125
+ projectRoot: string;
126
+ templateRoot: string;
127
+ platformType: string;
128
+ language: string;
129
+ }>): AgentClientWorkflowPlan {
130
+ const normalized = normalizeAgentClients(input.config);
131
+ const desired = applyMutation(normalized.state, input.mutation);
132
+ const adapters = listAgentClientAdapters();
133
+ const current = registryFrom(input.config.files);
134
+ const sharedDefaults = {
135
+ managed: defaults.files.managed.filter((entry) =>
136
+ !isPathOwnedByOtherPlatform(entry, input.platformType)
137
+ ),
138
+ merged: defaults.files.merged.filter((entry) =>
139
+ !isPathOwnedByOtherPlatform(entry, input.platformType)
140
+ ),
141
+ ejected: defaults.files.ejected
142
+ };
143
+ const projectAssets = planAgentClientProjectAssets({
144
+ current,
145
+ sharedDefaults,
146
+ enabledAdapters: listEnabledAgentClientAdapters(desired),
147
+ allAdapters: adapters
148
+ });
149
+
150
+ const nextConfig = structuredClone(input.config);
151
+ nextConfig.agentClients = serializeAgentClients(desired);
152
+ delete nextConfig.tuis;
153
+ if (normalized.remainingSandboxTools !== undefined) {
154
+ const sandbox = isRecord(nextConfig.sandbox) ? { ...nextConfig.sandbox } : {};
155
+ sandbox.tools = [
156
+ 'agent-infra',
157
+ ...normalized.remainingSandboxTools.filter((tool) => tool !== 'agent-infra')
158
+ ];
159
+ nextConfig.sandbox = sandbox;
160
+ }
161
+ const existingFiles = isRecord(nextConfig.files) ? nextConfig.files : {};
162
+ const existingBaselines = isRecord(existingFiles.managedBaselines)
163
+ ? existingFiles.managedBaselines
164
+ : {};
165
+ const baselines: Record<string, unknown> = { ...existingBaselines };
166
+ nextConfig.files = {
167
+ ...existingFiles,
168
+ managed: [...projectAssets.registry.managed],
169
+ merged: [...projectAssets.registry.merged],
170
+ ejected: [...projectAssets.registry.ejected],
171
+ managedBaselines: baselines
172
+ };
173
+
174
+ const projectName = String(input.config.project ?? '');
175
+ const language = input.language === 'zh-CN' ? 'zh-CN' : 'en';
176
+ const seedOperations: AgentClientSeedOperation[] = [];
177
+ for (const adapter of adapters) {
178
+ for (const seed of adapter.project.seedCommands) {
179
+ const relativeTarget = seed.target.replaceAll('${projectName}', projectName);
180
+ const target = path.join(input.projectRoot, relativeTarget);
181
+ const templatePath = path.join(input.templateRoot, seed.templates[language]);
182
+ const content = renderSeed(fs.readFileSync(templatePath, 'utf8'), input.config);
183
+ const templateHash = hash(content);
184
+ const exists = fs.existsSync(target);
185
+ const localHash = exists ? hash(fs.readFileSync(target)) : null;
186
+ const baseline = trustedBaseline(baselines[relativeTarget]);
187
+ const enabled = desired[adapter.id].enabled;
188
+
189
+ if (enabled) {
190
+ if (!exists || (baseline !== null && localHash === baseline)) {
191
+ baselines[relativeTarget] = templateHash;
192
+ seedOperations.push({
193
+ kind: localHash === templateHash ? 'unchanged' : 'write',
194
+ clientId: adapter.id,
195
+ target: relativeTarget,
196
+ ...(localHash === templateHash ? {} : { content })
197
+ });
198
+ } else if (localHash === templateHash) {
199
+ baselines[relativeTarget] = templateHash;
200
+ seedOperations.push({ kind: 'unchanged', clientId: adapter.id, target: relativeTarget });
201
+ } else {
202
+ seedOperations.push({
203
+ kind: 'protect',
204
+ clientId: adapter.id,
205
+ target: relativeTarget,
206
+ reason: baseline ? 'user-modified' : 'unknown-origin'
207
+ });
208
+ }
209
+ } else if (!exists) {
210
+ delete baselines[relativeTarget];
211
+ seedOperations.push({ kind: 'unchanged', clientId: adapter.id, target: relativeTarget });
212
+ } else if ((baseline !== null && localHash === baseline) || localHash === templateHash) {
213
+ delete baselines[relativeTarget];
214
+ seedOperations.push({ kind: 'remove', clientId: adapter.id, target: relativeTarget });
215
+ } else {
216
+ seedOperations.push({
217
+ kind: 'protect',
218
+ clientId: adapter.id,
219
+ target: relativeTarget,
220
+ reason: baseline ? 'user-modified' : 'unknown-origin'
221
+ });
222
+ }
223
+ }
224
+ }
225
+ if (Object.keys(baselines).length === 0) {
226
+ delete (nextConfig.files as Record<string, unknown>).managedBaselines;
227
+ }
228
+
229
+ const custom = normalizeCustomTUIs(input.projectRoot, input.config.customTUIs ?? []);
230
+ const nextSteps = renderNextStepCommands({
231
+ projectName,
232
+ state: desired,
233
+ customTUIs: custom.items,
234
+ skillName: 'update-agent-infra'
235
+ });
236
+ const actionableSeed = seedOperations.some((operation) =>
237
+ operation.kind === 'write' || operation.kind === 'remove'
238
+ );
239
+ const changed = JSON.stringify(nextConfig) !== JSON.stringify(input.config) || actionableSeed;
240
+
241
+ return Object.freeze({
242
+ source: normalized.source,
243
+ before: normalized.state,
244
+ desired,
245
+ nextConfig,
246
+ projectAssets,
247
+ seedOperations: Object.freeze(seedOperations),
248
+ nextSteps,
249
+ diagnostics: Object.freeze([...normalized.diagnostics, ...custom.diagnostics]),
250
+ changed,
251
+ projectRoot: input.projectRoot,
252
+ configPath: path.join(input.projectRoot, '.agents', '.airc.json')
253
+ });
254
+ }
255
+
256
+ function applyAgentClientReconciliation(
257
+ plan: AgentClientWorkflowPlan,
258
+ dependencies: AgentClientReconcileDependencies = {}
259
+ ): AgentClientReconcileResult {
260
+ const mkdirSync = dependencies.mkdirSync ?? fs.mkdirSync;
261
+ const writeFileSync = dependencies.writeFileSync ?? fs.writeFileSync;
262
+ const renameSync = dependencies.renameSync ?? fs.renameSync;
263
+ const unlinkSync = dependencies.unlinkSync ?? fs.unlinkSync;
264
+ const applied: string[] = [];
265
+ const protectedTargets: string[] = [];
266
+ const unchanged: string[] = [];
267
+
268
+ for (const operation of plan.seedOperations) {
269
+ const target = path.join(plan.projectRoot, operation.target);
270
+ if (operation.kind === 'write') {
271
+ mkdirSync(path.dirname(target), { recursive: true });
272
+ writeFileSync(target, operation.content!, 'utf8');
273
+ applied.push(operation.target);
274
+ } else if (operation.kind === 'remove') {
275
+ if (fs.existsSync(target)) unlinkSync(target);
276
+ applied.push(operation.target);
277
+ } else if (operation.kind === 'protect') {
278
+ protectedTargets.push(operation.target);
279
+ } else {
280
+ unchanged.push(operation.target);
281
+ }
282
+ }
283
+
284
+ const serialized = `${JSON.stringify(plan.nextConfig, null, 2)}\n`;
285
+ const previous = fs.existsSync(plan.configPath)
286
+ ? fs.readFileSync(plan.configPath, 'utf8')
287
+ : null;
288
+ const configUpdated = previous !== serialized;
289
+ if (configUpdated) {
290
+ mkdirSync(path.dirname(plan.configPath), { recursive: true });
291
+ const temporary = `${plan.configPath}.tmp-${process.pid}-${Date.now()}`;
292
+ try {
293
+ writeFileSync(temporary, serialized, 'utf8');
294
+ renameSync(temporary, plan.configPath);
295
+ } catch (error) {
296
+ try {
297
+ if (fs.existsSync(temporary)) unlinkSync(temporary);
298
+ } catch {
299
+ // Preserve the original error; a later idempotent rerun can clean stale state.
300
+ }
301
+ throw error;
302
+ }
303
+ }
304
+
305
+ return Object.freeze({
306
+ status: configUpdated || applied.length > 0 ? 'applied' : 'unchanged',
307
+ configUpdated,
308
+ applied: Object.freeze(applied),
309
+ protected: Object.freeze(protectedTargets),
310
+ unchanged: Object.freeze(unchanged)
311
+ });
312
+ }
313
+
314
+ export {
315
+ applyAgentClientReconciliation,
316
+ planAgentClientReconciliation
317
+ };
318
+ export type {
319
+ AgentClientReconcileDependencies,
320
+ AgentClientReconcileResult,
321
+ AgentClientSeedOperation,
322
+ AgentClientWorkflowMutation,
323
+ AgentClientWorkflowPlan
324
+ };
@@ -0,0 +1,144 @@
1
+ import { BUILTIN_AGENT_CLIENT_ADAPTERS } from './adapters/index.ts';
2
+ import { AGENT_CLIENT_IDS, isAgentClientId } from './types.ts';
3
+ import type {
4
+ AgentClientAdapter,
5
+ AgentClientManifestEntry,
6
+ AgentClientRegistry
7
+ } from './adapter.ts';
8
+ import type {
9
+ AgentClientCapabilityId,
10
+ AgentClientCapabilitySupport,
11
+ AgentClientState,
12
+ AgentClientSupportLevel
13
+ } from './types.ts';
14
+
15
+ function buildRegistry(): Readonly<{
16
+ registry: AgentClientRegistry;
17
+ adapters: readonly AgentClientAdapter[];
18
+ }> {
19
+ if (BUILTIN_AGENT_CLIENT_ADAPTERS.length !== AGENT_CLIENT_IDS.length) {
20
+ throw new Error('Agent Client Registry is incomplete');
21
+ }
22
+
23
+ const byId = new Map(
24
+ BUILTIN_AGENT_CLIENT_ADAPTERS.map((adapter) => [adapter.id, adapter])
25
+ );
26
+ if (byId.size !== BUILTIN_AGENT_CLIENT_ADAPTERS.length) {
27
+ throw new Error('Agent Client Registry contains duplicate IDs');
28
+ }
29
+
30
+ const adapters = Object.freeze(
31
+ AGENT_CLIENT_IDS.map((id) => {
32
+ const adapter = byId.get(id);
33
+ if (!adapter || adapter.id !== id) {
34
+ throw new Error(`Agent Client Registry is missing '${id}'`);
35
+ }
36
+ return adapter;
37
+ })
38
+ );
39
+
40
+ const ownedPaths = adapters.flatMap((adapter) =>
41
+ adapter.project.ownedPathPrefixes.map((prefix) => ({
42
+ id: adapter.id,
43
+ prefix
44
+ }))
45
+ );
46
+ for (const [index, current] of ownedPaths.entries()) {
47
+ for (const other of ownedPaths.slice(index + 1)) {
48
+ if (
49
+ current.id !== other.id
50
+ && (
51
+ current.prefix.startsWith(other.prefix)
52
+ || other.prefix.startsWith(current.prefix)
53
+ )
54
+ ) {
55
+ throw new Error(
56
+ `Agent Client owned paths overlap: '${current.prefix}' and '${other.prefix}'`
57
+ );
58
+ }
59
+ }
60
+ }
61
+
62
+ const registry = Object.freeze(
63
+ Object.fromEntries(adapters.map((adapter) => [adapter.id, adapter]))
64
+ ) as AgentClientRegistry;
65
+
66
+ return Object.freeze({ registry, adapters });
67
+ }
68
+
69
+ const built = buildRegistry();
70
+ const AGENT_CLIENT_REGISTRY = built.registry;
71
+ const AGENT_CLIENT_ADAPTERS = built.adapters;
72
+
73
+ function listAgentClientAdapters(): readonly AgentClientAdapter[] {
74
+ return AGENT_CLIENT_ADAPTERS;
75
+ }
76
+
77
+ function getAgentClientAdapter(id: unknown): AgentClientAdapter {
78
+ if (!isAgentClientId(id)) {
79
+ throw new Error(`No Agent Client adapter registered for '${String(id)}'`);
80
+ }
81
+ return AGENT_CLIENT_REGISTRY[id];
82
+ }
83
+
84
+ function listEnabledAgentClientAdapters(
85
+ state: AgentClientState
86
+ ): readonly AgentClientAdapter[] {
87
+ return Object.freeze(
88
+ AGENT_CLIENT_ADAPTERS.filter((adapter) => state[adapter.id].enabled)
89
+ );
90
+ }
91
+
92
+ function listInstalledAgentClientAdapters(
93
+ state: AgentClientState
94
+ ): readonly AgentClientAdapter[] {
95
+ return Object.freeze(
96
+ AGENT_CLIENT_ADAPTERS.filter((adapter) => state[adapter.id].installInSandbox)
97
+ );
98
+ }
99
+
100
+ function getAgentClientCapability(
101
+ id: unknown,
102
+ capability: AgentClientCapabilityId
103
+ ): AgentClientCapabilitySupport {
104
+ return getAgentClientAdapter(id).capabilities[capability];
105
+ }
106
+
107
+ function listEnabledAgentClientAdaptersByCapability(
108
+ state: AgentClientState,
109
+ capability: AgentClientCapabilityId,
110
+ acceptedLevels: readonly AgentClientSupportLevel[]
111
+ ): readonly AgentClientAdapter[] {
112
+ const accepted = new Set(acceptedLevels);
113
+ return Object.freeze(
114
+ AGENT_CLIENT_ADAPTERS.filter((adapter) =>
115
+ state[adapter.id].enabled
116
+ && accepted.has(adapter.capabilities[capability].level)
117
+ )
118
+ );
119
+ }
120
+
121
+ function createAgentClientManifest(): readonly AgentClientManifestEntry[] {
122
+ return Object.freeze(
123
+ AGENT_CLIENT_ADAPTERS.map((adapter) => Object.freeze({
124
+ id: adapter.id,
125
+ displayName: adapter.displayName,
126
+ invocation: adapter.invocation,
127
+ ownedPathPrefixes: Object.freeze([...adapter.project.ownedPathPrefixes]),
128
+ managed: Object.freeze([...adapter.project.managed]),
129
+ merged: Object.freeze([...adapter.project.merged]),
130
+ ejected: Object.freeze([...adapter.project.ejected])
131
+ }))
132
+ );
133
+ }
134
+
135
+ export {
136
+ AGENT_CLIENT_REGISTRY,
137
+ createAgentClientManifest,
138
+ getAgentClientAdapter,
139
+ getAgentClientCapability,
140
+ listAgentClientAdapters,
141
+ listEnabledAgentClientAdapters,
142
+ listEnabledAgentClientAdaptersByCapability,
143
+ listInstalledAgentClientAdapters
144
+ };
@@ -0,0 +1,62 @@
1
+ import {
2
+ AGENT_CLIENT_CAPABILITY_IDS,
3
+ AGENT_CLIENT_IDS,
4
+ AGENT_CLIENT_SUPPORT_LEVELS
5
+ } from './types.ts';
6
+
7
+ const AGENT_CLIENTS_SCHEMA = {
8
+ $schema: 'http://json-schema.org/draft-07/schema#',
9
+ type: 'object',
10
+ required: ['agentClients'],
11
+ properties: {
12
+ agentClients: {
13
+ type: 'array',
14
+ minItems: AGENT_CLIENT_IDS.length,
15
+ maxItems: AGENT_CLIENT_IDS.length,
16
+ uniqueItems: true,
17
+ description: 'Canonical Agent Client configuration. Object ID completeness and uniqueness are enforced by the runtime normalizer.',
18
+ items: {
19
+ type: 'object',
20
+ additionalProperties: false,
21
+ required: ['id', 'enabled', 'installInSandbox'],
22
+ properties: {
23
+ id: {
24
+ type: 'string',
25
+ enum: [...AGENT_CLIENT_IDS]
26
+ },
27
+ enabled: {
28
+ type: 'boolean'
29
+ },
30
+ installInSandbox: {
31
+ type: 'boolean'
32
+ }
33
+ }
34
+ }
35
+ },
36
+ customTUIs: {
37
+ type: 'array',
38
+ items: {
39
+ type: 'object',
40
+ additionalProperties: true,
41
+ required: ['name', 'dir', 'invoke'],
42
+ properties: {
43
+ name: { type: 'string' },
44
+ dir: { type: 'string' },
45
+ invoke: { type: 'string' }
46
+ }
47
+ }
48
+ }
49
+ },
50
+ definitions: {
51
+ agentClientCapabilityId: {
52
+ type: 'string',
53
+ enum: [...AGENT_CLIENT_CAPABILITY_IDS]
54
+ },
55
+ agentClientSupportLevel: {
56
+ type: 'string',
57
+ enum: [...AGENT_CLIENT_SUPPORT_LEVELS]
58
+ }
59
+ }
60
+ } as const;
61
+
62
+ export { AGENT_CLIENTS_SCHEMA };
@@ -0,0 +1,78 @@
1
+ const AGENT_CLIENT_IDS = [
2
+ 'claude-code',
3
+ 'codex',
4
+ 'gemini-cli',
5
+ 'opencode'
6
+ ] as const;
7
+
8
+ type AgentClientId = (typeof AGENT_CLIENT_IDS)[number];
9
+
10
+ type AgentClientConfig = Readonly<{
11
+ id: AgentClientId;
12
+ enabled: boolean;
13
+ installInSandbox: boolean;
14
+ }>;
15
+
16
+ type AgentClientsConfig = readonly AgentClientConfig[];
17
+
18
+ type AgentClientState = Readonly<
19
+ Record<
20
+ AgentClientId,
21
+ Readonly<{
22
+ enabled: boolean;
23
+ installInSandbox: boolean;
24
+ }>
25
+ >
26
+ >;
27
+
28
+ const AGENT_CLIENT_CAPABILITY_IDS = [
29
+ 'instructions',
30
+ 'skills',
31
+ 'commands',
32
+ 'hooks',
33
+ 'subagents',
34
+ 'orchestration',
35
+ 'sandbox',
36
+ 'verification'
37
+ ] as const;
38
+
39
+ const AGENT_CLIENT_SUPPORT_LEVELS = [
40
+ 'compatible',
41
+ 'integrated',
42
+ 'verified',
43
+ 'experimental',
44
+ 'unsupported'
45
+ ] as const;
46
+
47
+ type AgentClientCapabilityId = (typeof AGENT_CLIENT_CAPABILITY_IDS)[number];
48
+ type AgentClientSupportLevel = (typeof AGENT_CLIENT_SUPPORT_LEVELS)[number];
49
+
50
+ type AgentClientCapabilitySupport = Readonly<{
51
+ level: AgentClientSupportLevel;
52
+ }>;
53
+
54
+ type AgentClientCapabilityMap = Readonly<
55
+ Record<AgentClientCapabilityId, AgentClientCapabilitySupport>
56
+ >;
57
+
58
+ function isAgentClientId(value: unknown): value is AgentClientId {
59
+ return typeof value === 'string'
60
+ && (AGENT_CLIENT_IDS as readonly string[]).includes(value);
61
+ }
62
+
63
+ export {
64
+ AGENT_CLIENT_IDS,
65
+ AGENT_CLIENT_CAPABILITY_IDS,
66
+ AGENT_CLIENT_SUPPORT_LEVELS,
67
+ isAgentClientId
68
+ };
69
+ export type {
70
+ AgentClientId,
71
+ AgentClientConfig,
72
+ AgentClientsConfig,
73
+ AgentClientState,
74
+ AgentClientCapabilityId,
75
+ AgentClientSupportLevel,
76
+ AgentClientCapabilitySupport,
77
+ AgentClientCapabilityMap
78
+ };