8.1. SPEAR coding conventions
These are descriptions, not aspirations: each one was measured on the tree as
it stands, and the figure is given so that a later reader can check whether
it still holds rather than take it on trust. There is no linter configuration
in the repository — no .flake8, no pyproject.toml, no pre-commit hook
— which is deliberate. A convention that only a tool enforces is one nobody
has to understand.
8.1.1. Line length
Seventy-nine columns. The great majority of lines are within it; the exceptions are almost all long string literals and table rows where breaking would cost more than it buys.
Rationale: two files side by side on a laptop, and a diff in a terminal, both fit without wrapping. Wrapped diffs are where review stops being careful.
8.1.3. Docstrings
Triple double quotes, always. A module docstring says what the module is for in one sentence; a function docstring says what it guarantees, not how.
8.1.4. Typing
from __future__ import annotations at the top of every module (a few
older entry points such as rag_chat.py, and the cli/ modules split
from it, predate the rule), and return
annotations wherever the return type is not obvious from the name. The point is not type checking, which
nothing in CI runs; it is that a signature should answer “what comes back”
without the reader opening the body.
8.1.5. File headers
An existing header is never rewritten. Whoever it names stays named, in
the form it already has — Copyright (C) 2014-2017 Daniel Rossier, REDS
Institute from HEIG-VD, anything else. The only permitted change is the
year, and only when you actually changed the file: extend the range to the
current year (2014-2017 → 2014-2026), or turn a single year into a
range (2017 → 2017-2026).
Never replace the holder, never drop a name, never modernise the wording. Attribution is a record of who did the work, and a record that gets tidied is no longer a record.
8.1.6. Naming
Modules are nouns for what they hold (tool_registry, answer_scope,
evidence_handles). A predicate returns a boolean and reads as one at the
call site — retains_raw_pdf(root), withholds_local_tools(scope) — so
that a condition can be read aloud.
8.1.7. Vendored code
agent/hermes/ is code copied from Hermes Agent under its MIT licence. It
keeps its upstream form — each module names its upstream path in its header,
and nothing is changed unless that header says so — and
THIRD_PARTY_NOTICES.md records the provenance. These conventions do not
apply to it.
8.1.8. Fail-closed, in code
The rule that governs the harness governs its source too. A branch that cannot apply a confinement raises; it does not log and continue. If you find yourself writing a fallback that is almost as safe, the fallback is the bug: see the security model for what that costs when it is got wrong.
8.1.2. Comments
Leave a blank line between a multi-line comment block and the statement it introduces.
The blank line is what makes the block read as a paragraph about the code rather than as a label glued to one line.
Write comments about the decision, not about the mechanism. The mechanism is visible in the code underneath; why it was chosen, and what went wrong when it was not, is not. A comment that says what a line does earns nothing and rots at the first edit; one that records the failure a line prevents survives being moved.