@ranger1/dx 0.1.90 → 0.1.92

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.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/lib/cli/commands/core.js +92 -0
  3. package/lib/cli/help.js +2 -1
  4. package/lib/codex-initial.js +19 -215
  5. package/package.json +2 -2
  6. package/skills/backend-layering-audit-fixer/SKILL.md +180 -0
  7. package/{codex/skills → skills}/doctor/SKILL.md +2 -9
  8. package/{codex/skills → skills}/doctor/scripts/doctor.sh +2 -253
  9. package/skills/git-pr-ship/SKILL.md +481 -0
  10. package/skills/naming-audit-fixer/SKILL.md +149 -0
  11. package/skills/naming-audit-fixer/references/fix-guide.md +93 -0
  12. package/skills/naming-audit-fixer/scripts/audit_naming.py +534 -0
  13. package/codex/agents/fixer.toml +0 -37
  14. package/codex/agents/orchestrator.toml +0 -11
  15. package/codex/agents/reviewer.toml +0 -52
  16. package/codex/agents/spark.toml +0 -18
  17. package/codex/skills/pr-review-loop/SKILL.md +0 -209
  18. package/codex/skills/pr-review-loop/agents/openai.yaml +0 -4
  19. package/codex/skills/pr-review-loop/references/agents/pr-context.md +0 -73
  20. package/codex/skills/pr-review-loop/references/agents/pr-precheck.md +0 -161
  21. package/codex/skills/pr-review-loop/references/agents/pr-review-aggregate.md +0 -188
  22. package/codex/skills/pr-review-loop/references/skill-layout.md +0 -25
  23. package/codex/skills/pr-review-loop/scripts/gh_review_harvest.py +0 -292
  24. package/codex/skills/pr-review-loop/scripts/pr_context.py +0 -351
  25. package/codex/skills/pr-review-loop/scripts/pr_review_aggregate.py +0 -951
  26. package/codex/skills/pr-review-loop/scripts/test_pr_review_aggregate.py +0 -876
  27. package/codex/skills/pr-review-loop/scripts/test_validate_reviewer_prompts.py +0 -92
  28. package/codex/skills/pr-review-loop/scripts/validate_reviewer_prompts.py +0 -87
  29. /package/{codex/skills → skills}/doctor/agents/openai.yaml +0 -0
  30. /package/{codex/skills → skills}/e2e-audit-fixer/SKILL.md +0 -0
  31. /package/{codex/skills → skills}/e2e-audit-fixer/agents/openai.yaml +0 -0
  32. /package/{codex/skills → skills}/e2e-audit-fixer/scripts/e2e_e2e_audit.py +0 -0
  33. /package/{codex/skills → skills}/env-accessor-audit-fixer/SKILL.md +0 -0
  34. /package/{codex/skills → skills}/env-accessor-audit-fixer/agents/openai.yaml +0 -0
  35. /package/{codex/skills → skills}/env-accessor-audit-fixer/references/bootstrap-env-foundation.md +0 -0
  36. /package/{codex/skills → skills}/env-accessor-audit-fixer/scripts/env_accessor_audit.py +0 -0
  37. /package/{codex/skills → skills}/error-handling-audit-fixer/SKILL.md +0 -0
  38. /package/{codex/skills → skills}/error-handling-audit-fixer/agents/openai.yaml +0 -0
  39. /package/{codex/skills → skills}/error-handling-audit-fixer/references/error-handling-standard.md +0 -0
  40. /package/{codex/skills → skills}/error-handling-audit-fixer/references/foundation-bootstrap.md +0 -0
  41. /package/{codex/skills → skills}/error-handling-audit-fixer/scripts/error_handling_audit.py +0 -0
  42. /package/{codex/skills → skills}/gh-dependabot-cleanup/SKILL.md +0 -0
  43. /package/{codex/skills → skills}/gh-dependabot-cleanup/agents/openai.yaml +0 -0
  44. /package/{codex/skills → skills}/git-commit-and-pr/SKILL.md +0 -0
  45. /package/{codex/skills → skills}/git-commit-and-pr/agents/openai.yaml +0 -0
  46. /package/{codex/skills → skills}/git-release/SKILL.md +0 -0
  47. /package/{codex/skills → skills}/git-release/agents/openai.yaml +0 -0
  48. /package/{codex/skills → skills}/online-debug-guard/SKILL.md +0 -0
  49. /package/{codex/skills → skills}/online-debug-guard/agents/openai.yaml +0 -0
  50. /package/{codex/skills → skills}/pagination-dto-audit-fixer/SKILL.md +0 -0
  51. /package/{codex/skills → skills}/pagination-dto-audit-fixer/agents/openai.yaml +0 -0
  52. /package/{codex/skills → skills}/pagination-dto-audit-fixer/references/pagination-standard.md +0 -0
  53. /package/{codex/skills → skills}/pagination-dto-audit-fixer/scripts/pagination_dto_audit.py +0 -0
@@ -1,351 +0,0 @@
1
- #!/usr/bin/env python3
2
- # PR context builder (deterministic).
3
- # - Reads PR metadata + recent comments via gh
4
- # - Reads changed files via git diff (no patch)
5
- # - Writes Markdown context file to project cache: ./.cache/
6
- # - Prints exactly one JSON object to stdout
7
-
8
- import argparse
9
- import json
10
- import os
11
- import re
12
- import subprocess
13
- import sys
14
- from urllib.parse import urlparse
15
- from pathlib import Path
16
-
17
-
18
- def _repo_root():
19
- # Prefer git top-level so the cache follows the current repo.
20
- try:
21
- p = subprocess.run(
22
- ["git", "rev-parse", "--show-toplevel"],
23
- stdout=subprocess.PIPE,
24
- stderr=subprocess.DEVNULL,
25
- text=True,
26
- )
27
- out = (p.stdout or "").strip()
28
- if p.returncode == 0 and out:
29
- return Path(out)
30
- except Exception:
31
- pass
32
- return Path.cwd()
33
-
34
-
35
- def _cache_dir(repo_root):
36
- return (repo_root / ".cache").resolve()
37
-
38
-
39
- def _repo_relpath(repo_root, p):
40
- try:
41
- rel = p.resolve().relative_to(repo_root.resolve())
42
- return "./" + rel.as_posix()
43
- except Exception:
44
- # Fallback to basename-only.
45
- return os.path.basename(str(p))
46
-
47
-
48
- REPO_ROOT = _repo_root()
49
- CACHE_DIR = _cache_dir(REPO_ROOT)
50
- MARKER_SUBSTR = "<!-- pr-review-loop-marker"
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 _detect_git_remote_host():
67
- # Best-effort parse from origin remote.
68
- rc, origin_url, _ = _run_capture(["git", "remote", "get-url", "origin"])
69
- if rc != 0:
70
- rc, origin_url, _ = _run_capture(["git", "config", "--get", "remote.origin.url"])
71
- if rc != 0:
72
- return None
73
-
74
- url = (origin_url or "").strip()
75
- if not url:
76
- return None
77
-
78
- # Examples:
79
- # - git@github.com:owner/repo.git
80
- # - ssh://git@github.company.com/owner/repo.git
81
- # - https://github.com/owner/repo.git
82
- if url.startswith("git@"): # SCP-like syntax
83
- m = re.match(r"^git@([^:]+):", url)
84
- return m.group(1) if m else None
85
-
86
- if url.startswith("ssh://") or url.startswith("https://") or url.startswith("http://"):
87
- try:
88
- parsed = urlparse(url)
89
- return parsed.hostname
90
- except Exception:
91
- return None
92
-
93
- return None
94
-
95
-
96
- def _clip(s, n):
97
- if s is None:
98
- return ""
99
- s = str(s)
100
- return s if len(s) <= n else (s[:n] + "...")
101
-
102
-
103
- def _safe_basename(name):
104
- if not name:
105
- return None
106
- base = os.path.basename(name.strip())
107
- if base != name.strip():
108
- return None
109
- if base in (".", ".."):
110
- return None
111
- return base
112
-
113
-
114
- def _git_fetch_origin(ref):
115
- subprocess.run(["git", "fetch", "origin", ref], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
116
-
117
-
118
- def _gh_default_branch(owner_repo):
119
- if not owner_repo:
120
- return ""
121
- rc, out, _ = _run_capture(
122
- [
123
- "gh",
124
- "repo",
125
- "view",
126
- "--repo",
127
- owner_repo,
128
- "--json",
129
- "defaultBranchRef",
130
- "--jq",
131
- ".defaultBranchRef.name",
132
- ]
133
- )
134
- if rc != 0:
135
- return ""
136
- return (out or "").strip()
137
-
138
-
139
- def _git_numstat(base_ref, base_oid):
140
- # Prefer commit-oid diff when available; it's unambiguous for stacked PRs.
141
- candidates = []
142
- if base_oid:
143
- candidates.append(f"{base_oid}...HEAD")
144
- if base_ref:
145
- candidates.append(f"origin/{base_ref}...HEAD")
146
- candidates.append(f"{base_ref}...HEAD")
147
-
148
- for lhs in candidates:
149
- rc, out, _ = _run_capture(["git", "diff", "--numstat", lhs])
150
- if rc == 0:
151
- return out
152
- return ""
153
-
154
-
155
- def _parse_numstat(numstat_text):
156
- rows = []
157
- for line in (numstat_text or "").splitlines():
158
- parts = line.split("\t")
159
- if len(parts) < 3:
160
- continue
161
- add_s, del_s, path = parts[0].strip(), parts[1].strip(), parts[2].strip()
162
- if not path:
163
- continue
164
- rows.append((add_s, del_s, path))
165
- return rows
166
-
167
-
168
- def main(argv):
169
- class _ArgParser(argparse.ArgumentParser):
170
- def error(self, message):
171
- raise ValueError(message)
172
-
173
- parser = _ArgParser(add_help=False)
174
- parser.add_argument("--pr", type=int, required=True)
175
- parser.add_argument("--round", type=int, default=1)
176
- try:
177
- args = parser.parse_args(argv)
178
- except ValueError:
179
- _json_out({"error": "INVALID_ARGS"})
180
- return 2
181
-
182
- pr_number = int(args.pr)
183
- round_num = int(args.round)
184
-
185
- def _json_err(error_code, extra=None):
186
- obj = {"error": error_code, "prNumber": pr_number, "round": round_num}
187
- if isinstance(extra, dict) and extra:
188
- obj.update(extra)
189
- _json_out(obj)
190
-
191
- # Preconditions: be in a git repo and gh is authenticated.
192
- rc, out, _ = _run_capture(["git", "rev-parse", "--is-inside-work-tree"])
193
- if rc != 0 or out.strip() != "true":
194
- _json_err("NOT_A_GIT_REPO")
195
- return 1
196
-
197
- host = _detect_git_remote_host() or "github.com"
198
- rc, gh_out, gh_err = _run_capture(["gh", "auth", "status", "--hostname", host])
199
- if rc == 127:
200
- _json_err(
201
- "GH_CLI_NOT_FOUND",
202
- {
203
- "detail": "gh not found in PATH",
204
- "suggestion": "Install GitHub CLI: https://cli.github.com/",
205
- },
206
- )
207
- return 1
208
- if rc != 0:
209
- # If host detection is wrong, a global check might still succeed.
210
- rc2, gh_out2, gh_err2 = _run_capture(["gh", "auth", "status"])
211
- if rc2 == 0:
212
- pass
213
- else:
214
- detail = (gh_err or gh_out or "").strip()
215
- if len(detail) > 4000:
216
- detail = detail[-4000:]
217
- _json_err(
218
- "GH_NOT_AUTHENTICATED",
219
- {
220
- "host": host,
221
- "detail": detail,
222
- "suggestion": f"Run: gh auth login --hostname {host}",
223
- },
224
- )
225
- return 1
226
-
227
- rc, owner_repo, _ = _run_capture(["gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])
228
- owner_repo = owner_repo.strip() if rc == 0 else ""
229
- if not owner_repo:
230
- _json_err("REPO_NOT_FOUND")
231
- return 1
232
-
233
- fields = "number,url,title,body,isDraft,labels,baseRefName,headRefName,baseRefOid,headRefOid,comments"
234
- rc, pr_json, _ = _run_capture(["gh", "pr", "view", str(pr_number), "--repo", owner_repo, "--json", fields])
235
- if rc != 0:
236
- _json_err("PR_NOT_FOUND_OR_NO_ACCESS")
237
- return 1
238
- try:
239
- pr = json.loads(pr_json)
240
- except Exception:
241
- _json_err("PR_NOT_FOUND_OR_NO_ACCESS")
242
- return 1
243
-
244
- head_oid = (pr.get("headRefOid") or "").strip()
245
- base_oid = (pr.get("baseRefOid") or "").strip()
246
- base_ref = (pr.get("baseRefName") or "").strip()
247
-
248
- if not head_oid:
249
- _json_err("PR_HEAD_OID_NOT_FOUND")
250
- return 1
251
-
252
- head_short = head_oid[:7]
253
- if not base_ref:
254
- base_ref = _gh_default_branch(owner_repo)
255
- if not base_ref and not base_oid:
256
- _json_err("PR_BASE_REF_NOT_FOUND")
257
- return 1
258
- head_ref = (pr.get("headRefName") or "").strip()
259
- url = (pr.get("url") or "").strip()
260
-
261
- run_id = f"{pr_number}-{round_num}-{head_short}"
262
-
263
- if base_ref:
264
- _git_fetch_origin(base_ref)
265
- file_rows = _parse_numstat(_git_numstat(base_ref, base_oid))
266
-
267
- labels = []
268
- for l in (pr.get("labels") or []):
269
- if isinstance(l, dict) and l.get("name"):
270
- labels.append(str(l.get("name")))
271
-
272
- comments = pr.get("comments") or []
273
- recent = comments[-10:] if isinstance(comments, list) else []
274
- marker_count = 0
275
- for c in recent:
276
- if not isinstance(c, dict):
277
- continue
278
- body = c.get("body") or ""
279
- if isinstance(body, str) and MARKER_SUBSTR in body:
280
- marker_count += 1
281
-
282
- CACHE_DIR.mkdir(parents=True, exist_ok=True)
283
- context_file = f"pr-context-pr{pr_number}-r{round_num}-{run_id}.md"
284
- context_path = CACHE_DIR / context_file
285
-
286
- with open(context_path, "w", encoding="utf-8", newline="\n") as fp:
287
- fp.write("# PR Context\n\n")
288
- fp.write(f"- Repo: {owner_repo}\n")
289
- fp.write(f"- PR: #{pr_number} {url}\n")
290
- fp.write(f"- Round: {round_num}\n")
291
- fp.write(f"- RunId: {run_id}\n")
292
- fp.write(f"- Base: {base_ref}\n")
293
- fp.write(f"- Head: {head_ref}\n")
294
- fp.write(f"- HeadShort: {head_short}\n")
295
- fp.write(f"- HeadOid: {head_oid}\n")
296
- fp.write(f"- Draft: {pr.get('isDraft')}\n")
297
- fp.write(f"- Labels: {', '.join(labels) if labels else '(none)'}\n")
298
- fp.write(f"- ExistingLoopMarkers: {marker_count}\n\n")
299
-
300
- fp.write("## Title\n\n")
301
- fp.write(_clip(pr.get("title") or "", 200) + "\n\n")
302
-
303
- fp.write("## Body (excerpt)\n\n")
304
- fp.write(_clip(pr.get("body") or "", 2000) or "(empty)")
305
- fp.write("\n\n")
306
-
307
- fp.write(f"## Changed Files ({len(file_rows)})\n\n")
308
- if file_rows:
309
- for add_s, del_s, path in file_rows:
310
- fp.write(f"- +{add_s} -{del_s} {path}\n")
311
- else:
312
- fp.write("(none)\n")
313
- fp.write("\n")
314
-
315
- fp.write("## Recent Comments (excerpt)\n\n")
316
- if recent:
317
- for c in recent:
318
- if not isinstance(c, dict):
319
- continue
320
- author = None
321
- if isinstance(c.get("author"), dict):
322
- author = (c.get("author") or {}).get("login")
323
- fp.write(f"- {author or 'unknown'}: {_clip(c.get('body') or '', 300)}\n")
324
- else:
325
- fp.write("(none)\n")
326
-
327
- _json_out(
328
- {
329
- "agent": "pr-context",
330
- "prNumber": pr_number,
331
- "round": round_num,
332
- "runId": run_id,
333
- "repo": {"nameWithOwner": owner_repo},
334
- "headOid": head_oid,
335
- "headShort": head_short,
336
- "existingMarkerCount": marker_count,
337
- # Handoff should be repo-relative path so downstream agents can read it directly.
338
- "contextFile": _repo_relpath(REPO_ROOT, context_path),
339
- }
340
- )
341
- return 0
342
-
343
-
344
- if __name__ == "__main__":
345
- try:
346
- raise SystemExit(main(sys.argv[1:]))
347
- except SystemExit:
348
- raise
349
- except Exception:
350
- _json_out({"error": "PR_CONTEXT_SCRIPT_FAILED"})
351
- raise SystemExit(1)