Imported from jkomoros/card-web (
AGENTS.md). Install upstream withnpx skills add jkomoros/card-web. Copyright stays with the author.
AGENTS.md
Instructions for AI agents working in this repo. Humans may find the environment section useful too.
The default branch is master (not main).
The rule: no change lands on master without an adversarial review
Before committing to master — including changes that look trivial, and
including changes that came with a detailed issue telling you exactly what to
do — you MUST run a separate adversarial sub-agent against the diff and act
on what it finds.
This is not a code-review formality. Its purpose is to catch the specific failure mode that a single agent implementing a well-specified fix is worst at noticing: the fix does what the issue asked, the tests pass, and it is still wrong — because it was applied at the wrong layer, because another caller reaches the same defect, or because the tests prove something the product does not actually do.
What the adversarial agent must be told
Instruct it to argue AGAINST the change and to assume the author is wrong until proven otherwise. It must run code to test its hypotheses, not speculate, and it must clearly mark anything it could not verify by execution. A fabricated or unverified objection is worse than no objection, because it costs a real investigation to dismiss.
At minimum it must attempt these theses:
| Thesis | The question it must answer |
|---|---|
| Wrong layer | Is this papering over a defect that belongs one level down? Enumerate EVERY caller — is there a path to the same bad outcome that bypasses this fix? |
| Does not achieve the effect | Construct real inputs that still produce the bad outcome after the change. Include the case where the fix destroys something a user wanted kept. |
| Robustness | Does the predicate hold at the edges? If a helper has multiple code paths (DOM vs. no-document, worker vs. main thread), do they agree — or do the tests pass on a path the product never takes? |
| Performance | Quantify, don't hand-wave. Is new per-item work being paid inside a loop that already runs at scale? |
| Security | Does this reorder anything relative to sanitization, permission checks, or rules enforcement? |
| Test quality | Are the tests vacuous? Do they exercise the product's real path? Does simplified test fixture markup hide the case that actually breaks? |
Triage
The agent's findings must be sorted into BLOCKING / NON-BLOCKING / NOISE.
- BLOCKING must be fixed before landing, or explicitly overridden by the repo owner in the conversation. Do not override it yourself.
- NON-BLOCKING is first checked against the rule below — fix it if it is small and unambiguous. Only what genuinely does not belong in this change becomes a GitHub issue, and it becomes one before landing, not a TODO comment and not a promise in a commit message.
- NOISE is dropped. Say so; do not silently ignore it.
Leave everything a little better than you found it
Do not defer cleanup that is unambiguous and well-scoped. Fix it now.
Filing an issue is not free. It costs someone a second context-load to fix a thing you were already looking at, with the file already open and the reasoning already in your head. A one-line guard that makes a function match its four siblings is not a follow-up; it is part of doing the work.
Fix it in the current change when all of these hold:
- The correct fix is obvious — no design decision, no choice between defensible options, no need for the owner's preference.
- It is small, and testable the same way the rest of the change is.
- It is in code you are already touching or already had to understand.
File an issue instead when any of these hold:
- It needs a decision (which sentinel, which key, which tradeoff).
- It is large enough to dominate the diff, so the change stops being reviewable as one idea.
- It reaches into a subsystem you have not established the invariants of.
- Fixing it would fan out — each fix exposing two more. Scope is bounded by the change, not by the taste for tidying. If cleanup starts recursing, stop, land what you have, and file the rest.
The specific thing to stop doing: noticing a defect while fixing an adjacent one, in the same file, and writing it up instead of fixing it. Lint errors in a file you are already editing, an unguarded access next to four guarded ones, a comment your own change just made false — those are all "now", not "later".
When you do fix something extra, say so in the commit message and say why it belonged there. A reviewer should never have to guess why an unrelated-looking hunk is in the diff.
The adversarial agent produces claims, not verdicts
Its output is a list of things worth testing. It is not a review you accept, and "the adversarial agent said so" is not evidence. Before you act on a finding — especially one that changes the shape of the fix — verify it yourself, by running it. Subagents report confidently and are sometimes wrong; a finding adopted on trust is indistinguishable from one you invented.
This cuts both ways, and the second direction is the one that gets skipped:
- Before adopting a recommendation, reproduce the evidence for it. If the claim is that two implementations are equivalent, run both across a real battery of inputs and diff the output. Do not re-run the agent's own script; write your own.
- Before dismissing a finding as NOISE, confirm it is actually noise. The cheap failure is waving away a real defect because the agent's framing was annoying.
A refutation is a finding too, and often the most valuable one: proving that a proposed safeguard is unreachable dead code, or that an objection describes behaviour that already existed, converts a judgment call into a fact you can state plainly in the commit message. Verify those the same way.
Report to the owner what you verified and how, separately from what the agent merely asserted. If you could not verify something, say so and treat it as open.
Prove your tests are not vacuous
A test that passes with and without the fix proves nothing. Before landing,
revert the source change (git stash push <file>), rebuild, and confirm the new
tests actually FAIL. Then restore and rebuild. State the result.
A test kept deliberately as a regression guard — one that passes either way because it asserts the fix does not break an adjacent case — is fine, but say that is what it is.
When the adversarial pass may be skipped
Only for changes that cannot affect built or deployed behaviour: documentation,
comments, and this file. Everything touching src/, shared/, functions/,
tools/, test/, rules, or indexes needs it.
User-facing changes follow the house style
Anything a user can see — a string, a control, a dialog, a color — follows
docs/HOUSE-STYLE.md. Read it before adding or changing
one, and check its canonical primitive index (§5) before building a new
surface.
That index is the point. This codebase's recurring failure is not bad taste, it
is a component re-inventing a primitive that already exists: progress-panel.ts
was written to end progress-surface divergence and is used by two of eleven
long-running surfaces; snack-bar.ts carries the comment "the snack-bar existed
for exactly this and was used once, ever"; sync-copy.ts had to consolidate
eight different sentences for the same idea. Look for the primitive first.
Two rules there are hard, and both are enforced by review, not by a test — there is no CI check for either, so they hold only if you actually apply them:
- No new
alert()/confirm()/prompt(). The call-site count is pinned (§6, currently 51). It may fall; it may not rise. §6 carries the grep. If you are already editing a function that contains one, replace it. - No literal colors in component CSS. Every color comes from a token in
src/components/card-web-app.ts.
The document is normative, not descriptive. Where the app does not comply yet, that is recorded as debt in its §7 — do not cite non-compliant code as precedent for writing more of it.
Environment traps
These have each cost real debugging time. None are optional.
Node 20.20.0 is required. The login default is v18, which silently produces
different results and breaks firebase-tools. Every shell:
export PATH="$HOME/.nvm/versions/node/v20.20.0/bin:$PATH"
node --version # MUST print v20.20.0
Git hooks hang and stash unstaged changes. Always:
git -c core.hooksPath=/dev/null <cmd>
git commit --no-verify
Tests import compiled output. Build before running any mocha suite, or you
will validate a stale build (tools/assert-build-fresh.cjs catches the obvious
cases, but build first anyway):
npm run build:shared && npm run build:typescript
firestore.rules is GENERATED from firestore.TEMPLATE.rules by
npm run generate:config. Never edit firestore.rules directly; your change
will be silently overwritten.
gcloud's active config points at PROD (complexity-compendium). A stray
gcloud command hits production. npm run backup calls gcloudEnsureProd()
itself, so it repoints the project regardless of what your shell had set.
npm run deploy runs no tests and takes no backup. Both are manual,
separate steps. Nothing warns you if you skip them.
Verify against dev signed in as admin, not anonymous. In dev you MAY edit
cards and exercise the real editing flows — do so. The interesting state
(editing, saves in flight, large collections, the drawer with full sections)
only exists signed in; the anonymous view is a small, performant subset that
hides most regressions. Against the emulator, use
window.PERF_HARNESS.signInAsAdmin('perf-admin') (see the perf-harness
recipe); an anonymous walkthrough is not a verification of an editing or
performance change.
Agents may freely create, modify, and delete content in DEV — never in
prod. The dev project (dev-complexity-compendium) exists to be exercised:
create cards, edit them, bulk-import, delete, and deploy to dev, all without
asking. Deploying to dev is allowed and encouraged when it is the honest way
to verify a change. PROD (complexity-compendium) is the opposite: no writes,
no deploys, no "harmless" experiments — remember gcloud's active config
points at prod by default (see above).
Tests
npm test runs everything, including ~200 Firestore-emulator security tests.
CI (.github/workflows/test.yml) runs npm run test:ci, which is everything
except test:security — that one needs config.SECRET.json, which is
gitignored and correctly absent from CI. test/ci-coverage asserts that this is
the only exemption, so the workflow cannot silently drift into running less
than it claims. If you add a suite, that assertion is what will tell you.
Run the full suite before landing on master, not just the suite you think you
touched.
Commit messages
Explain WHY, not just what. Describe the failure mode being fixed and the alternative you rejected — the repo's history is used as an engineering record, and "what" is already in the diff.
Do not claim a verification you did not perform. If tests failed, say so; if a step was skipped, say that.