BETAb.wolfpack.quest — may be unfinished.what's different?View on production
WolfPack.quest
Sign in

Method

Every rule here started as something going wrong.

Wolf Pack runs a four-component platform — a Discord bot, a website, a log-parsing agent on each raider’s PC, and a desktop overlay app — built and maintained almost entirely by AI coding sessions that cannot see each other’s conversations. What holds it together is not a clever prompt. It is a set of written rules, each one adopted the day a specific failure made it necessary, and a verification gate that runs before anything ships.

This page is that method, in full. Scrub the timeline to watch it accumulate.

2,088
commits
2,284
tests
79
documents
16
standing rules

Measured 2026-08-23, first commit 2026-04-21. Numbers here are counted, never estimated.

If you are an agent reading this

Three levels of access, three ways in. All of them describe the same method.

  1. No repo access — fetch /ai.txt (plain markdown, the whole method in one document) or /ai.json (structured: rules, milestones, workflow stages, gates). Everything on this page is in both.
  2. Repo access — clone https://github.com/davehess/QuarmBossTracker and read CLAUDE.md then docs/GEMINI-SPARK-HELPER.md. Those are authoritative; this page is an index into them.
  3. Setting up your own workflow — the decision tree below is the procedure, and the gate is the part that is not optional. Adopt the shape, not the specifics: your project’s rules should come from your own incidents, not ours.

One caveat worth carrying: this method suits a repo with two very large single files, a small number of readers, and consequences that land on a schedule. Some of it would be overhead somewhere else.

How the method accumulated

Every node is a real commit. Drag the slider back and rules un-adopt: the lit rail is the method as it stood on that date, and the dimmed cards are rules that had not been learned yet. Orange is what went wrong, green is the rule that came out of it, blue is the commit that implemented it.

Current ruleset

The method becomes portable

Aug 23, 2026
16/16 rules in force
Apr 25, 2026Aug 23, 2026

  1. what forced itThe project was a single Discord respawn timer. Context for each session was retyped by hand, so it varied every time.

    what changedCLAUDE.md is committed alongside the deployment config — one file describing the architecture and the rules, versioned with the code it governs.

    measured afterEvery rule below is an edit to this one file. It now outranks the README where they disagree.

    commits
    • 44ca9487Add Railway deployment config and CLAUDE.md
  2. what forced itThe agent reads a raider’s entire EQ log — officer chat, tells, group, custom channels. Filtering after upload would mean the data had already left their machine.

    what changedA written privacy statement, and byte-level filtering before the parse so private channels never enter the pipeline. Visibility scopes and opt-out flags become mandatory on every log-derived stat.

    measured afterLater exceptions are narrow and documented: a single carve-out for public "Hail" lines was added for expansion flagging, in the privacy doc and on the member-facing page.

    rules this introduced
    • Private channels never leave the machine
    commits
    • b1009ecfprivacy statement draft + QoL north star + PoP flagging in queue
    • d3c9d9a6build: guard against the agent-dashboard escape bug (blank-page class)
  3. what forced itA session answered "we do not have that" about a feature that already existed, because it checked one of the four surfaces a feature can live on.

    what changedHOW-ITS-BUILT.md maps every feature to its file and surface, and the rule becomes: read the index before answering a does-this-exist question. Twelve days later the rule is written into CLAUDE.md explicitly.

    measured afterNow 79 documents. A stale index causes exactly the wrong negative answer, so shipping a feature refreshes its row.

    rules this introduced
    • Answer "do we already have X?" from the index
    commits
    • 9dbb9a07docs — HOW-ITS-BUILT.md: per-feature 'how X is built' reference
    • 05bd892ddocs — feature-index discipline: CLAUDE.md rule for 'do we have X' questions
  4. what forced itA mid-raid deploy restarted production surfaces during a raid, amplifying a queue backup and announcer spam.

    what changedNo pushes to main inside the raid window. A workflow turns any such push red — advisory, because Railway and Vercel deploy on push regardless. [hotfix] is the escape hatch, because a mid-raid fix is exactly what should ship mid-raid.

    measured afterA tripwire, not a gate. The enforcement that matters is in the instructions; the workflow catches everyone who has not read them.

    rules this introduced
    • Raid-night deploy freeze
    commits
    • 2f9c32c6bot v3.0.167 — pre-raid health check, raid-night deploy freeze, mid-raid shed switches
  5. what forced itWork-in-flight was tracked in several competing documents, so no single one could be trusted.

    what changedSTATUS.md becomes the single index: done, queued, abandoned, and items that need a session with local-machine access.

    commits
    • 0225ad10docs: consolidate scattered queues into STATUS.md + archive superseded
  6. what forced itAn undeclared global shipped and took a surface down. In an 18k-line file, that class of bug throws only when the offending line executes.

    what changedA deliberately narrow lint tripwire (one rule: no-undef, nothing stylistic), a blocking CI workflow, and the first characterization tests. The minimal-diff rule enters CLAUDE.md the same week.

    measured after6 test files at introduction. 151 files and 2,284 tests today.

    rules this introduced
    • Minimal diff
    • Fail open
    • Never deduplicate a per-observer stream
    commits
    • 243888e8bot v3.0.204 — ESLint no-undef gate + blocking test workflow
    • 7d91a4dcbot v3.0.205 — vitest characterization suite + CI test step
    • 944a87f1docs — commit .claude settings + minimal-diff rule
  7. what forced itWork was being drafted in chat tools with no repo access, against guesses about the architecture.

    what changedA self-contained brief with the architecture, the guardrails and the review bar, written to be pasted into a fresh chat.

    commits
    • d6786adedocs — AI-CONTRIBUTOR-BRIEF.md: self-contained brief for chat AIs that can't browse the repo
  8. what forced itThe log parser produces every figure on a parse card, a damage meter and a kill credit. A silent regression there is forty raiders with wrong numbers on a Sunday night.

    what changedA committed synthetic log is replayed through the shipped parser and diffed against a committed known-good result. Changing it on purpose means regenerating and reading every changed number.

    measured afterThe privacy assertions read the live parser rather than the expectation file, so regenerating the golden can never launder a privacy hole.

    commits
    • 8dac1be5test: #75 golden-log CI + pre-raid drill (agent parser regression net)
  9. what forced itSessions run on different machines and cannot share a conversation. Decisions made in chat were being re-litigated a week later.

    what changedDated decision records, a session-start digest that prints open items and live versions, and a recall command that answers questions from the committed docs with citations.

    measured after7 decision records so far. Retrieval was never the weak link — the writing discipline is what this milestone bought.

    rules this introduced
    • Decisions get written down, same session
    • Shipping updates the docs in the same change
    commits
    • 45629403docs — project memory: decision records, a SessionStart digest, and /recall
  10. what forced itCredit was being inferred from names in code comments — which is exactly how the wrong credits got there in the first place.

    what changedOne rule with one explicit exception, and a named table of who the exception covers. Character names in fixtures are excluded from the rule by construction.

    rules this introduced
    • Attribution is explicit
    commits
    • 6dae2bddattribution — everything is Hitya unless it came through the feedback form
  11. what forced itNothing had ever flowed from main to beta. Beta reached 79,199 lines behind and carried 35 test files against main’s 90 — which is why a priority-one parser bug rode through nine agent releases unseen.

    what changedA workflow merges main into beta on every push to main. Only two deliberately-ahead version files are excluded; any other conflict fails the run loudly rather than picking a side.

    measured after21 automatic syncs so far. The earlier practice — re-syncing by hand at each release — was a snapshot, not a link, and main moves 12–42 commits a day.

    rules this introduced
    • When main gets something, beta gets it too
    commits
    • e0ee9cd7ci — when main gets something, beta gets it too
  12. what forced itRetention windows, hosting mix and Discord layout were being decided per-choice and recorded, if at all, in runbooks written for one specific machine.

    what changedEvery 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 moment it is made.

    measured afterRetention is treated as a hosting-bill question, not a data-modelling one — hosted storage bills on egress, an on-premises box costs electricity, and the answer differs per deployment.

    rules this introduced
    • Deployment decisions are recorded as they are made
    commits
    • d017c3b3scripts+docs — the local box becomes an archive that never loses history
  13. what forced itPostgREST silently caps every response at 1000 rows. The same footgun was rediscovered four separate times, and each rediscovery wrote its own paginator — three paginators for one problem.

    what changedOne paginator per runtime, enforced by test. Existing over-cap call sites are counted into a baseline that may only shrink: converting one lowers it, adding one fails CI.

    measured afterA ratchet rather than a ban because 85 pre-existing sites could not be converted blind — each needs its ordering key checked by hand.

    rules this introduced
    • One paginated reader per runtime
    commits
    • 92447856bot v3.1.49 + web v1.1.58 — one paged reader per runtime, award identity in the schema
  14. what forced itAdoption was being reported in characters. One person runs 3–12, so a "178 characters on the new build" fleet was 16 people — a tenfold overstatement in every graduation argument.

    what changedAdoption counts distinct humans. In the same period, durable state stops going into Discord messages: Postgres is the home, Discord is a projection.

    rules this introduced
    • Count adoption in players, never characters
    • Postgres is the home; Discord is a projection
    commits
    • 9b579015docs — fleet counts are PLAYERS not characters (Hitya’s rule)
  15. what forced itPer-character layout saves existed for four minor versions in the tray menu only. The guild lead discovered them by accident.

    what changedAnything available from the tray is available from the dashboard in the same change, driving the same internals rather than a parallel path.

    rules this introduced
    • Every control exists on more than one surface
    commits
    • 99f48a98web v1.1.71 + docs — tray/dashboard parity rule, ledger + roadmap for the parity batch
  16. what forced itA different assistant was going to work in this repo, and would get none of the standing instructions automatically — no session-start hook, no accumulated conversation.

    what changedA working guide for any agentic session with repo access: boot order, the per-task loop, branch routing, the full verification gate including the two checks CI does not run, the three test tiers, and the footguns that have each already shipped a bug. Then this page, so the method is legible without a checkout at all.

    rules this introduced
    • Any assistant can pick up the work
    commits
    • 4dd1385bdocs — a working guide for non-Claude agentic sessions (Gemini Spark)

The rules in force today

Any assistant can pick up the work

adopted here

Two 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.md

Every control exists on more than one surface

in force

A 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.md

One paginated reader per runtime

in force

All 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.js

Count adoption in players, never characters

in force

Fleet 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.md

Postgres is the home; Discord is a projection

in force

No 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.md

Deployment decisions are recorded as they are made

in force

Any 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.md

Shipping updates the docs in the same change

in force

A 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.md

When main gets something, beta gets it too

in force

A 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.yml

Attribution is explicit

in force

Most 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.md

Decisions get written down, same session

in force

When 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.md

Never deduplicate a per-observer stream

in force

Live 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.md

Answer "do we already have X?" from the index

in force

Read 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.md

Fail open

in force

On 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.md

Minimal diff

in force

Change 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.md

Raid-night deploy freeze

in force

No 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.yml

Private channels never leave the machine

in force

Officer 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

One task, start to finish

The procedure every session follows, with the branch points drawn as branches. This is the part to copy if you are building your own loop.

  1. Load the committed context

    CLAUDE.md (architecture and rules; outranks the README), docs/STATUS.md (ledger and queue), docs/HOW-ITS-BUILT.md (feature → file index), then the newest docs/DECISIONS-*.md. Then the design or runbook doc for the area you are touching.

    guards againstSessions cannot share a conversation. Anything not in a committed file does not reach the next one.

  2. Find the real code before deciding it does not exist

    A feature can live on four surfaces and often spans several: the bot, the web app, the agent dashboard, and the desktop overlays. Check the index, then grep all four.

    guards againstThe negative answer is the failure-prone one. It has been wrong before.

  3. Route the change

    What you touched decides both the branch and which version file moves. Versions live in package.json and nowhere else.

    • the Discord bot and its HTTP APImain · bump the root package.json
    • the websitemain · bump web/package.json
    • the log agentbeta · bump the agent package.json only
    • the desktop appbeta · its version stays parked; the release workflow increments the prerelease tag
    • a database migrationmain, as a timestamped idempotent file — and if the column is needed now, apply it AND commit the identical file
    • documentation onlymain · no version bump

    guards againstA change spanning bot and agent is two commits on two branches. Cherry-pick between them; never merge the whole branch, because the beta branch carries a deliberately-ahead version and in-flight work.

  4. Check the clock before touching production

    Raids run Sunday, Wednesday and Thursday evenings Eastern. The freeze window is 19:30 to 00:30.

    • outside the windowship normally
    • inside it, and something is broken right nowship with [hotfix] in the commit message
    • inside it, and it can waitstage on a working branch and land it after midnight

    guards againstA push restarts the production surfaces the raid depends on.

  5. Make the smallest change that does the job

    Extract the decision into a small pure module where you can; leave the plumbing in place. Fail open on missing data. Never collapse a per-observer stream.

    guards againstTwo files here are 18k and 35k lines. Line count is a bad proxy for blast radius.

  6. Prove it

    Pure module → import and test it directly. Logic trapped in a monolith → slice the real function out of the shipped file and evaluate it, so the test tracks shipped code. A rule rather than a function → a guard test, and read the header of any guard test that goes red before changing it.

    guards againstUse real fixtures. An invented fixture that is subtly unlike production has shipped bugs here.

  7. Run the whole gate

    All of it, not the parts that seem relevant. Two of these are not in CI, which means the only other place they fail is the deploy.

    npm run lint
    npm run check:dashboard
    npm test
    cd web && npx tsc --noEmit   # if the web app changed
    npm run golden:check         # if the log parser changed
  8. Write the docs into the same commit

    The ledger entry, the feature index row, the design doc, a dated decision record for any call that was made, and a plain-language release note for anything user-facing. Deployment-shaped decisions also go in the self-host design doc.

    guards againstIf the doc edit is not in the diff, the ship is not done. A ledger that lags its code once reported a shipped feature as blocked.

  9. Commit, push, and say what actually happened

    Commit subject is "<component> vX.Y.Z — short reason"; the body explains why, not which files. Push to the branch the routing step named. Then report honestly: failures with their output, skipped scope named as skipped, uncertainty left open stated as uncertainty.

    guards againstContainers here come up shallow and with stale refs. Fetch and check the real remote head before concluding work was lost, and never force-push on a stale ref.

The gate

Five checks. Three block CI; two do not, which means the only other place they fail is the deploy — so they are run by hand, every time.

npm run lintblocks CI

A single no-undef tripwire over the two monoliths. An undeclared global in an 18k-line file throws only when that line executes; this catches it statically. Deliberately the only rule — the value is that it is zero-noise.

npm run check:dashboardblocks CI

The agent dashboard is one template literal with two layers of escaping; one wrong character blanks the whole page with no partial degradation. This parses every script block it emits, checks a required helper on every collapsible element, and fails if an embedded copy of an overlay has drifted from its source file.

npm testblocks CI

151 files, 2,284 tests. Includes guard tests that hold a rule rather than cover a function — the read-layer ratchet, workflow validity, the expansion lock, and the parser golden.

cd web && npx tsc --noEmitnot in CI — run it yourself

Type errors in the web app. Not in CI — the only other place this fails is the deploy, i.e. after the push.

npm run golden:checknot in CI — run it yourself

That the committed parser expectations still describe the parser. Changing them on purpose means regenerating and reading every changed number, because each one is a change in what the raid sees.

How the work is written

Direct push, not pull requests

This repo ships by pushing to a branch, not by opening pull requests, so commits are the durable unit of history. Bot, web and docs go to main; the desktop app and its bundled log agent go to a beta branch and graduate by file-level promotion, never by merging the whole branch. Version numbers live in package.json and nowhere else — never in a document.

Write the reason, not the change

Every rule here records the incident that produced it. "The threshold is 0.5" is worthless in six weeks; "0.5 because our raids measure 0.75–0.89 and outside raids 0.14–0.22, and it matches the constant the other surface already uses" is the whole point. Comments in this codebase carry reasons, not descriptions.

Tests are the record of why

Tests are named after the behaviour that was asked for and quote the request. Pure logic is extracted into small modules so it can be tested directly. Logic trapped inside a monolith is tested by slicing the real function out of the shipped file and evaluating it, so the test tracks the shipped code and fails loudly if the function is renamed.

Report honestly

If a test failed, say so with the output. If part of the scope was skipped, say which part and why. Scaling work down is the requester’s call, not the assistant’s. Uncertainty that was not closed gets stated as uncertainty — several rules here exist because a confident wrong answer was cheaper to give than a checked one.

Verify git state before concluding anything is lost

Cloud containers here come up shallow and with stale refs, so local branch pointers can look dozens of commits behind while the remote is fine. Fetch and check the real remote head before concluding work vanished, and never force-push on the basis of a stale ref.

The documents themselves

This page restates them; it does not replace them. A test asserts that every rule above still quotes text that actually appears in the file it cites, so the two cannot drift apart silently.

More context on what the platform actually is: the platform map, the release log, and what we collect.

16 milestones · 16 rules · 9 workflow stages · machine-readable at /ai.json and /ai.txt