19 — Contributing¶
This section is how contributions are made to The Aetherium Engine and how its development is organized — the commit conventions, the hygiene that keeps secrets and machine paths out of the tree, and the project-specific disciplines that keep the codebase sound. It is written descriptively: this is how the Engine is actually developed today, not an aspirational process dressed up for a doc.
It sits next to §10 — Development Workflow, and the boundary between them is clean. §10 covers doing the work: building the Engine, adding a feature through the eight-step path, running the tests, the four cross-cutting patterns every service reuses. This section covers contributing that work back: the conventions around a commit, the standing hygiene gate, and the handful of disciplines a change has to respect or it breaks something quietly. The two meet at exactly one point — the pre-commit checklist appears in both, because §10 owns the mechanical "before you commit" version and this section owns the contribution-facing one. Where §10 teaches the build-and-test loop, this section assumes it and does not re-teach it.
How development is organized¶
Start with the honest state, because it shapes everything else. Development today runs directly on main. The private Git remote is the source of truth, the history is linear, and there is no required feature-branch or pull-request pipeline standing between a commit and the main line — the loop is git pull, edit and run, git commit, git push, with the other development environment pulling when it next comes up (§10 — Development Workflow describes that two-machine rhythm). If you are proposing a change from outside that flow, a pull request is the mechanism GitHub gives you — but understand up front that the gates which keep the codebase sound are the ones in this section, applied by hand, not a pipeline that catches what you miss.
That is the part to internalize first: there is no continuous integration. No workflow builds or tests a change before it lands. There is no pre-commit hook, no commit-message linter, and no global.json pinning the SDK — your environment's .NET 10 SDK is what builds the Engine. The .github/ directory exists, but it holds development tooling — chat modes and agent skills — not automation that gates a contribution. The discipline is real; the enforcement is manual. A contributor who treats the checklist below as optional because "something downstream will catch it" will find that nothing does.
Two mechanical conventions are worth stating, because they are the few the tooling actually carries:
- Commit messages follow Conventional-Commits-style
type(scope): summary. The recent commits follow it consistently —docs(developer): draft section 18 — roadmapis a representative example — and a code change should take the same shape: atype(feat,fix,refactor,docs,chore), an optionalscopenaming the area touched, and a concise summary. It is a convention the project keeps by habit, not one a hook enforces, so consistency is your responsibility. - Line endings are normalized by
.gitattributes: LF for source by default, CRLF held for the Windows scripts (.ps1,.bat,.cmd). That is one of the few pieces of hygiene that is automatic. There is no.editorconfig, though — C# formatting follows the conventions already in the surrounding code rather than a tool, so match what you see in the file you are editing.
The pre-commit hygiene checklist¶
Because nothing automated stands between your commit and the shared tree, this checklist is the gate. Run it by hand before every commit; it is the contribution-facing companion to the mechanical version in §10 — Development Workflow:
- Tests pass. Run the suite; never commit a red build. With no CI, an untested commit is untested for everyone who pulls it next.
- No secrets. Confirm
appsettings.jsonstill carries theREPLACE_ME_VIA_LOCAL_OVERRIDEplaceholders and not real values, and scan your diff for any leaked credential. Real secrets belong only in the gitignoredappsettings.Local.json. - No machine paths. No absolute developer paths or drive-letter paths, and no LAN addresses, in committed code, config, or docs — use repo-relative paths and
localhost. - Casing. The local override file is
appsettings.Local.jsonwith a capitalL; .NET on Linux is case-sensitive and will not load a lowercase variant. - Memory bank updated for significant work — see the discipline below.
These are not ceremony. Each maps to a real failure with no other backstop: a secret is leaked the moment it is pushed to the remote, and a machine path breaks the build on the other operating system the Engine is developed on.
The disciplines that matter¶
Beyond hygiene, the Engine has a handful of project-specific rules that are easy to violate and quiet when violated — the kind a contributor has to know because the compiler will not warn them. Each is covered in depth where it lives; this is the contribution-facing summary of what to respect.
The four-edit tool rule¶
Adding an agent tool is not one edit — it is four, plus a fifth that bites. A tool's schema is reflected from its attributes, but its execution is wired by hand, so a new tool touches four places: the [McpServerTool] method itself, a dispatch arm in McpToolRouter.ExecuteToolAsync, a ToolToServerGroup entry, and a ToolDocs entry in PromptEnhancementService. Miss the dispatch arm and the model calls a tool that errors; miss the group map and no agent can see it; miss the docs entry and the Engine refuses to boot, because ToolCatalogValidator asserts that the router and the docs describe the same set. The fifth edit — a ToolSecurityLevels entry — is optional and the dangerous one: omit it and the tool runs at Standard tier with no approval gate, which for a destructive tool is the edit that bites. The full walkthrough is §12 — Extending the Engine; the reasoning behind the layered model is ADR 4 in §17.
The template casing trap¶
A Workflow Template is JSON with a split casing rule: the outer object is camelCase (id, name, defaultTier, seedKnowledgeBase) and everything inside the team block is snake_case (topology, is_supervisor, system_prompt, mcp_servers, model_id). Get it wrong — write modelId inside team, or seed_knowledge_base on the outer object — and the field is silently ignored: the deserializer keeps the default rather than raising an error, so the template loads and quietly misbehaves. It is the single most common template mistake. The schema is in §08 — Templates; the authoring walkthrough in §12 — Extending the Engine.
The SSR double-registration rule¶
The UI is a React single-page client served from the server's own wwwroot/app, and its TypeScript API types are generated from the server's OpenAPI document. Never hand-write a type that mirrors a server DTO: a hand-maintained copy is the tax that makes a two-language codebase rot, and it drifts silently because nothing fails to compile. Change the controller, run scripts/generate-contract.sh and bun run generate:api, and let the compiler find the callers. An endpoint that returns IActionResult with an anonymous object has no schema and arrives in TypeScript as unknown — that is the signal to give it a typed ActionResult<T> rather than to write the type by hand. Covered in §10 — Development Workflow.
The memory-bank discipline¶
The Engine's own development is organized around a persistent context layer in .memory/ — activeContext.md, progress.md, systemPatterns.md, and productContext.md — updated at the end of each meaningful session so the next one picks up without rebuilding the picture. If you contribute back, adopting the rhythm keeps the shared context honest, and the update-memory-bank skill automates it. This is a habit, not a mandate; §10 — Development Workflow owns the full practice.
Before you submit¶
Tying it together — the standing "is this change ready" list. Because no pipeline runs it for you, run it yourself:
- It builds — and publishes. A full-solution compile is
dotnet buildagainst the.slnx; but if your change affects the deployable server, confirm it publishes, and publish the server.csprojdirectly — never the.slnx, the canonical publish gotcha. On a fresh Linux clone, remember the one-timebin/andobj/mkdirprerequisite or the first build fails outright (§10 — Development Workflow, §14 — Deployment). - Tests pass. The xUnit suite, against the InMemory and Testcontainers patterns §10 describes.
- The catalog validates. If you touched tools, a clean boot is the signal:
ToolCatalogValidatorlogsOK — N tools registered, all documentedwhen the router and docs maps agree, and throws otherwise. A boot that fails here means a tool is half-wired — the four-edit rule above (§12 — Extending the Engine). - Hygiene is clean. The pre-commit checklist — secrets, paths, placeholders, casing.
- Decisions respected. A change that touches the load-bearing choices — the permission model, in-process-only tools, push-dispatched orchestration — should respect them, or make the explicit case for changing one. §17 — Architecture Decisions records what each cost and why it stands.
Where to go next¶
- §10 — Development Workflow — doing the work: the build, the feature path, the tests, the four patterns.
- §12 — Extending the Engine — the extension disciplines in full: adding a tool, a server class, a template, a skill.
- §17 — Architecture Decisions — the decisions a contribution should respect, and the reasoning behind each.