@ranger1/dx 0.1.16 → 0.1.18
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/@opencode/agents/claude-reviewer.md +57 -0
- package/@opencode/agents/codex-reviewer.md +58 -0
- package/@opencode/agents/gemini-reviewer.md +57 -0
- package/@opencode/agents/pr-context.md +195 -0
- package/@opencode/agents/pr-fix.md +173 -0
- package/@opencode/agents/pr-precheck.md +57 -0
- package/@opencode/agents/pr-review-aggregate.md +118 -0
- package/@opencode/commands/doctor.md +258 -0
- package/@opencode/commands/git-commit-and-pr.md +276 -0
- package/@opencode/commands/git-release.md +642 -0
- package/@opencode/commands/pr-review-loop.md +74 -0
- package/bin/dx.js +31 -0
- package/lib/cli/help.js +2 -0
- package/lib/opencode-initial.js +107 -0
- package/package.json +2 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: review (Claude)
|
|
3
|
+
mode: subagent
|
|
4
|
+
model: github-copilot/claude-sonnet-4.5
|
|
5
|
+
tools:
|
|
6
|
+
write: true
|
|
7
|
+
edit: false
|
|
8
|
+
bash: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# PR Reviewer (Claude)
|
|
12
|
+
|
|
13
|
+
## 输入(prompt 必须包含)
|
|
14
|
+
|
|
15
|
+
- `PR #<number>`
|
|
16
|
+
- `round: <number>`
|
|
17
|
+
- `contextFile: <path>`
|
|
18
|
+
|
|
19
|
+
## 输出(强制)
|
|
20
|
+
|
|
21
|
+
只输出一行:
|
|
22
|
+
|
|
23
|
+
`reviewFile: <path>`
|
|
24
|
+
|
|
25
|
+
## 规则
|
|
26
|
+
|
|
27
|
+
- 默认已在 PR head 分支(可直接读工作区代码)
|
|
28
|
+
- 可用 `git`/`gh` 只读命令获取 diff/上下文
|
|
29
|
+
- 写入 reviewFile:`~/.opencode/cache/review-CLD-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
30
|
+
- findings id 必须以 `CLD-` 开头
|
|
31
|
+
|
|
32
|
+
## reviewFile 格式(强制)
|
|
33
|
+
|
|
34
|
+
```md
|
|
35
|
+
# Review (CLD)
|
|
36
|
+
|
|
37
|
+
PR: <PR_NUMBER>
|
|
38
|
+
Round: <ROUND>
|
|
39
|
+
|
|
40
|
+
## Summary
|
|
41
|
+
|
|
42
|
+
P0: <n>
|
|
43
|
+
P1: <n>
|
|
44
|
+
P2: <n>
|
|
45
|
+
P3: <n>
|
|
46
|
+
|
|
47
|
+
## Findings
|
|
48
|
+
|
|
49
|
+
- id: CLD-001
|
|
50
|
+
priority: P1
|
|
51
|
+
category: quality|performance|security|architecture
|
|
52
|
+
file: <path>
|
|
53
|
+
line: <number|null>
|
|
54
|
+
title: <short>
|
|
55
|
+
description: <text>
|
|
56
|
+
suggestion: <text>
|
|
57
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: review (Codex)
|
|
3
|
+
mode: subagent
|
|
4
|
+
model: openai/gpt-5.2-codex
|
|
5
|
+
temperature: 0.1
|
|
6
|
+
tools:
|
|
7
|
+
write: true
|
|
8
|
+
edit: false
|
|
9
|
+
bash: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# PR Reviewer (Codex)
|
|
13
|
+
|
|
14
|
+
## 输入(prompt 必须包含)
|
|
15
|
+
|
|
16
|
+
- `PR #<number>`
|
|
17
|
+
- `round: <number>`
|
|
18
|
+
- `contextFile: <path>`
|
|
19
|
+
|
|
20
|
+
## 输出(强制)
|
|
21
|
+
|
|
22
|
+
只输出一行:
|
|
23
|
+
|
|
24
|
+
`reviewFile: <path>`
|
|
25
|
+
|
|
26
|
+
## 规则
|
|
27
|
+
|
|
28
|
+
- 默认已在 PR head 分支(可直接读工作区代码)
|
|
29
|
+
- 可用 `git`/`gh` 只读命令获取 diff/上下文
|
|
30
|
+
- 写入 reviewFile:`~/.opencode/cache/review-CDX-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
31
|
+
- findings id 必须以 `CDX-` 开头
|
|
32
|
+
|
|
33
|
+
## reviewFile 格式(强制)
|
|
34
|
+
|
|
35
|
+
```md
|
|
36
|
+
# Review (CDX)
|
|
37
|
+
|
|
38
|
+
PR: <PR_NUMBER>
|
|
39
|
+
Round: <ROUND>
|
|
40
|
+
|
|
41
|
+
## Summary
|
|
42
|
+
|
|
43
|
+
P0: <n>
|
|
44
|
+
P1: <n>
|
|
45
|
+
P2: <n>
|
|
46
|
+
P3: <n>
|
|
47
|
+
|
|
48
|
+
## Findings
|
|
49
|
+
|
|
50
|
+
- id: CDX-001
|
|
51
|
+
priority: P1
|
|
52
|
+
category: quality|performance|security|architecture
|
|
53
|
+
file: <path>
|
|
54
|
+
line: <number|null>
|
|
55
|
+
title: <short>
|
|
56
|
+
description: <text>
|
|
57
|
+
suggestion: <text>
|
|
58
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: review (Gemini)
|
|
3
|
+
mode: subagent
|
|
4
|
+
model: github-copilot/gemini-3-pro-preview
|
|
5
|
+
tools:
|
|
6
|
+
write: true
|
|
7
|
+
edit: false
|
|
8
|
+
bash: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# PR Reviewer (Gemini)
|
|
12
|
+
|
|
13
|
+
## 输入(prompt 必须包含)
|
|
14
|
+
|
|
15
|
+
- `PR #<number>`
|
|
16
|
+
- `round: <number>`
|
|
17
|
+
- `contextFile: <path>`
|
|
18
|
+
|
|
19
|
+
## 输出(强制)
|
|
20
|
+
|
|
21
|
+
只输出一行:
|
|
22
|
+
|
|
23
|
+
`reviewFile: <path>`
|
|
24
|
+
|
|
25
|
+
## 规则
|
|
26
|
+
|
|
27
|
+
- 默认已在 PR head 分支(可直接读工作区代码)
|
|
28
|
+
- 可用 `git`/`gh` 只读命令获取 diff/上下文
|
|
29
|
+
- 写入 reviewFile:`~/.opencode/cache/review-GMN-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
30
|
+
- findings id 必须以 `GMN-` 开头
|
|
31
|
+
|
|
32
|
+
## reviewFile 格式(强制)
|
|
33
|
+
|
|
34
|
+
```md
|
|
35
|
+
# Review (GMN)
|
|
36
|
+
|
|
37
|
+
PR: <PR_NUMBER>
|
|
38
|
+
Round: <ROUND>
|
|
39
|
+
|
|
40
|
+
## Summary
|
|
41
|
+
|
|
42
|
+
P0: <n>
|
|
43
|
+
P1: <n>
|
|
44
|
+
P2: <n>
|
|
45
|
+
P3: <n>
|
|
46
|
+
|
|
47
|
+
## Findings
|
|
48
|
+
|
|
49
|
+
- id: GMN-001
|
|
50
|
+
priority: P1
|
|
51
|
+
category: quality|performance|security|architecture
|
|
52
|
+
file: <path>
|
|
53
|
+
line: <number|null>
|
|
54
|
+
title: <short>
|
|
55
|
+
description: <text>
|
|
56
|
+
suggestion: <text>
|
|
57
|
+
```
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: build PR context file
|
|
3
|
+
mode: subagent
|
|
4
|
+
model: openai/gpt-5.1-codex-mini
|
|
5
|
+
temperature: 0.1
|
|
6
|
+
tools:
|
|
7
|
+
write: false
|
|
8
|
+
edit: false
|
|
9
|
+
bash: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# PR Context Builder
|
|
13
|
+
|
|
14
|
+
为 PR Review Loop 构建“松散上下文文件”(Markdown),让 reviewer 读取文件而不是在 prompt 里解析大段精确 JSON。
|
|
15
|
+
|
|
16
|
+
目标:
|
|
17
|
+
|
|
18
|
+
- 生成 PR context 文件(标题/描述/labels/分支/修改文件清单/评论摘要)
|
|
19
|
+
- 默认假设已通过 pr-precheck:gh 已认证、PR 可访问、当前分支已切到 PR head 分支
|
|
20
|
+
- 把信息拼成适合大模型阅读的 Markdown(不追求严格 schema)
|
|
21
|
+
- 落盘到按 `prNumber + round + runId` 命名的文件
|
|
22
|
+
- 返回“单一 JSON 对象”,只包含文件路径等元信息
|
|
23
|
+
|
|
24
|
+
## 输入要求(强制)
|
|
25
|
+
|
|
26
|
+
调用者必须在 prompt 中明确提供:
|
|
27
|
+
|
|
28
|
+
- PR 编号(如:`PR #123` 或 `prNumber: 123`)
|
|
29
|
+
- round(如:`round: 1`;无则默认 1)
|
|
30
|
+
|
|
31
|
+
## 允许/禁止
|
|
32
|
+
|
|
33
|
+
- ✅ 允许使用 `gh` 只读获取 PR 信息与评论
|
|
34
|
+
- ✅ 允许使用 `git` 获取修改文件清单(推荐)
|
|
35
|
+
- ✅ 允许写入缓存文件到 `~/.opencode/cache/`
|
|
36
|
+
- ✅ 允许使用本地脚本(python)拼接/裁剪内容
|
|
37
|
+
- ⛔ 禁止修改业务代码(只允许写入 `~/.opencode/cache/`)
|
|
38
|
+
- ⛔ 禁止发布 GitHub 评论(不调用 `gh pr comment/review`)
|
|
39
|
+
- ⛔ 禁止 push/force push/rebase
|
|
40
|
+
|
|
41
|
+
## 输出(强制)
|
|
42
|
+
|
|
43
|
+
你必须输出“单一 JSON 对象”,且能被 `JSON.parse()` 解析。
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
type PRContextBuildResult = {
|
|
47
|
+
agent: 'pr-context'
|
|
48
|
+
prNumber: number
|
|
49
|
+
round: number
|
|
50
|
+
runId: string
|
|
51
|
+
repo: { nameWithOwner: string }
|
|
52
|
+
headOid: string
|
|
53
|
+
existingMarkerCount?: number
|
|
54
|
+
contextFile: string
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 生成规则(强制)
|
|
59
|
+
|
|
60
|
+
1. 假设已完成 pr-precheck(gh 已认证、PR 可访问、当前分支已切到 PR head 分支、工作区可用)
|
|
61
|
+
2. 获取 PR 元信息(title/body/labels/base/head oid/ref/url/isDraft)
|
|
62
|
+
3. 获取修改文件清单(优先用 git diff 与 baseRefName 对比;不包含 patch)
|
|
63
|
+
4. 获取评论:最多最近 10 条,正文截断 300 字符
|
|
64
|
+
5. 生成 runId:必须包含 prNumber 与 round;使用 `sha1(prNumber:round:headOid)` 的前 12 位(不使用时间)
|
|
65
|
+
6. 写入 `~/.opencode/cache/pr-context-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
66
|
+
7. 上下文文件内容必须包含:
|
|
67
|
+
|
|
68
|
+
- PR 基本信息(repo/pr/url/base/head/headOid/labels)
|
|
69
|
+
- Title / Body 摘要
|
|
70
|
+
- 变更文件清单(每文件 additions/deletions)
|
|
71
|
+
- 最近评论摘要
|
|
72
|
+
- 历史 marker 计数(用于提示是否已跑过 loop)
|
|
73
|
+
|
|
74
|
+
## 实现建议(bash + python)
|
|
75
|
+
|
|
76
|
+
你可以按如下思路实现,并确保最终只输出 JSON:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
set -euo pipefail
|
|
80
|
+
|
|
81
|
+
mkdir -p "$HOME/.opencode/cache"
|
|
82
|
+
|
|
83
|
+
OWNER_REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
84
|
+
|
|
85
|
+
PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$OWNER_REPO" \
|
|
86
|
+
--json number,url,title,body,isDraft,labels,baseRefName,headRefName,baseRefOid,headRefOid,comments)
|
|
87
|
+
|
|
88
|
+
BASE_REF=$(echo "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("baseRefName") or "")')
|
|
89
|
+
test -n "$BASE_REF" || BASE_REF="main"
|
|
90
|
+
|
|
91
|
+
git fetch origin "$BASE_REF" > /dev/null 2>&1 || true
|
|
92
|
+
FILES_TXT=$(git diff --name-status "origin/$BASE_REF...HEAD" 2>/dev/null || git diff --name-status "$BASE_REF...HEAD" 2>/dev/null || true)
|
|
93
|
+
|
|
94
|
+
OWNER_REPO="$OWNER_REPO" PR_NUMBER="$PR_NUMBER" ROUND="$ROUND" PR_JSON="$PR_JSON" FILES_TXT="$FILES_TXT" \
|
|
95
|
+
python3 - <<'PY'
|
|
96
|
+
import json, os, hashlib
|
|
97
|
+
|
|
98
|
+
pr_number = int(os.environ['PR_NUMBER'])
|
|
99
|
+
round_num = int(os.environ.get('ROUND') or '1')
|
|
100
|
+
owner_repo = os.environ['OWNER_REPO']
|
|
101
|
+
pr = json.loads(os.environ['PR_JSON'])
|
|
102
|
+
files_txt = os.environ.get('FILES_TXT') or ''
|
|
103
|
+
|
|
104
|
+
head_oid = pr.get('headRefOid') or ''
|
|
105
|
+
run_seed = f"{pr_number}:{round_num}:{head_oid}".encode('utf-8')
|
|
106
|
+
run_id = hashlib.sha1(run_seed).hexdigest()[:12]
|
|
107
|
+
|
|
108
|
+
def clip(s, n):
|
|
109
|
+
if s is None:
|
|
110
|
+
return ''
|
|
111
|
+
s = str(s)
|
|
112
|
+
return s if len(s) <= n else (s[:n] + '...')
|
|
113
|
+
|
|
114
|
+
labels = [l.get('name') for l in (pr.get('labels') or []) if isinstance(l, dict) and l.get('name')]
|
|
115
|
+
|
|
116
|
+
dir_path = os.path.join(os.path.expanduser('~'), '.opencode', 'cache')
|
|
117
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
118
|
+
context_file = os.path.join(dir_path, f"pr-context-pr{pr_number}-r{round_num}-{run_id}.md")
|
|
119
|
+
|
|
120
|
+
base_ref = pr.get('baseRefName') or ''
|
|
121
|
+
head_ref = pr.get('headRefName') or ''
|
|
122
|
+
url = pr.get('url') or ''
|
|
123
|
+
|
|
124
|
+
file_rows = []
|
|
125
|
+
for line in files_txt.splitlines():
|
|
126
|
+
parts = line.split('\t')
|
|
127
|
+
if not parts:
|
|
128
|
+
continue
|
|
129
|
+
status = parts[0].strip()
|
|
130
|
+
path = parts[-1].strip() if len(parts) >= 2 else ''
|
|
131
|
+
if not path:
|
|
132
|
+
continue
|
|
133
|
+
file_rows.append((status, path))
|
|
134
|
+
|
|
135
|
+
comments = pr.get('comments') or []
|
|
136
|
+
recent = comments[-10:] if isinstance(comments, list) else []
|
|
137
|
+
|
|
138
|
+
with open(context_file, 'w', encoding='utf-8', newline='\n') as fp:
|
|
139
|
+
fp.write('# PR Context\n\n')
|
|
140
|
+
fp.write(f"- Repo: {owner_repo}\n")
|
|
141
|
+
fp.write(f"- PR: #{pr_number} {url}\n")
|
|
142
|
+
fp.write(f"- Round: {round_num}\n")
|
|
143
|
+
fp.write(f"- RunId: {run_id}\n")
|
|
144
|
+
fp.write(f"- Base: {base_ref}\n")
|
|
145
|
+
fp.write(f"- Head: {head_ref}\n")
|
|
146
|
+
fp.write(f"- HeadOid: {head_oid}\n")
|
|
147
|
+
fp.write(f"- Draft: {pr.get('isDraft')}\n")
|
|
148
|
+
marker = '<!-- pr-review-loop-marker'
|
|
149
|
+
marker_count = 0
|
|
150
|
+
for c in recent:
|
|
151
|
+
if not isinstance(c, dict):
|
|
152
|
+
continue
|
|
153
|
+
body = c.get('body') or ''
|
|
154
|
+
if isinstance(body, str) and marker in body:
|
|
155
|
+
marker_count += 1
|
|
156
|
+
|
|
157
|
+
fp.write(f"- Labels: {', '.join(labels) if labels else '(none)'}\n")
|
|
158
|
+
fp.write(f"- ExistingLoopMarkers: {marker_count}\n\n")
|
|
159
|
+
|
|
160
|
+
fp.write('## Title\n\n')
|
|
161
|
+
fp.write(clip(pr.get('title') or '', 200) + '\n\n')
|
|
162
|
+
|
|
163
|
+
fp.write('## Body (excerpt)\n\n')
|
|
164
|
+
fp.write(clip(pr.get('body') or '', 2000) or '(empty)')
|
|
165
|
+
fp.write('\n\n')
|
|
166
|
+
|
|
167
|
+
fp.write(f"## Changed Files ({len(file_rows)})\n\n")
|
|
168
|
+
for (status, path) in file_rows:
|
|
169
|
+
fp.write(f"- {status} {path}\n")
|
|
170
|
+
fp.write('\n')
|
|
171
|
+
|
|
172
|
+
fp.write('## Recent Comments (excerpt)\n\n')
|
|
173
|
+
if recent:
|
|
174
|
+
for c in recent:
|
|
175
|
+
if not isinstance(c, dict):
|
|
176
|
+
continue
|
|
177
|
+
author = (c.get('author') or {}).get('login') if isinstance(c.get('author'), dict) else None
|
|
178
|
+
fp.write(f"- {author or 'unknown'}: {clip(c.get('body') or '', 300)}\n")
|
|
179
|
+
else:
|
|
180
|
+
fp.write('(none)\n')
|
|
181
|
+
|
|
182
|
+
result = {
|
|
183
|
+
'agent': 'pr-context',
|
|
184
|
+
'prNumber': pr_number,
|
|
185
|
+
'round': round_num,
|
|
186
|
+
'runId': run_id,
|
|
187
|
+
'repo': {'nameWithOwner': owner_repo},
|
|
188
|
+
'headOid': head_oid,
|
|
189
|
+
'existingMarkerCount': marker_count,
|
|
190
|
+
'contextFile': context_file,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
print(json.dumps(result, ensure_ascii=True))
|
|
194
|
+
PY
|
|
195
|
+
```
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: PR fix review
|
|
3
|
+
mode: subagent
|
|
4
|
+
model: openai/gpt-5.2-codex
|
|
5
|
+
temperature: 0.1
|
|
6
|
+
tools:
|
|
7
|
+
write: true
|
|
8
|
+
edit: true
|
|
9
|
+
bash: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Fix Specialist
|
|
13
|
+
|
|
14
|
+
执行 PR 修复(基于 fixFile),并生成可直接发布到 GitHub 评论的修复报告(Markdown 文件)。
|
|
15
|
+
|
|
16
|
+
## Agent 角色定义
|
|
17
|
+
|
|
18
|
+
| 属性 | 描述 |
|
|
19
|
+
| -------------- | ------------------------------------------------------------------------ |
|
|
20
|
+
| **角色** | 代码修复 Specialist(执行层) |
|
|
21
|
+
| **上下文隔离** | 仅处理问题列表;不重新获取评审意见(默认不调用 `gh` 拉取 PR 上下文) |
|
|
22
|
+
| **输入** | PR 编号 + `fixFile`(Markdown 文件路径,Structured Handoff) |
|
|
23
|
+
| **输出** | fixReportFile(Markdown 文件路径) |
|
|
24
|
+
| **边界** | ✅ 可修改代码、提交并推送;⛔ 不发布 GitHub 评论(由 Orchestrator 负责) |
|
|
25
|
+
|
|
26
|
+
## 前置条件
|
|
27
|
+
|
|
28
|
+
### 必需输入
|
|
29
|
+
|
|
30
|
+
- **PR 编号**:调用者必须在 prompt 中明确提供(如:`请修复 PR #123`)
|
|
31
|
+
- **fixFile**:调用者必须在 prompt 中提供问题清单文件路径(Markdown,Structured Handoff)
|
|
32
|
+
|
|
33
|
+
### 失败快速退出
|
|
34
|
+
|
|
35
|
+
如未满足以下任一条件,立即返回错误 JSON 并退出:
|
|
36
|
+
|
|
37
|
+
- ❌ prompt 未包含 PR 编号 → `{"error":"MISSING_PR_NUMBER"}`
|
|
38
|
+
- ❌ prompt 未包含 fixFile → `{"error":"MISSING_FIX_FILE"}`
|
|
39
|
+
- ❌ fixFile 不存在/不可读 → `{"error":"FIX_FILE_NOT_READABLE"}`
|
|
40
|
+
- ❌ fixFile 无法解析出 issuesToFix → `{"error":"INVALID_FIX_FILE"}`
|
|
41
|
+
|
|
42
|
+
## 输入格式(Structured Handoff:fixFile,Markdown)
|
|
43
|
+
|
|
44
|
+
说明:fixFile 由编排器根据 reviewer 的 findings 聚合生成;不要求严格 JSON,但必须包含可解析的字段。
|
|
45
|
+
|
|
46
|
+
推荐最小格式(稳定、易解析):
|
|
47
|
+
|
|
48
|
+
```md
|
|
49
|
+
# Fix File
|
|
50
|
+
|
|
51
|
+
PR: 123
|
|
52
|
+
Round: 2
|
|
53
|
+
|
|
54
|
+
## IssuesToFix
|
|
55
|
+
|
|
56
|
+
- id: CDX-001
|
|
57
|
+
priority: P1
|
|
58
|
+
category: quality
|
|
59
|
+
file: apps/backend/src/foo.ts
|
|
60
|
+
line: 42
|
|
61
|
+
title: 未处理的异常
|
|
62
|
+
description: JSON.parse 可能抛出异常但未被捕获
|
|
63
|
+
suggestion: 添加 try/catch 并返回一致错误码
|
|
64
|
+
|
|
65
|
+
## OptionalIssues
|
|
66
|
+
|
|
67
|
+
- id: GMN-004
|
|
68
|
+
priority: P3
|
|
69
|
+
category: suggestion
|
|
70
|
+
file: apps/front/src/bar.tsx
|
|
71
|
+
line: null
|
|
72
|
+
title: 可读性优化
|
|
73
|
+
description: ...
|
|
74
|
+
suggestion: ...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
解析规则(强制):
|
|
78
|
+
|
|
79
|
+
- 仅处理 `## IssuesToFix` 段落里的条目;`## OptionalIssues` 可忽略或按需处理
|
|
80
|
+
- 每条必须至少包含:`id`、`priority`、`file`、`title`、`suggestion`
|
|
81
|
+
- `line` 允许为 `null`
|
|
82
|
+
|
|
83
|
+
## 工作流程
|
|
84
|
+
|
|
85
|
+
### 1. 读取 fixFile 并标准化
|
|
86
|
+
|
|
87
|
+
要求:只依赖 prompt 中的 `fixFile`;不要重新拉取/生成评审意见。
|
|
88
|
+
|
|
89
|
+
- 用 bash 读取 `fixFile`(例如 `cat "$fixFile"`)
|
|
90
|
+
- 从 `## IssuesToFix` 中解析条目,按 `priority` 排序并按 `id` 去重
|
|
91
|
+
- 解析失败则返回 `INVALID_FIX_FILE`
|
|
92
|
+
|
|
93
|
+
### 2. 逐项修复(No Scope Creep)
|
|
94
|
+
|
|
95
|
+
- 仅修复 fixFile 中列出的问题:`IssuesToFix`(必要)与 `OptionalIssues`(可选)
|
|
96
|
+
- 每个修复必须能明确对应到原问题的 `id`
|
|
97
|
+
- 无法修复时必须记录原因(例如:缺少上下文、超出本 PR 范围、需要产品决策、需要数据库迁移等)
|
|
98
|
+
|
|
99
|
+
执行前检查(强制):
|
|
100
|
+
|
|
101
|
+
- 当前分支禁止是 `main`/`master`(应已由 pr-context 切到 PR 分支)
|
|
102
|
+
|
|
103
|
+
### 3. 提交策略
|
|
104
|
+
|
|
105
|
+
- 强制:每个 findingId 单独一个提交(一个 findingId 对应一个 commit)
|
|
106
|
+
- 每个提交后立即推送到远端(禁止 force push)
|
|
107
|
+
- 约定:如无 upstream,首次用 `git push -u origin HEAD`,后续用 `git push`
|
|
108
|
+
- 所有问题处理完毕后,再执行一次 `git push` 作为兜底
|
|
109
|
+
|
|
110
|
+
提交信息建议(强制包含 findingId):
|
|
111
|
+
|
|
112
|
+
- `fix(pr #<PR_NUMBER>): <FINDING_ID> <title>`
|
|
113
|
+
|
|
114
|
+
## 修复原则(强制)
|
|
115
|
+
|
|
116
|
+
- 只修复 `issuesToFix`/`optionalIssues`;禁止顺手重构/格式化/改无关代码
|
|
117
|
+
- 不确定的问题降级为拒绝修复,并写清 `reason`(不要“猜”)
|
|
118
|
+
- 修改尽量小:最小 diff、保持既有风格与约定
|
|
119
|
+
|
|
120
|
+
## 重要约束(强制)
|
|
121
|
+
|
|
122
|
+
- ⛔ 不要发布评论到 GitHub(不调用 `gh pr comment/review`)
|
|
123
|
+
- ✅ 必须 push(禁止 force push;禁止 rebase)
|
|
124
|
+
- ✅ 必须生成 fixReportFile(Markdown),内容可直接发到 GitHub 评论
|
|
125
|
+
|
|
126
|
+
## 输出(强制)
|
|
127
|
+
|
|
128
|
+
写入:`~/.opencode/cache/fix-report-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
129
|
+
|
|
130
|
+
最终只输出一行:
|
|
131
|
+
|
|
132
|
+
`fixReportFile: <path>`
|
|
133
|
+
|
|
134
|
+
## fixReportFile 内容格式(强制)
|
|
135
|
+
|
|
136
|
+
fixReportFile 内容必须是可直接粘贴到 GitHub 评论的 Markdown,且不得包含本地缓存文件路径。
|
|
137
|
+
|
|
138
|
+
```md
|
|
139
|
+
# Fix Report
|
|
140
|
+
|
|
141
|
+
PR: <PR_NUMBER>
|
|
142
|
+
Round: <ROUND>
|
|
143
|
+
|
|
144
|
+
## Summary
|
|
145
|
+
|
|
146
|
+
Fixed: <n>
|
|
147
|
+
Rejected: <n>
|
|
148
|
+
|
|
149
|
+
## Fixed
|
|
150
|
+
|
|
151
|
+
- id: <FINDING_ID>
|
|
152
|
+
commit: <SHA>
|
|
153
|
+
note: <what changed>
|
|
154
|
+
|
|
155
|
+
## Rejected
|
|
156
|
+
|
|
157
|
+
- id: <FINDING_ID>
|
|
158
|
+
reason: <why>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Multi-Agent 约束(Contract)
|
|
162
|
+
|
|
163
|
+
| 约束 | 说明 |
|
|
164
|
+
| -------------------- | --------------------------------------------------------------------------- |
|
|
165
|
+
| **Structured Input** | 仅处理 `fixFile` 中的问题;不重新获取评审意见(默认不调用 `gh` 拉取上下文) |
|
|
166
|
+
| **Output** | 必须生成 fixReportFile(Markdown) |
|
|
167
|
+
| **ID Correlation** | 每条提交必须能关联到某个 findingId |
|
|
168
|
+
| **No Scope Creep** | ⛔ 不修复 fixFile 之外的问题,不引入无关变更 |
|
|
169
|
+
|
|
170
|
+
## 输出有效性保证
|
|
171
|
+
|
|
172
|
+
- fixReportFile 必须成功写入
|
|
173
|
+
- stdout 只能输出一行 `fixReportFile: <path>`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: PR precheck (checkout + lint + build)
|
|
3
|
+
mode: subagent
|
|
4
|
+
model: openai/gpt-5.2-codex
|
|
5
|
+
temperature: 0.1
|
|
6
|
+
tools:
|
|
7
|
+
write: true
|
|
8
|
+
edit: false
|
|
9
|
+
bash: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# PR Precheck
|
|
13
|
+
|
|
14
|
+
## 输入(prompt 必须包含)
|
|
15
|
+
|
|
16
|
+
- `PR #<number>`
|
|
17
|
+
|
|
18
|
+
## 要做的事(按顺序)
|
|
19
|
+
|
|
20
|
+
1. 校验环境/权限
|
|
21
|
+
|
|
22
|
+
- 必须在 git 仓库内,否则输出 `{"error":"NOT_A_GIT_REPO"}`
|
|
23
|
+
- `gh auth status` 必须通过,否则输出 `{"error":"GH_NOT_AUTHENTICATED"}`
|
|
24
|
+
- PR 必须存在且可访问,否则输出 `{"error":"PR_NOT_FOUND_OR_NO_ACCESS"}`
|
|
25
|
+
|
|
26
|
+
2. 切换到 PR 分支
|
|
27
|
+
|
|
28
|
+
- 读取 PR 的 `headRefName`
|
|
29
|
+
- 如果当前分支不是 headRefName:执行 `gh pr checkout <PR_NUMBER>`
|
|
30
|
+
- 切换失败输出 `{"error":"PR_CHECKOUT_FAILED"}`
|
|
31
|
+
|
|
32
|
+
3. 预检:lint + build
|
|
33
|
+
|
|
34
|
+
- 运行 `dx lint`
|
|
35
|
+
- 运行 `dx build affected --dev`
|
|
36
|
+
|
|
37
|
+
4. 若 lint/build 失败:生成 fixFile(Markdown)
|
|
38
|
+
|
|
39
|
+
- 写入前先 `mkdir -p "$HOME/.opencode/cache"`
|
|
40
|
+
- fixFile 路径:`~/.opencode/cache/precheck-fix-pr<PR_NUMBER>-<RUN_ID>.md`
|
|
41
|
+
- fixFile 只包含 `## IssuesToFix`
|
|
42
|
+
- 每条 issue 的 `id` 必须以 `PRE-` 开头(例如 `PRE-001`)
|
|
43
|
+
- 尽量从输出中提取 file/line;取不到则 `line: null`
|
|
44
|
+
|
|
45
|
+
## 输出(强制)
|
|
46
|
+
|
|
47
|
+
只输出一个 JSON 对象:
|
|
48
|
+
|
|
49
|
+
- 通过:`{"ok":true}`
|
|
50
|
+
- 需要修复:`{"ok":false,"fixFile":"~/.opencode/cache/precheck-fix-pr123-<RUN_ID>.md"}`
|
|
51
|
+
- 环境/权限/分支问题:`{"error":"..."}`
|
|
52
|
+
|
|
53
|
+
## 规则
|
|
54
|
+
|
|
55
|
+
- 不要输出任何时间字段
|
|
56
|
+
- 不要在 stdout 输出 lint/build 的长日志(写入 fixFile 的 description 即可)
|
|
57
|
+
- 允许使用 bash 生成 runId(例如 8-12 位随机/sha1 截断均可)
|