@fitlab-ai/agent-infra 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +368 -51
- package/README.zh-CN.md +369 -52
- package/bin/cli.js +1 -1
- package/lib/version.js +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.md +12 -0
- package/templates/.agents/README.zh-CN.md +12 -0
- package/templates/.agents/skills/analyze-task/SKILL.md +106 -105
- package/templates/.agents/skills/check-task/SKILL.md +108 -94
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +12 -0
- package/templates/.agents/skills/close-codescan/SKILL.md +64 -63
- package/templates/.agents/skills/close-dependabot/SKILL.md +71 -70
- package/templates/.agents/skills/commit/SKILL.md +19 -4
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +19 -4
- package/templates/.agents/skills/complete-task/SKILL.md +11 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/create-issue/SKILL.md +302 -0
- package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +302 -0
- package/templates/.agents/skills/create-pr/SKILL.md +140 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +140 -5
- package/templates/.agents/skills/create-release-note/SKILL.md +18 -11
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +18 -11
- package/templates/.agents/skills/create-task/SKILL.md +80 -78
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -6
- package/templates/.agents/skills/implement-task/SKILL.md +7 -2
- package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +7 -2
- package/templates/.agents/skills/import-codescan/SKILL.md +54 -53
- package/templates/.agents/skills/import-dependabot/SKILL.md +57 -56
- package/templates/.agents/skills/import-issue/SKILL.md +58 -58
- package/templates/.agents/skills/init-labels/SKILL.md +8 -0
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -0
- package/templates/.agents/skills/plan-task/SKILL.md +151 -149
- package/templates/.agents/skills/refine-task/SKILL.md +147 -137
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +10 -2
- package/templates/.agents/skills/review-task/SKILL.md +196 -186
- package/templates/.agents/skills/review-task/SKILL.zh-CN.md +13 -4
- package/templates/.agents/skills/sync-issue/SKILL.md +252 -272
- package/templates/.agents/skills/sync-issue/SKILL.zh-CN.md +26 -47
- package/templates/.agents/skills/sync-pr/SKILL.md +274 -82
- package/templates/.agents/skills/sync-pr/SKILL.zh-CN.md +251 -59
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +1 -1
- package/templates/.claude/CLAUDE.md +13 -0
- package/templates/.claude/CLAUDE.zh-CN.md +13 -0
- package/templates/.claude/commands/create-issue.md +8 -0
- package/templates/.claude/commands/create-issue.zh-CN.md +8 -0
- package/templates/.gemini/commands/_project_/create-issue.toml +8 -0
- package/templates/.gemini/commands/_project_/create-issue.zh-CN.toml +8 -0
- package/templates/.opencode/commands/create-issue.md +11 -0
- package/templates/.opencode/commands/create-issue.zh-CN.md +11 -0
- package/templates/AGENTS.md +13 -0
- package/templates/AGENTS.zh-CN.md +13 -0
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sync-issue
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Sync task progress to comments on the related GitHub Issue.
|
|
5
|
+
Triggered when the user asks to sync progress to an Issue.
|
|
6
|
+
Argument: task-id or issue-number.
|
|
6
7
|
---
|
|
7
8
|
|
|
8
|
-
#
|
|
9
|
+
# Sync Progress to Issue
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
Sync task progress to the related GitHub Issue. Argument: task-id or issue-number.
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## Execution Flow
|
|
13
14
|
|
|
14
|
-
### 1.
|
|
15
|
+
### 1. Parse the Argument
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
- `TASK-`
|
|
17
|
+
Identify the argument provided by the user:
|
|
18
|
+
- plain number (`123`) or `#` + number (`#123`) -> treat as an issue number
|
|
19
|
+
- Starts with `TASK-` -> treat as a task-id (current format)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
If the argument is an issue number, use Bash to search for the related task (note: `.agent-workspace` is a hidden directory, so Grep/Glob tools may skip it; you must use Bash):
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
grep -rl "^issue_number: {issue-number}$" \
|
|
@@ -27,44 +28,44 @@ grep -rl "^issue_number: {issue-number}$" \
|
|
|
27
28
|
2>/dev/null | head -1
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
31
|
+
- If a file path is returned (for example `.agent-workspace/completed/TASK-xxx/task.md`), extract `{task-id}` and the task directory from the path, then continue to Step 2
|
|
32
|
+
- If nothing is returned, output `No task found associated with Issue #{issue-number}`
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
If the argument is a task-id, continue with the normal Step 2 flow.
|
|
34
35
|
|
|
35
|
-
### 2.
|
|
36
|
+
### 2. Verify the Task Exists
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
For a `task-id`, search for the task in this order:
|
|
38
39
|
- `.agent-workspace/active/{task-id}/task.md`
|
|
39
40
|
- `.agent-workspace/blocked/{task-id}/task.md`
|
|
40
41
|
- `.agent-workspace/completed/{task-id}/task.md`
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
Note: `{task-id}` format is `TASK-{yyyyMMdd-HHmmss}`, for example `TASK-20260306-143022`
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
If Step 1 already found a matching task through the issue number, use that task directory directly for the remaining steps without scanning again.
|
|
45
46
|
|
|
46
|
-
### 3.
|
|
47
|
+
### 3. Read Task Information
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
- `issue_number
|
|
49
|
+
Extract from task.md:
|
|
50
|
+
- `issue_number` (required; if missing, prompt the user)
|
|
50
51
|
- `type`
|
|
51
|
-
-
|
|
52
|
-
- `current_step
|
|
52
|
+
- task title, description, and status
|
|
53
|
+
- `current_step`, `created_at`, `updated_at`, and `last_synced_at` (if present)
|
|
53
54
|
|
|
54
|
-
### 4.
|
|
55
|
+
### 4. Read Context Files
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
- `implementation.md
|
|
60
|
-
- `refinement.md
|
|
61
|
-
- `review.md
|
|
57
|
+
Check and read these files if they exist:
|
|
58
|
+
- highest-round `analysis.md` / `analysis-r{N}.md` - requirements analysis
|
|
59
|
+
- highest-round `plan.md` / `plan-r{N}.md` - technical plan
|
|
60
|
+
- `implementation.md`, `implementation-r*.md` - implementation reports
|
|
61
|
+
- `refinement.md`, `refinement-r*.md` - refinement reports
|
|
62
|
+
- `review.md`, `review-r*.md` - review reports
|
|
62
63
|
|
|
63
|
-
### 5.
|
|
64
|
+
### 5. Detect Delivery Status
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
Run the following checks in order. If any step fails, fall back to "Mode C: In development". Do not invent anything you cannot verify.
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
Before starting detection, first resolve repository coordinates and the absolute URL prefix:
|
|
68
69
|
|
|
69
70
|
```bash
|
|
70
71
|
repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner')"
|
|
@@ -72,203 +73,171 @@ owner="${repo%%/*}"
|
|
|
72
73
|
repo_url="https://github.com/$repo"
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
**a)
|
|
76
|
+
**a) Extract the commit hash**
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
Match the last `**Commit** by` record in `## Activity Log` in task.md. The Activity Log format is:
|
|
78
79
|
|
|
79
80
|
```text
|
|
80
81
|
**Commit** by {agent} — {hash} {subject}
|
|
81
82
|
```
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
Extract the first word as the commit hash. If none is found, mark it as "no commit".
|
|
84
85
|
|
|
85
|
-
**b)
|
|
86
|
+
**b) Detect whether the commit is on a protected branch**
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
If a commit hash exists, run:
|
|
88
89
|
|
|
89
90
|
```bash
|
|
90
91
|
git branch -a --contains {commit-hash} 2>/dev/null
|
|
91
92
|
```
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
94
|
+
Decision rules:
|
|
95
|
+
- output contains `main` or `master` -> merged into the main branch; record the branch name
|
|
96
|
+
- output matches a `{major}.{minor}.x` branch name -> merged into a release branch; record the branch name
|
|
97
|
+
- neither matches -> not merged into a protected branch
|
|
97
98
|
|
|
98
|
-
**c)
|
|
99
|
+
**c) Detect the linked PR**
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
Check the `pr_number` field in task.md. If it exists, run:
|
|
101
102
|
|
|
102
103
|
```bash
|
|
103
104
|
gh pr view {pr-number} --json state,mergedAt
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
Use the result to determine whether the PR is `OPEN`, `MERGED`, or another state.
|
|
107
108
|
|
|
108
|
-
**d)
|
|
109
|
+
**d) Determine the delivery mode**
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Choose the summary mode with this priority:
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
**e) 综合判定交付模式**
|
|
119
|
-
|
|
120
|
-
按以下优先级确定摘要模式:
|
|
121
|
-
|
|
122
|
-
| 条件 | 模式 |
|
|
123
|
-
|------|------|
|
|
124
|
-
| commit 已在受保护分支上 | 模式 A:已完成 |
|
|
125
|
-
| 有 PR,且状态为 `OPEN` 或 `MERGED` | 模式 B:PR 阶段 |
|
|
126
|
-
| 其他情况 | 模式 C:开发中 |
|
|
113
|
+
| Condition | Mode |
|
|
114
|
+
|---|---|
|
|
115
|
+
| commit is already on a protected branch | Mode A: Completed |
|
|
116
|
+
| PR exists and its state is `OPEN` or `MERGED` | Mode B: PR stage |
|
|
117
|
+
| anything else | Mode C: In development |
|
|
127
118
|
|
|
128
|
-
|
|
119
|
+
The priority must be `Mode A > Mode B > Mode C`. Even if a PR exists, treat it as "Completed" when the commit is already on a protected branch.
|
|
129
120
|
|
|
130
|
-
|
|
121
|
+
All later commit and PR links must use absolute URLs:
|
|
131
122
|
- `https://github.com/{owner}/{repo}/commit/{commit-hash}`
|
|
132
123
|
- `https://github.com/{owner}/{repo}/pull/{pr-number}`
|
|
133
124
|
|
|
134
|
-
|
|
125
|
+
Do not use relative paths such as `../../commit/...` or `../../pull/...`.
|
|
135
126
|
|
|
136
|
-
### 6.
|
|
127
|
+
### 6. Sync Labels and Issue Type
|
|
137
128
|
|
|
138
|
-
|
|
129
|
+
Sync Issue labels based on the detection result from Step 5.
|
|
139
130
|
|
|
140
|
-
**a)
|
|
131
|
+
**a) Check whether the label system has been initialized**
|
|
141
132
|
|
|
142
|
-
|
|
133
|
+
Run:
|
|
143
134
|
|
|
144
135
|
```bash
|
|
145
136
|
gh label list --search "type:" --limit 1 --json name --jq 'length'
|
|
146
137
|
```
|
|
147
138
|
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
**b) 同步 type label**
|
|
153
|
-
|
|
154
|
-
根据 task.md 的 `type` 字段按下表映射:
|
|
155
|
-
|
|
156
|
-
| task.md type | GitHub label |
|
|
157
|
-
|---|---|
|
|
158
|
-
| bug、bugfix | `type: bug` |
|
|
159
|
-
| feature | `type: feature` |
|
|
160
|
-
| enhancement | `type: enhancement` |
|
|
161
|
-
| documentation | `type: documentation` |
|
|
162
|
-
| dependency-upgrade | `type: dependency-upgrade` |
|
|
163
|
-
| task | `type: task` |
|
|
164
|
-
| 其他(含 refactoring 等) | 跳过 |
|
|
165
|
-
|
|
166
|
-
如果映射到具体 label,执行:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
gh issue edit {issue-number} --add-label "{type-label}"
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
未映射到标准 type label 时跳过,不创建新 label。
|
|
139
|
+
Decision rules:
|
|
140
|
+
- returns `0` -> the standard label system is missing; run the `init-labels` skill first (idempotent), then retry this step
|
|
141
|
+
- returns non-zero -> continue with label sync
|
|
173
142
|
|
|
174
|
-
**
|
|
143
|
+
**b) Sync the `status:` label**
|
|
175
144
|
|
|
176
|
-
|
|
145
|
+
First read existing `status:` labels on the Issue:
|
|
177
146
|
|
|
178
147
|
```bash
|
|
179
148
|
gh issue view {issue-number} --json labels --jq '.labels[].name | select(startswith("status:"))'
|
|
180
149
|
```
|
|
181
150
|
|
|
182
|
-
|
|
151
|
+
Remove each existing `status:` label:
|
|
183
152
|
|
|
184
153
|
```bash
|
|
185
154
|
gh issue edit {issue-number} --remove-label "{status-label}"
|
|
186
155
|
```
|
|
187
156
|
|
|
188
|
-
|
|
157
|
+
Then decide whether to add a new `status:` label using this priority:
|
|
189
158
|
|
|
190
|
-
|
|
|
159
|
+
| Condition | Action |
|
|
191
160
|
|---|---|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
161
|
+
| task is under the `blocked/` directory | add `status: blocked` |
|
|
162
|
+
| Mode A: Completed | do not add a new status label |
|
|
163
|
+
| Mode B: PR is MERGED | do not add a new status label |
|
|
164
|
+
| Mode B: PR is OPEN | add `status: in-progress` |
|
|
165
|
+
| Mode C + `current_step` ∈ {`requirement-analysis`, `technical-design`} | add `status: pending-design-work` |
|
|
166
|
+
| Mode C + `current_step` ∈ {`implementation`, `code-review`, `refinement`} | add `status: in-progress` |
|
|
198
167
|
|
|
199
|
-
|
|
168
|
+
If a new label needs to be added, run:
|
|
200
169
|
|
|
201
170
|
```bash
|
|
202
171
|
gh issue edit {issue-number} --add-label "{status-label}"
|
|
203
172
|
```
|
|
204
173
|
|
|
205
|
-
**
|
|
174
|
+
**c) Sync the `in:` label**
|
|
206
175
|
|
|
207
|
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
176
|
+
Extract affected file paths from implementation reports first, or from `analysis.md` as a fallback:
|
|
177
|
+
- prefer file paths listed under `## Modified Files`, especially `### New Files` / `### Modified Files`, in `implementation.md` and `implementation-r{N}.md`
|
|
178
|
+
- if no implementation report exists, fall back to the affected file list in the analysis report
|
|
210
179
|
|
|
211
|
-
|
|
212
|
-
1.
|
|
213
|
-
2.
|
|
214
|
-
3.
|
|
180
|
+
For each file path:
|
|
181
|
+
1. take the first-level directory as the module name
|
|
182
|
+
2. deduplicate
|
|
183
|
+
3. check whether the corresponding label exists in the repository:
|
|
215
184
|
|
|
216
185
|
```bash
|
|
217
186
|
gh label list --search "in: {module}" --limit 10 --json name --jq '.[].name'
|
|
218
187
|
```
|
|
219
188
|
|
|
220
|
-
4.
|
|
189
|
+
4. only when the exact `in: {module}` label exists, run:
|
|
221
190
|
|
|
222
191
|
```bash
|
|
223
192
|
gh issue edit {issue-number} --add-label "in: {module}"
|
|
224
193
|
```
|
|
225
194
|
|
|
226
|
-
5.
|
|
195
|
+
5. **Only add; do not remove** existing `in:` labels
|
|
227
196
|
|
|
228
|
-
**
|
|
197
|
+
**d) Sync the Issue Type field**
|
|
229
198
|
|
|
230
|
-
|
|
199
|
+
Map the `type` field in task.md to the native GitHub Issue Type:
|
|
231
200
|
|
|
232
201
|
| task.md type | GitHub Issue Type |
|
|
233
202
|
|---|---|
|
|
234
|
-
| `bug
|
|
235
|
-
| `feature
|
|
236
|
-
| `task
|
|
203
|
+
| `bug`, `bugfix` | `Bug` |
|
|
204
|
+
| `feature`, `enhancement` | `Feature` |
|
|
205
|
+
| `task`, `documentation`, `dependency-upgrade`, `chore`, `docs`, `refactor`, `refactoring`, and any other value | `Task` |
|
|
237
206
|
|
|
238
|
-
|
|
207
|
+
First query the Issue Types available in the organization:
|
|
239
208
|
|
|
240
209
|
```bash
|
|
241
210
|
gh api "orgs/$owner/issue-types" --jq '.[].name'
|
|
242
211
|
```
|
|
243
212
|
|
|
244
|
-
|
|
213
|
+
Then execute this only when the target type exists:
|
|
245
214
|
|
|
246
215
|
```bash
|
|
247
216
|
gh api "repos/$repo/issues/{issue-number}" -X PATCH -f type="{name}"
|
|
248
217
|
```
|
|
249
218
|
|
|
250
|
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
219
|
+
Fault-tolerance requirements:
|
|
220
|
+
- If the API returns `404`, the repo owner is not an organization, or Issue Types are not enabled for the repo, record `Issue Type: skipped (not enabled)` and continue; do not fail the whole sync
|
|
221
|
+
- If the target type does not exist, record `Issue Type: skipped (type not available)`
|
|
222
|
+
- Do not try to create new Issue Types; only use names that already exist in the organization
|
|
254
223
|
|
|
255
|
-
### 7.
|
|
224
|
+
### 7. Sync Development
|
|
256
225
|
|
|
257
|
-
|
|
226
|
+
If task.md contains `pr_number`, ensure the PR body links the current Issue.
|
|
258
227
|
|
|
259
|
-
1.
|
|
228
|
+
1. Read the PR body:
|
|
260
229
|
|
|
261
230
|
```bash
|
|
262
231
|
gh pr view {pr-number} --json body --jq '.body // ""'
|
|
263
232
|
```
|
|
264
233
|
|
|
265
|
-
2.
|
|
234
|
+
2. Check whether the body already contains any of:
|
|
266
235
|
- `Closes #{issue-number}`
|
|
267
236
|
- `Fixes #{issue-number}`
|
|
268
237
|
- `Resolves #{issue-number}`
|
|
269
238
|
|
|
270
|
-
3.
|
|
271
|
-
4.
|
|
239
|
+
3. If any keyword already exists, skip the update
|
|
240
|
+
4. Otherwise append this to the end of the body:
|
|
272
241
|
|
|
273
242
|
```bash
|
|
274
243
|
gh pr edit {pr-number} --body "$(cat <<'EOF'
|
|
@@ -279,85 +248,85 @@ EOF
|
|
|
279
248
|
)"
|
|
280
249
|
```
|
|
281
250
|
|
|
282
|
-
5.
|
|
251
|
+
5. If task.md does not contain `pr_number`, record `Development: N/A`
|
|
283
252
|
|
|
284
|
-
### 8.
|
|
253
|
+
### 8. Sync the Milestone
|
|
285
254
|
|
|
286
|
-
|
|
255
|
+
Assign a line milestone to the Issue based on the current Issue state, explicit task configuration, and branch strategy.
|
|
287
256
|
|
|
288
|
-
**a)
|
|
257
|
+
**a) Check whether the Issue already has a milestone**
|
|
289
258
|
|
|
290
|
-
|
|
259
|
+
Run:
|
|
291
260
|
|
|
292
261
|
```bash
|
|
293
262
|
gh issue view {issue-number} --json milestone --jq '.milestone.title // empty'
|
|
294
263
|
```
|
|
295
264
|
|
|
296
|
-
|
|
265
|
+
If the result is non-empty, preserve the existing milestone and record `Milestone: {existing} (preserved)`, then skip the remaining milestone-sync steps.
|
|
297
266
|
|
|
298
|
-
**b)
|
|
267
|
+
**b) Check whether task.md explicitly sets `milestone`**
|
|
299
268
|
|
|
300
|
-
|
|
301
|
-
|
|
269
|
+
If the frontmatter in task.md contains a non-empty `milestone` field, use it as the target milestone.
|
|
270
|
+
This field should contain a line milestone title or `General Backlog`; do not automatically set a specific version milestone here.
|
|
302
271
|
|
|
303
|
-
**c)
|
|
272
|
+
**c) Infer the target line milestone**
|
|
304
273
|
|
|
305
|
-
|
|
274
|
+
When task.md does not explicitly set `milestone`, infer it in this order:
|
|
306
275
|
|
|
307
|
-
1.
|
|
276
|
+
1. Detect the current branch:
|
|
308
277
|
|
|
309
278
|
```bash
|
|
310
279
|
git branch --show-current
|
|
311
280
|
```
|
|
312
281
|
|
|
313
|
-
-
|
|
282
|
+
- If the branch name matches `{major}.{minor}.x`, the target milestone is the same line milestone `{major}.{minor}.x`
|
|
314
283
|
|
|
315
|
-
2.
|
|
284
|
+
2. If the current branch is `main` or `master`, detect existing release branches:
|
|
316
285
|
|
|
317
286
|
```bash
|
|
318
287
|
git branch -a | grep -oE '[0-9]+\.[0-9]+\.x' | sort -V | tail -1
|
|
319
288
|
```
|
|
320
289
|
|
|
321
|
-
-
|
|
322
|
-
-
|
|
290
|
+
- If the highest release branch is `X.Y.x`, the target milestone is `(X+1).0.x`
|
|
291
|
+
- If no release branch exists, read the latest tag:
|
|
323
292
|
|
|
324
293
|
```bash
|
|
325
294
|
git tag --list 'v*' --sort=-v:refname | head -1
|
|
326
295
|
```
|
|
327
296
|
|
|
328
|
-
-
|
|
297
|
+
- When the latest tag exists and can be parsed as `X.Y.Z`, the target milestone is `X.Y.x`
|
|
329
298
|
|
|
330
|
-
3.
|
|
299
|
+
3. If none of the above yields a result, fall back to `General Backlog`
|
|
331
300
|
|
|
332
|
-
**d)
|
|
301
|
+
**d) Find the target milestone number**
|
|
333
302
|
|
|
334
|
-
|
|
303
|
+
Run:
|
|
335
304
|
|
|
336
305
|
```bash
|
|
337
306
|
gh api "repos/$repo/milestones" --paginate \
|
|
338
307
|
--jq '.[] | select(.title=="{target}") | .number'
|
|
339
308
|
```
|
|
340
309
|
|
|
341
|
-
-
|
|
342
|
-
-
|
|
310
|
+
- If the target milestone does not exist, fall back to `General Backlog`
|
|
311
|
+
- If `General Backlog` also does not exist, record `Milestone: skipped (not found)` and skip assignment
|
|
343
312
|
|
|
344
|
-
**e)
|
|
313
|
+
**e) Assign the Issue to the milestone**
|
|
345
314
|
|
|
346
|
-
|
|
315
|
+
Once a target milestone number is found, run:
|
|
347
316
|
|
|
348
317
|
```bash
|
|
349
318
|
gh api "repos/$repo/issues/{issue-number}" -X PATCH -F milestone={milestone-number}
|
|
350
319
|
```
|
|
351
320
|
|
|
352
|
-
|
|
353
|
-
- `Milestone: {target} (assigned)`
|
|
321
|
+
Record:
|
|
322
|
+
- `Milestone: {target} (assigned)` or
|
|
354
323
|
- `Milestone: General Backlog (fallback)`
|
|
355
324
|
|
|
356
|
-
### 9.
|
|
325
|
+
### 9. Fetch Existing Comments and Build the Published Artifact Set
|
|
357
326
|
|
|
358
|
-
|
|
327
|
+
Fetch all Issue comments in one pass, then build the set of published artifact stems from hidden markers and construct the local timeline of artifacts to publish.
|
|
359
328
|
|
|
360
|
-
|
|
329
|
+
First fetch comments (preserving comment id and body):
|
|
361
330
|
|
|
362
331
|
```bash
|
|
363
332
|
comments_jsonl="$(mktemp)"
|
|
@@ -367,38 +336,38 @@ gh api "repos/$repo/issues/{issue-number}/comments" \
|
|
|
367
336
|
--jq '.[] | {id, body}' > "$comments_jsonl"
|
|
368
337
|
```
|
|
369
338
|
|
|
370
|
-
|
|
339
|
+
Extract all Activity Log records in `task.md` that end with `→ {filename}`.
|
|
371
340
|
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
376
|
-
- `summary`
|
|
377
|
-
- `summary`
|
|
341
|
+
Parsing rules:
|
|
342
|
+
- Use the regex `/→\s+(\S+\.md)\s*$/` to extract filenames
|
|
343
|
+
- remove the `.md` suffix to get `{file-stem}`
|
|
344
|
+
- build the artifact timeline in Activity Log order
|
|
345
|
+
- append `summary` as a fixed final artifact at the end of the timeline
|
|
346
|
+
- `summary` is always last
|
|
378
347
|
|
|
379
|
-
|
|
348
|
+
Only include files that still exist in the task directory in the publish set. Skip missing files without error.
|
|
380
349
|
|
|
381
|
-
|
|
350
|
+
The first line of every sync comment must include a hidden marker:
|
|
382
351
|
|
|
383
352
|
```html
|
|
384
353
|
<!-- sync-issue:{task-id}:{file-stem} -->
|
|
385
354
|
```
|
|
386
355
|
|
|
387
|
-
|
|
356
|
+
Where `{file-stem}` is the filename without the `.md` suffix, for example `analysis`, `plan`, `implementation`, `implementation-r2`, or `review-r3`. `summary` still uses the literal `summary`.
|
|
388
357
|
|
|
389
|
-
|
|
358
|
+
Timeline example:
|
|
390
359
|
`analysis → plan → implementation → review → refinement → analysis-r2 → plan-r2 → implementation-r2 → review-r2 → summary`
|
|
391
360
|
|
|
392
|
-
|
|
361
|
+
For each `{file-stem}`, determine whether it has already been published with a local check:
|
|
393
362
|
|
|
394
363
|
```bash
|
|
395
364
|
grep -qF "<!-- sync-issue:{task-id}:{file-stem} -->" "$comments_jsonl"
|
|
396
365
|
```
|
|
397
366
|
|
|
398
|
-
-
|
|
399
|
-
-
|
|
367
|
+
- match found: this artifact has already been published and should be skipped by default
|
|
368
|
+
- no match: this artifact has not been published yet and can create a new comment
|
|
400
369
|
|
|
401
|
-
|
|
370
|
+
For the `summary` artifact, also extract the comment id for later updates:
|
|
402
371
|
|
|
403
372
|
```bash
|
|
404
373
|
summary_comment_id="$(
|
|
@@ -407,92 +376,92 @@ summary_comment_id="$(
|
|
|
407
376
|
)"
|
|
408
377
|
```
|
|
409
378
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
-
|
|
414
|
-
-
|
|
379
|
+
Before finishing Step 9, precompute `has_unpublished_artifacts` from the published/unpublished results above: whether any non-`summary` artifact remains unpublished. Keep this value fixed during Step 10. It is only used to decide whether `summary` should be updated in place or deleted and rebuilt at the end.
|
|
380
|
+
|
|
381
|
+
Idempotency requirements:
|
|
382
|
+
- On the first run, publish comments only for artifacts that currently exist
|
|
383
|
+
- On the second run, skip already published files and only publish new artifacts (for example `implementation-r2`, `review-r2`)
|
|
384
|
+
- If all artifact file comments have already been published and the `summary` content has not changed, publish no new comments
|
|
385
|
+
- If `summary` is already published but the delivery state has changed: delete the old `summary` and recreate it at the end when new artifacts are published this run; otherwise update the existing comment in place when no new artifacts are published
|
|
415
386
|
|
|
416
|
-
### 10.
|
|
387
|
+
### 10. Publish Context Files One by One in Timeline Order
|
|
417
388
|
|
|
418
|
-
|
|
389
|
+
Process the sorted artifact list from Step 9 one item at a time. Do not fall back to a fixed 5-step order, and do not merge multiple rounds of the same artifact type into a single comment.
|
|
419
390
|
|
|
420
|
-
**a)
|
|
391
|
+
**a) Prepare comment content for each artifact**
|
|
421
392
|
|
|
422
|
-
- `analysis
|
|
423
|
-
- `plan
|
|
424
|
-
- `analysis-r{N}
|
|
425
|
-
- `implementation
|
|
426
|
-
- `refinement
|
|
427
|
-
- `review
|
|
428
|
-
- `summary
|
|
393
|
+
- `analysis`: publish the full text of `analysis.md`
|
|
394
|
+
- `plan`: publish the full text of `plan.md`
|
|
395
|
+
- `analysis-r{N}`, `plan-r{N}`: publish one comment per file, using the artifact's original content as the comment body
|
|
396
|
+
- `implementation`, `implementation-r{N}`: publish one comment per file, using the corresponding implementation report as-is
|
|
397
|
+
- `refinement`, `refinement-r{N}`: publish one comment per file, using the corresponding refinement report as-is
|
|
398
|
+
- `review`, `review-r{N}`: publish one comment per file, using the corresponding review report as-is
|
|
399
|
+
- `summary`: generate a concise delivery summary that includes only the current delivery state and absolute GitHub links
|
|
429
400
|
|
|
430
|
-
|
|
401
|
+
All artifacts except `summary` must publish the original content directly. Do not compress them into another summary.
|
|
431
402
|
|
|
432
|
-
|
|
403
|
+
Use the same format for every comment:
|
|
433
404
|
|
|
434
405
|
```markdown
|
|
435
406
|
<!-- sync-issue:{task-id}:{file-stem} -->
|
|
436
|
-
## {
|
|
407
|
+
## {artifact title}
|
|
437
408
|
|
|
438
|
-
{
|
|
409
|
+
{original content or summary content}
|
|
439
410
|
|
|
440
411
|
---
|
|
441
|
-
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
- `analysis` ->
|
|
446
|
-
- `analysis-r2` ->
|
|
447
|
-
- `analysis-r{N}` ->
|
|
448
|
-
- `plan` ->
|
|
449
|
-
- `plan-r2` ->
|
|
450
|
-
- `plan-r{N}` ->
|
|
451
|
-
- `implementation` ->
|
|
452
|
-
- `implementation-r2` ->
|
|
453
|
-
- `implementation-r{N}` ->
|
|
454
|
-
- `refinement` ->
|
|
455
|
-
- `refinement-r2` ->
|
|
456
|
-
- `refinement-r{N}` ->
|
|
457
|
-
- `review` ->
|
|
458
|
-
- `review-r2` ->
|
|
459
|
-
- `review-r{N}` ->
|
|
460
|
-
- `summary` ->
|
|
461
|
-
|
|
462
|
-
`summary`
|
|
412
|
+
*Generated by AI · Internal tracking: {task-id}*
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Recommended title mapping:
|
|
416
|
+
- `analysis` -> `Requirements Analysis`
|
|
417
|
+
- `analysis-r2` -> `Requirements Analysis (Round 2)`
|
|
418
|
+
- `analysis-r{N}` -> `Requirements Analysis (Round {N})`
|
|
419
|
+
- `plan` -> `Technical Plan`
|
|
420
|
+
- `plan-r2` -> `Technical Plan (Round 2)`
|
|
421
|
+
- `plan-r{N}` -> `Technical Plan (Round {N})`
|
|
422
|
+
- `implementation` -> `Implementation Report (Round 1)`
|
|
423
|
+
- `implementation-r2` -> `Implementation Report (Round 2)`
|
|
424
|
+
- `implementation-r{N}` -> `Implementation Report (Round {N})`
|
|
425
|
+
- `refinement` -> `Refinement Report (Round 1)`
|
|
426
|
+
- `refinement-r2` -> `Refinement Report (Round 2)`
|
|
427
|
+
- `refinement-r{N}` -> `Refinement Report (Round {N})`
|
|
428
|
+
- `review` -> `Review Report (Round 1)`
|
|
429
|
+
- `review-r2` -> `Review Report (Round 2)`
|
|
430
|
+
- `review-r{N}` -> `Review Report (Round {N})`
|
|
431
|
+
- `summary` -> `Delivery Summary`
|
|
432
|
+
|
|
433
|
+
Recommended `summary` comment format:
|
|
463
434
|
|
|
464
435
|
```markdown
|
|
465
436
|
<!-- sync-issue:{task-id}:summary -->
|
|
466
|
-
##
|
|
437
|
+
## Delivery Summary
|
|
467
438
|
|
|
468
|
-
|
|
469
|
-
|
|
439
|
+
**Updated at**: {current time}
|
|
440
|
+
**Status**: {formatted status description}
|
|
470
441
|
|
|
471
|
-
|
|
|
442
|
+
| Type | Content |
|
|
472
443
|
|---|---|
|
|
473
|
-
|
|
|
474
|
-
| Commit | [`{commit-short}`](https://github.com/{owner}/{repo}/commit/{commit-hash})
|
|
475
|
-
| PR | [#{pr-number}](https://github.com/{owner}/{repo}/pull/{pr-number}) 或 `N/A` |
|
|
476
|
-
| Issue | `{issue-state}` |
|
|
444
|
+
| Branch | `{branch or N/A}` |
|
|
445
|
+
| Commit | [`{commit-short}`](https://github.com/{owner}/{repo}/commit/{commit-hash}) or `N/A` |
|
|
477
446
|
|
|
478
447
|
---
|
|
479
|
-
|
|
448
|
+
*Generated by AI · Internal tracking: {task-id}*
|
|
480
449
|
```
|
|
481
450
|
|
|
482
|
-
|
|
483
|
-
-
|
|
484
|
-
-
|
|
485
|
-
-
|
|
451
|
+
Formatted status description rules:
|
|
452
|
+
- Mode A: `✅ Completed`
|
|
453
|
+
- Mode B: `PR stage`
|
|
454
|
+
- Mode C: `In development, current step is {current_step}`
|
|
486
455
|
|
|
487
|
-
**b)
|
|
456
|
+
**b) Skip already-published or missing artifacts**
|
|
488
457
|
|
|
489
|
-
-
|
|
490
|
-
-
|
|
491
|
-
-
|
|
458
|
+
- For `analysis.md`, `plan.md`, `implementation*.md`, and `review*.md`: skip directly if the corresponding file does not exist, without error
|
|
459
|
+
- For any artifact: skip by default if its marker already exists
|
|
460
|
+
- For `summary`: regenerate candidate content even if its marker already exists, so you can compare whether an update is needed
|
|
492
461
|
|
|
493
|
-
**c)
|
|
462
|
+
**c) Publish a new comment**
|
|
494
463
|
|
|
495
|
-
|
|
464
|
+
When an artifact has not been published yet, run:
|
|
496
465
|
|
|
497
466
|
```bash
|
|
498
467
|
gh issue comment {issue-number} --body "$(cat <<'EOF'
|
|
@@ -501,69 +470,80 @@ EOF
|
|
|
501
470
|
)"
|
|
502
471
|
```
|
|
503
472
|
|
|
504
|
-
**d)
|
|
473
|
+
**d) Publish or rebuild the `summary` comment**
|
|
474
|
+
|
|
475
|
+
`summary` must always remain the last comment. Choose the handling strategy with these rules:
|
|
505
476
|
|
|
506
|
-
|
|
477
|
+
- `summary` does not exist: publish a new `summary` comment using Step 10c
|
|
478
|
+
- `summary` exists and `has_unpublished_artifacts=true`: delete the old `summary` comment first, then publish a new `summary` comment using Step 10c
|
|
479
|
+
- `summary` exists, `has_unpublished_artifacts=false`, and the newly generated content differs from the existing one: update the existing comment in place
|
|
480
|
+
- `summary` exists, `has_unpublished_artifacts=false`, and the content is the same: do nothing
|
|
481
|
+
|
|
482
|
+
To delete the old `summary` comment, run:
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
gh api "repos/$repo/issues/comments/{summary_comment_id}" -X DELETE
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
To update an existing `summary` comment in place, run:
|
|
507
489
|
|
|
508
490
|
```bash
|
|
509
|
-
gh api "repos/$repo/issues/comments/{
|
|
491
|
+
gh api "repos/$repo/issues/comments/{summary_comment_id}" -X PATCH -f body="$(cat <<'EOF'
|
|
510
492
|
{comment-body}
|
|
511
493
|
EOF
|
|
512
494
|
)"
|
|
513
495
|
```
|
|
514
496
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
**e) 零操作场景**
|
|
497
|
+
**e) No-op scenario**
|
|
518
498
|
|
|
519
|
-
|
|
520
|
-
-
|
|
521
|
-
-
|
|
499
|
+
If all artifacts are already synced and `summary` does not need an update:
|
|
500
|
+
- publish no new comments
|
|
501
|
+
- explicitly tell the user at the end: `All artifacts are already synced, no new content`
|
|
522
502
|
|
|
523
|
-
### 11.
|
|
503
|
+
### 11. Update Task Status
|
|
524
504
|
|
|
525
|
-
|
|
505
|
+
Get the current time:
|
|
526
506
|
|
|
527
507
|
```bash
|
|
528
508
|
date "+%Y-%m-%d %H:%M:%S"
|
|
529
509
|
```
|
|
530
510
|
|
|
531
|
-
|
|
532
|
-
-
|
|
511
|
+
Add or update the `last_synced_at` field in task.md with `{current time}`.
|
|
512
|
+
- **Append** to `## Activity Log` (do NOT overwrite previous entries):
|
|
533
513
|
```
|
|
534
514
|
- {yyyy-MM-dd HH:mm:ss} — **Sync to Issue** by {agent} — Progress synced to Issue #{issue-number}
|
|
535
515
|
```
|
|
536
516
|
|
|
537
|
-
### 12.
|
|
517
|
+
### 12. Inform User
|
|
538
518
|
|
|
539
519
|
```
|
|
540
|
-
|
|
520
|
+
Progress synced to Issue #{issue-number}.
|
|
541
521
|
|
|
542
|
-
|
|
543
|
-
-
|
|
544
|
-
-
|
|
545
|
-
-
|
|
546
|
-
-
|
|
547
|
-
- Labels
|
|
548
|
-
- Issue Type
|
|
549
|
-
- Milestone
|
|
550
|
-
- Development
|
|
522
|
+
Sync result:
|
|
523
|
+
- New comments published: {count}
|
|
524
|
+
- Comments updated: {count}
|
|
525
|
+
- Steps skipped: {step list or `none`}
|
|
526
|
+
- Current status: {status}
|
|
527
|
+
- Labels: status={status-label or cleared}, in:={added count}
|
|
528
|
+
- Issue Type: {Bug / Feature / Task / skipped}
|
|
529
|
+
- Milestone: {preserved / assigned / fallback / skipped}
|
|
530
|
+
- Development: {Closes link appended / link already existed / no PR, skipped}
|
|
551
531
|
|
|
552
|
-
|
|
532
|
+
View: https://github.com/{owner}/{repo}/issues/{issue-number}
|
|
553
533
|
|
|
554
|
-
|
|
534
|
+
If no comments were published or updated in this run, clearly state: all steps are already synced, no new content.
|
|
555
535
|
```
|
|
556
536
|
|
|
557
|
-
##
|
|
537
|
+
## Notes
|
|
558
538
|
|
|
559
|
-
1.
|
|
560
|
-
2.
|
|
561
|
-
3.
|
|
562
|
-
4.
|
|
539
|
+
1. **Requires an issue number**: task.md must contain `issue_number`. If missing, prompt the user.
|
|
540
|
+
2. **Audience**: `sync-issue` is for stakeholders, while `sync-pr` is for code reviewers. They have different focus areas.
|
|
541
|
+
3. **When to sync**: sync after major stages (analysis, design, implementation, review) or when blocked.
|
|
542
|
+
4. **Avoid noise**: do not sync too frequently. Although this skill uses hidden markers for idempotency, avoid meaningless repeated syncs.
|
|
563
543
|
|
|
564
|
-
##
|
|
544
|
+
## Error Handling
|
|
565
545
|
|
|
566
|
-
-
|
|
567
|
-
-
|
|
568
|
-
- Issue
|
|
569
|
-
- gh
|
|
546
|
+
- Task not found: output "Task {task-id} not found"
|
|
547
|
+
- Missing issue number: output "Task has no issue_number field"
|
|
548
|
+
- Issue not found: output "Issue #{number} not found"
|
|
549
|
+
- `gh` authentication failed: output "Please check GitHub CLI authentication"
|