This guide walks through installation, invocation, the routing rules, and the situations that justify reaching for the verification and quality-tool skills.
Copy the catalogue under Codex's skill directory:
mkdir -p ~/.codex/skills
cp -a skills/* ~/.codex/skills/The catalogue is self-contained; the only directories Codex needs are
skills/<name>/SKILL.md and skills/<name>/references/*.md.
The router is the default entry point. Invoke it explicitly in a session:
Use $python-router to plan this Python task.
The router resolves the task to the smallest useful follow-on skill;
each skill loads only its own SKILL.md until a reference is needed.
Avoid loading more than one language skill and one domain or quality
skill in the same turn.
For a known pressure point, call the skill directly:
Use $python-types-and-apis to review this `ParamSpec` decorator for
type-checker friendliness.
The router asks a short question list and resolves to a single skill:
- Typing or public-surface question →
python-types-and-apis. - Exception or logging question →
python-errors-and-logging. - Decorator, descriptor, context manager, or metaclass →
python-abstractions. - Iterator or generator refactor →
python-iterators-and-generators. - Container choice (msgspec, dataclass, TypedDict) →
python-data-shapes. - Concurrency or subinterpreter question →
python-concurrency. - Named test, finite parameter table, fixture, or plugin →
python-testing. - Cheap invariant over a broad input space →
hypothesis. - Verification escalation or adversary selection →
python-verification, then one primary deep dive fromhypothesis,crosshair, ormutmut. - Dead code, clones, profiling →
python-quality-tools. - Ruff configuration, defaults, suppression, or upgrade →
ruff-016.
Pairing rules:
- Web or worker boundaries usually pair
python-errors-and-loggingwithpython-concurrencyorpython-data-shapes. - Library API work usually pairs
python-types-and-apiswithpython-data-shapes(data-shaped surface) orpython-abstractions(behaviour-shaped surface). - A clear lightweight invariant goes straight to
hypothesis.python-verificationchooses an escalation path when the right adversary is unclear. mutmutmay pair with any testing style because it audits the suite rather than generating production inputs.
Pick the first level whose evidence matches the question:
- Named pytest example: one scenario, regression, exact output, or error contract matters.
- Parameterized pytest: a finite truth table, standards corpus, or set of cases whose rows each carry semantic meaning.
- Lightweight Hypothesis: one round trip, invariant, oracle, or metamorphic relation should hold across many cheap, repeatable inputs. A growing set of representative parameter rows is the usual signal.
- Structured or stateful Hypothesis: valid data has dependent or recursive structure, or failures depend on operation history.
- CrossHair: a small pure function needs bounded symbolic exploration of contracts or changed behaviour.
Mutation testing sits beside the hierarchy. Use mutmut when the question is whether the current suite would notice a plausible defect.
Leave the hierarchy for integration, scheduling, load, performance, resource, or native-code failures. Those need real or simulated boundaries, concurrency or stress tools, benchmarks and profilers, or native sanitizers.
Examples remain useful beside generated tests. Keep exact protocol examples and named regressions even when a property searches the wider domain.
Run lightweight Hypothesis properties on every CI run; run targeted CrossHair and mutmut on slower cadences.
- deadcode: when the question is "is this name still used?".
Run on changed files in CI; review
--fixdiffs by hand. - pyscn: when the question is "is this branch reachable?", "is
this block a clone?", or "is this module getting too coupled?".
Run weekly on
main; treat findings as a worklist, not a build failure. - Pyinstrument: when a request or test is slow and the question
is "where does the time go?". Use to find hot paths; use
pytest-benchmarkto regression-test them.
ruff-016 covers Ruff as a tool rather than any one rule family.
- Upgrading. Ruff 0.16.0 raised the default rule set from 59 to 413, started formatting Python blocks in Markdown files, and made fields in the JSON output nullable. An upgrade needs a plan, not a version bump.
- Configuration review. Whether
select,ignore, andper-file-ignoresstill say what the project means under the new defaults, and which of the settings added since 0.14.0 apply. - Suppression.
ruff: ignore,ruff: file-ignore, andruff: disable/enableversusnoqa, plus--add-ignoreand theRUF100–RUF106hygiene rules. - "Does this rule exist yet?" The reference tables record what stabilized in 0.15.0 and 0.16.0 and what remains in preview, material that postdates most models' training data.
Rule-level questions about exceptions and logging stay with
python-errors-and-logging; its ruff-rule-map.md reference is the
decision surface for TRY, BLE, EM, LOG, N818, and PERF203.
- Loading two language skills in one turn. Pick the one whose decision surface dominates and keep the other in reserve.
- Routing an obvious five-line invariant through a verification
ceremony instead of loading
hypothesisdirectly. - Replacing a finite normative parameter table with generated values. The table is the specification.
- Treating mutation testing as a build gate. The survivor list is a worklist; CI should publish the trend, not fail the build.
- Profiling without a baseline. Pyinstrument is most useful as a before-and-after comparison.
Keep new material under skills/. Match the shape of the existing
skills: YAML frontmatter, a working stance, a decision surface, red
flags, and references for longer detail. Update
skill-catalogue-status.md when adding or
retiring a skill, and (if the change is structural) record the
rationale in
execplans/initial-skill.md.