@fitlab-ai/agent-infra 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +94 -1
  2. package/README.zh-CN.md +94 -1
  3. package/lib/defaults.json +1 -0
  4. package/lib/sandbox/commands/create.js +7 -3
  5. package/lib/sandbox/shell.js +47 -7
  6. package/lib/sandbox/tools.js +18 -14
  7. package/package.json +1 -1
  8. package/templates/.agents/README.en.md +52 -0
  9. package/templates/.agents/README.zh-CN.md +52 -0
  10. package/templates/.agents/rules/issue-pr-commands.github.en.md +10 -1
  11. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +10 -1
  12. package/templates/.agents/rules/issue-sync.github.en.md +12 -10
  13. package/templates/.agents/rules/issue-sync.github.zh-CN.md +12 -10
  14. package/templates/.agents/rules/milestone-inference.github.en.md +6 -5
  15. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +6 -5
  16. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +87 -14
  17. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  18. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  19. package/templates/.agents/skills/create-pr/config/verify.json +2 -0
  20. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +6 -7
  21. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +6 -7
  22. package/templates/.agents/skills/create-release-note/SKILL.en.md +27 -2
  23. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +27 -2
  24. package/templates/.agents/skills/implement-task/SKILL.en.md +1 -1
  25. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +1 -1
  26. package/templates/.agents/skills/import-issue/SKILL.en.md +10 -2
  27. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -2
  28. package/templates/.agents/skills/import-issue/config/verify.json +2 -1
  29. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  30. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  31. package/templates/.agents/skills/refine-task/SKILL.en.md +1 -1
  32. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +1 -1
  33. package/templates/.agents/skills/review-task/SKILL.en.md +1 -1
  34. package/templates/.agents/skills/review-task/SKILL.zh-CN.md +1 -1
  35. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +316 -1
  36. package/templates/.github/scripts/sync-labels-to-set.sh +110 -0
  37. package/templates/.github/workflows/metadata-sync.yml +11 -20
  38. package/templates/.github/workflows/pr-label.yml +10 -19
  39. package/templates/.github/workflows/status-label.yml +20 -34
@@ -70,20 +70,22 @@ Decision rules:
70
70
 
71
71
  ## Direct `status:` Label Updates
72
72
 
73
- If task.md contains a valid `issue_number` (not empty and not `N/A`) and the Issue state is `OPEN`, replace every existing `status:` label and add the target one:
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" --json labels \
79
- --jq '.labels[].name | select(startswith("status:"))' 2>/dev/null \
80
- | while IFS= read -r label; do
81
- [ -z "$label" ] && continue
82
- if [ "$has_triage" = "true" ]; then
83
- gh issue edit {issue-number} -R "$upstream_repo" --remove-label "$label" 2>/dev/null || true
84
- fi
85
- done
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
- 如果 task.md 中存在有效的 `issue_number`(非空、非 `N/A`),且 Issue 状态为 `OPEN`,则替换所有 `status:` label 并设置目标值:
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" --json labels \
79
- --jq '.labels[].name | select(startswith("status:"))' 2>/dev/null \
80
- | while IFS= read -r label; do
81
- [ -z "$label" ] && continue
82
- if [ "$has_triage" = "true" ]; then
83
- gh issue edit {issue-number} -R "$upstream_repo" --remove-label "$label" 2>/dev/null || true
84
- fi
85
- done
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
@@ -50,17 +50,18 @@ Goal: narrow the Issue milestone from a release line to a concrete version when
50
50
 
51
51
  Preconditions:
52
52
  - task.md contains a valid `issue_number`
53
- - the current Issue milestone matches the release-line format `X.Y.x`
53
+ - the current Issue milestone matches the release-line format `X.Y.x` or is `General Backlog`
54
54
 
55
55
  Sequence:
56
56
  1. Query the current Issue milestone
57
- 2. If it is not in `X.Y.x` format, treat it as already specific enough and keep it unchanged
58
- 3. If it is in `X.Y.x` format, narrow it according to branch mode:
57
+ 2. If it is `General Backlog`, re-infer the release line using Phase 1, then try to narrow it to a concrete version; if inference fails, keep `General Backlog` unchanged
58
+ 3. If it is not in `X.Y.x` format, treat it as already specific enough and keep it unchanged
59
+ 4. If it is in `X.Y.x` format, narrow it according to branch mode:
59
60
  - Trunk mode: query open concrete-version milestones on that release line (for example `0.4.4`) and choose the latest one
60
61
  - Multi-version branch mode:
61
62
  - If the task branch was created from `origin/X.Y.x`, choose the latest concrete version on that line
62
63
  - If the task branch was created from `main`, find the highest release line and choose the latest concrete version on that line
63
- 4. When a target concrete version is found, run:
64
+ 5. When a target concrete version is found, run:
64
65
 
65
66
  ```bash
66
67
  if [ "$has_triage" = "true" ]; then
@@ -68,7 +69,7 @@ if [ "$has_triage" = "true" ]; then
68
69
  fi
69
70
  ```
70
71
 
71
- 5. If `has_triage=false`, the target milestone does not exist, or the branch ancestry cannot be determined reliably, keep the original milestone unchanged
72
+ 6. If `has_triage=false`, the target milestone does not exist, or the branch ancestry cannot be determined reliably, keep the original milestone unchanged
72
73
 
73
74
  Suggested concrete-version query:
74
75
 
@@ -50,17 +50,18 @@ Milestone 设置属于 `has_triage` 权限范围;如果调用方检测到 `has
50
50
 
51
51
  前置条件:
52
52
  - `task.md` 存在有效 `issue_number`
53
- - 当前 Issue milestone 为版本线格式 `X.Y.x`
53
+ - 当前 Issue milestone 为版本线格式 `X.Y.x` 或 `General Backlog`
54
54
 
55
55
  执行顺序:
56
56
  1. 查询 Issue 当前 milestone
57
- 2. 如果 milestone 不是 `X.Y.x` 格式 -> 视为已足够具体,保持不变
58
- 3. 如果 milestone `X.Y.x` -> 按分支模式收窄:
57
+ 2. 如果 milestone `General Backlog` -> 按阶段 1 规则重新推断版本线,再尝试收窄到具体版本;如果推断失败则保持 `General Backlog` 不变
58
+ 3. 如果 milestone 不是 `X.Y.x` 格式 -> 视为已足够具体,保持不变
59
+ 4. 如果 milestone 是 `X.Y.x` -> 按分支模式收窄:
59
60
  - 主干模式:查询该版本线下 open 的具体版本 milestone(如 `0.4.4`),取最新版本
60
61
  - 多版本分支模式:
61
62
  - 当前任务分支来自 `origin/X.Y.x` release line -> 在该版本线下取最新具体版本
62
63
  - 当前任务分支来自 `main` -> 找最高版本线,再取该版本线下的最新具体版本
63
- 4. 找到目标具体版本后,执行:
64
+ 5. 找到目标具体版本后,执行:
64
65
 
65
66
  ```bash
66
67
  if [ "$has_triage" = "true" ]; then
@@ -68,7 +69,7 @@ if [ "$has_triage" = "true" ]; then
68
69
  fi
69
70
  ```
70
71
 
71
- 5. 如果 `has_triage=false`、目标 milestone 不存在,或无法可靠判断 -> 保持原 milestone 不变
72
+ 6. 如果 `has_triage=false`、目标 milestone 不存在,或无法可靠判断 -> 保持原 milestone 不变
72
73
 
73
74
  具体版本查询建议:
74
75
 
@@ -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;
@@ -897,22 +935,22 @@ function resolveUpstreamRepo(taskDir) {
897
935
  return ownerRepo;
898
936
  }
899
937
 
900
- const upstreamResult = ghText([
938
+ const repoResult = ghJson([
901
939
  "api",
902
- `repos/${ownerRepo.value}`,
903
- "--jq",
904
- "if .fork then .parent.full_name else .full_name end"
940
+ `repos/${ownerRepo.value}`
905
941
  ], taskDir);
906
942
 
907
- if (!upstreamResult.ok) {
908
- return upstreamResult;
943
+ if (!repoResult.ok) {
944
+ return repoResult;
909
945
  }
910
946
 
911
- if (isBlank(upstreamResult.value)) {
947
+ const repo = repoResult.value && typeof repoResult.value === "object" ? repoResult.value : {};
948
+ const upstreamRepo = repo.fork ? repo.parent?.full_name : repo.full_name;
949
+ if (isBlank(upstreamRepo)) {
912
950
  return { ok: false, message: "Unable to resolve upstream repository" };
913
951
  }
914
952
 
915
- return { ok: true, value: upstreamResult.value };
953
+ return { ok: true, value: upstreamRepo };
916
954
  }
917
955
 
918
956
  function resolveOwnerRepo(taskDir) {
@@ -979,11 +1017,12 @@ function ghText(args, cwd) {
979
1017
  }
980
1018
 
981
1019
  function ghCommand(args, cwd) {
982
- const result = spawnSync("gh", args, {
1020
+ const command = resolveCommand("gh");
1021
+ const result = spawnSync(command, args, commandOptions(command, {
983
1022
  cwd,
984
1023
  encoding: "utf8",
985
1024
  env: process.env
986
- });
1025
+ }));
987
1026
 
988
1027
  if (result.status !== 0) {
989
1028
  const stderr = `${result.stderr || ""}${result.stdout || ""}`.trim();
@@ -999,11 +1038,12 @@ function ghPaginatedJson(args, cwd) {
999
1038
  }
1000
1039
 
1001
1040
  function gitText(args, cwd) {
1002
- const result = spawnSync("git", args, {
1041
+ const command = resolveCommand("git");
1042
+ const result = spawnSync(command, args, commandOptions(command, {
1003
1043
  cwd,
1004
1044
  encoding: "utf8",
1005
1045
  env: process.env
1006
- });
1046
+ }));
1007
1047
 
1008
1048
  if (result.status !== 0) {
1009
1049
  const stderr = `${result.stderr || ""}${result.stdout || ""}`.trim();
@@ -1072,6 +1112,39 @@ function sleep(delayMs) {
1072
1112
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
1073
1113
  }
1074
1114
 
1115
+ function resolveCommand(cmd) {
1116
+ if (process.platform !== "win32" || path.extname(cmd)) {
1117
+ return cmd;
1118
+ }
1119
+
1120
+ const pathValue = process.env.Path || process.env.PATH || "";
1121
+ const extensions = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
1122
+ .split(";")
1123
+ .filter(Boolean);
1124
+
1125
+ for (const dir of pathValue.split(path.delimiter).filter(Boolean)) {
1126
+ for (const extension of extensions) {
1127
+ const lowerCandidate = path.join(dir, `${cmd}${extension.toLowerCase()}`);
1128
+ if (fs.existsSync(lowerCandidate)) {
1129
+ return lowerCandidate;
1130
+ }
1131
+ const upperCandidate = path.join(dir, `${cmd}${extension.toUpperCase()}`);
1132
+ if (fs.existsSync(upperCandidate)) {
1133
+ return upperCandidate;
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ return cmd;
1139
+ }
1140
+
1141
+ function commandOptions(cmd, options) {
1142
+ if (process.platform === "win32" && /\.(?:bat|cmd)$/i.test(cmd)) {
1143
+ return { ...options, shell: true };
1144
+ }
1145
+ return options;
1146
+ }
1147
+
1075
1148
  function interpolate(template, taskDir, artifactFile) {
1076
1149
  const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
1077
1150
  return template
@@ -121,8 +121,8 @@ Update `.agents/workspace/active/{task-id}/task.md`:
121
121
  If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure):
122
122
  - Read `.agents/rules/issue-sync.md` before syncing, and complete upstream repository detection plus permission detection
123
123
  - Set `status: pending-design-work` by following issue-sync.md
124
- - Publish the `{analysis-artifact}` comment
125
124
  - Create or update the `<!-- sync-issue:{task-id}:task -->` comment (follow the task.md comment sync rule in issue-sync.md)
125
+ - Publish the `{analysis-artifact}` comment
126
126
 
127
127
  ### 7. Verification Gate
128
128
 
@@ -121,8 +121,8 @@ date "+%Y-%m-%d %H:%M:%S%:z"
121
121
  如果 task.md 中存在有效的 `issue_number`,执行以下同步操作(任一失败则跳过并继续):
122
122
  - 执行前先读取 `.agents/rules/issue-sync.md`,完成 upstream 仓库检测和权限检测
123
123
  - 按 issue-sync.md 设置 `status: pending-design-work`
124
- - 发布 `{analysis-artifact}` 评论
125
124
  - 创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论(按 issue-sync.md 的 task.md 评论同步规则)
125
+ - 发布 `{analysis-artifact}` 评论
126
126
 
127
127
  ### 7. 完成校验
128
128
 
@@ -21,6 +21,8 @@
21
21
  "when": "issue_number_exists",
22
22
  "expected_pr_comment_marker": "<!-- sync-pr:{task-id}:summary -->",
23
23
  "verify_in_labels_match_pr": true,
24
+ "verify_pr_type_label": true,
25
+ "verify_pr_assignee": true,
24
26
  "verify_milestone": true
25
27
  }
26
28
  }
@@ -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 edit` on the current repository.
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. handle the mapped type label via the PR update command and permission-degradation rules in `.agents/rules/issue-pr-commands.md`
56
- 3. handle inheritance of non-`type:` and non-`status:` Issue labels via repeated PR update commands and the same permission-degradation rules
57
- 4. copy the current Issue `in:` labels to the PR (commit already computed them, so do not recompute them here and do not write back to the Issue)
58
- 5. handle the milestone by following "Phase 3: `create-pr`" in `.agents/rules/milestone-inference.md`, including its permission rules, and reuse the Issue milestone directly
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 writes above, keep only the PR body linkage plus later comment sync.
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 edit` 仍保持作用于当前仓库。
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. `.agents/rules/issue-pr-commands.md` 的 PR 更新命令和权限降级规则处理映射后的 type label
56
- 3. 按同一规则的 PR 更新命令和权限降级规则处理非 `type:`、非 `status:` Issue labels 继承
57
- 4. Issue 当前的 `in:` labels 复制到 PR(commit 阶段已完成计算,此处不重新计算也不反向更新 Issue)
58
- 5. `.agents/rules/milestone-inference.md` 的「阶段 3:`create-pr`」及其权限规则处理 milestone,直接复用 Issue milestone
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
- 如果上述规则判定应跳过直接元数据写入,则只保留 PR 正文中的关联信息与后续评论同步。
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. Contributors: Deduplicated, sorted by contribution count (descending)
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. 展示并确认
@@ -95,8 +95,8 @@ Update `.agents/workspace/active/{task-id}/task.md`:
95
95
 
96
96
  If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure; read `.agents/rules/issue-sync.md` first and complete upstream repository detection plus permission detection):
97
97
  - Set `status: in-progress` by following issue-sync.md
98
- - Publish the `{implementation-artifact}` comment
99
98
  - Create or update the `<!-- sync-issue:{task-id}:task -->` comment (follow the task.md comment sync rule in issue-sync.md)
99
+ - Publish the `{implementation-artifact}` comment
100
100
 
101
101
  ### 10. Verification Gate
102
102
 
@@ -95,8 +95,8 @@ date "+%Y-%m-%d %H:%M:%S%:z"
95
95
 
96
96
  如果 task.md 中存在有效的 `issue_number`,执行以下同步操作(任一失败则跳过并继续;执行前先读取 `.agents/rules/issue-sync.md`,完成 upstream 仓库检测和权限检测):
97
97
  - 按 issue-sync.md 设置 `status: in-progress`
98
- - 发布 `{implementation-artifact}` 评论
99
98
  - 创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论(按 issue-sync.md 的 task.md 评论同步规则)
99
+ - 发布 `{implementation-artifact}` 评论
100
100
 
101
101
  ### 10. 完成校验
102
102
 
@@ -74,7 +74,14 @@ Update `.agents/workspace/active/{task-id}/task.md`:
74
74
 
75
75
  If task.md contains a valid `issue_number`, use the Issue update command from `.agents/rules/issue-pr-commands.md` to add the current executor as an assignee. The behavioral boundary still follows `.agents/rules/issue-sync.md`.
76
76
 
77
- ### 6. Verification Gate
77
+ ### 6. Sync to the Issue
78
+
79
+ If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure):
80
+ - Read `.agents/rules/issue-sync.md` before syncing, and complete upstream repository detection plus permission detection
81
+ - Check the Issue's current milestone; if it is unset, read `.agents/rules/milestone-inference.md` and infer plus set the milestone using "Stage 1: `create-issue`". If `has_triage=false` or the inference is uncertain, skip and continue
82
+ - Create or update the `<!-- sync-issue:{task-id}:task -->` comment (follow the task.md comment sync rule in issue-sync.md)
83
+
84
+ ### 7. Verification Gate
78
85
 
79
86
  Run the verification gate to confirm the task artifact and sync state are valid:
80
87
 
@@ -89,7 +96,7 @@ Handle the result as follows:
89
96
 
90
97
  Keep the gate output in your reply as fresh evidence. Do not claim completion without output from this run.
91
98
 
92
- ### 7. Inform User
99
+ ### 8. Inform User
93
100
 
94
101
  > Execute this step only after the verification gate passes.
95
102
 
@@ -119,6 +126,7 @@ Next step - run requirements analysis:
119
126
  - [ ] Updated `current_step` to requirement-analysis
120
127
  - [ ] Updated `updated_at` to the current time
121
128
  - [ ] Appended an Activity Log entry to task.md
129
+ - [ ] Synced the task comment to the Issue
122
130
  - [ ] Informed the user of the next step (must include all TUI command formats; do not filter)
123
131
  - [ ] **Did not modify any business code**
124
132
 
@@ -74,7 +74,14 @@ date "+%Y-%m-%d %H:%M:%S%:z"
74
74
 
75
75
  如果 task.md 中存在有效的 `issue_number`,按 `.agents/rules/issue-pr-commands.md` 的 Issue 更新命令为当前执行者添加 assignee;Assignee 同步的边界仍遵循 `.agents/rules/issue-sync.md`。
76
76
 
77
- ### 6. 完成校验
77
+ ### 6. 同步到 Issue
78
+
79
+ 如果 task.md 中存在有效的 `issue_number`,执行以下同步操作(任一失败则跳过并继续):
80
+ - 执行前先读取 `.agents/rules/issue-sync.md`,完成 upstream 仓库检测和权限检测
81
+ - 检查 Issue 当前 milestone;如果未设置,先读取 `.agents/rules/milestone-inference.md`,按其中的「阶段 1:`create-issue`」规则推断并设置 milestone;如果 `has_triage=false` 或推断不确定,跳过并继续
82
+ - 创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论(按 issue-sync.md 的 task.md 评论同步规则)
83
+
84
+ ### 7. 完成校验
78
85
 
79
86
  运行完成校验,确认任务产物和同步状态符合规范:
80
87
 
@@ -89,7 +96,7 @@ node .agents/scripts/validate-artifact.js gate import-issue .agents/workspace/ac
89
96
 
90
97
  将校验输出保留在回复中作为当次验证输出。没有当次校验输出,不得声明完成。
91
98
 
92
- ### 7. 告知用户
99
+ ### 8. 告知用户
93
100
 
94
101
  > 仅在校验通过后执行本步骤。
95
102
 
@@ -119,6 +126,7 @@ Issue #{number} 已导入。
119
126
  - [ ] 更新了 `current_step` 为 requirement-analysis
120
127
  - [ ] 更新了 `updated_at` 为当前时间
121
128
  - [ ] 追加了 Activity Log 条目到 task.md
129
+ - [ ] 同步了 task 评论到 Issue
122
130
  - [ ] 告知了用户下一步(必须展示所有 TUI 的命令格式,不要筛选)
123
131
  - [ ] **没有修改任何业务代码**
124
132
 
@@ -22,7 +22,8 @@
22
22
  },
23
23
  "platform-sync": {
24
24
  "when": "issue_number_exists",
25
- "issue_must_exist": true
25
+ "issue_must_exist": true,
26
+ "verify_task_comment_content": true
26
27
  }
27
28
  }
28
29
  }
@@ -99,8 +99,8 @@ Update `.agents/workspace/active/{task-id}/task.md`:
99
99
  If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure):
100
100
  - Read `.agents/rules/issue-sync.md` before syncing, and complete upstream repository detection plus permission detection
101
101
  - Set `status: pending-design-work` by following issue-sync.md
102
- - Publish the `{plan-artifact}` comment
103
102
  - Create or update the `<!-- sync-issue:{task-id}:task -->` comment (follow the task.md comment sync rule in issue-sync.md)
103
+ - Publish the `{plan-artifact}` comment
104
104
 
105
105
  ### 8. Verification Gate
106
106
 
@@ -99,8 +99,8 @@ date "+%Y-%m-%d %H:%M:%S%:z"
99
99
  如果 task.md 中存在有效的 `issue_number`,执行以下同步操作(任一失败则跳过并继续):
100
100
  - 执行前先读取 `.agents/rules/issue-sync.md`,完成 upstream 仓库检测和权限检测
101
101
  - 按 issue-sync.md 设置 `status: pending-design-work`
102
- - 发布 `{plan-artifact}` 评论
103
102
  - 创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论(按 issue-sync.md 的 task.md 评论同步规则)
103
+ - 发布 `{plan-artifact}` 评论
104
104
 
105
105
  ### 8. 完成校验
106
106
 
@@ -62,8 +62,8 @@ Update task.md:
62
62
  If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure):
63
63
  - Read `.agents/rules/issue-sync.md` before syncing, and complete upstream repository detection plus permission detection
64
64
  - Set `status: in-progress` by following issue-sync.md
65
- - Publish the `{refinement-artifact}` comment
66
65
  - Create or update the `<!-- sync-issue:{task-id}:task -->` comment (follow the task.md comment sync rule in issue-sync.md)
66
+ - Publish the `{refinement-artifact}` comment
67
67
 
68
68
  ### 7. Verification Gate
69
69