Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when configuring Python tooling or writing docstrings: apply our house ruff/mypy spec (line-length 120, exact rule set, tests override) and Google docstring section order, not the defaults.
.claude/skills/python-code-style/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 100% | 7 |
| Model | Lift | Δ tokens | Δ turns | Cases | Verified |
|---|---|---|---|---|---|
| gemini-3.5-flashbest | +63% | — | 0% | 24 | 86d ago |
| gemini-3.6-flash | +39% | +167% | 0% | 23 | 55d ago |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
Enforces this team's ARBITRARY house values for ruff, mypy, Google-style docstrings, typing, and imports. Apply whenever you write a pyproject.toml tooling section, write or edit a docstring, or write a type signature. These values are deliberate house choices — do NOT substitute the tool defaults (ruff defaults to 88, single quotes, a minimal rule set; the docstring section order is unspecified; typing.Optional is the common default). All of those are wrong here.
[tool.ruff]line-length = 120. NOT the 88 default, NOT 79, NOT 100.target-version = "py312".[tool.ruff.lint]select = ["E", "W", "F", "I", "B", "C4", "UP", "SIM"] — exactly these eight codes, inthis order. The four that base models routinely omit are B (bugbear), C4 (comprehensions), UP (pyupgrade), SIM (simplify) — always include all four.
ignore = ["E501"] — line length is the formatter's job, so the linter must not alsoflag it.
[tool.ruff.format]quote-style = "double".indent-style = "space".[tool.mypy]python_version = "3.12".strict = true.warn_return_any = true.warn_unused_ignores = true.[[tool.mypy.overrides]] block with module = "tests.*" anddisallow_untyped_defs = false. Test code is exempt from the required-annotation rule; application code is not.
Args:, then Returns:, then Raises:, then Example:. Never reorder them (a Returns: before Args:, or Raises: before Returns:, is wrong).
Args:, document every parameter by name.Raises:, document every exception the function can raise.Example: section uses >>> doctest prompts, and comes last.list[...], dict[...], tuple[...], set[...]. Never thecapitalized List/Dict/Tuple/Set from typing.
X | None. Never Optional[X]. Never Union[X, None].from myproject.models import User. Never relative(from ..models import User, from .utils import x).
then local.
BEFORE (base default):
toml[tool.ruff] line-length = 88
AFTER (house):
toml[tool.ruff] line-length = 120 target-version = "py312"
BEFORE (base picks a minimal set):
toml[tool.ruff.lint] select = ["E", "F"]
AFTER (house — all eight, with E501 ignored):
toml[tool.ruff.lint] select = ["E", "W", "F", "I", "B", "C4", "UP", "SIM"] ignore = ["E501"]
BEFORE (base omits format table, leaving the default single-quote behavior implicit):
toml# (no [tool.ruff.format] block)
AFTER:
toml[tool.ruff.format] quote-style = "double" indent-style = "space"
BEFORE (base turns on strict but forgets to exempt tests, so test files must be fully typed):
toml[tool.mypy] strict = true
AFTER:
toml[tool.mypy] python_version = "3.12" strict = true warn_return_any = true warn_unused_ignores = true [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false
BEFORE (base writes a NumPy/Sphinx mix, or puts Returns before Args):
pythondef get_user(user_id: str) -> User: """Get a user. :param user_id: the id :returns: the user """
AFTER (Google order: Args → Returns → Raises → Example):
pythondef get_user(user_id: str) -> User: """Retrieve a user by their unique identifier. Args: user_id: The user's unique identifier. Returns: The matching User. Raises: UserNotFoundError: If no user has that id. Example: >>> get_user("u_123") User(id='u_123') """
BEFORE (base default):
pythonfrom typing import Optional def find_user(email: str) -> Optional[User]: ...
AFTER:
pythondef find_user(email: str) -> User | None: ...
BEFORE:
pythonfrom typing import Dict, List def merge(records: List[Dict[str, object]]) -> List[Dict[str, object]]: ...
AFTER:
pythondef merge(records: list[dict[str, object]]) -> list[dict[str, object]]: ...
BEFORE (base reaches for a relative import inside a package):
pythonfrom ..models import User from .utils import retry
AFTER:
pythonfrom myproject.models import User from myproject.utils import retry
HTTPClient, notHttpClient; APIError, not ApiError. Functions/variables stay snake_case.
None (a pure side-effect) omits the Returns: sectionentirely — do not write Returns: None. Order is then just Args: (and Raises: / Example: if they apply).
Args:. Never write an empty Args: block.__init__ documents its parameters under Args: and never documents a returnvalue (it returns None by construction).
src.* or anyapplication module — only tests.* gets disallow_untyped_defs = false.
ignore. Do not also ignore E, F, or anything else tosilence noise; fix the code instead.
tuple/set follow the same generics rule as list/dict: tuple[int, str],set[str], never Tuple/Set.
__init__.py aggregating package symbols) still use absoluteimports — a package re-export is not a license for a relative import.
line-length = 88 (the default). DO set line-length = 120.select = ["E", "F"]. DO include B, C4, UP, SIM as well.ignore = ["E501"]. DO add it whenever the formatter owns wrapping.quote-style unset. DO set quote-style = "double".strict without the tests override. DO add the tests.* override.Optional[X] or Union[X, None]. DO write X | None.List/Dict from typing. DO use list/dict.line-length = 88 because that is ruff's documented default.["E", "F"] (or ["E", "W", "F"]) and dropping B/C4/UP/SIM.ignore = ["E501"], so the linter double-flags long lines.[tool.ruff.format] entirely, so quote style is unset.strict = true but forgetting the tests.* override.:param:, :returns:) or NumPy-style docstringsinstead of Google style.
Returns: before Args:, or omitting Raises: for a function that clearlythrows.
Optional[X] and List/Dict from typing instead of X | None andlist/dict.
line-length = 120, target-version = "py312"select = ["E", "W", "F", "I", "B", "C4", "UP", "SIM"], ignore = ["E501"][tool.ruff.format] → quote-style = "double", indent-style = "space"strict = true + warn_return_any + warn_unused_ignores[[tool.mypy.overrides]] module = "tests.*", disallow_untyped_defs = falseExample: uses >>>; Args: covers every param; Raises: covers every exceptionX | None not Optional; list/dict not List/Dict; absolute imports only| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.5-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +39 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.5-flash | verified | 6/27/2026 | +63% |
Other measured skills in the registry, with their headline benchmark lift.