@ranger1/dx 0.1.32 → 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 +24 -0
- package/@opencode/agents/pr-precheck.md +24 -0
- 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
|
|
@@ -44,3 +44,27 @@ python3 ~/.opencode/agents/pr_context.py --pr <PR_NUMBER> --round <ROUND>
|
|
|
44
44
|
- **失败/异常时**:
|
|
45
45
|
- 若脚本 stdout 已输出合法 JSON(包含 `error` 或其他字段)→ 仍然**原样返回该 JSON**。
|
|
46
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
|
+
```
|
|
@@ -38,6 +38,30 @@ python3 ~/.opencode/agents/pr_precheck.py <PR_NUMBER>
|
|
|
38
38
|
- 若脚本 stdout 已输出合法 JSON(包含 `error` 或其他字段)→ 仍然**原样返回该 JSON**。
|
|
39
39
|
- 若脚本未输出合法 JSON / 退出异常 → 仅输出一行 JSON:`{"error":"PR_PRECHECK_AGENT_FAILED"}`(必要时可加 `detail` 字段)。
|
|
40
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
|
+
|
|
41
65
|
## 仅当出现 merge 冲突时怎么处理
|
|
42
66
|
|
|
43
67
|
当脚本输出 `{"error":"PR_MERGE_CONFLICTS_UNRESOLVED"}` 时:
|
|
@@ -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"])
|