Compare commits

...

7 Commits

Author SHA1 Message Date
pzhang_zywl 500152510a test: agent_poller 新增 reopen-issue 命令 - Closes #61
CI / test (pull_request) Successful in 10s
2026-06-02 16:47:26 +08:00
pzhang_zywl 0d5bfa9276 Merge: resolve conflict in agent_poller.py
CI / test (push) Successful in 9s
2026-06-02 16:21:23 +08:00
pzhang_zywl eb2af77c90 Merge pull request 'fix: [test] blocked-check 将 API 错误误判为阻塞已解除 - Closes #58' (#60) from test/issue-58 into main
CI / test (push) Successful in 8s
2026-06-02 16:21:03 +08:00
pzhang_zywl eccaa28b1d test: blocked-check 用 _req_safe 替代 _req 避免 API 错误误判 - Closes #58
CI / test (pull_request) Successful in 12s
- 新增 _req_safe():API 错误返回 None 而非 sys.exit(1)
- blocked_check / _unblock_issues_blocked_by / _get_blocking_refs 改用 _req_safe
- API 失败时保守处理:保持 blocked 状态

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 16:20:12 +08:00
pzhang_zywl 2101a43b68 Merge pull request 'fix: [bug] IR 覆盖率回归:Layer B 从 92.6% 降至 63% + Layer A 新 schema 错误 - 来自 #18 - Closes #57' (#59) from dev/issue-57-fix-coverage-regression into main 2026-06-02 16:19:29 +08:00
pzhang_zywl 9f0872c36a Merge pull request 'fix: [bug] IR 覆盖率回归:Layer B 从 92.6% 降至 63% + Layer A 新 schema 错误 - 来自 #18 - Closes #57' (#59) from dev/issue-57-fix-coverage-regression into main
CI / test (push) Successful in 13s
2026-06-02 16:17:50 +08:00
pzhang_zywl d73da7cda9 test: blocked-check 用 _req_safe 替代 _req 避免 API 错误误判 - Closes #58
- 新增 _req_safe():API 错误返回 None 而非 sys.exit(1)
- blocked_check / _unblock_issues_blocked_by / _get_blocking_refs 改用 _req_safe
- API 失败时保守处理:保持 blocked 状态(不误解除)
- 验证:#18 正确识别被 #57 阻塞

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 16:17:39 +08:00
+52 -24
View File
@@ -56,6 +56,27 @@ def _req(method, path, data=None):
sys.exit(1) sys.exit(1)
def _req_safe(method, path, data=None):
"""Like _req but returns None on HTTPError instead of crashing.
Used for probing issue/PR existence where the caller can handle absence.
"""
url = f"{BASE}{path}"
payload = json.dumps(data).encode("utf-8") if data else None
req = urllib.request.Request(url, data=payload, method=method)
req.add_header("Authorization", f"token {GITEA_TOKEN}")
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req) as resp:
raw = resp.read()
if not raw:
return {}
return json.loads(raw)
except urllib.error.HTTPError as e:
body = e.read().decode()
print(f"API Error {e.code}: {body}", file=sys.stderr)
return None
# ── Issue operations ───────────────────────────────────────────────────────── # ── Issue operations ─────────────────────────────────────────────────────────
def list_issues(labels: list[str] | None = None): def list_issues(labels: list[str] | None = None):
@@ -82,17 +103,17 @@ def _get_blocking_refs(issue_num: int) -> set[int]:
""" """
refs: set[int] = set() refs: set[int] = set()
# Body # Body
issue = _req("GET", f"/issues/{issue_num}") issue = _req_safe("GET", f"/issues/{issue_num}")
if issue is None:
return refs # API error → return empty set, keep blocked
body = issue.get("body", "") or "" body = issue.get("body", "") or ""
refs.update(int(m.group(1)) for m in re.finditer(r'#(\d+)', body)) refs.update(int(m.group(1)) for m in re.finditer(r'#(\d+)', body))
# Comments # Comments
try: comments = _req_safe("GET", f"/issues/{issue_num}/comments")
comments = _req("GET", f"/issues/{issue_num}/comments") if comments:
for c in comments: for c in comments:
cbody = c.get("body", "") or "" cbody = c.get("body", "") or ""
refs.update(int(m.group(1)) for m in re.finditer(r'#(\d+)', cbody)) refs.update(int(m.group(1)) for m in re.finditer(r'#(\d+)', cbody))
except SystemExit:
pass
return refs return refs
@@ -103,12 +124,7 @@ def blocked_check():
If no references found or all referenced issues are closed, If no references found or all referenced issues are closed,
removes the 'blocked' label. removes the 'blocked' label.
""" """
try: all_blocked = _req_safe("GET", "/issues?state=open&labels=blocked")
all_blocked = _req("GET", "/issues?state=open&labels=blocked")
except SystemExit:
print("No blocked issues found.")
return
if not all_blocked: if not all_blocked:
print("No blocked issues found.") print("No blocked issues found.")
return return
@@ -119,13 +135,13 @@ def blocked_check():
all_resolved = True all_resolved = True
for blk in blocking_nums: for blk in blocking_nums:
try: blk_issue = _req_safe("GET", f"/issues/{blk}")
blk_issue = _req("GET", f"/issues/{blk}") if blk_issue is None:
all_resolved = False # API error → keep blocked
break
if blk_issue.get("state") != "closed": if blk_issue.get("state") != "closed":
all_resolved = False all_resolved = False
break break
except SystemExit:
pass
if all_resolved: if all_resolved:
current_label_names = [l["name"] for l in issue.get("labels", [])] current_label_names = [l["name"] for l in issue.get("labels", [])]
@@ -172,6 +188,15 @@ def close_issue(num, body=None):
return i return i
def reopen_issue(num, body=None):
"""Reopen a closed issue, optionally with a reason comment."""
if body:
comment_issue(num, f"## REOPEN\n\n{body}")
i = _req("PATCH", f"/issues/{num}", {"state": "open"})
print(f"Issue #{num} reopened")
return i
def _unblock_issues_blocked_by(closed_num): def _unblock_issues_blocked_by(closed_num):
"""Check issues blocked by *closed_num* and unblock if all blockers resolved. """Check issues blocked by *closed_num* and unblock if all blockers resolved.
@@ -179,10 +204,7 @@ def _unblock_issues_blocked_by(closed_num):
in any blocked issue and all referenced issues are now closed, in any blocked issue and all referenced issues are now closed,
removes the 'blocked' label and comments on the unblocked issue. removes the 'blocked' label and comments on the unblocked issue.
""" """
try: all_blocked = _req_safe("GET", "/issues?state=open&labels=blocked")
all_blocked = _req("GET", "/issues?state=open&labels=blocked")
except SystemExit:
return
if not all_blocked: if not all_blocked:
return return
@@ -196,13 +218,13 @@ def _unblock_issues_blocked_by(closed_num):
for blk in blocking_nums: for blk in blocking_nums:
if blk == closed_num: if blk == closed_num:
continue continue
try: blk_issue = _req_safe("GET", f"/issues/{blk}")
blk_issue = _req("GET", f"/issues/{blk}") if blk_issue is None:
all_resolved = False # API error → keep blocked
break
if blk_issue.get("state") != "closed": if blk_issue.get("state") != "closed":
all_resolved = False all_resolved = False
break break
except SystemExit:
pass # Inaccessible → treat as resolved
if all_resolved: if all_resolved:
current_label_names = [l["name"] for l in issue.get("labels", [])] current_label_names = [l["name"] for l in issue.get("labels", [])]
@@ -369,7 +391,8 @@ def main():
parser = argparse.ArgumentParser(description="Dev agent Gitea helper") parser = argparse.ArgumentParser(description="Dev agent Gitea helper")
parser.add_argument("--action", required=True, parser.add_argument("--action", required=True,
choices=["list", "get", "comment", "close-issue", choices=["list", "get", "comment", "close-issue",
"create-issue", "create-pr", "pr-status", "merge-pr", "lifecycle", "create-issue", "reopen-issue",
"create-pr", "pr-status", "merge-pr", "lifecycle",
"blocked-check"]) "blocked-check"])
parser.add_argument("--issue", type=int) parser.add_argument("--issue", type=int)
parser.add_argument("--pr", type=int) parser.add_argument("--pr", type=int)
@@ -407,6 +430,11 @@ def main():
print("--title is required for 'create-issue' action", file=sys.stderr) print("--title is required for 'create-issue' action", file=sys.stderr)
sys.exit(1) sys.exit(1)
create_issue(args.title, args.body, args.labels) create_issue(args.title, args.body, args.labels)
elif args.action == "reopen-issue":
if not args.issue:
print("--issue is required for 'reopen-issue' action", file=sys.stderr)
sys.exit(1)
reopen_issue(args.issue, args.body)
elif args.action == "create-pr": elif args.action == "create-pr":
if not args.issue or not args.branch: if not args.issue or not args.branch:
print("--issue and --branch are required for 'create-pr' action", file=sys.stderr) print("--issue and --branch are required for 'create-pr' action", file=sys.stderr)