@ranger1/dx 0.1.31 → 0.1.33
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/__pycache__/pr_context.cpython-314.pyc +0 -0
- package/@opencode/agents/__pycache__/pr_precheck.cpython-314.pyc +0 -0
- package/@opencode/agents/pr-context.md +35 -0
- package/@opencode/agents/pr-precheck.md +36 -0
- package/@opencode/agents/pr-review-aggregate.md +20 -3
- package/@opencode/agents/pr_context.py +61 -4
- package/@opencode/agents/pr_precheck.py +63 -4
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
@@ -33,3 +33,38 @@ tools:
|
|
|
33
33
|
```bash
|
|
34
34
|
python3 ~/.opencode/agents/pr_context.py --pr <PR_NUMBER> --round <ROUND>
|
|
35
35
|
```
|
|
36
|
+
|
|
37
|
+
## 脚本输出处理(强制)
|
|
38
|
+
|
|
39
|
+
- 脚本 stdout 只会输出**单一一行 JSON**(可 `JSON.parse()`)。
|
|
40
|
+
- **成功时**:你的最终输出必须是**脚本 stdout 的那一行 JSON 原样内容**。
|
|
41
|
+
- 禁止:解释/分析/补充文字
|
|
42
|
+
- 禁止:代码块(```)
|
|
43
|
+
- 禁止:前后空行
|
|
44
|
+
- **失败/异常时**:
|
|
45
|
+
- 若脚本 stdout 已输出合法 JSON(包含 `error` 或其他字段)→ 仍然**原样返回该 JSON**。
|
|
46
|
+
- 若脚本未输出合法 JSON / 退出异常 → 仅输出一行 JSON:`{"error":"PR_CONTEXT_AGENT_FAILED"}`(必要时可加 `detail` 字段)。
|
|
47
|
+
|
|
48
|
+
## GitHub 认证校验(重要)
|
|
49
|
+
|
|
50
|
+
脚本会在调用 `gh repo view/gh pr view` 之前校验 GitHub CLI 已认证。
|
|
51
|
+
|
|
52
|
+
- 为了避免 `gh auth status` 在“其他 host(例如 enterprise)认证异常”时误判,脚本会优先从 `git remote origin` 推断 host,并使用:
|
|
53
|
+
- `gh auth status --hostname <host>`
|
|
54
|
+
- 推断失败时默认使用 `github.com`。
|
|
55
|
+
|
|
56
|
+
可能出现的错误:
|
|
57
|
+
|
|
58
|
+
- `{"error":"GH_CLI_NOT_FOUND"}`:找不到 `gh` 命令(PATH 内未安装/不可执行)
|
|
59
|
+
- 处理:安装 GitHub CLI:https://cli.github.com/
|
|
60
|
+
- `{"error":"GH_NOT_AUTHENTICATED"}`:当前 repo 的 host 未认证
|
|
61
|
+
- 处理:`gh auth login --hostname <host>`
|
|
62
|
+
|
|
63
|
+
本地排查命令(在同一个 shell 环境运行):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git remote get-url origin
|
|
67
|
+
gh auth status
|
|
68
|
+
gh auth status --hostname github.com
|
|
69
|
+
env | grep '^GH_'
|
|
70
|
+
```
|
|
@@ -26,6 +26,42 @@ tools:
|
|
|
26
26
|
python3 ~/.opencode/agents/pr_precheck.py <PR_NUMBER>
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## 脚本输出处理(强制)
|
|
30
|
+
|
|
31
|
+
- 脚本 stdout 只会输出**单一一行 JSON**(可 `JSON.parse()`)。
|
|
32
|
+
- **成功时**:你的最终输出必须是**脚本 stdout 的那一行 JSON 原样内容**。
|
|
33
|
+
- 典型返回:`{"ok":true}` 或 `{"ok":false,"fixFile":"..."}`
|
|
34
|
+
- 禁止:解释/分析/补充文字
|
|
35
|
+
- 禁止:代码块(```)
|
|
36
|
+
- 禁止:前后空行
|
|
37
|
+
- **失败/异常时**:
|
|
38
|
+
- 若脚本 stdout 已输出合法 JSON(包含 `error` 或其他字段)→ 仍然**原样返回该 JSON**。
|
|
39
|
+
- 若脚本未输出合法 JSON / 退出异常 → 仅输出一行 JSON:`{"error":"PR_PRECHECK_AGENT_FAILED"}`(必要时可加 `detail` 字段)。
|
|
40
|
+
|
|
41
|
+
## GitHub 认证校验(重要)
|
|
42
|
+
|
|
43
|
+
脚本会在执行 `gh pr view/checkout` 之前校验 GitHub CLI 已认证。
|
|
44
|
+
|
|
45
|
+
- 为了避免 `gh auth status` 在“其他 host(例如 enterprise)认证异常”时误判,脚本会优先从 `git remote origin` 推断 host,并使用:
|
|
46
|
+
- `gh auth status --hostname <host>`
|
|
47
|
+
- 推断失败时默认使用 `github.com`。
|
|
48
|
+
|
|
49
|
+
可能出现的错误:
|
|
50
|
+
|
|
51
|
+
- `{"error":"GH_CLI_NOT_FOUND"}`:找不到 `gh` 命令(PATH 内未安装/不可执行)
|
|
52
|
+
- 处理:安装 GitHub CLI:https://cli.github.com/
|
|
53
|
+
- `{"error":"GH_NOT_AUTHENTICATED"}`:当前 repo 的 host 未认证
|
|
54
|
+
- 处理:`gh auth login --hostname <host>`
|
|
55
|
+
|
|
56
|
+
本地排查命令(在同一个 shell 环境运行):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git remote get-url origin
|
|
60
|
+
gh auth status
|
|
61
|
+
gh auth status --hostname github.com
|
|
62
|
+
env | grep '^GH_'
|
|
63
|
+
```
|
|
64
|
+
|
|
29
65
|
## 仅当出现 merge 冲突时怎么处理
|
|
30
66
|
|
|
31
67
|
当脚本输出 `{"error":"PR_MERGE_CONFLICTS_UNRESOLVED"}` 时:
|
|
@@ -46,11 +46,16 @@ reviewFile: review-GMN-pr123-r1-abcdef123456.md
|
|
|
46
46
|
|
|
47
47
|
所有确定性工作(解析/聚合/发评论/生成 fixFile/输出 JSON)都由 `~/.opencode/agents/pr_review_aggregate.py` 完成。
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
你只做两件事:
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
1) 在模式 A 里用大模型判断哪些 finding 是重复的,并把重复分组作为参数传给脚本(不落盘)。
|
|
52
|
+
2) 调用脚本后,把脚本 stdout 的 JSON **原样返回**给调用者(不做解释/分析)。
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
## 重复分组(仅作为脚本入参)
|
|
55
|
+
|
|
56
|
+
你需要基于 3 份 `reviewFile` 内容判断重复 finding 分组,生成**一行 JSON**(不要代码块、不要解释文字、不要换行)。
|
|
57
|
+
|
|
58
|
+
注意:这行 JSON **不是你的最终输出**,它只用于生成 `--duplicate-groups-b64` 传给脚本。
|
|
54
59
|
|
|
55
60
|
```json
|
|
56
61
|
{"duplicateGroups":[["CDX-001","CLD-003"],["GMN-002","CLD-005","CDX-004"]]}
|
|
@@ -81,3 +86,15 @@ python3 ~/.opencode/agents/pr_review_aggregate.py \
|
|
|
81
86
|
--run-id <RUN_ID> \
|
|
82
87
|
--fix-report-file <FIX_REPORT_FILE>
|
|
83
88
|
```
|
|
89
|
+
|
|
90
|
+
## 脚本输出处理(强制)
|
|
91
|
+
|
|
92
|
+
- 脚本 stdout 只会输出**单一一行 JSON**(可 `JSON.parse()`)。
|
|
93
|
+
- **成功时**:你的最终输出必须是**脚本 stdout 的那一行 JSON 原样内容**。
|
|
94
|
+
- 典型返回:`{"stop":true}` 或 `{"stop":false,"fixFile":"..."}` 或 `{"ok":true}`
|
|
95
|
+
- 禁止:解释/分析/补充文字
|
|
96
|
+
- 禁止:代码块(```)
|
|
97
|
+
- 禁止:前后空行
|
|
98
|
+
- **失败/异常时**:
|
|
99
|
+
- 若脚本 stdout 已输出合法 JSON(包含 `error` 或其他字段)→ 仍然**原样返回该 JSON**。
|
|
100
|
+
- 若脚本未输出合法 JSON / 退出异常 → 仅输出一行 JSON:`{"error":"PR_REVIEW_AGGREGATE_AGENT_FAILED"}`(必要时可加 `detail` 字段)。
|
|
@@ -9,8 +9,10 @@ import argparse
|
|
|
9
9
|
import hashlib
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import re
|
|
12
13
|
import subprocess
|
|
13
14
|
import sys
|
|
15
|
+
from urllib.parse import urlparse
|
|
14
16
|
from pathlib import Path
|
|
15
17
|
|
|
16
18
|
|
|
@@ -24,8 +26,41 @@ def _json_out(obj):
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
def _run_capture(cmd):
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
try:
|
|
30
|
+
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
31
|
+
return p.returncode, p.stdout, p.stderr
|
|
32
|
+
except FileNotFoundError as e:
|
|
33
|
+
return 127, "", str(e)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _detect_git_remote_host():
|
|
37
|
+
# Best-effort parse from origin remote.
|
|
38
|
+
rc, origin_url, _ = _run_capture(["git", "remote", "get-url", "origin"])
|
|
39
|
+
if rc != 0:
|
|
40
|
+
rc, origin_url, _ = _run_capture(["git", "config", "--get", "remote.origin.url"])
|
|
41
|
+
if rc != 0:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
url = (origin_url or "").strip()
|
|
45
|
+
if not url:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# Examples:
|
|
49
|
+
# - git@github.com:owner/repo.git
|
|
50
|
+
# - ssh://git@github.company.com/owner/repo.git
|
|
51
|
+
# - https://github.com/owner/repo.git
|
|
52
|
+
if url.startswith("git@"): # SCP-like syntax
|
|
53
|
+
m = re.match(r"^git@([^:]+):", url)
|
|
54
|
+
return m.group(1) if m else None
|
|
55
|
+
|
|
56
|
+
if url.startswith("ssh://") or url.startswith("https://") or url.startswith("http://"):
|
|
57
|
+
try:
|
|
58
|
+
parsed = urlparse(url)
|
|
59
|
+
return parsed.hostname
|
|
60
|
+
except Exception:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return None
|
|
29
64
|
|
|
30
65
|
|
|
31
66
|
def _clip(s, n):
|
|
@@ -95,9 +130,31 @@ def main(argv):
|
|
|
95
130
|
_json_out({"error": "NOT_A_GIT_REPO"})
|
|
96
131
|
return 1
|
|
97
132
|
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
host = _detect_git_remote_host() or "github.com"
|
|
134
|
+
rc, gh_out, gh_err = _run_capture(["gh", "auth", "status", "--hostname", host])
|
|
135
|
+
if rc == 127:
|
|
136
|
+
_json_out({
|
|
137
|
+
"error": "GH_CLI_NOT_FOUND",
|
|
138
|
+
"detail": "gh not found in PATH",
|
|
139
|
+
"suggestion": "Install GitHub CLI: https://cli.github.com/",
|
|
140
|
+
})
|
|
100
141
|
return 1
|
|
142
|
+
if rc != 0:
|
|
143
|
+
# If host detection is wrong, a global check might still succeed.
|
|
144
|
+
rc2, gh_out2, gh_err2 = _run_capture(["gh", "auth", "status"])
|
|
145
|
+
if rc2 == 0:
|
|
146
|
+
pass
|
|
147
|
+
else:
|
|
148
|
+
detail = (gh_err or gh_out or "").strip()
|
|
149
|
+
if len(detail) > 4000:
|
|
150
|
+
detail = detail[-4000:]
|
|
151
|
+
_json_out({
|
|
152
|
+
"error": "GH_NOT_AUTHENTICATED",
|
|
153
|
+
"host": host,
|
|
154
|
+
"detail": detail,
|
|
155
|
+
"suggestion": f"Run: gh auth login --hostname {host}",
|
|
156
|
+
})
|
|
157
|
+
return 1
|
|
101
158
|
|
|
102
159
|
rc, owner_repo, _ = _run_capture(["gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])
|
|
103
160
|
owner_repo = owner_repo.strip() if rc == 0 else ""
|
|
@@ -18,10 +18,19 @@ import re
|
|
|
18
18
|
import secrets
|
|
19
19
|
import subprocess
|
|
20
20
|
import sys
|
|
21
|
+
from urllib.parse import urlparse
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def run(cmd, *, cwd=None, stdout_path=None, stderr_path=None):
|
|
26
|
+
try:
|
|
27
|
+
return _run(cmd, cwd=cwd, stdout_path=stdout_path, stderr_path=stderr_path)
|
|
28
|
+
except FileNotFoundError as e:
|
|
29
|
+
# Match common shell semantics for "command not found".
|
|
30
|
+
return 127
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _run(cmd, *, cwd=None, stdout_path=None, stderr_path=None):
|
|
25
34
|
if stdout_path and stderr_path and stdout_path == stderr_path:
|
|
26
35
|
with open(stdout_path, "wb") as f:
|
|
27
36
|
p = subprocess.run(cmd, cwd=cwd, stdout=f, stderr=f)
|
|
@@ -45,8 +54,42 @@ def run(cmd, *, cwd=None, stdout_path=None, stderr_path=None):
|
|
|
45
54
|
|
|
46
55
|
|
|
47
56
|
def run_capture(cmd, *, cwd=None):
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
try:
|
|
58
|
+
p = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
59
|
+
return p.returncode, p.stdout, p.stderr
|
|
60
|
+
except FileNotFoundError as e:
|
|
61
|
+
return 127, "", str(e)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _detect_git_remote_host():
|
|
65
|
+
# Best-effort parse from origin remote.
|
|
66
|
+
rc, origin_url, _ = run_capture(["git", "remote", "get-url", "origin"])
|
|
67
|
+
if rc != 0:
|
|
68
|
+
rc, origin_url, _ = run_capture(["git", "config", "--get", "remote.origin.url"])
|
|
69
|
+
if rc != 0:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
url = (origin_url or "").strip()
|
|
73
|
+
if not url:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# Examples:
|
|
77
|
+
# - git@github.com:owner/repo.git
|
|
78
|
+
# - ssh://git@github.company.com/owner/repo.git
|
|
79
|
+
# - https://github.com/owner/repo.git
|
|
80
|
+
if url.startswith("git@"): # SCP-like syntax
|
|
81
|
+
# git@host:owner/repo(.git)
|
|
82
|
+
m = re.match(r"^git@([^:]+):", url)
|
|
83
|
+
return m.group(1) if m else None
|
|
84
|
+
|
|
85
|
+
if url.startswith("ssh://") or url.startswith("https://") or url.startswith("http://"):
|
|
86
|
+
try:
|
|
87
|
+
parsed = urlparse(url)
|
|
88
|
+
return parsed.hostname
|
|
89
|
+
except Exception:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
return None
|
|
50
93
|
|
|
51
94
|
|
|
52
95
|
def tail_text(path, max_lines=200, max_chars=12000):
|
|
@@ -101,9 +144,25 @@ def main():
|
|
|
101
144
|
print(json.dumps({"error": "NOT_A_GIT_REPO"}))
|
|
102
145
|
return 1
|
|
103
146
|
|
|
104
|
-
|
|
147
|
+
host = _detect_git_remote_host() or "github.com"
|
|
148
|
+
rc, gh_out, gh_err = run_capture(["gh", "auth", "status", "--hostname", host])
|
|
149
|
+
if rc == 127:
|
|
150
|
+
print(json.dumps({
|
|
151
|
+
"error": "GH_CLI_NOT_FOUND",
|
|
152
|
+
"detail": "gh not found in PATH",
|
|
153
|
+
"suggestion": "Install GitHub CLI: https://cli.github.com/",
|
|
154
|
+
}))
|
|
155
|
+
return 1
|
|
105
156
|
if rc != 0:
|
|
106
|
-
|
|
157
|
+
detail = (gh_err or gh_out or "").strip()
|
|
158
|
+
if len(detail) > 4000:
|
|
159
|
+
detail = detail[-4000:]
|
|
160
|
+
print(json.dumps({
|
|
161
|
+
"error": "GH_NOT_AUTHENTICATED",
|
|
162
|
+
"host": host,
|
|
163
|
+
"detail": detail,
|
|
164
|
+
"suggestion": f"Run: gh auth login --hostname {host}",
|
|
165
|
+
}))
|
|
107
166
|
return 1
|
|
108
167
|
|
|
109
168
|
rc, pr_json, _ = run_capture(["gh", "pr", "view", pr, "--json", "headRefName,baseRefName,mergeable"])
|