@fitlab-ai/agent-infra 0.5.1 → 0.5.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 (191) hide show
  1. package/README.md +15 -1
  2. package/README.zh-CN.md +15 -1
  3. package/lib/defaults.json +3 -0
  4. package/lib/init.js +15 -5
  5. package/lib/render.js +77 -15
  6. package/lib/sandbox/commands/enter.js +22 -7
  7. package/lib/sandbox/runtimes/base.dockerfile +10 -0
  8. package/lib/update.js +19 -5
  9. package/package.json +2 -1
  10. package/templates/.agents/{README.md → README.en.md} +13 -3
  11. package/templates/.agents/README.zh-CN.md +13 -3
  12. package/templates/.agents/rules/issue-pr-commands.github.en.md +111 -0
  13. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +111 -0
  14. package/templates/.agents/rules/label-milestone-setup.github.en.md +50 -0
  15. package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +50 -0
  16. package/templates/.agents/rules/release-commands.github.en.md +30 -0
  17. package/templates/.agents/rules/release-commands.github.zh-CN.md +30 -0
  18. package/templates/.agents/rules/security-alerts.github.en.md +43 -0
  19. package/templates/.agents/rules/security-alerts.github.zh-CN.md +43 -0
  20. package/templates/.agents/scripts/validate-artifact.js +0 -2
  21. package/templates/.agents/skills/cancel-task/{SKILL.md → SKILL.en.md} +7 -6
  22. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -6
  23. package/templates/.agents/skills/close-codescan/{SKILL.md → SKILL.en.md} +3 -11
  24. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -11
  25. package/templates/.agents/skills/close-dependabot/{SKILL.md → SKILL.en.md} +5 -13
  26. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +5 -13
  27. package/templates/.agents/skills/create-issue/{SKILL.md → SKILL.en.md} +2 -5
  28. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +2 -5
  29. package/templates/.agents/skills/create-issue/reference/{label-and-type.md → label-and-type.en.md} +4 -16
  30. package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +4 -16
  31. package/templates/.agents/skills/create-pr/{SKILL.md → SKILL.en.md} +3 -4
  32. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -4
  33. package/templates/.agents/skills/create-pr/reference/{pr-body-template.md → pr-body-template.en.md} +8 -13
  34. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +8 -13
  35. package/templates/.agents/skills/create-release-note/{SKILL.md → SKILL.en.md} +6 -18
  36. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +6 -18
  37. package/templates/.agents/skills/import-codescan/{SKILL.md → SKILL.en.md} +1 -3
  38. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -3
  39. package/templates/.agents/skills/import-dependabot/{SKILL.md → SKILL.en.md} +1 -3
  40. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -3
  41. package/templates/.agents/skills/import-issue/{SKILL.md → SKILL.en.md} +2 -10
  42. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +2 -10
  43. package/templates/.agents/skills/init-labels/{SKILL.md → SKILL.en.md} +9 -13
  44. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -13
  45. package/templates/.agents/skills/init-milestones/{SKILL.md → SKILL.en.md} +5 -6
  46. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +5 -6
  47. package/templates/.agents/skills/refine-title/{SKILL.md → SKILL.en.md} +7 -17
  48. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +6 -16
  49. package/templates/.agents/skills/release/{SKILL.md → SKILL.en.md} +2 -1
  50. package/templates/.agents/skills/release/SKILL.zh-CN.md +2 -1
  51. package/templates/.agents/skills/restore-task/{SKILL.md → SKILL.en.md} +5 -11
  52. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +5 -11
  53. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +219 -59
  54. /package/templates/.agents/{QUICKSTART.md → QUICKSTART.en.md} +0 -0
  55. /package/templates/.agents/rules/{commit-and-pr.md → commit-and-pr.en.md} +0 -0
  56. /package/templates/.agents/rules/{issue-sync.md → issue-sync.github.en.md} +0 -0
  57. /package/templates/.agents/rules/{issue-sync.zh-CN.md → issue-sync.github.zh-CN.md} +0 -0
  58. /package/templates/.agents/rules/{milestone-inference.md → milestone-inference.github.en.md} +0 -0
  59. /package/templates/.agents/rules/{milestone-inference.zh-CN.md → milestone-inference.github.zh-CN.md} +0 -0
  60. /package/templates/.agents/rules/{pr-sync.md → pr-sync.github.en.md} +0 -0
  61. /package/templates/.agents/rules/{pr-sync.zh-CN.md → pr-sync.github.zh-CN.md} +0 -0
  62. /package/templates/.agents/rules/{task-management.md → task-management.en.md} +0 -0
  63. /package/templates/.agents/skills/analyze-task/{SKILL.md → SKILL.en.md} +0 -0
  64. /package/templates/.agents/skills/archive-tasks/{SKILL.md → SKILL.en.md} +0 -0
  65. /package/templates/.agents/skills/block-task/{SKILL.md → SKILL.en.md} +0 -0
  66. /package/templates/.agents/skills/check-task/{SKILL.md → SKILL.en.md} +0 -0
  67. /package/templates/.agents/skills/commit/{SKILL.md → SKILL.en.md} +0 -0
  68. /package/templates/.agents/skills/commit/reference/{commit-message.md → commit-message.en.md} +0 -0
  69. /package/templates/.agents/skills/commit/reference/{copyright-check.md → copyright-check.en.md} +0 -0
  70. /package/templates/.agents/skills/commit/reference/{pr-summary-sync.md → pr-summary-sync.en.md} +0 -0
  71. /package/templates/.agents/skills/commit/reference/{task-status-update.md → task-status-update.en.md} +0 -0
  72. /package/templates/.agents/skills/complete-task/{SKILL.md → SKILL.en.md} +0 -0
  73. /package/templates/.agents/skills/create-issue/reference/{template-matching.md → template-matching.en.md} +0 -0
  74. /package/templates/.agents/skills/create-pr/reference/{branch-strategy.md → branch-strategy.en.md} +0 -0
  75. /package/templates/.agents/skills/create-pr/reference/{comment-publish.md → comment-publish.en.md} +0 -0
  76. /package/templates/.agents/skills/create-task/{SKILL.md → SKILL.en.md} +0 -0
  77. /package/templates/.agents/skills/implement-task/{SKILL.md → SKILL.en.md} +0 -0
  78. /package/templates/.agents/skills/implement-task/reference/{branch-management.md → branch-management.en.md} +0 -0
  79. /package/templates/.agents/skills/implement-task/reference/{implementation-rules.md → implementation-rules.en.md} +0 -0
  80. /package/templates/.agents/skills/implement-task/reference/{output-template.md → output-template.en.md} +0 -0
  81. /package/templates/.agents/skills/implement-task/reference/{report-template.md → report-template.en.md} +0 -0
  82. /package/templates/.agents/skills/init-labels/scripts/{init-labels.sh → init-labels.github.sh} +0 -0
  83. /package/templates/.agents/skills/init-milestones/scripts/{init-milestones.sh → init-milestones.github.sh} +0 -0
  84. /package/templates/.agents/skills/plan-task/{SKILL.md → SKILL.en.md} +0 -0
  85. /package/templates/.agents/skills/refine-task/{SKILL.md → SKILL.en.md} +0 -0
  86. /package/templates/.agents/skills/refine-task/reference/{fix-workflow.md → fix-workflow.en.md} +0 -0
  87. /package/templates/.agents/skills/refine-task/reference/{report-template.md → report-template.en.md} +0 -0
  88. /package/templates/.agents/skills/release/scripts/{manage-milestones.sh → manage-milestones.github.sh} +0 -0
  89. /package/templates/.agents/skills/review-task/{SKILL.md → SKILL.en.md} +0 -0
  90. /package/templates/.agents/skills/review-task/reference/{output-templates.md → output-templates.en.md} +0 -0
  91. /package/templates/.agents/skills/review-task/reference/{report-template.md → report-template.en.md} +0 -0
  92. /package/templates/.agents/skills/review-task/reference/{review-criteria.md → review-criteria.en.md} +0 -0
  93. /package/templates/.agents/skills/test/{SKILL.md → SKILL.en.md} +0 -0
  94. /package/templates/.agents/skills/test-integration/{SKILL.md → SKILL.en.md} +0 -0
  95. /package/templates/.agents/skills/update-agent-infra/{SKILL.md → SKILL.en.md} +0 -0
  96. /package/templates/.agents/skills/upgrade-dependency/{SKILL.md → SKILL.en.md} +0 -0
  97. /package/templates/.agents/templates/{handoff.md → handoff.en.md} +0 -0
  98. /package/templates/.agents/templates/{review-report.md → review-report.en.md} +0 -0
  99. /package/templates/.agents/templates/{task.md → task.en.md} +0 -0
  100. /package/templates/.agents/workflows/{bug-fix.yaml → bug-fix.en.yaml} +0 -0
  101. /package/templates/.agents/workflows/{code-review.yaml → code-review.en.yaml} +0 -0
  102. /package/templates/.agents/workflows/{feature-development.yaml → feature-development.en.yaml} +0 -0
  103. /package/templates/.agents/workflows/{refactoring.yaml → refactoring.en.yaml} +0 -0
  104. /package/templates/.agents/workspace/{README.md → README.en.md} +0 -0
  105. /package/templates/.claude/commands/{analyze-task.md → analyze-task.en.md} +0 -0
  106. /package/templates/.claude/commands/{archive-tasks.md → archive-tasks.en.md} +0 -0
  107. /package/templates/.claude/commands/{block-task.md → block-task.en.md} +0 -0
  108. /package/templates/.claude/commands/{cancel-task.md → cancel-task.en.md} +0 -0
  109. /package/templates/.claude/commands/{check-task.md → check-task.en.md} +0 -0
  110. /package/templates/.claude/commands/{close-codescan.md → close-codescan.en.md} +0 -0
  111. /package/templates/.claude/commands/{close-dependabot.md → close-dependabot.en.md} +0 -0
  112. /package/templates/.claude/commands/{commit.md → commit.en.md} +0 -0
  113. /package/templates/.claude/commands/{complete-task.md → complete-task.en.md} +0 -0
  114. /package/templates/.claude/commands/{create-issue.md → create-issue.en.md} +0 -0
  115. /package/templates/.claude/commands/{create-pr.md → create-pr.en.md} +0 -0
  116. /package/templates/.claude/commands/{create-release-note.md → create-release-note.en.md} +0 -0
  117. /package/templates/.claude/commands/{create-task.md → create-task.en.md} +0 -0
  118. /package/templates/.claude/commands/{implement-task.md → implement-task.en.md} +0 -0
  119. /package/templates/.claude/commands/{import-codescan.md → import-codescan.en.md} +0 -0
  120. /package/templates/.claude/commands/{import-dependabot.md → import-dependabot.en.md} +0 -0
  121. /package/templates/.claude/commands/{import-issue.md → import-issue.en.md} +0 -0
  122. /package/templates/.claude/commands/{init-labels.md → init-labels.en.md} +0 -0
  123. /package/templates/.claude/commands/{init-milestones.md → init-milestones.en.md} +0 -0
  124. /package/templates/.claude/commands/{plan-task.md → plan-task.en.md} +0 -0
  125. /package/templates/.claude/commands/{refine-task.md → refine-task.en.md} +0 -0
  126. /package/templates/.claude/commands/{refine-title.md → refine-title.en.md} +0 -0
  127. /package/templates/.claude/commands/{release.md → release.en.md} +0 -0
  128. /package/templates/.claude/commands/{restore-task.md → restore-task.en.md} +0 -0
  129. /package/templates/.claude/commands/{review-task.md → review-task.en.md} +0 -0
  130. /package/templates/.claude/commands/{test-integration.md → test-integration.en.md} +0 -0
  131. /package/templates/.claude/commands/{test.md → test.en.md} +0 -0
  132. /package/templates/.claude/commands/{update-agent-infra.md → update-agent-infra.en.md} +0 -0
  133. /package/templates/.claude/commands/{upgrade-dependency.md → upgrade-dependency.en.md} +0 -0
  134. /package/templates/.gemini/commands/_project_/{analyze-task.toml → analyze-task.en.toml} +0 -0
  135. /package/templates/.gemini/commands/_project_/{archive-tasks.toml → archive-tasks.en.toml} +0 -0
  136. /package/templates/.gemini/commands/_project_/{block-task.toml → block-task.en.toml} +0 -0
  137. /package/templates/.gemini/commands/_project_/{cancel-task.toml → cancel-task.en.toml} +0 -0
  138. /package/templates/.gemini/commands/_project_/{check-task.toml → check-task.en.toml} +0 -0
  139. /package/templates/.gemini/commands/_project_/{close-codescan.toml → close-codescan.en.toml} +0 -0
  140. /package/templates/.gemini/commands/_project_/{close-dependabot.toml → close-dependabot.en.toml} +0 -0
  141. /package/templates/.gemini/commands/_project_/{commit.toml → commit.en.toml} +0 -0
  142. /package/templates/.gemini/commands/_project_/{complete-task.toml → complete-task.en.toml} +0 -0
  143. /package/templates/.gemini/commands/_project_/{create-issue.toml → create-issue.en.toml} +0 -0
  144. /package/templates/.gemini/commands/_project_/{create-pr.toml → create-pr.en.toml} +0 -0
  145. /package/templates/.gemini/commands/_project_/{create-release-note.toml → create-release-note.en.toml} +0 -0
  146. /package/templates/.gemini/commands/_project_/{create-task.toml → create-task.en.toml} +0 -0
  147. /package/templates/.gemini/commands/_project_/{implement-task.toml → implement-task.en.toml} +0 -0
  148. /package/templates/.gemini/commands/_project_/{import-codescan.toml → import-codescan.en.toml} +0 -0
  149. /package/templates/.gemini/commands/_project_/{import-dependabot.toml → import-dependabot.en.toml} +0 -0
  150. /package/templates/.gemini/commands/_project_/{import-issue.toml → import-issue.en.toml} +0 -0
  151. /package/templates/.gemini/commands/_project_/{init-labels.toml → init-labels.en.toml} +0 -0
  152. /package/templates/.gemini/commands/_project_/{init-milestones.toml → init-milestones.en.toml} +0 -0
  153. /package/templates/.gemini/commands/_project_/{plan-task.toml → plan-task.en.toml} +0 -0
  154. /package/templates/.gemini/commands/_project_/{refine-task.toml → refine-task.en.toml} +0 -0
  155. /package/templates/.gemini/commands/_project_/{refine-title.toml → refine-title.en.toml} +0 -0
  156. /package/templates/.gemini/commands/_project_/{release.toml → release.en.toml} +0 -0
  157. /package/templates/.gemini/commands/_project_/{restore-task.toml → restore-task.en.toml} +0 -0
  158. /package/templates/.gemini/commands/_project_/{review-task.toml → review-task.en.toml} +0 -0
  159. /package/templates/.gemini/commands/_project_/{test-integration.toml → test-integration.en.toml} +0 -0
  160. /package/templates/.gemini/commands/_project_/{test.toml → test.en.toml} +0 -0
  161. /package/templates/.gemini/commands/_project_/{update-agent-infra.toml → update-agent-infra.en.toml} +0 -0
  162. /package/templates/.gemini/commands/_project_/{upgrade-dependency.toml → upgrade-dependency.en.toml} +0 -0
  163. /package/templates/.opencode/commands/{analyze-task.md → analyze-task.en.md} +0 -0
  164. /package/templates/.opencode/commands/{archive-tasks.md → archive-tasks.en.md} +0 -0
  165. /package/templates/.opencode/commands/{block-task.md → block-task.en.md} +0 -0
  166. /package/templates/.opencode/commands/{cancel-task.md → cancel-task.en.md} +0 -0
  167. /package/templates/.opencode/commands/{check-task.md → check-task.en.md} +0 -0
  168. /package/templates/.opencode/commands/{close-codescan.md → close-codescan.en.md} +0 -0
  169. /package/templates/.opencode/commands/{close-dependabot.md → close-dependabot.en.md} +0 -0
  170. /package/templates/.opencode/commands/{commit.md → commit.en.md} +0 -0
  171. /package/templates/.opencode/commands/{complete-task.md → complete-task.en.md} +0 -0
  172. /package/templates/.opencode/commands/{create-issue.md → create-issue.en.md} +0 -0
  173. /package/templates/.opencode/commands/{create-pr.md → create-pr.en.md} +0 -0
  174. /package/templates/.opencode/commands/{create-release-note.md → create-release-note.en.md} +0 -0
  175. /package/templates/.opencode/commands/{create-task.md → create-task.en.md} +0 -0
  176. /package/templates/.opencode/commands/{implement-task.md → implement-task.en.md} +0 -0
  177. /package/templates/.opencode/commands/{import-codescan.md → import-codescan.en.md} +0 -0
  178. /package/templates/.opencode/commands/{import-dependabot.md → import-dependabot.en.md} +0 -0
  179. /package/templates/.opencode/commands/{import-issue.md → import-issue.en.md} +0 -0
  180. /package/templates/.opencode/commands/{init-labels.md → init-labels.en.md} +0 -0
  181. /package/templates/.opencode/commands/{init-milestones.md → init-milestones.en.md} +0 -0
  182. /package/templates/.opencode/commands/{plan-task.md → plan-task.en.md} +0 -0
  183. /package/templates/.opencode/commands/{refine-task.md → refine-task.en.md} +0 -0
  184. /package/templates/.opencode/commands/{refine-title.md → refine-title.en.md} +0 -0
  185. /package/templates/.opencode/commands/{release.md → release.en.md} +0 -0
  186. /package/templates/.opencode/commands/{restore-task.md → restore-task.en.md} +0 -0
  187. /package/templates/.opencode/commands/{review-task.md → review-task.en.md} +0 -0
  188. /package/templates/.opencode/commands/{test-integration.md → test-integration.en.md} +0 -0
  189. /package/templates/.opencode/commands/{test.md → test.en.md} +0 -0
  190. /package/templates/.opencode/commands/{update-agent-infra.md → update-agent-infra.en.md} +0 -0
  191. /package/templates/.opencode/commands/{upgrade-dependency.md → upgrade-dependency.en.md} +0 -0
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: restore-task
3
- description: "Restore local task files from GitHub Issue comments"
3
+ description: "Restore local task files from platform Issue comments"
4
4
  ---
5
5
 
6
6
  # Restore Task
7
7
 
8
- Restore local task workspace files from GitHub Issue comments that contain sync markers.
8
+ Restore local task workspace files from platform Issue comments that contain sync markers.
9
9
 
10
10
  ## Boundary / Critical Rules
11
11
 
@@ -21,19 +21,13 @@ Restore local task workspace files from GitHub Issue comments that contain sync
21
21
  Check:
22
22
  - required `{issue-number}`
23
23
  - optional `{task-id}`
24
- - `gh auth status`
24
+ - read `.agents/rules/issue-pr-commands.md` first and use its authentication commands to verify current platform access
25
25
 
26
26
  If the user provided `{task-id}`, validate the `TASK-{yyyyMMdd-HHmmss}` format.
27
27
 
28
28
  ### 2. Fetch Issue Comments
29
29
 
30
- Read all Issue comments while preserving the original order and comment IDs.
31
-
32
- Suggested command:
33
-
34
- ```bash
35
- gh api "repos/{owner}/{repo}/issues/{issue-number}/comments" --paginate
36
- ```
30
+ Read all Issue comments by following the "Read Issue comments" command in `.agents/rules/issue-pr-commands.md`, preserving the original order and comment IDs.
37
31
 
38
32
  ### 3. Determine the task-id and Files to Restore
39
33
 
@@ -152,7 +146,7 @@ Stop after completing the checklist. Do not continue the workflow automatically.
152
146
  ## Error Handling
153
147
 
154
148
  - Issue missing or inaccessible
155
- - `gh` unavailable or unauthenticated
149
+ - Platform CLI unavailable or unauthenticated
156
150
  - No sync-marked comments found
157
151
  - Unable to determine a unique `task-id`
158
152
  - Target directory already exists
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: restore-task
3
- description: " GitHub Issue 评论还原本地任务文件"
3
+ description: "从平台 Issue 评论还原本地任务文件"
4
4
  ---
5
5
 
6
6
  # 还原任务
7
7
 
8
- 从带有 sync 标记的 GitHub Issue 评论中恢复本地任务工作区文件。
8
+ 从带有 sync 标记的平台 Issue 评论中恢复本地任务工作区文件。
9
9
 
10
10
  ## 行为边界 / 关键规则
11
11
 
@@ -21,19 +21,13 @@ description: "从 GitHub Issue 评论还原本地任务文件"
21
21
  检查:
22
22
  - 必填参数 `{issue-number}`
23
23
  - 可选参数 `{task-id}`
24
- - `gh auth status`
24
+ - 执行前先读取 `.agents/rules/issue-pr-commands.md`,并按其中的认证命令验证当前平台访问能力
25
25
 
26
26
  如果用户传入了 `{task-id}`,校验其格式为 `TASK-{yyyyMMdd-HHmmss}`。
27
27
 
28
28
  ### 2. 获取 Issue 评论
29
29
 
30
- 读取 Issue 的全部评论,保留原始顺序和评论 ID。
31
-
32
- 建议命令:
33
-
34
- ```bash
35
- gh api "repos/{owner}/{repo}/issues/{issue-number}/comments" --paginate
36
- ```
30
+ `.agents/rules/issue-pr-commands.md` 中的 “Issue 评论读取” 命令读取 Issue 的全部评论,保留原始顺序和评论 ID。
37
31
 
38
32
  ### 3. 确定 task-id 与待恢复文件
39
33
 
@@ -152,7 +146,7 @@ node .agents/scripts/validate-artifact.js gate restore-task .agents/workspace/ac
152
146
  ## 错误处理
153
147
 
154
148
  - Issue 不存在或无权访问
155
- - `gh` 未认证
149
+ - 平台 CLI 未认证
156
150
  - 找不到带 sync 标记的评论
157
151
  - 无法唯一确定 `task-id`
158
152
  - 目标目录已存在
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * sync-templates.js — Deterministic template sync for managed & ejected files.
4
3
  *
@@ -20,6 +19,9 @@ import path from 'node:path';
20
19
  import { fileURLToPath } from 'node:url';
21
20
 
22
21
  const DEFAULTS = {
22
+ "platform": {
23
+ "type": "github"
24
+ },
23
25
  "sandbox": {
24
26
  "runtimes": [
25
27
  "node20"
@@ -74,7 +76,11 @@ const DEFAULTS = {
74
76
  }
75
77
  };
76
78
 
77
- const INSTALLER_VERSION = "v0.5.1";
79
+ const INSTALLER_VERSION = "v0.5.2";
80
+ const PACKAGE_NAME = '@fitlab-ai/agent-infra';
81
+ // Add a new identifier here only after shipping matching .{platform}. template variants.
82
+ const KNOWN_PLATFORMS = new Set(['github']);
83
+ const KNOWN_LANGUAGES = new Set(['en', 'zh-CN']);
78
84
 
79
85
  function norm(p) { return p.replace(/\\/g, '/'); }
80
86
 
@@ -132,6 +138,53 @@ function renderPathname(p, project) {
132
138
  return p.replace(/_project_/g, project);
133
139
  }
134
140
 
141
+ function variantExt(relativePath) {
142
+ return path.extname(relativePath);
143
+ }
144
+
145
+ function variantBase(relativePath) {
146
+ const ext = variantExt(relativePath);
147
+ return relativePath.slice(0, -ext.length);
148
+ }
149
+
150
+ function withVariant(relativePath, variant) {
151
+ const ext = variantExt(relativePath);
152
+ const base = variantBase(relativePath);
153
+ return `${base}.${variant}${ext}`;
154
+ }
155
+
156
+ function stripVariant(relativePath, variant) {
157
+ return relativePath.replace(new RegExp(`\\.${variant.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.`), '.');
158
+ }
159
+
160
+ function isPlatformVariant(relativePath, platform) {
161
+ const platforms = new Set([...KNOWN_PLATFORMS, platform]);
162
+ for (const candidate of platforms) {
163
+ if (relativePath.includes(`.${candidate}.`)) {
164
+ return true;
165
+ }
166
+ }
167
+ return false;
168
+ }
169
+
170
+ function isLangVariant(relativePath) {
171
+ for (const lang of KNOWN_LANGUAGES) {
172
+ if (relativePath.includes(`.${lang}.`)) {
173
+ return true;
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+
179
+ function stripLangVariant(relativePath) {
180
+ for (const lang of KNOWN_LANGUAGES) {
181
+ if (relativePath.includes(`.${lang}.`)) {
182
+ return stripVariant(relativePath, lang);
183
+ }
184
+ }
185
+ return relativePath;
186
+ }
187
+
135
188
  function isTemplateDir(dir) {
136
189
  try {
137
190
  return fs.statSync(dir).isDirectory();
@@ -140,37 +193,107 @@ function isTemplateDir(dir) {
140
193
  }
141
194
  }
142
195
 
143
- function resolveInstalledTemplateDir(nodeModulesRoot) {
144
- if (!nodeModulesRoot) return null;
145
- const candidate = path.join(nodeModulesRoot, '@fitlab-ai', 'agent-infra', 'templates');
146
- return isTemplateDir(candidate) ? candidate : null;
196
+ function verifyPackageDir(dir) {
197
+ const pkgPath = path.join(dir, 'package.json');
198
+ if (!fs.existsSync(pkgPath)) {
199
+ return { templateRoot: null, reason: `package.json not found at ${pkgPath}` };
200
+ }
201
+
202
+ let pkg;
203
+ try {
204
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
205
+ } catch {
206
+ return { templateRoot: null, reason: `invalid package.json at ${pkgPath}` };
207
+ }
208
+
209
+ if (pkg.name !== PACKAGE_NAME) {
210
+ const packageName = typeof pkg.name === 'string' && pkg.name ? pkg.name : 'an unknown package';
211
+ return { templateRoot: null, reason: `${pkgPath} belongs to ${packageName}` };
212
+ }
213
+
214
+ const templateRoot = path.join(dir, 'templates');
215
+ if (!isTemplateDir(templateRoot)) {
216
+ return { templateRoot: null, reason: `templates/ not found at ${templateRoot}` };
217
+ }
218
+
219
+ return { templateRoot, reason: null };
147
220
  }
148
221
 
149
- function resolveTemplateRoot(projectRoot) {
222
+ function resolveUnixTemplateRoot(name) {
223
+ let linkPath;
150
224
  try {
151
- const globalRoot = childProcess.execSync('npm root -g', {
225
+ linkPath = childProcess.execSync(`command -v ${name}`, {
152
226
  encoding: 'utf8',
153
227
  stdio: ['pipe', 'pipe', 'pipe']
154
228
  }).trim();
155
- const globalTemplateRoot = resolveInstalledTemplateDir(globalRoot);
156
- if (globalTemplateRoot) return globalTemplateRoot;
157
229
  } catch {
158
- // npm may be unavailable or not configured.
230
+ return { templateRoot: null, reason: 'not found in PATH' };
159
231
  }
160
232
 
233
+ if (!linkPath) {
234
+ return { templateRoot: null, reason: 'not found in PATH' };
235
+ }
236
+
237
+ let realPath;
161
238
  try {
162
- const localRoot = childProcess.execSync('npm root', {
163
- cwd: projectRoot,
239
+ realPath = fs.realpathSync(linkPath);
240
+ } catch {
241
+ return { templateRoot: null, reason: `cannot resolve symlink target for ${linkPath}` };
242
+ }
243
+
244
+ let dir = path.dirname(realPath);
245
+ while (true) {
246
+ const pkgPath = path.join(dir, 'package.json');
247
+ if (fs.existsSync(pkgPath)) {
248
+ return verifyPackageDir(dir);
249
+ }
250
+
251
+ const parentDir = path.dirname(dir);
252
+ if (parentDir === dir) {
253
+ break;
254
+ }
255
+ dir = parentDir;
256
+ }
257
+
258
+ return { templateRoot: null, reason: `no package.json found above ${realPath}` };
259
+ }
260
+
261
+ function resolveWindowsTemplateRoot(name) {
262
+ let output;
263
+ try {
264
+ output = childProcess.execSync(`where ${name}`, {
164
265
  encoding: 'utf8',
165
266
  stdio: ['pipe', 'pipe', 'pipe']
166
267
  }).trim();
167
- const localTemplateRoot = resolveInstalledTemplateDir(localRoot);
168
- if (localTemplateRoot) return localTemplateRoot;
169
268
  } catch {
170
- // npm may be unavailable or the project may not have a local install.
269
+ return { templateRoot: null, reason: 'not found in PATH' };
270
+ }
271
+
272
+ const wrapperPaths = output.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
273
+ if (wrapperPaths.length === 0) {
274
+ return { templateRoot: null, reason: 'not found in PATH' };
171
275
  }
172
276
 
173
- return null;
277
+ const wrapperPath = wrapperPaths.find(line => /\.cmd$/i.test(line)) || wrapperPaths[0];
278
+ const packageDir = path.join(path.dirname(wrapperPath), 'node_modules', '@fitlab-ai', 'agent-infra');
279
+ return verifyPackageDir(packageDir);
280
+ }
281
+
282
+ function resolveTemplateRoot() {
283
+ const resolver = process.platform === 'win32'
284
+ ? resolveWindowsTemplateRoot
285
+ : resolveUnixTemplateRoot;
286
+ const errors = [];
287
+
288
+ for (const name of ['ai', 'agent-infra']) {
289
+ const result = resolver(name);
290
+ if (result.templateRoot) {
291
+ return result.templateRoot;
292
+ }
293
+ errors.push({ name, reason: result.reason });
294
+ }
295
+
296
+ return { templateRoot: null, errors };
174
297
  }
175
298
 
176
299
  function isBinary(fp) {
@@ -194,30 +317,60 @@ function gitUrl(dir) {
194
317
  function langSelect(rels, lang, allSet, project) {
195
318
  const sel = new Map();
196
319
 
197
- if (lang === 'zh-CN') {
198
- for (const r of rels) {
199
- if (!r.includes('.zh-CN.')) continue;
200
- const target = norm(renderPathname(r.replace(/\.zh-CN\./, '.'), project));
320
+ for (const r of rels) {
321
+ if (r.includes(`.${lang}.`)) {
322
+ const target = norm(renderPathname(stripVariant(r, lang), project));
201
323
  sel.set(target, r);
202
- }
203
- for (const r of rels) {
204
- if (r.includes('.zh-CN.')) continue;
324
+ } else if (!isLangVariant(r)) {
205
325
  const target = norm(renderPathname(r, project));
206
- if (sel.has(target)) continue;
207
- const ext = path.extname(r), base = r.slice(0, -ext.length);
208
- if (allSet.has(norm(base + '.zh-CN' + ext))) continue;
209
- sel.set(target, r);
210
- }
211
- } else {
212
- for (const r of rels) {
213
- if (r.includes('.zh-CN.')) continue;
214
- sel.set(norm(renderPathname(r, project)), r);
326
+ if (!sel.has(target)) {
327
+ sel.set(target, r);
328
+ }
215
329
  }
216
330
  }
217
331
 
218
332
  return sel;
219
333
  }
220
334
 
335
+ function platformSelect(entries, platform, project) {
336
+ const sel = new Map();
337
+
338
+ for (const [target, src] of entries) {
339
+ if (!target.includes(`.${platform}.`)) continue;
340
+ sel.set(norm(renderPathname(stripVariant(target, platform), project)), src);
341
+ }
342
+
343
+ for (const [target, src] of entries) {
344
+ const normalizedTarget = norm(renderPathname(target, project));
345
+ if (sel.has(normalizedTarget)) continue;
346
+ if (isPlatformVariant(target, platform)) continue;
347
+ sel.set(normalizedTarget, src);
348
+ }
349
+
350
+ return sel;
351
+ }
352
+
353
+ function entryVariantRels(entry, allSet, platform) {
354
+ const rels = [];
355
+ const normalized = norm(entry);
356
+ const candidates = [
357
+ normalized,
358
+ withVariant(normalized, 'en'),
359
+ withVariant(normalized, 'zh-CN'),
360
+ withVariant(normalized, platform),
361
+ withVariant(withVariant(normalized, platform), 'en'),
362
+ withVariant(withVariant(normalized, platform), 'zh-CN')
363
+ ];
364
+
365
+ for (const candidate of candidates) {
366
+ if (allSet.has(candidate) && !rels.includes(candidate)) {
367
+ rels.push(candidate);
368
+ }
369
+ }
370
+
371
+ return rels;
372
+ }
373
+
221
374
  function syncTemplates(projectRoot, templateRootOverride) {
222
375
  const configDir = path.join(projectRoot, '.agents');
223
376
  const cfgPath = path.join(configDir, '.airc.json');
@@ -228,14 +381,35 @@ function syncTemplates(projectRoot, templateRootOverride) {
228
381
 
229
382
  const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
230
383
  const configPathRel = norm(path.relative(projectRoot, cfgPath));
231
- const templateRoot = templateRootOverride || resolveTemplateRoot(projectRoot);
384
+ let templateRoot = templateRootOverride;
232
385
  if (!templateRoot) {
233
- return { error: 'Template source not found. Install via npm: npm install -g @fitlab-ai/agent-infra' };
386
+ const resolvedTemplateRoot = resolveTemplateRoot();
387
+ if (typeof resolvedTemplateRoot === 'string') {
388
+ templateRoot = resolvedTemplateRoot;
389
+ } else {
390
+ const details = resolvedTemplateRoot.errors
391
+ .map(({ name, reason }) => ` - ${name}: ${reason}`)
392
+ .join('\n');
393
+ return {
394
+ error: [
395
+ 'Template source not found.',
396
+ '',
397
+ 'Attempted binary lookups:',
398
+ details,
399
+ '',
400
+ 'Please ensure agent-infra is installed and available on PATH.',
401
+ 'If already installed, upgrade to the latest version or reinstall:',
402
+ ' npm install -g @fitlab-ai/agent-infra',
403
+ ' brew upgrade fitlab-ai/agent-infra/agent-infra || brew install fitlab-ai/agent-infra/agent-infra'
404
+ ].join('\n')
405
+ };
406
+ }
234
407
  }
235
408
  const version = INSTALLER_VERSION;
236
409
  const hadTemplateSource = Object.prototype.hasOwnProperty.call(cfg, 'templateSource');
237
410
 
238
411
  const { project, org, language: lang = 'en' } = cfg;
412
+ const platformType = cfg.platform?.type || DEFAULTS.platform.type;
239
413
  const vars = { project, org };
240
414
 
241
415
  const managed = [...(cfg.files.managed || [])];
@@ -274,15 +448,11 @@ function syncTemplates(projectRoot, templateRootOverride) {
274
448
  entryRels = walkDir(dir).map(f => norm(path.relative(templateRoot, f)));
275
449
  } else {
276
450
  entryRels = [];
277
- const n = norm(entry);
278
- if (allSet.has(n)) entryRels.push(n);
279
- const ext = path.extname(entry), base = entry.slice(0, -ext.length);
280
- const zh = norm(base + '.zh-CN' + ext);
281
- if (allSet.has(zh)) entryRels.push(zh);
451
+ entryRels = entryVariantRels(entry, allSet, platformType);
282
452
  if (!entryRels.length) continue;
283
453
  }
284
454
 
285
- const selected = langSelect(entryRels, lang, allSet, project);
455
+ const selected = platformSelect(langSelect(entryRels, lang, allSet, project), platformType, project);
286
456
 
287
457
  for (const [tgt, src] of selected) {
288
458
  if (expectedTargets) expectedTargets.add(tgt);
@@ -345,13 +515,10 @@ function syncTemplates(projectRoot, templateRootOverride) {
345
515
  continue;
346
516
  }
347
517
 
348
- let src = norm(entry);
349
- if (lang === 'zh-CN') {
350
- const ext = path.extname(entry), base = entry.slice(0, -ext.length);
351
- const zh = norm(base + '.zh-CN' + ext);
352
- if (allSet.has(zh)) src = zh;
353
- }
354
- if (!allSet.has(src)) continue;
518
+ const selected = platformSelect(langSelect(entryVariantRels(entry, allSet, platformType), lang, allSet, project), platformType, project);
519
+ const target = norm(renderPathname(entry, project));
520
+ const src = selected.get(target);
521
+ if (!src) continue;
355
522
 
356
523
  const content = renderContent(fs.readFileSync(path.join(templateRoot, src), 'utf8'), vars);
357
524
  const dir = path.dirname(dstFull);
@@ -364,22 +531,15 @@ function syncTemplates(projectRoot, templateRootOverride) {
364
531
  for (const entry of merged) {
365
532
  if (entry.includes('*')) {
366
533
  const hits = allRels.filter(r => {
367
- const t = norm(renderPathname(
368
- r.includes('.zh-CN.') ? r.replace(/\.zh-CN\./, '.') : r, project
369
- ));
534
+ const t = norm(renderPathname(stripLangVariant(r), project));
370
535
  return globMatch(entry, t);
371
536
  });
372
- for (const [t, s] of langSelect(hits, lang, allSet, project)) {
537
+ for (const [t, s] of platformSelect(langSelect(hits, lang, allSet, project), platformType, project)) {
373
538
  if (!mergedMap.has(t)) mergedMap.set(t, s);
374
539
  }
375
540
  } else {
376
- const rels = [];
377
- const n = norm(entry);
378
- if (allSet.has(n)) rels.push(n);
379
- const ext = path.extname(entry), base = entry.slice(0, -ext.length);
380
- const zh = norm(base + '.zh-CN' + ext);
381
- if (allSet.has(zh)) rels.push(zh);
382
- const selected = langSelect(rels, lang, allSet, project);
541
+ const rels = entryVariantRels(entry, allSet, platformType);
542
+ const selected = platformSelect(langSelect(rels, lang, allSet, project), platformType, project);
383
543
  for (const [t, s] of selected) {
384
544
  if (!mergedMap.has(t)) mergedMap.set(t, s);
385
545
  }