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.2. Comments

Leave a blank line between a multi-line comment block and the statement it introduces.

# A command's relative paths are judged from where it runs, not from the
# root: `cd board && echo > x` must be refused exactly where the same
# write by full path would be.

directories = _stage_directories(words, cwd or scope.root or ".")

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.

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.