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:
|
Usage:
|
||||||
uv run python scripts/audit_exception_handling.py # human report
|
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 scripts/audit_exception_handling.py --json # JSON output
|
||||||
uv run python python scripts/audit_exception_handling.py --src src
|
uv run python scripts/audit_exception_handling.py --src src # source dir
|
||||||
uv run python python scripts/audit_exception_handling.py --top 20
|
uv run python scripts/audit_exception_handling.py --top 20 # top N files
|
||||||
uv run python python scripts/audit_exception_handling.py --verbose
|
uv run python scripts/audit_exception_handling.py --verbose # every site
|
||||||
uv run python python scripts/audit_exception_handling.py --strict
|
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:
|
Exit codes:
|
||||||
0 - audit ran (informational mode; findings don't fail the script)
|
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)
|
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:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=__doc__,
|
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("--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("--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("--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)")
|
parser.add_argument("--exclude", action="append", default=[], help="Additional path components to exclude (can repeat)")
|
||||||
args = parser.parse_args()
|
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)
|
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
|
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))
|
print(render_human(reports, len(files), args.top, args.verbose))
|
||||||
|
|
||||||
if args.include_baseline:
|
if args.include_baseline:
|
||||||
|
|||||||
Reference in New Issue
Block a user