194 lines
6.5 KiB
Python
194 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the public shortcut catalog from real test results."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
READ_PATH = ROOT / "docs" / "shortcut-real-read-results.json"
|
|
WRITE_PATH = ROOT / "docs" / "shortcut-real-write-results.json"
|
|
GO_PATH = ROOT / "internal" / "shortcut" / "public_catalog_generated.go"
|
|
CATALOG_PATH = ROOT / "docs" / "shortcut-public-catalog.json"
|
|
FOLLOWUP_MD_PATH = ROOT / "docs" / "shortcut-real-test-followups.md"
|
|
FOLLOWUP_JSON_PATH = ROOT / "docs" / "shortcut-real-test-followups.json"
|
|
|
|
|
|
def load(path: Path) -> dict[str, Any]:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def squish(text: str, limit: int = 180) -> str:
|
|
text = re.sub(r"\s+", " ", text or "").strip()
|
|
if len(text) <= limit:
|
|
return text
|
|
return text[: limit - 1] + "…"
|
|
|
|
|
|
def error_text(row: dict[str, Any]) -> str:
|
|
return f"{row.get('stderr') or ''}\n{row.get('stdout') or ''}"
|
|
|
|
|
|
def parse_error_payload(row: dict[str, Any]) -> dict[str, Any]:
|
|
for key in ("stderr", "stdout"):
|
|
raw = (row.get(key) or "").strip()
|
|
if not raw:
|
|
continue
|
|
try:
|
|
value = json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
if isinstance(value, dict):
|
|
err = value.get("error")
|
|
if isinstance(err, dict):
|
|
return err
|
|
return value
|
|
return {}
|
|
|
|
|
|
def evidence(row: dict[str, Any]) -> str:
|
|
payload = parse_error_payload(row)
|
|
msg = payload.get("message")
|
|
if isinstance(msg, str) and msg.strip():
|
|
return squish(msg, 220)
|
|
return squish(error_text(row), 220)
|
|
|
|
|
|
def collect() -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
public: list[dict[str, Any]] = []
|
|
followups: list[dict[str, Any]] = []
|
|
for suite, path in (("read", READ_PATH), ("write", WRITE_PATH)):
|
|
data = load(path)
|
|
for row in data.get("results", []):
|
|
item = {
|
|
"suite": suite,
|
|
"service": row.get("service") or "",
|
|
"command": row.get("command") or "",
|
|
"risk": row.get("risk") or "",
|
|
"status": row.get("status") or "",
|
|
}
|
|
if row.get("status") == "real-ok":
|
|
public.append(item)
|
|
continue
|
|
followups.append({
|
|
**item,
|
|
"failure_category": row.get("failure_category") or "unclassified",
|
|
"fixability": row.get("fixability") or "",
|
|
"diagnosis": row.get("diagnosis") or "",
|
|
"evidence": evidence(row),
|
|
})
|
|
public.sort(key=lambda r: (r["service"], r["command"]))
|
|
followups.sort(key=lambda r: (r["suite"], r["service"], r["command"]))
|
|
return public, followups
|
|
|
|
|
|
def go_quote(s: str) -> str:
|
|
return json.dumps(s, ensure_ascii=False)
|
|
|
|
|
|
def write_go(rows: list[dict[str, Any]]) -> None:
|
|
lines = [
|
|
"// Code generated by scripts/gen_shortcut_public_catalog.py; DO NOT EDIT.",
|
|
"",
|
|
"package shortcut",
|
|
"",
|
|
"// publicShortcutCatalog contains the generated shortcut surface used by",
|
|
"// command discovery and skill generation.",
|
|
"func generatedPublicShortcutCatalog() map[string]struct{} {",
|
|
"\treturn map[string]struct{}{",
|
|
]
|
|
for row in rows:
|
|
key = f"{row['service']}\x00{row['command']}"
|
|
lines.append(f"\t\t{go_quote(key)}: {{}},")
|
|
lines.extend(["\t}", "}", ""])
|
|
GO_PATH.write_text("\n".join(lines), encoding="utf-8")
|
|
subprocess.run(["gofmt", "-w", str(GO_PATH)], check=True)
|
|
|
|
|
|
def md_escape(s: str) -> str:
|
|
return str(s).replace("|", "\\|").replace("\n", "<br>")
|
|
|
|
|
|
def write_followup_md(rows: list[dict[str, Any]]) -> None:
|
|
generated = dt.datetime.now().isoformat(timespec="seconds")
|
|
lines = [
|
|
"# Shortcut 真实测试跟进清单",
|
|
"",
|
|
f"生成时间:`{generated}`",
|
|
"",
|
|
"来源:`docs/shortcut-real-read-results.json` 与 `docs/shortcut-real-write-results.json`。",
|
|
"",
|
|
"口径:记录真实后端测试中需要继续定位的 case,用于 CR 和问题分派;Agent 使用入口以公开 shortcut catalog 和产品 skill 为准。",
|
|
"",
|
|
f"总计:{len(rows)} 条。",
|
|
"",
|
|
"| # | suite | shortcut | risk | status | category | fixability | 处理依据 |",
|
|
"|---:|---|---|---|---|---|---|---|",
|
|
]
|
|
for idx, row in enumerate(rows, 1):
|
|
shortcut = f"{row['service']} {row['command']}"
|
|
basis = row["diagnosis"] or row["evidence"]
|
|
lines.append(
|
|
f"| {idx} | {md_escape(row['suite'])} | `{md_escape(shortcut)}` | "
|
|
f"{md_escape(row['risk'])} | {md_escape(row['status'])} | "
|
|
f"{md_escape(row['failure_category'])} | {md_escape(row['fixability'])} | "
|
|
f"{md_escape(squish(basis, 160))} |"
|
|
)
|
|
lines.append("")
|
|
rendered = "\n".join(lines)
|
|
if FOLLOWUP_MD_PATH.exists():
|
|
existing = FOLLOWUP_MD_PATH.read_text(encoding="utf-8")
|
|
without_time = re.compile(r"生成时间:`[^`]+`")
|
|
if without_time.sub("生成时间:`<generated>`", existing) == without_time.sub(
|
|
"生成时间:`<generated>`", rendered
|
|
):
|
|
return
|
|
FOLLOWUP_MD_PATH.write_text(rendered, encoding="utf-8")
|
|
|
|
|
|
def write_json(public: list[dict[str, Any]], followups: list[dict[str, Any]]) -> None:
|
|
generated = dt.datetime.now().isoformat()
|
|
write_json_if_changed(CATALOG_PATH, {
|
|
"count": len(public),
|
|
"results": public,
|
|
}, generated)
|
|
write_json_if_changed(FOLLOWUP_JSON_PATH, {
|
|
"count": len(followups),
|
|
"results": followups,
|
|
}, generated)
|
|
|
|
|
|
def write_json_if_changed(path: Path, payload: dict[str, Any], generated: str) -> None:
|
|
if path.exists():
|
|
existing = load(path)
|
|
existing.pop("generated_at", None)
|
|
if existing == payload:
|
|
return
|
|
path.write_text(json.dumps({
|
|
"generated_at": generated,
|
|
**payload,
|
|
}, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
public, followups = collect()
|
|
write_go(public)
|
|
write_followup_md(followups)
|
|
write_json(public, followups)
|
|
print(f"public={len(public)} followups={len(followups)}")
|
|
print(f"written: {GO_PATH}")
|
|
print(f"written: {CATALOG_PATH}")
|
|
print(f"written: {FOLLOWUP_MD_PATH}")
|
|
print(f"written: {FOLLOWUP_JSON_PATH}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|