@fitlab-ai/agent-infra 0.5.2 → 0.5.4

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 (85) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +3 -3
  3. package/lib/merge.js +22 -7
  4. package/lib/sandbox/commands/rm.js +1 -1
  5. package/lib/sandbox/runtimes/base.dockerfile +17 -1
  6. package/package.json +1 -1
  7. package/templates/.agents/rules/issue-pr-commands.github.en.md +25 -9
  8. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +25 -9
  9. package/templates/.agents/rules/issue-sync.github.en.md +111 -23
  10. package/templates/.agents/rules/issue-sync.github.zh-CN.md +105 -17
  11. package/templates/.agents/rules/milestone-inference.github.en.md +13 -6
  12. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +13 -6
  13. package/templates/.agents/rules/pr-sync.github.en.md +3 -1
  14. package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -1
  15. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +1080 -0
  16. package/templates/.agents/scripts/validate-artifact.js +54 -805
  17. package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -4
  18. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -4
  19. package/templates/.agents/skills/analyze-task/config/verify.json +1 -1
  20. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
  21. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  22. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  23. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  24. package/templates/.agents/skills/cancel-task/SKILL.en.md +18 -18
  25. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +18 -18
  26. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  27. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  28. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  29. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  30. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  31. package/templates/.agents/skills/commit/SKILL.en.md +15 -3
  32. package/templates/.agents/skills/commit/SKILL.zh-CN.md +15 -3
  33. package/templates/.agents/skills/commit/config/verify.json +2 -1
  34. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +23 -0
  35. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +23 -0
  36. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
  37. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
  38. package/templates/.agents/skills/complete-task/SKILL.en.md +13 -13
  39. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +13 -13
  40. package/templates/.agents/skills/complete-task/config/verify.json +1 -1
  41. package/templates/.agents/skills/create-issue/SKILL.en.md +4 -2
  42. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +4 -2
  43. package/templates/.agents/skills/create-issue/config/verify.json +1 -1
  44. package/templates/.agents/skills/create-issue/reference/label-and-type.en.md +6 -1
  45. package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +6 -1
  46. package/templates/.agents/skills/create-pr/SKILL.en.md +5 -5
  47. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -5
  48. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  49. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +9 -5
  50. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +9 -5
  51. package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
  52. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
  53. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  54. package/templates/.agents/skills/implement-task/SKILL.en.md +6 -6
  55. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +6 -6
  56. package/templates/.agents/skills/implement-task/config/verify.json +1 -2
  57. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  58. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
  59. package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
  60. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  61. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
  62. package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
  63. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  64. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  65. package/templates/.agents/skills/import-issue/config/verify.json +1 -1
  66. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  67. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  68. package/templates/.agents/skills/plan-task/config/verify.json +1 -1
  69. package/templates/.agents/skills/refine-task/SKILL.en.md +4 -6
  70. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +4 -6
  71. package/templates/.agents/skills/refine-task/config/verify.json +1 -2
  72. package/templates/.agents/skills/refine-title/SKILL.en.md +5 -1
  73. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +5 -1
  74. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  75. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  76. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  77. package/templates/.agents/skills/review-task/SKILL.en.md +4 -4
  78. package/templates/.agents/skills/review-task/SKILL.zh-CN.md +4 -4
  79. package/templates/.agents/skills/review-task/config/verify.json +1 -1
  80. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +1 -1
  81. package/templates/.agents/templates/task.en.md +3 -3
  82. package/templates/.agents/templates/task.zh-CN.md +3 -3
  83. package/templates/.github/workflows/metadata-sync.yml +127 -0
  84. package/templates/.github/workflows/pr-label.yml +75 -0
  85. package/templates/.github/workflows/status-label.yml +12 -8
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">Agent Infra</h1>
6
6
 
7
7
  <p align="center">
8
- The missing collaboration layer for AI coding agents — unified skills and workflows for Claude Code, Codex, Gemini CLI, and OpenCode.
8
+ Collaboration infrastructure for AI coding agents — skills, workflows, and sandboxes for Claude Code, Codex, Gemini CLI, and OpenCode.
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -29,7 +29,7 @@
29
29
 
30
30
  Teams increasingly mix Claude Code, Codex, Gemini CLI, OpenCode, and other AI TUIs in the same repository, but each tool tends to introduce its own commands, prompts, and local conventions. Without a shared layer, the result is fragmented workflows, duplicated setup, and task history that is difficult to audit.
31
31
 
32
- agent-infra standardizes that collaboration surface. It gives every supported AI TUI the same task lifecycle, the same skill vocabulary, the same project governance files, and the same upgrade path, so teams can switch tools without rebuilding process from scratch.
32
+ agent-infra standardizes that shared infrastructure. It gives every supported AI TUI the same task lifecycle, the same skill vocabulary, the same project governance files, isolated development sandboxes, and the same upgrade path, so teams can switch tools without rebuilding process from scratch.
33
33
 
34
34
  <a id="see-it-in-action"></a>
35
35
 
@@ -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.2",
413
+ "templateVersion": "v0.5.4",
414
414
  "files": {
415
415
  "managed": [
416
416
  ".agents/workspace/README.md",
package/README.zh-CN.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">Agent Infra</h1>
6
6
 
7
7
  <p align="center">
8
- AI 编程代理之间缺失的协作层 —— 为 Claude Code、Codex、Gemini CLI、OpenCode 提供统一的 skills 和工作流。
8
+ AI 编程代理的协作基础设施 —— 为 Claude Code、Codex、Gemini CLI、OpenCode 提供 skills、工作流和沙箱。
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -29,7 +29,7 @@
29
29
 
30
30
  越来越多的团队会在同一个仓库里混用 Claude Code、Codex、Gemini CLI、OpenCode 等 AI TUI,但每个工具往往都会带来自己的命令体系、提示词习惯和本地约定。缺少共享层时,结果通常是工作流割裂、初始化重复、任务历史难以追踪。
31
31
 
32
- agent-infra 的目标就是把这层协作面标准化。它为所有支持的 AI TUI 提供统一的任务生命周期、统一的 skill 词汇、统一的项目治理文件以及统一的升级路径,让团队切换工具时不必重新发明流程。
32
+ agent-infra 的目标就是把这层共享基础设施标准化。它为所有支持的 AI TUI 提供统一的任务生命周期、统一的 skill 词汇、统一的项目治理文件、隔离的开发沙箱以及统一的升级路径,让团队切换工具时不必重新发明流程。
33
33
 
34
34
  <a id="see-it-in-action"></a>
35
35
 
@@ -410,7 +410,7 @@ import-issue #42 从 GitHub Issue 导入任务
410
410
  "project": "my-project",
411
411
  "org": "my-org",
412
412
  "language": "en",
413
- "templateVersion": "v0.5.2",
413
+ "templateVersion": "v0.5.4",
414
414
  "files": {
415
415
  "managed": [
416
416
  ".agents/workspace/README.md",
package/lib/merge.js CHANGED
@@ -404,15 +404,22 @@ function removeManifestFiles(rootDir) {
404
404
  }
405
405
 
406
406
  function formatTimestamp(date) {
407
+ const pad = (value) => String(value).padStart(2, '0');
408
+ const offsetMinutes = -date.getTimezoneOffset();
409
+ const sign = offsetMinutes >= 0 ? '+' : '-';
410
+ const absoluteOffsetMinutes = Math.abs(offsetMinutes);
411
+ const offsetHours = Math.floor(absoluteOffsetMinutes / 60);
412
+ const offsetRemainderMinutes = absoluteOffsetMinutes % 60;
413
+
407
414
  return [
408
415
  date.getFullYear(),
409
- String(date.getMonth() + 1).padStart(2, '0'),
410
- String(date.getDate()).padStart(2, '0')
416
+ pad(date.getMonth() + 1),
417
+ pad(date.getDate())
411
418
  ].join('-') + ' ' + [
412
- String(date.getHours()).padStart(2, '0'),
413
- String(date.getMinutes()).padStart(2, '0'),
414
- String(date.getSeconds()).padStart(2, '0')
415
- ].join(':');
419
+ pad(date.getHours()),
420
+ pad(date.getMinutes()),
421
+ pad(date.getSeconds())
422
+ ].join(':') + `${sign}${pad(offsetHours)}:${pad(offsetRemainderMinutes)}`;
416
423
  }
417
424
 
418
425
  function formatBackupTimestamp(date) {
@@ -485,7 +492,15 @@ function getTaskTimestamp(taskDir) {
485
492
  }
486
493
 
487
494
  function compareTimestamps(left, right) {
488
- return left.value.localeCompare(right.value);
495
+ const normalizeTimestamp = (timestamp) => (timestamp.includes('T') ? timestamp : timestamp.replace(' ', 'T'));
496
+ const leftMs = Date.parse(normalizeTimestamp(left.value));
497
+ const rightMs = Date.parse(normalizeTimestamp(right.value));
498
+
499
+ if (Number.isNaN(leftMs) || Number.isNaN(rightMs)) {
500
+ return left.value.localeCompare(right.value);
501
+ }
502
+
503
+ return leftMs - rightMs;
489
504
  }
490
505
 
491
506
  function scanWorkspaceSection(rootDir, sectionName) {
@@ -87,7 +87,7 @@ async function rmOne(config, tools, branch) {
87
87
 
88
88
  const shouldDeleteBranch = await p.confirm({
89
89
  message: `Also delete local branch '${effectiveBranch}'?`,
90
- initialValue: false
90
+ initialValue: true
91
91
  });
92
92
 
93
93
  if (!p.isCancel(shouldDeleteBranch) && shouldDeleteBranch) {
@@ -11,8 +11,10 @@ RUN (groupadd -g ${HOST_GID} devuser || true) && \
11
11
  useradd -u ${HOST_UID} -g ${HOST_GID} -m -s /bin/bash devuser
12
12
 
13
13
  RUN apt-get update && apt-get install -y \
14
- curl wget git vim tmux file \
14
+ curl wget git vim file \
15
15
  build-essential ca-certificates gnupg lsb-release \
16
+ libevent-core-2.1-7 libncursesw6 libtinfo6 \
17
+ pkg-config bison libevent-dev libncurses-dev \
16
18
  locales \
17
19
  && locale-gen en_US.UTF-8 \
18
20
  && (curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
@@ -20,6 +22,19 @@ RUN apt-get update && apt-get install -y \
20
22
  && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
21
23
  > /etc/apt/sources.list.d/github-cli.list \
22
24
  && apt-get update && apt-get install -y gh \
25
+ && TMUX_VERSION=3.6a \
26
+ && wget -qO /tmp/tmux.tar.gz \
27
+ "https://github.com/tmux/tmux/releases/download/${TMUX_VERSION}/tmux-${TMUX_VERSION}.tar.gz" \
28
+ && tar xzf /tmp/tmux.tar.gz -C /tmp \
29
+ && cd /tmp/tmux-${TMUX_VERSION} \
30
+ && ./configure --prefix=/usr/local \
31
+ && make -j"$(nproc)" \
32
+ && make install \
33
+ && cd / \
34
+ && rm -rf /tmp/tmux.tar.gz /tmp/tmux-${TMUX_VERSION} \
35
+ && apt-get purge -y pkg-config bison libevent-dev libncurses-dev \
36
+ && apt-get autoremove -y \
37
+ && apt-get clean \
23
38
  && rm -rf /var/lib/apt/lists/*
24
39
 
25
40
  # Enable extended keys in CSI u format so Shift+Enter and other modified
@@ -30,6 +45,7 @@ RUN printf '%s\n' \
30
45
  'set -g extended-keys-format csi-u' \
31
46
  "set -as terminal-features 'xterm*:extkeys'" \
32
47
  "set -ga update-environment 'TERM_PROGRAM TERM_PROGRAM_VERSION LC_TERMINAL LC_TERMINAL_VERSION'" \
48
+ 'set -g mouse on' \
33
49
  > /etc/tmux.conf
34
50
 
35
51
  ENV LANG=en_US.UTF-8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Gemini CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,50 +13,65 @@ gh repo view --json nameWithOwner
13
13
 
14
14
  If either command fails, stop or degrade according to the calling skill.
15
15
 
16
+ ## Upstream Repository and Permission Detection
17
+
18
+ Before any later `gh issue` or `gh api "repos/..."` call, follow `.agents/rules/issue-sync.md` to resolve `upstream_repo`, `has_triage`, and `has_push`.
19
+
20
+ - every later `gh issue` command must use `-R "$upstream_repo"`
21
+ - every later repository-scoped `gh api` command must use `"repos/$upstream_repo/..."`
22
+ - keep `gh pr *` commands on the current repository without adding `-R`
23
+ - keep organization-scoped commands such as `gh api "orgs/{owner}/..."` unchanged
24
+
16
25
  ## Read and Create Issues
17
26
 
18
27
  Read an Issue:
19
28
 
20
29
  ```bash
21
- gh issue view {issue-number} --json number,title,body,labels,state,milestone,url
30
+ gh issue view {issue-number} -R "$upstream_repo" --json number,title,body,labels,state,milestone,url
22
31
  ```
23
32
 
24
33
  Create an Issue:
25
34
 
26
35
  ```bash
27
- gh issue create --title "{title}" --body "{body}" --assignee @me {label-args} {milestone-arg}
36
+ gh issue create -R "$upstream_repo" --title "{title}" --body "{body}" --assignee @me {label-args} {milestone-arg}
28
37
  ```
29
38
 
30
39
  - expand `{label-args}` into repeated `--label` flags from the validated label list
40
+ - pass `{label-args}` only when `has_triage=true`; otherwise omit it and continue
31
41
  - omit all `--label` flags when nothing valid remains
42
+ - pass `{milestone-arg}` only when `has_triage=true`; otherwise omit it and continue
32
43
  - omit `{milestone-arg}` entirely when no milestone should be set
33
44
 
34
45
  Set the Issue Type:
35
46
 
36
47
  ```bash
37
48
  gh api "orgs/{owner}/issue-types" --jq '.[].name'
38
- gh api "repos/{owner}/{repo}/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
49
+ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
39
50
  ```
40
51
 
52
+ - set the Issue Type only when `has_push=true`; otherwise skip and continue
53
+
41
54
  ## Update Issues
42
55
 
43
56
  Use this shape when updating titles, labels, assignees, or milestones:
44
57
 
45
58
  ```bash
46
- gh issue edit {issue-number} {edit-args}
59
+ gh issue edit {issue-number} -R "$upstream_repo" {edit-args}
47
60
  ```
48
61
 
49
62
  Common arguments:
50
63
  - `--title "{title}"`
51
- - `--add-label "{label}"`
52
- - `--remove-label "{label}"`
64
+ - `--add-label "{label}"` (only when `has_triage=true`)
65
+ - `--remove-label "{label}"` (only when `has_triage=true`)
53
66
  - `--add-assignee @me`
54
- - `--milestone "{milestone}"`
67
+ - `--milestone "{milestone}"` (only when `has_triage=true`)
68
+
69
+ Do not pre-check assignee permissions. If the assignee command fails, silently skip it per the caller's contract.
55
70
 
56
71
  Close an Issue:
57
72
 
58
73
  ```bash
59
- gh issue close {issue-number} --reason "{reason}"
74
+ gh issue close {issue-number} -R "$upstream_repo" --reason "{reason}"
60
75
  ```
61
76
 
62
77
  ## Read Issue Comments
@@ -64,7 +79,7 @@ gh issue close {issue-number} --reason "{reason}"
64
79
  Read Issue comments or search for existing hidden markers:
65
80
 
66
81
  ```bash
67
- gh api "repos/{owner}/{repo}/issues/{issue-number}/comments" --paginate
82
+ gh api "repos/$upstream_repo/issues/{issue-number}/comments" --paginate
68
83
  ```
69
84
 
70
85
  ## Read and Create PRs
@@ -108,4 +123,5 @@ Common arguments:
108
123
 
109
124
  - read failures: stop or skip based on the calling skill
110
125
  - update failures: warn and continue when the caller marks the action as best-effort
126
+ - insufficient permission: skip direct writes according to the `has_triage` / `has_push` branch and continue
111
127
  - `@me` is resolved by `gh` CLI to the authenticated user
@@ -13,50 +13,65 @@ gh repo view --json nameWithOwner
13
13
 
14
14
  如果任一命令失败,按调用该规则的 skill 约定停止或降级。
15
15
 
16
+ ## Upstream 仓库与权限检测
17
+
18
+ 在后续任何 `gh issue` 或 `gh api "repos/..."` 操作之前,先按 `.agents/rules/issue-sync.md` 完成 `upstream_repo`、`has_triage` 和 `has_push` 检测。
19
+
20
+ - 后续所有 `gh issue` 命令统一使用 `-R "$upstream_repo"`
21
+ - 后续所有 repo 级 `gh api` 命令统一使用 `"repos/$upstream_repo/..."`
22
+ - `gh pr *` 命令保持作用于当前仓库,不额外加 `-R`
23
+ - `gh api "orgs/{owner}/..."` 这类 org 级命令保持不变
24
+
16
25
  ## Issue 读取与创建
17
26
 
18
27
  读取 Issue:
19
28
 
20
29
  ```bash
21
- gh issue view {issue-number} --json number,title,body,labels,state,milestone,url
30
+ gh issue view {issue-number} -R "$upstream_repo" --json number,title,body,labels,state,milestone,url
22
31
  ```
23
32
 
24
33
  创建 Issue:
25
34
 
26
35
  ```bash
27
- gh issue create --title "{title}" --body "{body}" --assignee @me {label-args} {milestone-arg}
36
+ gh issue create -R "$upstream_repo" --title "{title}" --body "{body}" --assignee @me {label-args} {milestone-arg}
28
37
  ```
29
38
 
30
39
  - `{label-args}` 由调用方按有效 label 列表展开为多个 `--label`
40
+ - 仅当 `has_triage=true` 时传入 `{label-args}`;否则整体省略并继续
31
41
  - 没有有效 label 时省略全部 `--label`
42
+ - 仅当 `has_triage=true` 时传入 `{milestone-arg}`;否则整体省略并继续
32
43
  - `{milestone-arg}` 为空时整体省略
33
44
 
34
45
  设置 Issue Type:
35
46
 
36
47
  ```bash
37
48
  gh api "orgs/{owner}/issue-types" --jq '.[].name'
38
- gh api "repos/{owner}/{repo}/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
49
+ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
39
50
  ```
40
51
 
52
+ - 仅当 `has_push=true` 时执行 Issue Type 设置;否则跳过并继续
53
+
41
54
  ## Issue 更新
42
55
 
43
56
  更新标题、label、assignee 或 milestone 时使用:
44
57
 
45
58
  ```bash
46
- gh issue edit {issue-number} {edit-args}
59
+ gh issue edit {issue-number} -R "$upstream_repo" {edit-args}
47
60
  ```
48
61
 
49
62
  常见参数:
50
63
  - `--title "{title}"`
51
- - `--add-label "{label}"`
52
- - `--remove-label "{label}"`
64
+ - `--add-label "{label}"`(仅当 `has_triage=true`)
65
+ - `--remove-label "{label}"`(仅当 `has_triage=true`)
53
66
  - `--add-assignee @me`
54
- - `--milestone "{milestone}"`
67
+ - `--milestone "{milestone}"`(仅当 `has_triage=true`)
68
+
69
+ Assignee 同步不做权限预判;如果命令失败,按调用方约定静默跳过。
55
70
 
56
71
  关闭 Issue:
57
72
 
58
73
  ```bash
59
- gh issue close {issue-number} --reason "{reason}"
74
+ gh issue close {issue-number} -R "$upstream_repo" --reason "{reason}"
60
75
  ```
61
76
 
62
77
  ## Issue 评论读取
@@ -64,7 +79,7 @@ gh issue close {issue-number} --reason "{reason}"
64
79
  读取 Issue 评论或按隐藏标记查找已有评论:
65
80
 
66
81
  ```bash
67
- gh api "repos/{owner}/{repo}/issues/{issue-number}/comments" --paginate
82
+ gh api "repos/$upstream_repo/issues/{issue-number}/comments" --paginate
68
83
  ```
69
84
 
70
85
  ## PR 读取与创建
@@ -108,4 +123,5 @@ gh pr edit {pr-number} {edit-args}
108
123
 
109
124
  - 读取失败:按调用方规则决定停止还是跳过
110
125
  - 更新失败:如果调用方标记为 best-effort,输出警告并继续
126
+ - 权限不足:按 `has_triage` / `has_push` 分支跳过直接写操作,不阻塞调用方
111
127
  - `@me` 由 `gh` CLI 解析为当前认证用户
@@ -2,45 +2,119 @@
2
2
 
3
3
  Read this file before a task skill updates a GitHub Issue.
4
4
 
5
+ ## Upstream Repository Detection
6
+
7
+ When an external contributor runs `gh` inside a fork, the default target is the fork instead of the upstream repository. Detect the upstream repository first and reuse `upstream_repo` for every later `gh issue` and `gh api "repos/..."` operation.
8
+
9
+ ```bash
10
+ upstream_repo=$(gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)" \
11
+ --jq 'if .fork then .parent.full_name else .full_name end' 2>/dev/null)
12
+ ```
13
+
14
+ - non-fork repository: returns the current repository `full_name`
15
+ - fork repository: returns the parent repository `full_name`
16
+ - every later `gh issue` command must use `-R "$upstream_repo"`
17
+ - every later `gh api "repos/..."` command must use `"repos/$upstream_repo/..."`
18
+
19
+ ## Permission Detection
20
+
21
+ Run one permission check against the upstream repository before any write operation. When detection fails, treat it as no permission so the workflow degrades safely.
22
+
23
+ ```bash
24
+ repo_perms=$(gh api "repos/$upstream_repo" --jq '.permissions' 2>/dev/null || echo '{}')
25
+ has_triage=$(printf '%s' "$repo_perms" | grep -q '"triage":true' 2>/dev/null && echo true || echo false)
26
+ has_push=$(printf '%s' "$repo_perms" | grep -q '"push":true' 2>/dev/null && echo true || echo false)
27
+ ```
28
+
29
+ Operation-to-permission mapping:
30
+
31
+ | Operation | Required permission | Notes |
32
+ |------|---------|------|
33
+ | add/remove labels | `has_triage` | triage is the minimum permission |
34
+ | add/remove milestones | `has_triage` | same as above |
35
+ | edit Issue body | `has_triage` | used by requirement checkbox sync |
36
+ | set Issue Type | `has_push` | requires write permission |
37
+ | set assignee | no check | skip directly when it fails |
38
+ | publish/update comments | no check | allowed for authenticated users in public repositories |
39
+
40
+ ## Degradation Rules
41
+
42
+ | Level | Operation type | With permission | Without permission |
43
+ |------|---------|--------|--------|
44
+ | silent degradation | label / milestone / Issue Type | run the `gh` command directly and also update the task comment | skip direct `gh` writes, update only the task comment, let the bot backfill |
45
+ | direct skip | assignee | run the `gh` command directly | do nothing else |
46
+ | normal execution | comments | run normally | run normally |
47
+
48
+ Key rules:
49
+
50
+ - task comment sync must continue whether write permission exists or not
51
+ - insufficient permission only affects direct Issue metadata writes and must not stop the skill
52
+ - keep the existing `2>/dev/null || true` error-tolerance pattern
53
+
54
+ ## External Contributor Locking
55
+
56
+ Maintainers (`has_triage=true`) are never blocked. External contributors (`has_triage=false`) must check whether the current task already has a `task` comment author on the Issue before they start.
57
+
58
+ ```bash
59
+ task_comment_author=$(gh api "repos/$upstream_repo/issues/{issue-number}/comments" \
60
+ --paginate --jq '[.[] | select(.body | test("<!-- sync-issue:{task-id}:task -->")) | .user.login] | first' \
61
+ 2>/dev/null || echo "")
62
+ current_user=$(gh api user --jq '.login' 2>/dev/null || echo "")
63
+ ```
64
+
65
+ Decision rules:
66
+
67
+ - no `task` comment exists: allow execution
68
+ - the `task` comment author is the current user: allow continuation
69
+ - the `task` comment author is another user: stop immediately and ask the contributor to coordinate with a maintainer before taking over
70
+
5
71
  ## Direct `status:` Label Updates
6
72
 
7
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:
8
74
 
9
75
  ```bash
10
- state=$(gh issue view {issue-number} --json state --jq '.state' 2>/dev/null)
76
+ state=$(gh issue view {issue-number} -R "$upstream_repo" --json state --jq '.state' 2>/dev/null)
11
77
  if [ "$state" = "OPEN" ]; then
12
- gh issue view {issue-number} --json labels \
78
+ gh issue view {issue-number} -R "$upstream_repo" --json labels \
13
79
  --jq '.labels[].name | select(startswith("status:"))' 2>/dev/null \
14
80
  | while IFS= read -r label; do
15
81
  [ -z "$label" ] && continue
16
- gh issue edit {issue-number} --remove-label "$label" 2>/dev/null || true
82
+ if [ "$has_triage" = "true" ]; then
83
+ gh issue edit {issue-number} -R "$upstream_repo" --remove-label "$label" 2>/dev/null || true
84
+ fi
17
85
  done
18
- gh issue edit {issue-number} --add-label "{target-status-label}" 2>/dev/null || true
86
+ if [ "$has_triage" = "true" ]; then
87
+ gh issue edit {issue-number} -R "$upstream_repo" --add-label "{target-status-label}" 2>/dev/null || true
88
+ fi
19
89
  fi
20
90
  ```
21
91
 
22
92
  Use `while IFS= read -r label` so labels like `status: in-progress` are handled line-by-line instead of being split on spaces.
23
93
 
94
+ If `has_triage=false`, skip direct label changes, update only the task comment, and let the bot backfill from the latest task metadata.
95
+
24
96
  If `gh` fails, skip and continue. Do not fail the skill.
25
97
 
26
98
  ## Assignee Sync
27
99
 
28
100
  When a skill creates or imports an Issue, automatically add the current executor as assignee:
29
101
 
30
- - `create-issue`: use `--assignee @me` in the `gh issue create` command
31
- - `import-issue`: run `gh issue edit {issue-number} --add-assignee @me 2>/dev/null || true` after import
102
+ - `create-issue`: use `--assignee @me` in `gh issue create` and include `-R "$upstream_repo"`
103
+ - `import-issue`: run `gh issue edit {issue-number} -R "$upstream_repo" --add-assignee @me 2>/dev/null || true` after import
32
104
 
33
- `@me` is resolved by `gh` CLI to the authenticated user. The operation is idempotent (adding an existing assignee is a no-op). If the command fails (e.g. insufficient permissions), skip and continue.
105
+ `@me` is resolved by `gh` CLI to the authenticated user. The operation is idempotent. If the command fails, skip it directly and do not provide a fallback path.
34
106
 
35
107
  ## `in:` Label Sync
36
108
 
109
+ > **Trigger timing**: run `in:` label sync only after code is committed (the `commit` skill). Do not run it during `implement-task` or `refine-task`. During `create-pr`, only copy the labels from the Issue to the PR without recomputing them.
110
+
37
111
  Read the `labels.in` mapping from `.agents/.airc.json`.
38
112
 
39
113
  ```bash
40
114
  git diff {base-branch}...HEAD --name-only
41
115
  ```
42
116
 
43
- `{base-branch}` is usually `main`; in PR context, use the PR's base branch.
117
+ `{base-branch}` is usually `main`; in PR context, use the PR base branch.
44
118
 
45
119
  ### When a mapping exists (precise add/remove)
46
120
 
@@ -48,15 +122,18 @@ git diff {base-branch}...HEAD --name-only
48
122
  2. Match each file against the directory prefixes in `labels.in` to compute the expected `in:` label set
49
123
  3. Query the current `in:` labels on the Issue or PR
50
124
  4. Apply the diff:
51
- - expected but missing -> `gh issue edit {issue-number} --add-label "in: {module}" 2>/dev/null || true`
52
- - present but no longer expected -> `gh issue edit {issue-number} --remove-label "in: {module}" 2>/dev/null || true`
125
+ - expected but missing: only when `has_triage=true`, run `gh issue edit {issue-number} -R "$upstream_repo" --add-label "in: {module}" 2>/dev/null || true`
126
+ - present but no longer expected: only when `has_triage=true`, run `gh issue edit {issue-number} -R "$upstream_repo" --remove-label "in: {module}" 2>/dev/null || true`
53
127
 
54
128
  ### When no mapping exists (add-only fallback)
55
129
 
56
130
  If `.airc.json` has no `labels.in` field or it is empty:
131
+
57
132
  1. query existing repository `in:` labels
58
133
  2. derive the top-level directory from each changed file
59
- 3. add matching labels only and never remove existing `in:` labels
134
+ 3. only when `has_triage=true`, add matching labels and never remove existing `in:` labels
135
+
136
+ If `has_triage=false`, skip direct `in:` label edits and keep task comment sync as the source for later automation backfill.
60
137
 
61
138
  ## Artifact Comment Publishing
62
139
 
@@ -69,7 +146,7 @@ The hidden marker must remain compatible:
69
146
  Check for an existing comment before publishing:
70
147
 
71
148
  ```bash
72
- gh api "repos/{owner}/{repo}/issues/{issue-number}/comments" \
149
+ gh api "repos/$upstream_repo/issues/{issue-number}/comments" \
73
150
  --paginate --jq '.[].body' \
74
151
  | grep -qF "<!-- sync-issue:{task-id}:{file-stem} -->"
75
152
  ```
@@ -99,20 +176,23 @@ Use this format:
99
176
  `{agent}` is the name of the AI agent currently executing the skill (for example `claude`, `codex`, or `gemini`).
100
177
 
101
178
  `summary` comments need extra handling:
179
+
102
180
  - find an existing `<!-- sync-issue:{task-id}:summary -->` comment ID first
103
181
  - create the comment when none exists
104
- - patch the existing comment in place when the body changed
182
+ - patch the existing comment in place when the body changed by using `gh api "repos/$upstream_repo/issues/comments/{comment-id}" -X PATCH -f body=...`
105
183
 
106
184
  ```bash
107
- summary_comment_id=$(gh api "repos/{owner}/{repo}/issues/{issue-number}/comments" \
185
+ summary_comment_id=$(gh api "repos/$upstream_repo/issues/{issue-number}/comments" \
108
186
  --paginate --jq '.[] | select(.body | startswith("<!-- sync-issue:{task-id}:summary -->")) | .id' \
109
187
  | head -n 1)
110
- gh api "repos/{owner}/{repo}/issues/comments/{comment-id}" -X PATCH -f body="$(cat <<'EOF'
188
+ gh api "repos/$upstream_repo/issues/comments/{comment-id}" -X PATCH -f body="$(cat <<'EOF'
111
189
  {comment-body}
112
190
  EOF
113
191
  )"
114
192
  ```
115
193
 
194
+ Comment publishing is not gated by `has_triage` or `has_push`.
195
+
116
196
  ## task.md Comment Sync
117
197
 
118
198
  Hidden marker:
@@ -124,7 +204,7 @@ Hidden marker:
124
204
  Use an idempotent update path for `task.md`:
125
205
 
126
206
  1. Read the full `task.md`
127
- 2. Wrap the YAML frontmatter (content between the `---` delimiters) inside a `<details><summary>Metadata (frontmatter)</summary>` block with a `yaml` code fence; render the remaining body as normal Markdown
207
+ 2. Wrap the YAML frontmatter (content between the `---` delimiters) inside a `<details><summary>Metadata (frontmatter)</summary>` block with a `yaml` code fence, then render the remaining body as normal Markdown
128
208
  3. Use `task` as `{file-stem}`
129
209
  4. Find an existing comment ID for the marker
130
210
  5. Create the comment when none exists
@@ -155,20 +235,23 @@ task.md comment format:
155
235
  *Generated by {agent} · Internal tracking: {task-id}*
156
236
  ```
157
237
 
158
- When restoring, extract the frontmatter from the `<details>` block and reassemble with the body to recover the original `task.md`.
238
+ When restoring, extract the frontmatter from the `<details>` block and reassemble it with the body to recover the original `task.md`.
159
239
 
160
240
  Title mapping:
241
+
161
242
  - `task` -> `Task File`
162
243
 
244
+ task comment sync always runs and is never downgraded.
245
+
163
246
  ## Backfill Rules (run before `/complete-task` archives)
164
247
 
165
248
  - Scan `task.md`, `analysis*.md`, `plan*.md`, `implementation*.md`, `review*.md`, and `refinement*.md` in the task directory
166
249
  - Check whether each `{file-stem}` was already published by its hidden marker; publish only missing artifacts
167
- - Backfill only appends missing comments; never delete or reorder existing comments
250
+ - Backfill only appends missing comments and never deletes or reorders existing comments
168
251
  - Resolve `{agent}` for backfilled comments in this order:
169
- 1. Match the artifact filename in Activity Log (for example `→ analysis.md`) and extract the executor from `by {agent}`
170
- 2. If no match is found, fall back to `assigned_to` in task.md frontmatter
171
- 3. If `assigned_to` is also unavailable, use the current backfilling agent
252
+ 1. match the artifact filename in Activity Log (for example `→ analysis.md`) and extract the executor from `by {agent}`
253
+ 2. if no match is found, fall back to `assigned_to` in task.md frontmatter
254
+ 3. if `assigned_to` is also unavailable, use the current backfilling agent
172
255
  - Derive the previous and next neighbors from Activity Log order and add this note below the title:
173
256
 
174
257
  ```markdown
@@ -178,6 +261,7 @@ Title mapping:
178
261
  - If only one neighbor exists, keep only that side of the note; if neither exists, omit the note
179
262
 
180
263
  Title mapping:
264
+
181
265
  - `task` -> `Task File`
182
266
  - `analysis` / `analysis-r{N}` -> `Requirements Analysis` / `Requirements Analysis (Round {N})`
183
267
  - `plan` / `plan-r{N}` -> `Technical Plan` / `Technical Plan (Round {N})`
@@ -186,6 +270,8 @@ Title mapping:
186
270
  - `refinement` / `refinement-r{N}` -> `Refinement Report (Round 1)` / `Refinement Report (Round {N})`
187
271
  - `summary` -> `Delivery Summary`
188
272
 
273
+ Backfilled comments are also not gated by `has_triage` or `has_push`.
274
+
189
275
  ## Requirement Checkbox Sync
190
276
 
191
277
  Extract checked `- [x]` items from the `## Requirements` section in task.md. Skip when none exist.
@@ -193,10 +279,12 @@ Extract checked `- [x]` items from the `## Requirements` section in task.md. Ski
193
279
  Read the current Issue body:
194
280
 
195
281
  ```bash
196
- gh issue view {issue-number} --json body --jq '.body'
282
+ gh issue view {issue-number} -R "$upstream_repo" --json body --jq '.body'
197
283
  ```
198
284
 
199
- Replace matching `- [ ] {text}` lines with `- [x] {text}` only when the body actually changes, then PATCH the full body with `gh api`.
285
+ Replace matching `- [ ] {text}` lines with `- [x] {text}`. Use `gh api` to PATCH the full body only when the body changed and `has_triage=true`.
286
+
287
+ If `has_triage=false`, skip the body PATCH, update only the task comment, and let the bot backfill from the latest task state.
200
288
 
201
289
  ## Shell Safety Rules
202
290