chore: ruff format

This commit is contained in:
Soulter
2026-03-15 22:43:29 +08:00
parent d87cf897da
commit 420d82df11
2 changed files with 200 additions and 1 deletions
+4 -1
View File
@@ -164,7 +164,10 @@ class CreateSkillPayloadTool(NeoSkillToolBase):
"type": "object", "type": "object",
"properties": { "properties": {
"payload": { "payload": {
"anyOf": [{"type": "object"}, {"type": "array", "items": {"type": "object"}}], "anyOf": [
{"type": "object"},
{"type": "array", "items": {"type": "object"}},
],
"description": ( "description": (
"Skill payload JSON. Typical schema: {skill_markdown, inputs, outputs, meta}. " "Skill payload JSON. Typical schema: {skill_markdown, inputs, outputs, meta}. "
"This only stores content and returns payload_ref; it does not create a candidate or release." "This only stores content and returns payload_ref; it does not create a candidate or release."
@@ -0,0 +1,196 @@
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True)
class Issue:
number: int
title: str
created_at: datetime
url: str
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Close duplicate open plugin-publish issues while keeping the latest one."
)
)
parser.add_argument(
"--repo",
default="AstrBotDevs/AstrBot",
help="GitHub repository in owner/name format.",
)
parser.add_argument(
"--label",
default="plugin-publish",
help="Issue label to target.",
)
parser.add_argument(
"--limit",
type=int,
default=1000,
help="Maximum number of open issues to inspect.",
)
parser.add_argument(
"--apply",
action="store_true",
help="Actually close duplicate issues. Defaults to dry-run.",
)
return parser.parse_args()
def run_gh_command(args: list[str]) -> str:
try:
completed = subprocess.run(
args,
check=True,
capture_output=True,
text=True,
)
except FileNotFoundError as exc:
raise RuntimeError("GitHub CLI `gh` is not installed or not in PATH.") from exc
except subprocess.CalledProcessError as exc:
stderr = exc.stderr.strip()
stdout = exc.stdout.strip()
details = stderr or stdout or str(exc)
raise RuntimeError(f"`{' '.join(args)}` failed: {details}") from exc
return completed.stdout
def load_open_issues(repo: str, label: str, limit: int) -> list[Issue]:
output = run_gh_command(
[
"gh",
"issue",
"list",
"--repo",
repo,
"--label",
label,
"--state",
"open",
"--limit",
str(limit),
"--json",
"number,title,createdAt,url",
]
)
items = json.loads(output)
return [
Issue(
number=item["number"],
title=item["title"],
created_at=datetime.fromisoformat(item["createdAt"].replace("Z", "+00:00")),
url=item["url"],
)
for item in items
]
def normalize_title(title: str) -> str:
return " ".join(title.split()).strip()
def find_duplicates(
issues: list[Issue],
) -> list[tuple[Issue, list[Issue]]]:
grouped: dict[str, list[Issue]] = defaultdict(list)
for issue in issues:
grouped[normalize_title(issue.title)].append(issue)
duplicate_groups: list[tuple[Issue, list[Issue]]] = []
for group in grouped.values():
if len(group) < 2:
continue
ordered = sorted(
group,
key=lambda issue: (issue.created_at, issue.number),
reverse=True,
)
keep = ordered[0]
close_candidates = ordered[1:]
duplicate_groups.append((keep, close_candidates))
duplicate_groups.sort(
key=lambda item: (item[0].created_at, item[0].number),
reverse=True,
)
return duplicate_groups
def print_plan(duplicate_groups: list[tuple[Issue, list[Issue]]], apply: bool) -> None:
action = "Will close" if apply else "Would close"
if not duplicate_groups:
print("No duplicate open issues found.")
return
total_to_close = sum(len(close_list) for _, close_list in duplicate_groups)
print(f"Found {len(duplicate_groups)} duplicate title groups.")
print(
f"{action} {total_to_close} issues and keep {len(duplicate_groups)} latest issues."
)
for keep, close_list in duplicate_groups:
print()
print(f'Keep #{keep.number} [{keep.created_at.isoformat()}] "{keep.title}"')
print(f" {keep.url}")
for issue in close_list:
print(
f'Close #{issue.number} [{issue.created_at.isoformat()}] "{issue.title}"'
)
print(f" {issue.url}")
def close_duplicates(
repo: str, duplicate_groups: list[tuple[Issue, list[Issue]]]
) -> None:
for keep, close_list in duplicate_groups:
reason = (
f"Closing as duplicate of #{keep.number}. "
"Keeping the latest open issue with this title."
)
for issue in close_list:
print(f"Closing #{issue.number} as duplicate of #{keep.number}...")
run_gh_command(
[
"gh",
"issue",
"close",
str(issue.number),
"--repo",
repo,
"--comment",
reason,
]
)
def main() -> int:
args = parse_args()
try:
issues = load_open_issues(args.repo, args.label, args.limit)
duplicate_groups = find_duplicates(issues)
print_plan(duplicate_groups, apply=args.apply)
if args.apply and duplicate_groups:
print()
close_duplicates(args.repo, duplicate_groups)
print("Done.")
elif not args.apply:
print()
print("Dry-run only. Re-run with `--apply` to close the duplicates.")
except RuntimeError as exc:
print(str(exc), file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())