@fitlab-ai/agent-infra 0.5.8 → 0.5.10

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 (67) hide show
  1. package/README.md +237 -5
  2. package/README.zh-CN.md +213 -5
  3. package/bin/cli.js +2 -2
  4. package/lib/init.js +18 -4
  5. package/lib/sandbox/commands/create.js +467 -240
  6. package/lib/sandbox/commands/enter.js +59 -26
  7. package/lib/sandbox/commands/ls.js +37 -6
  8. package/lib/sandbox/commands/rebuild.js +31 -15
  9. package/lib/sandbox/commands/refresh.js +119 -0
  10. package/lib/sandbox/commands/rm.js +59 -11
  11. package/lib/sandbox/commands/vm.js +56 -6
  12. package/lib/sandbox/config.js +9 -5
  13. package/lib/sandbox/constants.js +18 -3
  14. package/lib/sandbox/credentials.js +520 -0
  15. package/lib/sandbox/dotfiles.js +189 -0
  16. package/lib/sandbox/engine.js +135 -157
  17. package/lib/sandbox/engines/colima.js +79 -0
  18. package/lib/sandbox/engines/docker-desktop.js +34 -0
  19. package/lib/sandbox/engines/index.js +27 -0
  20. package/lib/sandbox/engines/native.js +112 -0
  21. package/lib/sandbox/engines/orbstack.js +76 -0
  22. package/lib/sandbox/engines/selinux.js +60 -0
  23. package/lib/sandbox/engines/wsl2-paths.js +59 -0
  24. package/lib/sandbox/engines/wsl2.js +72 -0
  25. package/lib/sandbox/index.js +10 -1
  26. package/lib/sandbox/runtimes/ai-tools.dockerfile +14 -1
  27. package/lib/sandbox/runtimes/base.dockerfile +125 -3
  28. package/lib/sandbox/shell.js +53 -2
  29. package/lib/sandbox/tools.js +5 -5
  30. package/package.json +8 -4
  31. package/templates/.agents/rules/create-issue.en.md +5 -0
  32. package/templates/.agents/rules/create-issue.github.en.md +176 -0
  33. package/templates/.agents/rules/create-issue.github.zh-CN.md +176 -0
  34. package/templates/.agents/rules/create-issue.zh-CN.md +5 -0
  35. package/templates/.agents/rules/issue-pr-commands.github.en.md +29 -0
  36. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +29 -0
  37. package/templates/.agents/rules/issue-sync.github.en.md +1 -1
  38. package/templates/.agents/rules/issue-sync.github.zh-CN.md +1 -1
  39. package/templates/.agents/rules/milestone-inference.github.en.md +2 -2
  40. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +2 -2
  41. package/templates/.agents/scripts/{platform-adapters/find-existing-task.github.js → find-existing-task.js} +22 -79
  42. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +72 -42
  43. package/templates/.agents/skills/create-task/SKILL.en.md +69 -11
  44. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +70 -12
  45. package/templates/.agents/skills/create-task/config/verify.json +6 -1
  46. package/templates/.agents/skills/implement-task/reference/implementation-rules.en.md +7 -12
  47. package/templates/.agents/skills/implement-task/reference/implementation-rules.zh-CN.md +7 -12
  48. package/templates/.agents/skills/import-issue/SKILL.en.md +7 -9
  49. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +7 -9
  50. package/templates/.agents/skills/refine-task/reference/fix-workflow.en.md +2 -2
  51. package/templates/.agents/skills/refine-task/reference/fix-workflow.zh-CN.md +2 -2
  52. package/templates/.agents/skills/test/SKILL.en.md +45 -6
  53. package/templates/.agents/skills/test/SKILL.zh-CN.md +45 -6
  54. package/templates/.agents/scripts/platform-adapters/find-existing-task.js +0 -5
  55. package/templates/.agents/skills/create-issue/SKILL.en.md +0 -118
  56. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +0 -118
  57. package/templates/.agents/skills/create-issue/config/verify.json +0 -30
  58. package/templates/.agents/skills/create-issue/reference/label-and-type.en.md +0 -71
  59. package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +0 -71
  60. package/templates/.agents/skills/create-issue/reference/template-matching.en.md +0 -17
  61. package/templates/.agents/skills/create-issue/reference/template-matching.zh-CN.md +0 -17
  62. package/templates/.claude/commands/create-issue.en.md +0 -8
  63. package/templates/.claude/commands/create-issue.zh-CN.md +0 -8
  64. package/templates/.gemini/commands/_project_/create-issue.en.toml +0 -8
  65. package/templates/.gemini/commands/_project_/create-issue.zh-CN.toml +0 -8
  66. package/templates/.opencode/commands/create-issue.en.md +0 -11
  67. package/templates/.opencode/commands/create-issue.zh-CN.md +0 -11
@@ -1,4 +1,4 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { text } from "node:stream/consumers";
2
2
  import { pathToFileURL } from "node:url";
3
3
 
4
4
  const markerPattern = /^<!-- sync-issue:(TASK-\d{8}-\d{6}):([a-z][a-z0-9-]*) -->$/;
@@ -10,16 +10,6 @@ function parseArgs(argv) {
10
10
 
11
11
  for (let index = 0; index < argv.length; index += 1) {
12
12
  const arg = argv[index];
13
- if (arg === "--issue") {
14
- args.issue = argv[index + 1];
15
- index += 1;
16
- continue;
17
- }
18
- if (arg === "--repo") {
19
- args.repo = argv[index + 1];
20
- index += 1;
21
- continue;
22
- }
23
13
  if (arg === "--format") {
24
14
  args.format = argv[index + 1];
25
15
  index += 1;
@@ -38,53 +28,12 @@ function parseArgs(argv) {
38
28
  function usage() {
39
29
  return [
40
30
  "Usage:",
41
- " node .agents/scripts/platform-adapters/find-existing-task.js --issue <number> [--repo <owner/name>] [--format json]"
31
+ " <issue comments JSON via stdin> | node .agents/scripts/find-existing-task.js [--format json]",
32
+ "",
33
+ "Reads issue comments JSON (JSON Lines or JSON Array) from stdin and prints {found, task_id, frontmatter?}."
42
34
  ].join("\n");
43
35
  }
44
36
 
45
- function runGh(args) {
46
- const ghBin = process.env.IMPORT_ISSUE_GH_BIN || "gh";
47
- const result = spawnSync(ghBin, args, {
48
- encoding: "utf8",
49
- maxBuffer: 10 * 1024 * 1024
50
- });
51
-
52
- if (result.error) {
53
- const error = new Error(`gh command failed: ${result.error.message}`);
54
- error.stderr = result.error.message;
55
- throw error;
56
- }
57
-
58
- if (result.status !== 0) {
59
- const stderr = result.stderr.trim() || `gh exited with status ${result.status}`;
60
- const error = new Error(stderr);
61
- error.stderr = stderr;
62
- throw error;
63
- }
64
-
65
- return result.stdout;
66
- }
67
-
68
- function resolveRepo(explicitRepo) {
69
- if (explicitRepo) {
70
- return explicitRepo;
71
- }
72
-
73
- const currentRepo = runGh(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]).trim();
74
- if (!currentRepo) {
75
- throw new Error("Cannot detect current GitHub repository");
76
- }
77
-
78
- const upstreamRepo = runGh([
79
- "api",
80
- `repos/${currentRepo}`,
81
- "--jq",
82
- "if .fork then .parent.full_name else .full_name end"
83
- ]).trim();
84
-
85
- return upstreamRepo || currentRepo;
86
- }
87
-
88
37
  function parseComments(output) {
89
38
  const trimmed = output.trim();
90
39
  if (!trimmed) {
@@ -102,18 +51,6 @@ function parseComments(output) {
102
51
  }
103
52
  }
104
53
 
105
- function fetchComments(repo, issueNumber) {
106
- const output = runGh([
107
- "api",
108
- `repos/${repo}/issues/${issueNumber}/comments`,
109
- "--paginate",
110
- "--jq",
111
- ".[] | @json"
112
- ]);
113
-
114
- return parseComments(output);
115
- }
116
-
117
54
  function firstLine(value) {
118
55
  return String(value || "").split(/\r?\n/, 1)[0].trim();
119
56
  }
@@ -231,7 +168,7 @@ function buildResult(comments) {
231
168
  return result;
232
169
  }
233
170
 
234
- function main() {
171
+ async function main() {
235
172
  let args;
236
173
  try {
237
174
  args = parseArgs(process.argv.slice(2));
@@ -246,27 +183,33 @@ function main() {
246
183
  return;
247
184
  }
248
185
 
249
- if (!args.issue) {
250
- console.error("Missing required argument: --issue");
251
- console.error(usage());
186
+ if (args.format !== "json") {
187
+ console.error(`Unsupported format: ${args.format}`);
252
188
  process.exit(1);
253
189
  }
254
190
 
255
- if (args.format !== "json") {
256
- console.error(`Unsupported format: ${args.format}`);
191
+ let raw;
192
+ try {
193
+ raw = await text(process.stdin);
194
+ } catch (error) {
195
+ console.error(`Cannot read stdin: ${error.message}`);
257
196
  process.exit(1);
258
197
  }
259
198
 
199
+ let comments;
260
200
  try {
261
- const repo = resolveRepo(args.repo);
262
- const comments = fetchComments(repo, args.issue);
263
- console.log(JSON.stringify(buildResult(comments), null, 2));
201
+ comments = parseComments(raw);
264
202
  } catch (error) {
265
- console.error(`Cannot scan issue comments: ${error.stderr || error.message}`);
266
- process.exit(2);
203
+ console.error(`Cannot parse stdin as JSON: ${error.message}`);
204
+ process.exit(1);
267
205
  }
206
+
207
+ console.log(JSON.stringify(buildResult(comments), null, 2));
268
208
  }
269
209
 
270
210
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
271
- main();
211
+ main().catch((error) => {
212
+ console.error(error.stack || error.message);
213
+ process.exit(1);
214
+ });
272
215
  }
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
- import { spawnSync } from "node:child_process";
4
+ import spawn from "cross-spawn";
5
5
 
6
6
  const CHECK_TYPE = "platform-sync";
7
7
  const DEFAULT_RETRY_DELAYS_MS = [3000, 10000];
@@ -498,7 +498,7 @@ function checkPrCommentLastCommit(context, remoteData) {
498
498
  );
499
499
  }
500
500
 
501
- const headResult = withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
501
+ const headResult = resolvePrHeadSha(context);
502
502
  if (!headResult.ok) {
503
503
  return headResult.type === "check_failed"
504
504
  ? failResult(CHECK_TYPE, headResult.message, headResult.type)
@@ -1034,7 +1034,7 @@ function resolveUpstreamRepo(taskDir) {
1034
1034
  }
1035
1035
 
1036
1036
  function resolveOwnerRepo(taskDir) {
1037
- const gitResult = spawnSync("git", ["remote", "get-url", "origin"], {
1037
+ const gitResult = spawn.sync("git", ["remote", "get-url", "origin"], {
1038
1038
  cwd: taskDir,
1039
1039
  encoding: "utf8"
1040
1040
  });
@@ -1097,12 +1097,12 @@ function ghText(args, cwd) {
1097
1097
  }
1098
1098
 
1099
1099
  function ghCommand(args, cwd) {
1100
- const command = resolveCommand("gh");
1101
- const result = spawnSync(command, args, commandOptions(command, {
1100
+ const gh = resolveGhCommand();
1101
+ const result = spawn.sync(gh.command, [...gh.preArgs, ...args], {
1102
1102
  cwd,
1103
1103
  encoding: "utf8",
1104
1104
  env: process.env
1105
- }));
1105
+ });
1106
1106
 
1107
1107
  if (result.status !== 0) {
1108
1108
  const stderr = `${result.stderr || ""}${result.stdout || ""}`.trim();
@@ -1113,17 +1113,35 @@ function ghCommand(args, cwd) {
1113
1113
  return { ok: true, value: result.stdout };
1114
1114
  }
1115
1115
 
1116
+ function resolveGhCommand() {
1117
+ const command = process.env.AGENT_INFRA_GH_BIN || "gh";
1118
+ const rawPreArgs = process.env.AGENT_INFRA_GH_ARGS_JSON;
1119
+ if (!rawPreArgs) {
1120
+ return { command, preArgs: [] };
1121
+ }
1122
+
1123
+ try {
1124
+ const preArgs = JSON.parse(rawPreArgs);
1125
+ if (Array.isArray(preArgs) && preArgs.every((arg) => typeof arg === "string")) {
1126
+ return { command, preArgs };
1127
+ }
1128
+ } catch {
1129
+ return { command, preArgs: [] };
1130
+ }
1131
+
1132
+ return { command, preArgs: [] };
1133
+ }
1134
+
1116
1135
  function ghPaginatedJson(args, cwd) {
1117
1136
  return ghJson(args, cwd);
1118
1137
  }
1119
1138
 
1120
1139
  function gitText(args, cwd) {
1121
- const command = resolveCommand("git");
1122
- const result = spawnSync(command, args, commandOptions(command, {
1140
+ const result = spawn.sync("git", args, {
1123
1141
  cwd,
1124
1142
  encoding: "utf8",
1125
1143
  env: process.env
1126
- }));
1144
+ });
1127
1145
 
1128
1146
  if (result.status !== 0) {
1129
1147
  const stderr = `${result.stderr || ""}${result.stdout || ""}`.trim();
@@ -1137,6 +1155,51 @@ function gitText(args, cwd) {
1137
1155
  return { ok: true, value: String(result.stdout || "").trim() };
1138
1156
  }
1139
1157
 
1158
+ function resolvePrHeadSha(context) {
1159
+ const fallback = () => withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
1160
+ const branch = String(context.task?.metadata?.branch || "").trim();
1161
+ if (!branch) {
1162
+ return fallback();
1163
+ }
1164
+
1165
+ const worktreeList = withRetry(() => gitText(["worktree", "list", "--porcelain"], context.taskDir));
1166
+ if (!worktreeList.ok) {
1167
+ return fallback();
1168
+ }
1169
+
1170
+ const matchedWorktree = findWorktreeForBranch(worktreeList.value, branch);
1171
+ if (!matchedWorktree) {
1172
+ return fallback();
1173
+ }
1174
+
1175
+ const headInWorktree = withRetry(() => gitText(["rev-parse", "HEAD"], matchedWorktree));
1176
+ if (!headInWorktree.ok) {
1177
+ return fallback();
1178
+ }
1179
+
1180
+ return headInWorktree;
1181
+ }
1182
+
1183
+ function findWorktreeForBranch(porcelainOutput, branch) {
1184
+ let currentWorktree = "";
1185
+ for (const rawLine of String(porcelainOutput || "").split("\n")) {
1186
+ const line = rawLine.trimEnd();
1187
+ if (line.startsWith("worktree ")) {
1188
+ currentWorktree = line.slice("worktree ".length).trim();
1189
+ continue;
1190
+ }
1191
+
1192
+ if (line.startsWith("branch refs/heads/")) {
1193
+ const usedBranch = line.slice("branch refs/heads/".length).trim();
1194
+ if (usedBranch === branch && currentWorktree) {
1195
+ return currentWorktree;
1196
+ }
1197
+ }
1198
+ }
1199
+
1200
+ return null;
1201
+ }
1202
+
1140
1203
  function withRetry(operation) {
1141
1204
  const delays = getRetryDelays();
1142
1205
  let lastFailure = null;
@@ -1192,39 +1255,6 @@ function sleep(delayMs) {
1192
1255
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
1193
1256
  }
1194
1257
 
1195
- function resolveCommand(cmd) {
1196
- if (process.platform !== "win32" || path.extname(cmd)) {
1197
- return cmd;
1198
- }
1199
-
1200
- const pathValue = process.env.Path || process.env.PATH || "";
1201
- const extensions = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
1202
- .split(";")
1203
- .filter(Boolean);
1204
-
1205
- for (const dir of pathValue.split(path.delimiter).filter(Boolean)) {
1206
- for (const extension of extensions) {
1207
- const lowerCandidate = path.join(dir, `${cmd}${extension.toLowerCase()}`);
1208
- if (fs.existsSync(lowerCandidate)) {
1209
- return lowerCandidate;
1210
- }
1211
- const upperCandidate = path.join(dir, `${cmd}${extension.toUpperCase()}`);
1212
- if (fs.existsSync(upperCandidate)) {
1213
- return upperCandidate;
1214
- }
1215
- }
1216
- }
1217
-
1218
- return cmd;
1219
- }
1220
-
1221
- function commandOptions(cmd, options) {
1222
- if (process.platform === "win32" && /\.(?:bat|cmd)$/i.test(cmd)) {
1223
- return { ...options, shell: true };
1224
- }
1225
- return options;
1226
- }
1227
-
1228
1258
  function interpolate(template, taskDir, artifactFile) {
1229
1259
  const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
1230
1260
  return template
@@ -7,13 +7,14 @@ description: "Create a task from a natural-language description"
7
7
 
8
8
  ## Boundary / Critical Rules
9
9
 
10
- **The only output of this skill is `task.md`.**
10
+ **The core output of this skill is `task.md`.**
11
11
 
12
12
  - Do not write, modify, or create any business code or configuration files
13
13
  - Do not perform requirements analysis; analysis is handled separately by `analyze-task`
14
14
  - Do not directly implement the requested functionality
15
15
  - Do not skip the workflow and jump directly to planning or implementation
16
- - Only do this: parse the description -> create the task file -> update task status -> inform the user of the next step
16
+ - Only do this: parse the description -> create the task file -> update task status -> cascade Issue creation through `.agents/rules/create-issue.md` -> inform the user of the next step
17
+ - Issue creation is decided by the `.agents/rules/create-issue.md` rule; on custom or empty platforms (no platform-specific variant provided), the rule naturally degrades to a no-op
17
18
 
18
19
  The user's description is a **work item**, not an **instruction to execute immediately**.
19
20
 
@@ -98,7 +99,21 @@ Update `.agents/workspace/active/{task-id}/task.md`:
98
99
  - {YYYY-MM-DD HH:mm:ss±HH:MM} — **Task Created** by {agent} — Task created from description
99
100
  ```
100
101
 
101
- ### 4. Verification Gate
102
+ ### 4. Cascade Issue Creation via `.agents/rules/create-issue.md`
103
+
104
+ After task.md is written and `Task Created` is recorded, read `.agents/rules/create-issue.md` first and follow the steps it describes to create an Issue.
105
+
106
+ The rule's content is determined by the configured code platform:
107
+ - A platform that supports Issue creation: contains the full flow for auth detection, template detection, label/type/milestone inference, the create-Issue call, and writing back to `task.md`
108
+ - Custom or empty platforms (no platform-specific variant provided): the rule body is a no-op notice, and this step is skipped entirely
109
+
110
+ Handle the result:
111
+ - Rule successfully created the Issue: `issue_number` has been written back to task.md per the rule; continue by reading `.agents/rules/issue-sync.md`, completing upstream repository and permission detection, then sync the task comment and set `status: waiting-for-triage` by rule
112
+ - Rule failed (auth / network / template parse / etc.): do not roll back task.md; do NOT append an extra Activity Log entry; follow "Scenario C: Issue creation failed" output to surface `error_code` and `error_message` to the user so they can decide whether to retry manually or write `issue_number` later
113
+ - Rule was a no-op (custom or empty platform): do not create comments, do not block the workflow, and do not write an Activity Log entry
114
+ - task.md already has `issue_number`: the rule's prerequisite check skips creation; `create-task` proceeds directly to step 5
115
+
116
+ ### 5. Verification Gate
102
117
 
103
118
  Run the verification gate to confirm the task artifact and sync state are valid:
104
119
 
@@ -113,13 +128,33 @@ Handle the result as follows:
113
128
 
114
129
  Keep the gate output in your reply as fresh evidence. Do not claim completion without output from this run.
115
130
 
116
- ### 5. Inform User
131
+ ### 6. Inform User
117
132
 
118
133
  > Execute this step only after the verification gate passes.
119
134
 
120
135
  > **IMPORTANT**: All TUI command formats listed below must be output in full. Do not show only the format for the current AI agent. If `.agents/.airc.json` configures custom TUIs (via `customTUIs`), read each tool's `name` and `invoke`, then add the matching command line in the same format (`${skillName}` becomes the skill name and `${projectName}` becomes the project name).
121
136
 
122
- Output format:
137
+ Scenario A: when an Issue was created, output:
138
+ ```
139
+ Task created and Issue creation cascaded successfully.
140
+
141
+ Task information:
142
+ - Task ID: {task-id}
143
+ - Title: {title}
144
+ - Type: {type}
145
+ - Workflow: {workflow}
146
+ - Issue: #{issue_number} {issue_url}
147
+
148
+ Output file:
149
+ - Task file: .agents/workspace/active/{task-id}/task.md
150
+
151
+ Next step - run requirements analysis:
152
+ - Claude Code / OpenCode: /analyze-task {task-id}
153
+ - Gemini CLI: /{{project}}:analyze-task {task-id}
154
+ - Codex CLI: $analyze-task {task-id}
155
+ ```
156
+
157
+ Scenario B: when no Issue was created, output:
123
158
  ```
124
159
  Task created.
125
160
 
@@ -132,15 +167,36 @@ Task information:
132
167
  Output file:
133
168
  - Task file: .agents/workspace/active/{task-id}/task.md
134
169
 
170
+ Next step - run requirements analysis:
171
+ - Claude Code / OpenCode: /analyze-task {task-id}
172
+ - Gemini CLI: /{{project}}:analyze-task {task-id}
173
+ - Codex CLI: $analyze-task {task-id}
174
+ ```
175
+
176
+ Scenario C: when Issue creation failed, output:
177
+ ```
178
+ Task created, but cascade Issue creation failed.
179
+
180
+ Task information:
181
+ - Task ID: {task-id}
182
+ - Title: {title}
183
+ - Type: {type}
184
+ - Workflow: {workflow}
185
+
186
+ Issue creation failed:
187
+ - Error code: {error_code}
188
+ - Reason: {error_message}
189
+ - Local task.md was kept and not rolled back
190
+
191
+ Output file:
192
+ - Task file: .agents/workspace/active/{task-id}/task.md
193
+
135
194
  Next step - run requirements analysis:
136
195
  - Claude Code / OpenCode: /analyze-task {task-id}
137
196
  - Gemini CLI: /{{project}}:analyze-task {task-id}
138
197
  - Codex CLI: $analyze-task {task-id}
139
198
 
140
- Or create an Issue first:
141
- - Claude Code / OpenCode: /create-issue {task-id}
142
- - Gemini CLI: /{{project}}:create-issue {task-id}
143
- - Codex CLI: $create-issue {task-id}
199
+ For later platform sync: after fixing auth / network / template issues, manually run the Issue creation flow in `.agents/rules/create-issue.md` for this task; or manually create/find an Issue and write `issue_number` into task.md so later skills can take over cascade sync.
144
200
  ```
145
201
 
146
202
  ## Completion Checklist
@@ -150,8 +206,9 @@ Or create an Issue first:
150
206
  - [ ] Updated `updated_at` to the current time in task.md
151
207
  - [ ] Updated `assigned_to` in task.md
152
208
  - [ ] Appended an Activity Log entry to task.md
209
+ - [ ] Tried cascading Issue creation through `.agents/rules/create-issue.md`; if it failed, kept task.md and recorded the reason
153
210
  - [ ] Informed the user of the next step (must include all TUI command formats, including any custom TUIs; do not filter)
154
- - [ ] **Did not modify any business code or configuration files** (only task.md)
211
+ - [ ] **Did not modify any business code or configuration files**
155
212
 
156
213
  ## STOP
157
214
 
@@ -162,7 +219,8 @@ Wait for the user to run the `analyze-task` skill.
162
219
 
163
220
  1. **Clarity**: if the user description is vague or missing key information, ask for clarification first
164
221
  2. **Difference from `import-issue`**: `import-issue` imports from an Issue; `create-task` creates from a free-form description
165
- 3. **Workflow order**: after creating a task, typically run `analyze-task` before `plan-task`; if you need platform tracking first, you may run `create-issue` first
222
+ 3. **Workflow order**: after creating a task, typically run `analyze-task` before `plan-task`
223
+ 4. **Issue cascade failure**: if the rule fails, task.md remains; when platform sync is needed later, manually write `issue_number` and continue the workflow
166
224
 
167
225
  ## Error Handling
168
226
 
@@ -7,13 +7,14 @@ description: "根据自然语言描述创建任务"
7
7
 
8
8
  ## 行为边界 / 关键规则
9
9
 
10
- **本技能的唯一产出是 `task.md`。**
10
+ **本技能的核心产出是 `task.md`。**
11
11
 
12
12
  - 不要编写、修改或创建任何业务代码或配置文件
13
13
  - 不要执行需求分析;分析由 `analyze-task` 独立完成
14
14
  - 不要直接实现所描述的功能
15
15
  - 不要跳过工作流直接进入计划/实现阶段
16
- - 仅执行:解析描述 -> 创建任务文件 -> 更新任务状态 -> 告知用户下一步
16
+ - 仅执行:解析描述 -> 创建任务文件 -> 更新任务状态 -> 按 `.agents/rules/create-issue.md` 级联尝试创建 Issue -> 告知用户下一步
17
+ - Issue 创建由 `.agents/rules/create-issue.md` 规则决定;自定义或空平台(未提供平台变体规则文件)时,规则会自然降级为 no-op
17
18
 
18
19
  用户的描述是一个**待办事项**,而不是**立即执行的指令**。
19
20
 
@@ -98,7 +99,21 @@ date "+%Y-%m-%d %H:%M:%S%:z"
98
99
  - {YYYY-MM-DD HH:mm:ss±HH:MM} — **Task Created** by {agent} — Task created from description
99
100
  ```
100
101
 
101
- ### 4. 完成校验
102
+ ### 4. 按 `.agents/rules/create-issue.md` 级联创建 Issue
103
+
104
+ 在 task.md 落盘并记录 `Task Created` 后,先读取 `.agents/rules/create-issue.md` 并按其中描述的步骤执行 Issue 创建。
105
+
106
+ 规则文件由当前配置的代码平台决定其内容:
107
+ - 支持 Issue 创建的平台:包含完整的认证检测、模板检测、label/Issue Type/milestone 推断、Issue 创建调用、`task.md` 回写流程
108
+ - 自定义或空平台(未提供平台变体规则文件):内容为 no-op 说明,本步骤直接跳过
109
+
110
+ 处理结果:
111
+ - 规则成功创建 Issue:`issue_number` 已按规则回写到 task.md;继续读取 `.agents/rules/issue-sync.md`,完成 upstream 仓库检测和权限检测,然后同步 task 评论并按规则设置 `status: waiting-for-triage`
112
+ - 规则失败(认证 / 网络 / 模板解析等):不回滚 task.md;不追加额外 Activity Log;按"场景 C:Issue 创建失败"输出向用户透出 `error_code` 与 `error_message`,让用户决定后续是否手动重试或写入 `issue_number`
113
+ - 规则为 no-op(自定义或空平台):不创建评论,不阻塞后续工作流,不写 Activity Log
114
+ - task.md 已存在 `issue_number`:规则中的前置检查会跳过;`create-task` 直接进入步骤 5
115
+
116
+ ### 5. 完成校验
102
117
 
103
118
  运行完成校验,确认任务产物和同步状态符合规范:
104
119
 
@@ -113,13 +128,33 @@ node .agents/scripts/validate-artifact.js gate create-task .agents/workspace/act
113
128
 
114
129
  将校验输出保留在回复中作为当次验证输出。没有当次校验输出,不得声明完成。
115
130
 
116
- ### 5. 告知用户
131
+ ### 6. 告知用户
117
132
 
118
133
  > 仅在校验通过后执行本步骤。
119
134
 
120
135
  > **重要**:以下「下一步」中列出的所有 TUI 命令格式必须完整输出,不要只展示当前 AI 代理对应的格式。如果 `.agents/.airc.json` 中配置了自定义 TUI(`customTUIs`),读取每个工具的 `name` 和 `invoke`,按同样格式补充对应命令行(`${skillName}` 替换为技能名,`${projectName}` 替换为项目名)。
121
136
 
122
- 输出格式:
137
+ 场景 A:已创建 Issue 时输出:
138
+ ```
139
+ 任务已创建,并已级联创建 Issue。
140
+
141
+ 任务信息:
142
+ - 任务 ID:{task-id}
143
+ - 标题:{title}
144
+ - 类型:{type}
145
+ - 工作流:{workflow}
146
+ - Issue:#{issue_number} {issue_url}
147
+
148
+ 产出文件:
149
+ - 任务文件:.agents/workspace/active/{task-id}/task.md
150
+
151
+ 下一步 - 执行需求分析:
152
+ - Claude Code / OpenCode:/analyze-task {task-id}
153
+ - Gemini CLI:/{{project}}:analyze-task {task-id}
154
+ - Codex CLI:$analyze-task {task-id}
155
+ ```
156
+
157
+ 场景 B:未创建 Issue 时输出:
123
158
  ```
124
159
  任务已创建。
125
160
 
@@ -134,13 +169,34 @@ node .agents/scripts/validate-artifact.js gate create-task .agents/workspace/act
134
169
 
135
170
  下一步 - 执行需求分析:
136
171
  - Claude Code / OpenCode:/analyze-task {task-id}
137
- - Gemini CLI:/agent-infra:analyze-task {task-id}
172
+ - Gemini CLI:/{{project}}:analyze-task {task-id}
173
+ - Codex CLI:$analyze-task {task-id}
174
+ ```
175
+
176
+ 场景 C:Issue 创建失败时输出:
177
+ ```
178
+ 任务已创建,但 Issue 级联创建失败。
179
+
180
+ 任务信息:
181
+ - 任务 ID:{task-id}
182
+ - 标题:{title}
183
+ - 类型:{type}
184
+ - 工作流:{workflow}
185
+
186
+ Issue 创建失败:
187
+ - 错误码:{error_code}
188
+ - 原因:{error_message}
189
+ - 本地 task.md 已保留,未回滚
190
+
191
+ 产出文件:
192
+ - 任务文件:.agents/workspace/active/{task-id}/task.md
193
+
194
+ 下一步 - 执行需求分析:
195
+ - Claude Code / OpenCode:/analyze-task {task-id}
196
+ - Gemini CLI:/{{project}}:analyze-task {task-id}
138
197
  - Codex CLI:$analyze-task {task-id}
139
198
 
140
- 或先创建 Issue
141
- - Claude Code / OpenCode:/create-issue {task-id}
142
- - Gemini CLI:/agent-infra:create-issue {task-id}
143
- - Codex CLI:$create-issue {task-id}
199
+ 后续如需平台同步:修复认证/网络/模板问题后,可按 `.agents/rules/create-issue.md` 对当前任务手动执行一次 Issue 创建;或手动创建/查找 Issue,并把 `issue_number` 写入 task.md,后续技能会接管级联同步。
144
200
  ```
145
201
 
146
202
  ## 完成检查清单
@@ -150,8 +206,9 @@ node .agents/scripts/validate-artifact.js gate create-task .agents/workspace/act
150
206
  - [ ] 更新了 task.md 中的 `updated_at` 为当前时间
151
207
  - [ ] 更新了 task.md 中的 `assigned_to`
152
208
  - [ ] 追加了 Activity Log 条目到 task.md
209
+ - [ ] 已按 `.agents/rules/create-issue.md` 尝试级联创建 Issue;失败时保留 task.md 并记录原因
153
210
  - [ ] 告知了用户下一步(必须展示所有 TUI 的命令格式,含自定义 TUI,不要筛选)
154
- - [ ] **没有修改任何业务代码或配置文件**(仅 task.md)
211
+ - [ ] **没有修改任何业务代码或配置文件**
155
212
 
156
213
  ## 停止
157
214
 
@@ -162,7 +219,8 @@ node .agents/scripts/validate-artifact.js gate create-task .agents/workspace/act
162
219
 
163
220
  1. **清晰度**:如果用户描述模糊或缺少关键信息,先要求澄清
164
221
  2. **与 import-issue 的区别**:`import-issue` 从 Issue 导入任务;`create-task` 从自由描述创建
165
- 3. **工作流顺序**:创建任务后,通常先执行 `analyze-task` 再进入 `plan-task`;如需先建立 平台跟踪,也可先执行 `create-issue`
222
+ 3. **工作流顺序**:创建任务后,通常先执行 `analyze-task` 再进入 `plan-task`
223
+ 4. **Issue 级联失败**:如果规则执行失败,task.md 仍保留;需要后续平台同步时,可手动写入 `issue_number` 后继续执行工作流
166
224
 
167
225
  ## 错误处理
168
226
 
@@ -20,6 +20,11 @@
20
20
  "expected_action_pattern": "Task Created",
21
21
  "freshness_minutes": 30
22
22
  },
23
- "platform-sync": null
23
+ "platform-sync": {
24
+ "when": "issue_number_exists",
25
+ "issue_must_exist": true,
26
+ "verify_task_comment_content": true,
27
+ "expected_status_label_key": "waitingForTriage"
28
+ }
24
29
  }
25
30
  }
@@ -16,31 +16,26 @@ Follow the `implementation` step in `.agents/workflows/feature-development.yaml`
16
16
  **Implementation principles**:
17
17
  1. **Follow the plan strictly**: do not deviate from the technical plan
18
18
  2. **Work step by step**: execute the planned sequence
19
- 3. **Keep testing continuously**: re-run tests as work progresses
19
+ 3. **Keep testing continuously**: run the **smoke subset** continuously as work progresses (see the `test` skill)
20
20
  4. **Keep it simple**: do not add unplanned features
21
21
 
22
22
  ## Run Test Verification
23
23
 
24
- Run the project test command. Refer to the `test` skill for project-specific commands.
24
+ During implementation:
25
+ - **Inner loop**: after each change, run the project's **smoke subset** (see the `test` skill) for fast feedback
26
+ - **Before writing the implementation report**: run the **core subset** as final verification so code entering review has passed the complete core checks
25
27
 
26
- ```bash
27
- # See .agents/skills/test/SKILL.md for the project's test command
28
- # Common patterns:
29
- # npm test (Node.js)
30
- # mvn test (Maven)
31
- # pytest (Python)
32
- # go test ./... (Go)
33
- ```
28
+ > Refer to the `test` skill for project-specific commands; downstream projects without layered scripts should fall back to the full project test command.
34
29
 
35
30
  If tests fail:
36
31
  - analyze the failure first and prioritize fixing issues introduced by this implementation
37
- - re-run tests after each fix until they pass, or confirm that the problem is an external blocker
32
+ - after each fix, re-run at least the smoke subset, then upgrade to core for the next full-pass verification
38
33
  - only stop without producing the implementation artifact when the failure is caused by an external blocker, missing environment, or unclear requirement that cannot be resolved inside the task
39
34
 
40
35
  Two-way failure handling:
41
36
  1. implementation-caused failures:
42
37
  - fix the code, tests, docs, or fixtures introduced by this implementation
43
- - re-run the test command after each fix
38
+ - re-run tests after each fix (smoke for the immediate fix verification, core for the round-level verification)
44
39
  - continue until all required tests pass
45
40
  2. external blockers:
46
41
  - confirm the failure comes from missing environment, unrelated upstream breakage, or unclear requirements outside this task