PR Pure-Python LOC Breakdown
Produce an accurate per-file breakdown of pure Python code added/removed in a
PR. "Pure" means only executable Python: it excludes markdown/docs/other
non-.py files, blank lines, whole-line comments, inline comments, and
docstrings (module / class / function string-literal statements).
Why a script (not eyeballing the diff)
git diff --stat counts every changed line including blanks, comments, and
docstrings, so it overstates code churn. Counting by reading the diff by hand is
error-prone and not reproducible. This skill ships count_py_loc.py, which:
- Resolves the base & head SHAs (via
ghfor a PR number, orgit merge-basefor two refs — so churn that arrived frommainmoving forward is excluded). - Lists changed
.pyfiles withgit diff --name-only <base> <head> -- '*.py'. - Reconstructs each file's base and head content (
git show <ref>:<path>). - Strips comments + docstrings + blank lines from each version using Python's
ast(docstring line ranges) andtokenize(whole-line comments). - Diffs the two cleaned versions with
difflib.unified_diff(n=0)and counts+/-lines → real added/removed pure-code lines per file.
Because it diffs cleaned files, multi-line docstrings, reformatting, and comment churn never inflate the numbers.
Steps
- Parse
$ARGUMENTS:- One integer (e.g.
277or#277) → PR mode (resolves refs viagh). - Two refs (e.g.
main feature-branch) → ref mode. - Empty → defaults to
merge-base(main, HEAD)..HEAD.
- One integer (e.g.
- Run the bundled script from the repo root:
(usepython3 .claude/skills/pr-loc-breakdown/count_py_loc.py $ARGUMENTSpython3;pythonmay not be on PATH) Add--jsonfor a machine-readable blob in addition to the table. - Present the table to the user. Lead with the total pure-Python added/removed, then the per-file rows (already sorted by churn, largest first).
- If the user asked only for additions (or only a subset), filter the reported rows accordingly — do not re-count by hand.
Notes
- Requires
ghonly in PR mode; ref mode and the default usegitalone. - The script self-heals on unparseable revisions: if a file version doesn't parse (partial/invalid Python at that SHA), it falls back to a blank+comment line filter and still strips full-line comments and blanks.
- Renames are counted as the diff between the old path's base content and the new path's head content as Git reports them; pure-rename-only files show 0/0 and are omitted.
- To reuse for non-PR comparisons (e.g. a tag range), pass two refs directly.
Output Format
# Pure-Python LOC breakdown — PR #277
# base <sha> -> head <sha>
FILE +add -del net
------------------------------------ ------ ------ ------
backend/app/services/foo.py 120 14 +106
mcp_server/server.py 88 30 +58
...
------------------------------------ ------ ------ ------
TOTAL 512 110 +402
# N python file(s) with pure-code changes