@fitlab-ai/agent-infra 0.5.4 → 0.5.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 +1 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +10 -1
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +10 -1
- package/templates/.agents/rules/issue-sync.github.en.md +12 -10
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +12 -10
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +40 -2
- package/templates/.agents/skills/create-pr/config/verify.json +2 -0
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +6 -7
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +6 -7
- package/templates/.agents/skills/create-release-note/SKILL.en.md +27 -2
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +27 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -1
- package/templates/.github/scripts/sync-labels-to-set.sh +110 -0
- package/templates/.github/workflows/metadata-sync.yml +11 -20
- package/templates/.github/workflows/pr-label.yml +9 -18
- package/templates/.github/workflows/status-label.yml +20 -34
package/README.md
CHANGED
|
@@ -410,7 +410,7 @@ The generated `.agents/.airc.json` file is the central contract between the boot
|
|
|
410
410
|
"project": "my-project",
|
|
411
411
|
"org": "my-org",
|
|
412
412
|
"language": "en",
|
|
413
|
-
"templateVersion": "v0.5.
|
|
413
|
+
"templateVersion": "v0.5.5",
|
|
414
414
|
"files": {
|
|
415
415
|
"managed": [
|
|
416
416
|
".agents/workspace/README.md",
|
package/README.zh-CN.md
CHANGED
package/lib/defaults.json
CHANGED
package/package.json
CHANGED
|
@@ -99,12 +99,21 @@ gh pr list --state {state} --base {base-branch} --json number,title,url,headRefN
|
|
|
99
99
|
Create a PR:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
gh pr create --base "{target-branch}" --title "{title}" --assignee @me
|
|
102
|
+
gh pr create --base "{target-branch}" --title "{title}" --assignee @me \
|
|
103
|
+
{label-args} {milestone-arg} \
|
|
104
|
+
--body "$(cat <<'EOF'
|
|
103
105
|
{pr-body}
|
|
104
106
|
EOF
|
|
105
107
|
)"
|
|
106
108
|
```
|
|
107
109
|
|
|
110
|
+
- expand `{label-args}` into repeated `--label "{label}"` flags from the validated label list
|
|
111
|
+
- pass `{label-args}` only when `has_triage=true`; otherwise omit it and continue
|
|
112
|
+
- omit all `--label` flags when nothing valid remains
|
|
113
|
+
- expand `{milestone-arg}` into `--milestone "{milestone}"`
|
|
114
|
+
- pass `{milestone-arg}` only when `has_triage=true`; otherwise omit it and continue
|
|
115
|
+
- omit `{milestone-arg}` entirely when no milestone should be set
|
|
116
|
+
|
|
108
117
|
## Update PRs
|
|
109
118
|
|
|
110
119
|
Update PR titles, labels, or milestones with:
|
|
@@ -99,12 +99,21 @@ gh pr list --state {state} --base {base-branch} --json number,title,url,headRefN
|
|
|
99
99
|
创建 PR:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
gh pr create --base "{target-branch}" --title "{title}" --assignee @me
|
|
102
|
+
gh pr create --base "{target-branch}" --title "{title}" --assignee @me \
|
|
103
|
+
{label-args} {milestone-arg} \
|
|
104
|
+
--body "$(cat <<'EOF'
|
|
103
105
|
{pr-body}
|
|
104
106
|
EOF
|
|
105
107
|
)"
|
|
106
108
|
```
|
|
107
109
|
|
|
110
|
+
- `{label-args}` 由调用方按有效 label 列表展开为多个 `--label "{label}"`
|
|
111
|
+
- 仅当 `has_triage=true` 时传入 `{label-args}`;否则整体省略并继续
|
|
112
|
+
- 没有有效 label 时省略全部 `--label`
|
|
113
|
+
- `{milestone-arg}` 展开为 `--milestone "{milestone}"`
|
|
114
|
+
- 仅当 `has_triage=true` 时传入 `{milestone-arg}`;否则整体省略并继续
|
|
115
|
+
- `{milestone-arg}` 为空时整体省略
|
|
116
|
+
|
|
108
117
|
## PR 更新
|
|
109
118
|
|
|
110
119
|
更新 PR 标题、label 或 milestone:
|
|
@@ -70,20 +70,22 @@ Decision rules:
|
|
|
70
70
|
|
|
71
71
|
## Direct `status:` Label Updates
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Algorithm note: keep the flow below aligned with `.github/scripts/sync-labels-to-set.sh` (set-diff sync). This is the AI agent-side equivalent implementation for the `target_set = {"{target-status-label}"}` case. If either side changes, update the other one in the same patch to avoid drift between the agent and the bot.
|
|
74
|
+
|
|
75
|
+
If task.md contains a valid `issue_number` (not empty and not `N/A`) and the Issue state is `OPEN`, sync the `status:` labels to the target value with an idempotent set diff:
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
78
|
state=$(gh issue view {issue-number} -R "$upstream_repo" --json state --jq '.state' 2>/dev/null)
|
|
77
79
|
if [ "$state" = "OPEN" ]; then
|
|
78
|
-
gh issue view {issue-number} -R "$upstream_repo"
|
|
79
|
-
--jq '.labels[].name | select(startswith("status:"))' 2>/dev/null
|
|
80
|
-
| while IFS= read -r label; do
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if [ "$has_triage" = "true" ]; then
|
|
80
|
+
current_status_labels=$(gh issue view {issue-number} -R "$upstream_repo" \
|
|
81
|
+
--json labels --jq '.labels[].name | select(startswith("status:"))' 2>/dev/null || true)
|
|
82
|
+
printf '%s\n' "$current_status_labels" | while IFS= read -r label; do
|
|
83
|
+
[ -z "$label" ] && continue
|
|
84
|
+
if [ "$label" != "{target-status-label}" ] && [ "$has_triage" = "true" ]; then
|
|
85
|
+
gh issue edit {issue-number} -R "$upstream_repo" --remove-label "$label" 2>/dev/null || true
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
if [ "$has_triage" = "true" ] && ! printf '%s\n' "$current_status_labels" | grep -qxF "{target-status-label}"; then
|
|
87
89
|
gh issue edit {issue-number} -R "$upstream_repo" --add-label "{target-status-label}" 2>/dev/null || true
|
|
88
90
|
fi
|
|
89
91
|
fi
|
|
@@ -70,20 +70,22 @@ current_user=$(gh api user --jq '.login' 2>/dev/null || echo "")
|
|
|
70
70
|
|
|
71
71
|
## status label 设置
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
算法说明:下面的流程与 `.github/scripts/sync-labels-to-set.sh` 保持一致(集合差集)。本章节是 AI Agent 侧的等价实现(`target_set = {"{target-status-label}"}` 的特例)。修改任一侧时,必须同步另一侧,避免 Agent 与 Bot 的行为漂移。
|
|
74
|
+
|
|
75
|
+
如果 task.md 中存在有效的 `issue_number`(非空、非 `N/A`),且 Issue 状态为 `OPEN`,则按幂等差集方式将 `status:` label 同步到目标值:
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
78
|
state=$(gh issue view {issue-number} -R "$upstream_repo" --json state --jq '.state' 2>/dev/null)
|
|
77
79
|
if [ "$state" = "OPEN" ]; then
|
|
78
|
-
gh issue view {issue-number} -R "$upstream_repo"
|
|
79
|
-
--jq '.labels[].name | select(startswith("status:"))' 2>/dev/null
|
|
80
|
-
| while IFS= read -r label; do
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if [ "$has_triage" = "true" ]; then
|
|
80
|
+
current_status_labels=$(gh issue view {issue-number} -R "$upstream_repo" \
|
|
81
|
+
--json labels --jq '.labels[].name | select(startswith("status:"))' 2>/dev/null || true)
|
|
82
|
+
printf '%s\n' "$current_status_labels" | while IFS= read -r label; do
|
|
83
|
+
[ -z "$label" ] && continue
|
|
84
|
+
if [ "$label" != "{target-status-label}" ] && [ "$has_triage" = "true" ]; then
|
|
85
|
+
gh issue edit {issue-number} -R "$upstream_repo" --remove-label "$label" 2>/dev/null || true
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
if [ "$has_triage" = "true" ] && ! printf '%s\n' "$current_status_labels" | grep -qxF "{target-status-label}"; then
|
|
87
89
|
gh issue edit {issue-number} -R "$upstream_repo" --add-label "{target-status-label}" 2>/dev/null || true
|
|
88
90
|
fi
|
|
89
91
|
fi
|
|
@@ -82,6 +82,7 @@ export function check({ taskDir, config, artifactFile }, shared) {
|
|
|
82
82
|
checkCommentContent,
|
|
83
83
|
checkTaskCommentContent,
|
|
84
84
|
checkInLabelsComputed,
|
|
85
|
+
checkPrTypeLabel,
|
|
85
86
|
checkInLabelsMatchPr,
|
|
86
87
|
checkPrAssignee,
|
|
87
88
|
checkSyncedRequirements,
|
|
@@ -251,10 +252,11 @@ function fetchRemoteData(context) {
|
|
|
251
252
|
let prMilestone;
|
|
252
253
|
let prAssignees;
|
|
253
254
|
if (((context.config.verify_in_labels_match_pr && context.hasTriage)
|
|
255
|
+
|| (context.config.verify_pr_type_label && context.hasTriage)
|
|
254
256
|
|| (context.config.verify_milestone && context.hasTriage)
|
|
255
257
|
|| (context.config.verify_pr_assignee && context.hasPush)) && context.prNumber) {
|
|
256
258
|
const prFields = [];
|
|
257
|
-
if (context.config.verify_in_labels_match_pr) {
|
|
259
|
+
if (context.config.verify_in_labels_match_pr || context.config.verify_pr_type_label) {
|
|
258
260
|
prFields.push("labels");
|
|
259
261
|
}
|
|
260
262
|
if (context.config.verify_milestone) {
|
|
@@ -280,7 +282,7 @@ function fetchRemoteData(context) {
|
|
|
280
282
|
};
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
prLabels = context.config.verify_in_labels_match_pr
|
|
285
|
+
prLabels = (context.config.verify_in_labels_match_pr || context.config.verify_pr_type_label)
|
|
284
286
|
? extractLabelNames(prResult.value?.labels)
|
|
285
287
|
: null;
|
|
286
288
|
prMilestone = context.config.verify_milestone
|
|
@@ -302,6 +304,22 @@ function fetchRemoteData(context) {
|
|
|
302
304
|
};
|
|
303
305
|
}
|
|
304
306
|
|
|
307
|
+
function mapTaskTypeToLabel(taskType) {
|
|
308
|
+
const mapping = {
|
|
309
|
+
bug: "type: bug",
|
|
310
|
+
bugfix: "type: bug",
|
|
311
|
+
feature: "type: feature",
|
|
312
|
+
enhancement: "type: enhancement",
|
|
313
|
+
refactor: "type: enhancement",
|
|
314
|
+
refactoring: "type: enhancement",
|
|
315
|
+
documentation: "type: documentation",
|
|
316
|
+
"dependency-upgrade": "type: dependency-upgrade",
|
|
317
|
+
task: "type: task"
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return mapping[taskType] || null;
|
|
321
|
+
}
|
|
322
|
+
|
|
305
323
|
function shouldFetchComments(config) {
|
|
306
324
|
return Boolean(
|
|
307
325
|
config.expected_comment_marker
|
|
@@ -481,6 +499,26 @@ function checkTaskCommentContent(context, remoteData) {
|
|
|
481
499
|
);
|
|
482
500
|
}
|
|
483
501
|
|
|
502
|
+
function checkPrTypeLabel(context, remoteData) {
|
|
503
|
+
if (!context.config.verify_pr_type_label || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
|
|
508
|
+
if (!expectedLabel) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (remoteData.prLabels.includes(expectedLabel)) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return failResult(CHECK_TYPE,
|
|
517
|
+
`Expected type label '${expectedLabel}' not found on PR #${context.prNumber}`,
|
|
518
|
+
"check_failed"
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
484
522
|
function checkInLabelsMatchPr(context, remoteData) {
|
|
485
523
|
if (!context.config.verify_in_labels_match_pr || !context.hasTriage || !context.prNumber || !remoteData.prLabels) {
|
|
486
524
|
return null;
|
|
@@ -27,7 +27,7 @@ git diff <target-branch>...HEAD
|
|
|
27
27
|
|
|
28
28
|
Read `.agents/rules/issue-pr-commands.md` before this step.
|
|
29
29
|
|
|
30
|
-
Before syncing linked Issue metadata, complete authentication and code-hosting platform detection through that rule. Keep `gh pr list` / `gh pr
|
|
30
|
+
Before syncing linked Issue metadata, complete authentication and code-hosting platform detection through that rule. Keep `gh pr list` / `gh pr create` on the current repository.
|
|
31
31
|
|
|
32
32
|
Before syncing labels, verify the standard label system:
|
|
33
33
|
|
|
@@ -52,13 +52,12 @@ Type label mapping:
|
|
|
52
52
|
|
|
53
53
|
Metadata sync order:
|
|
54
54
|
1. query Issue labels and milestone via the Issue read command in `.agents/rules/issue-pr-commands.md`
|
|
55
|
-
2.
|
|
56
|
-
3.
|
|
57
|
-
4.
|
|
58
|
-
5.
|
|
59
|
-
6. ensure the PR body contains `Closes #{issue-number}` or an equivalent closing keyword
|
|
55
|
+
2. build `{label-args}` from the mapped type label, non-`type:` / non-`status:` Issue labels, and the current Issue `in:` labels (commit already computed them, so do not recompute them here and do not write back to the Issue)
|
|
56
|
+
3. build `{milestone-arg}` by following "Phase 3: `create-pr`" in `.agents/rules/milestone-inference.md` and reusing the Issue milestone directly
|
|
57
|
+
4. pass `{label-args}` and `{milestone-arg}` atomically to `gh pr create` by using the create-PR command template and permission-degradation rules in `.agents/rules/issue-pr-commands.md`
|
|
58
|
+
5. ensure the PR body contains `Closes #{issue-number}` or an equivalent closing keyword
|
|
60
59
|
|
|
61
|
-
If those rules say to skip the direct metadata
|
|
60
|
+
If those rules say to skip the direct metadata arguments above, keep only the PR body linkage plus later comment sync.
|
|
62
61
|
|
|
63
62
|
Milestone rule:
|
|
64
63
|
- Follow "Phase 3: `create-pr`" in `.agents/rules/milestone-inference.md`
|
|
@@ -27,7 +27,7 @@ git diff <target-branch>...HEAD
|
|
|
27
27
|
|
|
28
28
|
执行前先读取 `.agents/rules/issue-pr-commands.md`。
|
|
29
29
|
|
|
30
|
-
同步关联 Issue 元数据前,先按该规则完成认证和代码托管平台检测;`gh pr list` / `gh pr
|
|
30
|
+
同步关联 Issue 元数据前,先按该规则完成认证和代码托管平台检测;`gh pr list` / `gh pr create` 仍保持作用于当前仓库。
|
|
31
31
|
|
|
32
32
|
在同步 label 之前,先确认标准 label 体系已经存在:
|
|
33
33
|
|
|
@@ -52,13 +52,12 @@ Type label 映射:
|
|
|
52
52
|
|
|
53
53
|
元数据同步顺序:
|
|
54
54
|
1. 按 `.agents/rules/issue-pr-commands.md` 的 Issue 读取命令查询关联 Issue 的 labels 和 milestone
|
|
55
|
-
2.
|
|
56
|
-
3.
|
|
57
|
-
4.
|
|
58
|
-
5.
|
|
59
|
-
6. 确保 PR 正文包含 `Closes #{issue-number}` 或等价的 closing keyword
|
|
55
|
+
2. 构建 `{label-args}`:包含映射后的 type label、非 `type:`/`status:` 的 Issue labels,以及 Issue 当前的 `in:` labels(commit 阶段已完成计算,此处不重新计算也不反向更新 Issue)
|
|
56
|
+
3. 构建 `{milestone-arg}`:按 `.agents/rules/milestone-inference.md` 的「阶段 3:`create-pr`」直接复用 Issue milestone
|
|
57
|
+
4. 按 `.agents/rules/issue-pr-commands.md` 的创建 PR 命令模板与权限降级规则,将 `{label-args}` 和 `{milestone-arg}` 原子化传入 `gh pr create`
|
|
58
|
+
5. 确保 PR 正文包含 `Closes #{issue-number}` 或等价的 closing keyword
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
如果上述规则判定应跳过直接元数据参数写入,则只保留 PR 正文中的关联信息与后续评论同步。
|
|
62
61
|
|
|
63
62
|
Milestone 规则:
|
|
64
63
|
- 按 `.agents/rules/milestone-inference.md` 的「阶段 3:`create-pr`」处理
|
|
@@ -54,7 +54,7 @@ Read `.agents/rules/release-commands.md` before this step.
|
|
|
54
54
|
- When generating release notes in Step 7, **must** follow both the historical format style and the full category list gathered in Step 3
|
|
55
55
|
- If no historical release notes exist, use the default format defined in Step 7
|
|
56
56
|
|
|
57
|
-
### 4. Collect Merged PRs
|
|
57
|
+
### 4. Collect Merged PRs and Contributors
|
|
58
58
|
|
|
59
59
|
Get the date range between tags, then query merged PRs:
|
|
60
60
|
|
|
@@ -71,6 +71,17 @@ Also collect direct commits without PRs:
|
|
|
71
71
|
git log v<prev-version>..v<version> --format="%H %s" --no-merges
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
Collect collaborative contributors from commit `Co-authored-by` trailers:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git log v<prev-version>..v<version> \
|
|
78
|
+
--no-merges \
|
|
79
|
+
--format='%(trailers:key=Co-authored-by,valueonly,unfold)' \
|
|
80
|
+
| grep -v '^$' | sort | uniq -c | sort -rn
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Each output line is `Name <email>` and `uniq -c` provides the number of commits where that identity appeared as a co-author within the range.
|
|
84
|
+
|
|
74
85
|
### 5. Collect Related Issues
|
|
75
86
|
|
|
76
87
|
From each PR body, extract linked Issues:
|
|
@@ -115,7 +126,21 @@ If no historical release notes exist, use the following default Markdown format:
|
|
|
115
126
|
1. Item format: `- [scope] Description by @author in [#N](url)`
|
|
116
127
|
2. Issue + PR: `in [#Issue](url) and [#PR](url)`
|
|
117
128
|
3. Description: Use PR title, remove `type(scope):` prefix, capitalize first letter
|
|
118
|
-
4.
|
|
129
|
+
4. **Contributor collection**:
|
|
130
|
+
- **Data sources**:
|
|
131
|
+
- PR authors from Step 4 `gh pr list --json author`
|
|
132
|
+
- Commit co-authors from Step 4 `git log ... --format='%(trailers:key=Co-authored-by,valueonly,unfold)'`
|
|
133
|
+
- **Contribution count**: `PR count + co-authored commit count` for the same identity, merged across both sources
|
|
134
|
+
- **Name -> `@login` mapping**:
|
|
135
|
+
- Raw `Co-authored-by` values are `Name <email>` and must be mapped to a GitHub `@login`
|
|
136
|
+
- Prefer email extraction: if it matches `(\d+\+)?(\S+?)@users\.noreply\.github\.com`, use the second capture group lowercased; this regex covers both `{id}+{login}@users.noreply.github.com` and `{login}@users.noreply.github.com`
|
|
137
|
+
- Otherwise use a Name heuristic: take the first token before a space and lowercase it, for example `Claude Opus 4.6 (1M context)` -> `@claude`, `Codex` -> `@codex`, `Gemini` -> `@gemini`
|
|
138
|
+
- If the login already appears in the PR author list, merge counts into that login so `Claude` and `@claude` do not become separate entries
|
|
139
|
+
- Merge all Name variants that map to the same login before counting and sorting; for example, `Claude` and `Claude Opus 4.6 (1M context)` should both collapse into `@claude`
|
|
140
|
+
- Preserve bot identities as-is, for example `dependabot[bot]`
|
|
141
|
+
- If the login still cannot be determined reliably, output `@{lowercased first Name token}` and append `<!-- TODO(reviewer): confirm GitHub login for {original Name <email>} -->` below the `Contributors` section
|
|
142
|
+
- **Sorting**: descending by contribution count, then lexicographically by login for ties
|
|
143
|
+
- **Deduplication**: use the final mapped `@login` as the key
|
|
119
144
|
5. Empty sections: Omit sections with no entries
|
|
120
145
|
|
|
121
146
|
### 8. Present and Confirm
|
|
@@ -54,7 +54,7 @@ git rev-parse v<prev-version>
|
|
|
54
54
|
- 后续步骤 7 生成发布说明时,**必须**同时参考步骤 3 的历史格式风格和完整分类清单,保持版本间的一致性
|
|
55
55
|
- 如果没有历史发布说明,则使用步骤 7 中定义的默认格式
|
|
56
56
|
|
|
57
|
-
### 4. 收集已合并的 PR
|
|
57
|
+
### 4. 收集已合并的 PR 与贡献者
|
|
58
58
|
|
|
59
59
|
获取标签之间的日期范围,然后查询已合并的 PR:
|
|
60
60
|
|
|
@@ -71,6 +71,17 @@ git log v<version> --format=%aI -1
|
|
|
71
71
|
git log v<prev-version>..v<version> --format="%H %s" --no-merges
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
从 commit `Co-authored-by` trailer 中收集协作贡献者:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git log v<prev-version>..v<version> \
|
|
78
|
+
--no-merges \
|
|
79
|
+
--format='%(trailers:key=Co-authored-by,valueonly,unfold)' \
|
|
80
|
+
| grep -v '^$' | sort | uniq -c | sort -rn
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
输出每行一个 `Name <email>`(`uniq -c` 给出该身份在范围内作为 co-author 的 commit 数)。
|
|
84
|
+
|
|
74
85
|
### 5. 收集关联 Issue
|
|
75
86
|
|
|
76
87
|
从每个 PR body 中提取关联的 Issue:
|
|
@@ -115,7 +126,21 @@ git log v<prev-version>..v<version> --format="%H %s" --no-merges
|
|
|
115
126
|
1. 条目格式:`- [scope] Description by @author in [#N](url)`
|
|
116
127
|
2. Issue + PR:`in [#Issue](url) and [#PR](url)`
|
|
117
128
|
3. 描述:使用 PR 标题,移除 `type(scope):` 前缀,首字母大写
|
|
118
|
-
4.
|
|
129
|
+
4. **贡献者搜集**:
|
|
130
|
+
- **数据源**:
|
|
131
|
+
- PR author:来自步骤 4 的 `gh pr list --json author`
|
|
132
|
+
- Commit co-authors:来自步骤 4 的 `git log ... --format='%(trailers:key=Co-authored-by,valueonly,unfold)'`
|
|
133
|
+
- **贡献数定义**:`该人的 PR 数 + 该人作为 co-author 的 commit 数`(同一身份跨来源合并计数)
|
|
134
|
+
- **Name → `@login` 映射**:
|
|
135
|
+
- `Co-authored-by` 原始格式为 `Name <email>`,需要推断对应的 GitHub `@login`
|
|
136
|
+
- 优先从 email 提取:匹配 `(\d+\+)?(\S+?)@users\.noreply\.github\.com` 时,取第二个捕获组并转为小写;该正则同时覆盖 `{id}+{login}@users.noreply.github.com` 与 `{login}@users.noreply.github.com`
|
|
137
|
+
- 否则按 Name 启发式:取首个空格前的 token 并转为小写(例如 `Claude Opus 4.6 (1M context)` → `@claude`、`Codex` → `@codex`、`Gemini` → `@gemini`)
|
|
138
|
+
- 已出现在 PR author 列表中的 login,必须按该 login 合并计数,避免把 `Claude` 和 `@claude` 拆成两个条目
|
|
139
|
+
- 同一 login 的所有 Name 变体都必须归并后再计数与排序;例如 `Claude` 与 `Claude Opus 4.6 (1M context)` 都映射到 `@claude` 时,应先合并为同一个贡献者
|
|
140
|
+
- Bot 身份保留原样(如 `dependabot[bot]`)
|
|
141
|
+
- 若仍无法可靠确定 login,则输出 `@{Name 首 token 小写}`,并在 `Contributors` 段落下追加 `<!-- TODO(reviewer): 确认 {原始 Name <email>} 的 GitHub login -->`
|
|
142
|
+
- **排序**:按贡献数降序;贡献数相同时按 login 字典序
|
|
143
|
+
- **去重**:以最终映射后的 `@login` 为键
|
|
119
144
|
5. 空部分:省略没有条目的部分
|
|
120
145
|
|
|
121
146
|
### 8. 展示并确认
|
|
@@ -56,6 +56,7 @@ const DEFAULTS = {
|
|
|
56
56
|
".claude/hooks/",
|
|
57
57
|
".gemini/commands/",
|
|
58
58
|
".github/hooks/check-version-format.sh",
|
|
59
|
+
".github/scripts/",
|
|
59
60
|
".opencode/commands/"
|
|
60
61
|
],
|
|
61
62
|
"merged": [
|
|
@@ -76,7 +77,7 @@ const DEFAULTS = {
|
|
|
76
77
|
}
|
|
77
78
|
};
|
|
78
79
|
|
|
79
|
-
const INSTALLER_VERSION = "v0.5.
|
|
80
|
+
const INSTALLER_VERSION = "v0.5.5";
|
|
80
81
|
const PACKAGE_NAME = '@fitlab-ai/agent-infra';
|
|
81
82
|
// Add a new identifier here only after shipping matching .{platform}. template variants.
|
|
82
83
|
const KNOWN_PLATFORMS = new Set(['github']);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Ensure the labels matching --prefix on an issue or PR equal the set passed via
|
|
3
|
+
# repeated --target flags (0, 1, or N labels).
|
|
4
|
+
# Algorithm must stay in sync with .agents/rules/issue-sync.md.
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
printf 'Usage: %s --repo <owner/repo> (--issue <number> | --pr <number>) --prefix <prefix> [--target <label> ...]\n' "$0" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
append_target() {
|
|
14
|
+
if [ -n "$targets" ]; then
|
|
15
|
+
targets=$(printf '%s\n%s' "$targets" "$1")
|
|
16
|
+
else
|
|
17
|
+
targets=$1
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
repo=""
|
|
22
|
+
number=""
|
|
23
|
+
kind=""
|
|
24
|
+
prefix=""
|
|
25
|
+
targets=""
|
|
26
|
+
|
|
27
|
+
while [ $# -gt 0 ]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--repo)
|
|
30
|
+
[ $# -ge 2 ] || usage
|
|
31
|
+
repo=$2
|
|
32
|
+
shift 2
|
|
33
|
+
;;
|
|
34
|
+
--issue)
|
|
35
|
+
[ $# -ge 2 ] || usage
|
|
36
|
+
[ -z "$kind" ] || usage
|
|
37
|
+
kind="issue"
|
|
38
|
+
number=$2
|
|
39
|
+
shift 2
|
|
40
|
+
;;
|
|
41
|
+
--pr)
|
|
42
|
+
[ $# -ge 2 ] || usage
|
|
43
|
+
[ -z "$kind" ] || usage
|
|
44
|
+
kind="pr"
|
|
45
|
+
number=$2
|
|
46
|
+
shift 2
|
|
47
|
+
;;
|
|
48
|
+
--prefix)
|
|
49
|
+
[ $# -ge 2 ] || usage
|
|
50
|
+
prefix=$2
|
|
51
|
+
shift 2
|
|
52
|
+
;;
|
|
53
|
+
--target)
|
|
54
|
+
[ $# -ge 2 ] || usage
|
|
55
|
+
append_target "$2"
|
|
56
|
+
shift 2
|
|
57
|
+
;;
|
|
58
|
+
*)
|
|
59
|
+
printf 'Unknown argument: %s\n' "$1" >&2
|
|
60
|
+
usage
|
|
61
|
+
;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
[ -n "$repo" ] || usage
|
|
66
|
+
[ -n "$number" ] || usage
|
|
67
|
+
[ -n "$kind" ] || usage
|
|
68
|
+
[ -n "$prefix" ] || usage
|
|
69
|
+
|
|
70
|
+
while IFS= read -r label; do
|
|
71
|
+
[ -z "$label" ] && continue
|
|
72
|
+
case "$label" in
|
|
73
|
+
"$prefix"*) ;;
|
|
74
|
+
*)
|
|
75
|
+
printf 'Target "%s" must start with prefix "%s"\n' "$label" "$prefix" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
done <<EOF
|
|
80
|
+
$targets
|
|
81
|
+
EOF
|
|
82
|
+
|
|
83
|
+
current_labels=$(gh "$kind" view "$number" \
|
|
84
|
+
--repo "$repo" \
|
|
85
|
+
--json labels --jq ".labels[].name | select(startswith(\"$prefix\"))" \
|
|
86
|
+
2>/dev/null || true)
|
|
87
|
+
|
|
88
|
+
while IFS= read -r label; do
|
|
89
|
+
[ -z "$label" ] && continue
|
|
90
|
+
if ! printf '%s\n' "$targets" | grep -qxF "$label"; then
|
|
91
|
+
gh "$kind" edit "$number" \
|
|
92
|
+
--repo "$repo" \
|
|
93
|
+
--remove-label "$label" \
|
|
94
|
+
2>/dev/null || true
|
|
95
|
+
fi
|
|
96
|
+
done <<EOF
|
|
97
|
+
$current_labels
|
|
98
|
+
EOF
|
|
99
|
+
|
|
100
|
+
while IFS= read -r label; do
|
|
101
|
+
[ -z "$label" ] && continue
|
|
102
|
+
if ! printf '%s\n' "$current_labels" | grep -qxF "$label"; then
|
|
103
|
+
gh "$kind" edit "$number" \
|
|
104
|
+
--repo "$repo" \
|
|
105
|
+
--add-label "$label" \
|
|
106
|
+
2>/dev/null || true
|
|
107
|
+
fi
|
|
108
|
+
done <<EOF
|
|
109
|
+
$targets
|
|
110
|
+
EOF
|
|
@@ -54,6 +54,13 @@ jobs:
|
|
|
54
54
|
printf 'type=%s\n' "$type" >> "$GITHUB_OUTPUT"
|
|
55
55
|
printf 'milestone=%s\n' "$milestone" >> "$GITHUB_OUTPUT"
|
|
56
56
|
|
|
57
|
+
- name: Checkout shared scripts
|
|
58
|
+
if: steps.metadata.outputs.is_task_comment == 'true' && steps.metadata.outputs.type != ''
|
|
59
|
+
uses: actions/checkout@v4
|
|
60
|
+
with:
|
|
61
|
+
sparse-checkout: .github/scripts
|
|
62
|
+
sparse-checkout-cone-mode: false
|
|
63
|
+
|
|
57
64
|
- name: Sync type label
|
|
58
65
|
if: steps.metadata.outputs.is_task_comment == 'true' && steps.metadata.outputs.type != ''
|
|
59
66
|
env:
|
|
@@ -72,27 +79,11 @@ jobs:
|
|
|
72
79
|
esac
|
|
73
80
|
|
|
74
81
|
if [ -n "$TYPE_LABEL" ]; then
|
|
75
|
-
|
|
82
|
+
.github/scripts/sync-labels-to-set.sh \
|
|
76
83
|
--repo "$GITHUB_REPOSITORY" \
|
|
77
|
-
--
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
printf '%s\n' "$current_type_labels" | while IFS= read -r label; do
|
|
81
|
-
[ -z "$label" ] && continue
|
|
82
|
-
if [ "$label" != "$TYPE_LABEL" ]; then
|
|
83
|
-
gh issue edit "$ISSUE_NUMBER" \
|
|
84
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
85
|
-
--remove-label "$label" \
|
|
86
|
-
2>/dev/null || true
|
|
87
|
-
fi
|
|
88
|
-
done || true
|
|
89
|
-
|
|
90
|
-
if ! printf '%s\n' "$current_type_labels" | grep -qxF "$TYPE_LABEL"; then
|
|
91
|
-
gh issue edit "$ISSUE_NUMBER" \
|
|
92
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
93
|
-
--add-label "$TYPE_LABEL" \
|
|
94
|
-
2>/dev/null || true
|
|
95
|
-
fi
|
|
84
|
+
--issue "$ISSUE_NUMBER" \
|
|
85
|
+
--prefix "type:" \
|
|
86
|
+
--target "$TYPE_LABEL"
|
|
96
87
|
fi
|
|
97
88
|
|
|
98
89
|
- name: Sync milestone
|
|
@@ -37,28 +37,19 @@ jobs:
|
|
|
37
37
|
| map("in: " + .key)
|
|
38
38
|
| .[]?')
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
set -- \
|
|
41
41
|
--repo "$GITHUB_REPOSITORY" \
|
|
42
|
-
--
|
|
43
|
-
|
|
42
|
+
--pr "$PR_NUMBER" \
|
|
43
|
+
--prefix "in: "
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
while IFS= read -r label; do
|
|
46
46
|
[ -z "$label" ] && continue
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
47
|
+
set -- "$@" --target "$label"
|
|
48
|
+
done <<EOF
|
|
49
|
+
$should_labels
|
|
50
|
+
EOF
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
[ -z "$label" ] && continue
|
|
56
|
-
if ! printf '%s\n' "$should_labels" | grep -qxF "$label"; then
|
|
57
|
-
gh pr edit "$PR_NUMBER" \
|
|
58
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
59
|
-
--remove-label "$label" 2>/dev/null || true
|
|
60
|
-
fi
|
|
61
|
-
done
|
|
52
|
+
.github/scripts/sync-labels-to-set.sh "$@"
|
|
62
53
|
|
|
63
54
|
- name: Assign PR creator if unassigned
|
|
64
55
|
env:
|
|
@@ -14,6 +14,16 @@ jobs:
|
|
|
14
14
|
manage-status-labels:
|
|
15
15
|
runs-on: ubuntu-latest
|
|
16
16
|
steps:
|
|
17
|
+
- name: Checkout shared scripts
|
|
18
|
+
if: >-
|
|
19
|
+
(github.event_name == 'issues' && github.event.action == 'closed' && github.event.issue.state_reason == 'completed') ||
|
|
20
|
+
(github.event_name == 'issues' && github.event.action == 'reopened') ||
|
|
21
|
+
(github.event_name == 'pull_request_target' && github.event.action == 'closed' && github.event.pull_request.merged == true)
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
sparse-checkout: .github/scripts
|
|
25
|
+
sparse-checkout-cone-mode: false
|
|
26
|
+
|
|
17
27
|
- name: Remove status labels on issue close
|
|
18
28
|
if: >-
|
|
19
29
|
github.event_name == 'issues' &&
|
|
@@ -23,16 +33,10 @@ jobs:
|
|
|
23
33
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
24
34
|
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
25
35
|
run: |
|
|
26
|
-
|
|
36
|
+
.github/scripts/sync-labels-to-set.sh \
|
|
27
37
|
--repo "$GITHUB_REPOSITORY" \
|
|
28
|
-
--
|
|
29
|
-
|
|
30
|
-
| while IFS= read -r label; do
|
|
31
|
-
[ -z "$label" ] && continue
|
|
32
|
-
gh issue edit "$ISSUE_NUMBER" \
|
|
33
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
34
|
-
--remove-label "$label"
|
|
35
|
-
done || true
|
|
38
|
+
--issue "$ISSUE_NUMBER" \
|
|
39
|
+
--prefix "status:"
|
|
36
40
|
|
|
37
41
|
- name: Add triage label on issue reopen
|
|
38
42
|
if: github.event_name == 'issues' && github.event.action == 'reopened'
|
|
@@ -40,23 +44,11 @@ jobs:
|
|
|
40
44
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
45
|
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
42
46
|
run: |
|
|
43
|
-
|
|
47
|
+
.github/scripts/sync-labels-to-set.sh \
|
|
44
48
|
--repo "$GITHUB_REPOSITORY" \
|
|
45
|
-
--
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
[ -z "$label" ] && continue
|
|
49
|
-
if [ "$label" != "status: waiting-for-triage" ]; then
|
|
50
|
-
gh issue edit "$ISSUE_NUMBER" \
|
|
51
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
52
|
-
--remove-label "$label"
|
|
53
|
-
fi
|
|
54
|
-
done || true
|
|
55
|
-
if ! printf '%s\n' "$current_status_labels" | grep -qxF "status: waiting-for-triage"; then
|
|
56
|
-
gh issue edit "$ISSUE_NUMBER" \
|
|
57
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
58
|
-
--add-label "status: waiting-for-triage"
|
|
59
|
-
fi
|
|
49
|
+
--issue "$ISSUE_NUMBER" \
|
|
50
|
+
--prefix "status:" \
|
|
51
|
+
--target "status: waiting-for-triage"
|
|
60
52
|
|
|
61
53
|
- name: Clean status labels on PR merge
|
|
62
54
|
if: >-
|
|
@@ -75,15 +67,9 @@ jobs:
|
|
|
75
67
|
--repo "$GITHUB_REPOSITORY" \
|
|
76
68
|
--json state --jq '.state')
|
|
77
69
|
if [ "$state" = "CLOSED" ]; then
|
|
78
|
-
|
|
70
|
+
.github/scripts/sync-labels-to-set.sh \
|
|
79
71
|
--repo "$GITHUB_REPOSITORY" \
|
|
80
|
-
--
|
|
81
|
-
|
|
82
|
-
| while IFS= read -r label; do
|
|
83
|
-
[ -z "$label" ] && continue
|
|
84
|
-
gh issue edit "$issue_number" \
|
|
85
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
86
|
-
--remove-label "$label"
|
|
87
|
-
done || true
|
|
72
|
+
--issue "$issue_number" \
|
|
73
|
+
--prefix "status:"
|
|
88
74
|
fi
|
|
89
75
|
done
|