Add: windows mvp - transparent bugs not fixed
This commit is contained in:
127
scripts/qa_validate.py
Normal file
127
scripts/qa_validate.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate issue QA workflow artifacts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ISSUES_DIR = ROOT / "issues"
|
||||
|
||||
REQUIRED_SECTIONS = (
|
||||
"Title",
|
||||
"Severity",
|
||||
"Environment",
|
||||
"Summary",
|
||||
"Reproduction Steps",
|
||||
"Expected Result",
|
||||
"Actual Result",
|
||||
"Root Cause Analysis",
|
||||
"Fix Plan",
|
||||
"Implementation Notes",
|
||||
"Verification",
|
||||
"Status History",
|
||||
"Closure",
|
||||
)
|
||||
|
||||
CLOSED_STATES = {"Verification Passed", "Closed"}
|
||||
|
||||
|
||||
def issue_files() -> list[Path]:
|
||||
return sorted(ISSUES_DIR.glob("issue[0-9]*.md"))
|
||||
|
||||
|
||||
def has_section(content: str, name: str) -> bool:
|
||||
pattern = rf"(?m)^##\s+{re.escape(name)}\s*$"
|
||||
return bool(re.search(pattern, content))
|
||||
|
||||
|
||||
def current_status(content: str) -> str | None:
|
||||
match = re.search(r"(?m)^- Current Status:\s*`?([^`\n]+)`?\s*$", content)
|
||||
return match.group(1).strip() if match else None
|
||||
|
||||
|
||||
def screenshot_refs(content: str) -> list[str]:
|
||||
pattern = r"issues/screenshots/[A-Za-z0-9._-]+\.png"
|
||||
return sorted(set(re.findall(pattern, content)))
|
||||
|
||||
|
||||
def has_before_ref(refs: list[str], issue_num: str) -> bool:
|
||||
legacy = f"issues/screenshots/issue{issue_num}.png"
|
||||
return any("-before-" in ref for ref in refs) or legacy in refs
|
||||
|
||||
|
||||
def has_after_ref(refs: list[str]) -> bool:
|
||||
return any("-after-" in ref for ref in refs)
|
||||
|
||||
|
||||
def command_check_present(content: str) -> bool:
|
||||
required = (
|
||||
"`cargo check --workspace`",
|
||||
"`cargo test --workspace`",
|
||||
"`just qa-validate`",
|
||||
)
|
||||
return all(token in content for token in required)
|
||||
|
||||
|
||||
def verify_issue(path: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
content = path.read_text(encoding="utf-8")
|
||||
issue_num_match = re.search(r"issue([0-9]+)\.md$", path.name)
|
||||
issue_num = issue_num_match.group(1) if issue_num_match else "?"
|
||||
|
||||
for section in REQUIRED_SECTIONS:
|
||||
if not has_section(content, section):
|
||||
errors.append(f"{path}: missing section '## {section}'")
|
||||
|
||||
refs = screenshot_refs(content)
|
||||
for ref in refs:
|
||||
if not (ROOT / ref).exists():
|
||||
errors.append(f"{path}: missing screenshot file: {ref}")
|
||||
|
||||
if not has_before_ref(refs, issue_num):
|
||||
errors.append(
|
||||
f"{path}: missing before screenshot reference "
|
||||
"(use issueN-before-...png or legacy issueN.png)"
|
||||
)
|
||||
|
||||
status = current_status(content)
|
||||
if status in CLOSED_STATES and not has_after_ref(refs):
|
||||
errors.append(
|
||||
f"{path}: status '{status}' requires at least one after screenshot reference"
|
||||
)
|
||||
|
||||
if status in CLOSED_STATES and not command_check_present(content):
|
||||
errors.append(
|
||||
f"{path}: status '{status}' requires command checklist entries for "
|
||||
"cargo check, cargo test, and just qa-validate"
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
files = issue_files()
|
||||
if not files:
|
||||
print("error: no issue files found under issues/", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
failures: list[str] = []
|
||||
for path in files:
|
||||
failures.extend(verify_issue(path))
|
||||
|
||||
if failures:
|
||||
for failure in failures:
|
||||
print(f"error: {failure}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"qa validation passed ({len(files)} issue file(s) checked)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Reference in New Issue
Block a user