@fitlab-ai/agent-infra 0.4.3 → 0.4.5

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 (52) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/lib/defaults.json +2 -1
  4. package/package.json +1 -1
  5. package/templates/.agents/rules/commit-and-pr.md +30 -0
  6. package/templates/.agents/rules/commit-and-pr.zh-CN.md +30 -0
  7. package/templates/.agents/rules/issue-sync.md +21 -2
  8. package/templates/.agents/rules/issue-sync.zh-CN.md +21 -2
  9. package/templates/.agents/rules/milestone-inference.md +102 -0
  10. package/templates/.agents/rules/milestone-inference.zh-CN.md +102 -0
  11. package/templates/.agents/rules/task-management.md +28 -0
  12. package/templates/.agents/rules/task-management.zh-CN.md +28 -0
  13. package/templates/.agents/scripts/validate-artifact.js +176 -12
  14. package/templates/.agents/skills/analyze-task/config/verify.json +3 -1
  15. package/templates/.agents/skills/cancel-task/SKILL.md +142 -0
  16. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +142 -0
  17. package/templates/.agents/skills/cancel-task/config/verify.json +30 -0
  18. package/templates/.agents/skills/complete-task/SKILL.md +1 -0
  19. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -0
  20. package/templates/.agents/skills/complete-task/config/verify.json +6 -1
  21. package/templates/.agents/skills/create-issue/SKILL.md +2 -2
  22. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +2 -2
  23. package/templates/.agents/skills/create-issue/config/verify.json +3 -1
  24. package/templates/.agents/skills/create-issue/reference/label-and-type.md +3 -1
  25. package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +3 -1
  26. package/templates/.agents/skills/create-pr/SKILL.md +1 -1
  27. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  28. package/templates/.agents/skills/create-pr/config/verify.json +2 -1
  29. package/templates/.agents/skills/create-pr/reference/pr-body-template.md +4 -12
  30. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +4 -12
  31. package/templates/.agents/skills/implement-task/SKILL.md +12 -8
  32. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +12 -8
  33. package/templates/.agents/skills/implement-task/config/verify.json +3 -1
  34. package/templates/.agents/skills/import-issue/SKILL.md +12 -2
  35. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +12 -2
  36. package/templates/.agents/skills/plan-task/config/verify.json +3 -1
  37. package/templates/.agents/skills/refine-task/SKILL.md +4 -10
  38. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +4 -10
  39. package/templates/.agents/skills/refine-task/config/verify.json +3 -1
  40. package/templates/.agents/skills/refine-task/reference/fix-workflow.md +7 -7
  41. package/templates/.agents/skills/refine-task/reference/fix-workflow.zh-CN.md +7 -7
  42. package/templates/.agents/skills/review-task/config/verify.json +3 -1
  43. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -2
  44. package/templates/.agents/templates/task.md +3 -7
  45. package/templates/.agents/templates/task.zh-CN.md +3 -7
  46. package/templates/.claude/commands/cancel-task.md +9 -0
  47. package/templates/.claude/commands/cancel-task.zh-CN.md +9 -0
  48. package/templates/.gemini/commands/_project_/cancel-task.toml +8 -0
  49. package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +8 -0
  50. package/templates/.github/workflows/status-label.yml +4 -1
  51. package/templates/.opencode/commands/cancel-task.md +11 -0
  52. package/templates/.opencode/commands/cancel-task.zh-CN.md +11 -0
@@ -154,6 +154,8 @@ function runCheck(type, context) {
154
154
  return checkArtifact(context);
155
155
  case "activity-log":
156
156
  return checkActivityLog(context);
157
+ case "completion-checklist":
158
+ return checkCompletionChecklist(context);
157
159
  case "github-sync":
158
160
  return checkGithubSync(context);
159
161
  default:
@@ -176,7 +178,7 @@ function checkTaskMeta({ taskDir, config }) {
176
178
  return failResult("task-meta", `Missing required fields: ${missingFields.join(", ")}`);
177
179
  }
178
180
 
179
- const invalidDates = ["created_at", "updated_at", "completed_at", "blocked_at"]
181
+ const invalidDates = ["created_at", "updated_at", "completed_at", "blocked_at", "cancelled_at"]
180
182
  .filter((field) => !isBlank(metadata[field]) && !DATE_TIME_PATTERN.test(metadata[field]));
181
183
  if (invalidDates.length > 0) {
182
184
  return failResult("task-meta", `Invalid date format in: ${invalidDates.join(", ")}`);
@@ -216,6 +218,10 @@ function checkTaskMeta({ taskDir, config }) {
216
218
  return failResult("task-meta", "Expected blocked_at to be present");
217
219
  }
218
220
 
221
+ if (config.require_cancelled_at && isBlank(metadata.cancelled_at)) {
222
+ return failResult("task-meta", "Expected cancelled_at to be present");
223
+ }
224
+
219
225
  if (config.match_task_dir !== false) {
220
226
  const expectedTaskId = path.basename(taskDir);
221
227
  if (metadata.id !== expectedTaskId) {
@@ -338,6 +344,43 @@ function checkActivityLog({ taskDir, config }) {
338
344
  return passResult("activity-log", `Latest entry '${latestAction}' at ${latestTimestamp}`);
339
345
  }
340
346
 
347
+ function checkCompletionChecklist({ taskDir, config }) {
348
+ const task = loadTask(taskDir);
349
+ if (!task.ok) {
350
+ return failResult("completion-checklist", task.message);
351
+ }
352
+
353
+ const checklist = getSectionContent(task.content, ["完成检查清单", "Completion Checklist"]);
354
+ if (!checklist) {
355
+ return failResult("completion-checklist", "Completion Checklist section not found");
356
+ }
357
+
358
+ const items = checklist
359
+ .split(/\r?\n/)
360
+ .map((line) => line.trim())
361
+ .filter((line) => /^- \[(?: |x|X)\] .+$/.test(line));
362
+
363
+ if (items.length === 0) {
364
+ return failResult("completion-checklist", "Completion Checklist has no checkbox items");
365
+ }
366
+
367
+ if (config.require_all_checked) {
368
+ const unchecked = items
369
+ .map((line) => line.match(/^- \[ \] (.+)$/))
370
+ .filter(Boolean)
371
+ .map((match) => match[1].trim());
372
+
373
+ if (unchecked.length > 0) {
374
+ return failResult(
375
+ "completion-checklist",
376
+ `Completion Checklist has unchecked items: ${unchecked.join(", ")}`
377
+ );
378
+ }
379
+ }
380
+
381
+ return passResult("completion-checklist", `Completion Checklist valid (${items.length} items checked)`);
382
+ }
383
+
341
384
  function checkGithubSync({ taskDir, config, artifactFile }) {
342
385
  const context = buildSyncContext({ taskDir, config, artifactFile });
343
386
  if (context.earlyReturn) {
@@ -356,7 +399,9 @@ function checkGithubSync({ taskDir, config, artifactFile }) {
356
399
  checkCommentContent,
357
400
  checkTaskCommentContent,
358
401
  checkInLabelsMatchPr,
359
- checkSyncedRequirements
402
+ checkSyncedRequirements,
403
+ checkIssueType,
404
+ checkMilestone
360
405
  ];
361
406
 
362
407
  for (const subCheck of subChecks) {
@@ -536,7 +581,7 @@ function fetchRemoteData(context) {
536
581
  "view",
537
582
  String(context.issueNumber),
538
583
  "--json",
539
- "state,labels,body"
584
+ "state,labels,body,milestone"
540
585
  ], context.taskDir));
541
586
  if (!issueResult.ok) {
542
587
  return {
@@ -599,14 +644,37 @@ function fetchRemoteData(context) {
599
644
  prComments = flattenComments(prCommentsResult.value);
600
645
  }
601
646
 
647
+ let issueType;
648
+ if (context.config.verify_issue_type) {
649
+ const issueTypeResult = withRetry(() => ghText([
650
+ "api",
651
+ `repos/${context.ownerRepo}/issues/${context.issueNumber}`,
652
+ "--jq",
653
+ ".type.name // empty"
654
+ ], context.taskDir));
655
+
656
+ if (issueTypeResult.ok) {
657
+ issueType = issueTypeResult.value || null;
658
+ }
659
+ }
660
+
602
661
  let prLabels = null;
603
- if (context.config.verify_in_labels_match_pr && context.prNumber) {
662
+ let prMilestone;
663
+ if ((context.config.verify_in_labels_match_pr || context.config.verify_milestone) && context.prNumber) {
664
+ const prFields = [];
665
+ if (context.config.verify_in_labels_match_pr) {
666
+ prFields.push("labels");
667
+ }
668
+ if (context.config.verify_milestone) {
669
+ prFields.push("milestone");
670
+ }
671
+
604
672
  const prResult = withRetry(() => ghJson([
605
673
  "pr",
606
674
  "view",
607
675
  String(context.prNumber),
608
676
  "--json",
609
- "labels"
677
+ prFields.join(",")
610
678
  ], context.taskDir));
611
679
 
612
680
  if (!prResult.ok) {
@@ -617,14 +685,21 @@ function fetchRemoteData(context) {
617
685
  };
618
686
  }
619
687
 
620
- prLabels = extractLabelNames(prResult.value?.labels);
688
+ prLabels = context.config.verify_in_labels_match_pr
689
+ ? extractLabelNames(prResult.value?.labels)
690
+ : null;
691
+ prMilestone = context.config.verify_milestone
692
+ ? prResult.value?.milestone ?? null
693
+ : undefined;
621
694
  }
622
695
 
623
696
  return {
624
697
  issue,
625
698
  comments,
626
699
  prComments,
627
- prLabels
700
+ prLabels,
701
+ issueType,
702
+ prMilestone
628
703
  };
629
704
  }
630
705
 
@@ -810,6 +885,59 @@ function checkSyncedRequirements(context, remoteData) {
810
885
  );
811
886
  }
812
887
 
888
+ function checkIssueType(context, remoteData) {
889
+ if (!context.config.verify_issue_type) {
890
+ return null;
891
+ }
892
+
893
+ if (remoteData.issueType === undefined) {
894
+ return null;
895
+ }
896
+
897
+ if (!remoteData.issueType) {
898
+ return failResult(
899
+ "github-sync",
900
+ `Issue #${context.issueNumber} has no Issue Type set`,
901
+ "check_failed"
902
+ );
903
+ }
904
+
905
+ const expectedType = mapTaskTypeToIssueType(context.task.metadata.type);
906
+ if (expectedType && remoteData.issueType !== expectedType) {
907
+ return failResult(
908
+ "github-sync",
909
+ `Issue #${context.issueNumber} has type '${remoteData.issueType}', expected '${expectedType}' (from task type '${context.task.metadata.type}')`,
910
+ "check_failed"
911
+ );
912
+ }
913
+
914
+ return null;
915
+ }
916
+
917
+ function checkMilestone(context, remoteData) {
918
+ if (!context.config.verify_milestone) {
919
+ return null;
920
+ }
921
+
922
+ if (!remoteData.issue?.milestone?.title) {
923
+ return failResult(
924
+ "github-sync",
925
+ `Issue #${context.issueNumber} has no milestone set`,
926
+ "check_failed"
927
+ );
928
+ }
929
+
930
+ if (context.prNumber && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
931
+ return failResult(
932
+ "github-sync",
933
+ `PR #${context.prNumber} has no milestone set`,
934
+ "check_failed"
935
+ );
936
+ }
937
+
938
+ return null;
939
+ }
940
+
813
941
  function findCommentByMarker(comments, marker) {
814
942
  return (comments || []).find((comment) => typeof comment.body === "string" && comment.body.includes(marker)) || null;
815
943
  }
@@ -945,6 +1073,24 @@ function extractLabelNames(labels) {
945
1073
  .filter((label) => typeof label === "string" && label.length > 0);
946
1074
  }
947
1075
 
1076
+ function mapTaskTypeToIssueType(taskType) {
1077
+ const mapping = {
1078
+ bug: "Bug",
1079
+ bugfix: "Bug",
1080
+ enhancement: "Feature",
1081
+ feature: "Feature",
1082
+ task: "Task",
1083
+ documentation: "Task",
1084
+ "dependency-upgrade": "Task",
1085
+ chore: "Task",
1086
+ docs: "Task",
1087
+ refactor: "Task",
1088
+ refactoring: "Task"
1089
+ };
1090
+
1091
+ return mapping[taskType] || "Task";
1092
+ }
1093
+
948
1094
  function arraysEqual(left, right) {
949
1095
  if (left.length !== right.length) {
950
1096
  return false;
@@ -992,6 +1138,28 @@ function resolveOwnerRepo(taskDir) {
992
1138
  }
993
1139
 
994
1140
  function ghJson(args, cwd) {
1141
+ const result = ghCommand(args, cwd);
1142
+ if (!result.ok) {
1143
+ return result;
1144
+ }
1145
+
1146
+ try {
1147
+ return { ok: true, value: JSON.parse(result.value || "null") };
1148
+ } catch (error) {
1149
+ return { ok: false, type: "network_error", message: `Invalid JSON from gh: ${error.message}` };
1150
+ }
1151
+ }
1152
+
1153
+ function ghText(args, cwd) {
1154
+ const result = ghCommand(args, cwd);
1155
+ if (!result.ok) {
1156
+ return result;
1157
+ }
1158
+
1159
+ return { ok: true, value: String(result.value || "").trim() };
1160
+ }
1161
+
1162
+ function ghCommand(args, cwd) {
995
1163
  const result = spawnSync("gh", args, {
996
1164
  cwd,
997
1165
  encoding: "utf8",
@@ -1004,11 +1172,7 @@ function ghJson(args, cwd) {
1004
1172
  return { ok: false, type: classified.type, message: classified.message };
1005
1173
  }
1006
1174
 
1007
- try {
1008
- return { ok: true, value: JSON.parse(result.stdout || "null") };
1009
- } catch (error) {
1010
- return { ok: false, type: "network_error", message: `Invalid JSON from gh: ${error.message}` };
1011
- }
1175
+ return { ok: true, value: result.stdout };
1012
1176
  }
1013
1177
 
1014
1178
  function ghPaginatedJson(args, cwd) {
@@ -35,7 +35,9 @@
35
35
  "expected_status_label": "status: pending-design-work",
36
36
  "expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
37
37
  "verify_comment_content": true,
38
- "verify_task_comment_content": true
38
+ "verify_task_comment_content": true,
39
+ "verify_issue_type": true,
40
+ "verify_milestone": true
39
41
  }
40
42
  }
41
43
  }
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: cancel-task
3
+ description: "Cancel an unneeded task and archive it"
4
+ ---
5
+
6
+ # Cancel Task
7
+
8
+ ## Boundary / Critical Rules
9
+
10
+ - This command terminates a task that no longer needs to continue and archives it into `completed/`
11
+ - Cancel only when the task no longer needs implementation, review, or follow-up work
12
+ - When a valid `issue_number` exists, GitHub Issue sync is required
13
+
14
+ ## Steps
15
+
16
+ ### 1. Verify Task Exists
17
+
18
+ Check these directories in order:
19
+ - `.agents/workspace/active/{task-id}/`
20
+ - `.agents/workspace/blocked/{task-id}/`
21
+ - `.agents/workspace/completed/{task-id}/`
22
+
23
+ Handling rules:
24
+ - If found in `active/` or `blocked/`: continue
25
+ - If found only in `completed/`: inform the user the task is already archived and stop
26
+ - If not found anywhere: prompt `Task {task-id} not found`
27
+
28
+ ### 2. Choose the Cancellation Label
29
+
30
+ Infer the GitHub Issue closing label from the cancellation reason:
31
+ - `status: superseded`: reason implies duplicate, replaced, merged into, or already covered by another Issue or PR
32
+ - `status: invalid`: reason implies invalid report, no real problem, cannot reproduce, or no issue after investigation
33
+ - `status: declined`: reason implies not planned, deprioritized, or explicitly rejected
34
+ - If nothing matches: fall back to `status: declined`
35
+
36
+ When syncing to the Issue, replace any existing `status:` labels with the inferred label.
37
+
38
+ ### 3. Update Task Metadata
39
+
40
+ Get the current time:
41
+
42
+ ```bash
43
+ date "+%Y-%m-%d %H:%M:%S"
44
+ ```
45
+
46
+ Update `task.md` in the task directory:
47
+ - `status`: completed
48
+ - `cancelled_at`: {current timestamp}
49
+ - `cancel_reason`: {cancellation reason}
50
+ - `updated_at`: {current timestamp}
51
+ - **Append** to `## Activity Log` (do NOT overwrite previous entries):
52
+ ```
53
+ - {yyyy-MM-dd HH:mm:ss} — **Cancelled** by {agent} — {one-line cancellation reason}
54
+ ```
55
+
56
+ ### 4. Archive the Task
57
+
58
+ Move the task directory into `.agents/workspace/completed/{task-id}`.
59
+
60
+ If the source directory is `blocked/`, move it from `blocked/`; if it is `active/`, move it from `active/`.
61
+
62
+ ### 5. Verify the Archive
63
+
64
+ ```bash
65
+ ls .agents/workspace/completed/{task-id}/task.md
66
+ ```
67
+
68
+ Confirm the task directory was moved successfully.
69
+
70
+ ### 6. Sync to Issue
71
+
72
+ Check whether `task.md` contains a valid `issue_number`. If not, skip this step.
73
+
74
+ > Issue sync rules live in `.agents/rules/issue-sync.md`. Read that file before syncing.
75
+
76
+ If a valid `issue_number` exists:
77
+ - Replace all `status:` labels with the label inferred in Step 2
78
+ - Remove all `in:` labels
79
+ - Remove the milestone
80
+ - Remove all assignees
81
+ - Publish a cancellation comment using the marker `<!-- sync-issue:{task-id}:cancel -->`
82
+ - Create or update the `<!-- sync-issue:{task-id}:task -->` comment using the task-comment sync rules from `.agents/rules/issue-sync.md`
83
+ - Close the Issue: `gh issue close {issue-number} --reason "not planned"`
84
+
85
+ The cancellation comment must include at least:
86
+ - the cancellation reason
87
+ - the selected `status:` label
88
+
89
+ ### 7. Verification Gate
90
+
91
+ Run the verification gate to confirm the archived task and sync state are valid:
92
+
93
+ ```bash
94
+ node .agents/scripts/validate-artifact.js gate cancel-task .agents/workspace/completed/{task-id} --format text
95
+ ```
96
+
97
+ Handle the result as follows:
98
+ - exit code 0 (all checks passed) -> continue to the "Inform User" step
99
+ - exit code 1 (validation failed) -> fix the reported issues and run the gate again
100
+ - exit code 2 (network blocked) -> stop and tell the user that human intervention is required
101
+
102
+ Keep the gate output in your reply as fresh evidence. Do not claim completion without output from this run.
103
+
104
+ ### 8. Inform User
105
+
106
+ > Execute this step only after the verification gate passes.
107
+
108
+ > **IMPORTANT**: All TUI command formats listed below must be output in full. Do not show only the format for the current AI agent.
109
+
110
+ Output format:
111
+ ```
112
+ Task {task-id} cancelled and archived.
113
+
114
+ Cancellation reason: {reason}
115
+ GitHub label: {status-label or skipped}
116
+ Archived to: .agents/workspace/completed/{task-id}/
117
+
118
+ Next step - inspect the archived task:
119
+ - Claude Code / OpenCode: /check-task {task-id}
120
+ - Gemini CLI: /{{project}}:check-task {task-id}
121
+ - Codex CLI: $check-task {task-id}
122
+ ```
123
+
124
+ ## Completion Checklist
125
+
126
+ - [ ] Recorded the cancellation reason and updated task.md
127
+ - [ ] Moved the task directory into `.agents/workspace/completed/`
128
+ - [ ] Completed GitHub sync when an Issue exists
129
+ - [ ] Ran and passed the verification gate
130
+ - [ ] Showed the full next-step command set to the user
131
+
132
+ ## Notes
133
+
134
+ 1. Cancelled tasks reuse the `completed` status instead of introducing `cancelled`
135
+ 2. Use `cancelled_at` and `cancel_reason` to distinguish cancellation from normal completion
136
+ 3. If closing the Issue fails, do not claim the cancellation is complete
137
+
138
+ ## Error Handling
139
+
140
+ - Task not found: `Task {task-id} not found`
141
+ - Task already archived: inform the user it is already in `completed/`
142
+ - Issue sync failed: keep the local archive result and tell the user manual GitHub follow-up is required
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: cancel-task
3
+ description: "取消不再需要的任务并归档"
4
+ ---
5
+
6
+ # 取消任务
7
+
8
+ ## 行为边界 / 关键规则
9
+
10
+ - 本命令用于终止一个不再需要继续执行的任务,并归档到 `completed/`
11
+ - 只有在确认该任务无需继续实现、审查或修复时才可取消
12
+ - 有效 `issue_number` 存在时,GitHub Issue 同步属于必做项
13
+
14
+ ## 执行步骤
15
+
16
+ ### 1. 验证任务存在
17
+
18
+ 依次检查以下目录:
19
+ - `.agents/workspace/active/{task-id}/`
20
+ - `.agents/workspace/blocked/{task-id}/`
21
+ - `.agents/workspace/completed/{task-id}/`
22
+
23
+ 处理规则:
24
+ - 如果在 `active/` 或 `blocked/` 中找到:继续
25
+ - 如果只在 `completed/` 中找到:告知用户任务已归档,停止
26
+ - 如果都不存在:提示 `Task {task-id} not found`
27
+
28
+ ### 2. 判断取消标签
29
+
30
+ 根据取消原因推断 GitHub Issue 关闭标签:
31
+ - `status: superseded`:原因包含“重复”、“替代”、“合并到”、“已由 #123 / PR 替代”等语义
32
+ - `status: invalid`:原因包含“误报”、“不存在”、“无法复现”、“排查后无问题”等语义
33
+ - `status: declined`:原因包含“不做”、“暂不实现”、“优先级调整”、“方案否决”等语义
34
+ - 以上都不匹配:回退到 `status: declined`
35
+
36
+ 后续同步到 Issue 时,使用最终推断结果替换现有 `status:` labels。
37
+
38
+ ### 3. 更新任务元数据
39
+
40
+ 获取当前时间:
41
+
42
+ ```bash
43
+ date "+%Y-%m-%d %H:%M:%S"
44
+ ```
45
+
46
+ 更新任务目录中的 `task.md`:
47
+ - `status`:completed
48
+ - `cancelled_at`:{当前时间戳}
49
+ - `cancel_reason`:{取消原因}
50
+ - `updated_at`:{当前时间戳}
51
+ - **追加**到 `## Activity Log`(不要覆盖之前记录):
52
+ ```
53
+ - {yyyy-MM-dd HH:mm:ss} — **Cancelled** by {agent} — {一行取消原因}
54
+ ```
55
+
56
+ ### 4. 归档任务
57
+
58
+ 将任务目录移动到 `.agents/workspace/completed/{task-id}`。
59
+
60
+ 如果源目录在 `blocked/`,从 `blocked/` 移动;如果源目录在 `active/`,从 `active/` 移动。
61
+
62
+ ### 5. 验证归档
63
+
64
+ ```bash
65
+ ls .agents/workspace/completed/{task-id}/task.md
66
+ ```
67
+
68
+ 确认任务目录已成功移动。
69
+
70
+ ### 6. 同步到 Issue
71
+
72
+ 检查 `task.md` 中是否存在有效的 `issue_number`。如果没有,跳过此步骤。
73
+
74
+ > Issue 同步规则见 `.agents/rules/issue-sync.md`。执行同步前先读取该文件。
75
+
76
+ 如果存在有效的 `issue_number`:
77
+ - 替换所有 `status:` labels,并设置步骤 2 推断出的标签
78
+ - 移除所有 `in:` labels
79
+ - 移除 milestone
80
+ - 移除全部 assignees
81
+ - 发布取消评论,隐藏标记使用 `<!-- sync-issue:{task-id}:cancel -->`
82
+ - 使用 `.agents/rules/issue-sync.md` 的 task.md 评论同步规则创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论
83
+ - 关闭 Issue:`gh issue close {issue-number} --reason "not planned"`
84
+
85
+ 取消评论至少包含:
86
+ - 取消原因
87
+ - 选定的 `status:` label
88
+
89
+ ### 7. 完成校验
90
+
91
+ 运行完成校验,确认任务归档和同步状态符合规范:
92
+
93
+ ```bash
94
+ node .agents/scripts/validate-artifact.js gate cancel-task .agents/workspace/completed/{task-id} --format text
95
+ ```
96
+
97
+ 处理结果:
98
+ - 退出码 0(全部通过)-> 继续到「告知用户」步骤
99
+ - 退出码 1(校验失败)-> 根据输出修复问题后重新运行校验
100
+ - 退出码 2(网络中断)-> 停止执行并告知用户需要人工介入
101
+
102
+ 将校验输出保留在回复中作为当次验证输出。没有当次校验输出,不得声明完成。
103
+
104
+ ### 8. 告知用户
105
+
106
+ > 仅在校验通过后执行本步骤。
107
+
108
+ > **重要**:以下「下一步」中列出的所有 TUI 命令格式必须完整输出,不要只展示当前 AI 代理对应的格式。
109
+
110
+ 输出格式:
111
+ ```
112
+ 任务 {task-id} 已取消并归档。
113
+
114
+ 取消原因:{reason}
115
+ GitHub 标签:{status-label 或 skipped}
116
+ 归档路径:.agents/workspace/completed/{task-id}/
117
+
118
+ 下一步 - 查看归档任务:
119
+ - Claude Code / OpenCode:/check-task {task-id}
120
+ - Gemini CLI:/{{project}}:check-task {task-id}
121
+ - Codex CLI:$check-task {task-id}
122
+ ```
123
+
124
+ ## 完成检查清单
125
+
126
+ - [ ] 已记录取消原因并更新 task.md
127
+ - [ ] 已将任务目录移动到 `.agents/workspace/completed/`
128
+ - [ ] 已在存在 Issue 时完成 GitHub 同步
129
+ - [ ] 已运行 gate 校验并通过
130
+ - [ ] 已向用户展示完整的下一步命令
131
+
132
+ ## 注意事项
133
+
134
+ 1. 取消任务不会新增 `cancelled` 状态值,而是复用 `completed`
135
+ 2. 必须通过 `cancelled_at` 和 `cancel_reason` 区分“取消”与“正常完成”
136
+ 3. 如果 Issue 关闭失败,不要宣称取消完成
137
+
138
+ ## 错误处理
139
+
140
+ - 任务未找到:`Task {task-id} not found`
141
+ - 任务已归档:提示任务已在 `completed/` 中
142
+ - Issue 同步失败:保留本地归档结果,并告知用户需要人工补齐 GitHub 操作
@@ -0,0 +1,30 @@
1
+ {
2
+ "skill": "cancel-task",
3
+ "checks": {
4
+ "task-meta": {
5
+ "required_fields": [
6
+ "id",
7
+ "type",
8
+ "workflow",
9
+ "status",
10
+ "created_at",
11
+ "updated_at",
12
+ "current_step",
13
+ "assigned_to",
14
+ "cancelled_at",
15
+ "cancel_reason"
16
+ ],
17
+ "expected_status": "completed",
18
+ "require_cancelled_at": true
19
+ },
20
+ "activity-log": {
21
+ "expected_action_pattern": "Cancelled",
22
+ "freshness_minutes": 30
23
+ },
24
+ "github-sync": {
25
+ "when": "issue_number_exists",
26
+ "expected_comment_marker": "<!-- sync-issue:{task-id}:cancel -->",
27
+ "verify_task_comment_content": true
28
+ }
29
+ }
30
+ }
@@ -61,6 +61,7 @@ Update `.agents/workspace/active/{task-id}/task.md`:
61
61
  - `completed_at`: {current timestamp}
62
62
  - `updated_at`: {current timestamp}
63
63
  - Mark all workflow steps as complete
64
+ - Verify and check off all items in `## Completion Checklist` (change `- [ ]` to `- [x]`)
64
65
  - **Append** to `## Activity Log` (do NOT overwrite previous entries):
65
66
  ```
66
67
  - {yyyy-MM-dd HH:mm:ss} — **Completed** by {agent} — Task archived to completed/
@@ -61,6 +61,7 @@ date "+%Y-%m-%d %H:%M:%S"
61
61
  - `completed_at`:{当前时间戳}
62
62
  - `updated_at`:{当前时间戳}
63
63
  - 标记所有工作流步骤为已完成
64
+ - 逐项验证并勾选 `## 完成检查清单` 中的所有条目(将 `- [ ]` 改为 `- [x]`)
64
65
  - **追加**到 `## Activity Log`(不要覆盖之前的记录):
65
66
  ```
66
67
  - {yyyy-MM-dd HH:mm:ss} — **Completed** by {agent} — Task archived to completed/
@@ -20,11 +20,16 @@
20
20
  "expected_action_pattern": "Completed",
21
21
  "freshness_minutes": 30
22
22
  },
23
+ "completion-checklist": {
24
+ "require_all_checked": true
25
+ },
23
26
  "github-sync": {
24
27
  "when": "issue_number_exists",
25
28
  "expected_comment_marker": "<!-- sync-issue:{task-id}:summary -->",
26
29
  "verify_task_comment_content": true,
27
- "sync_checked_requirements": true
30
+ "sync_checked_requirements": true,
31
+ "verify_issue_type": true,
32
+ "verify_milestone": true
28
33
  }
29
34
  }
30
35
  }
@@ -27,7 +27,7 @@ If `issue_number` already exists and is not empty or `N/A`, confirm with the use
27
27
 
28
28
  ### 2. Extract Task Information
29
29
 
30
- Extract the title, `## Description`, `## Requirements`, `type`, and `milestone` from task.md. Build the Issue title by mapping task.md `type` to a Conventional Commits type, inferring scope, and formatting it as `cc_type(scope): task_title` or `cc_type: task_title` when scope is unclear.
30
+ Extract the title, `## Description`, `## Requirements`, `type`, and `milestone` from task.md. Build the Issue title by mapping task.md `type` to a Conventional Commits type, inferring scope, and formatting it as `cc_type(scope): task_title` or `cc_type: task_title` when scope is unclear. If task.md does not provide an explicit `milestone` field, infer it by following "Phase 1: `create-issue`" in `.agents/rules/milestone-inference.md`.
31
31
 
32
32
  ### 3. Build Issue Content
33
33
 
@@ -39,7 +39,7 @@ Detect `.github/ISSUE_TEMPLATE` files and decide whether to use a matched templa
39
39
 
40
40
  ### 4. Create the Issue
41
41
 
42
- Create the Issue with `gh issue create --title "{title}" --body "{body}" ...` and omit `--label` when nothing valid remains.
42
+ Create the Issue with `gh issue create --title "{title}" --body "{body}" --assignee @me ...` and omit `--label` when nothing valid remains.
43
43
 
44
44
  If an Issue Type was selected, set it with:
45
45
  `gh api "repos/$repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent`
@@ -27,7 +27,7 @@ description: "从任务文件创建 GitHub Issue"
27
27
 
28
28
  ### 2. 提取任务信息
29
29
 
30
- 从 task.md 提取标题、`## Description`、`## Requirements`、`type` 和 `milestone`。构造 Issue 标题:将 task.md 的 `type` 映射为 Conventional Commits type,推断 scope,拼接为 `cc_type(scope): task_title` 或 `cc_type: task_title`(scope 不确定时省略)。
30
+ 从 task.md 提取标题、`## Description`、`## Requirements`、`type` 和 `milestone`。构造 Issue 标题:将 task.md 的 `type` 映射为 Conventional Commits type,推断 scope,拼接为 `cc_type(scope): task_title` 或 `cc_type: task_title`(scope 不确定时省略)。如果 task.md 没有显式 `milestone` 字段,按 `.agents/rules/milestone-inference.md` 的「阶段 1:`create-issue`」推断。
31
31
 
32
32
  ### 3. 构建 Issue 内容
33
33
 
@@ -39,7 +39,7 @@ description: "从任务文件创建 GitHub Issue"
39
39
 
40
40
  ### 4. 创建 Issue
41
41
 
42
- 使用 `gh issue create --title "{title}" --body "{body}" ...` 创建 Issue;如果没有有效 label,就省略 `--label`。
42
+ 使用 `gh issue create --title "{title}" --body "{body}" --assignee @me ...` 创建 Issue;如果没有有效 label,就省略 `--label`。
43
43
 
44
44
  如果已经确定了 Issue Type,则执行:
45
45
  `gh api "repos/$repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent`
@@ -21,7 +21,9 @@
21
21
  "github-sync": {
22
22
  "when": "issue_number_exists",
23
23
  "issue_must_exist": true,
24
- "verify_task_comment_content": true
24
+ "verify_task_comment_content": true,
25
+ "verify_issue_type": true,
26
+ "verify_milestone": true
25
27
  }
26
28
  }
27
29
  }