Private
Public Access
feat(scripts): add --summary and --by-size modes to exception_handling audit
This commit is contained in:
@@ -43,11 +43,21 @@ the fix could look like (e.g., "return Result(data=NIL_T, errors=[...])").
|
||||
|
||||
Usage:
|
||||
uv run python scripts/audit_exception_handling.py # human report
|
||||
uv run python python scripts/audit_exception_handling.py --json # JSON output
|
||||
uv run python python scripts/audit_exception_handling.py --src src
|
||||
uv run python python scripts/audit_exception_handling.py --top 20
|
||||
uv run python python scripts/audit_exception_handling.py --verbose
|
||||
uv run python python scripts/audit_exception_handling.py --strict
|
||||
uv run python scripts/audit_exception_handling.py --json # JSON output
|
||||
uv run python scripts/audit_exception_handling.py --src src # source dir
|
||||
uv run python scripts/audit_exception_handling.py --top 20 # top N files
|
||||
uv run python scripts/audit_exception_handling.py --verbose # every site
|
||||
uv run python scripts/audit_exception_handling.py --strict # exit 1 on violation
|
||||
uv run python scripts/audit_exception_handling.py --summary # per-file summary table
|
||||
uv run python scripts/audit_exception_handling.py --by-size # group by migration effort
|
||||
|
||||
Output modes (mutually exclusive; --json / --summary / --by-size override
|
||||
the default human-readable report):
|
||||
--summary: per-file table sorted by V+S descending. Use this for
|
||||
"which files have the most violations" planning questions.
|
||||
--by-size: groups files into small/medium/large/baseline buckets.
|
||||
Use this for "how many migration tracks do I need" planning.
|
||||
(default): top-N files with per-site breakdown and 1-line hints.
|
||||
|
||||
Exit codes:
|
||||
0 - audit ran (informational mode; findings don't fail the script)
|
||||
@@ -734,6 +744,90 @@ def render_json(reports: list[FileReport], files_scanned: int, top: int, verbose
|
||||
return json.dumps(output, indent=2)
|
||||
|
||||
|
||||
def render_summary(reports: list[FileReport], files_scanned: int) -> str:
|
||||
"""Per-file summary table. Used for planning migration tracks.
|
||||
|
||||
Columns: file, total, V (violations), S (suspicious), ? (unclear), C (compliant).
|
||||
Sorted by V+S descending so the highest-impact files are at the top.
|
||||
"""
|
||||
lines: list[str] = []
|
||||
lines.append("=== Exception Handling Audit: Per-File Summary ===\n")
|
||||
lines.append(f"Files scanned: {files_scanned}")
|
||||
lines.append(f"Files with findings: {len(reports)}\n")
|
||||
lines.append(f"{'file':<38} {'total':>6} {'V':>5} {'S':>5} {'?':>4} {'C':>5} baseline?")
|
||||
lines.append("-" * 90)
|
||||
for f in sorted(reports, key=lambda r: -(r.violation_count + r.suspicious_count)):
|
||||
total = f.violation_count + f.suspicious_count + f.unclear_count + f.compliant_count
|
||||
if total == 0:
|
||||
continue
|
||||
name = f.filename.replace("src/", "").replace("\\", "/")
|
||||
base = "*BASELINE*" if f.is_refactored_baseline else ""
|
||||
lines.append(f"{name:<38} {total:>6} {f.violation_count:>5} {f.suspicious_count:>5} {f.unclear_count:>4} {f.compliant_count:>5} {base}")
|
||||
lines.append("-" * 90)
|
||||
total_v = sum(r.violation_count for r in reports)
|
||||
total_s = sum(r.suspicious_count for r in reports)
|
||||
total_u = sum(r.unclear_count for r in reports)
|
||||
total_c = sum(r.compliant_count for r in reports)
|
||||
lines.append(f"{'TOTAL':<38} {total_v + total_s + total_u + total_c:>6} {total_v:>5} {total_s:>5} {total_u:>4} {total_c:>5}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_by_size(reports: list[FileReport], files_scanned: int) -> str:
|
||||
"""Group files by violation+suspicious count bucket for migration planning.
|
||||
|
||||
Buckets: small (<=5), medium (6-15), large (>=16). Plus the 3 refactored
|
||||
baseline files as a separate bucket (the convention reference; remaining
|
||||
gaps should be closed to make them pure compliant).
|
||||
"""
|
||||
lines: list[str] = []
|
||||
lines.append("=== Exception Handling Audit: Files Grouped by Migration Effort ===\n")
|
||||
lines.append(f"Files scanned: {files_scanned}")
|
||||
lines.append(f"Files with findings: {len(reports)}\n")
|
||||
|
||||
baseline = [r for r in reports if r.is_refactored_baseline]
|
||||
large = [r for r in reports if not r.is_refactored_baseline and r.violation_count + r.suspicious_count >= 16]
|
||||
medium = [r for r in reports if not r.is_refactored_baseline and 6 <= r.violation_count + r.suspicious_count <= 15]
|
||||
small = [r for r in reports if not r.is_refactored_baseline and r.violation_count + r.suspicious_count <= 5]
|
||||
|
||||
def _bucket(name: str, files: list[FileReport], note: str) -> None:
|
||||
if not files:
|
||||
return
|
||||
v = sum(r.violation_count for r in files)
|
||||
s = sum(r.suspicious_count for r in files)
|
||||
u = sum(r.unclear_count for r in files)
|
||||
c = sum(r.compliant_count for r in files)
|
||||
total = v + s + u + c
|
||||
lines.append(f"--- {name} ({len(files)} files, V+S={v+s}, V={v}, S={s}, ?={u}, C={c}, total={total}) ---")
|
||||
if note:
|
||||
lines.append(f" {note}")
|
||||
for r in sorted(files, key=lambda x: -(x.violation_count + x.suspicious_count)):
|
||||
name = r.filename.replace("src/", "").replace("\\", "/")
|
||||
lines.append(f" {name:<36} V={r.violation_count:>3} S={r.suspicious_count:>2} ?={r.unclear_count:>2} C={r.compliant_count:>3} total={len(r.findings)}")
|
||||
lines.append("")
|
||||
|
||||
_bucket(
|
||||
"LARGE (>=16 V+S; dedicated track per file)",
|
||||
large,
|
||||
"Each file is too big for a batched track. 1 track per file; 2-3 days Tier 2 each.",
|
||||
)
|
||||
_bucket(
|
||||
"MEDIUM (6-15 V+S; can group 2-3 files per track)",
|
||||
medium,
|
||||
"Each file is independent; can be batched in 1 track per group. 0.5-1 day Tier 2 each.",
|
||||
)
|
||||
_bucket(
|
||||
"SMALL (<=5 V+S; batched in one 'small files' track)",
|
||||
small,
|
||||
"Each file is small enough for a single batched track. 0.5-1 day Tier 2 for the whole batch.",
|
||||
)
|
||||
_bucket(
|
||||
"BASELINE (3 refactored files; the convention reference)",
|
||||
baseline,
|
||||
"These files ARE the convention. Remaining violations are gaps to close (deferred work from the parent track).",
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
@@ -746,6 +840,8 @@ def main() -> int:
|
||||
parser.add_argument("--include-tests", action="store_true", help="Also scan tests/ and scripts/")
|
||||
parser.add_argument("--strict", action="store_true", help="Exit 1 if any violations are found (for CI use)")
|
||||
parser.add_argument("--include-baseline", action="store_true", help="Include the 3 refactored files in the violation count (default: exclude)")
|
||||
parser.add_argument("--summary", action="store_true", help="Per-file summary table (for migration planning)")
|
||||
parser.add_argument("--by-size", action="store_true", help="Group files by migration effort bucket (small/medium/large/baseline)")
|
||||
parser.add_argument("--exclude", action="append", default=[], help="Additional path components to exclude (can repeat)")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -776,6 +872,14 @@ def main() -> int:
|
||||
total_violations = sum(r.violation_count for r in reports if not r.is_refactored_baseline)
|
||||
return 1 if (args.strict and total_violations > 0) else 0
|
||||
|
||||
if args.summary:
|
||||
print(render_summary(reports, len(files)))
|
||||
return 0
|
||||
|
||||
if args.by_size:
|
||||
print(render_by_size(reports, len(files)))
|
||||
return 0
|
||||
|
||||
print(render_human(reports, len(files), args.top, args.verbose))
|
||||
|
||||
if args.include_baseline:
|
||||
|
||||
Reference in New Issue
Block a user