Code Conventions
[[[appendices.code-conventions]]]
Overview
[[[appendices.code-conventions#overview]]]
Conventions for writing code and docs in this repo, for any contributor — not just Claude Code sessions.
CLAUDE.md at the repo root covers how to work with the AI assistant; this page covers how the codebase
itself is written.
1. Python
[[[appendices.code-conventions#python]]]
- Imports: relative within a package's library code (
from .field import Field); absolute only in tests and the app's entry file (from rehuco_agent.fields.field import Field). Relative keeps intra-package imports stable when a package is published or moved between depths. The codebase is mid-migration to this rule; therehuco-coresweep is a follow-up. - Module filenames: match their main class (
rehu_document.py→RehuDocument,field_registry.py→FieldRegistry), unless the file deliberately groups several related classes (properties.py→TypedProperty+SimpleProperty,fields/field.py→Field+FieldBinding+FieldModel). - Visibility: public or private (
__); no protected (_) unless the class is designed for inheritance. This holds at module level too — no_-prefixed module globals or classes. A helper used by only one class belongs inside that class (a__-private member, or a public classmethod if it's API — even the sentinel-in-signature case works, verified against 3.14's lazy annotations); anything that must stay at module level is a plain public name, kept out of the package API by what the package__init__exports (name mangling doesn't exist outside a class body, so a module-level_would be convention-only anyway). - Constants:
Finalwithout an explicit type when it can be inferred. - Naming: don't bake development-methodology/process labels into identifiers (e.g. a bare
TRACER_prefix naming a constant after "this was built during the tracer-bullet slice") — that context rots once the slice ends; put it in a docstring, not the name. - Overrides:
@overrideon every method that overrides a base-class method. - Docstrings: Sphinx-style on all functions, including private ones — one-line summary +
:param:/:returns:/:raises:as needed; no multi-paragraph docstrings for routine code. Closing"""on its own line for multi-line docstrings, on the same line for single-line ones. Constructor:param:entries go on the class docstring (IDE hover shows them);__init__gets no docstring. - Comments: only when the why is non-obvious (hidden constraint, subtle invariant, bug workaround). No narration of what the code does.
- Line length: 120 (ruff enforced).
2. PySide6 UI
[[[appendices.code-conventions#pyside-ui]]]
- Window/widget layouts live in
.uifiles, not built up in Python. For a custom widget, prefer a.uitoo, unless it's trivial (simple layout, or the class only overrides/adds behavior on an existing widget rather than composing a new layout). - Collocate a
.uiwith its controller class:foo_widget.ui,foo_widget.pynext to each other; the generatedfoo_widget_ui.pyis gitignored and rebuilt viamake uis— never hand-edit it. - A custom widget used inside a
.uiis placed as a base/QWidgetplaceholder and promoted to the real class, not embedded as literal custom XML. - Assets go through
.qrc. Icons are authored in an Affinity Designer master file and exported as.svg;make qrcsregenerates the gitignored_rc.py. A.uireferencing a qrc-managed resource (e.g.windowIcon) must declare<resources><include location="....qrc"/></resources>somake uisemits the matching resource import — verify the generated file after adding one. - Ask before adding a new asset-conversion pipeline (e.g. deriving
.icofrom the.svgmaster) rather than improvising one inline. - Every property/attribute in a
.uifile must be one Qt Designer's own Property Editor can display and edit. Don't hand-add XML that's schema-valid and works at runtime but isn't something Designer surfaces — a developer opening the file in Designer needs to be able to find and change it there. Anything Designer can't set belongs in the controller class's Python code aftersetupUi(), not in the.ui. - Name widgets in
.uifilessnake_case, not Qt Designer's defaultcamelCase— this is a recommendation, not a hard rule; usecamelCasewhere justified (e.g. overriding a name Qt itself expects). A widget name that's already a single dictionary word a spell checker recognizes (checkbox,toolbar) doesn't need splitting; a compound default that isn't a real single word (centralwidget,statusbar,menubar) becomescentral_widget,status_bar,menu_bar.
3. Markdown
[[[appendices.code-conventions#markdown]]]
Docs under docs/ are markdownlint-checked (.markdownlint.json; MD013 line length 120, tables exempt). Also:
- Blank lines around headings and lists (MD022/MD032); inside a blockquote the separator is an empty
>line, not a truly blank line. - Spaced table delimiter rows —
| --- | --- |— and single-space-padded cells. - No emphasis-as-heading (MD036) — use real
###headings. - Under a spec section heading, list its GitHub issue(s) as plain list items —
- [#N: title](url)— regardless of whether the issue is open or closed. Deliberately not a- [ ]/- [x]task list: a checkbox has to be ticked in a commit every time an issue closes, which is churn in the specs for state GitHub already tracks authoritatively. The link records which issues a section belongs to; their status is read on GitHub. - Cross-renderer link rendering. Docs are read both on GitHub (blob view, PRs) and the published mkdocs site, which render some references differently:
- A bare
#123issue/PR reference autolinks natively on GitHub already;pymdownx.magiclink(mkdocs.yml) gives it the same treatment on the published site. Its issue-shorthand pattern requires digits right after#, so it never collides with a[[doc#slug]]cross-reference token (slug segments are never all-digit) — see "Symbolic cross-references" indocs/specs/README.md. - A plain relative link from a doc into a repo file outside
docs/(e.g. a.pysource file underpackages/) resolves natively on GitHub, but 404s on the built mkdocs site, since onlydocs/is ever published. Write such links as plain relative paths regardless —tools/mkdocs_relative_link_hook.pyrewrites, at build time only, any link whose target falls outsidedocs_dirto an absolute{repo_url}/blob/{branch}/...link; GitHub needs no change.
4. Testing
[[[appendices.code-conventions#testing]]]
Each test's docstring ends with a **Test steps:** bullet list spelling out the steps and checks, so intent
is readable without tracing the code — a project convention, not a pytest feature. The test stack and
how to drive it are covered in [[appendices.testing#qa-gate]].