@fitlab-ai/agent-infra 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +63 -27
  2. package/README.zh-CN.md +61 -25
  3. package/bin/cli.ts +18 -6
  4. package/dist/bin/cli.js +20 -6
  5. package/dist/lib/cp.js +127 -0
  6. package/dist/lib/defaults.json +1 -0
  7. package/dist/lib/init.js +3 -0
  8. package/dist/lib/sandbox/clipboard/bridge.js +23 -4
  9. package/dist/lib/sandbox/clipboard/index.js +12 -3
  10. package/dist/lib/sandbox/commands/create.js +11 -2
  11. package/dist/lib/sandbox/commands/enter.js +29 -6
  12. package/dist/lib/sandbox/commands/list-running.js +108 -0
  13. package/dist/lib/sandbox/commands/ls.js +24 -45
  14. package/dist/lib/sandbox/commands/rebuild.js +15 -7
  15. package/dist/lib/sandbox/config.js +3 -0
  16. package/dist/lib/sandbox/index.js +6 -4
  17. package/dist/lib/sandbox/readme-scaffold.js +148 -0
  18. package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +12 -6
  19. package/dist/lib/sandbox/runtimes/base.dockerfile +3 -3
  20. package/dist/lib/sandbox/tools.js +213 -8
  21. package/dist/lib/update.js +12 -1
  22. package/lib/cp.ts +177 -0
  23. package/lib/defaults.json +1 -0
  24. package/lib/init.ts +10 -0
  25. package/lib/sandbox/clipboard/bridge.ts +23 -4
  26. package/lib/sandbox/clipboard/index.ts +12 -3
  27. package/lib/sandbox/commands/create.ts +18 -2
  28. package/lib/sandbox/commands/enter.ts +48 -6
  29. package/lib/sandbox/commands/list-running.ts +135 -0
  30. package/lib/sandbox/commands/ls.ts +28 -49
  31. package/lib/sandbox/commands/rebuild.ts +24 -7
  32. package/lib/sandbox/config.ts +7 -0
  33. package/lib/sandbox/index.ts +6 -4
  34. package/lib/sandbox/readme-scaffold.ts +177 -0
  35. package/lib/sandbox/runtimes/ai-tools.dockerfile +12 -6
  36. package/lib/sandbox/runtimes/base.dockerfile +3 -3
  37. package/lib/sandbox/tools.ts +248 -9
  38. package/lib/update.ts +15 -1
  39. package/package.json +1 -1
  40. package/templates/.agents/QUICKSTART.en.md +1 -1
  41. package/templates/.agents/QUICKSTART.zh-CN.md +1 -1
  42. package/templates/.agents/README.en.md +79 -2
  43. package/templates/.agents/README.zh-CN.md +79 -2
  44. package/templates/.agents/rules/create-issue.en.md +1 -1
  45. package/templates/.agents/rules/create-issue.github.en.md +1 -1
  46. package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
  47. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  48. package/templates/.agents/rules/issue-sync.github.en.md +6 -5
  49. package/templates/.agents/rules/issue-sync.github.zh-CN.md +6 -5
  50. package/templates/.agents/rules/milestone-inference.github.en.md +2 -2
  51. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +2 -2
  52. package/templates/.agents/rules/no-mid-flow-questions.en.md +57 -0
  53. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +57 -0
  54. package/templates/.agents/rules/pr-sync.github.en.md +4 -5
  55. package/templates/.agents/rules/pr-sync.github.zh-CN.md +4 -5
  56. package/templates/.agents/rules/task-management.en.md +9 -6
  57. package/templates/.agents/rules/task-management.zh-CN.md +9 -6
  58. package/templates/.agents/rules/testing-discipline.en.md +2 -2
  59. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -2
  60. package/templates/.agents/scripts/validate-artifact.js +1 -1
  61. package/templates/.agents/skills/analyze-task/SKILL.en.md +16 -4
  62. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +16 -4
  63. package/templates/.agents/skills/check-task/SKILL.en.md +43 -32
  64. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +42 -31
  65. package/templates/.agents/skills/code-task/SKILL.en.md +117 -0
  66. package/templates/.agents/skills/{implement-task → code-task}/SKILL.zh-CN.md +51 -24
  67. package/templates/.agents/skills/{implement-task → code-task}/config/verify.en.json +4 -4
  68. package/templates/.agents/skills/{implement-task → code-task}/config/verify.zh-CN.json +4 -4
  69. package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.zh-CN.md +2 -2
  70. package/templates/.agents/skills/{implement-task/reference/implementation-rules.en.md → code-task/reference/code-rules.en.md} +6 -6
  71. package/templates/.agents/skills/{implement-task/reference/implementation-rules.zh-CN.md → code-task/reference/code-rules.zh-CN.md} +3 -3
  72. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +69 -0
  73. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +69 -0
  74. package/templates/.agents/skills/{refine-task/reference/fix-workflow.en.md → code-task/reference/fix-mode.en.md} +12 -12
  75. package/templates/.agents/skills/{refine-task/reference/fix-workflow.zh-CN.md → code-task/reference/fix-mode.zh-CN.md} +8 -8
  76. package/templates/.agents/skills/code-task/reference/output-template.en.md +20 -0
  77. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +20 -0
  78. package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.en.md +4 -4
  79. package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.zh-CN.md +3 -3
  80. package/templates/.agents/skills/code-task/scripts/detect-mode.js +370 -0
  81. package/templates/.agents/skills/commit/SKILL.en.md +2 -2
  82. package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
  83. package/templates/.agents/skills/commit/reference/task-status-update.en.md +10 -6
  84. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +10 -6
  85. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -3
  86. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -3
  87. package/templates/.agents/skills/create-pr/SKILL.en.md +17 -1
  88. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -1
  89. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  90. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  91. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  92. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
  93. package/templates/.agents/skills/import-issue/SKILL.en.md +3 -3
  94. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
  95. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  96. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  97. package/templates/.agents/skills/restore-task/SKILL.en.md +4 -3
  98. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -3
  99. package/templates/.agents/skills/review-analysis/SKILL.en.md +76 -0
  100. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +102 -0
  101. package/templates/.agents/skills/review-analysis/config/verify.en.json +51 -0
  102. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +51 -0
  103. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +87 -0
  104. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +87 -0
  105. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +90 -0
  106. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +91 -0
  107. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +47 -0
  108. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +47 -0
  109. package/templates/.agents/skills/{review-task → review-code}/SKILL.en.md +11 -9
  110. package/templates/.agents/skills/{review-task → review-code}/SKILL.zh-CN.md +15 -9
  111. package/templates/.agents/skills/{review-task → review-code}/config/verify.en.json +7 -5
  112. package/templates/.agents/skills/{review-task → review-code}/config/verify.zh-CN.json +6 -4
  113. package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.en.md +21 -17
  114. package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.zh-CN.md +19 -15
  115. package/templates/.agents/skills/{review-task → review-code}/reference/report-template.en.md +5 -6
  116. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +91 -0
  117. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +48 -0
  118. package/templates/.agents/skills/{review-task → review-code}/reference/review-criteria.zh-CN.md +10 -4
  119. package/templates/.agents/skills/review-plan/SKILL.en.md +76 -0
  120. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +102 -0
  121. package/templates/.agents/skills/{refine-task → review-plan}/config/verify.en.json +14 -10
  122. package/templates/.agents/skills/{refine-task → review-plan}/config/verify.zh-CN.json +14 -10
  123. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +87 -0
  124. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +87 -0
  125. package/templates/.agents/skills/review-plan/reference/report-template.en.md +90 -0
  126. package/templates/.agents/skills/{review-task → review-plan}/reference/report-template.zh-CN.md +3 -3
  127. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +47 -0
  128. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +47 -0
  129. package/templates/.agents/skills/test/SKILL.en.md +2 -2
  130. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -31
  131. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +1 -0
  132. package/templates/.agents/templates/task.en.md +3 -3
  133. package/templates/.agents/templates/task.zh-CN.md +2 -2
  134. package/templates/.agents/workflows/bug-fix.en.yaml +126 -80
  135. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +90 -44
  136. package/templates/.agents/workflows/feature-development.en.yaml +115 -70
  137. package/templates/.agents/workflows/feature-development.zh-CN.yaml +92 -47
  138. package/templates/.agents/workflows/refactoring.en.yaml +123 -78
  139. package/templates/.agents/workflows/refactoring.zh-CN.yaml +89 -44
  140. package/templates/.claude/commands/code-task.en.md +8 -0
  141. package/templates/.claude/commands/code-task.zh-CN.md +8 -0
  142. package/templates/.claude/commands/review-analysis.en.md +8 -0
  143. package/templates/.claude/commands/review-analysis.zh-CN.md +8 -0
  144. package/templates/.claude/commands/review-code.en.md +8 -0
  145. package/templates/.claude/commands/review-code.zh-CN.md +8 -0
  146. package/templates/.claude/commands/review-plan.en.md +8 -0
  147. package/templates/.claude/commands/review-plan.zh-CN.md +8 -0
  148. package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +1 -1
  149. package/templates/.gemini/commands/_project_/code-task.en.toml +8 -0
  150. package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +8 -0
  151. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +1 -1
  152. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +1 -1
  153. package/templates/.gemini/commands/_project_/review-analysis.en.toml +8 -0
  154. package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +8 -0
  155. package/templates/.gemini/commands/_project_/review-code.en.toml +8 -0
  156. package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +8 -0
  157. package/templates/.gemini/commands/_project_/review-plan.en.toml +8 -0
  158. package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +8 -0
  159. package/templates/.opencode/commands/code-task.en.md +11 -0
  160. package/templates/.opencode/commands/code-task.zh-CN.md +11 -0
  161. package/templates/.opencode/commands/review-analysis.en.md +11 -0
  162. package/templates/.opencode/commands/review-analysis.zh-CN.md +11 -0
  163. package/templates/.opencode/commands/review-code.en.md +11 -0
  164. package/templates/.opencode/commands/review-code.zh-CN.md +11 -0
  165. package/templates/.opencode/commands/review-plan.en.md +11 -0
  166. package/templates/.opencode/commands/review-plan.zh-CN.md +11 -0
  167. package/templates/.agents/skills/implement-task/SKILL.en.md +0 -173
  168. package/templates/.agents/skills/implement-task/reference/output-template.en.md +0 -20
  169. package/templates/.agents/skills/implement-task/reference/output-template.zh-CN.md +0 -20
  170. package/templates/.agents/skills/refine-task/SKILL.en.md +0 -153
  171. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +0 -153
  172. package/templates/.agents/skills/refine-task/reference/report-template.en.md +0 -64
  173. package/templates/.agents/skills/refine-task/reference/report-template.zh-CN.md +0 -64
  174. package/templates/.agents/skills/review-task/reference/review-criteria.en.md +0 -42
  175. package/templates/.claude/commands/implement-task.en.md +0 -8
  176. package/templates/.claude/commands/implement-task.zh-CN.md +0 -8
  177. package/templates/.claude/commands/refine-task.en.md +0 -8
  178. package/templates/.claude/commands/refine-task.zh-CN.md +0 -8
  179. package/templates/.claude/commands/review-task.en.md +0 -8
  180. package/templates/.claude/commands/review-task.zh-CN.md +0 -8
  181. package/templates/.gemini/commands/_project_/implement-task.en.toml +0 -8
  182. package/templates/.gemini/commands/_project_/implement-task.zh-CN.toml +0 -8
  183. package/templates/.gemini/commands/_project_/refine-task.en.toml +0 -8
  184. package/templates/.gemini/commands/_project_/refine-task.zh-CN.toml +0 -8
  185. package/templates/.gemini/commands/_project_/review-task.en.toml +0 -8
  186. package/templates/.gemini/commands/_project_/review-task.zh-CN.toml +0 -8
  187. package/templates/.opencode/commands/implement-task.en.md +0 -11
  188. package/templates/.opencode/commands/implement-task.zh-CN.md +0 -11
  189. package/templates/.opencode/commands/refine-task.en.md +0 -11
  190. package/templates/.opencode/commands/refine-task.zh-CN.md +0 -11
  191. package/templates/.opencode/commands/review-task.en.md +0 -11
  192. package/templates/.opencode/commands/review-task.zh-CN.md +0 -11
  193. /package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.en.md +0 -0
@@ -1,10 +1,14 @@
1
1
  import { safeNameCandidates, sanitizeBranchName } from './constants.ts';
2
2
  import { hostJoin } from './engines/wsl2-paths.ts';
3
3
 
4
+ export type SandboxToolInstall =
5
+ | { type: 'npm'; cmd: string }
6
+ | { type: 'shell'; cmd: string };
7
+
4
8
  export type SandboxTool = {
5
9
  id: string;
6
10
  name: string;
7
- npmPackage: string;
11
+ install: SandboxToolInstall;
8
12
  sandboxBase: string;
9
13
  containerMount: string;
10
14
  versionCmd: string;
@@ -21,14 +25,17 @@ type ToolsConfig = {
21
25
  home: string;
22
26
  project: string;
23
27
  tools: string[];
28
+ customTools?: SandboxTool[];
24
29
  };
25
30
 
31
+ const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
32
+
26
33
  function createBuiltinTools(home: string, project: string): Record<string, SandboxTool> {
27
34
  return {
28
35
  'claude-code': {
29
36
  id: 'claude-code',
30
37
  name: 'Claude Code',
31
- npmPackage: '@anthropic-ai/claude-code@stable',
38
+ install: { type: 'npm', cmd: '@anthropic-ai/claude-code@stable' },
32
39
  sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'claude-code'),
33
40
  containerMount: '/home/devuser/.claude',
34
41
  versionCmd: 'claude --version',
@@ -58,7 +65,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
58
65
  codex: {
59
66
  id: 'codex',
60
67
  name: 'Codex',
61
- npmPackage: '@openai/codex',
68
+ install: { type: 'npm', cmd: '@openai/codex' },
62
69
  sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'codex'),
63
70
  containerMount: '/home/devuser/.codex',
64
71
  versionCmd: 'codex --version',
@@ -73,7 +80,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
73
80
  opencode: {
74
81
  id: 'opencode',
75
82
  name: 'OpenCode',
76
- npmPackage: 'opencode-ai',
83
+ install: { type: 'npm', cmd: 'opencode-ai' },
77
84
  sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'opencode'),
78
85
  containerMount: '/home/devuser/.local/share/opencode',
79
86
  versionCmd: 'opencode version',
@@ -92,7 +99,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
92
99
  'gemini-cli': {
93
100
  id: 'gemini-cli',
94
101
  name: 'Gemini CLI',
95
- npmPackage: '@google/gemini-cli',
102
+ install: { type: 'npm', cmd: '@google/gemini-cli' },
96
103
  sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'gemini-cli'),
97
104
  containerMount: '/home/devuser/.gemini',
98
105
  versionCmd: 'gemini --version',
@@ -108,16 +115,224 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
108
115
  };
109
116
  }
110
117
 
118
+ export function builtinToolIds(): string[] {
119
+ return Object.keys(createBuiltinTools('', ''));
120
+ }
121
+
111
122
  function validateTool(tool: SandboxTool): void {
112
- if (!tool.npmPackage || !tool.containerMount.startsWith('/')) {
113
- throw new Error(`Invalid sandbox tool descriptor: ${tool.id}`);
123
+ if (!tool.id || !TOOL_ID_PATTERN.test(tool.id)) {
124
+ throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
125
+ }
126
+ if (!tool.install || (tool.install.type !== 'npm' && tool.install.type !== 'shell')) {
127
+ throw new Error(`Sandbox tool ${tool.id} has invalid install.type`);
128
+ }
129
+ if (!tool.install.cmd) {
130
+ throw new Error(`Sandbox tool ${tool.id} has empty install.cmd`);
131
+ }
132
+ if (!tool.containerMount || !tool.containerMount.startsWith('/')) {
133
+ throw new Error(`Sandbox tool ${tool.id} containerMount must be an absolute path`);
134
+ }
135
+ }
136
+
137
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
138
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
139
+ }
140
+
141
+ function asString(value: unknown, field: string, context: string): string {
142
+ if (typeof value !== 'string') {
143
+ throw new Error(`${context}: field "${field}" must be a string`);
144
+ }
145
+ return value;
146
+ }
147
+
148
+ function asOptionalNonEmptyString(value: unknown, field: string, context: string): string | undefined {
149
+ if (value === undefined) {
150
+ return undefined;
151
+ }
152
+ if (typeof value !== 'string') {
153
+ throw new Error(`${context}: field "${field}" must be a string when provided`);
154
+ }
155
+ if (value.length === 0) {
156
+ throw new Error(`${context}: field "${field}" must be non-empty when provided`);
157
+ }
158
+ return value;
159
+ }
160
+
161
+ function asStringRecord(value: unknown, field: string, context: string): Record<string, string> | undefined {
162
+ if (value === undefined) {
163
+ return undefined;
164
+ }
165
+ if (!isPlainObject(value)) {
166
+ throw new Error(`${context}: field "${field}" must be an object when provided`);
167
+ }
168
+ const out: Record<string, string> = {};
169
+ for (const [key, val] of Object.entries(value)) {
170
+ if (typeof val !== 'string') {
171
+ throw new Error(`${context}: field "${field}.${key}" must be a string`);
172
+ }
173
+ out[key] = val;
174
+ }
175
+ return out;
176
+ }
177
+
178
+ function asStringArray(value: unknown, field: string, context: string): string[] | undefined {
179
+ if (value === undefined) {
180
+ return undefined;
181
+ }
182
+ if (!Array.isArray(value)) {
183
+ throw new Error(`${context}: field "${field}" must be an array when provided`);
184
+ }
185
+ return value.map((item, index) => {
186
+ if (typeof item !== 'string') {
187
+ throw new Error(`${context}: field "${field}[${index}]" must be a string`);
188
+ }
189
+ return item;
190
+ });
191
+ }
192
+
193
+ function parseInstall(value: unknown, context: string): SandboxToolInstall {
194
+ if (!isPlainObject(value)) {
195
+ throw new Error(`${context}: field "install" must be an object`);
196
+ }
197
+ const type = value.type;
198
+ if (type !== 'npm' && type !== 'shell') {
199
+ throw new Error(`${context}: field "install.type" must be "npm" or "shell"`);
200
+ }
201
+ const cmd = asString(value.cmd, 'install.cmd', context);
202
+ if (!cmd) {
203
+ throw new Error(`${context}: field "install.cmd" must be non-empty`);
204
+ }
205
+ return { type, cmd };
206
+ }
207
+
208
+ function parseHostPreSeedFiles(value: unknown, context: string): SandboxTool['hostPreSeedFiles'] {
209
+ if (value === undefined) {
210
+ return undefined;
211
+ }
212
+ if (!Array.isArray(value)) {
213
+ throw new Error(`${context}: field "hostPreSeedFiles" must be an array when provided`);
214
+ }
215
+ return value.map((item, index) => {
216
+ if (!isPlainObject(item)) {
217
+ throw new Error(`${context}: field "hostPreSeedFiles[${index}]" must be an object`);
218
+ }
219
+ return {
220
+ hostPath: asString(item.hostPath, `hostPreSeedFiles[${index}].hostPath`, context),
221
+ sandboxName: asString(item.sandboxName, `hostPreSeedFiles[${index}].sandboxName`, context)
222
+ };
223
+ });
224
+ }
225
+
226
+ function parseHostPreSeedDirs(value: unknown, context: string): SandboxTool['hostPreSeedDirs'] {
227
+ if (value === undefined) {
228
+ return undefined;
229
+ }
230
+ if (!Array.isArray(value)) {
231
+ throw new Error(`${context}: field "hostPreSeedDirs" must be an array when provided`);
114
232
  }
233
+ return value.map((item, index) => {
234
+ if (!isPlainObject(item)) {
235
+ throw new Error(`${context}: field "hostPreSeedDirs[${index}]" must be an object`);
236
+ }
237
+ return {
238
+ hostDir: asString(item.hostDir, `hostPreSeedDirs[${index}].hostDir`, context),
239
+ sandboxSubdir: asString(item.sandboxSubdir, `hostPreSeedDirs[${index}].sandboxSubdir`, context)
240
+ };
241
+ });
242
+ }
243
+
244
+ function parseHostLiveMounts(value: unknown, context: string): SandboxTool['hostLiveMounts'] {
245
+ if (value === undefined) {
246
+ return undefined;
247
+ }
248
+ if (!Array.isArray(value)) {
249
+ throw new Error(`${context}: field "hostLiveMounts" must be an array when provided`);
250
+ }
251
+ return value.map((item, index) => {
252
+ if (!isPlainObject(item)) {
253
+ throw new Error(`${context}: field "hostLiveMounts[${index}]" must be an object`);
254
+ }
255
+ return {
256
+ hostPath: asString(item.hostPath, `hostLiveMounts[${index}].hostPath`, context),
257
+ containerSubpath: asString(item.containerSubpath, `hostLiveMounts[${index}].containerSubpath`, context)
258
+ };
259
+ });
260
+ }
261
+
262
+ export function parseCustomTool(
263
+ entry: unknown,
264
+ index: number,
265
+ options: { home: string }
266
+ ): SandboxTool {
267
+ const context = `customTools[${index}]`;
268
+ if (!isPlainObject(entry)) {
269
+ throw new Error(`${context} must be an object`);
270
+ }
271
+
272
+ const id = asString(entry.id, 'id', context);
273
+ if (!TOOL_ID_PATTERN.test(id)) {
274
+ throw new Error(`${context}: field "id" must match ${TOOL_ID_PATTERN.source}`);
275
+ }
276
+
277
+ const containerMount = asOptionalNonEmptyString(entry.containerMount, 'containerMount', context)
278
+ ?? `/home/devuser/.${id}`;
279
+ if (!containerMount.startsWith('/')) {
280
+ throw new Error(`${context}: field "containerMount" must be an absolute path`);
281
+ }
282
+
283
+ const tool: SandboxTool = {
284
+ id,
285
+ name: asOptionalNonEmptyString(entry.name, 'name', context) ?? id,
286
+ install: parseInstall(entry.install, context),
287
+ sandboxBase: hostJoin(options.home, '.agent-infra', 'sandboxes', id),
288
+ containerMount,
289
+ versionCmd: asOptionalNonEmptyString(entry.versionCmd, 'versionCmd', context) ?? `which ${id}`,
290
+ setupHint: asOptionalNonEmptyString(entry.setupHint, 'setupHint', context)
291
+ ?? `Run \`${id}\` inside the container to set up.`,
292
+ envVars: asStringRecord(entry.envVars, 'envVars', context),
293
+ hostPreSeedFiles: parseHostPreSeedFiles(entry.hostPreSeedFiles, context),
294
+ hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
295
+ pathRewriteFiles: asStringArray(entry.pathRewriteFiles, 'pathRewriteFiles', context),
296
+ hostLiveMounts: parseHostLiveMounts(entry.hostLiveMounts, context),
297
+ postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context)
298
+ };
299
+
300
+ validateTool(tool);
301
+ return tool;
302
+ }
303
+
304
+ export function parseCustomTools(value: unknown, options: { home: string }): SandboxTool[] {
305
+ if (value === undefined || value === null) {
306
+ return [];
307
+ }
308
+ if (!Array.isArray(value)) {
309
+ throw new Error('sandbox: "customTools" must be an array');
310
+ }
311
+ return value.map((entry, index) => parseCustomTool(entry, index, options));
115
312
  }
116
313
 
117
314
  export function resolveTools(config: ToolsConfig): SandboxTool[] {
118
315
  const builtins = createBuiltinTools(config.home, config.project);
316
+ const customs = config.customTools ?? [];
317
+
318
+ const seen = new Set<string>();
319
+ for (const tool of customs) {
320
+ if (builtins[tool.id]) {
321
+ throw new Error(`Custom sandbox tool id "${tool.id}" collides with a built-in tool`);
322
+ }
323
+ if (seen.has(tool.id)) {
324
+ throw new Error(`Duplicate sandbox tool id "${tool.id}" in customTools`);
325
+ }
326
+ seen.add(tool.id);
327
+ }
328
+
329
+ const merged: Record<string, SandboxTool> = { ...builtins };
330
+ for (const tool of customs) {
331
+ merged[tool.id] = tool;
332
+ }
333
+
119
334
  return config.tools.map((id) => {
120
- const tool = builtins[id];
335
+ const tool = merged[id];
121
336
  if (!tool) {
122
337
  throw new Error(`Unknown sandbox tool: ${id}`);
123
338
  }
@@ -139,5 +354,29 @@ export function toolProjectDirCandidates(tool: SandboxTool, project: string): st
139
354
  }
140
355
 
141
356
  export function toolNpmPackagesArg(tools: SandboxTool[]): string {
142
- return tools.map((tool) => tool.npmPackage).join(' ');
357
+ return tools
358
+ .filter((tool) => tool.install.type === 'npm')
359
+ .map((tool) => tool.install.cmd)
360
+ .join(' ');
361
+ }
362
+
363
+ export function toolShellInstallScript(tools: SandboxTool[]): string {
364
+ const blocks = tools
365
+ .filter((tool) => tool.install.type === 'shell')
366
+ .map((tool) => `# install: ${tool.id}\n${tool.install.cmd}`);
367
+
368
+ if (blocks.length === 0) {
369
+ return '';
370
+ }
371
+
372
+ return ['#!/bin/bash', 'set -e', '', ...blocks, ''].join('\n');
373
+ }
374
+
375
+ export function toolShellInstallScriptBase64(tools: SandboxTool[]): string {
376
+ const script = toolShellInstallScript(tools);
377
+ return script ? Buffer.from(script, 'utf8').toString('base64') : '';
378
+ }
379
+
380
+ export function imageSignatureFields(tools: SandboxTool[]): Array<{ id: string; install: SandboxToolInstall }> {
381
+ return tools.map((tool) => ({ id: tool.id, install: tool.install }));
143
382
  }
package/lib/update.ts CHANGED
@@ -15,6 +15,7 @@ type UpdateConfig = {
15
15
  org: string;
16
16
  language: string;
17
17
  platform?: { type?: string };
18
+ requiresPullRequest?: boolean;
18
19
  sandbox?: Record<string, unknown>;
19
20
  labels?: Record<string, unknown>;
20
21
  files?: Partial<FileRegistry>;
@@ -22,6 +23,7 @@ type UpdateConfig = {
22
23
 
23
24
  type Defaults = {
24
25
  platform: { type: string };
26
+ requiresPullRequest: boolean;
25
27
  sandbox: Record<string, unknown>;
26
28
  labels: Record<string, unknown>;
27
29
  files: FileRegistry;
@@ -178,6 +180,7 @@ async function cmdUpdate(): Promise<void> {
178
180
  const platformAdded = !config.platform;
179
181
  const sandboxAdded = !config.sandbox;
180
182
  const labelsAdded = !config.labels;
183
+ const requiresPullRequestAdded = config.requiresPullRequest === undefined;
181
184
  let configChanged = changed;
182
185
 
183
186
  if (platformAdded) {
@@ -195,6 +198,11 @@ async function cmdUpdate(): Promise<void> {
195
198
  configChanged = true;
196
199
  }
197
200
 
201
+ if (requiresPullRequestAdded) {
202
+ config.requiresPullRequest = defaults.requiresPullRequest;
203
+ configChanged = true;
204
+ }
205
+
198
206
  if (configChanged) {
199
207
  console.log('');
200
208
  if (hasNewEntries) {
@@ -205,7 +213,7 @@ async function cmdUpdate(): Promise<void> {
205
213
  for (const entry of added.merged) {
206
214
  ok(` merged: ${entry}`);
207
215
  }
208
- } else if (platformAdded || sandboxAdded || labelsAdded) {
216
+ } else if (platformAdded || sandboxAdded || labelsAdded || requiresPullRequestAdded) {
209
217
  if (platformAdded) {
210
218
  info(`Default platform config added to ${CONFIG_PATH}.`);
211
219
  }
@@ -215,6 +223,9 @@ async function cmdUpdate(): Promise<void> {
215
223
  if (labelsAdded) {
216
224
  info(`Default labels.in config added to ${CONFIG_PATH}.`);
217
225
  }
226
+ if (requiresPullRequestAdded) {
227
+ info(`Default requiresPullRequest=${defaults.requiresPullRequest} added to ${CONFIG_PATH}.`);
228
+ }
218
229
  } else {
219
230
  info(`File registry changed in ${CONFIG_PATH}.`);
220
231
  }
@@ -227,6 +238,9 @@ async function cmdUpdate(): Promise<void> {
227
238
  if (hasNewEntries && platformAdded) {
228
239
  info(`Default platform config added to ${CONFIG_PATH}.`);
229
240
  }
241
+ if (hasNewEntries && requiresPullRequestAdded) {
242
+ info(`Default requiresPullRequest=${defaults.requiresPullRequest} added to ${CONFIG_PATH}.`);
243
+ }
230
244
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
231
245
  ok(`Updated ${CONFIG_PATH}`);
232
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Gemini CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -164,7 +164,7 @@ The receiving AI should read this document first to get up to speed.
164
164
 
165
165
  ### 1. One AI Per Phase
166
166
 
167
- Don't have multiple AIs working on the same files simultaneously. Follow the sequential workflow: analyze, design, implement, review, fix, commit.
167
+ Don't have multiple AIs working on the same files simultaneously. Follow the sequential workflow: analysis → analysis-review → design design-review → code → code-review → commit (when any review finds issues, re-run the matching upstream stage).
168
168
 
169
169
  ### 2. Always Create Handoff Documents
170
170
 
@@ -164,7 +164,7 @@ cp .agents/templates/handoff.md .agents/workspace/active/handoff-task-001-phase2
164
164
 
165
165
  ### 1. 每个阶段一个 AI
166
166
 
167
- 不要让多个 AI 同时处理相同的文件。遵循顺序工作流:分析、设计、实现、审查、修复、提交。
167
+ 不要让多个 AI 同时处理相同的文件。遵循顺序工作流:分析 → 分析审查 → 设计 → 设计审查 → 编码 → 代码审查 → 提交(任一审查发现问题时,回到同名上游阶段重跑)。
168
168
 
169
169
  ### 2. 始终创建交接文档
170
170
 
@@ -224,8 +224,8 @@ Supported `invoke` placeholders:
224
224
 
225
225
  | Placeholder | Replaced with | Example |
226
226
  |-------------|---------------|---------|
227
- | `${skillName}` | The skill command name, such as `review-task` or `commit`. | `<your-cli> ${skillName}` -> `<your-cli> review-task` |
228
- | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
227
+ | `${skillName}` | The skill command name, such as `review-code` or `commit`. | `<your-cli> ${skillName}` -> `<your-cli> review-code` |
228
+ | `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/agent-infra:review-code` |
229
229
 
230
230
  Non-namespaced custom TUI:
231
231
 
@@ -258,6 +258,83 @@ Namespaced custom TUI:
258
258
 
259
259
  `customTUIs` should contain one entry per custom TUI. To let `update-agent-infra` generate command files for custom skills, keep at least one existing command file in `dir` that references a built-in skill path such as `.agents/skills/analyze-task/SKILL.md`; agent-infra uses that file as the format reference.
260
260
 
261
+ ## Sandbox Custom Tools
262
+
263
+ `customTUIs` (above) generates slash-command files but does not change the sandbox image. To install a non-npm TUI (pip / cargo / curl-based / pre-built binary) into the sandbox image and live-mount its credentials, declare it under `sandbox.customTools` in `.agents/.airc.json`. Built-in tools (`claude-code`, `codex`, `opencode`, `gemini-cli`) keep working unchanged.
264
+
265
+ ### Required fields
266
+
267
+ | Field | Meaning |
268
+ |-------|---------|
269
+ | `id` | Lowercase id matching `^[a-z0-9][a-z0-9-]*$`. Referenced from `sandbox.tools`. Must not collide with a built-in id. |
270
+ | `install` | Install descriptor. `{ "type": "npm", "cmd": "<npm package spec>" }` runs `npm install -g <cmd>`. `{ "type": "shell", "cmd": "<shell>" }` runs the shell command(s) as `devuser` during image build. `cmd` must be non-empty. |
271
+
272
+ Minimal entry — the contract for getting a tool into the image is just these two fields:
273
+
274
+ ```json
275
+ {
276
+ "sandbox": {
277
+ "tools": ["my-shell-tool"],
278
+ "customTools": [
279
+ {
280
+ "id": "my-shell-tool",
281
+ "install": { "type": "shell", "cmd": "curl -fsSL https://example.com/install.sh | bash" }
282
+ }
283
+ ]
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### Optional integration fields
289
+
290
+ Add only the fields your tool actually needs. Omit them and the loader fills sensible defaults; provide them and the loader uses your value. Provide an explicit empty string and the loader rejects it (preventing silent install-verification bypass).
291
+
292
+ | Field | Default when omitted | When to provide |
293
+ |-------|---------------------|-----------------|
294
+ | `name` | `id` | A friendlier display name in sandbox reports / hints. |
295
+ | `containerMount` | `/home/devuser/.<id>` | Your tool stores its config / state somewhere other than `~/.<id>`. Must be an absolute path. |
296
+ | `versionCmd` | `which <id>` | The installed binary name differs from `id` (e.g. id `anthropic-claude`, binary `claude`); set `"claude --version"` so sandbox-create can verify the install. |
297
+ | `setupHint` | `Run \`<id>\` inside the container to set up.` | The setup story is non-obvious and worth a one-liner. |
298
+ | `envVars` | (none) | Your tool reads config from a path the env points to (e.g. `XDG_CONFIG_HOME`-style or a custom `*_CONFIG` env). Shape: `Record<string, string>`. |
299
+ | `hostPreSeedFiles` / `hostPreSeedDirs` | (none) | Seed the tool's sandbox dir from host files / directories on first launch. |
300
+ | `pathRewriteFiles` | (none) | Seeded files contain absolute host paths that need rewriting to container paths. |
301
+ | `hostLiveMounts` | (none) | Share host credentials live (e.g. OAuth tokens) with the container. Read-write. |
302
+ | `postSetupCmds` | (none) | Run commands inside the container after first setup (e.g. symlinks). |
303
+
304
+ > **`sandboxBase` is not user-configurable.** The loader always assigns `~/.agent-infra/sandboxes/<id>` so `ai sandbox rm` / `prune` can find tool state. Any `sandboxBase` value in `customTools` entries is silently ignored.
305
+
306
+ Real-world example — `anthropic-claude` as a user-defined id with binary name `claude` and host credential live-mount:
307
+
308
+ ```json
309
+ {
310
+ "sandbox": {
311
+ "tools": ["claude-code", "anthropic-claude"],
312
+ "customTools": [
313
+ {
314
+ "id": "anthropic-claude",
315
+ "install": { "type": "npm", "cmd": "@anthropic-ai/claude-code@stable" },
316
+ "versionCmd": "claude --version",
317
+ "hostLiveMounts": [
318
+ { "hostPath": "~/.claude/.credentials.json", "containerSubpath": ".credentials.json" }
319
+ ]
320
+ }
321
+ ]
322
+ }
323
+ }
324
+ ```
325
+
326
+ ### Trust boundary and execution context
327
+
328
+ - `install.cmd` runs as user `devuser` (non-root) during `docker build`. It can write to the container's filesystem but cannot escape to the host. The trust model is the same as for the `sandbox.dockerfile` escape hatch — you own the `.airc.json` in your repo, so you own what runs at build time.
329
+ - Because the build runs as `devuser`, shell installs cannot `sudo` / `apt-get`. Available options for non-npm distributions:
330
+ - User-scope installers landing in `~/.local/bin`, `~/.cargo/bin`, `~/.npm-global/bin` (e.g. `pipx`, `cargo install`, `curl … | bash` with `INSTALL_DIR=$HOME/.local/bin`).
331
+ - When you genuinely need root or system packages, fall back to the existing `sandbox.dockerfile` field and own the full Dockerfile.
332
+ - Changing `install.cmd` (or any field that participates in the image signature) triggers exactly one image rebuild on the next `ai sandbox` invocation.
333
+
334
+ ### Interaction with `sandbox.dockerfile`
335
+
336
+ When you set `sandbox.dockerfile` to point at your own Dockerfile, agent-infra still passes both `AI_TOOL_PACKAGES` (space-separated npm package specs) and `AI_TOOLS_SHELL_INSTALL_B64` (base64-encoded shell install script) as `--build-arg`. Your custom Dockerfile decides whether to consume them; if it does not declare the matching `ARG`, the shell installs for `customTools` are silently skipped — taking over the Dockerfile means taking over the install path.
337
+
261
338
  ## Skill Authoring Conventions
262
339
 
263
340
  When writing or updating `.agents/skills/*/SKILL.md` files and their templates, keep step numbering consistent:
@@ -224,8 +224,8 @@ args: "<task-id>" # 可选
224
224
 
225
225
  | 占位符 | 替换为 | 示例 |
226
226
  |--------|--------|------|
227
- | `${skillName}` | skill 命令名,例如 `review-task` 或 `commit`。 | `<your-cli> ${skillName}` -> `<your-cli> review-task` |
228
- | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/agent-infra:review-task` |
227
+ | `${skillName}` | skill 命令名,例如 `review-code` 或 `commit`。 | `<your-cli> ${skillName}` -> `<your-cli> review-code` |
228
+ | `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/agent-infra:review-code` |
229
229
 
230
230
  不带命名空间的自定义 TUI:
231
231
 
@@ -258,6 +258,83 @@ args: "<task-id>" # 可选
258
258
 
259
259
  `customTUIs` 每个条目对应一个自定义 TUI。若希望 `update-agent-infra` 为自定义 skill 生成命令文件,请在 `dir` 中保留至少一个引用内置 skill 路径的既有命令文件,例如 `.agents/skills/analyze-task/SKILL.md`;agent-infra 会以该文件作为格式参考。
260
260
 
261
+ ## 沙箱自定义工具(Sandbox Custom Tools)
262
+
263
+ 上文 `customTUIs` 只负责生成 slash-command 文件,**不影响沙箱镜像**。如果要把一个非 npm 分发的 TUI(pip / cargo / curl 脚本 / 裸二进制)装进沙箱镜像、并 live-mount 它的凭证目录,需要在 `.agents/.airc.json` 的 `sandbox.customTools` 中声明。内建的四个工具(`claude-code` / `codex` / `opencode` / `gemini-cli`)行为保持不变。
264
+
265
+ ### 必填字段
266
+
267
+ | 字段 | 含义 |
268
+ |------|------|
269
+ | `id` | 小写 id,匹配 `^[a-z0-9][a-z0-9-]*$`;由 `sandbox.tools` 引用;不可与内建 id 冲突。 |
270
+ | `install` | 安装描述符。`{ "type": "npm", "cmd": "<npm 包规范>" }` 执行 `npm install -g <cmd>`;`{ "type": "shell", "cmd": "<shell>" }` 在镜像构建阶段以 `devuser` 执行 shell。`cmd` 必须非空。 |
271
+
272
+ 最小入口——把一个工具装进镜像所需的契约只有这两个字段:
273
+
274
+ ```json
275
+ {
276
+ "sandbox": {
277
+ "tools": ["my-shell-tool"],
278
+ "customTools": [
279
+ {
280
+ "id": "my-shell-tool",
281
+ "install": { "type": "shell", "cmd": "curl -fsSL https://example.com/install.sh | bash" }
282
+ }
283
+ ]
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### 可选集成字段
289
+
290
+ 只在你的工具真正需要时才加。**省略**则 loader 用合理默认值;**显式提供**则用你给的值;**显式给空串**会被拒绝(防止安装验证被绕过)。
291
+
292
+ | 字段 | 省略时的默认值 | 什么时候应该提供 |
293
+ |------|---------------|----------------|
294
+ | `name` | `id` | 想在沙箱报告 / 提示里显示更友好的名称。 |
295
+ | `containerMount` | `/home/devuser/.<id>` | 工具的配置 / 状态目录不在 `~/.<id>` 而在别处。必须是绝对路径。 |
296
+ | `versionCmd` | `which <id>` | 安装后的可执行文件名与 `id` 不同(例如 id 是 `anthropic-claude`,二进制名是 `claude`);填 `"claude --version"` 让 sandbox-create 能验证安装。 |
297
+ | `setupHint` | `Run \`<id>\` inside the container to set up.` | setup 流程不一目了然,值得用一行说明。 |
298
+ | `envVars` | (无) | 工具通过环境变量找配置(如 `XDG_CONFIG_HOME` 风格或自定义 `*_CONFIG` 变量)。形状:`Record<string, string>`。 |
299
+ | `hostPreSeedFiles` / `hostPreSeedDirs` | (无) | 首次启动时从宿主复制文件 / 目录到工具沙箱配置目录。 |
300
+ | `pathRewriteFiles` | (无) | seed 进来的文件里有宿主绝对路径,需要改写为容器路径。 |
301
+ | `hostLiveMounts` | (无) | 把宿主凭证(如 OAuth token)实时挂进容器,读写共享。 |
302
+ | `postSetupCmds` | (无) | 首次安装完成后在容器内执行命令(如建符号链接)。 |
303
+
304
+ > **`sandboxBase` 不由用户配置。** loader 永远使用 `~/.agent-infra/sandboxes/<id>`,这样 `ai sandbox rm` / `prune` 才能找到工具状态目录。`customTools` 条目里写的任何 `sandboxBase` 都会被静默忽略。
305
+
306
+ 实际场景示例——`anthropic-claude` 作为用户自定义 id,二进制名是 `claude`,并把宿主凭证 live-mount 进来:
307
+
308
+ ```json
309
+ {
310
+ "sandbox": {
311
+ "tools": ["claude-code", "anthropic-claude"],
312
+ "customTools": [
313
+ {
314
+ "id": "anthropic-claude",
315
+ "install": { "type": "npm", "cmd": "@anthropic-ai/claude-code@stable" },
316
+ "versionCmd": "claude --version",
317
+ "hostLiveMounts": [
318
+ { "hostPath": "~/.claude/.credentials.json", "containerSubpath": ".credentials.json" }
319
+ ]
320
+ }
321
+ ]
322
+ }
323
+ }
324
+ ```
325
+
326
+ ### 信任边界与执行身份
327
+
328
+ - `install.cmd` 在 `docker build` 阶段以 `devuser`(非 root)身份执行,只能写容器内文件系统,不能逃逸到宿主。信任模型与现有 `sandbox.dockerfile` 一致:你是 `.airc.json` 的作者,本次构建做什么由你负责。
329
+ - 因为不是 root,shell 安装无法 `sudo` / `apt-get`。非 npm 分发的几条可用路径:
330
+ - 用户态安装器,落到 `~/.local/bin`、`~/.cargo/bin`、`~/.npm-global/bin`(如 `pipx`、`cargo install`、`curl … | bash` 配合 `INSTALL_DIR=$HOME/.local/bin`)。
331
+ - 确实需要 root / 系统包时,仍走原有 `sandbox.dockerfile` 字段,接管整个 Dockerfile。
332
+ - 修改 `install.cmd` 或任何参与镜像签名的字段,下次 `ai sandbox` 命令会触发一次镜像重建。
333
+
334
+ ### 与 `sandbox.dockerfile` 的交互
335
+
336
+ 当 `sandbox.dockerfile` 指向自定义 Dockerfile 时,agent-infra 仍会把 `AI_TOOL_PACKAGES`(空格分隔的 npm 包规范)和 `AI_TOOLS_SHELL_INSTALL_B64`(base64 编码的 shell 安装脚本)作为 `--build-arg` 传入。你的自定义 Dockerfile 若未声明对应 `ARG`,shell 安装路径会被 docker build 静默忽略——这是接管 Dockerfile 后的应有代价。
337
+
261
338
  ## Skill 编写规范
262
339
 
263
340
  编写或维护 `.agents/skills/*/SKILL.md` 及其模板时,步骤编号遵循以下规则:
@@ -2,4 +2,4 @@
2
2
 
3
3
  This code platform does not provide an Issue creation rule.
4
4
 
5
- `create-task` skips the cascade Issue creation step on this platform; the local `task.md` remains a valid artifact. If you later want to bind the task to an Issue, manually write `issue_number` into `task.md` and the subsequent skills (`commit` / `refine-task` / `complete-task`, etc.) will pick up Issue metadata syncing through the existing cascade rules.
5
+ `create-task` skips the cascade Issue creation step on this platform; the local `task.md` remains a valid artifact. If you later want to bind the task to an Issue, manually write `issue_number` into `task.md` and the subsequent skills (`commit` / `code-task` / `complete-task`, etc.) will pick up Issue metadata syncing through the existing cascade rules.
@@ -5,7 +5,7 @@ After `create-task` writes the local `task.md`, follow this rule to cascade Issu
5
5
  ## Boundary
6
6
 
7
7
  - Issue title and body must come from `task.md` only
8
- - Do not read `analysis.md`, `plan.md`, `implementation.md`, or any review artifact
8
+ - Do not read `analysis.md`, `review-analysis.md`, `plan.md`, `review-plan.md`, `code.md`, or any review-code artifact
9
9
  - Persistent outputs are limited to the remote Issue and the `issue_number` written back to `task.md`
10
10
  - If Issue creation fails, do not roll back `task.md`; the current task remains valid for the workflow, and the user can later manually fill `issue_number` so other skills' cascade sync takes over
11
11
 
@@ -5,7 +5,7 @@
5
5
  ## 行为边界
6
6
 
7
7
  - Issue 标题和正文只能来自 `task.md`
8
- - 不读取 `analysis.md`、`plan.md`、`implementation.md` 或审查产物
8
+ - 不读取 `analysis.md`、`review-analysis.md`、`plan.md`、`review-plan.md`、`code.md` 或代码审查产物
9
9
  - 持久产物只有:远端 Issue + `task.md` 中回写的 `issue_number`
10
10
  - Issue 创建失败时不回滚 `task.md`;当前 task 仍可继续后续工作流,未来可由用户手动写入 `issue_number`,让其它技能的级联同步接管
11
11
 
@@ -2,4 +2,4 @@
2
2
 
3
3
  当前代码平台未提供 Issue 创建规则。
4
4
 
5
- `create-task` 在本平台上会跳过级联创建 Issue 步骤;本地 `task.md` 仍然是有效产物。如果将来需要把任务绑定到一个 Issue,可手动在 `task.md` 中写入 `issue_number`,后续技能(`commit` / `refine-task` / `complete-task` 等)会按既有的级联同步规则自动接管 Issue 元数据更新。
5
+ `create-task` 在本平台上会跳过级联创建 Issue 步骤;本地 `task.md` 仍然是有效产物。如果将来需要把任务绑定到一个 Issue,可手动在 `task.md` 中写入 `issue_number`,后续技能(`commit` / `code-task` / `complete-task` 等)会按既有的级联同步规则自动接管 Issue 元数据更新。