Rules¶

!!! warning "Early development"
This is 0.1.0a1. The HS### identifiers on this page are not stable and
will change before 0.1.0. Do not pin automation to them yet.
Version 0.1.0a1 ships 19 rules. Every one of them is a structural
observation about Python source, read off the standard-library AST. None of
them executes the code being scanned, and none of them is a verdict.
What a rule is¶
A rule is a named condition plus the metadata needed to weigh it. In
src/humansays/catalog.py each one is a RuleSpec with five fields:
signal
: The human-readable name printed in text output, for example
many-arguments. This is what you see on a target line, not the HS###
code.
severity
: WARNING or ADVISORY. There are exactly two. There is no error level and
no bug level.
confidence
: How reliably the syntactic condition indicates the thing the rule is
actually about. HS015 sits at 0.99 because a staticmethod decorator
either is or is not present. HS009 sits at 0.55 because a long function
is sometimes just a long function.
weight
: 3.0 for a WARNING, 1.0 for an ADVISORY. Set from WARNING_WEIGHT
and ADVISORY_WEIGHT in catalog.py. A third constant, NOTICE_WEIGHT,
is defined at 0.0 and no rule uses it.
review_question
: The question a reviewer should be asking at that location. This is the
payload of the rule. The tool cannot answer it, which is the point.
How a finding becomes a score¶
Each finding contributes a penalty of weight * confidence. So one HS015
costs 3.0 * 0.99 = 2.97, and one HS009 costs 1.0 * 0.55 = 0.55.
Penalties are summed across the scan, expressed per 100 source lines as a density, and turned into a score:
density = penalty * 100 / lines
score = 100 / (1 + density / 7.5)
SCORE_TOLERANCE is 7.5, calibrated so roughly one warning per 100 lines
lands in the mid seventies. The curve decays smoothly and never leaves the 0
to 100 range, so a file with no weighted findings scores exactly 100.
Using density rather than a raw count means a large clean module is not punished for its size, and a small module full of warnings cannot hide behind a low total. Output and scoring covers the grade bands and the JSON shape.
The 19 shipped rules¶
| Code | Signal | Severity | Confidence | Weight | Page |
|---|---|---|---|---|---|
| HS001 | many-arguments |
WARNING | 0.80 | 3.0 | Function shape |
| HS002 | boolean-modes |
ADVISORY | 0.82 | 1.0 | Function shape |
| HS003 | deep-nesting |
WARNING | 0.76 | 3.0 | Function shape |
| HS004 | shared-mutable-state |
WARNING | 0.95 | 3.0 | State and boundaries |
| HS005 | broad-exception |
WARNING | 0.96 | 3.0 | Failure and imports |
| HS006 | multiple-mutation-owners |
WARNING | 0.70 | 3.0 | State and boundaries |
| HS007 | mixed-boundaries |
WARNING | 0.65 | 3.0 | State and boundaries |
| HS008 | low-class-cohesion |
ADVISORY | 0.65 | 1.0 | Class design |
| HS009 | long-function |
ADVISORY | 0.55 | 1.0 | Function shape |
| HS012 | many-class-attributes |
ADVISORY | 0.72 | 1.0 | Class design |
| HS013 | attribute-prefix-cluster |
WARNING | 0.84 | 3.0 | Class design |
| HS014 | validated-argument-bundle |
WARNING | 0.88 | 3.0 | Function shape |
| HS015 | static-method |
WARNING | 0.99 | 3.0 | Class design |
| HS016 | lambda-expression |
WARNING | 0.99 | 3.0 | Function shape |
| HS017 | long-file |
WARNING | 0.60 | 3.0 | State and boundaries |
| HS018 | many-base-classes |
WARNING | 0.78 | 3.0 | Class design |
| HS019 | many-branches |
WARNING | 0.74 | 3.0 | Function shape |
| HS021 | lazy-import |
ADVISORY | 0.85 | 1.0 | Failure and imports |
| HS022 | dense-function |
WARNING | 0.72 | 3.0 | Function shape |
The gaps at HS010, HS011, and HS020¶
The numbering runs from HS001 to HS022 with three codes missing. Each gap
is a prototype check that was deliberately dropped rather than an
implementation that is still owed:
HS010, comment counting. The raw count was noisy and duplicated evidence the narration signals already carried.HS011, docstring counting. A raw docstring count did not establish a structural problem on its own.HS020,from __future__ import annotations. Version-dependent modernization belongs to a general linter and loses value on newer Python.
The codes were retired rather than reused, so an HS### code means the same
thing across versions. tests/deletions/test_deleted_rules.py holds the three
in a DELETED_IDS set and asserts none of them can be emitted.
Reconciliation has the full mapping.
Why these 19¶
The shipped set is small on purpose. Every rule here has a condition that can be read off the syntax tree without guessing, a matched positive and negative fixture, and a review question worth asking. The much larger catalog of rules that are designed but not implemented lives under Planned rules, and nothing in it runs today.
The reasoning behind the rules, as opposed to their mechanics, is in Design philosophy.