@ranger1/dx 0.1.77 → 0.1.79
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 +102 -33
- package/bin/dx.js +3 -3
- package/lib/cli/commands/core.js +24 -1
- package/lib/cli/commands/db.js +6 -4
- package/lib/cli/commands/stack.js +198 -237
- package/lib/cli/commands/start.js +0 -6
- package/lib/cli/dx-cli.js +84 -1
- package/lib/cli/help.js +42 -9
- package/lib/{opencode-initial.js → codex-initial.js} +3 -82
- package/package.json +1 -2
- package/@opencode/agents/__pycache__/gh_review_harvest.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_context.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_precheck.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314-pytest-9.0.2.pyc +0 -0
- package/@opencode/agents/__pycache__/test_pr_review_aggregate.cpython-314.pyc +0 -0
- package/@opencode/agents/claude-reviewer.md +0 -82
- package/@opencode/agents/codex-reviewer.md +0 -83
- package/@opencode/agents/gemini-reviewer.md +0 -82
- package/@opencode/agents/gh-thread-reviewer.md +0 -122
- package/@opencode/agents/gh_review_harvest.py +0 -292
- package/@opencode/agents/pr-context.md +0 -82
- package/@opencode/agents/pr-fix.md +0 -243
- package/@opencode/agents/pr-precheck.md +0 -89
- package/@opencode/agents/pr-review-aggregate.md +0 -151
- package/@opencode/agents/pr_context.py +0 -351
- package/@opencode/agents/pr_precheck.py +0 -505
- package/@opencode/agents/pr_review_aggregate.py +0 -868
- package/@opencode/agents/test_pr_review_aggregate.py +0 -701
- package/@opencode/commands/doctor.md +0 -271
- package/@opencode/commands/git-commit-and-pr.md +0 -282
- package/@opencode/commands/git-release.md +0 -642
- package/@opencode/commands/oh_attach.json +0 -92
- package/@opencode/commands/opencode_attach.json +0 -29
- package/@opencode/commands/opencode_attach.py +0 -142
- package/@opencode/commands/pr-review-loop.md +0 -211
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: review (GitHub Harvest)
|
|
3
|
-
mode: subagent
|
|
4
|
-
model: openai/gpt-5.3-codex
|
|
5
|
-
temperature: 0.2
|
|
6
|
-
tools:
|
|
7
|
-
write: true
|
|
8
|
-
edit: false
|
|
9
|
-
bash: true
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# PR Reviewer (GitHub Harvest)
|
|
13
|
-
|
|
14
|
-
Harvest all GitHub PR review feedback (humans + bots, including Copilot) and normalize into a standard `reviewFile`.
|
|
15
|
-
|
|
16
|
-
## 输入(prompt 必须包含)
|
|
17
|
-
|
|
18
|
-
- `PR #<number>`
|
|
19
|
-
- `round: <number>`
|
|
20
|
-
- `runId: <string>`(必须透传,格式 `<PR>-<ROUND>-<HEAD_SHORT>`,禁止自行生成)
|
|
21
|
-
- `contextFile: <filename>`
|
|
22
|
-
|
|
23
|
-
## Cache 约定(强制)
|
|
24
|
-
|
|
25
|
-
- 缓存目录固定为 `./.cache/`;交接一律传 `./.cache/<file>`(repo 相对路径),禁止 basename-only(如 `foo.md`)。
|
|
26
|
-
|
|
27
|
-
## 输出(强制)
|
|
28
|
-
|
|
29
|
-
只输出一行:
|
|
30
|
-
|
|
31
|
-
`reviewFile: ./.cache/<file>.md`
|
|
32
|
-
|
|
33
|
-
## reviewFile 格式(强制)
|
|
34
|
-
|
|
35
|
-
```md
|
|
36
|
-
# Review (GHR)
|
|
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: GHR-RC-2752827557
|
|
51
|
-
priority: P1
|
|
52
|
-
category: quality|performance|security|architecture
|
|
53
|
-
file: <path>
|
|
54
|
-
line: <number|null>
|
|
55
|
-
title: <short>
|
|
56
|
-
description: <single-line text>
|
|
57
|
-
suggestion: <single-line text>
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## ID 规则(强制)
|
|
61
|
-
|
|
62
|
-
- Inline 评审(discussion_r...):`GHR-RC-<databaseId>`(databaseId 可映射到 `#discussion_r<databaseId>`)
|
|
63
|
-
- PR Review 总评:`GHR-RV-<reviewId>`
|
|
64
|
-
- PR 普通评论:`GHR-IC-<issueCommentId>`
|
|
65
|
-
|
|
66
|
-
## 执行步骤(强制)
|
|
67
|
-
|
|
68
|
-
1) Harvest(确定性)
|
|
69
|
-
|
|
70
|
-
- 调用脚本生成 raw JSON:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
python3 ~/.opencode/agents/gh_review_harvest.py \
|
|
74
|
-
--pr <PR_NUMBER> \
|
|
75
|
-
--round <ROUND> \
|
|
76
|
-
--run-id <RUN_ID>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
- 脚本 stdout 会输出一行 JSON:`{"rawFile":"./.cache/...json"}`,从中取 `rawFile`。
|
|
80
|
-
|
|
81
|
-
2) Normalize(LLM 分类)
|
|
82
|
-
|
|
83
|
-
- 读取 `rawFile`(JSON)后,提取“建议/问题”并生成 findings:
|
|
84
|
-
- 覆盖 humans + bots(不做作者白名单)。
|
|
85
|
-
- 忽略纯审批/无内容:如 `LGTM`、`Looks good`、`Approved` 等。
|
|
86
|
-
- 分类规则(大致):
|
|
87
|
-
- P0: 明确安全漏洞/数据泄漏/资金损失/远程执行
|
|
88
|
-
- P1: 逻辑 bug/权限绕过/会导致线上错误
|
|
89
|
-
- P2: 潜在 bug/鲁棒性/边界条件/可维护性重大问题
|
|
90
|
-
- P3: 风格/命名/小优化/可选建议
|
|
91
|
-
- `category` 只能取:quality|performance|security|architecture
|
|
92
|
-
|
|
93
|
-
3) 写入 reviewFile
|
|
94
|
-
|
|
95
|
-
- 文件名固定:`./.cache/review-GHR-pr<PR_NUMBER>-r<ROUND>-<RUN_ID>.md`
|
|
96
|
-
- 重要:`title/description/suggestion` 必须是单行;原文有换行时用 `\\n` 转义。
|
|
97
|
-
|
|
98
|
-
## 禁止事项(强制)
|
|
99
|
-
|
|
100
|
-
- ⛔ 不发布 GitHub 评论(不调用 `gh pr comment/review`)
|
|
101
|
-
- ⛔ 不修改代码(只输出 reviewFile)
|
|
102
|
-
- ⛔ 不生成/伪造 runId
|
|
103
|
-
|
|
104
|
-
## 决策日志约束(强制)
|
|
105
|
-
|
|
106
|
-
如果 prompt 中提供了 `decisionLogFile`,必须先读取并遵守以下规则:
|
|
107
|
-
|
|
108
|
-
1. **已修复问题**:不再提出本质相同的问题
|
|
109
|
-
2. **已拒绝问题**:
|
|
110
|
-
- 若你的发现 priority 比原问题高 ≥2 级(如 P3→P1, P2→P0),可以升级质疑
|
|
111
|
-
- 否则不再提出
|
|
112
|
-
|
|
113
|
-
3. **文件一致性**:
|
|
114
|
-
- 匹配 Decision Log 时,**必须检查 `file` 字段是否与当前 finding 的文件一致**。
|
|
115
|
-
- 若 decision-log 中的 `file` 与当前文件不一致(包括重命名、移动、删除),则**视为不同问题**,不进行 essence 匹配(即作为新问题处理)。
|
|
116
|
-
- 若 decision-log 条目缺少 `file` 字段,也视为不匹配。
|
|
117
|
-
|
|
118
|
-
判断"问题本质相同"时,比对 decision-log 中的 `essence` 字段与你发现的问题描述。
|
|
119
|
-
|
|
120
|
-
### 禁止事项
|
|
121
|
-
- ⛔ 不质疑已修复问题的实现方式(除非发现修复引入了新 bug)
|
|
122
|
-
- ⛔ 不重复提出已拒绝问题(除非满足升级质疑条件)
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
# Deterministic GitHub PR review harvester.
|
|
4
|
-
#
|
|
5
|
-
# - Fetches inline review threads via GraphQL (reviewThreads) with pagination.
|
|
6
|
-
# - Fetches PR reviews and PR issue comments via REST (gh api) with pagination.
|
|
7
|
-
# - Writes a raw JSON file into project cache: ./.cache/
|
|
8
|
-
# - Prints exactly one JSON object to stdout: {"rawFile":"./.cache/...json"}
|
|
9
|
-
|
|
10
|
-
import argparse
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import subprocess
|
|
14
|
-
import sys
|
|
15
|
-
from datetime import datetime, timezone
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
MARKER_SUBSTR = "<!-- pr-review-loop-marker"
|
|
20
|
-
|
|
21
|
-
def _repo_root():
|
|
22
|
-
try:
|
|
23
|
-
p = subprocess.run(
|
|
24
|
-
["git", "rev-parse", "--show-toplevel"],
|
|
25
|
-
stdout=subprocess.PIPE,
|
|
26
|
-
stderr=subprocess.DEVNULL,
|
|
27
|
-
text=True,
|
|
28
|
-
)
|
|
29
|
-
out = (p.stdout or "").strip()
|
|
30
|
-
if p.returncode == 0 and out:
|
|
31
|
-
return Path(out)
|
|
32
|
-
except Exception:
|
|
33
|
-
pass
|
|
34
|
-
return Path.cwd()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _cache_dir(repo_root):
|
|
38
|
-
return (repo_root / ".cache").resolve()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _repo_relpath(repo_root, p):
|
|
42
|
-
try:
|
|
43
|
-
rel = p.resolve().relative_to(repo_root.resolve())
|
|
44
|
-
return "./" + rel.as_posix()
|
|
45
|
-
except Exception:
|
|
46
|
-
return os.path.basename(str(p))
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
REPO_ROOT = _repo_root()
|
|
50
|
-
CACHE_DIR = _cache_dir(REPO_ROOT)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _json_out(obj):
|
|
54
|
-
sys.stdout.write(json.dumps(obj, ensure_ascii=True))
|
|
55
|
-
sys.stdout.write("\n")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _run_capture(cmd):
|
|
59
|
-
try:
|
|
60
|
-
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
61
|
-
return p.returncode, p.stdout, p.stderr
|
|
62
|
-
except FileNotFoundError as e:
|
|
63
|
-
return 127, "", str(e)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _has_loop_marker(text):
|
|
67
|
-
if not text:
|
|
68
|
-
return False
|
|
69
|
-
try:
|
|
70
|
-
return MARKER_SUBSTR in str(text)
|
|
71
|
-
except Exception:
|
|
72
|
-
return False
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _require_gh_auth():
|
|
76
|
-
rc, out, err = _run_capture(["gh", "auth", "status"])
|
|
77
|
-
if rc == 127:
|
|
78
|
-
return False, "GH_CLI_NOT_FOUND", "gh not found in PATH"
|
|
79
|
-
if rc != 0:
|
|
80
|
-
detail = (err or out or "").strip()
|
|
81
|
-
if len(detail) > 4000:
|
|
82
|
-
detail = detail[-4000:]
|
|
83
|
-
return False, "GH_NOT_AUTHENTICATED", detail
|
|
84
|
-
return True, None, None
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def _resolve_owner_repo(explicit_repo):
|
|
88
|
-
if explicit_repo:
|
|
89
|
-
s = str(explicit_repo).strip()
|
|
90
|
-
if s and "/" in s:
|
|
91
|
-
return s
|
|
92
|
-
rc, out, _ = _run_capture(["gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])
|
|
93
|
-
owner_repo = out.strip() if rc == 0 else ""
|
|
94
|
-
return owner_repo or None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _gh_api_json(args):
|
|
98
|
-
rc, out, err = _run_capture(["gh", "api"] + args)
|
|
99
|
-
if rc != 0:
|
|
100
|
-
raise RuntimeError(f"GH_API_FAILED: {(err or out or '').strip()}")
|
|
101
|
-
try:
|
|
102
|
-
return json.loads(out or "null")
|
|
103
|
-
except Exception:
|
|
104
|
-
raise RuntimeError("GH_API_JSON_PARSE_FAILED")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _gh_api_graphql(query, variables):
|
|
108
|
-
cmd = ["gh", "api", "graphql", "-f", f"query={query}"]
|
|
109
|
-
for k, v in (variables or {}).items():
|
|
110
|
-
if isinstance(v, int):
|
|
111
|
-
cmd.extend(["-F", f"{k}={v}"])
|
|
112
|
-
elif v is None:
|
|
113
|
-
cmd.extend(["-f", f"{k}="])
|
|
114
|
-
else:
|
|
115
|
-
cmd.extend(["-f", f"{k}={v}"])
|
|
116
|
-
|
|
117
|
-
rc, out, err = _run_capture(cmd)
|
|
118
|
-
if rc != 0:
|
|
119
|
-
raise RuntimeError(f"GH_GRAPHQL_FAILED: {(err or out or '').strip()}")
|
|
120
|
-
try:
|
|
121
|
-
return json.loads(out or "null")
|
|
122
|
-
except Exception:
|
|
123
|
-
raise RuntimeError("GH_GRAPHQL_JSON_PARSE_FAILED")
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def _flatten_threads(gql_data):
|
|
127
|
-
threads = []
|
|
128
|
-
pr = (((gql_data or {}).get("data") or {}).get("repository") or {}).get("pullRequest") or {}
|
|
129
|
-
conn = pr.get("reviewThreads") or {}
|
|
130
|
-
nodes = conn.get("nodes") or []
|
|
131
|
-
for t in nodes:
|
|
132
|
-
is_resolved = bool((t or {}).get("isResolved"))
|
|
133
|
-
is_outdated = bool((t or {}).get("isOutdated"))
|
|
134
|
-
if is_resolved or is_outdated:
|
|
135
|
-
continue
|
|
136
|
-
comments_conn = (t or {}).get("comments") or {}
|
|
137
|
-
comments_nodes = comments_conn.get("nodes") or []
|
|
138
|
-
comments = []
|
|
139
|
-
for c in comments_nodes:
|
|
140
|
-
body = (c or {}).get("body") or ""
|
|
141
|
-
body_text = (c or {}).get("bodyText") or ""
|
|
142
|
-
if _has_loop_marker(body) or _has_loop_marker(body_text):
|
|
143
|
-
continue
|
|
144
|
-
author = (c or {}).get("author") or {}
|
|
145
|
-
comments.append(
|
|
146
|
-
{
|
|
147
|
-
"id": (c or {}).get("id"),
|
|
148
|
-
"databaseId": (c or {}).get("databaseId"),
|
|
149
|
-
"url": (c or {}).get("url"),
|
|
150
|
-
"author": {
|
|
151
|
-
"login": author.get("login"),
|
|
152
|
-
"type": author.get("__typename"),
|
|
153
|
-
},
|
|
154
|
-
"body": body,
|
|
155
|
-
"bodyText": body_text,
|
|
156
|
-
"createdAt": (c or {}).get("createdAt"),
|
|
157
|
-
"updatedAt": (c or {}).get("updatedAt"),
|
|
158
|
-
}
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
if not comments:
|
|
162
|
-
continue
|
|
163
|
-
threads.append(
|
|
164
|
-
{
|
|
165
|
-
"id": (t or {}).get("id"),
|
|
166
|
-
"isResolved": False,
|
|
167
|
-
"isOutdated": False,
|
|
168
|
-
"path": (t or {}).get("path"),
|
|
169
|
-
"line": (t or {}).get("line"),
|
|
170
|
-
"originalLine": (t or {}).get("originalLine"),
|
|
171
|
-
"startLine": (t or {}).get("startLine"),
|
|
172
|
-
"originalStartLine": (t or {}).get("originalStartLine"),
|
|
173
|
-
"comments": comments,
|
|
174
|
-
}
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
page_info = conn.get("pageInfo") or {}
|
|
178
|
-
return threads, {
|
|
179
|
-
"hasNextPage": bool(page_info.get("hasNextPage")),
|
|
180
|
-
"endCursor": page_info.get("endCursor"),
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _fetch_all_review_threads(owner, repo, pr_number):
|
|
185
|
-
query = (
|
|
186
|
-
"query($owner:String!,$repo:String!,$prNumber:Int!,$after:String){"
|
|
187
|
-
"repository(owner:$owner,name:$repo){"
|
|
188
|
-
"pullRequest(number:$prNumber){"
|
|
189
|
-
"reviewThreads(first:100,after:$after){"
|
|
190
|
-
"pageInfo{hasNextPage endCursor}"
|
|
191
|
-
"nodes{"
|
|
192
|
-
"id isResolved isOutdated path line originalLine startLine originalStartLine "
|
|
193
|
-
"comments(first:100){nodes{"
|
|
194
|
-
"id databaseId url body bodyText createdAt updatedAt author{login __typename}"
|
|
195
|
-
"}}"
|
|
196
|
-
"}"
|
|
197
|
-
"}"
|
|
198
|
-
"}"
|
|
199
|
-
"}"
|
|
200
|
-
"}"
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
after = None
|
|
204
|
-
all_threads = []
|
|
205
|
-
while True:
|
|
206
|
-
data = _gh_api_graphql(query, {"owner": owner, "repo": repo, "prNumber": pr_number, "after": after})
|
|
207
|
-
threads, page = _flatten_threads(data)
|
|
208
|
-
all_threads.extend(threads)
|
|
209
|
-
if not page.get("hasNextPage"):
|
|
210
|
-
break
|
|
211
|
-
after = page.get("endCursor")
|
|
212
|
-
if not after:
|
|
213
|
-
break
|
|
214
|
-
return all_threads
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def main(argv):
|
|
218
|
-
class _ArgParser(argparse.ArgumentParser):
|
|
219
|
-
def error(self, message):
|
|
220
|
-
raise ValueError(message)
|
|
221
|
-
|
|
222
|
-
parser = _ArgParser(add_help=False)
|
|
223
|
-
parser.add_argument("--pr", type=int, required=True)
|
|
224
|
-
parser.add_argument("--round", type=int, default=1)
|
|
225
|
-
parser.add_argument("--run-id", required=True)
|
|
226
|
-
parser.add_argument("--repo")
|
|
227
|
-
|
|
228
|
-
try:
|
|
229
|
-
args = parser.parse_args(argv)
|
|
230
|
-
except ValueError:
|
|
231
|
-
_json_out({"error": "INVALID_ARGS"})
|
|
232
|
-
return 2
|
|
233
|
-
|
|
234
|
-
ok, code, detail = _require_gh_auth()
|
|
235
|
-
if not ok:
|
|
236
|
-
_json_out({"error": code, "detail": detail})
|
|
237
|
-
return 1
|
|
238
|
-
|
|
239
|
-
owner_repo = _resolve_owner_repo(args.repo)
|
|
240
|
-
if not owner_repo:
|
|
241
|
-
_json_out({"error": "REPO_NOT_FOUND"})
|
|
242
|
-
return 1
|
|
243
|
-
if "/" not in owner_repo:
|
|
244
|
-
_json_out({"error": "INVALID_REPO"})
|
|
245
|
-
return 1
|
|
246
|
-
|
|
247
|
-
owner, repo = owner_repo.split("/", 1)
|
|
248
|
-
pr_number = int(args.pr)
|
|
249
|
-
round_num = int(args.round)
|
|
250
|
-
run_id = str(args.run_id).strip()
|
|
251
|
-
if not run_id:
|
|
252
|
-
_json_out({"error": "MISSING_RUN_ID"})
|
|
253
|
-
return 1
|
|
254
|
-
|
|
255
|
-
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
256
|
-
raw_basename = f"gh-review-raw-pr{pr_number}-r{round_num}-{run_id}.json"
|
|
257
|
-
raw_path = CACHE_DIR / raw_basename
|
|
258
|
-
|
|
259
|
-
try:
|
|
260
|
-
threads = _fetch_all_review_threads(owner, repo, pr_number)
|
|
261
|
-
|
|
262
|
-
reviews = _gh_api_json([f"repos/{owner_repo}/pulls/{pr_number}/reviews", "--paginate"])
|
|
263
|
-
issue_comments = _gh_api_json([f"repos/{owner_repo}/issues/{pr_number}/comments", "--paginate"])
|
|
264
|
-
|
|
265
|
-
if isinstance(reviews, list):
|
|
266
|
-
reviews = [r for r in reviews if not _has_loop_marker((r or {}).get("body") or "")]
|
|
267
|
-
if isinstance(issue_comments, list):
|
|
268
|
-
issue_comments = [c for c in issue_comments if not _has_loop_marker((c or {}).get("body") or "")]
|
|
269
|
-
|
|
270
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
271
|
-
payload = {
|
|
272
|
-
"repo": owner_repo,
|
|
273
|
-
"pr": pr_number,
|
|
274
|
-
"round": round_num,
|
|
275
|
-
"runId": run_id,
|
|
276
|
-
"generatedAt": now,
|
|
277
|
-
"reviewThreads": threads,
|
|
278
|
-
"reviews": reviews if isinstance(reviews, list) else [],
|
|
279
|
-
"issueComments": issue_comments if isinstance(issue_comments, list) else [],
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
raw_path.write_text(json.dumps(payload, ensure_ascii=True), encoding="utf-8", newline="\n")
|
|
283
|
-
except Exception as e:
|
|
284
|
-
_json_out({"error": "HARVEST_FAILED", "detail": str(e)[:800]})
|
|
285
|
-
return 1
|
|
286
|
-
|
|
287
|
-
_json_out({"rawFile": _repo_relpath(REPO_ROOT, raw_path)})
|
|
288
|
-
return 0
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if __name__ == "__main__":
|
|
292
|
-
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: build PR context file
|
|
3
|
-
mode: subagent
|
|
4
|
-
model: openai/gpt-5.3-codex
|
|
5
|
-
temperature: 0.1
|
|
6
|
-
tools:
|
|
7
|
-
bash: true
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# PR Context Builder
|
|
11
|
-
|
|
12
|
-
为 PR Review Loop 构建上下文文件(Markdown)。确定性工作由脚本完成。
|
|
13
|
-
|
|
14
|
-
## 输入要求(强制)
|
|
15
|
-
|
|
16
|
-
调用者必须在 prompt 中明确提供:
|
|
17
|
-
|
|
18
|
-
- PR 编号(如:`PR #123` 或 `prNumber: 123`)
|
|
19
|
-
- round(如:`round: 1`;无则默认 1)
|
|
20
|
-
|
|
21
|
-
## 唯一标识 runId(强制)
|
|
22
|
-
|
|
23
|
-
- 脚本必须生成全局唯一标识 `runId`:`<PR>-<ROUND>-<HEAD_SHORT>`
|
|
24
|
-
- 其中:
|
|
25
|
-
- `<PR>`:PR 编号
|
|
26
|
-
- `<ROUND>`:当前轮次
|
|
27
|
-
- `<HEAD_SHORT>`:`headOid` 的前 7 位(git rev-parse --short HEAD)
|
|
28
|
-
- `runId` 必须包含在返回的 JSON 中,供后续步骤使用。
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## 输出(强制)
|
|
32
|
-
|
|
33
|
-
脚本会写入项目内 `./.cache/`,stdout 只输出单一 JSON(可 `JSON.parse()`)。
|
|
34
|
-
|
|
35
|
-
## Cache 约定(强制)
|
|
36
|
-
|
|
37
|
-
- 缓存目录固定为 `./.cache/`;交接一律传 `./.cache/<file>`(repo 相对路径),禁止 basename-only(如 `foo.md`)。
|
|
38
|
-
- 文件命名:`./.cache/pr-context-pr<PR>-r<ROUND>-<RUN_ID>.md`
|
|
39
|
-
- `RUN_ID` 格式必须为 `<PR>-<ROUND>-<HEAD_SHORT>`
|
|
40
|
-
|
|
41
|
-
## 调用脚本(强制)
|
|
42
|
-
|
|
43
|
-
脚本位置:`~/.opencode/agents/pr_context.py`
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
python3 ~/.opencode/agents/pr_context.py --pr <PR_NUMBER> --round <ROUND>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 脚本输出处理(强制)
|
|
50
|
-
|
|
51
|
-
- 脚本 stdout 只会输出**单一一行 JSON**(可 `JSON.parse()`)。
|
|
52
|
-
- **成功时**:你的最终输出必须是**脚本 stdout 的那一行 JSON 原样内容**。
|
|
53
|
-
- 禁止:解释/分析/补充文字
|
|
54
|
-
- 禁止:代码块(```)
|
|
55
|
-
- 禁止:前后空行
|
|
56
|
-
- **失败/异常时**:
|
|
57
|
-
- 若脚本 stdout 已输出合法 JSON(包含 `error` 或其他字段)→ 仍然**原样返回该 JSON**。
|
|
58
|
-
- 若脚本未输出合法 JSON / 退出异常 → 仅输出一行 JSON:`{"error":"PR_CONTEXT_AGENT_FAILED"}`(必要时可加 `detail` 字段)。
|
|
59
|
-
|
|
60
|
-
## GitHub 认证校验(重要)
|
|
61
|
-
|
|
62
|
-
脚本会在调用 `gh repo view/gh pr view` 之前校验 GitHub CLI 已认证。
|
|
63
|
-
|
|
64
|
-
- 为了避免 `gh auth status` 在“其他 host(例如 enterprise)认证异常”时误判,脚本会优先从 `git remote origin` 推断 host,并使用:
|
|
65
|
-
- `gh auth status --hostname <host>`
|
|
66
|
-
- 推断失败时默认使用 `github.com`。
|
|
67
|
-
|
|
68
|
-
可能出现的错误:
|
|
69
|
-
|
|
70
|
-
- `{"error":"GH_CLI_NOT_FOUND"}`:找不到 `gh` 命令(PATH 内未安装/不可执行)
|
|
71
|
-
- 处理:安装 GitHub CLI:https://cli.github.com/
|
|
72
|
-
- `{"error":"GH_NOT_AUTHENTICATED"}`:当前 repo 的 host 未认证
|
|
73
|
-
- 处理:`gh auth login --hostname <host>`
|
|
74
|
-
|
|
75
|
-
本地排查命令(在同一个 shell 环境运行):
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
git remote get-url origin
|
|
79
|
-
gh auth status
|
|
80
|
-
gh auth status --hostname github.com
|
|
81
|
-
env | grep '^GH_'
|
|
82
|
-
```
|