@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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/lib/defaults.json +2 -1
- package/package.json +1 -1
- package/templates/.agents/rules/commit-and-pr.md +30 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +30 -0
- package/templates/.agents/rules/issue-sync.md +21 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +21 -2
- package/templates/.agents/rules/milestone-inference.md +102 -0
- package/templates/.agents/rules/milestone-inference.zh-CN.md +102 -0
- package/templates/.agents/rules/task-management.md +28 -0
- package/templates/.agents/rules/task-management.zh-CN.md +28 -0
- package/templates/.agents/scripts/validate-artifact.js +176 -12
- package/templates/.agents/skills/analyze-task/config/verify.json +3 -1
- package/templates/.agents/skills/cancel-task/SKILL.md +142 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +142 -0
- package/templates/.agents/skills/cancel-task/config/verify.json +30 -0
- package/templates/.agents/skills/complete-task/SKILL.md +1 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/complete-task/config/verify.json +6 -1
- package/templates/.agents/skills/create-issue/SKILL.md +2 -2
- package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-issue/config/verify.json +3 -1
- package/templates/.agents/skills/create-issue/reference/label-and-type.md +3 -1
- package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +3 -1
- package/templates/.agents/skills/create-pr/SKILL.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/config/verify.json +2 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.md +4 -12
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +4 -12
- package/templates/.agents/skills/implement-task/SKILL.md +12 -8
- package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +12 -8
- package/templates/.agents/skills/implement-task/config/verify.json +3 -1
- package/templates/.agents/skills/import-issue/SKILL.md +12 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +12 -2
- package/templates/.agents/skills/plan-task/config/verify.json +3 -1
- package/templates/.agents/skills/refine-task/SKILL.md +4 -10
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +4 -10
- package/templates/.agents/skills/refine-task/config/verify.json +3 -1
- package/templates/.agents/skills/refine-task/reference/fix-workflow.md +7 -7
- package/templates/.agents/skills/refine-task/reference/fix-workflow.zh-CN.md +7 -7
- package/templates/.agents/skills/review-task/config/verify.json +3 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -2
- package/templates/.agents/templates/task.md +3 -7
- package/templates/.agents/templates/task.zh-CN.md +3 -7
- package/templates/.claude/commands/cancel-task.md +9 -0
- package/templates/.claude/commands/cancel-task.zh-CN.md +9 -0
- package/templates/.gemini/commands/_project_/cancel-task.toml +8 -0
- package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +8 -0
- package/templates/.github/workflows/status-label.yml +4 -1
- package/templates/.opencode/commands/cancel-task.md +11 -0
- 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
|
-
|
|
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
|
-
"
|
|
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 =
|
|
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
|
-
|
|
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`
|