@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.
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/merge.js +22 -7
- package/lib/sandbox/commands/rm.js +1 -1
- package/lib/sandbox/runtimes/base.dockerfile +17 -1
- package/package.json +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +25 -9
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +25 -9
- package/templates/.agents/rules/issue-sync.github.en.md +111 -23
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +105 -17
- package/templates/.agents/rules/milestone-inference.github.en.md +13 -6
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +13 -6
- package/templates/.agents/rules/pr-sync.github.en.md +3 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -1
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +1080 -0
- package/templates/.agents/scripts/validate-artifact.js +54 -805
- package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/config/verify.json +1 -1
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +18 -18
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +18 -18
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/SKILL.en.md +15 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +15 -3
- package/templates/.agents/skills/commit/config/verify.json +2 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +23 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +23 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +13 -13
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +13 -13
- package/templates/.agents/skills/complete-task/config/verify.json +1 -1
- package/templates/.agents/skills/create-issue/SKILL.en.md +4 -2
- package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/create-issue/config/verify.json +1 -1
- package/templates/.agents/skills/create-issue/reference/label-and-type.en.md +6 -1
- package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +6 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +9 -5
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +9 -5
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/implement-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/implement-task/config/verify.json +1 -2
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/config/verify.json +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.json +1 -1
- package/templates/.agents/skills/refine-task/SKILL.en.md +4 -6
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +4 -6
- package/templates/.agents/skills/refine-task/config/verify.json +1 -2
- package/templates/.agents/skills/refine-title/SKILL.en.md +5 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-task/config/verify.json +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +1 -1
- package/templates/.agents/templates/task.en.md +3 -3
- package/templates/.agents/templates/task.zh-CN.md +3 -3
- package/templates/.github/workflows/metadata-sync.yml +127 -0
- package/templates/.github/workflows/pr-label.yml +75 -0
- 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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
410
|
-
|
|
416
|
+
pad(date.getMonth() + 1),
|
|
417
|
+
pad(date.getDate())
|
|
411
418
|
].join('-') + ' ' + [
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
52
|
-
- present but no longer expected
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
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.
|
|
170
|
-
2.
|
|
171
|
-
3.
|
|
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}`
|
|
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
|
|