Any assistant can pick up the work
adopted hereTwo onboarding docs are maintained: one for an assistant with repo and shell access, one for a chat with neither. Each says which it is and points at the other.
becauseSessions run on different tools and cannot share a conversation. What is not written down for the next one does not survive.
docs/GEMINI-SPARK-HELPER.mdEvery control exists on more than one surface
in forceA control that ships in the tray menu ships on the dashboard in the same change, driving the same internals — never a parallel path.
becauseA control that exists in only one place is a control people forget exists. Per-character layout saves sat tray-only for four minor versions before the guild lead discovered them.
CLAUDE.mdOne paginated reader per runtime
in forceAll database reads go through a single paginator per runtime. A second paginator, or a new over-cap .limit(), fails CI.
becausePostgREST silently caps every response at 1000 rows, and asking for 5,000 does not lift it — the cap is applied on top, so you get a short array and a success code. The same footgun was rediscovered independently four times, each rediscovery writing its own paginator.
test/db-read-discipline.test.jsCount adoption in players, never characters
in forceFleet and adoption numbers count distinct uploading humans, each at their most recent version.
becauseOne person runs 3–12 characters, so character counts inflate roughly tenfold: a "178 characters on the new build" fleet was 16 people.
CLAUDE.mdPostgres is the home; Discord is a projection
in forceNo new durable state goes into Discord messages or the local state file. Postgres holds it; Discord renders it.
becauseThe state file does not persist across deploys, and treating Discord as a source of truth once posted the same raid review eleven times in one night.
CLAUDE.mdDeployment decisions are recorded as they are made
in forceAny decision that changes how the platform is deployed, what it stores, or what it costs to run gets a line in the self-host design doc at the time it is made.
becauseThe goal is a wizard that stands the whole platform up for another guild. It can only be built from decisions recorded as they happened; a choice captured only in a runbook is written for one specific box.
CLAUDE.mdShipping updates the docs in the same change
in forceA change updates its ledger entry and design doc in the same commit, and again when it graduates from beta to stable. If the doc edit is not in the diff, the ship is not done.
becauseA ledger that lags its code made the recall tool report a feature as "blocked on the call" the day after it shipped.
CLAUDE.mdWhen main gets something, beta gets it too
in forceA workflow merges main into beta on every push to main. Real conflicts fail the run loudly instead of auto-resolving.
becauseNothing flowed main to beta for months. Beta drifted 79,199 lines behind, carried 35 test files against main’s 90, and a priority-one parser bug rode through nine agent releases unseen because the test that would have caught it did not exist on that branch.
.github/workflows/sync-beta.ymlAttribution is explicit
in forceMost requests here come from one person under several character names, and are credited to them — but the list of those names is checked with them rather than inferred, and anyone not on it keeps their own name.
becauseThe list was wrong for three weeks: it carried a name belonging to a different member, and was missing one that did belong. A rule that folds many names into one person fails by erasing other people's contributions, and it fails silently — a wrong credit reads exactly like a right one, and only the person themselves ever notices. So an unrecognised name now keeps its own credit by default.
CLAUDE.mdDecisions get written down, same session
in forceWhen the guild lead makes a call — a default, a threshold, a policy, a "we do not do that" — append it to docs/DECISIONS-<date>.md before the session ends, with the reasoning and where it landed.
becauseSessions cannot share a conversation and a container reset takes the scratchpad with it. A decision that lives only in chat is lost. Retrieval was never the weak link — writing was.
CLAUDE.mdNever deduplicate a per-observer stream
in forceLive state, threat, casting, target-casts and encounter uploads are per observer. Each one is a distinct fact and is merged, never collapsed.
becauseTwo agents reporting the same fight are two viewpoints, not a duplicate. Collapsing them silently discards the coverage that makes merged parses work.
docs/AI-CONTRIBUTOR-BRIEF.mdAnswer "do we already have X?" from the index
in forceRead docs/HOW-ITS-BUILT.md first, then grep all four surfaces — bot, web, agent dashboard, Mimic. Never conclude "we do not have that" from one file.
becauseA feature can span four surfaces. A single grep produced a confident "we do not have that" for the eqclient/Zeal "Set up for me" writer, which already existed. The failure-prone direction is always the negative answer.
CLAUDE.mdFail open
in forceOn missing data, an unknown value, or a dependency being down, degrade to safe defaults. Never crash, never hide data that exists.
becauseThe consumers are raiders mid-pull. A surface that disappears is worse than one that shows less.
docs/AI-CONTRIBUTOR-BRIEF.mdMinimal diff
in forceChange only what the task requires. If it looks like adjacent code must change too, stop and say so before proceeding.
becauseThe bot is one ~18k-line file and the agent one ~35k-line file, so a small line count is a bad proxy for a small blast radius. Reaching into unrelated behaviour is the structural hazard, not diff size.
CLAUDE.mdRaid-night deploy freeze
in forceNo pushes to main on Sun/Wed/Thu between 19:30 and 00:30 Eastern. Mid-raid fixes ship with [hotfix] in the commit message.
becauseA push restarts the production surfaces the raid depends on. Mid-raid restarts amplified a queue backup and announcer spam on 2026-07-13.
.github/workflows/raid-freeze.ymlPrivate channels never leave the machine
in forceOfficer chat, tells, group and custom channels are dropped at byte level before parsing, on the user PC. Every log-derived stat declares a visibility scope, and opt-out flags are honoured by every consumer.
becauseThe agent reads a player’s whole log file. Filtering after upload would mean the data had already left, so the filter has to run before the parse.
docs/PRIVACY.md