writing-python
Skill4 starsChanged 3 months agoMITView on GitHub
How Python is written in this repo — free functions over classes, structured returns instead of mutated arguments, comprehensions, no module state, and the stdlib-only, annotation-free, Python 3.8 dialect the AST lints enforce. Covers modular structure and the import-layer rule, DRY without copying a helper into a second file, what makes a function testable from a `--selftest`, readability rules (guard clauses, named predicates, why-not-what docstrings), and the anti-patterns that have actually bitten here. Use when planning, writing, reading, reviewing or refactoring any `.py` under plugins/audit/, when adding a helper or a new script, when deciding where a function belongs, or when the same logic is about to exist in two places.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- weaverbase/skillstheir own version, first seen Oct 13, 2026
- Chogos/claude-skillstheir own version, first seen Mar 9, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- Chogos/claude-skills★ 1same name
- weaverbase/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository12 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- before-you-claimSkill
- choosing-what-to-optimizeSkill
- grill-meSkill
- no-silent-passSkill
- orchestrating-parallel-workSkill
- refactoring-the-assembled-uiSkill
- reproducing-ci-locallySkill
- running-resumable-sync-jobsSkill
- verifying-external-behaviorSkill
- writing-cssSkill
- writing-javascriptSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 5 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Not a general Python style guide.
- Distinctive: line 45
Comprehensions over accumulate loops: when the result is a list or dict and the body is one expression.
- Distinctive: line 77
If two modules need the same logic, it belongs in a lower layer both can import — not copied into the second file, and not re-derived.
- Distinctive: line 84
When a value must exist in two languages, pin the agreement with a case rather than a comment.
- Distinctive: line 86
Do not extract for its own sake.
- Distinctive: line 100
Keep functions short enough to hold.
- Very specific: line 106
Use None and build inside.
- Very specific: line 107
x = x or defaultwhen0,Falseor""are meaningful: Use x if x is not None else default.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Writing Python here
Not a general Python style guide. This repo bans things most guides mandate, and the bans are
read out of the AST rather than trusted — _output_lints.house_style_violations() fails the build, it
does not warn. Everything below is either enforced or measured from the tree as it stands.
The dialect, in one paragraph
Stdlib only. Python 3.8 floor. No typing, no dataclasses, no annotations, no walrus, no
from __future__ — banned by AST, because hooks start on every tool call and the import and
parse cost is real. %-style formatting; there is not one f-string in the tree. Reach for
os.path, json, re, subprocess — not for a dependency.
3.8 also rules out things that read as ordinary today: no X | Y unions, no match, no
dict1 | dict2 merge, no list[str]. vermin -t=3.8- catches these; it will not catch the
banned imports, which is why the AST lint exists alongside it.
Free functions, structured returns
The measured shape of this codebase is top-level functions by the hundred against a
handful of classes, comprehensions everywhere, and global statements you can count on one
hand. The ratio is the pattern to keep; the figures are not, so re-derive them rather than
trusting a sentence:
grep -rhc '^def ' plugins/audit/scripts plugins/audit/hooks --include='*.py' | paste -sd+ | bc
grep -rn '^class \|^ *global ' plugins/audit/scripts plugins/audit/hooks --include='*.py' | wc -l- Write a function, not a class. A class here needs a reason you can say out loud — the six that exist are all genuine (a lock, a launcher). Grouping related functions is what a module prefix is for.
- Return a new value; do not mutate an argument. 181 functions return a dict or tuple. A function that edits the caller's dict in place and also returns it is two contracts and the caller will rely on the wrong one.
- No module state. Constants at module level are fine (52 of them, uppercase, frozen tables).
Mutable module state that a function writes to is how two callers start disagreeing about the
same run. Where a cache is genuinely needed the existing pattern is an explicit
_cache/cache=Trueargument, not a hidden global. - Comprehensions over accumulate loops when the result is a list or dict and the body is one expression. Past that, a named function reads better than a nested comprehension.
- Push I/O to the edges. A function that takes values and returns values can be tested by a
test case with no temp directory and no
git init; one that reads a file and prints cannot. Split the read from the decision —decide()taking parsed input is the shape every hook already uses, and it is why their logic has cases at all.
Where code lives
- Name by role.
_underscore.pyis an importable helper;hyphen-name.pyis an entry point something invokes. The name is the contract. - A basename must be unique across all of
scripts/. A file may sit in a subdirectory — every scanner walks recursively — butimport,_loaderand_deps.LAYERSall resolve by basename, so two files sharing one would be a single node in the layer graph wearing both files' edges.layer_violations()fails the build by name on it. Directories are labels, not namespaces. - Never derive a directory from your own
__file__._output.pyis the anchor:SCRIPTS_DIR,PLUGIN_ROOT,HOOKS_DIR,TESTS_DIR,LINTS_DIR,REPO_ROOT. The pinnedPATH_PREAMBLEis the only place underscripts/allowed to touch__file__at all, anddepth_sensitive_paths()enforces it — the rule is "none", not "not a parent", because the sites it replaced were written as a two-step that any nesting-only lint waves through. - Imports go down, never sideways or up.
_deps.LAYERSassigns every module a layer andlayer_violations()fails an unplaced or wrongly-layered file by name.hooks/may import nothing fromscripts/at all. If a new helper does not fit a layer, that is information about the design, not an obstacle to route around. - Section markers every 400 lines. Past that a flat scroll stops being a map;
navigability_violations()enforces it for.py; the same rule for the front-end assets is author-enforced.
DRY, and the way it fails here
The rule is not "never repeat a line". It is one fact, one home.
- If two modules need the same logic, it belongs in a lower layer both can import — not copied into the second file, and not re-derived.
- The cautionary tale is real and still in the tree: the token formatter exists in Python and
is hand-written again in each of the two front-end surfaces. Two of the three now disagree —
uTok(2.6)rounds to"3"whilefmtTokens(2.6, 1)truncates to"2"— and both carry a comment claiming to mirror the Python. A copy with a comment saying it is a copy is still a copy. - When a value must exist in two languages, pin the agreement with a case rather than a comment. A claim that two implementations agree is testable; test it.
- Do not extract for its own sake. Two similar-looking blocks that answer different questions should stay apart — the wrong abstraction costs more than the duplication.
Readability
- Guard clauses over nesting. Return early; do not build an arrow of
if/else. - Name the predicate.
if _is_ready(task):beats an inlined three-termandchain, and the name is where the reasoning goes. - Module docstring says why the file exists, not what it does. That is a hard rule here and the reason the tree is navigable at 42k lines.
- Comment the non-obvious constraint, never the mechanism. The house comments that earn their place read like postmortems: what broke, and what would break again. A comment restating the next line is noise.
- No
*Utils,*Helper,*Manager,*Impl. Name a module for the thing it owns. - Keep functions short enough to hold. 39 functions in the tree exceed McCabe 15 — that is the known debt, not the target.
Anti-patterns and pitfalls
- Mutable default arguments (
def f(items=[])) — the default is created once and shared. UseNoneand build inside. x = x or defaultwhen0,Falseor""are meaningful. Usex if x is not None else default. This is the single most common silent-corruption bug in code that reduces data to a number.- Bare
except:or a broadexcept Exception:that returns a normal-shaped value. Either recover meaningfully or make the failure visible. Seeno-silent-pass. - Iterating a
setinto serialized output — the order varies per process and the real diff drowns in noise. Impose a total order first. - Catching an exception to skip an item, with no count and no name. Silent skips are how a job reports success over half the work.
- Reaching around the layer rule by importing inside a function to dodge the lint. The lint reads the AST; more importantly, the layering is the design.
- Adding a file without its five obligations — a
tests/test_<name>.py,safe_stdio()first in__main__, a_deps.LAYERSentry, aPLUGIN-BUILD-GUIDE.mdtree line and section, and (underscripts/)_output_lints.PATH_PREAMBLEpasted byte for byte above the first sibling import. Four of the five fail CI by name.
Testability
A suite in tests/ is not an afterthought here, and the sweep's coverage verdict fails a module
without one. Design for it:
- a function whose inputs are arguments and whose output is a return value needs no fixture;
- where a fixture is unavoidable, build it in a
tempfile.mkdtemp()and delete it infinally— never commit one; - new behaviour means new cases, and a case is only trusted once it has been seen red.
no-silent-pass owns that discipline — mutate the fix, mutate it the other way too, count rather
than assert presence, and choose fixture values that tell the two versions apart.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

