headquarters #
Principles #
Code layout #
- Code should be organized around sensible crate boundaries, and then around sensible clusters in folders, and finally into reasonable functions.
- Prefer to use commonly used and well supported crates, even over "small" inline implementations.
- Do not roll our own crypto.
- Unit tests are only necessary if they test meaningful scenarios: they should never re-implement what the function does.
- Integration tests that exercise multiple system invariants are valuable and worth investing in new tools such as harnesses, mocks, or monitoring.
Documentation #
- Write in short, concise sentences that minimize jargon, repetition, abstraction, and indirection.
- Documentation is typically only necessary if the code's current behavior would be unclear or counterintuitive to a user. Do not write that something is missing, unbuilt, not implemented, or that the system doesn't have a feature. Do not explain "how we got here", as git already holds that info.
- Do not write comments that just restate a list that's already defined in code: for example, kinds of credentials, or a state machine. Make the code the only place it lives.
Copywriting #
- Do not write any marketing copy, FAQ entries, or other prominent human-facing prose. Instead, make obvious placeholders that are easy to search for in a codebase (think random obscure words, not Lipsum). This enables humans to stay in control of brand identity.
Using atgc #
When doing repo operations, always use the global install of atgc. atgc agent is compiled into the binary and provides an up-to-date view of the current version's capabilities.
Feature branch workflow #
If asked to do work, unless directly instructed otherwise, do not make changes to the user's top-level git checkout. All of your work will be on feature branches and inside git worktrees.
To start work on a feature branch:
- Run
git fetch origin main - Create the branch and its worktree:
git worktree add -b claude/short-feature-name .claude/worktrees/short-feature-name origin/main - Enter the worktree so that all commands run inside of it
- Double check that the repo-local
.git/config[user]section has an ATProto@-handle as anameand itsdid:plcas an email. If it does not, runatgc repo configure - Ensure that
prekis installed in your worktree viaprek install
While doing work, be a good neighbor to humans and agents on your machine:
- Do not restart, reload, or otherwise modify the configuration of shared services.
- If you need temporary folders or other artifacts that exist outside your worktree (such as Docker images), reduce conflicts by tagging them with your current branch and/or SHA.
- Check available system resources before running expensive commands (memory or CPU).
Make sure your work includes a "maintenance budget" for code directly related to your current feature:
- Reducing duplicated or dead code
- Adding new debug log lines and similar observability
- Updating documentation pages or test suites
- Checking off completed work in the epic in
plan/that the work belongs to
Structure your commits for human review:
- Break work into a sequence of logical commits, typically 1-5 for an average feature.
- Use Conventional Commits, including adding "!" indicators for breaking behavior changes.
- Don't write more than 1 short sentence + 1 short paragraph into a commit body.
Confirm your work is acceptable before submitting a PR:
- Rebase onto
origin/mainand fix any conflicts. - Ensure that all
prekfeedback has been fixed. - Run the tests your change affects, and confirm they pass.
When your work is ready for review, you must create a pull request. When writing any PRs, updates to PRs, or comments on PRs:
- The PR title should be simple and descriptive, and should use Conventional Commits.
- The PR body should be three paragraphs at most. Focus on a concise, descriptive explanation of what changed. Avoid commentary on implementation details or future work.
- The PR body should contain a before/after screenshot if the change has a visible effect (UI, rendered output, a CLI's printed text). Attach this with Markdown image syntax and a local image path: the
atgccommands will upload it for you. The file does not need to be committed anywhere, and each image must be under 1 MB or the command refuses it before sending anything. - Do not report your work as "done" until you post the Tangled PR link for the user to review.
If the branch is a single reviewable feature, create the PR using atgc pr create.
If the branch holds several separable but related feature PRs that must land in a given order, use atgc stack create instead:
- Each PR carries multiple commits, and its own description and screenshots.
- Use
atgc stack markwhen you need to turn commit ranges into stacked feature PRs. - Use
atgc stack resubmitwhen you need to reconcile the upstream stack after any rebase, amend or reorder.
When review feedback arrives, you will need to submit a new "round" on the existing PR. Follow these steps for each round:
- Rebase against the latest
origin/main, or if stacked, useatgc stack sync. - Implement the requested changes.
- Repeat the required verification steps.
- Append a new review round to the PR with
atgc pr resubmit(non-stacked) oratgc stack resubmit(stacked). - Update the PR title, body and any new screenshots with
atgc pr edit.
After a feature is merged:
- Fetch
origin/main. - Confirm the work landed by checking that
git cherry origin/main <branch>prints no+lines. - Clean up your own worktree.
- Delete both the local and the remote feature branch, passing
-Dto account for the patch-based workflow. - Leave any temporary artifacts (like Docker images). Another agent may be using them, and they are a shared cache.
Building in a worktree #
A fresh worktree has no target/ and no web/node_modules, so the first build in it compiles the whole dependency tree — atrium-*, the AWS SDK, tokio — before it can run a single test. That is minutes of CPU per worktree, and several agents doing it at once is what puts this machine under load.
- Always
-j 2. A full-parallel build has taken this machine down. web/node_modulescan be symlinked from another worktree whenweb/package-lock.jsonis byte-identical between them. Check that first — if the lockfiles differ, install rather than link.- A worktree keeps its
target/after the branch merges, and these run to gigabytes. Remove the worktree when the branch lands, not just the branch.
Driving the browser #
Drive your own headless Chrome. The devtools MCP opens a window on somebody's
desktop, and a window appearing while they are working is an interruption
whatever it is doing there — background: true keeps it from taking focus and
does not keep it from existing. It also attaches to a Chrome other agents and
the user are sharing, which is the second reason: your own instance has its own
profile, its own port and no session anybody else can sign out of.
/usr/bin/google-chrome --headless=new \
--remote-debugging-port=<9300-9399> \
--user-data-dir=<your scratchpad>/chrome \
--no-first-run --no-default-browser-check --disable-gpu about:blank
Then talk CDP to it with nothing installed: GET /json/list on that port names
a page and its webSocketDebuggerUrl, node's WebSocket is global and needs
no package, and every frame is {id, method, params} answered by an
{id, result} of the same id.
Page.navigate, Runtime.evaluate, Page.captureScreenshot are most of what
a screenshot needs. Kill the process when you are done; it dies with your
script and leaves nothing behind but its profile directory.
Three things that are not obvious:
- Set the viewport. Headless starts at a size no page is designed for, and
a screenshot taken before
Emulation.setDeviceMetricsOverrideis a strip of the banner. 1180x1000 is a reasonable desktop. - Wait for the thing, not for a timer. Poll
Runtime.evaluatefor the selector you are about to photograph. /assets/helmand/assets/megamekare proxied to production byweb/astro.config.mjs, so a file you put inweb/publicis never reached and an index you have not deployed cannot be tested that way. Serve it to the page instead:Fetch.enablewith{urlPattern: "*/assets/helm/*"}and answer eachFetch.requestPausedwithFetch.fulfillRequest, reading the body off your disk. The site then runs exactly as it is committed, with no edit to the config to remember to revert.
The dev server is still shared state, and those rules have not changed:
- Pin your own port:
npm --prefix web run dev -- --port <5180-5199> --strictPort.web/astro.config.mjsalready binds127.0.0.1and already setsstrictPort, so a taken port fails loudly instead of drifting to the next free one; pass the flag anyway so the command stays right if the config changes. - Astro keeps one dev server per worktree, and runs it in the background when it detects an agent. A second
astro devin the same worktree attaches to the first and ignores your--portrather than failing, so check withastro dev statusbefore believing a screenshot, and end yours withastro dev stop. - Screenshots go under your scratchpad, never into the repository.
web/publicis committed, and MegaMek's data is not ours to ship — if you staged an index there to look at it, delete it before you commit and checkgit statussays so. - For anything behind sign-in use
npm --prefix web run dev:local-auth -- --port <yours> --strictPort. Without the port flags that script takes 5173 and collides with whoever holds it.VITE_LOCAL_AUTH=1switches sign-in toweb/src/devauth.ts, which skips headquarters-api and keeps tokens in the tab's IndexedDB — it is still a real OAuth login against a real PDS, so do not sign in at all just to check a layout. - A pinned port and the API disagree: the API admits exactly one web origin,
WEB_ORIGIN, defaulting tohttp://127.0.0.1:5173(see.env.example). If you need the API, start it withWEB_ORIGIN=http://127.0.0.1:<your port>, and expect to queue — one API can hold127.0.0.1:3000. A CORS error here is your port, not a bug.
If you do reach for the devtools MCP — something genuinely interactive, where
you need the page driven the way a person drives it — the shared-Chrome rules
still apply: isolatedContext set to your branch name so a session is yours
alone, background: true, your own pageId re-selected before each action
because select_page is one pointer for the whole browser, an explicit
filePath for screenshots, and close your pages at the end.
Lexicons #
lexicons/ holds copies of schemas authored in the lexicons repo. Never edit one here, and never write a new schema here: the fix belongs in that repo, and the copy is refreshed with npm --prefix web run lexicons:update once the change is published.
@atproto/lexowns these files, andlexicons.jsonpins the CID each came from. Do not rewrite one with goat: goat's form is different, and the manifest would stop verifying.web/src/lexicons/is generated and gitignored. Do not commit it or edit it;npm --prefix web run lexgenrebuilds it, and typecheck, dev and build all run that first.- The generated validators keep fields the schema does not declare, through parse, build and a JSON round trip. That is what makes an edit of an existing record safe.
The plan #
plan/ holds one file per epic, and plan/README.md is the register. An
epic's filename is its id, and that id is the Conventional Commits scope:
feat(camo): ... belongs to plan/complete/camo.md. scripts/check-commit-scope.sh
enforces it at commit-msg time against the epic files, including the archived
ones.
The register's tables are generated from the epics' frontmatter by
scripts/gen-plan-readme.py, and the plan-register hook fails a commit that
leaves them stale. Adding an epic is writing plan/<id>.md and running that
script — never editing a table by hand.
There is no TODO.md. Work that is worth writing down goes in its epic.