- Code is for humans. Write code with clarity and empathy—assume a tired teammate will need to debug it at 3 a.m.
- Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
- Clarity over cleverness. Be concise, but favour explicit over terse or obscure idioms. Prefer code that's easy to follow.
- Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
- Small, meaningful functions. Functions should have a clear purpose, single responsibility, and obey command/query segregation.
- Clear commit messages. Commit messages should be descriptive, explaining what was changed and why.
- Use consistent spelling and grammar. Comments must use en-GB-oxendict ("-ize" / "-yse" / "-our") spelling and grammar, with the exception of references to external APIs.
- Illustrate with clear examples. Function documentation must include clear examples demonstrating usage and outcome. Test documentation should omit examples that only restate the test logic.
- Keep file size manageable. No single code file should be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
- Name things precisely. Use clear, descriptive variable and function names.
For booleans, prefer names with
is,has, orshould. - Structure logically. Each file should encapsulate a coherent module. Group related code (for example, models + utilities + fixtures) close together.
- Group by feature, not layer. Colocate views, logic, fixtures, and helpers related to a domain concept rather than splitting by type.
- Use clear file boundaries. Each module, component, and package should have an obvious responsibility and avoid accidental coupling.
- Reference: Use the markdown files within the
docs/directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions. Start with documentation contents and repository layout when orienting within the project. - Update: When new decisions are made, requirements change, libraries are
added/removed, or architectural patterns evolve, proactively update the
relevant file(s) in the
docs/directory to reflect the latest state. - Design decisions: Record substantive decisions in the relevant design document. For major decisions, capture an architectural decision record (ADR) and reference it from the design document.
- User-facing behaviour: Update users' guide for behaviour or user-interface changes that users should know about.
- Internal interfaces: Document internally facing interfaces in the relevant component architecture document. Record internally facing conventions and practices in developers' guide.
- Style: All documentation must adhere to the documentation style guide.
- Atomicity: Aim for small, focused, atomic changes. Each change (and subsequent commit) should represent a single logical unit of work.
- Quality gates: Before considering a change complete or proposing a
commit, ensure all of the following are met:
- New functionality or behaviour changes are fully validated by relevant unit and behavioural tests.
- Bug fixes include a failing test before the fix and a passing test afterwards.
- Code passes lint checks.
- Formatting is correct and validated.
- For Python files:
-
Testing: Passes all relevant unit and behavioural tests (
make test). Usemake test WITH_ACT=1when local GitHub Actions workflow validation should run throughact; this setsRUN_ACT_VALIDATION=1for the pytest invocation. -
Linting: Passes lint checks (
make lint). -
Formatting: Adheres to formatting standards (
make check-fmt; usemake fmtto apply fixes). -
Typechecking: Passes type checking (
make typecheck). -
Auditing: Passes dependency vulnerability checks (
make audit). -
The generated Makefile wiring for these targets is:
make check-fmtruns Ruff formatting checks withruff format --check $(PYTHON_TARGETS).make lintrunsmake lint-python;make lint-pythonrunsruff check $(PYTHON_TARGETS), enforces 100% docstring coverage withinterrogate --fail-under 100 $(PYTHON_TARGETS), and runs the PyPy-backed Pylint runner against$(PYLINT_TARGETS).make typecheckrunsty check $(PYTHON_TARGETS).make testrunspytest -v -n $(PYTEST_XDIST_WORKERS)and honoursWITH_ACT=1throughRUN_ACT_VALIDATION=1.make auditrunspip-audit.
-
Temporary lint suppressions must include a link to the planned fix or implementation that will remove the suppression.
-
Markdown files (
.mdonly):- Linting: Passes markdown lint checks (
make markdownlint). - Spelling: Passes the en-GB-oxendict gate (
make spelling). - Mermaid diagrams: Passes validation using nixie (
make nixie).
- Linting: Passes markdown lint checks (
-
- Committing:
- Only changes that meet all quality gates should be committed.
- Write clear, descriptive commit messages that summarize the change,
following:
- Imperative mood in the subject line (for example, "Fix bug", "Add feature").
- Subject line length: around 50 characters or fewer.
- Body: Separate subject from body with a blank line. Explain what and why in wrapped lines (approximately 72 columns).
- Formatting: Use Markdown for formatted text inside the message body.
- Do not commit changes that fail any quality gate.
- Recognizing refactoring needs: regularly assess the codebase for potential
refactoring opportunities. Consider refactoring when you observe:
- Long methods/functions: functions that are excessively long or try to do too many things.
- Duplicated code: identical or very similar code blocks appearing in multiple places.
- Complex conditionals: deeply nested or overly complex
if/elseorswitchstatements. - Large code blocks for single values: significant logic blocks dedicated to calculating or deriving one value.
- Primitive obsession / data clumps: groups of simple variables that are frequently passed together, which may indicate a missing abstraction.
- Excessive parameters: functions or methods requiring too many parameters.
- Feature envy: methods that focus more on other data than their own.
- Shotgun surgery: one change that forces many files to be edited.
- Abstraction / port / helper policy: before adding a new abstraction, port,
or helper:
- Sweep the repository to confirm there is no existing equivalent helper, port, or abstraction.
- Document the new abstraction's intended scope and re-use policy.
- Record the decision in architecture, design, or developers-guide docs using
docs/contents.mdas the index.
- Post-commit review: after functional changes or bug fixes that meet quality gates, review changed code and adjacent areas using these heuristics.
- Separate atomic refactors: if refactoring is required, implement it in a separate atomic commit after the functional change and ensure it passes all relevant gates.
- For Python work, use
pytestfor unit tests andpytest-bddfor behavioural tests. Cover happy paths, unhappy paths, and relevant edge cases. - Keep pytest tests in the top-level
tests/tree. Do not place unit tests in package module directories orunittests/subdirectories, because CI coverage uses xdist-backed SlipCover support. - Snapshot tests (using
syrupy) should be provided where multivariant output format consistency is relevant to the requirements. Snapshot tests must capture meaningful, reviewer-useful contracts rather than generic dumps of broad objects. Keep snapshots focused on stable output boundaries, pair them with semantic assertions for the behaviours they represent, and avoid snapshot-only coverage for logic that can be asserted directly. Redact or normalize nondeterministic fields such as timestamps, absolute paths, random identifiers, ordering-dependent maps, hostnames, and environment-specific values before snapshotting. Do not accept brittle snapshots that churn after harmless formatting or dependency changes; narrow the captured output until a snapshot failure identifies a real contract change. - Add end-to-end tests where a change affects externally observable workflows, integration contracts, persistence, command-line behaviour, network boundaries, user interface flows, or other system-level behaviour.
- Use property tests with
hypothesisorCrossHairwhen a change introduces an invariant over a range of inputs, states, orderings, or transitions. - Run relevant unit, behavioural, property, and end-to-end suites before and after each change.
- Validate Markdown files using
make markdownlint. This also runs the pinned en-GB-oxendict spelling gate. - The spelling configuration
typos.tomlis generated. Generic Oxford stems belong in the shared base inleynos/agent-helper-scripts; project-specific accepted words, patterns, and exclusions belong intypos.local.toml. Regenerate withuv run scripts/generate_typos_config.pyrather than editingtypos.tomlby hand. - Run
make fmtafter documentation changes to format Markdown and fix table markup. - Validate Mermaid diagrams in Markdown by running
make nixie. - Markdown paragraphs and bullet points should be wrapped at 80 columns.
- Code blocks should be wrapped at 120 columns.
- Tables and headings should not be wrapped.
- Use dashes (
-) for list bullets. - Use GitHub-flavoured Markdown footnotes (
[^1]) for references and footnotes.
Record design decisions in the design document. Where a decision is substantive, record it in an ADR document following the documentation style guide, then reference that ADR from the design document.
Update docs/users-guide.md for any change to application behaviour or user
interface that users should know about. Document internally facing interfaces
or practices in the relevant component architecture document. Document
internally facing conventions or practices in docs/developers-guide.md.
For Python development, refer to the detailed guidelines in the .rules/
directory:
- Python code style guidelines - Core Python 3.13 style conventions.
- Python context managers - Best practices for context managers.
- Python exceptions and logging - Raising and handling exceptions and logging.
- Python generators - Generator and iterator patterns.
- Python project configuration -
pyproject.tomland packaging. - Python return patterns - Function return conventions.
- Python typing - Type annotation best practices.
Additional docs:
- Scripting standards - Guidance for writing
robust scripts, including secure command execution via
cuprum, catalogue allowlisting, and command mocking patterns withcmd-mox. - Before adding or updating helper scripts, read the scripting standards guide
and follow its
Cyclopts,cuprum,pathlib, andcmd-moxconventions.
The following tooling is available in this environment:
mbake— A Makefile validator. Run usingmbake validate Makefile.strace— Traces system calls and signals made by a process; useful for debugging runtime behaviour and syscalls.gdb— The GNU Debugger, for inspecting and controlling programs as they execute (or post-mortem via core dumps).ripgrep— Fast, recursive text search tool (grepalternative) that respects.gitignorefiles.ltrace— Traces calls to dynamic library functions made by a process.valgrind— Suite for detecting memory leaks, profiling, and debugging low-level memory errors.bpftrace— High-level tracing tool for eBPF, using a custom scripting language for kernel and application tracing.lsof— Lists open files and the processes using them.htop— Interactive process viewer (visual upgrade totop).iotop— Displays and monitors I/O usage by processes.ncdu— NCurses-based disk usage viewer for finding large files/folders.tree— Displays directory structure as a tree.bat—catclone with syntax highlighting, Git integration, and paging.delta— Syntax-highlighted pager for Git and diff output.tcpdump— Captures and analyses network traffic at the packet level.nmap— Network scanner for host discovery, port scanning, and service identification.lldb— LLVM debugger, alternative togdb.eza— Modernlsreplacement with more features and better defaults.fzf— Interactive fuzzy finder for selecting files and commands.hyperfine— Command-line benchmarking tool with statistical output.shellcheck— Linter for shell scripts.fd— Fast user-friendlyfindalternative with sensible defaults.checkmake— Linter forMakefiles, ensuring best practices.srgn— Structural grep and syntax-tree pattern editing.difft(Difftastic) — Semantic diff tool that compares code structure.
These practices help maintain a high-quality codebase and facilitate collaboration.