@ranger1/dx 0.1.28 → 0.1.29

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.
@@ -87,7 +87,6 @@ Round: 2
87
87
  要求:只依赖 prompt 中的 `fixFile`;不要重新拉取/生成评审意见。
88
88
 
89
89
  - 用 bash 读取 `fixFile`(例如 `cat "$fixFile"`)
90
- - 从 `## IssuesToFix` 中解析条目,按 `priority` 排序并按 `id` 去重
91
90
  - 解析失败则返回 `INVALID_FIX_FILE`
92
91
 
93
92
  ### 2. 逐项修复(No Scope Creep)
@@ -96,10 +95,6 @@ Round: 2
96
95
  - 每个修复必须能明确对应到原问题的 `id`
97
96
  - 无法修复时必须记录原因(例如:缺少上下文、超出本 PR 范围、需要产品决策、需要数据库迁移等)
98
97
 
99
- 执行前检查(强制):
100
-
101
- - 当前分支禁止是 `main`/`master`(应已由 pr-context 切到 PR 分支)
102
-
103
98
  ### 3. 提交策略
104
99
 
105
100
  - 强制:每个 findingId 单独一个提交(一个 findingId 对应一个 commit)
@@ -117,6 +112,7 @@ Round: 2
117
112
  - 不确定的问题降级为拒绝修复,并写清 `reason`(不要“猜”)
118
113
  - 修改尽量小:最小 diff、保持既有风格与约定
119
114
  - 修改项目里的json/jsonc文件的时候,使用python脚本进行修改,禁止手动拼接字符串,防止格式错误
115
+ - 修复完成之后,调用 dx lint 和 dx build all 确保编译通过
120
116
 
121
117
  ## 重要约束(强制)
122
118
 
@@ -15,8 +15,266 @@ tools:
15
15
 
16
16
  - `PR #<number>`
17
17
 
18
+ ## 一键脚本(推荐,省 token)
19
+
20
+ 把「环境/权限校验、PR 信息读取、checkout、base 分支 fetch、cache clear、lint+build、失败时写 fixFile、最终 JSON 输出」压到一次 `bash` 调用里执行。只有当脚本返回 merge 冲突相关错误时,才进入下面第 3 步做内容级合并。
21
+
22
+ 注意:脚本会把所有命令输出写入 `~/.opencode/cache/`,stdout 只打印最终单一 JSON。
23
+
24
+ ```bash
25
+ # 用法:把 PR 号填到 PR_NUMBER
26
+ PR_NUMBER=123 python3 - <<'PY'
27
+ import json
28
+ import os
29
+ import re
30
+ import secrets
31
+ import subprocess
32
+ from pathlib import Path
33
+
34
+
35
+ def run(cmd, *, cwd=None, stdout_path=None, stderr_path=None):
36
+ if stdout_path and stderr_path and stdout_path == stderr_path:
37
+ f = open(stdout_path, "wb")
38
+ try:
39
+ p = subprocess.run(cmd, cwd=cwd, stdout=f, stderr=f)
40
+ return p.returncode
41
+ finally:
42
+ f.close()
43
+
44
+ stdout_f = open(stdout_path, "wb") if stdout_path else subprocess.DEVNULL
45
+ stderr_f = open(stderr_path, "wb") if stderr_path else subprocess.DEVNULL
46
+ try:
47
+ p = subprocess.run(cmd, cwd=cwd, stdout=stdout_f, stderr=stderr_f)
48
+ return p.returncode
49
+ finally:
50
+ if stdout_path:
51
+ stdout_f.close()
52
+ if stderr_path:
53
+ stderr_f.close()
54
+
55
+
56
+ def run_capture(cmd, *, cwd=None):
57
+ p = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
58
+ return p.returncode, p.stdout, p.stderr
59
+
60
+
61
+ def tail_text(path, max_lines=200, max_chars=12000):
62
+ try:
63
+ data = Path(path).read_text(errors="replace")
64
+ except Exception:
65
+ return "(failed to read log)"
66
+ lines = data.splitlines()
67
+ tail = "\n".join(lines[-max_lines:])
68
+ if len(tail) > max_chars:
69
+ tail = tail[-max_chars:]
70
+ return tail
71
+
72
+
73
+ def first_file_line(text):
74
+ # Best-effort: match "path:line:col" or "path:line".
75
+ for m in re.finditer(r"^([^\s:]+\.[a-zA-Z0-9]+):(\d+)(?::(\d+))?\b", text, flags=re.M):
76
+ file = m.group(1)
77
+ line = int(m.group(2))
78
+ return file, line
79
+ return None, None
80
+
81
+
82
+ def write_fixfile(path, issues):
83
+ p = Path(path)
84
+ p.parent.mkdir(parents=True, exist_ok=True)
85
+ # Minimal schema for pr-fix parser.
86
+ out = ["## IssuesToFix", ""]
87
+ for it in issues:
88
+ out.append(f"- id: {it['id']}")
89
+ out.append(f" priority: {it['priority']}")
90
+ out.append(f" category: {it['category']}")
91
+ out.append(f" file: {it['file']}")
92
+ out.append(f" line: {it['line'] if it['line'] is not None else 'null'}")
93
+ out.append(f" title: {it['title']}")
94
+ # Keep as one line; caller should truncate.
95
+ desc = it["description"].replace("\n", "\\n")
96
+ sugg = it["suggestion"].replace("\n", "\\n")
97
+ out.append(f" description: {desc}")
98
+ out.append(f" suggestion: {sugg}")
99
+ p.write_text("\n".join(out) + "\n")
100
+
101
+
102
+ def main():
103
+ pr = os.environ.get("PR_NUMBER", "").strip()
104
+ if not pr.isdigit():
105
+ print(json.dumps({"error": "PR_NUMBER_NOT_PROVIDED"}))
106
+ return
107
+
108
+ # Step 1: must be in git repo.
109
+ rc, out, _ = run_capture(["git", "rev-parse", "--is-inside-work-tree"])
110
+ if rc != 0 or out.strip() != "true":
111
+ print(json.dumps({"error": "NOT_A_GIT_REPO"}))
112
+ return
113
+
114
+ # Step 1: gh auth.
115
+ rc = run(["gh", "auth", "status"]) # devnull
116
+ if rc != 0:
117
+ print(json.dumps({"error": "GH_NOT_AUTHENTICATED"}))
118
+ return
119
+
120
+ # Read PR info.
121
+ rc, pr_json, _ = run_capture(["gh", "pr", "view", pr, "--json", "headRefName,baseRefName,mergeable"])
122
+ if rc != 0:
123
+ print(json.dumps({"error": "PR_NOT_FOUND_OR_NO_ACCESS"}))
124
+ return
125
+ try:
126
+ pr_info = json.loads(pr_json)
127
+ except Exception:
128
+ print(json.dumps({"error": "PR_NOT_FOUND_OR_NO_ACCESS"}))
129
+ return
130
+
131
+ head = (pr_info.get("headRefName") or "").strip()
132
+ base = (pr_info.get("baseRefName") or "").strip()
133
+ mergeable = (pr_info.get("mergeable") or "").strip()
134
+
135
+ # Step 2: checkout PR branch if needed.
136
+ rc, cur_branch, _ = run_capture(["git", "rev-parse", "--abbrev-ref", "HEAD"])
137
+ if rc != 0:
138
+ print(json.dumps({"error": "PR_CHECKOUT_FAILED"}))
139
+ return
140
+ if head and cur_branch.strip() != head:
141
+ if run(["gh", "pr", "checkout", pr]) != 0:
142
+ print(json.dumps({"error": "PR_CHECKOUT_FAILED"}))
143
+ return
144
+
145
+ # Step 3 pre-req: resolve base ref.
146
+ if not base:
147
+ rc, out, _ = run_capture(["gh", "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"])
148
+ if rc == 0:
149
+ base = out.strip()
150
+ if not base:
151
+ print(json.dumps({"error": "PR_BASE_REF_NOT_FOUND"}))
152
+ return
153
+
154
+ # Fetch base.
155
+ if run(["git", "fetch", "origin", base]) != 0:
156
+ ok = False
157
+ for fallback in ("main", "master"):
158
+ if fallback == base:
159
+ continue
160
+ if run(["git", "fetch", "origin", fallback]) == 0:
161
+ base = fallback
162
+ ok = True
163
+ break
164
+ if not ok:
165
+ print(json.dumps({"error": "PR_BASE_REF_FETCH_FAILED"}))
166
+ return
167
+
168
+ # If mergeable reports conflict, ask agent to go to conflict-resolution step.
169
+ if mergeable == "CONFLICTING":
170
+ print(json.dumps({"error": "PR_MERGE_CONFLICTS_UNRESOLVED"}))
171
+ return
172
+
173
+ # Step 4: cache clear then lint + build (in parallel), logs to cache.
174
+ run_id = secrets.token_hex(4)
175
+ cache = Path.home() / ".opencode" / "cache"
176
+ cache.mkdir(parents=True, exist_ok=True)
177
+ cache_clear_log = cache / f"precheck-pr{pr}-{run_id}-cache-clear.log"
178
+ lint_log = cache / f"precheck-pr{pr}-{run_id}-lint.log"
179
+ build_log = cache / f"precheck-pr{pr}-{run_id}-build.log"
180
+ meta_log = cache / f"precheck-pr{pr}-{run_id}-meta.json"
181
+
182
+ # Keep meta for debugging (not printed to stdout).
183
+ meta_log.write_text(json.dumps({
184
+ "pr": int(pr),
185
+ "headRefName": head,
186
+ "baseRefName": base,
187
+ "mergeable": mergeable,
188
+ "cacheClearLog": str(cache_clear_log),
189
+ "lintLog": str(lint_log),
190
+ "buildLog": str(build_log),
191
+ }, indent=2) + "\n")
192
+
193
+ cache_rc = run(["dx", "cache", "clear"], stdout_path=str(cache_clear_log), stderr_path=str(cache_clear_log))
194
+ if cache_rc != 0:
195
+ fix_file = f"~/.opencode/cache/precheck-fix-pr{pr}-{run_id}.md"
196
+ fix_path = str(cache / f"precheck-fix-pr{pr}-{run_id}.md")
197
+ log_tail = tail_text(cache_clear_log)
198
+ issues = [{
199
+ "id": "PRE-001",
200
+ "priority": "P1",
201
+ "category": "quality",
202
+ "file": "<unknown>",
203
+ "line": None,
204
+ "title": "dx cache clear failed",
205
+ "description": log_tail,
206
+ "suggestion": f"Open log: {cache_clear_log}",
207
+ }]
208
+ write_fixfile(fix_path, issues)
209
+ print(json.dumps({"ok": False, "fixFile": fix_file}))
210
+ return
211
+
212
+ import threading
213
+
214
+ results = {}
215
+
216
+ def worker(name, cmd, log_path):
217
+ results[name] = run(cmd, stdout_path=str(log_path), stderr_path=str(log_path))
218
+
219
+ t1 = threading.Thread(target=worker, args=("lint", ["dx", "lint"], lint_log))
220
+ t2 = threading.Thread(target=worker, args=("build", ["dx", "build", "all"], build_log))
221
+ t1.start(); t2.start(); t1.join(); t2.join()
222
+
223
+ if results.get("lint", 1) == 0 and results.get("build", 1) == 0:
224
+ print(json.dumps({"ok": True}))
225
+ return
226
+
227
+ fix_file = f"~/.opencode/cache/precheck-fix-pr{pr}-{run_id}.md"
228
+ fix_path = str(cache / f"precheck-fix-pr{pr}-{run_id}.md")
229
+
230
+ issues = []
231
+ i = 1
232
+ if results.get("lint", 1) != 0:
233
+ log_tail = tail_text(lint_log)
234
+ file, line = first_file_line(log_tail)
235
+ issues.append({
236
+ "id": f"PRE-{i:03d}",
237
+ "priority": "P1",
238
+ "category": "lint",
239
+ "file": file or "<unknown>",
240
+ "line": line,
241
+ "title": "dx lint failed",
242
+ "description": log_tail,
243
+ "suggestion": f"Open log: {lint_log}",
244
+ })
245
+ i += 1
246
+ if results.get("build", 1) != 0:
247
+ log_tail = tail_text(build_log)
248
+ file, line = first_file_line(log_tail)
249
+ issues.append({
250
+ "id": f"PRE-{i:03d}",
251
+ "priority": "P0",
252
+ "category": "build",
253
+ "file": file or "<unknown>",
254
+ "line": line,
255
+ "title": "dx build all failed",
256
+ "description": log_tail,
257
+ "suggestion": f"Open log: {build_log}",
258
+ })
259
+
260
+ write_fixfile(fix_path, issues)
261
+ print(json.dumps({"ok": False, "fixFile": fix_file}))
262
+
263
+
264
+ if __name__ == "__main__":
265
+ try:
266
+ main()
267
+ except Exception:
268
+ # Keep stdout contract.
269
+ print(json.dumps({"error": "PRECHECK_SCRIPT_FAILED"}))
270
+
271
+ PY
272
+ ```
273
+
18
274
  ## 要做的事(按顺序)
19
275
 
276
+ 优先使用上面的「一键脚本」完成第 1/2/4/5 步;仅当脚本返回 merge 冲突相关错误时,再进入第 3 步进行内容级合并(完成后重跑脚本)。
277
+
20
278
  1. 校验环境/权限
21
279
 
22
280
  - 必须在 git 仓库内,否则输出 `{"error":"NOT_A_GIT_REPO"}`
@@ -68,8 +326,9 @@ tools:
68
326
 
69
327
  4. 预检:lint + build
70
328
 
329
+ - 运行 `dx cache clear`
71
330
  - 运行 `dx lint`
72
- - 运行 `dx build affected --dev -- --base=origin/<baseRefName> --head=HEAD`
331
+ - 运行 `dx build all`
73
332
 
74
333
  5. 若 lint/build 失败:生成 fixFile(Markdown)并返回失败
75
334
 
@@ -113,6 +113,10 @@ gh issue list --search "<关键词>" --limit 5
113
113
 
114
114
  #### 2.3 执行 Issue 创建
115
115
 
116
+ 使用命令查看现有标签
117
+ ```bash
118
+ gh label list --limit 20
119
+ ```
116
120
  使用 heredoc 格式执行 gh CLI:
117
121
 
118
122
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {