Code guidelines
What a change to this repository looks like. The lint, format, and check tooling is in Development.
Names
A name says what the thing does, so a reader does not open the body: functions are a verb over a domain noun (resolveOrgApiKey, seedOperator, materialiseScripts), types and tables are the domain noun (Repositories, artifact_collections), booleans read as a question (isReadOnlyRemote). Abbreviations are the domain's own (org, id, mcp) and nothing else. A name that needs a comment to be understood is the wrong name.
Functions
Functions are short and shallow. One function makes one kind of decision; a nested condition, a loop with a branch inside, or a second switch becomes a named function. Return early. A chain of ifs over a value is a lookup table. A function that takes a flag to do two things is two functions. The cyclomatic complexity of a function is a reason to split it before it is a reason to document it.
Scaffolding
One directory per feature, flat inside it. In the orchestrator each domain is a directory under src/ (schedules/, secrets/, recipes/), holding the files for that domain named for what they hold (service.ts, timing.ts, access.ts), and nothing deeper unless the domain has a sub-domain of its own. In the operator app each screen is a directory under src/features/. There is no utils/, helpers/, or common/: code two domains share goes in lib/ and says which two. HTTP stays thin in http/; the domain module owns the logic and the validation. A feature's tests are named for the feature.
Boundaries
A module reaches another through what that module exports, and a package through its exports list. Core imports nothing from an edition built on it, and eslint.boundary.js fails the file that tries. An edition attaches through the extension points, and a need it has that core lacks becomes an extension point with a core default, not a branch.
Types and validation
TypeScript is strict everywhere. Input is validated at the edge, once: TypeBox schemas on routes, a validation module per domain for what arrives from the operator or the model. Inside the domain the types are trusted and any does not appear.
Comments and documentation
A comment says why, not what; the code says what. Every page under docs/ describes what the code does today, in the present tense, with no history, roadmap, or record of a discussion. The one exception is the security register, which keeps the history of each issue. A change to behaviour updates the page that describes it in the same commit.
Commits
One change per commit. The message is one sentence in the imperative that states the outcome for the reader of git log, with a short body only when the why is not obvious. The pre-push hook runs the whole check on what is pushed.